diff --git a/previews/PR798/.documenter-siteinfo.json b/previews/PR798/.documenter-siteinfo.json new file mode 100644 index 0000000000..d6a8f7e865 --- /dev/null +++ b/previews/PR798/.documenter-siteinfo.json @@ -0,0 +1 @@ +{"documenter":{"julia_version":"1.11.1","generation_timestamp":"2024-11-12T15:38:29","documenter_version":"1.7.0"}} \ No newline at end of file diff --git a/previews/PR798/assets/citations.css b/previews/PR798/assets/citations.css new file mode 100644 index 0000000000..76140e873b --- /dev/null +++ b/previews/PR798/assets/citations.css @@ -0,0 +1,17 @@ +.citation dl { + display: grid; + grid-template-columns: max-content auto; } +.citation dt { + grid-column-start: 1; } +.citation dd { + grid-column-start: 2; + margin-bottom: 0.75em; } +.citation ul { + padding: 0 0 2.25em 0; + margin: 0; + list-style: none;} +.citation ul li { + text-indent: -2.25em; + margin: 0.33em 0.5em 0.5em 2.25em;} +.citation ol li { + padding-left:0.75em;} diff --git a/previews/PR798/assets/custom.css b/previews/PR798/assets/custom.css new file mode 100644 index 0000000000..1daa0f58cd --- /dev/null +++ b/previews/PR798/assets/custom.css @@ -0,0 +1,13 @@ +html.theme--documenter-light body div#documenter nav.docs-sidebar a.docs-logo img, +html.theme--documenter-dark body div#documenter nav.docs-sidebar a.docs-logo img, +html.theme--catppuccin-latte body div#documenter nav.docs-sidebar a.docs-logo img, +html.theme--catppuccin-frappe body div#documenter nav.docs-sidebar a.docs-logo img, +html.theme--catppuccin-macchiato body div#documenter nav.docs-sidebar a.docs-logo img, +html.theme--catppuccin-mocha body div#documenter nav.docs-sidebar a.docs-logo img { + max-height: 10rem; +} +div.docs-package-name { + font-family: "JuliaMono"; + font-weight: normal !important; + font-size: 1.7rem !important; +} diff --git a/previews/PR798/assets/documenter.js b/previews/PR798/assets/documenter.js new file mode 100644 index 0000000000..82252a11da --- /dev/null +++ b/previews/PR798/assets/documenter.js @@ -0,0 +1,1064 @@ +// Generated by Documenter.jl +requirejs.config({ + paths: { + 'highlight-julia': 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/languages/julia.min', + 'headroom': 'https://cdnjs.cloudflare.com/ajax/libs/headroom/0.12.0/headroom.min', + 'jqueryui': 'https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.13.2/jquery-ui.min', + 'katex-auto-render': 'https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.8/contrib/auto-render.min', + 'jquery': 'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.0/jquery.min', + 'headroom-jquery': 'https://cdnjs.cloudflare.com/ajax/libs/headroom/0.12.0/jQuery.headroom.min', + 'katex': 'https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.8/katex.min', + 'highlight': 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/highlight.min', + 'highlight-julia-repl': 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/languages/julia-repl.min', + }, + shim: { + "highlight-julia": { + "deps": [ + "highlight" + ] + }, + "katex-auto-render": { + "deps": [ + "katex" + ] + }, + "headroom-jquery": { + "deps": [ + "jquery", + "headroom" + ] + }, + "highlight-julia-repl": { + "deps": [ + "highlight" + ] + } +} +}); +//////////////////////////////////////////////////////////////////////////////// +require(['jquery', 'katex', 'katex-auto-render'], function($, katex, renderMathInElement) { +$(document).ready(function() { + renderMathInElement( + document.body, + { + "delimiters": [ + { + "left": "$", + "right": "$", + "display": false + }, + { + "left": "$$", + "right": "$$", + "display": true + }, + { + "left": "\\[", + "right": "\\]", + "display": true + } + ] +} + + ); +}) + +}) +//////////////////////////////////////////////////////////////////////////////// +require(['jquery', 'highlight', 'highlight-julia', 'highlight-julia-repl'], function($) { +$(document).ready(function() { + hljs.highlightAll(); +}) + +}) +//////////////////////////////////////////////////////////////////////////////// +require(['jquery'], function($) { + +let timer = 0; +var isExpanded = true; + +$(document).on( + "click", + ".docstring .docstring-article-toggle-button", + function () { + let articleToggleTitle = "Expand docstring"; + const parent = $(this).parent(); + + debounce(() => { + if (parent.siblings("section").is(":visible")) { + parent + .find("a.docstring-article-toggle-button") + .removeClass("fa-chevron-down") + .addClass("fa-chevron-right"); + } else { + parent + .find("a.docstring-article-toggle-button") + .removeClass("fa-chevron-right") + .addClass("fa-chevron-down"); + + articleToggleTitle = "Collapse docstring"; + } + + parent + .children(".docstring-article-toggle-button") + .prop("title", articleToggleTitle); + parent.siblings("section").slideToggle(); + }); + } +); + +$(document).on("click", ".docs-article-toggle-button", function (event) { + let articleToggleTitle = "Expand docstring"; + let navArticleToggleTitle = "Expand all docstrings"; + let animationSpeed = event.noToggleAnimation ? 0 : 400; + + debounce(() => { + if (isExpanded) { + $(this).removeClass("fa-chevron-up").addClass("fa-chevron-down"); + $("a.docstring-article-toggle-button") + .removeClass("fa-chevron-down") + .addClass("fa-chevron-right"); + + isExpanded = false; + + $(".docstring section").slideUp(animationSpeed); + } else { + $(this).removeClass("fa-chevron-down").addClass("fa-chevron-up"); + $("a.docstring-article-toggle-button") + .removeClass("fa-chevron-right") + .addClass("fa-chevron-down"); + + isExpanded = true; + articleToggleTitle = "Collapse docstring"; + navArticleToggleTitle = "Collapse all docstrings"; + + $(".docstring section").slideDown(animationSpeed); + } + + $(this).prop("title", navArticleToggleTitle); + $(".docstring-article-toggle-button").prop("title", articleToggleTitle); + }); +}); + +function debounce(callback, timeout = 300) { + if (Date.now() - timer > timeout) { + callback(); + } + + clearTimeout(timer); + + timer = Date.now(); +} + +}) +//////////////////////////////////////////////////////////////////////////////// +require([], function() { +function addCopyButtonCallbacks() { + for (const el of document.getElementsByTagName("pre")) { + const button = document.createElement("button"); + button.classList.add("copy-button", "fa-solid", "fa-copy"); + button.setAttribute("aria-label", "Copy this code block"); + button.setAttribute("title", "Copy"); + + el.appendChild(button); + + const success = function () { + button.classList.add("success", "fa-check"); + button.classList.remove("fa-copy"); + }; + + const failure = function () { + button.classList.add("error", "fa-xmark"); + button.classList.remove("fa-copy"); + }; + + button.addEventListener("click", function () { + copyToClipboard(el.innerText).then(success, failure); + + setTimeout(function () { + button.classList.add("fa-copy"); + button.classList.remove("success", "fa-check", "fa-xmark"); + }, 5000); + }); + } +} + +function copyToClipboard(text) { + // clipboard API is only available in secure contexts + if (window.navigator && window.navigator.clipboard) { + return window.navigator.clipboard.writeText(text); + } else { + return new Promise(function (resolve, reject) { + try { + const el = document.createElement("textarea"); + el.textContent = text; + el.style.position = "fixed"; + el.style.opacity = 0; + document.body.appendChild(el); + el.select(); + document.execCommand("copy"); + + resolve(); + } catch (err) { + reject(err); + } finally { + document.body.removeChild(el); + } + }); + } +} + +if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", addCopyButtonCallbacks); +} else { + addCopyButtonCallbacks(); +} + +}) +//////////////////////////////////////////////////////////////////////////////// +require(['jquery', 'headroom', 'headroom-jquery'], function($, Headroom) { + +// Manages the top navigation bar (hides it when the user starts scrolling down on the +// mobile). +window.Headroom = Headroom; // work around buggy module loading? +$(document).ready(function () { + $("#documenter .docs-navbar").headroom({ + tolerance: { up: 10, down: 10 }, + }); +}); + +}) +//////////////////////////////////////////////////////////////////////////////// +require(['jquery'], function($) { + +$(document).ready(function () { + let meta = $("div[data-docstringscollapsed]").data(); + + if (meta?.docstringscollapsed) { + $("#documenter-article-toggle-button").trigger({ + type: "click", + noToggleAnimation: true, + }); + } +}); + +}) +//////////////////////////////////////////////////////////////////////////////// +require(['jquery'], function($) { + +/* +To get an in-depth about the thought process you can refer: https://hetarth02.hashnode.dev/series/gsoc + +PSEUDOCODE: + +Searching happens automatically as the user types or adjusts the selected filters. +To preserve responsiveness, as much as possible of the slow parts of the search are done +in a web worker. Searching and result generation are done in the worker, and filtering and +DOM updates are done in the main thread. The filters are in the main thread as they should +be very quick to apply. This lets filters be changed without re-searching with minisearch +(which is possible even if filtering is on the worker thread) and also lets filters be +changed _while_ the worker is searching and without message passing (neither of which are +possible if filtering is on the worker thread) + +SEARCH WORKER: + +Import minisearch + +Build index + +On message from main thread + run search + find the first 200 unique results from each category, and compute their divs for display + note that this is necessary and sufficient information for the main thread to find the + first 200 unique results from any given filter set + post results to main thread + +MAIN: + +Launch worker + +Declare nonconstant globals (worker_is_running, last_search_text, unfiltered_results) + +On text update + if worker is not running, launch_search() + +launch_search + set worker_is_running to true, set last_search_text to the search text + post the search query to worker + +on message from worker + if last_search_text is not the same as the text in the search field, + the latest search result is not reflective of the latest search query, so update again + launch_search() + otherwise + set worker_is_running to false + + regardless, display the new search results to the user + save the unfiltered_results as a global + update_search() + +on filter click + adjust the filter selection + update_search() + +update_search + apply search filters by looping through the unfiltered_results and finding the first 200 + unique results that match the filters + + Update the DOM +*/ + +/////// SEARCH WORKER /////// + +function worker_function(documenterSearchIndex, documenterBaseURL, filters) { + importScripts( + "https://cdn.jsdelivr.net/npm/minisearch@6.1.0/dist/umd/index.min.js" + ); + + let data = documenterSearchIndex.map((x, key) => { + x["id"] = key; // minisearch requires a unique for each object + return x; + }); + + // list below is the lunr 2.1.3 list minus the intersect with names(Base) + // (all, any, get, in, is, only, which) and (do, else, for, let, where, while, with) + // ideally we'd just filter the original list but it's not available as a variable + const stopWords = new Set([ + "a", + "able", + "about", + "across", + "after", + "almost", + "also", + "am", + "among", + "an", + "and", + "are", + "as", + "at", + "be", + "because", + "been", + "but", + "by", + "can", + "cannot", + "could", + "dear", + "did", + "does", + "either", + "ever", + "every", + "from", + "got", + "had", + "has", + "have", + "he", + "her", + "hers", + "him", + "his", + "how", + "however", + "i", + "if", + "into", + "it", + "its", + "just", + "least", + "like", + "likely", + "may", + "me", + "might", + "most", + "must", + "my", + "neither", + "no", + "nor", + "not", + "of", + "off", + "often", + "on", + "or", + "other", + "our", + "own", + "rather", + "said", + "say", + "says", + "she", + "should", + "since", + "so", + "some", + "than", + "that", + "the", + "their", + "them", + "then", + "there", + "these", + "they", + "this", + "tis", + "to", + "too", + "twas", + "us", + "wants", + "was", + "we", + "were", + "what", + "when", + "who", + "whom", + "why", + "will", + "would", + "yet", + "you", + "your", + ]); + + let index = new MiniSearch({ + fields: ["title", "text"], // fields to index for full-text search + storeFields: ["location", "title", "text", "category", "page"], // fields to return with results + processTerm: (term) => { + let word = stopWords.has(term) ? null : term; + if (word) { + // custom trimmer that doesn't strip @ and !, which are used in julia macro and function names + word = word + .replace(/^[^a-zA-Z0-9@!]+/, "") + .replace(/[^a-zA-Z0-9@!]+$/, ""); + + word = word.toLowerCase(); + } + + return word ?? null; + }, + // add . as a separator, because otherwise "title": "Documenter.Anchors.add!", would not + // find anything if searching for "add!", only for the entire qualification + tokenize: (string) => string.split(/[\s\-\.]+/), + // options which will be applied during the search + searchOptions: { + prefix: true, + boost: { title: 100 }, + fuzzy: 2, + }, + }); + + index.addAll(data); + + /** + * Used to map characters to HTML entities. + * Refer: https://github.com/lodash/lodash/blob/main/src/escape.ts + */ + const htmlEscapes = { + "&": "&", + "<": "<", + ">": ">", + '"': """, + "'": "'", + }; + + /** + * Used to match HTML entities and HTML characters. + * Refer: https://github.com/lodash/lodash/blob/main/src/escape.ts + */ + const reUnescapedHtml = /[&<>"']/g; + const reHasUnescapedHtml = RegExp(reUnescapedHtml.source); + + /** + * Escape function from lodash + * Refer: https://github.com/lodash/lodash/blob/main/src/escape.ts + */ + function escape(string) { + return string && reHasUnescapedHtml.test(string) + ? string.replace(reUnescapedHtml, (chr) => htmlEscapes[chr]) + : string || ""; + } + + /** + * RegX escape function from MDN + * Refer: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping + */ + function escapeRegExp(string) { + return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string + } + + /** + * Make the result component given a minisearch result data object and the value + * of the search input as queryString. To view the result object structure, refer: + * https://lucaong.github.io/minisearch/modules/_minisearch_.html#searchresult + * + * @param {object} result + * @param {string} querystring + * @returns string + */ + function make_search_result(result, querystring) { + let search_divider = `
`; + let display_link = + result.location.slice(Math.max(0), Math.min(50, result.location.length)) + + (result.location.length > 30 ? "..." : ""); // To cut-off the link because it messes with the overflow of the whole div + + if (result.page !== "") { + display_link += ` (${result.page})`; + } + searchstring = escapeRegExp(querystring); + let textindex = new RegExp(`${searchstring}`, "i").exec(result.text); + let text = + textindex !== null + ? result.text.slice( + Math.max(textindex.index - 100, 0), + Math.min( + textindex.index + querystring.length + 100, + result.text.length + ) + ) + : ""; // cut-off text before and after from the match + + text = text.length ? escape(text) : ""; + + let display_result = text.length + ? "..." + + text.replace( + new RegExp(`${escape(searchstring)}`, "i"), // For first occurrence + '$&' + ) + + "..." + : ""; // highlights the match + + let in_code = false; + if (!["page", "section"].includes(result.category.toLowerCase())) { + in_code = true; + } + + // We encode the full url to escape some special characters which can lead to broken links + let result_div = ` + +
+
${escape(result.title)}
+
${result.category}
+
+

+ ${display_result} +

+
+ ${display_link} +
+
+ ${search_divider} + `; + + return result_div; + } + + self.onmessage = function (e) { + let query = e.data; + let results = index.search(query, { + filter: (result) => { + // Only return relevant results + return result.score >= 1; + }, + combineWith: "AND", + }); + + // Pre-filter to deduplicate and limit to 200 per category to the extent + // possible without knowing what the filters are. + let filtered_results = []; + let counts = {}; + for (let filter of filters) { + counts[filter] = 0; + } + let present = {}; + + for (let result of results) { + cat = result.category; + cnt = counts[cat]; + if (cnt < 200) { + id = cat + "---" + result.location; + if (present[id]) { + continue; + } + present[id] = true; + filtered_results.push({ + location: result.location, + category: cat, + div: make_search_result(result, query), + }); + } + } + + postMessage(filtered_results); + }; +} + +// `worker = Threads.@spawn worker_function(documenterSearchIndex)`, but in JavaScript! +const filters = [ + ...new Set(documenterSearchIndex["docs"].map((x) => x.category)), +]; +const worker_str = + "(" + + worker_function.toString() + + ")(" + + JSON.stringify(documenterSearchIndex["docs"]) + + "," + + JSON.stringify(documenterBaseURL) + + "," + + JSON.stringify(filters) + + ")"; +const worker_blob = new Blob([worker_str], { type: "text/javascript" }); +const worker = new Worker(URL.createObjectURL(worker_blob)); + +/////// SEARCH MAIN /////// + +// Whether the worker is currently handling a search. This is a boolean +// as the worker only ever handles 1 or 0 searches at a time. +var worker_is_running = false; + +// The last search text that was sent to the worker. This is used to determine +// if the worker should be launched again when it reports back results. +var last_search_text = ""; + +// The results of the last search. This, in combination with the state of the filters +// in the DOM, is used compute the results to display on calls to update_search. +var unfiltered_results = []; + +// Which filter is currently selected +var selected_filter = ""; + +$(document).on("input", ".documenter-search-input", function (event) { + if (!worker_is_running) { + launch_search(); + } +}); + +function launch_search() { + worker_is_running = true; + last_search_text = $(".documenter-search-input").val(); + worker.postMessage(last_search_text); +} + +worker.onmessage = function (e) { + if (last_search_text !== $(".documenter-search-input").val()) { + launch_search(); + } else { + worker_is_running = false; + } + + unfiltered_results = e.data; + update_search(); +}; + +$(document).on("click", ".search-filter", function () { + if ($(this).hasClass("search-filter-selected")) { + selected_filter = ""; + } else { + selected_filter = $(this).text().toLowerCase(); + } + + // This updates search results and toggles classes for UI: + update_search(); +}); + +/** + * Make/Update the search component + */ +function update_search() { + let querystring = $(".documenter-search-input").val(); + + if (querystring.trim()) { + if (selected_filter == "") { + results = unfiltered_results; + } else { + results = unfiltered_results.filter((result) => { + return selected_filter == result.category.toLowerCase(); + }); + } + + let search_result_container = ``; + let modal_filters = make_modal_body_filters(); + let search_divider = `
`; + + if (results.length) { + let links = []; + let count = 0; + let search_results = ""; + + for (var i = 0, n = results.length; i < n && count < 200; ++i) { + let result = results[i]; + if (result.location && !links.includes(result.location)) { + search_results += result.div; + count++; + links.push(result.location); + } + } + + if (count == 1) { + count_str = "1 result"; + } else if (count == 200) { + count_str = "200+ results"; + } else { + count_str = count + " results"; + } + let result_count = `
${count_str}
`; + + search_result_container = ` +
+ ${modal_filters} + ${search_divider} + ${result_count} +
+ ${search_results} +
+
+ `; + } else { + search_result_container = ` +
+ ${modal_filters} + ${search_divider} +
0 result(s)
+
+
No result found!
+ `; + } + + if ($(".search-modal-card-body").hasClass("is-justify-content-center")) { + $(".search-modal-card-body").removeClass("is-justify-content-center"); + } + + $(".search-modal-card-body").html(search_result_container); + } else { + if (!$(".search-modal-card-body").hasClass("is-justify-content-center")) { + $(".search-modal-card-body").addClass("is-justify-content-center"); + } + + $(".search-modal-card-body").html(` +
Type something to get started!
+ `); + } +} + +/** + * Make the modal filter html + * + * @returns string + */ +function make_modal_body_filters() { + let str = filters + .map((val) => { + if (selected_filter == val.toLowerCase()) { + return `${val}`; + } else { + return `${val}`; + } + }) + .join(""); + + return ` +
+ Filters: + ${str} +
`; +} + +}) +//////////////////////////////////////////////////////////////////////////////// +require(['jquery'], function($) { + +// Modal settings dialog +$(document).ready(function () { + var settings = $("#documenter-settings"); + $("#documenter-settings-button").click(function () { + settings.toggleClass("is-active"); + }); + // Close the dialog if X is clicked + $("#documenter-settings button.delete").click(function () { + settings.removeClass("is-active"); + }); + // Close dialog if ESC is pressed + $(document).keyup(function (e) { + if (e.keyCode == 27) settings.removeClass("is-active"); + }); +}); + +}) +//////////////////////////////////////////////////////////////////////////////// +require(['jquery'], function($) { + +$(document).ready(function () { + let search_modal_header = ` + + `; + + let initial_search_body = ` +
Type something to get started!
+ `; + + let search_modal_footer = ` + + `; + + $(document.body).append( + ` + + ` + ); + + document.querySelector(".docs-search-query").addEventListener("click", () => { + openModal(); + }); + + document + .querySelector(".close-search-modal") + .addEventListener("click", () => { + closeModal(); + }); + + $(document).on("click", ".search-result-link", function () { + closeModal(); + }); + + document.addEventListener("keydown", (event) => { + if ((event.ctrlKey || event.metaKey) && event.key === "/") { + openModal(); + } else if (event.key === "Escape") { + closeModal(); + } + + return false; + }); + + // Functions to open and close a modal + function openModal() { + let searchModal = document.querySelector("#search-modal"); + + searchModal.classList.add("is-active"); + document.querySelector(".documenter-search-input").focus(); + } + + function closeModal() { + let searchModal = document.querySelector("#search-modal"); + let initial_search_body = ` +
Type something to get started!
+ `; + + searchModal.classList.remove("is-active"); + document.querySelector(".documenter-search-input").blur(); + + if (!$(".search-modal-card-body").hasClass("is-justify-content-center")) { + $(".search-modal-card-body").addClass("is-justify-content-center"); + } + + $(".documenter-search-input").val(""); + $(".search-modal-card-body").html(initial_search_body); + } + + document + .querySelector("#search-modal .modal-background") + .addEventListener("click", () => { + closeModal(); + }); +}); + +}) +//////////////////////////////////////////////////////////////////////////////// +require(['jquery'], function($) { + +// Manages the showing and hiding of the sidebar. +$(document).ready(function () { + var sidebar = $("#documenter > .docs-sidebar"); + var sidebar_button = $("#documenter-sidebar-button"); + sidebar_button.click(function (ev) { + ev.preventDefault(); + sidebar.toggleClass("visible"); + if (sidebar.hasClass("visible")) { + // Makes sure that the current menu item is visible in the sidebar. + $("#documenter .docs-menu a.is-active").focus(); + } + }); + $("#documenter > .docs-main").bind("click", function (ev) { + if ($(ev.target).is(sidebar_button)) { + return; + } + if (sidebar.hasClass("visible")) { + sidebar.removeClass("visible"); + } + }); +}); + +// Resizes the package name / sitename in the sidebar if it is too wide. +// Inspired by: https://github.com/davatron5000/FitText.js +$(document).ready(function () { + e = $("#documenter .docs-autofit"); + function resize() { + var L = parseInt(e.css("max-width"), 10); + var L0 = e.width(); + if (L0 > L) { + var h0 = parseInt(e.css("font-size"), 10); + e.css("font-size", (L * h0) / L0); + // TODO: make sure it survives resizes? + } + } + // call once and then register events + resize(); + $(window).resize(resize); + $(window).on("orientationchange", resize); +}); + +// Scroll the navigation bar to the currently selected menu item +$(document).ready(function () { + var sidebar = $("#documenter .docs-menu").get(0); + var active = $("#documenter .docs-menu .is-active").get(0); + if (typeof active !== "undefined") { + sidebar.scrollTop = active.offsetTop - sidebar.offsetTop - 15; + } +}); + +}) +//////////////////////////////////////////////////////////////////////////////// +require(['jquery'], function($) { + +// Theme picker setup +$(document).ready(function () { + // onchange callback + $("#documenter-themepicker").change(function themepick_callback(ev) { + var themename = $("#documenter-themepicker option:selected").attr("value"); + if (themename === "auto") { + // set_theme(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'); + window.localStorage.removeItem("documenter-theme"); + } else { + // set_theme(themename); + window.localStorage.setItem("documenter-theme", themename); + } + // We re-use the global function from themeswap.js to actually do the swapping. + set_theme_from_local_storage(); + }); + + // Make sure that the themepicker displays the correct theme when the theme is retrieved + // from localStorage + if (typeof window.localStorage !== "undefined") { + var theme = window.localStorage.getItem("documenter-theme"); + if (theme !== null) { + $("#documenter-themepicker option").each(function (i, e) { + e.selected = e.value === theme; + }); + } + } +}); + +}) +//////////////////////////////////////////////////////////////////////////////// +require(['jquery'], function($) { + +// update the version selector with info from the siteinfo.js and ../versions.js files +$(document).ready(function () { + // If the version selector is disabled with DOCUMENTER_VERSION_SELECTOR_DISABLED in the + // siteinfo.js file, we just return immediately and not display the version selector. + if ( + typeof DOCUMENTER_VERSION_SELECTOR_DISABLED === "boolean" && + DOCUMENTER_VERSION_SELECTOR_DISABLED + ) { + return; + } + + var version_selector = $("#documenter .docs-version-selector"); + var version_selector_select = $("#documenter .docs-version-selector select"); + + version_selector_select.change(function (x) { + target_href = version_selector_select + .children("option:selected") + .get(0).value; + window.location.href = target_href; + }); + + // add the current version to the selector based on siteinfo.js, but only if the selector is empty + if ( + typeof DOCUMENTER_CURRENT_VERSION !== "undefined" && + $("#version-selector > option").length == 0 + ) { + var option = $( + "" + ); + version_selector_select.append(option); + } + + if (typeof DOC_VERSIONS !== "undefined") { + var existing_versions = version_selector_select.children("option"); + var existing_versions_texts = existing_versions.map(function (i, x) { + return x.text; + }); + DOC_VERSIONS.forEach(function (each) { + var version_url = documenterBaseURL + "/../" + each + "/"; + var existing_id = $.inArray(each, existing_versions_texts); + // if not already in the version selector, add it as a new option, + // otherwise update the old option with the URL and enable it + if (existing_id == -1) { + var option = $( + "" + ); + version_selector_select.append(option); + } else { + var option = existing_versions[existing_id]; + option.value = version_url; + option.disabled = false; + } + }); + } + + // only show the version selector if the selector has been populated + if (version_selector_select.children("option").length > 0) { + version_selector.toggleClass("visible"); + } +}); + +}) diff --git a/previews/PR798/assets/favicon.ico b/previews/PR798/assets/favicon.ico new file mode 100644 index 0000000000..6de022e8eb Binary files /dev/null and b/previews/PR798/assets/favicon.ico differ diff --git a/previews/PR798/assets/logo-horizontal-dark.svg b/previews/PR798/assets/logo-horizontal-dark.svg new file mode 100644 index 0000000000..1537aa3ebb --- /dev/null +++ b/previews/PR798/assets/logo-horizontal-dark.svg @@ -0,0 +1,356 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/previews/PR798/assets/logo-horizontal.svg b/previews/PR798/assets/logo-horizontal.svg new file mode 100644 index 0000000000..a9ac585c86 --- /dev/null +++ b/previews/PR798/assets/logo-horizontal.svg @@ -0,0 +1,356 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/previews/PR798/assets/logo-name.svg b/previews/PR798/assets/logo-name.svg new file mode 100644 index 0000000000..a7fafd0ef1 --- /dev/null +++ b/previews/PR798/assets/logo-name.svg @@ -0,0 +1,416 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/previews/PR798/assets/logo.svg b/previews/PR798/assets/logo.svg new file mode 100644 index 0000000000..4a227febdb --- /dev/null +++ b/previews/PR798/assets/logo.svg @@ -0,0 +1,243 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/previews/PR798/assets/references.bib b/previews/PR798/assets/references.bib new file mode 100644 index 0000000000..0678618983 --- /dev/null +++ b/previews/PR798/assets/references.bib @@ -0,0 +1,214 @@ +@article{JanHacJun2019regularizedthermotopopt, + title={An accurate and fast regularization approach to thermodynamic topology optimization}, + author={Jantos, Dustin R and Hackl, Klaus and Junker, Philipp}, + journal={International Journal for Numerical Methods in Engineering}, + volume={117}, + number={9}, + pages={991--1017}, + year={2019}, + url={https://doi.org/10.1002/nme.5988}, + doi={10.1002/nme.5988}, + publisher={Wiley Online Library} +} +@article{BlaJanJun2022taylorwlsthermotopopt, + title={Application of Taylor series combined with the weighted least square method to thermodynamic topology optimization}, + author={Blaszczyk, Mischa and Jantos, Dustin Roman and Junker, Philipp}, + journal={Computer Methods in Applied Mechanics and Engineering}, + volume={393}, + pages={114698}, + year={2022}, + url={https://doi.org/10.1016/j.cma.2022.114698}, + doi={10.1016/j.cma.2022.114698}, + publisher={Elsevier} +} +@article{Turcksin2016, + author={Turcksin, Bruno and Kronbichler, Martin and Bangerth, Wolfgang}, + title={WorkStream -- A Design Pattern for Multicore-Enabled Finite Element Computations}, + year={2016}, + issue_date={March 2017}, + publisher={Association for Computing Machinery}, + address={New York, NY, USA}, + volume={43}, + number={1}, + issn={0098-3500}, + url={https://doi.org/10.1145/2851488}, + doi={10.1145/2851488}, + journal={ACM Trans. Math. Softw.}, + month={aug}, + articleno={2}, + numpages={29}, + keywords={Finite element algorithms, assembly, pipeline software pattern} +} +@article{Scroggs2022, +author = {Scroggs, Matthew W. and Dokken, J\o{}rgen S. and Richardson, Chris N. and Wells, Garth N.}, +title = {Construction of Arbitrary Order Finite Element Degree-of-Freedom Maps on Polygonal and Polyhedral Cell Meshes}, +year = {2022}, +issue_date = {June 2022}, +publisher = {Association for Computing Machinery}, +address = {New York, NY, USA}, +volume = {48}, +number = {2}, +issn = {0098-3500}, +url = {https://doi.org/10.1145/3524456}, +doi = {10.1145/3524456}, +journal = {ACM Trans. Math. Softw.}, +month = {may}, +articleno = {18}, +numpages = {23}, +keywords = {Finite element methods, polyhedral cells, degrees-of-freedom} +} +@phdthesis{Cenanovic2017, + author={Mirza Cenanovic}, + title={Finite element methods for surface problems}, + school={Jönköping University, School of Engineering}, + year={2017}, +} +@misc{Kirby2017, + title={A general approach to transforming finite elements}, + author={Robert C. Kirby}, + year={2017}, + eprint={1706.09017}, + doi={10.48550/arXiv.1706.09017}, + archivePrefix={arXiv}, + primaryClass={math.NA}, +} +@article{SimMie:1992:act, +title = {Associative coupled thermoplasticity at finite strains: Formulation, numerical analysis and implementation}, +journal = {Computer Methods in Applied Mechanics and Engineering}, +volume = {98}, +number = {1}, +pages = {41-104}, +year = {1992}, +issn = {0045-7825}, +doi = {10.1016/0045-7825(92)90170-O}, +url = {https://www.sciencedirect.com/science/article/pii/004578259290170O}, +author = {J.C. Simo and C. Miehe}, +} +@book{Hol:2000:nsm, + title = {Nonlinear Solid Mechanics: A Continuum Approach for Engineering}, + shorttitle = {Nonlinear Solid Mechanics}, + author = {Holzapfel, Gerhard A.}, + year = {2000}, + publisher = {{Wiley}}, + address = {{Chichester ; New York}}, + isbn = {978-0-471-82304-9 978-0-471-82319-3}, + langid = {english}, + lccn = {QA808.2 .H655 2000}, + keywords = {Continuum mechanics} +} +@article{Cockburn:2002:unifiedanalysis, + ISSN = {00361429}, + URL = {http://www.jstor.org/stable/4101034}, + abstract = {We provide a framework for the analysis of a large class of discontinuous methods for second-order elliptic problems. It allows for the understanding and comparison of most of the discontinuous Galerkin methods that have been proposed over the past three decades for the numerical treatment of elliptic problems.}, + author = {Douglas N. Arnold and Franco Brezzi and Bernardo Cockburn and L. Donatella Marini}, + journal = {SIAM Journal on Numerical Analysis}, + number = {5}, + pages = {1749--1779}, + publisher = {Society for Industrial and Applied Mathematics}, + title = {Unified Analysis of Discontinuous Galerkin Methods for Elliptic Problems}, + urldate = {2023-12-20}, + volume = {39}, + year = {2002} +} +@article{Mu:2014:IP, +title = {Interior penalty discontinuous Galerkin method on very general polygonal and polyhedral meshes}, +journal = {Journal of Computational and Applied Mathematics}, +volume = {255}, +pages = {432-440}, +year = {2014}, +issn = {0377-0427}, +doi = {10.1016/j.cam.2013.06.003}, +url = {https://www.sciencedirect.com/science/article/pii/S0377042713002999}, +author = {Lin Mu and Junping Wang and Yanqiu Wang and Xiu Ye}, +keywords = {Discontinuous Galerkin, Finite element, Interior penalty, Second-order elliptic equations, Hybrid mesh}, +abstract = {This paper provides a theoretical foundation for interior penalty discontinuous Galerkin methods for second-order elliptic equations on very general polygonal or polyhedral meshes. The mesh can be composed of any polygons or polyhedra that satisfy certain shape regularity conditions characterized in a recent paper by two of the authors, Wang and Ye (2012) [11]. The usual H1-conforming finite element methods on such meshes are either very complicated or impossible to implement in practical computation. The interior penalty discontinuous Galerkin method provides a simple and effective alternative approach which is efficient and robust. Results with such general meshes have important application in computational sciences.} +} +@article{Jin:1984:sgq, + title = {Symmetric gaussian quadrature formulae for tetrahedronal regions}, + journal = {Computer Methods in Applied Mechanics and Engineering}, + volume = {43}, + number = {3}, + pages = {349-353}, + year = {1984}, + issn = {0045-7825}, + doi = {10.1016/0045-7825(84)90072-0}, + url = {https://www.sciencedirect.com/science/article/pii/0045782584900720}, + author = {Jinyun Yu}, + abstract = {Quadrature formulae of degrees 2 to 6 are presented for the numerical integration of a function over tetrahedronal regions. The formulae presented are of Gaussian type and fully symmetric with respect to the four vertices of the tetrahedron.} +} +@article{Dun:1985:hde, + title={High degree efficient symmetrical Gaussian quadrature rules for the triangle}, + author={Dunavant, D.A.}, + journal={International journal for numerical methods in engineering}, + volume={21}, + number={6}, + pages={1129--1148}, + year={1985}, + publisher={Wiley Online Library}, + doi={10.1002/nme.1620210612}, + url={https://onlinelibrary.wiley.com/doi/abs/10.1002/nme.1620210612}, +} +@article{WitVin:2015:isq, + title={On the identification of symmetric quadrature rules for finite element methods}, + author={Witherden, Freddie D and Vincent, Peter E}, + journal={Computers \& Mathematics with Applications}, + volume={69}, + number={10}, + pages={1232--1241}, + year={2015}, + publisher={Elsevier} +} +@article{Keast:1986:mtq, + title={Moderate-degree tetrahedral quadrature formulas}, + author={Keast, Patrick}, + journal={Computer methods in applied mechanics and engineering}, + volume={55}, + number={3}, + pages={339--348}, + year={1986}, + publisher={Elsevier}, + doi={10.1016/0045-7825(86)90059-9}, + url={https://www.sciencedirect.com/science/article/pii/0045782586900599}, +} +@article{RanTur:1992:snq, + title={Simple nonconforming quadrilateral Stokes element}, + author={Rannacher, Rolf and Turek, Stefan}, + journal={Numerical Methods for Partial Differential Equations}, + volume={8}, + number={2}, + pages={97--111}, + year={1992}, + publisher={Wiley Online Library} +} +@article{CroRav:1973:cnf, + title={Conforming and nonconforming finite element methods for solving the stationary Stokes equations I}, + author={Crouzeix, Michel and Raviart, P-A}, + journal={Revue fran{\c{c}}aise d'automatique informatique recherche op{\'e}rationnelle. Math{\'e}matique}, + volume={7}, + number={R3}, + pages={33--75}, + year={1973}, + publisher={EDP Sciences} +} +@book{Gatica2014, +title = {A Simple Introduction to the Mixed Finite Element Method: Theory and Applications}, +ISBN = {9783319036953}, +ISSN = {2191-8201}, +url = {http://dx.doi.org/10.1007/978-3-319-03695-3}, +DOI = {10.1007/978-3-319-03695-3}, +journal = {SpringerBriefs in Mathematics}, +publisher = {Springer International Publishing}, +author = {Gatica, Gabriel N.}, +year = {2014} +} +@book{Boffi2013, + title = {Mixed Finite Element Methods and Applications}, + ISBN = {9783642365195}, + ISSN = {0179-3632}, + url = {http://dx.doi.org/10.1007/978-3-642-36519-5}, + DOI = {10.1007/978-3-642-36519-5}, + journal = {Springer Series in Computational Mathematics}, + publisher = {Springer Berlin Heidelberg}, + author = {Boffi, Daniele and Brezzi, Franco and Fortin, Michel}, + year = {2013} +} diff --git a/previews/PR798/assets/themes/catppuccin-frappe.css b/previews/PR798/assets/themes/catppuccin-frappe.css new file mode 100644 index 0000000000..32e3f00823 --- /dev/null +++ b/previews/PR798/assets/themes/catppuccin-frappe.css @@ -0,0 +1 @@ +html.theme--catppuccin-frappe .pagination-previous,html.theme--catppuccin-frappe .pagination-next,html.theme--catppuccin-frappe .pagination-link,html.theme--catppuccin-frappe .pagination-ellipsis,html.theme--catppuccin-frappe .file-cta,html.theme--catppuccin-frappe .file-name,html.theme--catppuccin-frappe .select select,html.theme--catppuccin-frappe .textarea,html.theme--catppuccin-frappe .input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-frappe .button{-moz-appearance:none;-webkit-appearance:none;align-items:center;border:1px solid transparent;border-radius:.4em;box-shadow:none;display:inline-flex;font-size:1rem;height:2.5em;justify-content:flex-start;line-height:1.5;padding-bottom:calc(0.5em - 1px);padding-left:calc(0.75em - 1px);padding-right:calc(0.75em - 1px);padding-top:calc(0.5em - 1px);position:relative;vertical-align:top}html.theme--catppuccin-frappe .pagination-previous:focus,html.theme--catppuccin-frappe .pagination-next:focus,html.theme--catppuccin-frappe .pagination-link:focus,html.theme--catppuccin-frappe .pagination-ellipsis:focus,html.theme--catppuccin-frappe .file-cta:focus,html.theme--catppuccin-frappe .file-name:focus,html.theme--catppuccin-frappe .select select:focus,html.theme--catppuccin-frappe .textarea:focus,html.theme--catppuccin-frappe .input:focus,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input:focus,html.theme--catppuccin-frappe .button:focus,html.theme--catppuccin-frappe .is-focused.pagination-previous,html.theme--catppuccin-frappe .is-focused.pagination-next,html.theme--catppuccin-frappe .is-focused.pagination-link,html.theme--catppuccin-frappe .is-focused.pagination-ellipsis,html.theme--catppuccin-frappe .is-focused.file-cta,html.theme--catppuccin-frappe .is-focused.file-name,html.theme--catppuccin-frappe .select select.is-focused,html.theme--catppuccin-frappe .is-focused.textarea,html.theme--catppuccin-frappe .is-focused.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-frappe .is-focused.button,html.theme--catppuccin-frappe .pagination-previous:active,html.theme--catppuccin-frappe .pagination-next:active,html.theme--catppuccin-frappe .pagination-link:active,html.theme--catppuccin-frappe .pagination-ellipsis:active,html.theme--catppuccin-frappe .file-cta:active,html.theme--catppuccin-frappe .file-name:active,html.theme--catppuccin-frappe .select select:active,html.theme--catppuccin-frappe .textarea:active,html.theme--catppuccin-frappe .input:active,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input:active,html.theme--catppuccin-frappe .button:active,html.theme--catppuccin-frappe .is-active.pagination-previous,html.theme--catppuccin-frappe .is-active.pagination-next,html.theme--catppuccin-frappe .is-active.pagination-link,html.theme--catppuccin-frappe .is-active.pagination-ellipsis,html.theme--catppuccin-frappe .is-active.file-cta,html.theme--catppuccin-frappe .is-active.file-name,html.theme--catppuccin-frappe .select select.is-active,html.theme--catppuccin-frappe .is-active.textarea,html.theme--catppuccin-frappe .is-active.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-active,html.theme--catppuccin-frappe .is-active.button{outline:none}html.theme--catppuccin-frappe .pagination-previous[disabled],html.theme--catppuccin-frappe .pagination-next[disabled],html.theme--catppuccin-frappe .pagination-link[disabled],html.theme--catppuccin-frappe .pagination-ellipsis[disabled],html.theme--catppuccin-frappe .file-cta[disabled],html.theme--catppuccin-frappe .file-name[disabled],html.theme--catppuccin-frappe .select select[disabled],html.theme--catppuccin-frappe .textarea[disabled],html.theme--catppuccin-frappe .input[disabled],html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input[disabled],html.theme--catppuccin-frappe .button[disabled],fieldset[disabled] html.theme--catppuccin-frappe .pagination-previous,html.theme--catppuccin-frappe fieldset[disabled] .pagination-previous,fieldset[disabled] html.theme--catppuccin-frappe .pagination-next,html.theme--catppuccin-frappe fieldset[disabled] .pagination-next,fieldset[disabled] html.theme--catppuccin-frappe .pagination-link,html.theme--catppuccin-frappe fieldset[disabled] .pagination-link,fieldset[disabled] html.theme--catppuccin-frappe .pagination-ellipsis,html.theme--catppuccin-frappe fieldset[disabled] .pagination-ellipsis,fieldset[disabled] html.theme--catppuccin-frappe .file-cta,html.theme--catppuccin-frappe fieldset[disabled] .file-cta,fieldset[disabled] html.theme--catppuccin-frappe .file-name,html.theme--catppuccin-frappe fieldset[disabled] .file-name,fieldset[disabled] html.theme--catppuccin-frappe .select select,fieldset[disabled] html.theme--catppuccin-frappe .textarea,fieldset[disabled] html.theme--catppuccin-frappe .input,fieldset[disabled] html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-frappe fieldset[disabled] .select select,html.theme--catppuccin-frappe .select fieldset[disabled] select,html.theme--catppuccin-frappe fieldset[disabled] .textarea,html.theme--catppuccin-frappe fieldset[disabled] .input,html.theme--catppuccin-frappe fieldset[disabled] #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-frappe #documenter .docs-sidebar fieldset[disabled] form.docs-search>input,fieldset[disabled] html.theme--catppuccin-frappe .button,html.theme--catppuccin-frappe fieldset[disabled] .button{cursor:not-allowed}html.theme--catppuccin-frappe .tabs,html.theme--catppuccin-frappe .pagination-previous,html.theme--catppuccin-frappe .pagination-next,html.theme--catppuccin-frappe .pagination-link,html.theme--catppuccin-frappe .pagination-ellipsis,html.theme--catppuccin-frappe .breadcrumb,html.theme--catppuccin-frappe .file,html.theme--catppuccin-frappe .button,.is-unselectable{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}html.theme--catppuccin-frappe .navbar-link:not(.is-arrowless)::after,html.theme--catppuccin-frappe .select:not(.is-multiple):not(.is-loading)::after{border:3px solid rgba(0,0,0,0);border-radius:2px;border-right:0;border-top:0;content:" ";display:block;height:0.625em;margin-top:-0.4375em;pointer-events:none;position:absolute;top:50%;transform:rotate(-45deg);transform-origin:center;width:0.625em}html.theme--catppuccin-frappe .admonition:not(:last-child),html.theme--catppuccin-frappe .tabs:not(:last-child),html.theme--catppuccin-frappe .pagination:not(:last-child),html.theme--catppuccin-frappe .message:not(:last-child),html.theme--catppuccin-frappe .level:not(:last-child),html.theme--catppuccin-frappe .breadcrumb:not(:last-child),html.theme--catppuccin-frappe .block:not(:last-child),html.theme--catppuccin-frappe .title:not(:last-child),html.theme--catppuccin-frappe .subtitle:not(:last-child),html.theme--catppuccin-frappe .table-container:not(:last-child),html.theme--catppuccin-frappe .table:not(:last-child),html.theme--catppuccin-frappe .progress:not(:last-child),html.theme--catppuccin-frappe .notification:not(:last-child),html.theme--catppuccin-frappe .content:not(:last-child),html.theme--catppuccin-frappe .box:not(:last-child){margin-bottom:1.5rem}html.theme--catppuccin-frappe .modal-close,html.theme--catppuccin-frappe .delete{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-moz-appearance:none;-webkit-appearance:none;background-color:rgba(10,10,10,0.2);border:none;border-radius:9999px;cursor:pointer;pointer-events:auto;display:inline-block;flex-grow:0;flex-shrink:0;font-size:0;height:20px;max-height:20px;max-width:20px;min-height:20px;min-width:20px;outline:none;position:relative;vertical-align:top;width:20px}html.theme--catppuccin-frappe .modal-close::before,html.theme--catppuccin-frappe .delete::before,html.theme--catppuccin-frappe .modal-close::after,html.theme--catppuccin-frappe .delete::after{background-color:#fff;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}html.theme--catppuccin-frappe .modal-close::before,html.theme--catppuccin-frappe .delete::before{height:2px;width:50%}html.theme--catppuccin-frappe .modal-close::after,html.theme--catppuccin-frappe .delete::after{height:50%;width:2px}html.theme--catppuccin-frappe .modal-close:hover,html.theme--catppuccin-frappe .delete:hover,html.theme--catppuccin-frappe .modal-close:focus,html.theme--catppuccin-frappe .delete:focus{background-color:rgba(10,10,10,0.3)}html.theme--catppuccin-frappe .modal-close:active,html.theme--catppuccin-frappe .delete:active{background-color:rgba(10,10,10,0.4)}html.theme--catppuccin-frappe .is-small.modal-close,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.modal-close,html.theme--catppuccin-frappe .is-small.delete,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.delete{height:16px;max-height:16px;max-width:16px;min-height:16px;min-width:16px;width:16px}html.theme--catppuccin-frappe .is-medium.modal-close,html.theme--catppuccin-frappe .is-medium.delete{height:24px;max-height:24px;max-width:24px;min-height:24px;min-width:24px;width:24px}html.theme--catppuccin-frappe .is-large.modal-close,html.theme--catppuccin-frappe .is-large.delete{height:32px;max-height:32px;max-width:32px;min-height:32px;min-width:32px;width:32px}html.theme--catppuccin-frappe .control.is-loading::after,html.theme--catppuccin-frappe .select.is-loading::after,html.theme--catppuccin-frappe .loader,html.theme--catppuccin-frappe .button.is-loading::after{animation:spinAround 500ms infinite linear;border:2px solid #838ba7;border-radius:9999px;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:1em;position:relative;width:1em}html.theme--catppuccin-frappe .hero-video,html.theme--catppuccin-frappe .modal-background,html.theme--catppuccin-frappe .modal,html.theme--catppuccin-frappe .image.is-square img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-square img,html.theme--catppuccin-frappe .image.is-square .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-square .has-ratio,html.theme--catppuccin-frappe .image.is-1by1 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-1by1 img,html.theme--catppuccin-frappe .image.is-1by1 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-1by1 .has-ratio,html.theme--catppuccin-frappe .image.is-5by4 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-5by4 img,html.theme--catppuccin-frappe .image.is-5by4 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-5by4 .has-ratio,html.theme--catppuccin-frappe .image.is-4by3 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-4by3 img,html.theme--catppuccin-frappe .image.is-4by3 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-4by3 .has-ratio,html.theme--catppuccin-frappe .image.is-3by2 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by2 img,html.theme--catppuccin-frappe .image.is-3by2 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by2 .has-ratio,html.theme--catppuccin-frappe .image.is-5by3 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-5by3 img,html.theme--catppuccin-frappe .image.is-5by3 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-5by3 .has-ratio,html.theme--catppuccin-frappe .image.is-16by9 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-16by9 img,html.theme--catppuccin-frappe .image.is-16by9 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-16by9 .has-ratio,html.theme--catppuccin-frappe .image.is-2by1 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-2by1 img,html.theme--catppuccin-frappe .image.is-2by1 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-2by1 .has-ratio,html.theme--catppuccin-frappe .image.is-3by1 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by1 img,html.theme--catppuccin-frappe .image.is-3by1 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by1 .has-ratio,html.theme--catppuccin-frappe .image.is-4by5 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-4by5 img,html.theme--catppuccin-frappe .image.is-4by5 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-4by5 .has-ratio,html.theme--catppuccin-frappe .image.is-3by4 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by4 img,html.theme--catppuccin-frappe .image.is-3by4 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by4 .has-ratio,html.theme--catppuccin-frappe .image.is-2by3 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-2by3 img,html.theme--catppuccin-frappe .image.is-2by3 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-2by3 .has-ratio,html.theme--catppuccin-frappe .image.is-3by5 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by5 img,html.theme--catppuccin-frappe .image.is-3by5 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by5 .has-ratio,html.theme--catppuccin-frappe .image.is-9by16 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-9by16 img,html.theme--catppuccin-frappe .image.is-9by16 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-9by16 .has-ratio,html.theme--catppuccin-frappe .image.is-1by2 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-1by2 img,html.theme--catppuccin-frappe .image.is-1by2 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-1by2 .has-ratio,html.theme--catppuccin-frappe .image.is-1by3 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-1by3 img,html.theme--catppuccin-frappe .image.is-1by3 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-1by3 .has-ratio,.is-overlay{bottom:0;left:0;position:absolute;right:0;top:0}html.theme--catppuccin-frappe .navbar-burger{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;color:currentColor;font-family:inherit;font-size:1em;margin:0;padding:0}/*! minireset.css v0.0.6 | MIT License | github.com/jgthms/minireset.css */html,body,p,ol,ul,li,dl,dt,dd,blockquote,figure,fieldset,legend,textarea,pre,iframe,hr,h1,h2,h3,h4,h5,h6{margin:0;padding:0}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal}ul{list-style:none}button,input,select,textarea{margin:0}html{box-sizing:border-box}*,*::before,*::after{box-sizing:inherit}img,video{height:auto;max-width:100%}iframe{border:0}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}td:not([align]),th:not([align]){text-align:inherit}.has-text-white{color:#fff !important}a.has-text-white:hover,a.has-text-white:focus{color:#e6e6e6 !important}.has-background-white{background-color:#fff !important}.has-text-black{color:#0a0a0a !important}a.has-text-black:hover,a.has-text-black:focus{color:#000 !important}.has-background-black{background-color:#0a0a0a !important}.has-text-light{color:#f5f5f5 !important}a.has-text-light:hover,a.has-text-light:focus{color:#dbdbdb !important}.has-background-light{background-color:#f5f5f5 !important}.has-text-dark{color:#414559 !important}a.has-text-dark:hover,a.has-text-dark:focus{color:#2b2e3c !important}.has-background-dark{background-color:#414559 !important}.has-text-primary{color:#8caaee !important}a.has-text-primary:hover,a.has-text-primary:focus{color:#6089e7 !important}.has-background-primary{background-color:#8caaee !important}.has-text-primary-light{color:#edf2fc !important}a.has-text-primary-light:hover,a.has-text-primary-light:focus{color:#c1d1f6 !important}.has-background-primary-light{background-color:#edf2fc !important}.has-text-primary-dark{color:#153a8e !important}a.has-text-primary-dark:hover,a.has-text-primary-dark:focus{color:#1c4cbb !important}.has-background-primary-dark{background-color:#153a8e !important}.has-text-link{color:#8caaee !important}a.has-text-link:hover,a.has-text-link:focus{color:#6089e7 !important}.has-background-link{background-color:#8caaee !important}.has-text-link-light{color:#edf2fc !important}a.has-text-link-light:hover,a.has-text-link-light:focus{color:#c1d1f6 !important}.has-background-link-light{background-color:#edf2fc !important}.has-text-link-dark{color:#153a8e !important}a.has-text-link-dark:hover,a.has-text-link-dark:focus{color:#1c4cbb !important}.has-background-link-dark{background-color:#153a8e !important}.has-text-info{color:#81c8be !important}a.has-text-info:hover,a.has-text-info:focus{color:#5db9ac !important}.has-background-info{background-color:#81c8be !important}.has-text-info-light{color:#f1f9f8 !important}a.has-text-info-light:hover,a.has-text-info-light:focus{color:#cde9e5 !important}.has-background-info-light{background-color:#f1f9f8 !important}.has-text-info-dark{color:#2d675f !important}a.has-text-info-dark:hover,a.has-text-info-dark:focus{color:#3c8a7f !important}.has-background-info-dark{background-color:#2d675f !important}.has-text-success{color:#a6d189 !important}a.has-text-success:hover,a.has-text-success:focus{color:#8ac364 !important}.has-background-success{background-color:#a6d189 !important}.has-text-success-light{color:#f4f9f0 !important}a.has-text-success-light:hover,a.has-text-success-light:focus{color:#d8ebcc !important}.has-background-success-light{background-color:#f4f9f0 !important}.has-text-success-dark{color:#446a29 !important}a.has-text-success-dark:hover,a.has-text-success-dark:focus{color:#5b8f38 !important}.has-background-success-dark{background-color:#446a29 !important}.has-text-warning{color:#e5c890 !important}a.has-text-warning:hover,a.has-text-warning:focus{color:#dbb467 !important}.has-background-warning{background-color:#e5c890 !important}.has-text-warning-light{color:#fbf7ee !important}a.has-text-warning-light:hover,a.has-text-warning-light:focus{color:#f1e2c5 !important}.has-background-warning-light{background-color:#fbf7ee !important}.has-text-warning-dark{color:#78591c !important}a.has-text-warning-dark:hover,a.has-text-warning-dark:focus{color:#a17726 !important}.has-background-warning-dark{background-color:#78591c !important}.has-text-danger{color:#e78284 !important}a.has-text-danger:hover,a.has-text-danger:focus{color:#df575a !important}.has-background-danger{background-color:#e78284 !important}.has-text-danger-light{color:#fceeee !important}a.has-text-danger-light:hover,a.has-text-danger-light:focus{color:#f3c3c4 !important}.has-background-danger-light{background-color:#fceeee !important}.has-text-danger-dark{color:#9a1e20 !important}a.has-text-danger-dark:hover,a.has-text-danger-dark:focus{color:#c52629 !important}.has-background-danger-dark{background-color:#9a1e20 !important}.has-text-black-bis{color:#121212 !important}.has-background-black-bis{background-color:#121212 !important}.has-text-black-ter{color:#242424 !important}.has-background-black-ter{background-color:#242424 !important}.has-text-grey-darker{color:#414559 !important}.has-background-grey-darker{background-color:#414559 !important}.has-text-grey-dark{color:#51576d !important}.has-background-grey-dark{background-color:#51576d !important}.has-text-grey{color:#626880 !important}.has-background-grey{background-color:#626880 !important}.has-text-grey-light{color:#737994 !important}.has-background-grey-light{background-color:#737994 !important}.has-text-grey-lighter{color:#838ba7 !important}.has-background-grey-lighter{background-color:#838ba7 !important}.has-text-white-ter{color:#f5f5f5 !important}.has-background-white-ter{background-color:#f5f5f5 !important}.has-text-white-bis{color:#fafafa !important}.has-background-white-bis{background-color:#fafafa !important}.is-flex-direction-row{flex-direction:row !important}.is-flex-direction-row-reverse{flex-direction:row-reverse !important}.is-flex-direction-column{flex-direction:column !important}.is-flex-direction-column-reverse{flex-direction:column-reverse !important}.is-flex-wrap-nowrap{flex-wrap:nowrap !important}.is-flex-wrap-wrap{flex-wrap:wrap !important}.is-flex-wrap-wrap-reverse{flex-wrap:wrap-reverse !important}.is-justify-content-flex-start{justify-content:flex-start !important}.is-justify-content-flex-end{justify-content:flex-end !important}.is-justify-content-center{justify-content:center !important}.is-justify-content-space-between{justify-content:space-between !important}.is-justify-content-space-around{justify-content:space-around !important}.is-justify-content-space-evenly{justify-content:space-evenly !important}.is-justify-content-start{justify-content:start !important}.is-justify-content-end{justify-content:end !important}.is-justify-content-left{justify-content:left !important}.is-justify-content-right{justify-content:right !important}.is-align-content-flex-start{align-content:flex-start !important}.is-align-content-flex-end{align-content:flex-end !important}.is-align-content-center{align-content:center !important}.is-align-content-space-between{align-content:space-between !important}.is-align-content-space-around{align-content:space-around !important}.is-align-content-space-evenly{align-content:space-evenly !important}.is-align-content-stretch{align-content:stretch !important}.is-align-content-start{align-content:start !important}.is-align-content-end{align-content:end !important}.is-align-content-baseline{align-content:baseline !important}.is-align-items-stretch{align-items:stretch !important}.is-align-items-flex-start{align-items:flex-start !important}.is-align-items-flex-end{align-items:flex-end !important}.is-align-items-center{align-items:center !important}.is-align-items-baseline{align-items:baseline !important}.is-align-items-start{align-items:start !important}.is-align-items-end{align-items:end !important}.is-align-items-self-start{align-items:self-start !important}.is-align-items-self-end{align-items:self-end !important}.is-align-self-auto{align-self:auto !important}.is-align-self-flex-start{align-self:flex-start !important}.is-align-self-flex-end{align-self:flex-end !important}.is-align-self-center{align-self:center !important}.is-align-self-baseline{align-self:baseline !important}.is-align-self-stretch{align-self:stretch !important}.is-flex-grow-0{flex-grow:0 !important}.is-flex-grow-1{flex-grow:1 !important}.is-flex-grow-2{flex-grow:2 !important}.is-flex-grow-3{flex-grow:3 !important}.is-flex-grow-4{flex-grow:4 !important}.is-flex-grow-5{flex-grow:5 !important}.is-flex-shrink-0{flex-shrink:0 !important}.is-flex-shrink-1{flex-shrink:1 !important}.is-flex-shrink-2{flex-shrink:2 !important}.is-flex-shrink-3{flex-shrink:3 !important}.is-flex-shrink-4{flex-shrink:4 !important}.is-flex-shrink-5{flex-shrink:5 !important}.is-clearfix::after{clear:both;content:" ";display:table}.is-pulled-left{float:left !important}.is-pulled-right{float:right !important}.is-radiusless{border-radius:0 !important}.is-shadowless{box-shadow:none !important}.is-clickable{cursor:pointer !important;pointer-events:all !important}.is-clipped{overflow:hidden !important}.is-relative{position:relative !important}.is-marginless{margin:0 !important}.is-paddingless{padding:0 !important}.m-0{margin:0 !important}.mt-0{margin-top:0 !important}.mr-0{margin-right:0 !important}.mb-0{margin-bottom:0 !important}.ml-0{margin-left:0 !important}.mx-0{margin-left:0 !important;margin-right:0 !important}.my-0{margin-top:0 !important;margin-bottom:0 !important}.m-1{margin:.25rem !important}.mt-1{margin-top:.25rem !important}.mr-1{margin-right:.25rem !important}.mb-1{margin-bottom:.25rem !important}.ml-1{margin-left:.25rem !important}.mx-1{margin-left:.25rem !important;margin-right:.25rem !important}.my-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.m-2{margin:.5rem !important}.mt-2{margin-top:.5rem !important}.mr-2{margin-right:.5rem !important}.mb-2{margin-bottom:.5rem !important}.ml-2{margin-left:.5rem !important}.mx-2{margin-left:.5rem !important;margin-right:.5rem !important}.my-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.m-3{margin:.75rem !important}.mt-3{margin-top:.75rem !important}.mr-3{margin-right:.75rem !important}.mb-3{margin-bottom:.75rem !important}.ml-3{margin-left:.75rem !important}.mx-3{margin-left:.75rem !important;margin-right:.75rem !important}.my-3{margin-top:.75rem !important;margin-bottom:.75rem !important}.m-4{margin:1rem !important}.mt-4{margin-top:1rem !important}.mr-4{margin-right:1rem !important}.mb-4{margin-bottom:1rem !important}.ml-4{margin-left:1rem !important}.mx-4{margin-left:1rem !important;margin-right:1rem !important}.my-4{margin-top:1rem !important;margin-bottom:1rem !important}.m-5{margin:1.5rem !important}.mt-5{margin-top:1.5rem !important}.mr-5{margin-right:1.5rem !important}.mb-5{margin-bottom:1.5rem !important}.ml-5{margin-left:1.5rem !important}.mx-5{margin-left:1.5rem !important;margin-right:1.5rem !important}.my-5{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.m-6{margin:3rem !important}.mt-6{margin-top:3rem !important}.mr-6{margin-right:3rem !important}.mb-6{margin-bottom:3rem !important}.ml-6{margin-left:3rem !important}.mx-6{margin-left:3rem !important;margin-right:3rem !important}.my-6{margin-top:3rem !important;margin-bottom:3rem !important}.m-auto{margin:auto !important}.mt-auto{margin-top:auto !important}.mr-auto{margin-right:auto !important}.mb-auto{margin-bottom:auto !important}.ml-auto{margin-left:auto !important}.mx-auto{margin-left:auto !important;margin-right:auto !important}.my-auto{margin-top:auto !important;margin-bottom:auto !important}.p-0{padding:0 !important}.pt-0{padding-top:0 !important}.pr-0{padding-right:0 !important}.pb-0{padding-bottom:0 !important}.pl-0{padding-left:0 !important}.px-0{padding-left:0 !important;padding-right:0 !important}.py-0{padding-top:0 !important;padding-bottom:0 !important}.p-1{padding:.25rem !important}.pt-1{padding-top:.25rem !important}.pr-1{padding-right:.25rem !important}.pb-1{padding-bottom:.25rem !important}.pl-1{padding-left:.25rem !important}.px-1{padding-left:.25rem !important;padding-right:.25rem !important}.py-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.p-2{padding:.5rem !important}.pt-2{padding-top:.5rem !important}.pr-2{padding-right:.5rem !important}.pb-2{padding-bottom:.5rem !important}.pl-2{padding-left:.5rem !important}.px-2{padding-left:.5rem !important;padding-right:.5rem !important}.py-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.p-3{padding:.75rem !important}.pt-3{padding-top:.75rem !important}.pr-3{padding-right:.75rem !important}.pb-3{padding-bottom:.75rem !important}.pl-3{padding-left:.75rem !important}.px-3{padding-left:.75rem !important;padding-right:.75rem !important}.py-3{padding-top:.75rem !important;padding-bottom:.75rem !important}.p-4{padding:1rem !important}.pt-4{padding-top:1rem !important}.pr-4{padding-right:1rem !important}.pb-4{padding-bottom:1rem !important}.pl-4{padding-left:1rem !important}.px-4{padding-left:1rem !important;padding-right:1rem !important}.py-4{padding-top:1rem !important;padding-bottom:1rem !important}.p-5{padding:1.5rem !important}.pt-5{padding-top:1.5rem !important}.pr-5{padding-right:1.5rem !important}.pb-5{padding-bottom:1.5rem !important}.pl-5{padding-left:1.5rem !important}.px-5{padding-left:1.5rem !important;padding-right:1.5rem !important}.py-5{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.p-6{padding:3rem !important}.pt-6{padding-top:3rem !important}.pr-6{padding-right:3rem !important}.pb-6{padding-bottom:3rem !important}.pl-6{padding-left:3rem !important}.px-6{padding-left:3rem !important;padding-right:3rem !important}.py-6{padding-top:3rem !important;padding-bottom:3rem !important}.p-auto{padding:auto !important}.pt-auto{padding-top:auto !important}.pr-auto{padding-right:auto !important}.pb-auto{padding-bottom:auto !important}.pl-auto{padding-left:auto !important}.px-auto{padding-left:auto !important;padding-right:auto !important}.py-auto{padding-top:auto !important;padding-bottom:auto !important}.is-size-1{font-size:3rem !important}.is-size-2{font-size:2.5rem !important}.is-size-3{font-size:2rem !important}.is-size-4{font-size:1.5rem !important}.is-size-5{font-size:1.25rem !important}.is-size-6{font-size:1rem !important}.is-size-7,html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink{font-size:.75rem !important}@media screen and (max-width: 768px){.is-size-1-mobile{font-size:3rem !important}.is-size-2-mobile{font-size:2.5rem !important}.is-size-3-mobile{font-size:2rem !important}.is-size-4-mobile{font-size:1.5rem !important}.is-size-5-mobile{font-size:1.25rem !important}.is-size-6-mobile{font-size:1rem !important}.is-size-7-mobile{font-size:.75rem !important}}@media screen and (min-width: 769px),print{.is-size-1-tablet{font-size:3rem !important}.is-size-2-tablet{font-size:2.5rem !important}.is-size-3-tablet{font-size:2rem !important}.is-size-4-tablet{font-size:1.5rem !important}.is-size-5-tablet{font-size:1.25rem !important}.is-size-6-tablet{font-size:1rem !important}.is-size-7-tablet{font-size:.75rem !important}}@media screen and (max-width: 1055px){.is-size-1-touch{font-size:3rem !important}.is-size-2-touch{font-size:2.5rem !important}.is-size-3-touch{font-size:2rem !important}.is-size-4-touch{font-size:1.5rem !important}.is-size-5-touch{font-size:1.25rem !important}.is-size-6-touch{font-size:1rem !important}.is-size-7-touch{font-size:.75rem !important}}@media screen and (min-width: 1056px){.is-size-1-desktop{font-size:3rem !important}.is-size-2-desktop{font-size:2.5rem !important}.is-size-3-desktop{font-size:2rem !important}.is-size-4-desktop{font-size:1.5rem !important}.is-size-5-desktop{font-size:1.25rem !important}.is-size-6-desktop{font-size:1rem !important}.is-size-7-desktop{font-size:.75rem !important}}@media screen and (min-width: 1216px){.is-size-1-widescreen{font-size:3rem !important}.is-size-2-widescreen{font-size:2.5rem !important}.is-size-3-widescreen{font-size:2rem !important}.is-size-4-widescreen{font-size:1.5rem !important}.is-size-5-widescreen{font-size:1.25rem !important}.is-size-6-widescreen{font-size:1rem !important}.is-size-7-widescreen{font-size:.75rem !important}}@media screen and (min-width: 1408px){.is-size-1-fullhd{font-size:3rem !important}.is-size-2-fullhd{font-size:2.5rem !important}.is-size-3-fullhd{font-size:2rem !important}.is-size-4-fullhd{font-size:1.5rem !important}.is-size-5-fullhd{font-size:1.25rem !important}.is-size-6-fullhd{font-size:1rem !important}.is-size-7-fullhd{font-size:.75rem !important}}.has-text-centered{text-align:center !important}.has-text-justified{text-align:justify !important}.has-text-left{text-align:left !important}.has-text-right{text-align:right !important}@media screen and (max-width: 768px){.has-text-centered-mobile{text-align:center !important}}@media screen and (min-width: 769px),print{.has-text-centered-tablet{text-align:center !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-centered-tablet-only{text-align:center !important}}@media screen and (max-width: 1055px){.has-text-centered-touch{text-align:center !important}}@media screen and (min-width: 1056px){.has-text-centered-desktop{text-align:center !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-centered-desktop-only{text-align:center !important}}@media screen and (min-width: 1216px){.has-text-centered-widescreen{text-align:center !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-centered-widescreen-only{text-align:center !important}}@media screen and (min-width: 1408px){.has-text-centered-fullhd{text-align:center !important}}@media screen and (max-width: 768px){.has-text-justified-mobile{text-align:justify !important}}@media screen and (min-width: 769px),print{.has-text-justified-tablet{text-align:justify !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-justified-tablet-only{text-align:justify !important}}@media screen and (max-width: 1055px){.has-text-justified-touch{text-align:justify !important}}@media screen and (min-width: 1056px){.has-text-justified-desktop{text-align:justify !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-justified-desktop-only{text-align:justify !important}}@media screen and (min-width: 1216px){.has-text-justified-widescreen{text-align:justify !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-justified-widescreen-only{text-align:justify !important}}@media screen and (min-width: 1408px){.has-text-justified-fullhd{text-align:justify !important}}@media screen and (max-width: 768px){.has-text-left-mobile{text-align:left !important}}@media screen and (min-width: 769px),print{.has-text-left-tablet{text-align:left !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-left-tablet-only{text-align:left !important}}@media screen and (max-width: 1055px){.has-text-left-touch{text-align:left !important}}@media screen and (min-width: 1056px){.has-text-left-desktop{text-align:left !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-left-desktop-only{text-align:left !important}}@media screen and (min-width: 1216px){.has-text-left-widescreen{text-align:left !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-left-widescreen-only{text-align:left !important}}@media screen and (min-width: 1408px){.has-text-left-fullhd{text-align:left !important}}@media screen and (max-width: 768px){.has-text-right-mobile{text-align:right !important}}@media screen and (min-width: 769px),print{.has-text-right-tablet{text-align:right !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-right-tablet-only{text-align:right !important}}@media screen and (max-width: 1055px){.has-text-right-touch{text-align:right !important}}@media screen and (min-width: 1056px){.has-text-right-desktop{text-align:right !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-right-desktop-only{text-align:right !important}}@media screen and (min-width: 1216px){.has-text-right-widescreen{text-align:right !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-right-widescreen-only{text-align:right !important}}@media screen and (min-width: 1408px){.has-text-right-fullhd{text-align:right !important}}.is-capitalized{text-transform:capitalize !important}.is-lowercase{text-transform:lowercase !important}.is-uppercase{text-transform:uppercase !important}.is-italic{font-style:italic !important}.is-underlined{text-decoration:underline !important}.has-text-weight-light{font-weight:300 !important}.has-text-weight-normal{font-weight:400 !important}.has-text-weight-medium{font-weight:500 !important}.has-text-weight-semibold{font-weight:600 !important}.has-text-weight-bold{font-weight:700 !important}.is-family-primary{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-secondary{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-sans-serif{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-monospace{font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace !important}.is-family-code{font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace !important}.is-block{display:block !important}@media screen and (max-width: 768px){.is-block-mobile{display:block !important}}@media screen and (min-width: 769px),print{.is-block-tablet{display:block !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-block-tablet-only{display:block !important}}@media screen and (max-width: 1055px){.is-block-touch{display:block !important}}@media screen and (min-width: 1056px){.is-block-desktop{display:block !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-block-desktop-only{display:block !important}}@media screen and (min-width: 1216px){.is-block-widescreen{display:block !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-block-widescreen-only{display:block !important}}@media screen and (min-width: 1408px){.is-block-fullhd{display:block !important}}.is-flex{display:flex !important}@media screen and (max-width: 768px){.is-flex-mobile{display:flex !important}}@media screen and (min-width: 769px),print{.is-flex-tablet{display:flex !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-flex-tablet-only{display:flex !important}}@media screen and (max-width: 1055px){.is-flex-touch{display:flex !important}}@media screen and (min-width: 1056px){.is-flex-desktop{display:flex !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-flex-desktop-only{display:flex !important}}@media screen and (min-width: 1216px){.is-flex-widescreen{display:flex !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-flex-widescreen-only{display:flex !important}}@media screen and (min-width: 1408px){.is-flex-fullhd{display:flex !important}}.is-inline{display:inline !important}@media screen and (max-width: 768px){.is-inline-mobile{display:inline !important}}@media screen and (min-width: 769px),print{.is-inline-tablet{display:inline !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-tablet-only{display:inline !important}}@media screen and (max-width: 1055px){.is-inline-touch{display:inline !important}}@media screen and (min-width: 1056px){.is-inline-desktop{display:inline !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-desktop-only{display:inline !important}}@media screen and (min-width: 1216px){.is-inline-widescreen{display:inline !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-widescreen-only{display:inline !important}}@media screen and (min-width: 1408px){.is-inline-fullhd{display:inline !important}}.is-inline-block{display:inline-block !important}@media screen and (max-width: 768px){.is-inline-block-mobile{display:inline-block !important}}@media screen and (min-width: 769px),print{.is-inline-block-tablet{display:inline-block !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-block-tablet-only{display:inline-block !important}}@media screen and (max-width: 1055px){.is-inline-block-touch{display:inline-block !important}}@media screen and (min-width: 1056px){.is-inline-block-desktop{display:inline-block !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-block-desktop-only{display:inline-block !important}}@media screen and (min-width: 1216px){.is-inline-block-widescreen{display:inline-block !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-block-widescreen-only{display:inline-block !important}}@media screen and (min-width: 1408px){.is-inline-block-fullhd{display:inline-block !important}}.is-inline-flex{display:inline-flex !important}@media screen and (max-width: 768px){.is-inline-flex-mobile{display:inline-flex !important}}@media screen and (min-width: 769px),print{.is-inline-flex-tablet{display:inline-flex !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-flex-tablet-only{display:inline-flex !important}}@media screen and (max-width: 1055px){.is-inline-flex-touch{display:inline-flex !important}}@media screen and (min-width: 1056px){.is-inline-flex-desktop{display:inline-flex !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-flex-desktop-only{display:inline-flex !important}}@media screen and (min-width: 1216px){.is-inline-flex-widescreen{display:inline-flex !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-flex-widescreen-only{display:inline-flex !important}}@media screen and (min-width: 1408px){.is-inline-flex-fullhd{display:inline-flex !important}}.is-hidden{display:none !important}.is-sr-only{border:none !important;clip:rect(0, 0, 0, 0) !important;height:0.01em !important;overflow:hidden !important;padding:0 !important;position:absolute !important;white-space:nowrap !important;width:0.01em !important}@media screen and (max-width: 768px){.is-hidden-mobile{display:none !important}}@media screen and (min-width: 769px),print{.is-hidden-tablet{display:none !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-hidden-tablet-only{display:none !important}}@media screen and (max-width: 1055px){.is-hidden-touch{display:none !important}}@media screen and (min-width: 1056px){.is-hidden-desktop{display:none !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-hidden-desktop-only{display:none !important}}@media screen and (min-width: 1216px){.is-hidden-widescreen{display:none !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-hidden-widescreen-only{display:none !important}}@media screen and (min-width: 1408px){.is-hidden-fullhd{display:none !important}}.is-invisible{visibility:hidden !important}@media screen and (max-width: 768px){.is-invisible-mobile{visibility:hidden !important}}@media screen and (min-width: 769px),print{.is-invisible-tablet{visibility:hidden !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-invisible-tablet-only{visibility:hidden !important}}@media screen and (max-width: 1055px){.is-invisible-touch{visibility:hidden !important}}@media screen and (min-width: 1056px){.is-invisible-desktop{visibility:hidden !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-invisible-desktop-only{visibility:hidden !important}}@media screen and (min-width: 1216px){.is-invisible-widescreen{visibility:hidden !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-invisible-widescreen-only{visibility:hidden !important}}@media screen and (min-width: 1408px){.is-invisible-fullhd{visibility:hidden !important}}html.theme--catppuccin-frappe html{background-color:#303446;font-size:16px;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;min-width:300px;overflow-x:auto;overflow-y:scroll;text-rendering:optimizeLegibility;text-size-adjust:100%}html.theme--catppuccin-frappe article,html.theme--catppuccin-frappe aside,html.theme--catppuccin-frappe figure,html.theme--catppuccin-frappe footer,html.theme--catppuccin-frappe header,html.theme--catppuccin-frappe hgroup,html.theme--catppuccin-frappe section{display:block}html.theme--catppuccin-frappe body,html.theme--catppuccin-frappe button,html.theme--catppuccin-frappe input,html.theme--catppuccin-frappe optgroup,html.theme--catppuccin-frappe select,html.theme--catppuccin-frappe textarea{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif}html.theme--catppuccin-frappe code,html.theme--catppuccin-frappe pre{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto;font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace}html.theme--catppuccin-frappe body{color:#c6d0f5;font-size:1em;font-weight:400;line-height:1.5}html.theme--catppuccin-frappe a{color:#8caaee;cursor:pointer;text-decoration:none}html.theme--catppuccin-frappe a strong{color:currentColor}html.theme--catppuccin-frappe a:hover{color:#99d1db}html.theme--catppuccin-frappe code{background-color:#292c3c;color:#c6d0f5;font-size:.875em;font-weight:normal;padding:.1em}html.theme--catppuccin-frappe hr{background-color:#292c3c;border:none;display:block;height:2px;margin:1.5rem 0}html.theme--catppuccin-frappe img{height:auto;max-width:100%}html.theme--catppuccin-frappe input[type="checkbox"],html.theme--catppuccin-frappe input[type="radio"]{vertical-align:baseline}html.theme--catppuccin-frappe small{font-size:.875em}html.theme--catppuccin-frappe span{font-style:inherit;font-weight:inherit}html.theme--catppuccin-frappe strong{color:#b0bef1;font-weight:700}html.theme--catppuccin-frappe fieldset{border:none}html.theme--catppuccin-frappe pre{-webkit-overflow-scrolling:touch;background-color:#292c3c;color:#c6d0f5;font-size:.875em;overflow-x:auto;padding:1.25rem 1.5rem;white-space:pre;word-wrap:normal}html.theme--catppuccin-frappe pre code{background-color:transparent;color:currentColor;font-size:1em;padding:0}html.theme--catppuccin-frappe table td,html.theme--catppuccin-frappe table th{vertical-align:top}html.theme--catppuccin-frappe table td:not([align]),html.theme--catppuccin-frappe table th:not([align]){text-align:inherit}html.theme--catppuccin-frappe table th{color:#b0bef1}html.theme--catppuccin-frappe .box{background-color:#51576d;border-radius:8px;box-shadow:none;color:#c6d0f5;display:block;padding:1.25rem}html.theme--catppuccin-frappe a.box:hover,html.theme--catppuccin-frappe a.box:focus{box-shadow:0 0.5em 1em -0.125em rgba(10,10,10,0.1),0 0 0 1px #8caaee}html.theme--catppuccin-frappe a.box:active{box-shadow:inset 0 1px 2px rgba(10,10,10,0.2),0 0 0 1px #8caaee}html.theme--catppuccin-frappe .button{background-color:#292c3c;border-color:#484d69;border-width:1px;color:#8caaee;cursor:pointer;justify-content:center;padding-bottom:calc(0.5em - 1px);padding-left:1em;padding-right:1em;padding-top:calc(0.5em - 1px);text-align:center;white-space:nowrap}html.theme--catppuccin-frappe .button strong{color:inherit}html.theme--catppuccin-frappe .button .icon,html.theme--catppuccin-frappe .button .icon.is-small,html.theme--catppuccin-frappe .button #documenter .docs-sidebar form.docs-search>input.icon,html.theme--catppuccin-frappe #documenter .docs-sidebar .button form.docs-search>input.icon,html.theme--catppuccin-frappe .button .icon.is-medium,html.theme--catppuccin-frappe .button .icon.is-large{height:1.5em;width:1.5em}html.theme--catppuccin-frappe .button .icon:first-child:not(:last-child){margin-left:calc(-0.5em - 1px);margin-right:.25em}html.theme--catppuccin-frappe .button .icon:last-child:not(:first-child){margin-left:.25em;margin-right:calc(-0.5em - 1px)}html.theme--catppuccin-frappe .button .icon:first-child:last-child{margin-left:calc(-0.5em - 1px);margin-right:calc(-0.5em - 1px)}html.theme--catppuccin-frappe .button:hover,html.theme--catppuccin-frappe .button.is-hovered{border-color:#737994;color:#b0bef1}html.theme--catppuccin-frappe .button:focus,html.theme--catppuccin-frappe .button.is-focused{border-color:#737994;color:#769aeb}html.theme--catppuccin-frappe .button:focus:not(:active),html.theme--catppuccin-frappe .button.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(140,170,238,0.25)}html.theme--catppuccin-frappe .button:active,html.theme--catppuccin-frappe .button.is-active{border-color:#51576d;color:#b0bef1}html.theme--catppuccin-frappe .button.is-text{background-color:transparent;border-color:transparent;color:#c6d0f5;text-decoration:underline}html.theme--catppuccin-frappe .button.is-text:hover,html.theme--catppuccin-frappe .button.is-text.is-hovered,html.theme--catppuccin-frappe .button.is-text:focus,html.theme--catppuccin-frappe .button.is-text.is-focused{background-color:#292c3c;color:#b0bef1}html.theme--catppuccin-frappe .button.is-text:active,html.theme--catppuccin-frappe .button.is-text.is-active{background-color:#1f212d;color:#b0bef1}html.theme--catppuccin-frappe .button.is-text[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-text{background-color:transparent;border-color:transparent;box-shadow:none}html.theme--catppuccin-frappe .button.is-ghost{background:none;border-color:rgba(0,0,0,0);color:#8caaee;text-decoration:none}html.theme--catppuccin-frappe .button.is-ghost:hover,html.theme--catppuccin-frappe .button.is-ghost.is-hovered{color:#8caaee;text-decoration:underline}html.theme--catppuccin-frappe .button.is-white{background-color:#fff;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-frappe .button.is-white:hover,html.theme--catppuccin-frappe .button.is-white.is-hovered{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-frappe .button.is-white:focus,html.theme--catppuccin-frappe .button.is-white.is-focused{border-color:transparent;color:#0a0a0a}html.theme--catppuccin-frappe .button.is-white:focus:not(:active),html.theme--catppuccin-frappe .button.is-white.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}html.theme--catppuccin-frappe .button.is-white:active,html.theme--catppuccin-frappe .button.is-white.is-active{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-frappe .button.is-white[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-white{background-color:#fff;border-color:#fff;box-shadow:none}html.theme--catppuccin-frappe .button.is-white.is-inverted{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-frappe .button.is-white.is-inverted:hover,html.theme--catppuccin-frappe .button.is-white.is-inverted.is-hovered{background-color:#000}html.theme--catppuccin-frappe .button.is-white.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-white.is-inverted{background-color:#0a0a0a;border-color:transparent;box-shadow:none;color:#fff}html.theme--catppuccin-frappe .button.is-white.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--catppuccin-frappe .button.is-white.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-frappe .button.is-white.is-outlined:hover,html.theme--catppuccin-frappe .button.is-white.is-outlined.is-hovered,html.theme--catppuccin-frappe .button.is-white.is-outlined:focus,html.theme--catppuccin-frappe .button.is-white.is-outlined.is-focused{background-color:#fff;border-color:#fff;color:#0a0a0a}html.theme--catppuccin-frappe .button.is-white.is-outlined.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-frappe .button.is-white.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .button.is-white.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .button.is-white.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .button.is-white.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--catppuccin-frappe .button.is-white.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-white.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-frappe .button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}html.theme--catppuccin-frappe .button.is-white.is-inverted.is-outlined:hover,html.theme--catppuccin-frappe .button.is-white.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-frappe .button.is-white.is-inverted.is-outlined:focus,html.theme--catppuccin-frappe .button.is-white.is-inverted.is-outlined.is-focused{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-frappe .button.is-white.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .button.is-white.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .button.is-white.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .button.is-white.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-frappe .button.is-white.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}html.theme--catppuccin-frappe .button.is-black{background-color:#0a0a0a;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-black:hover,html.theme--catppuccin-frappe .button.is-black.is-hovered{background-color:#040404;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-black:focus,html.theme--catppuccin-frappe .button.is-black.is-focused{border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-black:focus:not(:active),html.theme--catppuccin-frappe .button.is-black.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}html.theme--catppuccin-frappe .button.is-black:active,html.theme--catppuccin-frappe .button.is-black.is-active{background-color:#000;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-black[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-black{background-color:#0a0a0a;border-color:#0a0a0a;box-shadow:none}html.theme--catppuccin-frappe .button.is-black.is-inverted{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-frappe .button.is-black.is-inverted:hover,html.theme--catppuccin-frappe .button.is-black.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--catppuccin-frappe .button.is-black.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-black.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#0a0a0a}html.theme--catppuccin-frappe .button.is-black.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-frappe .button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}html.theme--catppuccin-frappe .button.is-black.is-outlined:hover,html.theme--catppuccin-frappe .button.is-black.is-outlined.is-hovered,html.theme--catppuccin-frappe .button.is-black.is-outlined:focus,html.theme--catppuccin-frappe .button.is-black.is-outlined.is-focused{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}html.theme--catppuccin-frappe .button.is-black.is-outlined.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--catppuccin-frappe .button.is-black.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .button.is-black.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .button.is-black.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .button.is-black.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-frappe .button.is-black.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}html.theme--catppuccin-frappe .button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-frappe .button.is-black.is-inverted.is-outlined:hover,html.theme--catppuccin-frappe .button.is-black.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-frappe .button.is-black.is-inverted.is-outlined:focus,html.theme--catppuccin-frappe .button.is-black.is-inverted.is-outlined.is-focused{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-frappe .button.is-black.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .button.is-black.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .button.is-black.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .button.is-black.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--catppuccin-frappe .button.is-black.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-frappe .button.is-light{background-color:#f5f5f5;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-light:hover,html.theme--catppuccin-frappe .button.is-light.is-hovered{background-color:#eee;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-light:focus,html.theme--catppuccin-frappe .button.is-light.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-light:focus:not(:active),html.theme--catppuccin-frappe .button.is-light.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(245,245,245,0.25)}html.theme--catppuccin-frappe .button.is-light:active,html.theme--catppuccin-frappe .button.is-light.is-active{background-color:#e8e8e8;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-light[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-light{background-color:#f5f5f5;border-color:#f5f5f5;box-shadow:none}html.theme--catppuccin-frappe .button.is-light.is-inverted{background-color:rgba(0,0,0,0.7);color:#f5f5f5}html.theme--catppuccin-frappe .button.is-light.is-inverted:hover,html.theme--catppuccin-frappe .button.is-light.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-light.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-light.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#f5f5f5}html.theme--catppuccin-frappe .button.is-light.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-frappe .button.is-light.is-outlined{background-color:transparent;border-color:#f5f5f5;color:#f5f5f5}html.theme--catppuccin-frappe .button.is-light.is-outlined:hover,html.theme--catppuccin-frappe .button.is-light.is-outlined.is-hovered,html.theme--catppuccin-frappe .button.is-light.is-outlined:focus,html.theme--catppuccin-frappe .button.is-light.is-outlined.is-focused{background-color:#f5f5f5;border-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-light.is-outlined.is-loading::after{border-color:transparent transparent #f5f5f5 #f5f5f5 !important}html.theme--catppuccin-frappe .button.is-light.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .button.is-light.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .button.is-light.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .button.is-light.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-frappe .button.is-light.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-light.is-outlined{background-color:transparent;border-color:#f5f5f5;box-shadow:none;color:#f5f5f5}html.theme--catppuccin-frappe .button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-light.is-inverted.is-outlined:hover,html.theme--catppuccin-frappe .button.is-light.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-frappe .button.is-light.is-inverted.is-outlined:focus,html.theme--catppuccin-frappe .button.is-light.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#f5f5f5}html.theme--catppuccin-frappe .button.is-light.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .button.is-light.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .button.is-light.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .button.is-light.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #f5f5f5 #f5f5f5 !important}html.theme--catppuccin-frappe .button.is-light.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-dark,html.theme--catppuccin-frappe .content kbd.button{background-color:#414559;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-dark:hover,html.theme--catppuccin-frappe .content kbd.button:hover,html.theme--catppuccin-frappe .button.is-dark.is-hovered,html.theme--catppuccin-frappe .content kbd.button.is-hovered{background-color:#3c3f52;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-dark:focus,html.theme--catppuccin-frappe .content kbd.button:focus,html.theme--catppuccin-frappe .button.is-dark.is-focused,html.theme--catppuccin-frappe .content kbd.button.is-focused{border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-dark:focus:not(:active),html.theme--catppuccin-frappe .content kbd.button:focus:not(:active),html.theme--catppuccin-frappe .button.is-dark.is-focused:not(:active),html.theme--catppuccin-frappe .content kbd.button.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(65,69,89,0.25)}html.theme--catppuccin-frappe .button.is-dark:active,html.theme--catppuccin-frappe .content kbd.button:active,html.theme--catppuccin-frappe .button.is-dark.is-active,html.theme--catppuccin-frappe .content kbd.button.is-active{background-color:#363a4a;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-dark[disabled],html.theme--catppuccin-frappe .content kbd.button[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-dark,fieldset[disabled] html.theme--catppuccin-frappe .content kbd.button{background-color:#414559;border-color:#414559;box-shadow:none}html.theme--catppuccin-frappe .button.is-dark.is-inverted,html.theme--catppuccin-frappe .content kbd.button.is-inverted{background-color:#fff;color:#414559}html.theme--catppuccin-frappe .button.is-dark.is-inverted:hover,html.theme--catppuccin-frappe .content kbd.button.is-inverted:hover,html.theme--catppuccin-frappe .button.is-dark.is-inverted.is-hovered,html.theme--catppuccin-frappe .content kbd.button.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--catppuccin-frappe .button.is-dark.is-inverted[disabled],html.theme--catppuccin-frappe .content kbd.button.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-dark.is-inverted,fieldset[disabled] html.theme--catppuccin-frappe .content kbd.button.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#414559}html.theme--catppuccin-frappe .button.is-dark.is-loading::after,html.theme--catppuccin-frappe .content kbd.button.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-frappe .button.is-dark.is-outlined,html.theme--catppuccin-frappe .content kbd.button.is-outlined{background-color:transparent;border-color:#414559;color:#414559}html.theme--catppuccin-frappe .button.is-dark.is-outlined:hover,html.theme--catppuccin-frappe .content kbd.button.is-outlined:hover,html.theme--catppuccin-frappe .button.is-dark.is-outlined.is-hovered,html.theme--catppuccin-frappe .content kbd.button.is-outlined.is-hovered,html.theme--catppuccin-frappe .button.is-dark.is-outlined:focus,html.theme--catppuccin-frappe .content kbd.button.is-outlined:focus,html.theme--catppuccin-frappe .button.is-dark.is-outlined.is-focused,html.theme--catppuccin-frappe .content kbd.button.is-outlined.is-focused{background-color:#414559;border-color:#414559;color:#fff}html.theme--catppuccin-frappe .button.is-dark.is-outlined.is-loading::after,html.theme--catppuccin-frappe .content kbd.button.is-outlined.is-loading::after{border-color:transparent transparent #414559 #414559 !important}html.theme--catppuccin-frappe .button.is-dark.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .content kbd.button.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .button.is-dark.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .content kbd.button.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .button.is-dark.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .content kbd.button.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .button.is-dark.is-outlined.is-loading.is-focused::after,html.theme--catppuccin-frappe .content kbd.button.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-frappe .button.is-dark.is-outlined[disabled],html.theme--catppuccin-frappe .content kbd.button.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-dark.is-outlined,fieldset[disabled] html.theme--catppuccin-frappe .content kbd.button.is-outlined{background-color:transparent;border-color:#414559;box-shadow:none;color:#414559}html.theme--catppuccin-frappe .button.is-dark.is-inverted.is-outlined,html.theme--catppuccin-frappe .content kbd.button.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-frappe .button.is-dark.is-inverted.is-outlined:hover,html.theme--catppuccin-frappe .content kbd.button.is-inverted.is-outlined:hover,html.theme--catppuccin-frappe .button.is-dark.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-frappe .content kbd.button.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-frappe .button.is-dark.is-inverted.is-outlined:focus,html.theme--catppuccin-frappe .content kbd.button.is-inverted.is-outlined:focus,html.theme--catppuccin-frappe .button.is-dark.is-inverted.is-outlined.is-focused,html.theme--catppuccin-frappe .content kbd.button.is-inverted.is-outlined.is-focused{background-color:#fff;color:#414559}html.theme--catppuccin-frappe .button.is-dark.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .content kbd.button.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .button.is-dark.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .content kbd.button.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .button.is-dark.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .content kbd.button.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .button.is-dark.is-inverted.is-outlined.is-loading.is-focused::after,html.theme--catppuccin-frappe .content kbd.button.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #414559 #414559 !important}html.theme--catppuccin-frappe .button.is-dark.is-inverted.is-outlined[disabled],html.theme--catppuccin-frappe .content kbd.button.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-dark.is-inverted.is-outlined,fieldset[disabled] html.theme--catppuccin-frappe .content kbd.button.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-frappe .button.is-primary,html.theme--catppuccin-frappe .docstring>section>a.button.docs-sourcelink{background-color:#8caaee;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-primary:hover,html.theme--catppuccin-frappe .docstring>section>a.button.docs-sourcelink:hover,html.theme--catppuccin-frappe .button.is-primary.is-hovered,html.theme--catppuccin-frappe .docstring>section>a.button.is-hovered.docs-sourcelink{background-color:#81a2ec;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-primary:focus,html.theme--catppuccin-frappe .docstring>section>a.button.docs-sourcelink:focus,html.theme--catppuccin-frappe .button.is-primary.is-focused,html.theme--catppuccin-frappe .docstring>section>a.button.is-focused.docs-sourcelink{border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-primary:focus:not(:active),html.theme--catppuccin-frappe .docstring>section>a.button.docs-sourcelink:focus:not(:active),html.theme--catppuccin-frappe .button.is-primary.is-focused:not(:active),html.theme--catppuccin-frappe .docstring>section>a.button.is-focused.docs-sourcelink:not(:active){box-shadow:0 0 0 0.125em rgba(140,170,238,0.25)}html.theme--catppuccin-frappe .button.is-primary:active,html.theme--catppuccin-frappe .docstring>section>a.button.docs-sourcelink:active,html.theme--catppuccin-frappe .button.is-primary.is-active,html.theme--catppuccin-frappe .docstring>section>a.button.is-active.docs-sourcelink{background-color:#769aeb;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-primary[disabled],html.theme--catppuccin-frappe .docstring>section>a.button.docs-sourcelink[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-primary,fieldset[disabled] html.theme--catppuccin-frappe .docstring>section>a.button.docs-sourcelink{background-color:#8caaee;border-color:#8caaee;box-shadow:none}html.theme--catppuccin-frappe .button.is-primary.is-inverted,html.theme--catppuccin-frappe .docstring>section>a.button.is-inverted.docs-sourcelink{background-color:#fff;color:#8caaee}html.theme--catppuccin-frappe .button.is-primary.is-inverted:hover,html.theme--catppuccin-frappe .docstring>section>a.button.is-inverted.docs-sourcelink:hover,html.theme--catppuccin-frappe .button.is-primary.is-inverted.is-hovered,html.theme--catppuccin-frappe .docstring>section>a.button.is-inverted.is-hovered.docs-sourcelink{background-color:#f2f2f2}html.theme--catppuccin-frappe .button.is-primary.is-inverted[disabled],html.theme--catppuccin-frappe .docstring>section>a.button.is-inverted.docs-sourcelink[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-primary.is-inverted,fieldset[disabled] html.theme--catppuccin-frappe .docstring>section>a.button.is-inverted.docs-sourcelink{background-color:#fff;border-color:transparent;box-shadow:none;color:#8caaee}html.theme--catppuccin-frappe .button.is-primary.is-loading::after,html.theme--catppuccin-frappe .docstring>section>a.button.is-loading.docs-sourcelink::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-frappe .button.is-primary.is-outlined,html.theme--catppuccin-frappe .docstring>section>a.button.is-outlined.docs-sourcelink{background-color:transparent;border-color:#8caaee;color:#8caaee}html.theme--catppuccin-frappe .button.is-primary.is-outlined:hover,html.theme--catppuccin-frappe .docstring>section>a.button.is-outlined.docs-sourcelink:hover,html.theme--catppuccin-frappe .button.is-primary.is-outlined.is-hovered,html.theme--catppuccin-frappe .docstring>section>a.button.is-outlined.is-hovered.docs-sourcelink,html.theme--catppuccin-frappe .button.is-primary.is-outlined:focus,html.theme--catppuccin-frappe .docstring>section>a.button.is-outlined.docs-sourcelink:focus,html.theme--catppuccin-frappe .button.is-primary.is-outlined.is-focused,html.theme--catppuccin-frappe .docstring>section>a.button.is-outlined.is-focused.docs-sourcelink{background-color:#8caaee;border-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .button.is-primary.is-outlined.is-loading::after,html.theme--catppuccin-frappe .docstring>section>a.button.is-outlined.is-loading.docs-sourcelink::after{border-color:transparent transparent #8caaee #8caaee !important}html.theme--catppuccin-frappe .button.is-primary.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .docstring>section>a.button.is-outlined.is-loading.docs-sourcelink:hover::after,html.theme--catppuccin-frappe .button.is-primary.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .docstring>section>a.button.is-outlined.is-loading.is-hovered.docs-sourcelink::after,html.theme--catppuccin-frappe .button.is-primary.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .docstring>section>a.button.is-outlined.is-loading.docs-sourcelink:focus::after,html.theme--catppuccin-frappe .button.is-primary.is-outlined.is-loading.is-focused::after,html.theme--catppuccin-frappe .docstring>section>a.button.is-outlined.is-loading.is-focused.docs-sourcelink::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-frappe .button.is-primary.is-outlined[disabled],html.theme--catppuccin-frappe .docstring>section>a.button.is-outlined.docs-sourcelink[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-primary.is-outlined,fieldset[disabled] html.theme--catppuccin-frappe .docstring>section>a.button.is-outlined.docs-sourcelink{background-color:transparent;border-color:#8caaee;box-shadow:none;color:#8caaee}html.theme--catppuccin-frappe .button.is-primary.is-inverted.is-outlined,html.theme--catppuccin-frappe .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-frappe .button.is-primary.is-inverted.is-outlined:hover,html.theme--catppuccin-frappe .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink:hover,html.theme--catppuccin-frappe .button.is-primary.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-frappe .docstring>section>a.button.is-inverted.is-outlined.is-hovered.docs-sourcelink,html.theme--catppuccin-frappe .button.is-primary.is-inverted.is-outlined:focus,html.theme--catppuccin-frappe .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink:focus,html.theme--catppuccin-frappe .button.is-primary.is-inverted.is-outlined.is-focused,html.theme--catppuccin-frappe .docstring>section>a.button.is-inverted.is-outlined.is-focused.docs-sourcelink{background-color:#fff;color:#8caaee}html.theme--catppuccin-frappe .button.is-primary.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .docstring>section>a.button.is-inverted.is-outlined.is-loading.docs-sourcelink:hover::after,html.theme--catppuccin-frappe .button.is-primary.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .docstring>section>a.button.is-inverted.is-outlined.is-loading.is-hovered.docs-sourcelink::after,html.theme--catppuccin-frappe .button.is-primary.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .docstring>section>a.button.is-inverted.is-outlined.is-loading.docs-sourcelink:focus::after,html.theme--catppuccin-frappe .button.is-primary.is-inverted.is-outlined.is-loading.is-focused::after,html.theme--catppuccin-frappe .docstring>section>a.button.is-inverted.is-outlined.is-loading.is-focused.docs-sourcelink::after{border-color:transparent transparent #8caaee #8caaee !important}html.theme--catppuccin-frappe .button.is-primary.is-inverted.is-outlined[disabled],html.theme--catppuccin-frappe .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-primary.is-inverted.is-outlined,fieldset[disabled] html.theme--catppuccin-frappe .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-frappe .button.is-primary.is-light,html.theme--catppuccin-frappe .docstring>section>a.button.is-light.docs-sourcelink{background-color:#edf2fc;color:#153a8e}html.theme--catppuccin-frappe .button.is-primary.is-light:hover,html.theme--catppuccin-frappe .docstring>section>a.button.is-light.docs-sourcelink:hover,html.theme--catppuccin-frappe .button.is-primary.is-light.is-hovered,html.theme--catppuccin-frappe .docstring>section>a.button.is-light.is-hovered.docs-sourcelink{background-color:#e2eafb;border-color:transparent;color:#153a8e}html.theme--catppuccin-frappe .button.is-primary.is-light:active,html.theme--catppuccin-frappe .docstring>section>a.button.is-light.docs-sourcelink:active,html.theme--catppuccin-frappe .button.is-primary.is-light.is-active,html.theme--catppuccin-frappe .docstring>section>a.button.is-light.is-active.docs-sourcelink{background-color:#d7e1f9;border-color:transparent;color:#153a8e}html.theme--catppuccin-frappe .button.is-link{background-color:#8caaee;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-link:hover,html.theme--catppuccin-frappe .button.is-link.is-hovered{background-color:#81a2ec;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-link:focus,html.theme--catppuccin-frappe .button.is-link.is-focused{border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-link:focus:not(:active),html.theme--catppuccin-frappe .button.is-link.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(140,170,238,0.25)}html.theme--catppuccin-frappe .button.is-link:active,html.theme--catppuccin-frappe .button.is-link.is-active{background-color:#769aeb;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-link[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-link{background-color:#8caaee;border-color:#8caaee;box-shadow:none}html.theme--catppuccin-frappe .button.is-link.is-inverted{background-color:#fff;color:#8caaee}html.theme--catppuccin-frappe .button.is-link.is-inverted:hover,html.theme--catppuccin-frappe .button.is-link.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--catppuccin-frappe .button.is-link.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-link.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#8caaee}html.theme--catppuccin-frappe .button.is-link.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-frappe .button.is-link.is-outlined{background-color:transparent;border-color:#8caaee;color:#8caaee}html.theme--catppuccin-frappe .button.is-link.is-outlined:hover,html.theme--catppuccin-frappe .button.is-link.is-outlined.is-hovered,html.theme--catppuccin-frappe .button.is-link.is-outlined:focus,html.theme--catppuccin-frappe .button.is-link.is-outlined.is-focused{background-color:#8caaee;border-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .button.is-link.is-outlined.is-loading::after{border-color:transparent transparent #8caaee #8caaee !important}html.theme--catppuccin-frappe .button.is-link.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .button.is-link.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .button.is-link.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .button.is-link.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-frappe .button.is-link.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-link.is-outlined{background-color:transparent;border-color:#8caaee;box-shadow:none;color:#8caaee}html.theme--catppuccin-frappe .button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-frappe .button.is-link.is-inverted.is-outlined:hover,html.theme--catppuccin-frappe .button.is-link.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-frappe .button.is-link.is-inverted.is-outlined:focus,html.theme--catppuccin-frappe .button.is-link.is-inverted.is-outlined.is-focused{background-color:#fff;color:#8caaee}html.theme--catppuccin-frappe .button.is-link.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .button.is-link.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .button.is-link.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .button.is-link.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #8caaee #8caaee !important}html.theme--catppuccin-frappe .button.is-link.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-frappe .button.is-link.is-light{background-color:#edf2fc;color:#153a8e}html.theme--catppuccin-frappe .button.is-link.is-light:hover,html.theme--catppuccin-frappe .button.is-link.is-light.is-hovered{background-color:#e2eafb;border-color:transparent;color:#153a8e}html.theme--catppuccin-frappe .button.is-link.is-light:active,html.theme--catppuccin-frappe .button.is-link.is-light.is-active{background-color:#d7e1f9;border-color:transparent;color:#153a8e}html.theme--catppuccin-frappe .button.is-info{background-color:#81c8be;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-info:hover,html.theme--catppuccin-frappe .button.is-info.is-hovered{background-color:#78c4b9;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-info:focus,html.theme--catppuccin-frappe .button.is-info.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-info:focus:not(:active),html.theme--catppuccin-frappe .button.is-info.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(129,200,190,0.25)}html.theme--catppuccin-frappe .button.is-info:active,html.theme--catppuccin-frappe .button.is-info.is-active{background-color:#6fc0b5;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-info[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-info{background-color:#81c8be;border-color:#81c8be;box-shadow:none}html.theme--catppuccin-frappe .button.is-info.is-inverted{background-color:rgba(0,0,0,0.7);color:#81c8be}html.theme--catppuccin-frappe .button.is-info.is-inverted:hover,html.theme--catppuccin-frappe .button.is-info.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-info.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-info.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#81c8be}html.theme--catppuccin-frappe .button.is-info.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-frappe .button.is-info.is-outlined{background-color:transparent;border-color:#81c8be;color:#81c8be}html.theme--catppuccin-frappe .button.is-info.is-outlined:hover,html.theme--catppuccin-frappe .button.is-info.is-outlined.is-hovered,html.theme--catppuccin-frappe .button.is-info.is-outlined:focus,html.theme--catppuccin-frappe .button.is-info.is-outlined.is-focused{background-color:#81c8be;border-color:#81c8be;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-info.is-outlined.is-loading::after{border-color:transparent transparent #81c8be #81c8be !important}html.theme--catppuccin-frappe .button.is-info.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .button.is-info.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .button.is-info.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .button.is-info.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-frappe .button.is-info.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-info.is-outlined{background-color:transparent;border-color:#81c8be;box-shadow:none;color:#81c8be}html.theme--catppuccin-frappe .button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-info.is-inverted.is-outlined:hover,html.theme--catppuccin-frappe .button.is-info.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-frappe .button.is-info.is-inverted.is-outlined:focus,html.theme--catppuccin-frappe .button.is-info.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#81c8be}html.theme--catppuccin-frappe .button.is-info.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .button.is-info.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .button.is-info.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .button.is-info.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #81c8be #81c8be !important}html.theme--catppuccin-frappe .button.is-info.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-info.is-light{background-color:#f1f9f8;color:#2d675f}html.theme--catppuccin-frappe .button.is-info.is-light:hover,html.theme--catppuccin-frappe .button.is-info.is-light.is-hovered{background-color:#e8f5f3;border-color:transparent;color:#2d675f}html.theme--catppuccin-frappe .button.is-info.is-light:active,html.theme--catppuccin-frappe .button.is-info.is-light.is-active{background-color:#dff1ef;border-color:transparent;color:#2d675f}html.theme--catppuccin-frappe .button.is-success{background-color:#a6d189;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-success:hover,html.theme--catppuccin-frappe .button.is-success.is-hovered{background-color:#9fcd80;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-success:focus,html.theme--catppuccin-frappe .button.is-success.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-success:focus:not(:active),html.theme--catppuccin-frappe .button.is-success.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(166,209,137,0.25)}html.theme--catppuccin-frappe .button.is-success:active,html.theme--catppuccin-frappe .button.is-success.is-active{background-color:#98ca77;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-success[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-success{background-color:#a6d189;border-color:#a6d189;box-shadow:none}html.theme--catppuccin-frappe .button.is-success.is-inverted{background-color:rgba(0,0,0,0.7);color:#a6d189}html.theme--catppuccin-frappe .button.is-success.is-inverted:hover,html.theme--catppuccin-frappe .button.is-success.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-success.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-success.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#a6d189}html.theme--catppuccin-frappe .button.is-success.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-frappe .button.is-success.is-outlined{background-color:transparent;border-color:#a6d189;color:#a6d189}html.theme--catppuccin-frappe .button.is-success.is-outlined:hover,html.theme--catppuccin-frappe .button.is-success.is-outlined.is-hovered,html.theme--catppuccin-frappe .button.is-success.is-outlined:focus,html.theme--catppuccin-frappe .button.is-success.is-outlined.is-focused{background-color:#a6d189;border-color:#a6d189;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-success.is-outlined.is-loading::after{border-color:transparent transparent #a6d189 #a6d189 !important}html.theme--catppuccin-frappe .button.is-success.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .button.is-success.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .button.is-success.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .button.is-success.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-frappe .button.is-success.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-success.is-outlined{background-color:transparent;border-color:#a6d189;box-shadow:none;color:#a6d189}html.theme--catppuccin-frappe .button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-success.is-inverted.is-outlined:hover,html.theme--catppuccin-frappe .button.is-success.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-frappe .button.is-success.is-inverted.is-outlined:focus,html.theme--catppuccin-frappe .button.is-success.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#a6d189}html.theme--catppuccin-frappe .button.is-success.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .button.is-success.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .button.is-success.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .button.is-success.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #a6d189 #a6d189 !important}html.theme--catppuccin-frappe .button.is-success.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-success.is-light{background-color:#f4f9f0;color:#446a29}html.theme--catppuccin-frappe .button.is-success.is-light:hover,html.theme--catppuccin-frappe .button.is-success.is-light.is-hovered{background-color:#edf6e7;border-color:transparent;color:#446a29}html.theme--catppuccin-frappe .button.is-success.is-light:active,html.theme--catppuccin-frappe .button.is-success.is-light.is-active{background-color:#e6f2de;border-color:transparent;color:#446a29}html.theme--catppuccin-frappe .button.is-warning{background-color:#e5c890;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-warning:hover,html.theme--catppuccin-frappe .button.is-warning.is-hovered{background-color:#e3c386;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-warning:focus,html.theme--catppuccin-frappe .button.is-warning.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-warning:focus:not(:active),html.theme--catppuccin-frappe .button.is-warning.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(229,200,144,0.25)}html.theme--catppuccin-frappe .button.is-warning:active,html.theme--catppuccin-frappe .button.is-warning.is-active{background-color:#e0be7b;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-warning[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-warning{background-color:#e5c890;border-color:#e5c890;box-shadow:none}html.theme--catppuccin-frappe .button.is-warning.is-inverted{background-color:rgba(0,0,0,0.7);color:#e5c890}html.theme--catppuccin-frappe .button.is-warning.is-inverted:hover,html.theme--catppuccin-frappe .button.is-warning.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-warning.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-warning.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#e5c890}html.theme--catppuccin-frappe .button.is-warning.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-frappe .button.is-warning.is-outlined{background-color:transparent;border-color:#e5c890;color:#e5c890}html.theme--catppuccin-frappe .button.is-warning.is-outlined:hover,html.theme--catppuccin-frappe .button.is-warning.is-outlined.is-hovered,html.theme--catppuccin-frappe .button.is-warning.is-outlined:focus,html.theme--catppuccin-frappe .button.is-warning.is-outlined.is-focused{background-color:#e5c890;border-color:#e5c890;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-warning.is-outlined.is-loading::after{border-color:transparent transparent #e5c890 #e5c890 !important}html.theme--catppuccin-frappe .button.is-warning.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .button.is-warning.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .button.is-warning.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .button.is-warning.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-frappe .button.is-warning.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-warning.is-outlined{background-color:transparent;border-color:#e5c890;box-shadow:none;color:#e5c890}html.theme--catppuccin-frappe .button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-warning.is-inverted.is-outlined:hover,html.theme--catppuccin-frappe .button.is-warning.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-frappe .button.is-warning.is-inverted.is-outlined:focus,html.theme--catppuccin-frappe .button.is-warning.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#e5c890}html.theme--catppuccin-frappe .button.is-warning.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .button.is-warning.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .button.is-warning.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .button.is-warning.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #e5c890 #e5c890 !important}html.theme--catppuccin-frappe .button.is-warning.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-warning.is-light{background-color:#fbf7ee;color:#78591c}html.theme--catppuccin-frappe .button.is-warning.is-light:hover,html.theme--catppuccin-frappe .button.is-warning.is-light.is-hovered{background-color:#f9f2e4;border-color:transparent;color:#78591c}html.theme--catppuccin-frappe .button.is-warning.is-light:active,html.theme--catppuccin-frappe .button.is-warning.is-light.is-active{background-color:#f6edda;border-color:transparent;color:#78591c}html.theme--catppuccin-frappe .button.is-danger{background-color:#e78284;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-danger:hover,html.theme--catppuccin-frappe .button.is-danger.is-hovered{background-color:#e57779;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-danger:focus,html.theme--catppuccin-frappe .button.is-danger.is-focused{border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-danger:focus:not(:active),html.theme--catppuccin-frappe .button.is-danger.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(231,130,132,0.25)}html.theme--catppuccin-frappe .button.is-danger:active,html.theme--catppuccin-frappe .button.is-danger.is-active{background-color:#e36d6f;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-danger[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-danger{background-color:#e78284;border-color:#e78284;box-shadow:none}html.theme--catppuccin-frappe .button.is-danger.is-inverted{background-color:#fff;color:#e78284}html.theme--catppuccin-frappe .button.is-danger.is-inverted:hover,html.theme--catppuccin-frappe .button.is-danger.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--catppuccin-frappe .button.is-danger.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-danger.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#e78284}html.theme--catppuccin-frappe .button.is-danger.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-frappe .button.is-danger.is-outlined{background-color:transparent;border-color:#e78284;color:#e78284}html.theme--catppuccin-frappe .button.is-danger.is-outlined:hover,html.theme--catppuccin-frappe .button.is-danger.is-outlined.is-hovered,html.theme--catppuccin-frappe .button.is-danger.is-outlined:focus,html.theme--catppuccin-frappe .button.is-danger.is-outlined.is-focused{background-color:#e78284;border-color:#e78284;color:#fff}html.theme--catppuccin-frappe .button.is-danger.is-outlined.is-loading::after{border-color:transparent transparent #e78284 #e78284 !important}html.theme--catppuccin-frappe .button.is-danger.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .button.is-danger.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .button.is-danger.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .button.is-danger.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-frappe .button.is-danger.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-danger.is-outlined{background-color:transparent;border-color:#e78284;box-shadow:none;color:#e78284}html.theme--catppuccin-frappe .button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-frappe .button.is-danger.is-inverted.is-outlined:hover,html.theme--catppuccin-frappe .button.is-danger.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-frappe .button.is-danger.is-inverted.is-outlined:focus,html.theme--catppuccin-frappe .button.is-danger.is-inverted.is-outlined.is-focused{background-color:#fff;color:#e78284}html.theme--catppuccin-frappe .button.is-danger.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .button.is-danger.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .button.is-danger.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .button.is-danger.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #e78284 #e78284 !important}html.theme--catppuccin-frappe .button.is-danger.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-frappe .button.is-danger.is-light{background-color:#fceeee;color:#9a1e20}html.theme--catppuccin-frappe .button.is-danger.is-light:hover,html.theme--catppuccin-frappe .button.is-danger.is-light.is-hovered{background-color:#fae3e4;border-color:transparent;color:#9a1e20}html.theme--catppuccin-frappe .button.is-danger.is-light:active,html.theme--catppuccin-frappe .button.is-danger.is-light.is-active{background-color:#f8d8d9;border-color:transparent;color:#9a1e20}html.theme--catppuccin-frappe .button.is-small,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.button{font-size:.75rem}html.theme--catppuccin-frappe .button.is-small:not(.is-rounded),html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.button:not(.is-rounded){border-radius:3px}html.theme--catppuccin-frappe .button.is-normal{font-size:1rem}html.theme--catppuccin-frappe .button.is-medium{font-size:1.25rem}html.theme--catppuccin-frappe .button.is-large{font-size:1.5rem}html.theme--catppuccin-frappe .button[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button{background-color:#737994;border-color:#626880;box-shadow:none;opacity:.5}html.theme--catppuccin-frappe .button.is-fullwidth{display:flex;width:100%}html.theme--catppuccin-frappe .button.is-loading{color:transparent !important;pointer-events:none}html.theme--catppuccin-frappe .button.is-loading::after{position:absolute;left:calc(50% - (1em * 0.5));top:calc(50% - (1em * 0.5));position:absolute !important}html.theme--catppuccin-frappe .button.is-static{background-color:#292c3c;border-color:#626880;color:#838ba7;box-shadow:none;pointer-events:none}html.theme--catppuccin-frappe .button.is-rounded,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.button{border-radius:9999px;padding-left:calc(1em + 0.25em);padding-right:calc(1em + 0.25em)}html.theme--catppuccin-frappe .buttons{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}html.theme--catppuccin-frappe .buttons .button{margin-bottom:0.5rem}html.theme--catppuccin-frappe .buttons .button:not(:last-child):not(.is-fullwidth){margin-right:.5rem}html.theme--catppuccin-frappe .buttons:last-child{margin-bottom:-0.5rem}html.theme--catppuccin-frappe .buttons:not(:last-child){margin-bottom:1rem}html.theme--catppuccin-frappe .buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large){font-size:.75rem}html.theme--catppuccin-frappe .buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large):not(.is-rounded){border-radius:3px}html.theme--catppuccin-frappe .buttons.are-medium .button:not(.is-small):not(.is-normal):not(.is-large){font-size:1.25rem}html.theme--catppuccin-frappe .buttons.are-large .button:not(.is-small):not(.is-normal):not(.is-medium){font-size:1.5rem}html.theme--catppuccin-frappe .buttons.has-addons .button:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}html.theme--catppuccin-frappe .buttons.has-addons .button:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0;margin-right:-1px}html.theme--catppuccin-frappe .buttons.has-addons .button:last-child{margin-right:0}html.theme--catppuccin-frappe .buttons.has-addons .button:hover,html.theme--catppuccin-frappe .buttons.has-addons .button.is-hovered{z-index:2}html.theme--catppuccin-frappe .buttons.has-addons .button:focus,html.theme--catppuccin-frappe .buttons.has-addons .button.is-focused,html.theme--catppuccin-frappe .buttons.has-addons .button:active,html.theme--catppuccin-frappe .buttons.has-addons .button.is-active,html.theme--catppuccin-frappe .buttons.has-addons .button.is-selected{z-index:3}html.theme--catppuccin-frappe .buttons.has-addons .button:focus:hover,html.theme--catppuccin-frappe .buttons.has-addons .button.is-focused:hover,html.theme--catppuccin-frappe .buttons.has-addons .button:active:hover,html.theme--catppuccin-frappe .buttons.has-addons .button.is-active:hover,html.theme--catppuccin-frappe .buttons.has-addons .button.is-selected:hover{z-index:4}html.theme--catppuccin-frappe .buttons.has-addons .button.is-expanded{flex-grow:1;flex-shrink:1}html.theme--catppuccin-frappe .buttons.is-centered{justify-content:center}html.theme--catppuccin-frappe .buttons.is-centered:not(.has-addons) .button:not(.is-fullwidth){margin-left:0.25rem;margin-right:0.25rem}html.theme--catppuccin-frappe .buttons.is-right{justify-content:flex-end}html.theme--catppuccin-frappe .buttons.is-right:not(.has-addons) .button:not(.is-fullwidth){margin-left:0.25rem;margin-right:0.25rem}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .button.is-responsive.is-small,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-responsive{font-size:.5625rem}html.theme--catppuccin-frappe .button.is-responsive,html.theme--catppuccin-frappe .button.is-responsive.is-normal{font-size:.65625rem}html.theme--catppuccin-frappe .button.is-responsive.is-medium{font-size:.75rem}html.theme--catppuccin-frappe .button.is-responsive.is-large{font-size:1rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-frappe .button.is-responsive.is-small,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-responsive{font-size:.65625rem}html.theme--catppuccin-frappe .button.is-responsive,html.theme--catppuccin-frappe .button.is-responsive.is-normal{font-size:.75rem}html.theme--catppuccin-frappe .button.is-responsive.is-medium{font-size:1rem}html.theme--catppuccin-frappe .button.is-responsive.is-large{font-size:1.25rem}}html.theme--catppuccin-frappe .container{flex-grow:1;margin:0 auto;position:relative;width:auto}html.theme--catppuccin-frappe .container.is-fluid{max-width:none !important;padding-left:32px;padding-right:32px;width:100%}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .container{max-width:992px}}@media screen and (max-width: 1215px){html.theme--catppuccin-frappe .container.is-widescreen:not(.is-max-desktop){max-width:1152px}}@media screen and (max-width: 1407px){html.theme--catppuccin-frappe .container.is-fullhd:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}@media screen and (min-width: 1216px){html.theme--catppuccin-frappe .container:not(.is-max-desktop){max-width:1152px}}@media screen and (min-width: 1408px){html.theme--catppuccin-frappe .container:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}html.theme--catppuccin-frappe .content li+li{margin-top:0.25em}html.theme--catppuccin-frappe .content p:not(:last-child),html.theme--catppuccin-frappe .content dl:not(:last-child),html.theme--catppuccin-frappe .content ol:not(:last-child),html.theme--catppuccin-frappe .content ul:not(:last-child),html.theme--catppuccin-frappe .content blockquote:not(:last-child),html.theme--catppuccin-frappe .content pre:not(:last-child),html.theme--catppuccin-frappe .content table:not(:last-child){margin-bottom:1em}html.theme--catppuccin-frappe .content h1,html.theme--catppuccin-frappe .content h2,html.theme--catppuccin-frappe .content h3,html.theme--catppuccin-frappe .content h4,html.theme--catppuccin-frappe .content h5,html.theme--catppuccin-frappe .content h6{color:#c6d0f5;font-weight:600;line-height:1.125}html.theme--catppuccin-frappe .content h1{font-size:2em;margin-bottom:0.5em}html.theme--catppuccin-frappe .content h1:not(:first-child){margin-top:1em}html.theme--catppuccin-frappe .content h2{font-size:1.75em;margin-bottom:0.5714em}html.theme--catppuccin-frappe .content h2:not(:first-child){margin-top:1.1428em}html.theme--catppuccin-frappe .content h3{font-size:1.5em;margin-bottom:0.6666em}html.theme--catppuccin-frappe .content h3:not(:first-child){margin-top:1.3333em}html.theme--catppuccin-frappe .content h4{font-size:1.25em;margin-bottom:0.8em}html.theme--catppuccin-frappe .content h5{font-size:1.125em;margin-bottom:0.8888em}html.theme--catppuccin-frappe .content h6{font-size:1em;margin-bottom:1em}html.theme--catppuccin-frappe .content blockquote{background-color:#292c3c;border-left:5px solid #626880;padding:1.25em 1.5em}html.theme--catppuccin-frappe .content ol{list-style-position:outside;margin-left:2em;margin-top:1em}html.theme--catppuccin-frappe .content ol:not([type]){list-style-type:decimal}html.theme--catppuccin-frappe .content ol.is-lower-alpha:not([type]){list-style-type:lower-alpha}html.theme--catppuccin-frappe .content ol.is-lower-roman:not([type]){list-style-type:lower-roman}html.theme--catppuccin-frappe .content ol.is-upper-alpha:not([type]){list-style-type:upper-alpha}html.theme--catppuccin-frappe .content ol.is-upper-roman:not([type]){list-style-type:upper-roman}html.theme--catppuccin-frappe .content ul{list-style:disc outside;margin-left:2em;margin-top:1em}html.theme--catppuccin-frappe .content ul ul{list-style-type:circle;margin-top:0.5em}html.theme--catppuccin-frappe .content ul ul ul{list-style-type:square}html.theme--catppuccin-frappe .content dd{margin-left:2em}html.theme--catppuccin-frappe .content figure{margin-left:2em;margin-right:2em;text-align:center}html.theme--catppuccin-frappe .content figure:not(:first-child){margin-top:2em}html.theme--catppuccin-frappe .content figure:not(:last-child){margin-bottom:2em}html.theme--catppuccin-frappe .content figure img{display:inline-block}html.theme--catppuccin-frappe .content figure figcaption{font-style:italic}html.theme--catppuccin-frappe .content pre{-webkit-overflow-scrolling:touch;overflow-x:auto;padding:0;white-space:pre;word-wrap:normal}html.theme--catppuccin-frappe .content sup,html.theme--catppuccin-frappe .content sub{font-size:75%}html.theme--catppuccin-frappe .content table{width:100%}html.theme--catppuccin-frappe .content table td,html.theme--catppuccin-frappe .content table th{border:1px solid #626880;border-width:0 0 1px;padding:0.5em 0.75em;vertical-align:top}html.theme--catppuccin-frappe .content table th{color:#b0bef1}html.theme--catppuccin-frappe .content table th:not([align]){text-align:inherit}html.theme--catppuccin-frappe .content table thead td,html.theme--catppuccin-frappe .content table thead th{border-width:0 0 2px;color:#b0bef1}html.theme--catppuccin-frappe .content table tfoot td,html.theme--catppuccin-frappe .content table tfoot th{border-width:2px 0 0;color:#b0bef1}html.theme--catppuccin-frappe .content table tbody tr:last-child td,html.theme--catppuccin-frappe .content table tbody tr:last-child th{border-bottom-width:0}html.theme--catppuccin-frappe .content .tabs li+li{margin-top:0}html.theme--catppuccin-frappe .content.is-small,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.content{font-size:.75rem}html.theme--catppuccin-frappe .content.is-normal{font-size:1rem}html.theme--catppuccin-frappe .content.is-medium{font-size:1.25rem}html.theme--catppuccin-frappe .content.is-large{font-size:1.5rem}html.theme--catppuccin-frappe .icon{align-items:center;display:inline-flex;justify-content:center;height:1.5rem;width:1.5rem}html.theme--catppuccin-frappe .icon.is-small,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.icon{height:1rem;width:1rem}html.theme--catppuccin-frappe .icon.is-medium{height:2rem;width:2rem}html.theme--catppuccin-frappe .icon.is-large{height:3rem;width:3rem}html.theme--catppuccin-frappe .icon-text{align-items:flex-start;color:inherit;display:inline-flex;flex-wrap:wrap;line-height:1.5rem;vertical-align:top}html.theme--catppuccin-frappe .icon-text .icon{flex-grow:0;flex-shrink:0}html.theme--catppuccin-frappe .icon-text .icon:not(:last-child){margin-right:.25em}html.theme--catppuccin-frappe .icon-text .icon:not(:first-child){margin-left:.25em}html.theme--catppuccin-frappe div.icon-text{display:flex}html.theme--catppuccin-frappe .image,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img{display:block;position:relative}html.theme--catppuccin-frappe .image img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img img{display:block;height:auto;width:100%}html.theme--catppuccin-frappe .image img.is-rounded,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img img.is-rounded{border-radius:9999px}html.theme--catppuccin-frappe .image.is-fullwidth,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-fullwidth{width:100%}html.theme--catppuccin-frappe .image.is-square img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-square img,html.theme--catppuccin-frappe .image.is-square .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-square .has-ratio,html.theme--catppuccin-frappe .image.is-1by1 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-1by1 img,html.theme--catppuccin-frappe .image.is-1by1 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-1by1 .has-ratio,html.theme--catppuccin-frappe .image.is-5by4 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-5by4 img,html.theme--catppuccin-frappe .image.is-5by4 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-5by4 .has-ratio,html.theme--catppuccin-frappe .image.is-4by3 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-4by3 img,html.theme--catppuccin-frappe .image.is-4by3 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-4by3 .has-ratio,html.theme--catppuccin-frappe .image.is-3by2 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by2 img,html.theme--catppuccin-frappe .image.is-3by2 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by2 .has-ratio,html.theme--catppuccin-frappe .image.is-5by3 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-5by3 img,html.theme--catppuccin-frappe .image.is-5by3 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-5by3 .has-ratio,html.theme--catppuccin-frappe .image.is-16by9 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-16by9 img,html.theme--catppuccin-frappe .image.is-16by9 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-16by9 .has-ratio,html.theme--catppuccin-frappe .image.is-2by1 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-2by1 img,html.theme--catppuccin-frappe .image.is-2by1 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-2by1 .has-ratio,html.theme--catppuccin-frappe .image.is-3by1 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by1 img,html.theme--catppuccin-frappe .image.is-3by1 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by1 .has-ratio,html.theme--catppuccin-frappe .image.is-4by5 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-4by5 img,html.theme--catppuccin-frappe .image.is-4by5 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-4by5 .has-ratio,html.theme--catppuccin-frappe .image.is-3by4 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by4 img,html.theme--catppuccin-frappe .image.is-3by4 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by4 .has-ratio,html.theme--catppuccin-frappe .image.is-2by3 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-2by3 img,html.theme--catppuccin-frappe .image.is-2by3 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-2by3 .has-ratio,html.theme--catppuccin-frappe .image.is-3by5 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by5 img,html.theme--catppuccin-frappe .image.is-3by5 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by5 .has-ratio,html.theme--catppuccin-frappe .image.is-9by16 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-9by16 img,html.theme--catppuccin-frappe .image.is-9by16 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-9by16 .has-ratio,html.theme--catppuccin-frappe .image.is-1by2 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-1by2 img,html.theme--catppuccin-frappe .image.is-1by2 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-1by2 .has-ratio,html.theme--catppuccin-frappe .image.is-1by3 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-1by3 img,html.theme--catppuccin-frappe .image.is-1by3 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-1by3 .has-ratio{height:100%;width:100%}html.theme--catppuccin-frappe .image.is-square,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-square,html.theme--catppuccin-frappe .image.is-1by1,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-1by1{padding-top:100%}html.theme--catppuccin-frappe .image.is-5by4,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-5by4{padding-top:80%}html.theme--catppuccin-frappe .image.is-4by3,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-4by3{padding-top:75%}html.theme--catppuccin-frappe .image.is-3by2,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by2{padding-top:66.6666%}html.theme--catppuccin-frappe .image.is-5by3,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-5by3{padding-top:60%}html.theme--catppuccin-frappe .image.is-16by9,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-16by9{padding-top:56.25%}html.theme--catppuccin-frappe .image.is-2by1,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-2by1{padding-top:50%}html.theme--catppuccin-frappe .image.is-3by1,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by1{padding-top:33.3333%}html.theme--catppuccin-frappe .image.is-4by5,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-4by5{padding-top:125%}html.theme--catppuccin-frappe .image.is-3by4,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by4{padding-top:133.3333%}html.theme--catppuccin-frappe .image.is-2by3,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-2by3{padding-top:150%}html.theme--catppuccin-frappe .image.is-3by5,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by5{padding-top:166.6666%}html.theme--catppuccin-frappe .image.is-9by16,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-9by16{padding-top:177.7777%}html.theme--catppuccin-frappe .image.is-1by2,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-1by2{padding-top:200%}html.theme--catppuccin-frappe .image.is-1by3,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-1by3{padding-top:300%}html.theme--catppuccin-frappe .image.is-16x16,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-16x16{height:16px;width:16px}html.theme--catppuccin-frappe .image.is-24x24,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-24x24{height:24px;width:24px}html.theme--catppuccin-frappe .image.is-32x32,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-32x32{height:32px;width:32px}html.theme--catppuccin-frappe .image.is-48x48,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-48x48{height:48px;width:48px}html.theme--catppuccin-frappe .image.is-64x64,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-64x64{height:64px;width:64px}html.theme--catppuccin-frappe .image.is-96x96,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-96x96{height:96px;width:96px}html.theme--catppuccin-frappe .image.is-128x128,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-128x128{height:128px;width:128px}html.theme--catppuccin-frappe .notification{background-color:#292c3c;border-radius:.4em;position:relative;padding:1.25rem 2.5rem 1.25rem 1.5rem}html.theme--catppuccin-frappe .notification a:not(.button):not(.dropdown-item){color:currentColor;text-decoration:underline}html.theme--catppuccin-frappe .notification strong{color:currentColor}html.theme--catppuccin-frappe .notification code,html.theme--catppuccin-frappe .notification pre{background:#fff}html.theme--catppuccin-frappe .notification pre code{background:transparent}html.theme--catppuccin-frappe .notification>.delete{right:.5rem;position:absolute;top:0.5rem}html.theme--catppuccin-frappe .notification .title,html.theme--catppuccin-frappe .notification .subtitle,html.theme--catppuccin-frappe .notification .content{color:currentColor}html.theme--catppuccin-frappe .notification.is-white{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-frappe .notification.is-black{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-frappe .notification.is-light{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .notification.is-dark,html.theme--catppuccin-frappe .content kbd.notification{background-color:#414559;color:#fff}html.theme--catppuccin-frappe .notification.is-primary,html.theme--catppuccin-frappe .docstring>section>a.notification.docs-sourcelink{background-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .notification.is-primary.is-light,html.theme--catppuccin-frappe .docstring>section>a.notification.is-light.docs-sourcelink{background-color:#edf2fc;color:#153a8e}html.theme--catppuccin-frappe .notification.is-link{background-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .notification.is-link.is-light{background-color:#edf2fc;color:#153a8e}html.theme--catppuccin-frappe .notification.is-info{background-color:#81c8be;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .notification.is-info.is-light{background-color:#f1f9f8;color:#2d675f}html.theme--catppuccin-frappe .notification.is-success{background-color:#a6d189;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .notification.is-success.is-light{background-color:#f4f9f0;color:#446a29}html.theme--catppuccin-frappe .notification.is-warning{background-color:#e5c890;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .notification.is-warning.is-light{background-color:#fbf7ee;color:#78591c}html.theme--catppuccin-frappe .notification.is-danger{background-color:#e78284;color:#fff}html.theme--catppuccin-frappe .notification.is-danger.is-light{background-color:#fceeee;color:#9a1e20}html.theme--catppuccin-frappe .progress{-moz-appearance:none;-webkit-appearance:none;border:none;border-radius:9999px;display:block;height:1rem;overflow:hidden;padding:0;width:100%}html.theme--catppuccin-frappe .progress::-webkit-progress-bar{background-color:#51576d}html.theme--catppuccin-frappe .progress::-webkit-progress-value{background-color:#838ba7}html.theme--catppuccin-frappe .progress::-moz-progress-bar{background-color:#838ba7}html.theme--catppuccin-frappe .progress::-ms-fill{background-color:#838ba7;border:none}html.theme--catppuccin-frappe .progress.is-white::-webkit-progress-value{background-color:#fff}html.theme--catppuccin-frappe .progress.is-white::-moz-progress-bar{background-color:#fff}html.theme--catppuccin-frappe .progress.is-white::-ms-fill{background-color:#fff}html.theme--catppuccin-frappe .progress.is-white:indeterminate{background-image:linear-gradient(to right, #fff 30%, #51576d 30%)}html.theme--catppuccin-frappe .progress.is-black::-webkit-progress-value{background-color:#0a0a0a}html.theme--catppuccin-frappe .progress.is-black::-moz-progress-bar{background-color:#0a0a0a}html.theme--catppuccin-frappe .progress.is-black::-ms-fill{background-color:#0a0a0a}html.theme--catppuccin-frappe .progress.is-black:indeterminate{background-image:linear-gradient(to right, #0a0a0a 30%, #51576d 30%)}html.theme--catppuccin-frappe .progress.is-light::-webkit-progress-value{background-color:#f5f5f5}html.theme--catppuccin-frappe .progress.is-light::-moz-progress-bar{background-color:#f5f5f5}html.theme--catppuccin-frappe .progress.is-light::-ms-fill{background-color:#f5f5f5}html.theme--catppuccin-frappe .progress.is-light:indeterminate{background-image:linear-gradient(to right, #f5f5f5 30%, #51576d 30%)}html.theme--catppuccin-frappe .progress.is-dark::-webkit-progress-value,html.theme--catppuccin-frappe .content kbd.progress::-webkit-progress-value{background-color:#414559}html.theme--catppuccin-frappe .progress.is-dark::-moz-progress-bar,html.theme--catppuccin-frappe .content kbd.progress::-moz-progress-bar{background-color:#414559}html.theme--catppuccin-frappe .progress.is-dark::-ms-fill,html.theme--catppuccin-frappe .content kbd.progress::-ms-fill{background-color:#414559}html.theme--catppuccin-frappe .progress.is-dark:indeterminate,html.theme--catppuccin-frappe .content kbd.progress:indeterminate{background-image:linear-gradient(to right, #414559 30%, #51576d 30%)}html.theme--catppuccin-frappe .progress.is-primary::-webkit-progress-value,html.theme--catppuccin-frappe .docstring>section>a.progress.docs-sourcelink::-webkit-progress-value{background-color:#8caaee}html.theme--catppuccin-frappe .progress.is-primary::-moz-progress-bar,html.theme--catppuccin-frappe .docstring>section>a.progress.docs-sourcelink::-moz-progress-bar{background-color:#8caaee}html.theme--catppuccin-frappe .progress.is-primary::-ms-fill,html.theme--catppuccin-frappe .docstring>section>a.progress.docs-sourcelink::-ms-fill{background-color:#8caaee}html.theme--catppuccin-frappe .progress.is-primary:indeterminate,html.theme--catppuccin-frappe .docstring>section>a.progress.docs-sourcelink:indeterminate{background-image:linear-gradient(to right, #8caaee 30%, #51576d 30%)}html.theme--catppuccin-frappe .progress.is-link::-webkit-progress-value{background-color:#8caaee}html.theme--catppuccin-frappe .progress.is-link::-moz-progress-bar{background-color:#8caaee}html.theme--catppuccin-frappe .progress.is-link::-ms-fill{background-color:#8caaee}html.theme--catppuccin-frappe .progress.is-link:indeterminate{background-image:linear-gradient(to right, #8caaee 30%, #51576d 30%)}html.theme--catppuccin-frappe .progress.is-info::-webkit-progress-value{background-color:#81c8be}html.theme--catppuccin-frappe .progress.is-info::-moz-progress-bar{background-color:#81c8be}html.theme--catppuccin-frappe .progress.is-info::-ms-fill{background-color:#81c8be}html.theme--catppuccin-frappe .progress.is-info:indeterminate{background-image:linear-gradient(to right, #81c8be 30%, #51576d 30%)}html.theme--catppuccin-frappe .progress.is-success::-webkit-progress-value{background-color:#a6d189}html.theme--catppuccin-frappe .progress.is-success::-moz-progress-bar{background-color:#a6d189}html.theme--catppuccin-frappe .progress.is-success::-ms-fill{background-color:#a6d189}html.theme--catppuccin-frappe .progress.is-success:indeterminate{background-image:linear-gradient(to right, #a6d189 30%, #51576d 30%)}html.theme--catppuccin-frappe .progress.is-warning::-webkit-progress-value{background-color:#e5c890}html.theme--catppuccin-frappe .progress.is-warning::-moz-progress-bar{background-color:#e5c890}html.theme--catppuccin-frappe .progress.is-warning::-ms-fill{background-color:#e5c890}html.theme--catppuccin-frappe .progress.is-warning:indeterminate{background-image:linear-gradient(to right, #e5c890 30%, #51576d 30%)}html.theme--catppuccin-frappe .progress.is-danger::-webkit-progress-value{background-color:#e78284}html.theme--catppuccin-frappe .progress.is-danger::-moz-progress-bar{background-color:#e78284}html.theme--catppuccin-frappe .progress.is-danger::-ms-fill{background-color:#e78284}html.theme--catppuccin-frappe .progress.is-danger:indeterminate{background-image:linear-gradient(to right, #e78284 30%, #51576d 30%)}html.theme--catppuccin-frappe .progress:indeterminate{animation-duration:1.5s;animation-iteration-count:infinite;animation-name:moveIndeterminate;animation-timing-function:linear;background-color:#51576d;background-image:linear-gradient(to right, #c6d0f5 30%, #51576d 30%);background-position:top left;background-repeat:no-repeat;background-size:150% 150%}html.theme--catppuccin-frappe .progress:indeterminate::-webkit-progress-bar{background-color:transparent}html.theme--catppuccin-frappe .progress:indeterminate::-moz-progress-bar{background-color:transparent}html.theme--catppuccin-frappe .progress:indeterminate::-ms-fill{animation-name:none}html.theme--catppuccin-frappe .progress.is-small,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.progress{height:.75rem}html.theme--catppuccin-frappe .progress.is-medium{height:1.25rem}html.theme--catppuccin-frappe .progress.is-large{height:1.5rem}@keyframes moveIndeterminate{from{background-position:200% 0}to{background-position:-200% 0}}html.theme--catppuccin-frappe .table{background-color:#51576d;color:#c6d0f5}html.theme--catppuccin-frappe .table td,html.theme--catppuccin-frappe .table th{border:1px solid #626880;border-width:0 0 1px;padding:0.5em 0.75em;vertical-align:top}html.theme--catppuccin-frappe .table td.is-white,html.theme--catppuccin-frappe .table th.is-white{background-color:#fff;border-color:#fff;color:#0a0a0a}html.theme--catppuccin-frappe .table td.is-black,html.theme--catppuccin-frappe .table th.is-black{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}html.theme--catppuccin-frappe .table td.is-light,html.theme--catppuccin-frappe .table th.is-light{background-color:#f5f5f5;border-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .table td.is-dark,html.theme--catppuccin-frappe .table th.is-dark{background-color:#414559;border-color:#414559;color:#fff}html.theme--catppuccin-frappe .table td.is-primary,html.theme--catppuccin-frappe .table th.is-primary{background-color:#8caaee;border-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .table td.is-link,html.theme--catppuccin-frappe .table th.is-link{background-color:#8caaee;border-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .table td.is-info,html.theme--catppuccin-frappe .table th.is-info{background-color:#81c8be;border-color:#81c8be;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .table td.is-success,html.theme--catppuccin-frappe .table th.is-success{background-color:#a6d189;border-color:#a6d189;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .table td.is-warning,html.theme--catppuccin-frappe .table th.is-warning{background-color:#e5c890;border-color:#e5c890;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .table td.is-danger,html.theme--catppuccin-frappe .table th.is-danger{background-color:#e78284;border-color:#e78284;color:#fff}html.theme--catppuccin-frappe .table td.is-narrow,html.theme--catppuccin-frappe .table th.is-narrow{white-space:nowrap;width:1%}html.theme--catppuccin-frappe .table td.is-selected,html.theme--catppuccin-frappe .table th.is-selected{background-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .table td.is-selected a,html.theme--catppuccin-frappe .table td.is-selected strong,html.theme--catppuccin-frappe .table th.is-selected a,html.theme--catppuccin-frappe .table th.is-selected strong{color:currentColor}html.theme--catppuccin-frappe .table td.is-vcentered,html.theme--catppuccin-frappe .table th.is-vcentered{vertical-align:middle}html.theme--catppuccin-frappe .table th{color:#b0bef1}html.theme--catppuccin-frappe .table th:not([align]){text-align:left}html.theme--catppuccin-frappe .table tr.is-selected{background-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .table tr.is-selected a,html.theme--catppuccin-frappe .table tr.is-selected strong{color:currentColor}html.theme--catppuccin-frappe .table tr.is-selected td,html.theme--catppuccin-frappe .table tr.is-selected th{border-color:#fff;color:currentColor}html.theme--catppuccin-frappe .table thead{background-color:rgba(0,0,0,0)}html.theme--catppuccin-frappe .table thead td,html.theme--catppuccin-frappe .table thead th{border-width:0 0 2px;color:#b0bef1}html.theme--catppuccin-frappe .table tfoot{background-color:rgba(0,0,0,0)}html.theme--catppuccin-frappe .table tfoot td,html.theme--catppuccin-frappe .table tfoot th{border-width:2px 0 0;color:#b0bef1}html.theme--catppuccin-frappe .table tbody{background-color:rgba(0,0,0,0)}html.theme--catppuccin-frappe .table tbody tr:last-child td,html.theme--catppuccin-frappe .table tbody tr:last-child th{border-bottom-width:0}html.theme--catppuccin-frappe .table.is-bordered td,html.theme--catppuccin-frappe .table.is-bordered th{border-width:1px}html.theme--catppuccin-frappe .table.is-bordered tr:last-child td,html.theme--catppuccin-frappe .table.is-bordered tr:last-child th{border-bottom-width:1px}html.theme--catppuccin-frappe .table.is-fullwidth{width:100%}html.theme--catppuccin-frappe .table.is-hoverable tbody tr:not(.is-selected):hover{background-color:#414559}html.theme--catppuccin-frappe .table.is-hoverable.is-striped tbody tr:not(.is-selected):hover{background-color:#414559}html.theme--catppuccin-frappe .table.is-hoverable.is-striped tbody tr:not(.is-selected):hover:nth-child(even){background-color:#454a5f}html.theme--catppuccin-frappe .table.is-narrow td,html.theme--catppuccin-frappe .table.is-narrow th{padding:0.25em 0.5em}html.theme--catppuccin-frappe .table.is-striped tbody tr:not(.is-selected):nth-child(even){background-color:#414559}html.theme--catppuccin-frappe .table-container{-webkit-overflow-scrolling:touch;overflow:auto;overflow-y:hidden;max-width:100%}html.theme--catppuccin-frappe .tags{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}html.theme--catppuccin-frappe .tags .tag,html.theme--catppuccin-frappe .tags .content kbd,html.theme--catppuccin-frappe .content .tags kbd,html.theme--catppuccin-frappe .tags .docstring>section>a.docs-sourcelink{margin-bottom:0.5rem}html.theme--catppuccin-frappe .tags .tag:not(:last-child),html.theme--catppuccin-frappe .tags .content kbd:not(:last-child),html.theme--catppuccin-frappe .content .tags kbd:not(:last-child),html.theme--catppuccin-frappe .tags .docstring>section>a.docs-sourcelink:not(:last-child){margin-right:.5rem}html.theme--catppuccin-frappe .tags:last-child{margin-bottom:-0.5rem}html.theme--catppuccin-frappe .tags:not(:last-child){margin-bottom:1rem}html.theme--catppuccin-frappe .tags.are-medium .tag:not(.is-normal):not(.is-large),html.theme--catppuccin-frappe .tags.are-medium .content kbd:not(.is-normal):not(.is-large),html.theme--catppuccin-frappe .content .tags.are-medium kbd:not(.is-normal):not(.is-large),html.theme--catppuccin-frappe .tags.are-medium .docstring>section>a.docs-sourcelink:not(.is-normal):not(.is-large){font-size:1rem}html.theme--catppuccin-frappe .tags.are-large .tag:not(.is-normal):not(.is-medium),html.theme--catppuccin-frappe .tags.are-large .content kbd:not(.is-normal):not(.is-medium),html.theme--catppuccin-frappe .content .tags.are-large kbd:not(.is-normal):not(.is-medium),html.theme--catppuccin-frappe .tags.are-large .docstring>section>a.docs-sourcelink:not(.is-normal):not(.is-medium){font-size:1.25rem}html.theme--catppuccin-frappe .tags.is-centered{justify-content:center}html.theme--catppuccin-frappe .tags.is-centered .tag,html.theme--catppuccin-frappe .tags.is-centered .content kbd,html.theme--catppuccin-frappe .content .tags.is-centered kbd,html.theme--catppuccin-frappe .tags.is-centered .docstring>section>a.docs-sourcelink{margin-right:0.25rem;margin-left:0.25rem}html.theme--catppuccin-frappe .tags.is-right{justify-content:flex-end}html.theme--catppuccin-frappe .tags.is-right .tag:not(:first-child),html.theme--catppuccin-frappe .tags.is-right .content kbd:not(:first-child),html.theme--catppuccin-frappe .content .tags.is-right kbd:not(:first-child),html.theme--catppuccin-frappe .tags.is-right .docstring>section>a.docs-sourcelink:not(:first-child){margin-left:0.5rem}html.theme--catppuccin-frappe .tags.is-right .tag:not(:last-child),html.theme--catppuccin-frappe .tags.is-right .content kbd:not(:last-child),html.theme--catppuccin-frappe .content .tags.is-right kbd:not(:last-child),html.theme--catppuccin-frappe .tags.is-right .docstring>section>a.docs-sourcelink:not(:last-child){margin-right:0}html.theme--catppuccin-frappe .tags.has-addons .tag,html.theme--catppuccin-frappe .tags.has-addons .content kbd,html.theme--catppuccin-frappe .content .tags.has-addons kbd,html.theme--catppuccin-frappe .tags.has-addons .docstring>section>a.docs-sourcelink{margin-right:0}html.theme--catppuccin-frappe .tags.has-addons .tag:not(:first-child),html.theme--catppuccin-frappe .tags.has-addons .content kbd:not(:first-child),html.theme--catppuccin-frappe .content .tags.has-addons kbd:not(:first-child),html.theme--catppuccin-frappe .tags.has-addons .docstring>section>a.docs-sourcelink:not(:first-child){margin-left:0;border-top-left-radius:0;border-bottom-left-radius:0}html.theme--catppuccin-frappe .tags.has-addons .tag:not(:last-child),html.theme--catppuccin-frappe .tags.has-addons .content kbd:not(:last-child),html.theme--catppuccin-frappe .content .tags.has-addons kbd:not(:last-child),html.theme--catppuccin-frappe .tags.has-addons .docstring>section>a.docs-sourcelink:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}html.theme--catppuccin-frappe .tag:not(body),html.theme--catppuccin-frappe .content kbd:not(body),html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink:not(body){align-items:center;background-color:#292c3c;border-radius:.4em;color:#c6d0f5;display:inline-flex;font-size:.75rem;height:2em;justify-content:center;line-height:1.5;padding-left:0.75em;padding-right:0.75em;white-space:nowrap}html.theme--catppuccin-frappe .tag:not(body) .delete,html.theme--catppuccin-frappe .content kbd:not(body) .delete,html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink:not(body) .delete{margin-left:.25rem;margin-right:-.375rem}html.theme--catppuccin-frappe .tag.is-white:not(body),html.theme--catppuccin-frappe .content kbd.is-white:not(body),html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink.is-white:not(body){background-color:#fff;color:#0a0a0a}html.theme--catppuccin-frappe .tag.is-black:not(body),html.theme--catppuccin-frappe .content kbd.is-black:not(body),html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink.is-black:not(body){background-color:#0a0a0a;color:#fff}html.theme--catppuccin-frappe .tag.is-light:not(body),html.theme--catppuccin-frappe .content kbd.is-light:not(body),html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink.is-light:not(body){background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .tag.is-dark:not(body),html.theme--catppuccin-frappe .content kbd:not(body),html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink.is-dark:not(body),html.theme--catppuccin-frappe .content .docstring>section>kbd:not(body){background-color:#414559;color:#fff}html.theme--catppuccin-frappe .tag.is-primary:not(body),html.theme--catppuccin-frappe .content kbd.is-primary:not(body),html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink:not(body){background-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .tag.is-primary.is-light:not(body),html.theme--catppuccin-frappe .content kbd.is-primary.is-light:not(body),html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink.is-light:not(body){background-color:#edf2fc;color:#153a8e}html.theme--catppuccin-frappe .tag.is-link:not(body),html.theme--catppuccin-frappe .content kbd.is-link:not(body),html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink.is-link:not(body){background-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .tag.is-link.is-light:not(body),html.theme--catppuccin-frappe .content kbd.is-link.is-light:not(body),html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink.is-link.is-light:not(body){background-color:#edf2fc;color:#153a8e}html.theme--catppuccin-frappe .tag.is-info:not(body),html.theme--catppuccin-frappe .content kbd.is-info:not(body),html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink.is-info:not(body){background-color:#81c8be;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .tag.is-info.is-light:not(body),html.theme--catppuccin-frappe .content kbd.is-info.is-light:not(body),html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink.is-info.is-light:not(body){background-color:#f1f9f8;color:#2d675f}html.theme--catppuccin-frappe .tag.is-success:not(body),html.theme--catppuccin-frappe .content kbd.is-success:not(body),html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink.is-success:not(body){background-color:#a6d189;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .tag.is-success.is-light:not(body),html.theme--catppuccin-frappe .content kbd.is-success.is-light:not(body),html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink.is-success.is-light:not(body){background-color:#f4f9f0;color:#446a29}html.theme--catppuccin-frappe .tag.is-warning:not(body),html.theme--catppuccin-frappe .content kbd.is-warning:not(body),html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink.is-warning:not(body){background-color:#e5c890;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .tag.is-warning.is-light:not(body),html.theme--catppuccin-frappe .content kbd.is-warning.is-light:not(body),html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink.is-warning.is-light:not(body){background-color:#fbf7ee;color:#78591c}html.theme--catppuccin-frappe .tag.is-danger:not(body),html.theme--catppuccin-frappe .content kbd.is-danger:not(body),html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink.is-danger:not(body){background-color:#e78284;color:#fff}html.theme--catppuccin-frappe .tag.is-danger.is-light:not(body),html.theme--catppuccin-frappe .content kbd.is-danger.is-light:not(body),html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink.is-danger.is-light:not(body){background-color:#fceeee;color:#9a1e20}html.theme--catppuccin-frappe .tag.is-normal:not(body),html.theme--catppuccin-frappe .content kbd.is-normal:not(body),html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink.is-normal:not(body){font-size:.75rem}html.theme--catppuccin-frappe .tag.is-medium:not(body),html.theme--catppuccin-frappe .content kbd.is-medium:not(body),html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink.is-medium:not(body){font-size:1rem}html.theme--catppuccin-frappe .tag.is-large:not(body),html.theme--catppuccin-frappe .content kbd.is-large:not(body),html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink.is-large:not(body){font-size:1.25rem}html.theme--catppuccin-frappe .tag:not(body) .icon:first-child:not(:last-child),html.theme--catppuccin-frappe .content kbd:not(body) .icon:first-child:not(:last-child),html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink:not(body) .icon:first-child:not(:last-child){margin-left:-.375em;margin-right:.1875em}html.theme--catppuccin-frappe .tag:not(body) .icon:last-child:not(:first-child),html.theme--catppuccin-frappe .content kbd:not(body) .icon:last-child:not(:first-child),html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink:not(body) .icon:last-child:not(:first-child){margin-left:.1875em;margin-right:-.375em}html.theme--catppuccin-frappe .tag:not(body) .icon:first-child:last-child,html.theme--catppuccin-frappe .content kbd:not(body) .icon:first-child:last-child,html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink:not(body) .icon:first-child:last-child{margin-left:-.375em;margin-right:-.375em}html.theme--catppuccin-frappe .tag.is-delete:not(body),html.theme--catppuccin-frappe .content kbd.is-delete:not(body),html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink.is-delete:not(body){margin-left:1px;padding:0;position:relative;width:2em}html.theme--catppuccin-frappe .tag.is-delete:not(body)::before,html.theme--catppuccin-frappe .content kbd.is-delete:not(body)::before,html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink.is-delete:not(body)::before,html.theme--catppuccin-frappe .tag.is-delete:not(body)::after,html.theme--catppuccin-frappe .content kbd.is-delete:not(body)::after,html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink.is-delete:not(body)::after{background-color:currentColor;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}html.theme--catppuccin-frappe .tag.is-delete:not(body)::before,html.theme--catppuccin-frappe .content kbd.is-delete:not(body)::before,html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink.is-delete:not(body)::before{height:1px;width:50%}html.theme--catppuccin-frappe .tag.is-delete:not(body)::after,html.theme--catppuccin-frappe .content kbd.is-delete:not(body)::after,html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink.is-delete:not(body)::after{height:50%;width:1px}html.theme--catppuccin-frappe .tag.is-delete:not(body):hover,html.theme--catppuccin-frappe .content kbd.is-delete:not(body):hover,html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink.is-delete:not(body):hover,html.theme--catppuccin-frappe .tag.is-delete:not(body):focus,html.theme--catppuccin-frappe .content kbd.is-delete:not(body):focus,html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink.is-delete:not(body):focus{background-color:#1f212d}html.theme--catppuccin-frappe .tag.is-delete:not(body):active,html.theme--catppuccin-frappe .content kbd.is-delete:not(body):active,html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink.is-delete:not(body):active{background-color:#14161e}html.theme--catppuccin-frappe .tag.is-rounded:not(body),html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input:not(body),html.theme--catppuccin-frappe .content kbd.is-rounded:not(body),html.theme--catppuccin-frappe #documenter .docs-sidebar .content form.docs-search>input:not(body),html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink.is-rounded:not(body){border-radius:9999px}html.theme--catppuccin-frappe a.tag:hover,html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink:hover{text-decoration:underline}html.theme--catppuccin-frappe .title,html.theme--catppuccin-frappe .subtitle{word-break:break-word}html.theme--catppuccin-frappe .title em,html.theme--catppuccin-frappe .title span,html.theme--catppuccin-frappe .subtitle em,html.theme--catppuccin-frappe .subtitle span{font-weight:inherit}html.theme--catppuccin-frappe .title sub,html.theme--catppuccin-frappe .subtitle sub{font-size:.75em}html.theme--catppuccin-frappe .title sup,html.theme--catppuccin-frappe .subtitle sup{font-size:.75em}html.theme--catppuccin-frappe .title .tag,html.theme--catppuccin-frappe .title .content kbd,html.theme--catppuccin-frappe .content .title kbd,html.theme--catppuccin-frappe .title .docstring>section>a.docs-sourcelink,html.theme--catppuccin-frappe .subtitle .tag,html.theme--catppuccin-frappe .subtitle .content kbd,html.theme--catppuccin-frappe .content .subtitle kbd,html.theme--catppuccin-frappe .subtitle .docstring>section>a.docs-sourcelink{vertical-align:middle}html.theme--catppuccin-frappe .title{color:#fff;font-size:2rem;font-weight:500;line-height:1.125}html.theme--catppuccin-frappe .title strong{color:inherit;font-weight:inherit}html.theme--catppuccin-frappe .title:not(.is-spaced)+.subtitle{margin-top:-1.25rem}html.theme--catppuccin-frappe .title.is-1{font-size:3rem}html.theme--catppuccin-frappe .title.is-2{font-size:2.5rem}html.theme--catppuccin-frappe .title.is-3{font-size:2rem}html.theme--catppuccin-frappe .title.is-4{font-size:1.5rem}html.theme--catppuccin-frappe .title.is-5{font-size:1.25rem}html.theme--catppuccin-frappe .title.is-6{font-size:1rem}html.theme--catppuccin-frappe .title.is-7{font-size:.75rem}html.theme--catppuccin-frappe .subtitle{color:#737994;font-size:1.25rem;font-weight:400;line-height:1.25}html.theme--catppuccin-frappe .subtitle strong{color:#737994;font-weight:600}html.theme--catppuccin-frappe .subtitle:not(.is-spaced)+.title{margin-top:-1.25rem}html.theme--catppuccin-frappe .subtitle.is-1{font-size:3rem}html.theme--catppuccin-frappe .subtitle.is-2{font-size:2.5rem}html.theme--catppuccin-frappe .subtitle.is-3{font-size:2rem}html.theme--catppuccin-frappe .subtitle.is-4{font-size:1.5rem}html.theme--catppuccin-frappe .subtitle.is-5{font-size:1.25rem}html.theme--catppuccin-frappe .subtitle.is-6{font-size:1rem}html.theme--catppuccin-frappe .subtitle.is-7{font-size:.75rem}html.theme--catppuccin-frappe .heading{display:block;font-size:11px;letter-spacing:1px;margin-bottom:5px;text-transform:uppercase}html.theme--catppuccin-frappe .number{align-items:center;background-color:#292c3c;border-radius:9999px;display:inline-flex;font-size:1.25rem;height:2em;justify-content:center;margin-right:1.5rem;min-width:2.5em;padding:0.25rem 0.5rem;text-align:center;vertical-align:top}html.theme--catppuccin-frappe .select select,html.theme--catppuccin-frappe .textarea,html.theme--catppuccin-frappe .input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input{background-color:#303446;border-color:#626880;border-radius:.4em;color:#838ba7}html.theme--catppuccin-frappe .select select::-moz-placeholder,html.theme--catppuccin-frappe .textarea::-moz-placeholder,html.theme--catppuccin-frappe .input::-moz-placeholder,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input::-moz-placeholder{color:#868c98}html.theme--catppuccin-frappe .select select::-webkit-input-placeholder,html.theme--catppuccin-frappe .textarea::-webkit-input-placeholder,html.theme--catppuccin-frappe .input::-webkit-input-placeholder,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input::-webkit-input-placeholder{color:#868c98}html.theme--catppuccin-frappe .select select:-moz-placeholder,html.theme--catppuccin-frappe .textarea:-moz-placeholder,html.theme--catppuccin-frappe .input:-moz-placeholder,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input:-moz-placeholder{color:#868c98}html.theme--catppuccin-frappe .select select:-ms-input-placeholder,html.theme--catppuccin-frappe .textarea:-ms-input-placeholder,html.theme--catppuccin-frappe .input:-ms-input-placeholder,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input:-ms-input-placeholder{color:#868c98}html.theme--catppuccin-frappe .select select:hover,html.theme--catppuccin-frappe .textarea:hover,html.theme--catppuccin-frappe .input:hover,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input:hover,html.theme--catppuccin-frappe .select select.is-hovered,html.theme--catppuccin-frappe .is-hovered.textarea,html.theme--catppuccin-frappe .is-hovered.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-hovered{border-color:#737994}html.theme--catppuccin-frappe .select select:focus,html.theme--catppuccin-frappe .textarea:focus,html.theme--catppuccin-frappe .input:focus,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input:focus,html.theme--catppuccin-frappe .select select.is-focused,html.theme--catppuccin-frappe .is-focused.textarea,html.theme--catppuccin-frappe .is-focused.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-frappe .select select:active,html.theme--catppuccin-frappe .textarea:active,html.theme--catppuccin-frappe .input:active,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input:active,html.theme--catppuccin-frappe .select select.is-active,html.theme--catppuccin-frappe .is-active.textarea,html.theme--catppuccin-frappe .is-active.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-active{border-color:#8caaee;box-shadow:0 0 0 0.125em rgba(140,170,238,0.25)}html.theme--catppuccin-frappe .select select[disabled],html.theme--catppuccin-frappe .textarea[disabled],html.theme--catppuccin-frappe .input[disabled],html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input[disabled],fieldset[disabled] html.theme--catppuccin-frappe .select select,fieldset[disabled] html.theme--catppuccin-frappe .textarea,fieldset[disabled] html.theme--catppuccin-frappe .input,fieldset[disabled] html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input{background-color:#737994;border-color:#292c3c;box-shadow:none;color:#f1f4fd}html.theme--catppuccin-frappe .select select[disabled]::-moz-placeholder,html.theme--catppuccin-frappe .textarea[disabled]::-moz-placeholder,html.theme--catppuccin-frappe .input[disabled]::-moz-placeholder,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input[disabled]::-moz-placeholder,fieldset[disabled] html.theme--catppuccin-frappe .select select::-moz-placeholder,fieldset[disabled] html.theme--catppuccin-frappe .textarea::-moz-placeholder,fieldset[disabled] html.theme--catppuccin-frappe .input::-moz-placeholder,fieldset[disabled] html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input::-moz-placeholder{color:rgba(241,244,253,0.3)}html.theme--catppuccin-frappe .select select[disabled]::-webkit-input-placeholder,html.theme--catppuccin-frappe .textarea[disabled]::-webkit-input-placeholder,html.theme--catppuccin-frappe .input[disabled]::-webkit-input-placeholder,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input[disabled]::-webkit-input-placeholder,fieldset[disabled] html.theme--catppuccin-frappe .select select::-webkit-input-placeholder,fieldset[disabled] html.theme--catppuccin-frappe .textarea::-webkit-input-placeholder,fieldset[disabled] html.theme--catppuccin-frappe .input::-webkit-input-placeholder,fieldset[disabled] html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input::-webkit-input-placeholder{color:rgba(241,244,253,0.3)}html.theme--catppuccin-frappe .select select[disabled]:-moz-placeholder,html.theme--catppuccin-frappe .textarea[disabled]:-moz-placeholder,html.theme--catppuccin-frappe .input[disabled]:-moz-placeholder,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input[disabled]:-moz-placeholder,fieldset[disabled] html.theme--catppuccin-frappe .select select:-moz-placeholder,fieldset[disabled] html.theme--catppuccin-frappe .textarea:-moz-placeholder,fieldset[disabled] html.theme--catppuccin-frappe .input:-moz-placeholder,fieldset[disabled] html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input:-moz-placeholder{color:rgba(241,244,253,0.3)}html.theme--catppuccin-frappe .select select[disabled]:-ms-input-placeholder,html.theme--catppuccin-frappe .textarea[disabled]:-ms-input-placeholder,html.theme--catppuccin-frappe .input[disabled]:-ms-input-placeholder,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input[disabled]:-ms-input-placeholder,fieldset[disabled] html.theme--catppuccin-frappe .select select:-ms-input-placeholder,fieldset[disabled] html.theme--catppuccin-frappe .textarea:-ms-input-placeholder,fieldset[disabled] html.theme--catppuccin-frappe .input:-ms-input-placeholder,fieldset[disabled] html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input:-ms-input-placeholder{color:rgba(241,244,253,0.3)}html.theme--catppuccin-frappe .textarea,html.theme--catppuccin-frappe .input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input{box-shadow:inset 0 0.0625em 0.125em rgba(10,10,10,0.05);max-width:100%;width:100%}html.theme--catppuccin-frappe .textarea[readonly],html.theme--catppuccin-frappe .input[readonly],html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input[readonly]{box-shadow:none}html.theme--catppuccin-frappe .is-white.textarea,html.theme--catppuccin-frappe .is-white.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-white{border-color:#fff}html.theme--catppuccin-frappe .is-white.textarea:focus,html.theme--catppuccin-frappe .is-white.input:focus,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-white:focus,html.theme--catppuccin-frappe .is-white.is-focused.textarea,html.theme--catppuccin-frappe .is-white.is-focused.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-frappe .is-white.textarea:active,html.theme--catppuccin-frappe .is-white.input:active,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-white:active,html.theme--catppuccin-frappe .is-white.is-active.textarea,html.theme--catppuccin-frappe .is-white.is-active.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}html.theme--catppuccin-frappe .is-black.textarea,html.theme--catppuccin-frappe .is-black.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-black{border-color:#0a0a0a}html.theme--catppuccin-frappe .is-black.textarea:focus,html.theme--catppuccin-frappe .is-black.input:focus,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-black:focus,html.theme--catppuccin-frappe .is-black.is-focused.textarea,html.theme--catppuccin-frappe .is-black.is-focused.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-frappe .is-black.textarea:active,html.theme--catppuccin-frappe .is-black.input:active,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-black:active,html.theme--catppuccin-frappe .is-black.is-active.textarea,html.theme--catppuccin-frappe .is-black.is-active.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}html.theme--catppuccin-frappe .is-light.textarea,html.theme--catppuccin-frappe .is-light.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-light{border-color:#f5f5f5}html.theme--catppuccin-frappe .is-light.textarea:focus,html.theme--catppuccin-frappe .is-light.input:focus,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-light:focus,html.theme--catppuccin-frappe .is-light.is-focused.textarea,html.theme--catppuccin-frappe .is-light.is-focused.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-frappe .is-light.textarea:active,html.theme--catppuccin-frappe .is-light.input:active,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-light:active,html.theme--catppuccin-frappe .is-light.is-active.textarea,html.theme--catppuccin-frappe .is-light.is-active.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(245,245,245,0.25)}html.theme--catppuccin-frappe .is-dark.textarea,html.theme--catppuccin-frappe .content kbd.textarea,html.theme--catppuccin-frappe .is-dark.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-dark,html.theme--catppuccin-frappe .content kbd.input{border-color:#414559}html.theme--catppuccin-frappe .is-dark.textarea:focus,html.theme--catppuccin-frappe .content kbd.textarea:focus,html.theme--catppuccin-frappe .is-dark.input:focus,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-dark:focus,html.theme--catppuccin-frappe .content kbd.input:focus,html.theme--catppuccin-frappe .is-dark.is-focused.textarea,html.theme--catppuccin-frappe .content kbd.is-focused.textarea,html.theme--catppuccin-frappe .is-dark.is-focused.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-frappe .content kbd.is-focused.input,html.theme--catppuccin-frappe #documenter .docs-sidebar .content form.docs-search>input.is-focused,html.theme--catppuccin-frappe .is-dark.textarea:active,html.theme--catppuccin-frappe .content kbd.textarea:active,html.theme--catppuccin-frappe .is-dark.input:active,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-dark:active,html.theme--catppuccin-frappe .content kbd.input:active,html.theme--catppuccin-frappe .is-dark.is-active.textarea,html.theme--catppuccin-frappe .content kbd.is-active.textarea,html.theme--catppuccin-frappe .is-dark.is-active.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-active,html.theme--catppuccin-frappe .content kbd.is-active.input,html.theme--catppuccin-frappe #documenter .docs-sidebar .content form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(65,69,89,0.25)}html.theme--catppuccin-frappe .is-primary.textarea,html.theme--catppuccin-frappe .docstring>section>a.textarea.docs-sourcelink,html.theme--catppuccin-frappe .is-primary.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-primary,html.theme--catppuccin-frappe .docstring>section>a.input.docs-sourcelink{border-color:#8caaee}html.theme--catppuccin-frappe .is-primary.textarea:focus,html.theme--catppuccin-frappe .docstring>section>a.textarea.docs-sourcelink:focus,html.theme--catppuccin-frappe .is-primary.input:focus,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-primary:focus,html.theme--catppuccin-frappe .docstring>section>a.input.docs-sourcelink:focus,html.theme--catppuccin-frappe .is-primary.is-focused.textarea,html.theme--catppuccin-frappe .docstring>section>a.is-focused.textarea.docs-sourcelink,html.theme--catppuccin-frappe .is-primary.is-focused.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-frappe .docstring>section>a.is-focused.input.docs-sourcelink,html.theme--catppuccin-frappe .is-primary.textarea:active,html.theme--catppuccin-frappe .docstring>section>a.textarea.docs-sourcelink:active,html.theme--catppuccin-frappe .is-primary.input:active,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-primary:active,html.theme--catppuccin-frappe .docstring>section>a.input.docs-sourcelink:active,html.theme--catppuccin-frappe .is-primary.is-active.textarea,html.theme--catppuccin-frappe .docstring>section>a.is-active.textarea.docs-sourcelink,html.theme--catppuccin-frappe .is-primary.is-active.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-active,html.theme--catppuccin-frappe .docstring>section>a.is-active.input.docs-sourcelink{box-shadow:0 0 0 0.125em rgba(140,170,238,0.25)}html.theme--catppuccin-frappe .is-link.textarea,html.theme--catppuccin-frappe .is-link.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-link{border-color:#8caaee}html.theme--catppuccin-frappe .is-link.textarea:focus,html.theme--catppuccin-frappe .is-link.input:focus,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-link:focus,html.theme--catppuccin-frappe .is-link.is-focused.textarea,html.theme--catppuccin-frappe .is-link.is-focused.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-frappe .is-link.textarea:active,html.theme--catppuccin-frappe .is-link.input:active,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-link:active,html.theme--catppuccin-frappe .is-link.is-active.textarea,html.theme--catppuccin-frappe .is-link.is-active.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(140,170,238,0.25)}html.theme--catppuccin-frappe .is-info.textarea,html.theme--catppuccin-frappe .is-info.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-info{border-color:#81c8be}html.theme--catppuccin-frappe .is-info.textarea:focus,html.theme--catppuccin-frappe .is-info.input:focus,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-info:focus,html.theme--catppuccin-frappe .is-info.is-focused.textarea,html.theme--catppuccin-frappe .is-info.is-focused.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-frappe .is-info.textarea:active,html.theme--catppuccin-frappe .is-info.input:active,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-info:active,html.theme--catppuccin-frappe .is-info.is-active.textarea,html.theme--catppuccin-frappe .is-info.is-active.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(129,200,190,0.25)}html.theme--catppuccin-frappe .is-success.textarea,html.theme--catppuccin-frappe .is-success.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-success{border-color:#a6d189}html.theme--catppuccin-frappe .is-success.textarea:focus,html.theme--catppuccin-frappe .is-success.input:focus,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-success:focus,html.theme--catppuccin-frappe .is-success.is-focused.textarea,html.theme--catppuccin-frappe .is-success.is-focused.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-frappe .is-success.textarea:active,html.theme--catppuccin-frappe .is-success.input:active,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-success:active,html.theme--catppuccin-frappe .is-success.is-active.textarea,html.theme--catppuccin-frappe .is-success.is-active.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(166,209,137,0.25)}html.theme--catppuccin-frappe .is-warning.textarea,html.theme--catppuccin-frappe .is-warning.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-warning{border-color:#e5c890}html.theme--catppuccin-frappe .is-warning.textarea:focus,html.theme--catppuccin-frappe .is-warning.input:focus,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-warning:focus,html.theme--catppuccin-frappe .is-warning.is-focused.textarea,html.theme--catppuccin-frappe .is-warning.is-focused.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-frappe .is-warning.textarea:active,html.theme--catppuccin-frappe .is-warning.input:active,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-warning:active,html.theme--catppuccin-frappe .is-warning.is-active.textarea,html.theme--catppuccin-frappe .is-warning.is-active.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(229,200,144,0.25)}html.theme--catppuccin-frappe .is-danger.textarea,html.theme--catppuccin-frappe .is-danger.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-danger{border-color:#e78284}html.theme--catppuccin-frappe .is-danger.textarea:focus,html.theme--catppuccin-frappe .is-danger.input:focus,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-danger:focus,html.theme--catppuccin-frappe .is-danger.is-focused.textarea,html.theme--catppuccin-frappe .is-danger.is-focused.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-frappe .is-danger.textarea:active,html.theme--catppuccin-frappe .is-danger.input:active,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-danger:active,html.theme--catppuccin-frappe .is-danger.is-active.textarea,html.theme--catppuccin-frappe .is-danger.is-active.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(231,130,132,0.25)}html.theme--catppuccin-frappe .is-small.textarea,html.theme--catppuccin-frappe .is-small.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input{border-radius:3px;font-size:.75rem}html.theme--catppuccin-frappe .is-medium.textarea,html.theme--catppuccin-frappe .is-medium.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-medium{font-size:1.25rem}html.theme--catppuccin-frappe .is-large.textarea,html.theme--catppuccin-frappe .is-large.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-large{font-size:1.5rem}html.theme--catppuccin-frappe .is-fullwidth.textarea,html.theme--catppuccin-frappe .is-fullwidth.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-fullwidth{display:block;width:100%}html.theme--catppuccin-frappe .is-inline.textarea,html.theme--catppuccin-frappe .is-inline.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-inline{display:inline;width:auto}html.theme--catppuccin-frappe .input.is-rounded,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input{border-radius:9999px;padding-left:calc(calc(0.75em - 1px) + 0.375em);padding-right:calc(calc(0.75em - 1px) + 0.375em)}html.theme--catppuccin-frappe .input.is-static,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-static{background-color:transparent;border-color:transparent;box-shadow:none;padding-left:0;padding-right:0}html.theme--catppuccin-frappe .textarea{display:block;max-width:100%;min-width:100%;padding:calc(0.75em - 1px);resize:vertical}html.theme--catppuccin-frappe .textarea:not([rows]){max-height:40em;min-height:8em}html.theme--catppuccin-frappe .textarea[rows]{height:initial}html.theme--catppuccin-frappe .textarea.has-fixed-size{resize:none}html.theme--catppuccin-frappe .radio,html.theme--catppuccin-frappe .checkbox{cursor:pointer;display:inline-block;line-height:1.25;position:relative}html.theme--catppuccin-frappe .radio input,html.theme--catppuccin-frappe .checkbox input{cursor:pointer}html.theme--catppuccin-frappe .radio:hover,html.theme--catppuccin-frappe .checkbox:hover{color:#99d1db}html.theme--catppuccin-frappe .radio[disabled],html.theme--catppuccin-frappe .checkbox[disabled],fieldset[disabled] html.theme--catppuccin-frappe .radio,fieldset[disabled] html.theme--catppuccin-frappe .checkbox,html.theme--catppuccin-frappe .radio input[disabled],html.theme--catppuccin-frappe .checkbox input[disabled]{color:#f1f4fd;cursor:not-allowed}html.theme--catppuccin-frappe .radio+.radio{margin-left:.5em}html.theme--catppuccin-frappe .select{display:inline-block;max-width:100%;position:relative;vertical-align:top}html.theme--catppuccin-frappe .select:not(.is-multiple){height:2.5em}html.theme--catppuccin-frappe .select:not(.is-multiple):not(.is-loading)::after{border-color:#8caaee;right:1.125em;z-index:4}html.theme--catppuccin-frappe .select.is-rounded select,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.select select{border-radius:9999px;padding-left:1em}html.theme--catppuccin-frappe .select select{cursor:pointer;display:block;font-size:1em;max-width:100%;outline:none}html.theme--catppuccin-frappe .select select::-ms-expand{display:none}html.theme--catppuccin-frappe .select select[disabled]:hover,fieldset[disabled] html.theme--catppuccin-frappe .select select:hover{border-color:#292c3c}html.theme--catppuccin-frappe .select select:not([multiple]){padding-right:2.5em}html.theme--catppuccin-frappe .select select[multiple]{height:auto;padding:0}html.theme--catppuccin-frappe .select select[multiple] option{padding:0.5em 1em}html.theme--catppuccin-frappe .select:not(.is-multiple):not(.is-loading):hover::after{border-color:#99d1db}html.theme--catppuccin-frappe .select.is-white:not(:hover)::after{border-color:#fff}html.theme--catppuccin-frappe .select.is-white select{border-color:#fff}html.theme--catppuccin-frappe .select.is-white select:hover,html.theme--catppuccin-frappe .select.is-white select.is-hovered{border-color:#f2f2f2}html.theme--catppuccin-frappe .select.is-white select:focus,html.theme--catppuccin-frappe .select.is-white select.is-focused,html.theme--catppuccin-frappe .select.is-white select:active,html.theme--catppuccin-frappe .select.is-white select.is-active{box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}html.theme--catppuccin-frappe .select.is-black:not(:hover)::after{border-color:#0a0a0a}html.theme--catppuccin-frappe .select.is-black select{border-color:#0a0a0a}html.theme--catppuccin-frappe .select.is-black select:hover,html.theme--catppuccin-frappe .select.is-black select.is-hovered{border-color:#000}html.theme--catppuccin-frappe .select.is-black select:focus,html.theme--catppuccin-frappe .select.is-black select.is-focused,html.theme--catppuccin-frappe .select.is-black select:active,html.theme--catppuccin-frappe .select.is-black select.is-active{box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}html.theme--catppuccin-frappe .select.is-light:not(:hover)::after{border-color:#f5f5f5}html.theme--catppuccin-frappe .select.is-light select{border-color:#f5f5f5}html.theme--catppuccin-frappe .select.is-light select:hover,html.theme--catppuccin-frappe .select.is-light select.is-hovered{border-color:#e8e8e8}html.theme--catppuccin-frappe .select.is-light select:focus,html.theme--catppuccin-frappe .select.is-light select.is-focused,html.theme--catppuccin-frappe .select.is-light select:active,html.theme--catppuccin-frappe .select.is-light select.is-active{box-shadow:0 0 0 0.125em rgba(245,245,245,0.25)}html.theme--catppuccin-frappe .select.is-dark:not(:hover)::after,html.theme--catppuccin-frappe .content kbd.select:not(:hover)::after{border-color:#414559}html.theme--catppuccin-frappe .select.is-dark select,html.theme--catppuccin-frappe .content kbd.select select{border-color:#414559}html.theme--catppuccin-frappe .select.is-dark select:hover,html.theme--catppuccin-frappe .content kbd.select select:hover,html.theme--catppuccin-frappe .select.is-dark select.is-hovered,html.theme--catppuccin-frappe .content kbd.select select.is-hovered{border-color:#363a4a}html.theme--catppuccin-frappe .select.is-dark select:focus,html.theme--catppuccin-frappe .content kbd.select select:focus,html.theme--catppuccin-frappe .select.is-dark select.is-focused,html.theme--catppuccin-frappe .content kbd.select select.is-focused,html.theme--catppuccin-frappe .select.is-dark select:active,html.theme--catppuccin-frappe .content kbd.select select:active,html.theme--catppuccin-frappe .select.is-dark select.is-active,html.theme--catppuccin-frappe .content kbd.select select.is-active{box-shadow:0 0 0 0.125em rgba(65,69,89,0.25)}html.theme--catppuccin-frappe .select.is-primary:not(:hover)::after,html.theme--catppuccin-frappe .docstring>section>a.select.docs-sourcelink:not(:hover)::after{border-color:#8caaee}html.theme--catppuccin-frappe .select.is-primary select,html.theme--catppuccin-frappe .docstring>section>a.select.docs-sourcelink select{border-color:#8caaee}html.theme--catppuccin-frappe .select.is-primary select:hover,html.theme--catppuccin-frappe .docstring>section>a.select.docs-sourcelink select:hover,html.theme--catppuccin-frappe .select.is-primary select.is-hovered,html.theme--catppuccin-frappe .docstring>section>a.select.docs-sourcelink select.is-hovered{border-color:#769aeb}html.theme--catppuccin-frappe .select.is-primary select:focus,html.theme--catppuccin-frappe .docstring>section>a.select.docs-sourcelink select:focus,html.theme--catppuccin-frappe .select.is-primary select.is-focused,html.theme--catppuccin-frappe .docstring>section>a.select.docs-sourcelink select.is-focused,html.theme--catppuccin-frappe .select.is-primary select:active,html.theme--catppuccin-frappe .docstring>section>a.select.docs-sourcelink select:active,html.theme--catppuccin-frappe .select.is-primary select.is-active,html.theme--catppuccin-frappe .docstring>section>a.select.docs-sourcelink select.is-active{box-shadow:0 0 0 0.125em rgba(140,170,238,0.25)}html.theme--catppuccin-frappe .select.is-link:not(:hover)::after{border-color:#8caaee}html.theme--catppuccin-frappe .select.is-link select{border-color:#8caaee}html.theme--catppuccin-frappe .select.is-link select:hover,html.theme--catppuccin-frappe .select.is-link select.is-hovered{border-color:#769aeb}html.theme--catppuccin-frappe .select.is-link select:focus,html.theme--catppuccin-frappe .select.is-link select.is-focused,html.theme--catppuccin-frappe .select.is-link select:active,html.theme--catppuccin-frappe .select.is-link select.is-active{box-shadow:0 0 0 0.125em rgba(140,170,238,0.25)}html.theme--catppuccin-frappe .select.is-info:not(:hover)::after{border-color:#81c8be}html.theme--catppuccin-frappe .select.is-info select{border-color:#81c8be}html.theme--catppuccin-frappe .select.is-info select:hover,html.theme--catppuccin-frappe .select.is-info select.is-hovered{border-color:#6fc0b5}html.theme--catppuccin-frappe .select.is-info select:focus,html.theme--catppuccin-frappe .select.is-info select.is-focused,html.theme--catppuccin-frappe .select.is-info select:active,html.theme--catppuccin-frappe .select.is-info select.is-active{box-shadow:0 0 0 0.125em rgba(129,200,190,0.25)}html.theme--catppuccin-frappe .select.is-success:not(:hover)::after{border-color:#a6d189}html.theme--catppuccin-frappe .select.is-success select{border-color:#a6d189}html.theme--catppuccin-frappe .select.is-success select:hover,html.theme--catppuccin-frappe .select.is-success select.is-hovered{border-color:#98ca77}html.theme--catppuccin-frappe .select.is-success select:focus,html.theme--catppuccin-frappe .select.is-success select.is-focused,html.theme--catppuccin-frappe .select.is-success select:active,html.theme--catppuccin-frappe .select.is-success select.is-active{box-shadow:0 0 0 0.125em rgba(166,209,137,0.25)}html.theme--catppuccin-frappe .select.is-warning:not(:hover)::after{border-color:#e5c890}html.theme--catppuccin-frappe .select.is-warning select{border-color:#e5c890}html.theme--catppuccin-frappe .select.is-warning select:hover,html.theme--catppuccin-frappe .select.is-warning select.is-hovered{border-color:#e0be7b}html.theme--catppuccin-frappe .select.is-warning select:focus,html.theme--catppuccin-frappe .select.is-warning select.is-focused,html.theme--catppuccin-frappe .select.is-warning select:active,html.theme--catppuccin-frappe .select.is-warning select.is-active{box-shadow:0 0 0 0.125em rgba(229,200,144,0.25)}html.theme--catppuccin-frappe .select.is-danger:not(:hover)::after{border-color:#e78284}html.theme--catppuccin-frappe .select.is-danger select{border-color:#e78284}html.theme--catppuccin-frappe .select.is-danger select:hover,html.theme--catppuccin-frappe .select.is-danger select.is-hovered{border-color:#e36d6f}html.theme--catppuccin-frappe .select.is-danger select:focus,html.theme--catppuccin-frappe .select.is-danger select.is-focused,html.theme--catppuccin-frappe .select.is-danger select:active,html.theme--catppuccin-frappe .select.is-danger select.is-active{box-shadow:0 0 0 0.125em rgba(231,130,132,0.25)}html.theme--catppuccin-frappe .select.is-small,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.select{border-radius:3px;font-size:.75rem}html.theme--catppuccin-frappe .select.is-medium{font-size:1.25rem}html.theme--catppuccin-frappe .select.is-large{font-size:1.5rem}html.theme--catppuccin-frappe .select.is-disabled::after{border-color:#f1f4fd !important;opacity:0.5}html.theme--catppuccin-frappe .select.is-fullwidth{width:100%}html.theme--catppuccin-frappe .select.is-fullwidth select{width:100%}html.theme--catppuccin-frappe .select.is-loading::after{margin-top:0;position:absolute;right:.625em;top:0.625em;transform:none}html.theme--catppuccin-frappe .select.is-loading.is-small:after,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-loading:after{font-size:.75rem}html.theme--catppuccin-frappe .select.is-loading.is-medium:after{font-size:1.25rem}html.theme--catppuccin-frappe .select.is-loading.is-large:after{font-size:1.5rem}html.theme--catppuccin-frappe .file{align-items:stretch;display:flex;justify-content:flex-start;position:relative}html.theme--catppuccin-frappe .file.is-white .file-cta{background-color:#fff;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-frappe .file.is-white:hover .file-cta,html.theme--catppuccin-frappe .file.is-white.is-hovered .file-cta{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-frappe .file.is-white:focus .file-cta,html.theme--catppuccin-frappe .file.is-white.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(255,255,255,0.25);color:#0a0a0a}html.theme--catppuccin-frappe .file.is-white:active .file-cta,html.theme--catppuccin-frappe .file.is-white.is-active .file-cta{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-frappe .file.is-black .file-cta{background-color:#0a0a0a;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .file.is-black:hover .file-cta,html.theme--catppuccin-frappe .file.is-black.is-hovered .file-cta{background-color:#040404;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .file.is-black:focus .file-cta,html.theme--catppuccin-frappe .file.is-black.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(10,10,10,0.25);color:#fff}html.theme--catppuccin-frappe .file.is-black:active .file-cta,html.theme--catppuccin-frappe .file.is-black.is-active .file-cta{background-color:#000;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .file.is-light .file-cta{background-color:#f5f5f5;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .file.is-light:hover .file-cta,html.theme--catppuccin-frappe .file.is-light.is-hovered .file-cta{background-color:#eee;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .file.is-light:focus .file-cta,html.theme--catppuccin-frappe .file.is-light.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(245,245,245,0.25);color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .file.is-light:active .file-cta,html.theme--catppuccin-frappe .file.is-light.is-active .file-cta{background-color:#e8e8e8;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .file.is-dark .file-cta,html.theme--catppuccin-frappe .content kbd.file .file-cta{background-color:#414559;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .file.is-dark:hover .file-cta,html.theme--catppuccin-frappe .content kbd.file:hover .file-cta,html.theme--catppuccin-frappe .file.is-dark.is-hovered .file-cta,html.theme--catppuccin-frappe .content kbd.file.is-hovered .file-cta{background-color:#3c3f52;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .file.is-dark:focus .file-cta,html.theme--catppuccin-frappe .content kbd.file:focus .file-cta,html.theme--catppuccin-frappe .file.is-dark.is-focused .file-cta,html.theme--catppuccin-frappe .content kbd.file.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(65,69,89,0.25);color:#fff}html.theme--catppuccin-frappe .file.is-dark:active .file-cta,html.theme--catppuccin-frappe .content kbd.file:active .file-cta,html.theme--catppuccin-frappe .file.is-dark.is-active .file-cta,html.theme--catppuccin-frappe .content kbd.file.is-active .file-cta{background-color:#363a4a;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .file.is-primary .file-cta,html.theme--catppuccin-frappe .docstring>section>a.file.docs-sourcelink .file-cta{background-color:#8caaee;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .file.is-primary:hover .file-cta,html.theme--catppuccin-frappe .docstring>section>a.file.docs-sourcelink:hover .file-cta,html.theme--catppuccin-frappe .file.is-primary.is-hovered .file-cta,html.theme--catppuccin-frappe .docstring>section>a.file.is-hovered.docs-sourcelink .file-cta{background-color:#81a2ec;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .file.is-primary:focus .file-cta,html.theme--catppuccin-frappe .docstring>section>a.file.docs-sourcelink:focus .file-cta,html.theme--catppuccin-frappe .file.is-primary.is-focused .file-cta,html.theme--catppuccin-frappe .docstring>section>a.file.is-focused.docs-sourcelink .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(140,170,238,0.25);color:#fff}html.theme--catppuccin-frappe .file.is-primary:active .file-cta,html.theme--catppuccin-frappe .docstring>section>a.file.docs-sourcelink:active .file-cta,html.theme--catppuccin-frappe .file.is-primary.is-active .file-cta,html.theme--catppuccin-frappe .docstring>section>a.file.is-active.docs-sourcelink .file-cta{background-color:#769aeb;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .file.is-link .file-cta{background-color:#8caaee;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .file.is-link:hover .file-cta,html.theme--catppuccin-frappe .file.is-link.is-hovered .file-cta{background-color:#81a2ec;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .file.is-link:focus .file-cta,html.theme--catppuccin-frappe .file.is-link.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(140,170,238,0.25);color:#fff}html.theme--catppuccin-frappe .file.is-link:active .file-cta,html.theme--catppuccin-frappe .file.is-link.is-active .file-cta{background-color:#769aeb;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .file.is-info .file-cta{background-color:#81c8be;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .file.is-info:hover .file-cta,html.theme--catppuccin-frappe .file.is-info.is-hovered .file-cta{background-color:#78c4b9;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .file.is-info:focus .file-cta,html.theme--catppuccin-frappe .file.is-info.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(129,200,190,0.25);color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .file.is-info:active .file-cta,html.theme--catppuccin-frappe .file.is-info.is-active .file-cta{background-color:#6fc0b5;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .file.is-success .file-cta{background-color:#a6d189;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .file.is-success:hover .file-cta,html.theme--catppuccin-frappe .file.is-success.is-hovered .file-cta{background-color:#9fcd80;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .file.is-success:focus .file-cta,html.theme--catppuccin-frappe .file.is-success.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(166,209,137,0.25);color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .file.is-success:active .file-cta,html.theme--catppuccin-frappe .file.is-success.is-active .file-cta{background-color:#98ca77;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .file.is-warning .file-cta{background-color:#e5c890;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .file.is-warning:hover .file-cta,html.theme--catppuccin-frappe .file.is-warning.is-hovered .file-cta{background-color:#e3c386;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .file.is-warning:focus .file-cta,html.theme--catppuccin-frappe .file.is-warning.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(229,200,144,0.25);color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .file.is-warning:active .file-cta,html.theme--catppuccin-frappe .file.is-warning.is-active .file-cta{background-color:#e0be7b;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .file.is-danger .file-cta{background-color:#e78284;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .file.is-danger:hover .file-cta,html.theme--catppuccin-frappe .file.is-danger.is-hovered .file-cta{background-color:#e57779;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .file.is-danger:focus .file-cta,html.theme--catppuccin-frappe .file.is-danger.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(231,130,132,0.25);color:#fff}html.theme--catppuccin-frappe .file.is-danger:active .file-cta,html.theme--catppuccin-frappe .file.is-danger.is-active .file-cta{background-color:#e36d6f;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .file.is-small,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.file{font-size:.75rem}html.theme--catppuccin-frappe .file.is-normal{font-size:1rem}html.theme--catppuccin-frappe .file.is-medium{font-size:1.25rem}html.theme--catppuccin-frappe .file.is-medium .file-icon .fa{font-size:21px}html.theme--catppuccin-frappe .file.is-large{font-size:1.5rem}html.theme--catppuccin-frappe .file.is-large .file-icon .fa{font-size:28px}html.theme--catppuccin-frappe .file.has-name .file-cta{border-bottom-right-radius:0;border-top-right-radius:0}html.theme--catppuccin-frappe .file.has-name .file-name{border-bottom-left-radius:0;border-top-left-radius:0}html.theme--catppuccin-frappe .file.has-name.is-empty .file-cta{border-radius:.4em}html.theme--catppuccin-frappe .file.has-name.is-empty .file-name{display:none}html.theme--catppuccin-frappe .file.is-boxed .file-label{flex-direction:column}html.theme--catppuccin-frappe .file.is-boxed .file-cta{flex-direction:column;height:auto;padding:1em 3em}html.theme--catppuccin-frappe .file.is-boxed .file-name{border-width:0 1px 1px}html.theme--catppuccin-frappe .file.is-boxed .file-icon{height:1.5em;width:1.5em}html.theme--catppuccin-frappe .file.is-boxed .file-icon .fa{font-size:21px}html.theme--catppuccin-frappe .file.is-boxed.is-small .file-icon .fa,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-boxed .file-icon .fa{font-size:14px}html.theme--catppuccin-frappe .file.is-boxed.is-medium .file-icon .fa{font-size:28px}html.theme--catppuccin-frappe .file.is-boxed.is-large .file-icon .fa{font-size:35px}html.theme--catppuccin-frappe .file.is-boxed.has-name .file-cta{border-radius:.4em .4em 0 0}html.theme--catppuccin-frappe .file.is-boxed.has-name .file-name{border-radius:0 0 .4em .4em;border-width:0 1px 1px}html.theme--catppuccin-frappe .file.is-centered{justify-content:center}html.theme--catppuccin-frappe .file.is-fullwidth .file-label{width:100%}html.theme--catppuccin-frappe .file.is-fullwidth .file-name{flex-grow:1;max-width:none}html.theme--catppuccin-frappe .file.is-right{justify-content:flex-end}html.theme--catppuccin-frappe .file.is-right .file-cta{border-radius:0 .4em .4em 0}html.theme--catppuccin-frappe .file.is-right .file-name{border-radius:.4em 0 0 .4em;border-width:1px 0 1px 1px;order:-1}html.theme--catppuccin-frappe .file-label{align-items:stretch;display:flex;cursor:pointer;justify-content:flex-start;overflow:hidden;position:relative}html.theme--catppuccin-frappe .file-label:hover .file-cta{background-color:#3c3f52;color:#b0bef1}html.theme--catppuccin-frappe .file-label:hover .file-name{border-color:#5c6279}html.theme--catppuccin-frappe .file-label:active .file-cta{background-color:#363a4a;color:#b0bef1}html.theme--catppuccin-frappe .file-label:active .file-name{border-color:#575c72}html.theme--catppuccin-frappe .file-input{height:100%;left:0;opacity:0;outline:none;position:absolute;top:0;width:100%}html.theme--catppuccin-frappe .file-cta,html.theme--catppuccin-frappe .file-name{border-color:#626880;border-radius:.4em;font-size:1em;padding-left:1em;padding-right:1em;white-space:nowrap}html.theme--catppuccin-frappe .file-cta{background-color:#414559;color:#c6d0f5}html.theme--catppuccin-frappe .file-name{border-color:#626880;border-style:solid;border-width:1px 1px 1px 0;display:block;max-width:16em;overflow:hidden;text-align:inherit;text-overflow:ellipsis}html.theme--catppuccin-frappe .file-icon{align-items:center;display:flex;height:1em;justify-content:center;margin-right:.5em;width:1em}html.theme--catppuccin-frappe .file-icon .fa{font-size:14px}html.theme--catppuccin-frappe .label{color:#b0bef1;display:block;font-size:1rem;font-weight:700}html.theme--catppuccin-frappe .label:not(:last-child){margin-bottom:0.5em}html.theme--catppuccin-frappe .label.is-small,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.label{font-size:.75rem}html.theme--catppuccin-frappe .label.is-medium{font-size:1.25rem}html.theme--catppuccin-frappe .label.is-large{font-size:1.5rem}html.theme--catppuccin-frappe .help{display:block;font-size:.75rem;margin-top:0.25rem}html.theme--catppuccin-frappe .help.is-white{color:#fff}html.theme--catppuccin-frappe .help.is-black{color:#0a0a0a}html.theme--catppuccin-frappe .help.is-light{color:#f5f5f5}html.theme--catppuccin-frappe .help.is-dark,html.theme--catppuccin-frappe .content kbd.help{color:#414559}html.theme--catppuccin-frappe .help.is-primary,html.theme--catppuccin-frappe .docstring>section>a.help.docs-sourcelink{color:#8caaee}html.theme--catppuccin-frappe .help.is-link{color:#8caaee}html.theme--catppuccin-frappe .help.is-info{color:#81c8be}html.theme--catppuccin-frappe .help.is-success{color:#a6d189}html.theme--catppuccin-frappe .help.is-warning{color:#e5c890}html.theme--catppuccin-frappe .help.is-danger{color:#e78284}html.theme--catppuccin-frappe .field:not(:last-child){margin-bottom:0.75rem}html.theme--catppuccin-frappe .field.has-addons{display:flex;justify-content:flex-start}html.theme--catppuccin-frappe .field.has-addons .control:not(:last-child){margin-right:-1px}html.theme--catppuccin-frappe .field.has-addons .control:not(:first-child):not(:last-child) .button,html.theme--catppuccin-frappe .field.has-addons .control:not(:first-child):not(:last-child) .input,html.theme--catppuccin-frappe .field.has-addons .control:not(:first-child):not(:last-child) #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-frappe #documenter .docs-sidebar .field.has-addons .control:not(:first-child):not(:last-child) form.docs-search>input,html.theme--catppuccin-frappe .field.has-addons .control:not(:first-child):not(:last-child) .select select{border-radius:0}html.theme--catppuccin-frappe .field.has-addons .control:first-child:not(:only-child) .button,html.theme--catppuccin-frappe .field.has-addons .control:first-child:not(:only-child) .input,html.theme--catppuccin-frappe .field.has-addons .control:first-child:not(:only-child) #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-frappe #documenter .docs-sidebar .field.has-addons .control:first-child:not(:only-child) form.docs-search>input,html.theme--catppuccin-frappe .field.has-addons .control:first-child:not(:only-child) .select select{border-bottom-right-radius:0;border-top-right-radius:0}html.theme--catppuccin-frappe .field.has-addons .control:last-child:not(:only-child) .button,html.theme--catppuccin-frappe .field.has-addons .control:last-child:not(:only-child) .input,html.theme--catppuccin-frappe .field.has-addons .control:last-child:not(:only-child) #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-frappe #documenter .docs-sidebar .field.has-addons .control:last-child:not(:only-child) form.docs-search>input,html.theme--catppuccin-frappe .field.has-addons .control:last-child:not(:only-child) .select select{border-bottom-left-radius:0;border-top-left-radius:0}html.theme--catppuccin-frappe .field.has-addons .control .button:not([disabled]):hover,html.theme--catppuccin-frappe .field.has-addons .control .button.is-hovered:not([disabled]),html.theme--catppuccin-frappe .field.has-addons .control .input:not([disabled]):hover,html.theme--catppuccin-frappe .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):hover,html.theme--catppuccin-frappe #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):hover,html.theme--catppuccin-frappe .field.has-addons .control .input.is-hovered:not([disabled]),html.theme--catppuccin-frappe .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-hovered:not([disabled]),html.theme--catppuccin-frappe #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-hovered:not([disabled]),html.theme--catppuccin-frappe .field.has-addons .control .select select:not([disabled]):hover,html.theme--catppuccin-frappe .field.has-addons .control .select select.is-hovered:not([disabled]){z-index:2}html.theme--catppuccin-frappe .field.has-addons .control .button:not([disabled]):focus,html.theme--catppuccin-frappe .field.has-addons .control .button.is-focused:not([disabled]),html.theme--catppuccin-frappe .field.has-addons .control .button:not([disabled]):active,html.theme--catppuccin-frappe .field.has-addons .control .button.is-active:not([disabled]),html.theme--catppuccin-frappe .field.has-addons .control .input:not([disabled]):focus,html.theme--catppuccin-frappe .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):focus,html.theme--catppuccin-frappe #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):focus,html.theme--catppuccin-frappe .field.has-addons .control .input.is-focused:not([disabled]),html.theme--catppuccin-frappe .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-focused:not([disabled]),html.theme--catppuccin-frappe #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-focused:not([disabled]),html.theme--catppuccin-frappe .field.has-addons .control .input:not([disabled]):active,html.theme--catppuccin-frappe .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):active,html.theme--catppuccin-frappe #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):active,html.theme--catppuccin-frappe .field.has-addons .control .input.is-active:not([disabled]),html.theme--catppuccin-frappe .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-active:not([disabled]),html.theme--catppuccin-frappe #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-active:not([disabled]),html.theme--catppuccin-frappe .field.has-addons .control .select select:not([disabled]):focus,html.theme--catppuccin-frappe .field.has-addons .control .select select.is-focused:not([disabled]),html.theme--catppuccin-frappe .field.has-addons .control .select select:not([disabled]):active,html.theme--catppuccin-frappe .field.has-addons .control .select select.is-active:not([disabled]){z-index:3}html.theme--catppuccin-frappe .field.has-addons .control .button:not([disabled]):focus:hover,html.theme--catppuccin-frappe .field.has-addons .control .button.is-focused:not([disabled]):hover,html.theme--catppuccin-frappe .field.has-addons .control .button:not([disabled]):active:hover,html.theme--catppuccin-frappe .field.has-addons .control .button.is-active:not([disabled]):hover,html.theme--catppuccin-frappe .field.has-addons .control .input:not([disabled]):focus:hover,html.theme--catppuccin-frappe .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):focus:hover,html.theme--catppuccin-frappe #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):focus:hover,html.theme--catppuccin-frappe .field.has-addons .control .input.is-focused:not([disabled]):hover,html.theme--catppuccin-frappe .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-focused:not([disabled]):hover,html.theme--catppuccin-frappe #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-focused:not([disabled]):hover,html.theme--catppuccin-frappe .field.has-addons .control .input:not([disabled]):active:hover,html.theme--catppuccin-frappe .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):active:hover,html.theme--catppuccin-frappe #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):active:hover,html.theme--catppuccin-frappe .field.has-addons .control .input.is-active:not([disabled]):hover,html.theme--catppuccin-frappe .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-active:not([disabled]):hover,html.theme--catppuccin-frappe #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-active:not([disabled]):hover,html.theme--catppuccin-frappe .field.has-addons .control .select select:not([disabled]):focus:hover,html.theme--catppuccin-frappe .field.has-addons .control .select select.is-focused:not([disabled]):hover,html.theme--catppuccin-frappe .field.has-addons .control .select select:not([disabled]):active:hover,html.theme--catppuccin-frappe .field.has-addons .control .select select.is-active:not([disabled]):hover{z-index:4}html.theme--catppuccin-frappe .field.has-addons .control.is-expanded{flex-grow:1;flex-shrink:1}html.theme--catppuccin-frappe .field.has-addons.has-addons-centered{justify-content:center}html.theme--catppuccin-frappe .field.has-addons.has-addons-right{justify-content:flex-end}html.theme--catppuccin-frappe .field.has-addons.has-addons-fullwidth .control{flex-grow:1;flex-shrink:0}html.theme--catppuccin-frappe .field.is-grouped{display:flex;justify-content:flex-start}html.theme--catppuccin-frappe .field.is-grouped>.control{flex-shrink:0}html.theme--catppuccin-frappe .field.is-grouped>.control:not(:last-child){margin-bottom:0;margin-right:.75rem}html.theme--catppuccin-frappe .field.is-grouped>.control.is-expanded{flex-grow:1;flex-shrink:1}html.theme--catppuccin-frappe .field.is-grouped.is-grouped-centered{justify-content:center}html.theme--catppuccin-frappe .field.is-grouped.is-grouped-right{justify-content:flex-end}html.theme--catppuccin-frappe .field.is-grouped.is-grouped-multiline{flex-wrap:wrap}html.theme--catppuccin-frappe .field.is-grouped.is-grouped-multiline>.control:last-child,html.theme--catppuccin-frappe .field.is-grouped.is-grouped-multiline>.control:not(:last-child){margin-bottom:0.75rem}html.theme--catppuccin-frappe .field.is-grouped.is-grouped-multiline:last-child{margin-bottom:-0.75rem}html.theme--catppuccin-frappe .field.is-grouped.is-grouped-multiline:not(:last-child){margin-bottom:0}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .field.is-horizontal{display:flex}}html.theme--catppuccin-frappe .field-label .label{font-size:inherit}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .field-label{margin-bottom:0.5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .field-label{flex-basis:0;flex-grow:1;flex-shrink:0;margin-right:1.5rem;text-align:right}html.theme--catppuccin-frappe .field-label.is-small,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.field-label{font-size:.75rem;padding-top:0.375em}html.theme--catppuccin-frappe .field-label.is-normal{padding-top:0.375em}html.theme--catppuccin-frappe .field-label.is-medium{font-size:1.25rem;padding-top:0.375em}html.theme--catppuccin-frappe .field-label.is-large{font-size:1.5rem;padding-top:0.375em}}html.theme--catppuccin-frappe .field-body .field .field{margin-bottom:0}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .field-body{display:flex;flex-basis:0;flex-grow:5;flex-shrink:1}html.theme--catppuccin-frappe .field-body .field{margin-bottom:0}html.theme--catppuccin-frappe .field-body>.field{flex-shrink:1}html.theme--catppuccin-frappe .field-body>.field:not(.is-narrow){flex-grow:1}html.theme--catppuccin-frappe .field-body>.field:not(:last-child){margin-right:.75rem}}html.theme--catppuccin-frappe .control{box-sizing:border-box;clear:both;font-size:1rem;position:relative;text-align:inherit}html.theme--catppuccin-frappe .control.has-icons-left .input:focus~.icon,html.theme--catppuccin-frappe .control.has-icons-left #documenter .docs-sidebar form.docs-search>input:focus~.icon,html.theme--catppuccin-frappe #documenter .docs-sidebar .control.has-icons-left form.docs-search>input:focus~.icon,html.theme--catppuccin-frappe .control.has-icons-left .select:focus~.icon,html.theme--catppuccin-frappe .control.has-icons-right .input:focus~.icon,html.theme--catppuccin-frappe .control.has-icons-right #documenter .docs-sidebar form.docs-search>input:focus~.icon,html.theme--catppuccin-frappe #documenter .docs-sidebar .control.has-icons-right form.docs-search>input:focus~.icon,html.theme--catppuccin-frappe .control.has-icons-right .select:focus~.icon{color:#414559}html.theme--catppuccin-frappe .control.has-icons-left .input.is-small~.icon,html.theme--catppuccin-frappe .control.has-icons-left #documenter .docs-sidebar form.docs-search>input~.icon,html.theme--catppuccin-frappe #documenter .docs-sidebar .control.has-icons-left form.docs-search>input~.icon,html.theme--catppuccin-frappe .control.has-icons-left .select.is-small~.icon,html.theme--catppuccin-frappe .control.has-icons-right .input.is-small~.icon,html.theme--catppuccin-frappe .control.has-icons-right #documenter .docs-sidebar form.docs-search>input~.icon,html.theme--catppuccin-frappe #documenter .docs-sidebar .control.has-icons-right form.docs-search>input~.icon,html.theme--catppuccin-frappe .control.has-icons-right .select.is-small~.icon{font-size:.75rem}html.theme--catppuccin-frappe .control.has-icons-left .input.is-medium~.icon,html.theme--catppuccin-frappe .control.has-icons-left #documenter .docs-sidebar form.docs-search>input.is-medium~.icon,html.theme--catppuccin-frappe #documenter .docs-sidebar .control.has-icons-left form.docs-search>input.is-medium~.icon,html.theme--catppuccin-frappe .control.has-icons-left .select.is-medium~.icon,html.theme--catppuccin-frappe .control.has-icons-right .input.is-medium~.icon,html.theme--catppuccin-frappe .control.has-icons-right #documenter .docs-sidebar form.docs-search>input.is-medium~.icon,html.theme--catppuccin-frappe #documenter .docs-sidebar .control.has-icons-right form.docs-search>input.is-medium~.icon,html.theme--catppuccin-frappe .control.has-icons-right .select.is-medium~.icon{font-size:1.25rem}html.theme--catppuccin-frappe .control.has-icons-left .input.is-large~.icon,html.theme--catppuccin-frappe .control.has-icons-left #documenter .docs-sidebar form.docs-search>input.is-large~.icon,html.theme--catppuccin-frappe #documenter .docs-sidebar .control.has-icons-left form.docs-search>input.is-large~.icon,html.theme--catppuccin-frappe .control.has-icons-left .select.is-large~.icon,html.theme--catppuccin-frappe .control.has-icons-right .input.is-large~.icon,html.theme--catppuccin-frappe .control.has-icons-right #documenter .docs-sidebar form.docs-search>input.is-large~.icon,html.theme--catppuccin-frappe #documenter .docs-sidebar .control.has-icons-right form.docs-search>input.is-large~.icon,html.theme--catppuccin-frappe .control.has-icons-right .select.is-large~.icon{font-size:1.5rem}html.theme--catppuccin-frappe .control.has-icons-left .icon,html.theme--catppuccin-frappe .control.has-icons-right .icon{color:#626880;height:2.5em;pointer-events:none;position:absolute;top:0;width:2.5em;z-index:4}html.theme--catppuccin-frappe .control.has-icons-left .input,html.theme--catppuccin-frappe .control.has-icons-left #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-frappe #documenter .docs-sidebar .control.has-icons-left form.docs-search>input,html.theme--catppuccin-frappe .control.has-icons-left .select select{padding-left:2.5em}html.theme--catppuccin-frappe .control.has-icons-left .icon.is-left{left:0}html.theme--catppuccin-frappe .control.has-icons-right .input,html.theme--catppuccin-frappe .control.has-icons-right #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-frappe #documenter .docs-sidebar .control.has-icons-right form.docs-search>input,html.theme--catppuccin-frappe .control.has-icons-right .select select{padding-right:2.5em}html.theme--catppuccin-frappe .control.has-icons-right .icon.is-right{right:0}html.theme--catppuccin-frappe .control.is-loading::after{position:absolute !important;right:.625em;top:0.625em;z-index:4}html.theme--catppuccin-frappe .control.is-loading.is-small:after,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-loading:after{font-size:.75rem}html.theme--catppuccin-frappe .control.is-loading.is-medium:after{font-size:1.25rem}html.theme--catppuccin-frappe .control.is-loading.is-large:after{font-size:1.5rem}html.theme--catppuccin-frappe .breadcrumb{font-size:1rem;white-space:nowrap}html.theme--catppuccin-frappe .breadcrumb a{align-items:center;color:#8caaee;display:flex;justify-content:center;padding:0 .75em}html.theme--catppuccin-frappe .breadcrumb a:hover{color:#99d1db}html.theme--catppuccin-frappe .breadcrumb li{align-items:center;display:flex}html.theme--catppuccin-frappe .breadcrumb li:first-child a{padding-left:0}html.theme--catppuccin-frappe .breadcrumb li.is-active a{color:#b0bef1;cursor:default;pointer-events:none}html.theme--catppuccin-frappe .breadcrumb li+li::before{color:#737994;content:"\0002f"}html.theme--catppuccin-frappe .breadcrumb ul,html.theme--catppuccin-frappe .breadcrumb ol{align-items:flex-start;display:flex;flex-wrap:wrap;justify-content:flex-start}html.theme--catppuccin-frappe .breadcrumb .icon:first-child{margin-right:.5em}html.theme--catppuccin-frappe .breadcrumb .icon:last-child{margin-left:.5em}html.theme--catppuccin-frappe .breadcrumb.is-centered ol,html.theme--catppuccin-frappe .breadcrumb.is-centered ul{justify-content:center}html.theme--catppuccin-frappe .breadcrumb.is-right ol,html.theme--catppuccin-frappe .breadcrumb.is-right ul{justify-content:flex-end}html.theme--catppuccin-frappe .breadcrumb.is-small,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.breadcrumb{font-size:.75rem}html.theme--catppuccin-frappe .breadcrumb.is-medium{font-size:1.25rem}html.theme--catppuccin-frappe .breadcrumb.is-large{font-size:1.5rem}html.theme--catppuccin-frappe .breadcrumb.has-arrow-separator li+li::before{content:"\02192"}html.theme--catppuccin-frappe .breadcrumb.has-bullet-separator li+li::before{content:"\02022"}html.theme--catppuccin-frappe .breadcrumb.has-dot-separator li+li::before{content:"\000b7"}html.theme--catppuccin-frappe .breadcrumb.has-succeeds-separator li+li::before{content:"\0227B"}html.theme--catppuccin-frappe .card{background-color:#fff;border-radius:.25rem;box-shadow:#171717;color:#c6d0f5;max-width:100%;position:relative}html.theme--catppuccin-frappe .card-footer:first-child,html.theme--catppuccin-frappe .card-content:first-child,html.theme--catppuccin-frappe .card-header:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}html.theme--catppuccin-frappe .card-footer:last-child,html.theme--catppuccin-frappe .card-content:last-child,html.theme--catppuccin-frappe .card-header:last-child{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}html.theme--catppuccin-frappe .card-header{background-color:rgba(0,0,0,0);align-items:stretch;box-shadow:0 0.125em 0.25em rgba(10,10,10,0.1);display:flex}html.theme--catppuccin-frappe .card-header-title{align-items:center;color:#b0bef1;display:flex;flex-grow:1;font-weight:700;padding:0.75rem 1rem}html.theme--catppuccin-frappe .card-header-title.is-centered{justify-content:center}html.theme--catppuccin-frappe .card-header-icon{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;color:currentColor;font-family:inherit;font-size:1em;margin:0;padding:0;align-items:center;cursor:pointer;display:flex;justify-content:center;padding:0.75rem 1rem}html.theme--catppuccin-frappe .card-image{display:block;position:relative}html.theme--catppuccin-frappe .card-image:first-child img{border-top-left-radius:.25rem;border-top-right-radius:.25rem}html.theme--catppuccin-frappe .card-image:last-child img{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}html.theme--catppuccin-frappe .card-content{background-color:rgba(0,0,0,0);padding:1.5rem}html.theme--catppuccin-frappe .card-footer{background-color:rgba(0,0,0,0);border-top:1px solid #ededed;align-items:stretch;display:flex}html.theme--catppuccin-frappe .card-footer-item{align-items:center;display:flex;flex-basis:0;flex-grow:1;flex-shrink:0;justify-content:center;padding:.75rem}html.theme--catppuccin-frappe .card-footer-item:not(:last-child){border-right:1px solid #ededed}html.theme--catppuccin-frappe .card .media:not(:last-child){margin-bottom:1.5rem}html.theme--catppuccin-frappe .dropdown{display:inline-flex;position:relative;vertical-align:top}html.theme--catppuccin-frappe .dropdown.is-active .dropdown-menu,html.theme--catppuccin-frappe .dropdown.is-hoverable:hover .dropdown-menu{display:block}html.theme--catppuccin-frappe .dropdown.is-right .dropdown-menu{left:auto;right:0}html.theme--catppuccin-frappe .dropdown.is-up .dropdown-menu{bottom:100%;padding-bottom:4px;padding-top:initial;top:auto}html.theme--catppuccin-frappe .dropdown-menu{display:none;left:0;min-width:12rem;padding-top:4px;position:absolute;top:100%;z-index:20}html.theme--catppuccin-frappe .dropdown-content{background-color:#292c3c;border-radius:.4em;box-shadow:#171717;padding-bottom:.5rem;padding-top:.5rem}html.theme--catppuccin-frappe .dropdown-item{color:#c6d0f5;display:block;font-size:0.875rem;line-height:1.5;padding:0.375rem 1rem;position:relative}html.theme--catppuccin-frappe a.dropdown-item,html.theme--catppuccin-frappe button.dropdown-item{padding-right:3rem;text-align:inherit;white-space:nowrap;width:100%}html.theme--catppuccin-frappe a.dropdown-item:hover,html.theme--catppuccin-frappe button.dropdown-item:hover{background-color:#292c3c;color:#0a0a0a}html.theme--catppuccin-frappe a.dropdown-item.is-active,html.theme--catppuccin-frappe button.dropdown-item.is-active{background-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .dropdown-divider{background-color:#ededed;border:none;display:block;height:1px;margin:0.5rem 0}html.theme--catppuccin-frappe .level{align-items:center;justify-content:space-between}html.theme--catppuccin-frappe .level code{border-radius:.4em}html.theme--catppuccin-frappe .level img{display:inline-block;vertical-align:top}html.theme--catppuccin-frappe .level.is-mobile{display:flex}html.theme--catppuccin-frappe .level.is-mobile .level-left,html.theme--catppuccin-frappe .level.is-mobile .level-right{display:flex}html.theme--catppuccin-frappe .level.is-mobile .level-left+.level-right{margin-top:0}html.theme--catppuccin-frappe .level.is-mobile .level-item:not(:last-child){margin-bottom:0;margin-right:.75rem}html.theme--catppuccin-frappe .level.is-mobile .level-item:not(.is-narrow){flex-grow:1}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .level{display:flex}html.theme--catppuccin-frappe .level>.level-item:not(.is-narrow){flex-grow:1}}html.theme--catppuccin-frappe .level-item{align-items:center;display:flex;flex-basis:auto;flex-grow:0;flex-shrink:0;justify-content:center}html.theme--catppuccin-frappe .level-item .title,html.theme--catppuccin-frappe .level-item .subtitle{margin-bottom:0}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .level-item:not(:last-child){margin-bottom:.75rem}}html.theme--catppuccin-frappe .level-left,html.theme--catppuccin-frappe .level-right{flex-basis:auto;flex-grow:0;flex-shrink:0}html.theme--catppuccin-frappe .level-left .level-item.is-flexible,html.theme--catppuccin-frappe .level-right .level-item.is-flexible{flex-grow:1}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .level-left .level-item:not(:last-child),html.theme--catppuccin-frappe .level-right .level-item:not(:last-child){margin-right:.75rem}}html.theme--catppuccin-frappe .level-left{align-items:center;justify-content:flex-start}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .level-left+.level-right{margin-top:1.5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .level-left{display:flex}}html.theme--catppuccin-frappe .level-right{align-items:center;justify-content:flex-end}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .level-right{display:flex}}html.theme--catppuccin-frappe .media{align-items:flex-start;display:flex;text-align:inherit}html.theme--catppuccin-frappe .media .content:not(:last-child){margin-bottom:.75rem}html.theme--catppuccin-frappe .media .media{border-top:1px solid rgba(98,104,128,0.5);display:flex;padding-top:.75rem}html.theme--catppuccin-frappe .media .media .content:not(:last-child),html.theme--catppuccin-frappe .media .media .control:not(:last-child){margin-bottom:.5rem}html.theme--catppuccin-frappe .media .media .media{padding-top:.5rem}html.theme--catppuccin-frappe .media .media .media+.media{margin-top:.5rem}html.theme--catppuccin-frappe .media+.media{border-top:1px solid rgba(98,104,128,0.5);margin-top:1rem;padding-top:1rem}html.theme--catppuccin-frappe .media.is-large+.media{margin-top:1.5rem;padding-top:1.5rem}html.theme--catppuccin-frappe .media-left,html.theme--catppuccin-frappe .media-right{flex-basis:auto;flex-grow:0;flex-shrink:0}html.theme--catppuccin-frappe .media-left{margin-right:1rem}html.theme--catppuccin-frappe .media-right{margin-left:1rem}html.theme--catppuccin-frappe .media-content{flex-basis:auto;flex-grow:1;flex-shrink:1;text-align:inherit}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .media-content{overflow-x:auto}}html.theme--catppuccin-frappe .menu{font-size:1rem}html.theme--catppuccin-frappe .menu.is-small,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.menu{font-size:.75rem}html.theme--catppuccin-frappe .menu.is-medium{font-size:1.25rem}html.theme--catppuccin-frappe .menu.is-large{font-size:1.5rem}html.theme--catppuccin-frappe .menu-list{line-height:1.25}html.theme--catppuccin-frappe .menu-list a{border-radius:3px;color:#c6d0f5;display:block;padding:0.5em 0.75em}html.theme--catppuccin-frappe .menu-list a:hover{background-color:#292c3c;color:#b0bef1}html.theme--catppuccin-frappe .menu-list a.is-active{background-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .menu-list li ul{border-left:1px solid #626880;margin:.75em;padding-left:.75em}html.theme--catppuccin-frappe .menu-label{color:#f1f4fd;font-size:.75em;letter-spacing:.1em;text-transform:uppercase}html.theme--catppuccin-frappe .menu-label:not(:first-child){margin-top:1em}html.theme--catppuccin-frappe .menu-label:not(:last-child){margin-bottom:1em}html.theme--catppuccin-frappe .message{background-color:#292c3c;border-radius:.4em;font-size:1rem}html.theme--catppuccin-frappe .message strong{color:currentColor}html.theme--catppuccin-frappe .message a:not(.button):not(.tag):not(.dropdown-item){color:currentColor;text-decoration:underline}html.theme--catppuccin-frappe .message.is-small,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.message{font-size:.75rem}html.theme--catppuccin-frappe .message.is-medium{font-size:1.25rem}html.theme--catppuccin-frappe .message.is-large{font-size:1.5rem}html.theme--catppuccin-frappe .message.is-white{background-color:#fff}html.theme--catppuccin-frappe .message.is-white .message-header{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-frappe .message.is-white .message-body{border-color:#fff}html.theme--catppuccin-frappe .message.is-black{background-color:#fafafa}html.theme--catppuccin-frappe .message.is-black .message-header{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-frappe .message.is-black .message-body{border-color:#0a0a0a}html.theme--catppuccin-frappe .message.is-light{background-color:#fafafa}html.theme--catppuccin-frappe .message.is-light .message-header{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .message.is-light .message-body{border-color:#f5f5f5}html.theme--catppuccin-frappe .message.is-dark,html.theme--catppuccin-frappe .content kbd.message{background-color:#f9f9fb}html.theme--catppuccin-frappe .message.is-dark .message-header,html.theme--catppuccin-frappe .content kbd.message .message-header{background-color:#414559;color:#fff}html.theme--catppuccin-frappe .message.is-dark .message-body,html.theme--catppuccin-frappe .content kbd.message .message-body{border-color:#414559}html.theme--catppuccin-frappe .message.is-primary,html.theme--catppuccin-frappe .docstring>section>a.message.docs-sourcelink{background-color:#edf2fc}html.theme--catppuccin-frappe .message.is-primary .message-header,html.theme--catppuccin-frappe .docstring>section>a.message.docs-sourcelink .message-header{background-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .message.is-primary .message-body,html.theme--catppuccin-frappe .docstring>section>a.message.docs-sourcelink .message-body{border-color:#8caaee;color:#153a8e}html.theme--catppuccin-frappe .message.is-link{background-color:#edf2fc}html.theme--catppuccin-frappe .message.is-link .message-header{background-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .message.is-link .message-body{border-color:#8caaee;color:#153a8e}html.theme--catppuccin-frappe .message.is-info{background-color:#f1f9f8}html.theme--catppuccin-frappe .message.is-info .message-header{background-color:#81c8be;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .message.is-info .message-body{border-color:#81c8be;color:#2d675f}html.theme--catppuccin-frappe .message.is-success{background-color:#f4f9f0}html.theme--catppuccin-frappe .message.is-success .message-header{background-color:#a6d189;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .message.is-success .message-body{border-color:#a6d189;color:#446a29}html.theme--catppuccin-frappe .message.is-warning{background-color:#fbf7ee}html.theme--catppuccin-frappe .message.is-warning .message-header{background-color:#e5c890;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .message.is-warning .message-body{border-color:#e5c890;color:#78591c}html.theme--catppuccin-frappe .message.is-danger{background-color:#fceeee}html.theme--catppuccin-frappe .message.is-danger .message-header{background-color:#e78284;color:#fff}html.theme--catppuccin-frappe .message.is-danger .message-body{border-color:#e78284;color:#9a1e20}html.theme--catppuccin-frappe .message-header{align-items:center;background-color:#c6d0f5;border-radius:.4em .4em 0 0;color:rgba(0,0,0,0.7);display:flex;font-weight:700;justify-content:space-between;line-height:1.25;padding:0.75em 1em;position:relative}html.theme--catppuccin-frappe .message-header .delete{flex-grow:0;flex-shrink:0;margin-left:.75em}html.theme--catppuccin-frappe .message-header+.message-body{border-width:0;border-top-left-radius:0;border-top-right-radius:0}html.theme--catppuccin-frappe .message-body{border-color:#626880;border-radius:.4em;border-style:solid;border-width:0 0 0 4px;color:#c6d0f5;padding:1.25em 1.5em}html.theme--catppuccin-frappe .message-body code,html.theme--catppuccin-frappe .message-body pre{background-color:#fff}html.theme--catppuccin-frappe .message-body pre code{background-color:rgba(0,0,0,0)}html.theme--catppuccin-frappe .modal{align-items:center;display:none;flex-direction:column;justify-content:center;overflow:hidden;position:fixed;z-index:40}html.theme--catppuccin-frappe .modal.is-active{display:flex}html.theme--catppuccin-frappe .modal-background{background-color:rgba(10,10,10,0.86)}html.theme--catppuccin-frappe .modal-content,html.theme--catppuccin-frappe .modal-card{margin:0 20px;max-height:calc(100vh - 160px);overflow:auto;position:relative;width:100%}@media screen and (min-width: 769px){html.theme--catppuccin-frappe .modal-content,html.theme--catppuccin-frappe .modal-card{margin:0 auto;max-height:calc(100vh - 40px);width:640px}}html.theme--catppuccin-frappe .modal-close{background:none;height:40px;position:fixed;right:20px;top:20px;width:40px}html.theme--catppuccin-frappe .modal-card{display:flex;flex-direction:column;max-height:calc(100vh - 40px);overflow:hidden;-ms-overflow-y:visible}html.theme--catppuccin-frappe .modal-card-head,html.theme--catppuccin-frappe .modal-card-foot{align-items:center;background-color:#292c3c;display:flex;flex-shrink:0;justify-content:flex-start;padding:20px;position:relative}html.theme--catppuccin-frappe .modal-card-head{border-bottom:1px solid #626880;border-top-left-radius:8px;border-top-right-radius:8px}html.theme--catppuccin-frappe .modal-card-title{color:#c6d0f5;flex-grow:1;flex-shrink:0;font-size:1.5rem;line-height:1}html.theme--catppuccin-frappe .modal-card-foot{border-bottom-left-radius:8px;border-bottom-right-radius:8px;border-top:1px solid #626880}html.theme--catppuccin-frappe .modal-card-foot .button:not(:last-child){margin-right:.5em}html.theme--catppuccin-frappe .modal-card-body{-webkit-overflow-scrolling:touch;background-color:#303446;flex-grow:1;flex-shrink:1;overflow:auto;padding:20px}html.theme--catppuccin-frappe .navbar{background-color:#8caaee;min-height:4rem;position:relative;z-index:30}html.theme--catppuccin-frappe .navbar.is-white{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-frappe .navbar.is-white .navbar-brand>.navbar-item,html.theme--catppuccin-frappe .navbar.is-white .navbar-brand .navbar-link{color:#0a0a0a}html.theme--catppuccin-frappe .navbar.is-white .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-white .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-white .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-white .navbar-brand .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-white .navbar-brand .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-white .navbar-brand .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}html.theme--catppuccin-frappe .navbar.is-white .navbar-brand .navbar-link::after{border-color:#0a0a0a}html.theme--catppuccin-frappe .navbar.is-white .navbar-burger{color:#0a0a0a}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .navbar.is-white .navbar-start>.navbar-item,html.theme--catppuccin-frappe .navbar.is-white .navbar-start .navbar-link,html.theme--catppuccin-frappe .navbar.is-white .navbar-end>.navbar-item,html.theme--catppuccin-frappe .navbar.is-white .navbar-end .navbar-link{color:#0a0a0a}html.theme--catppuccin-frappe .navbar.is-white .navbar-start>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-white .navbar-start>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-white .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-white .navbar-start .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-white .navbar-start .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-white .navbar-start .navbar-link.is-active,html.theme--catppuccin-frappe .navbar.is-white .navbar-end>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-white .navbar-end>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-white .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-white .navbar-end .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-white .navbar-end .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-white .navbar-end .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}html.theme--catppuccin-frappe .navbar.is-white .navbar-start .navbar-link::after,html.theme--catppuccin-frappe .navbar.is-white .navbar-end .navbar-link::after{border-color:#0a0a0a}html.theme--catppuccin-frappe .navbar.is-white .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-frappe .navbar.is-white .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-frappe .navbar.is-white .navbar-item.has-dropdown.is-active .navbar-link{background-color:#f2f2f2;color:#0a0a0a}html.theme--catppuccin-frappe .navbar.is-white .navbar-dropdown a.navbar-item.is-active{background-color:#fff;color:#0a0a0a}}html.theme--catppuccin-frappe .navbar.is-black{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-frappe .navbar.is-black .navbar-brand>.navbar-item,html.theme--catppuccin-frappe .navbar.is-black .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-frappe .navbar.is-black .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-black .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-black .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-black .navbar-brand .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-black .navbar-brand .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-black .navbar-brand .navbar-link.is-active{background-color:#000;color:#fff}html.theme--catppuccin-frappe .navbar.is-black .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-frappe .navbar.is-black .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .navbar.is-black .navbar-start>.navbar-item,html.theme--catppuccin-frappe .navbar.is-black .navbar-start .navbar-link,html.theme--catppuccin-frappe .navbar.is-black .navbar-end>.navbar-item,html.theme--catppuccin-frappe .navbar.is-black .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-frappe .navbar.is-black .navbar-start>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-black .navbar-start>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-black .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-black .navbar-start .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-black .navbar-start .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-black .navbar-start .navbar-link.is-active,html.theme--catppuccin-frappe .navbar.is-black .navbar-end>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-black .navbar-end>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-black .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-black .navbar-end .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-black .navbar-end .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-black .navbar-end .navbar-link.is-active{background-color:#000;color:#fff}html.theme--catppuccin-frappe .navbar.is-black .navbar-start .navbar-link::after,html.theme--catppuccin-frappe .navbar.is-black .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-frappe .navbar.is-black .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-frappe .navbar.is-black .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-frappe .navbar.is-black .navbar-item.has-dropdown.is-active .navbar-link{background-color:#000;color:#fff}html.theme--catppuccin-frappe .navbar.is-black .navbar-dropdown a.navbar-item.is-active{background-color:#0a0a0a;color:#fff}}html.theme--catppuccin-frappe .navbar.is-light{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-light .navbar-brand>.navbar-item,html.theme--catppuccin-frappe .navbar.is-light .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-light .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-light .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-light .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-light .navbar-brand .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-light .navbar-brand .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-light .navbar-brand .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-light .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-light .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .navbar.is-light .navbar-start>.navbar-item,html.theme--catppuccin-frappe .navbar.is-light .navbar-start .navbar-link,html.theme--catppuccin-frappe .navbar.is-light .navbar-end>.navbar-item,html.theme--catppuccin-frappe .navbar.is-light .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-light .navbar-start>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-light .navbar-start>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-light .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-light .navbar-start .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-light .navbar-start .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-light .navbar-start .navbar-link.is-active,html.theme--catppuccin-frappe .navbar.is-light .navbar-end>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-light .navbar-end>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-light .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-light .navbar-end .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-light .navbar-end .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-light .navbar-end .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-light .navbar-start .navbar-link::after,html.theme--catppuccin-frappe .navbar.is-light .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-light .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-frappe .navbar.is-light .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-frappe .navbar.is-light .navbar-item.has-dropdown.is-active .navbar-link{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-light .navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}}html.theme--catppuccin-frappe .navbar.is-dark,html.theme--catppuccin-frappe .content kbd.navbar{background-color:#414559;color:#fff}html.theme--catppuccin-frappe .navbar.is-dark .navbar-brand>.navbar-item,html.theme--catppuccin-frappe .content kbd.navbar .navbar-brand>.navbar-item,html.theme--catppuccin-frappe .navbar.is-dark .navbar-brand .navbar-link,html.theme--catppuccin-frappe .content kbd.navbar .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-frappe .navbar.is-dark .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-frappe .content kbd.navbar .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-dark .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-frappe .content kbd.navbar .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-dark .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-frappe .content kbd.navbar .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-dark .navbar-brand .navbar-link:focus,html.theme--catppuccin-frappe .content kbd.navbar .navbar-brand .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-dark .navbar-brand .navbar-link:hover,html.theme--catppuccin-frappe .content kbd.navbar .navbar-brand .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-dark .navbar-brand .navbar-link.is-active,html.theme--catppuccin-frappe .content kbd.navbar .navbar-brand .navbar-link.is-active{background-color:#363a4a;color:#fff}html.theme--catppuccin-frappe .navbar.is-dark .navbar-brand .navbar-link::after,html.theme--catppuccin-frappe .content kbd.navbar .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-frappe .navbar.is-dark .navbar-burger,html.theme--catppuccin-frappe .content kbd.navbar .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .navbar.is-dark .navbar-start>.navbar-item,html.theme--catppuccin-frappe .content kbd.navbar .navbar-start>.navbar-item,html.theme--catppuccin-frappe .navbar.is-dark .navbar-start .navbar-link,html.theme--catppuccin-frappe .content kbd.navbar .navbar-start .navbar-link,html.theme--catppuccin-frappe .navbar.is-dark .navbar-end>.navbar-item,html.theme--catppuccin-frappe .content kbd.navbar .navbar-end>.navbar-item,html.theme--catppuccin-frappe .navbar.is-dark .navbar-end .navbar-link,html.theme--catppuccin-frappe .content kbd.navbar .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-frappe .navbar.is-dark .navbar-start>a.navbar-item:focus,html.theme--catppuccin-frappe .content kbd.navbar .navbar-start>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-dark .navbar-start>a.navbar-item:hover,html.theme--catppuccin-frappe .content kbd.navbar .navbar-start>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-dark .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-frappe .content kbd.navbar .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-dark .navbar-start .navbar-link:focus,html.theme--catppuccin-frappe .content kbd.navbar .navbar-start .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-dark .navbar-start .navbar-link:hover,html.theme--catppuccin-frappe .content kbd.navbar .navbar-start .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-dark .navbar-start .navbar-link.is-active,html.theme--catppuccin-frappe .content kbd.navbar .navbar-start .navbar-link.is-active,html.theme--catppuccin-frappe .navbar.is-dark .navbar-end>a.navbar-item:focus,html.theme--catppuccin-frappe .content kbd.navbar .navbar-end>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-dark .navbar-end>a.navbar-item:hover,html.theme--catppuccin-frappe .content kbd.navbar .navbar-end>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-dark .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-frappe .content kbd.navbar .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-dark .navbar-end .navbar-link:focus,html.theme--catppuccin-frappe .content kbd.navbar .navbar-end .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-dark .navbar-end .navbar-link:hover,html.theme--catppuccin-frappe .content kbd.navbar .navbar-end .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-dark .navbar-end .navbar-link.is-active,html.theme--catppuccin-frappe .content kbd.navbar .navbar-end .navbar-link.is-active{background-color:#363a4a;color:#fff}html.theme--catppuccin-frappe .navbar.is-dark .navbar-start .navbar-link::after,html.theme--catppuccin-frappe .content kbd.navbar .navbar-start .navbar-link::after,html.theme--catppuccin-frappe .navbar.is-dark .navbar-end .navbar-link::after,html.theme--catppuccin-frappe .content kbd.navbar .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-frappe .navbar.is-dark .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-frappe .content kbd.navbar .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-frappe .navbar.is-dark .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-frappe .content kbd.navbar .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-frappe .navbar.is-dark .navbar-item.has-dropdown.is-active .navbar-link,html.theme--catppuccin-frappe .content kbd.navbar .navbar-item.has-dropdown.is-active .navbar-link{background-color:#363a4a;color:#fff}html.theme--catppuccin-frappe .navbar.is-dark .navbar-dropdown a.navbar-item.is-active,html.theme--catppuccin-frappe .content kbd.navbar .navbar-dropdown a.navbar-item.is-active{background-color:#414559;color:#fff}}html.theme--catppuccin-frappe .navbar.is-primary,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink{background-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .navbar.is-primary .navbar-brand>.navbar-item,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-brand>.navbar-item,html.theme--catppuccin-frappe .navbar.is-primary .navbar-brand .navbar-link,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-frappe .navbar.is-primary .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-primary .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-primary .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-primary .navbar-brand .navbar-link:focus,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-primary .navbar-brand .navbar-link:hover,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-primary .navbar-brand .navbar-link.is-active,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link.is-active{background-color:#769aeb;color:#fff}html.theme--catppuccin-frappe .navbar.is-primary .navbar-brand .navbar-link::after,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-frappe .navbar.is-primary .navbar-burger,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .navbar.is-primary .navbar-start>.navbar-item,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-start>.navbar-item,html.theme--catppuccin-frappe .navbar.is-primary .navbar-start .navbar-link,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link,html.theme--catppuccin-frappe .navbar.is-primary .navbar-end>.navbar-item,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-end>.navbar-item,html.theme--catppuccin-frappe .navbar.is-primary .navbar-end .navbar-link,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-frappe .navbar.is-primary .navbar-start>a.navbar-item:focus,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-primary .navbar-start>a.navbar-item:hover,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-primary .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-primary .navbar-start .navbar-link:focus,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-primary .navbar-start .navbar-link:hover,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-primary .navbar-start .navbar-link.is-active,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link.is-active,html.theme--catppuccin-frappe .navbar.is-primary .navbar-end>a.navbar-item:focus,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-primary .navbar-end>a.navbar-item:hover,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-primary .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-primary .navbar-end .navbar-link:focus,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-primary .navbar-end .navbar-link:hover,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-primary .navbar-end .navbar-link.is-active,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link.is-active{background-color:#769aeb;color:#fff}html.theme--catppuccin-frappe .navbar.is-primary .navbar-start .navbar-link::after,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link::after,html.theme--catppuccin-frappe .navbar.is-primary .navbar-end .navbar-link::after,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-frappe .navbar.is-primary .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-frappe .navbar.is-primary .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-frappe .navbar.is-primary .navbar-item.has-dropdown.is-active .navbar-link,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown.is-active .navbar-link{background-color:#769aeb;color:#fff}html.theme--catppuccin-frappe .navbar.is-primary .navbar-dropdown a.navbar-item.is-active,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-dropdown a.navbar-item.is-active{background-color:#8caaee;color:#fff}}html.theme--catppuccin-frappe .navbar.is-link{background-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .navbar.is-link .navbar-brand>.navbar-item,html.theme--catppuccin-frappe .navbar.is-link .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-frappe .navbar.is-link .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-link .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-link .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-link .navbar-brand .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-link .navbar-brand .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-link .navbar-brand .navbar-link.is-active{background-color:#769aeb;color:#fff}html.theme--catppuccin-frappe .navbar.is-link .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-frappe .navbar.is-link .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .navbar.is-link .navbar-start>.navbar-item,html.theme--catppuccin-frappe .navbar.is-link .navbar-start .navbar-link,html.theme--catppuccin-frappe .navbar.is-link .navbar-end>.navbar-item,html.theme--catppuccin-frappe .navbar.is-link .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-frappe .navbar.is-link .navbar-start>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-link .navbar-start>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-link .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-link .navbar-start .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-link .navbar-start .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-link .navbar-start .navbar-link.is-active,html.theme--catppuccin-frappe .navbar.is-link .navbar-end>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-link .navbar-end>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-link .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-link .navbar-end .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-link .navbar-end .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-link .navbar-end .navbar-link.is-active{background-color:#769aeb;color:#fff}html.theme--catppuccin-frappe .navbar.is-link .navbar-start .navbar-link::after,html.theme--catppuccin-frappe .navbar.is-link .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-frappe .navbar.is-link .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-frappe .navbar.is-link .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-frappe .navbar.is-link .navbar-item.has-dropdown.is-active .navbar-link{background-color:#769aeb;color:#fff}html.theme--catppuccin-frappe .navbar.is-link .navbar-dropdown a.navbar-item.is-active{background-color:#8caaee;color:#fff}}html.theme--catppuccin-frappe .navbar.is-info{background-color:#81c8be;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-info .navbar-brand>.navbar-item,html.theme--catppuccin-frappe .navbar.is-info .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-info .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-info .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-info .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-info .navbar-brand .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-info .navbar-brand .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-info .navbar-brand .navbar-link.is-active{background-color:#6fc0b5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-info .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-info .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .navbar.is-info .navbar-start>.navbar-item,html.theme--catppuccin-frappe .navbar.is-info .navbar-start .navbar-link,html.theme--catppuccin-frappe .navbar.is-info .navbar-end>.navbar-item,html.theme--catppuccin-frappe .navbar.is-info .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-info .navbar-start>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-info .navbar-start>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-info .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-info .navbar-start .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-info .navbar-start .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-info .navbar-start .navbar-link.is-active,html.theme--catppuccin-frappe .navbar.is-info .navbar-end>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-info .navbar-end>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-info .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-info .navbar-end .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-info .navbar-end .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-info .navbar-end .navbar-link.is-active{background-color:#6fc0b5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-info .navbar-start .navbar-link::after,html.theme--catppuccin-frappe .navbar.is-info .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-info .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-frappe .navbar.is-info .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-frappe .navbar.is-info .navbar-item.has-dropdown.is-active .navbar-link{background-color:#6fc0b5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-info .navbar-dropdown a.navbar-item.is-active{background-color:#81c8be;color:rgba(0,0,0,0.7)}}html.theme--catppuccin-frappe .navbar.is-success{background-color:#a6d189;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-success .navbar-brand>.navbar-item,html.theme--catppuccin-frappe .navbar.is-success .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-success .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-success .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-success .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-success .navbar-brand .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-success .navbar-brand .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-success .navbar-brand .navbar-link.is-active{background-color:#98ca77;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-success .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-success .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .navbar.is-success .navbar-start>.navbar-item,html.theme--catppuccin-frappe .navbar.is-success .navbar-start .navbar-link,html.theme--catppuccin-frappe .navbar.is-success .navbar-end>.navbar-item,html.theme--catppuccin-frappe .navbar.is-success .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-success .navbar-start>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-success .navbar-start>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-success .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-success .navbar-start .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-success .navbar-start .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-success .navbar-start .navbar-link.is-active,html.theme--catppuccin-frappe .navbar.is-success .navbar-end>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-success .navbar-end>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-success .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-success .navbar-end .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-success .navbar-end .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-success .navbar-end .navbar-link.is-active{background-color:#98ca77;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-success .navbar-start .navbar-link::after,html.theme--catppuccin-frappe .navbar.is-success .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-success .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-frappe .navbar.is-success .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-frappe .navbar.is-success .navbar-item.has-dropdown.is-active .navbar-link{background-color:#98ca77;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-success .navbar-dropdown a.navbar-item.is-active{background-color:#a6d189;color:rgba(0,0,0,0.7)}}html.theme--catppuccin-frappe .navbar.is-warning{background-color:#e5c890;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-warning .navbar-brand>.navbar-item,html.theme--catppuccin-frappe .navbar.is-warning .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-warning .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-warning .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-warning .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-warning .navbar-brand .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-warning .navbar-brand .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-warning .navbar-brand .navbar-link.is-active{background-color:#e0be7b;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-warning .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-warning .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .navbar.is-warning .navbar-start>.navbar-item,html.theme--catppuccin-frappe .navbar.is-warning .navbar-start .navbar-link,html.theme--catppuccin-frappe .navbar.is-warning .navbar-end>.navbar-item,html.theme--catppuccin-frappe .navbar.is-warning .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-warning .navbar-start>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-warning .navbar-start>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-warning .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-warning .navbar-start .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-warning .navbar-start .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-warning .navbar-start .navbar-link.is-active,html.theme--catppuccin-frappe .navbar.is-warning .navbar-end>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-warning .navbar-end>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-warning .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-warning .navbar-end .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-warning .navbar-end .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-warning .navbar-end .navbar-link.is-active{background-color:#e0be7b;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-warning .navbar-start .navbar-link::after,html.theme--catppuccin-frappe .navbar.is-warning .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-warning .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-frappe .navbar.is-warning .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-frappe .navbar.is-warning .navbar-item.has-dropdown.is-active .navbar-link{background-color:#e0be7b;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-warning .navbar-dropdown a.navbar-item.is-active{background-color:#e5c890;color:rgba(0,0,0,0.7)}}html.theme--catppuccin-frappe .navbar.is-danger{background-color:#e78284;color:#fff}html.theme--catppuccin-frappe .navbar.is-danger .navbar-brand>.navbar-item,html.theme--catppuccin-frappe .navbar.is-danger .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-frappe .navbar.is-danger .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-danger .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-danger .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-danger .navbar-brand .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-danger .navbar-brand .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-danger .navbar-brand .navbar-link.is-active{background-color:#e36d6f;color:#fff}html.theme--catppuccin-frappe .navbar.is-danger .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-frappe .navbar.is-danger .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .navbar.is-danger .navbar-start>.navbar-item,html.theme--catppuccin-frappe .navbar.is-danger .navbar-start .navbar-link,html.theme--catppuccin-frappe .navbar.is-danger .navbar-end>.navbar-item,html.theme--catppuccin-frappe .navbar.is-danger .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-frappe .navbar.is-danger .navbar-start>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-danger .navbar-start>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-danger .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-danger .navbar-start .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-danger .navbar-start .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-danger .navbar-start .navbar-link.is-active,html.theme--catppuccin-frappe .navbar.is-danger .navbar-end>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-danger .navbar-end>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-danger .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-danger .navbar-end .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-danger .navbar-end .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-danger .navbar-end .navbar-link.is-active{background-color:#e36d6f;color:#fff}html.theme--catppuccin-frappe .navbar.is-danger .navbar-start .navbar-link::after,html.theme--catppuccin-frappe .navbar.is-danger .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-frappe .navbar.is-danger .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-frappe .navbar.is-danger .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-frappe .navbar.is-danger .navbar-item.has-dropdown.is-active .navbar-link{background-color:#e36d6f;color:#fff}html.theme--catppuccin-frappe .navbar.is-danger .navbar-dropdown a.navbar-item.is-active{background-color:#e78284;color:#fff}}html.theme--catppuccin-frappe .navbar>.container{align-items:stretch;display:flex;min-height:4rem;width:100%}html.theme--catppuccin-frappe .navbar.has-shadow{box-shadow:0 2px 0 0 #292c3c}html.theme--catppuccin-frappe .navbar.is-fixed-bottom,html.theme--catppuccin-frappe .navbar.is-fixed-top{left:0;position:fixed;right:0;z-index:30}html.theme--catppuccin-frappe .navbar.is-fixed-bottom{bottom:0}html.theme--catppuccin-frappe .navbar.is-fixed-bottom.has-shadow{box-shadow:0 -2px 0 0 #292c3c}html.theme--catppuccin-frappe .navbar.is-fixed-top{top:0}html.theme--catppuccin-frappe html.has-navbar-fixed-top,html.theme--catppuccin-frappe body.has-navbar-fixed-top{padding-top:4rem}html.theme--catppuccin-frappe html.has-navbar-fixed-bottom,html.theme--catppuccin-frappe body.has-navbar-fixed-bottom{padding-bottom:4rem}html.theme--catppuccin-frappe .navbar-brand,html.theme--catppuccin-frappe .navbar-tabs{align-items:stretch;display:flex;flex-shrink:0;min-height:4rem}html.theme--catppuccin-frappe .navbar-brand a.navbar-item:focus,html.theme--catppuccin-frappe .navbar-brand a.navbar-item:hover{background-color:transparent}html.theme--catppuccin-frappe .navbar-tabs{-webkit-overflow-scrolling:touch;max-width:100vw;overflow-x:auto;overflow-y:hidden}html.theme--catppuccin-frappe .navbar-burger{color:#c6d0f5;-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;cursor:pointer;display:block;height:4rem;position:relative;width:4rem;margin-left:auto}html.theme--catppuccin-frappe .navbar-burger span{background-color:currentColor;display:block;height:1px;left:calc(50% - 8px);position:absolute;transform-origin:center;transition-duration:86ms;transition-property:background-color, opacity, transform;transition-timing-function:ease-out;width:16px}html.theme--catppuccin-frappe .navbar-burger span:nth-child(1){top:calc(50% - 6px)}html.theme--catppuccin-frappe .navbar-burger span:nth-child(2){top:calc(50% - 1px)}html.theme--catppuccin-frappe .navbar-burger span:nth-child(3){top:calc(50% + 4px)}html.theme--catppuccin-frappe .navbar-burger:hover{background-color:rgba(0,0,0,0.05)}html.theme--catppuccin-frappe .navbar-burger.is-active span:nth-child(1){transform:translateY(5px) rotate(45deg)}html.theme--catppuccin-frappe .navbar-burger.is-active span:nth-child(2){opacity:0}html.theme--catppuccin-frappe .navbar-burger.is-active span:nth-child(3){transform:translateY(-5px) rotate(-45deg)}html.theme--catppuccin-frappe .navbar-menu{display:none}html.theme--catppuccin-frappe .navbar-item,html.theme--catppuccin-frappe .navbar-link{color:#c6d0f5;display:block;line-height:1.5;padding:0.5rem 0.75rem;position:relative}html.theme--catppuccin-frappe .navbar-item .icon:only-child,html.theme--catppuccin-frappe .navbar-link .icon:only-child{margin-left:-0.25rem;margin-right:-0.25rem}html.theme--catppuccin-frappe a.navbar-item,html.theme--catppuccin-frappe .navbar-link{cursor:pointer}html.theme--catppuccin-frappe a.navbar-item:focus,html.theme--catppuccin-frappe a.navbar-item:focus-within,html.theme--catppuccin-frappe a.navbar-item:hover,html.theme--catppuccin-frappe a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar-link:focus,html.theme--catppuccin-frappe .navbar-link:focus-within,html.theme--catppuccin-frappe .navbar-link:hover,html.theme--catppuccin-frappe .navbar-link.is-active{background-color:rgba(0,0,0,0);color:#8caaee}html.theme--catppuccin-frappe .navbar-item{flex-grow:0;flex-shrink:0}html.theme--catppuccin-frappe .navbar-item img{max-height:1.75rem}html.theme--catppuccin-frappe .navbar-item.has-dropdown{padding:0}html.theme--catppuccin-frappe .navbar-item.is-expanded{flex-grow:1;flex-shrink:1}html.theme--catppuccin-frappe .navbar-item.is-tab{border-bottom:1px solid transparent;min-height:4rem;padding-bottom:calc(0.5rem - 1px)}html.theme--catppuccin-frappe .navbar-item.is-tab:focus,html.theme--catppuccin-frappe .navbar-item.is-tab:hover{background-color:rgba(0,0,0,0);border-bottom-color:#8caaee}html.theme--catppuccin-frappe .navbar-item.is-tab.is-active{background-color:rgba(0,0,0,0);border-bottom-color:#8caaee;border-bottom-style:solid;border-bottom-width:3px;color:#8caaee;padding-bottom:calc(0.5rem - 3px)}html.theme--catppuccin-frappe .navbar-content{flex-grow:1;flex-shrink:1}html.theme--catppuccin-frappe .navbar-link:not(.is-arrowless){padding-right:2.5em}html.theme--catppuccin-frappe .navbar-link:not(.is-arrowless)::after{border-color:#fff;margin-top:-0.375em;right:1.125em}html.theme--catppuccin-frappe .navbar-dropdown{font-size:0.875rem;padding-bottom:0.5rem;padding-top:0.5rem}html.theme--catppuccin-frappe .navbar-dropdown .navbar-item{padding-left:1.5rem;padding-right:1.5rem}html.theme--catppuccin-frappe .navbar-divider{background-color:rgba(0,0,0,0.2);border:none;display:none;height:2px;margin:0.5rem 0}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .navbar>.container{display:block}html.theme--catppuccin-frappe .navbar-brand .navbar-item,html.theme--catppuccin-frappe .navbar-tabs .navbar-item{align-items:center;display:flex}html.theme--catppuccin-frappe .navbar-link::after{display:none}html.theme--catppuccin-frappe .navbar-menu{background-color:#8caaee;box-shadow:0 8px 16px rgba(10,10,10,0.1);padding:0.5rem 0}html.theme--catppuccin-frappe .navbar-menu.is-active{display:block}html.theme--catppuccin-frappe .navbar.is-fixed-bottom-touch,html.theme--catppuccin-frappe .navbar.is-fixed-top-touch{left:0;position:fixed;right:0;z-index:30}html.theme--catppuccin-frappe .navbar.is-fixed-bottom-touch{bottom:0}html.theme--catppuccin-frappe .navbar.is-fixed-bottom-touch.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,0.1)}html.theme--catppuccin-frappe .navbar.is-fixed-top-touch{top:0}html.theme--catppuccin-frappe .navbar.is-fixed-top .navbar-menu,html.theme--catppuccin-frappe .navbar.is-fixed-top-touch .navbar-menu{-webkit-overflow-scrolling:touch;max-height:calc(100vh - 4rem);overflow:auto}html.theme--catppuccin-frappe html.has-navbar-fixed-top-touch,html.theme--catppuccin-frappe body.has-navbar-fixed-top-touch{padding-top:4rem}html.theme--catppuccin-frappe html.has-navbar-fixed-bottom-touch,html.theme--catppuccin-frappe body.has-navbar-fixed-bottom-touch{padding-bottom:4rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .navbar,html.theme--catppuccin-frappe .navbar-menu,html.theme--catppuccin-frappe .navbar-start,html.theme--catppuccin-frappe .navbar-end{align-items:stretch;display:flex}html.theme--catppuccin-frappe .navbar{min-height:4rem}html.theme--catppuccin-frappe .navbar.is-spaced{padding:1rem 2rem}html.theme--catppuccin-frappe .navbar.is-spaced .navbar-start,html.theme--catppuccin-frappe .navbar.is-spaced .navbar-end{align-items:center}html.theme--catppuccin-frappe .navbar.is-spaced a.navbar-item,html.theme--catppuccin-frappe .navbar.is-spaced .navbar-link{border-radius:.4em}html.theme--catppuccin-frappe .navbar.is-transparent a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-transparent a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-transparent a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-transparent .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-transparent .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-transparent .navbar-link.is-active{background-color:transparent !important}html.theme--catppuccin-frappe .navbar.is-transparent .navbar-item.has-dropdown.is-active .navbar-link,html.theme--catppuccin-frappe .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus .navbar-link,html.theme--catppuccin-frappe .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus-within .navbar-link,html.theme--catppuccin-frappe .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:hover .navbar-link{background-color:transparent !important}html.theme--catppuccin-frappe .navbar.is-transparent .navbar-dropdown a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-transparent .navbar-dropdown a.navbar-item:hover{background-color:rgba(0,0,0,0);color:#838ba7}html.theme--catppuccin-frappe .navbar.is-transparent .navbar-dropdown a.navbar-item.is-active{background-color:rgba(0,0,0,0);color:#8caaee}html.theme--catppuccin-frappe .navbar-burger{display:none}html.theme--catppuccin-frappe .navbar-item,html.theme--catppuccin-frappe .navbar-link{align-items:center;display:flex}html.theme--catppuccin-frappe .navbar-item.has-dropdown{align-items:stretch}html.theme--catppuccin-frappe .navbar-item.has-dropdown-up .navbar-link::after{transform:rotate(135deg) translate(0.25em, -0.25em)}html.theme--catppuccin-frappe .navbar-item.has-dropdown-up .navbar-dropdown{border-bottom:1px solid rgba(0,0,0,0.2);border-radius:8px 8px 0 0;border-top:none;bottom:100%;box-shadow:0 -8px 8px rgba(10,10,10,0.1);top:auto}html.theme--catppuccin-frappe .navbar-item.is-active .navbar-dropdown,html.theme--catppuccin-frappe .navbar-item.is-hoverable:focus .navbar-dropdown,html.theme--catppuccin-frappe .navbar-item.is-hoverable:focus-within .navbar-dropdown,html.theme--catppuccin-frappe .navbar-item.is-hoverable:hover .navbar-dropdown{display:block}.navbar.is-spaced html.theme--catppuccin-frappe .navbar-item.is-active .navbar-dropdown,html.theme--catppuccin-frappe .navbar-item.is-active .navbar-dropdown.is-boxed,.navbar.is-spaced html.theme--catppuccin-frappe .navbar-item.is-hoverable:focus .navbar-dropdown,html.theme--catppuccin-frappe .navbar-item.is-hoverable:focus .navbar-dropdown.is-boxed,.navbar.is-spaced html.theme--catppuccin-frappe .navbar-item.is-hoverable:focus-within .navbar-dropdown,html.theme--catppuccin-frappe .navbar-item.is-hoverable:focus-within .navbar-dropdown.is-boxed,.navbar.is-spaced html.theme--catppuccin-frappe .navbar-item.is-hoverable:hover .navbar-dropdown,html.theme--catppuccin-frappe .navbar-item.is-hoverable:hover .navbar-dropdown.is-boxed{opacity:1;pointer-events:auto;transform:translateY(0)}html.theme--catppuccin-frappe .navbar-menu{flex-grow:1;flex-shrink:0}html.theme--catppuccin-frappe .navbar-start{justify-content:flex-start;margin-right:auto}html.theme--catppuccin-frappe .navbar-end{justify-content:flex-end;margin-left:auto}html.theme--catppuccin-frappe .navbar-dropdown{background-color:#8caaee;border-bottom-left-radius:8px;border-bottom-right-radius:8px;border-top:1px solid rgba(0,0,0,0.2);box-shadow:0 8px 8px rgba(10,10,10,0.1);display:none;font-size:0.875rem;left:0;min-width:100%;position:absolute;top:100%;z-index:20}html.theme--catppuccin-frappe .navbar-dropdown .navbar-item{padding:0.375rem 1rem;white-space:nowrap}html.theme--catppuccin-frappe .navbar-dropdown a.navbar-item{padding-right:3rem}html.theme--catppuccin-frappe .navbar-dropdown a.navbar-item:focus,html.theme--catppuccin-frappe .navbar-dropdown a.navbar-item:hover{background-color:rgba(0,0,0,0);color:#838ba7}html.theme--catppuccin-frappe .navbar-dropdown a.navbar-item.is-active{background-color:rgba(0,0,0,0);color:#8caaee}.navbar.is-spaced html.theme--catppuccin-frappe .navbar-dropdown,html.theme--catppuccin-frappe .navbar-dropdown.is-boxed{border-radius:8px;border-top:none;box-shadow:0 8px 8px rgba(10,10,10,0.1), 0 0 0 1px rgba(10,10,10,0.1);display:block;opacity:0;pointer-events:none;top:calc(100% + (-4px));transform:translateY(-5px);transition-duration:86ms;transition-property:opacity, transform}html.theme--catppuccin-frappe .navbar-dropdown.is-right{left:auto;right:0}html.theme--catppuccin-frappe .navbar-divider{display:block}html.theme--catppuccin-frappe .navbar>.container .navbar-brand,html.theme--catppuccin-frappe .container>.navbar .navbar-brand{margin-left:-.75rem}html.theme--catppuccin-frappe .navbar>.container .navbar-menu,html.theme--catppuccin-frappe .container>.navbar .navbar-menu{margin-right:-.75rem}html.theme--catppuccin-frappe .navbar.is-fixed-bottom-desktop,html.theme--catppuccin-frappe .navbar.is-fixed-top-desktop{left:0;position:fixed;right:0;z-index:30}html.theme--catppuccin-frappe .navbar.is-fixed-bottom-desktop{bottom:0}html.theme--catppuccin-frappe .navbar.is-fixed-bottom-desktop.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,0.1)}html.theme--catppuccin-frappe .navbar.is-fixed-top-desktop{top:0}html.theme--catppuccin-frappe html.has-navbar-fixed-top-desktop,html.theme--catppuccin-frappe body.has-navbar-fixed-top-desktop{padding-top:4rem}html.theme--catppuccin-frappe html.has-navbar-fixed-bottom-desktop,html.theme--catppuccin-frappe body.has-navbar-fixed-bottom-desktop{padding-bottom:4rem}html.theme--catppuccin-frappe html.has-spaced-navbar-fixed-top,html.theme--catppuccin-frappe body.has-spaced-navbar-fixed-top{padding-top:6rem}html.theme--catppuccin-frappe html.has-spaced-navbar-fixed-bottom,html.theme--catppuccin-frappe body.has-spaced-navbar-fixed-bottom{padding-bottom:6rem}html.theme--catppuccin-frappe a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar-link.is-active{color:#8caaee}html.theme--catppuccin-frappe a.navbar-item.is-active:not(:focus):not(:hover),html.theme--catppuccin-frappe .navbar-link.is-active:not(:focus):not(:hover){background-color:rgba(0,0,0,0)}html.theme--catppuccin-frappe .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-frappe .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-frappe .navbar-item.has-dropdown.is-active .navbar-link{background-color:rgba(0,0,0,0)}}html.theme--catppuccin-frappe .hero.is-fullheight-with-navbar{min-height:calc(100vh - 4rem)}html.theme--catppuccin-frappe .pagination{font-size:1rem;margin:-.25rem}html.theme--catppuccin-frappe .pagination.is-small,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.pagination{font-size:.75rem}html.theme--catppuccin-frappe .pagination.is-medium{font-size:1.25rem}html.theme--catppuccin-frappe .pagination.is-large{font-size:1.5rem}html.theme--catppuccin-frappe .pagination.is-rounded .pagination-previous,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.pagination .pagination-previous,html.theme--catppuccin-frappe .pagination.is-rounded .pagination-next,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.pagination .pagination-next{padding-left:1em;padding-right:1em;border-radius:9999px}html.theme--catppuccin-frappe .pagination.is-rounded .pagination-link,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.pagination .pagination-link{border-radius:9999px}html.theme--catppuccin-frappe .pagination,html.theme--catppuccin-frappe .pagination-list{align-items:center;display:flex;justify-content:center;text-align:center}html.theme--catppuccin-frappe .pagination-previous,html.theme--catppuccin-frappe .pagination-next,html.theme--catppuccin-frappe .pagination-link,html.theme--catppuccin-frappe .pagination-ellipsis{font-size:1em;justify-content:center;margin:.25rem;padding-left:.5em;padding-right:.5em;text-align:center}html.theme--catppuccin-frappe .pagination-previous,html.theme--catppuccin-frappe .pagination-next,html.theme--catppuccin-frappe .pagination-link{border-color:#626880;color:#8caaee;min-width:2.5em}html.theme--catppuccin-frappe .pagination-previous:hover,html.theme--catppuccin-frappe .pagination-next:hover,html.theme--catppuccin-frappe .pagination-link:hover{border-color:#737994;color:#99d1db}html.theme--catppuccin-frappe .pagination-previous:focus,html.theme--catppuccin-frappe .pagination-next:focus,html.theme--catppuccin-frappe .pagination-link:focus{border-color:#737994}html.theme--catppuccin-frappe .pagination-previous:active,html.theme--catppuccin-frappe .pagination-next:active,html.theme--catppuccin-frappe .pagination-link:active{box-shadow:inset 0 1px 2px rgba(10,10,10,0.2)}html.theme--catppuccin-frappe .pagination-previous[disabled],html.theme--catppuccin-frappe .pagination-previous.is-disabled,html.theme--catppuccin-frappe .pagination-next[disabled],html.theme--catppuccin-frappe .pagination-next.is-disabled,html.theme--catppuccin-frappe .pagination-link[disabled],html.theme--catppuccin-frappe .pagination-link.is-disabled{background-color:#626880;border-color:#626880;box-shadow:none;color:#f1f4fd;opacity:0.5}html.theme--catppuccin-frappe .pagination-previous,html.theme--catppuccin-frappe .pagination-next{padding-left:.75em;padding-right:.75em;white-space:nowrap}html.theme--catppuccin-frappe .pagination-link.is-current{background-color:#8caaee;border-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .pagination-ellipsis{color:#737994;pointer-events:none}html.theme--catppuccin-frappe .pagination-list{flex-wrap:wrap}html.theme--catppuccin-frappe .pagination-list li{list-style:none}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .pagination{flex-wrap:wrap}html.theme--catppuccin-frappe .pagination-previous,html.theme--catppuccin-frappe .pagination-next{flex-grow:1;flex-shrink:1}html.theme--catppuccin-frappe .pagination-list li{flex-grow:1;flex-shrink:1}}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .pagination-list{flex-grow:1;flex-shrink:1;justify-content:flex-start;order:1}html.theme--catppuccin-frappe .pagination-previous,html.theme--catppuccin-frappe .pagination-next,html.theme--catppuccin-frappe .pagination-link,html.theme--catppuccin-frappe .pagination-ellipsis{margin-bottom:0;margin-top:0}html.theme--catppuccin-frappe .pagination-previous{order:2}html.theme--catppuccin-frappe .pagination-next{order:3}html.theme--catppuccin-frappe .pagination{justify-content:space-between;margin-bottom:0;margin-top:0}html.theme--catppuccin-frappe .pagination.is-centered .pagination-previous{order:1}html.theme--catppuccin-frappe .pagination.is-centered .pagination-list{justify-content:center;order:2}html.theme--catppuccin-frappe .pagination.is-centered .pagination-next{order:3}html.theme--catppuccin-frappe .pagination.is-right .pagination-previous{order:1}html.theme--catppuccin-frappe .pagination.is-right .pagination-next{order:2}html.theme--catppuccin-frappe .pagination.is-right .pagination-list{justify-content:flex-end;order:3}}html.theme--catppuccin-frappe .panel{border-radius:8px;box-shadow:#171717;font-size:1rem}html.theme--catppuccin-frappe .panel:not(:last-child){margin-bottom:1.5rem}html.theme--catppuccin-frappe .panel.is-white .panel-heading{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-frappe .panel.is-white .panel-tabs a.is-active{border-bottom-color:#fff}html.theme--catppuccin-frappe .panel.is-white .panel-block.is-active .panel-icon{color:#fff}html.theme--catppuccin-frappe .panel.is-black .panel-heading{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-frappe .panel.is-black .panel-tabs a.is-active{border-bottom-color:#0a0a0a}html.theme--catppuccin-frappe .panel.is-black .panel-block.is-active .panel-icon{color:#0a0a0a}html.theme--catppuccin-frappe .panel.is-light .panel-heading{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .panel.is-light .panel-tabs a.is-active{border-bottom-color:#f5f5f5}html.theme--catppuccin-frappe .panel.is-light .panel-block.is-active .panel-icon{color:#f5f5f5}html.theme--catppuccin-frappe .panel.is-dark .panel-heading,html.theme--catppuccin-frappe .content kbd.panel .panel-heading{background-color:#414559;color:#fff}html.theme--catppuccin-frappe .panel.is-dark .panel-tabs a.is-active,html.theme--catppuccin-frappe .content kbd.panel .panel-tabs a.is-active{border-bottom-color:#414559}html.theme--catppuccin-frappe .panel.is-dark .panel-block.is-active .panel-icon,html.theme--catppuccin-frappe .content kbd.panel .panel-block.is-active .panel-icon{color:#414559}html.theme--catppuccin-frappe .panel.is-primary .panel-heading,html.theme--catppuccin-frappe .docstring>section>a.panel.docs-sourcelink .panel-heading{background-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .panel.is-primary .panel-tabs a.is-active,html.theme--catppuccin-frappe .docstring>section>a.panel.docs-sourcelink .panel-tabs a.is-active{border-bottom-color:#8caaee}html.theme--catppuccin-frappe .panel.is-primary .panel-block.is-active .panel-icon,html.theme--catppuccin-frappe .docstring>section>a.panel.docs-sourcelink .panel-block.is-active .panel-icon{color:#8caaee}html.theme--catppuccin-frappe .panel.is-link .panel-heading{background-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .panel.is-link .panel-tabs a.is-active{border-bottom-color:#8caaee}html.theme--catppuccin-frappe .panel.is-link .panel-block.is-active .panel-icon{color:#8caaee}html.theme--catppuccin-frappe .panel.is-info .panel-heading{background-color:#81c8be;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .panel.is-info .panel-tabs a.is-active{border-bottom-color:#81c8be}html.theme--catppuccin-frappe .panel.is-info .panel-block.is-active .panel-icon{color:#81c8be}html.theme--catppuccin-frappe .panel.is-success .panel-heading{background-color:#a6d189;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .panel.is-success .panel-tabs a.is-active{border-bottom-color:#a6d189}html.theme--catppuccin-frappe .panel.is-success .panel-block.is-active .panel-icon{color:#a6d189}html.theme--catppuccin-frappe .panel.is-warning .panel-heading{background-color:#e5c890;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .panel.is-warning .panel-tabs a.is-active{border-bottom-color:#e5c890}html.theme--catppuccin-frappe .panel.is-warning .panel-block.is-active .panel-icon{color:#e5c890}html.theme--catppuccin-frappe .panel.is-danger .panel-heading{background-color:#e78284;color:#fff}html.theme--catppuccin-frappe .panel.is-danger .panel-tabs a.is-active{border-bottom-color:#e78284}html.theme--catppuccin-frappe .panel.is-danger .panel-block.is-active .panel-icon{color:#e78284}html.theme--catppuccin-frappe .panel-tabs:not(:last-child),html.theme--catppuccin-frappe .panel-block:not(:last-child){border-bottom:1px solid #ededed}html.theme--catppuccin-frappe .panel-heading{background-color:#51576d;border-radius:8px 8px 0 0;color:#b0bef1;font-size:1.25em;font-weight:700;line-height:1.25;padding:0.75em 1em}html.theme--catppuccin-frappe .panel-tabs{align-items:flex-end;display:flex;font-size:.875em;justify-content:center}html.theme--catppuccin-frappe .panel-tabs a{border-bottom:1px solid #626880;margin-bottom:-1px;padding:0.5em}html.theme--catppuccin-frappe .panel-tabs a.is-active{border-bottom-color:#51576d;color:#769aeb}html.theme--catppuccin-frappe .panel-list a{color:#c6d0f5}html.theme--catppuccin-frappe .panel-list a:hover{color:#8caaee}html.theme--catppuccin-frappe .panel-block{align-items:center;color:#b0bef1;display:flex;justify-content:flex-start;padding:0.5em 0.75em}html.theme--catppuccin-frappe .panel-block input[type="checkbox"]{margin-right:.75em}html.theme--catppuccin-frappe .panel-block>.control{flex-grow:1;flex-shrink:1;width:100%}html.theme--catppuccin-frappe .panel-block.is-wrapped{flex-wrap:wrap}html.theme--catppuccin-frappe .panel-block.is-active{border-left-color:#8caaee;color:#769aeb}html.theme--catppuccin-frappe .panel-block.is-active .panel-icon{color:#8caaee}html.theme--catppuccin-frappe .panel-block:last-child{border-bottom-left-radius:8px;border-bottom-right-radius:8px}html.theme--catppuccin-frappe a.panel-block,html.theme--catppuccin-frappe label.panel-block{cursor:pointer}html.theme--catppuccin-frappe a.panel-block:hover,html.theme--catppuccin-frappe label.panel-block:hover{background-color:#292c3c}html.theme--catppuccin-frappe .panel-icon{display:inline-block;font-size:14px;height:1em;line-height:1em;text-align:center;vertical-align:top;width:1em;color:#f1f4fd;margin-right:.75em}html.theme--catppuccin-frappe .panel-icon .fa{font-size:inherit;line-height:inherit}html.theme--catppuccin-frappe .tabs{-webkit-overflow-scrolling:touch;align-items:stretch;display:flex;font-size:1rem;justify-content:space-between;overflow:hidden;overflow-x:auto;white-space:nowrap}html.theme--catppuccin-frappe .tabs a{align-items:center;border-bottom-color:#626880;border-bottom-style:solid;border-bottom-width:1px;color:#c6d0f5;display:flex;justify-content:center;margin-bottom:-1px;padding:0.5em 1em;vertical-align:top}html.theme--catppuccin-frappe .tabs a:hover{border-bottom-color:#b0bef1;color:#b0bef1}html.theme--catppuccin-frappe .tabs li{display:block}html.theme--catppuccin-frappe .tabs li.is-active a{border-bottom-color:#8caaee;color:#8caaee}html.theme--catppuccin-frappe .tabs ul{align-items:center;border-bottom-color:#626880;border-bottom-style:solid;border-bottom-width:1px;display:flex;flex-grow:1;flex-shrink:0;justify-content:flex-start}html.theme--catppuccin-frappe .tabs ul.is-left{padding-right:0.75em}html.theme--catppuccin-frappe .tabs ul.is-center{flex:none;justify-content:center;padding-left:0.75em;padding-right:0.75em}html.theme--catppuccin-frappe .tabs ul.is-right{justify-content:flex-end;padding-left:0.75em}html.theme--catppuccin-frappe .tabs .icon:first-child{margin-right:.5em}html.theme--catppuccin-frappe .tabs .icon:last-child{margin-left:.5em}html.theme--catppuccin-frappe .tabs.is-centered ul{justify-content:center}html.theme--catppuccin-frappe .tabs.is-right ul{justify-content:flex-end}html.theme--catppuccin-frappe .tabs.is-boxed a{border:1px solid transparent;border-radius:.4em .4em 0 0}html.theme--catppuccin-frappe .tabs.is-boxed a:hover{background-color:#292c3c;border-bottom-color:#626880}html.theme--catppuccin-frappe .tabs.is-boxed li.is-active a{background-color:#fff;border-color:#626880;border-bottom-color:rgba(0,0,0,0) !important}html.theme--catppuccin-frappe .tabs.is-fullwidth li{flex-grow:1;flex-shrink:0}html.theme--catppuccin-frappe .tabs.is-toggle a{border-color:#626880;border-style:solid;border-width:1px;margin-bottom:0;position:relative}html.theme--catppuccin-frappe .tabs.is-toggle a:hover{background-color:#292c3c;border-color:#737994;z-index:2}html.theme--catppuccin-frappe .tabs.is-toggle li+li{margin-left:-1px}html.theme--catppuccin-frappe .tabs.is-toggle li:first-child a{border-top-left-radius:.4em;border-bottom-left-radius:.4em}html.theme--catppuccin-frappe .tabs.is-toggle li:last-child a{border-top-right-radius:.4em;border-bottom-right-radius:.4em}html.theme--catppuccin-frappe .tabs.is-toggle li.is-active a{background-color:#8caaee;border-color:#8caaee;color:#fff;z-index:1}html.theme--catppuccin-frappe .tabs.is-toggle ul{border-bottom:none}html.theme--catppuccin-frappe .tabs.is-toggle.is-toggle-rounded li:first-child a{border-bottom-left-radius:9999px;border-top-left-radius:9999px;padding-left:1.25em}html.theme--catppuccin-frappe .tabs.is-toggle.is-toggle-rounded li:last-child a{border-bottom-right-radius:9999px;border-top-right-radius:9999px;padding-right:1.25em}html.theme--catppuccin-frappe .tabs.is-small,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.tabs{font-size:.75rem}html.theme--catppuccin-frappe .tabs.is-medium{font-size:1.25rem}html.theme--catppuccin-frappe .tabs.is-large{font-size:1.5rem}html.theme--catppuccin-frappe .column{display:block;flex-basis:0;flex-grow:1;flex-shrink:1;padding:.75rem}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-narrow{flex:none;width:unset}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-full{flex:none;width:100%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-three-quarters{flex:none;width:75%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-two-thirds{flex:none;width:66.6666%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-half{flex:none;width:50%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-one-third{flex:none;width:33.3333%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-one-quarter{flex:none;width:25%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-one-fifth{flex:none;width:20%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-two-fifths{flex:none;width:40%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-three-fifths{flex:none;width:60%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-four-fifths{flex:none;width:80%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-three-quarters{margin-left:75%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-two-thirds{margin-left:66.6666%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-half{margin-left:50%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-one-third{margin-left:33.3333%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-one-quarter{margin-left:25%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-one-fifth{margin-left:20%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-two-fifths{margin-left:40%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-three-fifths{margin-left:60%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-four-fifths{margin-left:80%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-0{flex:none;width:0%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-0{margin-left:0%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-1{flex:none;width:8.33333337%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-1{margin-left:8.33333337%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-2{flex:none;width:16.66666674%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-2{margin-left:16.66666674%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-3{flex:none;width:25%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-3{margin-left:25%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-4{flex:none;width:33.33333337%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-4{margin-left:33.33333337%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-5{flex:none;width:41.66666674%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-5{margin-left:41.66666674%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-6{flex:none;width:50%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-6{margin-left:50%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-7{flex:none;width:58.33333337%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-7{margin-left:58.33333337%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-8{flex:none;width:66.66666674%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-8{margin-left:66.66666674%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-9{flex:none;width:75%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-9{margin-left:75%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-10{flex:none;width:83.33333337%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-10{margin-left:83.33333337%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-11{flex:none;width:91.66666674%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-11{margin-left:91.66666674%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-12{flex:none;width:100%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-12{margin-left:100%}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .column.is-narrow-mobile{flex:none;width:unset}html.theme--catppuccin-frappe .column.is-full-mobile{flex:none;width:100%}html.theme--catppuccin-frappe .column.is-three-quarters-mobile{flex:none;width:75%}html.theme--catppuccin-frappe .column.is-two-thirds-mobile{flex:none;width:66.6666%}html.theme--catppuccin-frappe .column.is-half-mobile{flex:none;width:50%}html.theme--catppuccin-frappe .column.is-one-third-mobile{flex:none;width:33.3333%}html.theme--catppuccin-frappe .column.is-one-quarter-mobile{flex:none;width:25%}html.theme--catppuccin-frappe .column.is-one-fifth-mobile{flex:none;width:20%}html.theme--catppuccin-frappe .column.is-two-fifths-mobile{flex:none;width:40%}html.theme--catppuccin-frappe .column.is-three-fifths-mobile{flex:none;width:60%}html.theme--catppuccin-frappe .column.is-four-fifths-mobile{flex:none;width:80%}html.theme--catppuccin-frappe .column.is-offset-three-quarters-mobile{margin-left:75%}html.theme--catppuccin-frappe .column.is-offset-two-thirds-mobile{margin-left:66.6666%}html.theme--catppuccin-frappe .column.is-offset-half-mobile{margin-left:50%}html.theme--catppuccin-frappe .column.is-offset-one-third-mobile{margin-left:33.3333%}html.theme--catppuccin-frappe .column.is-offset-one-quarter-mobile{margin-left:25%}html.theme--catppuccin-frappe .column.is-offset-one-fifth-mobile{margin-left:20%}html.theme--catppuccin-frappe .column.is-offset-two-fifths-mobile{margin-left:40%}html.theme--catppuccin-frappe .column.is-offset-three-fifths-mobile{margin-left:60%}html.theme--catppuccin-frappe .column.is-offset-four-fifths-mobile{margin-left:80%}html.theme--catppuccin-frappe .column.is-0-mobile{flex:none;width:0%}html.theme--catppuccin-frappe .column.is-offset-0-mobile{margin-left:0%}html.theme--catppuccin-frappe .column.is-1-mobile{flex:none;width:8.33333337%}html.theme--catppuccin-frappe .column.is-offset-1-mobile{margin-left:8.33333337%}html.theme--catppuccin-frappe .column.is-2-mobile{flex:none;width:16.66666674%}html.theme--catppuccin-frappe .column.is-offset-2-mobile{margin-left:16.66666674%}html.theme--catppuccin-frappe .column.is-3-mobile{flex:none;width:25%}html.theme--catppuccin-frappe .column.is-offset-3-mobile{margin-left:25%}html.theme--catppuccin-frappe .column.is-4-mobile{flex:none;width:33.33333337%}html.theme--catppuccin-frappe .column.is-offset-4-mobile{margin-left:33.33333337%}html.theme--catppuccin-frappe .column.is-5-mobile{flex:none;width:41.66666674%}html.theme--catppuccin-frappe .column.is-offset-5-mobile{margin-left:41.66666674%}html.theme--catppuccin-frappe .column.is-6-mobile{flex:none;width:50%}html.theme--catppuccin-frappe .column.is-offset-6-mobile{margin-left:50%}html.theme--catppuccin-frappe .column.is-7-mobile{flex:none;width:58.33333337%}html.theme--catppuccin-frappe .column.is-offset-7-mobile{margin-left:58.33333337%}html.theme--catppuccin-frappe .column.is-8-mobile{flex:none;width:66.66666674%}html.theme--catppuccin-frappe .column.is-offset-8-mobile{margin-left:66.66666674%}html.theme--catppuccin-frappe .column.is-9-mobile{flex:none;width:75%}html.theme--catppuccin-frappe .column.is-offset-9-mobile{margin-left:75%}html.theme--catppuccin-frappe .column.is-10-mobile{flex:none;width:83.33333337%}html.theme--catppuccin-frappe .column.is-offset-10-mobile{margin-left:83.33333337%}html.theme--catppuccin-frappe .column.is-11-mobile{flex:none;width:91.66666674%}html.theme--catppuccin-frappe .column.is-offset-11-mobile{margin-left:91.66666674%}html.theme--catppuccin-frappe .column.is-12-mobile{flex:none;width:100%}html.theme--catppuccin-frappe .column.is-offset-12-mobile{margin-left:100%}}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .column.is-narrow,html.theme--catppuccin-frappe .column.is-narrow-tablet{flex:none;width:unset}html.theme--catppuccin-frappe .column.is-full,html.theme--catppuccin-frappe .column.is-full-tablet{flex:none;width:100%}html.theme--catppuccin-frappe .column.is-three-quarters,html.theme--catppuccin-frappe .column.is-three-quarters-tablet{flex:none;width:75%}html.theme--catppuccin-frappe .column.is-two-thirds,html.theme--catppuccin-frappe .column.is-two-thirds-tablet{flex:none;width:66.6666%}html.theme--catppuccin-frappe .column.is-half,html.theme--catppuccin-frappe .column.is-half-tablet{flex:none;width:50%}html.theme--catppuccin-frappe .column.is-one-third,html.theme--catppuccin-frappe .column.is-one-third-tablet{flex:none;width:33.3333%}html.theme--catppuccin-frappe .column.is-one-quarter,html.theme--catppuccin-frappe .column.is-one-quarter-tablet{flex:none;width:25%}html.theme--catppuccin-frappe .column.is-one-fifth,html.theme--catppuccin-frappe .column.is-one-fifth-tablet{flex:none;width:20%}html.theme--catppuccin-frappe .column.is-two-fifths,html.theme--catppuccin-frappe .column.is-two-fifths-tablet{flex:none;width:40%}html.theme--catppuccin-frappe .column.is-three-fifths,html.theme--catppuccin-frappe .column.is-three-fifths-tablet{flex:none;width:60%}html.theme--catppuccin-frappe .column.is-four-fifths,html.theme--catppuccin-frappe .column.is-four-fifths-tablet{flex:none;width:80%}html.theme--catppuccin-frappe .column.is-offset-three-quarters,html.theme--catppuccin-frappe .column.is-offset-three-quarters-tablet{margin-left:75%}html.theme--catppuccin-frappe .column.is-offset-two-thirds,html.theme--catppuccin-frappe .column.is-offset-two-thirds-tablet{margin-left:66.6666%}html.theme--catppuccin-frappe .column.is-offset-half,html.theme--catppuccin-frappe .column.is-offset-half-tablet{margin-left:50%}html.theme--catppuccin-frappe .column.is-offset-one-third,html.theme--catppuccin-frappe .column.is-offset-one-third-tablet{margin-left:33.3333%}html.theme--catppuccin-frappe .column.is-offset-one-quarter,html.theme--catppuccin-frappe .column.is-offset-one-quarter-tablet{margin-left:25%}html.theme--catppuccin-frappe .column.is-offset-one-fifth,html.theme--catppuccin-frappe .column.is-offset-one-fifth-tablet{margin-left:20%}html.theme--catppuccin-frappe .column.is-offset-two-fifths,html.theme--catppuccin-frappe .column.is-offset-two-fifths-tablet{margin-left:40%}html.theme--catppuccin-frappe .column.is-offset-three-fifths,html.theme--catppuccin-frappe .column.is-offset-three-fifths-tablet{margin-left:60%}html.theme--catppuccin-frappe .column.is-offset-four-fifths,html.theme--catppuccin-frappe .column.is-offset-four-fifths-tablet{margin-left:80%}html.theme--catppuccin-frappe .column.is-0,html.theme--catppuccin-frappe .column.is-0-tablet{flex:none;width:0%}html.theme--catppuccin-frappe .column.is-offset-0,html.theme--catppuccin-frappe .column.is-offset-0-tablet{margin-left:0%}html.theme--catppuccin-frappe .column.is-1,html.theme--catppuccin-frappe .column.is-1-tablet{flex:none;width:8.33333337%}html.theme--catppuccin-frappe .column.is-offset-1,html.theme--catppuccin-frappe .column.is-offset-1-tablet{margin-left:8.33333337%}html.theme--catppuccin-frappe .column.is-2,html.theme--catppuccin-frappe .column.is-2-tablet{flex:none;width:16.66666674%}html.theme--catppuccin-frappe .column.is-offset-2,html.theme--catppuccin-frappe .column.is-offset-2-tablet{margin-left:16.66666674%}html.theme--catppuccin-frappe .column.is-3,html.theme--catppuccin-frappe .column.is-3-tablet{flex:none;width:25%}html.theme--catppuccin-frappe .column.is-offset-3,html.theme--catppuccin-frappe .column.is-offset-3-tablet{margin-left:25%}html.theme--catppuccin-frappe .column.is-4,html.theme--catppuccin-frappe .column.is-4-tablet{flex:none;width:33.33333337%}html.theme--catppuccin-frappe .column.is-offset-4,html.theme--catppuccin-frappe .column.is-offset-4-tablet{margin-left:33.33333337%}html.theme--catppuccin-frappe .column.is-5,html.theme--catppuccin-frappe .column.is-5-tablet{flex:none;width:41.66666674%}html.theme--catppuccin-frappe .column.is-offset-5,html.theme--catppuccin-frappe .column.is-offset-5-tablet{margin-left:41.66666674%}html.theme--catppuccin-frappe .column.is-6,html.theme--catppuccin-frappe .column.is-6-tablet{flex:none;width:50%}html.theme--catppuccin-frappe .column.is-offset-6,html.theme--catppuccin-frappe .column.is-offset-6-tablet{margin-left:50%}html.theme--catppuccin-frappe .column.is-7,html.theme--catppuccin-frappe .column.is-7-tablet{flex:none;width:58.33333337%}html.theme--catppuccin-frappe .column.is-offset-7,html.theme--catppuccin-frappe .column.is-offset-7-tablet{margin-left:58.33333337%}html.theme--catppuccin-frappe .column.is-8,html.theme--catppuccin-frappe .column.is-8-tablet{flex:none;width:66.66666674%}html.theme--catppuccin-frappe .column.is-offset-8,html.theme--catppuccin-frappe .column.is-offset-8-tablet{margin-left:66.66666674%}html.theme--catppuccin-frappe .column.is-9,html.theme--catppuccin-frappe .column.is-9-tablet{flex:none;width:75%}html.theme--catppuccin-frappe .column.is-offset-9,html.theme--catppuccin-frappe .column.is-offset-9-tablet{margin-left:75%}html.theme--catppuccin-frappe .column.is-10,html.theme--catppuccin-frappe .column.is-10-tablet{flex:none;width:83.33333337%}html.theme--catppuccin-frappe .column.is-offset-10,html.theme--catppuccin-frappe .column.is-offset-10-tablet{margin-left:83.33333337%}html.theme--catppuccin-frappe .column.is-11,html.theme--catppuccin-frappe .column.is-11-tablet{flex:none;width:91.66666674%}html.theme--catppuccin-frappe .column.is-offset-11,html.theme--catppuccin-frappe .column.is-offset-11-tablet{margin-left:91.66666674%}html.theme--catppuccin-frappe .column.is-12,html.theme--catppuccin-frappe .column.is-12-tablet{flex:none;width:100%}html.theme--catppuccin-frappe .column.is-offset-12,html.theme--catppuccin-frappe .column.is-offset-12-tablet{margin-left:100%}}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .column.is-narrow-touch{flex:none;width:unset}html.theme--catppuccin-frappe .column.is-full-touch{flex:none;width:100%}html.theme--catppuccin-frappe .column.is-three-quarters-touch{flex:none;width:75%}html.theme--catppuccin-frappe .column.is-two-thirds-touch{flex:none;width:66.6666%}html.theme--catppuccin-frappe .column.is-half-touch{flex:none;width:50%}html.theme--catppuccin-frappe .column.is-one-third-touch{flex:none;width:33.3333%}html.theme--catppuccin-frappe .column.is-one-quarter-touch{flex:none;width:25%}html.theme--catppuccin-frappe .column.is-one-fifth-touch{flex:none;width:20%}html.theme--catppuccin-frappe .column.is-two-fifths-touch{flex:none;width:40%}html.theme--catppuccin-frappe .column.is-three-fifths-touch{flex:none;width:60%}html.theme--catppuccin-frappe .column.is-four-fifths-touch{flex:none;width:80%}html.theme--catppuccin-frappe .column.is-offset-three-quarters-touch{margin-left:75%}html.theme--catppuccin-frappe .column.is-offset-two-thirds-touch{margin-left:66.6666%}html.theme--catppuccin-frappe .column.is-offset-half-touch{margin-left:50%}html.theme--catppuccin-frappe .column.is-offset-one-third-touch{margin-left:33.3333%}html.theme--catppuccin-frappe .column.is-offset-one-quarter-touch{margin-left:25%}html.theme--catppuccin-frappe .column.is-offset-one-fifth-touch{margin-left:20%}html.theme--catppuccin-frappe .column.is-offset-two-fifths-touch{margin-left:40%}html.theme--catppuccin-frappe .column.is-offset-three-fifths-touch{margin-left:60%}html.theme--catppuccin-frappe .column.is-offset-four-fifths-touch{margin-left:80%}html.theme--catppuccin-frappe .column.is-0-touch{flex:none;width:0%}html.theme--catppuccin-frappe .column.is-offset-0-touch{margin-left:0%}html.theme--catppuccin-frappe .column.is-1-touch{flex:none;width:8.33333337%}html.theme--catppuccin-frappe .column.is-offset-1-touch{margin-left:8.33333337%}html.theme--catppuccin-frappe .column.is-2-touch{flex:none;width:16.66666674%}html.theme--catppuccin-frappe .column.is-offset-2-touch{margin-left:16.66666674%}html.theme--catppuccin-frappe .column.is-3-touch{flex:none;width:25%}html.theme--catppuccin-frappe .column.is-offset-3-touch{margin-left:25%}html.theme--catppuccin-frappe .column.is-4-touch{flex:none;width:33.33333337%}html.theme--catppuccin-frappe .column.is-offset-4-touch{margin-left:33.33333337%}html.theme--catppuccin-frappe .column.is-5-touch{flex:none;width:41.66666674%}html.theme--catppuccin-frappe .column.is-offset-5-touch{margin-left:41.66666674%}html.theme--catppuccin-frappe .column.is-6-touch{flex:none;width:50%}html.theme--catppuccin-frappe .column.is-offset-6-touch{margin-left:50%}html.theme--catppuccin-frappe .column.is-7-touch{flex:none;width:58.33333337%}html.theme--catppuccin-frappe .column.is-offset-7-touch{margin-left:58.33333337%}html.theme--catppuccin-frappe .column.is-8-touch{flex:none;width:66.66666674%}html.theme--catppuccin-frappe .column.is-offset-8-touch{margin-left:66.66666674%}html.theme--catppuccin-frappe .column.is-9-touch{flex:none;width:75%}html.theme--catppuccin-frappe .column.is-offset-9-touch{margin-left:75%}html.theme--catppuccin-frappe .column.is-10-touch{flex:none;width:83.33333337%}html.theme--catppuccin-frappe .column.is-offset-10-touch{margin-left:83.33333337%}html.theme--catppuccin-frappe .column.is-11-touch{flex:none;width:91.66666674%}html.theme--catppuccin-frappe .column.is-offset-11-touch{margin-left:91.66666674%}html.theme--catppuccin-frappe .column.is-12-touch{flex:none;width:100%}html.theme--catppuccin-frappe .column.is-offset-12-touch{margin-left:100%}}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .column.is-narrow-desktop{flex:none;width:unset}html.theme--catppuccin-frappe .column.is-full-desktop{flex:none;width:100%}html.theme--catppuccin-frappe .column.is-three-quarters-desktop{flex:none;width:75%}html.theme--catppuccin-frappe .column.is-two-thirds-desktop{flex:none;width:66.6666%}html.theme--catppuccin-frappe .column.is-half-desktop{flex:none;width:50%}html.theme--catppuccin-frappe .column.is-one-third-desktop{flex:none;width:33.3333%}html.theme--catppuccin-frappe .column.is-one-quarter-desktop{flex:none;width:25%}html.theme--catppuccin-frappe .column.is-one-fifth-desktop{flex:none;width:20%}html.theme--catppuccin-frappe .column.is-two-fifths-desktop{flex:none;width:40%}html.theme--catppuccin-frappe .column.is-three-fifths-desktop{flex:none;width:60%}html.theme--catppuccin-frappe .column.is-four-fifths-desktop{flex:none;width:80%}html.theme--catppuccin-frappe .column.is-offset-three-quarters-desktop{margin-left:75%}html.theme--catppuccin-frappe .column.is-offset-two-thirds-desktop{margin-left:66.6666%}html.theme--catppuccin-frappe .column.is-offset-half-desktop{margin-left:50%}html.theme--catppuccin-frappe .column.is-offset-one-third-desktop{margin-left:33.3333%}html.theme--catppuccin-frappe .column.is-offset-one-quarter-desktop{margin-left:25%}html.theme--catppuccin-frappe .column.is-offset-one-fifth-desktop{margin-left:20%}html.theme--catppuccin-frappe .column.is-offset-two-fifths-desktop{margin-left:40%}html.theme--catppuccin-frappe .column.is-offset-three-fifths-desktop{margin-left:60%}html.theme--catppuccin-frappe .column.is-offset-four-fifths-desktop{margin-left:80%}html.theme--catppuccin-frappe .column.is-0-desktop{flex:none;width:0%}html.theme--catppuccin-frappe .column.is-offset-0-desktop{margin-left:0%}html.theme--catppuccin-frappe .column.is-1-desktop{flex:none;width:8.33333337%}html.theme--catppuccin-frappe .column.is-offset-1-desktop{margin-left:8.33333337%}html.theme--catppuccin-frappe .column.is-2-desktop{flex:none;width:16.66666674%}html.theme--catppuccin-frappe .column.is-offset-2-desktop{margin-left:16.66666674%}html.theme--catppuccin-frappe .column.is-3-desktop{flex:none;width:25%}html.theme--catppuccin-frappe .column.is-offset-3-desktop{margin-left:25%}html.theme--catppuccin-frappe .column.is-4-desktop{flex:none;width:33.33333337%}html.theme--catppuccin-frappe .column.is-offset-4-desktop{margin-left:33.33333337%}html.theme--catppuccin-frappe .column.is-5-desktop{flex:none;width:41.66666674%}html.theme--catppuccin-frappe .column.is-offset-5-desktop{margin-left:41.66666674%}html.theme--catppuccin-frappe .column.is-6-desktop{flex:none;width:50%}html.theme--catppuccin-frappe .column.is-offset-6-desktop{margin-left:50%}html.theme--catppuccin-frappe .column.is-7-desktop{flex:none;width:58.33333337%}html.theme--catppuccin-frappe .column.is-offset-7-desktop{margin-left:58.33333337%}html.theme--catppuccin-frappe .column.is-8-desktop{flex:none;width:66.66666674%}html.theme--catppuccin-frappe .column.is-offset-8-desktop{margin-left:66.66666674%}html.theme--catppuccin-frappe .column.is-9-desktop{flex:none;width:75%}html.theme--catppuccin-frappe .column.is-offset-9-desktop{margin-left:75%}html.theme--catppuccin-frappe .column.is-10-desktop{flex:none;width:83.33333337%}html.theme--catppuccin-frappe .column.is-offset-10-desktop{margin-left:83.33333337%}html.theme--catppuccin-frappe .column.is-11-desktop{flex:none;width:91.66666674%}html.theme--catppuccin-frappe .column.is-offset-11-desktop{margin-left:91.66666674%}html.theme--catppuccin-frappe .column.is-12-desktop{flex:none;width:100%}html.theme--catppuccin-frappe .column.is-offset-12-desktop{margin-left:100%}}@media screen and (min-width: 1216px){html.theme--catppuccin-frappe .column.is-narrow-widescreen{flex:none;width:unset}html.theme--catppuccin-frappe .column.is-full-widescreen{flex:none;width:100%}html.theme--catppuccin-frappe .column.is-three-quarters-widescreen{flex:none;width:75%}html.theme--catppuccin-frappe .column.is-two-thirds-widescreen{flex:none;width:66.6666%}html.theme--catppuccin-frappe .column.is-half-widescreen{flex:none;width:50%}html.theme--catppuccin-frappe .column.is-one-third-widescreen{flex:none;width:33.3333%}html.theme--catppuccin-frappe .column.is-one-quarter-widescreen{flex:none;width:25%}html.theme--catppuccin-frappe .column.is-one-fifth-widescreen{flex:none;width:20%}html.theme--catppuccin-frappe .column.is-two-fifths-widescreen{flex:none;width:40%}html.theme--catppuccin-frappe .column.is-three-fifths-widescreen{flex:none;width:60%}html.theme--catppuccin-frappe .column.is-four-fifths-widescreen{flex:none;width:80%}html.theme--catppuccin-frappe .column.is-offset-three-quarters-widescreen{margin-left:75%}html.theme--catppuccin-frappe .column.is-offset-two-thirds-widescreen{margin-left:66.6666%}html.theme--catppuccin-frappe .column.is-offset-half-widescreen{margin-left:50%}html.theme--catppuccin-frappe .column.is-offset-one-third-widescreen{margin-left:33.3333%}html.theme--catppuccin-frappe .column.is-offset-one-quarter-widescreen{margin-left:25%}html.theme--catppuccin-frappe .column.is-offset-one-fifth-widescreen{margin-left:20%}html.theme--catppuccin-frappe .column.is-offset-two-fifths-widescreen{margin-left:40%}html.theme--catppuccin-frappe .column.is-offset-three-fifths-widescreen{margin-left:60%}html.theme--catppuccin-frappe .column.is-offset-four-fifths-widescreen{margin-left:80%}html.theme--catppuccin-frappe .column.is-0-widescreen{flex:none;width:0%}html.theme--catppuccin-frappe .column.is-offset-0-widescreen{margin-left:0%}html.theme--catppuccin-frappe .column.is-1-widescreen{flex:none;width:8.33333337%}html.theme--catppuccin-frappe .column.is-offset-1-widescreen{margin-left:8.33333337%}html.theme--catppuccin-frappe .column.is-2-widescreen{flex:none;width:16.66666674%}html.theme--catppuccin-frappe .column.is-offset-2-widescreen{margin-left:16.66666674%}html.theme--catppuccin-frappe .column.is-3-widescreen{flex:none;width:25%}html.theme--catppuccin-frappe .column.is-offset-3-widescreen{margin-left:25%}html.theme--catppuccin-frappe .column.is-4-widescreen{flex:none;width:33.33333337%}html.theme--catppuccin-frappe .column.is-offset-4-widescreen{margin-left:33.33333337%}html.theme--catppuccin-frappe .column.is-5-widescreen{flex:none;width:41.66666674%}html.theme--catppuccin-frappe .column.is-offset-5-widescreen{margin-left:41.66666674%}html.theme--catppuccin-frappe .column.is-6-widescreen{flex:none;width:50%}html.theme--catppuccin-frappe .column.is-offset-6-widescreen{margin-left:50%}html.theme--catppuccin-frappe .column.is-7-widescreen{flex:none;width:58.33333337%}html.theme--catppuccin-frappe .column.is-offset-7-widescreen{margin-left:58.33333337%}html.theme--catppuccin-frappe .column.is-8-widescreen{flex:none;width:66.66666674%}html.theme--catppuccin-frappe .column.is-offset-8-widescreen{margin-left:66.66666674%}html.theme--catppuccin-frappe .column.is-9-widescreen{flex:none;width:75%}html.theme--catppuccin-frappe .column.is-offset-9-widescreen{margin-left:75%}html.theme--catppuccin-frappe .column.is-10-widescreen{flex:none;width:83.33333337%}html.theme--catppuccin-frappe .column.is-offset-10-widescreen{margin-left:83.33333337%}html.theme--catppuccin-frappe .column.is-11-widescreen{flex:none;width:91.66666674%}html.theme--catppuccin-frappe .column.is-offset-11-widescreen{margin-left:91.66666674%}html.theme--catppuccin-frappe .column.is-12-widescreen{flex:none;width:100%}html.theme--catppuccin-frappe .column.is-offset-12-widescreen{margin-left:100%}}@media screen and (min-width: 1408px){html.theme--catppuccin-frappe .column.is-narrow-fullhd{flex:none;width:unset}html.theme--catppuccin-frappe .column.is-full-fullhd{flex:none;width:100%}html.theme--catppuccin-frappe .column.is-three-quarters-fullhd{flex:none;width:75%}html.theme--catppuccin-frappe .column.is-two-thirds-fullhd{flex:none;width:66.6666%}html.theme--catppuccin-frappe .column.is-half-fullhd{flex:none;width:50%}html.theme--catppuccin-frappe .column.is-one-third-fullhd{flex:none;width:33.3333%}html.theme--catppuccin-frappe .column.is-one-quarter-fullhd{flex:none;width:25%}html.theme--catppuccin-frappe .column.is-one-fifth-fullhd{flex:none;width:20%}html.theme--catppuccin-frappe .column.is-two-fifths-fullhd{flex:none;width:40%}html.theme--catppuccin-frappe .column.is-three-fifths-fullhd{flex:none;width:60%}html.theme--catppuccin-frappe .column.is-four-fifths-fullhd{flex:none;width:80%}html.theme--catppuccin-frappe .column.is-offset-three-quarters-fullhd{margin-left:75%}html.theme--catppuccin-frappe .column.is-offset-two-thirds-fullhd{margin-left:66.6666%}html.theme--catppuccin-frappe .column.is-offset-half-fullhd{margin-left:50%}html.theme--catppuccin-frappe .column.is-offset-one-third-fullhd{margin-left:33.3333%}html.theme--catppuccin-frappe .column.is-offset-one-quarter-fullhd{margin-left:25%}html.theme--catppuccin-frappe .column.is-offset-one-fifth-fullhd{margin-left:20%}html.theme--catppuccin-frappe .column.is-offset-two-fifths-fullhd{margin-left:40%}html.theme--catppuccin-frappe .column.is-offset-three-fifths-fullhd{margin-left:60%}html.theme--catppuccin-frappe .column.is-offset-four-fifths-fullhd{margin-left:80%}html.theme--catppuccin-frappe .column.is-0-fullhd{flex:none;width:0%}html.theme--catppuccin-frappe .column.is-offset-0-fullhd{margin-left:0%}html.theme--catppuccin-frappe .column.is-1-fullhd{flex:none;width:8.33333337%}html.theme--catppuccin-frappe .column.is-offset-1-fullhd{margin-left:8.33333337%}html.theme--catppuccin-frappe .column.is-2-fullhd{flex:none;width:16.66666674%}html.theme--catppuccin-frappe .column.is-offset-2-fullhd{margin-left:16.66666674%}html.theme--catppuccin-frappe .column.is-3-fullhd{flex:none;width:25%}html.theme--catppuccin-frappe .column.is-offset-3-fullhd{margin-left:25%}html.theme--catppuccin-frappe .column.is-4-fullhd{flex:none;width:33.33333337%}html.theme--catppuccin-frappe .column.is-offset-4-fullhd{margin-left:33.33333337%}html.theme--catppuccin-frappe .column.is-5-fullhd{flex:none;width:41.66666674%}html.theme--catppuccin-frappe .column.is-offset-5-fullhd{margin-left:41.66666674%}html.theme--catppuccin-frappe .column.is-6-fullhd{flex:none;width:50%}html.theme--catppuccin-frappe .column.is-offset-6-fullhd{margin-left:50%}html.theme--catppuccin-frappe .column.is-7-fullhd{flex:none;width:58.33333337%}html.theme--catppuccin-frappe .column.is-offset-7-fullhd{margin-left:58.33333337%}html.theme--catppuccin-frappe .column.is-8-fullhd{flex:none;width:66.66666674%}html.theme--catppuccin-frappe .column.is-offset-8-fullhd{margin-left:66.66666674%}html.theme--catppuccin-frappe .column.is-9-fullhd{flex:none;width:75%}html.theme--catppuccin-frappe .column.is-offset-9-fullhd{margin-left:75%}html.theme--catppuccin-frappe .column.is-10-fullhd{flex:none;width:83.33333337%}html.theme--catppuccin-frappe .column.is-offset-10-fullhd{margin-left:83.33333337%}html.theme--catppuccin-frappe .column.is-11-fullhd{flex:none;width:91.66666674%}html.theme--catppuccin-frappe .column.is-offset-11-fullhd{margin-left:91.66666674%}html.theme--catppuccin-frappe .column.is-12-fullhd{flex:none;width:100%}html.theme--catppuccin-frappe .column.is-offset-12-fullhd{margin-left:100%}}html.theme--catppuccin-frappe .columns{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}html.theme--catppuccin-frappe .columns:last-child{margin-bottom:-.75rem}html.theme--catppuccin-frappe .columns:not(:last-child){margin-bottom:calc(1.5rem - .75rem)}html.theme--catppuccin-frappe .columns.is-centered{justify-content:center}html.theme--catppuccin-frappe .columns.is-gapless{margin-left:0;margin-right:0;margin-top:0}html.theme--catppuccin-frappe .columns.is-gapless>.column{margin:0;padding:0 !important}html.theme--catppuccin-frappe .columns.is-gapless:not(:last-child){margin-bottom:1.5rem}html.theme--catppuccin-frappe .columns.is-gapless:last-child{margin-bottom:0}html.theme--catppuccin-frappe .columns.is-mobile{display:flex}html.theme--catppuccin-frappe .columns.is-multiline{flex-wrap:wrap}html.theme--catppuccin-frappe .columns.is-vcentered{align-items:center}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .columns:not(.is-desktop){display:flex}}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .columns.is-desktop{display:flex}}html.theme--catppuccin-frappe .columns.is-variable{--columnGap: 0.75rem;margin-left:calc(-1 * var(--columnGap));margin-right:calc(-1 * var(--columnGap))}html.theme--catppuccin-frappe .columns.is-variable>.column{padding-left:var(--columnGap);padding-right:var(--columnGap)}html.theme--catppuccin-frappe .columns.is-variable.is-0{--columnGap: 0rem}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .columns.is-variable.is-0-mobile{--columnGap: 0rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .columns.is-variable.is-0-tablet{--columnGap: 0rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-frappe .columns.is-variable.is-0-tablet-only{--columnGap: 0rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .columns.is-variable.is-0-touch{--columnGap: 0rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .columns.is-variable.is-0-desktop{--columnGap: 0rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-frappe .columns.is-variable.is-0-desktop-only{--columnGap: 0rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-frappe .columns.is-variable.is-0-widescreen{--columnGap: 0rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-frappe .columns.is-variable.is-0-widescreen-only{--columnGap: 0rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-frappe .columns.is-variable.is-0-fullhd{--columnGap: 0rem}}html.theme--catppuccin-frappe .columns.is-variable.is-1{--columnGap: .25rem}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .columns.is-variable.is-1-mobile{--columnGap: .25rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .columns.is-variable.is-1-tablet{--columnGap: .25rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-frappe .columns.is-variable.is-1-tablet-only{--columnGap: .25rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .columns.is-variable.is-1-touch{--columnGap: .25rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .columns.is-variable.is-1-desktop{--columnGap: .25rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-frappe .columns.is-variable.is-1-desktop-only{--columnGap: .25rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-frappe .columns.is-variable.is-1-widescreen{--columnGap: .25rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-frappe .columns.is-variable.is-1-widescreen-only{--columnGap: .25rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-frappe .columns.is-variable.is-1-fullhd{--columnGap: .25rem}}html.theme--catppuccin-frappe .columns.is-variable.is-2{--columnGap: .5rem}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .columns.is-variable.is-2-mobile{--columnGap: .5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .columns.is-variable.is-2-tablet{--columnGap: .5rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-frappe .columns.is-variable.is-2-tablet-only{--columnGap: .5rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .columns.is-variable.is-2-touch{--columnGap: .5rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .columns.is-variable.is-2-desktop{--columnGap: .5rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-frappe .columns.is-variable.is-2-desktop-only{--columnGap: .5rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-frappe .columns.is-variable.is-2-widescreen{--columnGap: .5rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-frappe .columns.is-variable.is-2-widescreen-only{--columnGap: .5rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-frappe .columns.is-variable.is-2-fullhd{--columnGap: .5rem}}html.theme--catppuccin-frappe .columns.is-variable.is-3{--columnGap: .75rem}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .columns.is-variable.is-3-mobile{--columnGap: .75rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .columns.is-variable.is-3-tablet{--columnGap: .75rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-frappe .columns.is-variable.is-3-tablet-only{--columnGap: .75rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .columns.is-variable.is-3-touch{--columnGap: .75rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .columns.is-variable.is-3-desktop{--columnGap: .75rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-frappe .columns.is-variable.is-3-desktop-only{--columnGap: .75rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-frappe .columns.is-variable.is-3-widescreen{--columnGap: .75rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-frappe .columns.is-variable.is-3-widescreen-only{--columnGap: .75rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-frappe .columns.is-variable.is-3-fullhd{--columnGap: .75rem}}html.theme--catppuccin-frappe .columns.is-variable.is-4{--columnGap: 1rem}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .columns.is-variable.is-4-mobile{--columnGap: 1rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .columns.is-variable.is-4-tablet{--columnGap: 1rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-frappe .columns.is-variable.is-4-tablet-only{--columnGap: 1rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .columns.is-variable.is-4-touch{--columnGap: 1rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .columns.is-variable.is-4-desktop{--columnGap: 1rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-frappe .columns.is-variable.is-4-desktop-only{--columnGap: 1rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-frappe .columns.is-variable.is-4-widescreen{--columnGap: 1rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-frappe .columns.is-variable.is-4-widescreen-only{--columnGap: 1rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-frappe .columns.is-variable.is-4-fullhd{--columnGap: 1rem}}html.theme--catppuccin-frappe .columns.is-variable.is-5{--columnGap: 1.25rem}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .columns.is-variable.is-5-mobile{--columnGap: 1.25rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .columns.is-variable.is-5-tablet{--columnGap: 1.25rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-frappe .columns.is-variable.is-5-tablet-only{--columnGap: 1.25rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .columns.is-variable.is-5-touch{--columnGap: 1.25rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .columns.is-variable.is-5-desktop{--columnGap: 1.25rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-frappe .columns.is-variable.is-5-desktop-only{--columnGap: 1.25rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-frappe .columns.is-variable.is-5-widescreen{--columnGap: 1.25rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-frappe .columns.is-variable.is-5-widescreen-only{--columnGap: 1.25rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-frappe .columns.is-variable.is-5-fullhd{--columnGap: 1.25rem}}html.theme--catppuccin-frappe .columns.is-variable.is-6{--columnGap: 1.5rem}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .columns.is-variable.is-6-mobile{--columnGap: 1.5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .columns.is-variable.is-6-tablet{--columnGap: 1.5rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-frappe .columns.is-variable.is-6-tablet-only{--columnGap: 1.5rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .columns.is-variable.is-6-touch{--columnGap: 1.5rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .columns.is-variable.is-6-desktop{--columnGap: 1.5rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-frappe .columns.is-variable.is-6-desktop-only{--columnGap: 1.5rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-frappe .columns.is-variable.is-6-widescreen{--columnGap: 1.5rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-frappe .columns.is-variable.is-6-widescreen-only{--columnGap: 1.5rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-frappe .columns.is-variable.is-6-fullhd{--columnGap: 1.5rem}}html.theme--catppuccin-frappe .columns.is-variable.is-7{--columnGap: 1.75rem}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .columns.is-variable.is-7-mobile{--columnGap: 1.75rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .columns.is-variable.is-7-tablet{--columnGap: 1.75rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-frappe .columns.is-variable.is-7-tablet-only{--columnGap: 1.75rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .columns.is-variable.is-7-touch{--columnGap: 1.75rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .columns.is-variable.is-7-desktop{--columnGap: 1.75rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-frappe .columns.is-variable.is-7-desktop-only{--columnGap: 1.75rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-frappe .columns.is-variable.is-7-widescreen{--columnGap: 1.75rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-frappe .columns.is-variable.is-7-widescreen-only{--columnGap: 1.75rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-frappe .columns.is-variable.is-7-fullhd{--columnGap: 1.75rem}}html.theme--catppuccin-frappe .columns.is-variable.is-8{--columnGap: 2rem}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .columns.is-variable.is-8-mobile{--columnGap: 2rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .columns.is-variable.is-8-tablet{--columnGap: 2rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-frappe .columns.is-variable.is-8-tablet-only{--columnGap: 2rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .columns.is-variable.is-8-touch{--columnGap: 2rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .columns.is-variable.is-8-desktop{--columnGap: 2rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-frappe .columns.is-variable.is-8-desktop-only{--columnGap: 2rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-frappe .columns.is-variable.is-8-widescreen{--columnGap: 2rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-frappe .columns.is-variable.is-8-widescreen-only{--columnGap: 2rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-frappe .columns.is-variable.is-8-fullhd{--columnGap: 2rem}}html.theme--catppuccin-frappe .tile{align-items:stretch;display:block;flex-basis:0;flex-grow:1;flex-shrink:1;min-height:min-content}html.theme--catppuccin-frappe .tile.is-ancestor{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}html.theme--catppuccin-frappe .tile.is-ancestor:last-child{margin-bottom:-.75rem}html.theme--catppuccin-frappe .tile.is-ancestor:not(:last-child){margin-bottom:.75rem}html.theme--catppuccin-frappe .tile.is-child{margin:0 !important}html.theme--catppuccin-frappe .tile.is-parent{padding:.75rem}html.theme--catppuccin-frappe .tile.is-vertical{flex-direction:column}html.theme--catppuccin-frappe .tile.is-vertical>.tile.is-child:not(:last-child){margin-bottom:1.5rem !important}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .tile:not(.is-child){display:flex}html.theme--catppuccin-frappe .tile.is-1{flex:none;width:8.33333337%}html.theme--catppuccin-frappe .tile.is-2{flex:none;width:16.66666674%}html.theme--catppuccin-frappe .tile.is-3{flex:none;width:25%}html.theme--catppuccin-frappe .tile.is-4{flex:none;width:33.33333337%}html.theme--catppuccin-frappe .tile.is-5{flex:none;width:41.66666674%}html.theme--catppuccin-frappe .tile.is-6{flex:none;width:50%}html.theme--catppuccin-frappe .tile.is-7{flex:none;width:58.33333337%}html.theme--catppuccin-frappe .tile.is-8{flex:none;width:66.66666674%}html.theme--catppuccin-frappe .tile.is-9{flex:none;width:75%}html.theme--catppuccin-frappe .tile.is-10{flex:none;width:83.33333337%}html.theme--catppuccin-frappe .tile.is-11{flex:none;width:91.66666674%}html.theme--catppuccin-frappe .tile.is-12{flex:none;width:100%}}html.theme--catppuccin-frappe .hero{align-items:stretch;display:flex;flex-direction:column;justify-content:space-between}html.theme--catppuccin-frappe .hero .navbar{background:none}html.theme--catppuccin-frappe .hero .tabs ul{border-bottom:none}html.theme--catppuccin-frappe .hero.is-white{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-frappe .hero.is-white a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-frappe .hero.is-white strong{color:inherit}html.theme--catppuccin-frappe .hero.is-white .title{color:#0a0a0a}html.theme--catppuccin-frappe .hero.is-white .subtitle{color:rgba(10,10,10,0.9)}html.theme--catppuccin-frappe .hero.is-white .subtitle a:not(.button),html.theme--catppuccin-frappe .hero.is-white .subtitle strong{color:#0a0a0a}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .hero.is-white .navbar-menu{background-color:#fff}}html.theme--catppuccin-frappe .hero.is-white .navbar-item,html.theme--catppuccin-frappe .hero.is-white .navbar-link{color:rgba(10,10,10,0.7)}html.theme--catppuccin-frappe .hero.is-white a.navbar-item:hover,html.theme--catppuccin-frappe .hero.is-white a.navbar-item.is-active,html.theme--catppuccin-frappe .hero.is-white .navbar-link:hover,html.theme--catppuccin-frappe .hero.is-white .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}html.theme--catppuccin-frappe .hero.is-white .tabs a{color:#0a0a0a;opacity:0.9}html.theme--catppuccin-frappe .hero.is-white .tabs a:hover{opacity:1}html.theme--catppuccin-frappe .hero.is-white .tabs li.is-active a{color:#fff !important;opacity:1}html.theme--catppuccin-frappe .hero.is-white .tabs.is-boxed a,html.theme--catppuccin-frappe .hero.is-white .tabs.is-toggle a{color:#0a0a0a}html.theme--catppuccin-frappe .hero.is-white .tabs.is-boxed a:hover,html.theme--catppuccin-frappe .hero.is-white .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-frappe .hero.is-white .tabs.is-boxed li.is-active a,html.theme--catppuccin-frappe .hero.is-white .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-frappe .hero.is-white .tabs.is-toggle li.is-active a,html.theme--catppuccin-frappe .hero.is-white .tabs.is-toggle li.is-active a:hover{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}html.theme--catppuccin-frappe .hero.is-white.is-bold{background-image:linear-gradient(141deg, #e8e3e4 0%, #fff 71%, #fff 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .hero.is-white.is-bold .navbar-menu{background-image:linear-gradient(141deg, #e8e3e4 0%, #fff 71%, #fff 100%)}}html.theme--catppuccin-frappe .hero.is-black{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-frappe .hero.is-black a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-frappe .hero.is-black strong{color:inherit}html.theme--catppuccin-frappe .hero.is-black .title{color:#fff}html.theme--catppuccin-frappe .hero.is-black .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-frappe .hero.is-black .subtitle a:not(.button),html.theme--catppuccin-frappe .hero.is-black .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .hero.is-black .navbar-menu{background-color:#0a0a0a}}html.theme--catppuccin-frappe .hero.is-black .navbar-item,html.theme--catppuccin-frappe .hero.is-black .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-frappe .hero.is-black a.navbar-item:hover,html.theme--catppuccin-frappe .hero.is-black a.navbar-item.is-active,html.theme--catppuccin-frappe .hero.is-black .navbar-link:hover,html.theme--catppuccin-frappe .hero.is-black .navbar-link.is-active{background-color:#000;color:#fff}html.theme--catppuccin-frappe .hero.is-black .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-frappe .hero.is-black .tabs a:hover{opacity:1}html.theme--catppuccin-frappe .hero.is-black .tabs li.is-active a{color:#0a0a0a !important;opacity:1}html.theme--catppuccin-frappe .hero.is-black .tabs.is-boxed a,html.theme--catppuccin-frappe .hero.is-black .tabs.is-toggle a{color:#fff}html.theme--catppuccin-frappe .hero.is-black .tabs.is-boxed a:hover,html.theme--catppuccin-frappe .hero.is-black .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-frappe .hero.is-black .tabs.is-boxed li.is-active a,html.theme--catppuccin-frappe .hero.is-black .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-frappe .hero.is-black .tabs.is-toggle li.is-active a,html.theme--catppuccin-frappe .hero.is-black .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#0a0a0a}html.theme--catppuccin-frappe .hero.is-black.is-bold{background-image:linear-gradient(141deg, #000 0%, #0a0a0a 71%, #181616 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .hero.is-black.is-bold .navbar-menu{background-image:linear-gradient(141deg, #000 0%, #0a0a0a 71%, #181616 100%)}}html.theme--catppuccin-frappe .hero.is-light{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-light a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-frappe .hero.is-light strong{color:inherit}html.theme--catppuccin-frappe .hero.is-light .title{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-light .subtitle{color:rgba(0,0,0,0.9)}html.theme--catppuccin-frappe .hero.is-light .subtitle a:not(.button),html.theme--catppuccin-frappe .hero.is-light .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .hero.is-light .navbar-menu{background-color:#f5f5f5}}html.theme--catppuccin-frappe .hero.is-light .navbar-item,html.theme--catppuccin-frappe .hero.is-light .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-light a.navbar-item:hover,html.theme--catppuccin-frappe .hero.is-light a.navbar-item.is-active,html.theme--catppuccin-frappe .hero.is-light .navbar-link:hover,html.theme--catppuccin-frappe .hero.is-light .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-light .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}html.theme--catppuccin-frappe .hero.is-light .tabs a:hover{opacity:1}html.theme--catppuccin-frappe .hero.is-light .tabs li.is-active a{color:#f5f5f5 !important;opacity:1}html.theme--catppuccin-frappe .hero.is-light .tabs.is-boxed a,html.theme--catppuccin-frappe .hero.is-light .tabs.is-toggle a{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-light .tabs.is-boxed a:hover,html.theme--catppuccin-frappe .hero.is-light .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-frappe .hero.is-light .tabs.is-boxed li.is-active a,html.theme--catppuccin-frappe .hero.is-light .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-frappe .hero.is-light .tabs.is-toggle li.is-active a,html.theme--catppuccin-frappe .hero.is-light .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#f5f5f5}html.theme--catppuccin-frappe .hero.is-light.is-bold{background-image:linear-gradient(141deg, #dfd8d9 0%, #f5f5f5 71%, #fff 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .hero.is-light.is-bold .navbar-menu{background-image:linear-gradient(141deg, #dfd8d9 0%, #f5f5f5 71%, #fff 100%)}}html.theme--catppuccin-frappe .hero.is-dark,html.theme--catppuccin-frappe .content kbd.hero{background-color:#414559;color:#fff}html.theme--catppuccin-frappe .hero.is-dark a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-frappe .content kbd.hero a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-frappe .hero.is-dark strong,html.theme--catppuccin-frappe .content kbd.hero strong{color:inherit}html.theme--catppuccin-frappe .hero.is-dark .title,html.theme--catppuccin-frappe .content kbd.hero .title{color:#fff}html.theme--catppuccin-frappe .hero.is-dark .subtitle,html.theme--catppuccin-frappe .content kbd.hero .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-frappe .hero.is-dark .subtitle a:not(.button),html.theme--catppuccin-frappe .content kbd.hero .subtitle a:not(.button),html.theme--catppuccin-frappe .hero.is-dark .subtitle strong,html.theme--catppuccin-frappe .content kbd.hero .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .hero.is-dark .navbar-menu,html.theme--catppuccin-frappe .content kbd.hero .navbar-menu{background-color:#414559}}html.theme--catppuccin-frappe .hero.is-dark .navbar-item,html.theme--catppuccin-frappe .content kbd.hero .navbar-item,html.theme--catppuccin-frappe .hero.is-dark .navbar-link,html.theme--catppuccin-frappe .content kbd.hero .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-frappe .hero.is-dark a.navbar-item:hover,html.theme--catppuccin-frappe .content kbd.hero a.navbar-item:hover,html.theme--catppuccin-frappe .hero.is-dark a.navbar-item.is-active,html.theme--catppuccin-frappe .content kbd.hero a.navbar-item.is-active,html.theme--catppuccin-frappe .hero.is-dark .navbar-link:hover,html.theme--catppuccin-frappe .content kbd.hero .navbar-link:hover,html.theme--catppuccin-frappe .hero.is-dark .navbar-link.is-active,html.theme--catppuccin-frappe .content kbd.hero .navbar-link.is-active{background-color:#363a4a;color:#fff}html.theme--catppuccin-frappe .hero.is-dark .tabs a,html.theme--catppuccin-frappe .content kbd.hero .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-frappe .hero.is-dark .tabs a:hover,html.theme--catppuccin-frappe .content kbd.hero .tabs a:hover{opacity:1}html.theme--catppuccin-frappe .hero.is-dark .tabs li.is-active a,html.theme--catppuccin-frappe .content kbd.hero .tabs li.is-active a{color:#414559 !important;opacity:1}html.theme--catppuccin-frappe .hero.is-dark .tabs.is-boxed a,html.theme--catppuccin-frappe .content kbd.hero .tabs.is-boxed a,html.theme--catppuccin-frappe .hero.is-dark .tabs.is-toggle a,html.theme--catppuccin-frappe .content kbd.hero .tabs.is-toggle a{color:#fff}html.theme--catppuccin-frappe .hero.is-dark .tabs.is-boxed a:hover,html.theme--catppuccin-frappe .content kbd.hero .tabs.is-boxed a:hover,html.theme--catppuccin-frappe .hero.is-dark .tabs.is-toggle a:hover,html.theme--catppuccin-frappe .content kbd.hero .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-frappe .hero.is-dark .tabs.is-boxed li.is-active a,html.theme--catppuccin-frappe .content kbd.hero .tabs.is-boxed li.is-active a,html.theme--catppuccin-frappe .hero.is-dark .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-frappe .hero.is-dark .tabs.is-toggle li.is-active a,html.theme--catppuccin-frappe .content kbd.hero .tabs.is-toggle li.is-active a,html.theme--catppuccin-frappe .hero.is-dark .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#414559}html.theme--catppuccin-frappe .hero.is-dark.is-bold,html.theme--catppuccin-frappe .content kbd.hero.is-bold{background-image:linear-gradient(141deg, #262f41 0%, #414559 71%, #47476c 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .hero.is-dark.is-bold .navbar-menu,html.theme--catppuccin-frappe .content kbd.hero.is-bold .navbar-menu{background-image:linear-gradient(141deg, #262f41 0%, #414559 71%, #47476c 100%)}}html.theme--catppuccin-frappe .hero.is-primary,html.theme--catppuccin-frappe .docstring>section>a.hero.docs-sourcelink{background-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .hero.is-primary a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-frappe .docstring>section>a.hero.docs-sourcelink a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-frappe .hero.is-primary strong,html.theme--catppuccin-frappe .docstring>section>a.hero.docs-sourcelink strong{color:inherit}html.theme--catppuccin-frappe .hero.is-primary .title,html.theme--catppuccin-frappe .docstring>section>a.hero.docs-sourcelink .title{color:#fff}html.theme--catppuccin-frappe .hero.is-primary .subtitle,html.theme--catppuccin-frappe .docstring>section>a.hero.docs-sourcelink .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-frappe .hero.is-primary .subtitle a:not(.button),html.theme--catppuccin-frappe .docstring>section>a.hero.docs-sourcelink .subtitle a:not(.button),html.theme--catppuccin-frappe .hero.is-primary .subtitle strong,html.theme--catppuccin-frappe .docstring>section>a.hero.docs-sourcelink .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .hero.is-primary .navbar-menu,html.theme--catppuccin-frappe .docstring>section>a.hero.docs-sourcelink .navbar-menu{background-color:#8caaee}}html.theme--catppuccin-frappe .hero.is-primary .navbar-item,html.theme--catppuccin-frappe .docstring>section>a.hero.docs-sourcelink .navbar-item,html.theme--catppuccin-frappe .hero.is-primary .navbar-link,html.theme--catppuccin-frappe .docstring>section>a.hero.docs-sourcelink .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-frappe .hero.is-primary a.navbar-item:hover,html.theme--catppuccin-frappe .docstring>section>a.hero.docs-sourcelink a.navbar-item:hover,html.theme--catppuccin-frappe .hero.is-primary a.navbar-item.is-active,html.theme--catppuccin-frappe .docstring>section>a.hero.docs-sourcelink a.navbar-item.is-active,html.theme--catppuccin-frappe .hero.is-primary .navbar-link:hover,html.theme--catppuccin-frappe .docstring>section>a.hero.docs-sourcelink .navbar-link:hover,html.theme--catppuccin-frappe .hero.is-primary .navbar-link.is-active,html.theme--catppuccin-frappe .docstring>section>a.hero.docs-sourcelink .navbar-link.is-active{background-color:#769aeb;color:#fff}html.theme--catppuccin-frappe .hero.is-primary .tabs a,html.theme--catppuccin-frappe .docstring>section>a.hero.docs-sourcelink .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-frappe .hero.is-primary .tabs a:hover,html.theme--catppuccin-frappe .docstring>section>a.hero.docs-sourcelink .tabs a:hover{opacity:1}html.theme--catppuccin-frappe .hero.is-primary .tabs li.is-active a,html.theme--catppuccin-frappe .docstring>section>a.hero.docs-sourcelink .tabs li.is-active a{color:#8caaee !important;opacity:1}html.theme--catppuccin-frappe .hero.is-primary .tabs.is-boxed a,html.theme--catppuccin-frappe .docstring>section>a.hero.docs-sourcelink .tabs.is-boxed a,html.theme--catppuccin-frappe .hero.is-primary .tabs.is-toggle a,html.theme--catppuccin-frappe .docstring>section>a.hero.docs-sourcelink .tabs.is-toggle a{color:#fff}html.theme--catppuccin-frappe .hero.is-primary .tabs.is-boxed a:hover,html.theme--catppuccin-frappe .docstring>section>a.hero.docs-sourcelink .tabs.is-boxed a:hover,html.theme--catppuccin-frappe .hero.is-primary .tabs.is-toggle a:hover,html.theme--catppuccin-frappe .docstring>section>a.hero.docs-sourcelink .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-frappe .hero.is-primary .tabs.is-boxed li.is-active a,html.theme--catppuccin-frappe .docstring>section>a.hero.docs-sourcelink .tabs.is-boxed li.is-active a,html.theme--catppuccin-frappe .hero.is-primary .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-frappe .hero.is-primary .tabs.is-toggle li.is-active a,html.theme--catppuccin-frappe .docstring>section>a.hero.docs-sourcelink .tabs.is-toggle li.is-active a,html.theme--catppuccin-frappe .hero.is-primary .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#8caaee}html.theme--catppuccin-frappe .hero.is-primary.is-bold,html.theme--catppuccin-frappe .docstring>section>a.hero.is-bold.docs-sourcelink{background-image:linear-gradient(141deg, #569ff1 0%, #8caaee 71%, #a0abf4 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .hero.is-primary.is-bold .navbar-menu,html.theme--catppuccin-frappe .docstring>section>a.hero.is-bold.docs-sourcelink .navbar-menu{background-image:linear-gradient(141deg, #569ff1 0%, #8caaee 71%, #a0abf4 100%)}}html.theme--catppuccin-frappe .hero.is-link{background-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .hero.is-link a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-frappe .hero.is-link strong{color:inherit}html.theme--catppuccin-frappe .hero.is-link .title{color:#fff}html.theme--catppuccin-frappe .hero.is-link .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-frappe .hero.is-link .subtitle a:not(.button),html.theme--catppuccin-frappe .hero.is-link .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .hero.is-link .navbar-menu{background-color:#8caaee}}html.theme--catppuccin-frappe .hero.is-link .navbar-item,html.theme--catppuccin-frappe .hero.is-link .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-frappe .hero.is-link a.navbar-item:hover,html.theme--catppuccin-frappe .hero.is-link a.navbar-item.is-active,html.theme--catppuccin-frappe .hero.is-link .navbar-link:hover,html.theme--catppuccin-frappe .hero.is-link .navbar-link.is-active{background-color:#769aeb;color:#fff}html.theme--catppuccin-frappe .hero.is-link .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-frappe .hero.is-link .tabs a:hover{opacity:1}html.theme--catppuccin-frappe .hero.is-link .tabs li.is-active a{color:#8caaee !important;opacity:1}html.theme--catppuccin-frappe .hero.is-link .tabs.is-boxed a,html.theme--catppuccin-frappe .hero.is-link .tabs.is-toggle a{color:#fff}html.theme--catppuccin-frappe .hero.is-link .tabs.is-boxed a:hover,html.theme--catppuccin-frappe .hero.is-link .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-frappe .hero.is-link .tabs.is-boxed li.is-active a,html.theme--catppuccin-frappe .hero.is-link .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-frappe .hero.is-link .tabs.is-toggle li.is-active a,html.theme--catppuccin-frappe .hero.is-link .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#8caaee}html.theme--catppuccin-frappe .hero.is-link.is-bold{background-image:linear-gradient(141deg, #569ff1 0%, #8caaee 71%, #a0abf4 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .hero.is-link.is-bold .navbar-menu{background-image:linear-gradient(141deg, #569ff1 0%, #8caaee 71%, #a0abf4 100%)}}html.theme--catppuccin-frappe .hero.is-info{background-color:#81c8be;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-info a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-frappe .hero.is-info strong{color:inherit}html.theme--catppuccin-frappe .hero.is-info .title{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-info .subtitle{color:rgba(0,0,0,0.9)}html.theme--catppuccin-frappe .hero.is-info .subtitle a:not(.button),html.theme--catppuccin-frappe .hero.is-info .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .hero.is-info .navbar-menu{background-color:#81c8be}}html.theme--catppuccin-frappe .hero.is-info .navbar-item,html.theme--catppuccin-frappe .hero.is-info .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-info a.navbar-item:hover,html.theme--catppuccin-frappe .hero.is-info a.navbar-item.is-active,html.theme--catppuccin-frappe .hero.is-info .navbar-link:hover,html.theme--catppuccin-frappe .hero.is-info .navbar-link.is-active{background-color:#6fc0b5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-info .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}html.theme--catppuccin-frappe .hero.is-info .tabs a:hover{opacity:1}html.theme--catppuccin-frappe .hero.is-info .tabs li.is-active a{color:#81c8be !important;opacity:1}html.theme--catppuccin-frappe .hero.is-info .tabs.is-boxed a,html.theme--catppuccin-frappe .hero.is-info .tabs.is-toggle a{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-info .tabs.is-boxed a:hover,html.theme--catppuccin-frappe .hero.is-info .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-frappe .hero.is-info .tabs.is-boxed li.is-active a,html.theme--catppuccin-frappe .hero.is-info .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-frappe .hero.is-info .tabs.is-toggle li.is-active a,html.theme--catppuccin-frappe .hero.is-info .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#81c8be}html.theme--catppuccin-frappe .hero.is-info.is-bold{background-image:linear-gradient(141deg, #52c4a1 0%, #81c8be 71%, #8fd2d4 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .hero.is-info.is-bold .navbar-menu{background-image:linear-gradient(141deg, #52c4a1 0%, #81c8be 71%, #8fd2d4 100%)}}html.theme--catppuccin-frappe .hero.is-success{background-color:#a6d189;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-success a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-frappe .hero.is-success strong{color:inherit}html.theme--catppuccin-frappe .hero.is-success .title{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-success .subtitle{color:rgba(0,0,0,0.9)}html.theme--catppuccin-frappe .hero.is-success .subtitle a:not(.button),html.theme--catppuccin-frappe .hero.is-success .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .hero.is-success .navbar-menu{background-color:#a6d189}}html.theme--catppuccin-frappe .hero.is-success .navbar-item,html.theme--catppuccin-frappe .hero.is-success .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-success a.navbar-item:hover,html.theme--catppuccin-frappe .hero.is-success a.navbar-item.is-active,html.theme--catppuccin-frappe .hero.is-success .navbar-link:hover,html.theme--catppuccin-frappe .hero.is-success .navbar-link.is-active{background-color:#98ca77;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-success .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}html.theme--catppuccin-frappe .hero.is-success .tabs a:hover{opacity:1}html.theme--catppuccin-frappe .hero.is-success .tabs li.is-active a{color:#a6d189 !important;opacity:1}html.theme--catppuccin-frappe .hero.is-success .tabs.is-boxed a,html.theme--catppuccin-frappe .hero.is-success .tabs.is-toggle a{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-success .tabs.is-boxed a:hover,html.theme--catppuccin-frappe .hero.is-success .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-frappe .hero.is-success .tabs.is-boxed li.is-active a,html.theme--catppuccin-frappe .hero.is-success .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-frappe .hero.is-success .tabs.is-toggle li.is-active a,html.theme--catppuccin-frappe .hero.is-success .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#a6d189}html.theme--catppuccin-frappe .hero.is-success.is-bold{background-image:linear-gradient(141deg, #9ccd5a 0%, #a6d189 71%, #a8dc98 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .hero.is-success.is-bold .navbar-menu{background-image:linear-gradient(141deg, #9ccd5a 0%, #a6d189 71%, #a8dc98 100%)}}html.theme--catppuccin-frappe .hero.is-warning{background-color:#e5c890;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-warning a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-frappe .hero.is-warning strong{color:inherit}html.theme--catppuccin-frappe .hero.is-warning .title{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-warning .subtitle{color:rgba(0,0,0,0.9)}html.theme--catppuccin-frappe .hero.is-warning .subtitle a:not(.button),html.theme--catppuccin-frappe .hero.is-warning .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .hero.is-warning .navbar-menu{background-color:#e5c890}}html.theme--catppuccin-frappe .hero.is-warning .navbar-item,html.theme--catppuccin-frappe .hero.is-warning .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-warning a.navbar-item:hover,html.theme--catppuccin-frappe .hero.is-warning a.navbar-item.is-active,html.theme--catppuccin-frappe .hero.is-warning .navbar-link:hover,html.theme--catppuccin-frappe .hero.is-warning .navbar-link.is-active{background-color:#e0be7b;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-warning .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}html.theme--catppuccin-frappe .hero.is-warning .tabs a:hover{opacity:1}html.theme--catppuccin-frappe .hero.is-warning .tabs li.is-active a{color:#e5c890 !important;opacity:1}html.theme--catppuccin-frappe .hero.is-warning .tabs.is-boxed a,html.theme--catppuccin-frappe .hero.is-warning .tabs.is-toggle a{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-warning .tabs.is-boxed a:hover,html.theme--catppuccin-frappe .hero.is-warning .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-frappe .hero.is-warning .tabs.is-boxed li.is-active a,html.theme--catppuccin-frappe .hero.is-warning .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-frappe .hero.is-warning .tabs.is-toggle li.is-active a,html.theme--catppuccin-frappe .hero.is-warning .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#e5c890}html.theme--catppuccin-frappe .hero.is-warning.is-bold{background-image:linear-gradient(141deg, #e5a05d 0%, #e5c890 71%, #ede0a2 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .hero.is-warning.is-bold .navbar-menu{background-image:linear-gradient(141deg, #e5a05d 0%, #e5c890 71%, #ede0a2 100%)}}html.theme--catppuccin-frappe .hero.is-danger{background-color:#e78284;color:#fff}html.theme--catppuccin-frappe .hero.is-danger a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-frappe .hero.is-danger strong{color:inherit}html.theme--catppuccin-frappe .hero.is-danger .title{color:#fff}html.theme--catppuccin-frappe .hero.is-danger .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-frappe .hero.is-danger .subtitle a:not(.button),html.theme--catppuccin-frappe .hero.is-danger .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .hero.is-danger .navbar-menu{background-color:#e78284}}html.theme--catppuccin-frappe .hero.is-danger .navbar-item,html.theme--catppuccin-frappe .hero.is-danger .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-frappe .hero.is-danger a.navbar-item:hover,html.theme--catppuccin-frappe .hero.is-danger a.navbar-item.is-active,html.theme--catppuccin-frappe .hero.is-danger .navbar-link:hover,html.theme--catppuccin-frappe .hero.is-danger .navbar-link.is-active{background-color:#e36d6f;color:#fff}html.theme--catppuccin-frappe .hero.is-danger .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-frappe .hero.is-danger .tabs a:hover{opacity:1}html.theme--catppuccin-frappe .hero.is-danger .tabs li.is-active a{color:#e78284 !important;opacity:1}html.theme--catppuccin-frappe .hero.is-danger .tabs.is-boxed a,html.theme--catppuccin-frappe .hero.is-danger .tabs.is-toggle a{color:#fff}html.theme--catppuccin-frappe .hero.is-danger .tabs.is-boxed a:hover,html.theme--catppuccin-frappe .hero.is-danger .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-frappe .hero.is-danger .tabs.is-boxed li.is-active a,html.theme--catppuccin-frappe .hero.is-danger .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-frappe .hero.is-danger .tabs.is-toggle li.is-active a,html.theme--catppuccin-frappe .hero.is-danger .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#e78284}html.theme--catppuccin-frappe .hero.is-danger.is-bold{background-image:linear-gradient(141deg, #e94d6a 0%, #e78284 71%, #eea294 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .hero.is-danger.is-bold .navbar-menu{background-image:linear-gradient(141deg, #e94d6a 0%, #e78284 71%, #eea294 100%)}}html.theme--catppuccin-frappe .hero.is-small .hero-body,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.hero .hero-body{padding:1.5rem}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .hero.is-medium .hero-body{padding:9rem 4.5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .hero.is-large .hero-body{padding:18rem 6rem}}html.theme--catppuccin-frappe .hero.is-halfheight .hero-body,html.theme--catppuccin-frappe .hero.is-fullheight .hero-body,html.theme--catppuccin-frappe .hero.is-fullheight-with-navbar .hero-body{align-items:center;display:flex}html.theme--catppuccin-frappe .hero.is-halfheight .hero-body>.container,html.theme--catppuccin-frappe .hero.is-fullheight .hero-body>.container,html.theme--catppuccin-frappe .hero.is-fullheight-with-navbar .hero-body>.container{flex-grow:1;flex-shrink:1}html.theme--catppuccin-frappe .hero.is-halfheight{min-height:50vh}html.theme--catppuccin-frappe .hero.is-fullheight{min-height:100vh}html.theme--catppuccin-frappe .hero-video{overflow:hidden}html.theme--catppuccin-frappe .hero-video video{left:50%;min-height:100%;min-width:100%;position:absolute;top:50%;transform:translate3d(-50%, -50%, 0)}html.theme--catppuccin-frappe .hero-video.is-transparent{opacity:0.3}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .hero-video{display:none}}html.theme--catppuccin-frappe .hero-buttons{margin-top:1.5rem}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .hero-buttons .button{display:flex}html.theme--catppuccin-frappe .hero-buttons .button:not(:last-child){margin-bottom:0.75rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .hero-buttons{display:flex;justify-content:center}html.theme--catppuccin-frappe .hero-buttons .button:not(:last-child){margin-right:1.5rem}}html.theme--catppuccin-frappe .hero-head,html.theme--catppuccin-frappe .hero-foot{flex-grow:0;flex-shrink:0}html.theme--catppuccin-frappe .hero-body{flex-grow:1;flex-shrink:0;padding:3rem 1.5rem}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .hero-body{padding:3rem 3rem}}html.theme--catppuccin-frappe .section{padding:3rem 1.5rem}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .section{padding:3rem 3rem}html.theme--catppuccin-frappe .section.is-medium{padding:9rem 4.5rem}html.theme--catppuccin-frappe .section.is-large{padding:18rem 6rem}}html.theme--catppuccin-frappe .footer{background-color:#292c3c;padding:3rem 1.5rem 6rem}html.theme--catppuccin-frappe h1 .docs-heading-anchor,html.theme--catppuccin-frappe h1 .docs-heading-anchor:hover,html.theme--catppuccin-frappe h1 .docs-heading-anchor:visited,html.theme--catppuccin-frappe h2 .docs-heading-anchor,html.theme--catppuccin-frappe h2 .docs-heading-anchor:hover,html.theme--catppuccin-frappe h2 .docs-heading-anchor:visited,html.theme--catppuccin-frappe h3 .docs-heading-anchor,html.theme--catppuccin-frappe h3 .docs-heading-anchor:hover,html.theme--catppuccin-frappe h3 .docs-heading-anchor:visited,html.theme--catppuccin-frappe h4 .docs-heading-anchor,html.theme--catppuccin-frappe h4 .docs-heading-anchor:hover,html.theme--catppuccin-frappe h4 .docs-heading-anchor:visited,html.theme--catppuccin-frappe h5 .docs-heading-anchor,html.theme--catppuccin-frappe h5 .docs-heading-anchor:hover,html.theme--catppuccin-frappe h5 .docs-heading-anchor:visited,html.theme--catppuccin-frappe h6 .docs-heading-anchor,html.theme--catppuccin-frappe h6 .docs-heading-anchor:hover,html.theme--catppuccin-frappe h6 .docs-heading-anchor:visited{color:#c6d0f5}html.theme--catppuccin-frappe h1 .docs-heading-anchor-permalink,html.theme--catppuccin-frappe h2 .docs-heading-anchor-permalink,html.theme--catppuccin-frappe h3 .docs-heading-anchor-permalink,html.theme--catppuccin-frappe h4 .docs-heading-anchor-permalink,html.theme--catppuccin-frappe h5 .docs-heading-anchor-permalink,html.theme--catppuccin-frappe h6 .docs-heading-anchor-permalink{visibility:hidden;vertical-align:middle;margin-left:0.5em;font-size:0.7rem}html.theme--catppuccin-frappe h1 .docs-heading-anchor-permalink::before,html.theme--catppuccin-frappe h2 .docs-heading-anchor-permalink::before,html.theme--catppuccin-frappe h3 .docs-heading-anchor-permalink::before,html.theme--catppuccin-frappe h4 .docs-heading-anchor-permalink::before,html.theme--catppuccin-frappe h5 .docs-heading-anchor-permalink::before,html.theme--catppuccin-frappe h6 .docs-heading-anchor-permalink::before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f0c1"}html.theme--catppuccin-frappe h1:hover .docs-heading-anchor-permalink,html.theme--catppuccin-frappe h2:hover .docs-heading-anchor-permalink,html.theme--catppuccin-frappe h3:hover .docs-heading-anchor-permalink,html.theme--catppuccin-frappe h4:hover .docs-heading-anchor-permalink,html.theme--catppuccin-frappe h5:hover .docs-heading-anchor-permalink,html.theme--catppuccin-frappe h6:hover .docs-heading-anchor-permalink{visibility:visible}html.theme--catppuccin-frappe .docs-light-only{display:none !important}html.theme--catppuccin-frappe pre{position:relative;overflow:hidden}html.theme--catppuccin-frappe pre code,html.theme--catppuccin-frappe pre code.hljs{padding:0 .75rem !important;overflow:auto;display:block}html.theme--catppuccin-frappe pre code:first-of-type,html.theme--catppuccin-frappe pre code.hljs:first-of-type{padding-top:0.5rem !important}html.theme--catppuccin-frappe pre code:last-of-type,html.theme--catppuccin-frappe pre code.hljs:last-of-type{padding-bottom:0.5rem !important}html.theme--catppuccin-frappe pre .copy-button{opacity:0.2;transition:opacity 0.2s;position:absolute;right:0em;top:0em;padding:0.5em;width:2.5em;height:2.5em;background:transparent;border:none;font-family:"Font Awesome 6 Free";color:#c6d0f5;cursor:pointer;text-align:center}html.theme--catppuccin-frappe pre .copy-button:focus,html.theme--catppuccin-frappe pre .copy-button:hover{opacity:1;background:rgba(198,208,245,0.1);color:#8caaee}html.theme--catppuccin-frappe pre .copy-button.success{color:#a6d189;opacity:1}html.theme--catppuccin-frappe pre .copy-button.error{color:#e78284;opacity:1}html.theme--catppuccin-frappe pre:hover .copy-button{opacity:1}html.theme--catppuccin-frappe .admonition{background-color:#292c3c;border-style:solid;border-width:2px;border-color:#b5bfe2;border-radius:4px;font-size:1rem}html.theme--catppuccin-frappe .admonition strong{color:currentColor}html.theme--catppuccin-frappe .admonition.is-small,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.admonition{font-size:.75rem}html.theme--catppuccin-frappe .admonition.is-medium{font-size:1.25rem}html.theme--catppuccin-frappe .admonition.is-large{font-size:1.5rem}html.theme--catppuccin-frappe .admonition.is-default{background-color:#292c3c;border-color:#b5bfe2}html.theme--catppuccin-frappe .admonition.is-default>.admonition-header{background-color:rgba(0,0,0,0);color:#b5bfe2}html.theme--catppuccin-frappe .admonition.is-default>.admonition-body{color:#c6d0f5}html.theme--catppuccin-frappe .admonition.is-info{background-color:#292c3c;border-color:#81c8be}html.theme--catppuccin-frappe .admonition.is-info>.admonition-header{background-color:rgba(0,0,0,0);color:#81c8be}html.theme--catppuccin-frappe .admonition.is-info>.admonition-body{color:#c6d0f5}html.theme--catppuccin-frappe .admonition.is-success{background-color:#292c3c;border-color:#a6d189}html.theme--catppuccin-frappe .admonition.is-success>.admonition-header{background-color:rgba(0,0,0,0);color:#a6d189}html.theme--catppuccin-frappe .admonition.is-success>.admonition-body{color:#c6d0f5}html.theme--catppuccin-frappe .admonition.is-warning{background-color:#292c3c;border-color:#e5c890}html.theme--catppuccin-frappe .admonition.is-warning>.admonition-header{background-color:rgba(0,0,0,0);color:#e5c890}html.theme--catppuccin-frappe .admonition.is-warning>.admonition-body{color:#c6d0f5}html.theme--catppuccin-frappe .admonition.is-danger{background-color:#292c3c;border-color:#e78284}html.theme--catppuccin-frappe .admonition.is-danger>.admonition-header{background-color:rgba(0,0,0,0);color:#e78284}html.theme--catppuccin-frappe .admonition.is-danger>.admonition-body{color:#c6d0f5}html.theme--catppuccin-frappe .admonition.is-compat{background-color:#292c3c;border-color:#99d1db}html.theme--catppuccin-frappe .admonition.is-compat>.admonition-header{background-color:rgba(0,0,0,0);color:#99d1db}html.theme--catppuccin-frappe .admonition.is-compat>.admonition-body{color:#c6d0f5}html.theme--catppuccin-frappe .admonition.is-todo{background-color:#292c3c;border-color:#ca9ee6}html.theme--catppuccin-frappe .admonition.is-todo>.admonition-header{background-color:rgba(0,0,0,0);color:#ca9ee6}html.theme--catppuccin-frappe .admonition.is-todo>.admonition-body{color:#c6d0f5}html.theme--catppuccin-frappe .admonition-header{color:#b5bfe2;background-color:rgba(0,0,0,0);align-items:center;font-weight:700;justify-content:space-between;line-height:1.25;padding:0.5rem .75rem;position:relative}html.theme--catppuccin-frappe .admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;margin-right:.75rem;content:"\f06a"}html.theme--catppuccin-frappe details.admonition.is-details>.admonition-header{list-style:none}html.theme--catppuccin-frappe details.admonition.is-details>.admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f055"}html.theme--catppuccin-frappe details.admonition.is-details[open]>.admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f056"}html.theme--catppuccin-frappe .admonition-body{color:#c6d0f5;padding:0.5rem .75rem}html.theme--catppuccin-frappe .admonition-body pre{background-color:#292c3c}html.theme--catppuccin-frappe .admonition-body code{background-color:#292c3c}html.theme--catppuccin-frappe .docstring{margin-bottom:1em;background-color:rgba(0,0,0,0);border:2px solid #626880;border-radius:4px;box-shadow:none;max-width:100%}html.theme--catppuccin-frappe .docstring>header{cursor:pointer;display:flex;flex-grow:1;align-items:stretch;padding:0.5rem .75rem;background-color:#292c3c;box-shadow:0 0.125em 0.25em rgba(10,10,10,0.1);box-shadow:none;border-bottom:1px solid #626880;overflow:auto}html.theme--catppuccin-frappe .docstring>header code{background-color:transparent}html.theme--catppuccin-frappe .docstring>header .docstring-article-toggle-button{min-width:1.1rem;padding:0.2rem 0.2rem 0.2rem 0}html.theme--catppuccin-frappe .docstring>header .docstring-binding{margin-right:0.3em}html.theme--catppuccin-frappe .docstring>header .docstring-category{margin-left:0.3em}html.theme--catppuccin-frappe .docstring>section{position:relative;padding:.75rem .75rem;border-bottom:1px solid #626880}html.theme--catppuccin-frappe .docstring>section:last-child{border-bottom:none}html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink{transition:opacity 0.3s;opacity:0;position:absolute;right:.375rem;bottom:.375rem}html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink:focus{opacity:1 !important}html.theme--catppuccin-frappe .docstring:hover>section>a.docs-sourcelink{opacity:0.2}html.theme--catppuccin-frappe .docstring:focus-within>section>a.docs-sourcelink{opacity:0.2}html.theme--catppuccin-frappe .docstring>section:hover a.docs-sourcelink{opacity:1}html.theme--catppuccin-frappe .documenter-example-output{background-color:#303446}html.theme--catppuccin-frappe .outdated-warning-overlay{position:fixed;top:0;left:0;right:0;box-shadow:0 0 10px rgba(0,0,0,0.3);z-index:999;background-color:#292c3c;color:#c6d0f5;border-bottom:3px solid rgba(0,0,0,0);padding:10px 35px;text-align:center;font-size:15px}html.theme--catppuccin-frappe .outdated-warning-overlay .outdated-warning-closer{position:absolute;top:calc(50% - 10px);right:18px;cursor:pointer;width:12px}html.theme--catppuccin-frappe .outdated-warning-overlay a{color:#8caaee}html.theme--catppuccin-frappe .outdated-warning-overlay a:hover{color:#99d1db}html.theme--catppuccin-frappe .content pre{border:2px solid #626880;border-radius:4px}html.theme--catppuccin-frappe .content code{font-weight:inherit}html.theme--catppuccin-frappe .content a code{color:#8caaee}html.theme--catppuccin-frappe .content a:hover code{color:#99d1db}html.theme--catppuccin-frappe .content h1 code,html.theme--catppuccin-frappe .content h2 code,html.theme--catppuccin-frappe .content h3 code,html.theme--catppuccin-frappe .content h4 code,html.theme--catppuccin-frappe .content h5 code,html.theme--catppuccin-frappe .content h6 code{color:#c6d0f5}html.theme--catppuccin-frappe .content table{display:block;width:initial;max-width:100%;overflow-x:auto}html.theme--catppuccin-frappe .content blockquote>ul:first-child,html.theme--catppuccin-frappe .content blockquote>ol:first-child,html.theme--catppuccin-frappe .content .admonition-body>ul:first-child,html.theme--catppuccin-frappe .content .admonition-body>ol:first-child{margin-top:0}html.theme--catppuccin-frappe pre,html.theme--catppuccin-frappe code{font-variant-ligatures:no-contextual}html.theme--catppuccin-frappe .breadcrumb a.is-disabled{cursor:default;pointer-events:none}html.theme--catppuccin-frappe .breadcrumb a.is-disabled,html.theme--catppuccin-frappe .breadcrumb a.is-disabled:hover{color:#b0bef1}html.theme--catppuccin-frappe .hljs{background:initial !important}html.theme--catppuccin-frappe .katex .katex-mathml{top:0;right:0}html.theme--catppuccin-frappe .katex-display,html.theme--catppuccin-frappe mjx-container,html.theme--catppuccin-frappe .MathJax_Display{margin:0.5em 0 !important}html.theme--catppuccin-frappe html{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto}html.theme--catppuccin-frappe li.no-marker{list-style:none}html.theme--catppuccin-frappe #documenter .docs-main>article{overflow-wrap:break-word}html.theme--catppuccin-frappe #documenter .docs-main>article .math-container{overflow-x:auto;overflow-y:hidden}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe #documenter .docs-main{max-width:52rem;margin-left:20rem;padding-right:1rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe #documenter .docs-main{width:100%}html.theme--catppuccin-frappe #documenter .docs-main>article{max-width:52rem;margin-left:auto;margin-right:auto;margin-bottom:1rem;padding:0 1rem}html.theme--catppuccin-frappe #documenter .docs-main>header,html.theme--catppuccin-frappe #documenter .docs-main>nav{max-width:100%;width:100%;margin:0}}html.theme--catppuccin-frappe #documenter .docs-main header.docs-navbar{background-color:#303446;border-bottom:1px solid #626880;z-index:2;min-height:4rem;margin-bottom:1rem;display:flex}html.theme--catppuccin-frappe #documenter .docs-main header.docs-navbar .breadcrumb{flex-grow:1;overflow-x:hidden}html.theme--catppuccin-frappe #documenter .docs-main header.docs-navbar .docs-sidebar-button{display:block;font-size:1.5rem;padding-bottom:0.1rem;margin-right:1rem}html.theme--catppuccin-frappe #documenter .docs-main header.docs-navbar .docs-right{display:flex;white-space:nowrap;gap:1rem;align-items:center}html.theme--catppuccin-frappe #documenter .docs-main header.docs-navbar .docs-right .docs-icon,html.theme--catppuccin-frappe #documenter .docs-main header.docs-navbar .docs-right .docs-label{display:inline-block}html.theme--catppuccin-frappe #documenter .docs-main header.docs-navbar .docs-right .docs-label{padding:0;margin-left:0.3em}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe #documenter .docs-main header.docs-navbar .docs-right .docs-navbar-link{margin-left:0.4rem;margin-right:0.4rem}}html.theme--catppuccin-frappe #documenter .docs-main header.docs-navbar>*{margin:auto 0}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe #documenter .docs-main header.docs-navbar{position:sticky;top:0;padding:0 1rem;transition-property:top, box-shadow;-webkit-transition-property:top, box-shadow;transition-duration:0.3s;-webkit-transition-duration:0.3s}html.theme--catppuccin-frappe #documenter .docs-main header.docs-navbar.headroom--not-top{box-shadow:.2rem 0rem .4rem #171717;transition-duration:0.7s;-webkit-transition-duration:0.7s}html.theme--catppuccin-frappe #documenter .docs-main header.docs-navbar.headroom--unpinned.headroom--not-top.headroom--not-bottom{top:-4.5rem;transition-duration:0.7s;-webkit-transition-duration:0.7s}}html.theme--catppuccin-frappe #documenter .docs-main section.footnotes{border-top:1px solid #626880}html.theme--catppuccin-frappe #documenter .docs-main section.footnotes li .tag:first-child,html.theme--catppuccin-frappe #documenter .docs-main section.footnotes li .docstring>section>a.docs-sourcelink:first-child,html.theme--catppuccin-frappe #documenter .docs-main section.footnotes li .content kbd:first-child,html.theme--catppuccin-frappe .content #documenter .docs-main section.footnotes li kbd:first-child{margin-right:1em;margin-bottom:0.4em}html.theme--catppuccin-frappe #documenter .docs-main .docs-footer{display:flex;flex-wrap:wrap;margin-left:0;margin-right:0;border-top:1px solid #626880;padding-top:1rem;padding-bottom:1rem}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe #documenter .docs-main .docs-footer{padding-left:1rem;padding-right:1rem}}html.theme--catppuccin-frappe #documenter .docs-main .docs-footer .docs-footer-nextpage,html.theme--catppuccin-frappe #documenter .docs-main .docs-footer .docs-footer-prevpage{flex-grow:1}html.theme--catppuccin-frappe #documenter .docs-main .docs-footer .docs-footer-nextpage{text-align:right}html.theme--catppuccin-frappe #documenter .docs-main .docs-footer .flexbox-break{flex-basis:100%;height:0}html.theme--catppuccin-frappe #documenter .docs-main .docs-footer .footer-message{font-size:0.8em;margin:0.5em auto 0 auto;text-align:center}html.theme--catppuccin-frappe #documenter .docs-sidebar{display:flex;flex-direction:column;color:#c6d0f5;background-color:#292c3c;border-right:1px solid #626880;padding:0;flex:0 0 18rem;z-index:5;font-size:1rem;position:fixed;left:-18rem;width:18rem;height:100%;transition:left 0.3s}html.theme--catppuccin-frappe #documenter .docs-sidebar.visible{left:0;box-shadow:.4rem 0rem .8rem #171717}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe #documenter .docs-sidebar.visible{box-shadow:none}}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe #documenter .docs-sidebar{left:0;top:0}}html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo{margin-top:1rem;padding:0 1rem}html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img{max-height:6rem;margin:auto}html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-package-name{flex-shrink:0;font-size:1.5rem;font-weight:700;text-align:center;white-space:nowrap;overflow:hidden;padding:0.5rem 0}html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-package-name .docs-autofit{max-width:16.2rem}html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-package-name a,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-package-name a:hover{color:#c6d0f5}html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-version-selector{border-top:1px solid #626880;display:none;padding:0.5rem}html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-version-selector.visible{display:flex}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu{flex-grow:1;user-select:none;border-top:1px solid #626880;padding-bottom:1.5rem}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu>li>.tocitem{font-weight:bold}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu>li li{font-size:.95rem;margin-left:1em;border-left:1px solid #626880}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu input.collapse-toggle{display:none}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu ul.collapsed{display:none}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu input:checked~ul.collapsed{display:block}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu label.tocitem{display:flex}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu label.tocitem .docs-label{flex-grow:2}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu label.tocitem .docs-chevron{display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1;font-size:.75rem;margin-left:1rem;margin-top:auto;margin-bottom:auto}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu label.tocitem .docs-chevron::before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f054"}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu input:checked~label.tocitem .docs-chevron::before{content:"\f078"}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu .tocitem{display:block;padding:0.5rem 0.5rem}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu .tocitem,html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu .tocitem:hover{color:#c6d0f5;background:#292c3c}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu a.tocitem:hover,html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu label.tocitem:hover{color:#c6d0f5;background-color:#313548}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu li.is-active{border-top:1px solid #626880;border-bottom:1px solid #626880;background-color:#232634}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu li.is-active .tocitem,html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu li.is-active .tocitem:hover{background-color:#232634;color:#c6d0f5}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu li.is-active ul.internal .tocitem:hover{background-color:#313548;color:#c6d0f5}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu>li.is-active:first-child{border-top:none}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu ul.internal{margin:0 0.5rem 0.5rem;border-top:1px solid #626880}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu ul.internal li{font-size:.85rem;border-left:none;margin-left:0;margin-top:0.5rem}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu ul.internal .tocitem{width:100%;padding:0}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu ul.internal .tocitem::before{content:"⚬";margin-right:0.4em}html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search{margin:auto;margin-top:0.5rem;margin-bottom:0.5rem}html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input{width:14.4rem}html.theme--catppuccin-frappe #documenter .docs-sidebar #documenter-search-query{color:#868c98;width:14.4rem;box-shadow:inset 0 1px 2px rgba(10,10,10,0.1)}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu{overflow-y:auto;-webkit-overflow-scroll:touch}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar{width:.3rem;background:none}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar-thumb{border-radius:5px 0px 0px 5px;background:#3a3e54}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar-thumb:hover{background:#4a506c}}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe #documenter .docs-sidebar{overflow-y:auto;-webkit-overflow-scroll:touch}html.theme--catppuccin-frappe #documenter .docs-sidebar::-webkit-scrollbar{width:.3rem;background:none}html.theme--catppuccin-frappe #documenter .docs-sidebar::-webkit-scrollbar-thumb{border-radius:5px 0px 0px 5px;background:#3a3e54}html.theme--catppuccin-frappe #documenter .docs-sidebar::-webkit-scrollbar-thumb:hover{background:#4a506c}}html.theme--catppuccin-frappe kbd.search-modal-key-hints{border-radius:0.25rem;border:1px solid rgba(245,245,245,0.6);box-shadow:0 2px 0 1px rgba(245,245,245,0.6);cursor:default;font-size:0.9rem;line-height:1.5;min-width:0.75rem;text-align:center;padding:0.1rem 0.3rem;position:relative;top:-1px}html.theme--catppuccin-frappe .search-min-width-50{min-width:50%}html.theme--catppuccin-frappe .search-min-height-100{min-height:100%}html.theme--catppuccin-frappe .search-modal-card-body{max-height:calc(100vh - 15rem)}html.theme--catppuccin-frappe .search-result-link{border-radius:0.7em;transition:all 300ms}html.theme--catppuccin-frappe .search-result-link:hover,html.theme--catppuccin-frappe .search-result-link:focus{background-color:rgba(0,128,128,0.1)}html.theme--catppuccin-frappe .search-result-link .property-search-result-badge,html.theme--catppuccin-frappe .search-result-link .search-filter{transition:all 300ms}html.theme--catppuccin-frappe .property-search-result-badge,html.theme--catppuccin-frappe .search-filter{padding:0.15em 0.5em;font-size:0.8em;font-style:italic;text-transform:none !important;line-height:1.5;color:#f5f5f5;background-color:rgba(51,65,85,0.501961);border-radius:0.6rem}html.theme--catppuccin-frappe .search-result-link:hover .property-search-result-badge,html.theme--catppuccin-frappe .search-result-link:hover .search-filter,html.theme--catppuccin-frappe .search-result-link:focus .property-search-result-badge,html.theme--catppuccin-frappe .search-result-link:focus .search-filter{color:#333;background-color:#f1f5f9}html.theme--catppuccin-frappe .search-filter{color:#333;background-color:#f5f5f5;transition:all 300ms}html.theme--catppuccin-frappe .search-filter:hover,html.theme--catppuccin-frappe .search-filter:focus{color:#333}html.theme--catppuccin-frappe .search-filter-selected{color:#414559;background-color:#babbf1}html.theme--catppuccin-frappe .search-filter-selected:hover,html.theme--catppuccin-frappe .search-filter-selected:focus{color:#414559}html.theme--catppuccin-frappe .search-result-highlight{background-color:#ffdd57;color:black}html.theme--catppuccin-frappe .search-divider{border-bottom:1px solid #626880}html.theme--catppuccin-frappe .search-result-title{width:85%;color:#f5f5f5}html.theme--catppuccin-frappe .search-result-code-title{font-size:0.875rem;font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace}html.theme--catppuccin-frappe #search-modal .modal-card-body::-webkit-scrollbar,html.theme--catppuccin-frappe #search-modal .filter-tabs::-webkit-scrollbar{height:10px;width:10px;background-color:transparent}html.theme--catppuccin-frappe #search-modal .modal-card-body::-webkit-scrollbar-thumb,html.theme--catppuccin-frappe #search-modal .filter-tabs::-webkit-scrollbar-thumb{background-color:gray;border-radius:1rem}html.theme--catppuccin-frappe #search-modal .modal-card-body::-webkit-scrollbar-track,html.theme--catppuccin-frappe #search-modal .filter-tabs::-webkit-scrollbar-track{-webkit-box-shadow:inset 0 0 6px rgba(0,0,0,0.6);background-color:transparent}html.theme--catppuccin-frappe .w-100{width:100%}html.theme--catppuccin-frappe .gap-2{gap:0.5rem}html.theme--catppuccin-frappe .gap-4{gap:1rem}html.theme--catppuccin-frappe .gap-8{gap:2rem}html.theme--catppuccin-frappe{background-color:#303446;font-size:16px;min-width:300px;overflow-x:auto;overflow-y:scroll;text-rendering:optimizeLegibility;text-size-adjust:100%}html.theme--catppuccin-frappe a{transition:all 200ms ease}html.theme--catppuccin-frappe .label{color:#c6d0f5}html.theme--catppuccin-frappe .button,html.theme--catppuccin-frappe .control.has-icons-left .icon,html.theme--catppuccin-frappe .control.has-icons-right .icon,html.theme--catppuccin-frappe .input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-frappe .pagination-ellipsis,html.theme--catppuccin-frappe .pagination-link,html.theme--catppuccin-frappe .pagination-next,html.theme--catppuccin-frappe .pagination-previous,html.theme--catppuccin-frappe .select,html.theme--catppuccin-frappe .select select,html.theme--catppuccin-frappe .textarea{height:2.5em;color:#c6d0f5}html.theme--catppuccin-frappe .input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-frappe .textarea{transition:all 200ms ease;box-shadow:none;border-width:1px;padding-left:1em;padding-right:1em;color:#c6d0f5}html.theme--catppuccin-frappe .select:after,html.theme--catppuccin-frappe .select select{border-width:1px}html.theme--catppuccin-frappe .menu-list a{transition:all 300ms ease}html.theme--catppuccin-frappe .modal-card-foot,html.theme--catppuccin-frappe .modal-card-head{border-color:#626880}html.theme--catppuccin-frappe .navbar{border-radius:.4em}html.theme--catppuccin-frappe .navbar.is-transparent{background:none}html.theme--catppuccin-frappe .navbar.is-primary .navbar-dropdown a.navbar-item.is-active,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-dropdown a.navbar-item.is-active{background-color:#8caaee}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .navbar .navbar-menu{background-color:#8caaee;border-radius:0 0 .4em .4em}}html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink:not(body){color:#414559}html.theme--catppuccin-frappe .tag.is-link:not(body),html.theme--catppuccin-frappe .docstring>section>a.is-link.docs-sourcelink:not(body),html.theme--catppuccin-frappe .content kbd.is-link:not(body){color:#414559}html.theme--catppuccin-frappe .ansi span.sgr1{font-weight:bolder}html.theme--catppuccin-frappe .ansi span.sgr2{font-weight:lighter}html.theme--catppuccin-frappe .ansi span.sgr3{font-style:italic}html.theme--catppuccin-frappe .ansi span.sgr4{text-decoration:underline}html.theme--catppuccin-frappe .ansi span.sgr7{color:#303446;background-color:#c6d0f5}html.theme--catppuccin-frappe .ansi span.sgr8{color:transparent}html.theme--catppuccin-frappe .ansi span.sgr8 span{color:transparent}html.theme--catppuccin-frappe .ansi span.sgr9{text-decoration:line-through}html.theme--catppuccin-frappe .ansi span.sgr30{color:#51576d}html.theme--catppuccin-frappe .ansi span.sgr31{color:#e78284}html.theme--catppuccin-frappe .ansi span.sgr32{color:#a6d189}html.theme--catppuccin-frappe .ansi span.sgr33{color:#e5c890}html.theme--catppuccin-frappe .ansi span.sgr34{color:#8caaee}html.theme--catppuccin-frappe .ansi span.sgr35{color:#f4b8e4}html.theme--catppuccin-frappe .ansi span.sgr36{color:#81c8be}html.theme--catppuccin-frappe .ansi span.sgr37{color:#b5bfe2}html.theme--catppuccin-frappe .ansi span.sgr40{background-color:#51576d}html.theme--catppuccin-frappe .ansi span.sgr41{background-color:#e78284}html.theme--catppuccin-frappe .ansi span.sgr42{background-color:#a6d189}html.theme--catppuccin-frappe .ansi span.sgr43{background-color:#e5c890}html.theme--catppuccin-frappe .ansi span.sgr44{background-color:#8caaee}html.theme--catppuccin-frappe .ansi span.sgr45{background-color:#f4b8e4}html.theme--catppuccin-frappe .ansi span.sgr46{background-color:#81c8be}html.theme--catppuccin-frappe .ansi span.sgr47{background-color:#b5bfe2}html.theme--catppuccin-frappe .ansi span.sgr90{color:#626880}html.theme--catppuccin-frappe .ansi span.sgr91{color:#e78284}html.theme--catppuccin-frappe .ansi span.sgr92{color:#a6d189}html.theme--catppuccin-frappe .ansi span.sgr93{color:#e5c890}html.theme--catppuccin-frappe .ansi span.sgr94{color:#8caaee}html.theme--catppuccin-frappe .ansi span.sgr95{color:#f4b8e4}html.theme--catppuccin-frappe .ansi span.sgr96{color:#81c8be}html.theme--catppuccin-frappe .ansi span.sgr97{color:#a5adce}html.theme--catppuccin-frappe .ansi span.sgr100{background-color:#626880}html.theme--catppuccin-frappe .ansi span.sgr101{background-color:#e78284}html.theme--catppuccin-frappe .ansi span.sgr102{background-color:#a6d189}html.theme--catppuccin-frappe .ansi span.sgr103{background-color:#e5c890}html.theme--catppuccin-frappe .ansi span.sgr104{background-color:#8caaee}html.theme--catppuccin-frappe .ansi span.sgr105{background-color:#f4b8e4}html.theme--catppuccin-frappe .ansi span.sgr106{background-color:#81c8be}html.theme--catppuccin-frappe .ansi span.sgr107{background-color:#a5adce}html.theme--catppuccin-frappe code.language-julia-repl>span.hljs-meta{color:#a6d189;font-weight:bolder}html.theme--catppuccin-frappe code .hljs{color:#c6d0f5;background:#303446}html.theme--catppuccin-frappe code .hljs-keyword{color:#ca9ee6}html.theme--catppuccin-frappe code .hljs-built_in{color:#e78284}html.theme--catppuccin-frappe code .hljs-type{color:#e5c890}html.theme--catppuccin-frappe code .hljs-literal{color:#ef9f76}html.theme--catppuccin-frappe code .hljs-number{color:#ef9f76}html.theme--catppuccin-frappe code .hljs-operator{color:#81c8be}html.theme--catppuccin-frappe code .hljs-punctuation{color:#b5bfe2}html.theme--catppuccin-frappe code .hljs-property{color:#81c8be}html.theme--catppuccin-frappe code .hljs-regexp{color:#f4b8e4}html.theme--catppuccin-frappe code .hljs-string{color:#a6d189}html.theme--catppuccin-frappe code .hljs-char.escape_{color:#a6d189}html.theme--catppuccin-frappe code .hljs-subst{color:#a5adce}html.theme--catppuccin-frappe code .hljs-symbol{color:#eebebe}html.theme--catppuccin-frappe code .hljs-variable{color:#ca9ee6}html.theme--catppuccin-frappe code .hljs-variable.language_{color:#ca9ee6}html.theme--catppuccin-frappe code .hljs-variable.constant_{color:#ef9f76}html.theme--catppuccin-frappe code .hljs-title{color:#8caaee}html.theme--catppuccin-frappe code .hljs-title.class_{color:#e5c890}html.theme--catppuccin-frappe code .hljs-title.function_{color:#8caaee}html.theme--catppuccin-frappe code .hljs-params{color:#c6d0f5}html.theme--catppuccin-frappe code .hljs-comment{color:#626880}html.theme--catppuccin-frappe code .hljs-doctag{color:#e78284}html.theme--catppuccin-frappe code .hljs-meta{color:#ef9f76}html.theme--catppuccin-frappe code .hljs-section{color:#8caaee}html.theme--catppuccin-frappe code .hljs-tag{color:#a5adce}html.theme--catppuccin-frappe code .hljs-name{color:#ca9ee6}html.theme--catppuccin-frappe code .hljs-attr{color:#8caaee}html.theme--catppuccin-frappe code .hljs-attribute{color:#a6d189}html.theme--catppuccin-frappe code .hljs-bullet{color:#81c8be}html.theme--catppuccin-frappe code .hljs-code{color:#a6d189}html.theme--catppuccin-frappe code .hljs-emphasis{color:#e78284;font-style:italic}html.theme--catppuccin-frappe code .hljs-strong{color:#e78284;font-weight:bold}html.theme--catppuccin-frappe code .hljs-formula{color:#81c8be}html.theme--catppuccin-frappe code .hljs-link{color:#85c1dc;font-style:italic}html.theme--catppuccin-frappe code .hljs-quote{color:#a6d189;font-style:italic}html.theme--catppuccin-frappe code .hljs-selector-tag{color:#e5c890}html.theme--catppuccin-frappe code .hljs-selector-id{color:#8caaee}html.theme--catppuccin-frappe code .hljs-selector-class{color:#81c8be}html.theme--catppuccin-frappe code .hljs-selector-attr{color:#ca9ee6}html.theme--catppuccin-frappe code .hljs-selector-pseudo{color:#81c8be}html.theme--catppuccin-frappe code .hljs-template-tag{color:#eebebe}html.theme--catppuccin-frappe code .hljs-template-variable{color:#eebebe}html.theme--catppuccin-frappe code .hljs-addition{color:#a6d189;background:rgba(166,227,161,0.15)}html.theme--catppuccin-frappe code .hljs-deletion{color:#e78284;background:rgba(243,139,168,0.15)}html.theme--catppuccin-frappe .search-result-link{border-radius:0.7em;transition:all 300ms}html.theme--catppuccin-frappe .search-result-link:hover,html.theme--catppuccin-frappe .search-result-link:focus{background-color:#414559}html.theme--catppuccin-frappe .search-result-link .property-search-result-badge,html.theme--catppuccin-frappe .search-result-link .search-filter{transition:all 300ms}html.theme--catppuccin-frappe .search-result-link:hover .property-search-result-badge,html.theme--catppuccin-frappe .search-result-link:hover .search-filter,html.theme--catppuccin-frappe .search-result-link:focus .property-search-result-badge,html.theme--catppuccin-frappe .search-result-link:focus .search-filter{color:#414559 !important;background-color:#babbf1 !important}html.theme--catppuccin-frappe .search-result-title{color:#c6d0f5}html.theme--catppuccin-frappe .search-result-highlight{background-color:#e78284;color:#292c3c}html.theme--catppuccin-frappe .search-divider{border-bottom:1px solid #5e6d6f50}html.theme--catppuccin-frappe .w-100{width:100%}html.theme--catppuccin-frappe .gap-2{gap:0.5rem}html.theme--catppuccin-frappe .gap-4{gap:1rem} diff --git a/previews/PR798/assets/themes/catppuccin-latte.css b/previews/PR798/assets/themes/catppuccin-latte.css new file mode 100644 index 0000000000..63160d3449 --- /dev/null +++ b/previews/PR798/assets/themes/catppuccin-latte.css @@ -0,0 +1 @@ +html.theme--catppuccin-latte .pagination-previous,html.theme--catppuccin-latte .pagination-next,html.theme--catppuccin-latte .pagination-link,html.theme--catppuccin-latte .pagination-ellipsis,html.theme--catppuccin-latte .file-cta,html.theme--catppuccin-latte .file-name,html.theme--catppuccin-latte .select select,html.theme--catppuccin-latte .textarea,html.theme--catppuccin-latte .input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-latte .button{-moz-appearance:none;-webkit-appearance:none;align-items:center;border:1px solid transparent;border-radius:.4em;box-shadow:none;display:inline-flex;font-size:1rem;height:2.5em;justify-content:flex-start;line-height:1.5;padding-bottom:calc(0.5em - 1px);padding-left:calc(0.75em - 1px);padding-right:calc(0.75em - 1px);padding-top:calc(0.5em - 1px);position:relative;vertical-align:top}html.theme--catppuccin-latte .pagination-previous:focus,html.theme--catppuccin-latte .pagination-next:focus,html.theme--catppuccin-latte .pagination-link:focus,html.theme--catppuccin-latte .pagination-ellipsis:focus,html.theme--catppuccin-latte .file-cta:focus,html.theme--catppuccin-latte .file-name:focus,html.theme--catppuccin-latte .select select:focus,html.theme--catppuccin-latte .textarea:focus,html.theme--catppuccin-latte .input:focus,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input:focus,html.theme--catppuccin-latte .button:focus,html.theme--catppuccin-latte .is-focused.pagination-previous,html.theme--catppuccin-latte .is-focused.pagination-next,html.theme--catppuccin-latte .is-focused.pagination-link,html.theme--catppuccin-latte .is-focused.pagination-ellipsis,html.theme--catppuccin-latte .is-focused.file-cta,html.theme--catppuccin-latte .is-focused.file-name,html.theme--catppuccin-latte .select select.is-focused,html.theme--catppuccin-latte .is-focused.textarea,html.theme--catppuccin-latte .is-focused.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-latte .is-focused.button,html.theme--catppuccin-latte .pagination-previous:active,html.theme--catppuccin-latte .pagination-next:active,html.theme--catppuccin-latte .pagination-link:active,html.theme--catppuccin-latte .pagination-ellipsis:active,html.theme--catppuccin-latte .file-cta:active,html.theme--catppuccin-latte .file-name:active,html.theme--catppuccin-latte .select select:active,html.theme--catppuccin-latte .textarea:active,html.theme--catppuccin-latte .input:active,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input:active,html.theme--catppuccin-latte .button:active,html.theme--catppuccin-latte .is-active.pagination-previous,html.theme--catppuccin-latte .is-active.pagination-next,html.theme--catppuccin-latte .is-active.pagination-link,html.theme--catppuccin-latte .is-active.pagination-ellipsis,html.theme--catppuccin-latte .is-active.file-cta,html.theme--catppuccin-latte .is-active.file-name,html.theme--catppuccin-latte .select select.is-active,html.theme--catppuccin-latte .is-active.textarea,html.theme--catppuccin-latte .is-active.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-active,html.theme--catppuccin-latte .is-active.button{outline:none}html.theme--catppuccin-latte .pagination-previous[disabled],html.theme--catppuccin-latte .pagination-next[disabled],html.theme--catppuccin-latte .pagination-link[disabled],html.theme--catppuccin-latte .pagination-ellipsis[disabled],html.theme--catppuccin-latte .file-cta[disabled],html.theme--catppuccin-latte .file-name[disabled],html.theme--catppuccin-latte .select select[disabled],html.theme--catppuccin-latte .textarea[disabled],html.theme--catppuccin-latte .input[disabled],html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input[disabled],html.theme--catppuccin-latte .button[disabled],fieldset[disabled] html.theme--catppuccin-latte .pagination-previous,html.theme--catppuccin-latte fieldset[disabled] .pagination-previous,fieldset[disabled] html.theme--catppuccin-latte .pagination-next,html.theme--catppuccin-latte fieldset[disabled] .pagination-next,fieldset[disabled] html.theme--catppuccin-latte .pagination-link,html.theme--catppuccin-latte fieldset[disabled] .pagination-link,fieldset[disabled] html.theme--catppuccin-latte .pagination-ellipsis,html.theme--catppuccin-latte fieldset[disabled] .pagination-ellipsis,fieldset[disabled] html.theme--catppuccin-latte .file-cta,html.theme--catppuccin-latte fieldset[disabled] .file-cta,fieldset[disabled] html.theme--catppuccin-latte .file-name,html.theme--catppuccin-latte fieldset[disabled] .file-name,fieldset[disabled] html.theme--catppuccin-latte .select select,fieldset[disabled] html.theme--catppuccin-latte .textarea,fieldset[disabled] html.theme--catppuccin-latte .input,fieldset[disabled] html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-latte fieldset[disabled] .select select,html.theme--catppuccin-latte .select fieldset[disabled] select,html.theme--catppuccin-latte fieldset[disabled] .textarea,html.theme--catppuccin-latte fieldset[disabled] .input,html.theme--catppuccin-latte fieldset[disabled] #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-latte #documenter .docs-sidebar fieldset[disabled] form.docs-search>input,fieldset[disabled] html.theme--catppuccin-latte .button,html.theme--catppuccin-latte fieldset[disabled] .button{cursor:not-allowed}html.theme--catppuccin-latte .tabs,html.theme--catppuccin-latte .pagination-previous,html.theme--catppuccin-latte .pagination-next,html.theme--catppuccin-latte .pagination-link,html.theme--catppuccin-latte .pagination-ellipsis,html.theme--catppuccin-latte .breadcrumb,html.theme--catppuccin-latte .file,html.theme--catppuccin-latte .button,.is-unselectable{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}html.theme--catppuccin-latte .navbar-link:not(.is-arrowless)::after,html.theme--catppuccin-latte .select:not(.is-multiple):not(.is-loading)::after{border:3px solid rgba(0,0,0,0);border-radius:2px;border-right:0;border-top:0;content:" ";display:block;height:0.625em;margin-top:-0.4375em;pointer-events:none;position:absolute;top:50%;transform:rotate(-45deg);transform-origin:center;width:0.625em}html.theme--catppuccin-latte .admonition:not(:last-child),html.theme--catppuccin-latte .tabs:not(:last-child),html.theme--catppuccin-latte .pagination:not(:last-child),html.theme--catppuccin-latte .message:not(:last-child),html.theme--catppuccin-latte .level:not(:last-child),html.theme--catppuccin-latte .breadcrumb:not(:last-child),html.theme--catppuccin-latte .block:not(:last-child),html.theme--catppuccin-latte .title:not(:last-child),html.theme--catppuccin-latte .subtitle:not(:last-child),html.theme--catppuccin-latte .table-container:not(:last-child),html.theme--catppuccin-latte .table:not(:last-child),html.theme--catppuccin-latte .progress:not(:last-child),html.theme--catppuccin-latte .notification:not(:last-child),html.theme--catppuccin-latte .content:not(:last-child),html.theme--catppuccin-latte .box:not(:last-child){margin-bottom:1.5rem}html.theme--catppuccin-latte .modal-close,html.theme--catppuccin-latte .delete{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-moz-appearance:none;-webkit-appearance:none;background-color:rgba(10,10,10,0.2);border:none;border-radius:9999px;cursor:pointer;pointer-events:auto;display:inline-block;flex-grow:0;flex-shrink:0;font-size:0;height:20px;max-height:20px;max-width:20px;min-height:20px;min-width:20px;outline:none;position:relative;vertical-align:top;width:20px}html.theme--catppuccin-latte .modal-close::before,html.theme--catppuccin-latte .delete::before,html.theme--catppuccin-latte .modal-close::after,html.theme--catppuccin-latte .delete::after{background-color:#fff;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}html.theme--catppuccin-latte .modal-close::before,html.theme--catppuccin-latte .delete::before{height:2px;width:50%}html.theme--catppuccin-latte .modal-close::after,html.theme--catppuccin-latte .delete::after{height:50%;width:2px}html.theme--catppuccin-latte .modal-close:hover,html.theme--catppuccin-latte .delete:hover,html.theme--catppuccin-latte .modal-close:focus,html.theme--catppuccin-latte .delete:focus{background-color:rgba(10,10,10,0.3)}html.theme--catppuccin-latte .modal-close:active,html.theme--catppuccin-latte .delete:active{background-color:rgba(10,10,10,0.4)}html.theme--catppuccin-latte .is-small.modal-close,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.modal-close,html.theme--catppuccin-latte .is-small.delete,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.delete{height:16px;max-height:16px;max-width:16px;min-height:16px;min-width:16px;width:16px}html.theme--catppuccin-latte .is-medium.modal-close,html.theme--catppuccin-latte .is-medium.delete{height:24px;max-height:24px;max-width:24px;min-height:24px;min-width:24px;width:24px}html.theme--catppuccin-latte .is-large.modal-close,html.theme--catppuccin-latte .is-large.delete{height:32px;max-height:32px;max-width:32px;min-height:32px;min-width:32px;width:32px}html.theme--catppuccin-latte .control.is-loading::after,html.theme--catppuccin-latte .select.is-loading::after,html.theme--catppuccin-latte .loader,html.theme--catppuccin-latte .button.is-loading::after{animation:spinAround 500ms infinite linear;border:2px solid #8c8fa1;border-radius:9999px;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:1em;position:relative;width:1em}html.theme--catppuccin-latte .hero-video,html.theme--catppuccin-latte .modal-background,html.theme--catppuccin-latte .modal,html.theme--catppuccin-latte .image.is-square img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-square img,html.theme--catppuccin-latte .image.is-square .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-square .has-ratio,html.theme--catppuccin-latte .image.is-1by1 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-1by1 img,html.theme--catppuccin-latte .image.is-1by1 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-1by1 .has-ratio,html.theme--catppuccin-latte .image.is-5by4 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-5by4 img,html.theme--catppuccin-latte .image.is-5by4 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-5by4 .has-ratio,html.theme--catppuccin-latte .image.is-4by3 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-4by3 img,html.theme--catppuccin-latte .image.is-4by3 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-4by3 .has-ratio,html.theme--catppuccin-latte .image.is-3by2 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by2 img,html.theme--catppuccin-latte .image.is-3by2 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by2 .has-ratio,html.theme--catppuccin-latte .image.is-5by3 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-5by3 img,html.theme--catppuccin-latte .image.is-5by3 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-5by3 .has-ratio,html.theme--catppuccin-latte .image.is-16by9 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-16by9 img,html.theme--catppuccin-latte .image.is-16by9 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-16by9 .has-ratio,html.theme--catppuccin-latte .image.is-2by1 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-2by1 img,html.theme--catppuccin-latte .image.is-2by1 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-2by1 .has-ratio,html.theme--catppuccin-latte .image.is-3by1 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by1 img,html.theme--catppuccin-latte .image.is-3by1 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by1 .has-ratio,html.theme--catppuccin-latte .image.is-4by5 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-4by5 img,html.theme--catppuccin-latte .image.is-4by5 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-4by5 .has-ratio,html.theme--catppuccin-latte .image.is-3by4 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by4 img,html.theme--catppuccin-latte .image.is-3by4 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by4 .has-ratio,html.theme--catppuccin-latte .image.is-2by3 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-2by3 img,html.theme--catppuccin-latte .image.is-2by3 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-2by3 .has-ratio,html.theme--catppuccin-latte .image.is-3by5 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by5 img,html.theme--catppuccin-latte .image.is-3by5 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by5 .has-ratio,html.theme--catppuccin-latte .image.is-9by16 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-9by16 img,html.theme--catppuccin-latte .image.is-9by16 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-9by16 .has-ratio,html.theme--catppuccin-latte .image.is-1by2 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-1by2 img,html.theme--catppuccin-latte .image.is-1by2 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-1by2 .has-ratio,html.theme--catppuccin-latte .image.is-1by3 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-1by3 img,html.theme--catppuccin-latte .image.is-1by3 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-1by3 .has-ratio,.is-overlay{bottom:0;left:0;position:absolute;right:0;top:0}html.theme--catppuccin-latte .navbar-burger{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;color:currentColor;font-family:inherit;font-size:1em;margin:0;padding:0}/*! minireset.css v0.0.6 | MIT License | github.com/jgthms/minireset.css */html,body,p,ol,ul,li,dl,dt,dd,blockquote,figure,fieldset,legend,textarea,pre,iframe,hr,h1,h2,h3,h4,h5,h6{margin:0;padding:0}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal}ul{list-style:none}button,input,select,textarea{margin:0}html{box-sizing:border-box}*,*::before,*::after{box-sizing:inherit}img,video{height:auto;max-width:100%}iframe{border:0}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}td:not([align]),th:not([align]){text-align:inherit}.has-text-white{color:#fff !important}a.has-text-white:hover,a.has-text-white:focus{color:#e6e6e6 !important}.has-background-white{background-color:#fff !important}.has-text-black{color:#0a0a0a !important}a.has-text-black:hover,a.has-text-black:focus{color:#000 !important}.has-background-black{background-color:#0a0a0a !important}.has-text-light{color:#f5f5f5 !important}a.has-text-light:hover,a.has-text-light:focus{color:#dbdbdb !important}.has-background-light{background-color:#f5f5f5 !important}.has-text-dark{color:#ccd0da !important}a.has-text-dark:hover,a.has-text-dark:focus{color:#aeb5c5 !important}.has-background-dark{background-color:#ccd0da !important}.has-text-primary{color:#1e66f5 !important}a.has-text-primary:hover,a.has-text-primary:focus{color:#0a4ed6 !important}.has-background-primary{background-color:#1e66f5 !important}.has-text-primary-light{color:#ebf2fe !important}a.has-text-primary-light:hover,a.has-text-primary-light:focus{color:#bbd1fc !important}.has-background-primary-light{background-color:#ebf2fe !important}.has-text-primary-dark{color:#0a52e1 !important}a.has-text-primary-dark:hover,a.has-text-primary-dark:focus{color:#286df5 !important}.has-background-primary-dark{background-color:#0a52e1 !important}.has-text-link{color:#1e66f5 !important}a.has-text-link:hover,a.has-text-link:focus{color:#0a4ed6 !important}.has-background-link{background-color:#1e66f5 !important}.has-text-link-light{color:#ebf2fe !important}a.has-text-link-light:hover,a.has-text-link-light:focus{color:#bbd1fc !important}.has-background-link-light{background-color:#ebf2fe !important}.has-text-link-dark{color:#0a52e1 !important}a.has-text-link-dark:hover,a.has-text-link-dark:focus{color:#286df5 !important}.has-background-link-dark{background-color:#0a52e1 !important}.has-text-info{color:#179299 !important}a.has-text-info:hover,a.has-text-info:focus{color:#10686d !important}.has-background-info{background-color:#179299 !important}.has-text-info-light{color:#edfcfc !important}a.has-text-info-light:hover,a.has-text-info-light:focus{color:#c1f3f6 !important}.has-background-info-light{background-color:#edfcfc !important}.has-text-info-dark{color:#1cb2ba !important}a.has-text-info-dark:hover,a.has-text-info-dark:focus{color:#2ad5df !important}.has-background-info-dark{background-color:#1cb2ba !important}.has-text-success{color:#40a02b !important}a.has-text-success:hover,a.has-text-success:focus{color:#307820 !important}.has-background-success{background-color:#40a02b !important}.has-text-success-light{color:#f1fbef !important}a.has-text-success-light:hover,a.has-text-success-light:focus{color:#cef0c7 !important}.has-background-success-light{background-color:#f1fbef !important}.has-text-success-dark{color:#40a12b !important}a.has-text-success-dark:hover,a.has-text-success-dark:focus{color:#50c936 !important}.has-background-success-dark{background-color:#40a12b !important}.has-text-warning{color:#df8e1d !important}a.has-text-warning:hover,a.has-text-warning:focus{color:#b27117 !important}.has-background-warning{background-color:#df8e1d !important}.has-text-warning-light{color:#fdf6ed !important}a.has-text-warning-light:hover,a.has-text-warning-light:focus{color:#f7e0c0 !important}.has-background-warning-light{background-color:#fdf6ed !important}.has-text-warning-dark{color:#9e6515 !important}a.has-text-warning-dark:hover,a.has-text-warning-dark:focus{color:#cb811a !important}.has-background-warning-dark{background-color:#9e6515 !important}.has-text-danger{color:#d20f39 !important}a.has-text-danger:hover,a.has-text-danger:focus{color:#a20c2c !important}.has-background-danger{background-color:#d20f39 !important}.has-text-danger-light{color:#feecf0 !important}a.has-text-danger-light:hover,a.has-text-danger-light:focus{color:#fabcca !important}.has-background-danger-light{background-color:#feecf0 !important}.has-text-danger-dark{color:#e9113f !important}a.has-text-danger-dark:hover,a.has-text-danger-dark:focus{color:#f13c63 !important}.has-background-danger-dark{background-color:#e9113f !important}.has-text-black-bis{color:#121212 !important}.has-background-black-bis{background-color:#121212 !important}.has-text-black-ter{color:#242424 !important}.has-background-black-ter{background-color:#242424 !important}.has-text-grey-darker{color:#ccd0da !important}.has-background-grey-darker{background-color:#ccd0da !important}.has-text-grey-dark{color:#bcc0cc !important}.has-background-grey-dark{background-color:#bcc0cc !important}.has-text-grey{color:#acb0be !important}.has-background-grey{background-color:#acb0be !important}.has-text-grey-light{color:#9ca0b0 !important}.has-background-grey-light{background-color:#9ca0b0 !important}.has-text-grey-lighter{color:#8c8fa1 !important}.has-background-grey-lighter{background-color:#8c8fa1 !important}.has-text-white-ter{color:#f5f5f5 !important}.has-background-white-ter{background-color:#f5f5f5 !important}.has-text-white-bis{color:#fafafa !important}.has-background-white-bis{background-color:#fafafa !important}.is-flex-direction-row{flex-direction:row !important}.is-flex-direction-row-reverse{flex-direction:row-reverse !important}.is-flex-direction-column{flex-direction:column !important}.is-flex-direction-column-reverse{flex-direction:column-reverse !important}.is-flex-wrap-nowrap{flex-wrap:nowrap !important}.is-flex-wrap-wrap{flex-wrap:wrap !important}.is-flex-wrap-wrap-reverse{flex-wrap:wrap-reverse !important}.is-justify-content-flex-start{justify-content:flex-start !important}.is-justify-content-flex-end{justify-content:flex-end !important}.is-justify-content-center{justify-content:center !important}.is-justify-content-space-between{justify-content:space-between !important}.is-justify-content-space-around{justify-content:space-around !important}.is-justify-content-space-evenly{justify-content:space-evenly !important}.is-justify-content-start{justify-content:start !important}.is-justify-content-end{justify-content:end !important}.is-justify-content-left{justify-content:left !important}.is-justify-content-right{justify-content:right !important}.is-align-content-flex-start{align-content:flex-start !important}.is-align-content-flex-end{align-content:flex-end !important}.is-align-content-center{align-content:center !important}.is-align-content-space-between{align-content:space-between !important}.is-align-content-space-around{align-content:space-around !important}.is-align-content-space-evenly{align-content:space-evenly !important}.is-align-content-stretch{align-content:stretch !important}.is-align-content-start{align-content:start !important}.is-align-content-end{align-content:end !important}.is-align-content-baseline{align-content:baseline !important}.is-align-items-stretch{align-items:stretch !important}.is-align-items-flex-start{align-items:flex-start !important}.is-align-items-flex-end{align-items:flex-end !important}.is-align-items-center{align-items:center !important}.is-align-items-baseline{align-items:baseline !important}.is-align-items-start{align-items:start !important}.is-align-items-end{align-items:end !important}.is-align-items-self-start{align-items:self-start !important}.is-align-items-self-end{align-items:self-end !important}.is-align-self-auto{align-self:auto !important}.is-align-self-flex-start{align-self:flex-start !important}.is-align-self-flex-end{align-self:flex-end !important}.is-align-self-center{align-self:center !important}.is-align-self-baseline{align-self:baseline !important}.is-align-self-stretch{align-self:stretch !important}.is-flex-grow-0{flex-grow:0 !important}.is-flex-grow-1{flex-grow:1 !important}.is-flex-grow-2{flex-grow:2 !important}.is-flex-grow-3{flex-grow:3 !important}.is-flex-grow-4{flex-grow:4 !important}.is-flex-grow-5{flex-grow:5 !important}.is-flex-shrink-0{flex-shrink:0 !important}.is-flex-shrink-1{flex-shrink:1 !important}.is-flex-shrink-2{flex-shrink:2 !important}.is-flex-shrink-3{flex-shrink:3 !important}.is-flex-shrink-4{flex-shrink:4 !important}.is-flex-shrink-5{flex-shrink:5 !important}.is-clearfix::after{clear:both;content:" ";display:table}.is-pulled-left{float:left !important}.is-pulled-right{float:right !important}.is-radiusless{border-radius:0 !important}.is-shadowless{box-shadow:none !important}.is-clickable{cursor:pointer !important;pointer-events:all !important}.is-clipped{overflow:hidden !important}.is-relative{position:relative !important}.is-marginless{margin:0 !important}.is-paddingless{padding:0 !important}.m-0{margin:0 !important}.mt-0{margin-top:0 !important}.mr-0{margin-right:0 !important}.mb-0{margin-bottom:0 !important}.ml-0{margin-left:0 !important}.mx-0{margin-left:0 !important;margin-right:0 !important}.my-0{margin-top:0 !important;margin-bottom:0 !important}.m-1{margin:.25rem !important}.mt-1{margin-top:.25rem !important}.mr-1{margin-right:.25rem !important}.mb-1{margin-bottom:.25rem !important}.ml-1{margin-left:.25rem !important}.mx-1{margin-left:.25rem !important;margin-right:.25rem !important}.my-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.m-2{margin:.5rem !important}.mt-2{margin-top:.5rem !important}.mr-2{margin-right:.5rem !important}.mb-2{margin-bottom:.5rem !important}.ml-2{margin-left:.5rem !important}.mx-2{margin-left:.5rem !important;margin-right:.5rem !important}.my-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.m-3{margin:.75rem !important}.mt-3{margin-top:.75rem !important}.mr-3{margin-right:.75rem !important}.mb-3{margin-bottom:.75rem !important}.ml-3{margin-left:.75rem !important}.mx-3{margin-left:.75rem !important;margin-right:.75rem !important}.my-3{margin-top:.75rem !important;margin-bottom:.75rem !important}.m-4{margin:1rem !important}.mt-4{margin-top:1rem !important}.mr-4{margin-right:1rem !important}.mb-4{margin-bottom:1rem !important}.ml-4{margin-left:1rem !important}.mx-4{margin-left:1rem !important;margin-right:1rem !important}.my-4{margin-top:1rem !important;margin-bottom:1rem !important}.m-5{margin:1.5rem !important}.mt-5{margin-top:1.5rem !important}.mr-5{margin-right:1.5rem !important}.mb-5{margin-bottom:1.5rem !important}.ml-5{margin-left:1.5rem !important}.mx-5{margin-left:1.5rem !important;margin-right:1.5rem !important}.my-5{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.m-6{margin:3rem !important}.mt-6{margin-top:3rem !important}.mr-6{margin-right:3rem !important}.mb-6{margin-bottom:3rem !important}.ml-6{margin-left:3rem !important}.mx-6{margin-left:3rem !important;margin-right:3rem !important}.my-6{margin-top:3rem !important;margin-bottom:3rem !important}.m-auto{margin:auto !important}.mt-auto{margin-top:auto !important}.mr-auto{margin-right:auto !important}.mb-auto{margin-bottom:auto !important}.ml-auto{margin-left:auto !important}.mx-auto{margin-left:auto !important;margin-right:auto !important}.my-auto{margin-top:auto !important;margin-bottom:auto !important}.p-0{padding:0 !important}.pt-0{padding-top:0 !important}.pr-0{padding-right:0 !important}.pb-0{padding-bottom:0 !important}.pl-0{padding-left:0 !important}.px-0{padding-left:0 !important;padding-right:0 !important}.py-0{padding-top:0 !important;padding-bottom:0 !important}.p-1{padding:.25rem !important}.pt-1{padding-top:.25rem !important}.pr-1{padding-right:.25rem !important}.pb-1{padding-bottom:.25rem !important}.pl-1{padding-left:.25rem !important}.px-1{padding-left:.25rem !important;padding-right:.25rem !important}.py-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.p-2{padding:.5rem !important}.pt-2{padding-top:.5rem !important}.pr-2{padding-right:.5rem !important}.pb-2{padding-bottom:.5rem !important}.pl-2{padding-left:.5rem !important}.px-2{padding-left:.5rem !important;padding-right:.5rem !important}.py-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.p-3{padding:.75rem !important}.pt-3{padding-top:.75rem !important}.pr-3{padding-right:.75rem !important}.pb-3{padding-bottom:.75rem !important}.pl-3{padding-left:.75rem !important}.px-3{padding-left:.75rem !important;padding-right:.75rem !important}.py-3{padding-top:.75rem !important;padding-bottom:.75rem !important}.p-4{padding:1rem !important}.pt-4{padding-top:1rem !important}.pr-4{padding-right:1rem !important}.pb-4{padding-bottom:1rem !important}.pl-4{padding-left:1rem !important}.px-4{padding-left:1rem !important;padding-right:1rem !important}.py-4{padding-top:1rem !important;padding-bottom:1rem !important}.p-5{padding:1.5rem !important}.pt-5{padding-top:1.5rem !important}.pr-5{padding-right:1.5rem !important}.pb-5{padding-bottom:1.5rem !important}.pl-5{padding-left:1.5rem !important}.px-5{padding-left:1.5rem !important;padding-right:1.5rem !important}.py-5{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.p-6{padding:3rem !important}.pt-6{padding-top:3rem !important}.pr-6{padding-right:3rem !important}.pb-6{padding-bottom:3rem !important}.pl-6{padding-left:3rem !important}.px-6{padding-left:3rem !important;padding-right:3rem !important}.py-6{padding-top:3rem !important;padding-bottom:3rem !important}.p-auto{padding:auto !important}.pt-auto{padding-top:auto !important}.pr-auto{padding-right:auto !important}.pb-auto{padding-bottom:auto !important}.pl-auto{padding-left:auto !important}.px-auto{padding-left:auto !important;padding-right:auto !important}.py-auto{padding-top:auto !important;padding-bottom:auto !important}.is-size-1{font-size:3rem !important}.is-size-2{font-size:2.5rem !important}.is-size-3{font-size:2rem !important}.is-size-4{font-size:1.5rem !important}.is-size-5{font-size:1.25rem !important}.is-size-6{font-size:1rem !important}.is-size-7,html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink{font-size:.75rem !important}@media screen and (max-width: 768px){.is-size-1-mobile{font-size:3rem !important}.is-size-2-mobile{font-size:2.5rem !important}.is-size-3-mobile{font-size:2rem !important}.is-size-4-mobile{font-size:1.5rem !important}.is-size-5-mobile{font-size:1.25rem !important}.is-size-6-mobile{font-size:1rem !important}.is-size-7-mobile{font-size:.75rem !important}}@media screen and (min-width: 769px),print{.is-size-1-tablet{font-size:3rem !important}.is-size-2-tablet{font-size:2.5rem !important}.is-size-3-tablet{font-size:2rem !important}.is-size-4-tablet{font-size:1.5rem !important}.is-size-5-tablet{font-size:1.25rem !important}.is-size-6-tablet{font-size:1rem !important}.is-size-7-tablet{font-size:.75rem !important}}@media screen and (max-width: 1055px){.is-size-1-touch{font-size:3rem !important}.is-size-2-touch{font-size:2.5rem !important}.is-size-3-touch{font-size:2rem !important}.is-size-4-touch{font-size:1.5rem !important}.is-size-5-touch{font-size:1.25rem !important}.is-size-6-touch{font-size:1rem !important}.is-size-7-touch{font-size:.75rem !important}}@media screen and (min-width: 1056px){.is-size-1-desktop{font-size:3rem !important}.is-size-2-desktop{font-size:2.5rem !important}.is-size-3-desktop{font-size:2rem !important}.is-size-4-desktop{font-size:1.5rem !important}.is-size-5-desktop{font-size:1.25rem !important}.is-size-6-desktop{font-size:1rem !important}.is-size-7-desktop{font-size:.75rem !important}}@media screen and (min-width: 1216px){.is-size-1-widescreen{font-size:3rem !important}.is-size-2-widescreen{font-size:2.5rem !important}.is-size-3-widescreen{font-size:2rem !important}.is-size-4-widescreen{font-size:1.5rem !important}.is-size-5-widescreen{font-size:1.25rem !important}.is-size-6-widescreen{font-size:1rem !important}.is-size-7-widescreen{font-size:.75rem !important}}@media screen and (min-width: 1408px){.is-size-1-fullhd{font-size:3rem !important}.is-size-2-fullhd{font-size:2.5rem !important}.is-size-3-fullhd{font-size:2rem !important}.is-size-4-fullhd{font-size:1.5rem !important}.is-size-5-fullhd{font-size:1.25rem !important}.is-size-6-fullhd{font-size:1rem !important}.is-size-7-fullhd{font-size:.75rem !important}}.has-text-centered{text-align:center !important}.has-text-justified{text-align:justify !important}.has-text-left{text-align:left !important}.has-text-right{text-align:right !important}@media screen and (max-width: 768px){.has-text-centered-mobile{text-align:center !important}}@media screen and (min-width: 769px),print{.has-text-centered-tablet{text-align:center !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-centered-tablet-only{text-align:center !important}}@media screen and (max-width: 1055px){.has-text-centered-touch{text-align:center !important}}@media screen and (min-width: 1056px){.has-text-centered-desktop{text-align:center !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-centered-desktop-only{text-align:center !important}}@media screen and (min-width: 1216px){.has-text-centered-widescreen{text-align:center !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-centered-widescreen-only{text-align:center !important}}@media screen and (min-width: 1408px){.has-text-centered-fullhd{text-align:center !important}}@media screen and (max-width: 768px){.has-text-justified-mobile{text-align:justify !important}}@media screen and (min-width: 769px),print{.has-text-justified-tablet{text-align:justify !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-justified-tablet-only{text-align:justify !important}}@media screen and (max-width: 1055px){.has-text-justified-touch{text-align:justify !important}}@media screen and (min-width: 1056px){.has-text-justified-desktop{text-align:justify !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-justified-desktop-only{text-align:justify !important}}@media screen and (min-width: 1216px){.has-text-justified-widescreen{text-align:justify !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-justified-widescreen-only{text-align:justify !important}}@media screen and (min-width: 1408px){.has-text-justified-fullhd{text-align:justify !important}}@media screen and (max-width: 768px){.has-text-left-mobile{text-align:left !important}}@media screen and (min-width: 769px),print{.has-text-left-tablet{text-align:left !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-left-tablet-only{text-align:left !important}}@media screen and (max-width: 1055px){.has-text-left-touch{text-align:left !important}}@media screen and (min-width: 1056px){.has-text-left-desktop{text-align:left !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-left-desktop-only{text-align:left !important}}@media screen and (min-width: 1216px){.has-text-left-widescreen{text-align:left !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-left-widescreen-only{text-align:left !important}}@media screen and (min-width: 1408px){.has-text-left-fullhd{text-align:left !important}}@media screen and (max-width: 768px){.has-text-right-mobile{text-align:right !important}}@media screen and (min-width: 769px),print{.has-text-right-tablet{text-align:right !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-right-tablet-only{text-align:right !important}}@media screen and (max-width: 1055px){.has-text-right-touch{text-align:right !important}}@media screen and (min-width: 1056px){.has-text-right-desktop{text-align:right !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-right-desktop-only{text-align:right !important}}@media screen and (min-width: 1216px){.has-text-right-widescreen{text-align:right !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-right-widescreen-only{text-align:right !important}}@media screen and (min-width: 1408px){.has-text-right-fullhd{text-align:right !important}}.is-capitalized{text-transform:capitalize !important}.is-lowercase{text-transform:lowercase !important}.is-uppercase{text-transform:uppercase !important}.is-italic{font-style:italic !important}.is-underlined{text-decoration:underline !important}.has-text-weight-light{font-weight:300 !important}.has-text-weight-normal{font-weight:400 !important}.has-text-weight-medium{font-weight:500 !important}.has-text-weight-semibold{font-weight:600 !important}.has-text-weight-bold{font-weight:700 !important}.is-family-primary{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-secondary{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-sans-serif{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-monospace{font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace !important}.is-family-code{font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace !important}.is-block{display:block !important}@media screen and (max-width: 768px){.is-block-mobile{display:block !important}}@media screen and (min-width: 769px),print{.is-block-tablet{display:block !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-block-tablet-only{display:block !important}}@media screen and (max-width: 1055px){.is-block-touch{display:block !important}}@media screen and (min-width: 1056px){.is-block-desktop{display:block !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-block-desktop-only{display:block !important}}@media screen and (min-width: 1216px){.is-block-widescreen{display:block !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-block-widescreen-only{display:block !important}}@media screen and (min-width: 1408px){.is-block-fullhd{display:block !important}}.is-flex{display:flex !important}@media screen and (max-width: 768px){.is-flex-mobile{display:flex !important}}@media screen and (min-width: 769px),print{.is-flex-tablet{display:flex !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-flex-tablet-only{display:flex !important}}@media screen and (max-width: 1055px){.is-flex-touch{display:flex !important}}@media screen and (min-width: 1056px){.is-flex-desktop{display:flex !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-flex-desktop-only{display:flex !important}}@media screen and (min-width: 1216px){.is-flex-widescreen{display:flex !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-flex-widescreen-only{display:flex !important}}@media screen and (min-width: 1408px){.is-flex-fullhd{display:flex !important}}.is-inline{display:inline !important}@media screen and (max-width: 768px){.is-inline-mobile{display:inline !important}}@media screen and (min-width: 769px),print{.is-inline-tablet{display:inline !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-tablet-only{display:inline !important}}@media screen and (max-width: 1055px){.is-inline-touch{display:inline !important}}@media screen and (min-width: 1056px){.is-inline-desktop{display:inline !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-desktop-only{display:inline !important}}@media screen and (min-width: 1216px){.is-inline-widescreen{display:inline !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-widescreen-only{display:inline !important}}@media screen and (min-width: 1408px){.is-inline-fullhd{display:inline !important}}.is-inline-block{display:inline-block !important}@media screen and (max-width: 768px){.is-inline-block-mobile{display:inline-block !important}}@media screen and (min-width: 769px),print{.is-inline-block-tablet{display:inline-block !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-block-tablet-only{display:inline-block !important}}@media screen and (max-width: 1055px){.is-inline-block-touch{display:inline-block !important}}@media screen and (min-width: 1056px){.is-inline-block-desktop{display:inline-block !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-block-desktop-only{display:inline-block !important}}@media screen and (min-width: 1216px){.is-inline-block-widescreen{display:inline-block !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-block-widescreen-only{display:inline-block !important}}@media screen and (min-width: 1408px){.is-inline-block-fullhd{display:inline-block !important}}.is-inline-flex{display:inline-flex !important}@media screen and (max-width: 768px){.is-inline-flex-mobile{display:inline-flex !important}}@media screen and (min-width: 769px),print{.is-inline-flex-tablet{display:inline-flex !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-flex-tablet-only{display:inline-flex !important}}@media screen and (max-width: 1055px){.is-inline-flex-touch{display:inline-flex !important}}@media screen and (min-width: 1056px){.is-inline-flex-desktop{display:inline-flex !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-flex-desktop-only{display:inline-flex !important}}@media screen and (min-width: 1216px){.is-inline-flex-widescreen{display:inline-flex !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-flex-widescreen-only{display:inline-flex !important}}@media screen and (min-width: 1408px){.is-inline-flex-fullhd{display:inline-flex !important}}.is-hidden{display:none !important}.is-sr-only{border:none !important;clip:rect(0, 0, 0, 0) !important;height:0.01em !important;overflow:hidden !important;padding:0 !important;position:absolute !important;white-space:nowrap !important;width:0.01em !important}@media screen and (max-width: 768px){.is-hidden-mobile{display:none !important}}@media screen and (min-width: 769px),print{.is-hidden-tablet{display:none !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-hidden-tablet-only{display:none !important}}@media screen and (max-width: 1055px){.is-hidden-touch{display:none !important}}@media screen and (min-width: 1056px){.is-hidden-desktop{display:none !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-hidden-desktop-only{display:none !important}}@media screen and (min-width: 1216px){.is-hidden-widescreen{display:none !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-hidden-widescreen-only{display:none !important}}@media screen and (min-width: 1408px){.is-hidden-fullhd{display:none !important}}.is-invisible{visibility:hidden !important}@media screen and (max-width: 768px){.is-invisible-mobile{visibility:hidden !important}}@media screen and (min-width: 769px),print{.is-invisible-tablet{visibility:hidden !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-invisible-tablet-only{visibility:hidden !important}}@media screen and (max-width: 1055px){.is-invisible-touch{visibility:hidden !important}}@media screen and (min-width: 1056px){.is-invisible-desktop{visibility:hidden !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-invisible-desktop-only{visibility:hidden !important}}@media screen and (min-width: 1216px){.is-invisible-widescreen{visibility:hidden !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-invisible-widescreen-only{visibility:hidden !important}}@media screen and (min-width: 1408px){.is-invisible-fullhd{visibility:hidden !important}}html.theme--catppuccin-latte html{background-color:#eff1f5;font-size:16px;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;min-width:300px;overflow-x:auto;overflow-y:scroll;text-rendering:optimizeLegibility;text-size-adjust:100%}html.theme--catppuccin-latte article,html.theme--catppuccin-latte aside,html.theme--catppuccin-latte figure,html.theme--catppuccin-latte footer,html.theme--catppuccin-latte header,html.theme--catppuccin-latte hgroup,html.theme--catppuccin-latte section{display:block}html.theme--catppuccin-latte body,html.theme--catppuccin-latte button,html.theme--catppuccin-latte input,html.theme--catppuccin-latte optgroup,html.theme--catppuccin-latte select,html.theme--catppuccin-latte textarea{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif}html.theme--catppuccin-latte code,html.theme--catppuccin-latte pre{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto;font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace}html.theme--catppuccin-latte body{color:#4c4f69;font-size:1em;font-weight:400;line-height:1.5}html.theme--catppuccin-latte a{color:#1e66f5;cursor:pointer;text-decoration:none}html.theme--catppuccin-latte a strong{color:currentColor}html.theme--catppuccin-latte a:hover{color:#04a5e5}html.theme--catppuccin-latte code{background-color:#e6e9ef;color:#4c4f69;font-size:.875em;font-weight:normal;padding:.1em}html.theme--catppuccin-latte hr{background-color:#e6e9ef;border:none;display:block;height:2px;margin:1.5rem 0}html.theme--catppuccin-latte img{height:auto;max-width:100%}html.theme--catppuccin-latte input[type="checkbox"],html.theme--catppuccin-latte input[type="radio"]{vertical-align:baseline}html.theme--catppuccin-latte small{font-size:.875em}html.theme--catppuccin-latte span{font-style:inherit;font-weight:inherit}html.theme--catppuccin-latte strong{color:#41445a;font-weight:700}html.theme--catppuccin-latte fieldset{border:none}html.theme--catppuccin-latte pre{-webkit-overflow-scrolling:touch;background-color:#e6e9ef;color:#4c4f69;font-size:.875em;overflow-x:auto;padding:1.25rem 1.5rem;white-space:pre;word-wrap:normal}html.theme--catppuccin-latte pre code{background-color:transparent;color:currentColor;font-size:1em;padding:0}html.theme--catppuccin-latte table td,html.theme--catppuccin-latte table th{vertical-align:top}html.theme--catppuccin-latte table td:not([align]),html.theme--catppuccin-latte table th:not([align]){text-align:inherit}html.theme--catppuccin-latte table th{color:#41445a}html.theme--catppuccin-latte .box{background-color:#bcc0cc;border-radius:8px;box-shadow:none;color:#4c4f69;display:block;padding:1.25rem}html.theme--catppuccin-latte a.box:hover,html.theme--catppuccin-latte a.box:focus{box-shadow:0 0.5em 1em -0.125em rgba(10,10,10,0.1),0 0 0 1px #1e66f5}html.theme--catppuccin-latte a.box:active{box-shadow:inset 0 1px 2px rgba(10,10,10,0.2),0 0 0 1px #1e66f5}html.theme--catppuccin-latte .button{background-color:#e6e9ef;border-color:#fff;border-width:1px;color:#1e66f5;cursor:pointer;justify-content:center;padding-bottom:calc(0.5em - 1px);padding-left:1em;padding-right:1em;padding-top:calc(0.5em - 1px);text-align:center;white-space:nowrap}html.theme--catppuccin-latte .button strong{color:inherit}html.theme--catppuccin-latte .button .icon,html.theme--catppuccin-latte .button .icon.is-small,html.theme--catppuccin-latte .button #documenter .docs-sidebar form.docs-search>input.icon,html.theme--catppuccin-latte #documenter .docs-sidebar .button form.docs-search>input.icon,html.theme--catppuccin-latte .button .icon.is-medium,html.theme--catppuccin-latte .button .icon.is-large{height:1.5em;width:1.5em}html.theme--catppuccin-latte .button .icon:first-child:not(:last-child){margin-left:calc(-0.5em - 1px);margin-right:.25em}html.theme--catppuccin-latte .button .icon:last-child:not(:first-child){margin-left:.25em;margin-right:calc(-0.5em - 1px)}html.theme--catppuccin-latte .button .icon:first-child:last-child{margin-left:calc(-0.5em - 1px);margin-right:calc(-0.5em - 1px)}html.theme--catppuccin-latte .button:hover,html.theme--catppuccin-latte .button.is-hovered{border-color:#9ca0b0;color:#41445a}html.theme--catppuccin-latte .button:focus,html.theme--catppuccin-latte .button.is-focused{border-color:#9ca0b0;color:#0b57ef}html.theme--catppuccin-latte .button:focus:not(:active),html.theme--catppuccin-latte .button.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(30,102,245,0.25)}html.theme--catppuccin-latte .button:active,html.theme--catppuccin-latte .button.is-active{border-color:#bcc0cc;color:#41445a}html.theme--catppuccin-latte .button.is-text{background-color:transparent;border-color:transparent;color:#4c4f69;text-decoration:underline}html.theme--catppuccin-latte .button.is-text:hover,html.theme--catppuccin-latte .button.is-text.is-hovered,html.theme--catppuccin-latte .button.is-text:focus,html.theme--catppuccin-latte .button.is-text.is-focused{background-color:#e6e9ef;color:#41445a}html.theme--catppuccin-latte .button.is-text:active,html.theme--catppuccin-latte .button.is-text.is-active{background-color:#d6dbe5;color:#41445a}html.theme--catppuccin-latte .button.is-text[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-text{background-color:transparent;border-color:transparent;box-shadow:none}html.theme--catppuccin-latte .button.is-ghost{background:none;border-color:rgba(0,0,0,0);color:#1e66f5;text-decoration:none}html.theme--catppuccin-latte .button.is-ghost:hover,html.theme--catppuccin-latte .button.is-ghost.is-hovered{color:#1e66f5;text-decoration:underline}html.theme--catppuccin-latte .button.is-white{background-color:#fff;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-latte .button.is-white:hover,html.theme--catppuccin-latte .button.is-white.is-hovered{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-latte .button.is-white:focus,html.theme--catppuccin-latte .button.is-white.is-focused{border-color:transparent;color:#0a0a0a}html.theme--catppuccin-latte .button.is-white:focus:not(:active),html.theme--catppuccin-latte .button.is-white.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}html.theme--catppuccin-latte .button.is-white:active,html.theme--catppuccin-latte .button.is-white.is-active{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-latte .button.is-white[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-white{background-color:#fff;border-color:#fff;box-shadow:none}html.theme--catppuccin-latte .button.is-white.is-inverted{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-latte .button.is-white.is-inverted:hover,html.theme--catppuccin-latte .button.is-white.is-inverted.is-hovered{background-color:#000}html.theme--catppuccin-latte .button.is-white.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-white.is-inverted{background-color:#0a0a0a;border-color:transparent;box-shadow:none;color:#fff}html.theme--catppuccin-latte .button.is-white.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--catppuccin-latte .button.is-white.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-latte .button.is-white.is-outlined:hover,html.theme--catppuccin-latte .button.is-white.is-outlined.is-hovered,html.theme--catppuccin-latte .button.is-white.is-outlined:focus,html.theme--catppuccin-latte .button.is-white.is-outlined.is-focused{background-color:#fff;border-color:#fff;color:#0a0a0a}html.theme--catppuccin-latte .button.is-white.is-outlined.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-latte .button.is-white.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .button.is-white.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .button.is-white.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .button.is-white.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--catppuccin-latte .button.is-white.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-white.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-latte .button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}html.theme--catppuccin-latte .button.is-white.is-inverted.is-outlined:hover,html.theme--catppuccin-latte .button.is-white.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-latte .button.is-white.is-inverted.is-outlined:focus,html.theme--catppuccin-latte .button.is-white.is-inverted.is-outlined.is-focused{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-latte .button.is-white.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .button.is-white.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .button.is-white.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .button.is-white.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-latte .button.is-white.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}html.theme--catppuccin-latte .button.is-black{background-color:#0a0a0a;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-black:hover,html.theme--catppuccin-latte .button.is-black.is-hovered{background-color:#040404;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-black:focus,html.theme--catppuccin-latte .button.is-black.is-focused{border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-black:focus:not(:active),html.theme--catppuccin-latte .button.is-black.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}html.theme--catppuccin-latte .button.is-black:active,html.theme--catppuccin-latte .button.is-black.is-active{background-color:#000;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-black[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-black{background-color:#0a0a0a;border-color:#0a0a0a;box-shadow:none}html.theme--catppuccin-latte .button.is-black.is-inverted{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-latte .button.is-black.is-inverted:hover,html.theme--catppuccin-latte .button.is-black.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--catppuccin-latte .button.is-black.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-black.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#0a0a0a}html.theme--catppuccin-latte .button.is-black.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-latte .button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}html.theme--catppuccin-latte .button.is-black.is-outlined:hover,html.theme--catppuccin-latte .button.is-black.is-outlined.is-hovered,html.theme--catppuccin-latte .button.is-black.is-outlined:focus,html.theme--catppuccin-latte .button.is-black.is-outlined.is-focused{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}html.theme--catppuccin-latte .button.is-black.is-outlined.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--catppuccin-latte .button.is-black.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .button.is-black.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .button.is-black.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .button.is-black.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-latte .button.is-black.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}html.theme--catppuccin-latte .button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-latte .button.is-black.is-inverted.is-outlined:hover,html.theme--catppuccin-latte .button.is-black.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-latte .button.is-black.is-inverted.is-outlined:focus,html.theme--catppuccin-latte .button.is-black.is-inverted.is-outlined.is-focused{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-latte .button.is-black.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .button.is-black.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .button.is-black.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .button.is-black.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--catppuccin-latte .button.is-black.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-latte .button.is-light{background-color:#f5f5f5;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .button.is-light:hover,html.theme--catppuccin-latte .button.is-light.is-hovered{background-color:#eee;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .button.is-light:focus,html.theme--catppuccin-latte .button.is-light.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .button.is-light:focus:not(:active),html.theme--catppuccin-latte .button.is-light.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(245,245,245,0.25)}html.theme--catppuccin-latte .button.is-light:active,html.theme--catppuccin-latte .button.is-light.is-active{background-color:#e8e8e8;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .button.is-light[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-light{background-color:#f5f5f5;border-color:#f5f5f5;box-shadow:none}html.theme--catppuccin-latte .button.is-light.is-inverted{background-color:rgba(0,0,0,0.7);color:#f5f5f5}html.theme--catppuccin-latte .button.is-light.is-inverted:hover,html.theme--catppuccin-latte .button.is-light.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .button.is-light.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-light.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#f5f5f5}html.theme--catppuccin-latte .button.is-light.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-latte .button.is-light.is-outlined{background-color:transparent;border-color:#f5f5f5;color:#f5f5f5}html.theme--catppuccin-latte .button.is-light.is-outlined:hover,html.theme--catppuccin-latte .button.is-light.is-outlined.is-hovered,html.theme--catppuccin-latte .button.is-light.is-outlined:focus,html.theme--catppuccin-latte .button.is-light.is-outlined.is-focused{background-color:#f5f5f5;border-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .button.is-light.is-outlined.is-loading::after{border-color:transparent transparent #f5f5f5 #f5f5f5 !important}html.theme--catppuccin-latte .button.is-light.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .button.is-light.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .button.is-light.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .button.is-light.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-latte .button.is-light.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-light.is-outlined{background-color:transparent;border-color:#f5f5f5;box-shadow:none;color:#f5f5f5}html.theme--catppuccin-latte .button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .button.is-light.is-inverted.is-outlined:hover,html.theme--catppuccin-latte .button.is-light.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-latte .button.is-light.is-inverted.is-outlined:focus,html.theme--catppuccin-latte .button.is-light.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#f5f5f5}html.theme--catppuccin-latte .button.is-light.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .button.is-light.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .button.is-light.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .button.is-light.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #f5f5f5 #f5f5f5 !important}html.theme--catppuccin-latte .button.is-light.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .button.is-dark,html.theme--catppuccin-latte .content kbd.button{background-color:#ccd0da;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .button.is-dark:hover,html.theme--catppuccin-latte .content kbd.button:hover,html.theme--catppuccin-latte .button.is-dark.is-hovered,html.theme--catppuccin-latte .content kbd.button.is-hovered{background-color:#c5c9d5;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .button.is-dark:focus,html.theme--catppuccin-latte .content kbd.button:focus,html.theme--catppuccin-latte .button.is-dark.is-focused,html.theme--catppuccin-latte .content kbd.button.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .button.is-dark:focus:not(:active),html.theme--catppuccin-latte .content kbd.button:focus:not(:active),html.theme--catppuccin-latte .button.is-dark.is-focused:not(:active),html.theme--catppuccin-latte .content kbd.button.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(204,208,218,0.25)}html.theme--catppuccin-latte .button.is-dark:active,html.theme--catppuccin-latte .content kbd.button:active,html.theme--catppuccin-latte .button.is-dark.is-active,html.theme--catppuccin-latte .content kbd.button.is-active{background-color:#bdc2cf;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .button.is-dark[disabled],html.theme--catppuccin-latte .content kbd.button[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-dark,fieldset[disabled] html.theme--catppuccin-latte .content kbd.button{background-color:#ccd0da;border-color:#ccd0da;box-shadow:none}html.theme--catppuccin-latte .button.is-dark.is-inverted,html.theme--catppuccin-latte .content kbd.button.is-inverted{background-color:rgba(0,0,0,0.7);color:#ccd0da}html.theme--catppuccin-latte .button.is-dark.is-inverted:hover,html.theme--catppuccin-latte .content kbd.button.is-inverted:hover,html.theme--catppuccin-latte .button.is-dark.is-inverted.is-hovered,html.theme--catppuccin-latte .content kbd.button.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .button.is-dark.is-inverted[disabled],html.theme--catppuccin-latte .content kbd.button.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-dark.is-inverted,fieldset[disabled] html.theme--catppuccin-latte .content kbd.button.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#ccd0da}html.theme--catppuccin-latte .button.is-dark.is-loading::after,html.theme--catppuccin-latte .content kbd.button.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-latte .button.is-dark.is-outlined,html.theme--catppuccin-latte .content kbd.button.is-outlined{background-color:transparent;border-color:#ccd0da;color:#ccd0da}html.theme--catppuccin-latte .button.is-dark.is-outlined:hover,html.theme--catppuccin-latte .content kbd.button.is-outlined:hover,html.theme--catppuccin-latte .button.is-dark.is-outlined.is-hovered,html.theme--catppuccin-latte .content kbd.button.is-outlined.is-hovered,html.theme--catppuccin-latte .button.is-dark.is-outlined:focus,html.theme--catppuccin-latte .content kbd.button.is-outlined:focus,html.theme--catppuccin-latte .button.is-dark.is-outlined.is-focused,html.theme--catppuccin-latte .content kbd.button.is-outlined.is-focused{background-color:#ccd0da;border-color:#ccd0da;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .button.is-dark.is-outlined.is-loading::after,html.theme--catppuccin-latte .content kbd.button.is-outlined.is-loading::after{border-color:transparent transparent #ccd0da #ccd0da !important}html.theme--catppuccin-latte .button.is-dark.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .content kbd.button.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .button.is-dark.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .content kbd.button.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .button.is-dark.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .content kbd.button.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .button.is-dark.is-outlined.is-loading.is-focused::after,html.theme--catppuccin-latte .content kbd.button.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-latte .button.is-dark.is-outlined[disabled],html.theme--catppuccin-latte .content kbd.button.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-dark.is-outlined,fieldset[disabled] html.theme--catppuccin-latte .content kbd.button.is-outlined{background-color:transparent;border-color:#ccd0da;box-shadow:none;color:#ccd0da}html.theme--catppuccin-latte .button.is-dark.is-inverted.is-outlined,html.theme--catppuccin-latte .content kbd.button.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .button.is-dark.is-inverted.is-outlined:hover,html.theme--catppuccin-latte .content kbd.button.is-inverted.is-outlined:hover,html.theme--catppuccin-latte .button.is-dark.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-latte .content kbd.button.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-latte .button.is-dark.is-inverted.is-outlined:focus,html.theme--catppuccin-latte .content kbd.button.is-inverted.is-outlined:focus,html.theme--catppuccin-latte .button.is-dark.is-inverted.is-outlined.is-focused,html.theme--catppuccin-latte .content kbd.button.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#ccd0da}html.theme--catppuccin-latte .button.is-dark.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .content kbd.button.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .button.is-dark.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .content kbd.button.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .button.is-dark.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .content kbd.button.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .button.is-dark.is-inverted.is-outlined.is-loading.is-focused::after,html.theme--catppuccin-latte .content kbd.button.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #ccd0da #ccd0da !important}html.theme--catppuccin-latte .button.is-dark.is-inverted.is-outlined[disabled],html.theme--catppuccin-latte .content kbd.button.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-dark.is-inverted.is-outlined,fieldset[disabled] html.theme--catppuccin-latte .content kbd.button.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .button.is-primary,html.theme--catppuccin-latte .docstring>section>a.button.docs-sourcelink{background-color:#1e66f5;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-primary:hover,html.theme--catppuccin-latte .docstring>section>a.button.docs-sourcelink:hover,html.theme--catppuccin-latte .button.is-primary.is-hovered,html.theme--catppuccin-latte .docstring>section>a.button.is-hovered.docs-sourcelink{background-color:#125ef4;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-primary:focus,html.theme--catppuccin-latte .docstring>section>a.button.docs-sourcelink:focus,html.theme--catppuccin-latte .button.is-primary.is-focused,html.theme--catppuccin-latte .docstring>section>a.button.is-focused.docs-sourcelink{border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-primary:focus:not(:active),html.theme--catppuccin-latte .docstring>section>a.button.docs-sourcelink:focus:not(:active),html.theme--catppuccin-latte .button.is-primary.is-focused:not(:active),html.theme--catppuccin-latte .docstring>section>a.button.is-focused.docs-sourcelink:not(:active){box-shadow:0 0 0 0.125em rgba(30,102,245,0.25)}html.theme--catppuccin-latte .button.is-primary:active,html.theme--catppuccin-latte .docstring>section>a.button.docs-sourcelink:active,html.theme--catppuccin-latte .button.is-primary.is-active,html.theme--catppuccin-latte .docstring>section>a.button.is-active.docs-sourcelink{background-color:#0b57ef;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-primary[disabled],html.theme--catppuccin-latte .docstring>section>a.button.docs-sourcelink[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-primary,fieldset[disabled] html.theme--catppuccin-latte .docstring>section>a.button.docs-sourcelink{background-color:#1e66f5;border-color:#1e66f5;box-shadow:none}html.theme--catppuccin-latte .button.is-primary.is-inverted,html.theme--catppuccin-latte .docstring>section>a.button.is-inverted.docs-sourcelink{background-color:#fff;color:#1e66f5}html.theme--catppuccin-latte .button.is-primary.is-inverted:hover,html.theme--catppuccin-latte .docstring>section>a.button.is-inverted.docs-sourcelink:hover,html.theme--catppuccin-latte .button.is-primary.is-inverted.is-hovered,html.theme--catppuccin-latte .docstring>section>a.button.is-inverted.is-hovered.docs-sourcelink{background-color:#f2f2f2}html.theme--catppuccin-latte .button.is-primary.is-inverted[disabled],html.theme--catppuccin-latte .docstring>section>a.button.is-inverted.docs-sourcelink[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-primary.is-inverted,fieldset[disabled] html.theme--catppuccin-latte .docstring>section>a.button.is-inverted.docs-sourcelink{background-color:#fff;border-color:transparent;box-shadow:none;color:#1e66f5}html.theme--catppuccin-latte .button.is-primary.is-loading::after,html.theme--catppuccin-latte .docstring>section>a.button.is-loading.docs-sourcelink::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-latte .button.is-primary.is-outlined,html.theme--catppuccin-latte .docstring>section>a.button.is-outlined.docs-sourcelink{background-color:transparent;border-color:#1e66f5;color:#1e66f5}html.theme--catppuccin-latte .button.is-primary.is-outlined:hover,html.theme--catppuccin-latte .docstring>section>a.button.is-outlined.docs-sourcelink:hover,html.theme--catppuccin-latte .button.is-primary.is-outlined.is-hovered,html.theme--catppuccin-latte .docstring>section>a.button.is-outlined.is-hovered.docs-sourcelink,html.theme--catppuccin-latte .button.is-primary.is-outlined:focus,html.theme--catppuccin-latte .docstring>section>a.button.is-outlined.docs-sourcelink:focus,html.theme--catppuccin-latte .button.is-primary.is-outlined.is-focused,html.theme--catppuccin-latte .docstring>section>a.button.is-outlined.is-focused.docs-sourcelink{background-color:#1e66f5;border-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .button.is-primary.is-outlined.is-loading::after,html.theme--catppuccin-latte .docstring>section>a.button.is-outlined.is-loading.docs-sourcelink::after{border-color:transparent transparent #1e66f5 #1e66f5 !important}html.theme--catppuccin-latte .button.is-primary.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .docstring>section>a.button.is-outlined.is-loading.docs-sourcelink:hover::after,html.theme--catppuccin-latte .button.is-primary.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .docstring>section>a.button.is-outlined.is-loading.is-hovered.docs-sourcelink::after,html.theme--catppuccin-latte .button.is-primary.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .docstring>section>a.button.is-outlined.is-loading.docs-sourcelink:focus::after,html.theme--catppuccin-latte .button.is-primary.is-outlined.is-loading.is-focused::after,html.theme--catppuccin-latte .docstring>section>a.button.is-outlined.is-loading.is-focused.docs-sourcelink::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-latte .button.is-primary.is-outlined[disabled],html.theme--catppuccin-latte .docstring>section>a.button.is-outlined.docs-sourcelink[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-primary.is-outlined,fieldset[disabled] html.theme--catppuccin-latte .docstring>section>a.button.is-outlined.docs-sourcelink{background-color:transparent;border-color:#1e66f5;box-shadow:none;color:#1e66f5}html.theme--catppuccin-latte .button.is-primary.is-inverted.is-outlined,html.theme--catppuccin-latte .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-latte .button.is-primary.is-inverted.is-outlined:hover,html.theme--catppuccin-latte .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink:hover,html.theme--catppuccin-latte .button.is-primary.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-latte .docstring>section>a.button.is-inverted.is-outlined.is-hovered.docs-sourcelink,html.theme--catppuccin-latte .button.is-primary.is-inverted.is-outlined:focus,html.theme--catppuccin-latte .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink:focus,html.theme--catppuccin-latte .button.is-primary.is-inverted.is-outlined.is-focused,html.theme--catppuccin-latte .docstring>section>a.button.is-inverted.is-outlined.is-focused.docs-sourcelink{background-color:#fff;color:#1e66f5}html.theme--catppuccin-latte .button.is-primary.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .docstring>section>a.button.is-inverted.is-outlined.is-loading.docs-sourcelink:hover::after,html.theme--catppuccin-latte .button.is-primary.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .docstring>section>a.button.is-inverted.is-outlined.is-loading.is-hovered.docs-sourcelink::after,html.theme--catppuccin-latte .button.is-primary.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .docstring>section>a.button.is-inverted.is-outlined.is-loading.docs-sourcelink:focus::after,html.theme--catppuccin-latte .button.is-primary.is-inverted.is-outlined.is-loading.is-focused::after,html.theme--catppuccin-latte .docstring>section>a.button.is-inverted.is-outlined.is-loading.is-focused.docs-sourcelink::after{border-color:transparent transparent #1e66f5 #1e66f5 !important}html.theme--catppuccin-latte .button.is-primary.is-inverted.is-outlined[disabled],html.theme--catppuccin-latte .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-primary.is-inverted.is-outlined,fieldset[disabled] html.theme--catppuccin-latte .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-latte .button.is-primary.is-light,html.theme--catppuccin-latte .docstring>section>a.button.is-light.docs-sourcelink{background-color:#ebf2fe;color:#0a52e1}html.theme--catppuccin-latte .button.is-primary.is-light:hover,html.theme--catppuccin-latte .docstring>section>a.button.is-light.docs-sourcelink:hover,html.theme--catppuccin-latte .button.is-primary.is-light.is-hovered,html.theme--catppuccin-latte .docstring>section>a.button.is-light.is-hovered.docs-sourcelink{background-color:#dfe9fe;border-color:transparent;color:#0a52e1}html.theme--catppuccin-latte .button.is-primary.is-light:active,html.theme--catppuccin-latte .docstring>section>a.button.is-light.docs-sourcelink:active,html.theme--catppuccin-latte .button.is-primary.is-light.is-active,html.theme--catppuccin-latte .docstring>section>a.button.is-light.is-active.docs-sourcelink{background-color:#d3e1fd;border-color:transparent;color:#0a52e1}html.theme--catppuccin-latte .button.is-link{background-color:#1e66f5;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-link:hover,html.theme--catppuccin-latte .button.is-link.is-hovered{background-color:#125ef4;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-link:focus,html.theme--catppuccin-latte .button.is-link.is-focused{border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-link:focus:not(:active),html.theme--catppuccin-latte .button.is-link.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(30,102,245,0.25)}html.theme--catppuccin-latte .button.is-link:active,html.theme--catppuccin-latte .button.is-link.is-active{background-color:#0b57ef;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-link[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-link{background-color:#1e66f5;border-color:#1e66f5;box-shadow:none}html.theme--catppuccin-latte .button.is-link.is-inverted{background-color:#fff;color:#1e66f5}html.theme--catppuccin-latte .button.is-link.is-inverted:hover,html.theme--catppuccin-latte .button.is-link.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--catppuccin-latte .button.is-link.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-link.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#1e66f5}html.theme--catppuccin-latte .button.is-link.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-latte .button.is-link.is-outlined{background-color:transparent;border-color:#1e66f5;color:#1e66f5}html.theme--catppuccin-latte .button.is-link.is-outlined:hover,html.theme--catppuccin-latte .button.is-link.is-outlined.is-hovered,html.theme--catppuccin-latte .button.is-link.is-outlined:focus,html.theme--catppuccin-latte .button.is-link.is-outlined.is-focused{background-color:#1e66f5;border-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .button.is-link.is-outlined.is-loading::after{border-color:transparent transparent #1e66f5 #1e66f5 !important}html.theme--catppuccin-latte .button.is-link.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .button.is-link.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .button.is-link.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .button.is-link.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-latte .button.is-link.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-link.is-outlined{background-color:transparent;border-color:#1e66f5;box-shadow:none;color:#1e66f5}html.theme--catppuccin-latte .button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-latte .button.is-link.is-inverted.is-outlined:hover,html.theme--catppuccin-latte .button.is-link.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-latte .button.is-link.is-inverted.is-outlined:focus,html.theme--catppuccin-latte .button.is-link.is-inverted.is-outlined.is-focused{background-color:#fff;color:#1e66f5}html.theme--catppuccin-latte .button.is-link.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .button.is-link.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .button.is-link.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .button.is-link.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #1e66f5 #1e66f5 !important}html.theme--catppuccin-latte .button.is-link.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-latte .button.is-link.is-light{background-color:#ebf2fe;color:#0a52e1}html.theme--catppuccin-latte .button.is-link.is-light:hover,html.theme--catppuccin-latte .button.is-link.is-light.is-hovered{background-color:#dfe9fe;border-color:transparent;color:#0a52e1}html.theme--catppuccin-latte .button.is-link.is-light:active,html.theme--catppuccin-latte .button.is-link.is-light.is-active{background-color:#d3e1fd;border-color:transparent;color:#0a52e1}html.theme--catppuccin-latte .button.is-info{background-color:#179299;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-info:hover,html.theme--catppuccin-latte .button.is-info.is-hovered{background-color:#15878e;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-info:focus,html.theme--catppuccin-latte .button.is-info.is-focused{border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-info:focus:not(:active),html.theme--catppuccin-latte .button.is-info.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(23,146,153,0.25)}html.theme--catppuccin-latte .button.is-info:active,html.theme--catppuccin-latte .button.is-info.is-active{background-color:#147d83;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-info[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-info{background-color:#179299;border-color:#179299;box-shadow:none}html.theme--catppuccin-latte .button.is-info.is-inverted{background-color:#fff;color:#179299}html.theme--catppuccin-latte .button.is-info.is-inverted:hover,html.theme--catppuccin-latte .button.is-info.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--catppuccin-latte .button.is-info.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-info.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#179299}html.theme--catppuccin-latte .button.is-info.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-latte .button.is-info.is-outlined{background-color:transparent;border-color:#179299;color:#179299}html.theme--catppuccin-latte .button.is-info.is-outlined:hover,html.theme--catppuccin-latte .button.is-info.is-outlined.is-hovered,html.theme--catppuccin-latte .button.is-info.is-outlined:focus,html.theme--catppuccin-latte .button.is-info.is-outlined.is-focused{background-color:#179299;border-color:#179299;color:#fff}html.theme--catppuccin-latte .button.is-info.is-outlined.is-loading::after{border-color:transparent transparent #179299 #179299 !important}html.theme--catppuccin-latte .button.is-info.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .button.is-info.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .button.is-info.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .button.is-info.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-latte .button.is-info.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-info.is-outlined{background-color:transparent;border-color:#179299;box-shadow:none;color:#179299}html.theme--catppuccin-latte .button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-latte .button.is-info.is-inverted.is-outlined:hover,html.theme--catppuccin-latte .button.is-info.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-latte .button.is-info.is-inverted.is-outlined:focus,html.theme--catppuccin-latte .button.is-info.is-inverted.is-outlined.is-focused{background-color:#fff;color:#179299}html.theme--catppuccin-latte .button.is-info.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .button.is-info.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .button.is-info.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .button.is-info.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #179299 #179299 !important}html.theme--catppuccin-latte .button.is-info.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-latte .button.is-info.is-light{background-color:#edfcfc;color:#1cb2ba}html.theme--catppuccin-latte .button.is-info.is-light:hover,html.theme--catppuccin-latte .button.is-info.is-light.is-hovered{background-color:#e2f9fb;border-color:transparent;color:#1cb2ba}html.theme--catppuccin-latte .button.is-info.is-light:active,html.theme--catppuccin-latte .button.is-info.is-light.is-active{background-color:#d7f7f9;border-color:transparent;color:#1cb2ba}html.theme--catppuccin-latte .button.is-success{background-color:#40a02b;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-success:hover,html.theme--catppuccin-latte .button.is-success.is-hovered{background-color:#3c9628;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-success:focus,html.theme--catppuccin-latte .button.is-success.is-focused{border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-success:focus:not(:active),html.theme--catppuccin-latte .button.is-success.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(64,160,43,0.25)}html.theme--catppuccin-latte .button.is-success:active,html.theme--catppuccin-latte .button.is-success.is-active{background-color:#388c26;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-success[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-success{background-color:#40a02b;border-color:#40a02b;box-shadow:none}html.theme--catppuccin-latte .button.is-success.is-inverted{background-color:#fff;color:#40a02b}html.theme--catppuccin-latte .button.is-success.is-inverted:hover,html.theme--catppuccin-latte .button.is-success.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--catppuccin-latte .button.is-success.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-success.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#40a02b}html.theme--catppuccin-latte .button.is-success.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-latte .button.is-success.is-outlined{background-color:transparent;border-color:#40a02b;color:#40a02b}html.theme--catppuccin-latte .button.is-success.is-outlined:hover,html.theme--catppuccin-latte .button.is-success.is-outlined.is-hovered,html.theme--catppuccin-latte .button.is-success.is-outlined:focus,html.theme--catppuccin-latte .button.is-success.is-outlined.is-focused{background-color:#40a02b;border-color:#40a02b;color:#fff}html.theme--catppuccin-latte .button.is-success.is-outlined.is-loading::after{border-color:transparent transparent #40a02b #40a02b !important}html.theme--catppuccin-latte .button.is-success.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .button.is-success.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .button.is-success.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .button.is-success.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-latte .button.is-success.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-success.is-outlined{background-color:transparent;border-color:#40a02b;box-shadow:none;color:#40a02b}html.theme--catppuccin-latte .button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-latte .button.is-success.is-inverted.is-outlined:hover,html.theme--catppuccin-latte .button.is-success.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-latte .button.is-success.is-inverted.is-outlined:focus,html.theme--catppuccin-latte .button.is-success.is-inverted.is-outlined.is-focused{background-color:#fff;color:#40a02b}html.theme--catppuccin-latte .button.is-success.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .button.is-success.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .button.is-success.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .button.is-success.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #40a02b #40a02b !important}html.theme--catppuccin-latte .button.is-success.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-latte .button.is-success.is-light{background-color:#f1fbef;color:#40a12b}html.theme--catppuccin-latte .button.is-success.is-light:hover,html.theme--catppuccin-latte .button.is-success.is-light.is-hovered{background-color:#e8f8e5;border-color:transparent;color:#40a12b}html.theme--catppuccin-latte .button.is-success.is-light:active,html.theme--catppuccin-latte .button.is-success.is-light.is-active{background-color:#e0f5db;border-color:transparent;color:#40a12b}html.theme--catppuccin-latte .button.is-warning{background-color:#df8e1d;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-warning:hover,html.theme--catppuccin-latte .button.is-warning.is-hovered{background-color:#d4871c;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-warning:focus,html.theme--catppuccin-latte .button.is-warning.is-focused{border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-warning:focus:not(:active),html.theme--catppuccin-latte .button.is-warning.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(223,142,29,0.25)}html.theme--catppuccin-latte .button.is-warning:active,html.theme--catppuccin-latte .button.is-warning.is-active{background-color:#c8801a;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-warning[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-warning{background-color:#df8e1d;border-color:#df8e1d;box-shadow:none}html.theme--catppuccin-latte .button.is-warning.is-inverted{background-color:#fff;color:#df8e1d}html.theme--catppuccin-latte .button.is-warning.is-inverted:hover,html.theme--catppuccin-latte .button.is-warning.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--catppuccin-latte .button.is-warning.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-warning.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#df8e1d}html.theme--catppuccin-latte .button.is-warning.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-latte .button.is-warning.is-outlined{background-color:transparent;border-color:#df8e1d;color:#df8e1d}html.theme--catppuccin-latte .button.is-warning.is-outlined:hover,html.theme--catppuccin-latte .button.is-warning.is-outlined.is-hovered,html.theme--catppuccin-latte .button.is-warning.is-outlined:focus,html.theme--catppuccin-latte .button.is-warning.is-outlined.is-focused{background-color:#df8e1d;border-color:#df8e1d;color:#fff}html.theme--catppuccin-latte .button.is-warning.is-outlined.is-loading::after{border-color:transparent transparent #df8e1d #df8e1d !important}html.theme--catppuccin-latte .button.is-warning.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .button.is-warning.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .button.is-warning.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .button.is-warning.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-latte .button.is-warning.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-warning.is-outlined{background-color:transparent;border-color:#df8e1d;box-shadow:none;color:#df8e1d}html.theme--catppuccin-latte .button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-latte .button.is-warning.is-inverted.is-outlined:hover,html.theme--catppuccin-latte .button.is-warning.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-latte .button.is-warning.is-inverted.is-outlined:focus,html.theme--catppuccin-latte .button.is-warning.is-inverted.is-outlined.is-focused{background-color:#fff;color:#df8e1d}html.theme--catppuccin-latte .button.is-warning.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .button.is-warning.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .button.is-warning.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .button.is-warning.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #df8e1d #df8e1d !important}html.theme--catppuccin-latte .button.is-warning.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-latte .button.is-warning.is-light{background-color:#fdf6ed;color:#9e6515}html.theme--catppuccin-latte .button.is-warning.is-light:hover,html.theme--catppuccin-latte .button.is-warning.is-light.is-hovered{background-color:#fbf1e2;border-color:transparent;color:#9e6515}html.theme--catppuccin-latte .button.is-warning.is-light:active,html.theme--catppuccin-latte .button.is-warning.is-light.is-active{background-color:#faebd6;border-color:transparent;color:#9e6515}html.theme--catppuccin-latte .button.is-danger{background-color:#d20f39;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-danger:hover,html.theme--catppuccin-latte .button.is-danger.is-hovered{background-color:#c60e36;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-danger:focus,html.theme--catppuccin-latte .button.is-danger.is-focused{border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-danger:focus:not(:active),html.theme--catppuccin-latte .button.is-danger.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(210,15,57,0.25)}html.theme--catppuccin-latte .button.is-danger:active,html.theme--catppuccin-latte .button.is-danger.is-active{background-color:#ba0d33;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-danger[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-danger{background-color:#d20f39;border-color:#d20f39;box-shadow:none}html.theme--catppuccin-latte .button.is-danger.is-inverted{background-color:#fff;color:#d20f39}html.theme--catppuccin-latte .button.is-danger.is-inverted:hover,html.theme--catppuccin-latte .button.is-danger.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--catppuccin-latte .button.is-danger.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-danger.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#d20f39}html.theme--catppuccin-latte .button.is-danger.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-latte .button.is-danger.is-outlined{background-color:transparent;border-color:#d20f39;color:#d20f39}html.theme--catppuccin-latte .button.is-danger.is-outlined:hover,html.theme--catppuccin-latte .button.is-danger.is-outlined.is-hovered,html.theme--catppuccin-latte .button.is-danger.is-outlined:focus,html.theme--catppuccin-latte .button.is-danger.is-outlined.is-focused{background-color:#d20f39;border-color:#d20f39;color:#fff}html.theme--catppuccin-latte .button.is-danger.is-outlined.is-loading::after{border-color:transparent transparent #d20f39 #d20f39 !important}html.theme--catppuccin-latte .button.is-danger.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .button.is-danger.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .button.is-danger.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .button.is-danger.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-latte .button.is-danger.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-danger.is-outlined{background-color:transparent;border-color:#d20f39;box-shadow:none;color:#d20f39}html.theme--catppuccin-latte .button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-latte .button.is-danger.is-inverted.is-outlined:hover,html.theme--catppuccin-latte .button.is-danger.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-latte .button.is-danger.is-inverted.is-outlined:focus,html.theme--catppuccin-latte .button.is-danger.is-inverted.is-outlined.is-focused{background-color:#fff;color:#d20f39}html.theme--catppuccin-latte .button.is-danger.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .button.is-danger.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .button.is-danger.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .button.is-danger.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #d20f39 #d20f39 !important}html.theme--catppuccin-latte .button.is-danger.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-latte .button.is-danger.is-light{background-color:#feecf0;color:#e9113f}html.theme--catppuccin-latte .button.is-danger.is-light:hover,html.theme--catppuccin-latte .button.is-danger.is-light.is-hovered{background-color:#fde0e6;border-color:transparent;color:#e9113f}html.theme--catppuccin-latte .button.is-danger.is-light:active,html.theme--catppuccin-latte .button.is-danger.is-light.is-active{background-color:#fcd4dd;border-color:transparent;color:#e9113f}html.theme--catppuccin-latte .button.is-small,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.button{font-size:.75rem}html.theme--catppuccin-latte .button.is-small:not(.is-rounded),html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.button:not(.is-rounded){border-radius:3px}html.theme--catppuccin-latte .button.is-normal{font-size:1rem}html.theme--catppuccin-latte .button.is-medium{font-size:1.25rem}html.theme--catppuccin-latte .button.is-large{font-size:1.5rem}html.theme--catppuccin-latte .button[disabled],fieldset[disabled] html.theme--catppuccin-latte .button{background-color:#9ca0b0;border-color:#acb0be;box-shadow:none;opacity:.5}html.theme--catppuccin-latte .button.is-fullwidth{display:flex;width:100%}html.theme--catppuccin-latte .button.is-loading{color:transparent !important;pointer-events:none}html.theme--catppuccin-latte .button.is-loading::after{position:absolute;left:calc(50% - (1em * 0.5));top:calc(50% - (1em * 0.5));position:absolute !important}html.theme--catppuccin-latte .button.is-static{background-color:#e6e9ef;border-color:#acb0be;color:#8c8fa1;box-shadow:none;pointer-events:none}html.theme--catppuccin-latte .button.is-rounded,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.button{border-radius:9999px;padding-left:calc(1em + 0.25em);padding-right:calc(1em + 0.25em)}html.theme--catppuccin-latte .buttons{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}html.theme--catppuccin-latte .buttons .button{margin-bottom:0.5rem}html.theme--catppuccin-latte .buttons .button:not(:last-child):not(.is-fullwidth){margin-right:.5rem}html.theme--catppuccin-latte .buttons:last-child{margin-bottom:-0.5rem}html.theme--catppuccin-latte .buttons:not(:last-child){margin-bottom:1rem}html.theme--catppuccin-latte .buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large){font-size:.75rem}html.theme--catppuccin-latte .buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large):not(.is-rounded){border-radius:3px}html.theme--catppuccin-latte .buttons.are-medium .button:not(.is-small):not(.is-normal):not(.is-large){font-size:1.25rem}html.theme--catppuccin-latte .buttons.are-large .button:not(.is-small):not(.is-normal):not(.is-medium){font-size:1.5rem}html.theme--catppuccin-latte .buttons.has-addons .button:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}html.theme--catppuccin-latte .buttons.has-addons .button:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0;margin-right:-1px}html.theme--catppuccin-latte .buttons.has-addons .button:last-child{margin-right:0}html.theme--catppuccin-latte .buttons.has-addons .button:hover,html.theme--catppuccin-latte .buttons.has-addons .button.is-hovered{z-index:2}html.theme--catppuccin-latte .buttons.has-addons .button:focus,html.theme--catppuccin-latte .buttons.has-addons .button.is-focused,html.theme--catppuccin-latte .buttons.has-addons .button:active,html.theme--catppuccin-latte .buttons.has-addons .button.is-active,html.theme--catppuccin-latte .buttons.has-addons .button.is-selected{z-index:3}html.theme--catppuccin-latte .buttons.has-addons .button:focus:hover,html.theme--catppuccin-latte .buttons.has-addons .button.is-focused:hover,html.theme--catppuccin-latte .buttons.has-addons .button:active:hover,html.theme--catppuccin-latte .buttons.has-addons .button.is-active:hover,html.theme--catppuccin-latte .buttons.has-addons .button.is-selected:hover{z-index:4}html.theme--catppuccin-latte .buttons.has-addons .button.is-expanded{flex-grow:1;flex-shrink:1}html.theme--catppuccin-latte .buttons.is-centered{justify-content:center}html.theme--catppuccin-latte .buttons.is-centered:not(.has-addons) .button:not(.is-fullwidth){margin-left:0.25rem;margin-right:0.25rem}html.theme--catppuccin-latte .buttons.is-right{justify-content:flex-end}html.theme--catppuccin-latte .buttons.is-right:not(.has-addons) .button:not(.is-fullwidth){margin-left:0.25rem;margin-right:0.25rem}@media screen and (max-width: 768px){html.theme--catppuccin-latte .button.is-responsive.is-small,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-responsive{font-size:.5625rem}html.theme--catppuccin-latte .button.is-responsive,html.theme--catppuccin-latte .button.is-responsive.is-normal{font-size:.65625rem}html.theme--catppuccin-latte .button.is-responsive.is-medium{font-size:.75rem}html.theme--catppuccin-latte .button.is-responsive.is-large{font-size:1rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-latte .button.is-responsive.is-small,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-responsive{font-size:.65625rem}html.theme--catppuccin-latte .button.is-responsive,html.theme--catppuccin-latte .button.is-responsive.is-normal{font-size:.75rem}html.theme--catppuccin-latte .button.is-responsive.is-medium{font-size:1rem}html.theme--catppuccin-latte .button.is-responsive.is-large{font-size:1.25rem}}html.theme--catppuccin-latte .container{flex-grow:1;margin:0 auto;position:relative;width:auto}html.theme--catppuccin-latte .container.is-fluid{max-width:none !important;padding-left:32px;padding-right:32px;width:100%}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .container{max-width:992px}}@media screen and (max-width: 1215px){html.theme--catppuccin-latte .container.is-widescreen:not(.is-max-desktop){max-width:1152px}}@media screen and (max-width: 1407px){html.theme--catppuccin-latte .container.is-fullhd:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}@media screen and (min-width: 1216px){html.theme--catppuccin-latte .container:not(.is-max-desktop){max-width:1152px}}@media screen and (min-width: 1408px){html.theme--catppuccin-latte .container:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}html.theme--catppuccin-latte .content li+li{margin-top:0.25em}html.theme--catppuccin-latte .content p:not(:last-child),html.theme--catppuccin-latte .content dl:not(:last-child),html.theme--catppuccin-latte .content ol:not(:last-child),html.theme--catppuccin-latte .content ul:not(:last-child),html.theme--catppuccin-latte .content blockquote:not(:last-child),html.theme--catppuccin-latte .content pre:not(:last-child),html.theme--catppuccin-latte .content table:not(:last-child){margin-bottom:1em}html.theme--catppuccin-latte .content h1,html.theme--catppuccin-latte .content h2,html.theme--catppuccin-latte .content h3,html.theme--catppuccin-latte .content h4,html.theme--catppuccin-latte .content h5,html.theme--catppuccin-latte .content h6{color:#4c4f69;font-weight:600;line-height:1.125}html.theme--catppuccin-latte .content h1{font-size:2em;margin-bottom:0.5em}html.theme--catppuccin-latte .content h1:not(:first-child){margin-top:1em}html.theme--catppuccin-latte .content h2{font-size:1.75em;margin-bottom:0.5714em}html.theme--catppuccin-latte .content h2:not(:first-child){margin-top:1.1428em}html.theme--catppuccin-latte .content h3{font-size:1.5em;margin-bottom:0.6666em}html.theme--catppuccin-latte .content h3:not(:first-child){margin-top:1.3333em}html.theme--catppuccin-latte .content h4{font-size:1.25em;margin-bottom:0.8em}html.theme--catppuccin-latte .content h5{font-size:1.125em;margin-bottom:0.8888em}html.theme--catppuccin-latte .content h6{font-size:1em;margin-bottom:1em}html.theme--catppuccin-latte .content blockquote{background-color:#e6e9ef;border-left:5px solid #acb0be;padding:1.25em 1.5em}html.theme--catppuccin-latte .content ol{list-style-position:outside;margin-left:2em;margin-top:1em}html.theme--catppuccin-latte .content ol:not([type]){list-style-type:decimal}html.theme--catppuccin-latte .content ol.is-lower-alpha:not([type]){list-style-type:lower-alpha}html.theme--catppuccin-latte .content ol.is-lower-roman:not([type]){list-style-type:lower-roman}html.theme--catppuccin-latte .content ol.is-upper-alpha:not([type]){list-style-type:upper-alpha}html.theme--catppuccin-latte .content ol.is-upper-roman:not([type]){list-style-type:upper-roman}html.theme--catppuccin-latte .content ul{list-style:disc outside;margin-left:2em;margin-top:1em}html.theme--catppuccin-latte .content ul ul{list-style-type:circle;margin-top:0.5em}html.theme--catppuccin-latte .content ul ul ul{list-style-type:square}html.theme--catppuccin-latte .content dd{margin-left:2em}html.theme--catppuccin-latte .content figure{margin-left:2em;margin-right:2em;text-align:center}html.theme--catppuccin-latte .content figure:not(:first-child){margin-top:2em}html.theme--catppuccin-latte .content figure:not(:last-child){margin-bottom:2em}html.theme--catppuccin-latte .content figure img{display:inline-block}html.theme--catppuccin-latte .content figure figcaption{font-style:italic}html.theme--catppuccin-latte .content pre{-webkit-overflow-scrolling:touch;overflow-x:auto;padding:0;white-space:pre;word-wrap:normal}html.theme--catppuccin-latte .content sup,html.theme--catppuccin-latte .content sub{font-size:75%}html.theme--catppuccin-latte .content table{width:100%}html.theme--catppuccin-latte .content table td,html.theme--catppuccin-latte .content table th{border:1px solid #acb0be;border-width:0 0 1px;padding:0.5em 0.75em;vertical-align:top}html.theme--catppuccin-latte .content table th{color:#41445a}html.theme--catppuccin-latte .content table th:not([align]){text-align:inherit}html.theme--catppuccin-latte .content table thead td,html.theme--catppuccin-latte .content table thead th{border-width:0 0 2px;color:#41445a}html.theme--catppuccin-latte .content table tfoot td,html.theme--catppuccin-latte .content table tfoot th{border-width:2px 0 0;color:#41445a}html.theme--catppuccin-latte .content table tbody tr:last-child td,html.theme--catppuccin-latte .content table tbody tr:last-child th{border-bottom-width:0}html.theme--catppuccin-latte .content .tabs li+li{margin-top:0}html.theme--catppuccin-latte .content.is-small,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.content{font-size:.75rem}html.theme--catppuccin-latte .content.is-normal{font-size:1rem}html.theme--catppuccin-latte .content.is-medium{font-size:1.25rem}html.theme--catppuccin-latte .content.is-large{font-size:1.5rem}html.theme--catppuccin-latte .icon{align-items:center;display:inline-flex;justify-content:center;height:1.5rem;width:1.5rem}html.theme--catppuccin-latte .icon.is-small,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.icon{height:1rem;width:1rem}html.theme--catppuccin-latte .icon.is-medium{height:2rem;width:2rem}html.theme--catppuccin-latte .icon.is-large{height:3rem;width:3rem}html.theme--catppuccin-latte .icon-text{align-items:flex-start;color:inherit;display:inline-flex;flex-wrap:wrap;line-height:1.5rem;vertical-align:top}html.theme--catppuccin-latte .icon-text .icon{flex-grow:0;flex-shrink:0}html.theme--catppuccin-latte .icon-text .icon:not(:last-child){margin-right:.25em}html.theme--catppuccin-latte .icon-text .icon:not(:first-child){margin-left:.25em}html.theme--catppuccin-latte div.icon-text{display:flex}html.theme--catppuccin-latte .image,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img{display:block;position:relative}html.theme--catppuccin-latte .image img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img img{display:block;height:auto;width:100%}html.theme--catppuccin-latte .image img.is-rounded,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img img.is-rounded{border-radius:9999px}html.theme--catppuccin-latte .image.is-fullwidth,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-fullwidth{width:100%}html.theme--catppuccin-latte .image.is-square img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-square img,html.theme--catppuccin-latte .image.is-square .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-square .has-ratio,html.theme--catppuccin-latte .image.is-1by1 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-1by1 img,html.theme--catppuccin-latte .image.is-1by1 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-1by1 .has-ratio,html.theme--catppuccin-latte .image.is-5by4 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-5by4 img,html.theme--catppuccin-latte .image.is-5by4 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-5by4 .has-ratio,html.theme--catppuccin-latte .image.is-4by3 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-4by3 img,html.theme--catppuccin-latte .image.is-4by3 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-4by3 .has-ratio,html.theme--catppuccin-latte .image.is-3by2 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by2 img,html.theme--catppuccin-latte .image.is-3by2 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by2 .has-ratio,html.theme--catppuccin-latte .image.is-5by3 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-5by3 img,html.theme--catppuccin-latte .image.is-5by3 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-5by3 .has-ratio,html.theme--catppuccin-latte .image.is-16by9 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-16by9 img,html.theme--catppuccin-latte .image.is-16by9 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-16by9 .has-ratio,html.theme--catppuccin-latte .image.is-2by1 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-2by1 img,html.theme--catppuccin-latte .image.is-2by1 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-2by1 .has-ratio,html.theme--catppuccin-latte .image.is-3by1 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by1 img,html.theme--catppuccin-latte .image.is-3by1 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by1 .has-ratio,html.theme--catppuccin-latte .image.is-4by5 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-4by5 img,html.theme--catppuccin-latte .image.is-4by5 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-4by5 .has-ratio,html.theme--catppuccin-latte .image.is-3by4 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by4 img,html.theme--catppuccin-latte .image.is-3by4 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by4 .has-ratio,html.theme--catppuccin-latte .image.is-2by3 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-2by3 img,html.theme--catppuccin-latte .image.is-2by3 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-2by3 .has-ratio,html.theme--catppuccin-latte .image.is-3by5 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by5 img,html.theme--catppuccin-latte .image.is-3by5 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by5 .has-ratio,html.theme--catppuccin-latte .image.is-9by16 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-9by16 img,html.theme--catppuccin-latte .image.is-9by16 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-9by16 .has-ratio,html.theme--catppuccin-latte .image.is-1by2 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-1by2 img,html.theme--catppuccin-latte .image.is-1by2 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-1by2 .has-ratio,html.theme--catppuccin-latte .image.is-1by3 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-1by3 img,html.theme--catppuccin-latte .image.is-1by3 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-1by3 .has-ratio{height:100%;width:100%}html.theme--catppuccin-latte .image.is-square,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-square,html.theme--catppuccin-latte .image.is-1by1,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-1by1{padding-top:100%}html.theme--catppuccin-latte .image.is-5by4,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-5by4{padding-top:80%}html.theme--catppuccin-latte .image.is-4by3,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-4by3{padding-top:75%}html.theme--catppuccin-latte .image.is-3by2,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by2{padding-top:66.6666%}html.theme--catppuccin-latte .image.is-5by3,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-5by3{padding-top:60%}html.theme--catppuccin-latte .image.is-16by9,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-16by9{padding-top:56.25%}html.theme--catppuccin-latte .image.is-2by1,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-2by1{padding-top:50%}html.theme--catppuccin-latte .image.is-3by1,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by1{padding-top:33.3333%}html.theme--catppuccin-latte .image.is-4by5,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-4by5{padding-top:125%}html.theme--catppuccin-latte .image.is-3by4,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by4{padding-top:133.3333%}html.theme--catppuccin-latte .image.is-2by3,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-2by3{padding-top:150%}html.theme--catppuccin-latte .image.is-3by5,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by5{padding-top:166.6666%}html.theme--catppuccin-latte .image.is-9by16,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-9by16{padding-top:177.7777%}html.theme--catppuccin-latte .image.is-1by2,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-1by2{padding-top:200%}html.theme--catppuccin-latte .image.is-1by3,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-1by3{padding-top:300%}html.theme--catppuccin-latte .image.is-16x16,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-16x16{height:16px;width:16px}html.theme--catppuccin-latte .image.is-24x24,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-24x24{height:24px;width:24px}html.theme--catppuccin-latte .image.is-32x32,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-32x32{height:32px;width:32px}html.theme--catppuccin-latte .image.is-48x48,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-48x48{height:48px;width:48px}html.theme--catppuccin-latte .image.is-64x64,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-64x64{height:64px;width:64px}html.theme--catppuccin-latte .image.is-96x96,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-96x96{height:96px;width:96px}html.theme--catppuccin-latte .image.is-128x128,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-128x128{height:128px;width:128px}html.theme--catppuccin-latte .notification{background-color:#e6e9ef;border-radius:.4em;position:relative;padding:1.25rem 2.5rem 1.25rem 1.5rem}html.theme--catppuccin-latte .notification a:not(.button):not(.dropdown-item){color:currentColor;text-decoration:underline}html.theme--catppuccin-latte .notification strong{color:currentColor}html.theme--catppuccin-latte .notification code,html.theme--catppuccin-latte .notification pre{background:#fff}html.theme--catppuccin-latte .notification pre code{background:transparent}html.theme--catppuccin-latte .notification>.delete{right:.5rem;position:absolute;top:0.5rem}html.theme--catppuccin-latte .notification .title,html.theme--catppuccin-latte .notification .subtitle,html.theme--catppuccin-latte .notification .content{color:currentColor}html.theme--catppuccin-latte .notification.is-white{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-latte .notification.is-black{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-latte .notification.is-light{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .notification.is-dark,html.theme--catppuccin-latte .content kbd.notification{background-color:#ccd0da;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .notification.is-primary,html.theme--catppuccin-latte .docstring>section>a.notification.docs-sourcelink{background-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .notification.is-primary.is-light,html.theme--catppuccin-latte .docstring>section>a.notification.is-light.docs-sourcelink{background-color:#ebf2fe;color:#0a52e1}html.theme--catppuccin-latte .notification.is-link{background-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .notification.is-link.is-light{background-color:#ebf2fe;color:#0a52e1}html.theme--catppuccin-latte .notification.is-info{background-color:#179299;color:#fff}html.theme--catppuccin-latte .notification.is-info.is-light{background-color:#edfcfc;color:#1cb2ba}html.theme--catppuccin-latte .notification.is-success{background-color:#40a02b;color:#fff}html.theme--catppuccin-latte .notification.is-success.is-light{background-color:#f1fbef;color:#40a12b}html.theme--catppuccin-latte .notification.is-warning{background-color:#df8e1d;color:#fff}html.theme--catppuccin-latte .notification.is-warning.is-light{background-color:#fdf6ed;color:#9e6515}html.theme--catppuccin-latte .notification.is-danger{background-color:#d20f39;color:#fff}html.theme--catppuccin-latte .notification.is-danger.is-light{background-color:#feecf0;color:#e9113f}html.theme--catppuccin-latte .progress{-moz-appearance:none;-webkit-appearance:none;border:none;border-radius:9999px;display:block;height:1rem;overflow:hidden;padding:0;width:100%}html.theme--catppuccin-latte .progress::-webkit-progress-bar{background-color:#bcc0cc}html.theme--catppuccin-latte .progress::-webkit-progress-value{background-color:#8c8fa1}html.theme--catppuccin-latte .progress::-moz-progress-bar{background-color:#8c8fa1}html.theme--catppuccin-latte .progress::-ms-fill{background-color:#8c8fa1;border:none}html.theme--catppuccin-latte .progress.is-white::-webkit-progress-value{background-color:#fff}html.theme--catppuccin-latte .progress.is-white::-moz-progress-bar{background-color:#fff}html.theme--catppuccin-latte .progress.is-white::-ms-fill{background-color:#fff}html.theme--catppuccin-latte .progress.is-white:indeterminate{background-image:linear-gradient(to right, #fff 30%, #bcc0cc 30%)}html.theme--catppuccin-latte .progress.is-black::-webkit-progress-value{background-color:#0a0a0a}html.theme--catppuccin-latte .progress.is-black::-moz-progress-bar{background-color:#0a0a0a}html.theme--catppuccin-latte .progress.is-black::-ms-fill{background-color:#0a0a0a}html.theme--catppuccin-latte .progress.is-black:indeterminate{background-image:linear-gradient(to right, #0a0a0a 30%, #bcc0cc 30%)}html.theme--catppuccin-latte .progress.is-light::-webkit-progress-value{background-color:#f5f5f5}html.theme--catppuccin-latte .progress.is-light::-moz-progress-bar{background-color:#f5f5f5}html.theme--catppuccin-latte .progress.is-light::-ms-fill{background-color:#f5f5f5}html.theme--catppuccin-latte .progress.is-light:indeterminate{background-image:linear-gradient(to right, #f5f5f5 30%, #bcc0cc 30%)}html.theme--catppuccin-latte .progress.is-dark::-webkit-progress-value,html.theme--catppuccin-latte .content kbd.progress::-webkit-progress-value{background-color:#ccd0da}html.theme--catppuccin-latte .progress.is-dark::-moz-progress-bar,html.theme--catppuccin-latte .content kbd.progress::-moz-progress-bar{background-color:#ccd0da}html.theme--catppuccin-latte .progress.is-dark::-ms-fill,html.theme--catppuccin-latte .content kbd.progress::-ms-fill{background-color:#ccd0da}html.theme--catppuccin-latte .progress.is-dark:indeterminate,html.theme--catppuccin-latte .content kbd.progress:indeterminate{background-image:linear-gradient(to right, #ccd0da 30%, #bcc0cc 30%)}html.theme--catppuccin-latte .progress.is-primary::-webkit-progress-value,html.theme--catppuccin-latte .docstring>section>a.progress.docs-sourcelink::-webkit-progress-value{background-color:#1e66f5}html.theme--catppuccin-latte .progress.is-primary::-moz-progress-bar,html.theme--catppuccin-latte .docstring>section>a.progress.docs-sourcelink::-moz-progress-bar{background-color:#1e66f5}html.theme--catppuccin-latte .progress.is-primary::-ms-fill,html.theme--catppuccin-latte .docstring>section>a.progress.docs-sourcelink::-ms-fill{background-color:#1e66f5}html.theme--catppuccin-latte .progress.is-primary:indeterminate,html.theme--catppuccin-latte .docstring>section>a.progress.docs-sourcelink:indeterminate{background-image:linear-gradient(to right, #1e66f5 30%, #bcc0cc 30%)}html.theme--catppuccin-latte .progress.is-link::-webkit-progress-value{background-color:#1e66f5}html.theme--catppuccin-latte .progress.is-link::-moz-progress-bar{background-color:#1e66f5}html.theme--catppuccin-latte .progress.is-link::-ms-fill{background-color:#1e66f5}html.theme--catppuccin-latte .progress.is-link:indeterminate{background-image:linear-gradient(to right, #1e66f5 30%, #bcc0cc 30%)}html.theme--catppuccin-latte .progress.is-info::-webkit-progress-value{background-color:#179299}html.theme--catppuccin-latte .progress.is-info::-moz-progress-bar{background-color:#179299}html.theme--catppuccin-latte .progress.is-info::-ms-fill{background-color:#179299}html.theme--catppuccin-latte .progress.is-info:indeterminate{background-image:linear-gradient(to right, #179299 30%, #bcc0cc 30%)}html.theme--catppuccin-latte .progress.is-success::-webkit-progress-value{background-color:#40a02b}html.theme--catppuccin-latte .progress.is-success::-moz-progress-bar{background-color:#40a02b}html.theme--catppuccin-latte .progress.is-success::-ms-fill{background-color:#40a02b}html.theme--catppuccin-latte .progress.is-success:indeterminate{background-image:linear-gradient(to right, #40a02b 30%, #bcc0cc 30%)}html.theme--catppuccin-latte .progress.is-warning::-webkit-progress-value{background-color:#df8e1d}html.theme--catppuccin-latte .progress.is-warning::-moz-progress-bar{background-color:#df8e1d}html.theme--catppuccin-latte .progress.is-warning::-ms-fill{background-color:#df8e1d}html.theme--catppuccin-latte .progress.is-warning:indeterminate{background-image:linear-gradient(to right, #df8e1d 30%, #bcc0cc 30%)}html.theme--catppuccin-latte .progress.is-danger::-webkit-progress-value{background-color:#d20f39}html.theme--catppuccin-latte .progress.is-danger::-moz-progress-bar{background-color:#d20f39}html.theme--catppuccin-latte .progress.is-danger::-ms-fill{background-color:#d20f39}html.theme--catppuccin-latte .progress.is-danger:indeterminate{background-image:linear-gradient(to right, #d20f39 30%, #bcc0cc 30%)}html.theme--catppuccin-latte .progress:indeterminate{animation-duration:1.5s;animation-iteration-count:infinite;animation-name:moveIndeterminate;animation-timing-function:linear;background-color:#bcc0cc;background-image:linear-gradient(to right, #4c4f69 30%, #bcc0cc 30%);background-position:top left;background-repeat:no-repeat;background-size:150% 150%}html.theme--catppuccin-latte .progress:indeterminate::-webkit-progress-bar{background-color:transparent}html.theme--catppuccin-latte .progress:indeterminate::-moz-progress-bar{background-color:transparent}html.theme--catppuccin-latte .progress:indeterminate::-ms-fill{animation-name:none}html.theme--catppuccin-latte .progress.is-small,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.progress{height:.75rem}html.theme--catppuccin-latte .progress.is-medium{height:1.25rem}html.theme--catppuccin-latte .progress.is-large{height:1.5rem}@keyframes moveIndeterminate{from{background-position:200% 0}to{background-position:-200% 0}}html.theme--catppuccin-latte .table{background-color:#bcc0cc;color:#4c4f69}html.theme--catppuccin-latte .table td,html.theme--catppuccin-latte .table th{border:1px solid #acb0be;border-width:0 0 1px;padding:0.5em 0.75em;vertical-align:top}html.theme--catppuccin-latte .table td.is-white,html.theme--catppuccin-latte .table th.is-white{background-color:#fff;border-color:#fff;color:#0a0a0a}html.theme--catppuccin-latte .table td.is-black,html.theme--catppuccin-latte .table th.is-black{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}html.theme--catppuccin-latte .table td.is-light,html.theme--catppuccin-latte .table th.is-light{background-color:#f5f5f5;border-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .table td.is-dark,html.theme--catppuccin-latte .table th.is-dark{background-color:#ccd0da;border-color:#ccd0da;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .table td.is-primary,html.theme--catppuccin-latte .table th.is-primary{background-color:#1e66f5;border-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .table td.is-link,html.theme--catppuccin-latte .table th.is-link{background-color:#1e66f5;border-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .table td.is-info,html.theme--catppuccin-latte .table th.is-info{background-color:#179299;border-color:#179299;color:#fff}html.theme--catppuccin-latte .table td.is-success,html.theme--catppuccin-latte .table th.is-success{background-color:#40a02b;border-color:#40a02b;color:#fff}html.theme--catppuccin-latte .table td.is-warning,html.theme--catppuccin-latte .table th.is-warning{background-color:#df8e1d;border-color:#df8e1d;color:#fff}html.theme--catppuccin-latte .table td.is-danger,html.theme--catppuccin-latte .table th.is-danger{background-color:#d20f39;border-color:#d20f39;color:#fff}html.theme--catppuccin-latte .table td.is-narrow,html.theme--catppuccin-latte .table th.is-narrow{white-space:nowrap;width:1%}html.theme--catppuccin-latte .table td.is-selected,html.theme--catppuccin-latte .table th.is-selected{background-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .table td.is-selected a,html.theme--catppuccin-latte .table td.is-selected strong,html.theme--catppuccin-latte .table th.is-selected a,html.theme--catppuccin-latte .table th.is-selected strong{color:currentColor}html.theme--catppuccin-latte .table td.is-vcentered,html.theme--catppuccin-latte .table th.is-vcentered{vertical-align:middle}html.theme--catppuccin-latte .table th{color:#41445a}html.theme--catppuccin-latte .table th:not([align]){text-align:left}html.theme--catppuccin-latte .table tr.is-selected{background-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .table tr.is-selected a,html.theme--catppuccin-latte .table tr.is-selected strong{color:currentColor}html.theme--catppuccin-latte .table tr.is-selected td,html.theme--catppuccin-latte .table tr.is-selected th{border-color:#fff;color:currentColor}html.theme--catppuccin-latte .table thead{background-color:rgba(0,0,0,0)}html.theme--catppuccin-latte .table thead td,html.theme--catppuccin-latte .table thead th{border-width:0 0 2px;color:#41445a}html.theme--catppuccin-latte .table tfoot{background-color:rgba(0,0,0,0)}html.theme--catppuccin-latte .table tfoot td,html.theme--catppuccin-latte .table tfoot th{border-width:2px 0 0;color:#41445a}html.theme--catppuccin-latte .table tbody{background-color:rgba(0,0,0,0)}html.theme--catppuccin-latte .table tbody tr:last-child td,html.theme--catppuccin-latte .table tbody tr:last-child th{border-bottom-width:0}html.theme--catppuccin-latte .table.is-bordered td,html.theme--catppuccin-latte .table.is-bordered th{border-width:1px}html.theme--catppuccin-latte .table.is-bordered tr:last-child td,html.theme--catppuccin-latte .table.is-bordered tr:last-child th{border-bottom-width:1px}html.theme--catppuccin-latte .table.is-fullwidth{width:100%}html.theme--catppuccin-latte .table.is-hoverable tbody tr:not(.is-selected):hover{background-color:#ccd0da}html.theme--catppuccin-latte .table.is-hoverable.is-striped tbody tr:not(.is-selected):hover{background-color:#ccd0da}html.theme--catppuccin-latte .table.is-hoverable.is-striped tbody tr:not(.is-selected):hover:nth-child(even){background-color:#d2d5de}html.theme--catppuccin-latte .table.is-narrow td,html.theme--catppuccin-latte .table.is-narrow th{padding:0.25em 0.5em}html.theme--catppuccin-latte .table.is-striped tbody tr:not(.is-selected):nth-child(even){background-color:#ccd0da}html.theme--catppuccin-latte .table-container{-webkit-overflow-scrolling:touch;overflow:auto;overflow-y:hidden;max-width:100%}html.theme--catppuccin-latte .tags{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}html.theme--catppuccin-latte .tags .tag,html.theme--catppuccin-latte .tags .content kbd,html.theme--catppuccin-latte .content .tags kbd,html.theme--catppuccin-latte .tags .docstring>section>a.docs-sourcelink{margin-bottom:0.5rem}html.theme--catppuccin-latte .tags .tag:not(:last-child),html.theme--catppuccin-latte .tags .content kbd:not(:last-child),html.theme--catppuccin-latte .content .tags kbd:not(:last-child),html.theme--catppuccin-latte .tags .docstring>section>a.docs-sourcelink:not(:last-child){margin-right:.5rem}html.theme--catppuccin-latte .tags:last-child{margin-bottom:-0.5rem}html.theme--catppuccin-latte .tags:not(:last-child){margin-bottom:1rem}html.theme--catppuccin-latte .tags.are-medium .tag:not(.is-normal):not(.is-large),html.theme--catppuccin-latte .tags.are-medium .content kbd:not(.is-normal):not(.is-large),html.theme--catppuccin-latte .content .tags.are-medium kbd:not(.is-normal):not(.is-large),html.theme--catppuccin-latte .tags.are-medium .docstring>section>a.docs-sourcelink:not(.is-normal):not(.is-large){font-size:1rem}html.theme--catppuccin-latte .tags.are-large .tag:not(.is-normal):not(.is-medium),html.theme--catppuccin-latte .tags.are-large .content kbd:not(.is-normal):not(.is-medium),html.theme--catppuccin-latte .content .tags.are-large kbd:not(.is-normal):not(.is-medium),html.theme--catppuccin-latte .tags.are-large .docstring>section>a.docs-sourcelink:not(.is-normal):not(.is-medium){font-size:1.25rem}html.theme--catppuccin-latte .tags.is-centered{justify-content:center}html.theme--catppuccin-latte .tags.is-centered .tag,html.theme--catppuccin-latte .tags.is-centered .content kbd,html.theme--catppuccin-latte .content .tags.is-centered kbd,html.theme--catppuccin-latte .tags.is-centered .docstring>section>a.docs-sourcelink{margin-right:0.25rem;margin-left:0.25rem}html.theme--catppuccin-latte .tags.is-right{justify-content:flex-end}html.theme--catppuccin-latte .tags.is-right .tag:not(:first-child),html.theme--catppuccin-latte .tags.is-right .content kbd:not(:first-child),html.theme--catppuccin-latte .content .tags.is-right kbd:not(:first-child),html.theme--catppuccin-latte .tags.is-right .docstring>section>a.docs-sourcelink:not(:first-child){margin-left:0.5rem}html.theme--catppuccin-latte .tags.is-right .tag:not(:last-child),html.theme--catppuccin-latte .tags.is-right .content kbd:not(:last-child),html.theme--catppuccin-latte .content .tags.is-right kbd:not(:last-child),html.theme--catppuccin-latte .tags.is-right .docstring>section>a.docs-sourcelink:not(:last-child){margin-right:0}html.theme--catppuccin-latte .tags.has-addons .tag,html.theme--catppuccin-latte .tags.has-addons .content kbd,html.theme--catppuccin-latte .content .tags.has-addons kbd,html.theme--catppuccin-latte .tags.has-addons .docstring>section>a.docs-sourcelink{margin-right:0}html.theme--catppuccin-latte .tags.has-addons .tag:not(:first-child),html.theme--catppuccin-latte .tags.has-addons .content kbd:not(:first-child),html.theme--catppuccin-latte .content .tags.has-addons kbd:not(:first-child),html.theme--catppuccin-latte .tags.has-addons .docstring>section>a.docs-sourcelink:not(:first-child){margin-left:0;border-top-left-radius:0;border-bottom-left-radius:0}html.theme--catppuccin-latte .tags.has-addons .tag:not(:last-child),html.theme--catppuccin-latte .tags.has-addons .content kbd:not(:last-child),html.theme--catppuccin-latte .content .tags.has-addons kbd:not(:last-child),html.theme--catppuccin-latte .tags.has-addons .docstring>section>a.docs-sourcelink:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}html.theme--catppuccin-latte .tag:not(body),html.theme--catppuccin-latte .content kbd:not(body),html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink:not(body){align-items:center;background-color:#e6e9ef;border-radius:.4em;color:#4c4f69;display:inline-flex;font-size:.75rem;height:2em;justify-content:center;line-height:1.5;padding-left:0.75em;padding-right:0.75em;white-space:nowrap}html.theme--catppuccin-latte .tag:not(body) .delete,html.theme--catppuccin-latte .content kbd:not(body) .delete,html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink:not(body) .delete{margin-left:.25rem;margin-right:-.375rem}html.theme--catppuccin-latte .tag.is-white:not(body),html.theme--catppuccin-latte .content kbd.is-white:not(body),html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink.is-white:not(body){background-color:#fff;color:#0a0a0a}html.theme--catppuccin-latte .tag.is-black:not(body),html.theme--catppuccin-latte .content kbd.is-black:not(body),html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink.is-black:not(body){background-color:#0a0a0a;color:#fff}html.theme--catppuccin-latte .tag.is-light:not(body),html.theme--catppuccin-latte .content kbd.is-light:not(body),html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink.is-light:not(body){background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .tag.is-dark:not(body),html.theme--catppuccin-latte .content kbd:not(body),html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink.is-dark:not(body),html.theme--catppuccin-latte .content .docstring>section>kbd:not(body){background-color:#ccd0da;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .tag.is-primary:not(body),html.theme--catppuccin-latte .content kbd.is-primary:not(body),html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink:not(body){background-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .tag.is-primary.is-light:not(body),html.theme--catppuccin-latte .content kbd.is-primary.is-light:not(body),html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink.is-light:not(body){background-color:#ebf2fe;color:#0a52e1}html.theme--catppuccin-latte .tag.is-link:not(body),html.theme--catppuccin-latte .content kbd.is-link:not(body),html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink.is-link:not(body){background-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .tag.is-link.is-light:not(body),html.theme--catppuccin-latte .content kbd.is-link.is-light:not(body),html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink.is-link.is-light:not(body){background-color:#ebf2fe;color:#0a52e1}html.theme--catppuccin-latte .tag.is-info:not(body),html.theme--catppuccin-latte .content kbd.is-info:not(body),html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink.is-info:not(body){background-color:#179299;color:#fff}html.theme--catppuccin-latte .tag.is-info.is-light:not(body),html.theme--catppuccin-latte .content kbd.is-info.is-light:not(body),html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink.is-info.is-light:not(body){background-color:#edfcfc;color:#1cb2ba}html.theme--catppuccin-latte .tag.is-success:not(body),html.theme--catppuccin-latte .content kbd.is-success:not(body),html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink.is-success:not(body){background-color:#40a02b;color:#fff}html.theme--catppuccin-latte .tag.is-success.is-light:not(body),html.theme--catppuccin-latte .content kbd.is-success.is-light:not(body),html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink.is-success.is-light:not(body){background-color:#f1fbef;color:#40a12b}html.theme--catppuccin-latte .tag.is-warning:not(body),html.theme--catppuccin-latte .content kbd.is-warning:not(body),html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink.is-warning:not(body){background-color:#df8e1d;color:#fff}html.theme--catppuccin-latte .tag.is-warning.is-light:not(body),html.theme--catppuccin-latte .content kbd.is-warning.is-light:not(body),html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink.is-warning.is-light:not(body){background-color:#fdf6ed;color:#9e6515}html.theme--catppuccin-latte .tag.is-danger:not(body),html.theme--catppuccin-latte .content kbd.is-danger:not(body),html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink.is-danger:not(body){background-color:#d20f39;color:#fff}html.theme--catppuccin-latte .tag.is-danger.is-light:not(body),html.theme--catppuccin-latte .content kbd.is-danger.is-light:not(body),html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink.is-danger.is-light:not(body){background-color:#feecf0;color:#e9113f}html.theme--catppuccin-latte .tag.is-normal:not(body),html.theme--catppuccin-latte .content kbd.is-normal:not(body),html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink.is-normal:not(body){font-size:.75rem}html.theme--catppuccin-latte .tag.is-medium:not(body),html.theme--catppuccin-latte .content kbd.is-medium:not(body),html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink.is-medium:not(body){font-size:1rem}html.theme--catppuccin-latte .tag.is-large:not(body),html.theme--catppuccin-latte .content kbd.is-large:not(body),html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink.is-large:not(body){font-size:1.25rem}html.theme--catppuccin-latte .tag:not(body) .icon:first-child:not(:last-child),html.theme--catppuccin-latte .content kbd:not(body) .icon:first-child:not(:last-child),html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink:not(body) .icon:first-child:not(:last-child){margin-left:-.375em;margin-right:.1875em}html.theme--catppuccin-latte .tag:not(body) .icon:last-child:not(:first-child),html.theme--catppuccin-latte .content kbd:not(body) .icon:last-child:not(:first-child),html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink:not(body) .icon:last-child:not(:first-child){margin-left:.1875em;margin-right:-.375em}html.theme--catppuccin-latte .tag:not(body) .icon:first-child:last-child,html.theme--catppuccin-latte .content kbd:not(body) .icon:first-child:last-child,html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink:not(body) .icon:first-child:last-child{margin-left:-.375em;margin-right:-.375em}html.theme--catppuccin-latte .tag.is-delete:not(body),html.theme--catppuccin-latte .content kbd.is-delete:not(body),html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink.is-delete:not(body){margin-left:1px;padding:0;position:relative;width:2em}html.theme--catppuccin-latte .tag.is-delete:not(body)::before,html.theme--catppuccin-latte .content kbd.is-delete:not(body)::before,html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink.is-delete:not(body)::before,html.theme--catppuccin-latte .tag.is-delete:not(body)::after,html.theme--catppuccin-latte .content kbd.is-delete:not(body)::after,html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink.is-delete:not(body)::after{background-color:currentColor;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}html.theme--catppuccin-latte .tag.is-delete:not(body)::before,html.theme--catppuccin-latte .content kbd.is-delete:not(body)::before,html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink.is-delete:not(body)::before{height:1px;width:50%}html.theme--catppuccin-latte .tag.is-delete:not(body)::after,html.theme--catppuccin-latte .content kbd.is-delete:not(body)::after,html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink.is-delete:not(body)::after{height:50%;width:1px}html.theme--catppuccin-latte .tag.is-delete:not(body):hover,html.theme--catppuccin-latte .content kbd.is-delete:not(body):hover,html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink.is-delete:not(body):hover,html.theme--catppuccin-latte .tag.is-delete:not(body):focus,html.theme--catppuccin-latte .content kbd.is-delete:not(body):focus,html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink.is-delete:not(body):focus{background-color:#d6dbe5}html.theme--catppuccin-latte .tag.is-delete:not(body):active,html.theme--catppuccin-latte .content kbd.is-delete:not(body):active,html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink.is-delete:not(body):active{background-color:#c7cedb}html.theme--catppuccin-latte .tag.is-rounded:not(body),html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input:not(body),html.theme--catppuccin-latte .content kbd.is-rounded:not(body),html.theme--catppuccin-latte #documenter .docs-sidebar .content form.docs-search>input:not(body),html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink.is-rounded:not(body){border-radius:9999px}html.theme--catppuccin-latte a.tag:hover,html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink:hover{text-decoration:underline}html.theme--catppuccin-latte .title,html.theme--catppuccin-latte .subtitle{word-break:break-word}html.theme--catppuccin-latte .title em,html.theme--catppuccin-latte .title span,html.theme--catppuccin-latte .subtitle em,html.theme--catppuccin-latte .subtitle span{font-weight:inherit}html.theme--catppuccin-latte .title sub,html.theme--catppuccin-latte .subtitle sub{font-size:.75em}html.theme--catppuccin-latte .title sup,html.theme--catppuccin-latte .subtitle sup{font-size:.75em}html.theme--catppuccin-latte .title .tag,html.theme--catppuccin-latte .title .content kbd,html.theme--catppuccin-latte .content .title kbd,html.theme--catppuccin-latte .title .docstring>section>a.docs-sourcelink,html.theme--catppuccin-latte .subtitle .tag,html.theme--catppuccin-latte .subtitle .content kbd,html.theme--catppuccin-latte .content .subtitle kbd,html.theme--catppuccin-latte .subtitle .docstring>section>a.docs-sourcelink{vertical-align:middle}html.theme--catppuccin-latte .title{color:#fff;font-size:2rem;font-weight:500;line-height:1.125}html.theme--catppuccin-latte .title strong{color:inherit;font-weight:inherit}html.theme--catppuccin-latte .title:not(.is-spaced)+.subtitle{margin-top:-1.25rem}html.theme--catppuccin-latte .title.is-1{font-size:3rem}html.theme--catppuccin-latte .title.is-2{font-size:2.5rem}html.theme--catppuccin-latte .title.is-3{font-size:2rem}html.theme--catppuccin-latte .title.is-4{font-size:1.5rem}html.theme--catppuccin-latte .title.is-5{font-size:1.25rem}html.theme--catppuccin-latte .title.is-6{font-size:1rem}html.theme--catppuccin-latte .title.is-7{font-size:.75rem}html.theme--catppuccin-latte .subtitle{color:#9ca0b0;font-size:1.25rem;font-weight:400;line-height:1.25}html.theme--catppuccin-latte .subtitle strong{color:#9ca0b0;font-weight:600}html.theme--catppuccin-latte .subtitle:not(.is-spaced)+.title{margin-top:-1.25rem}html.theme--catppuccin-latte .subtitle.is-1{font-size:3rem}html.theme--catppuccin-latte .subtitle.is-2{font-size:2.5rem}html.theme--catppuccin-latte .subtitle.is-3{font-size:2rem}html.theme--catppuccin-latte .subtitle.is-4{font-size:1.5rem}html.theme--catppuccin-latte .subtitle.is-5{font-size:1.25rem}html.theme--catppuccin-latte .subtitle.is-6{font-size:1rem}html.theme--catppuccin-latte .subtitle.is-7{font-size:.75rem}html.theme--catppuccin-latte .heading{display:block;font-size:11px;letter-spacing:1px;margin-bottom:5px;text-transform:uppercase}html.theme--catppuccin-latte .number{align-items:center;background-color:#e6e9ef;border-radius:9999px;display:inline-flex;font-size:1.25rem;height:2em;justify-content:center;margin-right:1.5rem;min-width:2.5em;padding:0.25rem 0.5rem;text-align:center;vertical-align:top}html.theme--catppuccin-latte .select select,html.theme--catppuccin-latte .textarea,html.theme--catppuccin-latte .input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input{background-color:#eff1f5;border-color:#acb0be;border-radius:.4em;color:#8c8fa1}html.theme--catppuccin-latte .select select::-moz-placeholder,html.theme--catppuccin-latte .textarea::-moz-placeholder,html.theme--catppuccin-latte .input::-moz-placeholder,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input::-moz-placeholder{color:#868c98}html.theme--catppuccin-latte .select select::-webkit-input-placeholder,html.theme--catppuccin-latte .textarea::-webkit-input-placeholder,html.theme--catppuccin-latte .input::-webkit-input-placeholder,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input::-webkit-input-placeholder{color:#868c98}html.theme--catppuccin-latte .select select:-moz-placeholder,html.theme--catppuccin-latte .textarea:-moz-placeholder,html.theme--catppuccin-latte .input:-moz-placeholder,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input:-moz-placeholder{color:#868c98}html.theme--catppuccin-latte .select select:-ms-input-placeholder,html.theme--catppuccin-latte .textarea:-ms-input-placeholder,html.theme--catppuccin-latte .input:-ms-input-placeholder,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input:-ms-input-placeholder{color:#868c98}html.theme--catppuccin-latte .select select:hover,html.theme--catppuccin-latte .textarea:hover,html.theme--catppuccin-latte .input:hover,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input:hover,html.theme--catppuccin-latte .select select.is-hovered,html.theme--catppuccin-latte .is-hovered.textarea,html.theme--catppuccin-latte .is-hovered.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-hovered{border-color:#9ca0b0}html.theme--catppuccin-latte .select select:focus,html.theme--catppuccin-latte .textarea:focus,html.theme--catppuccin-latte .input:focus,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input:focus,html.theme--catppuccin-latte .select select.is-focused,html.theme--catppuccin-latte .is-focused.textarea,html.theme--catppuccin-latte .is-focused.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-latte .select select:active,html.theme--catppuccin-latte .textarea:active,html.theme--catppuccin-latte .input:active,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input:active,html.theme--catppuccin-latte .select select.is-active,html.theme--catppuccin-latte .is-active.textarea,html.theme--catppuccin-latte .is-active.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-active{border-color:#1e66f5;box-shadow:0 0 0 0.125em rgba(30,102,245,0.25)}html.theme--catppuccin-latte .select select[disabled],html.theme--catppuccin-latte .textarea[disabled],html.theme--catppuccin-latte .input[disabled],html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input[disabled],fieldset[disabled] html.theme--catppuccin-latte .select select,fieldset[disabled] html.theme--catppuccin-latte .textarea,fieldset[disabled] html.theme--catppuccin-latte .input,fieldset[disabled] html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input{background-color:#9ca0b0;border-color:#e6e9ef;box-shadow:none;color:#616587}html.theme--catppuccin-latte .select select[disabled]::-moz-placeholder,html.theme--catppuccin-latte .textarea[disabled]::-moz-placeholder,html.theme--catppuccin-latte .input[disabled]::-moz-placeholder,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input[disabled]::-moz-placeholder,fieldset[disabled] html.theme--catppuccin-latte .select select::-moz-placeholder,fieldset[disabled] html.theme--catppuccin-latte .textarea::-moz-placeholder,fieldset[disabled] html.theme--catppuccin-latte .input::-moz-placeholder,fieldset[disabled] html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input::-moz-placeholder{color:rgba(97,101,135,0.3)}html.theme--catppuccin-latte .select select[disabled]::-webkit-input-placeholder,html.theme--catppuccin-latte .textarea[disabled]::-webkit-input-placeholder,html.theme--catppuccin-latte .input[disabled]::-webkit-input-placeholder,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input[disabled]::-webkit-input-placeholder,fieldset[disabled] html.theme--catppuccin-latte .select select::-webkit-input-placeholder,fieldset[disabled] html.theme--catppuccin-latte .textarea::-webkit-input-placeholder,fieldset[disabled] html.theme--catppuccin-latte .input::-webkit-input-placeholder,fieldset[disabled] html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input::-webkit-input-placeholder{color:rgba(97,101,135,0.3)}html.theme--catppuccin-latte .select select[disabled]:-moz-placeholder,html.theme--catppuccin-latte .textarea[disabled]:-moz-placeholder,html.theme--catppuccin-latte .input[disabled]:-moz-placeholder,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input[disabled]:-moz-placeholder,fieldset[disabled] html.theme--catppuccin-latte .select select:-moz-placeholder,fieldset[disabled] html.theme--catppuccin-latte .textarea:-moz-placeholder,fieldset[disabled] html.theme--catppuccin-latte .input:-moz-placeholder,fieldset[disabled] html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input:-moz-placeholder{color:rgba(97,101,135,0.3)}html.theme--catppuccin-latte .select select[disabled]:-ms-input-placeholder,html.theme--catppuccin-latte .textarea[disabled]:-ms-input-placeholder,html.theme--catppuccin-latte .input[disabled]:-ms-input-placeholder,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input[disabled]:-ms-input-placeholder,fieldset[disabled] html.theme--catppuccin-latte .select select:-ms-input-placeholder,fieldset[disabled] html.theme--catppuccin-latte .textarea:-ms-input-placeholder,fieldset[disabled] html.theme--catppuccin-latte .input:-ms-input-placeholder,fieldset[disabled] html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input:-ms-input-placeholder{color:rgba(97,101,135,0.3)}html.theme--catppuccin-latte .textarea,html.theme--catppuccin-latte .input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input{box-shadow:inset 0 0.0625em 0.125em rgba(10,10,10,0.05);max-width:100%;width:100%}html.theme--catppuccin-latte .textarea[readonly],html.theme--catppuccin-latte .input[readonly],html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input[readonly]{box-shadow:none}html.theme--catppuccin-latte .is-white.textarea,html.theme--catppuccin-latte .is-white.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-white{border-color:#fff}html.theme--catppuccin-latte .is-white.textarea:focus,html.theme--catppuccin-latte .is-white.input:focus,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-white:focus,html.theme--catppuccin-latte .is-white.is-focused.textarea,html.theme--catppuccin-latte .is-white.is-focused.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-latte .is-white.textarea:active,html.theme--catppuccin-latte .is-white.input:active,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-white:active,html.theme--catppuccin-latte .is-white.is-active.textarea,html.theme--catppuccin-latte .is-white.is-active.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}html.theme--catppuccin-latte .is-black.textarea,html.theme--catppuccin-latte .is-black.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-black{border-color:#0a0a0a}html.theme--catppuccin-latte .is-black.textarea:focus,html.theme--catppuccin-latte .is-black.input:focus,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-black:focus,html.theme--catppuccin-latte .is-black.is-focused.textarea,html.theme--catppuccin-latte .is-black.is-focused.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-latte .is-black.textarea:active,html.theme--catppuccin-latte .is-black.input:active,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-black:active,html.theme--catppuccin-latte .is-black.is-active.textarea,html.theme--catppuccin-latte .is-black.is-active.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}html.theme--catppuccin-latte .is-light.textarea,html.theme--catppuccin-latte .is-light.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-light{border-color:#f5f5f5}html.theme--catppuccin-latte .is-light.textarea:focus,html.theme--catppuccin-latte .is-light.input:focus,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-light:focus,html.theme--catppuccin-latte .is-light.is-focused.textarea,html.theme--catppuccin-latte .is-light.is-focused.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-latte .is-light.textarea:active,html.theme--catppuccin-latte .is-light.input:active,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-light:active,html.theme--catppuccin-latte .is-light.is-active.textarea,html.theme--catppuccin-latte .is-light.is-active.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(245,245,245,0.25)}html.theme--catppuccin-latte .is-dark.textarea,html.theme--catppuccin-latte .content kbd.textarea,html.theme--catppuccin-latte .is-dark.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-dark,html.theme--catppuccin-latte .content kbd.input{border-color:#ccd0da}html.theme--catppuccin-latte .is-dark.textarea:focus,html.theme--catppuccin-latte .content kbd.textarea:focus,html.theme--catppuccin-latte .is-dark.input:focus,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-dark:focus,html.theme--catppuccin-latte .content kbd.input:focus,html.theme--catppuccin-latte .is-dark.is-focused.textarea,html.theme--catppuccin-latte .content kbd.is-focused.textarea,html.theme--catppuccin-latte .is-dark.is-focused.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-latte .content kbd.is-focused.input,html.theme--catppuccin-latte #documenter .docs-sidebar .content form.docs-search>input.is-focused,html.theme--catppuccin-latte .is-dark.textarea:active,html.theme--catppuccin-latte .content kbd.textarea:active,html.theme--catppuccin-latte .is-dark.input:active,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-dark:active,html.theme--catppuccin-latte .content kbd.input:active,html.theme--catppuccin-latte .is-dark.is-active.textarea,html.theme--catppuccin-latte .content kbd.is-active.textarea,html.theme--catppuccin-latte .is-dark.is-active.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-active,html.theme--catppuccin-latte .content kbd.is-active.input,html.theme--catppuccin-latte #documenter .docs-sidebar .content form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(204,208,218,0.25)}html.theme--catppuccin-latte .is-primary.textarea,html.theme--catppuccin-latte .docstring>section>a.textarea.docs-sourcelink,html.theme--catppuccin-latte .is-primary.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-primary,html.theme--catppuccin-latte .docstring>section>a.input.docs-sourcelink{border-color:#1e66f5}html.theme--catppuccin-latte .is-primary.textarea:focus,html.theme--catppuccin-latte .docstring>section>a.textarea.docs-sourcelink:focus,html.theme--catppuccin-latte .is-primary.input:focus,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-primary:focus,html.theme--catppuccin-latte .docstring>section>a.input.docs-sourcelink:focus,html.theme--catppuccin-latte .is-primary.is-focused.textarea,html.theme--catppuccin-latte .docstring>section>a.is-focused.textarea.docs-sourcelink,html.theme--catppuccin-latte .is-primary.is-focused.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-latte .docstring>section>a.is-focused.input.docs-sourcelink,html.theme--catppuccin-latte .is-primary.textarea:active,html.theme--catppuccin-latte .docstring>section>a.textarea.docs-sourcelink:active,html.theme--catppuccin-latte .is-primary.input:active,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-primary:active,html.theme--catppuccin-latte .docstring>section>a.input.docs-sourcelink:active,html.theme--catppuccin-latte .is-primary.is-active.textarea,html.theme--catppuccin-latte .docstring>section>a.is-active.textarea.docs-sourcelink,html.theme--catppuccin-latte .is-primary.is-active.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-active,html.theme--catppuccin-latte .docstring>section>a.is-active.input.docs-sourcelink{box-shadow:0 0 0 0.125em rgba(30,102,245,0.25)}html.theme--catppuccin-latte .is-link.textarea,html.theme--catppuccin-latte .is-link.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-link{border-color:#1e66f5}html.theme--catppuccin-latte .is-link.textarea:focus,html.theme--catppuccin-latte .is-link.input:focus,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-link:focus,html.theme--catppuccin-latte .is-link.is-focused.textarea,html.theme--catppuccin-latte .is-link.is-focused.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-latte .is-link.textarea:active,html.theme--catppuccin-latte .is-link.input:active,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-link:active,html.theme--catppuccin-latte .is-link.is-active.textarea,html.theme--catppuccin-latte .is-link.is-active.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(30,102,245,0.25)}html.theme--catppuccin-latte .is-info.textarea,html.theme--catppuccin-latte .is-info.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-info{border-color:#179299}html.theme--catppuccin-latte .is-info.textarea:focus,html.theme--catppuccin-latte .is-info.input:focus,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-info:focus,html.theme--catppuccin-latte .is-info.is-focused.textarea,html.theme--catppuccin-latte .is-info.is-focused.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-latte .is-info.textarea:active,html.theme--catppuccin-latte .is-info.input:active,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-info:active,html.theme--catppuccin-latte .is-info.is-active.textarea,html.theme--catppuccin-latte .is-info.is-active.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(23,146,153,0.25)}html.theme--catppuccin-latte .is-success.textarea,html.theme--catppuccin-latte .is-success.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-success{border-color:#40a02b}html.theme--catppuccin-latte .is-success.textarea:focus,html.theme--catppuccin-latte .is-success.input:focus,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-success:focus,html.theme--catppuccin-latte .is-success.is-focused.textarea,html.theme--catppuccin-latte .is-success.is-focused.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-latte .is-success.textarea:active,html.theme--catppuccin-latte .is-success.input:active,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-success:active,html.theme--catppuccin-latte .is-success.is-active.textarea,html.theme--catppuccin-latte .is-success.is-active.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(64,160,43,0.25)}html.theme--catppuccin-latte .is-warning.textarea,html.theme--catppuccin-latte .is-warning.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-warning{border-color:#df8e1d}html.theme--catppuccin-latte .is-warning.textarea:focus,html.theme--catppuccin-latte .is-warning.input:focus,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-warning:focus,html.theme--catppuccin-latte .is-warning.is-focused.textarea,html.theme--catppuccin-latte .is-warning.is-focused.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-latte .is-warning.textarea:active,html.theme--catppuccin-latte .is-warning.input:active,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-warning:active,html.theme--catppuccin-latte .is-warning.is-active.textarea,html.theme--catppuccin-latte .is-warning.is-active.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(223,142,29,0.25)}html.theme--catppuccin-latte .is-danger.textarea,html.theme--catppuccin-latte .is-danger.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-danger{border-color:#d20f39}html.theme--catppuccin-latte .is-danger.textarea:focus,html.theme--catppuccin-latte .is-danger.input:focus,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-danger:focus,html.theme--catppuccin-latte .is-danger.is-focused.textarea,html.theme--catppuccin-latte .is-danger.is-focused.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-latte .is-danger.textarea:active,html.theme--catppuccin-latte .is-danger.input:active,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-danger:active,html.theme--catppuccin-latte .is-danger.is-active.textarea,html.theme--catppuccin-latte .is-danger.is-active.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(210,15,57,0.25)}html.theme--catppuccin-latte .is-small.textarea,html.theme--catppuccin-latte .is-small.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input{border-radius:3px;font-size:.75rem}html.theme--catppuccin-latte .is-medium.textarea,html.theme--catppuccin-latte .is-medium.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-medium{font-size:1.25rem}html.theme--catppuccin-latte .is-large.textarea,html.theme--catppuccin-latte .is-large.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-large{font-size:1.5rem}html.theme--catppuccin-latte .is-fullwidth.textarea,html.theme--catppuccin-latte .is-fullwidth.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-fullwidth{display:block;width:100%}html.theme--catppuccin-latte .is-inline.textarea,html.theme--catppuccin-latte .is-inline.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-inline{display:inline;width:auto}html.theme--catppuccin-latte .input.is-rounded,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input{border-radius:9999px;padding-left:calc(calc(0.75em - 1px) + 0.375em);padding-right:calc(calc(0.75em - 1px) + 0.375em)}html.theme--catppuccin-latte .input.is-static,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-static{background-color:transparent;border-color:transparent;box-shadow:none;padding-left:0;padding-right:0}html.theme--catppuccin-latte .textarea{display:block;max-width:100%;min-width:100%;padding:calc(0.75em - 1px);resize:vertical}html.theme--catppuccin-latte .textarea:not([rows]){max-height:40em;min-height:8em}html.theme--catppuccin-latte .textarea[rows]{height:initial}html.theme--catppuccin-latte .textarea.has-fixed-size{resize:none}html.theme--catppuccin-latte .radio,html.theme--catppuccin-latte .checkbox{cursor:pointer;display:inline-block;line-height:1.25;position:relative}html.theme--catppuccin-latte .radio input,html.theme--catppuccin-latte .checkbox input{cursor:pointer}html.theme--catppuccin-latte .radio:hover,html.theme--catppuccin-latte .checkbox:hover{color:#04a5e5}html.theme--catppuccin-latte .radio[disabled],html.theme--catppuccin-latte .checkbox[disabled],fieldset[disabled] html.theme--catppuccin-latte .radio,fieldset[disabled] html.theme--catppuccin-latte .checkbox,html.theme--catppuccin-latte .radio input[disabled],html.theme--catppuccin-latte .checkbox input[disabled]{color:#616587;cursor:not-allowed}html.theme--catppuccin-latte .radio+.radio{margin-left:.5em}html.theme--catppuccin-latte .select{display:inline-block;max-width:100%;position:relative;vertical-align:top}html.theme--catppuccin-latte .select:not(.is-multiple){height:2.5em}html.theme--catppuccin-latte .select:not(.is-multiple):not(.is-loading)::after{border-color:#1e66f5;right:1.125em;z-index:4}html.theme--catppuccin-latte .select.is-rounded select,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.select select{border-radius:9999px;padding-left:1em}html.theme--catppuccin-latte .select select{cursor:pointer;display:block;font-size:1em;max-width:100%;outline:none}html.theme--catppuccin-latte .select select::-ms-expand{display:none}html.theme--catppuccin-latte .select select[disabled]:hover,fieldset[disabled] html.theme--catppuccin-latte .select select:hover{border-color:#e6e9ef}html.theme--catppuccin-latte .select select:not([multiple]){padding-right:2.5em}html.theme--catppuccin-latte .select select[multiple]{height:auto;padding:0}html.theme--catppuccin-latte .select select[multiple] option{padding:0.5em 1em}html.theme--catppuccin-latte .select:not(.is-multiple):not(.is-loading):hover::after{border-color:#04a5e5}html.theme--catppuccin-latte .select.is-white:not(:hover)::after{border-color:#fff}html.theme--catppuccin-latte .select.is-white select{border-color:#fff}html.theme--catppuccin-latte .select.is-white select:hover,html.theme--catppuccin-latte .select.is-white select.is-hovered{border-color:#f2f2f2}html.theme--catppuccin-latte .select.is-white select:focus,html.theme--catppuccin-latte .select.is-white select.is-focused,html.theme--catppuccin-latte .select.is-white select:active,html.theme--catppuccin-latte .select.is-white select.is-active{box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}html.theme--catppuccin-latte .select.is-black:not(:hover)::after{border-color:#0a0a0a}html.theme--catppuccin-latte .select.is-black select{border-color:#0a0a0a}html.theme--catppuccin-latte .select.is-black select:hover,html.theme--catppuccin-latte .select.is-black select.is-hovered{border-color:#000}html.theme--catppuccin-latte .select.is-black select:focus,html.theme--catppuccin-latte .select.is-black select.is-focused,html.theme--catppuccin-latte .select.is-black select:active,html.theme--catppuccin-latte .select.is-black select.is-active{box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}html.theme--catppuccin-latte .select.is-light:not(:hover)::after{border-color:#f5f5f5}html.theme--catppuccin-latte .select.is-light select{border-color:#f5f5f5}html.theme--catppuccin-latte .select.is-light select:hover,html.theme--catppuccin-latte .select.is-light select.is-hovered{border-color:#e8e8e8}html.theme--catppuccin-latte .select.is-light select:focus,html.theme--catppuccin-latte .select.is-light select.is-focused,html.theme--catppuccin-latte .select.is-light select:active,html.theme--catppuccin-latte .select.is-light select.is-active{box-shadow:0 0 0 0.125em rgba(245,245,245,0.25)}html.theme--catppuccin-latte .select.is-dark:not(:hover)::after,html.theme--catppuccin-latte .content kbd.select:not(:hover)::after{border-color:#ccd0da}html.theme--catppuccin-latte .select.is-dark select,html.theme--catppuccin-latte .content kbd.select select{border-color:#ccd0da}html.theme--catppuccin-latte .select.is-dark select:hover,html.theme--catppuccin-latte .content kbd.select select:hover,html.theme--catppuccin-latte .select.is-dark select.is-hovered,html.theme--catppuccin-latte .content kbd.select select.is-hovered{border-color:#bdc2cf}html.theme--catppuccin-latte .select.is-dark select:focus,html.theme--catppuccin-latte .content kbd.select select:focus,html.theme--catppuccin-latte .select.is-dark select.is-focused,html.theme--catppuccin-latte .content kbd.select select.is-focused,html.theme--catppuccin-latte .select.is-dark select:active,html.theme--catppuccin-latte .content kbd.select select:active,html.theme--catppuccin-latte .select.is-dark select.is-active,html.theme--catppuccin-latte .content kbd.select select.is-active{box-shadow:0 0 0 0.125em rgba(204,208,218,0.25)}html.theme--catppuccin-latte .select.is-primary:not(:hover)::after,html.theme--catppuccin-latte .docstring>section>a.select.docs-sourcelink:not(:hover)::after{border-color:#1e66f5}html.theme--catppuccin-latte .select.is-primary select,html.theme--catppuccin-latte .docstring>section>a.select.docs-sourcelink select{border-color:#1e66f5}html.theme--catppuccin-latte .select.is-primary select:hover,html.theme--catppuccin-latte .docstring>section>a.select.docs-sourcelink select:hover,html.theme--catppuccin-latte .select.is-primary select.is-hovered,html.theme--catppuccin-latte .docstring>section>a.select.docs-sourcelink select.is-hovered{border-color:#0b57ef}html.theme--catppuccin-latte .select.is-primary select:focus,html.theme--catppuccin-latte .docstring>section>a.select.docs-sourcelink select:focus,html.theme--catppuccin-latte .select.is-primary select.is-focused,html.theme--catppuccin-latte .docstring>section>a.select.docs-sourcelink select.is-focused,html.theme--catppuccin-latte .select.is-primary select:active,html.theme--catppuccin-latte .docstring>section>a.select.docs-sourcelink select:active,html.theme--catppuccin-latte .select.is-primary select.is-active,html.theme--catppuccin-latte .docstring>section>a.select.docs-sourcelink select.is-active{box-shadow:0 0 0 0.125em rgba(30,102,245,0.25)}html.theme--catppuccin-latte .select.is-link:not(:hover)::after{border-color:#1e66f5}html.theme--catppuccin-latte .select.is-link select{border-color:#1e66f5}html.theme--catppuccin-latte .select.is-link select:hover,html.theme--catppuccin-latte .select.is-link select.is-hovered{border-color:#0b57ef}html.theme--catppuccin-latte .select.is-link select:focus,html.theme--catppuccin-latte .select.is-link select.is-focused,html.theme--catppuccin-latte .select.is-link select:active,html.theme--catppuccin-latte .select.is-link select.is-active{box-shadow:0 0 0 0.125em rgba(30,102,245,0.25)}html.theme--catppuccin-latte .select.is-info:not(:hover)::after{border-color:#179299}html.theme--catppuccin-latte .select.is-info select{border-color:#179299}html.theme--catppuccin-latte .select.is-info select:hover,html.theme--catppuccin-latte .select.is-info select.is-hovered{border-color:#147d83}html.theme--catppuccin-latte .select.is-info select:focus,html.theme--catppuccin-latte .select.is-info select.is-focused,html.theme--catppuccin-latte .select.is-info select:active,html.theme--catppuccin-latte .select.is-info select.is-active{box-shadow:0 0 0 0.125em rgba(23,146,153,0.25)}html.theme--catppuccin-latte .select.is-success:not(:hover)::after{border-color:#40a02b}html.theme--catppuccin-latte .select.is-success select{border-color:#40a02b}html.theme--catppuccin-latte .select.is-success select:hover,html.theme--catppuccin-latte .select.is-success select.is-hovered{border-color:#388c26}html.theme--catppuccin-latte .select.is-success select:focus,html.theme--catppuccin-latte .select.is-success select.is-focused,html.theme--catppuccin-latte .select.is-success select:active,html.theme--catppuccin-latte .select.is-success select.is-active{box-shadow:0 0 0 0.125em rgba(64,160,43,0.25)}html.theme--catppuccin-latte .select.is-warning:not(:hover)::after{border-color:#df8e1d}html.theme--catppuccin-latte .select.is-warning select{border-color:#df8e1d}html.theme--catppuccin-latte .select.is-warning select:hover,html.theme--catppuccin-latte .select.is-warning select.is-hovered{border-color:#c8801a}html.theme--catppuccin-latte .select.is-warning select:focus,html.theme--catppuccin-latte .select.is-warning select.is-focused,html.theme--catppuccin-latte .select.is-warning select:active,html.theme--catppuccin-latte .select.is-warning select.is-active{box-shadow:0 0 0 0.125em rgba(223,142,29,0.25)}html.theme--catppuccin-latte .select.is-danger:not(:hover)::after{border-color:#d20f39}html.theme--catppuccin-latte .select.is-danger select{border-color:#d20f39}html.theme--catppuccin-latte .select.is-danger select:hover,html.theme--catppuccin-latte .select.is-danger select.is-hovered{border-color:#ba0d33}html.theme--catppuccin-latte .select.is-danger select:focus,html.theme--catppuccin-latte .select.is-danger select.is-focused,html.theme--catppuccin-latte .select.is-danger select:active,html.theme--catppuccin-latte .select.is-danger select.is-active{box-shadow:0 0 0 0.125em rgba(210,15,57,0.25)}html.theme--catppuccin-latte .select.is-small,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.select{border-radius:3px;font-size:.75rem}html.theme--catppuccin-latte .select.is-medium{font-size:1.25rem}html.theme--catppuccin-latte .select.is-large{font-size:1.5rem}html.theme--catppuccin-latte .select.is-disabled::after{border-color:#616587 !important;opacity:0.5}html.theme--catppuccin-latte .select.is-fullwidth{width:100%}html.theme--catppuccin-latte .select.is-fullwidth select{width:100%}html.theme--catppuccin-latte .select.is-loading::after{margin-top:0;position:absolute;right:.625em;top:0.625em;transform:none}html.theme--catppuccin-latte .select.is-loading.is-small:after,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-loading:after{font-size:.75rem}html.theme--catppuccin-latte .select.is-loading.is-medium:after{font-size:1.25rem}html.theme--catppuccin-latte .select.is-loading.is-large:after{font-size:1.5rem}html.theme--catppuccin-latte .file{align-items:stretch;display:flex;justify-content:flex-start;position:relative}html.theme--catppuccin-latte .file.is-white .file-cta{background-color:#fff;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-latte .file.is-white:hover .file-cta,html.theme--catppuccin-latte .file.is-white.is-hovered .file-cta{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-latte .file.is-white:focus .file-cta,html.theme--catppuccin-latte .file.is-white.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(255,255,255,0.25);color:#0a0a0a}html.theme--catppuccin-latte .file.is-white:active .file-cta,html.theme--catppuccin-latte .file.is-white.is-active .file-cta{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-latte .file.is-black .file-cta{background-color:#0a0a0a;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-black:hover .file-cta,html.theme--catppuccin-latte .file.is-black.is-hovered .file-cta{background-color:#040404;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-black:focus .file-cta,html.theme--catppuccin-latte .file.is-black.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(10,10,10,0.25);color:#fff}html.theme--catppuccin-latte .file.is-black:active .file-cta,html.theme--catppuccin-latte .file.is-black.is-active .file-cta{background-color:#000;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-light .file-cta{background-color:#f5f5f5;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .file.is-light:hover .file-cta,html.theme--catppuccin-latte .file.is-light.is-hovered .file-cta{background-color:#eee;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .file.is-light:focus .file-cta,html.theme--catppuccin-latte .file.is-light.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(245,245,245,0.25);color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .file.is-light:active .file-cta,html.theme--catppuccin-latte .file.is-light.is-active .file-cta{background-color:#e8e8e8;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .file.is-dark .file-cta,html.theme--catppuccin-latte .content kbd.file .file-cta{background-color:#ccd0da;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .file.is-dark:hover .file-cta,html.theme--catppuccin-latte .content kbd.file:hover .file-cta,html.theme--catppuccin-latte .file.is-dark.is-hovered .file-cta,html.theme--catppuccin-latte .content kbd.file.is-hovered .file-cta{background-color:#c5c9d5;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .file.is-dark:focus .file-cta,html.theme--catppuccin-latte .content kbd.file:focus .file-cta,html.theme--catppuccin-latte .file.is-dark.is-focused .file-cta,html.theme--catppuccin-latte .content kbd.file.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(204,208,218,0.25);color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .file.is-dark:active .file-cta,html.theme--catppuccin-latte .content kbd.file:active .file-cta,html.theme--catppuccin-latte .file.is-dark.is-active .file-cta,html.theme--catppuccin-latte .content kbd.file.is-active .file-cta{background-color:#bdc2cf;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .file.is-primary .file-cta,html.theme--catppuccin-latte .docstring>section>a.file.docs-sourcelink .file-cta{background-color:#1e66f5;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-primary:hover .file-cta,html.theme--catppuccin-latte .docstring>section>a.file.docs-sourcelink:hover .file-cta,html.theme--catppuccin-latte .file.is-primary.is-hovered .file-cta,html.theme--catppuccin-latte .docstring>section>a.file.is-hovered.docs-sourcelink .file-cta{background-color:#125ef4;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-primary:focus .file-cta,html.theme--catppuccin-latte .docstring>section>a.file.docs-sourcelink:focus .file-cta,html.theme--catppuccin-latte .file.is-primary.is-focused .file-cta,html.theme--catppuccin-latte .docstring>section>a.file.is-focused.docs-sourcelink .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(30,102,245,0.25);color:#fff}html.theme--catppuccin-latte .file.is-primary:active .file-cta,html.theme--catppuccin-latte .docstring>section>a.file.docs-sourcelink:active .file-cta,html.theme--catppuccin-latte .file.is-primary.is-active .file-cta,html.theme--catppuccin-latte .docstring>section>a.file.is-active.docs-sourcelink .file-cta{background-color:#0b57ef;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-link .file-cta{background-color:#1e66f5;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-link:hover .file-cta,html.theme--catppuccin-latte .file.is-link.is-hovered .file-cta{background-color:#125ef4;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-link:focus .file-cta,html.theme--catppuccin-latte .file.is-link.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(30,102,245,0.25);color:#fff}html.theme--catppuccin-latte .file.is-link:active .file-cta,html.theme--catppuccin-latte .file.is-link.is-active .file-cta{background-color:#0b57ef;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-info .file-cta{background-color:#179299;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-info:hover .file-cta,html.theme--catppuccin-latte .file.is-info.is-hovered .file-cta{background-color:#15878e;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-info:focus .file-cta,html.theme--catppuccin-latte .file.is-info.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(23,146,153,0.25);color:#fff}html.theme--catppuccin-latte .file.is-info:active .file-cta,html.theme--catppuccin-latte .file.is-info.is-active .file-cta{background-color:#147d83;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-success .file-cta{background-color:#40a02b;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-success:hover .file-cta,html.theme--catppuccin-latte .file.is-success.is-hovered .file-cta{background-color:#3c9628;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-success:focus .file-cta,html.theme--catppuccin-latte .file.is-success.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(64,160,43,0.25);color:#fff}html.theme--catppuccin-latte .file.is-success:active .file-cta,html.theme--catppuccin-latte .file.is-success.is-active .file-cta{background-color:#388c26;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-warning .file-cta{background-color:#df8e1d;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-warning:hover .file-cta,html.theme--catppuccin-latte .file.is-warning.is-hovered .file-cta{background-color:#d4871c;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-warning:focus .file-cta,html.theme--catppuccin-latte .file.is-warning.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(223,142,29,0.25);color:#fff}html.theme--catppuccin-latte .file.is-warning:active .file-cta,html.theme--catppuccin-latte .file.is-warning.is-active .file-cta{background-color:#c8801a;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-danger .file-cta{background-color:#d20f39;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-danger:hover .file-cta,html.theme--catppuccin-latte .file.is-danger.is-hovered .file-cta{background-color:#c60e36;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-danger:focus .file-cta,html.theme--catppuccin-latte .file.is-danger.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(210,15,57,0.25);color:#fff}html.theme--catppuccin-latte .file.is-danger:active .file-cta,html.theme--catppuccin-latte .file.is-danger.is-active .file-cta{background-color:#ba0d33;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-small,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.file{font-size:.75rem}html.theme--catppuccin-latte .file.is-normal{font-size:1rem}html.theme--catppuccin-latte .file.is-medium{font-size:1.25rem}html.theme--catppuccin-latte .file.is-medium .file-icon .fa{font-size:21px}html.theme--catppuccin-latte .file.is-large{font-size:1.5rem}html.theme--catppuccin-latte .file.is-large .file-icon .fa{font-size:28px}html.theme--catppuccin-latte .file.has-name .file-cta{border-bottom-right-radius:0;border-top-right-radius:0}html.theme--catppuccin-latte .file.has-name .file-name{border-bottom-left-radius:0;border-top-left-radius:0}html.theme--catppuccin-latte .file.has-name.is-empty .file-cta{border-radius:.4em}html.theme--catppuccin-latte .file.has-name.is-empty .file-name{display:none}html.theme--catppuccin-latte .file.is-boxed .file-label{flex-direction:column}html.theme--catppuccin-latte .file.is-boxed .file-cta{flex-direction:column;height:auto;padding:1em 3em}html.theme--catppuccin-latte .file.is-boxed .file-name{border-width:0 1px 1px}html.theme--catppuccin-latte .file.is-boxed .file-icon{height:1.5em;width:1.5em}html.theme--catppuccin-latte .file.is-boxed .file-icon .fa{font-size:21px}html.theme--catppuccin-latte .file.is-boxed.is-small .file-icon .fa,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-boxed .file-icon .fa{font-size:14px}html.theme--catppuccin-latte .file.is-boxed.is-medium .file-icon .fa{font-size:28px}html.theme--catppuccin-latte .file.is-boxed.is-large .file-icon .fa{font-size:35px}html.theme--catppuccin-latte .file.is-boxed.has-name .file-cta{border-radius:.4em .4em 0 0}html.theme--catppuccin-latte .file.is-boxed.has-name .file-name{border-radius:0 0 .4em .4em;border-width:0 1px 1px}html.theme--catppuccin-latte .file.is-centered{justify-content:center}html.theme--catppuccin-latte .file.is-fullwidth .file-label{width:100%}html.theme--catppuccin-latte .file.is-fullwidth .file-name{flex-grow:1;max-width:none}html.theme--catppuccin-latte .file.is-right{justify-content:flex-end}html.theme--catppuccin-latte .file.is-right .file-cta{border-radius:0 .4em .4em 0}html.theme--catppuccin-latte .file.is-right .file-name{border-radius:.4em 0 0 .4em;border-width:1px 0 1px 1px;order:-1}html.theme--catppuccin-latte .file-label{align-items:stretch;display:flex;cursor:pointer;justify-content:flex-start;overflow:hidden;position:relative}html.theme--catppuccin-latte .file-label:hover .file-cta{background-color:#c5c9d5;color:#41445a}html.theme--catppuccin-latte .file-label:hover .file-name{border-color:#a5a9b8}html.theme--catppuccin-latte .file-label:active .file-cta{background-color:#bdc2cf;color:#41445a}html.theme--catppuccin-latte .file-label:active .file-name{border-color:#9ea2b3}html.theme--catppuccin-latte .file-input{height:100%;left:0;opacity:0;outline:none;position:absolute;top:0;width:100%}html.theme--catppuccin-latte .file-cta,html.theme--catppuccin-latte .file-name{border-color:#acb0be;border-radius:.4em;font-size:1em;padding-left:1em;padding-right:1em;white-space:nowrap}html.theme--catppuccin-latte .file-cta{background-color:#ccd0da;color:#4c4f69}html.theme--catppuccin-latte .file-name{border-color:#acb0be;border-style:solid;border-width:1px 1px 1px 0;display:block;max-width:16em;overflow:hidden;text-align:inherit;text-overflow:ellipsis}html.theme--catppuccin-latte .file-icon{align-items:center;display:flex;height:1em;justify-content:center;margin-right:.5em;width:1em}html.theme--catppuccin-latte .file-icon .fa{font-size:14px}html.theme--catppuccin-latte .label{color:#41445a;display:block;font-size:1rem;font-weight:700}html.theme--catppuccin-latte .label:not(:last-child){margin-bottom:0.5em}html.theme--catppuccin-latte .label.is-small,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.label{font-size:.75rem}html.theme--catppuccin-latte .label.is-medium{font-size:1.25rem}html.theme--catppuccin-latte .label.is-large{font-size:1.5rem}html.theme--catppuccin-latte .help{display:block;font-size:.75rem;margin-top:0.25rem}html.theme--catppuccin-latte .help.is-white{color:#fff}html.theme--catppuccin-latte .help.is-black{color:#0a0a0a}html.theme--catppuccin-latte .help.is-light{color:#f5f5f5}html.theme--catppuccin-latte .help.is-dark,html.theme--catppuccin-latte .content kbd.help{color:#ccd0da}html.theme--catppuccin-latte .help.is-primary,html.theme--catppuccin-latte .docstring>section>a.help.docs-sourcelink{color:#1e66f5}html.theme--catppuccin-latte .help.is-link{color:#1e66f5}html.theme--catppuccin-latte .help.is-info{color:#179299}html.theme--catppuccin-latte .help.is-success{color:#40a02b}html.theme--catppuccin-latte .help.is-warning{color:#df8e1d}html.theme--catppuccin-latte .help.is-danger{color:#d20f39}html.theme--catppuccin-latte .field:not(:last-child){margin-bottom:0.75rem}html.theme--catppuccin-latte .field.has-addons{display:flex;justify-content:flex-start}html.theme--catppuccin-latte .field.has-addons .control:not(:last-child){margin-right:-1px}html.theme--catppuccin-latte .field.has-addons .control:not(:first-child):not(:last-child) .button,html.theme--catppuccin-latte .field.has-addons .control:not(:first-child):not(:last-child) .input,html.theme--catppuccin-latte .field.has-addons .control:not(:first-child):not(:last-child) #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-latte #documenter .docs-sidebar .field.has-addons .control:not(:first-child):not(:last-child) form.docs-search>input,html.theme--catppuccin-latte .field.has-addons .control:not(:first-child):not(:last-child) .select select{border-radius:0}html.theme--catppuccin-latte .field.has-addons .control:first-child:not(:only-child) .button,html.theme--catppuccin-latte .field.has-addons .control:first-child:not(:only-child) .input,html.theme--catppuccin-latte .field.has-addons .control:first-child:not(:only-child) #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-latte #documenter .docs-sidebar .field.has-addons .control:first-child:not(:only-child) form.docs-search>input,html.theme--catppuccin-latte .field.has-addons .control:first-child:not(:only-child) .select select{border-bottom-right-radius:0;border-top-right-radius:0}html.theme--catppuccin-latte .field.has-addons .control:last-child:not(:only-child) .button,html.theme--catppuccin-latte .field.has-addons .control:last-child:not(:only-child) .input,html.theme--catppuccin-latte .field.has-addons .control:last-child:not(:only-child) #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-latte #documenter .docs-sidebar .field.has-addons .control:last-child:not(:only-child) form.docs-search>input,html.theme--catppuccin-latte .field.has-addons .control:last-child:not(:only-child) .select select{border-bottom-left-radius:0;border-top-left-radius:0}html.theme--catppuccin-latte .field.has-addons .control .button:not([disabled]):hover,html.theme--catppuccin-latte .field.has-addons .control .button.is-hovered:not([disabled]),html.theme--catppuccin-latte .field.has-addons .control .input:not([disabled]):hover,html.theme--catppuccin-latte .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):hover,html.theme--catppuccin-latte #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):hover,html.theme--catppuccin-latte .field.has-addons .control .input.is-hovered:not([disabled]),html.theme--catppuccin-latte .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-hovered:not([disabled]),html.theme--catppuccin-latte #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-hovered:not([disabled]),html.theme--catppuccin-latte .field.has-addons .control .select select:not([disabled]):hover,html.theme--catppuccin-latte .field.has-addons .control .select select.is-hovered:not([disabled]){z-index:2}html.theme--catppuccin-latte .field.has-addons .control .button:not([disabled]):focus,html.theme--catppuccin-latte .field.has-addons .control .button.is-focused:not([disabled]),html.theme--catppuccin-latte .field.has-addons .control .button:not([disabled]):active,html.theme--catppuccin-latte .field.has-addons .control .button.is-active:not([disabled]),html.theme--catppuccin-latte .field.has-addons .control .input:not([disabled]):focus,html.theme--catppuccin-latte .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):focus,html.theme--catppuccin-latte #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):focus,html.theme--catppuccin-latte .field.has-addons .control .input.is-focused:not([disabled]),html.theme--catppuccin-latte .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-focused:not([disabled]),html.theme--catppuccin-latte #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-focused:not([disabled]),html.theme--catppuccin-latte .field.has-addons .control .input:not([disabled]):active,html.theme--catppuccin-latte .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):active,html.theme--catppuccin-latte #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):active,html.theme--catppuccin-latte .field.has-addons .control .input.is-active:not([disabled]),html.theme--catppuccin-latte .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-active:not([disabled]),html.theme--catppuccin-latte #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-active:not([disabled]),html.theme--catppuccin-latte .field.has-addons .control .select select:not([disabled]):focus,html.theme--catppuccin-latte .field.has-addons .control .select select.is-focused:not([disabled]),html.theme--catppuccin-latte .field.has-addons .control .select select:not([disabled]):active,html.theme--catppuccin-latte .field.has-addons .control .select select.is-active:not([disabled]){z-index:3}html.theme--catppuccin-latte .field.has-addons .control .button:not([disabled]):focus:hover,html.theme--catppuccin-latte .field.has-addons .control .button.is-focused:not([disabled]):hover,html.theme--catppuccin-latte .field.has-addons .control .button:not([disabled]):active:hover,html.theme--catppuccin-latte .field.has-addons .control .button.is-active:not([disabled]):hover,html.theme--catppuccin-latte .field.has-addons .control .input:not([disabled]):focus:hover,html.theme--catppuccin-latte .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):focus:hover,html.theme--catppuccin-latte #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):focus:hover,html.theme--catppuccin-latte .field.has-addons .control .input.is-focused:not([disabled]):hover,html.theme--catppuccin-latte .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-focused:not([disabled]):hover,html.theme--catppuccin-latte #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-focused:not([disabled]):hover,html.theme--catppuccin-latte .field.has-addons .control .input:not([disabled]):active:hover,html.theme--catppuccin-latte .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):active:hover,html.theme--catppuccin-latte #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):active:hover,html.theme--catppuccin-latte .field.has-addons .control .input.is-active:not([disabled]):hover,html.theme--catppuccin-latte .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-active:not([disabled]):hover,html.theme--catppuccin-latte #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-active:not([disabled]):hover,html.theme--catppuccin-latte .field.has-addons .control .select select:not([disabled]):focus:hover,html.theme--catppuccin-latte .field.has-addons .control .select select.is-focused:not([disabled]):hover,html.theme--catppuccin-latte .field.has-addons .control .select select:not([disabled]):active:hover,html.theme--catppuccin-latte .field.has-addons .control .select select.is-active:not([disabled]):hover{z-index:4}html.theme--catppuccin-latte .field.has-addons .control.is-expanded{flex-grow:1;flex-shrink:1}html.theme--catppuccin-latte .field.has-addons.has-addons-centered{justify-content:center}html.theme--catppuccin-latte .field.has-addons.has-addons-right{justify-content:flex-end}html.theme--catppuccin-latte .field.has-addons.has-addons-fullwidth .control{flex-grow:1;flex-shrink:0}html.theme--catppuccin-latte .field.is-grouped{display:flex;justify-content:flex-start}html.theme--catppuccin-latte .field.is-grouped>.control{flex-shrink:0}html.theme--catppuccin-latte .field.is-grouped>.control:not(:last-child){margin-bottom:0;margin-right:.75rem}html.theme--catppuccin-latte .field.is-grouped>.control.is-expanded{flex-grow:1;flex-shrink:1}html.theme--catppuccin-latte .field.is-grouped.is-grouped-centered{justify-content:center}html.theme--catppuccin-latte .field.is-grouped.is-grouped-right{justify-content:flex-end}html.theme--catppuccin-latte .field.is-grouped.is-grouped-multiline{flex-wrap:wrap}html.theme--catppuccin-latte .field.is-grouped.is-grouped-multiline>.control:last-child,html.theme--catppuccin-latte .field.is-grouped.is-grouped-multiline>.control:not(:last-child){margin-bottom:0.75rem}html.theme--catppuccin-latte .field.is-grouped.is-grouped-multiline:last-child{margin-bottom:-0.75rem}html.theme--catppuccin-latte .field.is-grouped.is-grouped-multiline:not(:last-child){margin-bottom:0}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .field.is-horizontal{display:flex}}html.theme--catppuccin-latte .field-label .label{font-size:inherit}@media screen and (max-width: 768px){html.theme--catppuccin-latte .field-label{margin-bottom:0.5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .field-label{flex-basis:0;flex-grow:1;flex-shrink:0;margin-right:1.5rem;text-align:right}html.theme--catppuccin-latte .field-label.is-small,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.field-label{font-size:.75rem;padding-top:0.375em}html.theme--catppuccin-latte .field-label.is-normal{padding-top:0.375em}html.theme--catppuccin-latte .field-label.is-medium{font-size:1.25rem;padding-top:0.375em}html.theme--catppuccin-latte .field-label.is-large{font-size:1.5rem;padding-top:0.375em}}html.theme--catppuccin-latte .field-body .field .field{margin-bottom:0}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .field-body{display:flex;flex-basis:0;flex-grow:5;flex-shrink:1}html.theme--catppuccin-latte .field-body .field{margin-bottom:0}html.theme--catppuccin-latte .field-body>.field{flex-shrink:1}html.theme--catppuccin-latte .field-body>.field:not(.is-narrow){flex-grow:1}html.theme--catppuccin-latte .field-body>.field:not(:last-child){margin-right:.75rem}}html.theme--catppuccin-latte .control{box-sizing:border-box;clear:both;font-size:1rem;position:relative;text-align:inherit}html.theme--catppuccin-latte .control.has-icons-left .input:focus~.icon,html.theme--catppuccin-latte .control.has-icons-left #documenter .docs-sidebar form.docs-search>input:focus~.icon,html.theme--catppuccin-latte #documenter .docs-sidebar .control.has-icons-left form.docs-search>input:focus~.icon,html.theme--catppuccin-latte .control.has-icons-left .select:focus~.icon,html.theme--catppuccin-latte .control.has-icons-right .input:focus~.icon,html.theme--catppuccin-latte .control.has-icons-right #documenter .docs-sidebar form.docs-search>input:focus~.icon,html.theme--catppuccin-latte #documenter .docs-sidebar .control.has-icons-right form.docs-search>input:focus~.icon,html.theme--catppuccin-latte .control.has-icons-right .select:focus~.icon{color:#ccd0da}html.theme--catppuccin-latte .control.has-icons-left .input.is-small~.icon,html.theme--catppuccin-latte .control.has-icons-left #documenter .docs-sidebar form.docs-search>input~.icon,html.theme--catppuccin-latte #documenter .docs-sidebar .control.has-icons-left form.docs-search>input~.icon,html.theme--catppuccin-latte .control.has-icons-left .select.is-small~.icon,html.theme--catppuccin-latte .control.has-icons-right .input.is-small~.icon,html.theme--catppuccin-latte .control.has-icons-right #documenter .docs-sidebar form.docs-search>input~.icon,html.theme--catppuccin-latte #documenter .docs-sidebar .control.has-icons-right form.docs-search>input~.icon,html.theme--catppuccin-latte .control.has-icons-right .select.is-small~.icon{font-size:.75rem}html.theme--catppuccin-latte .control.has-icons-left .input.is-medium~.icon,html.theme--catppuccin-latte .control.has-icons-left #documenter .docs-sidebar form.docs-search>input.is-medium~.icon,html.theme--catppuccin-latte #documenter .docs-sidebar .control.has-icons-left form.docs-search>input.is-medium~.icon,html.theme--catppuccin-latte .control.has-icons-left .select.is-medium~.icon,html.theme--catppuccin-latte .control.has-icons-right .input.is-medium~.icon,html.theme--catppuccin-latte .control.has-icons-right #documenter .docs-sidebar form.docs-search>input.is-medium~.icon,html.theme--catppuccin-latte #documenter .docs-sidebar .control.has-icons-right form.docs-search>input.is-medium~.icon,html.theme--catppuccin-latte .control.has-icons-right .select.is-medium~.icon{font-size:1.25rem}html.theme--catppuccin-latte .control.has-icons-left .input.is-large~.icon,html.theme--catppuccin-latte .control.has-icons-left #documenter .docs-sidebar form.docs-search>input.is-large~.icon,html.theme--catppuccin-latte #documenter .docs-sidebar .control.has-icons-left form.docs-search>input.is-large~.icon,html.theme--catppuccin-latte .control.has-icons-left .select.is-large~.icon,html.theme--catppuccin-latte .control.has-icons-right .input.is-large~.icon,html.theme--catppuccin-latte .control.has-icons-right #documenter .docs-sidebar form.docs-search>input.is-large~.icon,html.theme--catppuccin-latte #documenter .docs-sidebar .control.has-icons-right form.docs-search>input.is-large~.icon,html.theme--catppuccin-latte .control.has-icons-right .select.is-large~.icon{font-size:1.5rem}html.theme--catppuccin-latte .control.has-icons-left .icon,html.theme--catppuccin-latte .control.has-icons-right .icon{color:#acb0be;height:2.5em;pointer-events:none;position:absolute;top:0;width:2.5em;z-index:4}html.theme--catppuccin-latte .control.has-icons-left .input,html.theme--catppuccin-latte .control.has-icons-left #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-latte #documenter .docs-sidebar .control.has-icons-left form.docs-search>input,html.theme--catppuccin-latte .control.has-icons-left .select select{padding-left:2.5em}html.theme--catppuccin-latte .control.has-icons-left .icon.is-left{left:0}html.theme--catppuccin-latte .control.has-icons-right .input,html.theme--catppuccin-latte .control.has-icons-right #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-latte #documenter .docs-sidebar .control.has-icons-right form.docs-search>input,html.theme--catppuccin-latte .control.has-icons-right .select select{padding-right:2.5em}html.theme--catppuccin-latte .control.has-icons-right .icon.is-right{right:0}html.theme--catppuccin-latte .control.is-loading::after{position:absolute !important;right:.625em;top:0.625em;z-index:4}html.theme--catppuccin-latte .control.is-loading.is-small:after,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-loading:after{font-size:.75rem}html.theme--catppuccin-latte .control.is-loading.is-medium:after{font-size:1.25rem}html.theme--catppuccin-latte .control.is-loading.is-large:after{font-size:1.5rem}html.theme--catppuccin-latte .breadcrumb{font-size:1rem;white-space:nowrap}html.theme--catppuccin-latte .breadcrumb a{align-items:center;color:#1e66f5;display:flex;justify-content:center;padding:0 .75em}html.theme--catppuccin-latte .breadcrumb a:hover{color:#04a5e5}html.theme--catppuccin-latte .breadcrumb li{align-items:center;display:flex}html.theme--catppuccin-latte .breadcrumb li:first-child a{padding-left:0}html.theme--catppuccin-latte .breadcrumb li.is-active a{color:#41445a;cursor:default;pointer-events:none}html.theme--catppuccin-latte .breadcrumb li+li::before{color:#9ca0b0;content:"\0002f"}html.theme--catppuccin-latte .breadcrumb ul,html.theme--catppuccin-latte .breadcrumb ol{align-items:flex-start;display:flex;flex-wrap:wrap;justify-content:flex-start}html.theme--catppuccin-latte .breadcrumb .icon:first-child{margin-right:.5em}html.theme--catppuccin-latte .breadcrumb .icon:last-child{margin-left:.5em}html.theme--catppuccin-latte .breadcrumb.is-centered ol,html.theme--catppuccin-latte .breadcrumb.is-centered ul{justify-content:center}html.theme--catppuccin-latte .breadcrumb.is-right ol,html.theme--catppuccin-latte .breadcrumb.is-right ul{justify-content:flex-end}html.theme--catppuccin-latte .breadcrumb.is-small,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.breadcrumb{font-size:.75rem}html.theme--catppuccin-latte .breadcrumb.is-medium{font-size:1.25rem}html.theme--catppuccin-latte .breadcrumb.is-large{font-size:1.5rem}html.theme--catppuccin-latte .breadcrumb.has-arrow-separator li+li::before{content:"\02192"}html.theme--catppuccin-latte .breadcrumb.has-bullet-separator li+li::before{content:"\02022"}html.theme--catppuccin-latte .breadcrumb.has-dot-separator li+li::before{content:"\000b7"}html.theme--catppuccin-latte .breadcrumb.has-succeeds-separator li+li::before{content:"\0227B"}html.theme--catppuccin-latte .card{background-color:#fff;border-radius:.25rem;box-shadow:#171717;color:#4c4f69;max-width:100%;position:relative}html.theme--catppuccin-latte .card-footer:first-child,html.theme--catppuccin-latte .card-content:first-child,html.theme--catppuccin-latte .card-header:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}html.theme--catppuccin-latte .card-footer:last-child,html.theme--catppuccin-latte .card-content:last-child,html.theme--catppuccin-latte .card-header:last-child{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}html.theme--catppuccin-latte .card-header{background-color:rgba(0,0,0,0);align-items:stretch;box-shadow:0 0.125em 0.25em rgba(10,10,10,0.1);display:flex}html.theme--catppuccin-latte .card-header-title{align-items:center;color:#41445a;display:flex;flex-grow:1;font-weight:700;padding:0.75rem 1rem}html.theme--catppuccin-latte .card-header-title.is-centered{justify-content:center}html.theme--catppuccin-latte .card-header-icon{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;color:currentColor;font-family:inherit;font-size:1em;margin:0;padding:0;align-items:center;cursor:pointer;display:flex;justify-content:center;padding:0.75rem 1rem}html.theme--catppuccin-latte .card-image{display:block;position:relative}html.theme--catppuccin-latte .card-image:first-child img{border-top-left-radius:.25rem;border-top-right-radius:.25rem}html.theme--catppuccin-latte .card-image:last-child img{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}html.theme--catppuccin-latte .card-content{background-color:rgba(0,0,0,0);padding:1.5rem}html.theme--catppuccin-latte .card-footer{background-color:rgba(0,0,0,0);border-top:1px solid #ededed;align-items:stretch;display:flex}html.theme--catppuccin-latte .card-footer-item{align-items:center;display:flex;flex-basis:0;flex-grow:1;flex-shrink:0;justify-content:center;padding:.75rem}html.theme--catppuccin-latte .card-footer-item:not(:last-child){border-right:1px solid #ededed}html.theme--catppuccin-latte .card .media:not(:last-child){margin-bottom:1.5rem}html.theme--catppuccin-latte .dropdown{display:inline-flex;position:relative;vertical-align:top}html.theme--catppuccin-latte .dropdown.is-active .dropdown-menu,html.theme--catppuccin-latte .dropdown.is-hoverable:hover .dropdown-menu{display:block}html.theme--catppuccin-latte .dropdown.is-right .dropdown-menu{left:auto;right:0}html.theme--catppuccin-latte .dropdown.is-up .dropdown-menu{bottom:100%;padding-bottom:4px;padding-top:initial;top:auto}html.theme--catppuccin-latte .dropdown-menu{display:none;left:0;min-width:12rem;padding-top:4px;position:absolute;top:100%;z-index:20}html.theme--catppuccin-latte .dropdown-content{background-color:#e6e9ef;border-radius:.4em;box-shadow:#171717;padding-bottom:.5rem;padding-top:.5rem}html.theme--catppuccin-latte .dropdown-item{color:#4c4f69;display:block;font-size:0.875rem;line-height:1.5;padding:0.375rem 1rem;position:relative}html.theme--catppuccin-latte a.dropdown-item,html.theme--catppuccin-latte button.dropdown-item{padding-right:3rem;text-align:inherit;white-space:nowrap;width:100%}html.theme--catppuccin-latte a.dropdown-item:hover,html.theme--catppuccin-latte button.dropdown-item:hover{background-color:#e6e9ef;color:#0a0a0a}html.theme--catppuccin-latte a.dropdown-item.is-active,html.theme--catppuccin-latte button.dropdown-item.is-active{background-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .dropdown-divider{background-color:#ededed;border:none;display:block;height:1px;margin:0.5rem 0}html.theme--catppuccin-latte .level{align-items:center;justify-content:space-between}html.theme--catppuccin-latte .level code{border-radius:.4em}html.theme--catppuccin-latte .level img{display:inline-block;vertical-align:top}html.theme--catppuccin-latte .level.is-mobile{display:flex}html.theme--catppuccin-latte .level.is-mobile .level-left,html.theme--catppuccin-latte .level.is-mobile .level-right{display:flex}html.theme--catppuccin-latte .level.is-mobile .level-left+.level-right{margin-top:0}html.theme--catppuccin-latte .level.is-mobile .level-item:not(:last-child){margin-bottom:0;margin-right:.75rem}html.theme--catppuccin-latte .level.is-mobile .level-item:not(.is-narrow){flex-grow:1}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .level{display:flex}html.theme--catppuccin-latte .level>.level-item:not(.is-narrow){flex-grow:1}}html.theme--catppuccin-latte .level-item{align-items:center;display:flex;flex-basis:auto;flex-grow:0;flex-shrink:0;justify-content:center}html.theme--catppuccin-latte .level-item .title,html.theme--catppuccin-latte .level-item .subtitle{margin-bottom:0}@media screen and (max-width: 768px){html.theme--catppuccin-latte .level-item:not(:last-child){margin-bottom:.75rem}}html.theme--catppuccin-latte .level-left,html.theme--catppuccin-latte .level-right{flex-basis:auto;flex-grow:0;flex-shrink:0}html.theme--catppuccin-latte .level-left .level-item.is-flexible,html.theme--catppuccin-latte .level-right .level-item.is-flexible{flex-grow:1}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .level-left .level-item:not(:last-child),html.theme--catppuccin-latte .level-right .level-item:not(:last-child){margin-right:.75rem}}html.theme--catppuccin-latte .level-left{align-items:center;justify-content:flex-start}@media screen and (max-width: 768px){html.theme--catppuccin-latte .level-left+.level-right{margin-top:1.5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .level-left{display:flex}}html.theme--catppuccin-latte .level-right{align-items:center;justify-content:flex-end}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .level-right{display:flex}}html.theme--catppuccin-latte .media{align-items:flex-start;display:flex;text-align:inherit}html.theme--catppuccin-latte .media .content:not(:last-child){margin-bottom:.75rem}html.theme--catppuccin-latte .media .media{border-top:1px solid rgba(172,176,190,0.5);display:flex;padding-top:.75rem}html.theme--catppuccin-latte .media .media .content:not(:last-child),html.theme--catppuccin-latte .media .media .control:not(:last-child){margin-bottom:.5rem}html.theme--catppuccin-latte .media .media .media{padding-top:.5rem}html.theme--catppuccin-latte .media .media .media+.media{margin-top:.5rem}html.theme--catppuccin-latte .media+.media{border-top:1px solid rgba(172,176,190,0.5);margin-top:1rem;padding-top:1rem}html.theme--catppuccin-latte .media.is-large+.media{margin-top:1.5rem;padding-top:1.5rem}html.theme--catppuccin-latte .media-left,html.theme--catppuccin-latte .media-right{flex-basis:auto;flex-grow:0;flex-shrink:0}html.theme--catppuccin-latte .media-left{margin-right:1rem}html.theme--catppuccin-latte .media-right{margin-left:1rem}html.theme--catppuccin-latte .media-content{flex-basis:auto;flex-grow:1;flex-shrink:1;text-align:inherit}@media screen and (max-width: 768px){html.theme--catppuccin-latte .media-content{overflow-x:auto}}html.theme--catppuccin-latte .menu{font-size:1rem}html.theme--catppuccin-latte .menu.is-small,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.menu{font-size:.75rem}html.theme--catppuccin-latte .menu.is-medium{font-size:1.25rem}html.theme--catppuccin-latte .menu.is-large{font-size:1.5rem}html.theme--catppuccin-latte .menu-list{line-height:1.25}html.theme--catppuccin-latte .menu-list a{border-radius:3px;color:#4c4f69;display:block;padding:0.5em 0.75em}html.theme--catppuccin-latte .menu-list a:hover{background-color:#e6e9ef;color:#41445a}html.theme--catppuccin-latte .menu-list a.is-active{background-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .menu-list li ul{border-left:1px solid #acb0be;margin:.75em;padding-left:.75em}html.theme--catppuccin-latte .menu-label{color:#616587;font-size:.75em;letter-spacing:.1em;text-transform:uppercase}html.theme--catppuccin-latte .menu-label:not(:first-child){margin-top:1em}html.theme--catppuccin-latte .menu-label:not(:last-child){margin-bottom:1em}html.theme--catppuccin-latte .message{background-color:#e6e9ef;border-radius:.4em;font-size:1rem}html.theme--catppuccin-latte .message strong{color:currentColor}html.theme--catppuccin-latte .message a:not(.button):not(.tag):not(.dropdown-item){color:currentColor;text-decoration:underline}html.theme--catppuccin-latte .message.is-small,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.message{font-size:.75rem}html.theme--catppuccin-latte .message.is-medium{font-size:1.25rem}html.theme--catppuccin-latte .message.is-large{font-size:1.5rem}html.theme--catppuccin-latte .message.is-white{background-color:#fff}html.theme--catppuccin-latte .message.is-white .message-header{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-latte .message.is-white .message-body{border-color:#fff}html.theme--catppuccin-latte .message.is-black{background-color:#fafafa}html.theme--catppuccin-latte .message.is-black .message-header{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-latte .message.is-black .message-body{border-color:#0a0a0a}html.theme--catppuccin-latte .message.is-light{background-color:#fafafa}html.theme--catppuccin-latte .message.is-light .message-header{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .message.is-light .message-body{border-color:#f5f5f5}html.theme--catppuccin-latte .message.is-dark,html.theme--catppuccin-latte .content kbd.message{background-color:#f9fafb}html.theme--catppuccin-latte .message.is-dark .message-header,html.theme--catppuccin-latte .content kbd.message .message-header{background-color:#ccd0da;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .message.is-dark .message-body,html.theme--catppuccin-latte .content kbd.message .message-body{border-color:#ccd0da}html.theme--catppuccin-latte .message.is-primary,html.theme--catppuccin-latte .docstring>section>a.message.docs-sourcelink{background-color:#ebf2fe}html.theme--catppuccin-latte .message.is-primary .message-header,html.theme--catppuccin-latte .docstring>section>a.message.docs-sourcelink .message-header{background-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .message.is-primary .message-body,html.theme--catppuccin-latte .docstring>section>a.message.docs-sourcelink .message-body{border-color:#1e66f5;color:#0a52e1}html.theme--catppuccin-latte .message.is-link{background-color:#ebf2fe}html.theme--catppuccin-latte .message.is-link .message-header{background-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .message.is-link .message-body{border-color:#1e66f5;color:#0a52e1}html.theme--catppuccin-latte .message.is-info{background-color:#edfcfc}html.theme--catppuccin-latte .message.is-info .message-header{background-color:#179299;color:#fff}html.theme--catppuccin-latte .message.is-info .message-body{border-color:#179299;color:#1cb2ba}html.theme--catppuccin-latte .message.is-success{background-color:#f1fbef}html.theme--catppuccin-latte .message.is-success .message-header{background-color:#40a02b;color:#fff}html.theme--catppuccin-latte .message.is-success .message-body{border-color:#40a02b;color:#40a12b}html.theme--catppuccin-latte .message.is-warning{background-color:#fdf6ed}html.theme--catppuccin-latte .message.is-warning .message-header{background-color:#df8e1d;color:#fff}html.theme--catppuccin-latte .message.is-warning .message-body{border-color:#df8e1d;color:#9e6515}html.theme--catppuccin-latte .message.is-danger{background-color:#feecf0}html.theme--catppuccin-latte .message.is-danger .message-header{background-color:#d20f39;color:#fff}html.theme--catppuccin-latte .message.is-danger .message-body{border-color:#d20f39;color:#e9113f}html.theme--catppuccin-latte .message-header{align-items:center;background-color:#4c4f69;border-radius:.4em .4em 0 0;color:#fff;display:flex;font-weight:700;justify-content:space-between;line-height:1.25;padding:0.75em 1em;position:relative}html.theme--catppuccin-latte .message-header .delete{flex-grow:0;flex-shrink:0;margin-left:.75em}html.theme--catppuccin-latte .message-header+.message-body{border-width:0;border-top-left-radius:0;border-top-right-radius:0}html.theme--catppuccin-latte .message-body{border-color:#acb0be;border-radius:.4em;border-style:solid;border-width:0 0 0 4px;color:#4c4f69;padding:1.25em 1.5em}html.theme--catppuccin-latte .message-body code,html.theme--catppuccin-latte .message-body pre{background-color:#fff}html.theme--catppuccin-latte .message-body pre code{background-color:rgba(0,0,0,0)}html.theme--catppuccin-latte .modal{align-items:center;display:none;flex-direction:column;justify-content:center;overflow:hidden;position:fixed;z-index:40}html.theme--catppuccin-latte .modal.is-active{display:flex}html.theme--catppuccin-latte .modal-background{background-color:rgba(10,10,10,0.86)}html.theme--catppuccin-latte .modal-content,html.theme--catppuccin-latte .modal-card{margin:0 20px;max-height:calc(100vh - 160px);overflow:auto;position:relative;width:100%}@media screen and (min-width: 769px){html.theme--catppuccin-latte .modal-content,html.theme--catppuccin-latte .modal-card{margin:0 auto;max-height:calc(100vh - 40px);width:640px}}html.theme--catppuccin-latte .modal-close{background:none;height:40px;position:fixed;right:20px;top:20px;width:40px}html.theme--catppuccin-latte .modal-card{display:flex;flex-direction:column;max-height:calc(100vh - 40px);overflow:hidden;-ms-overflow-y:visible}html.theme--catppuccin-latte .modal-card-head,html.theme--catppuccin-latte .modal-card-foot{align-items:center;background-color:#e6e9ef;display:flex;flex-shrink:0;justify-content:flex-start;padding:20px;position:relative}html.theme--catppuccin-latte .modal-card-head{border-bottom:1px solid #acb0be;border-top-left-radius:8px;border-top-right-radius:8px}html.theme--catppuccin-latte .modal-card-title{color:#4c4f69;flex-grow:1;flex-shrink:0;font-size:1.5rem;line-height:1}html.theme--catppuccin-latte .modal-card-foot{border-bottom-left-radius:8px;border-bottom-right-radius:8px;border-top:1px solid #acb0be}html.theme--catppuccin-latte .modal-card-foot .button:not(:last-child){margin-right:.5em}html.theme--catppuccin-latte .modal-card-body{-webkit-overflow-scrolling:touch;background-color:#eff1f5;flex-grow:1;flex-shrink:1;overflow:auto;padding:20px}html.theme--catppuccin-latte .navbar{background-color:#1e66f5;min-height:4rem;position:relative;z-index:30}html.theme--catppuccin-latte .navbar.is-white{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-latte .navbar.is-white .navbar-brand>.navbar-item,html.theme--catppuccin-latte .navbar.is-white .navbar-brand .navbar-link{color:#0a0a0a}html.theme--catppuccin-latte .navbar.is-white .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-white .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-white .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-white .navbar-brand .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-white .navbar-brand .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-white .navbar-brand .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}html.theme--catppuccin-latte .navbar.is-white .navbar-brand .navbar-link::after{border-color:#0a0a0a}html.theme--catppuccin-latte .navbar.is-white .navbar-burger{color:#0a0a0a}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .navbar.is-white .navbar-start>.navbar-item,html.theme--catppuccin-latte .navbar.is-white .navbar-start .navbar-link,html.theme--catppuccin-latte .navbar.is-white .navbar-end>.navbar-item,html.theme--catppuccin-latte .navbar.is-white .navbar-end .navbar-link{color:#0a0a0a}html.theme--catppuccin-latte .navbar.is-white .navbar-start>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-white .navbar-start>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-white .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-white .navbar-start .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-white .navbar-start .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-white .navbar-start .navbar-link.is-active,html.theme--catppuccin-latte .navbar.is-white .navbar-end>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-white .navbar-end>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-white .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-white .navbar-end .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-white .navbar-end .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-white .navbar-end .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}html.theme--catppuccin-latte .navbar.is-white .navbar-start .navbar-link::after,html.theme--catppuccin-latte .navbar.is-white .navbar-end .navbar-link::after{border-color:#0a0a0a}html.theme--catppuccin-latte .navbar.is-white .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-latte .navbar.is-white .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-latte .navbar.is-white .navbar-item.has-dropdown.is-active .navbar-link{background-color:#f2f2f2;color:#0a0a0a}html.theme--catppuccin-latte .navbar.is-white .navbar-dropdown a.navbar-item.is-active{background-color:#fff;color:#0a0a0a}}html.theme--catppuccin-latte .navbar.is-black{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-latte .navbar.is-black .navbar-brand>.navbar-item,html.theme--catppuccin-latte .navbar.is-black .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-latte .navbar.is-black .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-black .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-black .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-black .navbar-brand .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-black .navbar-brand .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-black .navbar-brand .navbar-link.is-active{background-color:#000;color:#fff}html.theme--catppuccin-latte .navbar.is-black .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-latte .navbar.is-black .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .navbar.is-black .navbar-start>.navbar-item,html.theme--catppuccin-latte .navbar.is-black .navbar-start .navbar-link,html.theme--catppuccin-latte .navbar.is-black .navbar-end>.navbar-item,html.theme--catppuccin-latte .navbar.is-black .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-latte .navbar.is-black .navbar-start>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-black .navbar-start>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-black .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-black .navbar-start .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-black .navbar-start .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-black .navbar-start .navbar-link.is-active,html.theme--catppuccin-latte .navbar.is-black .navbar-end>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-black .navbar-end>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-black .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-black .navbar-end .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-black .navbar-end .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-black .navbar-end .navbar-link.is-active{background-color:#000;color:#fff}html.theme--catppuccin-latte .navbar.is-black .navbar-start .navbar-link::after,html.theme--catppuccin-latte .navbar.is-black .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-latte .navbar.is-black .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-latte .navbar.is-black .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-latte .navbar.is-black .navbar-item.has-dropdown.is-active .navbar-link{background-color:#000;color:#fff}html.theme--catppuccin-latte .navbar.is-black .navbar-dropdown a.navbar-item.is-active{background-color:#0a0a0a;color:#fff}}html.theme--catppuccin-latte .navbar.is-light{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .navbar.is-light .navbar-brand>.navbar-item,html.theme--catppuccin-latte .navbar.is-light .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .navbar.is-light .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-light .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-light .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-light .navbar-brand .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-light .navbar-brand .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-light .navbar-brand .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .navbar.is-light .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .navbar.is-light .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .navbar.is-light .navbar-start>.navbar-item,html.theme--catppuccin-latte .navbar.is-light .navbar-start .navbar-link,html.theme--catppuccin-latte .navbar.is-light .navbar-end>.navbar-item,html.theme--catppuccin-latte .navbar.is-light .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .navbar.is-light .navbar-start>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-light .navbar-start>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-light .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-light .navbar-start .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-light .navbar-start .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-light .navbar-start .navbar-link.is-active,html.theme--catppuccin-latte .navbar.is-light .navbar-end>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-light .navbar-end>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-light .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-light .navbar-end .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-light .navbar-end .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-light .navbar-end .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .navbar.is-light .navbar-start .navbar-link::after,html.theme--catppuccin-latte .navbar.is-light .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .navbar.is-light .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-latte .navbar.is-light .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-latte .navbar.is-light .navbar-item.has-dropdown.is-active .navbar-link{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .navbar.is-light .navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}}html.theme--catppuccin-latte .navbar.is-dark,html.theme--catppuccin-latte .content kbd.navbar{background-color:#ccd0da;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .navbar.is-dark .navbar-brand>.navbar-item,html.theme--catppuccin-latte .content kbd.navbar .navbar-brand>.navbar-item,html.theme--catppuccin-latte .navbar.is-dark .navbar-brand .navbar-link,html.theme--catppuccin-latte .content kbd.navbar .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .navbar.is-dark .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-latte .content kbd.navbar .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-dark .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-latte .content kbd.navbar .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-dark .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-latte .content kbd.navbar .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-dark .navbar-brand .navbar-link:focus,html.theme--catppuccin-latte .content kbd.navbar .navbar-brand .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-dark .navbar-brand .navbar-link:hover,html.theme--catppuccin-latte .content kbd.navbar .navbar-brand .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-dark .navbar-brand .navbar-link.is-active,html.theme--catppuccin-latte .content kbd.navbar .navbar-brand .navbar-link.is-active{background-color:#bdc2cf;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .navbar.is-dark .navbar-brand .navbar-link::after,html.theme--catppuccin-latte .content kbd.navbar .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .navbar.is-dark .navbar-burger,html.theme--catppuccin-latte .content kbd.navbar .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .navbar.is-dark .navbar-start>.navbar-item,html.theme--catppuccin-latte .content kbd.navbar .navbar-start>.navbar-item,html.theme--catppuccin-latte .navbar.is-dark .navbar-start .navbar-link,html.theme--catppuccin-latte .content kbd.navbar .navbar-start .navbar-link,html.theme--catppuccin-latte .navbar.is-dark .navbar-end>.navbar-item,html.theme--catppuccin-latte .content kbd.navbar .navbar-end>.navbar-item,html.theme--catppuccin-latte .navbar.is-dark .navbar-end .navbar-link,html.theme--catppuccin-latte .content kbd.navbar .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .navbar.is-dark .navbar-start>a.navbar-item:focus,html.theme--catppuccin-latte .content kbd.navbar .navbar-start>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-dark .navbar-start>a.navbar-item:hover,html.theme--catppuccin-latte .content kbd.navbar .navbar-start>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-dark .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-latte .content kbd.navbar .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-dark .navbar-start .navbar-link:focus,html.theme--catppuccin-latte .content kbd.navbar .navbar-start .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-dark .navbar-start .navbar-link:hover,html.theme--catppuccin-latte .content kbd.navbar .navbar-start .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-dark .navbar-start .navbar-link.is-active,html.theme--catppuccin-latte .content kbd.navbar .navbar-start .navbar-link.is-active,html.theme--catppuccin-latte .navbar.is-dark .navbar-end>a.navbar-item:focus,html.theme--catppuccin-latte .content kbd.navbar .navbar-end>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-dark .navbar-end>a.navbar-item:hover,html.theme--catppuccin-latte .content kbd.navbar .navbar-end>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-dark .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-latte .content kbd.navbar .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-dark .navbar-end .navbar-link:focus,html.theme--catppuccin-latte .content kbd.navbar .navbar-end .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-dark .navbar-end .navbar-link:hover,html.theme--catppuccin-latte .content kbd.navbar .navbar-end .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-dark .navbar-end .navbar-link.is-active,html.theme--catppuccin-latte .content kbd.navbar .navbar-end .navbar-link.is-active{background-color:#bdc2cf;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .navbar.is-dark .navbar-start .navbar-link::after,html.theme--catppuccin-latte .content kbd.navbar .navbar-start .navbar-link::after,html.theme--catppuccin-latte .navbar.is-dark .navbar-end .navbar-link::after,html.theme--catppuccin-latte .content kbd.navbar .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .navbar.is-dark .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-latte .content kbd.navbar .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-latte .navbar.is-dark .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-latte .content kbd.navbar .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-latte .navbar.is-dark .navbar-item.has-dropdown.is-active .navbar-link,html.theme--catppuccin-latte .content kbd.navbar .navbar-item.has-dropdown.is-active .navbar-link{background-color:#bdc2cf;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .navbar.is-dark .navbar-dropdown a.navbar-item.is-active,html.theme--catppuccin-latte .content kbd.navbar .navbar-dropdown a.navbar-item.is-active{background-color:#ccd0da;color:rgba(0,0,0,0.7)}}html.theme--catppuccin-latte .navbar.is-primary,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink{background-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .navbar.is-primary .navbar-brand>.navbar-item,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-brand>.navbar-item,html.theme--catppuccin-latte .navbar.is-primary .navbar-brand .navbar-link,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-latte .navbar.is-primary .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-primary .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-primary .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-primary .navbar-brand .navbar-link:focus,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-primary .navbar-brand .navbar-link:hover,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-primary .navbar-brand .navbar-link.is-active,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link.is-active{background-color:#0b57ef;color:#fff}html.theme--catppuccin-latte .navbar.is-primary .navbar-brand .navbar-link::after,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-latte .navbar.is-primary .navbar-burger,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .navbar.is-primary .navbar-start>.navbar-item,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-start>.navbar-item,html.theme--catppuccin-latte .navbar.is-primary .navbar-start .navbar-link,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link,html.theme--catppuccin-latte .navbar.is-primary .navbar-end>.navbar-item,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-end>.navbar-item,html.theme--catppuccin-latte .navbar.is-primary .navbar-end .navbar-link,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-latte .navbar.is-primary .navbar-start>a.navbar-item:focus,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-primary .navbar-start>a.navbar-item:hover,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-primary .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-primary .navbar-start .navbar-link:focus,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-primary .navbar-start .navbar-link:hover,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-primary .navbar-start .navbar-link.is-active,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link.is-active,html.theme--catppuccin-latte .navbar.is-primary .navbar-end>a.navbar-item:focus,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-primary .navbar-end>a.navbar-item:hover,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-primary .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-primary .navbar-end .navbar-link:focus,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-primary .navbar-end .navbar-link:hover,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-primary .navbar-end .navbar-link.is-active,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link.is-active{background-color:#0b57ef;color:#fff}html.theme--catppuccin-latte .navbar.is-primary .navbar-start .navbar-link::after,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link::after,html.theme--catppuccin-latte .navbar.is-primary .navbar-end .navbar-link::after,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-latte .navbar.is-primary .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-latte .navbar.is-primary .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-latte .navbar.is-primary .navbar-item.has-dropdown.is-active .navbar-link,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown.is-active .navbar-link{background-color:#0b57ef;color:#fff}html.theme--catppuccin-latte .navbar.is-primary .navbar-dropdown a.navbar-item.is-active,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-dropdown a.navbar-item.is-active{background-color:#1e66f5;color:#fff}}html.theme--catppuccin-latte .navbar.is-link{background-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .navbar.is-link .navbar-brand>.navbar-item,html.theme--catppuccin-latte .navbar.is-link .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-latte .navbar.is-link .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-link .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-link .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-link .navbar-brand .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-link .navbar-brand .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-link .navbar-brand .navbar-link.is-active{background-color:#0b57ef;color:#fff}html.theme--catppuccin-latte .navbar.is-link .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-latte .navbar.is-link .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .navbar.is-link .navbar-start>.navbar-item,html.theme--catppuccin-latte .navbar.is-link .navbar-start .navbar-link,html.theme--catppuccin-latte .navbar.is-link .navbar-end>.navbar-item,html.theme--catppuccin-latte .navbar.is-link .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-latte .navbar.is-link .navbar-start>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-link .navbar-start>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-link .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-link .navbar-start .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-link .navbar-start .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-link .navbar-start .navbar-link.is-active,html.theme--catppuccin-latte .navbar.is-link .navbar-end>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-link .navbar-end>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-link .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-link .navbar-end .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-link .navbar-end .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-link .navbar-end .navbar-link.is-active{background-color:#0b57ef;color:#fff}html.theme--catppuccin-latte .navbar.is-link .navbar-start .navbar-link::after,html.theme--catppuccin-latte .navbar.is-link .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-latte .navbar.is-link .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-latte .navbar.is-link .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-latte .navbar.is-link .navbar-item.has-dropdown.is-active .navbar-link{background-color:#0b57ef;color:#fff}html.theme--catppuccin-latte .navbar.is-link .navbar-dropdown a.navbar-item.is-active{background-color:#1e66f5;color:#fff}}html.theme--catppuccin-latte .navbar.is-info{background-color:#179299;color:#fff}html.theme--catppuccin-latte .navbar.is-info .navbar-brand>.navbar-item,html.theme--catppuccin-latte .navbar.is-info .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-latte .navbar.is-info .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-info .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-info .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-info .navbar-brand .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-info .navbar-brand .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-info .navbar-brand .navbar-link.is-active{background-color:#147d83;color:#fff}html.theme--catppuccin-latte .navbar.is-info .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-latte .navbar.is-info .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .navbar.is-info .navbar-start>.navbar-item,html.theme--catppuccin-latte .navbar.is-info .navbar-start .navbar-link,html.theme--catppuccin-latte .navbar.is-info .navbar-end>.navbar-item,html.theme--catppuccin-latte .navbar.is-info .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-latte .navbar.is-info .navbar-start>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-info .navbar-start>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-info .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-info .navbar-start .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-info .navbar-start .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-info .navbar-start .navbar-link.is-active,html.theme--catppuccin-latte .navbar.is-info .navbar-end>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-info .navbar-end>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-info .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-info .navbar-end .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-info .navbar-end .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-info .navbar-end .navbar-link.is-active{background-color:#147d83;color:#fff}html.theme--catppuccin-latte .navbar.is-info .navbar-start .navbar-link::after,html.theme--catppuccin-latte .navbar.is-info .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-latte .navbar.is-info .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-latte .navbar.is-info .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-latte .navbar.is-info .navbar-item.has-dropdown.is-active .navbar-link{background-color:#147d83;color:#fff}html.theme--catppuccin-latte .navbar.is-info .navbar-dropdown a.navbar-item.is-active{background-color:#179299;color:#fff}}html.theme--catppuccin-latte .navbar.is-success{background-color:#40a02b;color:#fff}html.theme--catppuccin-latte .navbar.is-success .navbar-brand>.navbar-item,html.theme--catppuccin-latte .navbar.is-success .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-latte .navbar.is-success .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-success .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-success .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-success .navbar-brand .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-success .navbar-brand .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-success .navbar-brand .navbar-link.is-active{background-color:#388c26;color:#fff}html.theme--catppuccin-latte .navbar.is-success .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-latte .navbar.is-success .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .navbar.is-success .navbar-start>.navbar-item,html.theme--catppuccin-latte .navbar.is-success .navbar-start .navbar-link,html.theme--catppuccin-latte .navbar.is-success .navbar-end>.navbar-item,html.theme--catppuccin-latte .navbar.is-success .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-latte .navbar.is-success .navbar-start>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-success .navbar-start>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-success .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-success .navbar-start .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-success .navbar-start .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-success .navbar-start .navbar-link.is-active,html.theme--catppuccin-latte .navbar.is-success .navbar-end>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-success .navbar-end>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-success .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-success .navbar-end .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-success .navbar-end .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-success .navbar-end .navbar-link.is-active{background-color:#388c26;color:#fff}html.theme--catppuccin-latte .navbar.is-success .navbar-start .navbar-link::after,html.theme--catppuccin-latte .navbar.is-success .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-latte .navbar.is-success .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-latte .navbar.is-success .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-latte .navbar.is-success .navbar-item.has-dropdown.is-active .navbar-link{background-color:#388c26;color:#fff}html.theme--catppuccin-latte .navbar.is-success .navbar-dropdown a.navbar-item.is-active{background-color:#40a02b;color:#fff}}html.theme--catppuccin-latte .navbar.is-warning{background-color:#df8e1d;color:#fff}html.theme--catppuccin-latte .navbar.is-warning .navbar-brand>.navbar-item,html.theme--catppuccin-latte .navbar.is-warning .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-latte .navbar.is-warning .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-warning .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-warning .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-warning .navbar-brand .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-warning .navbar-brand .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-warning .navbar-brand .navbar-link.is-active{background-color:#c8801a;color:#fff}html.theme--catppuccin-latte .navbar.is-warning .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-latte .navbar.is-warning .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .navbar.is-warning .navbar-start>.navbar-item,html.theme--catppuccin-latte .navbar.is-warning .navbar-start .navbar-link,html.theme--catppuccin-latte .navbar.is-warning .navbar-end>.navbar-item,html.theme--catppuccin-latte .navbar.is-warning .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-latte .navbar.is-warning .navbar-start>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-warning .navbar-start>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-warning .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-warning .navbar-start .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-warning .navbar-start .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-warning .navbar-start .navbar-link.is-active,html.theme--catppuccin-latte .navbar.is-warning .navbar-end>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-warning .navbar-end>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-warning .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-warning .navbar-end .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-warning .navbar-end .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-warning .navbar-end .navbar-link.is-active{background-color:#c8801a;color:#fff}html.theme--catppuccin-latte .navbar.is-warning .navbar-start .navbar-link::after,html.theme--catppuccin-latte .navbar.is-warning .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-latte .navbar.is-warning .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-latte .navbar.is-warning .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-latte .navbar.is-warning .navbar-item.has-dropdown.is-active .navbar-link{background-color:#c8801a;color:#fff}html.theme--catppuccin-latte .navbar.is-warning .navbar-dropdown a.navbar-item.is-active{background-color:#df8e1d;color:#fff}}html.theme--catppuccin-latte .navbar.is-danger{background-color:#d20f39;color:#fff}html.theme--catppuccin-latte .navbar.is-danger .navbar-brand>.navbar-item,html.theme--catppuccin-latte .navbar.is-danger .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-latte .navbar.is-danger .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-danger .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-danger .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-danger .navbar-brand .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-danger .navbar-brand .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-danger .navbar-brand .navbar-link.is-active{background-color:#ba0d33;color:#fff}html.theme--catppuccin-latte .navbar.is-danger .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-latte .navbar.is-danger .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .navbar.is-danger .navbar-start>.navbar-item,html.theme--catppuccin-latte .navbar.is-danger .navbar-start .navbar-link,html.theme--catppuccin-latte .navbar.is-danger .navbar-end>.navbar-item,html.theme--catppuccin-latte .navbar.is-danger .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-latte .navbar.is-danger .navbar-start>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-danger .navbar-start>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-danger .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-danger .navbar-start .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-danger .navbar-start .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-danger .navbar-start .navbar-link.is-active,html.theme--catppuccin-latte .navbar.is-danger .navbar-end>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-danger .navbar-end>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-danger .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-danger .navbar-end .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-danger .navbar-end .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-danger .navbar-end .navbar-link.is-active{background-color:#ba0d33;color:#fff}html.theme--catppuccin-latte .navbar.is-danger .navbar-start .navbar-link::after,html.theme--catppuccin-latte .navbar.is-danger .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-latte .navbar.is-danger .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-latte .navbar.is-danger .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-latte .navbar.is-danger .navbar-item.has-dropdown.is-active .navbar-link{background-color:#ba0d33;color:#fff}html.theme--catppuccin-latte .navbar.is-danger .navbar-dropdown a.navbar-item.is-active{background-color:#d20f39;color:#fff}}html.theme--catppuccin-latte .navbar>.container{align-items:stretch;display:flex;min-height:4rem;width:100%}html.theme--catppuccin-latte .navbar.has-shadow{box-shadow:0 2px 0 0 #e6e9ef}html.theme--catppuccin-latte .navbar.is-fixed-bottom,html.theme--catppuccin-latte .navbar.is-fixed-top{left:0;position:fixed;right:0;z-index:30}html.theme--catppuccin-latte .navbar.is-fixed-bottom{bottom:0}html.theme--catppuccin-latte .navbar.is-fixed-bottom.has-shadow{box-shadow:0 -2px 0 0 #e6e9ef}html.theme--catppuccin-latte .navbar.is-fixed-top{top:0}html.theme--catppuccin-latte html.has-navbar-fixed-top,html.theme--catppuccin-latte body.has-navbar-fixed-top{padding-top:4rem}html.theme--catppuccin-latte html.has-navbar-fixed-bottom,html.theme--catppuccin-latte body.has-navbar-fixed-bottom{padding-bottom:4rem}html.theme--catppuccin-latte .navbar-brand,html.theme--catppuccin-latte .navbar-tabs{align-items:stretch;display:flex;flex-shrink:0;min-height:4rem}html.theme--catppuccin-latte .navbar-brand a.navbar-item:focus,html.theme--catppuccin-latte .navbar-brand a.navbar-item:hover{background-color:transparent}html.theme--catppuccin-latte .navbar-tabs{-webkit-overflow-scrolling:touch;max-width:100vw;overflow-x:auto;overflow-y:hidden}html.theme--catppuccin-latte .navbar-burger{color:#4c4f69;-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;cursor:pointer;display:block;height:4rem;position:relative;width:4rem;margin-left:auto}html.theme--catppuccin-latte .navbar-burger span{background-color:currentColor;display:block;height:1px;left:calc(50% - 8px);position:absolute;transform-origin:center;transition-duration:86ms;transition-property:background-color, opacity, transform;transition-timing-function:ease-out;width:16px}html.theme--catppuccin-latte .navbar-burger span:nth-child(1){top:calc(50% - 6px)}html.theme--catppuccin-latte .navbar-burger span:nth-child(2){top:calc(50% - 1px)}html.theme--catppuccin-latte .navbar-burger span:nth-child(3){top:calc(50% + 4px)}html.theme--catppuccin-latte .navbar-burger:hover{background-color:rgba(0,0,0,0.05)}html.theme--catppuccin-latte .navbar-burger.is-active span:nth-child(1){transform:translateY(5px) rotate(45deg)}html.theme--catppuccin-latte .navbar-burger.is-active span:nth-child(2){opacity:0}html.theme--catppuccin-latte .navbar-burger.is-active span:nth-child(3){transform:translateY(-5px) rotate(-45deg)}html.theme--catppuccin-latte .navbar-menu{display:none}html.theme--catppuccin-latte .navbar-item,html.theme--catppuccin-latte .navbar-link{color:#4c4f69;display:block;line-height:1.5;padding:0.5rem 0.75rem;position:relative}html.theme--catppuccin-latte .navbar-item .icon:only-child,html.theme--catppuccin-latte .navbar-link .icon:only-child{margin-left:-0.25rem;margin-right:-0.25rem}html.theme--catppuccin-latte a.navbar-item,html.theme--catppuccin-latte .navbar-link{cursor:pointer}html.theme--catppuccin-latte a.navbar-item:focus,html.theme--catppuccin-latte a.navbar-item:focus-within,html.theme--catppuccin-latte a.navbar-item:hover,html.theme--catppuccin-latte a.navbar-item.is-active,html.theme--catppuccin-latte .navbar-link:focus,html.theme--catppuccin-latte .navbar-link:focus-within,html.theme--catppuccin-latte .navbar-link:hover,html.theme--catppuccin-latte .navbar-link.is-active{background-color:rgba(0,0,0,0);color:#1e66f5}html.theme--catppuccin-latte .navbar-item{flex-grow:0;flex-shrink:0}html.theme--catppuccin-latte .navbar-item img{max-height:1.75rem}html.theme--catppuccin-latte .navbar-item.has-dropdown{padding:0}html.theme--catppuccin-latte .navbar-item.is-expanded{flex-grow:1;flex-shrink:1}html.theme--catppuccin-latte .navbar-item.is-tab{border-bottom:1px solid transparent;min-height:4rem;padding-bottom:calc(0.5rem - 1px)}html.theme--catppuccin-latte .navbar-item.is-tab:focus,html.theme--catppuccin-latte .navbar-item.is-tab:hover{background-color:rgba(0,0,0,0);border-bottom-color:#1e66f5}html.theme--catppuccin-latte .navbar-item.is-tab.is-active{background-color:rgba(0,0,0,0);border-bottom-color:#1e66f5;border-bottom-style:solid;border-bottom-width:3px;color:#1e66f5;padding-bottom:calc(0.5rem - 3px)}html.theme--catppuccin-latte .navbar-content{flex-grow:1;flex-shrink:1}html.theme--catppuccin-latte .navbar-link:not(.is-arrowless){padding-right:2.5em}html.theme--catppuccin-latte .navbar-link:not(.is-arrowless)::after{border-color:#fff;margin-top:-0.375em;right:1.125em}html.theme--catppuccin-latte .navbar-dropdown{font-size:0.875rem;padding-bottom:0.5rem;padding-top:0.5rem}html.theme--catppuccin-latte .navbar-dropdown .navbar-item{padding-left:1.5rem;padding-right:1.5rem}html.theme--catppuccin-latte .navbar-divider{background-color:rgba(0,0,0,0.2);border:none;display:none;height:2px;margin:0.5rem 0}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .navbar>.container{display:block}html.theme--catppuccin-latte .navbar-brand .navbar-item,html.theme--catppuccin-latte .navbar-tabs .navbar-item{align-items:center;display:flex}html.theme--catppuccin-latte .navbar-link::after{display:none}html.theme--catppuccin-latte .navbar-menu{background-color:#1e66f5;box-shadow:0 8px 16px rgba(10,10,10,0.1);padding:0.5rem 0}html.theme--catppuccin-latte .navbar-menu.is-active{display:block}html.theme--catppuccin-latte .navbar.is-fixed-bottom-touch,html.theme--catppuccin-latte .navbar.is-fixed-top-touch{left:0;position:fixed;right:0;z-index:30}html.theme--catppuccin-latte .navbar.is-fixed-bottom-touch{bottom:0}html.theme--catppuccin-latte .navbar.is-fixed-bottom-touch.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,0.1)}html.theme--catppuccin-latte .navbar.is-fixed-top-touch{top:0}html.theme--catppuccin-latte .navbar.is-fixed-top .navbar-menu,html.theme--catppuccin-latte .navbar.is-fixed-top-touch .navbar-menu{-webkit-overflow-scrolling:touch;max-height:calc(100vh - 4rem);overflow:auto}html.theme--catppuccin-latte html.has-navbar-fixed-top-touch,html.theme--catppuccin-latte body.has-navbar-fixed-top-touch{padding-top:4rem}html.theme--catppuccin-latte html.has-navbar-fixed-bottom-touch,html.theme--catppuccin-latte body.has-navbar-fixed-bottom-touch{padding-bottom:4rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .navbar,html.theme--catppuccin-latte .navbar-menu,html.theme--catppuccin-latte .navbar-start,html.theme--catppuccin-latte .navbar-end{align-items:stretch;display:flex}html.theme--catppuccin-latte .navbar{min-height:4rem}html.theme--catppuccin-latte .navbar.is-spaced{padding:1rem 2rem}html.theme--catppuccin-latte .navbar.is-spaced .navbar-start,html.theme--catppuccin-latte .navbar.is-spaced .navbar-end{align-items:center}html.theme--catppuccin-latte .navbar.is-spaced a.navbar-item,html.theme--catppuccin-latte .navbar.is-spaced .navbar-link{border-radius:.4em}html.theme--catppuccin-latte .navbar.is-transparent a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-transparent a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-transparent a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-transparent .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-transparent .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-transparent .navbar-link.is-active{background-color:transparent !important}html.theme--catppuccin-latte .navbar.is-transparent .navbar-item.has-dropdown.is-active .navbar-link,html.theme--catppuccin-latte .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus .navbar-link,html.theme--catppuccin-latte .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus-within .navbar-link,html.theme--catppuccin-latte .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:hover .navbar-link{background-color:transparent !important}html.theme--catppuccin-latte .navbar.is-transparent .navbar-dropdown a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-transparent .navbar-dropdown a.navbar-item:hover{background-color:rgba(0,0,0,0);color:#8c8fa1}html.theme--catppuccin-latte .navbar.is-transparent .navbar-dropdown a.navbar-item.is-active{background-color:rgba(0,0,0,0);color:#1e66f5}html.theme--catppuccin-latte .navbar-burger{display:none}html.theme--catppuccin-latte .navbar-item,html.theme--catppuccin-latte .navbar-link{align-items:center;display:flex}html.theme--catppuccin-latte .navbar-item.has-dropdown{align-items:stretch}html.theme--catppuccin-latte .navbar-item.has-dropdown-up .navbar-link::after{transform:rotate(135deg) translate(0.25em, -0.25em)}html.theme--catppuccin-latte .navbar-item.has-dropdown-up .navbar-dropdown{border-bottom:1px solid rgba(0,0,0,0.2);border-radius:8px 8px 0 0;border-top:none;bottom:100%;box-shadow:0 -8px 8px rgba(10,10,10,0.1);top:auto}html.theme--catppuccin-latte .navbar-item.is-active .navbar-dropdown,html.theme--catppuccin-latte .navbar-item.is-hoverable:focus .navbar-dropdown,html.theme--catppuccin-latte .navbar-item.is-hoverable:focus-within .navbar-dropdown,html.theme--catppuccin-latte .navbar-item.is-hoverable:hover .navbar-dropdown{display:block}.navbar.is-spaced html.theme--catppuccin-latte .navbar-item.is-active .navbar-dropdown,html.theme--catppuccin-latte .navbar-item.is-active .navbar-dropdown.is-boxed,.navbar.is-spaced html.theme--catppuccin-latte .navbar-item.is-hoverable:focus .navbar-dropdown,html.theme--catppuccin-latte .navbar-item.is-hoverable:focus .navbar-dropdown.is-boxed,.navbar.is-spaced html.theme--catppuccin-latte .navbar-item.is-hoverable:focus-within .navbar-dropdown,html.theme--catppuccin-latte .navbar-item.is-hoverable:focus-within .navbar-dropdown.is-boxed,.navbar.is-spaced html.theme--catppuccin-latte .navbar-item.is-hoverable:hover .navbar-dropdown,html.theme--catppuccin-latte .navbar-item.is-hoverable:hover .navbar-dropdown.is-boxed{opacity:1;pointer-events:auto;transform:translateY(0)}html.theme--catppuccin-latte .navbar-menu{flex-grow:1;flex-shrink:0}html.theme--catppuccin-latte .navbar-start{justify-content:flex-start;margin-right:auto}html.theme--catppuccin-latte .navbar-end{justify-content:flex-end;margin-left:auto}html.theme--catppuccin-latte .navbar-dropdown{background-color:#1e66f5;border-bottom-left-radius:8px;border-bottom-right-radius:8px;border-top:1px solid rgba(0,0,0,0.2);box-shadow:0 8px 8px rgba(10,10,10,0.1);display:none;font-size:0.875rem;left:0;min-width:100%;position:absolute;top:100%;z-index:20}html.theme--catppuccin-latte .navbar-dropdown .navbar-item{padding:0.375rem 1rem;white-space:nowrap}html.theme--catppuccin-latte .navbar-dropdown a.navbar-item{padding-right:3rem}html.theme--catppuccin-latte .navbar-dropdown a.navbar-item:focus,html.theme--catppuccin-latte .navbar-dropdown a.navbar-item:hover{background-color:rgba(0,0,0,0);color:#8c8fa1}html.theme--catppuccin-latte .navbar-dropdown a.navbar-item.is-active{background-color:rgba(0,0,0,0);color:#1e66f5}.navbar.is-spaced html.theme--catppuccin-latte .navbar-dropdown,html.theme--catppuccin-latte .navbar-dropdown.is-boxed{border-radius:8px;border-top:none;box-shadow:0 8px 8px rgba(10,10,10,0.1), 0 0 0 1px rgba(10,10,10,0.1);display:block;opacity:0;pointer-events:none;top:calc(100% + (-4px));transform:translateY(-5px);transition-duration:86ms;transition-property:opacity, transform}html.theme--catppuccin-latte .navbar-dropdown.is-right{left:auto;right:0}html.theme--catppuccin-latte .navbar-divider{display:block}html.theme--catppuccin-latte .navbar>.container .navbar-brand,html.theme--catppuccin-latte .container>.navbar .navbar-brand{margin-left:-.75rem}html.theme--catppuccin-latte .navbar>.container .navbar-menu,html.theme--catppuccin-latte .container>.navbar .navbar-menu{margin-right:-.75rem}html.theme--catppuccin-latte .navbar.is-fixed-bottom-desktop,html.theme--catppuccin-latte .navbar.is-fixed-top-desktop{left:0;position:fixed;right:0;z-index:30}html.theme--catppuccin-latte .navbar.is-fixed-bottom-desktop{bottom:0}html.theme--catppuccin-latte .navbar.is-fixed-bottom-desktop.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,0.1)}html.theme--catppuccin-latte .navbar.is-fixed-top-desktop{top:0}html.theme--catppuccin-latte html.has-navbar-fixed-top-desktop,html.theme--catppuccin-latte body.has-navbar-fixed-top-desktop{padding-top:4rem}html.theme--catppuccin-latte html.has-navbar-fixed-bottom-desktop,html.theme--catppuccin-latte body.has-navbar-fixed-bottom-desktop{padding-bottom:4rem}html.theme--catppuccin-latte html.has-spaced-navbar-fixed-top,html.theme--catppuccin-latte body.has-spaced-navbar-fixed-top{padding-top:6rem}html.theme--catppuccin-latte html.has-spaced-navbar-fixed-bottom,html.theme--catppuccin-latte body.has-spaced-navbar-fixed-bottom{padding-bottom:6rem}html.theme--catppuccin-latte a.navbar-item.is-active,html.theme--catppuccin-latte .navbar-link.is-active{color:#1e66f5}html.theme--catppuccin-latte a.navbar-item.is-active:not(:focus):not(:hover),html.theme--catppuccin-latte .navbar-link.is-active:not(:focus):not(:hover){background-color:rgba(0,0,0,0)}html.theme--catppuccin-latte .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-latte .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-latte .navbar-item.has-dropdown.is-active .navbar-link{background-color:rgba(0,0,0,0)}}html.theme--catppuccin-latte .hero.is-fullheight-with-navbar{min-height:calc(100vh - 4rem)}html.theme--catppuccin-latte .pagination{font-size:1rem;margin:-.25rem}html.theme--catppuccin-latte .pagination.is-small,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.pagination{font-size:.75rem}html.theme--catppuccin-latte .pagination.is-medium{font-size:1.25rem}html.theme--catppuccin-latte .pagination.is-large{font-size:1.5rem}html.theme--catppuccin-latte .pagination.is-rounded .pagination-previous,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.pagination .pagination-previous,html.theme--catppuccin-latte .pagination.is-rounded .pagination-next,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.pagination .pagination-next{padding-left:1em;padding-right:1em;border-radius:9999px}html.theme--catppuccin-latte .pagination.is-rounded .pagination-link,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.pagination .pagination-link{border-radius:9999px}html.theme--catppuccin-latte .pagination,html.theme--catppuccin-latte .pagination-list{align-items:center;display:flex;justify-content:center;text-align:center}html.theme--catppuccin-latte .pagination-previous,html.theme--catppuccin-latte .pagination-next,html.theme--catppuccin-latte .pagination-link,html.theme--catppuccin-latte .pagination-ellipsis{font-size:1em;justify-content:center;margin:.25rem;padding-left:.5em;padding-right:.5em;text-align:center}html.theme--catppuccin-latte .pagination-previous,html.theme--catppuccin-latte .pagination-next,html.theme--catppuccin-latte .pagination-link{border-color:#acb0be;color:#1e66f5;min-width:2.5em}html.theme--catppuccin-latte .pagination-previous:hover,html.theme--catppuccin-latte .pagination-next:hover,html.theme--catppuccin-latte .pagination-link:hover{border-color:#9ca0b0;color:#04a5e5}html.theme--catppuccin-latte .pagination-previous:focus,html.theme--catppuccin-latte .pagination-next:focus,html.theme--catppuccin-latte .pagination-link:focus{border-color:#9ca0b0}html.theme--catppuccin-latte .pagination-previous:active,html.theme--catppuccin-latte .pagination-next:active,html.theme--catppuccin-latte .pagination-link:active{box-shadow:inset 0 1px 2px rgba(10,10,10,0.2)}html.theme--catppuccin-latte .pagination-previous[disabled],html.theme--catppuccin-latte .pagination-previous.is-disabled,html.theme--catppuccin-latte .pagination-next[disabled],html.theme--catppuccin-latte .pagination-next.is-disabled,html.theme--catppuccin-latte .pagination-link[disabled],html.theme--catppuccin-latte .pagination-link.is-disabled{background-color:#acb0be;border-color:#acb0be;box-shadow:none;color:#616587;opacity:0.5}html.theme--catppuccin-latte .pagination-previous,html.theme--catppuccin-latte .pagination-next{padding-left:.75em;padding-right:.75em;white-space:nowrap}html.theme--catppuccin-latte .pagination-link.is-current{background-color:#1e66f5;border-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .pagination-ellipsis{color:#9ca0b0;pointer-events:none}html.theme--catppuccin-latte .pagination-list{flex-wrap:wrap}html.theme--catppuccin-latte .pagination-list li{list-style:none}@media screen and (max-width: 768px){html.theme--catppuccin-latte .pagination{flex-wrap:wrap}html.theme--catppuccin-latte .pagination-previous,html.theme--catppuccin-latte .pagination-next{flex-grow:1;flex-shrink:1}html.theme--catppuccin-latte .pagination-list li{flex-grow:1;flex-shrink:1}}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .pagination-list{flex-grow:1;flex-shrink:1;justify-content:flex-start;order:1}html.theme--catppuccin-latte .pagination-previous,html.theme--catppuccin-latte .pagination-next,html.theme--catppuccin-latte .pagination-link,html.theme--catppuccin-latte .pagination-ellipsis{margin-bottom:0;margin-top:0}html.theme--catppuccin-latte .pagination-previous{order:2}html.theme--catppuccin-latte .pagination-next{order:3}html.theme--catppuccin-latte .pagination{justify-content:space-between;margin-bottom:0;margin-top:0}html.theme--catppuccin-latte .pagination.is-centered .pagination-previous{order:1}html.theme--catppuccin-latte .pagination.is-centered .pagination-list{justify-content:center;order:2}html.theme--catppuccin-latte .pagination.is-centered .pagination-next{order:3}html.theme--catppuccin-latte .pagination.is-right .pagination-previous{order:1}html.theme--catppuccin-latte .pagination.is-right .pagination-next{order:2}html.theme--catppuccin-latte .pagination.is-right .pagination-list{justify-content:flex-end;order:3}}html.theme--catppuccin-latte .panel{border-radius:8px;box-shadow:#171717;font-size:1rem}html.theme--catppuccin-latte .panel:not(:last-child){margin-bottom:1.5rem}html.theme--catppuccin-latte .panel.is-white .panel-heading{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-latte .panel.is-white .panel-tabs a.is-active{border-bottom-color:#fff}html.theme--catppuccin-latte .panel.is-white .panel-block.is-active .panel-icon{color:#fff}html.theme--catppuccin-latte .panel.is-black .panel-heading{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-latte .panel.is-black .panel-tabs a.is-active{border-bottom-color:#0a0a0a}html.theme--catppuccin-latte .panel.is-black .panel-block.is-active .panel-icon{color:#0a0a0a}html.theme--catppuccin-latte .panel.is-light .panel-heading{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .panel.is-light .panel-tabs a.is-active{border-bottom-color:#f5f5f5}html.theme--catppuccin-latte .panel.is-light .panel-block.is-active .panel-icon{color:#f5f5f5}html.theme--catppuccin-latte .panel.is-dark .panel-heading,html.theme--catppuccin-latte .content kbd.panel .panel-heading{background-color:#ccd0da;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .panel.is-dark .panel-tabs a.is-active,html.theme--catppuccin-latte .content kbd.panel .panel-tabs a.is-active{border-bottom-color:#ccd0da}html.theme--catppuccin-latte .panel.is-dark .panel-block.is-active .panel-icon,html.theme--catppuccin-latte .content kbd.panel .panel-block.is-active .panel-icon{color:#ccd0da}html.theme--catppuccin-latte .panel.is-primary .panel-heading,html.theme--catppuccin-latte .docstring>section>a.panel.docs-sourcelink .panel-heading{background-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .panel.is-primary .panel-tabs a.is-active,html.theme--catppuccin-latte .docstring>section>a.panel.docs-sourcelink .panel-tabs a.is-active{border-bottom-color:#1e66f5}html.theme--catppuccin-latte .panel.is-primary .panel-block.is-active .panel-icon,html.theme--catppuccin-latte .docstring>section>a.panel.docs-sourcelink .panel-block.is-active .panel-icon{color:#1e66f5}html.theme--catppuccin-latte .panel.is-link .panel-heading{background-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .panel.is-link .panel-tabs a.is-active{border-bottom-color:#1e66f5}html.theme--catppuccin-latte .panel.is-link .panel-block.is-active .panel-icon{color:#1e66f5}html.theme--catppuccin-latte .panel.is-info .panel-heading{background-color:#179299;color:#fff}html.theme--catppuccin-latte .panel.is-info .panel-tabs a.is-active{border-bottom-color:#179299}html.theme--catppuccin-latte .panel.is-info .panel-block.is-active .panel-icon{color:#179299}html.theme--catppuccin-latte .panel.is-success .panel-heading{background-color:#40a02b;color:#fff}html.theme--catppuccin-latte .panel.is-success .panel-tabs a.is-active{border-bottom-color:#40a02b}html.theme--catppuccin-latte .panel.is-success .panel-block.is-active .panel-icon{color:#40a02b}html.theme--catppuccin-latte .panel.is-warning .panel-heading{background-color:#df8e1d;color:#fff}html.theme--catppuccin-latte .panel.is-warning .panel-tabs a.is-active{border-bottom-color:#df8e1d}html.theme--catppuccin-latte .panel.is-warning .panel-block.is-active .panel-icon{color:#df8e1d}html.theme--catppuccin-latte .panel.is-danger .panel-heading{background-color:#d20f39;color:#fff}html.theme--catppuccin-latte .panel.is-danger .panel-tabs a.is-active{border-bottom-color:#d20f39}html.theme--catppuccin-latte .panel.is-danger .panel-block.is-active .panel-icon{color:#d20f39}html.theme--catppuccin-latte .panel-tabs:not(:last-child),html.theme--catppuccin-latte .panel-block:not(:last-child){border-bottom:1px solid #ededed}html.theme--catppuccin-latte .panel-heading{background-color:#bcc0cc;border-radius:8px 8px 0 0;color:#41445a;font-size:1.25em;font-weight:700;line-height:1.25;padding:0.75em 1em}html.theme--catppuccin-latte .panel-tabs{align-items:flex-end;display:flex;font-size:.875em;justify-content:center}html.theme--catppuccin-latte .panel-tabs a{border-bottom:1px solid #acb0be;margin-bottom:-1px;padding:0.5em}html.theme--catppuccin-latte .panel-tabs a.is-active{border-bottom-color:#bcc0cc;color:#0b57ef}html.theme--catppuccin-latte .panel-list a{color:#4c4f69}html.theme--catppuccin-latte .panel-list a:hover{color:#1e66f5}html.theme--catppuccin-latte .panel-block{align-items:center;color:#41445a;display:flex;justify-content:flex-start;padding:0.5em 0.75em}html.theme--catppuccin-latte .panel-block input[type="checkbox"]{margin-right:.75em}html.theme--catppuccin-latte .panel-block>.control{flex-grow:1;flex-shrink:1;width:100%}html.theme--catppuccin-latte .panel-block.is-wrapped{flex-wrap:wrap}html.theme--catppuccin-latte .panel-block.is-active{border-left-color:#1e66f5;color:#0b57ef}html.theme--catppuccin-latte .panel-block.is-active .panel-icon{color:#1e66f5}html.theme--catppuccin-latte .panel-block:last-child{border-bottom-left-radius:8px;border-bottom-right-radius:8px}html.theme--catppuccin-latte a.panel-block,html.theme--catppuccin-latte label.panel-block{cursor:pointer}html.theme--catppuccin-latte a.panel-block:hover,html.theme--catppuccin-latte label.panel-block:hover{background-color:#e6e9ef}html.theme--catppuccin-latte .panel-icon{display:inline-block;font-size:14px;height:1em;line-height:1em;text-align:center;vertical-align:top;width:1em;color:#616587;margin-right:.75em}html.theme--catppuccin-latte .panel-icon .fa{font-size:inherit;line-height:inherit}html.theme--catppuccin-latte .tabs{-webkit-overflow-scrolling:touch;align-items:stretch;display:flex;font-size:1rem;justify-content:space-between;overflow:hidden;overflow-x:auto;white-space:nowrap}html.theme--catppuccin-latte .tabs a{align-items:center;border-bottom-color:#acb0be;border-bottom-style:solid;border-bottom-width:1px;color:#4c4f69;display:flex;justify-content:center;margin-bottom:-1px;padding:0.5em 1em;vertical-align:top}html.theme--catppuccin-latte .tabs a:hover{border-bottom-color:#41445a;color:#41445a}html.theme--catppuccin-latte .tabs li{display:block}html.theme--catppuccin-latte .tabs li.is-active a{border-bottom-color:#1e66f5;color:#1e66f5}html.theme--catppuccin-latte .tabs ul{align-items:center;border-bottom-color:#acb0be;border-bottom-style:solid;border-bottom-width:1px;display:flex;flex-grow:1;flex-shrink:0;justify-content:flex-start}html.theme--catppuccin-latte .tabs ul.is-left{padding-right:0.75em}html.theme--catppuccin-latte .tabs ul.is-center{flex:none;justify-content:center;padding-left:0.75em;padding-right:0.75em}html.theme--catppuccin-latte .tabs ul.is-right{justify-content:flex-end;padding-left:0.75em}html.theme--catppuccin-latte .tabs .icon:first-child{margin-right:.5em}html.theme--catppuccin-latte .tabs .icon:last-child{margin-left:.5em}html.theme--catppuccin-latte .tabs.is-centered ul{justify-content:center}html.theme--catppuccin-latte .tabs.is-right ul{justify-content:flex-end}html.theme--catppuccin-latte .tabs.is-boxed a{border:1px solid transparent;border-radius:.4em .4em 0 0}html.theme--catppuccin-latte .tabs.is-boxed a:hover{background-color:#e6e9ef;border-bottom-color:#acb0be}html.theme--catppuccin-latte .tabs.is-boxed li.is-active a{background-color:#fff;border-color:#acb0be;border-bottom-color:rgba(0,0,0,0) !important}html.theme--catppuccin-latte .tabs.is-fullwidth li{flex-grow:1;flex-shrink:0}html.theme--catppuccin-latte .tabs.is-toggle a{border-color:#acb0be;border-style:solid;border-width:1px;margin-bottom:0;position:relative}html.theme--catppuccin-latte .tabs.is-toggle a:hover{background-color:#e6e9ef;border-color:#9ca0b0;z-index:2}html.theme--catppuccin-latte .tabs.is-toggle li+li{margin-left:-1px}html.theme--catppuccin-latte .tabs.is-toggle li:first-child a{border-top-left-radius:.4em;border-bottom-left-radius:.4em}html.theme--catppuccin-latte .tabs.is-toggle li:last-child a{border-top-right-radius:.4em;border-bottom-right-radius:.4em}html.theme--catppuccin-latte .tabs.is-toggle li.is-active a{background-color:#1e66f5;border-color:#1e66f5;color:#fff;z-index:1}html.theme--catppuccin-latte .tabs.is-toggle ul{border-bottom:none}html.theme--catppuccin-latte .tabs.is-toggle.is-toggle-rounded li:first-child a{border-bottom-left-radius:9999px;border-top-left-radius:9999px;padding-left:1.25em}html.theme--catppuccin-latte .tabs.is-toggle.is-toggle-rounded li:last-child a{border-bottom-right-radius:9999px;border-top-right-radius:9999px;padding-right:1.25em}html.theme--catppuccin-latte .tabs.is-small,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.tabs{font-size:.75rem}html.theme--catppuccin-latte .tabs.is-medium{font-size:1.25rem}html.theme--catppuccin-latte .tabs.is-large{font-size:1.5rem}html.theme--catppuccin-latte .column{display:block;flex-basis:0;flex-grow:1;flex-shrink:1;padding:.75rem}.columns.is-mobile>html.theme--catppuccin-latte .column.is-narrow{flex:none;width:unset}.columns.is-mobile>html.theme--catppuccin-latte .column.is-full{flex:none;width:100%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-three-quarters{flex:none;width:75%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-two-thirds{flex:none;width:66.6666%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-half{flex:none;width:50%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-one-third{flex:none;width:33.3333%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-one-quarter{flex:none;width:25%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-one-fifth{flex:none;width:20%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-two-fifths{flex:none;width:40%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-three-fifths{flex:none;width:60%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-four-fifths{flex:none;width:80%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-three-quarters{margin-left:75%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-two-thirds{margin-left:66.6666%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-half{margin-left:50%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-one-third{margin-left:33.3333%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-one-quarter{margin-left:25%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-one-fifth{margin-left:20%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-two-fifths{margin-left:40%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-three-fifths{margin-left:60%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-four-fifths{margin-left:80%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-0{flex:none;width:0%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-0{margin-left:0%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-1{flex:none;width:8.33333337%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-1{margin-left:8.33333337%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-2{flex:none;width:16.66666674%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-2{margin-left:16.66666674%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-3{flex:none;width:25%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-3{margin-left:25%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-4{flex:none;width:33.33333337%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-4{margin-left:33.33333337%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-5{flex:none;width:41.66666674%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-5{margin-left:41.66666674%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-6{flex:none;width:50%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-6{margin-left:50%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-7{flex:none;width:58.33333337%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-7{margin-left:58.33333337%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-8{flex:none;width:66.66666674%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-8{margin-left:66.66666674%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-9{flex:none;width:75%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-9{margin-left:75%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-10{flex:none;width:83.33333337%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-10{margin-left:83.33333337%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-11{flex:none;width:91.66666674%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-11{margin-left:91.66666674%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-12{flex:none;width:100%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-12{margin-left:100%}@media screen and (max-width: 768px){html.theme--catppuccin-latte .column.is-narrow-mobile{flex:none;width:unset}html.theme--catppuccin-latte .column.is-full-mobile{flex:none;width:100%}html.theme--catppuccin-latte .column.is-three-quarters-mobile{flex:none;width:75%}html.theme--catppuccin-latte .column.is-two-thirds-mobile{flex:none;width:66.6666%}html.theme--catppuccin-latte .column.is-half-mobile{flex:none;width:50%}html.theme--catppuccin-latte .column.is-one-third-mobile{flex:none;width:33.3333%}html.theme--catppuccin-latte .column.is-one-quarter-mobile{flex:none;width:25%}html.theme--catppuccin-latte .column.is-one-fifth-mobile{flex:none;width:20%}html.theme--catppuccin-latte .column.is-two-fifths-mobile{flex:none;width:40%}html.theme--catppuccin-latte .column.is-three-fifths-mobile{flex:none;width:60%}html.theme--catppuccin-latte .column.is-four-fifths-mobile{flex:none;width:80%}html.theme--catppuccin-latte .column.is-offset-three-quarters-mobile{margin-left:75%}html.theme--catppuccin-latte .column.is-offset-two-thirds-mobile{margin-left:66.6666%}html.theme--catppuccin-latte .column.is-offset-half-mobile{margin-left:50%}html.theme--catppuccin-latte .column.is-offset-one-third-mobile{margin-left:33.3333%}html.theme--catppuccin-latte .column.is-offset-one-quarter-mobile{margin-left:25%}html.theme--catppuccin-latte .column.is-offset-one-fifth-mobile{margin-left:20%}html.theme--catppuccin-latte .column.is-offset-two-fifths-mobile{margin-left:40%}html.theme--catppuccin-latte .column.is-offset-three-fifths-mobile{margin-left:60%}html.theme--catppuccin-latte .column.is-offset-four-fifths-mobile{margin-left:80%}html.theme--catppuccin-latte .column.is-0-mobile{flex:none;width:0%}html.theme--catppuccin-latte .column.is-offset-0-mobile{margin-left:0%}html.theme--catppuccin-latte .column.is-1-mobile{flex:none;width:8.33333337%}html.theme--catppuccin-latte .column.is-offset-1-mobile{margin-left:8.33333337%}html.theme--catppuccin-latte .column.is-2-mobile{flex:none;width:16.66666674%}html.theme--catppuccin-latte .column.is-offset-2-mobile{margin-left:16.66666674%}html.theme--catppuccin-latte .column.is-3-mobile{flex:none;width:25%}html.theme--catppuccin-latte .column.is-offset-3-mobile{margin-left:25%}html.theme--catppuccin-latte .column.is-4-mobile{flex:none;width:33.33333337%}html.theme--catppuccin-latte .column.is-offset-4-mobile{margin-left:33.33333337%}html.theme--catppuccin-latte .column.is-5-mobile{flex:none;width:41.66666674%}html.theme--catppuccin-latte .column.is-offset-5-mobile{margin-left:41.66666674%}html.theme--catppuccin-latte .column.is-6-mobile{flex:none;width:50%}html.theme--catppuccin-latte .column.is-offset-6-mobile{margin-left:50%}html.theme--catppuccin-latte .column.is-7-mobile{flex:none;width:58.33333337%}html.theme--catppuccin-latte .column.is-offset-7-mobile{margin-left:58.33333337%}html.theme--catppuccin-latte .column.is-8-mobile{flex:none;width:66.66666674%}html.theme--catppuccin-latte .column.is-offset-8-mobile{margin-left:66.66666674%}html.theme--catppuccin-latte .column.is-9-mobile{flex:none;width:75%}html.theme--catppuccin-latte .column.is-offset-9-mobile{margin-left:75%}html.theme--catppuccin-latte .column.is-10-mobile{flex:none;width:83.33333337%}html.theme--catppuccin-latte .column.is-offset-10-mobile{margin-left:83.33333337%}html.theme--catppuccin-latte .column.is-11-mobile{flex:none;width:91.66666674%}html.theme--catppuccin-latte .column.is-offset-11-mobile{margin-left:91.66666674%}html.theme--catppuccin-latte .column.is-12-mobile{flex:none;width:100%}html.theme--catppuccin-latte .column.is-offset-12-mobile{margin-left:100%}}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .column.is-narrow,html.theme--catppuccin-latte .column.is-narrow-tablet{flex:none;width:unset}html.theme--catppuccin-latte .column.is-full,html.theme--catppuccin-latte .column.is-full-tablet{flex:none;width:100%}html.theme--catppuccin-latte .column.is-three-quarters,html.theme--catppuccin-latte .column.is-three-quarters-tablet{flex:none;width:75%}html.theme--catppuccin-latte .column.is-two-thirds,html.theme--catppuccin-latte .column.is-two-thirds-tablet{flex:none;width:66.6666%}html.theme--catppuccin-latte .column.is-half,html.theme--catppuccin-latte .column.is-half-tablet{flex:none;width:50%}html.theme--catppuccin-latte .column.is-one-third,html.theme--catppuccin-latte .column.is-one-third-tablet{flex:none;width:33.3333%}html.theme--catppuccin-latte .column.is-one-quarter,html.theme--catppuccin-latte .column.is-one-quarter-tablet{flex:none;width:25%}html.theme--catppuccin-latte .column.is-one-fifth,html.theme--catppuccin-latte .column.is-one-fifth-tablet{flex:none;width:20%}html.theme--catppuccin-latte .column.is-two-fifths,html.theme--catppuccin-latte .column.is-two-fifths-tablet{flex:none;width:40%}html.theme--catppuccin-latte .column.is-three-fifths,html.theme--catppuccin-latte .column.is-three-fifths-tablet{flex:none;width:60%}html.theme--catppuccin-latte .column.is-four-fifths,html.theme--catppuccin-latte .column.is-four-fifths-tablet{flex:none;width:80%}html.theme--catppuccin-latte .column.is-offset-three-quarters,html.theme--catppuccin-latte .column.is-offset-three-quarters-tablet{margin-left:75%}html.theme--catppuccin-latte .column.is-offset-two-thirds,html.theme--catppuccin-latte .column.is-offset-two-thirds-tablet{margin-left:66.6666%}html.theme--catppuccin-latte .column.is-offset-half,html.theme--catppuccin-latte .column.is-offset-half-tablet{margin-left:50%}html.theme--catppuccin-latte .column.is-offset-one-third,html.theme--catppuccin-latte .column.is-offset-one-third-tablet{margin-left:33.3333%}html.theme--catppuccin-latte .column.is-offset-one-quarter,html.theme--catppuccin-latte .column.is-offset-one-quarter-tablet{margin-left:25%}html.theme--catppuccin-latte .column.is-offset-one-fifth,html.theme--catppuccin-latte .column.is-offset-one-fifth-tablet{margin-left:20%}html.theme--catppuccin-latte .column.is-offset-two-fifths,html.theme--catppuccin-latte .column.is-offset-two-fifths-tablet{margin-left:40%}html.theme--catppuccin-latte .column.is-offset-three-fifths,html.theme--catppuccin-latte .column.is-offset-three-fifths-tablet{margin-left:60%}html.theme--catppuccin-latte .column.is-offset-four-fifths,html.theme--catppuccin-latte .column.is-offset-four-fifths-tablet{margin-left:80%}html.theme--catppuccin-latte .column.is-0,html.theme--catppuccin-latte .column.is-0-tablet{flex:none;width:0%}html.theme--catppuccin-latte .column.is-offset-0,html.theme--catppuccin-latte .column.is-offset-0-tablet{margin-left:0%}html.theme--catppuccin-latte .column.is-1,html.theme--catppuccin-latte .column.is-1-tablet{flex:none;width:8.33333337%}html.theme--catppuccin-latte .column.is-offset-1,html.theme--catppuccin-latte .column.is-offset-1-tablet{margin-left:8.33333337%}html.theme--catppuccin-latte .column.is-2,html.theme--catppuccin-latte .column.is-2-tablet{flex:none;width:16.66666674%}html.theme--catppuccin-latte .column.is-offset-2,html.theme--catppuccin-latte .column.is-offset-2-tablet{margin-left:16.66666674%}html.theme--catppuccin-latte .column.is-3,html.theme--catppuccin-latte .column.is-3-tablet{flex:none;width:25%}html.theme--catppuccin-latte .column.is-offset-3,html.theme--catppuccin-latte .column.is-offset-3-tablet{margin-left:25%}html.theme--catppuccin-latte .column.is-4,html.theme--catppuccin-latte .column.is-4-tablet{flex:none;width:33.33333337%}html.theme--catppuccin-latte .column.is-offset-4,html.theme--catppuccin-latte .column.is-offset-4-tablet{margin-left:33.33333337%}html.theme--catppuccin-latte .column.is-5,html.theme--catppuccin-latte .column.is-5-tablet{flex:none;width:41.66666674%}html.theme--catppuccin-latte .column.is-offset-5,html.theme--catppuccin-latte .column.is-offset-5-tablet{margin-left:41.66666674%}html.theme--catppuccin-latte .column.is-6,html.theme--catppuccin-latte .column.is-6-tablet{flex:none;width:50%}html.theme--catppuccin-latte .column.is-offset-6,html.theme--catppuccin-latte .column.is-offset-6-tablet{margin-left:50%}html.theme--catppuccin-latte .column.is-7,html.theme--catppuccin-latte .column.is-7-tablet{flex:none;width:58.33333337%}html.theme--catppuccin-latte .column.is-offset-7,html.theme--catppuccin-latte .column.is-offset-7-tablet{margin-left:58.33333337%}html.theme--catppuccin-latte .column.is-8,html.theme--catppuccin-latte .column.is-8-tablet{flex:none;width:66.66666674%}html.theme--catppuccin-latte .column.is-offset-8,html.theme--catppuccin-latte .column.is-offset-8-tablet{margin-left:66.66666674%}html.theme--catppuccin-latte .column.is-9,html.theme--catppuccin-latte .column.is-9-tablet{flex:none;width:75%}html.theme--catppuccin-latte .column.is-offset-9,html.theme--catppuccin-latte .column.is-offset-9-tablet{margin-left:75%}html.theme--catppuccin-latte .column.is-10,html.theme--catppuccin-latte .column.is-10-tablet{flex:none;width:83.33333337%}html.theme--catppuccin-latte .column.is-offset-10,html.theme--catppuccin-latte .column.is-offset-10-tablet{margin-left:83.33333337%}html.theme--catppuccin-latte .column.is-11,html.theme--catppuccin-latte .column.is-11-tablet{flex:none;width:91.66666674%}html.theme--catppuccin-latte .column.is-offset-11,html.theme--catppuccin-latte .column.is-offset-11-tablet{margin-left:91.66666674%}html.theme--catppuccin-latte .column.is-12,html.theme--catppuccin-latte .column.is-12-tablet{flex:none;width:100%}html.theme--catppuccin-latte .column.is-offset-12,html.theme--catppuccin-latte .column.is-offset-12-tablet{margin-left:100%}}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .column.is-narrow-touch{flex:none;width:unset}html.theme--catppuccin-latte .column.is-full-touch{flex:none;width:100%}html.theme--catppuccin-latte .column.is-three-quarters-touch{flex:none;width:75%}html.theme--catppuccin-latte .column.is-two-thirds-touch{flex:none;width:66.6666%}html.theme--catppuccin-latte .column.is-half-touch{flex:none;width:50%}html.theme--catppuccin-latte .column.is-one-third-touch{flex:none;width:33.3333%}html.theme--catppuccin-latte .column.is-one-quarter-touch{flex:none;width:25%}html.theme--catppuccin-latte .column.is-one-fifth-touch{flex:none;width:20%}html.theme--catppuccin-latte .column.is-two-fifths-touch{flex:none;width:40%}html.theme--catppuccin-latte .column.is-three-fifths-touch{flex:none;width:60%}html.theme--catppuccin-latte .column.is-four-fifths-touch{flex:none;width:80%}html.theme--catppuccin-latte .column.is-offset-three-quarters-touch{margin-left:75%}html.theme--catppuccin-latte .column.is-offset-two-thirds-touch{margin-left:66.6666%}html.theme--catppuccin-latte .column.is-offset-half-touch{margin-left:50%}html.theme--catppuccin-latte .column.is-offset-one-third-touch{margin-left:33.3333%}html.theme--catppuccin-latte .column.is-offset-one-quarter-touch{margin-left:25%}html.theme--catppuccin-latte .column.is-offset-one-fifth-touch{margin-left:20%}html.theme--catppuccin-latte .column.is-offset-two-fifths-touch{margin-left:40%}html.theme--catppuccin-latte .column.is-offset-three-fifths-touch{margin-left:60%}html.theme--catppuccin-latte .column.is-offset-four-fifths-touch{margin-left:80%}html.theme--catppuccin-latte .column.is-0-touch{flex:none;width:0%}html.theme--catppuccin-latte .column.is-offset-0-touch{margin-left:0%}html.theme--catppuccin-latte .column.is-1-touch{flex:none;width:8.33333337%}html.theme--catppuccin-latte .column.is-offset-1-touch{margin-left:8.33333337%}html.theme--catppuccin-latte .column.is-2-touch{flex:none;width:16.66666674%}html.theme--catppuccin-latte .column.is-offset-2-touch{margin-left:16.66666674%}html.theme--catppuccin-latte .column.is-3-touch{flex:none;width:25%}html.theme--catppuccin-latte .column.is-offset-3-touch{margin-left:25%}html.theme--catppuccin-latte .column.is-4-touch{flex:none;width:33.33333337%}html.theme--catppuccin-latte .column.is-offset-4-touch{margin-left:33.33333337%}html.theme--catppuccin-latte .column.is-5-touch{flex:none;width:41.66666674%}html.theme--catppuccin-latte .column.is-offset-5-touch{margin-left:41.66666674%}html.theme--catppuccin-latte .column.is-6-touch{flex:none;width:50%}html.theme--catppuccin-latte .column.is-offset-6-touch{margin-left:50%}html.theme--catppuccin-latte .column.is-7-touch{flex:none;width:58.33333337%}html.theme--catppuccin-latte .column.is-offset-7-touch{margin-left:58.33333337%}html.theme--catppuccin-latte .column.is-8-touch{flex:none;width:66.66666674%}html.theme--catppuccin-latte .column.is-offset-8-touch{margin-left:66.66666674%}html.theme--catppuccin-latte .column.is-9-touch{flex:none;width:75%}html.theme--catppuccin-latte .column.is-offset-9-touch{margin-left:75%}html.theme--catppuccin-latte .column.is-10-touch{flex:none;width:83.33333337%}html.theme--catppuccin-latte .column.is-offset-10-touch{margin-left:83.33333337%}html.theme--catppuccin-latte .column.is-11-touch{flex:none;width:91.66666674%}html.theme--catppuccin-latte .column.is-offset-11-touch{margin-left:91.66666674%}html.theme--catppuccin-latte .column.is-12-touch{flex:none;width:100%}html.theme--catppuccin-latte .column.is-offset-12-touch{margin-left:100%}}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .column.is-narrow-desktop{flex:none;width:unset}html.theme--catppuccin-latte .column.is-full-desktop{flex:none;width:100%}html.theme--catppuccin-latte .column.is-three-quarters-desktop{flex:none;width:75%}html.theme--catppuccin-latte .column.is-two-thirds-desktop{flex:none;width:66.6666%}html.theme--catppuccin-latte .column.is-half-desktop{flex:none;width:50%}html.theme--catppuccin-latte .column.is-one-third-desktop{flex:none;width:33.3333%}html.theme--catppuccin-latte .column.is-one-quarter-desktop{flex:none;width:25%}html.theme--catppuccin-latte .column.is-one-fifth-desktop{flex:none;width:20%}html.theme--catppuccin-latte .column.is-two-fifths-desktop{flex:none;width:40%}html.theme--catppuccin-latte .column.is-three-fifths-desktop{flex:none;width:60%}html.theme--catppuccin-latte .column.is-four-fifths-desktop{flex:none;width:80%}html.theme--catppuccin-latte .column.is-offset-three-quarters-desktop{margin-left:75%}html.theme--catppuccin-latte .column.is-offset-two-thirds-desktop{margin-left:66.6666%}html.theme--catppuccin-latte .column.is-offset-half-desktop{margin-left:50%}html.theme--catppuccin-latte .column.is-offset-one-third-desktop{margin-left:33.3333%}html.theme--catppuccin-latte .column.is-offset-one-quarter-desktop{margin-left:25%}html.theme--catppuccin-latte .column.is-offset-one-fifth-desktop{margin-left:20%}html.theme--catppuccin-latte .column.is-offset-two-fifths-desktop{margin-left:40%}html.theme--catppuccin-latte .column.is-offset-three-fifths-desktop{margin-left:60%}html.theme--catppuccin-latte .column.is-offset-four-fifths-desktop{margin-left:80%}html.theme--catppuccin-latte .column.is-0-desktop{flex:none;width:0%}html.theme--catppuccin-latte .column.is-offset-0-desktop{margin-left:0%}html.theme--catppuccin-latte .column.is-1-desktop{flex:none;width:8.33333337%}html.theme--catppuccin-latte .column.is-offset-1-desktop{margin-left:8.33333337%}html.theme--catppuccin-latte .column.is-2-desktop{flex:none;width:16.66666674%}html.theme--catppuccin-latte .column.is-offset-2-desktop{margin-left:16.66666674%}html.theme--catppuccin-latte .column.is-3-desktop{flex:none;width:25%}html.theme--catppuccin-latte .column.is-offset-3-desktop{margin-left:25%}html.theme--catppuccin-latte .column.is-4-desktop{flex:none;width:33.33333337%}html.theme--catppuccin-latte .column.is-offset-4-desktop{margin-left:33.33333337%}html.theme--catppuccin-latte .column.is-5-desktop{flex:none;width:41.66666674%}html.theme--catppuccin-latte .column.is-offset-5-desktop{margin-left:41.66666674%}html.theme--catppuccin-latte .column.is-6-desktop{flex:none;width:50%}html.theme--catppuccin-latte .column.is-offset-6-desktop{margin-left:50%}html.theme--catppuccin-latte .column.is-7-desktop{flex:none;width:58.33333337%}html.theme--catppuccin-latte .column.is-offset-7-desktop{margin-left:58.33333337%}html.theme--catppuccin-latte .column.is-8-desktop{flex:none;width:66.66666674%}html.theme--catppuccin-latte .column.is-offset-8-desktop{margin-left:66.66666674%}html.theme--catppuccin-latte .column.is-9-desktop{flex:none;width:75%}html.theme--catppuccin-latte .column.is-offset-9-desktop{margin-left:75%}html.theme--catppuccin-latte .column.is-10-desktop{flex:none;width:83.33333337%}html.theme--catppuccin-latte .column.is-offset-10-desktop{margin-left:83.33333337%}html.theme--catppuccin-latte .column.is-11-desktop{flex:none;width:91.66666674%}html.theme--catppuccin-latte .column.is-offset-11-desktop{margin-left:91.66666674%}html.theme--catppuccin-latte .column.is-12-desktop{flex:none;width:100%}html.theme--catppuccin-latte .column.is-offset-12-desktop{margin-left:100%}}@media screen and (min-width: 1216px){html.theme--catppuccin-latte .column.is-narrow-widescreen{flex:none;width:unset}html.theme--catppuccin-latte .column.is-full-widescreen{flex:none;width:100%}html.theme--catppuccin-latte .column.is-three-quarters-widescreen{flex:none;width:75%}html.theme--catppuccin-latte .column.is-two-thirds-widescreen{flex:none;width:66.6666%}html.theme--catppuccin-latte .column.is-half-widescreen{flex:none;width:50%}html.theme--catppuccin-latte .column.is-one-third-widescreen{flex:none;width:33.3333%}html.theme--catppuccin-latte .column.is-one-quarter-widescreen{flex:none;width:25%}html.theme--catppuccin-latte .column.is-one-fifth-widescreen{flex:none;width:20%}html.theme--catppuccin-latte .column.is-two-fifths-widescreen{flex:none;width:40%}html.theme--catppuccin-latte .column.is-three-fifths-widescreen{flex:none;width:60%}html.theme--catppuccin-latte .column.is-four-fifths-widescreen{flex:none;width:80%}html.theme--catppuccin-latte .column.is-offset-three-quarters-widescreen{margin-left:75%}html.theme--catppuccin-latte .column.is-offset-two-thirds-widescreen{margin-left:66.6666%}html.theme--catppuccin-latte .column.is-offset-half-widescreen{margin-left:50%}html.theme--catppuccin-latte .column.is-offset-one-third-widescreen{margin-left:33.3333%}html.theme--catppuccin-latte .column.is-offset-one-quarter-widescreen{margin-left:25%}html.theme--catppuccin-latte .column.is-offset-one-fifth-widescreen{margin-left:20%}html.theme--catppuccin-latte .column.is-offset-two-fifths-widescreen{margin-left:40%}html.theme--catppuccin-latte .column.is-offset-three-fifths-widescreen{margin-left:60%}html.theme--catppuccin-latte .column.is-offset-four-fifths-widescreen{margin-left:80%}html.theme--catppuccin-latte .column.is-0-widescreen{flex:none;width:0%}html.theme--catppuccin-latte .column.is-offset-0-widescreen{margin-left:0%}html.theme--catppuccin-latte .column.is-1-widescreen{flex:none;width:8.33333337%}html.theme--catppuccin-latte .column.is-offset-1-widescreen{margin-left:8.33333337%}html.theme--catppuccin-latte .column.is-2-widescreen{flex:none;width:16.66666674%}html.theme--catppuccin-latte .column.is-offset-2-widescreen{margin-left:16.66666674%}html.theme--catppuccin-latte .column.is-3-widescreen{flex:none;width:25%}html.theme--catppuccin-latte .column.is-offset-3-widescreen{margin-left:25%}html.theme--catppuccin-latte .column.is-4-widescreen{flex:none;width:33.33333337%}html.theme--catppuccin-latte .column.is-offset-4-widescreen{margin-left:33.33333337%}html.theme--catppuccin-latte .column.is-5-widescreen{flex:none;width:41.66666674%}html.theme--catppuccin-latte .column.is-offset-5-widescreen{margin-left:41.66666674%}html.theme--catppuccin-latte .column.is-6-widescreen{flex:none;width:50%}html.theme--catppuccin-latte .column.is-offset-6-widescreen{margin-left:50%}html.theme--catppuccin-latte .column.is-7-widescreen{flex:none;width:58.33333337%}html.theme--catppuccin-latte .column.is-offset-7-widescreen{margin-left:58.33333337%}html.theme--catppuccin-latte .column.is-8-widescreen{flex:none;width:66.66666674%}html.theme--catppuccin-latte .column.is-offset-8-widescreen{margin-left:66.66666674%}html.theme--catppuccin-latte .column.is-9-widescreen{flex:none;width:75%}html.theme--catppuccin-latte .column.is-offset-9-widescreen{margin-left:75%}html.theme--catppuccin-latte .column.is-10-widescreen{flex:none;width:83.33333337%}html.theme--catppuccin-latte .column.is-offset-10-widescreen{margin-left:83.33333337%}html.theme--catppuccin-latte .column.is-11-widescreen{flex:none;width:91.66666674%}html.theme--catppuccin-latte .column.is-offset-11-widescreen{margin-left:91.66666674%}html.theme--catppuccin-latte .column.is-12-widescreen{flex:none;width:100%}html.theme--catppuccin-latte .column.is-offset-12-widescreen{margin-left:100%}}@media screen and (min-width: 1408px){html.theme--catppuccin-latte .column.is-narrow-fullhd{flex:none;width:unset}html.theme--catppuccin-latte .column.is-full-fullhd{flex:none;width:100%}html.theme--catppuccin-latte .column.is-three-quarters-fullhd{flex:none;width:75%}html.theme--catppuccin-latte .column.is-two-thirds-fullhd{flex:none;width:66.6666%}html.theme--catppuccin-latte .column.is-half-fullhd{flex:none;width:50%}html.theme--catppuccin-latte .column.is-one-third-fullhd{flex:none;width:33.3333%}html.theme--catppuccin-latte .column.is-one-quarter-fullhd{flex:none;width:25%}html.theme--catppuccin-latte .column.is-one-fifth-fullhd{flex:none;width:20%}html.theme--catppuccin-latte .column.is-two-fifths-fullhd{flex:none;width:40%}html.theme--catppuccin-latte .column.is-three-fifths-fullhd{flex:none;width:60%}html.theme--catppuccin-latte .column.is-four-fifths-fullhd{flex:none;width:80%}html.theme--catppuccin-latte .column.is-offset-three-quarters-fullhd{margin-left:75%}html.theme--catppuccin-latte .column.is-offset-two-thirds-fullhd{margin-left:66.6666%}html.theme--catppuccin-latte .column.is-offset-half-fullhd{margin-left:50%}html.theme--catppuccin-latte .column.is-offset-one-third-fullhd{margin-left:33.3333%}html.theme--catppuccin-latte .column.is-offset-one-quarter-fullhd{margin-left:25%}html.theme--catppuccin-latte .column.is-offset-one-fifth-fullhd{margin-left:20%}html.theme--catppuccin-latte .column.is-offset-two-fifths-fullhd{margin-left:40%}html.theme--catppuccin-latte .column.is-offset-three-fifths-fullhd{margin-left:60%}html.theme--catppuccin-latte .column.is-offset-four-fifths-fullhd{margin-left:80%}html.theme--catppuccin-latte .column.is-0-fullhd{flex:none;width:0%}html.theme--catppuccin-latte .column.is-offset-0-fullhd{margin-left:0%}html.theme--catppuccin-latte .column.is-1-fullhd{flex:none;width:8.33333337%}html.theme--catppuccin-latte .column.is-offset-1-fullhd{margin-left:8.33333337%}html.theme--catppuccin-latte .column.is-2-fullhd{flex:none;width:16.66666674%}html.theme--catppuccin-latte .column.is-offset-2-fullhd{margin-left:16.66666674%}html.theme--catppuccin-latte .column.is-3-fullhd{flex:none;width:25%}html.theme--catppuccin-latte .column.is-offset-3-fullhd{margin-left:25%}html.theme--catppuccin-latte .column.is-4-fullhd{flex:none;width:33.33333337%}html.theme--catppuccin-latte .column.is-offset-4-fullhd{margin-left:33.33333337%}html.theme--catppuccin-latte .column.is-5-fullhd{flex:none;width:41.66666674%}html.theme--catppuccin-latte .column.is-offset-5-fullhd{margin-left:41.66666674%}html.theme--catppuccin-latte .column.is-6-fullhd{flex:none;width:50%}html.theme--catppuccin-latte .column.is-offset-6-fullhd{margin-left:50%}html.theme--catppuccin-latte .column.is-7-fullhd{flex:none;width:58.33333337%}html.theme--catppuccin-latte .column.is-offset-7-fullhd{margin-left:58.33333337%}html.theme--catppuccin-latte .column.is-8-fullhd{flex:none;width:66.66666674%}html.theme--catppuccin-latte .column.is-offset-8-fullhd{margin-left:66.66666674%}html.theme--catppuccin-latte .column.is-9-fullhd{flex:none;width:75%}html.theme--catppuccin-latte .column.is-offset-9-fullhd{margin-left:75%}html.theme--catppuccin-latte .column.is-10-fullhd{flex:none;width:83.33333337%}html.theme--catppuccin-latte .column.is-offset-10-fullhd{margin-left:83.33333337%}html.theme--catppuccin-latte .column.is-11-fullhd{flex:none;width:91.66666674%}html.theme--catppuccin-latte .column.is-offset-11-fullhd{margin-left:91.66666674%}html.theme--catppuccin-latte .column.is-12-fullhd{flex:none;width:100%}html.theme--catppuccin-latte .column.is-offset-12-fullhd{margin-left:100%}}html.theme--catppuccin-latte .columns{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}html.theme--catppuccin-latte .columns:last-child{margin-bottom:-.75rem}html.theme--catppuccin-latte .columns:not(:last-child){margin-bottom:calc(1.5rem - .75rem)}html.theme--catppuccin-latte .columns.is-centered{justify-content:center}html.theme--catppuccin-latte .columns.is-gapless{margin-left:0;margin-right:0;margin-top:0}html.theme--catppuccin-latte .columns.is-gapless>.column{margin:0;padding:0 !important}html.theme--catppuccin-latte .columns.is-gapless:not(:last-child){margin-bottom:1.5rem}html.theme--catppuccin-latte .columns.is-gapless:last-child{margin-bottom:0}html.theme--catppuccin-latte .columns.is-mobile{display:flex}html.theme--catppuccin-latte .columns.is-multiline{flex-wrap:wrap}html.theme--catppuccin-latte .columns.is-vcentered{align-items:center}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .columns:not(.is-desktop){display:flex}}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .columns.is-desktop{display:flex}}html.theme--catppuccin-latte .columns.is-variable{--columnGap: 0.75rem;margin-left:calc(-1 * var(--columnGap));margin-right:calc(-1 * var(--columnGap))}html.theme--catppuccin-latte .columns.is-variable>.column{padding-left:var(--columnGap);padding-right:var(--columnGap)}html.theme--catppuccin-latte .columns.is-variable.is-0{--columnGap: 0rem}@media screen and (max-width: 768px){html.theme--catppuccin-latte .columns.is-variable.is-0-mobile{--columnGap: 0rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .columns.is-variable.is-0-tablet{--columnGap: 0rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-latte .columns.is-variable.is-0-tablet-only{--columnGap: 0rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .columns.is-variable.is-0-touch{--columnGap: 0rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .columns.is-variable.is-0-desktop{--columnGap: 0rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-latte .columns.is-variable.is-0-desktop-only{--columnGap: 0rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-latte .columns.is-variable.is-0-widescreen{--columnGap: 0rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-latte .columns.is-variable.is-0-widescreen-only{--columnGap: 0rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-latte .columns.is-variable.is-0-fullhd{--columnGap: 0rem}}html.theme--catppuccin-latte .columns.is-variable.is-1{--columnGap: .25rem}@media screen and (max-width: 768px){html.theme--catppuccin-latte .columns.is-variable.is-1-mobile{--columnGap: .25rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .columns.is-variable.is-1-tablet{--columnGap: .25rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-latte .columns.is-variable.is-1-tablet-only{--columnGap: .25rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .columns.is-variable.is-1-touch{--columnGap: .25rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .columns.is-variable.is-1-desktop{--columnGap: .25rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-latte .columns.is-variable.is-1-desktop-only{--columnGap: .25rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-latte .columns.is-variable.is-1-widescreen{--columnGap: .25rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-latte .columns.is-variable.is-1-widescreen-only{--columnGap: .25rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-latte .columns.is-variable.is-1-fullhd{--columnGap: .25rem}}html.theme--catppuccin-latte .columns.is-variable.is-2{--columnGap: .5rem}@media screen and (max-width: 768px){html.theme--catppuccin-latte .columns.is-variable.is-2-mobile{--columnGap: .5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .columns.is-variable.is-2-tablet{--columnGap: .5rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-latte .columns.is-variable.is-2-tablet-only{--columnGap: .5rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .columns.is-variable.is-2-touch{--columnGap: .5rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .columns.is-variable.is-2-desktop{--columnGap: .5rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-latte .columns.is-variable.is-2-desktop-only{--columnGap: .5rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-latte .columns.is-variable.is-2-widescreen{--columnGap: .5rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-latte .columns.is-variable.is-2-widescreen-only{--columnGap: .5rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-latte .columns.is-variable.is-2-fullhd{--columnGap: .5rem}}html.theme--catppuccin-latte .columns.is-variable.is-3{--columnGap: .75rem}@media screen and (max-width: 768px){html.theme--catppuccin-latte .columns.is-variable.is-3-mobile{--columnGap: .75rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .columns.is-variable.is-3-tablet{--columnGap: .75rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-latte .columns.is-variable.is-3-tablet-only{--columnGap: .75rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .columns.is-variable.is-3-touch{--columnGap: .75rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .columns.is-variable.is-3-desktop{--columnGap: .75rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-latte .columns.is-variable.is-3-desktop-only{--columnGap: .75rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-latte .columns.is-variable.is-3-widescreen{--columnGap: .75rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-latte .columns.is-variable.is-3-widescreen-only{--columnGap: .75rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-latte .columns.is-variable.is-3-fullhd{--columnGap: .75rem}}html.theme--catppuccin-latte .columns.is-variable.is-4{--columnGap: 1rem}@media screen and (max-width: 768px){html.theme--catppuccin-latte .columns.is-variable.is-4-mobile{--columnGap: 1rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .columns.is-variable.is-4-tablet{--columnGap: 1rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-latte .columns.is-variable.is-4-tablet-only{--columnGap: 1rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .columns.is-variable.is-4-touch{--columnGap: 1rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .columns.is-variable.is-4-desktop{--columnGap: 1rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-latte .columns.is-variable.is-4-desktop-only{--columnGap: 1rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-latte .columns.is-variable.is-4-widescreen{--columnGap: 1rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-latte .columns.is-variable.is-4-widescreen-only{--columnGap: 1rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-latte .columns.is-variable.is-4-fullhd{--columnGap: 1rem}}html.theme--catppuccin-latte .columns.is-variable.is-5{--columnGap: 1.25rem}@media screen and (max-width: 768px){html.theme--catppuccin-latte .columns.is-variable.is-5-mobile{--columnGap: 1.25rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .columns.is-variable.is-5-tablet{--columnGap: 1.25rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-latte .columns.is-variable.is-5-tablet-only{--columnGap: 1.25rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .columns.is-variable.is-5-touch{--columnGap: 1.25rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .columns.is-variable.is-5-desktop{--columnGap: 1.25rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-latte .columns.is-variable.is-5-desktop-only{--columnGap: 1.25rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-latte .columns.is-variable.is-5-widescreen{--columnGap: 1.25rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-latte .columns.is-variable.is-5-widescreen-only{--columnGap: 1.25rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-latte .columns.is-variable.is-5-fullhd{--columnGap: 1.25rem}}html.theme--catppuccin-latte .columns.is-variable.is-6{--columnGap: 1.5rem}@media screen and (max-width: 768px){html.theme--catppuccin-latte .columns.is-variable.is-6-mobile{--columnGap: 1.5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .columns.is-variable.is-6-tablet{--columnGap: 1.5rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-latte .columns.is-variable.is-6-tablet-only{--columnGap: 1.5rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .columns.is-variable.is-6-touch{--columnGap: 1.5rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .columns.is-variable.is-6-desktop{--columnGap: 1.5rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-latte .columns.is-variable.is-6-desktop-only{--columnGap: 1.5rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-latte .columns.is-variable.is-6-widescreen{--columnGap: 1.5rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-latte .columns.is-variable.is-6-widescreen-only{--columnGap: 1.5rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-latte .columns.is-variable.is-6-fullhd{--columnGap: 1.5rem}}html.theme--catppuccin-latte .columns.is-variable.is-7{--columnGap: 1.75rem}@media screen and (max-width: 768px){html.theme--catppuccin-latte .columns.is-variable.is-7-mobile{--columnGap: 1.75rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .columns.is-variable.is-7-tablet{--columnGap: 1.75rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-latte .columns.is-variable.is-7-tablet-only{--columnGap: 1.75rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .columns.is-variable.is-7-touch{--columnGap: 1.75rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .columns.is-variable.is-7-desktop{--columnGap: 1.75rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-latte .columns.is-variable.is-7-desktop-only{--columnGap: 1.75rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-latte .columns.is-variable.is-7-widescreen{--columnGap: 1.75rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-latte .columns.is-variable.is-7-widescreen-only{--columnGap: 1.75rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-latte .columns.is-variable.is-7-fullhd{--columnGap: 1.75rem}}html.theme--catppuccin-latte .columns.is-variable.is-8{--columnGap: 2rem}@media screen and (max-width: 768px){html.theme--catppuccin-latte .columns.is-variable.is-8-mobile{--columnGap: 2rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .columns.is-variable.is-8-tablet{--columnGap: 2rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-latte .columns.is-variable.is-8-tablet-only{--columnGap: 2rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .columns.is-variable.is-8-touch{--columnGap: 2rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .columns.is-variable.is-8-desktop{--columnGap: 2rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-latte .columns.is-variable.is-8-desktop-only{--columnGap: 2rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-latte .columns.is-variable.is-8-widescreen{--columnGap: 2rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-latte .columns.is-variable.is-8-widescreen-only{--columnGap: 2rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-latte .columns.is-variable.is-8-fullhd{--columnGap: 2rem}}html.theme--catppuccin-latte .tile{align-items:stretch;display:block;flex-basis:0;flex-grow:1;flex-shrink:1;min-height:min-content}html.theme--catppuccin-latte .tile.is-ancestor{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}html.theme--catppuccin-latte .tile.is-ancestor:last-child{margin-bottom:-.75rem}html.theme--catppuccin-latte .tile.is-ancestor:not(:last-child){margin-bottom:.75rem}html.theme--catppuccin-latte .tile.is-child{margin:0 !important}html.theme--catppuccin-latte .tile.is-parent{padding:.75rem}html.theme--catppuccin-latte .tile.is-vertical{flex-direction:column}html.theme--catppuccin-latte .tile.is-vertical>.tile.is-child:not(:last-child){margin-bottom:1.5rem !important}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .tile:not(.is-child){display:flex}html.theme--catppuccin-latte .tile.is-1{flex:none;width:8.33333337%}html.theme--catppuccin-latte .tile.is-2{flex:none;width:16.66666674%}html.theme--catppuccin-latte .tile.is-3{flex:none;width:25%}html.theme--catppuccin-latte .tile.is-4{flex:none;width:33.33333337%}html.theme--catppuccin-latte .tile.is-5{flex:none;width:41.66666674%}html.theme--catppuccin-latte .tile.is-6{flex:none;width:50%}html.theme--catppuccin-latte .tile.is-7{flex:none;width:58.33333337%}html.theme--catppuccin-latte .tile.is-8{flex:none;width:66.66666674%}html.theme--catppuccin-latte .tile.is-9{flex:none;width:75%}html.theme--catppuccin-latte .tile.is-10{flex:none;width:83.33333337%}html.theme--catppuccin-latte .tile.is-11{flex:none;width:91.66666674%}html.theme--catppuccin-latte .tile.is-12{flex:none;width:100%}}html.theme--catppuccin-latte .hero{align-items:stretch;display:flex;flex-direction:column;justify-content:space-between}html.theme--catppuccin-latte .hero .navbar{background:none}html.theme--catppuccin-latte .hero .tabs ul{border-bottom:none}html.theme--catppuccin-latte .hero.is-white{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-latte .hero.is-white a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-latte .hero.is-white strong{color:inherit}html.theme--catppuccin-latte .hero.is-white .title{color:#0a0a0a}html.theme--catppuccin-latte .hero.is-white .subtitle{color:rgba(10,10,10,0.9)}html.theme--catppuccin-latte .hero.is-white .subtitle a:not(.button),html.theme--catppuccin-latte .hero.is-white .subtitle strong{color:#0a0a0a}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .hero.is-white .navbar-menu{background-color:#fff}}html.theme--catppuccin-latte .hero.is-white .navbar-item,html.theme--catppuccin-latte .hero.is-white .navbar-link{color:rgba(10,10,10,0.7)}html.theme--catppuccin-latte .hero.is-white a.navbar-item:hover,html.theme--catppuccin-latte .hero.is-white a.navbar-item.is-active,html.theme--catppuccin-latte .hero.is-white .navbar-link:hover,html.theme--catppuccin-latte .hero.is-white .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}html.theme--catppuccin-latte .hero.is-white .tabs a{color:#0a0a0a;opacity:0.9}html.theme--catppuccin-latte .hero.is-white .tabs a:hover{opacity:1}html.theme--catppuccin-latte .hero.is-white .tabs li.is-active a{color:#fff !important;opacity:1}html.theme--catppuccin-latte .hero.is-white .tabs.is-boxed a,html.theme--catppuccin-latte .hero.is-white .tabs.is-toggle a{color:#0a0a0a}html.theme--catppuccin-latte .hero.is-white .tabs.is-boxed a:hover,html.theme--catppuccin-latte .hero.is-white .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-latte .hero.is-white .tabs.is-boxed li.is-active a,html.theme--catppuccin-latte .hero.is-white .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-latte .hero.is-white .tabs.is-toggle li.is-active a,html.theme--catppuccin-latte .hero.is-white .tabs.is-toggle li.is-active a:hover{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}html.theme--catppuccin-latte .hero.is-white.is-bold{background-image:linear-gradient(141deg, #e8e3e4 0%, #fff 71%, #fff 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-latte .hero.is-white.is-bold .navbar-menu{background-image:linear-gradient(141deg, #e8e3e4 0%, #fff 71%, #fff 100%)}}html.theme--catppuccin-latte .hero.is-black{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-latte .hero.is-black a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-latte .hero.is-black strong{color:inherit}html.theme--catppuccin-latte .hero.is-black .title{color:#fff}html.theme--catppuccin-latte .hero.is-black .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-latte .hero.is-black .subtitle a:not(.button),html.theme--catppuccin-latte .hero.is-black .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .hero.is-black .navbar-menu{background-color:#0a0a0a}}html.theme--catppuccin-latte .hero.is-black .navbar-item,html.theme--catppuccin-latte .hero.is-black .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-latte .hero.is-black a.navbar-item:hover,html.theme--catppuccin-latte .hero.is-black a.navbar-item.is-active,html.theme--catppuccin-latte .hero.is-black .navbar-link:hover,html.theme--catppuccin-latte .hero.is-black .navbar-link.is-active{background-color:#000;color:#fff}html.theme--catppuccin-latte .hero.is-black .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-latte .hero.is-black .tabs a:hover{opacity:1}html.theme--catppuccin-latte .hero.is-black .tabs li.is-active a{color:#0a0a0a !important;opacity:1}html.theme--catppuccin-latte .hero.is-black .tabs.is-boxed a,html.theme--catppuccin-latte .hero.is-black .tabs.is-toggle a{color:#fff}html.theme--catppuccin-latte .hero.is-black .tabs.is-boxed a:hover,html.theme--catppuccin-latte .hero.is-black .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-latte .hero.is-black .tabs.is-boxed li.is-active a,html.theme--catppuccin-latte .hero.is-black .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-latte .hero.is-black .tabs.is-toggle li.is-active a,html.theme--catppuccin-latte .hero.is-black .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#0a0a0a}html.theme--catppuccin-latte .hero.is-black.is-bold{background-image:linear-gradient(141deg, #000 0%, #0a0a0a 71%, #181616 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-latte .hero.is-black.is-bold .navbar-menu{background-image:linear-gradient(141deg, #000 0%, #0a0a0a 71%, #181616 100%)}}html.theme--catppuccin-latte .hero.is-light{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .hero.is-light a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-latte .hero.is-light strong{color:inherit}html.theme--catppuccin-latte .hero.is-light .title{color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .hero.is-light .subtitle{color:rgba(0,0,0,0.9)}html.theme--catppuccin-latte .hero.is-light .subtitle a:not(.button),html.theme--catppuccin-latte .hero.is-light .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .hero.is-light .navbar-menu{background-color:#f5f5f5}}html.theme--catppuccin-latte .hero.is-light .navbar-item,html.theme--catppuccin-latte .hero.is-light .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .hero.is-light a.navbar-item:hover,html.theme--catppuccin-latte .hero.is-light a.navbar-item.is-active,html.theme--catppuccin-latte .hero.is-light .navbar-link:hover,html.theme--catppuccin-latte .hero.is-light .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .hero.is-light .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}html.theme--catppuccin-latte .hero.is-light .tabs a:hover{opacity:1}html.theme--catppuccin-latte .hero.is-light .tabs li.is-active a{color:#f5f5f5 !important;opacity:1}html.theme--catppuccin-latte .hero.is-light .tabs.is-boxed a,html.theme--catppuccin-latte .hero.is-light .tabs.is-toggle a{color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .hero.is-light .tabs.is-boxed a:hover,html.theme--catppuccin-latte .hero.is-light .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-latte .hero.is-light .tabs.is-boxed li.is-active a,html.theme--catppuccin-latte .hero.is-light .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-latte .hero.is-light .tabs.is-toggle li.is-active a,html.theme--catppuccin-latte .hero.is-light .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#f5f5f5}html.theme--catppuccin-latte .hero.is-light.is-bold{background-image:linear-gradient(141deg, #dfd8d9 0%, #f5f5f5 71%, #fff 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-latte .hero.is-light.is-bold .navbar-menu{background-image:linear-gradient(141deg, #dfd8d9 0%, #f5f5f5 71%, #fff 100%)}}html.theme--catppuccin-latte .hero.is-dark,html.theme--catppuccin-latte .content kbd.hero{background-color:#ccd0da;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .hero.is-dark a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-latte .content kbd.hero a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-latte .hero.is-dark strong,html.theme--catppuccin-latte .content kbd.hero strong{color:inherit}html.theme--catppuccin-latte .hero.is-dark .title,html.theme--catppuccin-latte .content kbd.hero .title{color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .hero.is-dark .subtitle,html.theme--catppuccin-latte .content kbd.hero .subtitle{color:rgba(0,0,0,0.9)}html.theme--catppuccin-latte .hero.is-dark .subtitle a:not(.button),html.theme--catppuccin-latte .content kbd.hero .subtitle a:not(.button),html.theme--catppuccin-latte .hero.is-dark .subtitle strong,html.theme--catppuccin-latte .content kbd.hero .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .hero.is-dark .navbar-menu,html.theme--catppuccin-latte .content kbd.hero .navbar-menu{background-color:#ccd0da}}html.theme--catppuccin-latte .hero.is-dark .navbar-item,html.theme--catppuccin-latte .content kbd.hero .navbar-item,html.theme--catppuccin-latte .hero.is-dark .navbar-link,html.theme--catppuccin-latte .content kbd.hero .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .hero.is-dark a.navbar-item:hover,html.theme--catppuccin-latte .content kbd.hero a.navbar-item:hover,html.theme--catppuccin-latte .hero.is-dark a.navbar-item.is-active,html.theme--catppuccin-latte .content kbd.hero a.navbar-item.is-active,html.theme--catppuccin-latte .hero.is-dark .navbar-link:hover,html.theme--catppuccin-latte .content kbd.hero .navbar-link:hover,html.theme--catppuccin-latte .hero.is-dark .navbar-link.is-active,html.theme--catppuccin-latte .content kbd.hero .navbar-link.is-active{background-color:#bdc2cf;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .hero.is-dark .tabs a,html.theme--catppuccin-latte .content kbd.hero .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}html.theme--catppuccin-latte .hero.is-dark .tabs a:hover,html.theme--catppuccin-latte .content kbd.hero .tabs a:hover{opacity:1}html.theme--catppuccin-latte .hero.is-dark .tabs li.is-active a,html.theme--catppuccin-latte .content kbd.hero .tabs li.is-active a{color:#ccd0da !important;opacity:1}html.theme--catppuccin-latte .hero.is-dark .tabs.is-boxed a,html.theme--catppuccin-latte .content kbd.hero .tabs.is-boxed a,html.theme--catppuccin-latte .hero.is-dark .tabs.is-toggle a,html.theme--catppuccin-latte .content kbd.hero .tabs.is-toggle a{color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .hero.is-dark .tabs.is-boxed a:hover,html.theme--catppuccin-latte .content kbd.hero .tabs.is-boxed a:hover,html.theme--catppuccin-latte .hero.is-dark .tabs.is-toggle a:hover,html.theme--catppuccin-latte .content kbd.hero .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-latte .hero.is-dark .tabs.is-boxed li.is-active a,html.theme--catppuccin-latte .content kbd.hero .tabs.is-boxed li.is-active a,html.theme--catppuccin-latte .hero.is-dark .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-latte .hero.is-dark .tabs.is-toggle li.is-active a,html.theme--catppuccin-latte .content kbd.hero .tabs.is-toggle li.is-active a,html.theme--catppuccin-latte .hero.is-dark .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#ccd0da}html.theme--catppuccin-latte .hero.is-dark.is-bold,html.theme--catppuccin-latte .content kbd.hero.is-bold{background-image:linear-gradient(141deg, #a7b8cc 0%, #ccd0da 71%, #d9dbe6 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-latte .hero.is-dark.is-bold .navbar-menu,html.theme--catppuccin-latte .content kbd.hero.is-bold .navbar-menu{background-image:linear-gradient(141deg, #a7b8cc 0%, #ccd0da 71%, #d9dbe6 100%)}}html.theme--catppuccin-latte .hero.is-primary,html.theme--catppuccin-latte .docstring>section>a.hero.docs-sourcelink{background-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .hero.is-primary a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-latte .docstring>section>a.hero.docs-sourcelink a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-latte .hero.is-primary strong,html.theme--catppuccin-latte .docstring>section>a.hero.docs-sourcelink strong{color:inherit}html.theme--catppuccin-latte .hero.is-primary .title,html.theme--catppuccin-latte .docstring>section>a.hero.docs-sourcelink .title{color:#fff}html.theme--catppuccin-latte .hero.is-primary .subtitle,html.theme--catppuccin-latte .docstring>section>a.hero.docs-sourcelink .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-latte .hero.is-primary .subtitle a:not(.button),html.theme--catppuccin-latte .docstring>section>a.hero.docs-sourcelink .subtitle a:not(.button),html.theme--catppuccin-latte .hero.is-primary .subtitle strong,html.theme--catppuccin-latte .docstring>section>a.hero.docs-sourcelink .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .hero.is-primary .navbar-menu,html.theme--catppuccin-latte .docstring>section>a.hero.docs-sourcelink .navbar-menu{background-color:#1e66f5}}html.theme--catppuccin-latte .hero.is-primary .navbar-item,html.theme--catppuccin-latte .docstring>section>a.hero.docs-sourcelink .navbar-item,html.theme--catppuccin-latte .hero.is-primary .navbar-link,html.theme--catppuccin-latte .docstring>section>a.hero.docs-sourcelink .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-latte .hero.is-primary a.navbar-item:hover,html.theme--catppuccin-latte .docstring>section>a.hero.docs-sourcelink a.navbar-item:hover,html.theme--catppuccin-latte .hero.is-primary a.navbar-item.is-active,html.theme--catppuccin-latte .docstring>section>a.hero.docs-sourcelink a.navbar-item.is-active,html.theme--catppuccin-latte .hero.is-primary .navbar-link:hover,html.theme--catppuccin-latte .docstring>section>a.hero.docs-sourcelink .navbar-link:hover,html.theme--catppuccin-latte .hero.is-primary .navbar-link.is-active,html.theme--catppuccin-latte .docstring>section>a.hero.docs-sourcelink .navbar-link.is-active{background-color:#0b57ef;color:#fff}html.theme--catppuccin-latte .hero.is-primary .tabs a,html.theme--catppuccin-latte .docstring>section>a.hero.docs-sourcelink .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-latte .hero.is-primary .tabs a:hover,html.theme--catppuccin-latte .docstring>section>a.hero.docs-sourcelink .tabs a:hover{opacity:1}html.theme--catppuccin-latte .hero.is-primary .tabs li.is-active a,html.theme--catppuccin-latte .docstring>section>a.hero.docs-sourcelink .tabs li.is-active a{color:#1e66f5 !important;opacity:1}html.theme--catppuccin-latte .hero.is-primary .tabs.is-boxed a,html.theme--catppuccin-latte .docstring>section>a.hero.docs-sourcelink .tabs.is-boxed a,html.theme--catppuccin-latte .hero.is-primary .tabs.is-toggle a,html.theme--catppuccin-latte .docstring>section>a.hero.docs-sourcelink .tabs.is-toggle a{color:#fff}html.theme--catppuccin-latte .hero.is-primary .tabs.is-boxed a:hover,html.theme--catppuccin-latte .docstring>section>a.hero.docs-sourcelink .tabs.is-boxed a:hover,html.theme--catppuccin-latte .hero.is-primary .tabs.is-toggle a:hover,html.theme--catppuccin-latte .docstring>section>a.hero.docs-sourcelink .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-latte .hero.is-primary .tabs.is-boxed li.is-active a,html.theme--catppuccin-latte .docstring>section>a.hero.docs-sourcelink .tabs.is-boxed li.is-active a,html.theme--catppuccin-latte .hero.is-primary .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-latte .hero.is-primary .tabs.is-toggle li.is-active a,html.theme--catppuccin-latte .docstring>section>a.hero.docs-sourcelink .tabs.is-toggle li.is-active a,html.theme--catppuccin-latte .hero.is-primary .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#1e66f5}html.theme--catppuccin-latte .hero.is-primary.is-bold,html.theme--catppuccin-latte .docstring>section>a.hero.is-bold.docs-sourcelink{background-image:linear-gradient(141deg, #0070e0 0%, #1e66f5 71%, #3153fb 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-latte .hero.is-primary.is-bold .navbar-menu,html.theme--catppuccin-latte .docstring>section>a.hero.is-bold.docs-sourcelink .navbar-menu{background-image:linear-gradient(141deg, #0070e0 0%, #1e66f5 71%, #3153fb 100%)}}html.theme--catppuccin-latte .hero.is-link{background-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .hero.is-link a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-latte .hero.is-link strong{color:inherit}html.theme--catppuccin-latte .hero.is-link .title{color:#fff}html.theme--catppuccin-latte .hero.is-link .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-latte .hero.is-link .subtitle a:not(.button),html.theme--catppuccin-latte .hero.is-link .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .hero.is-link .navbar-menu{background-color:#1e66f5}}html.theme--catppuccin-latte .hero.is-link .navbar-item,html.theme--catppuccin-latte .hero.is-link .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-latte .hero.is-link a.navbar-item:hover,html.theme--catppuccin-latte .hero.is-link a.navbar-item.is-active,html.theme--catppuccin-latte .hero.is-link .navbar-link:hover,html.theme--catppuccin-latte .hero.is-link .navbar-link.is-active{background-color:#0b57ef;color:#fff}html.theme--catppuccin-latte .hero.is-link .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-latte .hero.is-link .tabs a:hover{opacity:1}html.theme--catppuccin-latte .hero.is-link .tabs li.is-active a{color:#1e66f5 !important;opacity:1}html.theme--catppuccin-latte .hero.is-link .tabs.is-boxed a,html.theme--catppuccin-latte .hero.is-link .tabs.is-toggle a{color:#fff}html.theme--catppuccin-latte .hero.is-link .tabs.is-boxed a:hover,html.theme--catppuccin-latte .hero.is-link .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-latte .hero.is-link .tabs.is-boxed li.is-active a,html.theme--catppuccin-latte .hero.is-link .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-latte .hero.is-link .tabs.is-toggle li.is-active a,html.theme--catppuccin-latte .hero.is-link .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#1e66f5}html.theme--catppuccin-latte .hero.is-link.is-bold{background-image:linear-gradient(141deg, #0070e0 0%, #1e66f5 71%, #3153fb 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-latte .hero.is-link.is-bold .navbar-menu{background-image:linear-gradient(141deg, #0070e0 0%, #1e66f5 71%, #3153fb 100%)}}html.theme--catppuccin-latte .hero.is-info{background-color:#179299;color:#fff}html.theme--catppuccin-latte .hero.is-info a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-latte .hero.is-info strong{color:inherit}html.theme--catppuccin-latte .hero.is-info .title{color:#fff}html.theme--catppuccin-latte .hero.is-info .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-latte .hero.is-info .subtitle a:not(.button),html.theme--catppuccin-latte .hero.is-info .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .hero.is-info .navbar-menu{background-color:#179299}}html.theme--catppuccin-latte .hero.is-info .navbar-item,html.theme--catppuccin-latte .hero.is-info .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-latte .hero.is-info a.navbar-item:hover,html.theme--catppuccin-latte .hero.is-info a.navbar-item.is-active,html.theme--catppuccin-latte .hero.is-info .navbar-link:hover,html.theme--catppuccin-latte .hero.is-info .navbar-link.is-active{background-color:#147d83;color:#fff}html.theme--catppuccin-latte .hero.is-info .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-latte .hero.is-info .tabs a:hover{opacity:1}html.theme--catppuccin-latte .hero.is-info .tabs li.is-active a{color:#179299 !important;opacity:1}html.theme--catppuccin-latte .hero.is-info .tabs.is-boxed a,html.theme--catppuccin-latte .hero.is-info .tabs.is-toggle a{color:#fff}html.theme--catppuccin-latte .hero.is-info .tabs.is-boxed a:hover,html.theme--catppuccin-latte .hero.is-info .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-latte .hero.is-info .tabs.is-boxed li.is-active a,html.theme--catppuccin-latte .hero.is-info .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-latte .hero.is-info .tabs.is-toggle li.is-active a,html.theme--catppuccin-latte .hero.is-info .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#179299}html.theme--catppuccin-latte .hero.is-info.is-bold{background-image:linear-gradient(141deg, #0a7367 0%, #179299 71%, #1591b4 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-latte .hero.is-info.is-bold .navbar-menu{background-image:linear-gradient(141deg, #0a7367 0%, #179299 71%, #1591b4 100%)}}html.theme--catppuccin-latte .hero.is-success{background-color:#40a02b;color:#fff}html.theme--catppuccin-latte .hero.is-success a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-latte .hero.is-success strong{color:inherit}html.theme--catppuccin-latte .hero.is-success .title{color:#fff}html.theme--catppuccin-latte .hero.is-success .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-latte .hero.is-success .subtitle a:not(.button),html.theme--catppuccin-latte .hero.is-success .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .hero.is-success .navbar-menu{background-color:#40a02b}}html.theme--catppuccin-latte .hero.is-success .navbar-item,html.theme--catppuccin-latte .hero.is-success .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-latte .hero.is-success a.navbar-item:hover,html.theme--catppuccin-latte .hero.is-success a.navbar-item.is-active,html.theme--catppuccin-latte .hero.is-success .navbar-link:hover,html.theme--catppuccin-latte .hero.is-success .navbar-link.is-active{background-color:#388c26;color:#fff}html.theme--catppuccin-latte .hero.is-success .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-latte .hero.is-success .tabs a:hover{opacity:1}html.theme--catppuccin-latte .hero.is-success .tabs li.is-active a{color:#40a02b !important;opacity:1}html.theme--catppuccin-latte .hero.is-success .tabs.is-boxed a,html.theme--catppuccin-latte .hero.is-success .tabs.is-toggle a{color:#fff}html.theme--catppuccin-latte .hero.is-success .tabs.is-boxed a:hover,html.theme--catppuccin-latte .hero.is-success .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-latte .hero.is-success .tabs.is-boxed li.is-active a,html.theme--catppuccin-latte .hero.is-success .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-latte .hero.is-success .tabs.is-toggle li.is-active a,html.theme--catppuccin-latte .hero.is-success .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#40a02b}html.theme--catppuccin-latte .hero.is-success.is-bold{background-image:linear-gradient(141deg, #3c7f19 0%, #40a02b 71%, #2dba2b 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-latte .hero.is-success.is-bold .navbar-menu{background-image:linear-gradient(141deg, #3c7f19 0%, #40a02b 71%, #2dba2b 100%)}}html.theme--catppuccin-latte .hero.is-warning{background-color:#df8e1d;color:#fff}html.theme--catppuccin-latte .hero.is-warning a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-latte .hero.is-warning strong{color:inherit}html.theme--catppuccin-latte .hero.is-warning .title{color:#fff}html.theme--catppuccin-latte .hero.is-warning .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-latte .hero.is-warning .subtitle a:not(.button),html.theme--catppuccin-latte .hero.is-warning .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .hero.is-warning .navbar-menu{background-color:#df8e1d}}html.theme--catppuccin-latte .hero.is-warning .navbar-item,html.theme--catppuccin-latte .hero.is-warning .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-latte .hero.is-warning a.navbar-item:hover,html.theme--catppuccin-latte .hero.is-warning a.navbar-item.is-active,html.theme--catppuccin-latte .hero.is-warning .navbar-link:hover,html.theme--catppuccin-latte .hero.is-warning .navbar-link.is-active{background-color:#c8801a;color:#fff}html.theme--catppuccin-latte .hero.is-warning .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-latte .hero.is-warning .tabs a:hover{opacity:1}html.theme--catppuccin-latte .hero.is-warning .tabs li.is-active a{color:#df8e1d !important;opacity:1}html.theme--catppuccin-latte .hero.is-warning .tabs.is-boxed a,html.theme--catppuccin-latte .hero.is-warning .tabs.is-toggle a{color:#fff}html.theme--catppuccin-latte .hero.is-warning .tabs.is-boxed a:hover,html.theme--catppuccin-latte .hero.is-warning .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-latte .hero.is-warning .tabs.is-boxed li.is-active a,html.theme--catppuccin-latte .hero.is-warning .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-latte .hero.is-warning .tabs.is-toggle li.is-active a,html.theme--catppuccin-latte .hero.is-warning .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#df8e1d}html.theme--catppuccin-latte .hero.is-warning.is-bold{background-image:linear-gradient(141deg, #bc560d 0%, #df8e1d 71%, #eaba2b 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-latte .hero.is-warning.is-bold .navbar-menu{background-image:linear-gradient(141deg, #bc560d 0%, #df8e1d 71%, #eaba2b 100%)}}html.theme--catppuccin-latte .hero.is-danger{background-color:#d20f39;color:#fff}html.theme--catppuccin-latte .hero.is-danger a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-latte .hero.is-danger strong{color:inherit}html.theme--catppuccin-latte .hero.is-danger .title{color:#fff}html.theme--catppuccin-latte .hero.is-danger .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-latte .hero.is-danger .subtitle a:not(.button),html.theme--catppuccin-latte .hero.is-danger .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .hero.is-danger .navbar-menu{background-color:#d20f39}}html.theme--catppuccin-latte .hero.is-danger .navbar-item,html.theme--catppuccin-latte .hero.is-danger .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-latte .hero.is-danger a.navbar-item:hover,html.theme--catppuccin-latte .hero.is-danger a.navbar-item.is-active,html.theme--catppuccin-latte .hero.is-danger .navbar-link:hover,html.theme--catppuccin-latte .hero.is-danger .navbar-link.is-active{background-color:#ba0d33;color:#fff}html.theme--catppuccin-latte .hero.is-danger .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-latte .hero.is-danger .tabs a:hover{opacity:1}html.theme--catppuccin-latte .hero.is-danger .tabs li.is-active a{color:#d20f39 !important;opacity:1}html.theme--catppuccin-latte .hero.is-danger .tabs.is-boxed a,html.theme--catppuccin-latte .hero.is-danger .tabs.is-toggle a{color:#fff}html.theme--catppuccin-latte .hero.is-danger .tabs.is-boxed a:hover,html.theme--catppuccin-latte .hero.is-danger .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-latte .hero.is-danger .tabs.is-boxed li.is-active a,html.theme--catppuccin-latte .hero.is-danger .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-latte .hero.is-danger .tabs.is-toggle li.is-active a,html.theme--catppuccin-latte .hero.is-danger .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#d20f39}html.theme--catppuccin-latte .hero.is-danger.is-bold{background-image:linear-gradient(141deg, #ab0343 0%, #d20f39 71%, #f00a16 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-latte .hero.is-danger.is-bold .navbar-menu{background-image:linear-gradient(141deg, #ab0343 0%, #d20f39 71%, #f00a16 100%)}}html.theme--catppuccin-latte .hero.is-small .hero-body,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.hero .hero-body{padding:1.5rem}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .hero.is-medium .hero-body{padding:9rem 4.5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .hero.is-large .hero-body{padding:18rem 6rem}}html.theme--catppuccin-latte .hero.is-halfheight .hero-body,html.theme--catppuccin-latte .hero.is-fullheight .hero-body,html.theme--catppuccin-latte .hero.is-fullheight-with-navbar .hero-body{align-items:center;display:flex}html.theme--catppuccin-latte .hero.is-halfheight .hero-body>.container,html.theme--catppuccin-latte .hero.is-fullheight .hero-body>.container,html.theme--catppuccin-latte .hero.is-fullheight-with-navbar .hero-body>.container{flex-grow:1;flex-shrink:1}html.theme--catppuccin-latte .hero.is-halfheight{min-height:50vh}html.theme--catppuccin-latte .hero.is-fullheight{min-height:100vh}html.theme--catppuccin-latte .hero-video{overflow:hidden}html.theme--catppuccin-latte .hero-video video{left:50%;min-height:100%;min-width:100%;position:absolute;top:50%;transform:translate3d(-50%, -50%, 0)}html.theme--catppuccin-latte .hero-video.is-transparent{opacity:0.3}@media screen and (max-width: 768px){html.theme--catppuccin-latte .hero-video{display:none}}html.theme--catppuccin-latte .hero-buttons{margin-top:1.5rem}@media screen and (max-width: 768px){html.theme--catppuccin-latte .hero-buttons .button{display:flex}html.theme--catppuccin-latte .hero-buttons .button:not(:last-child){margin-bottom:0.75rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .hero-buttons{display:flex;justify-content:center}html.theme--catppuccin-latte .hero-buttons .button:not(:last-child){margin-right:1.5rem}}html.theme--catppuccin-latte .hero-head,html.theme--catppuccin-latte .hero-foot{flex-grow:0;flex-shrink:0}html.theme--catppuccin-latte .hero-body{flex-grow:1;flex-shrink:0;padding:3rem 1.5rem}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .hero-body{padding:3rem 3rem}}html.theme--catppuccin-latte .section{padding:3rem 1.5rem}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .section{padding:3rem 3rem}html.theme--catppuccin-latte .section.is-medium{padding:9rem 4.5rem}html.theme--catppuccin-latte .section.is-large{padding:18rem 6rem}}html.theme--catppuccin-latte .footer{background-color:#e6e9ef;padding:3rem 1.5rem 6rem}html.theme--catppuccin-latte h1 .docs-heading-anchor,html.theme--catppuccin-latte h1 .docs-heading-anchor:hover,html.theme--catppuccin-latte h1 .docs-heading-anchor:visited,html.theme--catppuccin-latte h2 .docs-heading-anchor,html.theme--catppuccin-latte h2 .docs-heading-anchor:hover,html.theme--catppuccin-latte h2 .docs-heading-anchor:visited,html.theme--catppuccin-latte h3 .docs-heading-anchor,html.theme--catppuccin-latte h3 .docs-heading-anchor:hover,html.theme--catppuccin-latte h3 .docs-heading-anchor:visited,html.theme--catppuccin-latte h4 .docs-heading-anchor,html.theme--catppuccin-latte h4 .docs-heading-anchor:hover,html.theme--catppuccin-latte h4 .docs-heading-anchor:visited,html.theme--catppuccin-latte h5 .docs-heading-anchor,html.theme--catppuccin-latte h5 .docs-heading-anchor:hover,html.theme--catppuccin-latte h5 .docs-heading-anchor:visited,html.theme--catppuccin-latte h6 .docs-heading-anchor,html.theme--catppuccin-latte h6 .docs-heading-anchor:hover,html.theme--catppuccin-latte h6 .docs-heading-anchor:visited{color:#4c4f69}html.theme--catppuccin-latte h1 .docs-heading-anchor-permalink,html.theme--catppuccin-latte h2 .docs-heading-anchor-permalink,html.theme--catppuccin-latte h3 .docs-heading-anchor-permalink,html.theme--catppuccin-latte h4 .docs-heading-anchor-permalink,html.theme--catppuccin-latte h5 .docs-heading-anchor-permalink,html.theme--catppuccin-latte h6 .docs-heading-anchor-permalink{visibility:hidden;vertical-align:middle;margin-left:0.5em;font-size:0.7rem}html.theme--catppuccin-latte h1 .docs-heading-anchor-permalink::before,html.theme--catppuccin-latte h2 .docs-heading-anchor-permalink::before,html.theme--catppuccin-latte h3 .docs-heading-anchor-permalink::before,html.theme--catppuccin-latte h4 .docs-heading-anchor-permalink::before,html.theme--catppuccin-latte h5 .docs-heading-anchor-permalink::before,html.theme--catppuccin-latte h6 .docs-heading-anchor-permalink::before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f0c1"}html.theme--catppuccin-latte h1:hover .docs-heading-anchor-permalink,html.theme--catppuccin-latte h2:hover .docs-heading-anchor-permalink,html.theme--catppuccin-latte h3:hover .docs-heading-anchor-permalink,html.theme--catppuccin-latte h4:hover .docs-heading-anchor-permalink,html.theme--catppuccin-latte h5:hover .docs-heading-anchor-permalink,html.theme--catppuccin-latte h6:hover .docs-heading-anchor-permalink{visibility:visible}html.theme--catppuccin-latte .docs-dark-only{display:none !important}html.theme--catppuccin-latte pre{position:relative;overflow:hidden}html.theme--catppuccin-latte pre code,html.theme--catppuccin-latte pre code.hljs{padding:0 .75rem !important;overflow:auto;display:block}html.theme--catppuccin-latte pre code:first-of-type,html.theme--catppuccin-latte pre code.hljs:first-of-type{padding-top:0.5rem !important}html.theme--catppuccin-latte pre code:last-of-type,html.theme--catppuccin-latte pre code.hljs:last-of-type{padding-bottom:0.5rem !important}html.theme--catppuccin-latte pre .copy-button{opacity:0.2;transition:opacity 0.2s;position:absolute;right:0em;top:0em;padding:0.5em;width:2.5em;height:2.5em;background:transparent;border:none;font-family:"Font Awesome 6 Free";color:#4c4f69;cursor:pointer;text-align:center}html.theme--catppuccin-latte pre .copy-button:focus,html.theme--catppuccin-latte pre .copy-button:hover{opacity:1;background:rgba(76,79,105,0.1);color:#1e66f5}html.theme--catppuccin-latte pre .copy-button.success{color:#40a02b;opacity:1}html.theme--catppuccin-latte pre .copy-button.error{color:#d20f39;opacity:1}html.theme--catppuccin-latte pre:hover .copy-button{opacity:1}html.theme--catppuccin-latte .admonition{background-color:#e6e9ef;border-style:solid;border-width:2px;border-color:#5c5f77;border-radius:4px;font-size:1rem}html.theme--catppuccin-latte .admonition strong{color:currentColor}html.theme--catppuccin-latte .admonition.is-small,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.admonition{font-size:.75rem}html.theme--catppuccin-latte .admonition.is-medium{font-size:1.25rem}html.theme--catppuccin-latte .admonition.is-large{font-size:1.5rem}html.theme--catppuccin-latte .admonition.is-default{background-color:#e6e9ef;border-color:#5c5f77}html.theme--catppuccin-latte .admonition.is-default>.admonition-header{background-color:rgba(0,0,0,0);color:#5c5f77}html.theme--catppuccin-latte .admonition.is-default>.admonition-body{color:#4c4f69}html.theme--catppuccin-latte .admonition.is-info{background-color:#e6e9ef;border-color:#179299}html.theme--catppuccin-latte .admonition.is-info>.admonition-header{background-color:rgba(0,0,0,0);color:#179299}html.theme--catppuccin-latte .admonition.is-info>.admonition-body{color:#4c4f69}html.theme--catppuccin-latte .admonition.is-success{background-color:#e6e9ef;border-color:#40a02b}html.theme--catppuccin-latte .admonition.is-success>.admonition-header{background-color:rgba(0,0,0,0);color:#40a02b}html.theme--catppuccin-latte .admonition.is-success>.admonition-body{color:#4c4f69}html.theme--catppuccin-latte .admonition.is-warning{background-color:#e6e9ef;border-color:#df8e1d}html.theme--catppuccin-latte .admonition.is-warning>.admonition-header{background-color:rgba(0,0,0,0);color:#df8e1d}html.theme--catppuccin-latte .admonition.is-warning>.admonition-body{color:#4c4f69}html.theme--catppuccin-latte .admonition.is-danger{background-color:#e6e9ef;border-color:#d20f39}html.theme--catppuccin-latte .admonition.is-danger>.admonition-header{background-color:rgba(0,0,0,0);color:#d20f39}html.theme--catppuccin-latte .admonition.is-danger>.admonition-body{color:#4c4f69}html.theme--catppuccin-latte .admonition.is-compat{background-color:#e6e9ef;border-color:#04a5e5}html.theme--catppuccin-latte .admonition.is-compat>.admonition-header{background-color:rgba(0,0,0,0);color:#04a5e5}html.theme--catppuccin-latte .admonition.is-compat>.admonition-body{color:#4c4f69}html.theme--catppuccin-latte .admonition.is-todo{background-color:#e6e9ef;border-color:#8839ef}html.theme--catppuccin-latte .admonition.is-todo>.admonition-header{background-color:rgba(0,0,0,0);color:#8839ef}html.theme--catppuccin-latte .admonition.is-todo>.admonition-body{color:#4c4f69}html.theme--catppuccin-latte .admonition-header{color:#5c5f77;background-color:rgba(0,0,0,0);align-items:center;font-weight:700;justify-content:space-between;line-height:1.25;padding:0.5rem .75rem;position:relative}html.theme--catppuccin-latte .admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;margin-right:.75rem;content:"\f06a"}html.theme--catppuccin-latte details.admonition.is-details>.admonition-header{list-style:none}html.theme--catppuccin-latte details.admonition.is-details>.admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f055"}html.theme--catppuccin-latte details.admonition.is-details[open]>.admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f056"}html.theme--catppuccin-latte .admonition-body{color:#4c4f69;padding:0.5rem .75rem}html.theme--catppuccin-latte .admonition-body pre{background-color:#e6e9ef}html.theme--catppuccin-latte .admonition-body code{background-color:#e6e9ef}html.theme--catppuccin-latte .docstring{margin-bottom:1em;background-color:rgba(0,0,0,0);border:2px solid #acb0be;border-radius:4px;box-shadow:none;max-width:100%}html.theme--catppuccin-latte .docstring>header{cursor:pointer;display:flex;flex-grow:1;align-items:stretch;padding:0.5rem .75rem;background-color:#e6e9ef;box-shadow:0 0.125em 0.25em rgba(10,10,10,0.1);box-shadow:none;border-bottom:1px solid #acb0be;overflow:auto}html.theme--catppuccin-latte .docstring>header code{background-color:transparent}html.theme--catppuccin-latte .docstring>header .docstring-article-toggle-button{min-width:1.1rem;padding:0.2rem 0.2rem 0.2rem 0}html.theme--catppuccin-latte .docstring>header .docstring-binding{margin-right:0.3em}html.theme--catppuccin-latte .docstring>header .docstring-category{margin-left:0.3em}html.theme--catppuccin-latte .docstring>section{position:relative;padding:.75rem .75rem;border-bottom:1px solid #acb0be}html.theme--catppuccin-latte .docstring>section:last-child{border-bottom:none}html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink{transition:opacity 0.3s;opacity:0;position:absolute;right:.375rem;bottom:.375rem}html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink:focus{opacity:1 !important}html.theme--catppuccin-latte .docstring:hover>section>a.docs-sourcelink{opacity:0.2}html.theme--catppuccin-latte .docstring:focus-within>section>a.docs-sourcelink{opacity:0.2}html.theme--catppuccin-latte .docstring>section:hover a.docs-sourcelink{opacity:1}html.theme--catppuccin-latte .documenter-example-output{background-color:#eff1f5}html.theme--catppuccin-latte .outdated-warning-overlay{position:fixed;top:0;left:0;right:0;box-shadow:0 0 10px rgba(0,0,0,0.3);z-index:999;background-color:#e6e9ef;color:#4c4f69;border-bottom:3px solid rgba(0,0,0,0);padding:10px 35px;text-align:center;font-size:15px}html.theme--catppuccin-latte .outdated-warning-overlay .outdated-warning-closer{position:absolute;top:calc(50% - 10px);right:18px;cursor:pointer;width:12px}html.theme--catppuccin-latte .outdated-warning-overlay a{color:#1e66f5}html.theme--catppuccin-latte .outdated-warning-overlay a:hover{color:#04a5e5}html.theme--catppuccin-latte .content pre{border:2px solid #acb0be;border-radius:4px}html.theme--catppuccin-latte .content code{font-weight:inherit}html.theme--catppuccin-latte .content a code{color:#1e66f5}html.theme--catppuccin-latte .content a:hover code{color:#04a5e5}html.theme--catppuccin-latte .content h1 code,html.theme--catppuccin-latte .content h2 code,html.theme--catppuccin-latte .content h3 code,html.theme--catppuccin-latte .content h4 code,html.theme--catppuccin-latte .content h5 code,html.theme--catppuccin-latte .content h6 code{color:#4c4f69}html.theme--catppuccin-latte .content table{display:block;width:initial;max-width:100%;overflow-x:auto}html.theme--catppuccin-latte .content blockquote>ul:first-child,html.theme--catppuccin-latte .content blockquote>ol:first-child,html.theme--catppuccin-latte .content .admonition-body>ul:first-child,html.theme--catppuccin-latte .content .admonition-body>ol:first-child{margin-top:0}html.theme--catppuccin-latte pre,html.theme--catppuccin-latte code{font-variant-ligatures:no-contextual}html.theme--catppuccin-latte .breadcrumb a.is-disabled{cursor:default;pointer-events:none}html.theme--catppuccin-latte .breadcrumb a.is-disabled,html.theme--catppuccin-latte .breadcrumb a.is-disabled:hover{color:#41445a}html.theme--catppuccin-latte .hljs{background:initial !important}html.theme--catppuccin-latte .katex .katex-mathml{top:0;right:0}html.theme--catppuccin-latte .katex-display,html.theme--catppuccin-latte mjx-container,html.theme--catppuccin-latte .MathJax_Display{margin:0.5em 0 !important}html.theme--catppuccin-latte html{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto}html.theme--catppuccin-latte li.no-marker{list-style:none}html.theme--catppuccin-latte #documenter .docs-main>article{overflow-wrap:break-word}html.theme--catppuccin-latte #documenter .docs-main>article .math-container{overflow-x:auto;overflow-y:hidden}@media screen and (min-width: 1056px){html.theme--catppuccin-latte #documenter .docs-main{max-width:52rem;margin-left:20rem;padding-right:1rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-latte #documenter .docs-main{width:100%}html.theme--catppuccin-latte #documenter .docs-main>article{max-width:52rem;margin-left:auto;margin-right:auto;margin-bottom:1rem;padding:0 1rem}html.theme--catppuccin-latte #documenter .docs-main>header,html.theme--catppuccin-latte #documenter .docs-main>nav{max-width:100%;width:100%;margin:0}}html.theme--catppuccin-latte #documenter .docs-main header.docs-navbar{background-color:#eff1f5;border-bottom:1px solid #acb0be;z-index:2;min-height:4rem;margin-bottom:1rem;display:flex}html.theme--catppuccin-latte #documenter .docs-main header.docs-navbar .breadcrumb{flex-grow:1;overflow-x:hidden}html.theme--catppuccin-latte #documenter .docs-main header.docs-navbar .docs-sidebar-button{display:block;font-size:1.5rem;padding-bottom:0.1rem;margin-right:1rem}html.theme--catppuccin-latte #documenter .docs-main header.docs-navbar .docs-right{display:flex;white-space:nowrap;gap:1rem;align-items:center}html.theme--catppuccin-latte #documenter .docs-main header.docs-navbar .docs-right .docs-icon,html.theme--catppuccin-latte #documenter .docs-main header.docs-navbar .docs-right .docs-label{display:inline-block}html.theme--catppuccin-latte #documenter .docs-main header.docs-navbar .docs-right .docs-label{padding:0;margin-left:0.3em}@media screen and (max-width: 1055px){html.theme--catppuccin-latte #documenter .docs-main header.docs-navbar .docs-right .docs-navbar-link{margin-left:0.4rem;margin-right:0.4rem}}html.theme--catppuccin-latte #documenter .docs-main header.docs-navbar>*{margin:auto 0}@media screen and (max-width: 1055px){html.theme--catppuccin-latte #documenter .docs-main header.docs-navbar{position:sticky;top:0;padding:0 1rem;transition-property:top, box-shadow;-webkit-transition-property:top, box-shadow;transition-duration:0.3s;-webkit-transition-duration:0.3s}html.theme--catppuccin-latte #documenter .docs-main header.docs-navbar.headroom--not-top{box-shadow:.2rem 0rem .4rem #171717;transition-duration:0.7s;-webkit-transition-duration:0.7s}html.theme--catppuccin-latte #documenter .docs-main header.docs-navbar.headroom--unpinned.headroom--not-top.headroom--not-bottom{top:-4.5rem;transition-duration:0.7s;-webkit-transition-duration:0.7s}}html.theme--catppuccin-latte #documenter .docs-main section.footnotes{border-top:1px solid #acb0be}html.theme--catppuccin-latte #documenter .docs-main section.footnotes li .tag:first-child,html.theme--catppuccin-latte #documenter .docs-main section.footnotes li .docstring>section>a.docs-sourcelink:first-child,html.theme--catppuccin-latte #documenter .docs-main section.footnotes li .content kbd:first-child,html.theme--catppuccin-latte .content #documenter .docs-main section.footnotes li kbd:first-child{margin-right:1em;margin-bottom:0.4em}html.theme--catppuccin-latte #documenter .docs-main .docs-footer{display:flex;flex-wrap:wrap;margin-left:0;margin-right:0;border-top:1px solid #acb0be;padding-top:1rem;padding-bottom:1rem}@media screen and (max-width: 1055px){html.theme--catppuccin-latte #documenter .docs-main .docs-footer{padding-left:1rem;padding-right:1rem}}html.theme--catppuccin-latte #documenter .docs-main .docs-footer .docs-footer-nextpage,html.theme--catppuccin-latte #documenter .docs-main .docs-footer .docs-footer-prevpage{flex-grow:1}html.theme--catppuccin-latte #documenter .docs-main .docs-footer .docs-footer-nextpage{text-align:right}html.theme--catppuccin-latte #documenter .docs-main .docs-footer .flexbox-break{flex-basis:100%;height:0}html.theme--catppuccin-latte #documenter .docs-main .docs-footer .footer-message{font-size:0.8em;margin:0.5em auto 0 auto;text-align:center}html.theme--catppuccin-latte #documenter .docs-sidebar{display:flex;flex-direction:column;color:#4c4f69;background-color:#e6e9ef;border-right:1px solid #acb0be;padding:0;flex:0 0 18rem;z-index:5;font-size:1rem;position:fixed;left:-18rem;width:18rem;height:100%;transition:left 0.3s}html.theme--catppuccin-latte #documenter .docs-sidebar.visible{left:0;box-shadow:.4rem 0rem .8rem #171717}@media screen and (min-width: 1056px){html.theme--catppuccin-latte #documenter .docs-sidebar.visible{box-shadow:none}}@media screen and (min-width: 1056px){html.theme--catppuccin-latte #documenter .docs-sidebar{left:0;top:0}}html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo{margin-top:1rem;padding:0 1rem}html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img{max-height:6rem;margin:auto}html.theme--catppuccin-latte #documenter .docs-sidebar .docs-package-name{flex-shrink:0;font-size:1.5rem;font-weight:700;text-align:center;white-space:nowrap;overflow:hidden;padding:0.5rem 0}html.theme--catppuccin-latte #documenter .docs-sidebar .docs-package-name .docs-autofit{max-width:16.2rem}html.theme--catppuccin-latte #documenter .docs-sidebar .docs-package-name a,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-package-name a:hover{color:#4c4f69}html.theme--catppuccin-latte #documenter .docs-sidebar .docs-version-selector{border-top:1px solid #acb0be;display:none;padding:0.5rem}html.theme--catppuccin-latte #documenter .docs-sidebar .docs-version-selector.visible{display:flex}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu{flex-grow:1;user-select:none;border-top:1px solid #acb0be;padding-bottom:1.5rem}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu>li>.tocitem{font-weight:bold}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu>li li{font-size:.95rem;margin-left:1em;border-left:1px solid #acb0be}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu input.collapse-toggle{display:none}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu ul.collapsed{display:none}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu input:checked~ul.collapsed{display:block}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu label.tocitem{display:flex}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu label.tocitem .docs-label{flex-grow:2}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu label.tocitem .docs-chevron{display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1;font-size:.75rem;margin-left:1rem;margin-top:auto;margin-bottom:auto}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu label.tocitem .docs-chevron::before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f054"}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu input:checked~label.tocitem .docs-chevron::before{content:"\f078"}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu .tocitem{display:block;padding:0.5rem 0.5rem}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu .tocitem,html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu .tocitem:hover{color:#4c4f69;background:#e6e9ef}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu a.tocitem:hover,html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu label.tocitem:hover{color:#4c4f69;background-color:#f2f4f7}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu li.is-active{border-top:1px solid #acb0be;border-bottom:1px solid #acb0be;background-color:#dce0e8}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu li.is-active .tocitem,html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu li.is-active .tocitem:hover{background-color:#dce0e8;color:#4c4f69}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu li.is-active ul.internal .tocitem:hover{background-color:#f2f4f7;color:#4c4f69}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu>li.is-active:first-child{border-top:none}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu ul.internal{margin:0 0.5rem 0.5rem;border-top:1px solid #acb0be}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu ul.internal li{font-size:.85rem;border-left:none;margin-left:0;margin-top:0.5rem}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu ul.internal .tocitem{width:100%;padding:0}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu ul.internal .tocitem::before{content:"⚬";margin-right:0.4em}html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search{margin:auto;margin-top:0.5rem;margin-bottom:0.5rem}html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input{width:14.4rem}html.theme--catppuccin-latte #documenter .docs-sidebar #documenter-search-query{color:#868c98;width:14.4rem;box-shadow:inset 0 1px 2px rgba(10,10,10,0.1)}@media screen and (min-width: 1056px){html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu{overflow-y:auto;-webkit-overflow-scroll:touch}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar{width:.3rem;background:none}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar-thumb{border-radius:5px 0px 0px 5px;background:#fff}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar-thumb:hover{background:#fff}}@media screen and (max-width: 1055px){html.theme--catppuccin-latte #documenter .docs-sidebar{overflow-y:auto;-webkit-overflow-scroll:touch}html.theme--catppuccin-latte #documenter .docs-sidebar::-webkit-scrollbar{width:.3rem;background:none}html.theme--catppuccin-latte #documenter .docs-sidebar::-webkit-scrollbar-thumb{border-radius:5px 0px 0px 5px;background:#fff}html.theme--catppuccin-latte #documenter .docs-sidebar::-webkit-scrollbar-thumb:hover{background:#fff}}html.theme--catppuccin-latte kbd.search-modal-key-hints{border-radius:0.25rem;border:1px solid rgba(245,245,245,0.6);box-shadow:0 2px 0 1px rgba(245,245,245,0.6);cursor:default;font-size:0.9rem;line-height:1.5;min-width:0.75rem;text-align:center;padding:0.1rem 0.3rem;position:relative;top:-1px}html.theme--catppuccin-latte .search-min-width-50{min-width:50%}html.theme--catppuccin-latte .search-min-height-100{min-height:100%}html.theme--catppuccin-latte .search-modal-card-body{max-height:calc(100vh - 15rem)}html.theme--catppuccin-latte .search-result-link{border-radius:0.7em;transition:all 300ms}html.theme--catppuccin-latte .search-result-link:hover,html.theme--catppuccin-latte .search-result-link:focus{background-color:rgba(0,128,128,0.1)}html.theme--catppuccin-latte .search-result-link .property-search-result-badge,html.theme--catppuccin-latte .search-result-link .search-filter{transition:all 300ms}html.theme--catppuccin-latte .property-search-result-badge,html.theme--catppuccin-latte .search-filter{padding:0.15em 0.5em;font-size:0.8em;font-style:italic;text-transform:none !important;line-height:1.5;color:#f5f5f5;background-color:rgba(51,65,85,0.501961);border-radius:0.6rem}html.theme--catppuccin-latte .search-result-link:hover .property-search-result-badge,html.theme--catppuccin-latte .search-result-link:hover .search-filter,html.theme--catppuccin-latte .search-result-link:focus .property-search-result-badge,html.theme--catppuccin-latte .search-result-link:focus .search-filter{color:#333;background-color:#f1f5f9}html.theme--catppuccin-latte .search-filter{color:#333;background-color:#f5f5f5;transition:all 300ms}html.theme--catppuccin-latte .search-filter:hover,html.theme--catppuccin-latte .search-filter:focus{color:#333}html.theme--catppuccin-latte .search-filter-selected{color:#ccd0da;background-color:#7287fd}html.theme--catppuccin-latte .search-filter-selected:hover,html.theme--catppuccin-latte .search-filter-selected:focus{color:#ccd0da}html.theme--catppuccin-latte .search-result-highlight{background-color:#ffdd57;color:black}html.theme--catppuccin-latte .search-divider{border-bottom:1px solid #acb0be}html.theme--catppuccin-latte .search-result-title{width:85%;color:#f5f5f5}html.theme--catppuccin-latte .search-result-code-title{font-size:0.875rem;font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace}html.theme--catppuccin-latte #search-modal .modal-card-body::-webkit-scrollbar,html.theme--catppuccin-latte #search-modal .filter-tabs::-webkit-scrollbar{height:10px;width:10px;background-color:transparent}html.theme--catppuccin-latte #search-modal .modal-card-body::-webkit-scrollbar-thumb,html.theme--catppuccin-latte #search-modal .filter-tabs::-webkit-scrollbar-thumb{background-color:gray;border-radius:1rem}html.theme--catppuccin-latte #search-modal .modal-card-body::-webkit-scrollbar-track,html.theme--catppuccin-latte #search-modal .filter-tabs::-webkit-scrollbar-track{-webkit-box-shadow:inset 0 0 6px rgba(0,0,0,0.6);background-color:transparent}html.theme--catppuccin-latte .w-100{width:100%}html.theme--catppuccin-latte .gap-2{gap:0.5rem}html.theme--catppuccin-latte .gap-4{gap:1rem}html.theme--catppuccin-latte .gap-8{gap:2rem}html.theme--catppuccin-latte{background-color:#eff1f5;font-size:16px;min-width:300px;overflow-x:auto;overflow-y:scroll;text-rendering:optimizeLegibility;text-size-adjust:100%}html.theme--catppuccin-latte a{transition:all 200ms ease}html.theme--catppuccin-latte .label{color:#4c4f69}html.theme--catppuccin-latte .button,html.theme--catppuccin-latte .control.has-icons-left .icon,html.theme--catppuccin-latte .control.has-icons-right .icon,html.theme--catppuccin-latte .input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-latte .pagination-ellipsis,html.theme--catppuccin-latte .pagination-link,html.theme--catppuccin-latte .pagination-next,html.theme--catppuccin-latte .pagination-previous,html.theme--catppuccin-latte .select,html.theme--catppuccin-latte .select select,html.theme--catppuccin-latte .textarea{height:2.5em;color:#4c4f69}html.theme--catppuccin-latte .input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-latte .textarea{transition:all 200ms ease;box-shadow:none;border-width:1px;padding-left:1em;padding-right:1em;color:#4c4f69}html.theme--catppuccin-latte .select:after,html.theme--catppuccin-latte .select select{border-width:1px}html.theme--catppuccin-latte .menu-list a{transition:all 300ms ease}html.theme--catppuccin-latte .modal-card-foot,html.theme--catppuccin-latte .modal-card-head{border-color:#acb0be}html.theme--catppuccin-latte .navbar{border-radius:.4em}html.theme--catppuccin-latte .navbar.is-transparent{background:none}html.theme--catppuccin-latte .navbar.is-primary .navbar-dropdown a.navbar-item.is-active,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-dropdown a.navbar-item.is-active{background-color:#1e66f5}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .navbar .navbar-menu{background-color:#1e66f5;border-radius:0 0 .4em .4em}}html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink:not(body){color:#ccd0da}html.theme--catppuccin-latte .tag.is-link:not(body),html.theme--catppuccin-latte .docstring>section>a.is-link.docs-sourcelink:not(body),html.theme--catppuccin-latte .content kbd.is-link:not(body){color:#ccd0da}html.theme--catppuccin-latte .ansi span.sgr1{font-weight:bolder}html.theme--catppuccin-latte .ansi span.sgr2{font-weight:lighter}html.theme--catppuccin-latte .ansi span.sgr3{font-style:italic}html.theme--catppuccin-latte .ansi span.sgr4{text-decoration:underline}html.theme--catppuccin-latte .ansi span.sgr7{color:#eff1f5;background-color:#4c4f69}html.theme--catppuccin-latte .ansi span.sgr8{color:transparent}html.theme--catppuccin-latte .ansi span.sgr8 span{color:transparent}html.theme--catppuccin-latte .ansi span.sgr9{text-decoration:line-through}html.theme--catppuccin-latte .ansi span.sgr30{color:#5c5f77}html.theme--catppuccin-latte .ansi span.sgr31{color:#d20f39}html.theme--catppuccin-latte .ansi span.sgr32{color:#40a02b}html.theme--catppuccin-latte .ansi span.sgr33{color:#df8e1d}html.theme--catppuccin-latte .ansi span.sgr34{color:#1e66f5}html.theme--catppuccin-latte .ansi span.sgr35{color:#ea76cb}html.theme--catppuccin-latte .ansi span.sgr36{color:#179299}html.theme--catppuccin-latte .ansi span.sgr37{color:#acb0be}html.theme--catppuccin-latte .ansi span.sgr40{background-color:#5c5f77}html.theme--catppuccin-latte .ansi span.sgr41{background-color:#d20f39}html.theme--catppuccin-latte .ansi span.sgr42{background-color:#40a02b}html.theme--catppuccin-latte .ansi span.sgr43{background-color:#df8e1d}html.theme--catppuccin-latte .ansi span.sgr44{background-color:#1e66f5}html.theme--catppuccin-latte .ansi span.sgr45{background-color:#ea76cb}html.theme--catppuccin-latte .ansi span.sgr46{background-color:#179299}html.theme--catppuccin-latte .ansi span.sgr47{background-color:#acb0be}html.theme--catppuccin-latte .ansi span.sgr90{color:#6c6f85}html.theme--catppuccin-latte .ansi span.sgr91{color:#d20f39}html.theme--catppuccin-latte .ansi span.sgr92{color:#40a02b}html.theme--catppuccin-latte .ansi span.sgr93{color:#df8e1d}html.theme--catppuccin-latte .ansi span.sgr94{color:#1e66f5}html.theme--catppuccin-latte .ansi span.sgr95{color:#ea76cb}html.theme--catppuccin-latte .ansi span.sgr96{color:#179299}html.theme--catppuccin-latte .ansi span.sgr97{color:#bcc0cc}html.theme--catppuccin-latte .ansi span.sgr100{background-color:#6c6f85}html.theme--catppuccin-latte .ansi span.sgr101{background-color:#d20f39}html.theme--catppuccin-latte .ansi span.sgr102{background-color:#40a02b}html.theme--catppuccin-latte .ansi span.sgr103{background-color:#df8e1d}html.theme--catppuccin-latte .ansi span.sgr104{background-color:#1e66f5}html.theme--catppuccin-latte .ansi span.sgr105{background-color:#ea76cb}html.theme--catppuccin-latte .ansi span.sgr106{background-color:#179299}html.theme--catppuccin-latte .ansi span.sgr107{background-color:#bcc0cc}html.theme--catppuccin-latte code.language-julia-repl>span.hljs-meta{color:#40a02b;font-weight:bolder}html.theme--catppuccin-latte code .hljs{color:#4c4f69;background:#eff1f5}html.theme--catppuccin-latte code .hljs-keyword{color:#8839ef}html.theme--catppuccin-latte code .hljs-built_in{color:#d20f39}html.theme--catppuccin-latte code .hljs-type{color:#df8e1d}html.theme--catppuccin-latte code .hljs-literal{color:#fe640b}html.theme--catppuccin-latte code .hljs-number{color:#fe640b}html.theme--catppuccin-latte code .hljs-operator{color:#179299}html.theme--catppuccin-latte code .hljs-punctuation{color:#5c5f77}html.theme--catppuccin-latte code .hljs-property{color:#179299}html.theme--catppuccin-latte code .hljs-regexp{color:#ea76cb}html.theme--catppuccin-latte code .hljs-string{color:#40a02b}html.theme--catppuccin-latte code .hljs-char.escape_{color:#40a02b}html.theme--catppuccin-latte code .hljs-subst{color:#6c6f85}html.theme--catppuccin-latte code .hljs-symbol{color:#dd7878}html.theme--catppuccin-latte code .hljs-variable{color:#8839ef}html.theme--catppuccin-latte code .hljs-variable.language_{color:#8839ef}html.theme--catppuccin-latte code .hljs-variable.constant_{color:#fe640b}html.theme--catppuccin-latte code .hljs-title{color:#1e66f5}html.theme--catppuccin-latte code .hljs-title.class_{color:#df8e1d}html.theme--catppuccin-latte code .hljs-title.function_{color:#1e66f5}html.theme--catppuccin-latte code .hljs-params{color:#4c4f69}html.theme--catppuccin-latte code .hljs-comment{color:#acb0be}html.theme--catppuccin-latte code .hljs-doctag{color:#d20f39}html.theme--catppuccin-latte code .hljs-meta{color:#fe640b}html.theme--catppuccin-latte code .hljs-section{color:#1e66f5}html.theme--catppuccin-latte code .hljs-tag{color:#6c6f85}html.theme--catppuccin-latte code .hljs-name{color:#8839ef}html.theme--catppuccin-latte code .hljs-attr{color:#1e66f5}html.theme--catppuccin-latte code .hljs-attribute{color:#40a02b}html.theme--catppuccin-latte code .hljs-bullet{color:#179299}html.theme--catppuccin-latte code .hljs-code{color:#40a02b}html.theme--catppuccin-latte code .hljs-emphasis{color:#d20f39;font-style:italic}html.theme--catppuccin-latte code .hljs-strong{color:#d20f39;font-weight:bold}html.theme--catppuccin-latte code .hljs-formula{color:#179299}html.theme--catppuccin-latte code .hljs-link{color:#209fb5;font-style:italic}html.theme--catppuccin-latte code .hljs-quote{color:#40a02b;font-style:italic}html.theme--catppuccin-latte code .hljs-selector-tag{color:#df8e1d}html.theme--catppuccin-latte code .hljs-selector-id{color:#1e66f5}html.theme--catppuccin-latte code .hljs-selector-class{color:#179299}html.theme--catppuccin-latte code .hljs-selector-attr{color:#8839ef}html.theme--catppuccin-latte code .hljs-selector-pseudo{color:#179299}html.theme--catppuccin-latte code .hljs-template-tag{color:#dd7878}html.theme--catppuccin-latte code .hljs-template-variable{color:#dd7878}html.theme--catppuccin-latte code .hljs-addition{color:#40a02b;background:rgba(166,227,161,0.15)}html.theme--catppuccin-latte code .hljs-deletion{color:#d20f39;background:rgba(243,139,168,0.15)}html.theme--catppuccin-latte .search-result-link{border-radius:0.7em;transition:all 300ms}html.theme--catppuccin-latte .search-result-link:hover,html.theme--catppuccin-latte .search-result-link:focus{background-color:#ccd0da}html.theme--catppuccin-latte .search-result-link .property-search-result-badge,html.theme--catppuccin-latte .search-result-link .search-filter{transition:all 300ms}html.theme--catppuccin-latte .search-result-link:hover .property-search-result-badge,html.theme--catppuccin-latte .search-result-link:hover .search-filter,html.theme--catppuccin-latte .search-result-link:focus .property-search-result-badge,html.theme--catppuccin-latte .search-result-link:focus .search-filter{color:#ccd0da !important;background-color:#7287fd !important}html.theme--catppuccin-latte .search-result-title{color:#4c4f69}html.theme--catppuccin-latte .search-result-highlight{background-color:#d20f39;color:#e6e9ef}html.theme--catppuccin-latte .search-divider{border-bottom:1px solid #5e6d6f50}html.theme--catppuccin-latte .w-100{width:100%}html.theme--catppuccin-latte .gap-2{gap:0.5rem}html.theme--catppuccin-latte .gap-4{gap:1rem} diff --git a/previews/PR798/assets/themes/catppuccin-macchiato.css b/previews/PR798/assets/themes/catppuccin-macchiato.css new file mode 100644 index 0000000000..a9cf9c573f --- /dev/null +++ b/previews/PR798/assets/themes/catppuccin-macchiato.css @@ -0,0 +1 @@ +html.theme--catppuccin-macchiato .pagination-previous,html.theme--catppuccin-macchiato .pagination-next,html.theme--catppuccin-macchiato .pagination-link,html.theme--catppuccin-macchiato .pagination-ellipsis,html.theme--catppuccin-macchiato .file-cta,html.theme--catppuccin-macchiato .file-name,html.theme--catppuccin-macchiato .select select,html.theme--catppuccin-macchiato .textarea,html.theme--catppuccin-macchiato .input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-macchiato .button{-moz-appearance:none;-webkit-appearance:none;align-items:center;border:1px solid transparent;border-radius:.4em;box-shadow:none;display:inline-flex;font-size:1rem;height:2.5em;justify-content:flex-start;line-height:1.5;padding-bottom:calc(0.5em - 1px);padding-left:calc(0.75em - 1px);padding-right:calc(0.75em - 1px);padding-top:calc(0.5em - 1px);position:relative;vertical-align:top}html.theme--catppuccin-macchiato .pagination-previous:focus,html.theme--catppuccin-macchiato .pagination-next:focus,html.theme--catppuccin-macchiato .pagination-link:focus,html.theme--catppuccin-macchiato .pagination-ellipsis:focus,html.theme--catppuccin-macchiato .file-cta:focus,html.theme--catppuccin-macchiato .file-name:focus,html.theme--catppuccin-macchiato .select select:focus,html.theme--catppuccin-macchiato .textarea:focus,html.theme--catppuccin-macchiato .input:focus,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input:focus,html.theme--catppuccin-macchiato .button:focus,html.theme--catppuccin-macchiato .is-focused.pagination-previous,html.theme--catppuccin-macchiato .is-focused.pagination-next,html.theme--catppuccin-macchiato .is-focused.pagination-link,html.theme--catppuccin-macchiato .is-focused.pagination-ellipsis,html.theme--catppuccin-macchiato .is-focused.file-cta,html.theme--catppuccin-macchiato .is-focused.file-name,html.theme--catppuccin-macchiato .select select.is-focused,html.theme--catppuccin-macchiato .is-focused.textarea,html.theme--catppuccin-macchiato .is-focused.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-macchiato .is-focused.button,html.theme--catppuccin-macchiato .pagination-previous:active,html.theme--catppuccin-macchiato .pagination-next:active,html.theme--catppuccin-macchiato .pagination-link:active,html.theme--catppuccin-macchiato .pagination-ellipsis:active,html.theme--catppuccin-macchiato .file-cta:active,html.theme--catppuccin-macchiato .file-name:active,html.theme--catppuccin-macchiato .select select:active,html.theme--catppuccin-macchiato .textarea:active,html.theme--catppuccin-macchiato .input:active,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input:active,html.theme--catppuccin-macchiato .button:active,html.theme--catppuccin-macchiato .is-active.pagination-previous,html.theme--catppuccin-macchiato .is-active.pagination-next,html.theme--catppuccin-macchiato .is-active.pagination-link,html.theme--catppuccin-macchiato .is-active.pagination-ellipsis,html.theme--catppuccin-macchiato .is-active.file-cta,html.theme--catppuccin-macchiato .is-active.file-name,html.theme--catppuccin-macchiato .select select.is-active,html.theme--catppuccin-macchiato .is-active.textarea,html.theme--catppuccin-macchiato .is-active.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-active,html.theme--catppuccin-macchiato .is-active.button{outline:none}html.theme--catppuccin-macchiato .pagination-previous[disabled],html.theme--catppuccin-macchiato .pagination-next[disabled],html.theme--catppuccin-macchiato .pagination-link[disabled],html.theme--catppuccin-macchiato .pagination-ellipsis[disabled],html.theme--catppuccin-macchiato .file-cta[disabled],html.theme--catppuccin-macchiato .file-name[disabled],html.theme--catppuccin-macchiato .select select[disabled],html.theme--catppuccin-macchiato .textarea[disabled],html.theme--catppuccin-macchiato .input[disabled],html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input[disabled],html.theme--catppuccin-macchiato .button[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .pagination-previous,html.theme--catppuccin-macchiato fieldset[disabled] .pagination-previous,fieldset[disabled] html.theme--catppuccin-macchiato .pagination-next,html.theme--catppuccin-macchiato fieldset[disabled] .pagination-next,fieldset[disabled] html.theme--catppuccin-macchiato .pagination-link,html.theme--catppuccin-macchiato fieldset[disabled] .pagination-link,fieldset[disabled] html.theme--catppuccin-macchiato .pagination-ellipsis,html.theme--catppuccin-macchiato fieldset[disabled] .pagination-ellipsis,fieldset[disabled] html.theme--catppuccin-macchiato .file-cta,html.theme--catppuccin-macchiato fieldset[disabled] .file-cta,fieldset[disabled] html.theme--catppuccin-macchiato .file-name,html.theme--catppuccin-macchiato fieldset[disabled] .file-name,fieldset[disabled] html.theme--catppuccin-macchiato .select select,fieldset[disabled] html.theme--catppuccin-macchiato .textarea,fieldset[disabled] html.theme--catppuccin-macchiato .input,fieldset[disabled] html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-macchiato fieldset[disabled] .select select,html.theme--catppuccin-macchiato .select fieldset[disabled] select,html.theme--catppuccin-macchiato fieldset[disabled] .textarea,html.theme--catppuccin-macchiato fieldset[disabled] .input,html.theme--catppuccin-macchiato fieldset[disabled] #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-macchiato #documenter .docs-sidebar fieldset[disabled] form.docs-search>input,fieldset[disabled] html.theme--catppuccin-macchiato .button,html.theme--catppuccin-macchiato fieldset[disabled] .button{cursor:not-allowed}html.theme--catppuccin-macchiato .tabs,html.theme--catppuccin-macchiato .pagination-previous,html.theme--catppuccin-macchiato .pagination-next,html.theme--catppuccin-macchiato .pagination-link,html.theme--catppuccin-macchiato .pagination-ellipsis,html.theme--catppuccin-macchiato .breadcrumb,html.theme--catppuccin-macchiato .file,html.theme--catppuccin-macchiato .button,.is-unselectable{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}html.theme--catppuccin-macchiato .navbar-link:not(.is-arrowless)::after,html.theme--catppuccin-macchiato .select:not(.is-multiple):not(.is-loading)::after{border:3px solid rgba(0,0,0,0);border-radius:2px;border-right:0;border-top:0;content:" ";display:block;height:0.625em;margin-top:-0.4375em;pointer-events:none;position:absolute;top:50%;transform:rotate(-45deg);transform-origin:center;width:0.625em}html.theme--catppuccin-macchiato .admonition:not(:last-child),html.theme--catppuccin-macchiato .tabs:not(:last-child),html.theme--catppuccin-macchiato .pagination:not(:last-child),html.theme--catppuccin-macchiato .message:not(:last-child),html.theme--catppuccin-macchiato .level:not(:last-child),html.theme--catppuccin-macchiato .breadcrumb:not(:last-child),html.theme--catppuccin-macchiato .block:not(:last-child),html.theme--catppuccin-macchiato .title:not(:last-child),html.theme--catppuccin-macchiato .subtitle:not(:last-child),html.theme--catppuccin-macchiato .table-container:not(:last-child),html.theme--catppuccin-macchiato .table:not(:last-child),html.theme--catppuccin-macchiato .progress:not(:last-child),html.theme--catppuccin-macchiato .notification:not(:last-child),html.theme--catppuccin-macchiato .content:not(:last-child),html.theme--catppuccin-macchiato .box:not(:last-child){margin-bottom:1.5rem}html.theme--catppuccin-macchiato .modal-close,html.theme--catppuccin-macchiato .delete{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-moz-appearance:none;-webkit-appearance:none;background-color:rgba(10,10,10,0.2);border:none;border-radius:9999px;cursor:pointer;pointer-events:auto;display:inline-block;flex-grow:0;flex-shrink:0;font-size:0;height:20px;max-height:20px;max-width:20px;min-height:20px;min-width:20px;outline:none;position:relative;vertical-align:top;width:20px}html.theme--catppuccin-macchiato .modal-close::before,html.theme--catppuccin-macchiato .delete::before,html.theme--catppuccin-macchiato .modal-close::after,html.theme--catppuccin-macchiato .delete::after{background-color:#fff;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}html.theme--catppuccin-macchiato .modal-close::before,html.theme--catppuccin-macchiato .delete::before{height:2px;width:50%}html.theme--catppuccin-macchiato .modal-close::after,html.theme--catppuccin-macchiato .delete::after{height:50%;width:2px}html.theme--catppuccin-macchiato .modal-close:hover,html.theme--catppuccin-macchiato .delete:hover,html.theme--catppuccin-macchiato .modal-close:focus,html.theme--catppuccin-macchiato .delete:focus{background-color:rgba(10,10,10,0.3)}html.theme--catppuccin-macchiato .modal-close:active,html.theme--catppuccin-macchiato .delete:active{background-color:rgba(10,10,10,0.4)}html.theme--catppuccin-macchiato .is-small.modal-close,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.modal-close,html.theme--catppuccin-macchiato .is-small.delete,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.delete{height:16px;max-height:16px;max-width:16px;min-height:16px;min-width:16px;width:16px}html.theme--catppuccin-macchiato .is-medium.modal-close,html.theme--catppuccin-macchiato .is-medium.delete{height:24px;max-height:24px;max-width:24px;min-height:24px;min-width:24px;width:24px}html.theme--catppuccin-macchiato .is-large.modal-close,html.theme--catppuccin-macchiato .is-large.delete{height:32px;max-height:32px;max-width:32px;min-height:32px;min-width:32px;width:32px}html.theme--catppuccin-macchiato .control.is-loading::after,html.theme--catppuccin-macchiato .select.is-loading::after,html.theme--catppuccin-macchiato .loader,html.theme--catppuccin-macchiato .button.is-loading::after{animation:spinAround 500ms infinite linear;border:2px solid #8087a2;border-radius:9999px;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:1em;position:relative;width:1em}html.theme--catppuccin-macchiato .hero-video,html.theme--catppuccin-macchiato .modal-background,html.theme--catppuccin-macchiato .modal,html.theme--catppuccin-macchiato .image.is-square img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-square img,html.theme--catppuccin-macchiato .image.is-square .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-square .has-ratio,html.theme--catppuccin-macchiato .image.is-1by1 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-1by1 img,html.theme--catppuccin-macchiato .image.is-1by1 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-1by1 .has-ratio,html.theme--catppuccin-macchiato .image.is-5by4 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-5by4 img,html.theme--catppuccin-macchiato .image.is-5by4 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-5by4 .has-ratio,html.theme--catppuccin-macchiato .image.is-4by3 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-4by3 img,html.theme--catppuccin-macchiato .image.is-4by3 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-4by3 .has-ratio,html.theme--catppuccin-macchiato .image.is-3by2 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by2 img,html.theme--catppuccin-macchiato .image.is-3by2 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by2 .has-ratio,html.theme--catppuccin-macchiato .image.is-5by3 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-5by3 img,html.theme--catppuccin-macchiato .image.is-5by3 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-5by3 .has-ratio,html.theme--catppuccin-macchiato .image.is-16by9 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-16by9 img,html.theme--catppuccin-macchiato .image.is-16by9 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-16by9 .has-ratio,html.theme--catppuccin-macchiato .image.is-2by1 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-2by1 img,html.theme--catppuccin-macchiato .image.is-2by1 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-2by1 .has-ratio,html.theme--catppuccin-macchiato .image.is-3by1 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by1 img,html.theme--catppuccin-macchiato .image.is-3by1 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by1 .has-ratio,html.theme--catppuccin-macchiato .image.is-4by5 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-4by5 img,html.theme--catppuccin-macchiato .image.is-4by5 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-4by5 .has-ratio,html.theme--catppuccin-macchiato .image.is-3by4 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by4 img,html.theme--catppuccin-macchiato .image.is-3by4 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by4 .has-ratio,html.theme--catppuccin-macchiato .image.is-2by3 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-2by3 img,html.theme--catppuccin-macchiato .image.is-2by3 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-2by3 .has-ratio,html.theme--catppuccin-macchiato .image.is-3by5 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by5 img,html.theme--catppuccin-macchiato .image.is-3by5 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by5 .has-ratio,html.theme--catppuccin-macchiato .image.is-9by16 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-9by16 img,html.theme--catppuccin-macchiato .image.is-9by16 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-9by16 .has-ratio,html.theme--catppuccin-macchiato .image.is-1by2 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-1by2 img,html.theme--catppuccin-macchiato .image.is-1by2 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-1by2 .has-ratio,html.theme--catppuccin-macchiato .image.is-1by3 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-1by3 img,html.theme--catppuccin-macchiato .image.is-1by3 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-1by3 .has-ratio,.is-overlay{bottom:0;left:0;position:absolute;right:0;top:0}html.theme--catppuccin-macchiato .navbar-burger{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;color:currentColor;font-family:inherit;font-size:1em;margin:0;padding:0}/*! minireset.css v0.0.6 | MIT License | github.com/jgthms/minireset.css */html,body,p,ol,ul,li,dl,dt,dd,blockquote,figure,fieldset,legend,textarea,pre,iframe,hr,h1,h2,h3,h4,h5,h6{margin:0;padding:0}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal}ul{list-style:none}button,input,select,textarea{margin:0}html{box-sizing:border-box}*,*::before,*::after{box-sizing:inherit}img,video{height:auto;max-width:100%}iframe{border:0}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}td:not([align]),th:not([align]){text-align:inherit}.has-text-white{color:#fff !important}a.has-text-white:hover,a.has-text-white:focus{color:#e6e6e6 !important}.has-background-white{background-color:#fff !important}.has-text-black{color:#0a0a0a !important}a.has-text-black:hover,a.has-text-black:focus{color:#000 !important}.has-background-black{background-color:#0a0a0a !important}.has-text-light{color:#f5f5f5 !important}a.has-text-light:hover,a.has-text-light:focus{color:#dbdbdb !important}.has-background-light{background-color:#f5f5f5 !important}.has-text-dark{color:#363a4f !important}a.has-text-dark:hover,a.has-text-dark:focus{color:#212431 !important}.has-background-dark{background-color:#363a4f !important}.has-text-primary{color:#8aadf4 !important}a.has-text-primary:hover,a.has-text-primary:focus{color:#5b8cf0 !important}.has-background-primary{background-color:#8aadf4 !important}.has-text-primary-light{color:#ecf2fd !important}a.has-text-primary-light:hover,a.has-text-primary-light:focus{color:#bed1f9 !important}.has-background-primary-light{background-color:#ecf2fd !important}.has-text-primary-dark{color:#0e3b95 !important}a.has-text-primary-dark:hover,a.has-text-primary-dark:focus{color:#124dc4 !important}.has-background-primary-dark{background-color:#0e3b95 !important}.has-text-link{color:#8aadf4 !important}a.has-text-link:hover,a.has-text-link:focus{color:#5b8cf0 !important}.has-background-link{background-color:#8aadf4 !important}.has-text-link-light{color:#ecf2fd !important}a.has-text-link-light:hover,a.has-text-link-light:focus{color:#bed1f9 !important}.has-background-link-light{background-color:#ecf2fd !important}.has-text-link-dark{color:#0e3b95 !important}a.has-text-link-dark:hover,a.has-text-link-dark:focus{color:#124dc4 !important}.has-background-link-dark{background-color:#0e3b95 !important}.has-text-info{color:#8bd5ca !important}a.has-text-info:hover,a.has-text-info:focus{color:#66c7b9 !important}.has-background-info{background-color:#8bd5ca !important}.has-text-info-light{color:#f0faf8 !important}a.has-text-info-light:hover,a.has-text-info-light:focus{color:#cbece7 !important}.has-background-info-light{background-color:#f0faf8 !important}.has-text-info-dark{color:#276d62 !important}a.has-text-info-dark:hover,a.has-text-info-dark:focus{color:#359284 !important}.has-background-info-dark{background-color:#276d62 !important}.has-text-success{color:#a6da95 !important}a.has-text-success:hover,a.has-text-success:focus{color:#86cd6f !important}.has-background-success{background-color:#a6da95 !important}.has-text-success-light{color:#f2faf0 !important}a.has-text-success-light:hover,a.has-text-success-light:focus{color:#d3edca !important}.has-background-success-light{background-color:#f2faf0 !important}.has-text-success-dark{color:#386e26 !important}a.has-text-success-dark:hover,a.has-text-success-dark:focus{color:#4b9333 !important}.has-background-success-dark{background-color:#386e26 !important}.has-text-warning{color:#eed49f !important}a.has-text-warning:hover,a.has-text-warning:focus{color:#e6c174 !important}.has-background-warning{background-color:#eed49f !important}.has-text-warning-light{color:#fcf7ee !important}a.has-text-warning-light:hover,a.has-text-warning-light:focus{color:#f4e4c2 !important}.has-background-warning-light{background-color:#fcf7ee !important}.has-text-warning-dark{color:#7e5c16 !important}a.has-text-warning-dark:hover,a.has-text-warning-dark:focus{color:#a97b1e !important}.has-background-warning-dark{background-color:#7e5c16 !important}.has-text-danger{color:#ed8796 !important}a.has-text-danger:hover,a.has-text-danger:focus{color:#e65b6f !important}.has-background-danger{background-color:#ed8796 !important}.has-text-danger-light{color:#fcedef !important}a.has-text-danger-light:hover,a.has-text-danger-light:focus{color:#f6c1c9 !important}.has-background-danger-light{background-color:#fcedef !important}.has-text-danger-dark{color:#971729 !important}a.has-text-danger-dark:hover,a.has-text-danger-dark:focus{color:#c31d36 !important}.has-background-danger-dark{background-color:#971729 !important}.has-text-black-bis{color:#121212 !important}.has-background-black-bis{background-color:#121212 !important}.has-text-black-ter{color:#242424 !important}.has-background-black-ter{background-color:#242424 !important}.has-text-grey-darker{color:#363a4f !important}.has-background-grey-darker{background-color:#363a4f !important}.has-text-grey-dark{color:#494d64 !important}.has-background-grey-dark{background-color:#494d64 !important}.has-text-grey{color:#5b6078 !important}.has-background-grey{background-color:#5b6078 !important}.has-text-grey-light{color:#6e738d !important}.has-background-grey-light{background-color:#6e738d !important}.has-text-grey-lighter{color:#8087a2 !important}.has-background-grey-lighter{background-color:#8087a2 !important}.has-text-white-ter{color:#f5f5f5 !important}.has-background-white-ter{background-color:#f5f5f5 !important}.has-text-white-bis{color:#fafafa !important}.has-background-white-bis{background-color:#fafafa !important}.is-flex-direction-row{flex-direction:row !important}.is-flex-direction-row-reverse{flex-direction:row-reverse !important}.is-flex-direction-column{flex-direction:column !important}.is-flex-direction-column-reverse{flex-direction:column-reverse !important}.is-flex-wrap-nowrap{flex-wrap:nowrap !important}.is-flex-wrap-wrap{flex-wrap:wrap !important}.is-flex-wrap-wrap-reverse{flex-wrap:wrap-reverse !important}.is-justify-content-flex-start{justify-content:flex-start !important}.is-justify-content-flex-end{justify-content:flex-end !important}.is-justify-content-center{justify-content:center !important}.is-justify-content-space-between{justify-content:space-between !important}.is-justify-content-space-around{justify-content:space-around !important}.is-justify-content-space-evenly{justify-content:space-evenly !important}.is-justify-content-start{justify-content:start !important}.is-justify-content-end{justify-content:end !important}.is-justify-content-left{justify-content:left !important}.is-justify-content-right{justify-content:right !important}.is-align-content-flex-start{align-content:flex-start !important}.is-align-content-flex-end{align-content:flex-end !important}.is-align-content-center{align-content:center !important}.is-align-content-space-between{align-content:space-between !important}.is-align-content-space-around{align-content:space-around !important}.is-align-content-space-evenly{align-content:space-evenly !important}.is-align-content-stretch{align-content:stretch !important}.is-align-content-start{align-content:start !important}.is-align-content-end{align-content:end !important}.is-align-content-baseline{align-content:baseline !important}.is-align-items-stretch{align-items:stretch !important}.is-align-items-flex-start{align-items:flex-start !important}.is-align-items-flex-end{align-items:flex-end !important}.is-align-items-center{align-items:center !important}.is-align-items-baseline{align-items:baseline !important}.is-align-items-start{align-items:start !important}.is-align-items-end{align-items:end !important}.is-align-items-self-start{align-items:self-start !important}.is-align-items-self-end{align-items:self-end !important}.is-align-self-auto{align-self:auto !important}.is-align-self-flex-start{align-self:flex-start !important}.is-align-self-flex-end{align-self:flex-end !important}.is-align-self-center{align-self:center !important}.is-align-self-baseline{align-self:baseline !important}.is-align-self-stretch{align-self:stretch !important}.is-flex-grow-0{flex-grow:0 !important}.is-flex-grow-1{flex-grow:1 !important}.is-flex-grow-2{flex-grow:2 !important}.is-flex-grow-3{flex-grow:3 !important}.is-flex-grow-4{flex-grow:4 !important}.is-flex-grow-5{flex-grow:5 !important}.is-flex-shrink-0{flex-shrink:0 !important}.is-flex-shrink-1{flex-shrink:1 !important}.is-flex-shrink-2{flex-shrink:2 !important}.is-flex-shrink-3{flex-shrink:3 !important}.is-flex-shrink-4{flex-shrink:4 !important}.is-flex-shrink-5{flex-shrink:5 !important}.is-clearfix::after{clear:both;content:" ";display:table}.is-pulled-left{float:left !important}.is-pulled-right{float:right !important}.is-radiusless{border-radius:0 !important}.is-shadowless{box-shadow:none !important}.is-clickable{cursor:pointer !important;pointer-events:all !important}.is-clipped{overflow:hidden !important}.is-relative{position:relative !important}.is-marginless{margin:0 !important}.is-paddingless{padding:0 !important}.m-0{margin:0 !important}.mt-0{margin-top:0 !important}.mr-0{margin-right:0 !important}.mb-0{margin-bottom:0 !important}.ml-0{margin-left:0 !important}.mx-0{margin-left:0 !important;margin-right:0 !important}.my-0{margin-top:0 !important;margin-bottom:0 !important}.m-1{margin:.25rem !important}.mt-1{margin-top:.25rem !important}.mr-1{margin-right:.25rem !important}.mb-1{margin-bottom:.25rem !important}.ml-1{margin-left:.25rem !important}.mx-1{margin-left:.25rem !important;margin-right:.25rem !important}.my-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.m-2{margin:.5rem !important}.mt-2{margin-top:.5rem !important}.mr-2{margin-right:.5rem !important}.mb-2{margin-bottom:.5rem !important}.ml-2{margin-left:.5rem !important}.mx-2{margin-left:.5rem !important;margin-right:.5rem !important}.my-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.m-3{margin:.75rem !important}.mt-3{margin-top:.75rem !important}.mr-3{margin-right:.75rem !important}.mb-3{margin-bottom:.75rem !important}.ml-3{margin-left:.75rem !important}.mx-3{margin-left:.75rem !important;margin-right:.75rem !important}.my-3{margin-top:.75rem !important;margin-bottom:.75rem !important}.m-4{margin:1rem !important}.mt-4{margin-top:1rem !important}.mr-4{margin-right:1rem !important}.mb-4{margin-bottom:1rem !important}.ml-4{margin-left:1rem !important}.mx-4{margin-left:1rem !important;margin-right:1rem !important}.my-4{margin-top:1rem !important;margin-bottom:1rem !important}.m-5{margin:1.5rem !important}.mt-5{margin-top:1.5rem !important}.mr-5{margin-right:1.5rem !important}.mb-5{margin-bottom:1.5rem !important}.ml-5{margin-left:1.5rem !important}.mx-5{margin-left:1.5rem !important;margin-right:1.5rem !important}.my-5{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.m-6{margin:3rem !important}.mt-6{margin-top:3rem !important}.mr-6{margin-right:3rem !important}.mb-6{margin-bottom:3rem !important}.ml-6{margin-left:3rem !important}.mx-6{margin-left:3rem !important;margin-right:3rem !important}.my-6{margin-top:3rem !important;margin-bottom:3rem !important}.m-auto{margin:auto !important}.mt-auto{margin-top:auto !important}.mr-auto{margin-right:auto !important}.mb-auto{margin-bottom:auto !important}.ml-auto{margin-left:auto !important}.mx-auto{margin-left:auto !important;margin-right:auto !important}.my-auto{margin-top:auto !important;margin-bottom:auto !important}.p-0{padding:0 !important}.pt-0{padding-top:0 !important}.pr-0{padding-right:0 !important}.pb-0{padding-bottom:0 !important}.pl-0{padding-left:0 !important}.px-0{padding-left:0 !important;padding-right:0 !important}.py-0{padding-top:0 !important;padding-bottom:0 !important}.p-1{padding:.25rem !important}.pt-1{padding-top:.25rem !important}.pr-1{padding-right:.25rem !important}.pb-1{padding-bottom:.25rem !important}.pl-1{padding-left:.25rem !important}.px-1{padding-left:.25rem !important;padding-right:.25rem !important}.py-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.p-2{padding:.5rem !important}.pt-2{padding-top:.5rem !important}.pr-2{padding-right:.5rem !important}.pb-2{padding-bottom:.5rem !important}.pl-2{padding-left:.5rem !important}.px-2{padding-left:.5rem !important;padding-right:.5rem !important}.py-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.p-3{padding:.75rem !important}.pt-3{padding-top:.75rem !important}.pr-3{padding-right:.75rem !important}.pb-3{padding-bottom:.75rem !important}.pl-3{padding-left:.75rem !important}.px-3{padding-left:.75rem !important;padding-right:.75rem !important}.py-3{padding-top:.75rem !important;padding-bottom:.75rem !important}.p-4{padding:1rem !important}.pt-4{padding-top:1rem !important}.pr-4{padding-right:1rem !important}.pb-4{padding-bottom:1rem !important}.pl-4{padding-left:1rem !important}.px-4{padding-left:1rem !important;padding-right:1rem !important}.py-4{padding-top:1rem !important;padding-bottom:1rem !important}.p-5{padding:1.5rem !important}.pt-5{padding-top:1.5rem !important}.pr-5{padding-right:1.5rem !important}.pb-5{padding-bottom:1.5rem !important}.pl-5{padding-left:1.5rem !important}.px-5{padding-left:1.5rem !important;padding-right:1.5rem !important}.py-5{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.p-6{padding:3rem !important}.pt-6{padding-top:3rem !important}.pr-6{padding-right:3rem !important}.pb-6{padding-bottom:3rem !important}.pl-6{padding-left:3rem !important}.px-6{padding-left:3rem !important;padding-right:3rem !important}.py-6{padding-top:3rem !important;padding-bottom:3rem !important}.p-auto{padding:auto !important}.pt-auto{padding-top:auto !important}.pr-auto{padding-right:auto !important}.pb-auto{padding-bottom:auto !important}.pl-auto{padding-left:auto !important}.px-auto{padding-left:auto !important;padding-right:auto !important}.py-auto{padding-top:auto !important;padding-bottom:auto !important}.is-size-1{font-size:3rem !important}.is-size-2{font-size:2.5rem !important}.is-size-3{font-size:2rem !important}.is-size-4{font-size:1.5rem !important}.is-size-5{font-size:1.25rem !important}.is-size-6{font-size:1rem !important}.is-size-7,html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink{font-size:.75rem !important}@media screen and (max-width: 768px){.is-size-1-mobile{font-size:3rem !important}.is-size-2-mobile{font-size:2.5rem !important}.is-size-3-mobile{font-size:2rem !important}.is-size-4-mobile{font-size:1.5rem !important}.is-size-5-mobile{font-size:1.25rem !important}.is-size-6-mobile{font-size:1rem !important}.is-size-7-mobile{font-size:.75rem !important}}@media screen and (min-width: 769px),print{.is-size-1-tablet{font-size:3rem !important}.is-size-2-tablet{font-size:2.5rem !important}.is-size-3-tablet{font-size:2rem !important}.is-size-4-tablet{font-size:1.5rem !important}.is-size-5-tablet{font-size:1.25rem !important}.is-size-6-tablet{font-size:1rem !important}.is-size-7-tablet{font-size:.75rem !important}}@media screen and (max-width: 1055px){.is-size-1-touch{font-size:3rem !important}.is-size-2-touch{font-size:2.5rem !important}.is-size-3-touch{font-size:2rem !important}.is-size-4-touch{font-size:1.5rem !important}.is-size-5-touch{font-size:1.25rem !important}.is-size-6-touch{font-size:1rem !important}.is-size-7-touch{font-size:.75rem !important}}@media screen and (min-width: 1056px){.is-size-1-desktop{font-size:3rem !important}.is-size-2-desktop{font-size:2.5rem !important}.is-size-3-desktop{font-size:2rem !important}.is-size-4-desktop{font-size:1.5rem !important}.is-size-5-desktop{font-size:1.25rem !important}.is-size-6-desktop{font-size:1rem !important}.is-size-7-desktop{font-size:.75rem !important}}@media screen and (min-width: 1216px){.is-size-1-widescreen{font-size:3rem !important}.is-size-2-widescreen{font-size:2.5rem !important}.is-size-3-widescreen{font-size:2rem !important}.is-size-4-widescreen{font-size:1.5rem !important}.is-size-5-widescreen{font-size:1.25rem !important}.is-size-6-widescreen{font-size:1rem !important}.is-size-7-widescreen{font-size:.75rem !important}}@media screen and (min-width: 1408px){.is-size-1-fullhd{font-size:3rem !important}.is-size-2-fullhd{font-size:2.5rem !important}.is-size-3-fullhd{font-size:2rem !important}.is-size-4-fullhd{font-size:1.5rem !important}.is-size-5-fullhd{font-size:1.25rem !important}.is-size-6-fullhd{font-size:1rem !important}.is-size-7-fullhd{font-size:.75rem !important}}.has-text-centered{text-align:center !important}.has-text-justified{text-align:justify !important}.has-text-left{text-align:left !important}.has-text-right{text-align:right !important}@media screen and (max-width: 768px){.has-text-centered-mobile{text-align:center !important}}@media screen and (min-width: 769px),print{.has-text-centered-tablet{text-align:center !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-centered-tablet-only{text-align:center !important}}@media screen and (max-width: 1055px){.has-text-centered-touch{text-align:center !important}}@media screen and (min-width: 1056px){.has-text-centered-desktop{text-align:center !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-centered-desktop-only{text-align:center !important}}@media screen and (min-width: 1216px){.has-text-centered-widescreen{text-align:center !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-centered-widescreen-only{text-align:center !important}}@media screen and (min-width: 1408px){.has-text-centered-fullhd{text-align:center !important}}@media screen and (max-width: 768px){.has-text-justified-mobile{text-align:justify !important}}@media screen and (min-width: 769px),print{.has-text-justified-tablet{text-align:justify !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-justified-tablet-only{text-align:justify !important}}@media screen and (max-width: 1055px){.has-text-justified-touch{text-align:justify !important}}@media screen and (min-width: 1056px){.has-text-justified-desktop{text-align:justify !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-justified-desktop-only{text-align:justify !important}}@media screen and (min-width: 1216px){.has-text-justified-widescreen{text-align:justify !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-justified-widescreen-only{text-align:justify !important}}@media screen and (min-width: 1408px){.has-text-justified-fullhd{text-align:justify !important}}@media screen and (max-width: 768px){.has-text-left-mobile{text-align:left !important}}@media screen and (min-width: 769px),print{.has-text-left-tablet{text-align:left !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-left-tablet-only{text-align:left !important}}@media screen and (max-width: 1055px){.has-text-left-touch{text-align:left !important}}@media screen and (min-width: 1056px){.has-text-left-desktop{text-align:left !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-left-desktop-only{text-align:left !important}}@media screen and (min-width: 1216px){.has-text-left-widescreen{text-align:left !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-left-widescreen-only{text-align:left !important}}@media screen and (min-width: 1408px){.has-text-left-fullhd{text-align:left !important}}@media screen and (max-width: 768px){.has-text-right-mobile{text-align:right !important}}@media screen and (min-width: 769px),print{.has-text-right-tablet{text-align:right !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-right-tablet-only{text-align:right !important}}@media screen and (max-width: 1055px){.has-text-right-touch{text-align:right !important}}@media screen and (min-width: 1056px){.has-text-right-desktop{text-align:right !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-right-desktop-only{text-align:right !important}}@media screen and (min-width: 1216px){.has-text-right-widescreen{text-align:right !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-right-widescreen-only{text-align:right !important}}@media screen and (min-width: 1408px){.has-text-right-fullhd{text-align:right !important}}.is-capitalized{text-transform:capitalize !important}.is-lowercase{text-transform:lowercase !important}.is-uppercase{text-transform:uppercase !important}.is-italic{font-style:italic !important}.is-underlined{text-decoration:underline !important}.has-text-weight-light{font-weight:300 !important}.has-text-weight-normal{font-weight:400 !important}.has-text-weight-medium{font-weight:500 !important}.has-text-weight-semibold{font-weight:600 !important}.has-text-weight-bold{font-weight:700 !important}.is-family-primary{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-secondary{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-sans-serif{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-monospace{font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace !important}.is-family-code{font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace !important}.is-block{display:block !important}@media screen and (max-width: 768px){.is-block-mobile{display:block !important}}@media screen and (min-width: 769px),print{.is-block-tablet{display:block !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-block-tablet-only{display:block !important}}@media screen and (max-width: 1055px){.is-block-touch{display:block !important}}@media screen and (min-width: 1056px){.is-block-desktop{display:block !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-block-desktop-only{display:block !important}}@media screen and (min-width: 1216px){.is-block-widescreen{display:block !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-block-widescreen-only{display:block !important}}@media screen and (min-width: 1408px){.is-block-fullhd{display:block !important}}.is-flex{display:flex !important}@media screen and (max-width: 768px){.is-flex-mobile{display:flex !important}}@media screen and (min-width: 769px),print{.is-flex-tablet{display:flex !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-flex-tablet-only{display:flex !important}}@media screen and (max-width: 1055px){.is-flex-touch{display:flex !important}}@media screen and (min-width: 1056px){.is-flex-desktop{display:flex !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-flex-desktop-only{display:flex !important}}@media screen and (min-width: 1216px){.is-flex-widescreen{display:flex !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-flex-widescreen-only{display:flex !important}}@media screen and (min-width: 1408px){.is-flex-fullhd{display:flex !important}}.is-inline{display:inline !important}@media screen and (max-width: 768px){.is-inline-mobile{display:inline !important}}@media screen and (min-width: 769px),print{.is-inline-tablet{display:inline !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-tablet-only{display:inline !important}}@media screen and (max-width: 1055px){.is-inline-touch{display:inline !important}}@media screen and (min-width: 1056px){.is-inline-desktop{display:inline !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-desktop-only{display:inline !important}}@media screen and (min-width: 1216px){.is-inline-widescreen{display:inline !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-widescreen-only{display:inline !important}}@media screen and (min-width: 1408px){.is-inline-fullhd{display:inline !important}}.is-inline-block{display:inline-block !important}@media screen and (max-width: 768px){.is-inline-block-mobile{display:inline-block !important}}@media screen and (min-width: 769px),print{.is-inline-block-tablet{display:inline-block !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-block-tablet-only{display:inline-block !important}}@media screen and (max-width: 1055px){.is-inline-block-touch{display:inline-block !important}}@media screen and (min-width: 1056px){.is-inline-block-desktop{display:inline-block !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-block-desktop-only{display:inline-block !important}}@media screen and (min-width: 1216px){.is-inline-block-widescreen{display:inline-block !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-block-widescreen-only{display:inline-block !important}}@media screen and (min-width: 1408px){.is-inline-block-fullhd{display:inline-block !important}}.is-inline-flex{display:inline-flex !important}@media screen and (max-width: 768px){.is-inline-flex-mobile{display:inline-flex !important}}@media screen and (min-width: 769px),print{.is-inline-flex-tablet{display:inline-flex !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-flex-tablet-only{display:inline-flex !important}}@media screen and (max-width: 1055px){.is-inline-flex-touch{display:inline-flex !important}}@media screen and (min-width: 1056px){.is-inline-flex-desktop{display:inline-flex !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-flex-desktop-only{display:inline-flex !important}}@media screen and (min-width: 1216px){.is-inline-flex-widescreen{display:inline-flex !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-flex-widescreen-only{display:inline-flex !important}}@media screen and (min-width: 1408px){.is-inline-flex-fullhd{display:inline-flex !important}}.is-hidden{display:none !important}.is-sr-only{border:none !important;clip:rect(0, 0, 0, 0) !important;height:0.01em !important;overflow:hidden !important;padding:0 !important;position:absolute !important;white-space:nowrap !important;width:0.01em !important}@media screen and (max-width: 768px){.is-hidden-mobile{display:none !important}}@media screen and (min-width: 769px),print{.is-hidden-tablet{display:none !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-hidden-tablet-only{display:none !important}}@media screen and (max-width: 1055px){.is-hidden-touch{display:none !important}}@media screen and (min-width: 1056px){.is-hidden-desktop{display:none !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-hidden-desktop-only{display:none !important}}@media screen and (min-width: 1216px){.is-hidden-widescreen{display:none !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-hidden-widescreen-only{display:none !important}}@media screen and (min-width: 1408px){.is-hidden-fullhd{display:none !important}}.is-invisible{visibility:hidden !important}@media screen and (max-width: 768px){.is-invisible-mobile{visibility:hidden !important}}@media screen and (min-width: 769px),print{.is-invisible-tablet{visibility:hidden !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-invisible-tablet-only{visibility:hidden !important}}@media screen and (max-width: 1055px){.is-invisible-touch{visibility:hidden !important}}@media screen and (min-width: 1056px){.is-invisible-desktop{visibility:hidden !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-invisible-desktop-only{visibility:hidden !important}}@media screen and (min-width: 1216px){.is-invisible-widescreen{visibility:hidden !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-invisible-widescreen-only{visibility:hidden !important}}@media screen and (min-width: 1408px){.is-invisible-fullhd{visibility:hidden !important}}html.theme--catppuccin-macchiato html{background-color:#24273a;font-size:16px;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;min-width:300px;overflow-x:auto;overflow-y:scroll;text-rendering:optimizeLegibility;text-size-adjust:100%}html.theme--catppuccin-macchiato article,html.theme--catppuccin-macchiato aside,html.theme--catppuccin-macchiato figure,html.theme--catppuccin-macchiato footer,html.theme--catppuccin-macchiato header,html.theme--catppuccin-macchiato hgroup,html.theme--catppuccin-macchiato section{display:block}html.theme--catppuccin-macchiato body,html.theme--catppuccin-macchiato button,html.theme--catppuccin-macchiato input,html.theme--catppuccin-macchiato optgroup,html.theme--catppuccin-macchiato select,html.theme--catppuccin-macchiato textarea{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif}html.theme--catppuccin-macchiato code,html.theme--catppuccin-macchiato pre{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto;font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace}html.theme--catppuccin-macchiato body{color:#cad3f5;font-size:1em;font-weight:400;line-height:1.5}html.theme--catppuccin-macchiato a{color:#8aadf4;cursor:pointer;text-decoration:none}html.theme--catppuccin-macchiato a strong{color:currentColor}html.theme--catppuccin-macchiato a:hover{color:#91d7e3}html.theme--catppuccin-macchiato code{background-color:#1e2030;color:#cad3f5;font-size:.875em;font-weight:normal;padding:.1em}html.theme--catppuccin-macchiato hr{background-color:#1e2030;border:none;display:block;height:2px;margin:1.5rem 0}html.theme--catppuccin-macchiato img{height:auto;max-width:100%}html.theme--catppuccin-macchiato input[type="checkbox"],html.theme--catppuccin-macchiato input[type="radio"]{vertical-align:baseline}html.theme--catppuccin-macchiato small{font-size:.875em}html.theme--catppuccin-macchiato span{font-style:inherit;font-weight:inherit}html.theme--catppuccin-macchiato strong{color:#b5c1f1;font-weight:700}html.theme--catppuccin-macchiato fieldset{border:none}html.theme--catppuccin-macchiato pre{-webkit-overflow-scrolling:touch;background-color:#1e2030;color:#cad3f5;font-size:.875em;overflow-x:auto;padding:1.25rem 1.5rem;white-space:pre;word-wrap:normal}html.theme--catppuccin-macchiato pre code{background-color:transparent;color:currentColor;font-size:1em;padding:0}html.theme--catppuccin-macchiato table td,html.theme--catppuccin-macchiato table th{vertical-align:top}html.theme--catppuccin-macchiato table td:not([align]),html.theme--catppuccin-macchiato table th:not([align]){text-align:inherit}html.theme--catppuccin-macchiato table th{color:#b5c1f1}html.theme--catppuccin-macchiato .box{background-color:#494d64;border-radius:8px;box-shadow:none;color:#cad3f5;display:block;padding:1.25rem}html.theme--catppuccin-macchiato a.box:hover,html.theme--catppuccin-macchiato a.box:focus{box-shadow:0 0.5em 1em -0.125em rgba(10,10,10,0.1),0 0 0 1px #8aadf4}html.theme--catppuccin-macchiato a.box:active{box-shadow:inset 0 1px 2px rgba(10,10,10,0.2),0 0 0 1px #8aadf4}html.theme--catppuccin-macchiato .button{background-color:#1e2030;border-color:#3b3f5f;border-width:1px;color:#8aadf4;cursor:pointer;justify-content:center;padding-bottom:calc(0.5em - 1px);padding-left:1em;padding-right:1em;padding-top:calc(0.5em - 1px);text-align:center;white-space:nowrap}html.theme--catppuccin-macchiato .button strong{color:inherit}html.theme--catppuccin-macchiato .button .icon,html.theme--catppuccin-macchiato .button .icon.is-small,html.theme--catppuccin-macchiato .button #documenter .docs-sidebar form.docs-search>input.icon,html.theme--catppuccin-macchiato #documenter .docs-sidebar .button form.docs-search>input.icon,html.theme--catppuccin-macchiato .button .icon.is-medium,html.theme--catppuccin-macchiato .button .icon.is-large{height:1.5em;width:1.5em}html.theme--catppuccin-macchiato .button .icon:first-child:not(:last-child){margin-left:calc(-0.5em - 1px);margin-right:.25em}html.theme--catppuccin-macchiato .button .icon:last-child:not(:first-child){margin-left:.25em;margin-right:calc(-0.5em - 1px)}html.theme--catppuccin-macchiato .button .icon:first-child:last-child{margin-left:calc(-0.5em - 1px);margin-right:calc(-0.5em - 1px)}html.theme--catppuccin-macchiato .button:hover,html.theme--catppuccin-macchiato .button.is-hovered{border-color:#6e738d;color:#b5c1f1}html.theme--catppuccin-macchiato .button:focus,html.theme--catppuccin-macchiato .button.is-focused{border-color:#6e738d;color:#739df2}html.theme--catppuccin-macchiato .button:focus:not(:active),html.theme--catppuccin-macchiato .button.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(138,173,244,0.25)}html.theme--catppuccin-macchiato .button:active,html.theme--catppuccin-macchiato .button.is-active{border-color:#494d64;color:#b5c1f1}html.theme--catppuccin-macchiato .button.is-text{background-color:transparent;border-color:transparent;color:#cad3f5;text-decoration:underline}html.theme--catppuccin-macchiato .button.is-text:hover,html.theme--catppuccin-macchiato .button.is-text.is-hovered,html.theme--catppuccin-macchiato .button.is-text:focus,html.theme--catppuccin-macchiato .button.is-text.is-focused{background-color:#1e2030;color:#b5c1f1}html.theme--catppuccin-macchiato .button.is-text:active,html.theme--catppuccin-macchiato .button.is-text.is-active{background-color:#141620;color:#b5c1f1}html.theme--catppuccin-macchiato .button.is-text[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-text{background-color:transparent;border-color:transparent;box-shadow:none}html.theme--catppuccin-macchiato .button.is-ghost{background:none;border-color:rgba(0,0,0,0);color:#8aadf4;text-decoration:none}html.theme--catppuccin-macchiato .button.is-ghost:hover,html.theme--catppuccin-macchiato .button.is-ghost.is-hovered{color:#8aadf4;text-decoration:underline}html.theme--catppuccin-macchiato .button.is-white{background-color:#fff;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-macchiato .button.is-white:hover,html.theme--catppuccin-macchiato .button.is-white.is-hovered{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-macchiato .button.is-white:focus,html.theme--catppuccin-macchiato .button.is-white.is-focused{border-color:transparent;color:#0a0a0a}html.theme--catppuccin-macchiato .button.is-white:focus:not(:active),html.theme--catppuccin-macchiato .button.is-white.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}html.theme--catppuccin-macchiato .button.is-white:active,html.theme--catppuccin-macchiato .button.is-white.is-active{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-macchiato .button.is-white[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-white{background-color:#fff;border-color:#fff;box-shadow:none}html.theme--catppuccin-macchiato .button.is-white.is-inverted{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-macchiato .button.is-white.is-inverted:hover,html.theme--catppuccin-macchiato .button.is-white.is-inverted.is-hovered{background-color:#000}html.theme--catppuccin-macchiato .button.is-white.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-white.is-inverted{background-color:#0a0a0a;border-color:transparent;box-shadow:none;color:#fff}html.theme--catppuccin-macchiato .button.is-white.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--catppuccin-macchiato .button.is-white.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-macchiato .button.is-white.is-outlined:hover,html.theme--catppuccin-macchiato .button.is-white.is-outlined.is-hovered,html.theme--catppuccin-macchiato .button.is-white.is-outlined:focus,html.theme--catppuccin-macchiato .button.is-white.is-outlined.is-focused{background-color:#fff;border-color:#fff;color:#0a0a0a}html.theme--catppuccin-macchiato .button.is-white.is-outlined.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-macchiato .button.is-white.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .button.is-white.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .button.is-white.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .button.is-white.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--catppuccin-macchiato .button.is-white.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-white.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-macchiato .button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}html.theme--catppuccin-macchiato .button.is-white.is-inverted.is-outlined:hover,html.theme--catppuccin-macchiato .button.is-white.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-macchiato .button.is-white.is-inverted.is-outlined:focus,html.theme--catppuccin-macchiato .button.is-white.is-inverted.is-outlined.is-focused{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-macchiato .button.is-white.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .button.is-white.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .button.is-white.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .button.is-white.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-macchiato .button.is-white.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}html.theme--catppuccin-macchiato .button.is-black{background-color:#0a0a0a;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-black:hover,html.theme--catppuccin-macchiato .button.is-black.is-hovered{background-color:#040404;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-black:focus,html.theme--catppuccin-macchiato .button.is-black.is-focused{border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-black:focus:not(:active),html.theme--catppuccin-macchiato .button.is-black.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}html.theme--catppuccin-macchiato .button.is-black:active,html.theme--catppuccin-macchiato .button.is-black.is-active{background-color:#000;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-black[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-black{background-color:#0a0a0a;border-color:#0a0a0a;box-shadow:none}html.theme--catppuccin-macchiato .button.is-black.is-inverted{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-macchiato .button.is-black.is-inverted:hover,html.theme--catppuccin-macchiato .button.is-black.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--catppuccin-macchiato .button.is-black.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-black.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#0a0a0a}html.theme--catppuccin-macchiato .button.is-black.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-macchiato .button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}html.theme--catppuccin-macchiato .button.is-black.is-outlined:hover,html.theme--catppuccin-macchiato .button.is-black.is-outlined.is-hovered,html.theme--catppuccin-macchiato .button.is-black.is-outlined:focus,html.theme--catppuccin-macchiato .button.is-black.is-outlined.is-focused{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}html.theme--catppuccin-macchiato .button.is-black.is-outlined.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--catppuccin-macchiato .button.is-black.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .button.is-black.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .button.is-black.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .button.is-black.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-macchiato .button.is-black.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}html.theme--catppuccin-macchiato .button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-macchiato .button.is-black.is-inverted.is-outlined:hover,html.theme--catppuccin-macchiato .button.is-black.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-macchiato .button.is-black.is-inverted.is-outlined:focus,html.theme--catppuccin-macchiato .button.is-black.is-inverted.is-outlined.is-focused{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-macchiato .button.is-black.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .button.is-black.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .button.is-black.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .button.is-black.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--catppuccin-macchiato .button.is-black.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-macchiato .button.is-light{background-color:#f5f5f5;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-light:hover,html.theme--catppuccin-macchiato .button.is-light.is-hovered{background-color:#eee;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-light:focus,html.theme--catppuccin-macchiato .button.is-light.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-light:focus:not(:active),html.theme--catppuccin-macchiato .button.is-light.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(245,245,245,0.25)}html.theme--catppuccin-macchiato .button.is-light:active,html.theme--catppuccin-macchiato .button.is-light.is-active{background-color:#e8e8e8;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-light[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-light{background-color:#f5f5f5;border-color:#f5f5f5;box-shadow:none}html.theme--catppuccin-macchiato .button.is-light.is-inverted{background-color:rgba(0,0,0,0.7);color:#f5f5f5}html.theme--catppuccin-macchiato .button.is-light.is-inverted:hover,html.theme--catppuccin-macchiato .button.is-light.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-light.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-light.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#f5f5f5}html.theme--catppuccin-macchiato .button.is-light.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-macchiato .button.is-light.is-outlined{background-color:transparent;border-color:#f5f5f5;color:#f5f5f5}html.theme--catppuccin-macchiato .button.is-light.is-outlined:hover,html.theme--catppuccin-macchiato .button.is-light.is-outlined.is-hovered,html.theme--catppuccin-macchiato .button.is-light.is-outlined:focus,html.theme--catppuccin-macchiato .button.is-light.is-outlined.is-focused{background-color:#f5f5f5;border-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-light.is-outlined.is-loading::after{border-color:transparent transparent #f5f5f5 #f5f5f5 !important}html.theme--catppuccin-macchiato .button.is-light.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .button.is-light.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .button.is-light.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .button.is-light.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-macchiato .button.is-light.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-light.is-outlined{background-color:transparent;border-color:#f5f5f5;box-shadow:none;color:#f5f5f5}html.theme--catppuccin-macchiato .button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-light.is-inverted.is-outlined:hover,html.theme--catppuccin-macchiato .button.is-light.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-macchiato .button.is-light.is-inverted.is-outlined:focus,html.theme--catppuccin-macchiato .button.is-light.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#f5f5f5}html.theme--catppuccin-macchiato .button.is-light.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .button.is-light.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .button.is-light.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .button.is-light.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #f5f5f5 #f5f5f5 !important}html.theme--catppuccin-macchiato .button.is-light.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-dark,html.theme--catppuccin-macchiato .content kbd.button{background-color:#363a4f;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-dark:hover,html.theme--catppuccin-macchiato .content kbd.button:hover,html.theme--catppuccin-macchiato .button.is-dark.is-hovered,html.theme--catppuccin-macchiato .content kbd.button.is-hovered{background-color:#313447;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-dark:focus,html.theme--catppuccin-macchiato .content kbd.button:focus,html.theme--catppuccin-macchiato .button.is-dark.is-focused,html.theme--catppuccin-macchiato .content kbd.button.is-focused{border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-dark:focus:not(:active),html.theme--catppuccin-macchiato .content kbd.button:focus:not(:active),html.theme--catppuccin-macchiato .button.is-dark.is-focused:not(:active),html.theme--catppuccin-macchiato .content kbd.button.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(54,58,79,0.25)}html.theme--catppuccin-macchiato .button.is-dark:active,html.theme--catppuccin-macchiato .content kbd.button:active,html.theme--catppuccin-macchiato .button.is-dark.is-active,html.theme--catppuccin-macchiato .content kbd.button.is-active{background-color:#2c2f40;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-dark[disabled],html.theme--catppuccin-macchiato .content kbd.button[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-dark,fieldset[disabled] html.theme--catppuccin-macchiato .content kbd.button{background-color:#363a4f;border-color:#363a4f;box-shadow:none}html.theme--catppuccin-macchiato .button.is-dark.is-inverted,html.theme--catppuccin-macchiato .content kbd.button.is-inverted{background-color:#fff;color:#363a4f}html.theme--catppuccin-macchiato .button.is-dark.is-inverted:hover,html.theme--catppuccin-macchiato .content kbd.button.is-inverted:hover,html.theme--catppuccin-macchiato .button.is-dark.is-inverted.is-hovered,html.theme--catppuccin-macchiato .content kbd.button.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--catppuccin-macchiato .button.is-dark.is-inverted[disabled],html.theme--catppuccin-macchiato .content kbd.button.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-dark.is-inverted,fieldset[disabled] html.theme--catppuccin-macchiato .content kbd.button.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#363a4f}html.theme--catppuccin-macchiato .button.is-dark.is-loading::after,html.theme--catppuccin-macchiato .content kbd.button.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-macchiato .button.is-dark.is-outlined,html.theme--catppuccin-macchiato .content kbd.button.is-outlined{background-color:transparent;border-color:#363a4f;color:#363a4f}html.theme--catppuccin-macchiato .button.is-dark.is-outlined:hover,html.theme--catppuccin-macchiato .content kbd.button.is-outlined:hover,html.theme--catppuccin-macchiato .button.is-dark.is-outlined.is-hovered,html.theme--catppuccin-macchiato .content kbd.button.is-outlined.is-hovered,html.theme--catppuccin-macchiato .button.is-dark.is-outlined:focus,html.theme--catppuccin-macchiato .content kbd.button.is-outlined:focus,html.theme--catppuccin-macchiato .button.is-dark.is-outlined.is-focused,html.theme--catppuccin-macchiato .content kbd.button.is-outlined.is-focused{background-color:#363a4f;border-color:#363a4f;color:#fff}html.theme--catppuccin-macchiato .button.is-dark.is-outlined.is-loading::after,html.theme--catppuccin-macchiato .content kbd.button.is-outlined.is-loading::after{border-color:transparent transparent #363a4f #363a4f !important}html.theme--catppuccin-macchiato .button.is-dark.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .content kbd.button.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .button.is-dark.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .content kbd.button.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .button.is-dark.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .content kbd.button.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .button.is-dark.is-outlined.is-loading.is-focused::after,html.theme--catppuccin-macchiato .content kbd.button.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-macchiato .button.is-dark.is-outlined[disabled],html.theme--catppuccin-macchiato .content kbd.button.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-dark.is-outlined,fieldset[disabled] html.theme--catppuccin-macchiato .content kbd.button.is-outlined{background-color:transparent;border-color:#363a4f;box-shadow:none;color:#363a4f}html.theme--catppuccin-macchiato .button.is-dark.is-inverted.is-outlined,html.theme--catppuccin-macchiato .content kbd.button.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-macchiato .button.is-dark.is-inverted.is-outlined:hover,html.theme--catppuccin-macchiato .content kbd.button.is-inverted.is-outlined:hover,html.theme--catppuccin-macchiato .button.is-dark.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-macchiato .content kbd.button.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-macchiato .button.is-dark.is-inverted.is-outlined:focus,html.theme--catppuccin-macchiato .content kbd.button.is-inverted.is-outlined:focus,html.theme--catppuccin-macchiato .button.is-dark.is-inverted.is-outlined.is-focused,html.theme--catppuccin-macchiato .content kbd.button.is-inverted.is-outlined.is-focused{background-color:#fff;color:#363a4f}html.theme--catppuccin-macchiato .button.is-dark.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .content kbd.button.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .button.is-dark.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .content kbd.button.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .button.is-dark.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .content kbd.button.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .button.is-dark.is-inverted.is-outlined.is-loading.is-focused::after,html.theme--catppuccin-macchiato .content kbd.button.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #363a4f #363a4f !important}html.theme--catppuccin-macchiato .button.is-dark.is-inverted.is-outlined[disabled],html.theme--catppuccin-macchiato .content kbd.button.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-dark.is-inverted.is-outlined,fieldset[disabled] html.theme--catppuccin-macchiato .content kbd.button.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-macchiato .button.is-primary,html.theme--catppuccin-macchiato .docstring>section>a.button.docs-sourcelink{background-color:#8aadf4;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-primary:hover,html.theme--catppuccin-macchiato .docstring>section>a.button.docs-sourcelink:hover,html.theme--catppuccin-macchiato .button.is-primary.is-hovered,html.theme--catppuccin-macchiato .docstring>section>a.button.is-hovered.docs-sourcelink{background-color:#7ea5f3;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-primary:focus,html.theme--catppuccin-macchiato .docstring>section>a.button.docs-sourcelink:focus,html.theme--catppuccin-macchiato .button.is-primary.is-focused,html.theme--catppuccin-macchiato .docstring>section>a.button.is-focused.docs-sourcelink{border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-primary:focus:not(:active),html.theme--catppuccin-macchiato .docstring>section>a.button.docs-sourcelink:focus:not(:active),html.theme--catppuccin-macchiato .button.is-primary.is-focused:not(:active),html.theme--catppuccin-macchiato .docstring>section>a.button.is-focused.docs-sourcelink:not(:active){box-shadow:0 0 0 0.125em rgba(138,173,244,0.25)}html.theme--catppuccin-macchiato .button.is-primary:active,html.theme--catppuccin-macchiato .docstring>section>a.button.docs-sourcelink:active,html.theme--catppuccin-macchiato .button.is-primary.is-active,html.theme--catppuccin-macchiato .docstring>section>a.button.is-active.docs-sourcelink{background-color:#739df2;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-primary[disabled],html.theme--catppuccin-macchiato .docstring>section>a.button.docs-sourcelink[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-primary,fieldset[disabled] html.theme--catppuccin-macchiato .docstring>section>a.button.docs-sourcelink{background-color:#8aadf4;border-color:#8aadf4;box-shadow:none}html.theme--catppuccin-macchiato .button.is-primary.is-inverted,html.theme--catppuccin-macchiato .docstring>section>a.button.is-inverted.docs-sourcelink{background-color:#fff;color:#8aadf4}html.theme--catppuccin-macchiato .button.is-primary.is-inverted:hover,html.theme--catppuccin-macchiato .docstring>section>a.button.is-inverted.docs-sourcelink:hover,html.theme--catppuccin-macchiato .button.is-primary.is-inverted.is-hovered,html.theme--catppuccin-macchiato .docstring>section>a.button.is-inverted.is-hovered.docs-sourcelink{background-color:#f2f2f2}html.theme--catppuccin-macchiato .button.is-primary.is-inverted[disabled],html.theme--catppuccin-macchiato .docstring>section>a.button.is-inverted.docs-sourcelink[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-primary.is-inverted,fieldset[disabled] html.theme--catppuccin-macchiato .docstring>section>a.button.is-inverted.docs-sourcelink{background-color:#fff;border-color:transparent;box-shadow:none;color:#8aadf4}html.theme--catppuccin-macchiato .button.is-primary.is-loading::after,html.theme--catppuccin-macchiato .docstring>section>a.button.is-loading.docs-sourcelink::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-macchiato .button.is-primary.is-outlined,html.theme--catppuccin-macchiato .docstring>section>a.button.is-outlined.docs-sourcelink{background-color:transparent;border-color:#8aadf4;color:#8aadf4}html.theme--catppuccin-macchiato .button.is-primary.is-outlined:hover,html.theme--catppuccin-macchiato .docstring>section>a.button.is-outlined.docs-sourcelink:hover,html.theme--catppuccin-macchiato .button.is-primary.is-outlined.is-hovered,html.theme--catppuccin-macchiato .docstring>section>a.button.is-outlined.is-hovered.docs-sourcelink,html.theme--catppuccin-macchiato .button.is-primary.is-outlined:focus,html.theme--catppuccin-macchiato .docstring>section>a.button.is-outlined.docs-sourcelink:focus,html.theme--catppuccin-macchiato .button.is-primary.is-outlined.is-focused,html.theme--catppuccin-macchiato .docstring>section>a.button.is-outlined.is-focused.docs-sourcelink{background-color:#8aadf4;border-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .button.is-primary.is-outlined.is-loading::after,html.theme--catppuccin-macchiato .docstring>section>a.button.is-outlined.is-loading.docs-sourcelink::after{border-color:transparent transparent #8aadf4 #8aadf4 !important}html.theme--catppuccin-macchiato .button.is-primary.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .docstring>section>a.button.is-outlined.is-loading.docs-sourcelink:hover::after,html.theme--catppuccin-macchiato .button.is-primary.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .docstring>section>a.button.is-outlined.is-loading.is-hovered.docs-sourcelink::after,html.theme--catppuccin-macchiato .button.is-primary.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .docstring>section>a.button.is-outlined.is-loading.docs-sourcelink:focus::after,html.theme--catppuccin-macchiato .button.is-primary.is-outlined.is-loading.is-focused::after,html.theme--catppuccin-macchiato .docstring>section>a.button.is-outlined.is-loading.is-focused.docs-sourcelink::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-macchiato .button.is-primary.is-outlined[disabled],html.theme--catppuccin-macchiato .docstring>section>a.button.is-outlined.docs-sourcelink[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-primary.is-outlined,fieldset[disabled] html.theme--catppuccin-macchiato .docstring>section>a.button.is-outlined.docs-sourcelink{background-color:transparent;border-color:#8aadf4;box-shadow:none;color:#8aadf4}html.theme--catppuccin-macchiato .button.is-primary.is-inverted.is-outlined,html.theme--catppuccin-macchiato .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-macchiato .button.is-primary.is-inverted.is-outlined:hover,html.theme--catppuccin-macchiato .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink:hover,html.theme--catppuccin-macchiato .button.is-primary.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-macchiato .docstring>section>a.button.is-inverted.is-outlined.is-hovered.docs-sourcelink,html.theme--catppuccin-macchiato .button.is-primary.is-inverted.is-outlined:focus,html.theme--catppuccin-macchiato .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink:focus,html.theme--catppuccin-macchiato .button.is-primary.is-inverted.is-outlined.is-focused,html.theme--catppuccin-macchiato .docstring>section>a.button.is-inverted.is-outlined.is-focused.docs-sourcelink{background-color:#fff;color:#8aadf4}html.theme--catppuccin-macchiato .button.is-primary.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .docstring>section>a.button.is-inverted.is-outlined.is-loading.docs-sourcelink:hover::after,html.theme--catppuccin-macchiato .button.is-primary.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .docstring>section>a.button.is-inverted.is-outlined.is-loading.is-hovered.docs-sourcelink::after,html.theme--catppuccin-macchiato .button.is-primary.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .docstring>section>a.button.is-inverted.is-outlined.is-loading.docs-sourcelink:focus::after,html.theme--catppuccin-macchiato .button.is-primary.is-inverted.is-outlined.is-loading.is-focused::after,html.theme--catppuccin-macchiato .docstring>section>a.button.is-inverted.is-outlined.is-loading.is-focused.docs-sourcelink::after{border-color:transparent transparent #8aadf4 #8aadf4 !important}html.theme--catppuccin-macchiato .button.is-primary.is-inverted.is-outlined[disabled],html.theme--catppuccin-macchiato .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-primary.is-inverted.is-outlined,fieldset[disabled] html.theme--catppuccin-macchiato .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-macchiato .button.is-primary.is-light,html.theme--catppuccin-macchiato .docstring>section>a.button.is-light.docs-sourcelink{background-color:#ecf2fd;color:#0e3b95}html.theme--catppuccin-macchiato .button.is-primary.is-light:hover,html.theme--catppuccin-macchiato .docstring>section>a.button.is-light.docs-sourcelink:hover,html.theme--catppuccin-macchiato .button.is-primary.is-light.is-hovered,html.theme--catppuccin-macchiato .docstring>section>a.button.is-light.is-hovered.docs-sourcelink{background-color:#e1eafc;border-color:transparent;color:#0e3b95}html.theme--catppuccin-macchiato .button.is-primary.is-light:active,html.theme--catppuccin-macchiato .docstring>section>a.button.is-light.docs-sourcelink:active,html.theme--catppuccin-macchiato .button.is-primary.is-light.is-active,html.theme--catppuccin-macchiato .docstring>section>a.button.is-light.is-active.docs-sourcelink{background-color:#d5e2fb;border-color:transparent;color:#0e3b95}html.theme--catppuccin-macchiato .button.is-link{background-color:#8aadf4;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-link:hover,html.theme--catppuccin-macchiato .button.is-link.is-hovered{background-color:#7ea5f3;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-link:focus,html.theme--catppuccin-macchiato .button.is-link.is-focused{border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-link:focus:not(:active),html.theme--catppuccin-macchiato .button.is-link.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(138,173,244,0.25)}html.theme--catppuccin-macchiato .button.is-link:active,html.theme--catppuccin-macchiato .button.is-link.is-active{background-color:#739df2;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-link[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-link{background-color:#8aadf4;border-color:#8aadf4;box-shadow:none}html.theme--catppuccin-macchiato .button.is-link.is-inverted{background-color:#fff;color:#8aadf4}html.theme--catppuccin-macchiato .button.is-link.is-inverted:hover,html.theme--catppuccin-macchiato .button.is-link.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--catppuccin-macchiato .button.is-link.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-link.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#8aadf4}html.theme--catppuccin-macchiato .button.is-link.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-macchiato .button.is-link.is-outlined{background-color:transparent;border-color:#8aadf4;color:#8aadf4}html.theme--catppuccin-macchiato .button.is-link.is-outlined:hover,html.theme--catppuccin-macchiato .button.is-link.is-outlined.is-hovered,html.theme--catppuccin-macchiato .button.is-link.is-outlined:focus,html.theme--catppuccin-macchiato .button.is-link.is-outlined.is-focused{background-color:#8aadf4;border-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .button.is-link.is-outlined.is-loading::after{border-color:transparent transparent #8aadf4 #8aadf4 !important}html.theme--catppuccin-macchiato .button.is-link.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .button.is-link.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .button.is-link.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .button.is-link.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-macchiato .button.is-link.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-link.is-outlined{background-color:transparent;border-color:#8aadf4;box-shadow:none;color:#8aadf4}html.theme--catppuccin-macchiato .button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-macchiato .button.is-link.is-inverted.is-outlined:hover,html.theme--catppuccin-macchiato .button.is-link.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-macchiato .button.is-link.is-inverted.is-outlined:focus,html.theme--catppuccin-macchiato .button.is-link.is-inverted.is-outlined.is-focused{background-color:#fff;color:#8aadf4}html.theme--catppuccin-macchiato .button.is-link.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .button.is-link.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .button.is-link.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .button.is-link.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #8aadf4 #8aadf4 !important}html.theme--catppuccin-macchiato .button.is-link.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-macchiato .button.is-link.is-light{background-color:#ecf2fd;color:#0e3b95}html.theme--catppuccin-macchiato .button.is-link.is-light:hover,html.theme--catppuccin-macchiato .button.is-link.is-light.is-hovered{background-color:#e1eafc;border-color:transparent;color:#0e3b95}html.theme--catppuccin-macchiato .button.is-link.is-light:active,html.theme--catppuccin-macchiato .button.is-link.is-light.is-active{background-color:#d5e2fb;border-color:transparent;color:#0e3b95}html.theme--catppuccin-macchiato .button.is-info{background-color:#8bd5ca;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-info:hover,html.theme--catppuccin-macchiato .button.is-info.is-hovered{background-color:#82d2c6;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-info:focus,html.theme--catppuccin-macchiato .button.is-info.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-info:focus:not(:active),html.theme--catppuccin-macchiato .button.is-info.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(139,213,202,0.25)}html.theme--catppuccin-macchiato .button.is-info:active,html.theme--catppuccin-macchiato .button.is-info.is-active{background-color:#78cec1;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-info[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-info{background-color:#8bd5ca;border-color:#8bd5ca;box-shadow:none}html.theme--catppuccin-macchiato .button.is-info.is-inverted{background-color:rgba(0,0,0,0.7);color:#8bd5ca}html.theme--catppuccin-macchiato .button.is-info.is-inverted:hover,html.theme--catppuccin-macchiato .button.is-info.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-info.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-info.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#8bd5ca}html.theme--catppuccin-macchiato .button.is-info.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-macchiato .button.is-info.is-outlined{background-color:transparent;border-color:#8bd5ca;color:#8bd5ca}html.theme--catppuccin-macchiato .button.is-info.is-outlined:hover,html.theme--catppuccin-macchiato .button.is-info.is-outlined.is-hovered,html.theme--catppuccin-macchiato .button.is-info.is-outlined:focus,html.theme--catppuccin-macchiato .button.is-info.is-outlined.is-focused{background-color:#8bd5ca;border-color:#8bd5ca;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-info.is-outlined.is-loading::after{border-color:transparent transparent #8bd5ca #8bd5ca !important}html.theme--catppuccin-macchiato .button.is-info.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .button.is-info.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .button.is-info.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .button.is-info.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-macchiato .button.is-info.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-info.is-outlined{background-color:transparent;border-color:#8bd5ca;box-shadow:none;color:#8bd5ca}html.theme--catppuccin-macchiato .button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-info.is-inverted.is-outlined:hover,html.theme--catppuccin-macchiato .button.is-info.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-macchiato .button.is-info.is-inverted.is-outlined:focus,html.theme--catppuccin-macchiato .button.is-info.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#8bd5ca}html.theme--catppuccin-macchiato .button.is-info.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .button.is-info.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .button.is-info.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .button.is-info.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #8bd5ca #8bd5ca !important}html.theme--catppuccin-macchiato .button.is-info.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-info.is-light{background-color:#f0faf8;color:#276d62}html.theme--catppuccin-macchiato .button.is-info.is-light:hover,html.theme--catppuccin-macchiato .button.is-info.is-light.is-hovered{background-color:#e7f6f4;border-color:transparent;color:#276d62}html.theme--catppuccin-macchiato .button.is-info.is-light:active,html.theme--catppuccin-macchiato .button.is-info.is-light.is-active{background-color:#ddf3f0;border-color:transparent;color:#276d62}html.theme--catppuccin-macchiato .button.is-success{background-color:#a6da95;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-success:hover,html.theme--catppuccin-macchiato .button.is-success.is-hovered{background-color:#9ed78c;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-success:focus,html.theme--catppuccin-macchiato .button.is-success.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-success:focus:not(:active),html.theme--catppuccin-macchiato .button.is-success.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(166,218,149,0.25)}html.theme--catppuccin-macchiato .button.is-success:active,html.theme--catppuccin-macchiato .button.is-success.is-active{background-color:#96d382;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-success[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-success{background-color:#a6da95;border-color:#a6da95;box-shadow:none}html.theme--catppuccin-macchiato .button.is-success.is-inverted{background-color:rgba(0,0,0,0.7);color:#a6da95}html.theme--catppuccin-macchiato .button.is-success.is-inverted:hover,html.theme--catppuccin-macchiato .button.is-success.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-success.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-success.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#a6da95}html.theme--catppuccin-macchiato .button.is-success.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-macchiato .button.is-success.is-outlined{background-color:transparent;border-color:#a6da95;color:#a6da95}html.theme--catppuccin-macchiato .button.is-success.is-outlined:hover,html.theme--catppuccin-macchiato .button.is-success.is-outlined.is-hovered,html.theme--catppuccin-macchiato .button.is-success.is-outlined:focus,html.theme--catppuccin-macchiato .button.is-success.is-outlined.is-focused{background-color:#a6da95;border-color:#a6da95;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-success.is-outlined.is-loading::after{border-color:transparent transparent #a6da95 #a6da95 !important}html.theme--catppuccin-macchiato .button.is-success.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .button.is-success.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .button.is-success.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .button.is-success.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-macchiato .button.is-success.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-success.is-outlined{background-color:transparent;border-color:#a6da95;box-shadow:none;color:#a6da95}html.theme--catppuccin-macchiato .button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-success.is-inverted.is-outlined:hover,html.theme--catppuccin-macchiato .button.is-success.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-macchiato .button.is-success.is-inverted.is-outlined:focus,html.theme--catppuccin-macchiato .button.is-success.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#a6da95}html.theme--catppuccin-macchiato .button.is-success.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .button.is-success.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .button.is-success.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .button.is-success.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #a6da95 #a6da95 !important}html.theme--catppuccin-macchiato .button.is-success.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-success.is-light{background-color:#f2faf0;color:#386e26}html.theme--catppuccin-macchiato .button.is-success.is-light:hover,html.theme--catppuccin-macchiato .button.is-success.is-light.is-hovered{background-color:#eaf6e6;border-color:transparent;color:#386e26}html.theme--catppuccin-macchiato .button.is-success.is-light:active,html.theme--catppuccin-macchiato .button.is-success.is-light.is-active{background-color:#e2f3dd;border-color:transparent;color:#386e26}html.theme--catppuccin-macchiato .button.is-warning{background-color:#eed49f;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-warning:hover,html.theme--catppuccin-macchiato .button.is-warning.is-hovered{background-color:#eccf94;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-warning:focus,html.theme--catppuccin-macchiato .button.is-warning.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-warning:focus:not(:active),html.theme--catppuccin-macchiato .button.is-warning.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(238,212,159,0.25)}html.theme--catppuccin-macchiato .button.is-warning:active,html.theme--catppuccin-macchiato .button.is-warning.is-active{background-color:#eaca89;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-warning[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-warning{background-color:#eed49f;border-color:#eed49f;box-shadow:none}html.theme--catppuccin-macchiato .button.is-warning.is-inverted{background-color:rgba(0,0,0,0.7);color:#eed49f}html.theme--catppuccin-macchiato .button.is-warning.is-inverted:hover,html.theme--catppuccin-macchiato .button.is-warning.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-warning.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-warning.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#eed49f}html.theme--catppuccin-macchiato .button.is-warning.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-macchiato .button.is-warning.is-outlined{background-color:transparent;border-color:#eed49f;color:#eed49f}html.theme--catppuccin-macchiato .button.is-warning.is-outlined:hover,html.theme--catppuccin-macchiato .button.is-warning.is-outlined.is-hovered,html.theme--catppuccin-macchiato .button.is-warning.is-outlined:focus,html.theme--catppuccin-macchiato .button.is-warning.is-outlined.is-focused{background-color:#eed49f;border-color:#eed49f;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-warning.is-outlined.is-loading::after{border-color:transparent transparent #eed49f #eed49f !important}html.theme--catppuccin-macchiato .button.is-warning.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .button.is-warning.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .button.is-warning.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .button.is-warning.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-macchiato .button.is-warning.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-warning.is-outlined{background-color:transparent;border-color:#eed49f;box-shadow:none;color:#eed49f}html.theme--catppuccin-macchiato .button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-warning.is-inverted.is-outlined:hover,html.theme--catppuccin-macchiato .button.is-warning.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-macchiato .button.is-warning.is-inverted.is-outlined:focus,html.theme--catppuccin-macchiato .button.is-warning.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#eed49f}html.theme--catppuccin-macchiato .button.is-warning.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .button.is-warning.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .button.is-warning.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .button.is-warning.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #eed49f #eed49f !important}html.theme--catppuccin-macchiato .button.is-warning.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-warning.is-light{background-color:#fcf7ee;color:#7e5c16}html.theme--catppuccin-macchiato .button.is-warning.is-light:hover,html.theme--catppuccin-macchiato .button.is-warning.is-light.is-hovered{background-color:#faf2e3;border-color:transparent;color:#7e5c16}html.theme--catppuccin-macchiato .button.is-warning.is-light:active,html.theme--catppuccin-macchiato .button.is-warning.is-light.is-active{background-color:#f8eed8;border-color:transparent;color:#7e5c16}html.theme--catppuccin-macchiato .button.is-danger{background-color:#ed8796;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-danger:hover,html.theme--catppuccin-macchiato .button.is-danger.is-hovered{background-color:#eb7c8c;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-danger:focus,html.theme--catppuccin-macchiato .button.is-danger.is-focused{border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-danger:focus:not(:active),html.theme--catppuccin-macchiato .button.is-danger.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(237,135,150,0.25)}html.theme--catppuccin-macchiato .button.is-danger:active,html.theme--catppuccin-macchiato .button.is-danger.is-active{background-color:#ea7183;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-danger[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-danger{background-color:#ed8796;border-color:#ed8796;box-shadow:none}html.theme--catppuccin-macchiato .button.is-danger.is-inverted{background-color:#fff;color:#ed8796}html.theme--catppuccin-macchiato .button.is-danger.is-inverted:hover,html.theme--catppuccin-macchiato .button.is-danger.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--catppuccin-macchiato .button.is-danger.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-danger.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#ed8796}html.theme--catppuccin-macchiato .button.is-danger.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-macchiato .button.is-danger.is-outlined{background-color:transparent;border-color:#ed8796;color:#ed8796}html.theme--catppuccin-macchiato .button.is-danger.is-outlined:hover,html.theme--catppuccin-macchiato .button.is-danger.is-outlined.is-hovered,html.theme--catppuccin-macchiato .button.is-danger.is-outlined:focus,html.theme--catppuccin-macchiato .button.is-danger.is-outlined.is-focused{background-color:#ed8796;border-color:#ed8796;color:#fff}html.theme--catppuccin-macchiato .button.is-danger.is-outlined.is-loading::after{border-color:transparent transparent #ed8796 #ed8796 !important}html.theme--catppuccin-macchiato .button.is-danger.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .button.is-danger.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .button.is-danger.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .button.is-danger.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-macchiato .button.is-danger.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-danger.is-outlined{background-color:transparent;border-color:#ed8796;box-shadow:none;color:#ed8796}html.theme--catppuccin-macchiato .button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-macchiato .button.is-danger.is-inverted.is-outlined:hover,html.theme--catppuccin-macchiato .button.is-danger.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-macchiato .button.is-danger.is-inverted.is-outlined:focus,html.theme--catppuccin-macchiato .button.is-danger.is-inverted.is-outlined.is-focused{background-color:#fff;color:#ed8796}html.theme--catppuccin-macchiato .button.is-danger.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .button.is-danger.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .button.is-danger.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .button.is-danger.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #ed8796 #ed8796 !important}html.theme--catppuccin-macchiato .button.is-danger.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-macchiato .button.is-danger.is-light{background-color:#fcedef;color:#971729}html.theme--catppuccin-macchiato .button.is-danger.is-light:hover,html.theme--catppuccin-macchiato .button.is-danger.is-light.is-hovered{background-color:#fbe2e6;border-color:transparent;color:#971729}html.theme--catppuccin-macchiato .button.is-danger.is-light:active,html.theme--catppuccin-macchiato .button.is-danger.is-light.is-active{background-color:#f9d7dc;border-color:transparent;color:#971729}html.theme--catppuccin-macchiato .button.is-small,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.button{font-size:.75rem}html.theme--catppuccin-macchiato .button.is-small:not(.is-rounded),html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.button:not(.is-rounded){border-radius:3px}html.theme--catppuccin-macchiato .button.is-normal{font-size:1rem}html.theme--catppuccin-macchiato .button.is-medium{font-size:1.25rem}html.theme--catppuccin-macchiato .button.is-large{font-size:1.5rem}html.theme--catppuccin-macchiato .button[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button{background-color:#6e738d;border-color:#5b6078;box-shadow:none;opacity:.5}html.theme--catppuccin-macchiato .button.is-fullwidth{display:flex;width:100%}html.theme--catppuccin-macchiato .button.is-loading{color:transparent !important;pointer-events:none}html.theme--catppuccin-macchiato .button.is-loading::after{position:absolute;left:calc(50% - (1em * 0.5));top:calc(50% - (1em * 0.5));position:absolute !important}html.theme--catppuccin-macchiato .button.is-static{background-color:#1e2030;border-color:#5b6078;color:#8087a2;box-shadow:none;pointer-events:none}html.theme--catppuccin-macchiato .button.is-rounded,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.button{border-radius:9999px;padding-left:calc(1em + 0.25em);padding-right:calc(1em + 0.25em)}html.theme--catppuccin-macchiato .buttons{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}html.theme--catppuccin-macchiato .buttons .button{margin-bottom:0.5rem}html.theme--catppuccin-macchiato .buttons .button:not(:last-child):not(.is-fullwidth){margin-right:.5rem}html.theme--catppuccin-macchiato .buttons:last-child{margin-bottom:-0.5rem}html.theme--catppuccin-macchiato .buttons:not(:last-child){margin-bottom:1rem}html.theme--catppuccin-macchiato .buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large){font-size:.75rem}html.theme--catppuccin-macchiato .buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large):not(.is-rounded){border-radius:3px}html.theme--catppuccin-macchiato .buttons.are-medium .button:not(.is-small):not(.is-normal):not(.is-large){font-size:1.25rem}html.theme--catppuccin-macchiato .buttons.are-large .button:not(.is-small):not(.is-normal):not(.is-medium){font-size:1.5rem}html.theme--catppuccin-macchiato .buttons.has-addons .button:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}html.theme--catppuccin-macchiato .buttons.has-addons .button:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0;margin-right:-1px}html.theme--catppuccin-macchiato .buttons.has-addons .button:last-child{margin-right:0}html.theme--catppuccin-macchiato .buttons.has-addons .button:hover,html.theme--catppuccin-macchiato .buttons.has-addons .button.is-hovered{z-index:2}html.theme--catppuccin-macchiato .buttons.has-addons .button:focus,html.theme--catppuccin-macchiato .buttons.has-addons .button.is-focused,html.theme--catppuccin-macchiato .buttons.has-addons .button:active,html.theme--catppuccin-macchiato .buttons.has-addons .button.is-active,html.theme--catppuccin-macchiato .buttons.has-addons .button.is-selected{z-index:3}html.theme--catppuccin-macchiato .buttons.has-addons .button:focus:hover,html.theme--catppuccin-macchiato .buttons.has-addons .button.is-focused:hover,html.theme--catppuccin-macchiato .buttons.has-addons .button:active:hover,html.theme--catppuccin-macchiato .buttons.has-addons .button.is-active:hover,html.theme--catppuccin-macchiato .buttons.has-addons .button.is-selected:hover{z-index:4}html.theme--catppuccin-macchiato .buttons.has-addons .button.is-expanded{flex-grow:1;flex-shrink:1}html.theme--catppuccin-macchiato .buttons.is-centered{justify-content:center}html.theme--catppuccin-macchiato .buttons.is-centered:not(.has-addons) .button:not(.is-fullwidth){margin-left:0.25rem;margin-right:0.25rem}html.theme--catppuccin-macchiato .buttons.is-right{justify-content:flex-end}html.theme--catppuccin-macchiato .buttons.is-right:not(.has-addons) .button:not(.is-fullwidth){margin-left:0.25rem;margin-right:0.25rem}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .button.is-responsive.is-small,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-responsive{font-size:.5625rem}html.theme--catppuccin-macchiato .button.is-responsive,html.theme--catppuccin-macchiato .button.is-responsive.is-normal{font-size:.65625rem}html.theme--catppuccin-macchiato .button.is-responsive.is-medium{font-size:.75rem}html.theme--catppuccin-macchiato .button.is-responsive.is-large{font-size:1rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-macchiato .button.is-responsive.is-small,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-responsive{font-size:.65625rem}html.theme--catppuccin-macchiato .button.is-responsive,html.theme--catppuccin-macchiato .button.is-responsive.is-normal{font-size:.75rem}html.theme--catppuccin-macchiato .button.is-responsive.is-medium{font-size:1rem}html.theme--catppuccin-macchiato .button.is-responsive.is-large{font-size:1.25rem}}html.theme--catppuccin-macchiato .container{flex-grow:1;margin:0 auto;position:relative;width:auto}html.theme--catppuccin-macchiato .container.is-fluid{max-width:none !important;padding-left:32px;padding-right:32px;width:100%}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .container{max-width:992px}}@media screen and (max-width: 1215px){html.theme--catppuccin-macchiato .container.is-widescreen:not(.is-max-desktop){max-width:1152px}}@media screen and (max-width: 1407px){html.theme--catppuccin-macchiato .container.is-fullhd:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}@media screen and (min-width: 1216px){html.theme--catppuccin-macchiato .container:not(.is-max-desktop){max-width:1152px}}@media screen and (min-width: 1408px){html.theme--catppuccin-macchiato .container:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}html.theme--catppuccin-macchiato .content li+li{margin-top:0.25em}html.theme--catppuccin-macchiato .content p:not(:last-child),html.theme--catppuccin-macchiato .content dl:not(:last-child),html.theme--catppuccin-macchiato .content ol:not(:last-child),html.theme--catppuccin-macchiato .content ul:not(:last-child),html.theme--catppuccin-macchiato .content blockquote:not(:last-child),html.theme--catppuccin-macchiato .content pre:not(:last-child),html.theme--catppuccin-macchiato .content table:not(:last-child){margin-bottom:1em}html.theme--catppuccin-macchiato .content h1,html.theme--catppuccin-macchiato .content h2,html.theme--catppuccin-macchiato .content h3,html.theme--catppuccin-macchiato .content h4,html.theme--catppuccin-macchiato .content h5,html.theme--catppuccin-macchiato .content h6{color:#cad3f5;font-weight:600;line-height:1.125}html.theme--catppuccin-macchiato .content h1{font-size:2em;margin-bottom:0.5em}html.theme--catppuccin-macchiato .content h1:not(:first-child){margin-top:1em}html.theme--catppuccin-macchiato .content h2{font-size:1.75em;margin-bottom:0.5714em}html.theme--catppuccin-macchiato .content h2:not(:first-child){margin-top:1.1428em}html.theme--catppuccin-macchiato .content h3{font-size:1.5em;margin-bottom:0.6666em}html.theme--catppuccin-macchiato .content h3:not(:first-child){margin-top:1.3333em}html.theme--catppuccin-macchiato .content h4{font-size:1.25em;margin-bottom:0.8em}html.theme--catppuccin-macchiato .content h5{font-size:1.125em;margin-bottom:0.8888em}html.theme--catppuccin-macchiato .content h6{font-size:1em;margin-bottom:1em}html.theme--catppuccin-macchiato .content blockquote{background-color:#1e2030;border-left:5px solid #5b6078;padding:1.25em 1.5em}html.theme--catppuccin-macchiato .content ol{list-style-position:outside;margin-left:2em;margin-top:1em}html.theme--catppuccin-macchiato .content ol:not([type]){list-style-type:decimal}html.theme--catppuccin-macchiato .content ol.is-lower-alpha:not([type]){list-style-type:lower-alpha}html.theme--catppuccin-macchiato .content ol.is-lower-roman:not([type]){list-style-type:lower-roman}html.theme--catppuccin-macchiato .content ol.is-upper-alpha:not([type]){list-style-type:upper-alpha}html.theme--catppuccin-macchiato .content ol.is-upper-roman:not([type]){list-style-type:upper-roman}html.theme--catppuccin-macchiato .content ul{list-style:disc outside;margin-left:2em;margin-top:1em}html.theme--catppuccin-macchiato .content ul ul{list-style-type:circle;margin-top:0.5em}html.theme--catppuccin-macchiato .content ul ul ul{list-style-type:square}html.theme--catppuccin-macchiato .content dd{margin-left:2em}html.theme--catppuccin-macchiato .content figure{margin-left:2em;margin-right:2em;text-align:center}html.theme--catppuccin-macchiato .content figure:not(:first-child){margin-top:2em}html.theme--catppuccin-macchiato .content figure:not(:last-child){margin-bottom:2em}html.theme--catppuccin-macchiato .content figure img{display:inline-block}html.theme--catppuccin-macchiato .content figure figcaption{font-style:italic}html.theme--catppuccin-macchiato .content pre{-webkit-overflow-scrolling:touch;overflow-x:auto;padding:0;white-space:pre;word-wrap:normal}html.theme--catppuccin-macchiato .content sup,html.theme--catppuccin-macchiato .content sub{font-size:75%}html.theme--catppuccin-macchiato .content table{width:100%}html.theme--catppuccin-macchiato .content table td,html.theme--catppuccin-macchiato .content table th{border:1px solid #5b6078;border-width:0 0 1px;padding:0.5em 0.75em;vertical-align:top}html.theme--catppuccin-macchiato .content table th{color:#b5c1f1}html.theme--catppuccin-macchiato .content table th:not([align]){text-align:inherit}html.theme--catppuccin-macchiato .content table thead td,html.theme--catppuccin-macchiato .content table thead th{border-width:0 0 2px;color:#b5c1f1}html.theme--catppuccin-macchiato .content table tfoot td,html.theme--catppuccin-macchiato .content table tfoot th{border-width:2px 0 0;color:#b5c1f1}html.theme--catppuccin-macchiato .content table tbody tr:last-child td,html.theme--catppuccin-macchiato .content table tbody tr:last-child th{border-bottom-width:0}html.theme--catppuccin-macchiato .content .tabs li+li{margin-top:0}html.theme--catppuccin-macchiato .content.is-small,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.content{font-size:.75rem}html.theme--catppuccin-macchiato .content.is-normal{font-size:1rem}html.theme--catppuccin-macchiato .content.is-medium{font-size:1.25rem}html.theme--catppuccin-macchiato .content.is-large{font-size:1.5rem}html.theme--catppuccin-macchiato .icon{align-items:center;display:inline-flex;justify-content:center;height:1.5rem;width:1.5rem}html.theme--catppuccin-macchiato .icon.is-small,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.icon{height:1rem;width:1rem}html.theme--catppuccin-macchiato .icon.is-medium{height:2rem;width:2rem}html.theme--catppuccin-macchiato .icon.is-large{height:3rem;width:3rem}html.theme--catppuccin-macchiato .icon-text{align-items:flex-start;color:inherit;display:inline-flex;flex-wrap:wrap;line-height:1.5rem;vertical-align:top}html.theme--catppuccin-macchiato .icon-text .icon{flex-grow:0;flex-shrink:0}html.theme--catppuccin-macchiato .icon-text .icon:not(:last-child){margin-right:.25em}html.theme--catppuccin-macchiato .icon-text .icon:not(:first-child){margin-left:.25em}html.theme--catppuccin-macchiato div.icon-text{display:flex}html.theme--catppuccin-macchiato .image,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img{display:block;position:relative}html.theme--catppuccin-macchiato .image img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img img{display:block;height:auto;width:100%}html.theme--catppuccin-macchiato .image img.is-rounded,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img img.is-rounded{border-radius:9999px}html.theme--catppuccin-macchiato .image.is-fullwidth,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-fullwidth{width:100%}html.theme--catppuccin-macchiato .image.is-square img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-square img,html.theme--catppuccin-macchiato .image.is-square .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-square .has-ratio,html.theme--catppuccin-macchiato .image.is-1by1 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-1by1 img,html.theme--catppuccin-macchiato .image.is-1by1 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-1by1 .has-ratio,html.theme--catppuccin-macchiato .image.is-5by4 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-5by4 img,html.theme--catppuccin-macchiato .image.is-5by4 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-5by4 .has-ratio,html.theme--catppuccin-macchiato .image.is-4by3 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-4by3 img,html.theme--catppuccin-macchiato .image.is-4by3 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-4by3 .has-ratio,html.theme--catppuccin-macchiato .image.is-3by2 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by2 img,html.theme--catppuccin-macchiato .image.is-3by2 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by2 .has-ratio,html.theme--catppuccin-macchiato .image.is-5by3 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-5by3 img,html.theme--catppuccin-macchiato .image.is-5by3 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-5by3 .has-ratio,html.theme--catppuccin-macchiato .image.is-16by9 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-16by9 img,html.theme--catppuccin-macchiato .image.is-16by9 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-16by9 .has-ratio,html.theme--catppuccin-macchiato .image.is-2by1 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-2by1 img,html.theme--catppuccin-macchiato .image.is-2by1 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-2by1 .has-ratio,html.theme--catppuccin-macchiato .image.is-3by1 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by1 img,html.theme--catppuccin-macchiato .image.is-3by1 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by1 .has-ratio,html.theme--catppuccin-macchiato .image.is-4by5 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-4by5 img,html.theme--catppuccin-macchiato .image.is-4by5 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-4by5 .has-ratio,html.theme--catppuccin-macchiato .image.is-3by4 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by4 img,html.theme--catppuccin-macchiato .image.is-3by4 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by4 .has-ratio,html.theme--catppuccin-macchiato .image.is-2by3 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-2by3 img,html.theme--catppuccin-macchiato .image.is-2by3 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-2by3 .has-ratio,html.theme--catppuccin-macchiato .image.is-3by5 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by5 img,html.theme--catppuccin-macchiato .image.is-3by5 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by5 .has-ratio,html.theme--catppuccin-macchiato .image.is-9by16 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-9by16 img,html.theme--catppuccin-macchiato .image.is-9by16 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-9by16 .has-ratio,html.theme--catppuccin-macchiato .image.is-1by2 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-1by2 img,html.theme--catppuccin-macchiato .image.is-1by2 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-1by2 .has-ratio,html.theme--catppuccin-macchiato .image.is-1by3 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-1by3 img,html.theme--catppuccin-macchiato .image.is-1by3 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-1by3 .has-ratio{height:100%;width:100%}html.theme--catppuccin-macchiato .image.is-square,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-square,html.theme--catppuccin-macchiato .image.is-1by1,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-1by1{padding-top:100%}html.theme--catppuccin-macchiato .image.is-5by4,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-5by4{padding-top:80%}html.theme--catppuccin-macchiato .image.is-4by3,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-4by3{padding-top:75%}html.theme--catppuccin-macchiato .image.is-3by2,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by2{padding-top:66.6666%}html.theme--catppuccin-macchiato .image.is-5by3,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-5by3{padding-top:60%}html.theme--catppuccin-macchiato .image.is-16by9,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-16by9{padding-top:56.25%}html.theme--catppuccin-macchiato .image.is-2by1,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-2by1{padding-top:50%}html.theme--catppuccin-macchiato .image.is-3by1,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by1{padding-top:33.3333%}html.theme--catppuccin-macchiato .image.is-4by5,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-4by5{padding-top:125%}html.theme--catppuccin-macchiato .image.is-3by4,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by4{padding-top:133.3333%}html.theme--catppuccin-macchiato .image.is-2by3,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-2by3{padding-top:150%}html.theme--catppuccin-macchiato .image.is-3by5,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by5{padding-top:166.6666%}html.theme--catppuccin-macchiato .image.is-9by16,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-9by16{padding-top:177.7777%}html.theme--catppuccin-macchiato .image.is-1by2,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-1by2{padding-top:200%}html.theme--catppuccin-macchiato .image.is-1by3,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-1by3{padding-top:300%}html.theme--catppuccin-macchiato .image.is-16x16,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-16x16{height:16px;width:16px}html.theme--catppuccin-macchiato .image.is-24x24,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-24x24{height:24px;width:24px}html.theme--catppuccin-macchiato .image.is-32x32,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-32x32{height:32px;width:32px}html.theme--catppuccin-macchiato .image.is-48x48,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-48x48{height:48px;width:48px}html.theme--catppuccin-macchiato .image.is-64x64,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-64x64{height:64px;width:64px}html.theme--catppuccin-macchiato .image.is-96x96,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-96x96{height:96px;width:96px}html.theme--catppuccin-macchiato .image.is-128x128,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-128x128{height:128px;width:128px}html.theme--catppuccin-macchiato .notification{background-color:#1e2030;border-radius:.4em;position:relative;padding:1.25rem 2.5rem 1.25rem 1.5rem}html.theme--catppuccin-macchiato .notification a:not(.button):not(.dropdown-item){color:currentColor;text-decoration:underline}html.theme--catppuccin-macchiato .notification strong{color:currentColor}html.theme--catppuccin-macchiato .notification code,html.theme--catppuccin-macchiato .notification pre{background:#fff}html.theme--catppuccin-macchiato .notification pre code{background:transparent}html.theme--catppuccin-macchiato .notification>.delete{right:.5rem;position:absolute;top:0.5rem}html.theme--catppuccin-macchiato .notification .title,html.theme--catppuccin-macchiato .notification .subtitle,html.theme--catppuccin-macchiato .notification .content{color:currentColor}html.theme--catppuccin-macchiato .notification.is-white{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-macchiato .notification.is-black{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-macchiato .notification.is-light{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .notification.is-dark,html.theme--catppuccin-macchiato .content kbd.notification{background-color:#363a4f;color:#fff}html.theme--catppuccin-macchiato .notification.is-primary,html.theme--catppuccin-macchiato .docstring>section>a.notification.docs-sourcelink{background-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .notification.is-primary.is-light,html.theme--catppuccin-macchiato .docstring>section>a.notification.is-light.docs-sourcelink{background-color:#ecf2fd;color:#0e3b95}html.theme--catppuccin-macchiato .notification.is-link{background-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .notification.is-link.is-light{background-color:#ecf2fd;color:#0e3b95}html.theme--catppuccin-macchiato .notification.is-info{background-color:#8bd5ca;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .notification.is-info.is-light{background-color:#f0faf8;color:#276d62}html.theme--catppuccin-macchiato .notification.is-success{background-color:#a6da95;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .notification.is-success.is-light{background-color:#f2faf0;color:#386e26}html.theme--catppuccin-macchiato .notification.is-warning{background-color:#eed49f;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .notification.is-warning.is-light{background-color:#fcf7ee;color:#7e5c16}html.theme--catppuccin-macchiato .notification.is-danger{background-color:#ed8796;color:#fff}html.theme--catppuccin-macchiato .notification.is-danger.is-light{background-color:#fcedef;color:#971729}html.theme--catppuccin-macchiato .progress{-moz-appearance:none;-webkit-appearance:none;border:none;border-radius:9999px;display:block;height:1rem;overflow:hidden;padding:0;width:100%}html.theme--catppuccin-macchiato .progress::-webkit-progress-bar{background-color:#494d64}html.theme--catppuccin-macchiato .progress::-webkit-progress-value{background-color:#8087a2}html.theme--catppuccin-macchiato .progress::-moz-progress-bar{background-color:#8087a2}html.theme--catppuccin-macchiato .progress::-ms-fill{background-color:#8087a2;border:none}html.theme--catppuccin-macchiato .progress.is-white::-webkit-progress-value{background-color:#fff}html.theme--catppuccin-macchiato .progress.is-white::-moz-progress-bar{background-color:#fff}html.theme--catppuccin-macchiato .progress.is-white::-ms-fill{background-color:#fff}html.theme--catppuccin-macchiato .progress.is-white:indeterminate{background-image:linear-gradient(to right, #fff 30%, #494d64 30%)}html.theme--catppuccin-macchiato .progress.is-black::-webkit-progress-value{background-color:#0a0a0a}html.theme--catppuccin-macchiato .progress.is-black::-moz-progress-bar{background-color:#0a0a0a}html.theme--catppuccin-macchiato .progress.is-black::-ms-fill{background-color:#0a0a0a}html.theme--catppuccin-macchiato .progress.is-black:indeterminate{background-image:linear-gradient(to right, #0a0a0a 30%, #494d64 30%)}html.theme--catppuccin-macchiato .progress.is-light::-webkit-progress-value{background-color:#f5f5f5}html.theme--catppuccin-macchiato .progress.is-light::-moz-progress-bar{background-color:#f5f5f5}html.theme--catppuccin-macchiato .progress.is-light::-ms-fill{background-color:#f5f5f5}html.theme--catppuccin-macchiato .progress.is-light:indeterminate{background-image:linear-gradient(to right, #f5f5f5 30%, #494d64 30%)}html.theme--catppuccin-macchiato .progress.is-dark::-webkit-progress-value,html.theme--catppuccin-macchiato .content kbd.progress::-webkit-progress-value{background-color:#363a4f}html.theme--catppuccin-macchiato .progress.is-dark::-moz-progress-bar,html.theme--catppuccin-macchiato .content kbd.progress::-moz-progress-bar{background-color:#363a4f}html.theme--catppuccin-macchiato .progress.is-dark::-ms-fill,html.theme--catppuccin-macchiato .content kbd.progress::-ms-fill{background-color:#363a4f}html.theme--catppuccin-macchiato .progress.is-dark:indeterminate,html.theme--catppuccin-macchiato .content kbd.progress:indeterminate{background-image:linear-gradient(to right, #363a4f 30%, #494d64 30%)}html.theme--catppuccin-macchiato .progress.is-primary::-webkit-progress-value,html.theme--catppuccin-macchiato .docstring>section>a.progress.docs-sourcelink::-webkit-progress-value{background-color:#8aadf4}html.theme--catppuccin-macchiato .progress.is-primary::-moz-progress-bar,html.theme--catppuccin-macchiato .docstring>section>a.progress.docs-sourcelink::-moz-progress-bar{background-color:#8aadf4}html.theme--catppuccin-macchiato .progress.is-primary::-ms-fill,html.theme--catppuccin-macchiato .docstring>section>a.progress.docs-sourcelink::-ms-fill{background-color:#8aadf4}html.theme--catppuccin-macchiato .progress.is-primary:indeterminate,html.theme--catppuccin-macchiato .docstring>section>a.progress.docs-sourcelink:indeterminate{background-image:linear-gradient(to right, #8aadf4 30%, #494d64 30%)}html.theme--catppuccin-macchiato .progress.is-link::-webkit-progress-value{background-color:#8aadf4}html.theme--catppuccin-macchiato .progress.is-link::-moz-progress-bar{background-color:#8aadf4}html.theme--catppuccin-macchiato .progress.is-link::-ms-fill{background-color:#8aadf4}html.theme--catppuccin-macchiato .progress.is-link:indeterminate{background-image:linear-gradient(to right, #8aadf4 30%, #494d64 30%)}html.theme--catppuccin-macchiato .progress.is-info::-webkit-progress-value{background-color:#8bd5ca}html.theme--catppuccin-macchiato .progress.is-info::-moz-progress-bar{background-color:#8bd5ca}html.theme--catppuccin-macchiato .progress.is-info::-ms-fill{background-color:#8bd5ca}html.theme--catppuccin-macchiato .progress.is-info:indeterminate{background-image:linear-gradient(to right, #8bd5ca 30%, #494d64 30%)}html.theme--catppuccin-macchiato .progress.is-success::-webkit-progress-value{background-color:#a6da95}html.theme--catppuccin-macchiato .progress.is-success::-moz-progress-bar{background-color:#a6da95}html.theme--catppuccin-macchiato .progress.is-success::-ms-fill{background-color:#a6da95}html.theme--catppuccin-macchiato .progress.is-success:indeterminate{background-image:linear-gradient(to right, #a6da95 30%, #494d64 30%)}html.theme--catppuccin-macchiato .progress.is-warning::-webkit-progress-value{background-color:#eed49f}html.theme--catppuccin-macchiato .progress.is-warning::-moz-progress-bar{background-color:#eed49f}html.theme--catppuccin-macchiato .progress.is-warning::-ms-fill{background-color:#eed49f}html.theme--catppuccin-macchiato .progress.is-warning:indeterminate{background-image:linear-gradient(to right, #eed49f 30%, #494d64 30%)}html.theme--catppuccin-macchiato .progress.is-danger::-webkit-progress-value{background-color:#ed8796}html.theme--catppuccin-macchiato .progress.is-danger::-moz-progress-bar{background-color:#ed8796}html.theme--catppuccin-macchiato .progress.is-danger::-ms-fill{background-color:#ed8796}html.theme--catppuccin-macchiato .progress.is-danger:indeterminate{background-image:linear-gradient(to right, #ed8796 30%, #494d64 30%)}html.theme--catppuccin-macchiato .progress:indeterminate{animation-duration:1.5s;animation-iteration-count:infinite;animation-name:moveIndeterminate;animation-timing-function:linear;background-color:#494d64;background-image:linear-gradient(to right, #cad3f5 30%, #494d64 30%);background-position:top left;background-repeat:no-repeat;background-size:150% 150%}html.theme--catppuccin-macchiato .progress:indeterminate::-webkit-progress-bar{background-color:transparent}html.theme--catppuccin-macchiato .progress:indeterminate::-moz-progress-bar{background-color:transparent}html.theme--catppuccin-macchiato .progress:indeterminate::-ms-fill{animation-name:none}html.theme--catppuccin-macchiato .progress.is-small,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.progress{height:.75rem}html.theme--catppuccin-macchiato .progress.is-medium{height:1.25rem}html.theme--catppuccin-macchiato .progress.is-large{height:1.5rem}@keyframes moveIndeterminate{from{background-position:200% 0}to{background-position:-200% 0}}html.theme--catppuccin-macchiato .table{background-color:#494d64;color:#cad3f5}html.theme--catppuccin-macchiato .table td,html.theme--catppuccin-macchiato .table th{border:1px solid #5b6078;border-width:0 0 1px;padding:0.5em 0.75em;vertical-align:top}html.theme--catppuccin-macchiato .table td.is-white,html.theme--catppuccin-macchiato .table th.is-white{background-color:#fff;border-color:#fff;color:#0a0a0a}html.theme--catppuccin-macchiato .table td.is-black,html.theme--catppuccin-macchiato .table th.is-black{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}html.theme--catppuccin-macchiato .table td.is-light,html.theme--catppuccin-macchiato .table th.is-light{background-color:#f5f5f5;border-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .table td.is-dark,html.theme--catppuccin-macchiato .table th.is-dark{background-color:#363a4f;border-color:#363a4f;color:#fff}html.theme--catppuccin-macchiato .table td.is-primary,html.theme--catppuccin-macchiato .table th.is-primary{background-color:#8aadf4;border-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .table td.is-link,html.theme--catppuccin-macchiato .table th.is-link{background-color:#8aadf4;border-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .table td.is-info,html.theme--catppuccin-macchiato .table th.is-info{background-color:#8bd5ca;border-color:#8bd5ca;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .table td.is-success,html.theme--catppuccin-macchiato .table th.is-success{background-color:#a6da95;border-color:#a6da95;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .table td.is-warning,html.theme--catppuccin-macchiato .table th.is-warning{background-color:#eed49f;border-color:#eed49f;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .table td.is-danger,html.theme--catppuccin-macchiato .table th.is-danger{background-color:#ed8796;border-color:#ed8796;color:#fff}html.theme--catppuccin-macchiato .table td.is-narrow,html.theme--catppuccin-macchiato .table th.is-narrow{white-space:nowrap;width:1%}html.theme--catppuccin-macchiato .table td.is-selected,html.theme--catppuccin-macchiato .table th.is-selected{background-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .table td.is-selected a,html.theme--catppuccin-macchiato .table td.is-selected strong,html.theme--catppuccin-macchiato .table th.is-selected a,html.theme--catppuccin-macchiato .table th.is-selected strong{color:currentColor}html.theme--catppuccin-macchiato .table td.is-vcentered,html.theme--catppuccin-macchiato .table th.is-vcentered{vertical-align:middle}html.theme--catppuccin-macchiato .table th{color:#b5c1f1}html.theme--catppuccin-macchiato .table th:not([align]){text-align:left}html.theme--catppuccin-macchiato .table tr.is-selected{background-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .table tr.is-selected a,html.theme--catppuccin-macchiato .table tr.is-selected strong{color:currentColor}html.theme--catppuccin-macchiato .table tr.is-selected td,html.theme--catppuccin-macchiato .table tr.is-selected th{border-color:#fff;color:currentColor}html.theme--catppuccin-macchiato .table thead{background-color:rgba(0,0,0,0)}html.theme--catppuccin-macchiato .table thead td,html.theme--catppuccin-macchiato .table thead th{border-width:0 0 2px;color:#b5c1f1}html.theme--catppuccin-macchiato .table tfoot{background-color:rgba(0,0,0,0)}html.theme--catppuccin-macchiato .table tfoot td,html.theme--catppuccin-macchiato .table tfoot th{border-width:2px 0 0;color:#b5c1f1}html.theme--catppuccin-macchiato .table tbody{background-color:rgba(0,0,0,0)}html.theme--catppuccin-macchiato .table tbody tr:last-child td,html.theme--catppuccin-macchiato .table tbody tr:last-child th{border-bottom-width:0}html.theme--catppuccin-macchiato .table.is-bordered td,html.theme--catppuccin-macchiato .table.is-bordered th{border-width:1px}html.theme--catppuccin-macchiato .table.is-bordered tr:last-child td,html.theme--catppuccin-macchiato .table.is-bordered tr:last-child th{border-bottom-width:1px}html.theme--catppuccin-macchiato .table.is-fullwidth{width:100%}html.theme--catppuccin-macchiato .table.is-hoverable tbody tr:not(.is-selected):hover{background-color:#363a4f}html.theme--catppuccin-macchiato .table.is-hoverable.is-striped tbody tr:not(.is-selected):hover{background-color:#363a4f}html.theme--catppuccin-macchiato .table.is-hoverable.is-striped tbody tr:not(.is-selected):hover:nth-child(even){background-color:#3a3e55}html.theme--catppuccin-macchiato .table.is-narrow td,html.theme--catppuccin-macchiato .table.is-narrow th{padding:0.25em 0.5em}html.theme--catppuccin-macchiato .table.is-striped tbody tr:not(.is-selected):nth-child(even){background-color:#363a4f}html.theme--catppuccin-macchiato .table-container{-webkit-overflow-scrolling:touch;overflow:auto;overflow-y:hidden;max-width:100%}html.theme--catppuccin-macchiato .tags{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}html.theme--catppuccin-macchiato .tags .tag,html.theme--catppuccin-macchiato .tags .content kbd,html.theme--catppuccin-macchiato .content .tags kbd,html.theme--catppuccin-macchiato .tags .docstring>section>a.docs-sourcelink{margin-bottom:0.5rem}html.theme--catppuccin-macchiato .tags .tag:not(:last-child),html.theme--catppuccin-macchiato .tags .content kbd:not(:last-child),html.theme--catppuccin-macchiato .content .tags kbd:not(:last-child),html.theme--catppuccin-macchiato .tags .docstring>section>a.docs-sourcelink:not(:last-child){margin-right:.5rem}html.theme--catppuccin-macchiato .tags:last-child{margin-bottom:-0.5rem}html.theme--catppuccin-macchiato .tags:not(:last-child){margin-bottom:1rem}html.theme--catppuccin-macchiato .tags.are-medium .tag:not(.is-normal):not(.is-large),html.theme--catppuccin-macchiato .tags.are-medium .content kbd:not(.is-normal):not(.is-large),html.theme--catppuccin-macchiato .content .tags.are-medium kbd:not(.is-normal):not(.is-large),html.theme--catppuccin-macchiato .tags.are-medium .docstring>section>a.docs-sourcelink:not(.is-normal):not(.is-large){font-size:1rem}html.theme--catppuccin-macchiato .tags.are-large .tag:not(.is-normal):not(.is-medium),html.theme--catppuccin-macchiato .tags.are-large .content kbd:not(.is-normal):not(.is-medium),html.theme--catppuccin-macchiato .content .tags.are-large kbd:not(.is-normal):not(.is-medium),html.theme--catppuccin-macchiato .tags.are-large .docstring>section>a.docs-sourcelink:not(.is-normal):not(.is-medium){font-size:1.25rem}html.theme--catppuccin-macchiato .tags.is-centered{justify-content:center}html.theme--catppuccin-macchiato .tags.is-centered .tag,html.theme--catppuccin-macchiato .tags.is-centered .content kbd,html.theme--catppuccin-macchiato .content .tags.is-centered kbd,html.theme--catppuccin-macchiato .tags.is-centered .docstring>section>a.docs-sourcelink{margin-right:0.25rem;margin-left:0.25rem}html.theme--catppuccin-macchiato .tags.is-right{justify-content:flex-end}html.theme--catppuccin-macchiato .tags.is-right .tag:not(:first-child),html.theme--catppuccin-macchiato .tags.is-right .content kbd:not(:first-child),html.theme--catppuccin-macchiato .content .tags.is-right kbd:not(:first-child),html.theme--catppuccin-macchiato .tags.is-right .docstring>section>a.docs-sourcelink:not(:first-child){margin-left:0.5rem}html.theme--catppuccin-macchiato .tags.is-right .tag:not(:last-child),html.theme--catppuccin-macchiato .tags.is-right .content kbd:not(:last-child),html.theme--catppuccin-macchiato .content .tags.is-right kbd:not(:last-child),html.theme--catppuccin-macchiato .tags.is-right .docstring>section>a.docs-sourcelink:not(:last-child){margin-right:0}html.theme--catppuccin-macchiato .tags.has-addons .tag,html.theme--catppuccin-macchiato .tags.has-addons .content kbd,html.theme--catppuccin-macchiato .content .tags.has-addons kbd,html.theme--catppuccin-macchiato .tags.has-addons .docstring>section>a.docs-sourcelink{margin-right:0}html.theme--catppuccin-macchiato .tags.has-addons .tag:not(:first-child),html.theme--catppuccin-macchiato .tags.has-addons .content kbd:not(:first-child),html.theme--catppuccin-macchiato .content .tags.has-addons kbd:not(:first-child),html.theme--catppuccin-macchiato .tags.has-addons .docstring>section>a.docs-sourcelink:not(:first-child){margin-left:0;border-top-left-radius:0;border-bottom-left-radius:0}html.theme--catppuccin-macchiato .tags.has-addons .tag:not(:last-child),html.theme--catppuccin-macchiato .tags.has-addons .content kbd:not(:last-child),html.theme--catppuccin-macchiato .content .tags.has-addons kbd:not(:last-child),html.theme--catppuccin-macchiato .tags.has-addons .docstring>section>a.docs-sourcelink:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}html.theme--catppuccin-macchiato .tag:not(body),html.theme--catppuccin-macchiato .content kbd:not(body),html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink:not(body){align-items:center;background-color:#1e2030;border-radius:.4em;color:#cad3f5;display:inline-flex;font-size:.75rem;height:2em;justify-content:center;line-height:1.5;padding-left:0.75em;padding-right:0.75em;white-space:nowrap}html.theme--catppuccin-macchiato .tag:not(body) .delete,html.theme--catppuccin-macchiato .content kbd:not(body) .delete,html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink:not(body) .delete{margin-left:.25rem;margin-right:-.375rem}html.theme--catppuccin-macchiato .tag.is-white:not(body),html.theme--catppuccin-macchiato .content kbd.is-white:not(body),html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink.is-white:not(body){background-color:#fff;color:#0a0a0a}html.theme--catppuccin-macchiato .tag.is-black:not(body),html.theme--catppuccin-macchiato .content kbd.is-black:not(body),html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink.is-black:not(body){background-color:#0a0a0a;color:#fff}html.theme--catppuccin-macchiato .tag.is-light:not(body),html.theme--catppuccin-macchiato .content kbd.is-light:not(body),html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink.is-light:not(body){background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .tag.is-dark:not(body),html.theme--catppuccin-macchiato .content kbd:not(body),html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink.is-dark:not(body),html.theme--catppuccin-macchiato .content .docstring>section>kbd:not(body){background-color:#363a4f;color:#fff}html.theme--catppuccin-macchiato .tag.is-primary:not(body),html.theme--catppuccin-macchiato .content kbd.is-primary:not(body),html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink:not(body){background-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .tag.is-primary.is-light:not(body),html.theme--catppuccin-macchiato .content kbd.is-primary.is-light:not(body),html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink.is-light:not(body){background-color:#ecf2fd;color:#0e3b95}html.theme--catppuccin-macchiato .tag.is-link:not(body),html.theme--catppuccin-macchiato .content kbd.is-link:not(body),html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink.is-link:not(body){background-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .tag.is-link.is-light:not(body),html.theme--catppuccin-macchiato .content kbd.is-link.is-light:not(body),html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink.is-link.is-light:not(body){background-color:#ecf2fd;color:#0e3b95}html.theme--catppuccin-macchiato .tag.is-info:not(body),html.theme--catppuccin-macchiato .content kbd.is-info:not(body),html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink.is-info:not(body){background-color:#8bd5ca;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .tag.is-info.is-light:not(body),html.theme--catppuccin-macchiato .content kbd.is-info.is-light:not(body),html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink.is-info.is-light:not(body){background-color:#f0faf8;color:#276d62}html.theme--catppuccin-macchiato .tag.is-success:not(body),html.theme--catppuccin-macchiato .content kbd.is-success:not(body),html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink.is-success:not(body){background-color:#a6da95;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .tag.is-success.is-light:not(body),html.theme--catppuccin-macchiato .content kbd.is-success.is-light:not(body),html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink.is-success.is-light:not(body){background-color:#f2faf0;color:#386e26}html.theme--catppuccin-macchiato .tag.is-warning:not(body),html.theme--catppuccin-macchiato .content kbd.is-warning:not(body),html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink.is-warning:not(body){background-color:#eed49f;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .tag.is-warning.is-light:not(body),html.theme--catppuccin-macchiato .content kbd.is-warning.is-light:not(body),html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink.is-warning.is-light:not(body){background-color:#fcf7ee;color:#7e5c16}html.theme--catppuccin-macchiato .tag.is-danger:not(body),html.theme--catppuccin-macchiato .content kbd.is-danger:not(body),html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink.is-danger:not(body){background-color:#ed8796;color:#fff}html.theme--catppuccin-macchiato .tag.is-danger.is-light:not(body),html.theme--catppuccin-macchiato .content kbd.is-danger.is-light:not(body),html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink.is-danger.is-light:not(body){background-color:#fcedef;color:#971729}html.theme--catppuccin-macchiato .tag.is-normal:not(body),html.theme--catppuccin-macchiato .content kbd.is-normal:not(body),html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink.is-normal:not(body){font-size:.75rem}html.theme--catppuccin-macchiato .tag.is-medium:not(body),html.theme--catppuccin-macchiato .content kbd.is-medium:not(body),html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink.is-medium:not(body){font-size:1rem}html.theme--catppuccin-macchiato .tag.is-large:not(body),html.theme--catppuccin-macchiato .content kbd.is-large:not(body),html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink.is-large:not(body){font-size:1.25rem}html.theme--catppuccin-macchiato .tag:not(body) .icon:first-child:not(:last-child),html.theme--catppuccin-macchiato .content kbd:not(body) .icon:first-child:not(:last-child),html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink:not(body) .icon:first-child:not(:last-child){margin-left:-.375em;margin-right:.1875em}html.theme--catppuccin-macchiato .tag:not(body) .icon:last-child:not(:first-child),html.theme--catppuccin-macchiato .content kbd:not(body) .icon:last-child:not(:first-child),html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink:not(body) .icon:last-child:not(:first-child){margin-left:.1875em;margin-right:-.375em}html.theme--catppuccin-macchiato .tag:not(body) .icon:first-child:last-child,html.theme--catppuccin-macchiato .content kbd:not(body) .icon:first-child:last-child,html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink:not(body) .icon:first-child:last-child{margin-left:-.375em;margin-right:-.375em}html.theme--catppuccin-macchiato .tag.is-delete:not(body),html.theme--catppuccin-macchiato .content kbd.is-delete:not(body),html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink.is-delete:not(body){margin-left:1px;padding:0;position:relative;width:2em}html.theme--catppuccin-macchiato .tag.is-delete:not(body)::before,html.theme--catppuccin-macchiato .content kbd.is-delete:not(body)::before,html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink.is-delete:not(body)::before,html.theme--catppuccin-macchiato .tag.is-delete:not(body)::after,html.theme--catppuccin-macchiato .content kbd.is-delete:not(body)::after,html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink.is-delete:not(body)::after{background-color:currentColor;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}html.theme--catppuccin-macchiato .tag.is-delete:not(body)::before,html.theme--catppuccin-macchiato .content kbd.is-delete:not(body)::before,html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink.is-delete:not(body)::before{height:1px;width:50%}html.theme--catppuccin-macchiato .tag.is-delete:not(body)::after,html.theme--catppuccin-macchiato .content kbd.is-delete:not(body)::after,html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink.is-delete:not(body)::after{height:50%;width:1px}html.theme--catppuccin-macchiato .tag.is-delete:not(body):hover,html.theme--catppuccin-macchiato .content kbd.is-delete:not(body):hover,html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink.is-delete:not(body):hover,html.theme--catppuccin-macchiato .tag.is-delete:not(body):focus,html.theme--catppuccin-macchiato .content kbd.is-delete:not(body):focus,html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink.is-delete:not(body):focus{background-color:#141620}html.theme--catppuccin-macchiato .tag.is-delete:not(body):active,html.theme--catppuccin-macchiato .content kbd.is-delete:not(body):active,html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink.is-delete:not(body):active{background-color:#0a0b11}html.theme--catppuccin-macchiato .tag.is-rounded:not(body),html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input:not(body),html.theme--catppuccin-macchiato .content kbd.is-rounded:not(body),html.theme--catppuccin-macchiato #documenter .docs-sidebar .content form.docs-search>input:not(body),html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink.is-rounded:not(body){border-radius:9999px}html.theme--catppuccin-macchiato a.tag:hover,html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink:hover{text-decoration:underline}html.theme--catppuccin-macchiato .title,html.theme--catppuccin-macchiato .subtitle{word-break:break-word}html.theme--catppuccin-macchiato .title em,html.theme--catppuccin-macchiato .title span,html.theme--catppuccin-macchiato .subtitle em,html.theme--catppuccin-macchiato .subtitle span{font-weight:inherit}html.theme--catppuccin-macchiato .title sub,html.theme--catppuccin-macchiato .subtitle sub{font-size:.75em}html.theme--catppuccin-macchiato .title sup,html.theme--catppuccin-macchiato .subtitle sup{font-size:.75em}html.theme--catppuccin-macchiato .title .tag,html.theme--catppuccin-macchiato .title .content kbd,html.theme--catppuccin-macchiato .content .title kbd,html.theme--catppuccin-macchiato .title .docstring>section>a.docs-sourcelink,html.theme--catppuccin-macchiato .subtitle .tag,html.theme--catppuccin-macchiato .subtitle .content kbd,html.theme--catppuccin-macchiato .content .subtitle kbd,html.theme--catppuccin-macchiato .subtitle .docstring>section>a.docs-sourcelink{vertical-align:middle}html.theme--catppuccin-macchiato .title{color:#fff;font-size:2rem;font-weight:500;line-height:1.125}html.theme--catppuccin-macchiato .title strong{color:inherit;font-weight:inherit}html.theme--catppuccin-macchiato .title:not(.is-spaced)+.subtitle{margin-top:-1.25rem}html.theme--catppuccin-macchiato .title.is-1{font-size:3rem}html.theme--catppuccin-macchiato .title.is-2{font-size:2.5rem}html.theme--catppuccin-macchiato .title.is-3{font-size:2rem}html.theme--catppuccin-macchiato .title.is-4{font-size:1.5rem}html.theme--catppuccin-macchiato .title.is-5{font-size:1.25rem}html.theme--catppuccin-macchiato .title.is-6{font-size:1rem}html.theme--catppuccin-macchiato .title.is-7{font-size:.75rem}html.theme--catppuccin-macchiato .subtitle{color:#6e738d;font-size:1.25rem;font-weight:400;line-height:1.25}html.theme--catppuccin-macchiato .subtitle strong{color:#6e738d;font-weight:600}html.theme--catppuccin-macchiato .subtitle:not(.is-spaced)+.title{margin-top:-1.25rem}html.theme--catppuccin-macchiato .subtitle.is-1{font-size:3rem}html.theme--catppuccin-macchiato .subtitle.is-2{font-size:2.5rem}html.theme--catppuccin-macchiato .subtitle.is-3{font-size:2rem}html.theme--catppuccin-macchiato .subtitle.is-4{font-size:1.5rem}html.theme--catppuccin-macchiato .subtitle.is-5{font-size:1.25rem}html.theme--catppuccin-macchiato .subtitle.is-6{font-size:1rem}html.theme--catppuccin-macchiato .subtitle.is-7{font-size:.75rem}html.theme--catppuccin-macchiato .heading{display:block;font-size:11px;letter-spacing:1px;margin-bottom:5px;text-transform:uppercase}html.theme--catppuccin-macchiato .number{align-items:center;background-color:#1e2030;border-radius:9999px;display:inline-flex;font-size:1.25rem;height:2em;justify-content:center;margin-right:1.5rem;min-width:2.5em;padding:0.25rem 0.5rem;text-align:center;vertical-align:top}html.theme--catppuccin-macchiato .select select,html.theme--catppuccin-macchiato .textarea,html.theme--catppuccin-macchiato .input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input{background-color:#24273a;border-color:#5b6078;border-radius:.4em;color:#8087a2}html.theme--catppuccin-macchiato .select select::-moz-placeholder,html.theme--catppuccin-macchiato .textarea::-moz-placeholder,html.theme--catppuccin-macchiato .input::-moz-placeholder,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input::-moz-placeholder{color:#868c98}html.theme--catppuccin-macchiato .select select::-webkit-input-placeholder,html.theme--catppuccin-macchiato .textarea::-webkit-input-placeholder,html.theme--catppuccin-macchiato .input::-webkit-input-placeholder,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input::-webkit-input-placeholder{color:#868c98}html.theme--catppuccin-macchiato .select select:-moz-placeholder,html.theme--catppuccin-macchiato .textarea:-moz-placeholder,html.theme--catppuccin-macchiato .input:-moz-placeholder,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input:-moz-placeholder{color:#868c98}html.theme--catppuccin-macchiato .select select:-ms-input-placeholder,html.theme--catppuccin-macchiato .textarea:-ms-input-placeholder,html.theme--catppuccin-macchiato .input:-ms-input-placeholder,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input:-ms-input-placeholder{color:#868c98}html.theme--catppuccin-macchiato .select select:hover,html.theme--catppuccin-macchiato .textarea:hover,html.theme--catppuccin-macchiato .input:hover,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input:hover,html.theme--catppuccin-macchiato .select select.is-hovered,html.theme--catppuccin-macchiato .is-hovered.textarea,html.theme--catppuccin-macchiato .is-hovered.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-hovered{border-color:#6e738d}html.theme--catppuccin-macchiato .select select:focus,html.theme--catppuccin-macchiato .textarea:focus,html.theme--catppuccin-macchiato .input:focus,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input:focus,html.theme--catppuccin-macchiato .select select.is-focused,html.theme--catppuccin-macchiato .is-focused.textarea,html.theme--catppuccin-macchiato .is-focused.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-macchiato .select select:active,html.theme--catppuccin-macchiato .textarea:active,html.theme--catppuccin-macchiato .input:active,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input:active,html.theme--catppuccin-macchiato .select select.is-active,html.theme--catppuccin-macchiato .is-active.textarea,html.theme--catppuccin-macchiato .is-active.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-active{border-color:#8aadf4;box-shadow:0 0 0 0.125em rgba(138,173,244,0.25)}html.theme--catppuccin-macchiato .select select[disabled],html.theme--catppuccin-macchiato .textarea[disabled],html.theme--catppuccin-macchiato .input[disabled],html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .select select,fieldset[disabled] html.theme--catppuccin-macchiato .textarea,fieldset[disabled] html.theme--catppuccin-macchiato .input,fieldset[disabled] html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input{background-color:#6e738d;border-color:#1e2030;box-shadow:none;color:#f5f7fd}html.theme--catppuccin-macchiato .select select[disabled]::-moz-placeholder,html.theme--catppuccin-macchiato .textarea[disabled]::-moz-placeholder,html.theme--catppuccin-macchiato .input[disabled]::-moz-placeholder,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input[disabled]::-moz-placeholder,fieldset[disabled] html.theme--catppuccin-macchiato .select select::-moz-placeholder,fieldset[disabled] html.theme--catppuccin-macchiato .textarea::-moz-placeholder,fieldset[disabled] html.theme--catppuccin-macchiato .input::-moz-placeholder,fieldset[disabled] html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input::-moz-placeholder{color:rgba(245,247,253,0.3)}html.theme--catppuccin-macchiato .select select[disabled]::-webkit-input-placeholder,html.theme--catppuccin-macchiato .textarea[disabled]::-webkit-input-placeholder,html.theme--catppuccin-macchiato .input[disabled]::-webkit-input-placeholder,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input[disabled]::-webkit-input-placeholder,fieldset[disabled] html.theme--catppuccin-macchiato .select select::-webkit-input-placeholder,fieldset[disabled] html.theme--catppuccin-macchiato .textarea::-webkit-input-placeholder,fieldset[disabled] html.theme--catppuccin-macchiato .input::-webkit-input-placeholder,fieldset[disabled] html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input::-webkit-input-placeholder{color:rgba(245,247,253,0.3)}html.theme--catppuccin-macchiato .select select[disabled]:-moz-placeholder,html.theme--catppuccin-macchiato .textarea[disabled]:-moz-placeholder,html.theme--catppuccin-macchiato .input[disabled]:-moz-placeholder,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input[disabled]:-moz-placeholder,fieldset[disabled] html.theme--catppuccin-macchiato .select select:-moz-placeholder,fieldset[disabled] html.theme--catppuccin-macchiato .textarea:-moz-placeholder,fieldset[disabled] html.theme--catppuccin-macchiato .input:-moz-placeholder,fieldset[disabled] html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input:-moz-placeholder{color:rgba(245,247,253,0.3)}html.theme--catppuccin-macchiato .select select[disabled]:-ms-input-placeholder,html.theme--catppuccin-macchiato .textarea[disabled]:-ms-input-placeholder,html.theme--catppuccin-macchiato .input[disabled]:-ms-input-placeholder,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input[disabled]:-ms-input-placeholder,fieldset[disabled] html.theme--catppuccin-macchiato .select select:-ms-input-placeholder,fieldset[disabled] html.theme--catppuccin-macchiato .textarea:-ms-input-placeholder,fieldset[disabled] html.theme--catppuccin-macchiato .input:-ms-input-placeholder,fieldset[disabled] html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input:-ms-input-placeholder{color:rgba(245,247,253,0.3)}html.theme--catppuccin-macchiato .textarea,html.theme--catppuccin-macchiato .input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input{box-shadow:inset 0 0.0625em 0.125em rgba(10,10,10,0.05);max-width:100%;width:100%}html.theme--catppuccin-macchiato .textarea[readonly],html.theme--catppuccin-macchiato .input[readonly],html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input[readonly]{box-shadow:none}html.theme--catppuccin-macchiato .is-white.textarea,html.theme--catppuccin-macchiato .is-white.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-white{border-color:#fff}html.theme--catppuccin-macchiato .is-white.textarea:focus,html.theme--catppuccin-macchiato .is-white.input:focus,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-white:focus,html.theme--catppuccin-macchiato .is-white.is-focused.textarea,html.theme--catppuccin-macchiato .is-white.is-focused.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-macchiato .is-white.textarea:active,html.theme--catppuccin-macchiato .is-white.input:active,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-white:active,html.theme--catppuccin-macchiato .is-white.is-active.textarea,html.theme--catppuccin-macchiato .is-white.is-active.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}html.theme--catppuccin-macchiato .is-black.textarea,html.theme--catppuccin-macchiato .is-black.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-black{border-color:#0a0a0a}html.theme--catppuccin-macchiato .is-black.textarea:focus,html.theme--catppuccin-macchiato .is-black.input:focus,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-black:focus,html.theme--catppuccin-macchiato .is-black.is-focused.textarea,html.theme--catppuccin-macchiato .is-black.is-focused.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-macchiato .is-black.textarea:active,html.theme--catppuccin-macchiato .is-black.input:active,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-black:active,html.theme--catppuccin-macchiato .is-black.is-active.textarea,html.theme--catppuccin-macchiato .is-black.is-active.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}html.theme--catppuccin-macchiato .is-light.textarea,html.theme--catppuccin-macchiato .is-light.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-light{border-color:#f5f5f5}html.theme--catppuccin-macchiato .is-light.textarea:focus,html.theme--catppuccin-macchiato .is-light.input:focus,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-light:focus,html.theme--catppuccin-macchiato .is-light.is-focused.textarea,html.theme--catppuccin-macchiato .is-light.is-focused.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-macchiato .is-light.textarea:active,html.theme--catppuccin-macchiato .is-light.input:active,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-light:active,html.theme--catppuccin-macchiato .is-light.is-active.textarea,html.theme--catppuccin-macchiato .is-light.is-active.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(245,245,245,0.25)}html.theme--catppuccin-macchiato .is-dark.textarea,html.theme--catppuccin-macchiato .content kbd.textarea,html.theme--catppuccin-macchiato .is-dark.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-dark,html.theme--catppuccin-macchiato .content kbd.input{border-color:#363a4f}html.theme--catppuccin-macchiato .is-dark.textarea:focus,html.theme--catppuccin-macchiato .content kbd.textarea:focus,html.theme--catppuccin-macchiato .is-dark.input:focus,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-dark:focus,html.theme--catppuccin-macchiato .content kbd.input:focus,html.theme--catppuccin-macchiato .is-dark.is-focused.textarea,html.theme--catppuccin-macchiato .content kbd.is-focused.textarea,html.theme--catppuccin-macchiato .is-dark.is-focused.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-macchiato .content kbd.is-focused.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar .content form.docs-search>input.is-focused,html.theme--catppuccin-macchiato .is-dark.textarea:active,html.theme--catppuccin-macchiato .content kbd.textarea:active,html.theme--catppuccin-macchiato .is-dark.input:active,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-dark:active,html.theme--catppuccin-macchiato .content kbd.input:active,html.theme--catppuccin-macchiato .is-dark.is-active.textarea,html.theme--catppuccin-macchiato .content kbd.is-active.textarea,html.theme--catppuccin-macchiato .is-dark.is-active.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-active,html.theme--catppuccin-macchiato .content kbd.is-active.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar .content form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(54,58,79,0.25)}html.theme--catppuccin-macchiato .is-primary.textarea,html.theme--catppuccin-macchiato .docstring>section>a.textarea.docs-sourcelink,html.theme--catppuccin-macchiato .is-primary.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-primary,html.theme--catppuccin-macchiato .docstring>section>a.input.docs-sourcelink{border-color:#8aadf4}html.theme--catppuccin-macchiato .is-primary.textarea:focus,html.theme--catppuccin-macchiato .docstring>section>a.textarea.docs-sourcelink:focus,html.theme--catppuccin-macchiato .is-primary.input:focus,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-primary:focus,html.theme--catppuccin-macchiato .docstring>section>a.input.docs-sourcelink:focus,html.theme--catppuccin-macchiato .is-primary.is-focused.textarea,html.theme--catppuccin-macchiato .docstring>section>a.is-focused.textarea.docs-sourcelink,html.theme--catppuccin-macchiato .is-primary.is-focused.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-macchiato .docstring>section>a.is-focused.input.docs-sourcelink,html.theme--catppuccin-macchiato .is-primary.textarea:active,html.theme--catppuccin-macchiato .docstring>section>a.textarea.docs-sourcelink:active,html.theme--catppuccin-macchiato .is-primary.input:active,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-primary:active,html.theme--catppuccin-macchiato .docstring>section>a.input.docs-sourcelink:active,html.theme--catppuccin-macchiato .is-primary.is-active.textarea,html.theme--catppuccin-macchiato .docstring>section>a.is-active.textarea.docs-sourcelink,html.theme--catppuccin-macchiato .is-primary.is-active.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-active,html.theme--catppuccin-macchiato .docstring>section>a.is-active.input.docs-sourcelink{box-shadow:0 0 0 0.125em rgba(138,173,244,0.25)}html.theme--catppuccin-macchiato .is-link.textarea,html.theme--catppuccin-macchiato .is-link.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-link{border-color:#8aadf4}html.theme--catppuccin-macchiato .is-link.textarea:focus,html.theme--catppuccin-macchiato .is-link.input:focus,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-link:focus,html.theme--catppuccin-macchiato .is-link.is-focused.textarea,html.theme--catppuccin-macchiato .is-link.is-focused.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-macchiato .is-link.textarea:active,html.theme--catppuccin-macchiato .is-link.input:active,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-link:active,html.theme--catppuccin-macchiato .is-link.is-active.textarea,html.theme--catppuccin-macchiato .is-link.is-active.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(138,173,244,0.25)}html.theme--catppuccin-macchiato .is-info.textarea,html.theme--catppuccin-macchiato .is-info.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-info{border-color:#8bd5ca}html.theme--catppuccin-macchiato .is-info.textarea:focus,html.theme--catppuccin-macchiato .is-info.input:focus,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-info:focus,html.theme--catppuccin-macchiato .is-info.is-focused.textarea,html.theme--catppuccin-macchiato .is-info.is-focused.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-macchiato .is-info.textarea:active,html.theme--catppuccin-macchiato .is-info.input:active,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-info:active,html.theme--catppuccin-macchiato .is-info.is-active.textarea,html.theme--catppuccin-macchiato .is-info.is-active.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(139,213,202,0.25)}html.theme--catppuccin-macchiato .is-success.textarea,html.theme--catppuccin-macchiato .is-success.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-success{border-color:#a6da95}html.theme--catppuccin-macchiato .is-success.textarea:focus,html.theme--catppuccin-macchiato .is-success.input:focus,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-success:focus,html.theme--catppuccin-macchiato .is-success.is-focused.textarea,html.theme--catppuccin-macchiato .is-success.is-focused.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-macchiato .is-success.textarea:active,html.theme--catppuccin-macchiato .is-success.input:active,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-success:active,html.theme--catppuccin-macchiato .is-success.is-active.textarea,html.theme--catppuccin-macchiato .is-success.is-active.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(166,218,149,0.25)}html.theme--catppuccin-macchiato .is-warning.textarea,html.theme--catppuccin-macchiato .is-warning.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-warning{border-color:#eed49f}html.theme--catppuccin-macchiato .is-warning.textarea:focus,html.theme--catppuccin-macchiato .is-warning.input:focus,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-warning:focus,html.theme--catppuccin-macchiato .is-warning.is-focused.textarea,html.theme--catppuccin-macchiato .is-warning.is-focused.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-macchiato .is-warning.textarea:active,html.theme--catppuccin-macchiato .is-warning.input:active,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-warning:active,html.theme--catppuccin-macchiato .is-warning.is-active.textarea,html.theme--catppuccin-macchiato .is-warning.is-active.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(238,212,159,0.25)}html.theme--catppuccin-macchiato .is-danger.textarea,html.theme--catppuccin-macchiato .is-danger.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-danger{border-color:#ed8796}html.theme--catppuccin-macchiato .is-danger.textarea:focus,html.theme--catppuccin-macchiato .is-danger.input:focus,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-danger:focus,html.theme--catppuccin-macchiato .is-danger.is-focused.textarea,html.theme--catppuccin-macchiato .is-danger.is-focused.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-macchiato .is-danger.textarea:active,html.theme--catppuccin-macchiato .is-danger.input:active,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-danger:active,html.theme--catppuccin-macchiato .is-danger.is-active.textarea,html.theme--catppuccin-macchiato .is-danger.is-active.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(237,135,150,0.25)}html.theme--catppuccin-macchiato .is-small.textarea,html.theme--catppuccin-macchiato .is-small.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input{border-radius:3px;font-size:.75rem}html.theme--catppuccin-macchiato .is-medium.textarea,html.theme--catppuccin-macchiato .is-medium.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-medium{font-size:1.25rem}html.theme--catppuccin-macchiato .is-large.textarea,html.theme--catppuccin-macchiato .is-large.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-large{font-size:1.5rem}html.theme--catppuccin-macchiato .is-fullwidth.textarea,html.theme--catppuccin-macchiato .is-fullwidth.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-fullwidth{display:block;width:100%}html.theme--catppuccin-macchiato .is-inline.textarea,html.theme--catppuccin-macchiato .is-inline.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-inline{display:inline;width:auto}html.theme--catppuccin-macchiato .input.is-rounded,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input{border-radius:9999px;padding-left:calc(calc(0.75em - 1px) + 0.375em);padding-right:calc(calc(0.75em - 1px) + 0.375em)}html.theme--catppuccin-macchiato .input.is-static,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-static{background-color:transparent;border-color:transparent;box-shadow:none;padding-left:0;padding-right:0}html.theme--catppuccin-macchiato .textarea{display:block;max-width:100%;min-width:100%;padding:calc(0.75em - 1px);resize:vertical}html.theme--catppuccin-macchiato .textarea:not([rows]){max-height:40em;min-height:8em}html.theme--catppuccin-macchiato .textarea[rows]{height:initial}html.theme--catppuccin-macchiato .textarea.has-fixed-size{resize:none}html.theme--catppuccin-macchiato .radio,html.theme--catppuccin-macchiato .checkbox{cursor:pointer;display:inline-block;line-height:1.25;position:relative}html.theme--catppuccin-macchiato .radio input,html.theme--catppuccin-macchiato .checkbox input{cursor:pointer}html.theme--catppuccin-macchiato .radio:hover,html.theme--catppuccin-macchiato .checkbox:hover{color:#91d7e3}html.theme--catppuccin-macchiato .radio[disabled],html.theme--catppuccin-macchiato .checkbox[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .radio,fieldset[disabled] html.theme--catppuccin-macchiato .checkbox,html.theme--catppuccin-macchiato .radio input[disabled],html.theme--catppuccin-macchiato .checkbox input[disabled]{color:#f5f7fd;cursor:not-allowed}html.theme--catppuccin-macchiato .radio+.radio{margin-left:.5em}html.theme--catppuccin-macchiato .select{display:inline-block;max-width:100%;position:relative;vertical-align:top}html.theme--catppuccin-macchiato .select:not(.is-multiple){height:2.5em}html.theme--catppuccin-macchiato .select:not(.is-multiple):not(.is-loading)::after{border-color:#8aadf4;right:1.125em;z-index:4}html.theme--catppuccin-macchiato .select.is-rounded select,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.select select{border-radius:9999px;padding-left:1em}html.theme--catppuccin-macchiato .select select{cursor:pointer;display:block;font-size:1em;max-width:100%;outline:none}html.theme--catppuccin-macchiato .select select::-ms-expand{display:none}html.theme--catppuccin-macchiato .select select[disabled]:hover,fieldset[disabled] html.theme--catppuccin-macchiato .select select:hover{border-color:#1e2030}html.theme--catppuccin-macchiato .select select:not([multiple]){padding-right:2.5em}html.theme--catppuccin-macchiato .select select[multiple]{height:auto;padding:0}html.theme--catppuccin-macchiato .select select[multiple] option{padding:0.5em 1em}html.theme--catppuccin-macchiato .select:not(.is-multiple):not(.is-loading):hover::after{border-color:#91d7e3}html.theme--catppuccin-macchiato .select.is-white:not(:hover)::after{border-color:#fff}html.theme--catppuccin-macchiato .select.is-white select{border-color:#fff}html.theme--catppuccin-macchiato .select.is-white select:hover,html.theme--catppuccin-macchiato .select.is-white select.is-hovered{border-color:#f2f2f2}html.theme--catppuccin-macchiato .select.is-white select:focus,html.theme--catppuccin-macchiato .select.is-white select.is-focused,html.theme--catppuccin-macchiato .select.is-white select:active,html.theme--catppuccin-macchiato .select.is-white select.is-active{box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}html.theme--catppuccin-macchiato .select.is-black:not(:hover)::after{border-color:#0a0a0a}html.theme--catppuccin-macchiato .select.is-black select{border-color:#0a0a0a}html.theme--catppuccin-macchiato .select.is-black select:hover,html.theme--catppuccin-macchiato .select.is-black select.is-hovered{border-color:#000}html.theme--catppuccin-macchiato .select.is-black select:focus,html.theme--catppuccin-macchiato .select.is-black select.is-focused,html.theme--catppuccin-macchiato .select.is-black select:active,html.theme--catppuccin-macchiato .select.is-black select.is-active{box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}html.theme--catppuccin-macchiato .select.is-light:not(:hover)::after{border-color:#f5f5f5}html.theme--catppuccin-macchiato .select.is-light select{border-color:#f5f5f5}html.theme--catppuccin-macchiato .select.is-light select:hover,html.theme--catppuccin-macchiato .select.is-light select.is-hovered{border-color:#e8e8e8}html.theme--catppuccin-macchiato .select.is-light select:focus,html.theme--catppuccin-macchiato .select.is-light select.is-focused,html.theme--catppuccin-macchiato .select.is-light select:active,html.theme--catppuccin-macchiato .select.is-light select.is-active{box-shadow:0 0 0 0.125em rgba(245,245,245,0.25)}html.theme--catppuccin-macchiato .select.is-dark:not(:hover)::after,html.theme--catppuccin-macchiato .content kbd.select:not(:hover)::after{border-color:#363a4f}html.theme--catppuccin-macchiato .select.is-dark select,html.theme--catppuccin-macchiato .content kbd.select select{border-color:#363a4f}html.theme--catppuccin-macchiato .select.is-dark select:hover,html.theme--catppuccin-macchiato .content kbd.select select:hover,html.theme--catppuccin-macchiato .select.is-dark select.is-hovered,html.theme--catppuccin-macchiato .content kbd.select select.is-hovered{border-color:#2c2f40}html.theme--catppuccin-macchiato .select.is-dark select:focus,html.theme--catppuccin-macchiato .content kbd.select select:focus,html.theme--catppuccin-macchiato .select.is-dark select.is-focused,html.theme--catppuccin-macchiato .content kbd.select select.is-focused,html.theme--catppuccin-macchiato .select.is-dark select:active,html.theme--catppuccin-macchiato .content kbd.select select:active,html.theme--catppuccin-macchiato .select.is-dark select.is-active,html.theme--catppuccin-macchiato .content kbd.select select.is-active{box-shadow:0 0 0 0.125em rgba(54,58,79,0.25)}html.theme--catppuccin-macchiato .select.is-primary:not(:hover)::after,html.theme--catppuccin-macchiato .docstring>section>a.select.docs-sourcelink:not(:hover)::after{border-color:#8aadf4}html.theme--catppuccin-macchiato .select.is-primary select,html.theme--catppuccin-macchiato .docstring>section>a.select.docs-sourcelink select{border-color:#8aadf4}html.theme--catppuccin-macchiato .select.is-primary select:hover,html.theme--catppuccin-macchiato .docstring>section>a.select.docs-sourcelink select:hover,html.theme--catppuccin-macchiato .select.is-primary select.is-hovered,html.theme--catppuccin-macchiato .docstring>section>a.select.docs-sourcelink select.is-hovered{border-color:#739df2}html.theme--catppuccin-macchiato .select.is-primary select:focus,html.theme--catppuccin-macchiato .docstring>section>a.select.docs-sourcelink select:focus,html.theme--catppuccin-macchiato .select.is-primary select.is-focused,html.theme--catppuccin-macchiato .docstring>section>a.select.docs-sourcelink select.is-focused,html.theme--catppuccin-macchiato .select.is-primary select:active,html.theme--catppuccin-macchiato .docstring>section>a.select.docs-sourcelink select:active,html.theme--catppuccin-macchiato .select.is-primary select.is-active,html.theme--catppuccin-macchiato .docstring>section>a.select.docs-sourcelink select.is-active{box-shadow:0 0 0 0.125em rgba(138,173,244,0.25)}html.theme--catppuccin-macchiato .select.is-link:not(:hover)::after{border-color:#8aadf4}html.theme--catppuccin-macchiato .select.is-link select{border-color:#8aadf4}html.theme--catppuccin-macchiato .select.is-link select:hover,html.theme--catppuccin-macchiato .select.is-link select.is-hovered{border-color:#739df2}html.theme--catppuccin-macchiato .select.is-link select:focus,html.theme--catppuccin-macchiato .select.is-link select.is-focused,html.theme--catppuccin-macchiato .select.is-link select:active,html.theme--catppuccin-macchiato .select.is-link select.is-active{box-shadow:0 0 0 0.125em rgba(138,173,244,0.25)}html.theme--catppuccin-macchiato .select.is-info:not(:hover)::after{border-color:#8bd5ca}html.theme--catppuccin-macchiato .select.is-info select{border-color:#8bd5ca}html.theme--catppuccin-macchiato .select.is-info select:hover,html.theme--catppuccin-macchiato .select.is-info select.is-hovered{border-color:#78cec1}html.theme--catppuccin-macchiato .select.is-info select:focus,html.theme--catppuccin-macchiato .select.is-info select.is-focused,html.theme--catppuccin-macchiato .select.is-info select:active,html.theme--catppuccin-macchiato .select.is-info select.is-active{box-shadow:0 0 0 0.125em rgba(139,213,202,0.25)}html.theme--catppuccin-macchiato .select.is-success:not(:hover)::after{border-color:#a6da95}html.theme--catppuccin-macchiato .select.is-success select{border-color:#a6da95}html.theme--catppuccin-macchiato .select.is-success select:hover,html.theme--catppuccin-macchiato .select.is-success select.is-hovered{border-color:#96d382}html.theme--catppuccin-macchiato .select.is-success select:focus,html.theme--catppuccin-macchiato .select.is-success select.is-focused,html.theme--catppuccin-macchiato .select.is-success select:active,html.theme--catppuccin-macchiato .select.is-success select.is-active{box-shadow:0 0 0 0.125em rgba(166,218,149,0.25)}html.theme--catppuccin-macchiato .select.is-warning:not(:hover)::after{border-color:#eed49f}html.theme--catppuccin-macchiato .select.is-warning select{border-color:#eed49f}html.theme--catppuccin-macchiato .select.is-warning select:hover,html.theme--catppuccin-macchiato .select.is-warning select.is-hovered{border-color:#eaca89}html.theme--catppuccin-macchiato .select.is-warning select:focus,html.theme--catppuccin-macchiato .select.is-warning select.is-focused,html.theme--catppuccin-macchiato .select.is-warning select:active,html.theme--catppuccin-macchiato .select.is-warning select.is-active{box-shadow:0 0 0 0.125em rgba(238,212,159,0.25)}html.theme--catppuccin-macchiato .select.is-danger:not(:hover)::after{border-color:#ed8796}html.theme--catppuccin-macchiato .select.is-danger select{border-color:#ed8796}html.theme--catppuccin-macchiato .select.is-danger select:hover,html.theme--catppuccin-macchiato .select.is-danger select.is-hovered{border-color:#ea7183}html.theme--catppuccin-macchiato .select.is-danger select:focus,html.theme--catppuccin-macchiato .select.is-danger select.is-focused,html.theme--catppuccin-macchiato .select.is-danger select:active,html.theme--catppuccin-macchiato .select.is-danger select.is-active{box-shadow:0 0 0 0.125em rgba(237,135,150,0.25)}html.theme--catppuccin-macchiato .select.is-small,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.select{border-radius:3px;font-size:.75rem}html.theme--catppuccin-macchiato .select.is-medium{font-size:1.25rem}html.theme--catppuccin-macchiato .select.is-large{font-size:1.5rem}html.theme--catppuccin-macchiato .select.is-disabled::after{border-color:#f5f7fd !important;opacity:0.5}html.theme--catppuccin-macchiato .select.is-fullwidth{width:100%}html.theme--catppuccin-macchiato .select.is-fullwidth select{width:100%}html.theme--catppuccin-macchiato .select.is-loading::after{margin-top:0;position:absolute;right:.625em;top:0.625em;transform:none}html.theme--catppuccin-macchiato .select.is-loading.is-small:after,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-loading:after{font-size:.75rem}html.theme--catppuccin-macchiato .select.is-loading.is-medium:after{font-size:1.25rem}html.theme--catppuccin-macchiato .select.is-loading.is-large:after{font-size:1.5rem}html.theme--catppuccin-macchiato .file{align-items:stretch;display:flex;justify-content:flex-start;position:relative}html.theme--catppuccin-macchiato .file.is-white .file-cta{background-color:#fff;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-macchiato .file.is-white:hover .file-cta,html.theme--catppuccin-macchiato .file.is-white.is-hovered .file-cta{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-macchiato .file.is-white:focus .file-cta,html.theme--catppuccin-macchiato .file.is-white.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(255,255,255,0.25);color:#0a0a0a}html.theme--catppuccin-macchiato .file.is-white:active .file-cta,html.theme--catppuccin-macchiato .file.is-white.is-active .file-cta{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-macchiato .file.is-black .file-cta{background-color:#0a0a0a;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .file.is-black:hover .file-cta,html.theme--catppuccin-macchiato .file.is-black.is-hovered .file-cta{background-color:#040404;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .file.is-black:focus .file-cta,html.theme--catppuccin-macchiato .file.is-black.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(10,10,10,0.25);color:#fff}html.theme--catppuccin-macchiato .file.is-black:active .file-cta,html.theme--catppuccin-macchiato .file.is-black.is-active .file-cta{background-color:#000;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .file.is-light .file-cta{background-color:#f5f5f5;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .file.is-light:hover .file-cta,html.theme--catppuccin-macchiato .file.is-light.is-hovered .file-cta{background-color:#eee;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .file.is-light:focus .file-cta,html.theme--catppuccin-macchiato .file.is-light.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(245,245,245,0.25);color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .file.is-light:active .file-cta,html.theme--catppuccin-macchiato .file.is-light.is-active .file-cta{background-color:#e8e8e8;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .file.is-dark .file-cta,html.theme--catppuccin-macchiato .content kbd.file .file-cta{background-color:#363a4f;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .file.is-dark:hover .file-cta,html.theme--catppuccin-macchiato .content kbd.file:hover .file-cta,html.theme--catppuccin-macchiato .file.is-dark.is-hovered .file-cta,html.theme--catppuccin-macchiato .content kbd.file.is-hovered .file-cta{background-color:#313447;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .file.is-dark:focus .file-cta,html.theme--catppuccin-macchiato .content kbd.file:focus .file-cta,html.theme--catppuccin-macchiato .file.is-dark.is-focused .file-cta,html.theme--catppuccin-macchiato .content kbd.file.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(54,58,79,0.25);color:#fff}html.theme--catppuccin-macchiato .file.is-dark:active .file-cta,html.theme--catppuccin-macchiato .content kbd.file:active .file-cta,html.theme--catppuccin-macchiato .file.is-dark.is-active .file-cta,html.theme--catppuccin-macchiato .content kbd.file.is-active .file-cta{background-color:#2c2f40;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .file.is-primary .file-cta,html.theme--catppuccin-macchiato .docstring>section>a.file.docs-sourcelink .file-cta{background-color:#8aadf4;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .file.is-primary:hover .file-cta,html.theme--catppuccin-macchiato .docstring>section>a.file.docs-sourcelink:hover .file-cta,html.theme--catppuccin-macchiato .file.is-primary.is-hovered .file-cta,html.theme--catppuccin-macchiato .docstring>section>a.file.is-hovered.docs-sourcelink .file-cta{background-color:#7ea5f3;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .file.is-primary:focus .file-cta,html.theme--catppuccin-macchiato .docstring>section>a.file.docs-sourcelink:focus .file-cta,html.theme--catppuccin-macchiato .file.is-primary.is-focused .file-cta,html.theme--catppuccin-macchiato .docstring>section>a.file.is-focused.docs-sourcelink .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(138,173,244,0.25);color:#fff}html.theme--catppuccin-macchiato .file.is-primary:active .file-cta,html.theme--catppuccin-macchiato .docstring>section>a.file.docs-sourcelink:active .file-cta,html.theme--catppuccin-macchiato .file.is-primary.is-active .file-cta,html.theme--catppuccin-macchiato .docstring>section>a.file.is-active.docs-sourcelink .file-cta{background-color:#739df2;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .file.is-link .file-cta{background-color:#8aadf4;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .file.is-link:hover .file-cta,html.theme--catppuccin-macchiato .file.is-link.is-hovered .file-cta{background-color:#7ea5f3;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .file.is-link:focus .file-cta,html.theme--catppuccin-macchiato .file.is-link.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(138,173,244,0.25);color:#fff}html.theme--catppuccin-macchiato .file.is-link:active .file-cta,html.theme--catppuccin-macchiato .file.is-link.is-active .file-cta{background-color:#739df2;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .file.is-info .file-cta{background-color:#8bd5ca;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .file.is-info:hover .file-cta,html.theme--catppuccin-macchiato .file.is-info.is-hovered .file-cta{background-color:#82d2c6;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .file.is-info:focus .file-cta,html.theme--catppuccin-macchiato .file.is-info.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(139,213,202,0.25);color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .file.is-info:active .file-cta,html.theme--catppuccin-macchiato .file.is-info.is-active .file-cta{background-color:#78cec1;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .file.is-success .file-cta{background-color:#a6da95;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .file.is-success:hover .file-cta,html.theme--catppuccin-macchiato .file.is-success.is-hovered .file-cta{background-color:#9ed78c;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .file.is-success:focus .file-cta,html.theme--catppuccin-macchiato .file.is-success.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(166,218,149,0.25);color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .file.is-success:active .file-cta,html.theme--catppuccin-macchiato .file.is-success.is-active .file-cta{background-color:#96d382;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .file.is-warning .file-cta{background-color:#eed49f;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .file.is-warning:hover .file-cta,html.theme--catppuccin-macchiato .file.is-warning.is-hovered .file-cta{background-color:#eccf94;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .file.is-warning:focus .file-cta,html.theme--catppuccin-macchiato .file.is-warning.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(238,212,159,0.25);color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .file.is-warning:active .file-cta,html.theme--catppuccin-macchiato .file.is-warning.is-active .file-cta{background-color:#eaca89;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .file.is-danger .file-cta{background-color:#ed8796;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .file.is-danger:hover .file-cta,html.theme--catppuccin-macchiato .file.is-danger.is-hovered .file-cta{background-color:#eb7c8c;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .file.is-danger:focus .file-cta,html.theme--catppuccin-macchiato .file.is-danger.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(237,135,150,0.25);color:#fff}html.theme--catppuccin-macchiato .file.is-danger:active .file-cta,html.theme--catppuccin-macchiato .file.is-danger.is-active .file-cta{background-color:#ea7183;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .file.is-small,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.file{font-size:.75rem}html.theme--catppuccin-macchiato .file.is-normal{font-size:1rem}html.theme--catppuccin-macchiato .file.is-medium{font-size:1.25rem}html.theme--catppuccin-macchiato .file.is-medium .file-icon .fa{font-size:21px}html.theme--catppuccin-macchiato .file.is-large{font-size:1.5rem}html.theme--catppuccin-macchiato .file.is-large .file-icon .fa{font-size:28px}html.theme--catppuccin-macchiato .file.has-name .file-cta{border-bottom-right-radius:0;border-top-right-radius:0}html.theme--catppuccin-macchiato .file.has-name .file-name{border-bottom-left-radius:0;border-top-left-radius:0}html.theme--catppuccin-macchiato .file.has-name.is-empty .file-cta{border-radius:.4em}html.theme--catppuccin-macchiato .file.has-name.is-empty .file-name{display:none}html.theme--catppuccin-macchiato .file.is-boxed .file-label{flex-direction:column}html.theme--catppuccin-macchiato .file.is-boxed .file-cta{flex-direction:column;height:auto;padding:1em 3em}html.theme--catppuccin-macchiato .file.is-boxed .file-name{border-width:0 1px 1px}html.theme--catppuccin-macchiato .file.is-boxed .file-icon{height:1.5em;width:1.5em}html.theme--catppuccin-macchiato .file.is-boxed .file-icon .fa{font-size:21px}html.theme--catppuccin-macchiato .file.is-boxed.is-small .file-icon .fa,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-boxed .file-icon .fa{font-size:14px}html.theme--catppuccin-macchiato .file.is-boxed.is-medium .file-icon .fa{font-size:28px}html.theme--catppuccin-macchiato .file.is-boxed.is-large .file-icon .fa{font-size:35px}html.theme--catppuccin-macchiato .file.is-boxed.has-name .file-cta{border-radius:.4em .4em 0 0}html.theme--catppuccin-macchiato .file.is-boxed.has-name .file-name{border-radius:0 0 .4em .4em;border-width:0 1px 1px}html.theme--catppuccin-macchiato .file.is-centered{justify-content:center}html.theme--catppuccin-macchiato .file.is-fullwidth .file-label{width:100%}html.theme--catppuccin-macchiato .file.is-fullwidth .file-name{flex-grow:1;max-width:none}html.theme--catppuccin-macchiato .file.is-right{justify-content:flex-end}html.theme--catppuccin-macchiato .file.is-right .file-cta{border-radius:0 .4em .4em 0}html.theme--catppuccin-macchiato .file.is-right .file-name{border-radius:.4em 0 0 .4em;border-width:1px 0 1px 1px;order:-1}html.theme--catppuccin-macchiato .file-label{align-items:stretch;display:flex;cursor:pointer;justify-content:flex-start;overflow:hidden;position:relative}html.theme--catppuccin-macchiato .file-label:hover .file-cta{background-color:#313447;color:#b5c1f1}html.theme--catppuccin-macchiato .file-label:hover .file-name{border-color:#565a71}html.theme--catppuccin-macchiato .file-label:active .file-cta{background-color:#2c2f40;color:#b5c1f1}html.theme--catppuccin-macchiato .file-label:active .file-name{border-color:#505469}html.theme--catppuccin-macchiato .file-input{height:100%;left:0;opacity:0;outline:none;position:absolute;top:0;width:100%}html.theme--catppuccin-macchiato .file-cta,html.theme--catppuccin-macchiato .file-name{border-color:#5b6078;border-radius:.4em;font-size:1em;padding-left:1em;padding-right:1em;white-space:nowrap}html.theme--catppuccin-macchiato .file-cta{background-color:#363a4f;color:#cad3f5}html.theme--catppuccin-macchiato .file-name{border-color:#5b6078;border-style:solid;border-width:1px 1px 1px 0;display:block;max-width:16em;overflow:hidden;text-align:inherit;text-overflow:ellipsis}html.theme--catppuccin-macchiato .file-icon{align-items:center;display:flex;height:1em;justify-content:center;margin-right:.5em;width:1em}html.theme--catppuccin-macchiato .file-icon .fa{font-size:14px}html.theme--catppuccin-macchiato .label{color:#b5c1f1;display:block;font-size:1rem;font-weight:700}html.theme--catppuccin-macchiato .label:not(:last-child){margin-bottom:0.5em}html.theme--catppuccin-macchiato .label.is-small,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.label{font-size:.75rem}html.theme--catppuccin-macchiato .label.is-medium{font-size:1.25rem}html.theme--catppuccin-macchiato .label.is-large{font-size:1.5rem}html.theme--catppuccin-macchiato .help{display:block;font-size:.75rem;margin-top:0.25rem}html.theme--catppuccin-macchiato .help.is-white{color:#fff}html.theme--catppuccin-macchiato .help.is-black{color:#0a0a0a}html.theme--catppuccin-macchiato .help.is-light{color:#f5f5f5}html.theme--catppuccin-macchiato .help.is-dark,html.theme--catppuccin-macchiato .content kbd.help{color:#363a4f}html.theme--catppuccin-macchiato .help.is-primary,html.theme--catppuccin-macchiato .docstring>section>a.help.docs-sourcelink{color:#8aadf4}html.theme--catppuccin-macchiato .help.is-link{color:#8aadf4}html.theme--catppuccin-macchiato .help.is-info{color:#8bd5ca}html.theme--catppuccin-macchiato .help.is-success{color:#a6da95}html.theme--catppuccin-macchiato .help.is-warning{color:#eed49f}html.theme--catppuccin-macchiato .help.is-danger{color:#ed8796}html.theme--catppuccin-macchiato .field:not(:last-child){margin-bottom:0.75rem}html.theme--catppuccin-macchiato .field.has-addons{display:flex;justify-content:flex-start}html.theme--catppuccin-macchiato .field.has-addons .control:not(:last-child){margin-right:-1px}html.theme--catppuccin-macchiato .field.has-addons .control:not(:first-child):not(:last-child) .button,html.theme--catppuccin-macchiato .field.has-addons .control:not(:first-child):not(:last-child) .input,html.theme--catppuccin-macchiato .field.has-addons .control:not(:first-child):not(:last-child) #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-macchiato #documenter .docs-sidebar .field.has-addons .control:not(:first-child):not(:last-child) form.docs-search>input,html.theme--catppuccin-macchiato .field.has-addons .control:not(:first-child):not(:last-child) .select select{border-radius:0}html.theme--catppuccin-macchiato .field.has-addons .control:first-child:not(:only-child) .button,html.theme--catppuccin-macchiato .field.has-addons .control:first-child:not(:only-child) .input,html.theme--catppuccin-macchiato .field.has-addons .control:first-child:not(:only-child) #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-macchiato #documenter .docs-sidebar .field.has-addons .control:first-child:not(:only-child) form.docs-search>input,html.theme--catppuccin-macchiato .field.has-addons .control:first-child:not(:only-child) .select select{border-bottom-right-radius:0;border-top-right-radius:0}html.theme--catppuccin-macchiato .field.has-addons .control:last-child:not(:only-child) .button,html.theme--catppuccin-macchiato .field.has-addons .control:last-child:not(:only-child) .input,html.theme--catppuccin-macchiato .field.has-addons .control:last-child:not(:only-child) #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-macchiato #documenter .docs-sidebar .field.has-addons .control:last-child:not(:only-child) form.docs-search>input,html.theme--catppuccin-macchiato .field.has-addons .control:last-child:not(:only-child) .select select{border-bottom-left-radius:0;border-top-left-radius:0}html.theme--catppuccin-macchiato .field.has-addons .control .button:not([disabled]):hover,html.theme--catppuccin-macchiato .field.has-addons .control .button.is-hovered:not([disabled]),html.theme--catppuccin-macchiato .field.has-addons .control .input:not([disabled]):hover,html.theme--catppuccin-macchiato .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):hover,html.theme--catppuccin-macchiato #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):hover,html.theme--catppuccin-macchiato .field.has-addons .control .input.is-hovered:not([disabled]),html.theme--catppuccin-macchiato .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-hovered:not([disabled]),html.theme--catppuccin-macchiato #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-hovered:not([disabled]),html.theme--catppuccin-macchiato .field.has-addons .control .select select:not([disabled]):hover,html.theme--catppuccin-macchiato .field.has-addons .control .select select.is-hovered:not([disabled]){z-index:2}html.theme--catppuccin-macchiato .field.has-addons .control .button:not([disabled]):focus,html.theme--catppuccin-macchiato .field.has-addons .control .button.is-focused:not([disabled]),html.theme--catppuccin-macchiato .field.has-addons .control .button:not([disabled]):active,html.theme--catppuccin-macchiato .field.has-addons .control .button.is-active:not([disabled]),html.theme--catppuccin-macchiato .field.has-addons .control .input:not([disabled]):focus,html.theme--catppuccin-macchiato .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):focus,html.theme--catppuccin-macchiato #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):focus,html.theme--catppuccin-macchiato .field.has-addons .control .input.is-focused:not([disabled]),html.theme--catppuccin-macchiato .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-focused:not([disabled]),html.theme--catppuccin-macchiato #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-focused:not([disabled]),html.theme--catppuccin-macchiato .field.has-addons .control .input:not([disabled]):active,html.theme--catppuccin-macchiato .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):active,html.theme--catppuccin-macchiato #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):active,html.theme--catppuccin-macchiato .field.has-addons .control .input.is-active:not([disabled]),html.theme--catppuccin-macchiato .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-active:not([disabled]),html.theme--catppuccin-macchiato #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-active:not([disabled]),html.theme--catppuccin-macchiato .field.has-addons .control .select select:not([disabled]):focus,html.theme--catppuccin-macchiato .field.has-addons .control .select select.is-focused:not([disabled]),html.theme--catppuccin-macchiato .field.has-addons .control .select select:not([disabled]):active,html.theme--catppuccin-macchiato .field.has-addons .control .select select.is-active:not([disabled]){z-index:3}html.theme--catppuccin-macchiato .field.has-addons .control .button:not([disabled]):focus:hover,html.theme--catppuccin-macchiato .field.has-addons .control .button.is-focused:not([disabled]):hover,html.theme--catppuccin-macchiato .field.has-addons .control .button:not([disabled]):active:hover,html.theme--catppuccin-macchiato .field.has-addons .control .button.is-active:not([disabled]):hover,html.theme--catppuccin-macchiato .field.has-addons .control .input:not([disabled]):focus:hover,html.theme--catppuccin-macchiato .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):focus:hover,html.theme--catppuccin-macchiato #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):focus:hover,html.theme--catppuccin-macchiato .field.has-addons .control .input.is-focused:not([disabled]):hover,html.theme--catppuccin-macchiato .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-focused:not([disabled]):hover,html.theme--catppuccin-macchiato #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-focused:not([disabled]):hover,html.theme--catppuccin-macchiato .field.has-addons .control .input:not([disabled]):active:hover,html.theme--catppuccin-macchiato .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):active:hover,html.theme--catppuccin-macchiato #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):active:hover,html.theme--catppuccin-macchiato .field.has-addons .control .input.is-active:not([disabled]):hover,html.theme--catppuccin-macchiato .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-active:not([disabled]):hover,html.theme--catppuccin-macchiato #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-active:not([disabled]):hover,html.theme--catppuccin-macchiato .field.has-addons .control .select select:not([disabled]):focus:hover,html.theme--catppuccin-macchiato .field.has-addons .control .select select.is-focused:not([disabled]):hover,html.theme--catppuccin-macchiato .field.has-addons .control .select select:not([disabled]):active:hover,html.theme--catppuccin-macchiato .field.has-addons .control .select select.is-active:not([disabled]):hover{z-index:4}html.theme--catppuccin-macchiato .field.has-addons .control.is-expanded{flex-grow:1;flex-shrink:1}html.theme--catppuccin-macchiato .field.has-addons.has-addons-centered{justify-content:center}html.theme--catppuccin-macchiato .field.has-addons.has-addons-right{justify-content:flex-end}html.theme--catppuccin-macchiato .field.has-addons.has-addons-fullwidth .control{flex-grow:1;flex-shrink:0}html.theme--catppuccin-macchiato .field.is-grouped{display:flex;justify-content:flex-start}html.theme--catppuccin-macchiato .field.is-grouped>.control{flex-shrink:0}html.theme--catppuccin-macchiato .field.is-grouped>.control:not(:last-child){margin-bottom:0;margin-right:.75rem}html.theme--catppuccin-macchiato .field.is-grouped>.control.is-expanded{flex-grow:1;flex-shrink:1}html.theme--catppuccin-macchiato .field.is-grouped.is-grouped-centered{justify-content:center}html.theme--catppuccin-macchiato .field.is-grouped.is-grouped-right{justify-content:flex-end}html.theme--catppuccin-macchiato .field.is-grouped.is-grouped-multiline{flex-wrap:wrap}html.theme--catppuccin-macchiato .field.is-grouped.is-grouped-multiline>.control:last-child,html.theme--catppuccin-macchiato .field.is-grouped.is-grouped-multiline>.control:not(:last-child){margin-bottom:0.75rem}html.theme--catppuccin-macchiato .field.is-grouped.is-grouped-multiline:last-child{margin-bottom:-0.75rem}html.theme--catppuccin-macchiato .field.is-grouped.is-grouped-multiline:not(:last-child){margin-bottom:0}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .field.is-horizontal{display:flex}}html.theme--catppuccin-macchiato .field-label .label{font-size:inherit}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .field-label{margin-bottom:0.5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .field-label{flex-basis:0;flex-grow:1;flex-shrink:0;margin-right:1.5rem;text-align:right}html.theme--catppuccin-macchiato .field-label.is-small,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.field-label{font-size:.75rem;padding-top:0.375em}html.theme--catppuccin-macchiato .field-label.is-normal{padding-top:0.375em}html.theme--catppuccin-macchiato .field-label.is-medium{font-size:1.25rem;padding-top:0.375em}html.theme--catppuccin-macchiato .field-label.is-large{font-size:1.5rem;padding-top:0.375em}}html.theme--catppuccin-macchiato .field-body .field .field{margin-bottom:0}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .field-body{display:flex;flex-basis:0;flex-grow:5;flex-shrink:1}html.theme--catppuccin-macchiato .field-body .field{margin-bottom:0}html.theme--catppuccin-macchiato .field-body>.field{flex-shrink:1}html.theme--catppuccin-macchiato .field-body>.field:not(.is-narrow){flex-grow:1}html.theme--catppuccin-macchiato .field-body>.field:not(:last-child){margin-right:.75rem}}html.theme--catppuccin-macchiato .control{box-sizing:border-box;clear:both;font-size:1rem;position:relative;text-align:inherit}html.theme--catppuccin-macchiato .control.has-icons-left .input:focus~.icon,html.theme--catppuccin-macchiato .control.has-icons-left #documenter .docs-sidebar form.docs-search>input:focus~.icon,html.theme--catppuccin-macchiato #documenter .docs-sidebar .control.has-icons-left form.docs-search>input:focus~.icon,html.theme--catppuccin-macchiato .control.has-icons-left .select:focus~.icon,html.theme--catppuccin-macchiato .control.has-icons-right .input:focus~.icon,html.theme--catppuccin-macchiato .control.has-icons-right #documenter .docs-sidebar form.docs-search>input:focus~.icon,html.theme--catppuccin-macchiato #documenter .docs-sidebar .control.has-icons-right form.docs-search>input:focus~.icon,html.theme--catppuccin-macchiato .control.has-icons-right .select:focus~.icon{color:#363a4f}html.theme--catppuccin-macchiato .control.has-icons-left .input.is-small~.icon,html.theme--catppuccin-macchiato .control.has-icons-left #documenter .docs-sidebar form.docs-search>input~.icon,html.theme--catppuccin-macchiato #documenter .docs-sidebar .control.has-icons-left form.docs-search>input~.icon,html.theme--catppuccin-macchiato .control.has-icons-left .select.is-small~.icon,html.theme--catppuccin-macchiato .control.has-icons-right .input.is-small~.icon,html.theme--catppuccin-macchiato .control.has-icons-right #documenter .docs-sidebar form.docs-search>input~.icon,html.theme--catppuccin-macchiato #documenter .docs-sidebar .control.has-icons-right form.docs-search>input~.icon,html.theme--catppuccin-macchiato .control.has-icons-right .select.is-small~.icon{font-size:.75rem}html.theme--catppuccin-macchiato .control.has-icons-left .input.is-medium~.icon,html.theme--catppuccin-macchiato .control.has-icons-left #documenter .docs-sidebar form.docs-search>input.is-medium~.icon,html.theme--catppuccin-macchiato #documenter .docs-sidebar .control.has-icons-left form.docs-search>input.is-medium~.icon,html.theme--catppuccin-macchiato .control.has-icons-left .select.is-medium~.icon,html.theme--catppuccin-macchiato .control.has-icons-right .input.is-medium~.icon,html.theme--catppuccin-macchiato .control.has-icons-right #documenter .docs-sidebar form.docs-search>input.is-medium~.icon,html.theme--catppuccin-macchiato #documenter .docs-sidebar .control.has-icons-right form.docs-search>input.is-medium~.icon,html.theme--catppuccin-macchiato .control.has-icons-right .select.is-medium~.icon{font-size:1.25rem}html.theme--catppuccin-macchiato .control.has-icons-left .input.is-large~.icon,html.theme--catppuccin-macchiato .control.has-icons-left #documenter .docs-sidebar form.docs-search>input.is-large~.icon,html.theme--catppuccin-macchiato #documenter .docs-sidebar .control.has-icons-left form.docs-search>input.is-large~.icon,html.theme--catppuccin-macchiato .control.has-icons-left .select.is-large~.icon,html.theme--catppuccin-macchiato .control.has-icons-right .input.is-large~.icon,html.theme--catppuccin-macchiato .control.has-icons-right #documenter .docs-sidebar form.docs-search>input.is-large~.icon,html.theme--catppuccin-macchiato #documenter .docs-sidebar .control.has-icons-right form.docs-search>input.is-large~.icon,html.theme--catppuccin-macchiato .control.has-icons-right .select.is-large~.icon{font-size:1.5rem}html.theme--catppuccin-macchiato .control.has-icons-left .icon,html.theme--catppuccin-macchiato .control.has-icons-right .icon{color:#5b6078;height:2.5em;pointer-events:none;position:absolute;top:0;width:2.5em;z-index:4}html.theme--catppuccin-macchiato .control.has-icons-left .input,html.theme--catppuccin-macchiato .control.has-icons-left #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-macchiato #documenter .docs-sidebar .control.has-icons-left form.docs-search>input,html.theme--catppuccin-macchiato .control.has-icons-left .select select{padding-left:2.5em}html.theme--catppuccin-macchiato .control.has-icons-left .icon.is-left{left:0}html.theme--catppuccin-macchiato .control.has-icons-right .input,html.theme--catppuccin-macchiato .control.has-icons-right #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-macchiato #documenter .docs-sidebar .control.has-icons-right form.docs-search>input,html.theme--catppuccin-macchiato .control.has-icons-right .select select{padding-right:2.5em}html.theme--catppuccin-macchiato .control.has-icons-right .icon.is-right{right:0}html.theme--catppuccin-macchiato .control.is-loading::after{position:absolute !important;right:.625em;top:0.625em;z-index:4}html.theme--catppuccin-macchiato .control.is-loading.is-small:after,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-loading:after{font-size:.75rem}html.theme--catppuccin-macchiato .control.is-loading.is-medium:after{font-size:1.25rem}html.theme--catppuccin-macchiato .control.is-loading.is-large:after{font-size:1.5rem}html.theme--catppuccin-macchiato .breadcrumb{font-size:1rem;white-space:nowrap}html.theme--catppuccin-macchiato .breadcrumb a{align-items:center;color:#8aadf4;display:flex;justify-content:center;padding:0 .75em}html.theme--catppuccin-macchiato .breadcrumb a:hover{color:#91d7e3}html.theme--catppuccin-macchiato .breadcrumb li{align-items:center;display:flex}html.theme--catppuccin-macchiato .breadcrumb li:first-child a{padding-left:0}html.theme--catppuccin-macchiato .breadcrumb li.is-active a{color:#b5c1f1;cursor:default;pointer-events:none}html.theme--catppuccin-macchiato .breadcrumb li+li::before{color:#6e738d;content:"\0002f"}html.theme--catppuccin-macchiato .breadcrumb ul,html.theme--catppuccin-macchiato .breadcrumb ol{align-items:flex-start;display:flex;flex-wrap:wrap;justify-content:flex-start}html.theme--catppuccin-macchiato .breadcrumb .icon:first-child{margin-right:.5em}html.theme--catppuccin-macchiato .breadcrumb .icon:last-child{margin-left:.5em}html.theme--catppuccin-macchiato .breadcrumb.is-centered ol,html.theme--catppuccin-macchiato .breadcrumb.is-centered ul{justify-content:center}html.theme--catppuccin-macchiato .breadcrumb.is-right ol,html.theme--catppuccin-macchiato .breadcrumb.is-right ul{justify-content:flex-end}html.theme--catppuccin-macchiato .breadcrumb.is-small,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.breadcrumb{font-size:.75rem}html.theme--catppuccin-macchiato .breadcrumb.is-medium{font-size:1.25rem}html.theme--catppuccin-macchiato .breadcrumb.is-large{font-size:1.5rem}html.theme--catppuccin-macchiato .breadcrumb.has-arrow-separator li+li::before{content:"\02192"}html.theme--catppuccin-macchiato .breadcrumb.has-bullet-separator li+li::before{content:"\02022"}html.theme--catppuccin-macchiato .breadcrumb.has-dot-separator li+li::before{content:"\000b7"}html.theme--catppuccin-macchiato .breadcrumb.has-succeeds-separator li+li::before{content:"\0227B"}html.theme--catppuccin-macchiato .card{background-color:#fff;border-radius:.25rem;box-shadow:#171717;color:#cad3f5;max-width:100%;position:relative}html.theme--catppuccin-macchiato .card-footer:first-child,html.theme--catppuccin-macchiato .card-content:first-child,html.theme--catppuccin-macchiato .card-header:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}html.theme--catppuccin-macchiato .card-footer:last-child,html.theme--catppuccin-macchiato .card-content:last-child,html.theme--catppuccin-macchiato .card-header:last-child{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}html.theme--catppuccin-macchiato .card-header{background-color:rgba(0,0,0,0);align-items:stretch;box-shadow:0 0.125em 0.25em rgba(10,10,10,0.1);display:flex}html.theme--catppuccin-macchiato .card-header-title{align-items:center;color:#b5c1f1;display:flex;flex-grow:1;font-weight:700;padding:0.75rem 1rem}html.theme--catppuccin-macchiato .card-header-title.is-centered{justify-content:center}html.theme--catppuccin-macchiato .card-header-icon{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;color:currentColor;font-family:inherit;font-size:1em;margin:0;padding:0;align-items:center;cursor:pointer;display:flex;justify-content:center;padding:0.75rem 1rem}html.theme--catppuccin-macchiato .card-image{display:block;position:relative}html.theme--catppuccin-macchiato .card-image:first-child img{border-top-left-radius:.25rem;border-top-right-radius:.25rem}html.theme--catppuccin-macchiato .card-image:last-child img{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}html.theme--catppuccin-macchiato .card-content{background-color:rgba(0,0,0,0);padding:1.5rem}html.theme--catppuccin-macchiato .card-footer{background-color:rgba(0,0,0,0);border-top:1px solid #ededed;align-items:stretch;display:flex}html.theme--catppuccin-macchiato .card-footer-item{align-items:center;display:flex;flex-basis:0;flex-grow:1;flex-shrink:0;justify-content:center;padding:.75rem}html.theme--catppuccin-macchiato .card-footer-item:not(:last-child){border-right:1px solid #ededed}html.theme--catppuccin-macchiato .card .media:not(:last-child){margin-bottom:1.5rem}html.theme--catppuccin-macchiato .dropdown{display:inline-flex;position:relative;vertical-align:top}html.theme--catppuccin-macchiato .dropdown.is-active .dropdown-menu,html.theme--catppuccin-macchiato .dropdown.is-hoverable:hover .dropdown-menu{display:block}html.theme--catppuccin-macchiato .dropdown.is-right .dropdown-menu{left:auto;right:0}html.theme--catppuccin-macchiato .dropdown.is-up .dropdown-menu{bottom:100%;padding-bottom:4px;padding-top:initial;top:auto}html.theme--catppuccin-macchiato .dropdown-menu{display:none;left:0;min-width:12rem;padding-top:4px;position:absolute;top:100%;z-index:20}html.theme--catppuccin-macchiato .dropdown-content{background-color:#1e2030;border-radius:.4em;box-shadow:#171717;padding-bottom:.5rem;padding-top:.5rem}html.theme--catppuccin-macchiato .dropdown-item{color:#cad3f5;display:block;font-size:0.875rem;line-height:1.5;padding:0.375rem 1rem;position:relative}html.theme--catppuccin-macchiato a.dropdown-item,html.theme--catppuccin-macchiato button.dropdown-item{padding-right:3rem;text-align:inherit;white-space:nowrap;width:100%}html.theme--catppuccin-macchiato a.dropdown-item:hover,html.theme--catppuccin-macchiato button.dropdown-item:hover{background-color:#1e2030;color:#0a0a0a}html.theme--catppuccin-macchiato a.dropdown-item.is-active,html.theme--catppuccin-macchiato button.dropdown-item.is-active{background-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .dropdown-divider{background-color:#ededed;border:none;display:block;height:1px;margin:0.5rem 0}html.theme--catppuccin-macchiato .level{align-items:center;justify-content:space-between}html.theme--catppuccin-macchiato .level code{border-radius:.4em}html.theme--catppuccin-macchiato .level img{display:inline-block;vertical-align:top}html.theme--catppuccin-macchiato .level.is-mobile{display:flex}html.theme--catppuccin-macchiato .level.is-mobile .level-left,html.theme--catppuccin-macchiato .level.is-mobile .level-right{display:flex}html.theme--catppuccin-macchiato .level.is-mobile .level-left+.level-right{margin-top:0}html.theme--catppuccin-macchiato .level.is-mobile .level-item:not(:last-child){margin-bottom:0;margin-right:.75rem}html.theme--catppuccin-macchiato .level.is-mobile .level-item:not(.is-narrow){flex-grow:1}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .level{display:flex}html.theme--catppuccin-macchiato .level>.level-item:not(.is-narrow){flex-grow:1}}html.theme--catppuccin-macchiato .level-item{align-items:center;display:flex;flex-basis:auto;flex-grow:0;flex-shrink:0;justify-content:center}html.theme--catppuccin-macchiato .level-item .title,html.theme--catppuccin-macchiato .level-item .subtitle{margin-bottom:0}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .level-item:not(:last-child){margin-bottom:.75rem}}html.theme--catppuccin-macchiato .level-left,html.theme--catppuccin-macchiato .level-right{flex-basis:auto;flex-grow:0;flex-shrink:0}html.theme--catppuccin-macchiato .level-left .level-item.is-flexible,html.theme--catppuccin-macchiato .level-right .level-item.is-flexible{flex-grow:1}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .level-left .level-item:not(:last-child),html.theme--catppuccin-macchiato .level-right .level-item:not(:last-child){margin-right:.75rem}}html.theme--catppuccin-macchiato .level-left{align-items:center;justify-content:flex-start}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .level-left+.level-right{margin-top:1.5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .level-left{display:flex}}html.theme--catppuccin-macchiato .level-right{align-items:center;justify-content:flex-end}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .level-right{display:flex}}html.theme--catppuccin-macchiato .media{align-items:flex-start;display:flex;text-align:inherit}html.theme--catppuccin-macchiato .media .content:not(:last-child){margin-bottom:.75rem}html.theme--catppuccin-macchiato .media .media{border-top:1px solid rgba(91,96,120,0.5);display:flex;padding-top:.75rem}html.theme--catppuccin-macchiato .media .media .content:not(:last-child),html.theme--catppuccin-macchiato .media .media .control:not(:last-child){margin-bottom:.5rem}html.theme--catppuccin-macchiato .media .media .media{padding-top:.5rem}html.theme--catppuccin-macchiato .media .media .media+.media{margin-top:.5rem}html.theme--catppuccin-macchiato .media+.media{border-top:1px solid rgba(91,96,120,0.5);margin-top:1rem;padding-top:1rem}html.theme--catppuccin-macchiato .media.is-large+.media{margin-top:1.5rem;padding-top:1.5rem}html.theme--catppuccin-macchiato .media-left,html.theme--catppuccin-macchiato .media-right{flex-basis:auto;flex-grow:0;flex-shrink:0}html.theme--catppuccin-macchiato .media-left{margin-right:1rem}html.theme--catppuccin-macchiato .media-right{margin-left:1rem}html.theme--catppuccin-macchiato .media-content{flex-basis:auto;flex-grow:1;flex-shrink:1;text-align:inherit}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .media-content{overflow-x:auto}}html.theme--catppuccin-macchiato .menu{font-size:1rem}html.theme--catppuccin-macchiato .menu.is-small,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.menu{font-size:.75rem}html.theme--catppuccin-macchiato .menu.is-medium{font-size:1.25rem}html.theme--catppuccin-macchiato .menu.is-large{font-size:1.5rem}html.theme--catppuccin-macchiato .menu-list{line-height:1.25}html.theme--catppuccin-macchiato .menu-list a{border-radius:3px;color:#cad3f5;display:block;padding:0.5em 0.75em}html.theme--catppuccin-macchiato .menu-list a:hover{background-color:#1e2030;color:#b5c1f1}html.theme--catppuccin-macchiato .menu-list a.is-active{background-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .menu-list li ul{border-left:1px solid #5b6078;margin:.75em;padding-left:.75em}html.theme--catppuccin-macchiato .menu-label{color:#f5f7fd;font-size:.75em;letter-spacing:.1em;text-transform:uppercase}html.theme--catppuccin-macchiato .menu-label:not(:first-child){margin-top:1em}html.theme--catppuccin-macchiato .menu-label:not(:last-child){margin-bottom:1em}html.theme--catppuccin-macchiato .message{background-color:#1e2030;border-radius:.4em;font-size:1rem}html.theme--catppuccin-macchiato .message strong{color:currentColor}html.theme--catppuccin-macchiato .message a:not(.button):not(.tag):not(.dropdown-item){color:currentColor;text-decoration:underline}html.theme--catppuccin-macchiato .message.is-small,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.message{font-size:.75rem}html.theme--catppuccin-macchiato .message.is-medium{font-size:1.25rem}html.theme--catppuccin-macchiato .message.is-large{font-size:1.5rem}html.theme--catppuccin-macchiato .message.is-white{background-color:#fff}html.theme--catppuccin-macchiato .message.is-white .message-header{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-macchiato .message.is-white .message-body{border-color:#fff}html.theme--catppuccin-macchiato .message.is-black{background-color:#fafafa}html.theme--catppuccin-macchiato .message.is-black .message-header{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-macchiato .message.is-black .message-body{border-color:#0a0a0a}html.theme--catppuccin-macchiato .message.is-light{background-color:#fafafa}html.theme--catppuccin-macchiato .message.is-light .message-header{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .message.is-light .message-body{border-color:#f5f5f5}html.theme--catppuccin-macchiato .message.is-dark,html.theme--catppuccin-macchiato .content kbd.message{background-color:#f9f9fb}html.theme--catppuccin-macchiato .message.is-dark .message-header,html.theme--catppuccin-macchiato .content kbd.message .message-header{background-color:#363a4f;color:#fff}html.theme--catppuccin-macchiato .message.is-dark .message-body,html.theme--catppuccin-macchiato .content kbd.message .message-body{border-color:#363a4f}html.theme--catppuccin-macchiato .message.is-primary,html.theme--catppuccin-macchiato .docstring>section>a.message.docs-sourcelink{background-color:#ecf2fd}html.theme--catppuccin-macchiato .message.is-primary .message-header,html.theme--catppuccin-macchiato .docstring>section>a.message.docs-sourcelink .message-header{background-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .message.is-primary .message-body,html.theme--catppuccin-macchiato .docstring>section>a.message.docs-sourcelink .message-body{border-color:#8aadf4;color:#0e3b95}html.theme--catppuccin-macchiato .message.is-link{background-color:#ecf2fd}html.theme--catppuccin-macchiato .message.is-link .message-header{background-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .message.is-link .message-body{border-color:#8aadf4;color:#0e3b95}html.theme--catppuccin-macchiato .message.is-info{background-color:#f0faf8}html.theme--catppuccin-macchiato .message.is-info .message-header{background-color:#8bd5ca;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .message.is-info .message-body{border-color:#8bd5ca;color:#276d62}html.theme--catppuccin-macchiato .message.is-success{background-color:#f2faf0}html.theme--catppuccin-macchiato .message.is-success .message-header{background-color:#a6da95;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .message.is-success .message-body{border-color:#a6da95;color:#386e26}html.theme--catppuccin-macchiato .message.is-warning{background-color:#fcf7ee}html.theme--catppuccin-macchiato .message.is-warning .message-header{background-color:#eed49f;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .message.is-warning .message-body{border-color:#eed49f;color:#7e5c16}html.theme--catppuccin-macchiato .message.is-danger{background-color:#fcedef}html.theme--catppuccin-macchiato .message.is-danger .message-header{background-color:#ed8796;color:#fff}html.theme--catppuccin-macchiato .message.is-danger .message-body{border-color:#ed8796;color:#971729}html.theme--catppuccin-macchiato .message-header{align-items:center;background-color:#cad3f5;border-radius:.4em .4em 0 0;color:rgba(0,0,0,0.7);display:flex;font-weight:700;justify-content:space-between;line-height:1.25;padding:0.75em 1em;position:relative}html.theme--catppuccin-macchiato .message-header .delete{flex-grow:0;flex-shrink:0;margin-left:.75em}html.theme--catppuccin-macchiato .message-header+.message-body{border-width:0;border-top-left-radius:0;border-top-right-radius:0}html.theme--catppuccin-macchiato .message-body{border-color:#5b6078;border-radius:.4em;border-style:solid;border-width:0 0 0 4px;color:#cad3f5;padding:1.25em 1.5em}html.theme--catppuccin-macchiato .message-body code,html.theme--catppuccin-macchiato .message-body pre{background-color:#fff}html.theme--catppuccin-macchiato .message-body pre code{background-color:rgba(0,0,0,0)}html.theme--catppuccin-macchiato .modal{align-items:center;display:none;flex-direction:column;justify-content:center;overflow:hidden;position:fixed;z-index:40}html.theme--catppuccin-macchiato .modal.is-active{display:flex}html.theme--catppuccin-macchiato .modal-background{background-color:rgba(10,10,10,0.86)}html.theme--catppuccin-macchiato .modal-content,html.theme--catppuccin-macchiato .modal-card{margin:0 20px;max-height:calc(100vh - 160px);overflow:auto;position:relative;width:100%}@media screen and (min-width: 769px){html.theme--catppuccin-macchiato .modal-content,html.theme--catppuccin-macchiato .modal-card{margin:0 auto;max-height:calc(100vh - 40px);width:640px}}html.theme--catppuccin-macchiato .modal-close{background:none;height:40px;position:fixed;right:20px;top:20px;width:40px}html.theme--catppuccin-macchiato .modal-card{display:flex;flex-direction:column;max-height:calc(100vh - 40px);overflow:hidden;-ms-overflow-y:visible}html.theme--catppuccin-macchiato .modal-card-head,html.theme--catppuccin-macchiato .modal-card-foot{align-items:center;background-color:#1e2030;display:flex;flex-shrink:0;justify-content:flex-start;padding:20px;position:relative}html.theme--catppuccin-macchiato .modal-card-head{border-bottom:1px solid #5b6078;border-top-left-radius:8px;border-top-right-radius:8px}html.theme--catppuccin-macchiato .modal-card-title{color:#cad3f5;flex-grow:1;flex-shrink:0;font-size:1.5rem;line-height:1}html.theme--catppuccin-macchiato .modal-card-foot{border-bottom-left-radius:8px;border-bottom-right-radius:8px;border-top:1px solid #5b6078}html.theme--catppuccin-macchiato .modal-card-foot .button:not(:last-child){margin-right:.5em}html.theme--catppuccin-macchiato .modal-card-body{-webkit-overflow-scrolling:touch;background-color:#24273a;flex-grow:1;flex-shrink:1;overflow:auto;padding:20px}html.theme--catppuccin-macchiato .navbar{background-color:#8aadf4;min-height:4rem;position:relative;z-index:30}html.theme--catppuccin-macchiato .navbar.is-white{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-macchiato .navbar.is-white .navbar-brand>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-white .navbar-brand .navbar-link{color:#0a0a0a}html.theme--catppuccin-macchiato .navbar.is-white .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-white .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-white .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-white .navbar-brand .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-white .navbar-brand .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-white .navbar-brand .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}html.theme--catppuccin-macchiato .navbar.is-white .navbar-brand .navbar-link::after{border-color:#0a0a0a}html.theme--catppuccin-macchiato .navbar.is-white .navbar-burger{color:#0a0a0a}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .navbar.is-white .navbar-start>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-white .navbar-start .navbar-link,html.theme--catppuccin-macchiato .navbar.is-white .navbar-end>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-white .navbar-end .navbar-link{color:#0a0a0a}html.theme--catppuccin-macchiato .navbar.is-white .navbar-start>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-white .navbar-start>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-white .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-white .navbar-start .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-white .navbar-start .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-white .navbar-start .navbar-link.is-active,html.theme--catppuccin-macchiato .navbar.is-white .navbar-end>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-white .navbar-end>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-white .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-white .navbar-end .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-white .navbar-end .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-white .navbar-end .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}html.theme--catppuccin-macchiato .navbar.is-white .navbar-start .navbar-link::after,html.theme--catppuccin-macchiato .navbar.is-white .navbar-end .navbar-link::after{border-color:#0a0a0a}html.theme--catppuccin-macchiato .navbar.is-white .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-macchiato .navbar.is-white .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-macchiato .navbar.is-white .navbar-item.has-dropdown.is-active .navbar-link{background-color:#f2f2f2;color:#0a0a0a}html.theme--catppuccin-macchiato .navbar.is-white .navbar-dropdown a.navbar-item.is-active{background-color:#fff;color:#0a0a0a}}html.theme--catppuccin-macchiato .navbar.is-black{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-macchiato .navbar.is-black .navbar-brand>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-black .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-macchiato .navbar.is-black .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-black .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-black .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-black .navbar-brand .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-black .navbar-brand .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-black .navbar-brand .navbar-link.is-active{background-color:#000;color:#fff}html.theme--catppuccin-macchiato .navbar.is-black .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-macchiato .navbar.is-black .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .navbar.is-black .navbar-start>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-black .navbar-start .navbar-link,html.theme--catppuccin-macchiato .navbar.is-black .navbar-end>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-black .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-macchiato .navbar.is-black .navbar-start>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-black .navbar-start>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-black .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-black .navbar-start .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-black .navbar-start .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-black .navbar-start .navbar-link.is-active,html.theme--catppuccin-macchiato .navbar.is-black .navbar-end>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-black .navbar-end>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-black .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-black .navbar-end .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-black .navbar-end .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-black .navbar-end .navbar-link.is-active{background-color:#000;color:#fff}html.theme--catppuccin-macchiato .navbar.is-black .navbar-start .navbar-link::after,html.theme--catppuccin-macchiato .navbar.is-black .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-macchiato .navbar.is-black .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-macchiato .navbar.is-black .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-macchiato .navbar.is-black .navbar-item.has-dropdown.is-active .navbar-link{background-color:#000;color:#fff}html.theme--catppuccin-macchiato .navbar.is-black .navbar-dropdown a.navbar-item.is-active{background-color:#0a0a0a;color:#fff}}html.theme--catppuccin-macchiato .navbar.is-light{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-light .navbar-brand>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-light .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-light .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-light .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-light .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-light .navbar-brand .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-light .navbar-brand .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-light .navbar-brand .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-light .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-light .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .navbar.is-light .navbar-start>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-light .navbar-start .navbar-link,html.theme--catppuccin-macchiato .navbar.is-light .navbar-end>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-light .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-light .navbar-start>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-light .navbar-start>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-light .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-light .navbar-start .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-light .navbar-start .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-light .navbar-start .navbar-link.is-active,html.theme--catppuccin-macchiato .navbar.is-light .navbar-end>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-light .navbar-end>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-light .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-light .navbar-end .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-light .navbar-end .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-light .navbar-end .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-light .navbar-start .navbar-link::after,html.theme--catppuccin-macchiato .navbar.is-light .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-light .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-macchiato .navbar.is-light .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-macchiato .navbar.is-light .navbar-item.has-dropdown.is-active .navbar-link{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-light .navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}}html.theme--catppuccin-macchiato .navbar.is-dark,html.theme--catppuccin-macchiato .content kbd.navbar{background-color:#363a4f;color:#fff}html.theme--catppuccin-macchiato .navbar.is-dark .navbar-brand>.navbar-item,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-brand>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-brand .navbar-link,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-macchiato .navbar.is-dark .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-brand .navbar-link:focus,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-brand .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-brand .navbar-link:hover,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-brand .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-brand .navbar-link.is-active,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-brand .navbar-link.is-active{background-color:#2c2f40;color:#fff}html.theme--catppuccin-macchiato .navbar.is-dark .navbar-brand .navbar-link::after,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-macchiato .navbar.is-dark .navbar-burger,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .navbar.is-dark .navbar-start>.navbar-item,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-start>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-start .navbar-link,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-start .navbar-link,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-end>.navbar-item,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-end>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-end .navbar-link,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-macchiato .navbar.is-dark .navbar-start>a.navbar-item:focus,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-start>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-start>a.navbar-item:hover,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-start>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-start .navbar-link:focus,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-start .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-start .navbar-link:hover,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-start .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-start .navbar-link.is-active,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-start .navbar-link.is-active,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-end>a.navbar-item:focus,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-end>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-end>a.navbar-item:hover,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-end>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-end .navbar-link:focus,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-end .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-end .navbar-link:hover,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-end .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-end .navbar-link.is-active,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-end .navbar-link.is-active{background-color:#2c2f40;color:#fff}html.theme--catppuccin-macchiato .navbar.is-dark .navbar-start .navbar-link::after,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-start .navbar-link::after,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-end .navbar-link::after,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-macchiato .navbar.is-dark .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-item.has-dropdown.is-active .navbar-link,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-item.has-dropdown.is-active .navbar-link{background-color:#2c2f40;color:#fff}html.theme--catppuccin-macchiato .navbar.is-dark .navbar-dropdown a.navbar-item.is-active,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-dropdown a.navbar-item.is-active{background-color:#363a4f;color:#fff}}html.theme--catppuccin-macchiato .navbar.is-primary,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink{background-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .navbar.is-primary .navbar-brand>.navbar-item,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-brand>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-brand .navbar-link,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-macchiato .navbar.is-primary .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-brand .navbar-link:focus,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-brand .navbar-link:hover,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-brand .navbar-link.is-active,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link.is-active{background-color:#739df2;color:#fff}html.theme--catppuccin-macchiato .navbar.is-primary .navbar-brand .navbar-link::after,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-macchiato .navbar.is-primary .navbar-burger,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .navbar.is-primary .navbar-start>.navbar-item,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-start>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-start .navbar-link,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-end>.navbar-item,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-end>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-end .navbar-link,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-macchiato .navbar.is-primary .navbar-start>a.navbar-item:focus,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-start>a.navbar-item:hover,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-start .navbar-link:focus,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-start .navbar-link:hover,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-start .navbar-link.is-active,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link.is-active,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-end>a.navbar-item:focus,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-end>a.navbar-item:hover,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-end .navbar-link:focus,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-end .navbar-link:hover,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-end .navbar-link.is-active,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link.is-active{background-color:#739df2;color:#fff}html.theme--catppuccin-macchiato .navbar.is-primary .navbar-start .navbar-link::after,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link::after,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-end .navbar-link::after,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-macchiato .navbar.is-primary .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-item.has-dropdown.is-active .navbar-link,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown.is-active .navbar-link{background-color:#739df2;color:#fff}html.theme--catppuccin-macchiato .navbar.is-primary .navbar-dropdown a.navbar-item.is-active,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-dropdown a.navbar-item.is-active{background-color:#8aadf4;color:#fff}}html.theme--catppuccin-macchiato .navbar.is-link{background-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .navbar.is-link .navbar-brand>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-link .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-macchiato .navbar.is-link .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-link .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-link .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-link .navbar-brand .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-link .navbar-brand .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-link .navbar-brand .navbar-link.is-active{background-color:#739df2;color:#fff}html.theme--catppuccin-macchiato .navbar.is-link .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-macchiato .navbar.is-link .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .navbar.is-link .navbar-start>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-link .navbar-start .navbar-link,html.theme--catppuccin-macchiato .navbar.is-link .navbar-end>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-link .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-macchiato .navbar.is-link .navbar-start>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-link .navbar-start>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-link .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-link .navbar-start .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-link .navbar-start .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-link .navbar-start .navbar-link.is-active,html.theme--catppuccin-macchiato .navbar.is-link .navbar-end>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-link .navbar-end>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-link .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-link .navbar-end .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-link .navbar-end .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-link .navbar-end .navbar-link.is-active{background-color:#739df2;color:#fff}html.theme--catppuccin-macchiato .navbar.is-link .navbar-start .navbar-link::after,html.theme--catppuccin-macchiato .navbar.is-link .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-macchiato .navbar.is-link .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-macchiato .navbar.is-link .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-macchiato .navbar.is-link .navbar-item.has-dropdown.is-active .navbar-link{background-color:#739df2;color:#fff}html.theme--catppuccin-macchiato .navbar.is-link .navbar-dropdown a.navbar-item.is-active{background-color:#8aadf4;color:#fff}}html.theme--catppuccin-macchiato .navbar.is-info{background-color:#8bd5ca;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-info .navbar-brand>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-info .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-info .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-info .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-info .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-info .navbar-brand .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-info .navbar-brand .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-info .navbar-brand .navbar-link.is-active{background-color:#78cec1;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-info .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-info .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .navbar.is-info .navbar-start>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-info .navbar-start .navbar-link,html.theme--catppuccin-macchiato .navbar.is-info .navbar-end>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-info .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-info .navbar-start>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-info .navbar-start>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-info .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-info .navbar-start .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-info .navbar-start .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-info .navbar-start .navbar-link.is-active,html.theme--catppuccin-macchiato .navbar.is-info .navbar-end>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-info .navbar-end>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-info .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-info .navbar-end .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-info .navbar-end .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-info .navbar-end .navbar-link.is-active{background-color:#78cec1;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-info .navbar-start .navbar-link::after,html.theme--catppuccin-macchiato .navbar.is-info .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-info .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-macchiato .navbar.is-info .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-macchiato .navbar.is-info .navbar-item.has-dropdown.is-active .navbar-link{background-color:#78cec1;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-info .navbar-dropdown a.navbar-item.is-active{background-color:#8bd5ca;color:rgba(0,0,0,0.7)}}html.theme--catppuccin-macchiato .navbar.is-success{background-color:#a6da95;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-success .navbar-brand>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-success .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-success .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-success .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-success .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-success .navbar-brand .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-success .navbar-brand .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-success .navbar-brand .navbar-link.is-active{background-color:#96d382;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-success .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-success .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .navbar.is-success .navbar-start>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-success .navbar-start .navbar-link,html.theme--catppuccin-macchiato .navbar.is-success .navbar-end>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-success .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-success .navbar-start>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-success .navbar-start>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-success .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-success .navbar-start .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-success .navbar-start .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-success .navbar-start .navbar-link.is-active,html.theme--catppuccin-macchiato .navbar.is-success .navbar-end>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-success .navbar-end>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-success .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-success .navbar-end .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-success .navbar-end .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-success .navbar-end .navbar-link.is-active{background-color:#96d382;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-success .navbar-start .navbar-link::after,html.theme--catppuccin-macchiato .navbar.is-success .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-success .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-macchiato .navbar.is-success .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-macchiato .navbar.is-success .navbar-item.has-dropdown.is-active .navbar-link{background-color:#96d382;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-success .navbar-dropdown a.navbar-item.is-active{background-color:#a6da95;color:rgba(0,0,0,0.7)}}html.theme--catppuccin-macchiato .navbar.is-warning{background-color:#eed49f;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-warning .navbar-brand>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-warning .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-brand .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-brand .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-brand .navbar-link.is-active{background-color:#eaca89;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-warning .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-warning .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .navbar.is-warning .navbar-start>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-start .navbar-link,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-end>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-warning .navbar-start>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-start>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-start .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-start .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-start .navbar-link.is-active,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-end>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-end>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-end .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-end .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-end .navbar-link.is-active{background-color:#eaca89;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-warning .navbar-start .navbar-link::after,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-warning .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-item.has-dropdown.is-active .navbar-link{background-color:#eaca89;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-warning .navbar-dropdown a.navbar-item.is-active{background-color:#eed49f;color:rgba(0,0,0,0.7)}}html.theme--catppuccin-macchiato .navbar.is-danger{background-color:#ed8796;color:#fff}html.theme--catppuccin-macchiato .navbar.is-danger .navbar-brand>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-macchiato .navbar.is-danger .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-brand .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-brand .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-brand .navbar-link.is-active{background-color:#ea7183;color:#fff}html.theme--catppuccin-macchiato .navbar.is-danger .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-macchiato .navbar.is-danger .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .navbar.is-danger .navbar-start>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-start .navbar-link,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-end>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-macchiato .navbar.is-danger .navbar-start>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-start>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-start .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-start .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-start .navbar-link.is-active,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-end>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-end>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-end .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-end .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-end .navbar-link.is-active{background-color:#ea7183;color:#fff}html.theme--catppuccin-macchiato .navbar.is-danger .navbar-start .navbar-link::after,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-macchiato .navbar.is-danger .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-item.has-dropdown.is-active .navbar-link{background-color:#ea7183;color:#fff}html.theme--catppuccin-macchiato .navbar.is-danger .navbar-dropdown a.navbar-item.is-active{background-color:#ed8796;color:#fff}}html.theme--catppuccin-macchiato .navbar>.container{align-items:stretch;display:flex;min-height:4rem;width:100%}html.theme--catppuccin-macchiato .navbar.has-shadow{box-shadow:0 2px 0 0 #1e2030}html.theme--catppuccin-macchiato .navbar.is-fixed-bottom,html.theme--catppuccin-macchiato .navbar.is-fixed-top{left:0;position:fixed;right:0;z-index:30}html.theme--catppuccin-macchiato .navbar.is-fixed-bottom{bottom:0}html.theme--catppuccin-macchiato .navbar.is-fixed-bottom.has-shadow{box-shadow:0 -2px 0 0 #1e2030}html.theme--catppuccin-macchiato .navbar.is-fixed-top{top:0}html.theme--catppuccin-macchiato html.has-navbar-fixed-top,html.theme--catppuccin-macchiato body.has-navbar-fixed-top{padding-top:4rem}html.theme--catppuccin-macchiato html.has-navbar-fixed-bottom,html.theme--catppuccin-macchiato body.has-navbar-fixed-bottom{padding-bottom:4rem}html.theme--catppuccin-macchiato .navbar-brand,html.theme--catppuccin-macchiato .navbar-tabs{align-items:stretch;display:flex;flex-shrink:0;min-height:4rem}html.theme--catppuccin-macchiato .navbar-brand a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar-brand a.navbar-item:hover{background-color:transparent}html.theme--catppuccin-macchiato .navbar-tabs{-webkit-overflow-scrolling:touch;max-width:100vw;overflow-x:auto;overflow-y:hidden}html.theme--catppuccin-macchiato .navbar-burger{color:#cad3f5;-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;cursor:pointer;display:block;height:4rem;position:relative;width:4rem;margin-left:auto}html.theme--catppuccin-macchiato .navbar-burger span{background-color:currentColor;display:block;height:1px;left:calc(50% - 8px);position:absolute;transform-origin:center;transition-duration:86ms;transition-property:background-color, opacity, transform;transition-timing-function:ease-out;width:16px}html.theme--catppuccin-macchiato .navbar-burger span:nth-child(1){top:calc(50% - 6px)}html.theme--catppuccin-macchiato .navbar-burger span:nth-child(2){top:calc(50% - 1px)}html.theme--catppuccin-macchiato .navbar-burger span:nth-child(3){top:calc(50% + 4px)}html.theme--catppuccin-macchiato .navbar-burger:hover{background-color:rgba(0,0,0,0.05)}html.theme--catppuccin-macchiato .navbar-burger.is-active span:nth-child(1){transform:translateY(5px) rotate(45deg)}html.theme--catppuccin-macchiato .navbar-burger.is-active span:nth-child(2){opacity:0}html.theme--catppuccin-macchiato .navbar-burger.is-active span:nth-child(3){transform:translateY(-5px) rotate(-45deg)}html.theme--catppuccin-macchiato .navbar-menu{display:none}html.theme--catppuccin-macchiato .navbar-item,html.theme--catppuccin-macchiato .navbar-link{color:#cad3f5;display:block;line-height:1.5;padding:0.5rem 0.75rem;position:relative}html.theme--catppuccin-macchiato .navbar-item .icon:only-child,html.theme--catppuccin-macchiato .navbar-link .icon:only-child{margin-left:-0.25rem;margin-right:-0.25rem}html.theme--catppuccin-macchiato a.navbar-item,html.theme--catppuccin-macchiato .navbar-link{cursor:pointer}html.theme--catppuccin-macchiato a.navbar-item:focus,html.theme--catppuccin-macchiato a.navbar-item:focus-within,html.theme--catppuccin-macchiato a.navbar-item:hover,html.theme--catppuccin-macchiato a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar-link:focus,html.theme--catppuccin-macchiato .navbar-link:focus-within,html.theme--catppuccin-macchiato .navbar-link:hover,html.theme--catppuccin-macchiato .navbar-link.is-active{background-color:rgba(0,0,0,0);color:#8aadf4}html.theme--catppuccin-macchiato .navbar-item{flex-grow:0;flex-shrink:0}html.theme--catppuccin-macchiato .navbar-item img{max-height:1.75rem}html.theme--catppuccin-macchiato .navbar-item.has-dropdown{padding:0}html.theme--catppuccin-macchiato .navbar-item.is-expanded{flex-grow:1;flex-shrink:1}html.theme--catppuccin-macchiato .navbar-item.is-tab{border-bottom:1px solid transparent;min-height:4rem;padding-bottom:calc(0.5rem - 1px)}html.theme--catppuccin-macchiato .navbar-item.is-tab:focus,html.theme--catppuccin-macchiato .navbar-item.is-tab:hover{background-color:rgba(0,0,0,0);border-bottom-color:#8aadf4}html.theme--catppuccin-macchiato .navbar-item.is-tab.is-active{background-color:rgba(0,0,0,0);border-bottom-color:#8aadf4;border-bottom-style:solid;border-bottom-width:3px;color:#8aadf4;padding-bottom:calc(0.5rem - 3px)}html.theme--catppuccin-macchiato .navbar-content{flex-grow:1;flex-shrink:1}html.theme--catppuccin-macchiato .navbar-link:not(.is-arrowless){padding-right:2.5em}html.theme--catppuccin-macchiato .navbar-link:not(.is-arrowless)::after{border-color:#fff;margin-top:-0.375em;right:1.125em}html.theme--catppuccin-macchiato .navbar-dropdown{font-size:0.875rem;padding-bottom:0.5rem;padding-top:0.5rem}html.theme--catppuccin-macchiato .navbar-dropdown .navbar-item{padding-left:1.5rem;padding-right:1.5rem}html.theme--catppuccin-macchiato .navbar-divider{background-color:rgba(0,0,0,0.2);border:none;display:none;height:2px;margin:0.5rem 0}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .navbar>.container{display:block}html.theme--catppuccin-macchiato .navbar-brand .navbar-item,html.theme--catppuccin-macchiato .navbar-tabs .navbar-item{align-items:center;display:flex}html.theme--catppuccin-macchiato .navbar-link::after{display:none}html.theme--catppuccin-macchiato .navbar-menu{background-color:#8aadf4;box-shadow:0 8px 16px rgba(10,10,10,0.1);padding:0.5rem 0}html.theme--catppuccin-macchiato .navbar-menu.is-active{display:block}html.theme--catppuccin-macchiato .navbar.is-fixed-bottom-touch,html.theme--catppuccin-macchiato .navbar.is-fixed-top-touch{left:0;position:fixed;right:0;z-index:30}html.theme--catppuccin-macchiato .navbar.is-fixed-bottom-touch{bottom:0}html.theme--catppuccin-macchiato .navbar.is-fixed-bottom-touch.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,0.1)}html.theme--catppuccin-macchiato .navbar.is-fixed-top-touch{top:0}html.theme--catppuccin-macchiato .navbar.is-fixed-top .navbar-menu,html.theme--catppuccin-macchiato .navbar.is-fixed-top-touch .navbar-menu{-webkit-overflow-scrolling:touch;max-height:calc(100vh - 4rem);overflow:auto}html.theme--catppuccin-macchiato html.has-navbar-fixed-top-touch,html.theme--catppuccin-macchiato body.has-navbar-fixed-top-touch{padding-top:4rem}html.theme--catppuccin-macchiato html.has-navbar-fixed-bottom-touch,html.theme--catppuccin-macchiato body.has-navbar-fixed-bottom-touch{padding-bottom:4rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .navbar,html.theme--catppuccin-macchiato .navbar-menu,html.theme--catppuccin-macchiato .navbar-start,html.theme--catppuccin-macchiato .navbar-end{align-items:stretch;display:flex}html.theme--catppuccin-macchiato .navbar{min-height:4rem}html.theme--catppuccin-macchiato .navbar.is-spaced{padding:1rem 2rem}html.theme--catppuccin-macchiato .navbar.is-spaced .navbar-start,html.theme--catppuccin-macchiato .navbar.is-spaced .navbar-end{align-items:center}html.theme--catppuccin-macchiato .navbar.is-spaced a.navbar-item,html.theme--catppuccin-macchiato .navbar.is-spaced .navbar-link{border-radius:.4em}html.theme--catppuccin-macchiato .navbar.is-transparent a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-transparent a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-transparent a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-transparent .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-transparent .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-transparent .navbar-link.is-active{background-color:transparent !important}html.theme--catppuccin-macchiato .navbar.is-transparent .navbar-item.has-dropdown.is-active .navbar-link,html.theme--catppuccin-macchiato .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus .navbar-link,html.theme--catppuccin-macchiato .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus-within .navbar-link,html.theme--catppuccin-macchiato .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:hover .navbar-link{background-color:transparent !important}html.theme--catppuccin-macchiato .navbar.is-transparent .navbar-dropdown a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-transparent .navbar-dropdown a.navbar-item:hover{background-color:rgba(0,0,0,0);color:#8087a2}html.theme--catppuccin-macchiato .navbar.is-transparent .navbar-dropdown a.navbar-item.is-active{background-color:rgba(0,0,0,0);color:#8aadf4}html.theme--catppuccin-macchiato .navbar-burger{display:none}html.theme--catppuccin-macchiato .navbar-item,html.theme--catppuccin-macchiato .navbar-link{align-items:center;display:flex}html.theme--catppuccin-macchiato .navbar-item.has-dropdown{align-items:stretch}html.theme--catppuccin-macchiato .navbar-item.has-dropdown-up .navbar-link::after{transform:rotate(135deg) translate(0.25em, -0.25em)}html.theme--catppuccin-macchiato .navbar-item.has-dropdown-up .navbar-dropdown{border-bottom:1px solid rgba(0,0,0,0.2);border-radius:8px 8px 0 0;border-top:none;bottom:100%;box-shadow:0 -8px 8px rgba(10,10,10,0.1);top:auto}html.theme--catppuccin-macchiato .navbar-item.is-active .navbar-dropdown,html.theme--catppuccin-macchiato .navbar-item.is-hoverable:focus .navbar-dropdown,html.theme--catppuccin-macchiato .navbar-item.is-hoverable:focus-within .navbar-dropdown,html.theme--catppuccin-macchiato .navbar-item.is-hoverable:hover .navbar-dropdown{display:block}.navbar.is-spaced html.theme--catppuccin-macchiato .navbar-item.is-active .navbar-dropdown,html.theme--catppuccin-macchiato .navbar-item.is-active .navbar-dropdown.is-boxed,.navbar.is-spaced html.theme--catppuccin-macchiato .navbar-item.is-hoverable:focus .navbar-dropdown,html.theme--catppuccin-macchiato .navbar-item.is-hoverable:focus .navbar-dropdown.is-boxed,.navbar.is-spaced html.theme--catppuccin-macchiato .navbar-item.is-hoverable:focus-within .navbar-dropdown,html.theme--catppuccin-macchiato .navbar-item.is-hoverable:focus-within .navbar-dropdown.is-boxed,.navbar.is-spaced html.theme--catppuccin-macchiato .navbar-item.is-hoverable:hover .navbar-dropdown,html.theme--catppuccin-macchiato .navbar-item.is-hoverable:hover .navbar-dropdown.is-boxed{opacity:1;pointer-events:auto;transform:translateY(0)}html.theme--catppuccin-macchiato .navbar-menu{flex-grow:1;flex-shrink:0}html.theme--catppuccin-macchiato .navbar-start{justify-content:flex-start;margin-right:auto}html.theme--catppuccin-macchiato .navbar-end{justify-content:flex-end;margin-left:auto}html.theme--catppuccin-macchiato .navbar-dropdown{background-color:#8aadf4;border-bottom-left-radius:8px;border-bottom-right-radius:8px;border-top:1px solid rgba(0,0,0,0.2);box-shadow:0 8px 8px rgba(10,10,10,0.1);display:none;font-size:0.875rem;left:0;min-width:100%;position:absolute;top:100%;z-index:20}html.theme--catppuccin-macchiato .navbar-dropdown .navbar-item{padding:0.375rem 1rem;white-space:nowrap}html.theme--catppuccin-macchiato .navbar-dropdown a.navbar-item{padding-right:3rem}html.theme--catppuccin-macchiato .navbar-dropdown a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar-dropdown a.navbar-item:hover{background-color:rgba(0,0,0,0);color:#8087a2}html.theme--catppuccin-macchiato .navbar-dropdown a.navbar-item.is-active{background-color:rgba(0,0,0,0);color:#8aadf4}.navbar.is-spaced html.theme--catppuccin-macchiato .navbar-dropdown,html.theme--catppuccin-macchiato .navbar-dropdown.is-boxed{border-radius:8px;border-top:none;box-shadow:0 8px 8px rgba(10,10,10,0.1), 0 0 0 1px rgba(10,10,10,0.1);display:block;opacity:0;pointer-events:none;top:calc(100% + (-4px));transform:translateY(-5px);transition-duration:86ms;transition-property:opacity, transform}html.theme--catppuccin-macchiato .navbar-dropdown.is-right{left:auto;right:0}html.theme--catppuccin-macchiato .navbar-divider{display:block}html.theme--catppuccin-macchiato .navbar>.container .navbar-brand,html.theme--catppuccin-macchiato .container>.navbar .navbar-brand{margin-left:-.75rem}html.theme--catppuccin-macchiato .navbar>.container .navbar-menu,html.theme--catppuccin-macchiato .container>.navbar .navbar-menu{margin-right:-.75rem}html.theme--catppuccin-macchiato .navbar.is-fixed-bottom-desktop,html.theme--catppuccin-macchiato .navbar.is-fixed-top-desktop{left:0;position:fixed;right:0;z-index:30}html.theme--catppuccin-macchiato .navbar.is-fixed-bottom-desktop{bottom:0}html.theme--catppuccin-macchiato .navbar.is-fixed-bottom-desktop.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,0.1)}html.theme--catppuccin-macchiato .navbar.is-fixed-top-desktop{top:0}html.theme--catppuccin-macchiato html.has-navbar-fixed-top-desktop,html.theme--catppuccin-macchiato body.has-navbar-fixed-top-desktop{padding-top:4rem}html.theme--catppuccin-macchiato html.has-navbar-fixed-bottom-desktop,html.theme--catppuccin-macchiato body.has-navbar-fixed-bottom-desktop{padding-bottom:4rem}html.theme--catppuccin-macchiato html.has-spaced-navbar-fixed-top,html.theme--catppuccin-macchiato body.has-spaced-navbar-fixed-top{padding-top:6rem}html.theme--catppuccin-macchiato html.has-spaced-navbar-fixed-bottom,html.theme--catppuccin-macchiato body.has-spaced-navbar-fixed-bottom{padding-bottom:6rem}html.theme--catppuccin-macchiato a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar-link.is-active{color:#8aadf4}html.theme--catppuccin-macchiato a.navbar-item.is-active:not(:focus):not(:hover),html.theme--catppuccin-macchiato .navbar-link.is-active:not(:focus):not(:hover){background-color:rgba(0,0,0,0)}html.theme--catppuccin-macchiato .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-macchiato .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-macchiato .navbar-item.has-dropdown.is-active .navbar-link{background-color:rgba(0,0,0,0)}}html.theme--catppuccin-macchiato .hero.is-fullheight-with-navbar{min-height:calc(100vh - 4rem)}html.theme--catppuccin-macchiato .pagination{font-size:1rem;margin:-.25rem}html.theme--catppuccin-macchiato .pagination.is-small,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.pagination{font-size:.75rem}html.theme--catppuccin-macchiato .pagination.is-medium{font-size:1.25rem}html.theme--catppuccin-macchiato .pagination.is-large{font-size:1.5rem}html.theme--catppuccin-macchiato .pagination.is-rounded .pagination-previous,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.pagination .pagination-previous,html.theme--catppuccin-macchiato .pagination.is-rounded .pagination-next,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.pagination .pagination-next{padding-left:1em;padding-right:1em;border-radius:9999px}html.theme--catppuccin-macchiato .pagination.is-rounded .pagination-link,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.pagination .pagination-link{border-radius:9999px}html.theme--catppuccin-macchiato .pagination,html.theme--catppuccin-macchiato .pagination-list{align-items:center;display:flex;justify-content:center;text-align:center}html.theme--catppuccin-macchiato .pagination-previous,html.theme--catppuccin-macchiato .pagination-next,html.theme--catppuccin-macchiato .pagination-link,html.theme--catppuccin-macchiato .pagination-ellipsis{font-size:1em;justify-content:center;margin:.25rem;padding-left:.5em;padding-right:.5em;text-align:center}html.theme--catppuccin-macchiato .pagination-previous,html.theme--catppuccin-macchiato .pagination-next,html.theme--catppuccin-macchiato .pagination-link{border-color:#5b6078;color:#8aadf4;min-width:2.5em}html.theme--catppuccin-macchiato .pagination-previous:hover,html.theme--catppuccin-macchiato .pagination-next:hover,html.theme--catppuccin-macchiato .pagination-link:hover{border-color:#6e738d;color:#91d7e3}html.theme--catppuccin-macchiato .pagination-previous:focus,html.theme--catppuccin-macchiato .pagination-next:focus,html.theme--catppuccin-macchiato .pagination-link:focus{border-color:#6e738d}html.theme--catppuccin-macchiato .pagination-previous:active,html.theme--catppuccin-macchiato .pagination-next:active,html.theme--catppuccin-macchiato .pagination-link:active{box-shadow:inset 0 1px 2px rgba(10,10,10,0.2)}html.theme--catppuccin-macchiato .pagination-previous[disabled],html.theme--catppuccin-macchiato .pagination-previous.is-disabled,html.theme--catppuccin-macchiato .pagination-next[disabled],html.theme--catppuccin-macchiato .pagination-next.is-disabled,html.theme--catppuccin-macchiato .pagination-link[disabled],html.theme--catppuccin-macchiato .pagination-link.is-disabled{background-color:#5b6078;border-color:#5b6078;box-shadow:none;color:#f5f7fd;opacity:0.5}html.theme--catppuccin-macchiato .pagination-previous,html.theme--catppuccin-macchiato .pagination-next{padding-left:.75em;padding-right:.75em;white-space:nowrap}html.theme--catppuccin-macchiato .pagination-link.is-current{background-color:#8aadf4;border-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .pagination-ellipsis{color:#6e738d;pointer-events:none}html.theme--catppuccin-macchiato .pagination-list{flex-wrap:wrap}html.theme--catppuccin-macchiato .pagination-list li{list-style:none}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .pagination{flex-wrap:wrap}html.theme--catppuccin-macchiato .pagination-previous,html.theme--catppuccin-macchiato .pagination-next{flex-grow:1;flex-shrink:1}html.theme--catppuccin-macchiato .pagination-list li{flex-grow:1;flex-shrink:1}}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .pagination-list{flex-grow:1;flex-shrink:1;justify-content:flex-start;order:1}html.theme--catppuccin-macchiato .pagination-previous,html.theme--catppuccin-macchiato .pagination-next,html.theme--catppuccin-macchiato .pagination-link,html.theme--catppuccin-macchiato .pagination-ellipsis{margin-bottom:0;margin-top:0}html.theme--catppuccin-macchiato .pagination-previous{order:2}html.theme--catppuccin-macchiato .pagination-next{order:3}html.theme--catppuccin-macchiato .pagination{justify-content:space-between;margin-bottom:0;margin-top:0}html.theme--catppuccin-macchiato .pagination.is-centered .pagination-previous{order:1}html.theme--catppuccin-macchiato .pagination.is-centered .pagination-list{justify-content:center;order:2}html.theme--catppuccin-macchiato .pagination.is-centered .pagination-next{order:3}html.theme--catppuccin-macchiato .pagination.is-right .pagination-previous{order:1}html.theme--catppuccin-macchiato .pagination.is-right .pagination-next{order:2}html.theme--catppuccin-macchiato .pagination.is-right .pagination-list{justify-content:flex-end;order:3}}html.theme--catppuccin-macchiato .panel{border-radius:8px;box-shadow:#171717;font-size:1rem}html.theme--catppuccin-macchiato .panel:not(:last-child){margin-bottom:1.5rem}html.theme--catppuccin-macchiato .panel.is-white .panel-heading{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-macchiato .panel.is-white .panel-tabs a.is-active{border-bottom-color:#fff}html.theme--catppuccin-macchiato .panel.is-white .panel-block.is-active .panel-icon{color:#fff}html.theme--catppuccin-macchiato .panel.is-black .panel-heading{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-macchiato .panel.is-black .panel-tabs a.is-active{border-bottom-color:#0a0a0a}html.theme--catppuccin-macchiato .panel.is-black .panel-block.is-active .panel-icon{color:#0a0a0a}html.theme--catppuccin-macchiato .panel.is-light .panel-heading{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .panel.is-light .panel-tabs a.is-active{border-bottom-color:#f5f5f5}html.theme--catppuccin-macchiato .panel.is-light .panel-block.is-active .panel-icon{color:#f5f5f5}html.theme--catppuccin-macchiato .panel.is-dark .panel-heading,html.theme--catppuccin-macchiato .content kbd.panel .panel-heading{background-color:#363a4f;color:#fff}html.theme--catppuccin-macchiato .panel.is-dark .panel-tabs a.is-active,html.theme--catppuccin-macchiato .content kbd.panel .panel-tabs a.is-active{border-bottom-color:#363a4f}html.theme--catppuccin-macchiato .panel.is-dark .panel-block.is-active .panel-icon,html.theme--catppuccin-macchiato .content kbd.panel .panel-block.is-active .panel-icon{color:#363a4f}html.theme--catppuccin-macchiato .panel.is-primary .panel-heading,html.theme--catppuccin-macchiato .docstring>section>a.panel.docs-sourcelink .panel-heading{background-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .panel.is-primary .panel-tabs a.is-active,html.theme--catppuccin-macchiato .docstring>section>a.panel.docs-sourcelink .panel-tabs a.is-active{border-bottom-color:#8aadf4}html.theme--catppuccin-macchiato .panel.is-primary .panel-block.is-active .panel-icon,html.theme--catppuccin-macchiato .docstring>section>a.panel.docs-sourcelink .panel-block.is-active .panel-icon{color:#8aadf4}html.theme--catppuccin-macchiato .panel.is-link .panel-heading{background-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .panel.is-link .panel-tabs a.is-active{border-bottom-color:#8aadf4}html.theme--catppuccin-macchiato .panel.is-link .panel-block.is-active .panel-icon{color:#8aadf4}html.theme--catppuccin-macchiato .panel.is-info .panel-heading{background-color:#8bd5ca;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .panel.is-info .panel-tabs a.is-active{border-bottom-color:#8bd5ca}html.theme--catppuccin-macchiato .panel.is-info .panel-block.is-active .panel-icon{color:#8bd5ca}html.theme--catppuccin-macchiato .panel.is-success .panel-heading{background-color:#a6da95;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .panel.is-success .panel-tabs a.is-active{border-bottom-color:#a6da95}html.theme--catppuccin-macchiato .panel.is-success .panel-block.is-active .panel-icon{color:#a6da95}html.theme--catppuccin-macchiato .panel.is-warning .panel-heading{background-color:#eed49f;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .panel.is-warning .panel-tabs a.is-active{border-bottom-color:#eed49f}html.theme--catppuccin-macchiato .panel.is-warning .panel-block.is-active .panel-icon{color:#eed49f}html.theme--catppuccin-macchiato .panel.is-danger .panel-heading{background-color:#ed8796;color:#fff}html.theme--catppuccin-macchiato .panel.is-danger .panel-tabs a.is-active{border-bottom-color:#ed8796}html.theme--catppuccin-macchiato .panel.is-danger .panel-block.is-active .panel-icon{color:#ed8796}html.theme--catppuccin-macchiato .panel-tabs:not(:last-child),html.theme--catppuccin-macchiato .panel-block:not(:last-child){border-bottom:1px solid #ededed}html.theme--catppuccin-macchiato .panel-heading{background-color:#494d64;border-radius:8px 8px 0 0;color:#b5c1f1;font-size:1.25em;font-weight:700;line-height:1.25;padding:0.75em 1em}html.theme--catppuccin-macchiato .panel-tabs{align-items:flex-end;display:flex;font-size:.875em;justify-content:center}html.theme--catppuccin-macchiato .panel-tabs a{border-bottom:1px solid #5b6078;margin-bottom:-1px;padding:0.5em}html.theme--catppuccin-macchiato .panel-tabs a.is-active{border-bottom-color:#494d64;color:#739df2}html.theme--catppuccin-macchiato .panel-list a{color:#cad3f5}html.theme--catppuccin-macchiato .panel-list a:hover{color:#8aadf4}html.theme--catppuccin-macchiato .panel-block{align-items:center;color:#b5c1f1;display:flex;justify-content:flex-start;padding:0.5em 0.75em}html.theme--catppuccin-macchiato .panel-block input[type="checkbox"]{margin-right:.75em}html.theme--catppuccin-macchiato .panel-block>.control{flex-grow:1;flex-shrink:1;width:100%}html.theme--catppuccin-macchiato .panel-block.is-wrapped{flex-wrap:wrap}html.theme--catppuccin-macchiato .panel-block.is-active{border-left-color:#8aadf4;color:#739df2}html.theme--catppuccin-macchiato .panel-block.is-active .panel-icon{color:#8aadf4}html.theme--catppuccin-macchiato .panel-block:last-child{border-bottom-left-radius:8px;border-bottom-right-radius:8px}html.theme--catppuccin-macchiato a.panel-block,html.theme--catppuccin-macchiato label.panel-block{cursor:pointer}html.theme--catppuccin-macchiato a.panel-block:hover,html.theme--catppuccin-macchiato label.panel-block:hover{background-color:#1e2030}html.theme--catppuccin-macchiato .panel-icon{display:inline-block;font-size:14px;height:1em;line-height:1em;text-align:center;vertical-align:top;width:1em;color:#f5f7fd;margin-right:.75em}html.theme--catppuccin-macchiato .panel-icon .fa{font-size:inherit;line-height:inherit}html.theme--catppuccin-macchiato .tabs{-webkit-overflow-scrolling:touch;align-items:stretch;display:flex;font-size:1rem;justify-content:space-between;overflow:hidden;overflow-x:auto;white-space:nowrap}html.theme--catppuccin-macchiato .tabs a{align-items:center;border-bottom-color:#5b6078;border-bottom-style:solid;border-bottom-width:1px;color:#cad3f5;display:flex;justify-content:center;margin-bottom:-1px;padding:0.5em 1em;vertical-align:top}html.theme--catppuccin-macchiato .tabs a:hover{border-bottom-color:#b5c1f1;color:#b5c1f1}html.theme--catppuccin-macchiato .tabs li{display:block}html.theme--catppuccin-macchiato .tabs li.is-active a{border-bottom-color:#8aadf4;color:#8aadf4}html.theme--catppuccin-macchiato .tabs ul{align-items:center;border-bottom-color:#5b6078;border-bottom-style:solid;border-bottom-width:1px;display:flex;flex-grow:1;flex-shrink:0;justify-content:flex-start}html.theme--catppuccin-macchiato .tabs ul.is-left{padding-right:0.75em}html.theme--catppuccin-macchiato .tabs ul.is-center{flex:none;justify-content:center;padding-left:0.75em;padding-right:0.75em}html.theme--catppuccin-macchiato .tabs ul.is-right{justify-content:flex-end;padding-left:0.75em}html.theme--catppuccin-macchiato .tabs .icon:first-child{margin-right:.5em}html.theme--catppuccin-macchiato .tabs .icon:last-child{margin-left:.5em}html.theme--catppuccin-macchiato .tabs.is-centered ul{justify-content:center}html.theme--catppuccin-macchiato .tabs.is-right ul{justify-content:flex-end}html.theme--catppuccin-macchiato .tabs.is-boxed a{border:1px solid transparent;border-radius:.4em .4em 0 0}html.theme--catppuccin-macchiato .tabs.is-boxed a:hover{background-color:#1e2030;border-bottom-color:#5b6078}html.theme--catppuccin-macchiato .tabs.is-boxed li.is-active a{background-color:#fff;border-color:#5b6078;border-bottom-color:rgba(0,0,0,0) !important}html.theme--catppuccin-macchiato .tabs.is-fullwidth li{flex-grow:1;flex-shrink:0}html.theme--catppuccin-macchiato .tabs.is-toggle a{border-color:#5b6078;border-style:solid;border-width:1px;margin-bottom:0;position:relative}html.theme--catppuccin-macchiato .tabs.is-toggle a:hover{background-color:#1e2030;border-color:#6e738d;z-index:2}html.theme--catppuccin-macchiato .tabs.is-toggle li+li{margin-left:-1px}html.theme--catppuccin-macchiato .tabs.is-toggle li:first-child a{border-top-left-radius:.4em;border-bottom-left-radius:.4em}html.theme--catppuccin-macchiato .tabs.is-toggle li:last-child a{border-top-right-radius:.4em;border-bottom-right-radius:.4em}html.theme--catppuccin-macchiato .tabs.is-toggle li.is-active a{background-color:#8aadf4;border-color:#8aadf4;color:#fff;z-index:1}html.theme--catppuccin-macchiato .tabs.is-toggle ul{border-bottom:none}html.theme--catppuccin-macchiato .tabs.is-toggle.is-toggle-rounded li:first-child a{border-bottom-left-radius:9999px;border-top-left-radius:9999px;padding-left:1.25em}html.theme--catppuccin-macchiato .tabs.is-toggle.is-toggle-rounded li:last-child a{border-bottom-right-radius:9999px;border-top-right-radius:9999px;padding-right:1.25em}html.theme--catppuccin-macchiato .tabs.is-small,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.tabs{font-size:.75rem}html.theme--catppuccin-macchiato .tabs.is-medium{font-size:1.25rem}html.theme--catppuccin-macchiato .tabs.is-large{font-size:1.5rem}html.theme--catppuccin-macchiato .column{display:block;flex-basis:0;flex-grow:1;flex-shrink:1;padding:.75rem}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-narrow{flex:none;width:unset}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-full{flex:none;width:100%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-three-quarters{flex:none;width:75%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-two-thirds{flex:none;width:66.6666%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-half{flex:none;width:50%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-one-third{flex:none;width:33.3333%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-one-quarter{flex:none;width:25%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-one-fifth{flex:none;width:20%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-two-fifths{flex:none;width:40%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-three-fifths{flex:none;width:60%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-four-fifths{flex:none;width:80%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-three-quarters{margin-left:75%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-two-thirds{margin-left:66.6666%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-half{margin-left:50%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-one-third{margin-left:33.3333%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-one-quarter{margin-left:25%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-one-fifth{margin-left:20%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-two-fifths{margin-left:40%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-three-fifths{margin-left:60%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-four-fifths{margin-left:80%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-0{flex:none;width:0%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-0{margin-left:0%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-1{flex:none;width:8.33333337%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-1{margin-left:8.33333337%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-2{flex:none;width:16.66666674%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-2{margin-left:16.66666674%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-3{flex:none;width:25%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-3{margin-left:25%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-4{flex:none;width:33.33333337%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-4{margin-left:33.33333337%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-5{flex:none;width:41.66666674%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-5{margin-left:41.66666674%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-6{flex:none;width:50%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-6{margin-left:50%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-7{flex:none;width:58.33333337%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-7{margin-left:58.33333337%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-8{flex:none;width:66.66666674%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-8{margin-left:66.66666674%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-9{flex:none;width:75%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-9{margin-left:75%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-10{flex:none;width:83.33333337%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-10{margin-left:83.33333337%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-11{flex:none;width:91.66666674%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-11{margin-left:91.66666674%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-12{flex:none;width:100%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-12{margin-left:100%}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .column.is-narrow-mobile{flex:none;width:unset}html.theme--catppuccin-macchiato .column.is-full-mobile{flex:none;width:100%}html.theme--catppuccin-macchiato .column.is-three-quarters-mobile{flex:none;width:75%}html.theme--catppuccin-macchiato .column.is-two-thirds-mobile{flex:none;width:66.6666%}html.theme--catppuccin-macchiato .column.is-half-mobile{flex:none;width:50%}html.theme--catppuccin-macchiato .column.is-one-third-mobile{flex:none;width:33.3333%}html.theme--catppuccin-macchiato .column.is-one-quarter-mobile{flex:none;width:25%}html.theme--catppuccin-macchiato .column.is-one-fifth-mobile{flex:none;width:20%}html.theme--catppuccin-macchiato .column.is-two-fifths-mobile{flex:none;width:40%}html.theme--catppuccin-macchiato .column.is-three-fifths-mobile{flex:none;width:60%}html.theme--catppuccin-macchiato .column.is-four-fifths-mobile{flex:none;width:80%}html.theme--catppuccin-macchiato .column.is-offset-three-quarters-mobile{margin-left:75%}html.theme--catppuccin-macchiato .column.is-offset-two-thirds-mobile{margin-left:66.6666%}html.theme--catppuccin-macchiato .column.is-offset-half-mobile{margin-left:50%}html.theme--catppuccin-macchiato .column.is-offset-one-third-mobile{margin-left:33.3333%}html.theme--catppuccin-macchiato .column.is-offset-one-quarter-mobile{margin-left:25%}html.theme--catppuccin-macchiato .column.is-offset-one-fifth-mobile{margin-left:20%}html.theme--catppuccin-macchiato .column.is-offset-two-fifths-mobile{margin-left:40%}html.theme--catppuccin-macchiato .column.is-offset-three-fifths-mobile{margin-left:60%}html.theme--catppuccin-macchiato .column.is-offset-four-fifths-mobile{margin-left:80%}html.theme--catppuccin-macchiato .column.is-0-mobile{flex:none;width:0%}html.theme--catppuccin-macchiato .column.is-offset-0-mobile{margin-left:0%}html.theme--catppuccin-macchiato .column.is-1-mobile{flex:none;width:8.33333337%}html.theme--catppuccin-macchiato .column.is-offset-1-mobile{margin-left:8.33333337%}html.theme--catppuccin-macchiato .column.is-2-mobile{flex:none;width:16.66666674%}html.theme--catppuccin-macchiato .column.is-offset-2-mobile{margin-left:16.66666674%}html.theme--catppuccin-macchiato .column.is-3-mobile{flex:none;width:25%}html.theme--catppuccin-macchiato .column.is-offset-3-mobile{margin-left:25%}html.theme--catppuccin-macchiato .column.is-4-mobile{flex:none;width:33.33333337%}html.theme--catppuccin-macchiato .column.is-offset-4-mobile{margin-left:33.33333337%}html.theme--catppuccin-macchiato .column.is-5-mobile{flex:none;width:41.66666674%}html.theme--catppuccin-macchiato .column.is-offset-5-mobile{margin-left:41.66666674%}html.theme--catppuccin-macchiato .column.is-6-mobile{flex:none;width:50%}html.theme--catppuccin-macchiato .column.is-offset-6-mobile{margin-left:50%}html.theme--catppuccin-macchiato .column.is-7-mobile{flex:none;width:58.33333337%}html.theme--catppuccin-macchiato .column.is-offset-7-mobile{margin-left:58.33333337%}html.theme--catppuccin-macchiato .column.is-8-mobile{flex:none;width:66.66666674%}html.theme--catppuccin-macchiato .column.is-offset-8-mobile{margin-left:66.66666674%}html.theme--catppuccin-macchiato .column.is-9-mobile{flex:none;width:75%}html.theme--catppuccin-macchiato .column.is-offset-9-mobile{margin-left:75%}html.theme--catppuccin-macchiato .column.is-10-mobile{flex:none;width:83.33333337%}html.theme--catppuccin-macchiato .column.is-offset-10-mobile{margin-left:83.33333337%}html.theme--catppuccin-macchiato .column.is-11-mobile{flex:none;width:91.66666674%}html.theme--catppuccin-macchiato .column.is-offset-11-mobile{margin-left:91.66666674%}html.theme--catppuccin-macchiato .column.is-12-mobile{flex:none;width:100%}html.theme--catppuccin-macchiato .column.is-offset-12-mobile{margin-left:100%}}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .column.is-narrow,html.theme--catppuccin-macchiato .column.is-narrow-tablet{flex:none;width:unset}html.theme--catppuccin-macchiato .column.is-full,html.theme--catppuccin-macchiato .column.is-full-tablet{flex:none;width:100%}html.theme--catppuccin-macchiato .column.is-three-quarters,html.theme--catppuccin-macchiato .column.is-three-quarters-tablet{flex:none;width:75%}html.theme--catppuccin-macchiato .column.is-two-thirds,html.theme--catppuccin-macchiato .column.is-two-thirds-tablet{flex:none;width:66.6666%}html.theme--catppuccin-macchiato .column.is-half,html.theme--catppuccin-macchiato .column.is-half-tablet{flex:none;width:50%}html.theme--catppuccin-macchiato .column.is-one-third,html.theme--catppuccin-macchiato .column.is-one-third-tablet{flex:none;width:33.3333%}html.theme--catppuccin-macchiato .column.is-one-quarter,html.theme--catppuccin-macchiato .column.is-one-quarter-tablet{flex:none;width:25%}html.theme--catppuccin-macchiato .column.is-one-fifth,html.theme--catppuccin-macchiato .column.is-one-fifth-tablet{flex:none;width:20%}html.theme--catppuccin-macchiato .column.is-two-fifths,html.theme--catppuccin-macchiato .column.is-two-fifths-tablet{flex:none;width:40%}html.theme--catppuccin-macchiato .column.is-three-fifths,html.theme--catppuccin-macchiato .column.is-three-fifths-tablet{flex:none;width:60%}html.theme--catppuccin-macchiato .column.is-four-fifths,html.theme--catppuccin-macchiato .column.is-four-fifths-tablet{flex:none;width:80%}html.theme--catppuccin-macchiato .column.is-offset-three-quarters,html.theme--catppuccin-macchiato .column.is-offset-three-quarters-tablet{margin-left:75%}html.theme--catppuccin-macchiato .column.is-offset-two-thirds,html.theme--catppuccin-macchiato .column.is-offset-two-thirds-tablet{margin-left:66.6666%}html.theme--catppuccin-macchiato .column.is-offset-half,html.theme--catppuccin-macchiato .column.is-offset-half-tablet{margin-left:50%}html.theme--catppuccin-macchiato .column.is-offset-one-third,html.theme--catppuccin-macchiato .column.is-offset-one-third-tablet{margin-left:33.3333%}html.theme--catppuccin-macchiato .column.is-offset-one-quarter,html.theme--catppuccin-macchiato .column.is-offset-one-quarter-tablet{margin-left:25%}html.theme--catppuccin-macchiato .column.is-offset-one-fifth,html.theme--catppuccin-macchiato .column.is-offset-one-fifth-tablet{margin-left:20%}html.theme--catppuccin-macchiato .column.is-offset-two-fifths,html.theme--catppuccin-macchiato .column.is-offset-two-fifths-tablet{margin-left:40%}html.theme--catppuccin-macchiato .column.is-offset-three-fifths,html.theme--catppuccin-macchiato .column.is-offset-three-fifths-tablet{margin-left:60%}html.theme--catppuccin-macchiato .column.is-offset-four-fifths,html.theme--catppuccin-macchiato .column.is-offset-four-fifths-tablet{margin-left:80%}html.theme--catppuccin-macchiato .column.is-0,html.theme--catppuccin-macchiato .column.is-0-tablet{flex:none;width:0%}html.theme--catppuccin-macchiato .column.is-offset-0,html.theme--catppuccin-macchiato .column.is-offset-0-tablet{margin-left:0%}html.theme--catppuccin-macchiato .column.is-1,html.theme--catppuccin-macchiato .column.is-1-tablet{flex:none;width:8.33333337%}html.theme--catppuccin-macchiato .column.is-offset-1,html.theme--catppuccin-macchiato .column.is-offset-1-tablet{margin-left:8.33333337%}html.theme--catppuccin-macchiato .column.is-2,html.theme--catppuccin-macchiato .column.is-2-tablet{flex:none;width:16.66666674%}html.theme--catppuccin-macchiato .column.is-offset-2,html.theme--catppuccin-macchiato .column.is-offset-2-tablet{margin-left:16.66666674%}html.theme--catppuccin-macchiato .column.is-3,html.theme--catppuccin-macchiato .column.is-3-tablet{flex:none;width:25%}html.theme--catppuccin-macchiato .column.is-offset-3,html.theme--catppuccin-macchiato .column.is-offset-3-tablet{margin-left:25%}html.theme--catppuccin-macchiato .column.is-4,html.theme--catppuccin-macchiato .column.is-4-tablet{flex:none;width:33.33333337%}html.theme--catppuccin-macchiato .column.is-offset-4,html.theme--catppuccin-macchiato .column.is-offset-4-tablet{margin-left:33.33333337%}html.theme--catppuccin-macchiato .column.is-5,html.theme--catppuccin-macchiato .column.is-5-tablet{flex:none;width:41.66666674%}html.theme--catppuccin-macchiato .column.is-offset-5,html.theme--catppuccin-macchiato .column.is-offset-5-tablet{margin-left:41.66666674%}html.theme--catppuccin-macchiato .column.is-6,html.theme--catppuccin-macchiato .column.is-6-tablet{flex:none;width:50%}html.theme--catppuccin-macchiato .column.is-offset-6,html.theme--catppuccin-macchiato .column.is-offset-6-tablet{margin-left:50%}html.theme--catppuccin-macchiato .column.is-7,html.theme--catppuccin-macchiato .column.is-7-tablet{flex:none;width:58.33333337%}html.theme--catppuccin-macchiato .column.is-offset-7,html.theme--catppuccin-macchiato .column.is-offset-7-tablet{margin-left:58.33333337%}html.theme--catppuccin-macchiato .column.is-8,html.theme--catppuccin-macchiato .column.is-8-tablet{flex:none;width:66.66666674%}html.theme--catppuccin-macchiato .column.is-offset-8,html.theme--catppuccin-macchiato .column.is-offset-8-tablet{margin-left:66.66666674%}html.theme--catppuccin-macchiato .column.is-9,html.theme--catppuccin-macchiato .column.is-9-tablet{flex:none;width:75%}html.theme--catppuccin-macchiato .column.is-offset-9,html.theme--catppuccin-macchiato .column.is-offset-9-tablet{margin-left:75%}html.theme--catppuccin-macchiato .column.is-10,html.theme--catppuccin-macchiato .column.is-10-tablet{flex:none;width:83.33333337%}html.theme--catppuccin-macchiato .column.is-offset-10,html.theme--catppuccin-macchiato .column.is-offset-10-tablet{margin-left:83.33333337%}html.theme--catppuccin-macchiato .column.is-11,html.theme--catppuccin-macchiato .column.is-11-tablet{flex:none;width:91.66666674%}html.theme--catppuccin-macchiato .column.is-offset-11,html.theme--catppuccin-macchiato .column.is-offset-11-tablet{margin-left:91.66666674%}html.theme--catppuccin-macchiato .column.is-12,html.theme--catppuccin-macchiato .column.is-12-tablet{flex:none;width:100%}html.theme--catppuccin-macchiato .column.is-offset-12,html.theme--catppuccin-macchiato .column.is-offset-12-tablet{margin-left:100%}}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .column.is-narrow-touch{flex:none;width:unset}html.theme--catppuccin-macchiato .column.is-full-touch{flex:none;width:100%}html.theme--catppuccin-macchiato .column.is-three-quarters-touch{flex:none;width:75%}html.theme--catppuccin-macchiato .column.is-two-thirds-touch{flex:none;width:66.6666%}html.theme--catppuccin-macchiato .column.is-half-touch{flex:none;width:50%}html.theme--catppuccin-macchiato .column.is-one-third-touch{flex:none;width:33.3333%}html.theme--catppuccin-macchiato .column.is-one-quarter-touch{flex:none;width:25%}html.theme--catppuccin-macchiato .column.is-one-fifth-touch{flex:none;width:20%}html.theme--catppuccin-macchiato .column.is-two-fifths-touch{flex:none;width:40%}html.theme--catppuccin-macchiato .column.is-three-fifths-touch{flex:none;width:60%}html.theme--catppuccin-macchiato .column.is-four-fifths-touch{flex:none;width:80%}html.theme--catppuccin-macchiato .column.is-offset-three-quarters-touch{margin-left:75%}html.theme--catppuccin-macchiato .column.is-offset-two-thirds-touch{margin-left:66.6666%}html.theme--catppuccin-macchiato .column.is-offset-half-touch{margin-left:50%}html.theme--catppuccin-macchiato .column.is-offset-one-third-touch{margin-left:33.3333%}html.theme--catppuccin-macchiato .column.is-offset-one-quarter-touch{margin-left:25%}html.theme--catppuccin-macchiato .column.is-offset-one-fifth-touch{margin-left:20%}html.theme--catppuccin-macchiato .column.is-offset-two-fifths-touch{margin-left:40%}html.theme--catppuccin-macchiato .column.is-offset-three-fifths-touch{margin-left:60%}html.theme--catppuccin-macchiato .column.is-offset-four-fifths-touch{margin-left:80%}html.theme--catppuccin-macchiato .column.is-0-touch{flex:none;width:0%}html.theme--catppuccin-macchiato .column.is-offset-0-touch{margin-left:0%}html.theme--catppuccin-macchiato .column.is-1-touch{flex:none;width:8.33333337%}html.theme--catppuccin-macchiato .column.is-offset-1-touch{margin-left:8.33333337%}html.theme--catppuccin-macchiato .column.is-2-touch{flex:none;width:16.66666674%}html.theme--catppuccin-macchiato .column.is-offset-2-touch{margin-left:16.66666674%}html.theme--catppuccin-macchiato .column.is-3-touch{flex:none;width:25%}html.theme--catppuccin-macchiato .column.is-offset-3-touch{margin-left:25%}html.theme--catppuccin-macchiato .column.is-4-touch{flex:none;width:33.33333337%}html.theme--catppuccin-macchiato .column.is-offset-4-touch{margin-left:33.33333337%}html.theme--catppuccin-macchiato .column.is-5-touch{flex:none;width:41.66666674%}html.theme--catppuccin-macchiato .column.is-offset-5-touch{margin-left:41.66666674%}html.theme--catppuccin-macchiato .column.is-6-touch{flex:none;width:50%}html.theme--catppuccin-macchiato .column.is-offset-6-touch{margin-left:50%}html.theme--catppuccin-macchiato .column.is-7-touch{flex:none;width:58.33333337%}html.theme--catppuccin-macchiato .column.is-offset-7-touch{margin-left:58.33333337%}html.theme--catppuccin-macchiato .column.is-8-touch{flex:none;width:66.66666674%}html.theme--catppuccin-macchiato .column.is-offset-8-touch{margin-left:66.66666674%}html.theme--catppuccin-macchiato .column.is-9-touch{flex:none;width:75%}html.theme--catppuccin-macchiato .column.is-offset-9-touch{margin-left:75%}html.theme--catppuccin-macchiato .column.is-10-touch{flex:none;width:83.33333337%}html.theme--catppuccin-macchiato .column.is-offset-10-touch{margin-left:83.33333337%}html.theme--catppuccin-macchiato .column.is-11-touch{flex:none;width:91.66666674%}html.theme--catppuccin-macchiato .column.is-offset-11-touch{margin-left:91.66666674%}html.theme--catppuccin-macchiato .column.is-12-touch{flex:none;width:100%}html.theme--catppuccin-macchiato .column.is-offset-12-touch{margin-left:100%}}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .column.is-narrow-desktop{flex:none;width:unset}html.theme--catppuccin-macchiato .column.is-full-desktop{flex:none;width:100%}html.theme--catppuccin-macchiato .column.is-three-quarters-desktop{flex:none;width:75%}html.theme--catppuccin-macchiato .column.is-two-thirds-desktop{flex:none;width:66.6666%}html.theme--catppuccin-macchiato .column.is-half-desktop{flex:none;width:50%}html.theme--catppuccin-macchiato .column.is-one-third-desktop{flex:none;width:33.3333%}html.theme--catppuccin-macchiato .column.is-one-quarter-desktop{flex:none;width:25%}html.theme--catppuccin-macchiato .column.is-one-fifth-desktop{flex:none;width:20%}html.theme--catppuccin-macchiato .column.is-two-fifths-desktop{flex:none;width:40%}html.theme--catppuccin-macchiato .column.is-three-fifths-desktop{flex:none;width:60%}html.theme--catppuccin-macchiato .column.is-four-fifths-desktop{flex:none;width:80%}html.theme--catppuccin-macchiato .column.is-offset-three-quarters-desktop{margin-left:75%}html.theme--catppuccin-macchiato .column.is-offset-two-thirds-desktop{margin-left:66.6666%}html.theme--catppuccin-macchiato .column.is-offset-half-desktop{margin-left:50%}html.theme--catppuccin-macchiato .column.is-offset-one-third-desktop{margin-left:33.3333%}html.theme--catppuccin-macchiato .column.is-offset-one-quarter-desktop{margin-left:25%}html.theme--catppuccin-macchiato .column.is-offset-one-fifth-desktop{margin-left:20%}html.theme--catppuccin-macchiato .column.is-offset-two-fifths-desktop{margin-left:40%}html.theme--catppuccin-macchiato .column.is-offset-three-fifths-desktop{margin-left:60%}html.theme--catppuccin-macchiato .column.is-offset-four-fifths-desktop{margin-left:80%}html.theme--catppuccin-macchiato .column.is-0-desktop{flex:none;width:0%}html.theme--catppuccin-macchiato .column.is-offset-0-desktop{margin-left:0%}html.theme--catppuccin-macchiato .column.is-1-desktop{flex:none;width:8.33333337%}html.theme--catppuccin-macchiato .column.is-offset-1-desktop{margin-left:8.33333337%}html.theme--catppuccin-macchiato .column.is-2-desktop{flex:none;width:16.66666674%}html.theme--catppuccin-macchiato .column.is-offset-2-desktop{margin-left:16.66666674%}html.theme--catppuccin-macchiato .column.is-3-desktop{flex:none;width:25%}html.theme--catppuccin-macchiato .column.is-offset-3-desktop{margin-left:25%}html.theme--catppuccin-macchiato .column.is-4-desktop{flex:none;width:33.33333337%}html.theme--catppuccin-macchiato .column.is-offset-4-desktop{margin-left:33.33333337%}html.theme--catppuccin-macchiato .column.is-5-desktop{flex:none;width:41.66666674%}html.theme--catppuccin-macchiato .column.is-offset-5-desktop{margin-left:41.66666674%}html.theme--catppuccin-macchiato .column.is-6-desktop{flex:none;width:50%}html.theme--catppuccin-macchiato .column.is-offset-6-desktop{margin-left:50%}html.theme--catppuccin-macchiato .column.is-7-desktop{flex:none;width:58.33333337%}html.theme--catppuccin-macchiato .column.is-offset-7-desktop{margin-left:58.33333337%}html.theme--catppuccin-macchiato .column.is-8-desktop{flex:none;width:66.66666674%}html.theme--catppuccin-macchiato .column.is-offset-8-desktop{margin-left:66.66666674%}html.theme--catppuccin-macchiato .column.is-9-desktop{flex:none;width:75%}html.theme--catppuccin-macchiato .column.is-offset-9-desktop{margin-left:75%}html.theme--catppuccin-macchiato .column.is-10-desktop{flex:none;width:83.33333337%}html.theme--catppuccin-macchiato .column.is-offset-10-desktop{margin-left:83.33333337%}html.theme--catppuccin-macchiato .column.is-11-desktop{flex:none;width:91.66666674%}html.theme--catppuccin-macchiato .column.is-offset-11-desktop{margin-left:91.66666674%}html.theme--catppuccin-macchiato .column.is-12-desktop{flex:none;width:100%}html.theme--catppuccin-macchiato .column.is-offset-12-desktop{margin-left:100%}}@media screen and (min-width: 1216px){html.theme--catppuccin-macchiato .column.is-narrow-widescreen{flex:none;width:unset}html.theme--catppuccin-macchiato .column.is-full-widescreen{flex:none;width:100%}html.theme--catppuccin-macchiato .column.is-three-quarters-widescreen{flex:none;width:75%}html.theme--catppuccin-macchiato .column.is-two-thirds-widescreen{flex:none;width:66.6666%}html.theme--catppuccin-macchiato .column.is-half-widescreen{flex:none;width:50%}html.theme--catppuccin-macchiato .column.is-one-third-widescreen{flex:none;width:33.3333%}html.theme--catppuccin-macchiato .column.is-one-quarter-widescreen{flex:none;width:25%}html.theme--catppuccin-macchiato .column.is-one-fifth-widescreen{flex:none;width:20%}html.theme--catppuccin-macchiato .column.is-two-fifths-widescreen{flex:none;width:40%}html.theme--catppuccin-macchiato .column.is-three-fifths-widescreen{flex:none;width:60%}html.theme--catppuccin-macchiato .column.is-four-fifths-widescreen{flex:none;width:80%}html.theme--catppuccin-macchiato .column.is-offset-three-quarters-widescreen{margin-left:75%}html.theme--catppuccin-macchiato .column.is-offset-two-thirds-widescreen{margin-left:66.6666%}html.theme--catppuccin-macchiato .column.is-offset-half-widescreen{margin-left:50%}html.theme--catppuccin-macchiato .column.is-offset-one-third-widescreen{margin-left:33.3333%}html.theme--catppuccin-macchiato .column.is-offset-one-quarter-widescreen{margin-left:25%}html.theme--catppuccin-macchiato .column.is-offset-one-fifth-widescreen{margin-left:20%}html.theme--catppuccin-macchiato .column.is-offset-two-fifths-widescreen{margin-left:40%}html.theme--catppuccin-macchiato .column.is-offset-three-fifths-widescreen{margin-left:60%}html.theme--catppuccin-macchiato .column.is-offset-four-fifths-widescreen{margin-left:80%}html.theme--catppuccin-macchiato .column.is-0-widescreen{flex:none;width:0%}html.theme--catppuccin-macchiato .column.is-offset-0-widescreen{margin-left:0%}html.theme--catppuccin-macchiato .column.is-1-widescreen{flex:none;width:8.33333337%}html.theme--catppuccin-macchiato .column.is-offset-1-widescreen{margin-left:8.33333337%}html.theme--catppuccin-macchiato .column.is-2-widescreen{flex:none;width:16.66666674%}html.theme--catppuccin-macchiato .column.is-offset-2-widescreen{margin-left:16.66666674%}html.theme--catppuccin-macchiato .column.is-3-widescreen{flex:none;width:25%}html.theme--catppuccin-macchiato .column.is-offset-3-widescreen{margin-left:25%}html.theme--catppuccin-macchiato .column.is-4-widescreen{flex:none;width:33.33333337%}html.theme--catppuccin-macchiato .column.is-offset-4-widescreen{margin-left:33.33333337%}html.theme--catppuccin-macchiato .column.is-5-widescreen{flex:none;width:41.66666674%}html.theme--catppuccin-macchiato .column.is-offset-5-widescreen{margin-left:41.66666674%}html.theme--catppuccin-macchiato .column.is-6-widescreen{flex:none;width:50%}html.theme--catppuccin-macchiato .column.is-offset-6-widescreen{margin-left:50%}html.theme--catppuccin-macchiato .column.is-7-widescreen{flex:none;width:58.33333337%}html.theme--catppuccin-macchiato .column.is-offset-7-widescreen{margin-left:58.33333337%}html.theme--catppuccin-macchiato .column.is-8-widescreen{flex:none;width:66.66666674%}html.theme--catppuccin-macchiato .column.is-offset-8-widescreen{margin-left:66.66666674%}html.theme--catppuccin-macchiato .column.is-9-widescreen{flex:none;width:75%}html.theme--catppuccin-macchiato .column.is-offset-9-widescreen{margin-left:75%}html.theme--catppuccin-macchiato .column.is-10-widescreen{flex:none;width:83.33333337%}html.theme--catppuccin-macchiato .column.is-offset-10-widescreen{margin-left:83.33333337%}html.theme--catppuccin-macchiato .column.is-11-widescreen{flex:none;width:91.66666674%}html.theme--catppuccin-macchiato .column.is-offset-11-widescreen{margin-left:91.66666674%}html.theme--catppuccin-macchiato .column.is-12-widescreen{flex:none;width:100%}html.theme--catppuccin-macchiato .column.is-offset-12-widescreen{margin-left:100%}}@media screen and (min-width: 1408px){html.theme--catppuccin-macchiato .column.is-narrow-fullhd{flex:none;width:unset}html.theme--catppuccin-macchiato .column.is-full-fullhd{flex:none;width:100%}html.theme--catppuccin-macchiato .column.is-three-quarters-fullhd{flex:none;width:75%}html.theme--catppuccin-macchiato .column.is-two-thirds-fullhd{flex:none;width:66.6666%}html.theme--catppuccin-macchiato .column.is-half-fullhd{flex:none;width:50%}html.theme--catppuccin-macchiato .column.is-one-third-fullhd{flex:none;width:33.3333%}html.theme--catppuccin-macchiato .column.is-one-quarter-fullhd{flex:none;width:25%}html.theme--catppuccin-macchiato .column.is-one-fifth-fullhd{flex:none;width:20%}html.theme--catppuccin-macchiato .column.is-two-fifths-fullhd{flex:none;width:40%}html.theme--catppuccin-macchiato .column.is-three-fifths-fullhd{flex:none;width:60%}html.theme--catppuccin-macchiato .column.is-four-fifths-fullhd{flex:none;width:80%}html.theme--catppuccin-macchiato .column.is-offset-three-quarters-fullhd{margin-left:75%}html.theme--catppuccin-macchiato .column.is-offset-two-thirds-fullhd{margin-left:66.6666%}html.theme--catppuccin-macchiato .column.is-offset-half-fullhd{margin-left:50%}html.theme--catppuccin-macchiato .column.is-offset-one-third-fullhd{margin-left:33.3333%}html.theme--catppuccin-macchiato .column.is-offset-one-quarter-fullhd{margin-left:25%}html.theme--catppuccin-macchiato .column.is-offset-one-fifth-fullhd{margin-left:20%}html.theme--catppuccin-macchiato .column.is-offset-two-fifths-fullhd{margin-left:40%}html.theme--catppuccin-macchiato .column.is-offset-three-fifths-fullhd{margin-left:60%}html.theme--catppuccin-macchiato .column.is-offset-four-fifths-fullhd{margin-left:80%}html.theme--catppuccin-macchiato .column.is-0-fullhd{flex:none;width:0%}html.theme--catppuccin-macchiato .column.is-offset-0-fullhd{margin-left:0%}html.theme--catppuccin-macchiato .column.is-1-fullhd{flex:none;width:8.33333337%}html.theme--catppuccin-macchiato .column.is-offset-1-fullhd{margin-left:8.33333337%}html.theme--catppuccin-macchiato .column.is-2-fullhd{flex:none;width:16.66666674%}html.theme--catppuccin-macchiato .column.is-offset-2-fullhd{margin-left:16.66666674%}html.theme--catppuccin-macchiato .column.is-3-fullhd{flex:none;width:25%}html.theme--catppuccin-macchiato .column.is-offset-3-fullhd{margin-left:25%}html.theme--catppuccin-macchiato .column.is-4-fullhd{flex:none;width:33.33333337%}html.theme--catppuccin-macchiato .column.is-offset-4-fullhd{margin-left:33.33333337%}html.theme--catppuccin-macchiato .column.is-5-fullhd{flex:none;width:41.66666674%}html.theme--catppuccin-macchiato .column.is-offset-5-fullhd{margin-left:41.66666674%}html.theme--catppuccin-macchiato .column.is-6-fullhd{flex:none;width:50%}html.theme--catppuccin-macchiato .column.is-offset-6-fullhd{margin-left:50%}html.theme--catppuccin-macchiato .column.is-7-fullhd{flex:none;width:58.33333337%}html.theme--catppuccin-macchiato .column.is-offset-7-fullhd{margin-left:58.33333337%}html.theme--catppuccin-macchiato .column.is-8-fullhd{flex:none;width:66.66666674%}html.theme--catppuccin-macchiato .column.is-offset-8-fullhd{margin-left:66.66666674%}html.theme--catppuccin-macchiato .column.is-9-fullhd{flex:none;width:75%}html.theme--catppuccin-macchiato .column.is-offset-9-fullhd{margin-left:75%}html.theme--catppuccin-macchiato .column.is-10-fullhd{flex:none;width:83.33333337%}html.theme--catppuccin-macchiato .column.is-offset-10-fullhd{margin-left:83.33333337%}html.theme--catppuccin-macchiato .column.is-11-fullhd{flex:none;width:91.66666674%}html.theme--catppuccin-macchiato .column.is-offset-11-fullhd{margin-left:91.66666674%}html.theme--catppuccin-macchiato .column.is-12-fullhd{flex:none;width:100%}html.theme--catppuccin-macchiato .column.is-offset-12-fullhd{margin-left:100%}}html.theme--catppuccin-macchiato .columns{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}html.theme--catppuccin-macchiato .columns:last-child{margin-bottom:-.75rem}html.theme--catppuccin-macchiato .columns:not(:last-child){margin-bottom:calc(1.5rem - .75rem)}html.theme--catppuccin-macchiato .columns.is-centered{justify-content:center}html.theme--catppuccin-macchiato .columns.is-gapless{margin-left:0;margin-right:0;margin-top:0}html.theme--catppuccin-macchiato .columns.is-gapless>.column{margin:0;padding:0 !important}html.theme--catppuccin-macchiato .columns.is-gapless:not(:last-child){margin-bottom:1.5rem}html.theme--catppuccin-macchiato .columns.is-gapless:last-child{margin-bottom:0}html.theme--catppuccin-macchiato .columns.is-mobile{display:flex}html.theme--catppuccin-macchiato .columns.is-multiline{flex-wrap:wrap}html.theme--catppuccin-macchiato .columns.is-vcentered{align-items:center}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .columns:not(.is-desktop){display:flex}}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .columns.is-desktop{display:flex}}html.theme--catppuccin-macchiato .columns.is-variable{--columnGap: 0.75rem;margin-left:calc(-1 * var(--columnGap));margin-right:calc(-1 * var(--columnGap))}html.theme--catppuccin-macchiato .columns.is-variable>.column{padding-left:var(--columnGap);padding-right:var(--columnGap)}html.theme--catppuccin-macchiato .columns.is-variable.is-0{--columnGap: 0rem}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .columns.is-variable.is-0-mobile{--columnGap: 0rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .columns.is-variable.is-0-tablet{--columnGap: 0rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-macchiato .columns.is-variable.is-0-tablet-only{--columnGap: 0rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .columns.is-variable.is-0-touch{--columnGap: 0rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .columns.is-variable.is-0-desktop{--columnGap: 0rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-macchiato .columns.is-variable.is-0-desktop-only{--columnGap: 0rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-macchiato .columns.is-variable.is-0-widescreen{--columnGap: 0rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-macchiato .columns.is-variable.is-0-widescreen-only{--columnGap: 0rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-macchiato .columns.is-variable.is-0-fullhd{--columnGap: 0rem}}html.theme--catppuccin-macchiato .columns.is-variable.is-1{--columnGap: .25rem}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .columns.is-variable.is-1-mobile{--columnGap: .25rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .columns.is-variable.is-1-tablet{--columnGap: .25rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-macchiato .columns.is-variable.is-1-tablet-only{--columnGap: .25rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .columns.is-variable.is-1-touch{--columnGap: .25rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .columns.is-variable.is-1-desktop{--columnGap: .25rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-macchiato .columns.is-variable.is-1-desktop-only{--columnGap: .25rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-macchiato .columns.is-variable.is-1-widescreen{--columnGap: .25rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-macchiato .columns.is-variable.is-1-widescreen-only{--columnGap: .25rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-macchiato .columns.is-variable.is-1-fullhd{--columnGap: .25rem}}html.theme--catppuccin-macchiato .columns.is-variable.is-2{--columnGap: .5rem}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .columns.is-variable.is-2-mobile{--columnGap: .5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .columns.is-variable.is-2-tablet{--columnGap: .5rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-macchiato .columns.is-variable.is-2-tablet-only{--columnGap: .5rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .columns.is-variable.is-2-touch{--columnGap: .5rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .columns.is-variable.is-2-desktop{--columnGap: .5rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-macchiato .columns.is-variable.is-2-desktop-only{--columnGap: .5rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-macchiato .columns.is-variable.is-2-widescreen{--columnGap: .5rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-macchiato .columns.is-variable.is-2-widescreen-only{--columnGap: .5rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-macchiato .columns.is-variable.is-2-fullhd{--columnGap: .5rem}}html.theme--catppuccin-macchiato .columns.is-variable.is-3{--columnGap: .75rem}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .columns.is-variable.is-3-mobile{--columnGap: .75rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .columns.is-variable.is-3-tablet{--columnGap: .75rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-macchiato .columns.is-variable.is-3-tablet-only{--columnGap: .75rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .columns.is-variable.is-3-touch{--columnGap: .75rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .columns.is-variable.is-3-desktop{--columnGap: .75rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-macchiato .columns.is-variable.is-3-desktop-only{--columnGap: .75rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-macchiato .columns.is-variable.is-3-widescreen{--columnGap: .75rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-macchiato .columns.is-variable.is-3-widescreen-only{--columnGap: .75rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-macchiato .columns.is-variable.is-3-fullhd{--columnGap: .75rem}}html.theme--catppuccin-macchiato .columns.is-variable.is-4{--columnGap: 1rem}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .columns.is-variable.is-4-mobile{--columnGap: 1rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .columns.is-variable.is-4-tablet{--columnGap: 1rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-macchiato .columns.is-variable.is-4-tablet-only{--columnGap: 1rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .columns.is-variable.is-4-touch{--columnGap: 1rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .columns.is-variable.is-4-desktop{--columnGap: 1rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-macchiato .columns.is-variable.is-4-desktop-only{--columnGap: 1rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-macchiato .columns.is-variable.is-4-widescreen{--columnGap: 1rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-macchiato .columns.is-variable.is-4-widescreen-only{--columnGap: 1rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-macchiato .columns.is-variable.is-4-fullhd{--columnGap: 1rem}}html.theme--catppuccin-macchiato .columns.is-variable.is-5{--columnGap: 1.25rem}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .columns.is-variable.is-5-mobile{--columnGap: 1.25rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .columns.is-variable.is-5-tablet{--columnGap: 1.25rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-macchiato .columns.is-variable.is-5-tablet-only{--columnGap: 1.25rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .columns.is-variable.is-5-touch{--columnGap: 1.25rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .columns.is-variable.is-5-desktop{--columnGap: 1.25rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-macchiato .columns.is-variable.is-5-desktop-only{--columnGap: 1.25rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-macchiato .columns.is-variable.is-5-widescreen{--columnGap: 1.25rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-macchiato .columns.is-variable.is-5-widescreen-only{--columnGap: 1.25rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-macchiato .columns.is-variable.is-5-fullhd{--columnGap: 1.25rem}}html.theme--catppuccin-macchiato .columns.is-variable.is-6{--columnGap: 1.5rem}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .columns.is-variable.is-6-mobile{--columnGap: 1.5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .columns.is-variable.is-6-tablet{--columnGap: 1.5rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-macchiato .columns.is-variable.is-6-tablet-only{--columnGap: 1.5rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .columns.is-variable.is-6-touch{--columnGap: 1.5rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .columns.is-variable.is-6-desktop{--columnGap: 1.5rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-macchiato .columns.is-variable.is-6-desktop-only{--columnGap: 1.5rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-macchiato .columns.is-variable.is-6-widescreen{--columnGap: 1.5rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-macchiato .columns.is-variable.is-6-widescreen-only{--columnGap: 1.5rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-macchiato .columns.is-variable.is-6-fullhd{--columnGap: 1.5rem}}html.theme--catppuccin-macchiato .columns.is-variable.is-7{--columnGap: 1.75rem}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .columns.is-variable.is-7-mobile{--columnGap: 1.75rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .columns.is-variable.is-7-tablet{--columnGap: 1.75rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-macchiato .columns.is-variable.is-7-tablet-only{--columnGap: 1.75rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .columns.is-variable.is-7-touch{--columnGap: 1.75rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .columns.is-variable.is-7-desktop{--columnGap: 1.75rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-macchiato .columns.is-variable.is-7-desktop-only{--columnGap: 1.75rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-macchiato .columns.is-variable.is-7-widescreen{--columnGap: 1.75rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-macchiato .columns.is-variable.is-7-widescreen-only{--columnGap: 1.75rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-macchiato .columns.is-variable.is-7-fullhd{--columnGap: 1.75rem}}html.theme--catppuccin-macchiato .columns.is-variable.is-8{--columnGap: 2rem}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .columns.is-variable.is-8-mobile{--columnGap: 2rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .columns.is-variable.is-8-tablet{--columnGap: 2rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-macchiato .columns.is-variable.is-8-tablet-only{--columnGap: 2rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .columns.is-variable.is-8-touch{--columnGap: 2rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .columns.is-variable.is-8-desktop{--columnGap: 2rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-macchiato .columns.is-variable.is-8-desktop-only{--columnGap: 2rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-macchiato .columns.is-variable.is-8-widescreen{--columnGap: 2rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-macchiato .columns.is-variable.is-8-widescreen-only{--columnGap: 2rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-macchiato .columns.is-variable.is-8-fullhd{--columnGap: 2rem}}html.theme--catppuccin-macchiato .tile{align-items:stretch;display:block;flex-basis:0;flex-grow:1;flex-shrink:1;min-height:min-content}html.theme--catppuccin-macchiato .tile.is-ancestor{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}html.theme--catppuccin-macchiato .tile.is-ancestor:last-child{margin-bottom:-.75rem}html.theme--catppuccin-macchiato .tile.is-ancestor:not(:last-child){margin-bottom:.75rem}html.theme--catppuccin-macchiato .tile.is-child{margin:0 !important}html.theme--catppuccin-macchiato .tile.is-parent{padding:.75rem}html.theme--catppuccin-macchiato .tile.is-vertical{flex-direction:column}html.theme--catppuccin-macchiato .tile.is-vertical>.tile.is-child:not(:last-child){margin-bottom:1.5rem !important}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .tile:not(.is-child){display:flex}html.theme--catppuccin-macchiato .tile.is-1{flex:none;width:8.33333337%}html.theme--catppuccin-macchiato .tile.is-2{flex:none;width:16.66666674%}html.theme--catppuccin-macchiato .tile.is-3{flex:none;width:25%}html.theme--catppuccin-macchiato .tile.is-4{flex:none;width:33.33333337%}html.theme--catppuccin-macchiato .tile.is-5{flex:none;width:41.66666674%}html.theme--catppuccin-macchiato .tile.is-6{flex:none;width:50%}html.theme--catppuccin-macchiato .tile.is-7{flex:none;width:58.33333337%}html.theme--catppuccin-macchiato .tile.is-8{flex:none;width:66.66666674%}html.theme--catppuccin-macchiato .tile.is-9{flex:none;width:75%}html.theme--catppuccin-macchiato .tile.is-10{flex:none;width:83.33333337%}html.theme--catppuccin-macchiato .tile.is-11{flex:none;width:91.66666674%}html.theme--catppuccin-macchiato .tile.is-12{flex:none;width:100%}}html.theme--catppuccin-macchiato .hero{align-items:stretch;display:flex;flex-direction:column;justify-content:space-between}html.theme--catppuccin-macchiato .hero .navbar{background:none}html.theme--catppuccin-macchiato .hero .tabs ul{border-bottom:none}html.theme--catppuccin-macchiato .hero.is-white{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-macchiato .hero.is-white a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-macchiato .hero.is-white strong{color:inherit}html.theme--catppuccin-macchiato .hero.is-white .title{color:#0a0a0a}html.theme--catppuccin-macchiato .hero.is-white .subtitle{color:rgba(10,10,10,0.9)}html.theme--catppuccin-macchiato .hero.is-white .subtitle a:not(.button),html.theme--catppuccin-macchiato .hero.is-white .subtitle strong{color:#0a0a0a}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .hero.is-white .navbar-menu{background-color:#fff}}html.theme--catppuccin-macchiato .hero.is-white .navbar-item,html.theme--catppuccin-macchiato .hero.is-white .navbar-link{color:rgba(10,10,10,0.7)}html.theme--catppuccin-macchiato .hero.is-white a.navbar-item:hover,html.theme--catppuccin-macchiato .hero.is-white a.navbar-item.is-active,html.theme--catppuccin-macchiato .hero.is-white .navbar-link:hover,html.theme--catppuccin-macchiato .hero.is-white .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}html.theme--catppuccin-macchiato .hero.is-white .tabs a{color:#0a0a0a;opacity:0.9}html.theme--catppuccin-macchiato .hero.is-white .tabs a:hover{opacity:1}html.theme--catppuccin-macchiato .hero.is-white .tabs li.is-active a{color:#fff !important;opacity:1}html.theme--catppuccin-macchiato .hero.is-white .tabs.is-boxed a,html.theme--catppuccin-macchiato .hero.is-white .tabs.is-toggle a{color:#0a0a0a}html.theme--catppuccin-macchiato .hero.is-white .tabs.is-boxed a:hover,html.theme--catppuccin-macchiato .hero.is-white .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-macchiato .hero.is-white .tabs.is-boxed li.is-active a,html.theme--catppuccin-macchiato .hero.is-white .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-macchiato .hero.is-white .tabs.is-toggle li.is-active a,html.theme--catppuccin-macchiato .hero.is-white .tabs.is-toggle li.is-active a:hover{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}html.theme--catppuccin-macchiato .hero.is-white.is-bold{background-image:linear-gradient(141deg, #e8e3e4 0%, #fff 71%, #fff 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .hero.is-white.is-bold .navbar-menu{background-image:linear-gradient(141deg, #e8e3e4 0%, #fff 71%, #fff 100%)}}html.theme--catppuccin-macchiato .hero.is-black{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-macchiato .hero.is-black a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-macchiato .hero.is-black strong{color:inherit}html.theme--catppuccin-macchiato .hero.is-black .title{color:#fff}html.theme--catppuccin-macchiato .hero.is-black .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-macchiato .hero.is-black .subtitle a:not(.button),html.theme--catppuccin-macchiato .hero.is-black .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .hero.is-black .navbar-menu{background-color:#0a0a0a}}html.theme--catppuccin-macchiato .hero.is-black .navbar-item,html.theme--catppuccin-macchiato .hero.is-black .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-macchiato .hero.is-black a.navbar-item:hover,html.theme--catppuccin-macchiato .hero.is-black a.navbar-item.is-active,html.theme--catppuccin-macchiato .hero.is-black .navbar-link:hover,html.theme--catppuccin-macchiato .hero.is-black .navbar-link.is-active{background-color:#000;color:#fff}html.theme--catppuccin-macchiato .hero.is-black .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-macchiato .hero.is-black .tabs a:hover{opacity:1}html.theme--catppuccin-macchiato .hero.is-black .tabs li.is-active a{color:#0a0a0a !important;opacity:1}html.theme--catppuccin-macchiato .hero.is-black .tabs.is-boxed a,html.theme--catppuccin-macchiato .hero.is-black .tabs.is-toggle a{color:#fff}html.theme--catppuccin-macchiato .hero.is-black .tabs.is-boxed a:hover,html.theme--catppuccin-macchiato .hero.is-black .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-macchiato .hero.is-black .tabs.is-boxed li.is-active a,html.theme--catppuccin-macchiato .hero.is-black .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-macchiato .hero.is-black .tabs.is-toggle li.is-active a,html.theme--catppuccin-macchiato .hero.is-black .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#0a0a0a}html.theme--catppuccin-macchiato .hero.is-black.is-bold{background-image:linear-gradient(141deg, #000 0%, #0a0a0a 71%, #181616 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .hero.is-black.is-bold .navbar-menu{background-image:linear-gradient(141deg, #000 0%, #0a0a0a 71%, #181616 100%)}}html.theme--catppuccin-macchiato .hero.is-light{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-light a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-macchiato .hero.is-light strong{color:inherit}html.theme--catppuccin-macchiato .hero.is-light .title{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-light .subtitle{color:rgba(0,0,0,0.9)}html.theme--catppuccin-macchiato .hero.is-light .subtitle a:not(.button),html.theme--catppuccin-macchiato .hero.is-light .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .hero.is-light .navbar-menu{background-color:#f5f5f5}}html.theme--catppuccin-macchiato .hero.is-light .navbar-item,html.theme--catppuccin-macchiato .hero.is-light .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-light a.navbar-item:hover,html.theme--catppuccin-macchiato .hero.is-light a.navbar-item.is-active,html.theme--catppuccin-macchiato .hero.is-light .navbar-link:hover,html.theme--catppuccin-macchiato .hero.is-light .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-light .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}html.theme--catppuccin-macchiato .hero.is-light .tabs a:hover{opacity:1}html.theme--catppuccin-macchiato .hero.is-light .tabs li.is-active a{color:#f5f5f5 !important;opacity:1}html.theme--catppuccin-macchiato .hero.is-light .tabs.is-boxed a,html.theme--catppuccin-macchiato .hero.is-light .tabs.is-toggle a{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-light .tabs.is-boxed a:hover,html.theme--catppuccin-macchiato .hero.is-light .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-macchiato .hero.is-light .tabs.is-boxed li.is-active a,html.theme--catppuccin-macchiato .hero.is-light .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-macchiato .hero.is-light .tabs.is-toggle li.is-active a,html.theme--catppuccin-macchiato .hero.is-light .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#f5f5f5}html.theme--catppuccin-macchiato .hero.is-light.is-bold{background-image:linear-gradient(141deg, #dfd8d9 0%, #f5f5f5 71%, #fff 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .hero.is-light.is-bold .navbar-menu{background-image:linear-gradient(141deg, #dfd8d9 0%, #f5f5f5 71%, #fff 100%)}}html.theme--catppuccin-macchiato .hero.is-dark,html.theme--catppuccin-macchiato .content kbd.hero{background-color:#363a4f;color:#fff}html.theme--catppuccin-macchiato .hero.is-dark a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-macchiato .content kbd.hero a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-macchiato .hero.is-dark strong,html.theme--catppuccin-macchiato .content kbd.hero strong{color:inherit}html.theme--catppuccin-macchiato .hero.is-dark .title,html.theme--catppuccin-macchiato .content kbd.hero .title{color:#fff}html.theme--catppuccin-macchiato .hero.is-dark .subtitle,html.theme--catppuccin-macchiato .content kbd.hero .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-macchiato .hero.is-dark .subtitle a:not(.button),html.theme--catppuccin-macchiato .content kbd.hero .subtitle a:not(.button),html.theme--catppuccin-macchiato .hero.is-dark .subtitle strong,html.theme--catppuccin-macchiato .content kbd.hero .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .hero.is-dark .navbar-menu,html.theme--catppuccin-macchiato .content kbd.hero .navbar-menu{background-color:#363a4f}}html.theme--catppuccin-macchiato .hero.is-dark .navbar-item,html.theme--catppuccin-macchiato .content kbd.hero .navbar-item,html.theme--catppuccin-macchiato .hero.is-dark .navbar-link,html.theme--catppuccin-macchiato .content kbd.hero .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-macchiato .hero.is-dark a.navbar-item:hover,html.theme--catppuccin-macchiato .content kbd.hero a.navbar-item:hover,html.theme--catppuccin-macchiato .hero.is-dark a.navbar-item.is-active,html.theme--catppuccin-macchiato .content kbd.hero a.navbar-item.is-active,html.theme--catppuccin-macchiato .hero.is-dark .navbar-link:hover,html.theme--catppuccin-macchiato .content kbd.hero .navbar-link:hover,html.theme--catppuccin-macchiato .hero.is-dark .navbar-link.is-active,html.theme--catppuccin-macchiato .content kbd.hero .navbar-link.is-active{background-color:#2c2f40;color:#fff}html.theme--catppuccin-macchiato .hero.is-dark .tabs a,html.theme--catppuccin-macchiato .content kbd.hero .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-macchiato .hero.is-dark .tabs a:hover,html.theme--catppuccin-macchiato .content kbd.hero .tabs a:hover{opacity:1}html.theme--catppuccin-macchiato .hero.is-dark .tabs li.is-active a,html.theme--catppuccin-macchiato .content kbd.hero .tabs li.is-active a{color:#363a4f !important;opacity:1}html.theme--catppuccin-macchiato .hero.is-dark .tabs.is-boxed a,html.theme--catppuccin-macchiato .content kbd.hero .tabs.is-boxed a,html.theme--catppuccin-macchiato .hero.is-dark .tabs.is-toggle a,html.theme--catppuccin-macchiato .content kbd.hero .tabs.is-toggle a{color:#fff}html.theme--catppuccin-macchiato .hero.is-dark .tabs.is-boxed a:hover,html.theme--catppuccin-macchiato .content kbd.hero .tabs.is-boxed a:hover,html.theme--catppuccin-macchiato .hero.is-dark .tabs.is-toggle a:hover,html.theme--catppuccin-macchiato .content kbd.hero .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-macchiato .hero.is-dark .tabs.is-boxed li.is-active a,html.theme--catppuccin-macchiato .content kbd.hero .tabs.is-boxed li.is-active a,html.theme--catppuccin-macchiato .hero.is-dark .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-macchiato .hero.is-dark .tabs.is-toggle li.is-active a,html.theme--catppuccin-macchiato .content kbd.hero .tabs.is-toggle li.is-active a,html.theme--catppuccin-macchiato .hero.is-dark .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#363a4f}html.theme--catppuccin-macchiato .hero.is-dark.is-bold,html.theme--catppuccin-macchiato .content kbd.hero.is-bold{background-image:linear-gradient(141deg, #1d2535 0%, #363a4f 71%, #3d3c62 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .hero.is-dark.is-bold .navbar-menu,html.theme--catppuccin-macchiato .content kbd.hero.is-bold .navbar-menu{background-image:linear-gradient(141deg, #1d2535 0%, #363a4f 71%, #3d3c62 100%)}}html.theme--catppuccin-macchiato .hero.is-primary,html.theme--catppuccin-macchiato .docstring>section>a.hero.docs-sourcelink{background-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .hero.is-primary a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-macchiato .docstring>section>a.hero.docs-sourcelink a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-macchiato .hero.is-primary strong,html.theme--catppuccin-macchiato .docstring>section>a.hero.docs-sourcelink strong{color:inherit}html.theme--catppuccin-macchiato .hero.is-primary .title,html.theme--catppuccin-macchiato .docstring>section>a.hero.docs-sourcelink .title{color:#fff}html.theme--catppuccin-macchiato .hero.is-primary .subtitle,html.theme--catppuccin-macchiato .docstring>section>a.hero.docs-sourcelink .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-macchiato .hero.is-primary .subtitle a:not(.button),html.theme--catppuccin-macchiato .docstring>section>a.hero.docs-sourcelink .subtitle a:not(.button),html.theme--catppuccin-macchiato .hero.is-primary .subtitle strong,html.theme--catppuccin-macchiato .docstring>section>a.hero.docs-sourcelink .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .hero.is-primary .navbar-menu,html.theme--catppuccin-macchiato .docstring>section>a.hero.docs-sourcelink .navbar-menu{background-color:#8aadf4}}html.theme--catppuccin-macchiato .hero.is-primary .navbar-item,html.theme--catppuccin-macchiato .docstring>section>a.hero.docs-sourcelink .navbar-item,html.theme--catppuccin-macchiato .hero.is-primary .navbar-link,html.theme--catppuccin-macchiato .docstring>section>a.hero.docs-sourcelink .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-macchiato .hero.is-primary a.navbar-item:hover,html.theme--catppuccin-macchiato .docstring>section>a.hero.docs-sourcelink a.navbar-item:hover,html.theme--catppuccin-macchiato .hero.is-primary a.navbar-item.is-active,html.theme--catppuccin-macchiato .docstring>section>a.hero.docs-sourcelink a.navbar-item.is-active,html.theme--catppuccin-macchiato .hero.is-primary .navbar-link:hover,html.theme--catppuccin-macchiato .docstring>section>a.hero.docs-sourcelink .navbar-link:hover,html.theme--catppuccin-macchiato .hero.is-primary .navbar-link.is-active,html.theme--catppuccin-macchiato .docstring>section>a.hero.docs-sourcelink .navbar-link.is-active{background-color:#739df2;color:#fff}html.theme--catppuccin-macchiato .hero.is-primary .tabs a,html.theme--catppuccin-macchiato .docstring>section>a.hero.docs-sourcelink .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-macchiato .hero.is-primary .tabs a:hover,html.theme--catppuccin-macchiato .docstring>section>a.hero.docs-sourcelink .tabs a:hover{opacity:1}html.theme--catppuccin-macchiato .hero.is-primary .tabs li.is-active a,html.theme--catppuccin-macchiato .docstring>section>a.hero.docs-sourcelink .tabs li.is-active a{color:#8aadf4 !important;opacity:1}html.theme--catppuccin-macchiato .hero.is-primary .tabs.is-boxed a,html.theme--catppuccin-macchiato .docstring>section>a.hero.docs-sourcelink .tabs.is-boxed a,html.theme--catppuccin-macchiato .hero.is-primary .tabs.is-toggle a,html.theme--catppuccin-macchiato .docstring>section>a.hero.docs-sourcelink .tabs.is-toggle a{color:#fff}html.theme--catppuccin-macchiato .hero.is-primary .tabs.is-boxed a:hover,html.theme--catppuccin-macchiato .docstring>section>a.hero.docs-sourcelink .tabs.is-boxed a:hover,html.theme--catppuccin-macchiato .hero.is-primary .tabs.is-toggle a:hover,html.theme--catppuccin-macchiato .docstring>section>a.hero.docs-sourcelink .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-macchiato .hero.is-primary .tabs.is-boxed li.is-active a,html.theme--catppuccin-macchiato .docstring>section>a.hero.docs-sourcelink .tabs.is-boxed li.is-active a,html.theme--catppuccin-macchiato .hero.is-primary .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-macchiato .hero.is-primary .tabs.is-toggle li.is-active a,html.theme--catppuccin-macchiato .docstring>section>a.hero.docs-sourcelink .tabs.is-toggle li.is-active a,html.theme--catppuccin-macchiato .hero.is-primary .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#8aadf4}html.theme--catppuccin-macchiato .hero.is-primary.is-bold,html.theme--catppuccin-macchiato .docstring>section>a.hero.is-bold.docs-sourcelink{background-image:linear-gradient(141deg, #52a5f9 0%, #8aadf4 71%, #9fadf9 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .hero.is-primary.is-bold .navbar-menu,html.theme--catppuccin-macchiato .docstring>section>a.hero.is-bold.docs-sourcelink .navbar-menu{background-image:linear-gradient(141deg, #52a5f9 0%, #8aadf4 71%, #9fadf9 100%)}}html.theme--catppuccin-macchiato .hero.is-link{background-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .hero.is-link a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-macchiato .hero.is-link strong{color:inherit}html.theme--catppuccin-macchiato .hero.is-link .title{color:#fff}html.theme--catppuccin-macchiato .hero.is-link .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-macchiato .hero.is-link .subtitle a:not(.button),html.theme--catppuccin-macchiato .hero.is-link .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .hero.is-link .navbar-menu{background-color:#8aadf4}}html.theme--catppuccin-macchiato .hero.is-link .navbar-item,html.theme--catppuccin-macchiato .hero.is-link .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-macchiato .hero.is-link a.navbar-item:hover,html.theme--catppuccin-macchiato .hero.is-link a.navbar-item.is-active,html.theme--catppuccin-macchiato .hero.is-link .navbar-link:hover,html.theme--catppuccin-macchiato .hero.is-link .navbar-link.is-active{background-color:#739df2;color:#fff}html.theme--catppuccin-macchiato .hero.is-link .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-macchiato .hero.is-link .tabs a:hover{opacity:1}html.theme--catppuccin-macchiato .hero.is-link .tabs li.is-active a{color:#8aadf4 !important;opacity:1}html.theme--catppuccin-macchiato .hero.is-link .tabs.is-boxed a,html.theme--catppuccin-macchiato .hero.is-link .tabs.is-toggle a{color:#fff}html.theme--catppuccin-macchiato .hero.is-link .tabs.is-boxed a:hover,html.theme--catppuccin-macchiato .hero.is-link .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-macchiato .hero.is-link .tabs.is-boxed li.is-active a,html.theme--catppuccin-macchiato .hero.is-link .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-macchiato .hero.is-link .tabs.is-toggle li.is-active a,html.theme--catppuccin-macchiato .hero.is-link .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#8aadf4}html.theme--catppuccin-macchiato .hero.is-link.is-bold{background-image:linear-gradient(141deg, #52a5f9 0%, #8aadf4 71%, #9fadf9 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .hero.is-link.is-bold .navbar-menu{background-image:linear-gradient(141deg, #52a5f9 0%, #8aadf4 71%, #9fadf9 100%)}}html.theme--catppuccin-macchiato .hero.is-info{background-color:#8bd5ca;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-info a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-macchiato .hero.is-info strong{color:inherit}html.theme--catppuccin-macchiato .hero.is-info .title{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-info .subtitle{color:rgba(0,0,0,0.9)}html.theme--catppuccin-macchiato .hero.is-info .subtitle a:not(.button),html.theme--catppuccin-macchiato .hero.is-info .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .hero.is-info .navbar-menu{background-color:#8bd5ca}}html.theme--catppuccin-macchiato .hero.is-info .navbar-item,html.theme--catppuccin-macchiato .hero.is-info .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-info a.navbar-item:hover,html.theme--catppuccin-macchiato .hero.is-info a.navbar-item.is-active,html.theme--catppuccin-macchiato .hero.is-info .navbar-link:hover,html.theme--catppuccin-macchiato .hero.is-info .navbar-link.is-active{background-color:#78cec1;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-info .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}html.theme--catppuccin-macchiato .hero.is-info .tabs a:hover{opacity:1}html.theme--catppuccin-macchiato .hero.is-info .tabs li.is-active a{color:#8bd5ca !important;opacity:1}html.theme--catppuccin-macchiato .hero.is-info .tabs.is-boxed a,html.theme--catppuccin-macchiato .hero.is-info .tabs.is-toggle a{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-info .tabs.is-boxed a:hover,html.theme--catppuccin-macchiato .hero.is-info .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-macchiato .hero.is-info .tabs.is-boxed li.is-active a,html.theme--catppuccin-macchiato .hero.is-info .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-macchiato .hero.is-info .tabs.is-toggle li.is-active a,html.theme--catppuccin-macchiato .hero.is-info .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#8bd5ca}html.theme--catppuccin-macchiato .hero.is-info.is-bold{background-image:linear-gradient(141deg, #5bd2ac 0%, #8bd5ca 71%, #9adedf 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .hero.is-info.is-bold .navbar-menu{background-image:linear-gradient(141deg, #5bd2ac 0%, #8bd5ca 71%, #9adedf 100%)}}html.theme--catppuccin-macchiato .hero.is-success{background-color:#a6da95;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-success a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-macchiato .hero.is-success strong{color:inherit}html.theme--catppuccin-macchiato .hero.is-success .title{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-success .subtitle{color:rgba(0,0,0,0.9)}html.theme--catppuccin-macchiato .hero.is-success .subtitle a:not(.button),html.theme--catppuccin-macchiato .hero.is-success .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .hero.is-success .navbar-menu{background-color:#a6da95}}html.theme--catppuccin-macchiato .hero.is-success .navbar-item,html.theme--catppuccin-macchiato .hero.is-success .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-success a.navbar-item:hover,html.theme--catppuccin-macchiato .hero.is-success a.navbar-item.is-active,html.theme--catppuccin-macchiato .hero.is-success .navbar-link:hover,html.theme--catppuccin-macchiato .hero.is-success .navbar-link.is-active{background-color:#96d382;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-success .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}html.theme--catppuccin-macchiato .hero.is-success .tabs a:hover{opacity:1}html.theme--catppuccin-macchiato .hero.is-success .tabs li.is-active a{color:#a6da95 !important;opacity:1}html.theme--catppuccin-macchiato .hero.is-success .tabs.is-boxed a,html.theme--catppuccin-macchiato .hero.is-success .tabs.is-toggle a{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-success .tabs.is-boxed a:hover,html.theme--catppuccin-macchiato .hero.is-success .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-macchiato .hero.is-success .tabs.is-boxed li.is-active a,html.theme--catppuccin-macchiato .hero.is-success .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-macchiato .hero.is-success .tabs.is-toggle li.is-active a,html.theme--catppuccin-macchiato .hero.is-success .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#a6da95}html.theme--catppuccin-macchiato .hero.is-success.is-bold{background-image:linear-gradient(141deg, #94d765 0%, #a6da95 71%, #aae4a5 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .hero.is-success.is-bold .navbar-menu{background-image:linear-gradient(141deg, #94d765 0%, #a6da95 71%, #aae4a5 100%)}}html.theme--catppuccin-macchiato .hero.is-warning{background-color:#eed49f;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-warning a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-macchiato .hero.is-warning strong{color:inherit}html.theme--catppuccin-macchiato .hero.is-warning .title{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-warning .subtitle{color:rgba(0,0,0,0.9)}html.theme--catppuccin-macchiato .hero.is-warning .subtitle a:not(.button),html.theme--catppuccin-macchiato .hero.is-warning .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .hero.is-warning .navbar-menu{background-color:#eed49f}}html.theme--catppuccin-macchiato .hero.is-warning .navbar-item,html.theme--catppuccin-macchiato .hero.is-warning .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-warning a.navbar-item:hover,html.theme--catppuccin-macchiato .hero.is-warning a.navbar-item.is-active,html.theme--catppuccin-macchiato .hero.is-warning .navbar-link:hover,html.theme--catppuccin-macchiato .hero.is-warning .navbar-link.is-active{background-color:#eaca89;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-warning .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}html.theme--catppuccin-macchiato .hero.is-warning .tabs a:hover{opacity:1}html.theme--catppuccin-macchiato .hero.is-warning .tabs li.is-active a{color:#eed49f !important;opacity:1}html.theme--catppuccin-macchiato .hero.is-warning .tabs.is-boxed a,html.theme--catppuccin-macchiato .hero.is-warning .tabs.is-toggle a{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-warning .tabs.is-boxed a:hover,html.theme--catppuccin-macchiato .hero.is-warning .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-macchiato .hero.is-warning .tabs.is-boxed li.is-active a,html.theme--catppuccin-macchiato .hero.is-warning .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-macchiato .hero.is-warning .tabs.is-toggle li.is-active a,html.theme--catppuccin-macchiato .hero.is-warning .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#eed49f}html.theme--catppuccin-macchiato .hero.is-warning.is-bold{background-image:linear-gradient(141deg, #efae6b 0%, #eed49f 71%, #f4e9b2 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .hero.is-warning.is-bold .navbar-menu{background-image:linear-gradient(141deg, #efae6b 0%, #eed49f 71%, #f4e9b2 100%)}}html.theme--catppuccin-macchiato .hero.is-danger{background-color:#ed8796;color:#fff}html.theme--catppuccin-macchiato .hero.is-danger a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-macchiato .hero.is-danger strong{color:inherit}html.theme--catppuccin-macchiato .hero.is-danger .title{color:#fff}html.theme--catppuccin-macchiato .hero.is-danger .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-macchiato .hero.is-danger .subtitle a:not(.button),html.theme--catppuccin-macchiato .hero.is-danger .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .hero.is-danger .navbar-menu{background-color:#ed8796}}html.theme--catppuccin-macchiato .hero.is-danger .navbar-item,html.theme--catppuccin-macchiato .hero.is-danger .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-macchiato .hero.is-danger a.navbar-item:hover,html.theme--catppuccin-macchiato .hero.is-danger a.navbar-item.is-active,html.theme--catppuccin-macchiato .hero.is-danger .navbar-link:hover,html.theme--catppuccin-macchiato .hero.is-danger .navbar-link.is-active{background-color:#ea7183;color:#fff}html.theme--catppuccin-macchiato .hero.is-danger .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-macchiato .hero.is-danger .tabs a:hover{opacity:1}html.theme--catppuccin-macchiato .hero.is-danger .tabs li.is-active a{color:#ed8796 !important;opacity:1}html.theme--catppuccin-macchiato .hero.is-danger .tabs.is-boxed a,html.theme--catppuccin-macchiato .hero.is-danger .tabs.is-toggle a{color:#fff}html.theme--catppuccin-macchiato .hero.is-danger .tabs.is-boxed a:hover,html.theme--catppuccin-macchiato .hero.is-danger .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-macchiato .hero.is-danger .tabs.is-boxed li.is-active a,html.theme--catppuccin-macchiato .hero.is-danger .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-macchiato .hero.is-danger .tabs.is-toggle li.is-active a,html.theme--catppuccin-macchiato .hero.is-danger .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#ed8796}html.theme--catppuccin-macchiato .hero.is-danger.is-bold{background-image:linear-gradient(141deg, #f05183 0%, #ed8796 71%, #f39c9a 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .hero.is-danger.is-bold .navbar-menu{background-image:linear-gradient(141deg, #f05183 0%, #ed8796 71%, #f39c9a 100%)}}html.theme--catppuccin-macchiato .hero.is-small .hero-body,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.hero .hero-body{padding:1.5rem}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .hero.is-medium .hero-body{padding:9rem 4.5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .hero.is-large .hero-body{padding:18rem 6rem}}html.theme--catppuccin-macchiato .hero.is-halfheight .hero-body,html.theme--catppuccin-macchiato .hero.is-fullheight .hero-body,html.theme--catppuccin-macchiato .hero.is-fullheight-with-navbar .hero-body{align-items:center;display:flex}html.theme--catppuccin-macchiato .hero.is-halfheight .hero-body>.container,html.theme--catppuccin-macchiato .hero.is-fullheight .hero-body>.container,html.theme--catppuccin-macchiato .hero.is-fullheight-with-navbar .hero-body>.container{flex-grow:1;flex-shrink:1}html.theme--catppuccin-macchiato .hero.is-halfheight{min-height:50vh}html.theme--catppuccin-macchiato .hero.is-fullheight{min-height:100vh}html.theme--catppuccin-macchiato .hero-video{overflow:hidden}html.theme--catppuccin-macchiato .hero-video video{left:50%;min-height:100%;min-width:100%;position:absolute;top:50%;transform:translate3d(-50%, -50%, 0)}html.theme--catppuccin-macchiato .hero-video.is-transparent{opacity:0.3}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .hero-video{display:none}}html.theme--catppuccin-macchiato .hero-buttons{margin-top:1.5rem}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .hero-buttons .button{display:flex}html.theme--catppuccin-macchiato .hero-buttons .button:not(:last-child){margin-bottom:0.75rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .hero-buttons{display:flex;justify-content:center}html.theme--catppuccin-macchiato .hero-buttons .button:not(:last-child){margin-right:1.5rem}}html.theme--catppuccin-macchiato .hero-head,html.theme--catppuccin-macchiato .hero-foot{flex-grow:0;flex-shrink:0}html.theme--catppuccin-macchiato .hero-body{flex-grow:1;flex-shrink:0;padding:3rem 1.5rem}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .hero-body{padding:3rem 3rem}}html.theme--catppuccin-macchiato .section{padding:3rem 1.5rem}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .section{padding:3rem 3rem}html.theme--catppuccin-macchiato .section.is-medium{padding:9rem 4.5rem}html.theme--catppuccin-macchiato .section.is-large{padding:18rem 6rem}}html.theme--catppuccin-macchiato .footer{background-color:#1e2030;padding:3rem 1.5rem 6rem}html.theme--catppuccin-macchiato h1 .docs-heading-anchor,html.theme--catppuccin-macchiato h1 .docs-heading-anchor:hover,html.theme--catppuccin-macchiato h1 .docs-heading-anchor:visited,html.theme--catppuccin-macchiato h2 .docs-heading-anchor,html.theme--catppuccin-macchiato h2 .docs-heading-anchor:hover,html.theme--catppuccin-macchiato h2 .docs-heading-anchor:visited,html.theme--catppuccin-macchiato h3 .docs-heading-anchor,html.theme--catppuccin-macchiato h3 .docs-heading-anchor:hover,html.theme--catppuccin-macchiato h3 .docs-heading-anchor:visited,html.theme--catppuccin-macchiato h4 .docs-heading-anchor,html.theme--catppuccin-macchiato h4 .docs-heading-anchor:hover,html.theme--catppuccin-macchiato h4 .docs-heading-anchor:visited,html.theme--catppuccin-macchiato h5 .docs-heading-anchor,html.theme--catppuccin-macchiato h5 .docs-heading-anchor:hover,html.theme--catppuccin-macchiato h5 .docs-heading-anchor:visited,html.theme--catppuccin-macchiato h6 .docs-heading-anchor,html.theme--catppuccin-macchiato h6 .docs-heading-anchor:hover,html.theme--catppuccin-macchiato h6 .docs-heading-anchor:visited{color:#cad3f5}html.theme--catppuccin-macchiato h1 .docs-heading-anchor-permalink,html.theme--catppuccin-macchiato h2 .docs-heading-anchor-permalink,html.theme--catppuccin-macchiato h3 .docs-heading-anchor-permalink,html.theme--catppuccin-macchiato h4 .docs-heading-anchor-permalink,html.theme--catppuccin-macchiato h5 .docs-heading-anchor-permalink,html.theme--catppuccin-macchiato h6 .docs-heading-anchor-permalink{visibility:hidden;vertical-align:middle;margin-left:0.5em;font-size:0.7rem}html.theme--catppuccin-macchiato h1 .docs-heading-anchor-permalink::before,html.theme--catppuccin-macchiato h2 .docs-heading-anchor-permalink::before,html.theme--catppuccin-macchiato h3 .docs-heading-anchor-permalink::before,html.theme--catppuccin-macchiato h4 .docs-heading-anchor-permalink::before,html.theme--catppuccin-macchiato h5 .docs-heading-anchor-permalink::before,html.theme--catppuccin-macchiato h6 .docs-heading-anchor-permalink::before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f0c1"}html.theme--catppuccin-macchiato h1:hover .docs-heading-anchor-permalink,html.theme--catppuccin-macchiato h2:hover .docs-heading-anchor-permalink,html.theme--catppuccin-macchiato h3:hover .docs-heading-anchor-permalink,html.theme--catppuccin-macchiato h4:hover .docs-heading-anchor-permalink,html.theme--catppuccin-macchiato h5:hover .docs-heading-anchor-permalink,html.theme--catppuccin-macchiato h6:hover .docs-heading-anchor-permalink{visibility:visible}html.theme--catppuccin-macchiato .docs-light-only{display:none !important}html.theme--catppuccin-macchiato pre{position:relative;overflow:hidden}html.theme--catppuccin-macchiato pre code,html.theme--catppuccin-macchiato pre code.hljs{padding:0 .75rem !important;overflow:auto;display:block}html.theme--catppuccin-macchiato pre code:first-of-type,html.theme--catppuccin-macchiato pre code.hljs:first-of-type{padding-top:0.5rem !important}html.theme--catppuccin-macchiato pre code:last-of-type,html.theme--catppuccin-macchiato pre code.hljs:last-of-type{padding-bottom:0.5rem !important}html.theme--catppuccin-macchiato pre .copy-button{opacity:0.2;transition:opacity 0.2s;position:absolute;right:0em;top:0em;padding:0.5em;width:2.5em;height:2.5em;background:transparent;border:none;font-family:"Font Awesome 6 Free";color:#cad3f5;cursor:pointer;text-align:center}html.theme--catppuccin-macchiato pre .copy-button:focus,html.theme--catppuccin-macchiato pre .copy-button:hover{opacity:1;background:rgba(202,211,245,0.1);color:#8aadf4}html.theme--catppuccin-macchiato pre .copy-button.success{color:#a6da95;opacity:1}html.theme--catppuccin-macchiato pre .copy-button.error{color:#ed8796;opacity:1}html.theme--catppuccin-macchiato pre:hover .copy-button{opacity:1}html.theme--catppuccin-macchiato .admonition{background-color:#1e2030;border-style:solid;border-width:2px;border-color:#b8c0e0;border-radius:4px;font-size:1rem}html.theme--catppuccin-macchiato .admonition strong{color:currentColor}html.theme--catppuccin-macchiato .admonition.is-small,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.admonition{font-size:.75rem}html.theme--catppuccin-macchiato .admonition.is-medium{font-size:1.25rem}html.theme--catppuccin-macchiato .admonition.is-large{font-size:1.5rem}html.theme--catppuccin-macchiato .admonition.is-default{background-color:#1e2030;border-color:#b8c0e0}html.theme--catppuccin-macchiato .admonition.is-default>.admonition-header{background-color:rgba(0,0,0,0);color:#b8c0e0}html.theme--catppuccin-macchiato .admonition.is-default>.admonition-body{color:#cad3f5}html.theme--catppuccin-macchiato .admonition.is-info{background-color:#1e2030;border-color:#8bd5ca}html.theme--catppuccin-macchiato .admonition.is-info>.admonition-header{background-color:rgba(0,0,0,0);color:#8bd5ca}html.theme--catppuccin-macchiato .admonition.is-info>.admonition-body{color:#cad3f5}html.theme--catppuccin-macchiato .admonition.is-success{background-color:#1e2030;border-color:#a6da95}html.theme--catppuccin-macchiato .admonition.is-success>.admonition-header{background-color:rgba(0,0,0,0);color:#a6da95}html.theme--catppuccin-macchiato .admonition.is-success>.admonition-body{color:#cad3f5}html.theme--catppuccin-macchiato .admonition.is-warning{background-color:#1e2030;border-color:#eed49f}html.theme--catppuccin-macchiato .admonition.is-warning>.admonition-header{background-color:rgba(0,0,0,0);color:#eed49f}html.theme--catppuccin-macchiato .admonition.is-warning>.admonition-body{color:#cad3f5}html.theme--catppuccin-macchiato .admonition.is-danger{background-color:#1e2030;border-color:#ed8796}html.theme--catppuccin-macchiato .admonition.is-danger>.admonition-header{background-color:rgba(0,0,0,0);color:#ed8796}html.theme--catppuccin-macchiato .admonition.is-danger>.admonition-body{color:#cad3f5}html.theme--catppuccin-macchiato .admonition.is-compat{background-color:#1e2030;border-color:#91d7e3}html.theme--catppuccin-macchiato .admonition.is-compat>.admonition-header{background-color:rgba(0,0,0,0);color:#91d7e3}html.theme--catppuccin-macchiato .admonition.is-compat>.admonition-body{color:#cad3f5}html.theme--catppuccin-macchiato .admonition.is-todo{background-color:#1e2030;border-color:#c6a0f6}html.theme--catppuccin-macchiato .admonition.is-todo>.admonition-header{background-color:rgba(0,0,0,0);color:#c6a0f6}html.theme--catppuccin-macchiato .admonition.is-todo>.admonition-body{color:#cad3f5}html.theme--catppuccin-macchiato .admonition-header{color:#b8c0e0;background-color:rgba(0,0,0,0);align-items:center;font-weight:700;justify-content:space-between;line-height:1.25;padding:0.5rem .75rem;position:relative}html.theme--catppuccin-macchiato .admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;margin-right:.75rem;content:"\f06a"}html.theme--catppuccin-macchiato details.admonition.is-details>.admonition-header{list-style:none}html.theme--catppuccin-macchiato details.admonition.is-details>.admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f055"}html.theme--catppuccin-macchiato details.admonition.is-details[open]>.admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f056"}html.theme--catppuccin-macchiato .admonition-body{color:#cad3f5;padding:0.5rem .75rem}html.theme--catppuccin-macchiato .admonition-body pre{background-color:#1e2030}html.theme--catppuccin-macchiato .admonition-body code{background-color:#1e2030}html.theme--catppuccin-macchiato .docstring{margin-bottom:1em;background-color:rgba(0,0,0,0);border:2px solid #5b6078;border-radius:4px;box-shadow:none;max-width:100%}html.theme--catppuccin-macchiato .docstring>header{cursor:pointer;display:flex;flex-grow:1;align-items:stretch;padding:0.5rem .75rem;background-color:#1e2030;box-shadow:0 0.125em 0.25em rgba(10,10,10,0.1);box-shadow:none;border-bottom:1px solid #5b6078;overflow:auto}html.theme--catppuccin-macchiato .docstring>header code{background-color:transparent}html.theme--catppuccin-macchiato .docstring>header .docstring-article-toggle-button{min-width:1.1rem;padding:0.2rem 0.2rem 0.2rem 0}html.theme--catppuccin-macchiato .docstring>header .docstring-binding{margin-right:0.3em}html.theme--catppuccin-macchiato .docstring>header .docstring-category{margin-left:0.3em}html.theme--catppuccin-macchiato .docstring>section{position:relative;padding:.75rem .75rem;border-bottom:1px solid #5b6078}html.theme--catppuccin-macchiato .docstring>section:last-child{border-bottom:none}html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink{transition:opacity 0.3s;opacity:0;position:absolute;right:.375rem;bottom:.375rem}html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink:focus{opacity:1 !important}html.theme--catppuccin-macchiato .docstring:hover>section>a.docs-sourcelink{opacity:0.2}html.theme--catppuccin-macchiato .docstring:focus-within>section>a.docs-sourcelink{opacity:0.2}html.theme--catppuccin-macchiato .docstring>section:hover a.docs-sourcelink{opacity:1}html.theme--catppuccin-macchiato .documenter-example-output{background-color:#24273a}html.theme--catppuccin-macchiato .outdated-warning-overlay{position:fixed;top:0;left:0;right:0;box-shadow:0 0 10px rgba(0,0,0,0.3);z-index:999;background-color:#1e2030;color:#cad3f5;border-bottom:3px solid rgba(0,0,0,0);padding:10px 35px;text-align:center;font-size:15px}html.theme--catppuccin-macchiato .outdated-warning-overlay .outdated-warning-closer{position:absolute;top:calc(50% - 10px);right:18px;cursor:pointer;width:12px}html.theme--catppuccin-macchiato .outdated-warning-overlay a{color:#8aadf4}html.theme--catppuccin-macchiato .outdated-warning-overlay a:hover{color:#91d7e3}html.theme--catppuccin-macchiato .content pre{border:2px solid #5b6078;border-radius:4px}html.theme--catppuccin-macchiato .content code{font-weight:inherit}html.theme--catppuccin-macchiato .content a code{color:#8aadf4}html.theme--catppuccin-macchiato .content a:hover code{color:#91d7e3}html.theme--catppuccin-macchiato .content h1 code,html.theme--catppuccin-macchiato .content h2 code,html.theme--catppuccin-macchiato .content h3 code,html.theme--catppuccin-macchiato .content h4 code,html.theme--catppuccin-macchiato .content h5 code,html.theme--catppuccin-macchiato .content h6 code{color:#cad3f5}html.theme--catppuccin-macchiato .content table{display:block;width:initial;max-width:100%;overflow-x:auto}html.theme--catppuccin-macchiato .content blockquote>ul:first-child,html.theme--catppuccin-macchiato .content blockquote>ol:first-child,html.theme--catppuccin-macchiato .content .admonition-body>ul:first-child,html.theme--catppuccin-macchiato .content .admonition-body>ol:first-child{margin-top:0}html.theme--catppuccin-macchiato pre,html.theme--catppuccin-macchiato code{font-variant-ligatures:no-contextual}html.theme--catppuccin-macchiato .breadcrumb a.is-disabled{cursor:default;pointer-events:none}html.theme--catppuccin-macchiato .breadcrumb a.is-disabled,html.theme--catppuccin-macchiato .breadcrumb a.is-disabled:hover{color:#b5c1f1}html.theme--catppuccin-macchiato .hljs{background:initial !important}html.theme--catppuccin-macchiato .katex .katex-mathml{top:0;right:0}html.theme--catppuccin-macchiato .katex-display,html.theme--catppuccin-macchiato mjx-container,html.theme--catppuccin-macchiato .MathJax_Display{margin:0.5em 0 !important}html.theme--catppuccin-macchiato html{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto}html.theme--catppuccin-macchiato li.no-marker{list-style:none}html.theme--catppuccin-macchiato #documenter .docs-main>article{overflow-wrap:break-word}html.theme--catppuccin-macchiato #documenter .docs-main>article .math-container{overflow-x:auto;overflow-y:hidden}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato #documenter .docs-main{max-width:52rem;margin-left:20rem;padding-right:1rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato #documenter .docs-main{width:100%}html.theme--catppuccin-macchiato #documenter .docs-main>article{max-width:52rem;margin-left:auto;margin-right:auto;margin-bottom:1rem;padding:0 1rem}html.theme--catppuccin-macchiato #documenter .docs-main>header,html.theme--catppuccin-macchiato #documenter .docs-main>nav{max-width:100%;width:100%;margin:0}}html.theme--catppuccin-macchiato #documenter .docs-main header.docs-navbar{background-color:#24273a;border-bottom:1px solid #5b6078;z-index:2;min-height:4rem;margin-bottom:1rem;display:flex}html.theme--catppuccin-macchiato #documenter .docs-main header.docs-navbar .breadcrumb{flex-grow:1;overflow-x:hidden}html.theme--catppuccin-macchiato #documenter .docs-main header.docs-navbar .docs-sidebar-button{display:block;font-size:1.5rem;padding-bottom:0.1rem;margin-right:1rem}html.theme--catppuccin-macchiato #documenter .docs-main header.docs-navbar .docs-right{display:flex;white-space:nowrap;gap:1rem;align-items:center}html.theme--catppuccin-macchiato #documenter .docs-main header.docs-navbar .docs-right .docs-icon,html.theme--catppuccin-macchiato #documenter .docs-main header.docs-navbar .docs-right .docs-label{display:inline-block}html.theme--catppuccin-macchiato #documenter .docs-main header.docs-navbar .docs-right .docs-label{padding:0;margin-left:0.3em}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato #documenter .docs-main header.docs-navbar .docs-right .docs-navbar-link{margin-left:0.4rem;margin-right:0.4rem}}html.theme--catppuccin-macchiato #documenter .docs-main header.docs-navbar>*{margin:auto 0}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato #documenter .docs-main header.docs-navbar{position:sticky;top:0;padding:0 1rem;transition-property:top, box-shadow;-webkit-transition-property:top, box-shadow;transition-duration:0.3s;-webkit-transition-duration:0.3s}html.theme--catppuccin-macchiato #documenter .docs-main header.docs-navbar.headroom--not-top{box-shadow:.2rem 0rem .4rem #171717;transition-duration:0.7s;-webkit-transition-duration:0.7s}html.theme--catppuccin-macchiato #documenter .docs-main header.docs-navbar.headroom--unpinned.headroom--not-top.headroom--not-bottom{top:-4.5rem;transition-duration:0.7s;-webkit-transition-duration:0.7s}}html.theme--catppuccin-macchiato #documenter .docs-main section.footnotes{border-top:1px solid #5b6078}html.theme--catppuccin-macchiato #documenter .docs-main section.footnotes li .tag:first-child,html.theme--catppuccin-macchiato #documenter .docs-main section.footnotes li .docstring>section>a.docs-sourcelink:first-child,html.theme--catppuccin-macchiato #documenter .docs-main section.footnotes li .content kbd:first-child,html.theme--catppuccin-macchiato .content #documenter .docs-main section.footnotes li kbd:first-child{margin-right:1em;margin-bottom:0.4em}html.theme--catppuccin-macchiato #documenter .docs-main .docs-footer{display:flex;flex-wrap:wrap;margin-left:0;margin-right:0;border-top:1px solid #5b6078;padding-top:1rem;padding-bottom:1rem}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato #documenter .docs-main .docs-footer{padding-left:1rem;padding-right:1rem}}html.theme--catppuccin-macchiato #documenter .docs-main .docs-footer .docs-footer-nextpage,html.theme--catppuccin-macchiato #documenter .docs-main .docs-footer .docs-footer-prevpage{flex-grow:1}html.theme--catppuccin-macchiato #documenter .docs-main .docs-footer .docs-footer-nextpage{text-align:right}html.theme--catppuccin-macchiato #documenter .docs-main .docs-footer .flexbox-break{flex-basis:100%;height:0}html.theme--catppuccin-macchiato #documenter .docs-main .docs-footer .footer-message{font-size:0.8em;margin:0.5em auto 0 auto;text-align:center}html.theme--catppuccin-macchiato #documenter .docs-sidebar{display:flex;flex-direction:column;color:#cad3f5;background-color:#1e2030;border-right:1px solid #5b6078;padding:0;flex:0 0 18rem;z-index:5;font-size:1rem;position:fixed;left:-18rem;width:18rem;height:100%;transition:left 0.3s}html.theme--catppuccin-macchiato #documenter .docs-sidebar.visible{left:0;box-shadow:.4rem 0rem .8rem #171717}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato #documenter .docs-sidebar.visible{box-shadow:none}}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato #documenter .docs-sidebar{left:0;top:0}}html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo{margin-top:1rem;padding:0 1rem}html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img{max-height:6rem;margin:auto}html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-package-name{flex-shrink:0;font-size:1.5rem;font-weight:700;text-align:center;white-space:nowrap;overflow:hidden;padding:0.5rem 0}html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-package-name .docs-autofit{max-width:16.2rem}html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-package-name a,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-package-name a:hover{color:#cad3f5}html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-version-selector{border-top:1px solid #5b6078;display:none;padding:0.5rem}html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-version-selector.visible{display:flex}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu{flex-grow:1;user-select:none;border-top:1px solid #5b6078;padding-bottom:1.5rem}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu>li>.tocitem{font-weight:bold}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu>li li{font-size:.95rem;margin-left:1em;border-left:1px solid #5b6078}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu input.collapse-toggle{display:none}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu ul.collapsed{display:none}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu input:checked~ul.collapsed{display:block}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu label.tocitem{display:flex}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu label.tocitem .docs-label{flex-grow:2}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu label.tocitem .docs-chevron{display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1;font-size:.75rem;margin-left:1rem;margin-top:auto;margin-bottom:auto}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu label.tocitem .docs-chevron::before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f054"}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu input:checked~label.tocitem .docs-chevron::before{content:"\f078"}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu .tocitem{display:block;padding:0.5rem 0.5rem}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu .tocitem,html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu .tocitem:hover{color:#cad3f5;background:#1e2030}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu a.tocitem:hover,html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu label.tocitem:hover{color:#cad3f5;background-color:#26283d}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu li.is-active{border-top:1px solid #5b6078;border-bottom:1px solid #5b6078;background-color:#181926}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu li.is-active .tocitem,html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu li.is-active .tocitem:hover{background-color:#181926;color:#cad3f5}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu li.is-active ul.internal .tocitem:hover{background-color:#26283d;color:#cad3f5}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu>li.is-active:first-child{border-top:none}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu ul.internal{margin:0 0.5rem 0.5rem;border-top:1px solid #5b6078}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu ul.internal li{font-size:.85rem;border-left:none;margin-left:0;margin-top:0.5rem}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu ul.internal .tocitem{width:100%;padding:0}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu ul.internal .tocitem::before{content:"⚬";margin-right:0.4em}html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search{margin:auto;margin-top:0.5rem;margin-bottom:0.5rem}html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input{width:14.4rem}html.theme--catppuccin-macchiato #documenter .docs-sidebar #documenter-search-query{color:#868c98;width:14.4rem;box-shadow:inset 0 1px 2px rgba(10,10,10,0.1)}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu{overflow-y:auto;-webkit-overflow-scroll:touch}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar{width:.3rem;background:none}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar-thumb{border-radius:5px 0px 0px 5px;background:#2e3149}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar-thumb:hover{background:#3d4162}}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato #documenter .docs-sidebar{overflow-y:auto;-webkit-overflow-scroll:touch}html.theme--catppuccin-macchiato #documenter .docs-sidebar::-webkit-scrollbar{width:.3rem;background:none}html.theme--catppuccin-macchiato #documenter .docs-sidebar::-webkit-scrollbar-thumb{border-radius:5px 0px 0px 5px;background:#2e3149}html.theme--catppuccin-macchiato #documenter .docs-sidebar::-webkit-scrollbar-thumb:hover{background:#3d4162}}html.theme--catppuccin-macchiato kbd.search-modal-key-hints{border-radius:0.25rem;border:1px solid rgba(245,245,245,0.6);box-shadow:0 2px 0 1px rgba(245,245,245,0.6);cursor:default;font-size:0.9rem;line-height:1.5;min-width:0.75rem;text-align:center;padding:0.1rem 0.3rem;position:relative;top:-1px}html.theme--catppuccin-macchiato .search-min-width-50{min-width:50%}html.theme--catppuccin-macchiato .search-min-height-100{min-height:100%}html.theme--catppuccin-macchiato .search-modal-card-body{max-height:calc(100vh - 15rem)}html.theme--catppuccin-macchiato .search-result-link{border-radius:0.7em;transition:all 300ms}html.theme--catppuccin-macchiato .search-result-link:hover,html.theme--catppuccin-macchiato .search-result-link:focus{background-color:rgba(0,128,128,0.1)}html.theme--catppuccin-macchiato .search-result-link .property-search-result-badge,html.theme--catppuccin-macchiato .search-result-link .search-filter{transition:all 300ms}html.theme--catppuccin-macchiato .property-search-result-badge,html.theme--catppuccin-macchiato .search-filter{padding:0.15em 0.5em;font-size:0.8em;font-style:italic;text-transform:none !important;line-height:1.5;color:#f5f5f5;background-color:rgba(51,65,85,0.501961);border-radius:0.6rem}html.theme--catppuccin-macchiato .search-result-link:hover .property-search-result-badge,html.theme--catppuccin-macchiato .search-result-link:hover .search-filter,html.theme--catppuccin-macchiato .search-result-link:focus .property-search-result-badge,html.theme--catppuccin-macchiato .search-result-link:focus .search-filter{color:#333;background-color:#f1f5f9}html.theme--catppuccin-macchiato .search-filter{color:#333;background-color:#f5f5f5;transition:all 300ms}html.theme--catppuccin-macchiato .search-filter:hover,html.theme--catppuccin-macchiato .search-filter:focus{color:#333}html.theme--catppuccin-macchiato .search-filter-selected{color:#363a4f;background-color:#b7bdf8}html.theme--catppuccin-macchiato .search-filter-selected:hover,html.theme--catppuccin-macchiato .search-filter-selected:focus{color:#363a4f}html.theme--catppuccin-macchiato .search-result-highlight{background-color:#ffdd57;color:black}html.theme--catppuccin-macchiato .search-divider{border-bottom:1px solid #5b6078}html.theme--catppuccin-macchiato .search-result-title{width:85%;color:#f5f5f5}html.theme--catppuccin-macchiato .search-result-code-title{font-size:0.875rem;font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace}html.theme--catppuccin-macchiato #search-modal .modal-card-body::-webkit-scrollbar,html.theme--catppuccin-macchiato #search-modal .filter-tabs::-webkit-scrollbar{height:10px;width:10px;background-color:transparent}html.theme--catppuccin-macchiato #search-modal .modal-card-body::-webkit-scrollbar-thumb,html.theme--catppuccin-macchiato #search-modal .filter-tabs::-webkit-scrollbar-thumb{background-color:gray;border-radius:1rem}html.theme--catppuccin-macchiato #search-modal .modal-card-body::-webkit-scrollbar-track,html.theme--catppuccin-macchiato #search-modal .filter-tabs::-webkit-scrollbar-track{-webkit-box-shadow:inset 0 0 6px rgba(0,0,0,0.6);background-color:transparent}html.theme--catppuccin-macchiato .w-100{width:100%}html.theme--catppuccin-macchiato .gap-2{gap:0.5rem}html.theme--catppuccin-macchiato .gap-4{gap:1rem}html.theme--catppuccin-macchiato .gap-8{gap:2rem}html.theme--catppuccin-macchiato{background-color:#24273a;font-size:16px;min-width:300px;overflow-x:auto;overflow-y:scroll;text-rendering:optimizeLegibility;text-size-adjust:100%}html.theme--catppuccin-macchiato a{transition:all 200ms ease}html.theme--catppuccin-macchiato .label{color:#cad3f5}html.theme--catppuccin-macchiato .button,html.theme--catppuccin-macchiato .control.has-icons-left .icon,html.theme--catppuccin-macchiato .control.has-icons-right .icon,html.theme--catppuccin-macchiato .input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-macchiato .pagination-ellipsis,html.theme--catppuccin-macchiato .pagination-link,html.theme--catppuccin-macchiato .pagination-next,html.theme--catppuccin-macchiato .pagination-previous,html.theme--catppuccin-macchiato .select,html.theme--catppuccin-macchiato .select select,html.theme--catppuccin-macchiato .textarea{height:2.5em;color:#cad3f5}html.theme--catppuccin-macchiato .input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-macchiato .textarea{transition:all 200ms ease;box-shadow:none;border-width:1px;padding-left:1em;padding-right:1em;color:#cad3f5}html.theme--catppuccin-macchiato .select:after,html.theme--catppuccin-macchiato .select select{border-width:1px}html.theme--catppuccin-macchiato .menu-list a{transition:all 300ms ease}html.theme--catppuccin-macchiato .modal-card-foot,html.theme--catppuccin-macchiato .modal-card-head{border-color:#5b6078}html.theme--catppuccin-macchiato .navbar{border-radius:.4em}html.theme--catppuccin-macchiato .navbar.is-transparent{background:none}html.theme--catppuccin-macchiato .navbar.is-primary .navbar-dropdown a.navbar-item.is-active,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-dropdown a.navbar-item.is-active{background-color:#8aadf4}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .navbar .navbar-menu{background-color:#8aadf4;border-radius:0 0 .4em .4em}}html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink:not(body){color:#363a4f}html.theme--catppuccin-macchiato .tag.is-link:not(body),html.theme--catppuccin-macchiato .docstring>section>a.is-link.docs-sourcelink:not(body),html.theme--catppuccin-macchiato .content kbd.is-link:not(body){color:#363a4f}html.theme--catppuccin-macchiato .ansi span.sgr1{font-weight:bolder}html.theme--catppuccin-macchiato .ansi span.sgr2{font-weight:lighter}html.theme--catppuccin-macchiato .ansi span.sgr3{font-style:italic}html.theme--catppuccin-macchiato .ansi span.sgr4{text-decoration:underline}html.theme--catppuccin-macchiato .ansi span.sgr7{color:#24273a;background-color:#cad3f5}html.theme--catppuccin-macchiato .ansi span.sgr8{color:transparent}html.theme--catppuccin-macchiato .ansi span.sgr8 span{color:transparent}html.theme--catppuccin-macchiato .ansi span.sgr9{text-decoration:line-through}html.theme--catppuccin-macchiato .ansi span.sgr30{color:#494d64}html.theme--catppuccin-macchiato .ansi span.sgr31{color:#ed8796}html.theme--catppuccin-macchiato .ansi span.sgr32{color:#a6da95}html.theme--catppuccin-macchiato .ansi span.sgr33{color:#eed49f}html.theme--catppuccin-macchiato .ansi span.sgr34{color:#8aadf4}html.theme--catppuccin-macchiato .ansi span.sgr35{color:#f5bde6}html.theme--catppuccin-macchiato .ansi span.sgr36{color:#8bd5ca}html.theme--catppuccin-macchiato .ansi span.sgr37{color:#b8c0e0}html.theme--catppuccin-macchiato .ansi span.sgr40{background-color:#494d64}html.theme--catppuccin-macchiato .ansi span.sgr41{background-color:#ed8796}html.theme--catppuccin-macchiato .ansi span.sgr42{background-color:#a6da95}html.theme--catppuccin-macchiato .ansi span.sgr43{background-color:#eed49f}html.theme--catppuccin-macchiato .ansi span.sgr44{background-color:#8aadf4}html.theme--catppuccin-macchiato .ansi span.sgr45{background-color:#f5bde6}html.theme--catppuccin-macchiato .ansi span.sgr46{background-color:#8bd5ca}html.theme--catppuccin-macchiato .ansi span.sgr47{background-color:#b8c0e0}html.theme--catppuccin-macchiato .ansi span.sgr90{color:#5b6078}html.theme--catppuccin-macchiato .ansi span.sgr91{color:#ed8796}html.theme--catppuccin-macchiato .ansi span.sgr92{color:#a6da95}html.theme--catppuccin-macchiato .ansi span.sgr93{color:#eed49f}html.theme--catppuccin-macchiato .ansi span.sgr94{color:#8aadf4}html.theme--catppuccin-macchiato .ansi span.sgr95{color:#f5bde6}html.theme--catppuccin-macchiato .ansi span.sgr96{color:#8bd5ca}html.theme--catppuccin-macchiato .ansi span.sgr97{color:#a5adcb}html.theme--catppuccin-macchiato .ansi span.sgr100{background-color:#5b6078}html.theme--catppuccin-macchiato .ansi span.sgr101{background-color:#ed8796}html.theme--catppuccin-macchiato .ansi span.sgr102{background-color:#a6da95}html.theme--catppuccin-macchiato .ansi span.sgr103{background-color:#eed49f}html.theme--catppuccin-macchiato .ansi span.sgr104{background-color:#8aadf4}html.theme--catppuccin-macchiato .ansi span.sgr105{background-color:#f5bde6}html.theme--catppuccin-macchiato .ansi span.sgr106{background-color:#8bd5ca}html.theme--catppuccin-macchiato .ansi span.sgr107{background-color:#a5adcb}html.theme--catppuccin-macchiato code.language-julia-repl>span.hljs-meta{color:#a6da95;font-weight:bolder}html.theme--catppuccin-macchiato code .hljs{color:#cad3f5;background:#24273a}html.theme--catppuccin-macchiato code .hljs-keyword{color:#c6a0f6}html.theme--catppuccin-macchiato code .hljs-built_in{color:#ed8796}html.theme--catppuccin-macchiato code .hljs-type{color:#eed49f}html.theme--catppuccin-macchiato code .hljs-literal{color:#f5a97f}html.theme--catppuccin-macchiato code .hljs-number{color:#f5a97f}html.theme--catppuccin-macchiato code .hljs-operator{color:#8bd5ca}html.theme--catppuccin-macchiato code .hljs-punctuation{color:#b8c0e0}html.theme--catppuccin-macchiato code .hljs-property{color:#8bd5ca}html.theme--catppuccin-macchiato code .hljs-regexp{color:#f5bde6}html.theme--catppuccin-macchiato code .hljs-string{color:#a6da95}html.theme--catppuccin-macchiato code .hljs-char.escape_{color:#a6da95}html.theme--catppuccin-macchiato code .hljs-subst{color:#a5adcb}html.theme--catppuccin-macchiato code .hljs-symbol{color:#f0c6c6}html.theme--catppuccin-macchiato code .hljs-variable{color:#c6a0f6}html.theme--catppuccin-macchiato code .hljs-variable.language_{color:#c6a0f6}html.theme--catppuccin-macchiato code .hljs-variable.constant_{color:#f5a97f}html.theme--catppuccin-macchiato code .hljs-title{color:#8aadf4}html.theme--catppuccin-macchiato code .hljs-title.class_{color:#eed49f}html.theme--catppuccin-macchiato code .hljs-title.function_{color:#8aadf4}html.theme--catppuccin-macchiato code .hljs-params{color:#cad3f5}html.theme--catppuccin-macchiato code .hljs-comment{color:#5b6078}html.theme--catppuccin-macchiato code .hljs-doctag{color:#ed8796}html.theme--catppuccin-macchiato code .hljs-meta{color:#f5a97f}html.theme--catppuccin-macchiato code .hljs-section{color:#8aadf4}html.theme--catppuccin-macchiato code .hljs-tag{color:#a5adcb}html.theme--catppuccin-macchiato code .hljs-name{color:#c6a0f6}html.theme--catppuccin-macchiato code .hljs-attr{color:#8aadf4}html.theme--catppuccin-macchiato code .hljs-attribute{color:#a6da95}html.theme--catppuccin-macchiato code .hljs-bullet{color:#8bd5ca}html.theme--catppuccin-macchiato code .hljs-code{color:#a6da95}html.theme--catppuccin-macchiato code .hljs-emphasis{color:#ed8796;font-style:italic}html.theme--catppuccin-macchiato code .hljs-strong{color:#ed8796;font-weight:bold}html.theme--catppuccin-macchiato code .hljs-formula{color:#8bd5ca}html.theme--catppuccin-macchiato code .hljs-link{color:#7dc4e4;font-style:italic}html.theme--catppuccin-macchiato code .hljs-quote{color:#a6da95;font-style:italic}html.theme--catppuccin-macchiato code .hljs-selector-tag{color:#eed49f}html.theme--catppuccin-macchiato code .hljs-selector-id{color:#8aadf4}html.theme--catppuccin-macchiato code .hljs-selector-class{color:#8bd5ca}html.theme--catppuccin-macchiato code .hljs-selector-attr{color:#c6a0f6}html.theme--catppuccin-macchiato code .hljs-selector-pseudo{color:#8bd5ca}html.theme--catppuccin-macchiato code .hljs-template-tag{color:#f0c6c6}html.theme--catppuccin-macchiato code .hljs-template-variable{color:#f0c6c6}html.theme--catppuccin-macchiato code .hljs-addition{color:#a6da95;background:rgba(166,227,161,0.15)}html.theme--catppuccin-macchiato code .hljs-deletion{color:#ed8796;background:rgba(243,139,168,0.15)}html.theme--catppuccin-macchiato .search-result-link{border-radius:0.7em;transition:all 300ms}html.theme--catppuccin-macchiato .search-result-link:hover,html.theme--catppuccin-macchiato .search-result-link:focus{background-color:#363a4f}html.theme--catppuccin-macchiato .search-result-link .property-search-result-badge,html.theme--catppuccin-macchiato .search-result-link .search-filter{transition:all 300ms}html.theme--catppuccin-macchiato .search-result-link:hover .property-search-result-badge,html.theme--catppuccin-macchiato .search-result-link:hover .search-filter,html.theme--catppuccin-macchiato .search-result-link:focus .property-search-result-badge,html.theme--catppuccin-macchiato .search-result-link:focus .search-filter{color:#363a4f !important;background-color:#b7bdf8 !important}html.theme--catppuccin-macchiato .search-result-title{color:#cad3f5}html.theme--catppuccin-macchiato .search-result-highlight{background-color:#ed8796;color:#1e2030}html.theme--catppuccin-macchiato .search-divider{border-bottom:1px solid #5e6d6f50}html.theme--catppuccin-macchiato .w-100{width:100%}html.theme--catppuccin-macchiato .gap-2{gap:0.5rem}html.theme--catppuccin-macchiato .gap-4{gap:1rem} diff --git a/previews/PR798/assets/themes/catppuccin-mocha.css b/previews/PR798/assets/themes/catppuccin-mocha.css new file mode 100644 index 0000000000..8b82652560 --- /dev/null +++ b/previews/PR798/assets/themes/catppuccin-mocha.css @@ -0,0 +1 @@ +html.theme--catppuccin-mocha .pagination-previous,html.theme--catppuccin-mocha .pagination-next,html.theme--catppuccin-mocha .pagination-link,html.theme--catppuccin-mocha .pagination-ellipsis,html.theme--catppuccin-mocha .file-cta,html.theme--catppuccin-mocha .file-name,html.theme--catppuccin-mocha .select select,html.theme--catppuccin-mocha .textarea,html.theme--catppuccin-mocha .input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-mocha .button{-moz-appearance:none;-webkit-appearance:none;align-items:center;border:1px solid transparent;border-radius:.4em;box-shadow:none;display:inline-flex;font-size:1rem;height:2.5em;justify-content:flex-start;line-height:1.5;padding-bottom:calc(0.5em - 1px);padding-left:calc(0.75em - 1px);padding-right:calc(0.75em - 1px);padding-top:calc(0.5em - 1px);position:relative;vertical-align:top}html.theme--catppuccin-mocha .pagination-previous:focus,html.theme--catppuccin-mocha .pagination-next:focus,html.theme--catppuccin-mocha .pagination-link:focus,html.theme--catppuccin-mocha .pagination-ellipsis:focus,html.theme--catppuccin-mocha .file-cta:focus,html.theme--catppuccin-mocha .file-name:focus,html.theme--catppuccin-mocha .select select:focus,html.theme--catppuccin-mocha .textarea:focus,html.theme--catppuccin-mocha .input:focus,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input:focus,html.theme--catppuccin-mocha .button:focus,html.theme--catppuccin-mocha .is-focused.pagination-previous,html.theme--catppuccin-mocha .is-focused.pagination-next,html.theme--catppuccin-mocha .is-focused.pagination-link,html.theme--catppuccin-mocha .is-focused.pagination-ellipsis,html.theme--catppuccin-mocha .is-focused.file-cta,html.theme--catppuccin-mocha .is-focused.file-name,html.theme--catppuccin-mocha .select select.is-focused,html.theme--catppuccin-mocha .is-focused.textarea,html.theme--catppuccin-mocha .is-focused.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-mocha .is-focused.button,html.theme--catppuccin-mocha .pagination-previous:active,html.theme--catppuccin-mocha .pagination-next:active,html.theme--catppuccin-mocha .pagination-link:active,html.theme--catppuccin-mocha .pagination-ellipsis:active,html.theme--catppuccin-mocha .file-cta:active,html.theme--catppuccin-mocha .file-name:active,html.theme--catppuccin-mocha .select select:active,html.theme--catppuccin-mocha .textarea:active,html.theme--catppuccin-mocha .input:active,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input:active,html.theme--catppuccin-mocha .button:active,html.theme--catppuccin-mocha .is-active.pagination-previous,html.theme--catppuccin-mocha .is-active.pagination-next,html.theme--catppuccin-mocha .is-active.pagination-link,html.theme--catppuccin-mocha .is-active.pagination-ellipsis,html.theme--catppuccin-mocha .is-active.file-cta,html.theme--catppuccin-mocha .is-active.file-name,html.theme--catppuccin-mocha .select select.is-active,html.theme--catppuccin-mocha .is-active.textarea,html.theme--catppuccin-mocha .is-active.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-active,html.theme--catppuccin-mocha .is-active.button{outline:none}html.theme--catppuccin-mocha .pagination-previous[disabled],html.theme--catppuccin-mocha .pagination-next[disabled],html.theme--catppuccin-mocha .pagination-link[disabled],html.theme--catppuccin-mocha .pagination-ellipsis[disabled],html.theme--catppuccin-mocha .file-cta[disabled],html.theme--catppuccin-mocha .file-name[disabled],html.theme--catppuccin-mocha .select select[disabled],html.theme--catppuccin-mocha .textarea[disabled],html.theme--catppuccin-mocha .input[disabled],html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input[disabled],html.theme--catppuccin-mocha .button[disabled],fieldset[disabled] html.theme--catppuccin-mocha .pagination-previous,html.theme--catppuccin-mocha fieldset[disabled] .pagination-previous,fieldset[disabled] html.theme--catppuccin-mocha .pagination-next,html.theme--catppuccin-mocha fieldset[disabled] .pagination-next,fieldset[disabled] html.theme--catppuccin-mocha .pagination-link,html.theme--catppuccin-mocha fieldset[disabled] .pagination-link,fieldset[disabled] html.theme--catppuccin-mocha .pagination-ellipsis,html.theme--catppuccin-mocha fieldset[disabled] .pagination-ellipsis,fieldset[disabled] html.theme--catppuccin-mocha .file-cta,html.theme--catppuccin-mocha fieldset[disabled] .file-cta,fieldset[disabled] html.theme--catppuccin-mocha .file-name,html.theme--catppuccin-mocha fieldset[disabled] .file-name,fieldset[disabled] html.theme--catppuccin-mocha .select select,fieldset[disabled] html.theme--catppuccin-mocha .textarea,fieldset[disabled] html.theme--catppuccin-mocha .input,fieldset[disabled] html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-mocha fieldset[disabled] .select select,html.theme--catppuccin-mocha .select fieldset[disabled] select,html.theme--catppuccin-mocha fieldset[disabled] .textarea,html.theme--catppuccin-mocha fieldset[disabled] .input,html.theme--catppuccin-mocha fieldset[disabled] #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-mocha #documenter .docs-sidebar fieldset[disabled] form.docs-search>input,fieldset[disabled] html.theme--catppuccin-mocha .button,html.theme--catppuccin-mocha fieldset[disabled] .button{cursor:not-allowed}html.theme--catppuccin-mocha .tabs,html.theme--catppuccin-mocha .pagination-previous,html.theme--catppuccin-mocha .pagination-next,html.theme--catppuccin-mocha .pagination-link,html.theme--catppuccin-mocha .pagination-ellipsis,html.theme--catppuccin-mocha .breadcrumb,html.theme--catppuccin-mocha .file,html.theme--catppuccin-mocha .button,.is-unselectable{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}html.theme--catppuccin-mocha .navbar-link:not(.is-arrowless)::after,html.theme--catppuccin-mocha .select:not(.is-multiple):not(.is-loading)::after{border:3px solid rgba(0,0,0,0);border-radius:2px;border-right:0;border-top:0;content:" ";display:block;height:0.625em;margin-top:-0.4375em;pointer-events:none;position:absolute;top:50%;transform:rotate(-45deg);transform-origin:center;width:0.625em}html.theme--catppuccin-mocha .admonition:not(:last-child),html.theme--catppuccin-mocha .tabs:not(:last-child),html.theme--catppuccin-mocha .pagination:not(:last-child),html.theme--catppuccin-mocha .message:not(:last-child),html.theme--catppuccin-mocha .level:not(:last-child),html.theme--catppuccin-mocha .breadcrumb:not(:last-child),html.theme--catppuccin-mocha .block:not(:last-child),html.theme--catppuccin-mocha .title:not(:last-child),html.theme--catppuccin-mocha .subtitle:not(:last-child),html.theme--catppuccin-mocha .table-container:not(:last-child),html.theme--catppuccin-mocha .table:not(:last-child),html.theme--catppuccin-mocha .progress:not(:last-child),html.theme--catppuccin-mocha .notification:not(:last-child),html.theme--catppuccin-mocha .content:not(:last-child),html.theme--catppuccin-mocha .box:not(:last-child){margin-bottom:1.5rem}html.theme--catppuccin-mocha .modal-close,html.theme--catppuccin-mocha .delete{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-moz-appearance:none;-webkit-appearance:none;background-color:rgba(10,10,10,0.2);border:none;border-radius:9999px;cursor:pointer;pointer-events:auto;display:inline-block;flex-grow:0;flex-shrink:0;font-size:0;height:20px;max-height:20px;max-width:20px;min-height:20px;min-width:20px;outline:none;position:relative;vertical-align:top;width:20px}html.theme--catppuccin-mocha .modal-close::before,html.theme--catppuccin-mocha .delete::before,html.theme--catppuccin-mocha .modal-close::after,html.theme--catppuccin-mocha .delete::after{background-color:#fff;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}html.theme--catppuccin-mocha .modal-close::before,html.theme--catppuccin-mocha .delete::before{height:2px;width:50%}html.theme--catppuccin-mocha .modal-close::after,html.theme--catppuccin-mocha .delete::after{height:50%;width:2px}html.theme--catppuccin-mocha .modal-close:hover,html.theme--catppuccin-mocha .delete:hover,html.theme--catppuccin-mocha .modal-close:focus,html.theme--catppuccin-mocha .delete:focus{background-color:rgba(10,10,10,0.3)}html.theme--catppuccin-mocha .modal-close:active,html.theme--catppuccin-mocha .delete:active{background-color:rgba(10,10,10,0.4)}html.theme--catppuccin-mocha .is-small.modal-close,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.modal-close,html.theme--catppuccin-mocha .is-small.delete,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.delete{height:16px;max-height:16px;max-width:16px;min-height:16px;min-width:16px;width:16px}html.theme--catppuccin-mocha .is-medium.modal-close,html.theme--catppuccin-mocha .is-medium.delete{height:24px;max-height:24px;max-width:24px;min-height:24px;min-width:24px;width:24px}html.theme--catppuccin-mocha .is-large.modal-close,html.theme--catppuccin-mocha .is-large.delete{height:32px;max-height:32px;max-width:32px;min-height:32px;min-width:32px;width:32px}html.theme--catppuccin-mocha .control.is-loading::after,html.theme--catppuccin-mocha .select.is-loading::after,html.theme--catppuccin-mocha .loader,html.theme--catppuccin-mocha .button.is-loading::after{animation:spinAround 500ms infinite linear;border:2px solid #7f849c;border-radius:9999px;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:1em;position:relative;width:1em}html.theme--catppuccin-mocha .hero-video,html.theme--catppuccin-mocha .modal-background,html.theme--catppuccin-mocha .modal,html.theme--catppuccin-mocha .image.is-square img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-square img,html.theme--catppuccin-mocha .image.is-square .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-square .has-ratio,html.theme--catppuccin-mocha .image.is-1by1 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-1by1 img,html.theme--catppuccin-mocha .image.is-1by1 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-1by1 .has-ratio,html.theme--catppuccin-mocha .image.is-5by4 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-5by4 img,html.theme--catppuccin-mocha .image.is-5by4 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-5by4 .has-ratio,html.theme--catppuccin-mocha .image.is-4by3 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-4by3 img,html.theme--catppuccin-mocha .image.is-4by3 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-4by3 .has-ratio,html.theme--catppuccin-mocha .image.is-3by2 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by2 img,html.theme--catppuccin-mocha .image.is-3by2 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by2 .has-ratio,html.theme--catppuccin-mocha .image.is-5by3 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-5by3 img,html.theme--catppuccin-mocha .image.is-5by3 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-5by3 .has-ratio,html.theme--catppuccin-mocha .image.is-16by9 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-16by9 img,html.theme--catppuccin-mocha .image.is-16by9 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-16by9 .has-ratio,html.theme--catppuccin-mocha .image.is-2by1 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-2by1 img,html.theme--catppuccin-mocha .image.is-2by1 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-2by1 .has-ratio,html.theme--catppuccin-mocha .image.is-3by1 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by1 img,html.theme--catppuccin-mocha .image.is-3by1 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by1 .has-ratio,html.theme--catppuccin-mocha .image.is-4by5 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-4by5 img,html.theme--catppuccin-mocha .image.is-4by5 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-4by5 .has-ratio,html.theme--catppuccin-mocha .image.is-3by4 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by4 img,html.theme--catppuccin-mocha .image.is-3by4 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by4 .has-ratio,html.theme--catppuccin-mocha .image.is-2by3 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-2by3 img,html.theme--catppuccin-mocha .image.is-2by3 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-2by3 .has-ratio,html.theme--catppuccin-mocha .image.is-3by5 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by5 img,html.theme--catppuccin-mocha .image.is-3by5 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by5 .has-ratio,html.theme--catppuccin-mocha .image.is-9by16 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-9by16 img,html.theme--catppuccin-mocha .image.is-9by16 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-9by16 .has-ratio,html.theme--catppuccin-mocha .image.is-1by2 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-1by2 img,html.theme--catppuccin-mocha .image.is-1by2 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-1by2 .has-ratio,html.theme--catppuccin-mocha .image.is-1by3 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-1by3 img,html.theme--catppuccin-mocha .image.is-1by3 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-1by3 .has-ratio,.is-overlay{bottom:0;left:0;position:absolute;right:0;top:0}html.theme--catppuccin-mocha .navbar-burger{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;color:currentColor;font-family:inherit;font-size:1em;margin:0;padding:0}/*! minireset.css v0.0.6 | MIT License | github.com/jgthms/minireset.css */html,body,p,ol,ul,li,dl,dt,dd,blockquote,figure,fieldset,legend,textarea,pre,iframe,hr,h1,h2,h3,h4,h5,h6{margin:0;padding:0}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal}ul{list-style:none}button,input,select,textarea{margin:0}html{box-sizing:border-box}*,*::before,*::after{box-sizing:inherit}img,video{height:auto;max-width:100%}iframe{border:0}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}td:not([align]),th:not([align]){text-align:inherit}.has-text-white{color:#fff !important}a.has-text-white:hover,a.has-text-white:focus{color:#e6e6e6 !important}.has-background-white{background-color:#fff !important}.has-text-black{color:#0a0a0a !important}a.has-text-black:hover,a.has-text-black:focus{color:#000 !important}.has-background-black{background-color:#0a0a0a !important}.has-text-light{color:#f5f5f5 !important}a.has-text-light:hover,a.has-text-light:focus{color:#dbdbdb !important}.has-background-light{background-color:#f5f5f5 !important}.has-text-dark{color:#313244 !important}a.has-text-dark:hover,a.has-text-dark:focus{color:#1c1c26 !important}.has-background-dark{background-color:#313244 !important}.has-text-primary{color:#89b4fa !important}a.has-text-primary:hover,a.has-text-primary:focus{color:#5895f8 !important}.has-background-primary{background-color:#89b4fa !important}.has-text-primary-light{color:#ebf3fe !important}a.has-text-primary-light:hover,a.has-text-primary-light:focus{color:#bbd3fc !important}.has-background-primary-light{background-color:#ebf3fe !important}.has-text-primary-dark{color:#063c93 !important}a.has-text-primary-dark:hover,a.has-text-primary-dark:focus{color:#0850c4 !important}.has-background-primary-dark{background-color:#063c93 !important}.has-text-link{color:#89b4fa !important}a.has-text-link:hover,a.has-text-link:focus{color:#5895f8 !important}.has-background-link{background-color:#89b4fa !important}.has-text-link-light{color:#ebf3fe !important}a.has-text-link-light:hover,a.has-text-link-light:focus{color:#bbd3fc !important}.has-background-link-light{background-color:#ebf3fe !important}.has-text-link-dark{color:#063c93 !important}a.has-text-link-dark:hover,a.has-text-link-dark:focus{color:#0850c4 !important}.has-background-link-dark{background-color:#063c93 !important}.has-text-info{color:#94e2d5 !important}a.has-text-info:hover,a.has-text-info:focus{color:#6cd7c5 !important}.has-background-info{background-color:#94e2d5 !important}.has-text-info-light{color:#effbf9 !important}a.has-text-info-light:hover,a.has-text-info-light:focus{color:#c7f0e9 !important}.has-background-info-light{background-color:#effbf9 !important}.has-text-info-dark{color:#207466 !important}a.has-text-info-dark:hover,a.has-text-info-dark:focus{color:#2a9c89 !important}.has-background-info-dark{background-color:#207466 !important}.has-text-success{color:#a6e3a1 !important}a.has-text-success:hover,a.has-text-success:focus{color:#81d77a !important}.has-background-success{background-color:#a6e3a1 !important}.has-text-success-light{color:#f0faef !important}a.has-text-success-light:hover,a.has-text-success-light:focus{color:#cbefc8 !important}.has-background-success-light{background-color:#f0faef !important}.has-text-success-dark{color:#287222 !important}a.has-text-success-dark:hover,a.has-text-success-dark:focus{color:#36992e !important}.has-background-success-dark{background-color:#287222 !important}.has-text-warning{color:#f9e2af !important}a.has-text-warning:hover,a.has-text-warning:focus{color:#f5d180 !important}.has-background-warning{background-color:#f9e2af !important}.has-text-warning-light{color:#fef8ec !important}a.has-text-warning-light:hover,a.has-text-warning-light:focus{color:#fae7bd !important}.has-background-warning-light{background-color:#fef8ec !important}.has-text-warning-dark{color:#8a620a !important}a.has-text-warning-dark:hover,a.has-text-warning-dark:focus{color:#b9840e !important}.has-background-warning-dark{background-color:#8a620a !important}.has-text-danger{color:#f38ba8 !important}a.has-text-danger:hover,a.has-text-danger:focus{color:#ee5d85 !important}.has-background-danger{background-color:#f38ba8 !important}.has-text-danger-light{color:#fdedf1 !important}a.has-text-danger-light:hover,a.has-text-danger-light:focus{color:#f8bece !important}.has-background-danger-light{background-color:#fdedf1 !important}.has-text-danger-dark{color:#991036 !important}a.has-text-danger-dark:hover,a.has-text-danger-dark:focus{color:#c71546 !important}.has-background-danger-dark{background-color:#991036 !important}.has-text-black-bis{color:#121212 !important}.has-background-black-bis{background-color:#121212 !important}.has-text-black-ter{color:#242424 !important}.has-background-black-ter{background-color:#242424 !important}.has-text-grey-darker{color:#313244 !important}.has-background-grey-darker{background-color:#313244 !important}.has-text-grey-dark{color:#45475a !important}.has-background-grey-dark{background-color:#45475a !important}.has-text-grey{color:#585b70 !important}.has-background-grey{background-color:#585b70 !important}.has-text-grey-light{color:#6c7086 !important}.has-background-grey-light{background-color:#6c7086 !important}.has-text-grey-lighter{color:#7f849c !important}.has-background-grey-lighter{background-color:#7f849c !important}.has-text-white-ter{color:#f5f5f5 !important}.has-background-white-ter{background-color:#f5f5f5 !important}.has-text-white-bis{color:#fafafa !important}.has-background-white-bis{background-color:#fafafa !important}.is-flex-direction-row{flex-direction:row !important}.is-flex-direction-row-reverse{flex-direction:row-reverse !important}.is-flex-direction-column{flex-direction:column !important}.is-flex-direction-column-reverse{flex-direction:column-reverse !important}.is-flex-wrap-nowrap{flex-wrap:nowrap !important}.is-flex-wrap-wrap{flex-wrap:wrap !important}.is-flex-wrap-wrap-reverse{flex-wrap:wrap-reverse !important}.is-justify-content-flex-start{justify-content:flex-start !important}.is-justify-content-flex-end{justify-content:flex-end !important}.is-justify-content-center{justify-content:center !important}.is-justify-content-space-between{justify-content:space-between !important}.is-justify-content-space-around{justify-content:space-around !important}.is-justify-content-space-evenly{justify-content:space-evenly !important}.is-justify-content-start{justify-content:start !important}.is-justify-content-end{justify-content:end !important}.is-justify-content-left{justify-content:left !important}.is-justify-content-right{justify-content:right !important}.is-align-content-flex-start{align-content:flex-start !important}.is-align-content-flex-end{align-content:flex-end !important}.is-align-content-center{align-content:center !important}.is-align-content-space-between{align-content:space-between !important}.is-align-content-space-around{align-content:space-around !important}.is-align-content-space-evenly{align-content:space-evenly !important}.is-align-content-stretch{align-content:stretch !important}.is-align-content-start{align-content:start !important}.is-align-content-end{align-content:end !important}.is-align-content-baseline{align-content:baseline !important}.is-align-items-stretch{align-items:stretch !important}.is-align-items-flex-start{align-items:flex-start !important}.is-align-items-flex-end{align-items:flex-end !important}.is-align-items-center{align-items:center !important}.is-align-items-baseline{align-items:baseline !important}.is-align-items-start{align-items:start !important}.is-align-items-end{align-items:end !important}.is-align-items-self-start{align-items:self-start !important}.is-align-items-self-end{align-items:self-end !important}.is-align-self-auto{align-self:auto !important}.is-align-self-flex-start{align-self:flex-start !important}.is-align-self-flex-end{align-self:flex-end !important}.is-align-self-center{align-self:center !important}.is-align-self-baseline{align-self:baseline !important}.is-align-self-stretch{align-self:stretch !important}.is-flex-grow-0{flex-grow:0 !important}.is-flex-grow-1{flex-grow:1 !important}.is-flex-grow-2{flex-grow:2 !important}.is-flex-grow-3{flex-grow:3 !important}.is-flex-grow-4{flex-grow:4 !important}.is-flex-grow-5{flex-grow:5 !important}.is-flex-shrink-0{flex-shrink:0 !important}.is-flex-shrink-1{flex-shrink:1 !important}.is-flex-shrink-2{flex-shrink:2 !important}.is-flex-shrink-3{flex-shrink:3 !important}.is-flex-shrink-4{flex-shrink:4 !important}.is-flex-shrink-5{flex-shrink:5 !important}.is-clearfix::after{clear:both;content:" ";display:table}.is-pulled-left{float:left !important}.is-pulled-right{float:right !important}.is-radiusless{border-radius:0 !important}.is-shadowless{box-shadow:none !important}.is-clickable{cursor:pointer !important;pointer-events:all !important}.is-clipped{overflow:hidden !important}.is-relative{position:relative !important}.is-marginless{margin:0 !important}.is-paddingless{padding:0 !important}.m-0{margin:0 !important}.mt-0{margin-top:0 !important}.mr-0{margin-right:0 !important}.mb-0{margin-bottom:0 !important}.ml-0{margin-left:0 !important}.mx-0{margin-left:0 !important;margin-right:0 !important}.my-0{margin-top:0 !important;margin-bottom:0 !important}.m-1{margin:.25rem !important}.mt-1{margin-top:.25rem !important}.mr-1{margin-right:.25rem !important}.mb-1{margin-bottom:.25rem !important}.ml-1{margin-left:.25rem !important}.mx-1{margin-left:.25rem !important;margin-right:.25rem !important}.my-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.m-2{margin:.5rem !important}.mt-2{margin-top:.5rem !important}.mr-2{margin-right:.5rem !important}.mb-2{margin-bottom:.5rem !important}.ml-2{margin-left:.5rem !important}.mx-2{margin-left:.5rem !important;margin-right:.5rem !important}.my-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.m-3{margin:.75rem !important}.mt-3{margin-top:.75rem !important}.mr-3{margin-right:.75rem !important}.mb-3{margin-bottom:.75rem !important}.ml-3{margin-left:.75rem !important}.mx-3{margin-left:.75rem !important;margin-right:.75rem !important}.my-3{margin-top:.75rem !important;margin-bottom:.75rem !important}.m-4{margin:1rem !important}.mt-4{margin-top:1rem !important}.mr-4{margin-right:1rem !important}.mb-4{margin-bottom:1rem !important}.ml-4{margin-left:1rem !important}.mx-4{margin-left:1rem !important;margin-right:1rem !important}.my-4{margin-top:1rem !important;margin-bottom:1rem !important}.m-5{margin:1.5rem !important}.mt-5{margin-top:1.5rem !important}.mr-5{margin-right:1.5rem !important}.mb-5{margin-bottom:1.5rem !important}.ml-5{margin-left:1.5rem !important}.mx-5{margin-left:1.5rem !important;margin-right:1.5rem !important}.my-5{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.m-6{margin:3rem !important}.mt-6{margin-top:3rem !important}.mr-6{margin-right:3rem !important}.mb-6{margin-bottom:3rem !important}.ml-6{margin-left:3rem !important}.mx-6{margin-left:3rem !important;margin-right:3rem !important}.my-6{margin-top:3rem !important;margin-bottom:3rem !important}.m-auto{margin:auto !important}.mt-auto{margin-top:auto !important}.mr-auto{margin-right:auto !important}.mb-auto{margin-bottom:auto !important}.ml-auto{margin-left:auto !important}.mx-auto{margin-left:auto !important;margin-right:auto !important}.my-auto{margin-top:auto !important;margin-bottom:auto !important}.p-0{padding:0 !important}.pt-0{padding-top:0 !important}.pr-0{padding-right:0 !important}.pb-0{padding-bottom:0 !important}.pl-0{padding-left:0 !important}.px-0{padding-left:0 !important;padding-right:0 !important}.py-0{padding-top:0 !important;padding-bottom:0 !important}.p-1{padding:.25rem !important}.pt-1{padding-top:.25rem !important}.pr-1{padding-right:.25rem !important}.pb-1{padding-bottom:.25rem !important}.pl-1{padding-left:.25rem !important}.px-1{padding-left:.25rem !important;padding-right:.25rem !important}.py-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.p-2{padding:.5rem !important}.pt-2{padding-top:.5rem !important}.pr-2{padding-right:.5rem !important}.pb-2{padding-bottom:.5rem !important}.pl-2{padding-left:.5rem !important}.px-2{padding-left:.5rem !important;padding-right:.5rem !important}.py-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.p-3{padding:.75rem !important}.pt-3{padding-top:.75rem !important}.pr-3{padding-right:.75rem !important}.pb-3{padding-bottom:.75rem !important}.pl-3{padding-left:.75rem !important}.px-3{padding-left:.75rem !important;padding-right:.75rem !important}.py-3{padding-top:.75rem !important;padding-bottom:.75rem !important}.p-4{padding:1rem !important}.pt-4{padding-top:1rem !important}.pr-4{padding-right:1rem !important}.pb-4{padding-bottom:1rem !important}.pl-4{padding-left:1rem !important}.px-4{padding-left:1rem !important;padding-right:1rem !important}.py-4{padding-top:1rem !important;padding-bottom:1rem !important}.p-5{padding:1.5rem !important}.pt-5{padding-top:1.5rem !important}.pr-5{padding-right:1.5rem !important}.pb-5{padding-bottom:1.5rem !important}.pl-5{padding-left:1.5rem !important}.px-5{padding-left:1.5rem !important;padding-right:1.5rem !important}.py-5{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.p-6{padding:3rem !important}.pt-6{padding-top:3rem !important}.pr-6{padding-right:3rem !important}.pb-6{padding-bottom:3rem !important}.pl-6{padding-left:3rem !important}.px-6{padding-left:3rem !important;padding-right:3rem !important}.py-6{padding-top:3rem !important;padding-bottom:3rem !important}.p-auto{padding:auto !important}.pt-auto{padding-top:auto !important}.pr-auto{padding-right:auto !important}.pb-auto{padding-bottom:auto !important}.pl-auto{padding-left:auto !important}.px-auto{padding-left:auto !important;padding-right:auto !important}.py-auto{padding-top:auto !important;padding-bottom:auto !important}.is-size-1{font-size:3rem !important}.is-size-2{font-size:2.5rem !important}.is-size-3{font-size:2rem !important}.is-size-4{font-size:1.5rem !important}.is-size-5{font-size:1.25rem !important}.is-size-6{font-size:1rem !important}.is-size-7,html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink{font-size:.75rem !important}@media screen and (max-width: 768px){.is-size-1-mobile{font-size:3rem !important}.is-size-2-mobile{font-size:2.5rem !important}.is-size-3-mobile{font-size:2rem !important}.is-size-4-mobile{font-size:1.5rem !important}.is-size-5-mobile{font-size:1.25rem !important}.is-size-6-mobile{font-size:1rem !important}.is-size-7-mobile{font-size:.75rem !important}}@media screen and (min-width: 769px),print{.is-size-1-tablet{font-size:3rem !important}.is-size-2-tablet{font-size:2.5rem !important}.is-size-3-tablet{font-size:2rem !important}.is-size-4-tablet{font-size:1.5rem !important}.is-size-5-tablet{font-size:1.25rem !important}.is-size-6-tablet{font-size:1rem !important}.is-size-7-tablet{font-size:.75rem !important}}@media screen and (max-width: 1055px){.is-size-1-touch{font-size:3rem !important}.is-size-2-touch{font-size:2.5rem !important}.is-size-3-touch{font-size:2rem !important}.is-size-4-touch{font-size:1.5rem !important}.is-size-5-touch{font-size:1.25rem !important}.is-size-6-touch{font-size:1rem !important}.is-size-7-touch{font-size:.75rem !important}}@media screen and (min-width: 1056px){.is-size-1-desktop{font-size:3rem !important}.is-size-2-desktop{font-size:2.5rem !important}.is-size-3-desktop{font-size:2rem !important}.is-size-4-desktop{font-size:1.5rem !important}.is-size-5-desktop{font-size:1.25rem !important}.is-size-6-desktop{font-size:1rem !important}.is-size-7-desktop{font-size:.75rem !important}}@media screen and (min-width: 1216px){.is-size-1-widescreen{font-size:3rem !important}.is-size-2-widescreen{font-size:2.5rem !important}.is-size-3-widescreen{font-size:2rem !important}.is-size-4-widescreen{font-size:1.5rem !important}.is-size-5-widescreen{font-size:1.25rem !important}.is-size-6-widescreen{font-size:1rem !important}.is-size-7-widescreen{font-size:.75rem !important}}@media screen and (min-width: 1408px){.is-size-1-fullhd{font-size:3rem !important}.is-size-2-fullhd{font-size:2.5rem !important}.is-size-3-fullhd{font-size:2rem !important}.is-size-4-fullhd{font-size:1.5rem !important}.is-size-5-fullhd{font-size:1.25rem !important}.is-size-6-fullhd{font-size:1rem !important}.is-size-7-fullhd{font-size:.75rem !important}}.has-text-centered{text-align:center !important}.has-text-justified{text-align:justify !important}.has-text-left{text-align:left !important}.has-text-right{text-align:right !important}@media screen and (max-width: 768px){.has-text-centered-mobile{text-align:center !important}}@media screen and (min-width: 769px),print{.has-text-centered-tablet{text-align:center !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-centered-tablet-only{text-align:center !important}}@media screen and (max-width: 1055px){.has-text-centered-touch{text-align:center !important}}@media screen and (min-width: 1056px){.has-text-centered-desktop{text-align:center !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-centered-desktop-only{text-align:center !important}}@media screen and (min-width: 1216px){.has-text-centered-widescreen{text-align:center !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-centered-widescreen-only{text-align:center !important}}@media screen and (min-width: 1408px){.has-text-centered-fullhd{text-align:center !important}}@media screen and (max-width: 768px){.has-text-justified-mobile{text-align:justify !important}}@media screen and (min-width: 769px),print{.has-text-justified-tablet{text-align:justify !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-justified-tablet-only{text-align:justify !important}}@media screen and (max-width: 1055px){.has-text-justified-touch{text-align:justify !important}}@media screen and (min-width: 1056px){.has-text-justified-desktop{text-align:justify !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-justified-desktop-only{text-align:justify !important}}@media screen and (min-width: 1216px){.has-text-justified-widescreen{text-align:justify !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-justified-widescreen-only{text-align:justify !important}}@media screen and (min-width: 1408px){.has-text-justified-fullhd{text-align:justify !important}}@media screen and (max-width: 768px){.has-text-left-mobile{text-align:left !important}}@media screen and (min-width: 769px),print{.has-text-left-tablet{text-align:left !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-left-tablet-only{text-align:left !important}}@media screen and (max-width: 1055px){.has-text-left-touch{text-align:left !important}}@media screen and (min-width: 1056px){.has-text-left-desktop{text-align:left !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-left-desktop-only{text-align:left !important}}@media screen and (min-width: 1216px){.has-text-left-widescreen{text-align:left !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-left-widescreen-only{text-align:left !important}}@media screen and (min-width: 1408px){.has-text-left-fullhd{text-align:left !important}}@media screen and (max-width: 768px){.has-text-right-mobile{text-align:right !important}}@media screen and (min-width: 769px),print{.has-text-right-tablet{text-align:right !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-right-tablet-only{text-align:right !important}}@media screen and (max-width: 1055px){.has-text-right-touch{text-align:right !important}}@media screen and (min-width: 1056px){.has-text-right-desktop{text-align:right !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-right-desktop-only{text-align:right !important}}@media screen and (min-width: 1216px){.has-text-right-widescreen{text-align:right !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-right-widescreen-only{text-align:right !important}}@media screen and (min-width: 1408px){.has-text-right-fullhd{text-align:right !important}}.is-capitalized{text-transform:capitalize !important}.is-lowercase{text-transform:lowercase !important}.is-uppercase{text-transform:uppercase !important}.is-italic{font-style:italic !important}.is-underlined{text-decoration:underline !important}.has-text-weight-light{font-weight:300 !important}.has-text-weight-normal{font-weight:400 !important}.has-text-weight-medium{font-weight:500 !important}.has-text-weight-semibold{font-weight:600 !important}.has-text-weight-bold{font-weight:700 !important}.is-family-primary{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-secondary{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-sans-serif{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-monospace{font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace !important}.is-family-code{font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace !important}.is-block{display:block !important}@media screen and (max-width: 768px){.is-block-mobile{display:block !important}}@media screen and (min-width: 769px),print{.is-block-tablet{display:block !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-block-tablet-only{display:block !important}}@media screen and (max-width: 1055px){.is-block-touch{display:block !important}}@media screen and (min-width: 1056px){.is-block-desktop{display:block !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-block-desktop-only{display:block !important}}@media screen and (min-width: 1216px){.is-block-widescreen{display:block !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-block-widescreen-only{display:block !important}}@media screen and (min-width: 1408px){.is-block-fullhd{display:block !important}}.is-flex{display:flex !important}@media screen and (max-width: 768px){.is-flex-mobile{display:flex !important}}@media screen and (min-width: 769px),print{.is-flex-tablet{display:flex !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-flex-tablet-only{display:flex !important}}@media screen and (max-width: 1055px){.is-flex-touch{display:flex !important}}@media screen and (min-width: 1056px){.is-flex-desktop{display:flex !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-flex-desktop-only{display:flex !important}}@media screen and (min-width: 1216px){.is-flex-widescreen{display:flex !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-flex-widescreen-only{display:flex !important}}@media screen and (min-width: 1408px){.is-flex-fullhd{display:flex !important}}.is-inline{display:inline !important}@media screen and (max-width: 768px){.is-inline-mobile{display:inline !important}}@media screen and (min-width: 769px),print{.is-inline-tablet{display:inline !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-tablet-only{display:inline !important}}@media screen and (max-width: 1055px){.is-inline-touch{display:inline !important}}@media screen and (min-width: 1056px){.is-inline-desktop{display:inline !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-desktop-only{display:inline !important}}@media screen and (min-width: 1216px){.is-inline-widescreen{display:inline !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-widescreen-only{display:inline !important}}@media screen and (min-width: 1408px){.is-inline-fullhd{display:inline !important}}.is-inline-block{display:inline-block !important}@media screen and (max-width: 768px){.is-inline-block-mobile{display:inline-block !important}}@media screen and (min-width: 769px),print{.is-inline-block-tablet{display:inline-block !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-block-tablet-only{display:inline-block !important}}@media screen and (max-width: 1055px){.is-inline-block-touch{display:inline-block !important}}@media screen and (min-width: 1056px){.is-inline-block-desktop{display:inline-block !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-block-desktop-only{display:inline-block !important}}@media screen and (min-width: 1216px){.is-inline-block-widescreen{display:inline-block !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-block-widescreen-only{display:inline-block !important}}@media screen and (min-width: 1408px){.is-inline-block-fullhd{display:inline-block !important}}.is-inline-flex{display:inline-flex !important}@media screen and (max-width: 768px){.is-inline-flex-mobile{display:inline-flex !important}}@media screen and (min-width: 769px),print{.is-inline-flex-tablet{display:inline-flex !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-flex-tablet-only{display:inline-flex !important}}@media screen and (max-width: 1055px){.is-inline-flex-touch{display:inline-flex !important}}@media screen and (min-width: 1056px){.is-inline-flex-desktop{display:inline-flex !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-flex-desktop-only{display:inline-flex !important}}@media screen and (min-width: 1216px){.is-inline-flex-widescreen{display:inline-flex !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-flex-widescreen-only{display:inline-flex !important}}@media screen and (min-width: 1408px){.is-inline-flex-fullhd{display:inline-flex !important}}.is-hidden{display:none !important}.is-sr-only{border:none !important;clip:rect(0, 0, 0, 0) !important;height:0.01em !important;overflow:hidden !important;padding:0 !important;position:absolute !important;white-space:nowrap !important;width:0.01em !important}@media screen and (max-width: 768px){.is-hidden-mobile{display:none !important}}@media screen and (min-width: 769px),print{.is-hidden-tablet{display:none !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-hidden-tablet-only{display:none !important}}@media screen and (max-width: 1055px){.is-hidden-touch{display:none !important}}@media screen and (min-width: 1056px){.is-hidden-desktop{display:none !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-hidden-desktop-only{display:none !important}}@media screen and (min-width: 1216px){.is-hidden-widescreen{display:none !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-hidden-widescreen-only{display:none !important}}@media screen and (min-width: 1408px){.is-hidden-fullhd{display:none !important}}.is-invisible{visibility:hidden !important}@media screen and (max-width: 768px){.is-invisible-mobile{visibility:hidden !important}}@media screen and (min-width: 769px),print{.is-invisible-tablet{visibility:hidden !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-invisible-tablet-only{visibility:hidden !important}}@media screen and (max-width: 1055px){.is-invisible-touch{visibility:hidden !important}}@media screen and (min-width: 1056px){.is-invisible-desktop{visibility:hidden !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-invisible-desktop-only{visibility:hidden !important}}@media screen and (min-width: 1216px){.is-invisible-widescreen{visibility:hidden !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-invisible-widescreen-only{visibility:hidden !important}}@media screen and (min-width: 1408px){.is-invisible-fullhd{visibility:hidden !important}}html.theme--catppuccin-mocha html{background-color:#1e1e2e;font-size:16px;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;min-width:300px;overflow-x:auto;overflow-y:scroll;text-rendering:optimizeLegibility;text-size-adjust:100%}html.theme--catppuccin-mocha article,html.theme--catppuccin-mocha aside,html.theme--catppuccin-mocha figure,html.theme--catppuccin-mocha footer,html.theme--catppuccin-mocha header,html.theme--catppuccin-mocha hgroup,html.theme--catppuccin-mocha section{display:block}html.theme--catppuccin-mocha body,html.theme--catppuccin-mocha button,html.theme--catppuccin-mocha input,html.theme--catppuccin-mocha optgroup,html.theme--catppuccin-mocha select,html.theme--catppuccin-mocha textarea{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif}html.theme--catppuccin-mocha code,html.theme--catppuccin-mocha pre{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto;font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace}html.theme--catppuccin-mocha body{color:#cdd6f4;font-size:1em;font-weight:400;line-height:1.5}html.theme--catppuccin-mocha a{color:#89b4fa;cursor:pointer;text-decoration:none}html.theme--catppuccin-mocha a strong{color:currentColor}html.theme--catppuccin-mocha a:hover{color:#89dceb}html.theme--catppuccin-mocha code{background-color:#181825;color:#cdd6f4;font-size:.875em;font-weight:normal;padding:.1em}html.theme--catppuccin-mocha hr{background-color:#181825;border:none;display:block;height:2px;margin:1.5rem 0}html.theme--catppuccin-mocha img{height:auto;max-width:100%}html.theme--catppuccin-mocha input[type="checkbox"],html.theme--catppuccin-mocha input[type="radio"]{vertical-align:baseline}html.theme--catppuccin-mocha small{font-size:.875em}html.theme--catppuccin-mocha span{font-style:inherit;font-weight:inherit}html.theme--catppuccin-mocha strong{color:#b8c5ef;font-weight:700}html.theme--catppuccin-mocha fieldset{border:none}html.theme--catppuccin-mocha pre{-webkit-overflow-scrolling:touch;background-color:#181825;color:#cdd6f4;font-size:.875em;overflow-x:auto;padding:1.25rem 1.5rem;white-space:pre;word-wrap:normal}html.theme--catppuccin-mocha pre code{background-color:transparent;color:currentColor;font-size:1em;padding:0}html.theme--catppuccin-mocha table td,html.theme--catppuccin-mocha table th{vertical-align:top}html.theme--catppuccin-mocha table td:not([align]),html.theme--catppuccin-mocha table th:not([align]){text-align:inherit}html.theme--catppuccin-mocha table th{color:#b8c5ef}html.theme--catppuccin-mocha .box{background-color:#45475a;border-radius:8px;box-shadow:none;color:#cdd6f4;display:block;padding:1.25rem}html.theme--catppuccin-mocha a.box:hover,html.theme--catppuccin-mocha a.box:focus{box-shadow:0 0.5em 1em -0.125em rgba(10,10,10,0.1),0 0 0 1px #89b4fa}html.theme--catppuccin-mocha a.box:active{box-shadow:inset 0 1px 2px rgba(10,10,10,0.2),0 0 0 1px #89b4fa}html.theme--catppuccin-mocha .button{background-color:#181825;border-color:#363653;border-width:1px;color:#89b4fa;cursor:pointer;justify-content:center;padding-bottom:calc(0.5em - 1px);padding-left:1em;padding-right:1em;padding-top:calc(0.5em - 1px);text-align:center;white-space:nowrap}html.theme--catppuccin-mocha .button strong{color:inherit}html.theme--catppuccin-mocha .button .icon,html.theme--catppuccin-mocha .button .icon.is-small,html.theme--catppuccin-mocha .button #documenter .docs-sidebar form.docs-search>input.icon,html.theme--catppuccin-mocha #documenter .docs-sidebar .button form.docs-search>input.icon,html.theme--catppuccin-mocha .button .icon.is-medium,html.theme--catppuccin-mocha .button .icon.is-large{height:1.5em;width:1.5em}html.theme--catppuccin-mocha .button .icon:first-child:not(:last-child){margin-left:calc(-0.5em - 1px);margin-right:.25em}html.theme--catppuccin-mocha .button .icon:last-child:not(:first-child){margin-left:.25em;margin-right:calc(-0.5em - 1px)}html.theme--catppuccin-mocha .button .icon:first-child:last-child{margin-left:calc(-0.5em - 1px);margin-right:calc(-0.5em - 1px)}html.theme--catppuccin-mocha .button:hover,html.theme--catppuccin-mocha .button.is-hovered{border-color:#6c7086;color:#b8c5ef}html.theme--catppuccin-mocha .button:focus,html.theme--catppuccin-mocha .button.is-focused{border-color:#6c7086;color:#71a4f9}html.theme--catppuccin-mocha .button:focus:not(:active),html.theme--catppuccin-mocha .button.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(137,180,250,0.25)}html.theme--catppuccin-mocha .button:active,html.theme--catppuccin-mocha .button.is-active{border-color:#45475a;color:#b8c5ef}html.theme--catppuccin-mocha .button.is-text{background-color:transparent;border-color:transparent;color:#cdd6f4;text-decoration:underline}html.theme--catppuccin-mocha .button.is-text:hover,html.theme--catppuccin-mocha .button.is-text.is-hovered,html.theme--catppuccin-mocha .button.is-text:focus,html.theme--catppuccin-mocha .button.is-text.is-focused{background-color:#181825;color:#b8c5ef}html.theme--catppuccin-mocha .button.is-text:active,html.theme--catppuccin-mocha .button.is-text.is-active{background-color:#0e0e16;color:#b8c5ef}html.theme--catppuccin-mocha .button.is-text[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-text{background-color:transparent;border-color:transparent;box-shadow:none}html.theme--catppuccin-mocha .button.is-ghost{background:none;border-color:rgba(0,0,0,0);color:#89b4fa;text-decoration:none}html.theme--catppuccin-mocha .button.is-ghost:hover,html.theme--catppuccin-mocha .button.is-ghost.is-hovered{color:#89b4fa;text-decoration:underline}html.theme--catppuccin-mocha .button.is-white{background-color:#fff;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-mocha .button.is-white:hover,html.theme--catppuccin-mocha .button.is-white.is-hovered{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-mocha .button.is-white:focus,html.theme--catppuccin-mocha .button.is-white.is-focused{border-color:transparent;color:#0a0a0a}html.theme--catppuccin-mocha .button.is-white:focus:not(:active),html.theme--catppuccin-mocha .button.is-white.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}html.theme--catppuccin-mocha .button.is-white:active,html.theme--catppuccin-mocha .button.is-white.is-active{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-mocha .button.is-white[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-white{background-color:#fff;border-color:#fff;box-shadow:none}html.theme--catppuccin-mocha .button.is-white.is-inverted{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-mocha .button.is-white.is-inverted:hover,html.theme--catppuccin-mocha .button.is-white.is-inverted.is-hovered{background-color:#000}html.theme--catppuccin-mocha .button.is-white.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-white.is-inverted{background-color:#0a0a0a;border-color:transparent;box-shadow:none;color:#fff}html.theme--catppuccin-mocha .button.is-white.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--catppuccin-mocha .button.is-white.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-mocha .button.is-white.is-outlined:hover,html.theme--catppuccin-mocha .button.is-white.is-outlined.is-hovered,html.theme--catppuccin-mocha .button.is-white.is-outlined:focus,html.theme--catppuccin-mocha .button.is-white.is-outlined.is-focused{background-color:#fff;border-color:#fff;color:#0a0a0a}html.theme--catppuccin-mocha .button.is-white.is-outlined.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-mocha .button.is-white.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .button.is-white.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .button.is-white.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .button.is-white.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--catppuccin-mocha .button.is-white.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-white.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-mocha .button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}html.theme--catppuccin-mocha .button.is-white.is-inverted.is-outlined:hover,html.theme--catppuccin-mocha .button.is-white.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-mocha .button.is-white.is-inverted.is-outlined:focus,html.theme--catppuccin-mocha .button.is-white.is-inverted.is-outlined.is-focused{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-mocha .button.is-white.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .button.is-white.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .button.is-white.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .button.is-white.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-mocha .button.is-white.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}html.theme--catppuccin-mocha .button.is-black{background-color:#0a0a0a;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-black:hover,html.theme--catppuccin-mocha .button.is-black.is-hovered{background-color:#040404;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-black:focus,html.theme--catppuccin-mocha .button.is-black.is-focused{border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-black:focus:not(:active),html.theme--catppuccin-mocha .button.is-black.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}html.theme--catppuccin-mocha .button.is-black:active,html.theme--catppuccin-mocha .button.is-black.is-active{background-color:#000;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-black[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-black{background-color:#0a0a0a;border-color:#0a0a0a;box-shadow:none}html.theme--catppuccin-mocha .button.is-black.is-inverted{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-mocha .button.is-black.is-inverted:hover,html.theme--catppuccin-mocha .button.is-black.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--catppuccin-mocha .button.is-black.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-black.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#0a0a0a}html.theme--catppuccin-mocha .button.is-black.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-mocha .button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}html.theme--catppuccin-mocha .button.is-black.is-outlined:hover,html.theme--catppuccin-mocha .button.is-black.is-outlined.is-hovered,html.theme--catppuccin-mocha .button.is-black.is-outlined:focus,html.theme--catppuccin-mocha .button.is-black.is-outlined.is-focused{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}html.theme--catppuccin-mocha .button.is-black.is-outlined.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--catppuccin-mocha .button.is-black.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .button.is-black.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .button.is-black.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .button.is-black.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-mocha .button.is-black.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}html.theme--catppuccin-mocha .button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-mocha .button.is-black.is-inverted.is-outlined:hover,html.theme--catppuccin-mocha .button.is-black.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-mocha .button.is-black.is-inverted.is-outlined:focus,html.theme--catppuccin-mocha .button.is-black.is-inverted.is-outlined.is-focused{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-mocha .button.is-black.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .button.is-black.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .button.is-black.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .button.is-black.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--catppuccin-mocha .button.is-black.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-mocha .button.is-light{background-color:#f5f5f5;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-light:hover,html.theme--catppuccin-mocha .button.is-light.is-hovered{background-color:#eee;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-light:focus,html.theme--catppuccin-mocha .button.is-light.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-light:focus:not(:active),html.theme--catppuccin-mocha .button.is-light.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(245,245,245,0.25)}html.theme--catppuccin-mocha .button.is-light:active,html.theme--catppuccin-mocha .button.is-light.is-active{background-color:#e8e8e8;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-light[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-light{background-color:#f5f5f5;border-color:#f5f5f5;box-shadow:none}html.theme--catppuccin-mocha .button.is-light.is-inverted{background-color:rgba(0,0,0,0.7);color:#f5f5f5}html.theme--catppuccin-mocha .button.is-light.is-inverted:hover,html.theme--catppuccin-mocha .button.is-light.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-light.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-light.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#f5f5f5}html.theme--catppuccin-mocha .button.is-light.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-mocha .button.is-light.is-outlined{background-color:transparent;border-color:#f5f5f5;color:#f5f5f5}html.theme--catppuccin-mocha .button.is-light.is-outlined:hover,html.theme--catppuccin-mocha .button.is-light.is-outlined.is-hovered,html.theme--catppuccin-mocha .button.is-light.is-outlined:focus,html.theme--catppuccin-mocha .button.is-light.is-outlined.is-focused{background-color:#f5f5f5;border-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-light.is-outlined.is-loading::after{border-color:transparent transparent #f5f5f5 #f5f5f5 !important}html.theme--catppuccin-mocha .button.is-light.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .button.is-light.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .button.is-light.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .button.is-light.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-mocha .button.is-light.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-light.is-outlined{background-color:transparent;border-color:#f5f5f5;box-shadow:none;color:#f5f5f5}html.theme--catppuccin-mocha .button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-light.is-inverted.is-outlined:hover,html.theme--catppuccin-mocha .button.is-light.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-mocha .button.is-light.is-inverted.is-outlined:focus,html.theme--catppuccin-mocha .button.is-light.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#f5f5f5}html.theme--catppuccin-mocha .button.is-light.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .button.is-light.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .button.is-light.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .button.is-light.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #f5f5f5 #f5f5f5 !important}html.theme--catppuccin-mocha .button.is-light.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-dark,html.theme--catppuccin-mocha .content kbd.button{background-color:#313244;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-dark:hover,html.theme--catppuccin-mocha .content kbd.button:hover,html.theme--catppuccin-mocha .button.is-dark.is-hovered,html.theme--catppuccin-mocha .content kbd.button.is-hovered{background-color:#2c2d3d;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-dark:focus,html.theme--catppuccin-mocha .content kbd.button:focus,html.theme--catppuccin-mocha .button.is-dark.is-focused,html.theme--catppuccin-mocha .content kbd.button.is-focused{border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-dark:focus:not(:active),html.theme--catppuccin-mocha .content kbd.button:focus:not(:active),html.theme--catppuccin-mocha .button.is-dark.is-focused:not(:active),html.theme--catppuccin-mocha .content kbd.button.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(49,50,68,0.25)}html.theme--catppuccin-mocha .button.is-dark:active,html.theme--catppuccin-mocha .content kbd.button:active,html.theme--catppuccin-mocha .button.is-dark.is-active,html.theme--catppuccin-mocha .content kbd.button.is-active{background-color:#262735;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-dark[disabled],html.theme--catppuccin-mocha .content kbd.button[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-dark,fieldset[disabled] html.theme--catppuccin-mocha .content kbd.button{background-color:#313244;border-color:#313244;box-shadow:none}html.theme--catppuccin-mocha .button.is-dark.is-inverted,html.theme--catppuccin-mocha .content kbd.button.is-inverted{background-color:#fff;color:#313244}html.theme--catppuccin-mocha .button.is-dark.is-inverted:hover,html.theme--catppuccin-mocha .content kbd.button.is-inverted:hover,html.theme--catppuccin-mocha .button.is-dark.is-inverted.is-hovered,html.theme--catppuccin-mocha .content kbd.button.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--catppuccin-mocha .button.is-dark.is-inverted[disabled],html.theme--catppuccin-mocha .content kbd.button.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-dark.is-inverted,fieldset[disabled] html.theme--catppuccin-mocha .content kbd.button.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#313244}html.theme--catppuccin-mocha .button.is-dark.is-loading::after,html.theme--catppuccin-mocha .content kbd.button.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-mocha .button.is-dark.is-outlined,html.theme--catppuccin-mocha .content kbd.button.is-outlined{background-color:transparent;border-color:#313244;color:#313244}html.theme--catppuccin-mocha .button.is-dark.is-outlined:hover,html.theme--catppuccin-mocha .content kbd.button.is-outlined:hover,html.theme--catppuccin-mocha .button.is-dark.is-outlined.is-hovered,html.theme--catppuccin-mocha .content kbd.button.is-outlined.is-hovered,html.theme--catppuccin-mocha .button.is-dark.is-outlined:focus,html.theme--catppuccin-mocha .content kbd.button.is-outlined:focus,html.theme--catppuccin-mocha .button.is-dark.is-outlined.is-focused,html.theme--catppuccin-mocha .content kbd.button.is-outlined.is-focused{background-color:#313244;border-color:#313244;color:#fff}html.theme--catppuccin-mocha .button.is-dark.is-outlined.is-loading::after,html.theme--catppuccin-mocha .content kbd.button.is-outlined.is-loading::after{border-color:transparent transparent #313244 #313244 !important}html.theme--catppuccin-mocha .button.is-dark.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .content kbd.button.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .button.is-dark.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .content kbd.button.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .button.is-dark.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .content kbd.button.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .button.is-dark.is-outlined.is-loading.is-focused::after,html.theme--catppuccin-mocha .content kbd.button.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-mocha .button.is-dark.is-outlined[disabled],html.theme--catppuccin-mocha .content kbd.button.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-dark.is-outlined,fieldset[disabled] html.theme--catppuccin-mocha .content kbd.button.is-outlined{background-color:transparent;border-color:#313244;box-shadow:none;color:#313244}html.theme--catppuccin-mocha .button.is-dark.is-inverted.is-outlined,html.theme--catppuccin-mocha .content kbd.button.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-mocha .button.is-dark.is-inverted.is-outlined:hover,html.theme--catppuccin-mocha .content kbd.button.is-inverted.is-outlined:hover,html.theme--catppuccin-mocha .button.is-dark.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-mocha .content kbd.button.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-mocha .button.is-dark.is-inverted.is-outlined:focus,html.theme--catppuccin-mocha .content kbd.button.is-inverted.is-outlined:focus,html.theme--catppuccin-mocha .button.is-dark.is-inverted.is-outlined.is-focused,html.theme--catppuccin-mocha .content kbd.button.is-inverted.is-outlined.is-focused{background-color:#fff;color:#313244}html.theme--catppuccin-mocha .button.is-dark.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .content kbd.button.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .button.is-dark.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .content kbd.button.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .button.is-dark.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .content kbd.button.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .button.is-dark.is-inverted.is-outlined.is-loading.is-focused::after,html.theme--catppuccin-mocha .content kbd.button.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #313244 #313244 !important}html.theme--catppuccin-mocha .button.is-dark.is-inverted.is-outlined[disabled],html.theme--catppuccin-mocha .content kbd.button.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-dark.is-inverted.is-outlined,fieldset[disabled] html.theme--catppuccin-mocha .content kbd.button.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-mocha .button.is-primary,html.theme--catppuccin-mocha .docstring>section>a.button.docs-sourcelink{background-color:#89b4fa;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-primary:hover,html.theme--catppuccin-mocha .docstring>section>a.button.docs-sourcelink:hover,html.theme--catppuccin-mocha .button.is-primary.is-hovered,html.theme--catppuccin-mocha .docstring>section>a.button.is-hovered.docs-sourcelink{background-color:#7dacf9;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-primary:focus,html.theme--catppuccin-mocha .docstring>section>a.button.docs-sourcelink:focus,html.theme--catppuccin-mocha .button.is-primary.is-focused,html.theme--catppuccin-mocha .docstring>section>a.button.is-focused.docs-sourcelink{border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-primary:focus:not(:active),html.theme--catppuccin-mocha .docstring>section>a.button.docs-sourcelink:focus:not(:active),html.theme--catppuccin-mocha .button.is-primary.is-focused:not(:active),html.theme--catppuccin-mocha .docstring>section>a.button.is-focused.docs-sourcelink:not(:active){box-shadow:0 0 0 0.125em rgba(137,180,250,0.25)}html.theme--catppuccin-mocha .button.is-primary:active,html.theme--catppuccin-mocha .docstring>section>a.button.docs-sourcelink:active,html.theme--catppuccin-mocha .button.is-primary.is-active,html.theme--catppuccin-mocha .docstring>section>a.button.is-active.docs-sourcelink{background-color:#71a4f9;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-primary[disabled],html.theme--catppuccin-mocha .docstring>section>a.button.docs-sourcelink[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-primary,fieldset[disabled] html.theme--catppuccin-mocha .docstring>section>a.button.docs-sourcelink{background-color:#89b4fa;border-color:#89b4fa;box-shadow:none}html.theme--catppuccin-mocha .button.is-primary.is-inverted,html.theme--catppuccin-mocha .docstring>section>a.button.is-inverted.docs-sourcelink{background-color:#fff;color:#89b4fa}html.theme--catppuccin-mocha .button.is-primary.is-inverted:hover,html.theme--catppuccin-mocha .docstring>section>a.button.is-inverted.docs-sourcelink:hover,html.theme--catppuccin-mocha .button.is-primary.is-inverted.is-hovered,html.theme--catppuccin-mocha .docstring>section>a.button.is-inverted.is-hovered.docs-sourcelink{background-color:#f2f2f2}html.theme--catppuccin-mocha .button.is-primary.is-inverted[disabled],html.theme--catppuccin-mocha .docstring>section>a.button.is-inverted.docs-sourcelink[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-primary.is-inverted,fieldset[disabled] html.theme--catppuccin-mocha .docstring>section>a.button.is-inverted.docs-sourcelink{background-color:#fff;border-color:transparent;box-shadow:none;color:#89b4fa}html.theme--catppuccin-mocha .button.is-primary.is-loading::after,html.theme--catppuccin-mocha .docstring>section>a.button.is-loading.docs-sourcelink::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-mocha .button.is-primary.is-outlined,html.theme--catppuccin-mocha .docstring>section>a.button.is-outlined.docs-sourcelink{background-color:transparent;border-color:#89b4fa;color:#89b4fa}html.theme--catppuccin-mocha .button.is-primary.is-outlined:hover,html.theme--catppuccin-mocha .docstring>section>a.button.is-outlined.docs-sourcelink:hover,html.theme--catppuccin-mocha .button.is-primary.is-outlined.is-hovered,html.theme--catppuccin-mocha .docstring>section>a.button.is-outlined.is-hovered.docs-sourcelink,html.theme--catppuccin-mocha .button.is-primary.is-outlined:focus,html.theme--catppuccin-mocha .docstring>section>a.button.is-outlined.docs-sourcelink:focus,html.theme--catppuccin-mocha .button.is-primary.is-outlined.is-focused,html.theme--catppuccin-mocha .docstring>section>a.button.is-outlined.is-focused.docs-sourcelink{background-color:#89b4fa;border-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .button.is-primary.is-outlined.is-loading::after,html.theme--catppuccin-mocha .docstring>section>a.button.is-outlined.is-loading.docs-sourcelink::after{border-color:transparent transparent #89b4fa #89b4fa !important}html.theme--catppuccin-mocha .button.is-primary.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .docstring>section>a.button.is-outlined.is-loading.docs-sourcelink:hover::after,html.theme--catppuccin-mocha .button.is-primary.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .docstring>section>a.button.is-outlined.is-loading.is-hovered.docs-sourcelink::after,html.theme--catppuccin-mocha .button.is-primary.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .docstring>section>a.button.is-outlined.is-loading.docs-sourcelink:focus::after,html.theme--catppuccin-mocha .button.is-primary.is-outlined.is-loading.is-focused::after,html.theme--catppuccin-mocha .docstring>section>a.button.is-outlined.is-loading.is-focused.docs-sourcelink::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-mocha .button.is-primary.is-outlined[disabled],html.theme--catppuccin-mocha .docstring>section>a.button.is-outlined.docs-sourcelink[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-primary.is-outlined,fieldset[disabled] html.theme--catppuccin-mocha .docstring>section>a.button.is-outlined.docs-sourcelink{background-color:transparent;border-color:#89b4fa;box-shadow:none;color:#89b4fa}html.theme--catppuccin-mocha .button.is-primary.is-inverted.is-outlined,html.theme--catppuccin-mocha .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-mocha .button.is-primary.is-inverted.is-outlined:hover,html.theme--catppuccin-mocha .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink:hover,html.theme--catppuccin-mocha .button.is-primary.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-mocha .docstring>section>a.button.is-inverted.is-outlined.is-hovered.docs-sourcelink,html.theme--catppuccin-mocha .button.is-primary.is-inverted.is-outlined:focus,html.theme--catppuccin-mocha .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink:focus,html.theme--catppuccin-mocha .button.is-primary.is-inverted.is-outlined.is-focused,html.theme--catppuccin-mocha .docstring>section>a.button.is-inverted.is-outlined.is-focused.docs-sourcelink{background-color:#fff;color:#89b4fa}html.theme--catppuccin-mocha .button.is-primary.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .docstring>section>a.button.is-inverted.is-outlined.is-loading.docs-sourcelink:hover::after,html.theme--catppuccin-mocha .button.is-primary.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .docstring>section>a.button.is-inverted.is-outlined.is-loading.is-hovered.docs-sourcelink::after,html.theme--catppuccin-mocha .button.is-primary.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .docstring>section>a.button.is-inverted.is-outlined.is-loading.docs-sourcelink:focus::after,html.theme--catppuccin-mocha .button.is-primary.is-inverted.is-outlined.is-loading.is-focused::after,html.theme--catppuccin-mocha .docstring>section>a.button.is-inverted.is-outlined.is-loading.is-focused.docs-sourcelink::after{border-color:transparent transparent #89b4fa #89b4fa !important}html.theme--catppuccin-mocha .button.is-primary.is-inverted.is-outlined[disabled],html.theme--catppuccin-mocha .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-primary.is-inverted.is-outlined,fieldset[disabled] html.theme--catppuccin-mocha .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-mocha .button.is-primary.is-light,html.theme--catppuccin-mocha .docstring>section>a.button.is-light.docs-sourcelink{background-color:#ebf3fe;color:#063c93}html.theme--catppuccin-mocha .button.is-primary.is-light:hover,html.theme--catppuccin-mocha .docstring>section>a.button.is-light.docs-sourcelink:hover,html.theme--catppuccin-mocha .button.is-primary.is-light.is-hovered,html.theme--catppuccin-mocha .docstring>section>a.button.is-light.is-hovered.docs-sourcelink{background-color:#dfebfe;border-color:transparent;color:#063c93}html.theme--catppuccin-mocha .button.is-primary.is-light:active,html.theme--catppuccin-mocha .docstring>section>a.button.is-light.docs-sourcelink:active,html.theme--catppuccin-mocha .button.is-primary.is-light.is-active,html.theme--catppuccin-mocha .docstring>section>a.button.is-light.is-active.docs-sourcelink{background-color:#d3e3fd;border-color:transparent;color:#063c93}html.theme--catppuccin-mocha .button.is-link{background-color:#89b4fa;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-link:hover,html.theme--catppuccin-mocha .button.is-link.is-hovered{background-color:#7dacf9;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-link:focus,html.theme--catppuccin-mocha .button.is-link.is-focused{border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-link:focus:not(:active),html.theme--catppuccin-mocha .button.is-link.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(137,180,250,0.25)}html.theme--catppuccin-mocha .button.is-link:active,html.theme--catppuccin-mocha .button.is-link.is-active{background-color:#71a4f9;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-link[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-link{background-color:#89b4fa;border-color:#89b4fa;box-shadow:none}html.theme--catppuccin-mocha .button.is-link.is-inverted{background-color:#fff;color:#89b4fa}html.theme--catppuccin-mocha .button.is-link.is-inverted:hover,html.theme--catppuccin-mocha .button.is-link.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--catppuccin-mocha .button.is-link.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-link.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#89b4fa}html.theme--catppuccin-mocha .button.is-link.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-mocha .button.is-link.is-outlined{background-color:transparent;border-color:#89b4fa;color:#89b4fa}html.theme--catppuccin-mocha .button.is-link.is-outlined:hover,html.theme--catppuccin-mocha .button.is-link.is-outlined.is-hovered,html.theme--catppuccin-mocha .button.is-link.is-outlined:focus,html.theme--catppuccin-mocha .button.is-link.is-outlined.is-focused{background-color:#89b4fa;border-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .button.is-link.is-outlined.is-loading::after{border-color:transparent transparent #89b4fa #89b4fa !important}html.theme--catppuccin-mocha .button.is-link.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .button.is-link.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .button.is-link.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .button.is-link.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-mocha .button.is-link.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-link.is-outlined{background-color:transparent;border-color:#89b4fa;box-shadow:none;color:#89b4fa}html.theme--catppuccin-mocha .button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-mocha .button.is-link.is-inverted.is-outlined:hover,html.theme--catppuccin-mocha .button.is-link.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-mocha .button.is-link.is-inverted.is-outlined:focus,html.theme--catppuccin-mocha .button.is-link.is-inverted.is-outlined.is-focused{background-color:#fff;color:#89b4fa}html.theme--catppuccin-mocha .button.is-link.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .button.is-link.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .button.is-link.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .button.is-link.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #89b4fa #89b4fa !important}html.theme--catppuccin-mocha .button.is-link.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-mocha .button.is-link.is-light{background-color:#ebf3fe;color:#063c93}html.theme--catppuccin-mocha .button.is-link.is-light:hover,html.theme--catppuccin-mocha .button.is-link.is-light.is-hovered{background-color:#dfebfe;border-color:transparent;color:#063c93}html.theme--catppuccin-mocha .button.is-link.is-light:active,html.theme--catppuccin-mocha .button.is-link.is-light.is-active{background-color:#d3e3fd;border-color:transparent;color:#063c93}html.theme--catppuccin-mocha .button.is-info{background-color:#94e2d5;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-info:hover,html.theme--catppuccin-mocha .button.is-info.is-hovered{background-color:#8adfd1;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-info:focus,html.theme--catppuccin-mocha .button.is-info.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-info:focus:not(:active),html.theme--catppuccin-mocha .button.is-info.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(148,226,213,0.25)}html.theme--catppuccin-mocha .button.is-info:active,html.theme--catppuccin-mocha .button.is-info.is-active{background-color:#80ddcd;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-info[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-info{background-color:#94e2d5;border-color:#94e2d5;box-shadow:none}html.theme--catppuccin-mocha .button.is-info.is-inverted{background-color:rgba(0,0,0,0.7);color:#94e2d5}html.theme--catppuccin-mocha .button.is-info.is-inverted:hover,html.theme--catppuccin-mocha .button.is-info.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-info.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-info.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#94e2d5}html.theme--catppuccin-mocha .button.is-info.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-mocha .button.is-info.is-outlined{background-color:transparent;border-color:#94e2d5;color:#94e2d5}html.theme--catppuccin-mocha .button.is-info.is-outlined:hover,html.theme--catppuccin-mocha .button.is-info.is-outlined.is-hovered,html.theme--catppuccin-mocha .button.is-info.is-outlined:focus,html.theme--catppuccin-mocha .button.is-info.is-outlined.is-focused{background-color:#94e2d5;border-color:#94e2d5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-info.is-outlined.is-loading::after{border-color:transparent transparent #94e2d5 #94e2d5 !important}html.theme--catppuccin-mocha .button.is-info.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .button.is-info.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .button.is-info.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .button.is-info.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-mocha .button.is-info.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-info.is-outlined{background-color:transparent;border-color:#94e2d5;box-shadow:none;color:#94e2d5}html.theme--catppuccin-mocha .button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-info.is-inverted.is-outlined:hover,html.theme--catppuccin-mocha .button.is-info.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-mocha .button.is-info.is-inverted.is-outlined:focus,html.theme--catppuccin-mocha .button.is-info.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#94e2d5}html.theme--catppuccin-mocha .button.is-info.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .button.is-info.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .button.is-info.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .button.is-info.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #94e2d5 #94e2d5 !important}html.theme--catppuccin-mocha .button.is-info.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-info.is-light{background-color:#effbf9;color:#207466}html.theme--catppuccin-mocha .button.is-info.is-light:hover,html.theme--catppuccin-mocha .button.is-info.is-light.is-hovered{background-color:#e5f8f5;border-color:transparent;color:#207466}html.theme--catppuccin-mocha .button.is-info.is-light:active,html.theme--catppuccin-mocha .button.is-info.is-light.is-active{background-color:#dbf5f1;border-color:transparent;color:#207466}html.theme--catppuccin-mocha .button.is-success{background-color:#a6e3a1;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-success:hover,html.theme--catppuccin-mocha .button.is-success.is-hovered{background-color:#9de097;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-success:focus,html.theme--catppuccin-mocha .button.is-success.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-success:focus:not(:active),html.theme--catppuccin-mocha .button.is-success.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(166,227,161,0.25)}html.theme--catppuccin-mocha .button.is-success:active,html.theme--catppuccin-mocha .button.is-success.is-active{background-color:#93dd8d;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-success[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-success{background-color:#a6e3a1;border-color:#a6e3a1;box-shadow:none}html.theme--catppuccin-mocha .button.is-success.is-inverted{background-color:rgba(0,0,0,0.7);color:#a6e3a1}html.theme--catppuccin-mocha .button.is-success.is-inverted:hover,html.theme--catppuccin-mocha .button.is-success.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-success.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-success.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#a6e3a1}html.theme--catppuccin-mocha .button.is-success.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-mocha .button.is-success.is-outlined{background-color:transparent;border-color:#a6e3a1;color:#a6e3a1}html.theme--catppuccin-mocha .button.is-success.is-outlined:hover,html.theme--catppuccin-mocha .button.is-success.is-outlined.is-hovered,html.theme--catppuccin-mocha .button.is-success.is-outlined:focus,html.theme--catppuccin-mocha .button.is-success.is-outlined.is-focused{background-color:#a6e3a1;border-color:#a6e3a1;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-success.is-outlined.is-loading::after{border-color:transparent transparent #a6e3a1 #a6e3a1 !important}html.theme--catppuccin-mocha .button.is-success.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .button.is-success.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .button.is-success.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .button.is-success.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-mocha .button.is-success.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-success.is-outlined{background-color:transparent;border-color:#a6e3a1;box-shadow:none;color:#a6e3a1}html.theme--catppuccin-mocha .button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-success.is-inverted.is-outlined:hover,html.theme--catppuccin-mocha .button.is-success.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-mocha .button.is-success.is-inverted.is-outlined:focus,html.theme--catppuccin-mocha .button.is-success.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#a6e3a1}html.theme--catppuccin-mocha .button.is-success.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .button.is-success.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .button.is-success.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .button.is-success.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #a6e3a1 #a6e3a1 !important}html.theme--catppuccin-mocha .button.is-success.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-success.is-light{background-color:#f0faef;color:#287222}html.theme--catppuccin-mocha .button.is-success.is-light:hover,html.theme--catppuccin-mocha .button.is-success.is-light.is-hovered{background-color:#e7f7e5;border-color:transparent;color:#287222}html.theme--catppuccin-mocha .button.is-success.is-light:active,html.theme--catppuccin-mocha .button.is-success.is-light.is-active{background-color:#def4dc;border-color:transparent;color:#287222}html.theme--catppuccin-mocha .button.is-warning{background-color:#f9e2af;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-warning:hover,html.theme--catppuccin-mocha .button.is-warning.is-hovered{background-color:#f8dea3;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-warning:focus,html.theme--catppuccin-mocha .button.is-warning.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-warning:focus:not(:active),html.theme--catppuccin-mocha .button.is-warning.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(249,226,175,0.25)}html.theme--catppuccin-mocha .button.is-warning:active,html.theme--catppuccin-mocha .button.is-warning.is-active{background-color:#f7d997;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-warning[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-warning{background-color:#f9e2af;border-color:#f9e2af;box-shadow:none}html.theme--catppuccin-mocha .button.is-warning.is-inverted{background-color:rgba(0,0,0,0.7);color:#f9e2af}html.theme--catppuccin-mocha .button.is-warning.is-inverted:hover,html.theme--catppuccin-mocha .button.is-warning.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-warning.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-warning.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#f9e2af}html.theme--catppuccin-mocha .button.is-warning.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-mocha .button.is-warning.is-outlined{background-color:transparent;border-color:#f9e2af;color:#f9e2af}html.theme--catppuccin-mocha .button.is-warning.is-outlined:hover,html.theme--catppuccin-mocha .button.is-warning.is-outlined.is-hovered,html.theme--catppuccin-mocha .button.is-warning.is-outlined:focus,html.theme--catppuccin-mocha .button.is-warning.is-outlined.is-focused{background-color:#f9e2af;border-color:#f9e2af;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-warning.is-outlined.is-loading::after{border-color:transparent transparent #f9e2af #f9e2af !important}html.theme--catppuccin-mocha .button.is-warning.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .button.is-warning.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .button.is-warning.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .button.is-warning.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-mocha .button.is-warning.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-warning.is-outlined{background-color:transparent;border-color:#f9e2af;box-shadow:none;color:#f9e2af}html.theme--catppuccin-mocha .button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-warning.is-inverted.is-outlined:hover,html.theme--catppuccin-mocha .button.is-warning.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-mocha .button.is-warning.is-inverted.is-outlined:focus,html.theme--catppuccin-mocha .button.is-warning.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#f9e2af}html.theme--catppuccin-mocha .button.is-warning.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .button.is-warning.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .button.is-warning.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .button.is-warning.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #f9e2af #f9e2af !important}html.theme--catppuccin-mocha .button.is-warning.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-warning.is-light{background-color:#fef8ec;color:#8a620a}html.theme--catppuccin-mocha .button.is-warning.is-light:hover,html.theme--catppuccin-mocha .button.is-warning.is-light.is-hovered{background-color:#fdf4e0;border-color:transparent;color:#8a620a}html.theme--catppuccin-mocha .button.is-warning.is-light:active,html.theme--catppuccin-mocha .button.is-warning.is-light.is-active{background-color:#fcf0d4;border-color:transparent;color:#8a620a}html.theme--catppuccin-mocha .button.is-danger{background-color:#f38ba8;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-danger:hover,html.theme--catppuccin-mocha .button.is-danger.is-hovered{background-color:#f27f9f;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-danger:focus,html.theme--catppuccin-mocha .button.is-danger.is-focused{border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-danger:focus:not(:active),html.theme--catppuccin-mocha .button.is-danger.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(243,139,168,0.25)}html.theme--catppuccin-mocha .button.is-danger:active,html.theme--catppuccin-mocha .button.is-danger.is-active{background-color:#f17497;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-danger[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-danger{background-color:#f38ba8;border-color:#f38ba8;box-shadow:none}html.theme--catppuccin-mocha .button.is-danger.is-inverted{background-color:#fff;color:#f38ba8}html.theme--catppuccin-mocha .button.is-danger.is-inverted:hover,html.theme--catppuccin-mocha .button.is-danger.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--catppuccin-mocha .button.is-danger.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-danger.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#f38ba8}html.theme--catppuccin-mocha .button.is-danger.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-mocha .button.is-danger.is-outlined{background-color:transparent;border-color:#f38ba8;color:#f38ba8}html.theme--catppuccin-mocha .button.is-danger.is-outlined:hover,html.theme--catppuccin-mocha .button.is-danger.is-outlined.is-hovered,html.theme--catppuccin-mocha .button.is-danger.is-outlined:focus,html.theme--catppuccin-mocha .button.is-danger.is-outlined.is-focused{background-color:#f38ba8;border-color:#f38ba8;color:#fff}html.theme--catppuccin-mocha .button.is-danger.is-outlined.is-loading::after{border-color:transparent transparent #f38ba8 #f38ba8 !important}html.theme--catppuccin-mocha .button.is-danger.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .button.is-danger.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .button.is-danger.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .button.is-danger.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-mocha .button.is-danger.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-danger.is-outlined{background-color:transparent;border-color:#f38ba8;box-shadow:none;color:#f38ba8}html.theme--catppuccin-mocha .button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-mocha .button.is-danger.is-inverted.is-outlined:hover,html.theme--catppuccin-mocha .button.is-danger.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-mocha .button.is-danger.is-inverted.is-outlined:focus,html.theme--catppuccin-mocha .button.is-danger.is-inverted.is-outlined.is-focused{background-color:#fff;color:#f38ba8}html.theme--catppuccin-mocha .button.is-danger.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .button.is-danger.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .button.is-danger.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .button.is-danger.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #f38ba8 #f38ba8 !important}html.theme--catppuccin-mocha .button.is-danger.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-mocha .button.is-danger.is-light{background-color:#fdedf1;color:#991036}html.theme--catppuccin-mocha .button.is-danger.is-light:hover,html.theme--catppuccin-mocha .button.is-danger.is-light.is-hovered{background-color:#fce1e8;border-color:transparent;color:#991036}html.theme--catppuccin-mocha .button.is-danger.is-light:active,html.theme--catppuccin-mocha .button.is-danger.is-light.is-active{background-color:#fbd5e0;border-color:transparent;color:#991036}html.theme--catppuccin-mocha .button.is-small,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.button{font-size:.75rem}html.theme--catppuccin-mocha .button.is-small:not(.is-rounded),html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.button:not(.is-rounded){border-radius:3px}html.theme--catppuccin-mocha .button.is-normal{font-size:1rem}html.theme--catppuccin-mocha .button.is-medium{font-size:1.25rem}html.theme--catppuccin-mocha .button.is-large{font-size:1.5rem}html.theme--catppuccin-mocha .button[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button{background-color:#6c7086;border-color:#585b70;box-shadow:none;opacity:.5}html.theme--catppuccin-mocha .button.is-fullwidth{display:flex;width:100%}html.theme--catppuccin-mocha .button.is-loading{color:transparent !important;pointer-events:none}html.theme--catppuccin-mocha .button.is-loading::after{position:absolute;left:calc(50% - (1em * 0.5));top:calc(50% - (1em * 0.5));position:absolute !important}html.theme--catppuccin-mocha .button.is-static{background-color:#181825;border-color:#585b70;color:#7f849c;box-shadow:none;pointer-events:none}html.theme--catppuccin-mocha .button.is-rounded,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.button{border-radius:9999px;padding-left:calc(1em + 0.25em);padding-right:calc(1em + 0.25em)}html.theme--catppuccin-mocha .buttons{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}html.theme--catppuccin-mocha .buttons .button{margin-bottom:0.5rem}html.theme--catppuccin-mocha .buttons .button:not(:last-child):not(.is-fullwidth){margin-right:.5rem}html.theme--catppuccin-mocha .buttons:last-child{margin-bottom:-0.5rem}html.theme--catppuccin-mocha .buttons:not(:last-child){margin-bottom:1rem}html.theme--catppuccin-mocha .buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large){font-size:.75rem}html.theme--catppuccin-mocha .buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large):not(.is-rounded){border-radius:3px}html.theme--catppuccin-mocha .buttons.are-medium .button:not(.is-small):not(.is-normal):not(.is-large){font-size:1.25rem}html.theme--catppuccin-mocha .buttons.are-large .button:not(.is-small):not(.is-normal):not(.is-medium){font-size:1.5rem}html.theme--catppuccin-mocha .buttons.has-addons .button:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}html.theme--catppuccin-mocha .buttons.has-addons .button:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0;margin-right:-1px}html.theme--catppuccin-mocha .buttons.has-addons .button:last-child{margin-right:0}html.theme--catppuccin-mocha .buttons.has-addons .button:hover,html.theme--catppuccin-mocha .buttons.has-addons .button.is-hovered{z-index:2}html.theme--catppuccin-mocha .buttons.has-addons .button:focus,html.theme--catppuccin-mocha .buttons.has-addons .button.is-focused,html.theme--catppuccin-mocha .buttons.has-addons .button:active,html.theme--catppuccin-mocha .buttons.has-addons .button.is-active,html.theme--catppuccin-mocha .buttons.has-addons .button.is-selected{z-index:3}html.theme--catppuccin-mocha .buttons.has-addons .button:focus:hover,html.theme--catppuccin-mocha .buttons.has-addons .button.is-focused:hover,html.theme--catppuccin-mocha .buttons.has-addons .button:active:hover,html.theme--catppuccin-mocha .buttons.has-addons .button.is-active:hover,html.theme--catppuccin-mocha .buttons.has-addons .button.is-selected:hover{z-index:4}html.theme--catppuccin-mocha .buttons.has-addons .button.is-expanded{flex-grow:1;flex-shrink:1}html.theme--catppuccin-mocha .buttons.is-centered{justify-content:center}html.theme--catppuccin-mocha .buttons.is-centered:not(.has-addons) .button:not(.is-fullwidth){margin-left:0.25rem;margin-right:0.25rem}html.theme--catppuccin-mocha .buttons.is-right{justify-content:flex-end}html.theme--catppuccin-mocha .buttons.is-right:not(.has-addons) .button:not(.is-fullwidth){margin-left:0.25rem;margin-right:0.25rem}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .button.is-responsive.is-small,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-responsive{font-size:.5625rem}html.theme--catppuccin-mocha .button.is-responsive,html.theme--catppuccin-mocha .button.is-responsive.is-normal{font-size:.65625rem}html.theme--catppuccin-mocha .button.is-responsive.is-medium{font-size:.75rem}html.theme--catppuccin-mocha .button.is-responsive.is-large{font-size:1rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-mocha .button.is-responsive.is-small,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-responsive{font-size:.65625rem}html.theme--catppuccin-mocha .button.is-responsive,html.theme--catppuccin-mocha .button.is-responsive.is-normal{font-size:.75rem}html.theme--catppuccin-mocha .button.is-responsive.is-medium{font-size:1rem}html.theme--catppuccin-mocha .button.is-responsive.is-large{font-size:1.25rem}}html.theme--catppuccin-mocha .container{flex-grow:1;margin:0 auto;position:relative;width:auto}html.theme--catppuccin-mocha .container.is-fluid{max-width:none !important;padding-left:32px;padding-right:32px;width:100%}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .container{max-width:992px}}@media screen and (max-width: 1215px){html.theme--catppuccin-mocha .container.is-widescreen:not(.is-max-desktop){max-width:1152px}}@media screen and (max-width: 1407px){html.theme--catppuccin-mocha .container.is-fullhd:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}@media screen and (min-width: 1216px){html.theme--catppuccin-mocha .container:not(.is-max-desktop){max-width:1152px}}@media screen and (min-width: 1408px){html.theme--catppuccin-mocha .container:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}html.theme--catppuccin-mocha .content li+li{margin-top:0.25em}html.theme--catppuccin-mocha .content p:not(:last-child),html.theme--catppuccin-mocha .content dl:not(:last-child),html.theme--catppuccin-mocha .content ol:not(:last-child),html.theme--catppuccin-mocha .content ul:not(:last-child),html.theme--catppuccin-mocha .content blockquote:not(:last-child),html.theme--catppuccin-mocha .content pre:not(:last-child),html.theme--catppuccin-mocha .content table:not(:last-child){margin-bottom:1em}html.theme--catppuccin-mocha .content h1,html.theme--catppuccin-mocha .content h2,html.theme--catppuccin-mocha .content h3,html.theme--catppuccin-mocha .content h4,html.theme--catppuccin-mocha .content h5,html.theme--catppuccin-mocha .content h6{color:#cdd6f4;font-weight:600;line-height:1.125}html.theme--catppuccin-mocha .content h1{font-size:2em;margin-bottom:0.5em}html.theme--catppuccin-mocha .content h1:not(:first-child){margin-top:1em}html.theme--catppuccin-mocha .content h2{font-size:1.75em;margin-bottom:0.5714em}html.theme--catppuccin-mocha .content h2:not(:first-child){margin-top:1.1428em}html.theme--catppuccin-mocha .content h3{font-size:1.5em;margin-bottom:0.6666em}html.theme--catppuccin-mocha .content h3:not(:first-child){margin-top:1.3333em}html.theme--catppuccin-mocha .content h4{font-size:1.25em;margin-bottom:0.8em}html.theme--catppuccin-mocha .content h5{font-size:1.125em;margin-bottom:0.8888em}html.theme--catppuccin-mocha .content h6{font-size:1em;margin-bottom:1em}html.theme--catppuccin-mocha .content blockquote{background-color:#181825;border-left:5px solid #585b70;padding:1.25em 1.5em}html.theme--catppuccin-mocha .content ol{list-style-position:outside;margin-left:2em;margin-top:1em}html.theme--catppuccin-mocha .content ol:not([type]){list-style-type:decimal}html.theme--catppuccin-mocha .content ol.is-lower-alpha:not([type]){list-style-type:lower-alpha}html.theme--catppuccin-mocha .content ol.is-lower-roman:not([type]){list-style-type:lower-roman}html.theme--catppuccin-mocha .content ol.is-upper-alpha:not([type]){list-style-type:upper-alpha}html.theme--catppuccin-mocha .content ol.is-upper-roman:not([type]){list-style-type:upper-roman}html.theme--catppuccin-mocha .content ul{list-style:disc outside;margin-left:2em;margin-top:1em}html.theme--catppuccin-mocha .content ul ul{list-style-type:circle;margin-top:0.5em}html.theme--catppuccin-mocha .content ul ul ul{list-style-type:square}html.theme--catppuccin-mocha .content dd{margin-left:2em}html.theme--catppuccin-mocha .content figure{margin-left:2em;margin-right:2em;text-align:center}html.theme--catppuccin-mocha .content figure:not(:first-child){margin-top:2em}html.theme--catppuccin-mocha .content figure:not(:last-child){margin-bottom:2em}html.theme--catppuccin-mocha .content figure img{display:inline-block}html.theme--catppuccin-mocha .content figure figcaption{font-style:italic}html.theme--catppuccin-mocha .content pre{-webkit-overflow-scrolling:touch;overflow-x:auto;padding:0;white-space:pre;word-wrap:normal}html.theme--catppuccin-mocha .content sup,html.theme--catppuccin-mocha .content sub{font-size:75%}html.theme--catppuccin-mocha .content table{width:100%}html.theme--catppuccin-mocha .content table td,html.theme--catppuccin-mocha .content table th{border:1px solid #585b70;border-width:0 0 1px;padding:0.5em 0.75em;vertical-align:top}html.theme--catppuccin-mocha .content table th{color:#b8c5ef}html.theme--catppuccin-mocha .content table th:not([align]){text-align:inherit}html.theme--catppuccin-mocha .content table thead td,html.theme--catppuccin-mocha .content table thead th{border-width:0 0 2px;color:#b8c5ef}html.theme--catppuccin-mocha .content table tfoot td,html.theme--catppuccin-mocha .content table tfoot th{border-width:2px 0 0;color:#b8c5ef}html.theme--catppuccin-mocha .content table tbody tr:last-child td,html.theme--catppuccin-mocha .content table tbody tr:last-child th{border-bottom-width:0}html.theme--catppuccin-mocha .content .tabs li+li{margin-top:0}html.theme--catppuccin-mocha .content.is-small,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.content{font-size:.75rem}html.theme--catppuccin-mocha .content.is-normal{font-size:1rem}html.theme--catppuccin-mocha .content.is-medium{font-size:1.25rem}html.theme--catppuccin-mocha .content.is-large{font-size:1.5rem}html.theme--catppuccin-mocha .icon{align-items:center;display:inline-flex;justify-content:center;height:1.5rem;width:1.5rem}html.theme--catppuccin-mocha .icon.is-small,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.icon{height:1rem;width:1rem}html.theme--catppuccin-mocha .icon.is-medium{height:2rem;width:2rem}html.theme--catppuccin-mocha .icon.is-large{height:3rem;width:3rem}html.theme--catppuccin-mocha .icon-text{align-items:flex-start;color:inherit;display:inline-flex;flex-wrap:wrap;line-height:1.5rem;vertical-align:top}html.theme--catppuccin-mocha .icon-text .icon{flex-grow:0;flex-shrink:0}html.theme--catppuccin-mocha .icon-text .icon:not(:last-child){margin-right:.25em}html.theme--catppuccin-mocha .icon-text .icon:not(:first-child){margin-left:.25em}html.theme--catppuccin-mocha div.icon-text{display:flex}html.theme--catppuccin-mocha .image,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img{display:block;position:relative}html.theme--catppuccin-mocha .image img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img img{display:block;height:auto;width:100%}html.theme--catppuccin-mocha .image img.is-rounded,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img img.is-rounded{border-radius:9999px}html.theme--catppuccin-mocha .image.is-fullwidth,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-fullwidth{width:100%}html.theme--catppuccin-mocha .image.is-square img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-square img,html.theme--catppuccin-mocha .image.is-square .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-square .has-ratio,html.theme--catppuccin-mocha .image.is-1by1 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-1by1 img,html.theme--catppuccin-mocha .image.is-1by1 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-1by1 .has-ratio,html.theme--catppuccin-mocha .image.is-5by4 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-5by4 img,html.theme--catppuccin-mocha .image.is-5by4 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-5by4 .has-ratio,html.theme--catppuccin-mocha .image.is-4by3 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-4by3 img,html.theme--catppuccin-mocha .image.is-4by3 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-4by3 .has-ratio,html.theme--catppuccin-mocha .image.is-3by2 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by2 img,html.theme--catppuccin-mocha .image.is-3by2 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by2 .has-ratio,html.theme--catppuccin-mocha .image.is-5by3 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-5by3 img,html.theme--catppuccin-mocha .image.is-5by3 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-5by3 .has-ratio,html.theme--catppuccin-mocha .image.is-16by9 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-16by9 img,html.theme--catppuccin-mocha .image.is-16by9 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-16by9 .has-ratio,html.theme--catppuccin-mocha .image.is-2by1 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-2by1 img,html.theme--catppuccin-mocha .image.is-2by1 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-2by1 .has-ratio,html.theme--catppuccin-mocha .image.is-3by1 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by1 img,html.theme--catppuccin-mocha .image.is-3by1 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by1 .has-ratio,html.theme--catppuccin-mocha .image.is-4by5 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-4by5 img,html.theme--catppuccin-mocha .image.is-4by5 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-4by5 .has-ratio,html.theme--catppuccin-mocha .image.is-3by4 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by4 img,html.theme--catppuccin-mocha .image.is-3by4 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by4 .has-ratio,html.theme--catppuccin-mocha .image.is-2by3 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-2by3 img,html.theme--catppuccin-mocha .image.is-2by3 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-2by3 .has-ratio,html.theme--catppuccin-mocha .image.is-3by5 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by5 img,html.theme--catppuccin-mocha .image.is-3by5 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by5 .has-ratio,html.theme--catppuccin-mocha .image.is-9by16 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-9by16 img,html.theme--catppuccin-mocha .image.is-9by16 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-9by16 .has-ratio,html.theme--catppuccin-mocha .image.is-1by2 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-1by2 img,html.theme--catppuccin-mocha .image.is-1by2 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-1by2 .has-ratio,html.theme--catppuccin-mocha .image.is-1by3 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-1by3 img,html.theme--catppuccin-mocha .image.is-1by3 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-1by3 .has-ratio{height:100%;width:100%}html.theme--catppuccin-mocha .image.is-square,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-square,html.theme--catppuccin-mocha .image.is-1by1,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-1by1{padding-top:100%}html.theme--catppuccin-mocha .image.is-5by4,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-5by4{padding-top:80%}html.theme--catppuccin-mocha .image.is-4by3,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-4by3{padding-top:75%}html.theme--catppuccin-mocha .image.is-3by2,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by2{padding-top:66.6666%}html.theme--catppuccin-mocha .image.is-5by3,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-5by3{padding-top:60%}html.theme--catppuccin-mocha .image.is-16by9,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-16by9{padding-top:56.25%}html.theme--catppuccin-mocha .image.is-2by1,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-2by1{padding-top:50%}html.theme--catppuccin-mocha .image.is-3by1,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by1{padding-top:33.3333%}html.theme--catppuccin-mocha .image.is-4by5,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-4by5{padding-top:125%}html.theme--catppuccin-mocha .image.is-3by4,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by4{padding-top:133.3333%}html.theme--catppuccin-mocha .image.is-2by3,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-2by3{padding-top:150%}html.theme--catppuccin-mocha .image.is-3by5,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by5{padding-top:166.6666%}html.theme--catppuccin-mocha .image.is-9by16,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-9by16{padding-top:177.7777%}html.theme--catppuccin-mocha .image.is-1by2,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-1by2{padding-top:200%}html.theme--catppuccin-mocha .image.is-1by3,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-1by3{padding-top:300%}html.theme--catppuccin-mocha .image.is-16x16,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-16x16{height:16px;width:16px}html.theme--catppuccin-mocha .image.is-24x24,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-24x24{height:24px;width:24px}html.theme--catppuccin-mocha .image.is-32x32,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-32x32{height:32px;width:32px}html.theme--catppuccin-mocha .image.is-48x48,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-48x48{height:48px;width:48px}html.theme--catppuccin-mocha .image.is-64x64,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-64x64{height:64px;width:64px}html.theme--catppuccin-mocha .image.is-96x96,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-96x96{height:96px;width:96px}html.theme--catppuccin-mocha .image.is-128x128,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-128x128{height:128px;width:128px}html.theme--catppuccin-mocha .notification{background-color:#181825;border-radius:.4em;position:relative;padding:1.25rem 2.5rem 1.25rem 1.5rem}html.theme--catppuccin-mocha .notification a:not(.button):not(.dropdown-item){color:currentColor;text-decoration:underline}html.theme--catppuccin-mocha .notification strong{color:currentColor}html.theme--catppuccin-mocha .notification code,html.theme--catppuccin-mocha .notification pre{background:#fff}html.theme--catppuccin-mocha .notification pre code{background:transparent}html.theme--catppuccin-mocha .notification>.delete{right:.5rem;position:absolute;top:0.5rem}html.theme--catppuccin-mocha .notification .title,html.theme--catppuccin-mocha .notification .subtitle,html.theme--catppuccin-mocha .notification .content{color:currentColor}html.theme--catppuccin-mocha .notification.is-white{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-mocha .notification.is-black{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-mocha .notification.is-light{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .notification.is-dark,html.theme--catppuccin-mocha .content kbd.notification{background-color:#313244;color:#fff}html.theme--catppuccin-mocha .notification.is-primary,html.theme--catppuccin-mocha .docstring>section>a.notification.docs-sourcelink{background-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .notification.is-primary.is-light,html.theme--catppuccin-mocha .docstring>section>a.notification.is-light.docs-sourcelink{background-color:#ebf3fe;color:#063c93}html.theme--catppuccin-mocha .notification.is-link{background-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .notification.is-link.is-light{background-color:#ebf3fe;color:#063c93}html.theme--catppuccin-mocha .notification.is-info{background-color:#94e2d5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .notification.is-info.is-light{background-color:#effbf9;color:#207466}html.theme--catppuccin-mocha .notification.is-success{background-color:#a6e3a1;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .notification.is-success.is-light{background-color:#f0faef;color:#287222}html.theme--catppuccin-mocha .notification.is-warning{background-color:#f9e2af;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .notification.is-warning.is-light{background-color:#fef8ec;color:#8a620a}html.theme--catppuccin-mocha .notification.is-danger{background-color:#f38ba8;color:#fff}html.theme--catppuccin-mocha .notification.is-danger.is-light{background-color:#fdedf1;color:#991036}html.theme--catppuccin-mocha .progress{-moz-appearance:none;-webkit-appearance:none;border:none;border-radius:9999px;display:block;height:1rem;overflow:hidden;padding:0;width:100%}html.theme--catppuccin-mocha .progress::-webkit-progress-bar{background-color:#45475a}html.theme--catppuccin-mocha .progress::-webkit-progress-value{background-color:#7f849c}html.theme--catppuccin-mocha .progress::-moz-progress-bar{background-color:#7f849c}html.theme--catppuccin-mocha .progress::-ms-fill{background-color:#7f849c;border:none}html.theme--catppuccin-mocha .progress.is-white::-webkit-progress-value{background-color:#fff}html.theme--catppuccin-mocha .progress.is-white::-moz-progress-bar{background-color:#fff}html.theme--catppuccin-mocha .progress.is-white::-ms-fill{background-color:#fff}html.theme--catppuccin-mocha .progress.is-white:indeterminate{background-image:linear-gradient(to right, #fff 30%, #45475a 30%)}html.theme--catppuccin-mocha .progress.is-black::-webkit-progress-value{background-color:#0a0a0a}html.theme--catppuccin-mocha .progress.is-black::-moz-progress-bar{background-color:#0a0a0a}html.theme--catppuccin-mocha .progress.is-black::-ms-fill{background-color:#0a0a0a}html.theme--catppuccin-mocha .progress.is-black:indeterminate{background-image:linear-gradient(to right, #0a0a0a 30%, #45475a 30%)}html.theme--catppuccin-mocha .progress.is-light::-webkit-progress-value{background-color:#f5f5f5}html.theme--catppuccin-mocha .progress.is-light::-moz-progress-bar{background-color:#f5f5f5}html.theme--catppuccin-mocha .progress.is-light::-ms-fill{background-color:#f5f5f5}html.theme--catppuccin-mocha .progress.is-light:indeterminate{background-image:linear-gradient(to right, #f5f5f5 30%, #45475a 30%)}html.theme--catppuccin-mocha .progress.is-dark::-webkit-progress-value,html.theme--catppuccin-mocha .content kbd.progress::-webkit-progress-value{background-color:#313244}html.theme--catppuccin-mocha .progress.is-dark::-moz-progress-bar,html.theme--catppuccin-mocha .content kbd.progress::-moz-progress-bar{background-color:#313244}html.theme--catppuccin-mocha .progress.is-dark::-ms-fill,html.theme--catppuccin-mocha .content kbd.progress::-ms-fill{background-color:#313244}html.theme--catppuccin-mocha .progress.is-dark:indeterminate,html.theme--catppuccin-mocha .content kbd.progress:indeterminate{background-image:linear-gradient(to right, #313244 30%, #45475a 30%)}html.theme--catppuccin-mocha .progress.is-primary::-webkit-progress-value,html.theme--catppuccin-mocha .docstring>section>a.progress.docs-sourcelink::-webkit-progress-value{background-color:#89b4fa}html.theme--catppuccin-mocha .progress.is-primary::-moz-progress-bar,html.theme--catppuccin-mocha .docstring>section>a.progress.docs-sourcelink::-moz-progress-bar{background-color:#89b4fa}html.theme--catppuccin-mocha .progress.is-primary::-ms-fill,html.theme--catppuccin-mocha .docstring>section>a.progress.docs-sourcelink::-ms-fill{background-color:#89b4fa}html.theme--catppuccin-mocha .progress.is-primary:indeterminate,html.theme--catppuccin-mocha .docstring>section>a.progress.docs-sourcelink:indeterminate{background-image:linear-gradient(to right, #89b4fa 30%, #45475a 30%)}html.theme--catppuccin-mocha .progress.is-link::-webkit-progress-value{background-color:#89b4fa}html.theme--catppuccin-mocha .progress.is-link::-moz-progress-bar{background-color:#89b4fa}html.theme--catppuccin-mocha .progress.is-link::-ms-fill{background-color:#89b4fa}html.theme--catppuccin-mocha .progress.is-link:indeterminate{background-image:linear-gradient(to right, #89b4fa 30%, #45475a 30%)}html.theme--catppuccin-mocha .progress.is-info::-webkit-progress-value{background-color:#94e2d5}html.theme--catppuccin-mocha .progress.is-info::-moz-progress-bar{background-color:#94e2d5}html.theme--catppuccin-mocha .progress.is-info::-ms-fill{background-color:#94e2d5}html.theme--catppuccin-mocha .progress.is-info:indeterminate{background-image:linear-gradient(to right, #94e2d5 30%, #45475a 30%)}html.theme--catppuccin-mocha .progress.is-success::-webkit-progress-value{background-color:#a6e3a1}html.theme--catppuccin-mocha .progress.is-success::-moz-progress-bar{background-color:#a6e3a1}html.theme--catppuccin-mocha .progress.is-success::-ms-fill{background-color:#a6e3a1}html.theme--catppuccin-mocha .progress.is-success:indeterminate{background-image:linear-gradient(to right, #a6e3a1 30%, #45475a 30%)}html.theme--catppuccin-mocha .progress.is-warning::-webkit-progress-value{background-color:#f9e2af}html.theme--catppuccin-mocha .progress.is-warning::-moz-progress-bar{background-color:#f9e2af}html.theme--catppuccin-mocha .progress.is-warning::-ms-fill{background-color:#f9e2af}html.theme--catppuccin-mocha .progress.is-warning:indeterminate{background-image:linear-gradient(to right, #f9e2af 30%, #45475a 30%)}html.theme--catppuccin-mocha .progress.is-danger::-webkit-progress-value{background-color:#f38ba8}html.theme--catppuccin-mocha .progress.is-danger::-moz-progress-bar{background-color:#f38ba8}html.theme--catppuccin-mocha .progress.is-danger::-ms-fill{background-color:#f38ba8}html.theme--catppuccin-mocha .progress.is-danger:indeterminate{background-image:linear-gradient(to right, #f38ba8 30%, #45475a 30%)}html.theme--catppuccin-mocha .progress:indeterminate{animation-duration:1.5s;animation-iteration-count:infinite;animation-name:moveIndeterminate;animation-timing-function:linear;background-color:#45475a;background-image:linear-gradient(to right, #cdd6f4 30%, #45475a 30%);background-position:top left;background-repeat:no-repeat;background-size:150% 150%}html.theme--catppuccin-mocha .progress:indeterminate::-webkit-progress-bar{background-color:transparent}html.theme--catppuccin-mocha .progress:indeterminate::-moz-progress-bar{background-color:transparent}html.theme--catppuccin-mocha .progress:indeterminate::-ms-fill{animation-name:none}html.theme--catppuccin-mocha .progress.is-small,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.progress{height:.75rem}html.theme--catppuccin-mocha .progress.is-medium{height:1.25rem}html.theme--catppuccin-mocha .progress.is-large{height:1.5rem}@keyframes moveIndeterminate{from{background-position:200% 0}to{background-position:-200% 0}}html.theme--catppuccin-mocha .table{background-color:#45475a;color:#cdd6f4}html.theme--catppuccin-mocha .table td,html.theme--catppuccin-mocha .table th{border:1px solid #585b70;border-width:0 0 1px;padding:0.5em 0.75em;vertical-align:top}html.theme--catppuccin-mocha .table td.is-white,html.theme--catppuccin-mocha .table th.is-white{background-color:#fff;border-color:#fff;color:#0a0a0a}html.theme--catppuccin-mocha .table td.is-black,html.theme--catppuccin-mocha .table th.is-black{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}html.theme--catppuccin-mocha .table td.is-light,html.theme--catppuccin-mocha .table th.is-light{background-color:#f5f5f5;border-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .table td.is-dark,html.theme--catppuccin-mocha .table th.is-dark{background-color:#313244;border-color:#313244;color:#fff}html.theme--catppuccin-mocha .table td.is-primary,html.theme--catppuccin-mocha .table th.is-primary{background-color:#89b4fa;border-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .table td.is-link,html.theme--catppuccin-mocha .table th.is-link{background-color:#89b4fa;border-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .table td.is-info,html.theme--catppuccin-mocha .table th.is-info{background-color:#94e2d5;border-color:#94e2d5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .table td.is-success,html.theme--catppuccin-mocha .table th.is-success{background-color:#a6e3a1;border-color:#a6e3a1;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .table td.is-warning,html.theme--catppuccin-mocha .table th.is-warning{background-color:#f9e2af;border-color:#f9e2af;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .table td.is-danger,html.theme--catppuccin-mocha .table th.is-danger{background-color:#f38ba8;border-color:#f38ba8;color:#fff}html.theme--catppuccin-mocha .table td.is-narrow,html.theme--catppuccin-mocha .table th.is-narrow{white-space:nowrap;width:1%}html.theme--catppuccin-mocha .table td.is-selected,html.theme--catppuccin-mocha .table th.is-selected{background-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .table td.is-selected a,html.theme--catppuccin-mocha .table td.is-selected strong,html.theme--catppuccin-mocha .table th.is-selected a,html.theme--catppuccin-mocha .table th.is-selected strong{color:currentColor}html.theme--catppuccin-mocha .table td.is-vcentered,html.theme--catppuccin-mocha .table th.is-vcentered{vertical-align:middle}html.theme--catppuccin-mocha .table th{color:#b8c5ef}html.theme--catppuccin-mocha .table th:not([align]){text-align:left}html.theme--catppuccin-mocha .table tr.is-selected{background-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .table tr.is-selected a,html.theme--catppuccin-mocha .table tr.is-selected strong{color:currentColor}html.theme--catppuccin-mocha .table tr.is-selected td,html.theme--catppuccin-mocha .table tr.is-selected th{border-color:#fff;color:currentColor}html.theme--catppuccin-mocha .table thead{background-color:rgba(0,0,0,0)}html.theme--catppuccin-mocha .table thead td,html.theme--catppuccin-mocha .table thead th{border-width:0 0 2px;color:#b8c5ef}html.theme--catppuccin-mocha .table tfoot{background-color:rgba(0,0,0,0)}html.theme--catppuccin-mocha .table tfoot td,html.theme--catppuccin-mocha .table tfoot th{border-width:2px 0 0;color:#b8c5ef}html.theme--catppuccin-mocha .table tbody{background-color:rgba(0,0,0,0)}html.theme--catppuccin-mocha .table tbody tr:last-child td,html.theme--catppuccin-mocha .table tbody tr:last-child th{border-bottom-width:0}html.theme--catppuccin-mocha .table.is-bordered td,html.theme--catppuccin-mocha .table.is-bordered th{border-width:1px}html.theme--catppuccin-mocha .table.is-bordered tr:last-child td,html.theme--catppuccin-mocha .table.is-bordered tr:last-child th{border-bottom-width:1px}html.theme--catppuccin-mocha .table.is-fullwidth{width:100%}html.theme--catppuccin-mocha .table.is-hoverable tbody tr:not(.is-selected):hover{background-color:#313244}html.theme--catppuccin-mocha .table.is-hoverable.is-striped tbody tr:not(.is-selected):hover{background-color:#313244}html.theme--catppuccin-mocha .table.is-hoverable.is-striped tbody tr:not(.is-selected):hover:nth-child(even){background-color:#35364a}html.theme--catppuccin-mocha .table.is-narrow td,html.theme--catppuccin-mocha .table.is-narrow th{padding:0.25em 0.5em}html.theme--catppuccin-mocha .table.is-striped tbody tr:not(.is-selected):nth-child(even){background-color:#313244}html.theme--catppuccin-mocha .table-container{-webkit-overflow-scrolling:touch;overflow:auto;overflow-y:hidden;max-width:100%}html.theme--catppuccin-mocha .tags{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}html.theme--catppuccin-mocha .tags .tag,html.theme--catppuccin-mocha .tags .content kbd,html.theme--catppuccin-mocha .content .tags kbd,html.theme--catppuccin-mocha .tags .docstring>section>a.docs-sourcelink{margin-bottom:0.5rem}html.theme--catppuccin-mocha .tags .tag:not(:last-child),html.theme--catppuccin-mocha .tags .content kbd:not(:last-child),html.theme--catppuccin-mocha .content .tags kbd:not(:last-child),html.theme--catppuccin-mocha .tags .docstring>section>a.docs-sourcelink:not(:last-child){margin-right:.5rem}html.theme--catppuccin-mocha .tags:last-child{margin-bottom:-0.5rem}html.theme--catppuccin-mocha .tags:not(:last-child){margin-bottom:1rem}html.theme--catppuccin-mocha .tags.are-medium .tag:not(.is-normal):not(.is-large),html.theme--catppuccin-mocha .tags.are-medium .content kbd:not(.is-normal):not(.is-large),html.theme--catppuccin-mocha .content .tags.are-medium kbd:not(.is-normal):not(.is-large),html.theme--catppuccin-mocha .tags.are-medium .docstring>section>a.docs-sourcelink:not(.is-normal):not(.is-large){font-size:1rem}html.theme--catppuccin-mocha .tags.are-large .tag:not(.is-normal):not(.is-medium),html.theme--catppuccin-mocha .tags.are-large .content kbd:not(.is-normal):not(.is-medium),html.theme--catppuccin-mocha .content .tags.are-large kbd:not(.is-normal):not(.is-medium),html.theme--catppuccin-mocha .tags.are-large .docstring>section>a.docs-sourcelink:not(.is-normal):not(.is-medium){font-size:1.25rem}html.theme--catppuccin-mocha .tags.is-centered{justify-content:center}html.theme--catppuccin-mocha .tags.is-centered .tag,html.theme--catppuccin-mocha .tags.is-centered .content kbd,html.theme--catppuccin-mocha .content .tags.is-centered kbd,html.theme--catppuccin-mocha .tags.is-centered .docstring>section>a.docs-sourcelink{margin-right:0.25rem;margin-left:0.25rem}html.theme--catppuccin-mocha .tags.is-right{justify-content:flex-end}html.theme--catppuccin-mocha .tags.is-right .tag:not(:first-child),html.theme--catppuccin-mocha .tags.is-right .content kbd:not(:first-child),html.theme--catppuccin-mocha .content .tags.is-right kbd:not(:first-child),html.theme--catppuccin-mocha .tags.is-right .docstring>section>a.docs-sourcelink:not(:first-child){margin-left:0.5rem}html.theme--catppuccin-mocha .tags.is-right .tag:not(:last-child),html.theme--catppuccin-mocha .tags.is-right .content kbd:not(:last-child),html.theme--catppuccin-mocha .content .tags.is-right kbd:not(:last-child),html.theme--catppuccin-mocha .tags.is-right .docstring>section>a.docs-sourcelink:not(:last-child){margin-right:0}html.theme--catppuccin-mocha .tags.has-addons .tag,html.theme--catppuccin-mocha .tags.has-addons .content kbd,html.theme--catppuccin-mocha .content .tags.has-addons kbd,html.theme--catppuccin-mocha .tags.has-addons .docstring>section>a.docs-sourcelink{margin-right:0}html.theme--catppuccin-mocha .tags.has-addons .tag:not(:first-child),html.theme--catppuccin-mocha .tags.has-addons .content kbd:not(:first-child),html.theme--catppuccin-mocha .content .tags.has-addons kbd:not(:first-child),html.theme--catppuccin-mocha .tags.has-addons .docstring>section>a.docs-sourcelink:not(:first-child){margin-left:0;border-top-left-radius:0;border-bottom-left-radius:0}html.theme--catppuccin-mocha .tags.has-addons .tag:not(:last-child),html.theme--catppuccin-mocha .tags.has-addons .content kbd:not(:last-child),html.theme--catppuccin-mocha .content .tags.has-addons kbd:not(:last-child),html.theme--catppuccin-mocha .tags.has-addons .docstring>section>a.docs-sourcelink:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}html.theme--catppuccin-mocha .tag:not(body),html.theme--catppuccin-mocha .content kbd:not(body),html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink:not(body){align-items:center;background-color:#181825;border-radius:.4em;color:#cdd6f4;display:inline-flex;font-size:.75rem;height:2em;justify-content:center;line-height:1.5;padding-left:0.75em;padding-right:0.75em;white-space:nowrap}html.theme--catppuccin-mocha .tag:not(body) .delete,html.theme--catppuccin-mocha .content kbd:not(body) .delete,html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink:not(body) .delete{margin-left:.25rem;margin-right:-.375rem}html.theme--catppuccin-mocha .tag.is-white:not(body),html.theme--catppuccin-mocha .content kbd.is-white:not(body),html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink.is-white:not(body){background-color:#fff;color:#0a0a0a}html.theme--catppuccin-mocha .tag.is-black:not(body),html.theme--catppuccin-mocha .content kbd.is-black:not(body),html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink.is-black:not(body){background-color:#0a0a0a;color:#fff}html.theme--catppuccin-mocha .tag.is-light:not(body),html.theme--catppuccin-mocha .content kbd.is-light:not(body),html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink.is-light:not(body){background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .tag.is-dark:not(body),html.theme--catppuccin-mocha .content kbd:not(body),html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink.is-dark:not(body),html.theme--catppuccin-mocha .content .docstring>section>kbd:not(body){background-color:#313244;color:#fff}html.theme--catppuccin-mocha .tag.is-primary:not(body),html.theme--catppuccin-mocha .content kbd.is-primary:not(body),html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink:not(body){background-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .tag.is-primary.is-light:not(body),html.theme--catppuccin-mocha .content kbd.is-primary.is-light:not(body),html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink.is-light:not(body){background-color:#ebf3fe;color:#063c93}html.theme--catppuccin-mocha .tag.is-link:not(body),html.theme--catppuccin-mocha .content kbd.is-link:not(body),html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink.is-link:not(body){background-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .tag.is-link.is-light:not(body),html.theme--catppuccin-mocha .content kbd.is-link.is-light:not(body),html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink.is-link.is-light:not(body){background-color:#ebf3fe;color:#063c93}html.theme--catppuccin-mocha .tag.is-info:not(body),html.theme--catppuccin-mocha .content kbd.is-info:not(body),html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink.is-info:not(body){background-color:#94e2d5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .tag.is-info.is-light:not(body),html.theme--catppuccin-mocha .content kbd.is-info.is-light:not(body),html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink.is-info.is-light:not(body){background-color:#effbf9;color:#207466}html.theme--catppuccin-mocha .tag.is-success:not(body),html.theme--catppuccin-mocha .content kbd.is-success:not(body),html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink.is-success:not(body){background-color:#a6e3a1;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .tag.is-success.is-light:not(body),html.theme--catppuccin-mocha .content kbd.is-success.is-light:not(body),html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink.is-success.is-light:not(body){background-color:#f0faef;color:#287222}html.theme--catppuccin-mocha .tag.is-warning:not(body),html.theme--catppuccin-mocha .content kbd.is-warning:not(body),html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink.is-warning:not(body){background-color:#f9e2af;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .tag.is-warning.is-light:not(body),html.theme--catppuccin-mocha .content kbd.is-warning.is-light:not(body),html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink.is-warning.is-light:not(body){background-color:#fef8ec;color:#8a620a}html.theme--catppuccin-mocha .tag.is-danger:not(body),html.theme--catppuccin-mocha .content kbd.is-danger:not(body),html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink.is-danger:not(body){background-color:#f38ba8;color:#fff}html.theme--catppuccin-mocha .tag.is-danger.is-light:not(body),html.theme--catppuccin-mocha .content kbd.is-danger.is-light:not(body),html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink.is-danger.is-light:not(body){background-color:#fdedf1;color:#991036}html.theme--catppuccin-mocha .tag.is-normal:not(body),html.theme--catppuccin-mocha .content kbd.is-normal:not(body),html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink.is-normal:not(body){font-size:.75rem}html.theme--catppuccin-mocha .tag.is-medium:not(body),html.theme--catppuccin-mocha .content kbd.is-medium:not(body),html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink.is-medium:not(body){font-size:1rem}html.theme--catppuccin-mocha .tag.is-large:not(body),html.theme--catppuccin-mocha .content kbd.is-large:not(body),html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink.is-large:not(body){font-size:1.25rem}html.theme--catppuccin-mocha .tag:not(body) .icon:first-child:not(:last-child),html.theme--catppuccin-mocha .content kbd:not(body) .icon:first-child:not(:last-child),html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink:not(body) .icon:first-child:not(:last-child){margin-left:-.375em;margin-right:.1875em}html.theme--catppuccin-mocha .tag:not(body) .icon:last-child:not(:first-child),html.theme--catppuccin-mocha .content kbd:not(body) .icon:last-child:not(:first-child),html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink:not(body) .icon:last-child:not(:first-child){margin-left:.1875em;margin-right:-.375em}html.theme--catppuccin-mocha .tag:not(body) .icon:first-child:last-child,html.theme--catppuccin-mocha .content kbd:not(body) .icon:first-child:last-child,html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink:not(body) .icon:first-child:last-child{margin-left:-.375em;margin-right:-.375em}html.theme--catppuccin-mocha .tag.is-delete:not(body),html.theme--catppuccin-mocha .content kbd.is-delete:not(body),html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink.is-delete:not(body){margin-left:1px;padding:0;position:relative;width:2em}html.theme--catppuccin-mocha .tag.is-delete:not(body)::before,html.theme--catppuccin-mocha .content kbd.is-delete:not(body)::before,html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink.is-delete:not(body)::before,html.theme--catppuccin-mocha .tag.is-delete:not(body)::after,html.theme--catppuccin-mocha .content kbd.is-delete:not(body)::after,html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink.is-delete:not(body)::after{background-color:currentColor;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}html.theme--catppuccin-mocha .tag.is-delete:not(body)::before,html.theme--catppuccin-mocha .content kbd.is-delete:not(body)::before,html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink.is-delete:not(body)::before{height:1px;width:50%}html.theme--catppuccin-mocha .tag.is-delete:not(body)::after,html.theme--catppuccin-mocha .content kbd.is-delete:not(body)::after,html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink.is-delete:not(body)::after{height:50%;width:1px}html.theme--catppuccin-mocha .tag.is-delete:not(body):hover,html.theme--catppuccin-mocha .content kbd.is-delete:not(body):hover,html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink.is-delete:not(body):hover,html.theme--catppuccin-mocha .tag.is-delete:not(body):focus,html.theme--catppuccin-mocha .content kbd.is-delete:not(body):focus,html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink.is-delete:not(body):focus{background-color:#0e0e16}html.theme--catppuccin-mocha .tag.is-delete:not(body):active,html.theme--catppuccin-mocha .content kbd.is-delete:not(body):active,html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink.is-delete:not(body):active{background-color:#040406}html.theme--catppuccin-mocha .tag.is-rounded:not(body),html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input:not(body),html.theme--catppuccin-mocha .content kbd.is-rounded:not(body),html.theme--catppuccin-mocha #documenter .docs-sidebar .content form.docs-search>input:not(body),html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink.is-rounded:not(body){border-radius:9999px}html.theme--catppuccin-mocha a.tag:hover,html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink:hover{text-decoration:underline}html.theme--catppuccin-mocha .title,html.theme--catppuccin-mocha .subtitle{word-break:break-word}html.theme--catppuccin-mocha .title em,html.theme--catppuccin-mocha .title span,html.theme--catppuccin-mocha .subtitle em,html.theme--catppuccin-mocha .subtitle span{font-weight:inherit}html.theme--catppuccin-mocha .title sub,html.theme--catppuccin-mocha .subtitle sub{font-size:.75em}html.theme--catppuccin-mocha .title sup,html.theme--catppuccin-mocha .subtitle sup{font-size:.75em}html.theme--catppuccin-mocha .title .tag,html.theme--catppuccin-mocha .title .content kbd,html.theme--catppuccin-mocha .content .title kbd,html.theme--catppuccin-mocha .title .docstring>section>a.docs-sourcelink,html.theme--catppuccin-mocha .subtitle .tag,html.theme--catppuccin-mocha .subtitle .content kbd,html.theme--catppuccin-mocha .content .subtitle kbd,html.theme--catppuccin-mocha .subtitle .docstring>section>a.docs-sourcelink{vertical-align:middle}html.theme--catppuccin-mocha .title{color:#fff;font-size:2rem;font-weight:500;line-height:1.125}html.theme--catppuccin-mocha .title strong{color:inherit;font-weight:inherit}html.theme--catppuccin-mocha .title:not(.is-spaced)+.subtitle{margin-top:-1.25rem}html.theme--catppuccin-mocha .title.is-1{font-size:3rem}html.theme--catppuccin-mocha .title.is-2{font-size:2.5rem}html.theme--catppuccin-mocha .title.is-3{font-size:2rem}html.theme--catppuccin-mocha .title.is-4{font-size:1.5rem}html.theme--catppuccin-mocha .title.is-5{font-size:1.25rem}html.theme--catppuccin-mocha .title.is-6{font-size:1rem}html.theme--catppuccin-mocha .title.is-7{font-size:.75rem}html.theme--catppuccin-mocha .subtitle{color:#6c7086;font-size:1.25rem;font-weight:400;line-height:1.25}html.theme--catppuccin-mocha .subtitle strong{color:#6c7086;font-weight:600}html.theme--catppuccin-mocha .subtitle:not(.is-spaced)+.title{margin-top:-1.25rem}html.theme--catppuccin-mocha .subtitle.is-1{font-size:3rem}html.theme--catppuccin-mocha .subtitle.is-2{font-size:2.5rem}html.theme--catppuccin-mocha .subtitle.is-3{font-size:2rem}html.theme--catppuccin-mocha .subtitle.is-4{font-size:1.5rem}html.theme--catppuccin-mocha .subtitle.is-5{font-size:1.25rem}html.theme--catppuccin-mocha .subtitle.is-6{font-size:1rem}html.theme--catppuccin-mocha .subtitle.is-7{font-size:.75rem}html.theme--catppuccin-mocha .heading{display:block;font-size:11px;letter-spacing:1px;margin-bottom:5px;text-transform:uppercase}html.theme--catppuccin-mocha .number{align-items:center;background-color:#181825;border-radius:9999px;display:inline-flex;font-size:1.25rem;height:2em;justify-content:center;margin-right:1.5rem;min-width:2.5em;padding:0.25rem 0.5rem;text-align:center;vertical-align:top}html.theme--catppuccin-mocha .select select,html.theme--catppuccin-mocha .textarea,html.theme--catppuccin-mocha .input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input{background-color:#1e1e2e;border-color:#585b70;border-radius:.4em;color:#7f849c}html.theme--catppuccin-mocha .select select::-moz-placeholder,html.theme--catppuccin-mocha .textarea::-moz-placeholder,html.theme--catppuccin-mocha .input::-moz-placeholder,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input::-moz-placeholder{color:#868c98}html.theme--catppuccin-mocha .select select::-webkit-input-placeholder,html.theme--catppuccin-mocha .textarea::-webkit-input-placeholder,html.theme--catppuccin-mocha .input::-webkit-input-placeholder,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input::-webkit-input-placeholder{color:#868c98}html.theme--catppuccin-mocha .select select:-moz-placeholder,html.theme--catppuccin-mocha .textarea:-moz-placeholder,html.theme--catppuccin-mocha .input:-moz-placeholder,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input:-moz-placeholder{color:#868c98}html.theme--catppuccin-mocha .select select:-ms-input-placeholder,html.theme--catppuccin-mocha .textarea:-ms-input-placeholder,html.theme--catppuccin-mocha .input:-ms-input-placeholder,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input:-ms-input-placeholder{color:#868c98}html.theme--catppuccin-mocha .select select:hover,html.theme--catppuccin-mocha .textarea:hover,html.theme--catppuccin-mocha .input:hover,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input:hover,html.theme--catppuccin-mocha .select select.is-hovered,html.theme--catppuccin-mocha .is-hovered.textarea,html.theme--catppuccin-mocha .is-hovered.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-hovered{border-color:#6c7086}html.theme--catppuccin-mocha .select select:focus,html.theme--catppuccin-mocha .textarea:focus,html.theme--catppuccin-mocha .input:focus,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input:focus,html.theme--catppuccin-mocha .select select.is-focused,html.theme--catppuccin-mocha .is-focused.textarea,html.theme--catppuccin-mocha .is-focused.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-mocha .select select:active,html.theme--catppuccin-mocha .textarea:active,html.theme--catppuccin-mocha .input:active,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input:active,html.theme--catppuccin-mocha .select select.is-active,html.theme--catppuccin-mocha .is-active.textarea,html.theme--catppuccin-mocha .is-active.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-active{border-color:#89b4fa;box-shadow:0 0 0 0.125em rgba(137,180,250,0.25)}html.theme--catppuccin-mocha .select select[disabled],html.theme--catppuccin-mocha .textarea[disabled],html.theme--catppuccin-mocha .input[disabled],html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input[disabled],fieldset[disabled] html.theme--catppuccin-mocha .select select,fieldset[disabled] html.theme--catppuccin-mocha .textarea,fieldset[disabled] html.theme--catppuccin-mocha .input,fieldset[disabled] html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input{background-color:#6c7086;border-color:#181825;box-shadow:none;color:#f7f8fd}html.theme--catppuccin-mocha .select select[disabled]::-moz-placeholder,html.theme--catppuccin-mocha .textarea[disabled]::-moz-placeholder,html.theme--catppuccin-mocha .input[disabled]::-moz-placeholder,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input[disabled]::-moz-placeholder,fieldset[disabled] html.theme--catppuccin-mocha .select select::-moz-placeholder,fieldset[disabled] html.theme--catppuccin-mocha .textarea::-moz-placeholder,fieldset[disabled] html.theme--catppuccin-mocha .input::-moz-placeholder,fieldset[disabled] html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input::-moz-placeholder{color:rgba(247,248,253,0.3)}html.theme--catppuccin-mocha .select select[disabled]::-webkit-input-placeholder,html.theme--catppuccin-mocha .textarea[disabled]::-webkit-input-placeholder,html.theme--catppuccin-mocha .input[disabled]::-webkit-input-placeholder,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input[disabled]::-webkit-input-placeholder,fieldset[disabled] html.theme--catppuccin-mocha .select select::-webkit-input-placeholder,fieldset[disabled] html.theme--catppuccin-mocha .textarea::-webkit-input-placeholder,fieldset[disabled] html.theme--catppuccin-mocha .input::-webkit-input-placeholder,fieldset[disabled] html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input::-webkit-input-placeholder{color:rgba(247,248,253,0.3)}html.theme--catppuccin-mocha .select select[disabled]:-moz-placeholder,html.theme--catppuccin-mocha .textarea[disabled]:-moz-placeholder,html.theme--catppuccin-mocha .input[disabled]:-moz-placeholder,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input[disabled]:-moz-placeholder,fieldset[disabled] html.theme--catppuccin-mocha .select select:-moz-placeholder,fieldset[disabled] html.theme--catppuccin-mocha .textarea:-moz-placeholder,fieldset[disabled] html.theme--catppuccin-mocha .input:-moz-placeholder,fieldset[disabled] html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input:-moz-placeholder{color:rgba(247,248,253,0.3)}html.theme--catppuccin-mocha .select select[disabled]:-ms-input-placeholder,html.theme--catppuccin-mocha .textarea[disabled]:-ms-input-placeholder,html.theme--catppuccin-mocha .input[disabled]:-ms-input-placeholder,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input[disabled]:-ms-input-placeholder,fieldset[disabled] html.theme--catppuccin-mocha .select select:-ms-input-placeholder,fieldset[disabled] html.theme--catppuccin-mocha .textarea:-ms-input-placeholder,fieldset[disabled] html.theme--catppuccin-mocha .input:-ms-input-placeholder,fieldset[disabled] html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input:-ms-input-placeholder{color:rgba(247,248,253,0.3)}html.theme--catppuccin-mocha .textarea,html.theme--catppuccin-mocha .input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input{box-shadow:inset 0 0.0625em 0.125em rgba(10,10,10,0.05);max-width:100%;width:100%}html.theme--catppuccin-mocha .textarea[readonly],html.theme--catppuccin-mocha .input[readonly],html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input[readonly]{box-shadow:none}html.theme--catppuccin-mocha .is-white.textarea,html.theme--catppuccin-mocha .is-white.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-white{border-color:#fff}html.theme--catppuccin-mocha .is-white.textarea:focus,html.theme--catppuccin-mocha .is-white.input:focus,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-white:focus,html.theme--catppuccin-mocha .is-white.is-focused.textarea,html.theme--catppuccin-mocha .is-white.is-focused.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-mocha .is-white.textarea:active,html.theme--catppuccin-mocha .is-white.input:active,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-white:active,html.theme--catppuccin-mocha .is-white.is-active.textarea,html.theme--catppuccin-mocha .is-white.is-active.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}html.theme--catppuccin-mocha .is-black.textarea,html.theme--catppuccin-mocha .is-black.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-black{border-color:#0a0a0a}html.theme--catppuccin-mocha .is-black.textarea:focus,html.theme--catppuccin-mocha .is-black.input:focus,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-black:focus,html.theme--catppuccin-mocha .is-black.is-focused.textarea,html.theme--catppuccin-mocha .is-black.is-focused.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-mocha .is-black.textarea:active,html.theme--catppuccin-mocha .is-black.input:active,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-black:active,html.theme--catppuccin-mocha .is-black.is-active.textarea,html.theme--catppuccin-mocha .is-black.is-active.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}html.theme--catppuccin-mocha .is-light.textarea,html.theme--catppuccin-mocha .is-light.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-light{border-color:#f5f5f5}html.theme--catppuccin-mocha .is-light.textarea:focus,html.theme--catppuccin-mocha .is-light.input:focus,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-light:focus,html.theme--catppuccin-mocha .is-light.is-focused.textarea,html.theme--catppuccin-mocha .is-light.is-focused.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-mocha .is-light.textarea:active,html.theme--catppuccin-mocha .is-light.input:active,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-light:active,html.theme--catppuccin-mocha .is-light.is-active.textarea,html.theme--catppuccin-mocha .is-light.is-active.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(245,245,245,0.25)}html.theme--catppuccin-mocha .is-dark.textarea,html.theme--catppuccin-mocha .content kbd.textarea,html.theme--catppuccin-mocha .is-dark.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-dark,html.theme--catppuccin-mocha .content kbd.input{border-color:#313244}html.theme--catppuccin-mocha .is-dark.textarea:focus,html.theme--catppuccin-mocha .content kbd.textarea:focus,html.theme--catppuccin-mocha .is-dark.input:focus,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-dark:focus,html.theme--catppuccin-mocha .content kbd.input:focus,html.theme--catppuccin-mocha .is-dark.is-focused.textarea,html.theme--catppuccin-mocha .content kbd.is-focused.textarea,html.theme--catppuccin-mocha .is-dark.is-focused.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-mocha .content kbd.is-focused.input,html.theme--catppuccin-mocha #documenter .docs-sidebar .content form.docs-search>input.is-focused,html.theme--catppuccin-mocha .is-dark.textarea:active,html.theme--catppuccin-mocha .content kbd.textarea:active,html.theme--catppuccin-mocha .is-dark.input:active,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-dark:active,html.theme--catppuccin-mocha .content kbd.input:active,html.theme--catppuccin-mocha .is-dark.is-active.textarea,html.theme--catppuccin-mocha .content kbd.is-active.textarea,html.theme--catppuccin-mocha .is-dark.is-active.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-active,html.theme--catppuccin-mocha .content kbd.is-active.input,html.theme--catppuccin-mocha #documenter .docs-sidebar .content form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(49,50,68,0.25)}html.theme--catppuccin-mocha .is-primary.textarea,html.theme--catppuccin-mocha .docstring>section>a.textarea.docs-sourcelink,html.theme--catppuccin-mocha .is-primary.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-primary,html.theme--catppuccin-mocha .docstring>section>a.input.docs-sourcelink{border-color:#89b4fa}html.theme--catppuccin-mocha .is-primary.textarea:focus,html.theme--catppuccin-mocha .docstring>section>a.textarea.docs-sourcelink:focus,html.theme--catppuccin-mocha .is-primary.input:focus,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-primary:focus,html.theme--catppuccin-mocha .docstring>section>a.input.docs-sourcelink:focus,html.theme--catppuccin-mocha .is-primary.is-focused.textarea,html.theme--catppuccin-mocha .docstring>section>a.is-focused.textarea.docs-sourcelink,html.theme--catppuccin-mocha .is-primary.is-focused.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-mocha .docstring>section>a.is-focused.input.docs-sourcelink,html.theme--catppuccin-mocha .is-primary.textarea:active,html.theme--catppuccin-mocha .docstring>section>a.textarea.docs-sourcelink:active,html.theme--catppuccin-mocha .is-primary.input:active,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-primary:active,html.theme--catppuccin-mocha .docstring>section>a.input.docs-sourcelink:active,html.theme--catppuccin-mocha .is-primary.is-active.textarea,html.theme--catppuccin-mocha .docstring>section>a.is-active.textarea.docs-sourcelink,html.theme--catppuccin-mocha .is-primary.is-active.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-active,html.theme--catppuccin-mocha .docstring>section>a.is-active.input.docs-sourcelink{box-shadow:0 0 0 0.125em rgba(137,180,250,0.25)}html.theme--catppuccin-mocha .is-link.textarea,html.theme--catppuccin-mocha .is-link.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-link{border-color:#89b4fa}html.theme--catppuccin-mocha .is-link.textarea:focus,html.theme--catppuccin-mocha .is-link.input:focus,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-link:focus,html.theme--catppuccin-mocha .is-link.is-focused.textarea,html.theme--catppuccin-mocha .is-link.is-focused.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-mocha .is-link.textarea:active,html.theme--catppuccin-mocha .is-link.input:active,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-link:active,html.theme--catppuccin-mocha .is-link.is-active.textarea,html.theme--catppuccin-mocha .is-link.is-active.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(137,180,250,0.25)}html.theme--catppuccin-mocha .is-info.textarea,html.theme--catppuccin-mocha .is-info.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-info{border-color:#94e2d5}html.theme--catppuccin-mocha .is-info.textarea:focus,html.theme--catppuccin-mocha .is-info.input:focus,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-info:focus,html.theme--catppuccin-mocha .is-info.is-focused.textarea,html.theme--catppuccin-mocha .is-info.is-focused.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-mocha .is-info.textarea:active,html.theme--catppuccin-mocha .is-info.input:active,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-info:active,html.theme--catppuccin-mocha .is-info.is-active.textarea,html.theme--catppuccin-mocha .is-info.is-active.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(148,226,213,0.25)}html.theme--catppuccin-mocha .is-success.textarea,html.theme--catppuccin-mocha .is-success.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-success{border-color:#a6e3a1}html.theme--catppuccin-mocha .is-success.textarea:focus,html.theme--catppuccin-mocha .is-success.input:focus,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-success:focus,html.theme--catppuccin-mocha .is-success.is-focused.textarea,html.theme--catppuccin-mocha .is-success.is-focused.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-mocha .is-success.textarea:active,html.theme--catppuccin-mocha .is-success.input:active,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-success:active,html.theme--catppuccin-mocha .is-success.is-active.textarea,html.theme--catppuccin-mocha .is-success.is-active.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(166,227,161,0.25)}html.theme--catppuccin-mocha .is-warning.textarea,html.theme--catppuccin-mocha .is-warning.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-warning{border-color:#f9e2af}html.theme--catppuccin-mocha .is-warning.textarea:focus,html.theme--catppuccin-mocha .is-warning.input:focus,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-warning:focus,html.theme--catppuccin-mocha .is-warning.is-focused.textarea,html.theme--catppuccin-mocha .is-warning.is-focused.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-mocha .is-warning.textarea:active,html.theme--catppuccin-mocha .is-warning.input:active,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-warning:active,html.theme--catppuccin-mocha .is-warning.is-active.textarea,html.theme--catppuccin-mocha .is-warning.is-active.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(249,226,175,0.25)}html.theme--catppuccin-mocha .is-danger.textarea,html.theme--catppuccin-mocha .is-danger.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-danger{border-color:#f38ba8}html.theme--catppuccin-mocha .is-danger.textarea:focus,html.theme--catppuccin-mocha .is-danger.input:focus,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-danger:focus,html.theme--catppuccin-mocha .is-danger.is-focused.textarea,html.theme--catppuccin-mocha .is-danger.is-focused.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-mocha .is-danger.textarea:active,html.theme--catppuccin-mocha .is-danger.input:active,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-danger:active,html.theme--catppuccin-mocha .is-danger.is-active.textarea,html.theme--catppuccin-mocha .is-danger.is-active.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(243,139,168,0.25)}html.theme--catppuccin-mocha .is-small.textarea,html.theme--catppuccin-mocha .is-small.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input{border-radius:3px;font-size:.75rem}html.theme--catppuccin-mocha .is-medium.textarea,html.theme--catppuccin-mocha .is-medium.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-medium{font-size:1.25rem}html.theme--catppuccin-mocha .is-large.textarea,html.theme--catppuccin-mocha .is-large.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-large{font-size:1.5rem}html.theme--catppuccin-mocha .is-fullwidth.textarea,html.theme--catppuccin-mocha .is-fullwidth.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-fullwidth{display:block;width:100%}html.theme--catppuccin-mocha .is-inline.textarea,html.theme--catppuccin-mocha .is-inline.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-inline{display:inline;width:auto}html.theme--catppuccin-mocha .input.is-rounded,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input{border-radius:9999px;padding-left:calc(calc(0.75em - 1px) + 0.375em);padding-right:calc(calc(0.75em - 1px) + 0.375em)}html.theme--catppuccin-mocha .input.is-static,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-static{background-color:transparent;border-color:transparent;box-shadow:none;padding-left:0;padding-right:0}html.theme--catppuccin-mocha .textarea{display:block;max-width:100%;min-width:100%;padding:calc(0.75em - 1px);resize:vertical}html.theme--catppuccin-mocha .textarea:not([rows]){max-height:40em;min-height:8em}html.theme--catppuccin-mocha .textarea[rows]{height:initial}html.theme--catppuccin-mocha .textarea.has-fixed-size{resize:none}html.theme--catppuccin-mocha .radio,html.theme--catppuccin-mocha .checkbox{cursor:pointer;display:inline-block;line-height:1.25;position:relative}html.theme--catppuccin-mocha .radio input,html.theme--catppuccin-mocha .checkbox input{cursor:pointer}html.theme--catppuccin-mocha .radio:hover,html.theme--catppuccin-mocha .checkbox:hover{color:#89dceb}html.theme--catppuccin-mocha .radio[disabled],html.theme--catppuccin-mocha .checkbox[disabled],fieldset[disabled] html.theme--catppuccin-mocha .radio,fieldset[disabled] html.theme--catppuccin-mocha .checkbox,html.theme--catppuccin-mocha .radio input[disabled],html.theme--catppuccin-mocha .checkbox input[disabled]{color:#f7f8fd;cursor:not-allowed}html.theme--catppuccin-mocha .radio+.radio{margin-left:.5em}html.theme--catppuccin-mocha .select{display:inline-block;max-width:100%;position:relative;vertical-align:top}html.theme--catppuccin-mocha .select:not(.is-multiple){height:2.5em}html.theme--catppuccin-mocha .select:not(.is-multiple):not(.is-loading)::after{border-color:#89b4fa;right:1.125em;z-index:4}html.theme--catppuccin-mocha .select.is-rounded select,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.select select{border-radius:9999px;padding-left:1em}html.theme--catppuccin-mocha .select select{cursor:pointer;display:block;font-size:1em;max-width:100%;outline:none}html.theme--catppuccin-mocha .select select::-ms-expand{display:none}html.theme--catppuccin-mocha .select select[disabled]:hover,fieldset[disabled] html.theme--catppuccin-mocha .select select:hover{border-color:#181825}html.theme--catppuccin-mocha .select select:not([multiple]){padding-right:2.5em}html.theme--catppuccin-mocha .select select[multiple]{height:auto;padding:0}html.theme--catppuccin-mocha .select select[multiple] option{padding:0.5em 1em}html.theme--catppuccin-mocha .select:not(.is-multiple):not(.is-loading):hover::after{border-color:#89dceb}html.theme--catppuccin-mocha .select.is-white:not(:hover)::after{border-color:#fff}html.theme--catppuccin-mocha .select.is-white select{border-color:#fff}html.theme--catppuccin-mocha .select.is-white select:hover,html.theme--catppuccin-mocha .select.is-white select.is-hovered{border-color:#f2f2f2}html.theme--catppuccin-mocha .select.is-white select:focus,html.theme--catppuccin-mocha .select.is-white select.is-focused,html.theme--catppuccin-mocha .select.is-white select:active,html.theme--catppuccin-mocha .select.is-white select.is-active{box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}html.theme--catppuccin-mocha .select.is-black:not(:hover)::after{border-color:#0a0a0a}html.theme--catppuccin-mocha .select.is-black select{border-color:#0a0a0a}html.theme--catppuccin-mocha .select.is-black select:hover,html.theme--catppuccin-mocha .select.is-black select.is-hovered{border-color:#000}html.theme--catppuccin-mocha .select.is-black select:focus,html.theme--catppuccin-mocha .select.is-black select.is-focused,html.theme--catppuccin-mocha .select.is-black select:active,html.theme--catppuccin-mocha .select.is-black select.is-active{box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}html.theme--catppuccin-mocha .select.is-light:not(:hover)::after{border-color:#f5f5f5}html.theme--catppuccin-mocha .select.is-light select{border-color:#f5f5f5}html.theme--catppuccin-mocha .select.is-light select:hover,html.theme--catppuccin-mocha .select.is-light select.is-hovered{border-color:#e8e8e8}html.theme--catppuccin-mocha .select.is-light select:focus,html.theme--catppuccin-mocha .select.is-light select.is-focused,html.theme--catppuccin-mocha .select.is-light select:active,html.theme--catppuccin-mocha .select.is-light select.is-active{box-shadow:0 0 0 0.125em rgba(245,245,245,0.25)}html.theme--catppuccin-mocha .select.is-dark:not(:hover)::after,html.theme--catppuccin-mocha .content kbd.select:not(:hover)::after{border-color:#313244}html.theme--catppuccin-mocha .select.is-dark select,html.theme--catppuccin-mocha .content kbd.select select{border-color:#313244}html.theme--catppuccin-mocha .select.is-dark select:hover,html.theme--catppuccin-mocha .content kbd.select select:hover,html.theme--catppuccin-mocha .select.is-dark select.is-hovered,html.theme--catppuccin-mocha .content kbd.select select.is-hovered{border-color:#262735}html.theme--catppuccin-mocha .select.is-dark select:focus,html.theme--catppuccin-mocha .content kbd.select select:focus,html.theme--catppuccin-mocha .select.is-dark select.is-focused,html.theme--catppuccin-mocha .content kbd.select select.is-focused,html.theme--catppuccin-mocha .select.is-dark select:active,html.theme--catppuccin-mocha .content kbd.select select:active,html.theme--catppuccin-mocha .select.is-dark select.is-active,html.theme--catppuccin-mocha .content kbd.select select.is-active{box-shadow:0 0 0 0.125em rgba(49,50,68,0.25)}html.theme--catppuccin-mocha .select.is-primary:not(:hover)::after,html.theme--catppuccin-mocha .docstring>section>a.select.docs-sourcelink:not(:hover)::after{border-color:#89b4fa}html.theme--catppuccin-mocha .select.is-primary select,html.theme--catppuccin-mocha .docstring>section>a.select.docs-sourcelink select{border-color:#89b4fa}html.theme--catppuccin-mocha .select.is-primary select:hover,html.theme--catppuccin-mocha .docstring>section>a.select.docs-sourcelink select:hover,html.theme--catppuccin-mocha .select.is-primary select.is-hovered,html.theme--catppuccin-mocha .docstring>section>a.select.docs-sourcelink select.is-hovered{border-color:#71a4f9}html.theme--catppuccin-mocha .select.is-primary select:focus,html.theme--catppuccin-mocha .docstring>section>a.select.docs-sourcelink select:focus,html.theme--catppuccin-mocha .select.is-primary select.is-focused,html.theme--catppuccin-mocha .docstring>section>a.select.docs-sourcelink select.is-focused,html.theme--catppuccin-mocha .select.is-primary select:active,html.theme--catppuccin-mocha .docstring>section>a.select.docs-sourcelink select:active,html.theme--catppuccin-mocha .select.is-primary select.is-active,html.theme--catppuccin-mocha .docstring>section>a.select.docs-sourcelink select.is-active{box-shadow:0 0 0 0.125em rgba(137,180,250,0.25)}html.theme--catppuccin-mocha .select.is-link:not(:hover)::after{border-color:#89b4fa}html.theme--catppuccin-mocha .select.is-link select{border-color:#89b4fa}html.theme--catppuccin-mocha .select.is-link select:hover,html.theme--catppuccin-mocha .select.is-link select.is-hovered{border-color:#71a4f9}html.theme--catppuccin-mocha .select.is-link select:focus,html.theme--catppuccin-mocha .select.is-link select.is-focused,html.theme--catppuccin-mocha .select.is-link select:active,html.theme--catppuccin-mocha .select.is-link select.is-active{box-shadow:0 0 0 0.125em rgba(137,180,250,0.25)}html.theme--catppuccin-mocha .select.is-info:not(:hover)::after{border-color:#94e2d5}html.theme--catppuccin-mocha .select.is-info select{border-color:#94e2d5}html.theme--catppuccin-mocha .select.is-info select:hover,html.theme--catppuccin-mocha .select.is-info select.is-hovered{border-color:#80ddcd}html.theme--catppuccin-mocha .select.is-info select:focus,html.theme--catppuccin-mocha .select.is-info select.is-focused,html.theme--catppuccin-mocha .select.is-info select:active,html.theme--catppuccin-mocha .select.is-info select.is-active{box-shadow:0 0 0 0.125em rgba(148,226,213,0.25)}html.theme--catppuccin-mocha .select.is-success:not(:hover)::after{border-color:#a6e3a1}html.theme--catppuccin-mocha .select.is-success select{border-color:#a6e3a1}html.theme--catppuccin-mocha .select.is-success select:hover,html.theme--catppuccin-mocha .select.is-success select.is-hovered{border-color:#93dd8d}html.theme--catppuccin-mocha .select.is-success select:focus,html.theme--catppuccin-mocha .select.is-success select.is-focused,html.theme--catppuccin-mocha .select.is-success select:active,html.theme--catppuccin-mocha .select.is-success select.is-active{box-shadow:0 0 0 0.125em rgba(166,227,161,0.25)}html.theme--catppuccin-mocha .select.is-warning:not(:hover)::after{border-color:#f9e2af}html.theme--catppuccin-mocha .select.is-warning select{border-color:#f9e2af}html.theme--catppuccin-mocha .select.is-warning select:hover,html.theme--catppuccin-mocha .select.is-warning select.is-hovered{border-color:#f7d997}html.theme--catppuccin-mocha .select.is-warning select:focus,html.theme--catppuccin-mocha .select.is-warning select.is-focused,html.theme--catppuccin-mocha .select.is-warning select:active,html.theme--catppuccin-mocha .select.is-warning select.is-active{box-shadow:0 0 0 0.125em rgba(249,226,175,0.25)}html.theme--catppuccin-mocha .select.is-danger:not(:hover)::after{border-color:#f38ba8}html.theme--catppuccin-mocha .select.is-danger select{border-color:#f38ba8}html.theme--catppuccin-mocha .select.is-danger select:hover,html.theme--catppuccin-mocha .select.is-danger select.is-hovered{border-color:#f17497}html.theme--catppuccin-mocha .select.is-danger select:focus,html.theme--catppuccin-mocha .select.is-danger select.is-focused,html.theme--catppuccin-mocha .select.is-danger select:active,html.theme--catppuccin-mocha .select.is-danger select.is-active{box-shadow:0 0 0 0.125em rgba(243,139,168,0.25)}html.theme--catppuccin-mocha .select.is-small,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.select{border-radius:3px;font-size:.75rem}html.theme--catppuccin-mocha .select.is-medium{font-size:1.25rem}html.theme--catppuccin-mocha .select.is-large{font-size:1.5rem}html.theme--catppuccin-mocha .select.is-disabled::after{border-color:#f7f8fd !important;opacity:0.5}html.theme--catppuccin-mocha .select.is-fullwidth{width:100%}html.theme--catppuccin-mocha .select.is-fullwidth select{width:100%}html.theme--catppuccin-mocha .select.is-loading::after{margin-top:0;position:absolute;right:.625em;top:0.625em;transform:none}html.theme--catppuccin-mocha .select.is-loading.is-small:after,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-loading:after{font-size:.75rem}html.theme--catppuccin-mocha .select.is-loading.is-medium:after{font-size:1.25rem}html.theme--catppuccin-mocha .select.is-loading.is-large:after{font-size:1.5rem}html.theme--catppuccin-mocha .file{align-items:stretch;display:flex;justify-content:flex-start;position:relative}html.theme--catppuccin-mocha .file.is-white .file-cta{background-color:#fff;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-mocha .file.is-white:hover .file-cta,html.theme--catppuccin-mocha .file.is-white.is-hovered .file-cta{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-mocha .file.is-white:focus .file-cta,html.theme--catppuccin-mocha .file.is-white.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(255,255,255,0.25);color:#0a0a0a}html.theme--catppuccin-mocha .file.is-white:active .file-cta,html.theme--catppuccin-mocha .file.is-white.is-active .file-cta{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-mocha .file.is-black .file-cta{background-color:#0a0a0a;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .file.is-black:hover .file-cta,html.theme--catppuccin-mocha .file.is-black.is-hovered .file-cta{background-color:#040404;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .file.is-black:focus .file-cta,html.theme--catppuccin-mocha .file.is-black.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(10,10,10,0.25);color:#fff}html.theme--catppuccin-mocha .file.is-black:active .file-cta,html.theme--catppuccin-mocha .file.is-black.is-active .file-cta{background-color:#000;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .file.is-light .file-cta{background-color:#f5f5f5;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .file.is-light:hover .file-cta,html.theme--catppuccin-mocha .file.is-light.is-hovered .file-cta{background-color:#eee;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .file.is-light:focus .file-cta,html.theme--catppuccin-mocha .file.is-light.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(245,245,245,0.25);color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .file.is-light:active .file-cta,html.theme--catppuccin-mocha .file.is-light.is-active .file-cta{background-color:#e8e8e8;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .file.is-dark .file-cta,html.theme--catppuccin-mocha .content kbd.file .file-cta{background-color:#313244;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .file.is-dark:hover .file-cta,html.theme--catppuccin-mocha .content kbd.file:hover .file-cta,html.theme--catppuccin-mocha .file.is-dark.is-hovered .file-cta,html.theme--catppuccin-mocha .content kbd.file.is-hovered .file-cta{background-color:#2c2d3d;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .file.is-dark:focus .file-cta,html.theme--catppuccin-mocha .content kbd.file:focus .file-cta,html.theme--catppuccin-mocha .file.is-dark.is-focused .file-cta,html.theme--catppuccin-mocha .content kbd.file.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(49,50,68,0.25);color:#fff}html.theme--catppuccin-mocha .file.is-dark:active .file-cta,html.theme--catppuccin-mocha .content kbd.file:active .file-cta,html.theme--catppuccin-mocha .file.is-dark.is-active .file-cta,html.theme--catppuccin-mocha .content kbd.file.is-active .file-cta{background-color:#262735;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .file.is-primary .file-cta,html.theme--catppuccin-mocha .docstring>section>a.file.docs-sourcelink .file-cta{background-color:#89b4fa;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .file.is-primary:hover .file-cta,html.theme--catppuccin-mocha .docstring>section>a.file.docs-sourcelink:hover .file-cta,html.theme--catppuccin-mocha .file.is-primary.is-hovered .file-cta,html.theme--catppuccin-mocha .docstring>section>a.file.is-hovered.docs-sourcelink .file-cta{background-color:#7dacf9;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .file.is-primary:focus .file-cta,html.theme--catppuccin-mocha .docstring>section>a.file.docs-sourcelink:focus .file-cta,html.theme--catppuccin-mocha .file.is-primary.is-focused .file-cta,html.theme--catppuccin-mocha .docstring>section>a.file.is-focused.docs-sourcelink .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(137,180,250,0.25);color:#fff}html.theme--catppuccin-mocha .file.is-primary:active .file-cta,html.theme--catppuccin-mocha .docstring>section>a.file.docs-sourcelink:active .file-cta,html.theme--catppuccin-mocha .file.is-primary.is-active .file-cta,html.theme--catppuccin-mocha .docstring>section>a.file.is-active.docs-sourcelink .file-cta{background-color:#71a4f9;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .file.is-link .file-cta{background-color:#89b4fa;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .file.is-link:hover .file-cta,html.theme--catppuccin-mocha .file.is-link.is-hovered .file-cta{background-color:#7dacf9;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .file.is-link:focus .file-cta,html.theme--catppuccin-mocha .file.is-link.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(137,180,250,0.25);color:#fff}html.theme--catppuccin-mocha .file.is-link:active .file-cta,html.theme--catppuccin-mocha .file.is-link.is-active .file-cta{background-color:#71a4f9;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .file.is-info .file-cta{background-color:#94e2d5;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .file.is-info:hover .file-cta,html.theme--catppuccin-mocha .file.is-info.is-hovered .file-cta{background-color:#8adfd1;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .file.is-info:focus .file-cta,html.theme--catppuccin-mocha .file.is-info.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(148,226,213,0.25);color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .file.is-info:active .file-cta,html.theme--catppuccin-mocha .file.is-info.is-active .file-cta{background-color:#80ddcd;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .file.is-success .file-cta{background-color:#a6e3a1;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .file.is-success:hover .file-cta,html.theme--catppuccin-mocha .file.is-success.is-hovered .file-cta{background-color:#9de097;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .file.is-success:focus .file-cta,html.theme--catppuccin-mocha .file.is-success.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(166,227,161,0.25);color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .file.is-success:active .file-cta,html.theme--catppuccin-mocha .file.is-success.is-active .file-cta{background-color:#93dd8d;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .file.is-warning .file-cta{background-color:#f9e2af;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .file.is-warning:hover .file-cta,html.theme--catppuccin-mocha .file.is-warning.is-hovered .file-cta{background-color:#f8dea3;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .file.is-warning:focus .file-cta,html.theme--catppuccin-mocha .file.is-warning.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(249,226,175,0.25);color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .file.is-warning:active .file-cta,html.theme--catppuccin-mocha .file.is-warning.is-active .file-cta{background-color:#f7d997;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .file.is-danger .file-cta{background-color:#f38ba8;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .file.is-danger:hover .file-cta,html.theme--catppuccin-mocha .file.is-danger.is-hovered .file-cta{background-color:#f27f9f;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .file.is-danger:focus .file-cta,html.theme--catppuccin-mocha .file.is-danger.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(243,139,168,0.25);color:#fff}html.theme--catppuccin-mocha .file.is-danger:active .file-cta,html.theme--catppuccin-mocha .file.is-danger.is-active .file-cta{background-color:#f17497;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .file.is-small,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.file{font-size:.75rem}html.theme--catppuccin-mocha .file.is-normal{font-size:1rem}html.theme--catppuccin-mocha .file.is-medium{font-size:1.25rem}html.theme--catppuccin-mocha .file.is-medium .file-icon .fa{font-size:21px}html.theme--catppuccin-mocha .file.is-large{font-size:1.5rem}html.theme--catppuccin-mocha .file.is-large .file-icon .fa{font-size:28px}html.theme--catppuccin-mocha .file.has-name .file-cta{border-bottom-right-radius:0;border-top-right-radius:0}html.theme--catppuccin-mocha .file.has-name .file-name{border-bottom-left-radius:0;border-top-left-radius:0}html.theme--catppuccin-mocha .file.has-name.is-empty .file-cta{border-radius:.4em}html.theme--catppuccin-mocha .file.has-name.is-empty .file-name{display:none}html.theme--catppuccin-mocha .file.is-boxed .file-label{flex-direction:column}html.theme--catppuccin-mocha .file.is-boxed .file-cta{flex-direction:column;height:auto;padding:1em 3em}html.theme--catppuccin-mocha .file.is-boxed .file-name{border-width:0 1px 1px}html.theme--catppuccin-mocha .file.is-boxed .file-icon{height:1.5em;width:1.5em}html.theme--catppuccin-mocha .file.is-boxed .file-icon .fa{font-size:21px}html.theme--catppuccin-mocha .file.is-boxed.is-small .file-icon .fa,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-boxed .file-icon .fa{font-size:14px}html.theme--catppuccin-mocha .file.is-boxed.is-medium .file-icon .fa{font-size:28px}html.theme--catppuccin-mocha .file.is-boxed.is-large .file-icon .fa{font-size:35px}html.theme--catppuccin-mocha .file.is-boxed.has-name .file-cta{border-radius:.4em .4em 0 0}html.theme--catppuccin-mocha .file.is-boxed.has-name .file-name{border-radius:0 0 .4em .4em;border-width:0 1px 1px}html.theme--catppuccin-mocha .file.is-centered{justify-content:center}html.theme--catppuccin-mocha .file.is-fullwidth .file-label{width:100%}html.theme--catppuccin-mocha .file.is-fullwidth .file-name{flex-grow:1;max-width:none}html.theme--catppuccin-mocha .file.is-right{justify-content:flex-end}html.theme--catppuccin-mocha .file.is-right .file-cta{border-radius:0 .4em .4em 0}html.theme--catppuccin-mocha .file.is-right .file-name{border-radius:.4em 0 0 .4em;border-width:1px 0 1px 1px;order:-1}html.theme--catppuccin-mocha .file-label{align-items:stretch;display:flex;cursor:pointer;justify-content:flex-start;overflow:hidden;position:relative}html.theme--catppuccin-mocha .file-label:hover .file-cta{background-color:#2c2d3d;color:#b8c5ef}html.theme--catppuccin-mocha .file-label:hover .file-name{border-color:#525569}html.theme--catppuccin-mocha .file-label:active .file-cta{background-color:#262735;color:#b8c5ef}html.theme--catppuccin-mocha .file-label:active .file-name{border-color:#4d4f62}html.theme--catppuccin-mocha .file-input{height:100%;left:0;opacity:0;outline:none;position:absolute;top:0;width:100%}html.theme--catppuccin-mocha .file-cta,html.theme--catppuccin-mocha .file-name{border-color:#585b70;border-radius:.4em;font-size:1em;padding-left:1em;padding-right:1em;white-space:nowrap}html.theme--catppuccin-mocha .file-cta{background-color:#313244;color:#cdd6f4}html.theme--catppuccin-mocha .file-name{border-color:#585b70;border-style:solid;border-width:1px 1px 1px 0;display:block;max-width:16em;overflow:hidden;text-align:inherit;text-overflow:ellipsis}html.theme--catppuccin-mocha .file-icon{align-items:center;display:flex;height:1em;justify-content:center;margin-right:.5em;width:1em}html.theme--catppuccin-mocha .file-icon .fa{font-size:14px}html.theme--catppuccin-mocha .label{color:#b8c5ef;display:block;font-size:1rem;font-weight:700}html.theme--catppuccin-mocha .label:not(:last-child){margin-bottom:0.5em}html.theme--catppuccin-mocha .label.is-small,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.label{font-size:.75rem}html.theme--catppuccin-mocha .label.is-medium{font-size:1.25rem}html.theme--catppuccin-mocha .label.is-large{font-size:1.5rem}html.theme--catppuccin-mocha .help{display:block;font-size:.75rem;margin-top:0.25rem}html.theme--catppuccin-mocha .help.is-white{color:#fff}html.theme--catppuccin-mocha .help.is-black{color:#0a0a0a}html.theme--catppuccin-mocha .help.is-light{color:#f5f5f5}html.theme--catppuccin-mocha .help.is-dark,html.theme--catppuccin-mocha .content kbd.help{color:#313244}html.theme--catppuccin-mocha .help.is-primary,html.theme--catppuccin-mocha .docstring>section>a.help.docs-sourcelink{color:#89b4fa}html.theme--catppuccin-mocha .help.is-link{color:#89b4fa}html.theme--catppuccin-mocha .help.is-info{color:#94e2d5}html.theme--catppuccin-mocha .help.is-success{color:#a6e3a1}html.theme--catppuccin-mocha .help.is-warning{color:#f9e2af}html.theme--catppuccin-mocha .help.is-danger{color:#f38ba8}html.theme--catppuccin-mocha .field:not(:last-child){margin-bottom:0.75rem}html.theme--catppuccin-mocha .field.has-addons{display:flex;justify-content:flex-start}html.theme--catppuccin-mocha .field.has-addons .control:not(:last-child){margin-right:-1px}html.theme--catppuccin-mocha .field.has-addons .control:not(:first-child):not(:last-child) .button,html.theme--catppuccin-mocha .field.has-addons .control:not(:first-child):not(:last-child) .input,html.theme--catppuccin-mocha .field.has-addons .control:not(:first-child):not(:last-child) #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-mocha #documenter .docs-sidebar .field.has-addons .control:not(:first-child):not(:last-child) form.docs-search>input,html.theme--catppuccin-mocha .field.has-addons .control:not(:first-child):not(:last-child) .select select{border-radius:0}html.theme--catppuccin-mocha .field.has-addons .control:first-child:not(:only-child) .button,html.theme--catppuccin-mocha .field.has-addons .control:first-child:not(:only-child) .input,html.theme--catppuccin-mocha .field.has-addons .control:first-child:not(:only-child) #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-mocha #documenter .docs-sidebar .field.has-addons .control:first-child:not(:only-child) form.docs-search>input,html.theme--catppuccin-mocha .field.has-addons .control:first-child:not(:only-child) .select select{border-bottom-right-radius:0;border-top-right-radius:0}html.theme--catppuccin-mocha .field.has-addons .control:last-child:not(:only-child) .button,html.theme--catppuccin-mocha .field.has-addons .control:last-child:not(:only-child) .input,html.theme--catppuccin-mocha .field.has-addons .control:last-child:not(:only-child) #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-mocha #documenter .docs-sidebar .field.has-addons .control:last-child:not(:only-child) form.docs-search>input,html.theme--catppuccin-mocha .field.has-addons .control:last-child:not(:only-child) .select select{border-bottom-left-radius:0;border-top-left-radius:0}html.theme--catppuccin-mocha .field.has-addons .control .button:not([disabled]):hover,html.theme--catppuccin-mocha .field.has-addons .control .button.is-hovered:not([disabled]),html.theme--catppuccin-mocha .field.has-addons .control .input:not([disabled]):hover,html.theme--catppuccin-mocha .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):hover,html.theme--catppuccin-mocha #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):hover,html.theme--catppuccin-mocha .field.has-addons .control .input.is-hovered:not([disabled]),html.theme--catppuccin-mocha .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-hovered:not([disabled]),html.theme--catppuccin-mocha #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-hovered:not([disabled]),html.theme--catppuccin-mocha .field.has-addons .control .select select:not([disabled]):hover,html.theme--catppuccin-mocha .field.has-addons .control .select select.is-hovered:not([disabled]){z-index:2}html.theme--catppuccin-mocha .field.has-addons .control .button:not([disabled]):focus,html.theme--catppuccin-mocha .field.has-addons .control .button.is-focused:not([disabled]),html.theme--catppuccin-mocha .field.has-addons .control .button:not([disabled]):active,html.theme--catppuccin-mocha .field.has-addons .control .button.is-active:not([disabled]),html.theme--catppuccin-mocha .field.has-addons .control .input:not([disabled]):focus,html.theme--catppuccin-mocha .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):focus,html.theme--catppuccin-mocha #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):focus,html.theme--catppuccin-mocha .field.has-addons .control .input.is-focused:not([disabled]),html.theme--catppuccin-mocha .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-focused:not([disabled]),html.theme--catppuccin-mocha #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-focused:not([disabled]),html.theme--catppuccin-mocha .field.has-addons .control .input:not([disabled]):active,html.theme--catppuccin-mocha .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):active,html.theme--catppuccin-mocha #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):active,html.theme--catppuccin-mocha .field.has-addons .control .input.is-active:not([disabled]),html.theme--catppuccin-mocha .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-active:not([disabled]),html.theme--catppuccin-mocha #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-active:not([disabled]),html.theme--catppuccin-mocha .field.has-addons .control .select select:not([disabled]):focus,html.theme--catppuccin-mocha .field.has-addons .control .select select.is-focused:not([disabled]),html.theme--catppuccin-mocha .field.has-addons .control .select select:not([disabled]):active,html.theme--catppuccin-mocha .field.has-addons .control .select select.is-active:not([disabled]){z-index:3}html.theme--catppuccin-mocha .field.has-addons .control .button:not([disabled]):focus:hover,html.theme--catppuccin-mocha .field.has-addons .control .button.is-focused:not([disabled]):hover,html.theme--catppuccin-mocha .field.has-addons .control .button:not([disabled]):active:hover,html.theme--catppuccin-mocha .field.has-addons .control .button.is-active:not([disabled]):hover,html.theme--catppuccin-mocha .field.has-addons .control .input:not([disabled]):focus:hover,html.theme--catppuccin-mocha .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):focus:hover,html.theme--catppuccin-mocha #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):focus:hover,html.theme--catppuccin-mocha .field.has-addons .control .input.is-focused:not([disabled]):hover,html.theme--catppuccin-mocha .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-focused:not([disabled]):hover,html.theme--catppuccin-mocha #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-focused:not([disabled]):hover,html.theme--catppuccin-mocha .field.has-addons .control .input:not([disabled]):active:hover,html.theme--catppuccin-mocha .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):active:hover,html.theme--catppuccin-mocha #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):active:hover,html.theme--catppuccin-mocha .field.has-addons .control .input.is-active:not([disabled]):hover,html.theme--catppuccin-mocha .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-active:not([disabled]):hover,html.theme--catppuccin-mocha #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-active:not([disabled]):hover,html.theme--catppuccin-mocha .field.has-addons .control .select select:not([disabled]):focus:hover,html.theme--catppuccin-mocha .field.has-addons .control .select select.is-focused:not([disabled]):hover,html.theme--catppuccin-mocha .field.has-addons .control .select select:not([disabled]):active:hover,html.theme--catppuccin-mocha .field.has-addons .control .select select.is-active:not([disabled]):hover{z-index:4}html.theme--catppuccin-mocha .field.has-addons .control.is-expanded{flex-grow:1;flex-shrink:1}html.theme--catppuccin-mocha .field.has-addons.has-addons-centered{justify-content:center}html.theme--catppuccin-mocha .field.has-addons.has-addons-right{justify-content:flex-end}html.theme--catppuccin-mocha .field.has-addons.has-addons-fullwidth .control{flex-grow:1;flex-shrink:0}html.theme--catppuccin-mocha .field.is-grouped{display:flex;justify-content:flex-start}html.theme--catppuccin-mocha .field.is-grouped>.control{flex-shrink:0}html.theme--catppuccin-mocha .field.is-grouped>.control:not(:last-child){margin-bottom:0;margin-right:.75rem}html.theme--catppuccin-mocha .field.is-grouped>.control.is-expanded{flex-grow:1;flex-shrink:1}html.theme--catppuccin-mocha .field.is-grouped.is-grouped-centered{justify-content:center}html.theme--catppuccin-mocha .field.is-grouped.is-grouped-right{justify-content:flex-end}html.theme--catppuccin-mocha .field.is-grouped.is-grouped-multiline{flex-wrap:wrap}html.theme--catppuccin-mocha .field.is-grouped.is-grouped-multiline>.control:last-child,html.theme--catppuccin-mocha .field.is-grouped.is-grouped-multiline>.control:not(:last-child){margin-bottom:0.75rem}html.theme--catppuccin-mocha .field.is-grouped.is-grouped-multiline:last-child{margin-bottom:-0.75rem}html.theme--catppuccin-mocha .field.is-grouped.is-grouped-multiline:not(:last-child){margin-bottom:0}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .field.is-horizontal{display:flex}}html.theme--catppuccin-mocha .field-label .label{font-size:inherit}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .field-label{margin-bottom:0.5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .field-label{flex-basis:0;flex-grow:1;flex-shrink:0;margin-right:1.5rem;text-align:right}html.theme--catppuccin-mocha .field-label.is-small,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.field-label{font-size:.75rem;padding-top:0.375em}html.theme--catppuccin-mocha .field-label.is-normal{padding-top:0.375em}html.theme--catppuccin-mocha .field-label.is-medium{font-size:1.25rem;padding-top:0.375em}html.theme--catppuccin-mocha .field-label.is-large{font-size:1.5rem;padding-top:0.375em}}html.theme--catppuccin-mocha .field-body .field .field{margin-bottom:0}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .field-body{display:flex;flex-basis:0;flex-grow:5;flex-shrink:1}html.theme--catppuccin-mocha .field-body .field{margin-bottom:0}html.theme--catppuccin-mocha .field-body>.field{flex-shrink:1}html.theme--catppuccin-mocha .field-body>.field:not(.is-narrow){flex-grow:1}html.theme--catppuccin-mocha .field-body>.field:not(:last-child){margin-right:.75rem}}html.theme--catppuccin-mocha .control{box-sizing:border-box;clear:both;font-size:1rem;position:relative;text-align:inherit}html.theme--catppuccin-mocha .control.has-icons-left .input:focus~.icon,html.theme--catppuccin-mocha .control.has-icons-left #documenter .docs-sidebar form.docs-search>input:focus~.icon,html.theme--catppuccin-mocha #documenter .docs-sidebar .control.has-icons-left form.docs-search>input:focus~.icon,html.theme--catppuccin-mocha .control.has-icons-left .select:focus~.icon,html.theme--catppuccin-mocha .control.has-icons-right .input:focus~.icon,html.theme--catppuccin-mocha .control.has-icons-right #documenter .docs-sidebar form.docs-search>input:focus~.icon,html.theme--catppuccin-mocha #documenter .docs-sidebar .control.has-icons-right form.docs-search>input:focus~.icon,html.theme--catppuccin-mocha .control.has-icons-right .select:focus~.icon{color:#313244}html.theme--catppuccin-mocha .control.has-icons-left .input.is-small~.icon,html.theme--catppuccin-mocha .control.has-icons-left #documenter .docs-sidebar form.docs-search>input~.icon,html.theme--catppuccin-mocha #documenter .docs-sidebar .control.has-icons-left form.docs-search>input~.icon,html.theme--catppuccin-mocha .control.has-icons-left .select.is-small~.icon,html.theme--catppuccin-mocha .control.has-icons-right .input.is-small~.icon,html.theme--catppuccin-mocha .control.has-icons-right #documenter .docs-sidebar form.docs-search>input~.icon,html.theme--catppuccin-mocha #documenter .docs-sidebar .control.has-icons-right form.docs-search>input~.icon,html.theme--catppuccin-mocha .control.has-icons-right .select.is-small~.icon{font-size:.75rem}html.theme--catppuccin-mocha .control.has-icons-left .input.is-medium~.icon,html.theme--catppuccin-mocha .control.has-icons-left #documenter .docs-sidebar form.docs-search>input.is-medium~.icon,html.theme--catppuccin-mocha #documenter .docs-sidebar .control.has-icons-left form.docs-search>input.is-medium~.icon,html.theme--catppuccin-mocha .control.has-icons-left .select.is-medium~.icon,html.theme--catppuccin-mocha .control.has-icons-right .input.is-medium~.icon,html.theme--catppuccin-mocha .control.has-icons-right #documenter .docs-sidebar form.docs-search>input.is-medium~.icon,html.theme--catppuccin-mocha #documenter .docs-sidebar .control.has-icons-right form.docs-search>input.is-medium~.icon,html.theme--catppuccin-mocha .control.has-icons-right .select.is-medium~.icon{font-size:1.25rem}html.theme--catppuccin-mocha .control.has-icons-left .input.is-large~.icon,html.theme--catppuccin-mocha .control.has-icons-left #documenter .docs-sidebar form.docs-search>input.is-large~.icon,html.theme--catppuccin-mocha #documenter .docs-sidebar .control.has-icons-left form.docs-search>input.is-large~.icon,html.theme--catppuccin-mocha .control.has-icons-left .select.is-large~.icon,html.theme--catppuccin-mocha .control.has-icons-right .input.is-large~.icon,html.theme--catppuccin-mocha .control.has-icons-right #documenter .docs-sidebar form.docs-search>input.is-large~.icon,html.theme--catppuccin-mocha #documenter .docs-sidebar .control.has-icons-right form.docs-search>input.is-large~.icon,html.theme--catppuccin-mocha .control.has-icons-right .select.is-large~.icon{font-size:1.5rem}html.theme--catppuccin-mocha .control.has-icons-left .icon,html.theme--catppuccin-mocha .control.has-icons-right .icon{color:#585b70;height:2.5em;pointer-events:none;position:absolute;top:0;width:2.5em;z-index:4}html.theme--catppuccin-mocha .control.has-icons-left .input,html.theme--catppuccin-mocha .control.has-icons-left #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-mocha #documenter .docs-sidebar .control.has-icons-left form.docs-search>input,html.theme--catppuccin-mocha .control.has-icons-left .select select{padding-left:2.5em}html.theme--catppuccin-mocha .control.has-icons-left .icon.is-left{left:0}html.theme--catppuccin-mocha .control.has-icons-right .input,html.theme--catppuccin-mocha .control.has-icons-right #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-mocha #documenter .docs-sidebar .control.has-icons-right form.docs-search>input,html.theme--catppuccin-mocha .control.has-icons-right .select select{padding-right:2.5em}html.theme--catppuccin-mocha .control.has-icons-right .icon.is-right{right:0}html.theme--catppuccin-mocha .control.is-loading::after{position:absolute !important;right:.625em;top:0.625em;z-index:4}html.theme--catppuccin-mocha .control.is-loading.is-small:after,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-loading:after{font-size:.75rem}html.theme--catppuccin-mocha .control.is-loading.is-medium:after{font-size:1.25rem}html.theme--catppuccin-mocha .control.is-loading.is-large:after{font-size:1.5rem}html.theme--catppuccin-mocha .breadcrumb{font-size:1rem;white-space:nowrap}html.theme--catppuccin-mocha .breadcrumb a{align-items:center;color:#89b4fa;display:flex;justify-content:center;padding:0 .75em}html.theme--catppuccin-mocha .breadcrumb a:hover{color:#89dceb}html.theme--catppuccin-mocha .breadcrumb li{align-items:center;display:flex}html.theme--catppuccin-mocha .breadcrumb li:first-child a{padding-left:0}html.theme--catppuccin-mocha .breadcrumb li.is-active a{color:#b8c5ef;cursor:default;pointer-events:none}html.theme--catppuccin-mocha .breadcrumb li+li::before{color:#6c7086;content:"\0002f"}html.theme--catppuccin-mocha .breadcrumb ul,html.theme--catppuccin-mocha .breadcrumb ol{align-items:flex-start;display:flex;flex-wrap:wrap;justify-content:flex-start}html.theme--catppuccin-mocha .breadcrumb .icon:first-child{margin-right:.5em}html.theme--catppuccin-mocha .breadcrumb .icon:last-child{margin-left:.5em}html.theme--catppuccin-mocha .breadcrumb.is-centered ol,html.theme--catppuccin-mocha .breadcrumb.is-centered ul{justify-content:center}html.theme--catppuccin-mocha .breadcrumb.is-right ol,html.theme--catppuccin-mocha .breadcrumb.is-right ul{justify-content:flex-end}html.theme--catppuccin-mocha .breadcrumb.is-small,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.breadcrumb{font-size:.75rem}html.theme--catppuccin-mocha .breadcrumb.is-medium{font-size:1.25rem}html.theme--catppuccin-mocha .breadcrumb.is-large{font-size:1.5rem}html.theme--catppuccin-mocha .breadcrumb.has-arrow-separator li+li::before{content:"\02192"}html.theme--catppuccin-mocha .breadcrumb.has-bullet-separator li+li::before{content:"\02022"}html.theme--catppuccin-mocha .breadcrumb.has-dot-separator li+li::before{content:"\000b7"}html.theme--catppuccin-mocha .breadcrumb.has-succeeds-separator li+li::before{content:"\0227B"}html.theme--catppuccin-mocha .card{background-color:#fff;border-radius:.25rem;box-shadow:#171717;color:#cdd6f4;max-width:100%;position:relative}html.theme--catppuccin-mocha .card-footer:first-child,html.theme--catppuccin-mocha .card-content:first-child,html.theme--catppuccin-mocha .card-header:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}html.theme--catppuccin-mocha .card-footer:last-child,html.theme--catppuccin-mocha .card-content:last-child,html.theme--catppuccin-mocha .card-header:last-child{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}html.theme--catppuccin-mocha .card-header{background-color:rgba(0,0,0,0);align-items:stretch;box-shadow:0 0.125em 0.25em rgba(10,10,10,0.1);display:flex}html.theme--catppuccin-mocha .card-header-title{align-items:center;color:#b8c5ef;display:flex;flex-grow:1;font-weight:700;padding:0.75rem 1rem}html.theme--catppuccin-mocha .card-header-title.is-centered{justify-content:center}html.theme--catppuccin-mocha .card-header-icon{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;color:currentColor;font-family:inherit;font-size:1em;margin:0;padding:0;align-items:center;cursor:pointer;display:flex;justify-content:center;padding:0.75rem 1rem}html.theme--catppuccin-mocha .card-image{display:block;position:relative}html.theme--catppuccin-mocha .card-image:first-child img{border-top-left-radius:.25rem;border-top-right-radius:.25rem}html.theme--catppuccin-mocha .card-image:last-child img{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}html.theme--catppuccin-mocha .card-content{background-color:rgba(0,0,0,0);padding:1.5rem}html.theme--catppuccin-mocha .card-footer{background-color:rgba(0,0,0,0);border-top:1px solid #ededed;align-items:stretch;display:flex}html.theme--catppuccin-mocha .card-footer-item{align-items:center;display:flex;flex-basis:0;flex-grow:1;flex-shrink:0;justify-content:center;padding:.75rem}html.theme--catppuccin-mocha .card-footer-item:not(:last-child){border-right:1px solid #ededed}html.theme--catppuccin-mocha .card .media:not(:last-child){margin-bottom:1.5rem}html.theme--catppuccin-mocha .dropdown{display:inline-flex;position:relative;vertical-align:top}html.theme--catppuccin-mocha .dropdown.is-active .dropdown-menu,html.theme--catppuccin-mocha .dropdown.is-hoverable:hover .dropdown-menu{display:block}html.theme--catppuccin-mocha .dropdown.is-right .dropdown-menu{left:auto;right:0}html.theme--catppuccin-mocha .dropdown.is-up .dropdown-menu{bottom:100%;padding-bottom:4px;padding-top:initial;top:auto}html.theme--catppuccin-mocha .dropdown-menu{display:none;left:0;min-width:12rem;padding-top:4px;position:absolute;top:100%;z-index:20}html.theme--catppuccin-mocha .dropdown-content{background-color:#181825;border-radius:.4em;box-shadow:#171717;padding-bottom:.5rem;padding-top:.5rem}html.theme--catppuccin-mocha .dropdown-item{color:#cdd6f4;display:block;font-size:0.875rem;line-height:1.5;padding:0.375rem 1rem;position:relative}html.theme--catppuccin-mocha a.dropdown-item,html.theme--catppuccin-mocha button.dropdown-item{padding-right:3rem;text-align:inherit;white-space:nowrap;width:100%}html.theme--catppuccin-mocha a.dropdown-item:hover,html.theme--catppuccin-mocha button.dropdown-item:hover{background-color:#181825;color:#0a0a0a}html.theme--catppuccin-mocha a.dropdown-item.is-active,html.theme--catppuccin-mocha button.dropdown-item.is-active{background-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .dropdown-divider{background-color:#ededed;border:none;display:block;height:1px;margin:0.5rem 0}html.theme--catppuccin-mocha .level{align-items:center;justify-content:space-between}html.theme--catppuccin-mocha .level code{border-radius:.4em}html.theme--catppuccin-mocha .level img{display:inline-block;vertical-align:top}html.theme--catppuccin-mocha .level.is-mobile{display:flex}html.theme--catppuccin-mocha .level.is-mobile .level-left,html.theme--catppuccin-mocha .level.is-mobile .level-right{display:flex}html.theme--catppuccin-mocha .level.is-mobile .level-left+.level-right{margin-top:0}html.theme--catppuccin-mocha .level.is-mobile .level-item:not(:last-child){margin-bottom:0;margin-right:.75rem}html.theme--catppuccin-mocha .level.is-mobile .level-item:not(.is-narrow){flex-grow:1}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .level{display:flex}html.theme--catppuccin-mocha .level>.level-item:not(.is-narrow){flex-grow:1}}html.theme--catppuccin-mocha .level-item{align-items:center;display:flex;flex-basis:auto;flex-grow:0;flex-shrink:0;justify-content:center}html.theme--catppuccin-mocha .level-item .title,html.theme--catppuccin-mocha .level-item .subtitle{margin-bottom:0}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .level-item:not(:last-child){margin-bottom:.75rem}}html.theme--catppuccin-mocha .level-left,html.theme--catppuccin-mocha .level-right{flex-basis:auto;flex-grow:0;flex-shrink:0}html.theme--catppuccin-mocha .level-left .level-item.is-flexible,html.theme--catppuccin-mocha .level-right .level-item.is-flexible{flex-grow:1}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .level-left .level-item:not(:last-child),html.theme--catppuccin-mocha .level-right .level-item:not(:last-child){margin-right:.75rem}}html.theme--catppuccin-mocha .level-left{align-items:center;justify-content:flex-start}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .level-left+.level-right{margin-top:1.5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .level-left{display:flex}}html.theme--catppuccin-mocha .level-right{align-items:center;justify-content:flex-end}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .level-right{display:flex}}html.theme--catppuccin-mocha .media{align-items:flex-start;display:flex;text-align:inherit}html.theme--catppuccin-mocha .media .content:not(:last-child){margin-bottom:.75rem}html.theme--catppuccin-mocha .media .media{border-top:1px solid rgba(88,91,112,0.5);display:flex;padding-top:.75rem}html.theme--catppuccin-mocha .media .media .content:not(:last-child),html.theme--catppuccin-mocha .media .media .control:not(:last-child){margin-bottom:.5rem}html.theme--catppuccin-mocha .media .media .media{padding-top:.5rem}html.theme--catppuccin-mocha .media .media .media+.media{margin-top:.5rem}html.theme--catppuccin-mocha .media+.media{border-top:1px solid rgba(88,91,112,0.5);margin-top:1rem;padding-top:1rem}html.theme--catppuccin-mocha .media.is-large+.media{margin-top:1.5rem;padding-top:1.5rem}html.theme--catppuccin-mocha .media-left,html.theme--catppuccin-mocha .media-right{flex-basis:auto;flex-grow:0;flex-shrink:0}html.theme--catppuccin-mocha .media-left{margin-right:1rem}html.theme--catppuccin-mocha .media-right{margin-left:1rem}html.theme--catppuccin-mocha .media-content{flex-basis:auto;flex-grow:1;flex-shrink:1;text-align:inherit}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .media-content{overflow-x:auto}}html.theme--catppuccin-mocha .menu{font-size:1rem}html.theme--catppuccin-mocha .menu.is-small,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.menu{font-size:.75rem}html.theme--catppuccin-mocha .menu.is-medium{font-size:1.25rem}html.theme--catppuccin-mocha .menu.is-large{font-size:1.5rem}html.theme--catppuccin-mocha .menu-list{line-height:1.25}html.theme--catppuccin-mocha .menu-list a{border-radius:3px;color:#cdd6f4;display:block;padding:0.5em 0.75em}html.theme--catppuccin-mocha .menu-list a:hover{background-color:#181825;color:#b8c5ef}html.theme--catppuccin-mocha .menu-list a.is-active{background-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .menu-list li ul{border-left:1px solid #585b70;margin:.75em;padding-left:.75em}html.theme--catppuccin-mocha .menu-label{color:#f7f8fd;font-size:.75em;letter-spacing:.1em;text-transform:uppercase}html.theme--catppuccin-mocha .menu-label:not(:first-child){margin-top:1em}html.theme--catppuccin-mocha .menu-label:not(:last-child){margin-bottom:1em}html.theme--catppuccin-mocha .message{background-color:#181825;border-radius:.4em;font-size:1rem}html.theme--catppuccin-mocha .message strong{color:currentColor}html.theme--catppuccin-mocha .message a:not(.button):not(.tag):not(.dropdown-item){color:currentColor;text-decoration:underline}html.theme--catppuccin-mocha .message.is-small,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.message{font-size:.75rem}html.theme--catppuccin-mocha .message.is-medium{font-size:1.25rem}html.theme--catppuccin-mocha .message.is-large{font-size:1.5rem}html.theme--catppuccin-mocha .message.is-white{background-color:#fff}html.theme--catppuccin-mocha .message.is-white .message-header{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-mocha .message.is-white .message-body{border-color:#fff}html.theme--catppuccin-mocha .message.is-black{background-color:#fafafa}html.theme--catppuccin-mocha .message.is-black .message-header{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-mocha .message.is-black .message-body{border-color:#0a0a0a}html.theme--catppuccin-mocha .message.is-light{background-color:#fafafa}html.theme--catppuccin-mocha .message.is-light .message-header{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .message.is-light .message-body{border-color:#f5f5f5}html.theme--catppuccin-mocha .message.is-dark,html.theme--catppuccin-mocha .content kbd.message{background-color:#f9f9fb}html.theme--catppuccin-mocha .message.is-dark .message-header,html.theme--catppuccin-mocha .content kbd.message .message-header{background-color:#313244;color:#fff}html.theme--catppuccin-mocha .message.is-dark .message-body,html.theme--catppuccin-mocha .content kbd.message .message-body{border-color:#313244}html.theme--catppuccin-mocha .message.is-primary,html.theme--catppuccin-mocha .docstring>section>a.message.docs-sourcelink{background-color:#ebf3fe}html.theme--catppuccin-mocha .message.is-primary .message-header,html.theme--catppuccin-mocha .docstring>section>a.message.docs-sourcelink .message-header{background-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .message.is-primary .message-body,html.theme--catppuccin-mocha .docstring>section>a.message.docs-sourcelink .message-body{border-color:#89b4fa;color:#063c93}html.theme--catppuccin-mocha .message.is-link{background-color:#ebf3fe}html.theme--catppuccin-mocha .message.is-link .message-header{background-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .message.is-link .message-body{border-color:#89b4fa;color:#063c93}html.theme--catppuccin-mocha .message.is-info{background-color:#effbf9}html.theme--catppuccin-mocha .message.is-info .message-header{background-color:#94e2d5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .message.is-info .message-body{border-color:#94e2d5;color:#207466}html.theme--catppuccin-mocha .message.is-success{background-color:#f0faef}html.theme--catppuccin-mocha .message.is-success .message-header{background-color:#a6e3a1;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .message.is-success .message-body{border-color:#a6e3a1;color:#287222}html.theme--catppuccin-mocha .message.is-warning{background-color:#fef8ec}html.theme--catppuccin-mocha .message.is-warning .message-header{background-color:#f9e2af;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .message.is-warning .message-body{border-color:#f9e2af;color:#8a620a}html.theme--catppuccin-mocha .message.is-danger{background-color:#fdedf1}html.theme--catppuccin-mocha .message.is-danger .message-header{background-color:#f38ba8;color:#fff}html.theme--catppuccin-mocha .message.is-danger .message-body{border-color:#f38ba8;color:#991036}html.theme--catppuccin-mocha .message-header{align-items:center;background-color:#cdd6f4;border-radius:.4em .4em 0 0;color:rgba(0,0,0,0.7);display:flex;font-weight:700;justify-content:space-between;line-height:1.25;padding:0.75em 1em;position:relative}html.theme--catppuccin-mocha .message-header .delete{flex-grow:0;flex-shrink:0;margin-left:.75em}html.theme--catppuccin-mocha .message-header+.message-body{border-width:0;border-top-left-radius:0;border-top-right-radius:0}html.theme--catppuccin-mocha .message-body{border-color:#585b70;border-radius:.4em;border-style:solid;border-width:0 0 0 4px;color:#cdd6f4;padding:1.25em 1.5em}html.theme--catppuccin-mocha .message-body code,html.theme--catppuccin-mocha .message-body pre{background-color:#fff}html.theme--catppuccin-mocha .message-body pre code{background-color:rgba(0,0,0,0)}html.theme--catppuccin-mocha .modal{align-items:center;display:none;flex-direction:column;justify-content:center;overflow:hidden;position:fixed;z-index:40}html.theme--catppuccin-mocha .modal.is-active{display:flex}html.theme--catppuccin-mocha .modal-background{background-color:rgba(10,10,10,0.86)}html.theme--catppuccin-mocha .modal-content,html.theme--catppuccin-mocha .modal-card{margin:0 20px;max-height:calc(100vh - 160px);overflow:auto;position:relative;width:100%}@media screen and (min-width: 769px){html.theme--catppuccin-mocha .modal-content,html.theme--catppuccin-mocha .modal-card{margin:0 auto;max-height:calc(100vh - 40px);width:640px}}html.theme--catppuccin-mocha .modal-close{background:none;height:40px;position:fixed;right:20px;top:20px;width:40px}html.theme--catppuccin-mocha .modal-card{display:flex;flex-direction:column;max-height:calc(100vh - 40px);overflow:hidden;-ms-overflow-y:visible}html.theme--catppuccin-mocha .modal-card-head,html.theme--catppuccin-mocha .modal-card-foot{align-items:center;background-color:#181825;display:flex;flex-shrink:0;justify-content:flex-start;padding:20px;position:relative}html.theme--catppuccin-mocha .modal-card-head{border-bottom:1px solid #585b70;border-top-left-radius:8px;border-top-right-radius:8px}html.theme--catppuccin-mocha .modal-card-title{color:#cdd6f4;flex-grow:1;flex-shrink:0;font-size:1.5rem;line-height:1}html.theme--catppuccin-mocha .modal-card-foot{border-bottom-left-radius:8px;border-bottom-right-radius:8px;border-top:1px solid #585b70}html.theme--catppuccin-mocha .modal-card-foot .button:not(:last-child){margin-right:.5em}html.theme--catppuccin-mocha .modal-card-body{-webkit-overflow-scrolling:touch;background-color:#1e1e2e;flex-grow:1;flex-shrink:1;overflow:auto;padding:20px}html.theme--catppuccin-mocha .navbar{background-color:#89b4fa;min-height:4rem;position:relative;z-index:30}html.theme--catppuccin-mocha .navbar.is-white{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-mocha .navbar.is-white .navbar-brand>.navbar-item,html.theme--catppuccin-mocha .navbar.is-white .navbar-brand .navbar-link{color:#0a0a0a}html.theme--catppuccin-mocha .navbar.is-white .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-white .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-white .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-white .navbar-brand .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-white .navbar-brand .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-white .navbar-brand .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}html.theme--catppuccin-mocha .navbar.is-white .navbar-brand .navbar-link::after{border-color:#0a0a0a}html.theme--catppuccin-mocha .navbar.is-white .navbar-burger{color:#0a0a0a}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .navbar.is-white .navbar-start>.navbar-item,html.theme--catppuccin-mocha .navbar.is-white .navbar-start .navbar-link,html.theme--catppuccin-mocha .navbar.is-white .navbar-end>.navbar-item,html.theme--catppuccin-mocha .navbar.is-white .navbar-end .navbar-link{color:#0a0a0a}html.theme--catppuccin-mocha .navbar.is-white .navbar-start>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-white .navbar-start>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-white .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-white .navbar-start .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-white .navbar-start .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-white .navbar-start .navbar-link.is-active,html.theme--catppuccin-mocha .navbar.is-white .navbar-end>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-white .navbar-end>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-white .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-white .navbar-end .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-white .navbar-end .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-white .navbar-end .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}html.theme--catppuccin-mocha .navbar.is-white .navbar-start .navbar-link::after,html.theme--catppuccin-mocha .navbar.is-white .navbar-end .navbar-link::after{border-color:#0a0a0a}html.theme--catppuccin-mocha .navbar.is-white .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-mocha .navbar.is-white .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-mocha .navbar.is-white .navbar-item.has-dropdown.is-active .navbar-link{background-color:#f2f2f2;color:#0a0a0a}html.theme--catppuccin-mocha .navbar.is-white .navbar-dropdown a.navbar-item.is-active{background-color:#fff;color:#0a0a0a}}html.theme--catppuccin-mocha .navbar.is-black{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-mocha .navbar.is-black .navbar-brand>.navbar-item,html.theme--catppuccin-mocha .navbar.is-black .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-mocha .navbar.is-black .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-black .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-black .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-black .navbar-brand .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-black .navbar-brand .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-black .navbar-brand .navbar-link.is-active{background-color:#000;color:#fff}html.theme--catppuccin-mocha .navbar.is-black .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-mocha .navbar.is-black .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .navbar.is-black .navbar-start>.navbar-item,html.theme--catppuccin-mocha .navbar.is-black .navbar-start .navbar-link,html.theme--catppuccin-mocha .navbar.is-black .navbar-end>.navbar-item,html.theme--catppuccin-mocha .navbar.is-black .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-mocha .navbar.is-black .navbar-start>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-black .navbar-start>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-black .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-black .navbar-start .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-black .navbar-start .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-black .navbar-start .navbar-link.is-active,html.theme--catppuccin-mocha .navbar.is-black .navbar-end>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-black .navbar-end>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-black .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-black .navbar-end .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-black .navbar-end .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-black .navbar-end .navbar-link.is-active{background-color:#000;color:#fff}html.theme--catppuccin-mocha .navbar.is-black .navbar-start .navbar-link::after,html.theme--catppuccin-mocha .navbar.is-black .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-mocha .navbar.is-black .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-mocha .navbar.is-black .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-mocha .navbar.is-black .navbar-item.has-dropdown.is-active .navbar-link{background-color:#000;color:#fff}html.theme--catppuccin-mocha .navbar.is-black .navbar-dropdown a.navbar-item.is-active{background-color:#0a0a0a;color:#fff}}html.theme--catppuccin-mocha .navbar.is-light{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-light .navbar-brand>.navbar-item,html.theme--catppuccin-mocha .navbar.is-light .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-light .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-light .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-light .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-light .navbar-brand .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-light .navbar-brand .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-light .navbar-brand .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-light .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-light .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .navbar.is-light .navbar-start>.navbar-item,html.theme--catppuccin-mocha .navbar.is-light .navbar-start .navbar-link,html.theme--catppuccin-mocha .navbar.is-light .navbar-end>.navbar-item,html.theme--catppuccin-mocha .navbar.is-light .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-light .navbar-start>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-light .navbar-start>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-light .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-light .navbar-start .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-light .navbar-start .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-light .navbar-start .navbar-link.is-active,html.theme--catppuccin-mocha .navbar.is-light .navbar-end>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-light .navbar-end>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-light .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-light .navbar-end .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-light .navbar-end .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-light .navbar-end .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-light .navbar-start .navbar-link::after,html.theme--catppuccin-mocha .navbar.is-light .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-light .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-mocha .navbar.is-light .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-mocha .navbar.is-light .navbar-item.has-dropdown.is-active .navbar-link{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-light .navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}}html.theme--catppuccin-mocha .navbar.is-dark,html.theme--catppuccin-mocha .content kbd.navbar{background-color:#313244;color:#fff}html.theme--catppuccin-mocha .navbar.is-dark .navbar-brand>.navbar-item,html.theme--catppuccin-mocha .content kbd.navbar .navbar-brand>.navbar-item,html.theme--catppuccin-mocha .navbar.is-dark .navbar-brand .navbar-link,html.theme--catppuccin-mocha .content kbd.navbar .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-mocha .navbar.is-dark .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-mocha .content kbd.navbar .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-dark .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-mocha .content kbd.navbar .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-dark .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-mocha .content kbd.navbar .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-dark .navbar-brand .navbar-link:focus,html.theme--catppuccin-mocha .content kbd.navbar .navbar-brand .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-dark .navbar-brand .navbar-link:hover,html.theme--catppuccin-mocha .content kbd.navbar .navbar-brand .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-dark .navbar-brand .navbar-link.is-active,html.theme--catppuccin-mocha .content kbd.navbar .navbar-brand .navbar-link.is-active{background-color:#262735;color:#fff}html.theme--catppuccin-mocha .navbar.is-dark .navbar-brand .navbar-link::after,html.theme--catppuccin-mocha .content kbd.navbar .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-mocha .navbar.is-dark .navbar-burger,html.theme--catppuccin-mocha .content kbd.navbar .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .navbar.is-dark .navbar-start>.navbar-item,html.theme--catppuccin-mocha .content kbd.navbar .navbar-start>.navbar-item,html.theme--catppuccin-mocha .navbar.is-dark .navbar-start .navbar-link,html.theme--catppuccin-mocha .content kbd.navbar .navbar-start .navbar-link,html.theme--catppuccin-mocha .navbar.is-dark .navbar-end>.navbar-item,html.theme--catppuccin-mocha .content kbd.navbar .navbar-end>.navbar-item,html.theme--catppuccin-mocha .navbar.is-dark .navbar-end .navbar-link,html.theme--catppuccin-mocha .content kbd.navbar .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-mocha .navbar.is-dark .navbar-start>a.navbar-item:focus,html.theme--catppuccin-mocha .content kbd.navbar .navbar-start>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-dark .navbar-start>a.navbar-item:hover,html.theme--catppuccin-mocha .content kbd.navbar .navbar-start>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-dark .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-mocha .content kbd.navbar .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-dark .navbar-start .navbar-link:focus,html.theme--catppuccin-mocha .content kbd.navbar .navbar-start .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-dark .navbar-start .navbar-link:hover,html.theme--catppuccin-mocha .content kbd.navbar .navbar-start .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-dark .navbar-start .navbar-link.is-active,html.theme--catppuccin-mocha .content kbd.navbar .navbar-start .navbar-link.is-active,html.theme--catppuccin-mocha .navbar.is-dark .navbar-end>a.navbar-item:focus,html.theme--catppuccin-mocha .content kbd.navbar .navbar-end>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-dark .navbar-end>a.navbar-item:hover,html.theme--catppuccin-mocha .content kbd.navbar .navbar-end>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-dark .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-mocha .content kbd.navbar .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-dark .navbar-end .navbar-link:focus,html.theme--catppuccin-mocha .content kbd.navbar .navbar-end .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-dark .navbar-end .navbar-link:hover,html.theme--catppuccin-mocha .content kbd.navbar .navbar-end .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-dark .navbar-end .navbar-link.is-active,html.theme--catppuccin-mocha .content kbd.navbar .navbar-end .navbar-link.is-active{background-color:#262735;color:#fff}html.theme--catppuccin-mocha .navbar.is-dark .navbar-start .navbar-link::after,html.theme--catppuccin-mocha .content kbd.navbar .navbar-start .navbar-link::after,html.theme--catppuccin-mocha .navbar.is-dark .navbar-end .navbar-link::after,html.theme--catppuccin-mocha .content kbd.navbar .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-mocha .navbar.is-dark .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-mocha .content kbd.navbar .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-mocha .navbar.is-dark .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-mocha .content kbd.navbar .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-mocha .navbar.is-dark .navbar-item.has-dropdown.is-active .navbar-link,html.theme--catppuccin-mocha .content kbd.navbar .navbar-item.has-dropdown.is-active .navbar-link{background-color:#262735;color:#fff}html.theme--catppuccin-mocha .navbar.is-dark .navbar-dropdown a.navbar-item.is-active,html.theme--catppuccin-mocha .content kbd.navbar .navbar-dropdown a.navbar-item.is-active{background-color:#313244;color:#fff}}html.theme--catppuccin-mocha .navbar.is-primary,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink{background-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .navbar.is-primary .navbar-brand>.navbar-item,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-brand>.navbar-item,html.theme--catppuccin-mocha .navbar.is-primary .navbar-brand .navbar-link,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-mocha .navbar.is-primary .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-primary .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-primary .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-primary .navbar-brand .navbar-link:focus,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-primary .navbar-brand .navbar-link:hover,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-primary .navbar-brand .navbar-link.is-active,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link.is-active{background-color:#71a4f9;color:#fff}html.theme--catppuccin-mocha .navbar.is-primary .navbar-brand .navbar-link::after,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-mocha .navbar.is-primary .navbar-burger,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .navbar.is-primary .navbar-start>.navbar-item,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-start>.navbar-item,html.theme--catppuccin-mocha .navbar.is-primary .navbar-start .navbar-link,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link,html.theme--catppuccin-mocha .navbar.is-primary .navbar-end>.navbar-item,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-end>.navbar-item,html.theme--catppuccin-mocha .navbar.is-primary .navbar-end .navbar-link,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-mocha .navbar.is-primary .navbar-start>a.navbar-item:focus,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-primary .navbar-start>a.navbar-item:hover,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-primary .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-primary .navbar-start .navbar-link:focus,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-primary .navbar-start .navbar-link:hover,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-primary .navbar-start .navbar-link.is-active,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link.is-active,html.theme--catppuccin-mocha .navbar.is-primary .navbar-end>a.navbar-item:focus,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-primary .navbar-end>a.navbar-item:hover,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-primary .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-primary .navbar-end .navbar-link:focus,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-primary .navbar-end .navbar-link:hover,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-primary .navbar-end .navbar-link.is-active,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link.is-active{background-color:#71a4f9;color:#fff}html.theme--catppuccin-mocha .navbar.is-primary .navbar-start .navbar-link::after,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link::after,html.theme--catppuccin-mocha .navbar.is-primary .navbar-end .navbar-link::after,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-mocha .navbar.is-primary .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-mocha .navbar.is-primary .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-mocha .navbar.is-primary .navbar-item.has-dropdown.is-active .navbar-link,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown.is-active .navbar-link{background-color:#71a4f9;color:#fff}html.theme--catppuccin-mocha .navbar.is-primary .navbar-dropdown a.navbar-item.is-active,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-dropdown a.navbar-item.is-active{background-color:#89b4fa;color:#fff}}html.theme--catppuccin-mocha .navbar.is-link{background-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .navbar.is-link .navbar-brand>.navbar-item,html.theme--catppuccin-mocha .navbar.is-link .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-mocha .navbar.is-link .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-link .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-link .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-link .navbar-brand .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-link .navbar-brand .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-link .navbar-brand .navbar-link.is-active{background-color:#71a4f9;color:#fff}html.theme--catppuccin-mocha .navbar.is-link .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-mocha .navbar.is-link .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .navbar.is-link .navbar-start>.navbar-item,html.theme--catppuccin-mocha .navbar.is-link .navbar-start .navbar-link,html.theme--catppuccin-mocha .navbar.is-link .navbar-end>.navbar-item,html.theme--catppuccin-mocha .navbar.is-link .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-mocha .navbar.is-link .navbar-start>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-link .navbar-start>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-link .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-link .navbar-start .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-link .navbar-start .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-link .navbar-start .navbar-link.is-active,html.theme--catppuccin-mocha .navbar.is-link .navbar-end>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-link .navbar-end>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-link .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-link .navbar-end .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-link .navbar-end .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-link .navbar-end .navbar-link.is-active{background-color:#71a4f9;color:#fff}html.theme--catppuccin-mocha .navbar.is-link .navbar-start .navbar-link::after,html.theme--catppuccin-mocha .navbar.is-link .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-mocha .navbar.is-link .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-mocha .navbar.is-link .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-mocha .navbar.is-link .navbar-item.has-dropdown.is-active .navbar-link{background-color:#71a4f9;color:#fff}html.theme--catppuccin-mocha .navbar.is-link .navbar-dropdown a.navbar-item.is-active{background-color:#89b4fa;color:#fff}}html.theme--catppuccin-mocha .navbar.is-info{background-color:#94e2d5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-info .navbar-brand>.navbar-item,html.theme--catppuccin-mocha .navbar.is-info .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-info .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-info .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-info .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-info .navbar-brand .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-info .navbar-brand .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-info .navbar-brand .navbar-link.is-active{background-color:#80ddcd;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-info .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-info .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .navbar.is-info .navbar-start>.navbar-item,html.theme--catppuccin-mocha .navbar.is-info .navbar-start .navbar-link,html.theme--catppuccin-mocha .navbar.is-info .navbar-end>.navbar-item,html.theme--catppuccin-mocha .navbar.is-info .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-info .navbar-start>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-info .navbar-start>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-info .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-info .navbar-start .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-info .navbar-start .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-info .navbar-start .navbar-link.is-active,html.theme--catppuccin-mocha .navbar.is-info .navbar-end>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-info .navbar-end>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-info .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-info .navbar-end .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-info .navbar-end .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-info .navbar-end .navbar-link.is-active{background-color:#80ddcd;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-info .navbar-start .navbar-link::after,html.theme--catppuccin-mocha .navbar.is-info .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-info .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-mocha .navbar.is-info .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-mocha .navbar.is-info .navbar-item.has-dropdown.is-active .navbar-link{background-color:#80ddcd;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-info .navbar-dropdown a.navbar-item.is-active{background-color:#94e2d5;color:rgba(0,0,0,0.7)}}html.theme--catppuccin-mocha .navbar.is-success{background-color:#a6e3a1;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-success .navbar-brand>.navbar-item,html.theme--catppuccin-mocha .navbar.is-success .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-success .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-success .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-success .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-success .navbar-brand .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-success .navbar-brand .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-success .navbar-brand .navbar-link.is-active{background-color:#93dd8d;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-success .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-success .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .navbar.is-success .navbar-start>.navbar-item,html.theme--catppuccin-mocha .navbar.is-success .navbar-start .navbar-link,html.theme--catppuccin-mocha .navbar.is-success .navbar-end>.navbar-item,html.theme--catppuccin-mocha .navbar.is-success .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-success .navbar-start>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-success .navbar-start>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-success .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-success .navbar-start .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-success .navbar-start .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-success .navbar-start .navbar-link.is-active,html.theme--catppuccin-mocha .navbar.is-success .navbar-end>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-success .navbar-end>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-success .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-success .navbar-end .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-success .navbar-end .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-success .navbar-end .navbar-link.is-active{background-color:#93dd8d;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-success .navbar-start .navbar-link::after,html.theme--catppuccin-mocha .navbar.is-success .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-success .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-mocha .navbar.is-success .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-mocha .navbar.is-success .navbar-item.has-dropdown.is-active .navbar-link{background-color:#93dd8d;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-success .navbar-dropdown a.navbar-item.is-active{background-color:#a6e3a1;color:rgba(0,0,0,0.7)}}html.theme--catppuccin-mocha .navbar.is-warning{background-color:#f9e2af;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-warning .navbar-brand>.navbar-item,html.theme--catppuccin-mocha .navbar.is-warning .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-warning .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-warning .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-warning .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-warning .navbar-brand .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-warning .navbar-brand .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-warning .navbar-brand .navbar-link.is-active{background-color:#f7d997;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-warning .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-warning .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .navbar.is-warning .navbar-start>.navbar-item,html.theme--catppuccin-mocha .navbar.is-warning .navbar-start .navbar-link,html.theme--catppuccin-mocha .navbar.is-warning .navbar-end>.navbar-item,html.theme--catppuccin-mocha .navbar.is-warning .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-warning .navbar-start>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-warning .navbar-start>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-warning .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-warning .navbar-start .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-warning .navbar-start .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-warning .navbar-start .navbar-link.is-active,html.theme--catppuccin-mocha .navbar.is-warning .navbar-end>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-warning .navbar-end>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-warning .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-warning .navbar-end .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-warning .navbar-end .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-warning .navbar-end .navbar-link.is-active{background-color:#f7d997;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-warning .navbar-start .navbar-link::after,html.theme--catppuccin-mocha .navbar.is-warning .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-warning .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-mocha .navbar.is-warning .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-mocha .navbar.is-warning .navbar-item.has-dropdown.is-active .navbar-link{background-color:#f7d997;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-warning .navbar-dropdown a.navbar-item.is-active{background-color:#f9e2af;color:rgba(0,0,0,0.7)}}html.theme--catppuccin-mocha .navbar.is-danger{background-color:#f38ba8;color:#fff}html.theme--catppuccin-mocha .navbar.is-danger .navbar-brand>.navbar-item,html.theme--catppuccin-mocha .navbar.is-danger .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-mocha .navbar.is-danger .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-danger .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-danger .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-danger .navbar-brand .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-danger .navbar-brand .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-danger .navbar-brand .navbar-link.is-active{background-color:#f17497;color:#fff}html.theme--catppuccin-mocha .navbar.is-danger .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-mocha .navbar.is-danger .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .navbar.is-danger .navbar-start>.navbar-item,html.theme--catppuccin-mocha .navbar.is-danger .navbar-start .navbar-link,html.theme--catppuccin-mocha .navbar.is-danger .navbar-end>.navbar-item,html.theme--catppuccin-mocha .navbar.is-danger .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-mocha .navbar.is-danger .navbar-start>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-danger .navbar-start>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-danger .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-danger .navbar-start .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-danger .navbar-start .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-danger .navbar-start .navbar-link.is-active,html.theme--catppuccin-mocha .navbar.is-danger .navbar-end>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-danger .navbar-end>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-danger .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-danger .navbar-end .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-danger .navbar-end .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-danger .navbar-end .navbar-link.is-active{background-color:#f17497;color:#fff}html.theme--catppuccin-mocha .navbar.is-danger .navbar-start .navbar-link::after,html.theme--catppuccin-mocha .navbar.is-danger .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-mocha .navbar.is-danger .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-mocha .navbar.is-danger .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-mocha .navbar.is-danger .navbar-item.has-dropdown.is-active .navbar-link{background-color:#f17497;color:#fff}html.theme--catppuccin-mocha .navbar.is-danger .navbar-dropdown a.navbar-item.is-active{background-color:#f38ba8;color:#fff}}html.theme--catppuccin-mocha .navbar>.container{align-items:stretch;display:flex;min-height:4rem;width:100%}html.theme--catppuccin-mocha .navbar.has-shadow{box-shadow:0 2px 0 0 #181825}html.theme--catppuccin-mocha .navbar.is-fixed-bottom,html.theme--catppuccin-mocha .navbar.is-fixed-top{left:0;position:fixed;right:0;z-index:30}html.theme--catppuccin-mocha .navbar.is-fixed-bottom{bottom:0}html.theme--catppuccin-mocha .navbar.is-fixed-bottom.has-shadow{box-shadow:0 -2px 0 0 #181825}html.theme--catppuccin-mocha .navbar.is-fixed-top{top:0}html.theme--catppuccin-mocha html.has-navbar-fixed-top,html.theme--catppuccin-mocha body.has-navbar-fixed-top{padding-top:4rem}html.theme--catppuccin-mocha html.has-navbar-fixed-bottom,html.theme--catppuccin-mocha body.has-navbar-fixed-bottom{padding-bottom:4rem}html.theme--catppuccin-mocha .navbar-brand,html.theme--catppuccin-mocha .navbar-tabs{align-items:stretch;display:flex;flex-shrink:0;min-height:4rem}html.theme--catppuccin-mocha .navbar-brand a.navbar-item:focus,html.theme--catppuccin-mocha .navbar-brand a.navbar-item:hover{background-color:transparent}html.theme--catppuccin-mocha .navbar-tabs{-webkit-overflow-scrolling:touch;max-width:100vw;overflow-x:auto;overflow-y:hidden}html.theme--catppuccin-mocha .navbar-burger{color:#cdd6f4;-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;cursor:pointer;display:block;height:4rem;position:relative;width:4rem;margin-left:auto}html.theme--catppuccin-mocha .navbar-burger span{background-color:currentColor;display:block;height:1px;left:calc(50% - 8px);position:absolute;transform-origin:center;transition-duration:86ms;transition-property:background-color, opacity, transform;transition-timing-function:ease-out;width:16px}html.theme--catppuccin-mocha .navbar-burger span:nth-child(1){top:calc(50% - 6px)}html.theme--catppuccin-mocha .navbar-burger span:nth-child(2){top:calc(50% - 1px)}html.theme--catppuccin-mocha .navbar-burger span:nth-child(3){top:calc(50% + 4px)}html.theme--catppuccin-mocha .navbar-burger:hover{background-color:rgba(0,0,0,0.05)}html.theme--catppuccin-mocha .navbar-burger.is-active span:nth-child(1){transform:translateY(5px) rotate(45deg)}html.theme--catppuccin-mocha .navbar-burger.is-active span:nth-child(2){opacity:0}html.theme--catppuccin-mocha .navbar-burger.is-active span:nth-child(3){transform:translateY(-5px) rotate(-45deg)}html.theme--catppuccin-mocha .navbar-menu{display:none}html.theme--catppuccin-mocha .navbar-item,html.theme--catppuccin-mocha .navbar-link{color:#cdd6f4;display:block;line-height:1.5;padding:0.5rem 0.75rem;position:relative}html.theme--catppuccin-mocha .navbar-item .icon:only-child,html.theme--catppuccin-mocha .navbar-link .icon:only-child{margin-left:-0.25rem;margin-right:-0.25rem}html.theme--catppuccin-mocha a.navbar-item,html.theme--catppuccin-mocha .navbar-link{cursor:pointer}html.theme--catppuccin-mocha a.navbar-item:focus,html.theme--catppuccin-mocha a.navbar-item:focus-within,html.theme--catppuccin-mocha a.navbar-item:hover,html.theme--catppuccin-mocha a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar-link:focus,html.theme--catppuccin-mocha .navbar-link:focus-within,html.theme--catppuccin-mocha .navbar-link:hover,html.theme--catppuccin-mocha .navbar-link.is-active{background-color:rgba(0,0,0,0);color:#89b4fa}html.theme--catppuccin-mocha .navbar-item{flex-grow:0;flex-shrink:0}html.theme--catppuccin-mocha .navbar-item img{max-height:1.75rem}html.theme--catppuccin-mocha .navbar-item.has-dropdown{padding:0}html.theme--catppuccin-mocha .navbar-item.is-expanded{flex-grow:1;flex-shrink:1}html.theme--catppuccin-mocha .navbar-item.is-tab{border-bottom:1px solid transparent;min-height:4rem;padding-bottom:calc(0.5rem - 1px)}html.theme--catppuccin-mocha .navbar-item.is-tab:focus,html.theme--catppuccin-mocha .navbar-item.is-tab:hover{background-color:rgba(0,0,0,0);border-bottom-color:#89b4fa}html.theme--catppuccin-mocha .navbar-item.is-tab.is-active{background-color:rgba(0,0,0,0);border-bottom-color:#89b4fa;border-bottom-style:solid;border-bottom-width:3px;color:#89b4fa;padding-bottom:calc(0.5rem - 3px)}html.theme--catppuccin-mocha .navbar-content{flex-grow:1;flex-shrink:1}html.theme--catppuccin-mocha .navbar-link:not(.is-arrowless){padding-right:2.5em}html.theme--catppuccin-mocha .navbar-link:not(.is-arrowless)::after{border-color:#fff;margin-top:-0.375em;right:1.125em}html.theme--catppuccin-mocha .navbar-dropdown{font-size:0.875rem;padding-bottom:0.5rem;padding-top:0.5rem}html.theme--catppuccin-mocha .navbar-dropdown .navbar-item{padding-left:1.5rem;padding-right:1.5rem}html.theme--catppuccin-mocha .navbar-divider{background-color:rgba(0,0,0,0.2);border:none;display:none;height:2px;margin:0.5rem 0}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .navbar>.container{display:block}html.theme--catppuccin-mocha .navbar-brand .navbar-item,html.theme--catppuccin-mocha .navbar-tabs .navbar-item{align-items:center;display:flex}html.theme--catppuccin-mocha .navbar-link::after{display:none}html.theme--catppuccin-mocha .navbar-menu{background-color:#89b4fa;box-shadow:0 8px 16px rgba(10,10,10,0.1);padding:0.5rem 0}html.theme--catppuccin-mocha .navbar-menu.is-active{display:block}html.theme--catppuccin-mocha .navbar.is-fixed-bottom-touch,html.theme--catppuccin-mocha .navbar.is-fixed-top-touch{left:0;position:fixed;right:0;z-index:30}html.theme--catppuccin-mocha .navbar.is-fixed-bottom-touch{bottom:0}html.theme--catppuccin-mocha .navbar.is-fixed-bottom-touch.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,0.1)}html.theme--catppuccin-mocha .navbar.is-fixed-top-touch{top:0}html.theme--catppuccin-mocha .navbar.is-fixed-top .navbar-menu,html.theme--catppuccin-mocha .navbar.is-fixed-top-touch .navbar-menu{-webkit-overflow-scrolling:touch;max-height:calc(100vh - 4rem);overflow:auto}html.theme--catppuccin-mocha html.has-navbar-fixed-top-touch,html.theme--catppuccin-mocha body.has-navbar-fixed-top-touch{padding-top:4rem}html.theme--catppuccin-mocha html.has-navbar-fixed-bottom-touch,html.theme--catppuccin-mocha body.has-navbar-fixed-bottom-touch{padding-bottom:4rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .navbar,html.theme--catppuccin-mocha .navbar-menu,html.theme--catppuccin-mocha .navbar-start,html.theme--catppuccin-mocha .navbar-end{align-items:stretch;display:flex}html.theme--catppuccin-mocha .navbar{min-height:4rem}html.theme--catppuccin-mocha .navbar.is-spaced{padding:1rem 2rem}html.theme--catppuccin-mocha .navbar.is-spaced .navbar-start,html.theme--catppuccin-mocha .navbar.is-spaced .navbar-end{align-items:center}html.theme--catppuccin-mocha .navbar.is-spaced a.navbar-item,html.theme--catppuccin-mocha .navbar.is-spaced .navbar-link{border-radius:.4em}html.theme--catppuccin-mocha .navbar.is-transparent a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-transparent a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-transparent a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-transparent .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-transparent .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-transparent .navbar-link.is-active{background-color:transparent !important}html.theme--catppuccin-mocha .navbar.is-transparent .navbar-item.has-dropdown.is-active .navbar-link,html.theme--catppuccin-mocha .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus .navbar-link,html.theme--catppuccin-mocha .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus-within .navbar-link,html.theme--catppuccin-mocha .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:hover .navbar-link{background-color:transparent !important}html.theme--catppuccin-mocha .navbar.is-transparent .navbar-dropdown a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-transparent .navbar-dropdown a.navbar-item:hover{background-color:rgba(0,0,0,0);color:#7f849c}html.theme--catppuccin-mocha .navbar.is-transparent .navbar-dropdown a.navbar-item.is-active{background-color:rgba(0,0,0,0);color:#89b4fa}html.theme--catppuccin-mocha .navbar-burger{display:none}html.theme--catppuccin-mocha .navbar-item,html.theme--catppuccin-mocha .navbar-link{align-items:center;display:flex}html.theme--catppuccin-mocha .navbar-item.has-dropdown{align-items:stretch}html.theme--catppuccin-mocha .navbar-item.has-dropdown-up .navbar-link::after{transform:rotate(135deg) translate(0.25em, -0.25em)}html.theme--catppuccin-mocha .navbar-item.has-dropdown-up .navbar-dropdown{border-bottom:1px solid rgba(0,0,0,0.2);border-radius:8px 8px 0 0;border-top:none;bottom:100%;box-shadow:0 -8px 8px rgba(10,10,10,0.1);top:auto}html.theme--catppuccin-mocha .navbar-item.is-active .navbar-dropdown,html.theme--catppuccin-mocha .navbar-item.is-hoverable:focus .navbar-dropdown,html.theme--catppuccin-mocha .navbar-item.is-hoverable:focus-within .navbar-dropdown,html.theme--catppuccin-mocha .navbar-item.is-hoverable:hover .navbar-dropdown{display:block}.navbar.is-spaced html.theme--catppuccin-mocha .navbar-item.is-active .navbar-dropdown,html.theme--catppuccin-mocha .navbar-item.is-active .navbar-dropdown.is-boxed,.navbar.is-spaced html.theme--catppuccin-mocha .navbar-item.is-hoverable:focus .navbar-dropdown,html.theme--catppuccin-mocha .navbar-item.is-hoverable:focus .navbar-dropdown.is-boxed,.navbar.is-spaced html.theme--catppuccin-mocha .navbar-item.is-hoverable:focus-within .navbar-dropdown,html.theme--catppuccin-mocha .navbar-item.is-hoverable:focus-within .navbar-dropdown.is-boxed,.navbar.is-spaced html.theme--catppuccin-mocha .navbar-item.is-hoverable:hover .navbar-dropdown,html.theme--catppuccin-mocha .navbar-item.is-hoverable:hover .navbar-dropdown.is-boxed{opacity:1;pointer-events:auto;transform:translateY(0)}html.theme--catppuccin-mocha .navbar-menu{flex-grow:1;flex-shrink:0}html.theme--catppuccin-mocha .navbar-start{justify-content:flex-start;margin-right:auto}html.theme--catppuccin-mocha .navbar-end{justify-content:flex-end;margin-left:auto}html.theme--catppuccin-mocha .navbar-dropdown{background-color:#89b4fa;border-bottom-left-radius:8px;border-bottom-right-radius:8px;border-top:1px solid rgba(0,0,0,0.2);box-shadow:0 8px 8px rgba(10,10,10,0.1);display:none;font-size:0.875rem;left:0;min-width:100%;position:absolute;top:100%;z-index:20}html.theme--catppuccin-mocha .navbar-dropdown .navbar-item{padding:0.375rem 1rem;white-space:nowrap}html.theme--catppuccin-mocha .navbar-dropdown a.navbar-item{padding-right:3rem}html.theme--catppuccin-mocha .navbar-dropdown a.navbar-item:focus,html.theme--catppuccin-mocha .navbar-dropdown a.navbar-item:hover{background-color:rgba(0,0,0,0);color:#7f849c}html.theme--catppuccin-mocha .navbar-dropdown a.navbar-item.is-active{background-color:rgba(0,0,0,0);color:#89b4fa}.navbar.is-spaced html.theme--catppuccin-mocha .navbar-dropdown,html.theme--catppuccin-mocha .navbar-dropdown.is-boxed{border-radius:8px;border-top:none;box-shadow:0 8px 8px rgba(10,10,10,0.1), 0 0 0 1px rgba(10,10,10,0.1);display:block;opacity:0;pointer-events:none;top:calc(100% + (-4px));transform:translateY(-5px);transition-duration:86ms;transition-property:opacity, transform}html.theme--catppuccin-mocha .navbar-dropdown.is-right{left:auto;right:0}html.theme--catppuccin-mocha .navbar-divider{display:block}html.theme--catppuccin-mocha .navbar>.container .navbar-brand,html.theme--catppuccin-mocha .container>.navbar .navbar-brand{margin-left:-.75rem}html.theme--catppuccin-mocha .navbar>.container .navbar-menu,html.theme--catppuccin-mocha .container>.navbar .navbar-menu{margin-right:-.75rem}html.theme--catppuccin-mocha .navbar.is-fixed-bottom-desktop,html.theme--catppuccin-mocha .navbar.is-fixed-top-desktop{left:0;position:fixed;right:0;z-index:30}html.theme--catppuccin-mocha .navbar.is-fixed-bottom-desktop{bottom:0}html.theme--catppuccin-mocha .navbar.is-fixed-bottom-desktop.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,0.1)}html.theme--catppuccin-mocha .navbar.is-fixed-top-desktop{top:0}html.theme--catppuccin-mocha html.has-navbar-fixed-top-desktop,html.theme--catppuccin-mocha body.has-navbar-fixed-top-desktop{padding-top:4rem}html.theme--catppuccin-mocha html.has-navbar-fixed-bottom-desktop,html.theme--catppuccin-mocha body.has-navbar-fixed-bottom-desktop{padding-bottom:4rem}html.theme--catppuccin-mocha html.has-spaced-navbar-fixed-top,html.theme--catppuccin-mocha body.has-spaced-navbar-fixed-top{padding-top:6rem}html.theme--catppuccin-mocha html.has-spaced-navbar-fixed-bottom,html.theme--catppuccin-mocha body.has-spaced-navbar-fixed-bottom{padding-bottom:6rem}html.theme--catppuccin-mocha a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar-link.is-active{color:#89b4fa}html.theme--catppuccin-mocha a.navbar-item.is-active:not(:focus):not(:hover),html.theme--catppuccin-mocha .navbar-link.is-active:not(:focus):not(:hover){background-color:rgba(0,0,0,0)}html.theme--catppuccin-mocha .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-mocha .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-mocha .navbar-item.has-dropdown.is-active .navbar-link{background-color:rgba(0,0,0,0)}}html.theme--catppuccin-mocha .hero.is-fullheight-with-navbar{min-height:calc(100vh - 4rem)}html.theme--catppuccin-mocha .pagination{font-size:1rem;margin:-.25rem}html.theme--catppuccin-mocha .pagination.is-small,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.pagination{font-size:.75rem}html.theme--catppuccin-mocha .pagination.is-medium{font-size:1.25rem}html.theme--catppuccin-mocha .pagination.is-large{font-size:1.5rem}html.theme--catppuccin-mocha .pagination.is-rounded .pagination-previous,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.pagination .pagination-previous,html.theme--catppuccin-mocha .pagination.is-rounded .pagination-next,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.pagination .pagination-next{padding-left:1em;padding-right:1em;border-radius:9999px}html.theme--catppuccin-mocha .pagination.is-rounded .pagination-link,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.pagination .pagination-link{border-radius:9999px}html.theme--catppuccin-mocha .pagination,html.theme--catppuccin-mocha .pagination-list{align-items:center;display:flex;justify-content:center;text-align:center}html.theme--catppuccin-mocha .pagination-previous,html.theme--catppuccin-mocha .pagination-next,html.theme--catppuccin-mocha .pagination-link,html.theme--catppuccin-mocha .pagination-ellipsis{font-size:1em;justify-content:center;margin:.25rem;padding-left:.5em;padding-right:.5em;text-align:center}html.theme--catppuccin-mocha .pagination-previous,html.theme--catppuccin-mocha .pagination-next,html.theme--catppuccin-mocha .pagination-link{border-color:#585b70;color:#89b4fa;min-width:2.5em}html.theme--catppuccin-mocha .pagination-previous:hover,html.theme--catppuccin-mocha .pagination-next:hover,html.theme--catppuccin-mocha .pagination-link:hover{border-color:#6c7086;color:#89dceb}html.theme--catppuccin-mocha .pagination-previous:focus,html.theme--catppuccin-mocha .pagination-next:focus,html.theme--catppuccin-mocha .pagination-link:focus{border-color:#6c7086}html.theme--catppuccin-mocha .pagination-previous:active,html.theme--catppuccin-mocha .pagination-next:active,html.theme--catppuccin-mocha .pagination-link:active{box-shadow:inset 0 1px 2px rgba(10,10,10,0.2)}html.theme--catppuccin-mocha .pagination-previous[disabled],html.theme--catppuccin-mocha .pagination-previous.is-disabled,html.theme--catppuccin-mocha .pagination-next[disabled],html.theme--catppuccin-mocha .pagination-next.is-disabled,html.theme--catppuccin-mocha .pagination-link[disabled],html.theme--catppuccin-mocha .pagination-link.is-disabled{background-color:#585b70;border-color:#585b70;box-shadow:none;color:#f7f8fd;opacity:0.5}html.theme--catppuccin-mocha .pagination-previous,html.theme--catppuccin-mocha .pagination-next{padding-left:.75em;padding-right:.75em;white-space:nowrap}html.theme--catppuccin-mocha .pagination-link.is-current{background-color:#89b4fa;border-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .pagination-ellipsis{color:#6c7086;pointer-events:none}html.theme--catppuccin-mocha .pagination-list{flex-wrap:wrap}html.theme--catppuccin-mocha .pagination-list li{list-style:none}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .pagination{flex-wrap:wrap}html.theme--catppuccin-mocha .pagination-previous,html.theme--catppuccin-mocha .pagination-next{flex-grow:1;flex-shrink:1}html.theme--catppuccin-mocha .pagination-list li{flex-grow:1;flex-shrink:1}}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .pagination-list{flex-grow:1;flex-shrink:1;justify-content:flex-start;order:1}html.theme--catppuccin-mocha .pagination-previous,html.theme--catppuccin-mocha .pagination-next,html.theme--catppuccin-mocha .pagination-link,html.theme--catppuccin-mocha .pagination-ellipsis{margin-bottom:0;margin-top:0}html.theme--catppuccin-mocha .pagination-previous{order:2}html.theme--catppuccin-mocha .pagination-next{order:3}html.theme--catppuccin-mocha .pagination{justify-content:space-between;margin-bottom:0;margin-top:0}html.theme--catppuccin-mocha .pagination.is-centered .pagination-previous{order:1}html.theme--catppuccin-mocha .pagination.is-centered .pagination-list{justify-content:center;order:2}html.theme--catppuccin-mocha .pagination.is-centered .pagination-next{order:3}html.theme--catppuccin-mocha .pagination.is-right .pagination-previous{order:1}html.theme--catppuccin-mocha .pagination.is-right .pagination-next{order:2}html.theme--catppuccin-mocha .pagination.is-right .pagination-list{justify-content:flex-end;order:3}}html.theme--catppuccin-mocha .panel{border-radius:8px;box-shadow:#171717;font-size:1rem}html.theme--catppuccin-mocha .panel:not(:last-child){margin-bottom:1.5rem}html.theme--catppuccin-mocha .panel.is-white .panel-heading{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-mocha .panel.is-white .panel-tabs a.is-active{border-bottom-color:#fff}html.theme--catppuccin-mocha .panel.is-white .panel-block.is-active .panel-icon{color:#fff}html.theme--catppuccin-mocha .panel.is-black .panel-heading{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-mocha .panel.is-black .panel-tabs a.is-active{border-bottom-color:#0a0a0a}html.theme--catppuccin-mocha .panel.is-black .panel-block.is-active .panel-icon{color:#0a0a0a}html.theme--catppuccin-mocha .panel.is-light .panel-heading{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .panel.is-light .panel-tabs a.is-active{border-bottom-color:#f5f5f5}html.theme--catppuccin-mocha .panel.is-light .panel-block.is-active .panel-icon{color:#f5f5f5}html.theme--catppuccin-mocha .panel.is-dark .panel-heading,html.theme--catppuccin-mocha .content kbd.panel .panel-heading{background-color:#313244;color:#fff}html.theme--catppuccin-mocha .panel.is-dark .panel-tabs a.is-active,html.theme--catppuccin-mocha .content kbd.panel .panel-tabs a.is-active{border-bottom-color:#313244}html.theme--catppuccin-mocha .panel.is-dark .panel-block.is-active .panel-icon,html.theme--catppuccin-mocha .content kbd.panel .panel-block.is-active .panel-icon{color:#313244}html.theme--catppuccin-mocha .panel.is-primary .panel-heading,html.theme--catppuccin-mocha .docstring>section>a.panel.docs-sourcelink .panel-heading{background-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .panel.is-primary .panel-tabs a.is-active,html.theme--catppuccin-mocha .docstring>section>a.panel.docs-sourcelink .panel-tabs a.is-active{border-bottom-color:#89b4fa}html.theme--catppuccin-mocha .panel.is-primary .panel-block.is-active .panel-icon,html.theme--catppuccin-mocha .docstring>section>a.panel.docs-sourcelink .panel-block.is-active .panel-icon{color:#89b4fa}html.theme--catppuccin-mocha .panel.is-link .panel-heading{background-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .panel.is-link .panel-tabs a.is-active{border-bottom-color:#89b4fa}html.theme--catppuccin-mocha .panel.is-link .panel-block.is-active .panel-icon{color:#89b4fa}html.theme--catppuccin-mocha .panel.is-info .panel-heading{background-color:#94e2d5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .panel.is-info .panel-tabs a.is-active{border-bottom-color:#94e2d5}html.theme--catppuccin-mocha .panel.is-info .panel-block.is-active .panel-icon{color:#94e2d5}html.theme--catppuccin-mocha .panel.is-success .panel-heading{background-color:#a6e3a1;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .panel.is-success .panel-tabs a.is-active{border-bottom-color:#a6e3a1}html.theme--catppuccin-mocha .panel.is-success .panel-block.is-active .panel-icon{color:#a6e3a1}html.theme--catppuccin-mocha .panel.is-warning .panel-heading{background-color:#f9e2af;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .panel.is-warning .panel-tabs a.is-active{border-bottom-color:#f9e2af}html.theme--catppuccin-mocha .panel.is-warning .panel-block.is-active .panel-icon{color:#f9e2af}html.theme--catppuccin-mocha .panel.is-danger .panel-heading{background-color:#f38ba8;color:#fff}html.theme--catppuccin-mocha .panel.is-danger .panel-tabs a.is-active{border-bottom-color:#f38ba8}html.theme--catppuccin-mocha .panel.is-danger .panel-block.is-active .panel-icon{color:#f38ba8}html.theme--catppuccin-mocha .panel-tabs:not(:last-child),html.theme--catppuccin-mocha .panel-block:not(:last-child){border-bottom:1px solid #ededed}html.theme--catppuccin-mocha .panel-heading{background-color:#45475a;border-radius:8px 8px 0 0;color:#b8c5ef;font-size:1.25em;font-weight:700;line-height:1.25;padding:0.75em 1em}html.theme--catppuccin-mocha .panel-tabs{align-items:flex-end;display:flex;font-size:.875em;justify-content:center}html.theme--catppuccin-mocha .panel-tabs a{border-bottom:1px solid #585b70;margin-bottom:-1px;padding:0.5em}html.theme--catppuccin-mocha .panel-tabs a.is-active{border-bottom-color:#45475a;color:#71a4f9}html.theme--catppuccin-mocha .panel-list a{color:#cdd6f4}html.theme--catppuccin-mocha .panel-list a:hover{color:#89b4fa}html.theme--catppuccin-mocha .panel-block{align-items:center;color:#b8c5ef;display:flex;justify-content:flex-start;padding:0.5em 0.75em}html.theme--catppuccin-mocha .panel-block input[type="checkbox"]{margin-right:.75em}html.theme--catppuccin-mocha .panel-block>.control{flex-grow:1;flex-shrink:1;width:100%}html.theme--catppuccin-mocha .panel-block.is-wrapped{flex-wrap:wrap}html.theme--catppuccin-mocha .panel-block.is-active{border-left-color:#89b4fa;color:#71a4f9}html.theme--catppuccin-mocha .panel-block.is-active .panel-icon{color:#89b4fa}html.theme--catppuccin-mocha .panel-block:last-child{border-bottom-left-radius:8px;border-bottom-right-radius:8px}html.theme--catppuccin-mocha a.panel-block,html.theme--catppuccin-mocha label.panel-block{cursor:pointer}html.theme--catppuccin-mocha a.panel-block:hover,html.theme--catppuccin-mocha label.panel-block:hover{background-color:#181825}html.theme--catppuccin-mocha .panel-icon{display:inline-block;font-size:14px;height:1em;line-height:1em;text-align:center;vertical-align:top;width:1em;color:#f7f8fd;margin-right:.75em}html.theme--catppuccin-mocha .panel-icon .fa{font-size:inherit;line-height:inherit}html.theme--catppuccin-mocha .tabs{-webkit-overflow-scrolling:touch;align-items:stretch;display:flex;font-size:1rem;justify-content:space-between;overflow:hidden;overflow-x:auto;white-space:nowrap}html.theme--catppuccin-mocha .tabs a{align-items:center;border-bottom-color:#585b70;border-bottom-style:solid;border-bottom-width:1px;color:#cdd6f4;display:flex;justify-content:center;margin-bottom:-1px;padding:0.5em 1em;vertical-align:top}html.theme--catppuccin-mocha .tabs a:hover{border-bottom-color:#b8c5ef;color:#b8c5ef}html.theme--catppuccin-mocha .tabs li{display:block}html.theme--catppuccin-mocha .tabs li.is-active a{border-bottom-color:#89b4fa;color:#89b4fa}html.theme--catppuccin-mocha .tabs ul{align-items:center;border-bottom-color:#585b70;border-bottom-style:solid;border-bottom-width:1px;display:flex;flex-grow:1;flex-shrink:0;justify-content:flex-start}html.theme--catppuccin-mocha .tabs ul.is-left{padding-right:0.75em}html.theme--catppuccin-mocha .tabs ul.is-center{flex:none;justify-content:center;padding-left:0.75em;padding-right:0.75em}html.theme--catppuccin-mocha .tabs ul.is-right{justify-content:flex-end;padding-left:0.75em}html.theme--catppuccin-mocha .tabs .icon:first-child{margin-right:.5em}html.theme--catppuccin-mocha .tabs .icon:last-child{margin-left:.5em}html.theme--catppuccin-mocha .tabs.is-centered ul{justify-content:center}html.theme--catppuccin-mocha .tabs.is-right ul{justify-content:flex-end}html.theme--catppuccin-mocha .tabs.is-boxed a{border:1px solid transparent;border-radius:.4em .4em 0 0}html.theme--catppuccin-mocha .tabs.is-boxed a:hover{background-color:#181825;border-bottom-color:#585b70}html.theme--catppuccin-mocha .tabs.is-boxed li.is-active a{background-color:#fff;border-color:#585b70;border-bottom-color:rgba(0,0,0,0) !important}html.theme--catppuccin-mocha .tabs.is-fullwidth li{flex-grow:1;flex-shrink:0}html.theme--catppuccin-mocha .tabs.is-toggle a{border-color:#585b70;border-style:solid;border-width:1px;margin-bottom:0;position:relative}html.theme--catppuccin-mocha .tabs.is-toggle a:hover{background-color:#181825;border-color:#6c7086;z-index:2}html.theme--catppuccin-mocha .tabs.is-toggle li+li{margin-left:-1px}html.theme--catppuccin-mocha .tabs.is-toggle li:first-child a{border-top-left-radius:.4em;border-bottom-left-radius:.4em}html.theme--catppuccin-mocha .tabs.is-toggle li:last-child a{border-top-right-radius:.4em;border-bottom-right-radius:.4em}html.theme--catppuccin-mocha .tabs.is-toggle li.is-active a{background-color:#89b4fa;border-color:#89b4fa;color:#fff;z-index:1}html.theme--catppuccin-mocha .tabs.is-toggle ul{border-bottom:none}html.theme--catppuccin-mocha .tabs.is-toggle.is-toggle-rounded li:first-child a{border-bottom-left-radius:9999px;border-top-left-radius:9999px;padding-left:1.25em}html.theme--catppuccin-mocha .tabs.is-toggle.is-toggle-rounded li:last-child a{border-bottom-right-radius:9999px;border-top-right-radius:9999px;padding-right:1.25em}html.theme--catppuccin-mocha .tabs.is-small,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.tabs{font-size:.75rem}html.theme--catppuccin-mocha .tabs.is-medium{font-size:1.25rem}html.theme--catppuccin-mocha .tabs.is-large{font-size:1.5rem}html.theme--catppuccin-mocha .column{display:block;flex-basis:0;flex-grow:1;flex-shrink:1;padding:.75rem}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-narrow{flex:none;width:unset}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-full{flex:none;width:100%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-three-quarters{flex:none;width:75%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-two-thirds{flex:none;width:66.6666%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-half{flex:none;width:50%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-one-third{flex:none;width:33.3333%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-one-quarter{flex:none;width:25%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-one-fifth{flex:none;width:20%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-two-fifths{flex:none;width:40%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-three-fifths{flex:none;width:60%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-four-fifths{flex:none;width:80%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-three-quarters{margin-left:75%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-two-thirds{margin-left:66.6666%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-half{margin-left:50%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-one-third{margin-left:33.3333%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-one-quarter{margin-left:25%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-one-fifth{margin-left:20%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-two-fifths{margin-left:40%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-three-fifths{margin-left:60%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-four-fifths{margin-left:80%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-0{flex:none;width:0%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-0{margin-left:0%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-1{flex:none;width:8.33333337%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-1{margin-left:8.33333337%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-2{flex:none;width:16.66666674%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-2{margin-left:16.66666674%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-3{flex:none;width:25%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-3{margin-left:25%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-4{flex:none;width:33.33333337%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-4{margin-left:33.33333337%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-5{flex:none;width:41.66666674%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-5{margin-left:41.66666674%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-6{flex:none;width:50%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-6{margin-left:50%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-7{flex:none;width:58.33333337%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-7{margin-left:58.33333337%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-8{flex:none;width:66.66666674%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-8{margin-left:66.66666674%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-9{flex:none;width:75%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-9{margin-left:75%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-10{flex:none;width:83.33333337%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-10{margin-left:83.33333337%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-11{flex:none;width:91.66666674%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-11{margin-left:91.66666674%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-12{flex:none;width:100%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-12{margin-left:100%}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .column.is-narrow-mobile{flex:none;width:unset}html.theme--catppuccin-mocha .column.is-full-mobile{flex:none;width:100%}html.theme--catppuccin-mocha .column.is-three-quarters-mobile{flex:none;width:75%}html.theme--catppuccin-mocha .column.is-two-thirds-mobile{flex:none;width:66.6666%}html.theme--catppuccin-mocha .column.is-half-mobile{flex:none;width:50%}html.theme--catppuccin-mocha .column.is-one-third-mobile{flex:none;width:33.3333%}html.theme--catppuccin-mocha .column.is-one-quarter-mobile{flex:none;width:25%}html.theme--catppuccin-mocha .column.is-one-fifth-mobile{flex:none;width:20%}html.theme--catppuccin-mocha .column.is-two-fifths-mobile{flex:none;width:40%}html.theme--catppuccin-mocha .column.is-three-fifths-mobile{flex:none;width:60%}html.theme--catppuccin-mocha .column.is-four-fifths-mobile{flex:none;width:80%}html.theme--catppuccin-mocha .column.is-offset-three-quarters-mobile{margin-left:75%}html.theme--catppuccin-mocha .column.is-offset-two-thirds-mobile{margin-left:66.6666%}html.theme--catppuccin-mocha .column.is-offset-half-mobile{margin-left:50%}html.theme--catppuccin-mocha .column.is-offset-one-third-mobile{margin-left:33.3333%}html.theme--catppuccin-mocha .column.is-offset-one-quarter-mobile{margin-left:25%}html.theme--catppuccin-mocha .column.is-offset-one-fifth-mobile{margin-left:20%}html.theme--catppuccin-mocha .column.is-offset-two-fifths-mobile{margin-left:40%}html.theme--catppuccin-mocha .column.is-offset-three-fifths-mobile{margin-left:60%}html.theme--catppuccin-mocha .column.is-offset-four-fifths-mobile{margin-left:80%}html.theme--catppuccin-mocha .column.is-0-mobile{flex:none;width:0%}html.theme--catppuccin-mocha .column.is-offset-0-mobile{margin-left:0%}html.theme--catppuccin-mocha .column.is-1-mobile{flex:none;width:8.33333337%}html.theme--catppuccin-mocha .column.is-offset-1-mobile{margin-left:8.33333337%}html.theme--catppuccin-mocha .column.is-2-mobile{flex:none;width:16.66666674%}html.theme--catppuccin-mocha .column.is-offset-2-mobile{margin-left:16.66666674%}html.theme--catppuccin-mocha .column.is-3-mobile{flex:none;width:25%}html.theme--catppuccin-mocha .column.is-offset-3-mobile{margin-left:25%}html.theme--catppuccin-mocha .column.is-4-mobile{flex:none;width:33.33333337%}html.theme--catppuccin-mocha .column.is-offset-4-mobile{margin-left:33.33333337%}html.theme--catppuccin-mocha .column.is-5-mobile{flex:none;width:41.66666674%}html.theme--catppuccin-mocha .column.is-offset-5-mobile{margin-left:41.66666674%}html.theme--catppuccin-mocha .column.is-6-mobile{flex:none;width:50%}html.theme--catppuccin-mocha .column.is-offset-6-mobile{margin-left:50%}html.theme--catppuccin-mocha .column.is-7-mobile{flex:none;width:58.33333337%}html.theme--catppuccin-mocha .column.is-offset-7-mobile{margin-left:58.33333337%}html.theme--catppuccin-mocha .column.is-8-mobile{flex:none;width:66.66666674%}html.theme--catppuccin-mocha .column.is-offset-8-mobile{margin-left:66.66666674%}html.theme--catppuccin-mocha .column.is-9-mobile{flex:none;width:75%}html.theme--catppuccin-mocha .column.is-offset-9-mobile{margin-left:75%}html.theme--catppuccin-mocha .column.is-10-mobile{flex:none;width:83.33333337%}html.theme--catppuccin-mocha .column.is-offset-10-mobile{margin-left:83.33333337%}html.theme--catppuccin-mocha .column.is-11-mobile{flex:none;width:91.66666674%}html.theme--catppuccin-mocha .column.is-offset-11-mobile{margin-left:91.66666674%}html.theme--catppuccin-mocha .column.is-12-mobile{flex:none;width:100%}html.theme--catppuccin-mocha .column.is-offset-12-mobile{margin-left:100%}}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .column.is-narrow,html.theme--catppuccin-mocha .column.is-narrow-tablet{flex:none;width:unset}html.theme--catppuccin-mocha .column.is-full,html.theme--catppuccin-mocha .column.is-full-tablet{flex:none;width:100%}html.theme--catppuccin-mocha .column.is-three-quarters,html.theme--catppuccin-mocha .column.is-three-quarters-tablet{flex:none;width:75%}html.theme--catppuccin-mocha .column.is-two-thirds,html.theme--catppuccin-mocha .column.is-two-thirds-tablet{flex:none;width:66.6666%}html.theme--catppuccin-mocha .column.is-half,html.theme--catppuccin-mocha .column.is-half-tablet{flex:none;width:50%}html.theme--catppuccin-mocha .column.is-one-third,html.theme--catppuccin-mocha .column.is-one-third-tablet{flex:none;width:33.3333%}html.theme--catppuccin-mocha .column.is-one-quarter,html.theme--catppuccin-mocha .column.is-one-quarter-tablet{flex:none;width:25%}html.theme--catppuccin-mocha .column.is-one-fifth,html.theme--catppuccin-mocha .column.is-one-fifth-tablet{flex:none;width:20%}html.theme--catppuccin-mocha .column.is-two-fifths,html.theme--catppuccin-mocha .column.is-two-fifths-tablet{flex:none;width:40%}html.theme--catppuccin-mocha .column.is-three-fifths,html.theme--catppuccin-mocha .column.is-three-fifths-tablet{flex:none;width:60%}html.theme--catppuccin-mocha .column.is-four-fifths,html.theme--catppuccin-mocha .column.is-four-fifths-tablet{flex:none;width:80%}html.theme--catppuccin-mocha .column.is-offset-three-quarters,html.theme--catppuccin-mocha .column.is-offset-three-quarters-tablet{margin-left:75%}html.theme--catppuccin-mocha .column.is-offset-two-thirds,html.theme--catppuccin-mocha .column.is-offset-two-thirds-tablet{margin-left:66.6666%}html.theme--catppuccin-mocha .column.is-offset-half,html.theme--catppuccin-mocha .column.is-offset-half-tablet{margin-left:50%}html.theme--catppuccin-mocha .column.is-offset-one-third,html.theme--catppuccin-mocha .column.is-offset-one-third-tablet{margin-left:33.3333%}html.theme--catppuccin-mocha .column.is-offset-one-quarter,html.theme--catppuccin-mocha .column.is-offset-one-quarter-tablet{margin-left:25%}html.theme--catppuccin-mocha .column.is-offset-one-fifth,html.theme--catppuccin-mocha .column.is-offset-one-fifth-tablet{margin-left:20%}html.theme--catppuccin-mocha .column.is-offset-two-fifths,html.theme--catppuccin-mocha .column.is-offset-two-fifths-tablet{margin-left:40%}html.theme--catppuccin-mocha .column.is-offset-three-fifths,html.theme--catppuccin-mocha .column.is-offset-three-fifths-tablet{margin-left:60%}html.theme--catppuccin-mocha .column.is-offset-four-fifths,html.theme--catppuccin-mocha .column.is-offset-four-fifths-tablet{margin-left:80%}html.theme--catppuccin-mocha .column.is-0,html.theme--catppuccin-mocha .column.is-0-tablet{flex:none;width:0%}html.theme--catppuccin-mocha .column.is-offset-0,html.theme--catppuccin-mocha .column.is-offset-0-tablet{margin-left:0%}html.theme--catppuccin-mocha .column.is-1,html.theme--catppuccin-mocha .column.is-1-tablet{flex:none;width:8.33333337%}html.theme--catppuccin-mocha .column.is-offset-1,html.theme--catppuccin-mocha .column.is-offset-1-tablet{margin-left:8.33333337%}html.theme--catppuccin-mocha .column.is-2,html.theme--catppuccin-mocha .column.is-2-tablet{flex:none;width:16.66666674%}html.theme--catppuccin-mocha .column.is-offset-2,html.theme--catppuccin-mocha .column.is-offset-2-tablet{margin-left:16.66666674%}html.theme--catppuccin-mocha .column.is-3,html.theme--catppuccin-mocha .column.is-3-tablet{flex:none;width:25%}html.theme--catppuccin-mocha .column.is-offset-3,html.theme--catppuccin-mocha .column.is-offset-3-tablet{margin-left:25%}html.theme--catppuccin-mocha .column.is-4,html.theme--catppuccin-mocha .column.is-4-tablet{flex:none;width:33.33333337%}html.theme--catppuccin-mocha .column.is-offset-4,html.theme--catppuccin-mocha .column.is-offset-4-tablet{margin-left:33.33333337%}html.theme--catppuccin-mocha .column.is-5,html.theme--catppuccin-mocha .column.is-5-tablet{flex:none;width:41.66666674%}html.theme--catppuccin-mocha .column.is-offset-5,html.theme--catppuccin-mocha .column.is-offset-5-tablet{margin-left:41.66666674%}html.theme--catppuccin-mocha .column.is-6,html.theme--catppuccin-mocha .column.is-6-tablet{flex:none;width:50%}html.theme--catppuccin-mocha .column.is-offset-6,html.theme--catppuccin-mocha .column.is-offset-6-tablet{margin-left:50%}html.theme--catppuccin-mocha .column.is-7,html.theme--catppuccin-mocha .column.is-7-tablet{flex:none;width:58.33333337%}html.theme--catppuccin-mocha .column.is-offset-7,html.theme--catppuccin-mocha .column.is-offset-7-tablet{margin-left:58.33333337%}html.theme--catppuccin-mocha .column.is-8,html.theme--catppuccin-mocha .column.is-8-tablet{flex:none;width:66.66666674%}html.theme--catppuccin-mocha .column.is-offset-8,html.theme--catppuccin-mocha .column.is-offset-8-tablet{margin-left:66.66666674%}html.theme--catppuccin-mocha .column.is-9,html.theme--catppuccin-mocha .column.is-9-tablet{flex:none;width:75%}html.theme--catppuccin-mocha .column.is-offset-9,html.theme--catppuccin-mocha .column.is-offset-9-tablet{margin-left:75%}html.theme--catppuccin-mocha .column.is-10,html.theme--catppuccin-mocha .column.is-10-tablet{flex:none;width:83.33333337%}html.theme--catppuccin-mocha .column.is-offset-10,html.theme--catppuccin-mocha .column.is-offset-10-tablet{margin-left:83.33333337%}html.theme--catppuccin-mocha .column.is-11,html.theme--catppuccin-mocha .column.is-11-tablet{flex:none;width:91.66666674%}html.theme--catppuccin-mocha .column.is-offset-11,html.theme--catppuccin-mocha .column.is-offset-11-tablet{margin-left:91.66666674%}html.theme--catppuccin-mocha .column.is-12,html.theme--catppuccin-mocha .column.is-12-tablet{flex:none;width:100%}html.theme--catppuccin-mocha .column.is-offset-12,html.theme--catppuccin-mocha .column.is-offset-12-tablet{margin-left:100%}}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .column.is-narrow-touch{flex:none;width:unset}html.theme--catppuccin-mocha .column.is-full-touch{flex:none;width:100%}html.theme--catppuccin-mocha .column.is-three-quarters-touch{flex:none;width:75%}html.theme--catppuccin-mocha .column.is-two-thirds-touch{flex:none;width:66.6666%}html.theme--catppuccin-mocha .column.is-half-touch{flex:none;width:50%}html.theme--catppuccin-mocha .column.is-one-third-touch{flex:none;width:33.3333%}html.theme--catppuccin-mocha .column.is-one-quarter-touch{flex:none;width:25%}html.theme--catppuccin-mocha .column.is-one-fifth-touch{flex:none;width:20%}html.theme--catppuccin-mocha .column.is-two-fifths-touch{flex:none;width:40%}html.theme--catppuccin-mocha .column.is-three-fifths-touch{flex:none;width:60%}html.theme--catppuccin-mocha .column.is-four-fifths-touch{flex:none;width:80%}html.theme--catppuccin-mocha .column.is-offset-three-quarters-touch{margin-left:75%}html.theme--catppuccin-mocha .column.is-offset-two-thirds-touch{margin-left:66.6666%}html.theme--catppuccin-mocha .column.is-offset-half-touch{margin-left:50%}html.theme--catppuccin-mocha .column.is-offset-one-third-touch{margin-left:33.3333%}html.theme--catppuccin-mocha .column.is-offset-one-quarter-touch{margin-left:25%}html.theme--catppuccin-mocha .column.is-offset-one-fifth-touch{margin-left:20%}html.theme--catppuccin-mocha .column.is-offset-two-fifths-touch{margin-left:40%}html.theme--catppuccin-mocha .column.is-offset-three-fifths-touch{margin-left:60%}html.theme--catppuccin-mocha .column.is-offset-four-fifths-touch{margin-left:80%}html.theme--catppuccin-mocha .column.is-0-touch{flex:none;width:0%}html.theme--catppuccin-mocha .column.is-offset-0-touch{margin-left:0%}html.theme--catppuccin-mocha .column.is-1-touch{flex:none;width:8.33333337%}html.theme--catppuccin-mocha .column.is-offset-1-touch{margin-left:8.33333337%}html.theme--catppuccin-mocha .column.is-2-touch{flex:none;width:16.66666674%}html.theme--catppuccin-mocha .column.is-offset-2-touch{margin-left:16.66666674%}html.theme--catppuccin-mocha .column.is-3-touch{flex:none;width:25%}html.theme--catppuccin-mocha .column.is-offset-3-touch{margin-left:25%}html.theme--catppuccin-mocha .column.is-4-touch{flex:none;width:33.33333337%}html.theme--catppuccin-mocha .column.is-offset-4-touch{margin-left:33.33333337%}html.theme--catppuccin-mocha .column.is-5-touch{flex:none;width:41.66666674%}html.theme--catppuccin-mocha .column.is-offset-5-touch{margin-left:41.66666674%}html.theme--catppuccin-mocha .column.is-6-touch{flex:none;width:50%}html.theme--catppuccin-mocha .column.is-offset-6-touch{margin-left:50%}html.theme--catppuccin-mocha .column.is-7-touch{flex:none;width:58.33333337%}html.theme--catppuccin-mocha .column.is-offset-7-touch{margin-left:58.33333337%}html.theme--catppuccin-mocha .column.is-8-touch{flex:none;width:66.66666674%}html.theme--catppuccin-mocha .column.is-offset-8-touch{margin-left:66.66666674%}html.theme--catppuccin-mocha .column.is-9-touch{flex:none;width:75%}html.theme--catppuccin-mocha .column.is-offset-9-touch{margin-left:75%}html.theme--catppuccin-mocha .column.is-10-touch{flex:none;width:83.33333337%}html.theme--catppuccin-mocha .column.is-offset-10-touch{margin-left:83.33333337%}html.theme--catppuccin-mocha .column.is-11-touch{flex:none;width:91.66666674%}html.theme--catppuccin-mocha .column.is-offset-11-touch{margin-left:91.66666674%}html.theme--catppuccin-mocha .column.is-12-touch{flex:none;width:100%}html.theme--catppuccin-mocha .column.is-offset-12-touch{margin-left:100%}}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .column.is-narrow-desktop{flex:none;width:unset}html.theme--catppuccin-mocha .column.is-full-desktop{flex:none;width:100%}html.theme--catppuccin-mocha .column.is-three-quarters-desktop{flex:none;width:75%}html.theme--catppuccin-mocha .column.is-two-thirds-desktop{flex:none;width:66.6666%}html.theme--catppuccin-mocha .column.is-half-desktop{flex:none;width:50%}html.theme--catppuccin-mocha .column.is-one-third-desktop{flex:none;width:33.3333%}html.theme--catppuccin-mocha .column.is-one-quarter-desktop{flex:none;width:25%}html.theme--catppuccin-mocha .column.is-one-fifth-desktop{flex:none;width:20%}html.theme--catppuccin-mocha .column.is-two-fifths-desktop{flex:none;width:40%}html.theme--catppuccin-mocha .column.is-three-fifths-desktop{flex:none;width:60%}html.theme--catppuccin-mocha .column.is-four-fifths-desktop{flex:none;width:80%}html.theme--catppuccin-mocha .column.is-offset-three-quarters-desktop{margin-left:75%}html.theme--catppuccin-mocha .column.is-offset-two-thirds-desktop{margin-left:66.6666%}html.theme--catppuccin-mocha .column.is-offset-half-desktop{margin-left:50%}html.theme--catppuccin-mocha .column.is-offset-one-third-desktop{margin-left:33.3333%}html.theme--catppuccin-mocha .column.is-offset-one-quarter-desktop{margin-left:25%}html.theme--catppuccin-mocha .column.is-offset-one-fifth-desktop{margin-left:20%}html.theme--catppuccin-mocha .column.is-offset-two-fifths-desktop{margin-left:40%}html.theme--catppuccin-mocha .column.is-offset-three-fifths-desktop{margin-left:60%}html.theme--catppuccin-mocha .column.is-offset-four-fifths-desktop{margin-left:80%}html.theme--catppuccin-mocha .column.is-0-desktop{flex:none;width:0%}html.theme--catppuccin-mocha .column.is-offset-0-desktop{margin-left:0%}html.theme--catppuccin-mocha .column.is-1-desktop{flex:none;width:8.33333337%}html.theme--catppuccin-mocha .column.is-offset-1-desktop{margin-left:8.33333337%}html.theme--catppuccin-mocha .column.is-2-desktop{flex:none;width:16.66666674%}html.theme--catppuccin-mocha .column.is-offset-2-desktop{margin-left:16.66666674%}html.theme--catppuccin-mocha .column.is-3-desktop{flex:none;width:25%}html.theme--catppuccin-mocha .column.is-offset-3-desktop{margin-left:25%}html.theme--catppuccin-mocha .column.is-4-desktop{flex:none;width:33.33333337%}html.theme--catppuccin-mocha .column.is-offset-4-desktop{margin-left:33.33333337%}html.theme--catppuccin-mocha .column.is-5-desktop{flex:none;width:41.66666674%}html.theme--catppuccin-mocha .column.is-offset-5-desktop{margin-left:41.66666674%}html.theme--catppuccin-mocha .column.is-6-desktop{flex:none;width:50%}html.theme--catppuccin-mocha .column.is-offset-6-desktop{margin-left:50%}html.theme--catppuccin-mocha .column.is-7-desktop{flex:none;width:58.33333337%}html.theme--catppuccin-mocha .column.is-offset-7-desktop{margin-left:58.33333337%}html.theme--catppuccin-mocha .column.is-8-desktop{flex:none;width:66.66666674%}html.theme--catppuccin-mocha .column.is-offset-8-desktop{margin-left:66.66666674%}html.theme--catppuccin-mocha .column.is-9-desktop{flex:none;width:75%}html.theme--catppuccin-mocha .column.is-offset-9-desktop{margin-left:75%}html.theme--catppuccin-mocha .column.is-10-desktop{flex:none;width:83.33333337%}html.theme--catppuccin-mocha .column.is-offset-10-desktop{margin-left:83.33333337%}html.theme--catppuccin-mocha .column.is-11-desktop{flex:none;width:91.66666674%}html.theme--catppuccin-mocha .column.is-offset-11-desktop{margin-left:91.66666674%}html.theme--catppuccin-mocha .column.is-12-desktop{flex:none;width:100%}html.theme--catppuccin-mocha .column.is-offset-12-desktop{margin-left:100%}}@media screen and (min-width: 1216px){html.theme--catppuccin-mocha .column.is-narrow-widescreen{flex:none;width:unset}html.theme--catppuccin-mocha .column.is-full-widescreen{flex:none;width:100%}html.theme--catppuccin-mocha .column.is-three-quarters-widescreen{flex:none;width:75%}html.theme--catppuccin-mocha .column.is-two-thirds-widescreen{flex:none;width:66.6666%}html.theme--catppuccin-mocha .column.is-half-widescreen{flex:none;width:50%}html.theme--catppuccin-mocha .column.is-one-third-widescreen{flex:none;width:33.3333%}html.theme--catppuccin-mocha .column.is-one-quarter-widescreen{flex:none;width:25%}html.theme--catppuccin-mocha .column.is-one-fifth-widescreen{flex:none;width:20%}html.theme--catppuccin-mocha .column.is-two-fifths-widescreen{flex:none;width:40%}html.theme--catppuccin-mocha .column.is-three-fifths-widescreen{flex:none;width:60%}html.theme--catppuccin-mocha .column.is-four-fifths-widescreen{flex:none;width:80%}html.theme--catppuccin-mocha .column.is-offset-three-quarters-widescreen{margin-left:75%}html.theme--catppuccin-mocha .column.is-offset-two-thirds-widescreen{margin-left:66.6666%}html.theme--catppuccin-mocha .column.is-offset-half-widescreen{margin-left:50%}html.theme--catppuccin-mocha .column.is-offset-one-third-widescreen{margin-left:33.3333%}html.theme--catppuccin-mocha .column.is-offset-one-quarter-widescreen{margin-left:25%}html.theme--catppuccin-mocha .column.is-offset-one-fifth-widescreen{margin-left:20%}html.theme--catppuccin-mocha .column.is-offset-two-fifths-widescreen{margin-left:40%}html.theme--catppuccin-mocha .column.is-offset-three-fifths-widescreen{margin-left:60%}html.theme--catppuccin-mocha .column.is-offset-four-fifths-widescreen{margin-left:80%}html.theme--catppuccin-mocha .column.is-0-widescreen{flex:none;width:0%}html.theme--catppuccin-mocha .column.is-offset-0-widescreen{margin-left:0%}html.theme--catppuccin-mocha .column.is-1-widescreen{flex:none;width:8.33333337%}html.theme--catppuccin-mocha .column.is-offset-1-widescreen{margin-left:8.33333337%}html.theme--catppuccin-mocha .column.is-2-widescreen{flex:none;width:16.66666674%}html.theme--catppuccin-mocha .column.is-offset-2-widescreen{margin-left:16.66666674%}html.theme--catppuccin-mocha .column.is-3-widescreen{flex:none;width:25%}html.theme--catppuccin-mocha .column.is-offset-3-widescreen{margin-left:25%}html.theme--catppuccin-mocha .column.is-4-widescreen{flex:none;width:33.33333337%}html.theme--catppuccin-mocha .column.is-offset-4-widescreen{margin-left:33.33333337%}html.theme--catppuccin-mocha .column.is-5-widescreen{flex:none;width:41.66666674%}html.theme--catppuccin-mocha .column.is-offset-5-widescreen{margin-left:41.66666674%}html.theme--catppuccin-mocha .column.is-6-widescreen{flex:none;width:50%}html.theme--catppuccin-mocha .column.is-offset-6-widescreen{margin-left:50%}html.theme--catppuccin-mocha .column.is-7-widescreen{flex:none;width:58.33333337%}html.theme--catppuccin-mocha .column.is-offset-7-widescreen{margin-left:58.33333337%}html.theme--catppuccin-mocha .column.is-8-widescreen{flex:none;width:66.66666674%}html.theme--catppuccin-mocha .column.is-offset-8-widescreen{margin-left:66.66666674%}html.theme--catppuccin-mocha .column.is-9-widescreen{flex:none;width:75%}html.theme--catppuccin-mocha .column.is-offset-9-widescreen{margin-left:75%}html.theme--catppuccin-mocha .column.is-10-widescreen{flex:none;width:83.33333337%}html.theme--catppuccin-mocha .column.is-offset-10-widescreen{margin-left:83.33333337%}html.theme--catppuccin-mocha .column.is-11-widescreen{flex:none;width:91.66666674%}html.theme--catppuccin-mocha .column.is-offset-11-widescreen{margin-left:91.66666674%}html.theme--catppuccin-mocha .column.is-12-widescreen{flex:none;width:100%}html.theme--catppuccin-mocha .column.is-offset-12-widescreen{margin-left:100%}}@media screen and (min-width: 1408px){html.theme--catppuccin-mocha .column.is-narrow-fullhd{flex:none;width:unset}html.theme--catppuccin-mocha .column.is-full-fullhd{flex:none;width:100%}html.theme--catppuccin-mocha .column.is-three-quarters-fullhd{flex:none;width:75%}html.theme--catppuccin-mocha .column.is-two-thirds-fullhd{flex:none;width:66.6666%}html.theme--catppuccin-mocha .column.is-half-fullhd{flex:none;width:50%}html.theme--catppuccin-mocha .column.is-one-third-fullhd{flex:none;width:33.3333%}html.theme--catppuccin-mocha .column.is-one-quarter-fullhd{flex:none;width:25%}html.theme--catppuccin-mocha .column.is-one-fifth-fullhd{flex:none;width:20%}html.theme--catppuccin-mocha .column.is-two-fifths-fullhd{flex:none;width:40%}html.theme--catppuccin-mocha .column.is-three-fifths-fullhd{flex:none;width:60%}html.theme--catppuccin-mocha .column.is-four-fifths-fullhd{flex:none;width:80%}html.theme--catppuccin-mocha .column.is-offset-three-quarters-fullhd{margin-left:75%}html.theme--catppuccin-mocha .column.is-offset-two-thirds-fullhd{margin-left:66.6666%}html.theme--catppuccin-mocha .column.is-offset-half-fullhd{margin-left:50%}html.theme--catppuccin-mocha .column.is-offset-one-third-fullhd{margin-left:33.3333%}html.theme--catppuccin-mocha .column.is-offset-one-quarter-fullhd{margin-left:25%}html.theme--catppuccin-mocha .column.is-offset-one-fifth-fullhd{margin-left:20%}html.theme--catppuccin-mocha .column.is-offset-two-fifths-fullhd{margin-left:40%}html.theme--catppuccin-mocha .column.is-offset-three-fifths-fullhd{margin-left:60%}html.theme--catppuccin-mocha .column.is-offset-four-fifths-fullhd{margin-left:80%}html.theme--catppuccin-mocha .column.is-0-fullhd{flex:none;width:0%}html.theme--catppuccin-mocha .column.is-offset-0-fullhd{margin-left:0%}html.theme--catppuccin-mocha .column.is-1-fullhd{flex:none;width:8.33333337%}html.theme--catppuccin-mocha .column.is-offset-1-fullhd{margin-left:8.33333337%}html.theme--catppuccin-mocha .column.is-2-fullhd{flex:none;width:16.66666674%}html.theme--catppuccin-mocha .column.is-offset-2-fullhd{margin-left:16.66666674%}html.theme--catppuccin-mocha .column.is-3-fullhd{flex:none;width:25%}html.theme--catppuccin-mocha .column.is-offset-3-fullhd{margin-left:25%}html.theme--catppuccin-mocha .column.is-4-fullhd{flex:none;width:33.33333337%}html.theme--catppuccin-mocha .column.is-offset-4-fullhd{margin-left:33.33333337%}html.theme--catppuccin-mocha .column.is-5-fullhd{flex:none;width:41.66666674%}html.theme--catppuccin-mocha .column.is-offset-5-fullhd{margin-left:41.66666674%}html.theme--catppuccin-mocha .column.is-6-fullhd{flex:none;width:50%}html.theme--catppuccin-mocha .column.is-offset-6-fullhd{margin-left:50%}html.theme--catppuccin-mocha .column.is-7-fullhd{flex:none;width:58.33333337%}html.theme--catppuccin-mocha .column.is-offset-7-fullhd{margin-left:58.33333337%}html.theme--catppuccin-mocha .column.is-8-fullhd{flex:none;width:66.66666674%}html.theme--catppuccin-mocha .column.is-offset-8-fullhd{margin-left:66.66666674%}html.theme--catppuccin-mocha .column.is-9-fullhd{flex:none;width:75%}html.theme--catppuccin-mocha .column.is-offset-9-fullhd{margin-left:75%}html.theme--catppuccin-mocha .column.is-10-fullhd{flex:none;width:83.33333337%}html.theme--catppuccin-mocha .column.is-offset-10-fullhd{margin-left:83.33333337%}html.theme--catppuccin-mocha .column.is-11-fullhd{flex:none;width:91.66666674%}html.theme--catppuccin-mocha .column.is-offset-11-fullhd{margin-left:91.66666674%}html.theme--catppuccin-mocha .column.is-12-fullhd{flex:none;width:100%}html.theme--catppuccin-mocha .column.is-offset-12-fullhd{margin-left:100%}}html.theme--catppuccin-mocha .columns{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}html.theme--catppuccin-mocha .columns:last-child{margin-bottom:-.75rem}html.theme--catppuccin-mocha .columns:not(:last-child){margin-bottom:calc(1.5rem - .75rem)}html.theme--catppuccin-mocha .columns.is-centered{justify-content:center}html.theme--catppuccin-mocha .columns.is-gapless{margin-left:0;margin-right:0;margin-top:0}html.theme--catppuccin-mocha .columns.is-gapless>.column{margin:0;padding:0 !important}html.theme--catppuccin-mocha .columns.is-gapless:not(:last-child){margin-bottom:1.5rem}html.theme--catppuccin-mocha .columns.is-gapless:last-child{margin-bottom:0}html.theme--catppuccin-mocha .columns.is-mobile{display:flex}html.theme--catppuccin-mocha .columns.is-multiline{flex-wrap:wrap}html.theme--catppuccin-mocha .columns.is-vcentered{align-items:center}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .columns:not(.is-desktop){display:flex}}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .columns.is-desktop{display:flex}}html.theme--catppuccin-mocha .columns.is-variable{--columnGap: 0.75rem;margin-left:calc(-1 * var(--columnGap));margin-right:calc(-1 * var(--columnGap))}html.theme--catppuccin-mocha .columns.is-variable>.column{padding-left:var(--columnGap);padding-right:var(--columnGap)}html.theme--catppuccin-mocha .columns.is-variable.is-0{--columnGap: 0rem}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .columns.is-variable.is-0-mobile{--columnGap: 0rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .columns.is-variable.is-0-tablet{--columnGap: 0rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-mocha .columns.is-variable.is-0-tablet-only{--columnGap: 0rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .columns.is-variable.is-0-touch{--columnGap: 0rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .columns.is-variable.is-0-desktop{--columnGap: 0rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-mocha .columns.is-variable.is-0-desktop-only{--columnGap: 0rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-mocha .columns.is-variable.is-0-widescreen{--columnGap: 0rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-mocha .columns.is-variable.is-0-widescreen-only{--columnGap: 0rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-mocha .columns.is-variable.is-0-fullhd{--columnGap: 0rem}}html.theme--catppuccin-mocha .columns.is-variable.is-1{--columnGap: .25rem}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .columns.is-variable.is-1-mobile{--columnGap: .25rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .columns.is-variable.is-1-tablet{--columnGap: .25rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-mocha .columns.is-variable.is-1-tablet-only{--columnGap: .25rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .columns.is-variable.is-1-touch{--columnGap: .25rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .columns.is-variable.is-1-desktop{--columnGap: .25rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-mocha .columns.is-variable.is-1-desktop-only{--columnGap: .25rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-mocha .columns.is-variable.is-1-widescreen{--columnGap: .25rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-mocha .columns.is-variable.is-1-widescreen-only{--columnGap: .25rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-mocha .columns.is-variable.is-1-fullhd{--columnGap: .25rem}}html.theme--catppuccin-mocha .columns.is-variable.is-2{--columnGap: .5rem}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .columns.is-variable.is-2-mobile{--columnGap: .5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .columns.is-variable.is-2-tablet{--columnGap: .5rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-mocha .columns.is-variable.is-2-tablet-only{--columnGap: .5rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .columns.is-variable.is-2-touch{--columnGap: .5rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .columns.is-variable.is-2-desktop{--columnGap: .5rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-mocha .columns.is-variable.is-2-desktop-only{--columnGap: .5rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-mocha .columns.is-variable.is-2-widescreen{--columnGap: .5rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-mocha .columns.is-variable.is-2-widescreen-only{--columnGap: .5rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-mocha .columns.is-variable.is-2-fullhd{--columnGap: .5rem}}html.theme--catppuccin-mocha .columns.is-variable.is-3{--columnGap: .75rem}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .columns.is-variable.is-3-mobile{--columnGap: .75rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .columns.is-variable.is-3-tablet{--columnGap: .75rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-mocha .columns.is-variable.is-3-tablet-only{--columnGap: .75rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .columns.is-variable.is-3-touch{--columnGap: .75rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .columns.is-variable.is-3-desktop{--columnGap: .75rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-mocha .columns.is-variable.is-3-desktop-only{--columnGap: .75rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-mocha .columns.is-variable.is-3-widescreen{--columnGap: .75rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-mocha .columns.is-variable.is-3-widescreen-only{--columnGap: .75rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-mocha .columns.is-variable.is-3-fullhd{--columnGap: .75rem}}html.theme--catppuccin-mocha .columns.is-variable.is-4{--columnGap: 1rem}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .columns.is-variable.is-4-mobile{--columnGap: 1rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .columns.is-variable.is-4-tablet{--columnGap: 1rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-mocha .columns.is-variable.is-4-tablet-only{--columnGap: 1rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .columns.is-variable.is-4-touch{--columnGap: 1rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .columns.is-variable.is-4-desktop{--columnGap: 1rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-mocha .columns.is-variable.is-4-desktop-only{--columnGap: 1rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-mocha .columns.is-variable.is-4-widescreen{--columnGap: 1rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-mocha .columns.is-variable.is-4-widescreen-only{--columnGap: 1rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-mocha .columns.is-variable.is-4-fullhd{--columnGap: 1rem}}html.theme--catppuccin-mocha .columns.is-variable.is-5{--columnGap: 1.25rem}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .columns.is-variable.is-5-mobile{--columnGap: 1.25rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .columns.is-variable.is-5-tablet{--columnGap: 1.25rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-mocha .columns.is-variable.is-5-tablet-only{--columnGap: 1.25rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .columns.is-variable.is-5-touch{--columnGap: 1.25rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .columns.is-variable.is-5-desktop{--columnGap: 1.25rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-mocha .columns.is-variable.is-5-desktop-only{--columnGap: 1.25rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-mocha .columns.is-variable.is-5-widescreen{--columnGap: 1.25rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-mocha .columns.is-variable.is-5-widescreen-only{--columnGap: 1.25rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-mocha .columns.is-variable.is-5-fullhd{--columnGap: 1.25rem}}html.theme--catppuccin-mocha .columns.is-variable.is-6{--columnGap: 1.5rem}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .columns.is-variable.is-6-mobile{--columnGap: 1.5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .columns.is-variable.is-6-tablet{--columnGap: 1.5rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-mocha .columns.is-variable.is-6-tablet-only{--columnGap: 1.5rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .columns.is-variable.is-6-touch{--columnGap: 1.5rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .columns.is-variable.is-6-desktop{--columnGap: 1.5rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-mocha .columns.is-variable.is-6-desktop-only{--columnGap: 1.5rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-mocha .columns.is-variable.is-6-widescreen{--columnGap: 1.5rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-mocha .columns.is-variable.is-6-widescreen-only{--columnGap: 1.5rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-mocha .columns.is-variable.is-6-fullhd{--columnGap: 1.5rem}}html.theme--catppuccin-mocha .columns.is-variable.is-7{--columnGap: 1.75rem}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .columns.is-variable.is-7-mobile{--columnGap: 1.75rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .columns.is-variable.is-7-tablet{--columnGap: 1.75rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-mocha .columns.is-variable.is-7-tablet-only{--columnGap: 1.75rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .columns.is-variable.is-7-touch{--columnGap: 1.75rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .columns.is-variable.is-7-desktop{--columnGap: 1.75rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-mocha .columns.is-variable.is-7-desktop-only{--columnGap: 1.75rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-mocha .columns.is-variable.is-7-widescreen{--columnGap: 1.75rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-mocha .columns.is-variable.is-7-widescreen-only{--columnGap: 1.75rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-mocha .columns.is-variable.is-7-fullhd{--columnGap: 1.75rem}}html.theme--catppuccin-mocha .columns.is-variable.is-8{--columnGap: 2rem}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .columns.is-variable.is-8-mobile{--columnGap: 2rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .columns.is-variable.is-8-tablet{--columnGap: 2rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-mocha .columns.is-variable.is-8-tablet-only{--columnGap: 2rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .columns.is-variable.is-8-touch{--columnGap: 2rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .columns.is-variable.is-8-desktop{--columnGap: 2rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-mocha .columns.is-variable.is-8-desktop-only{--columnGap: 2rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-mocha .columns.is-variable.is-8-widescreen{--columnGap: 2rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-mocha .columns.is-variable.is-8-widescreen-only{--columnGap: 2rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-mocha .columns.is-variable.is-8-fullhd{--columnGap: 2rem}}html.theme--catppuccin-mocha .tile{align-items:stretch;display:block;flex-basis:0;flex-grow:1;flex-shrink:1;min-height:min-content}html.theme--catppuccin-mocha .tile.is-ancestor{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}html.theme--catppuccin-mocha .tile.is-ancestor:last-child{margin-bottom:-.75rem}html.theme--catppuccin-mocha .tile.is-ancestor:not(:last-child){margin-bottom:.75rem}html.theme--catppuccin-mocha .tile.is-child{margin:0 !important}html.theme--catppuccin-mocha .tile.is-parent{padding:.75rem}html.theme--catppuccin-mocha .tile.is-vertical{flex-direction:column}html.theme--catppuccin-mocha .tile.is-vertical>.tile.is-child:not(:last-child){margin-bottom:1.5rem !important}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .tile:not(.is-child){display:flex}html.theme--catppuccin-mocha .tile.is-1{flex:none;width:8.33333337%}html.theme--catppuccin-mocha .tile.is-2{flex:none;width:16.66666674%}html.theme--catppuccin-mocha .tile.is-3{flex:none;width:25%}html.theme--catppuccin-mocha .tile.is-4{flex:none;width:33.33333337%}html.theme--catppuccin-mocha .tile.is-5{flex:none;width:41.66666674%}html.theme--catppuccin-mocha .tile.is-6{flex:none;width:50%}html.theme--catppuccin-mocha .tile.is-7{flex:none;width:58.33333337%}html.theme--catppuccin-mocha .tile.is-8{flex:none;width:66.66666674%}html.theme--catppuccin-mocha .tile.is-9{flex:none;width:75%}html.theme--catppuccin-mocha .tile.is-10{flex:none;width:83.33333337%}html.theme--catppuccin-mocha .tile.is-11{flex:none;width:91.66666674%}html.theme--catppuccin-mocha .tile.is-12{flex:none;width:100%}}html.theme--catppuccin-mocha .hero{align-items:stretch;display:flex;flex-direction:column;justify-content:space-between}html.theme--catppuccin-mocha .hero .navbar{background:none}html.theme--catppuccin-mocha .hero .tabs ul{border-bottom:none}html.theme--catppuccin-mocha .hero.is-white{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-mocha .hero.is-white a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-mocha .hero.is-white strong{color:inherit}html.theme--catppuccin-mocha .hero.is-white .title{color:#0a0a0a}html.theme--catppuccin-mocha .hero.is-white .subtitle{color:rgba(10,10,10,0.9)}html.theme--catppuccin-mocha .hero.is-white .subtitle a:not(.button),html.theme--catppuccin-mocha .hero.is-white .subtitle strong{color:#0a0a0a}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .hero.is-white .navbar-menu{background-color:#fff}}html.theme--catppuccin-mocha .hero.is-white .navbar-item,html.theme--catppuccin-mocha .hero.is-white .navbar-link{color:rgba(10,10,10,0.7)}html.theme--catppuccin-mocha .hero.is-white a.navbar-item:hover,html.theme--catppuccin-mocha .hero.is-white a.navbar-item.is-active,html.theme--catppuccin-mocha .hero.is-white .navbar-link:hover,html.theme--catppuccin-mocha .hero.is-white .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}html.theme--catppuccin-mocha .hero.is-white .tabs a{color:#0a0a0a;opacity:0.9}html.theme--catppuccin-mocha .hero.is-white .tabs a:hover{opacity:1}html.theme--catppuccin-mocha .hero.is-white .tabs li.is-active a{color:#fff !important;opacity:1}html.theme--catppuccin-mocha .hero.is-white .tabs.is-boxed a,html.theme--catppuccin-mocha .hero.is-white .tabs.is-toggle a{color:#0a0a0a}html.theme--catppuccin-mocha .hero.is-white .tabs.is-boxed a:hover,html.theme--catppuccin-mocha .hero.is-white .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-mocha .hero.is-white .tabs.is-boxed li.is-active a,html.theme--catppuccin-mocha .hero.is-white .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-mocha .hero.is-white .tabs.is-toggle li.is-active a,html.theme--catppuccin-mocha .hero.is-white .tabs.is-toggle li.is-active a:hover{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}html.theme--catppuccin-mocha .hero.is-white.is-bold{background-image:linear-gradient(141deg, #e8e3e4 0%, #fff 71%, #fff 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .hero.is-white.is-bold .navbar-menu{background-image:linear-gradient(141deg, #e8e3e4 0%, #fff 71%, #fff 100%)}}html.theme--catppuccin-mocha .hero.is-black{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-mocha .hero.is-black a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-mocha .hero.is-black strong{color:inherit}html.theme--catppuccin-mocha .hero.is-black .title{color:#fff}html.theme--catppuccin-mocha .hero.is-black .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-mocha .hero.is-black .subtitle a:not(.button),html.theme--catppuccin-mocha .hero.is-black .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .hero.is-black .navbar-menu{background-color:#0a0a0a}}html.theme--catppuccin-mocha .hero.is-black .navbar-item,html.theme--catppuccin-mocha .hero.is-black .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-mocha .hero.is-black a.navbar-item:hover,html.theme--catppuccin-mocha .hero.is-black a.navbar-item.is-active,html.theme--catppuccin-mocha .hero.is-black .navbar-link:hover,html.theme--catppuccin-mocha .hero.is-black .navbar-link.is-active{background-color:#000;color:#fff}html.theme--catppuccin-mocha .hero.is-black .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-mocha .hero.is-black .tabs a:hover{opacity:1}html.theme--catppuccin-mocha .hero.is-black .tabs li.is-active a{color:#0a0a0a !important;opacity:1}html.theme--catppuccin-mocha .hero.is-black .tabs.is-boxed a,html.theme--catppuccin-mocha .hero.is-black .tabs.is-toggle a{color:#fff}html.theme--catppuccin-mocha .hero.is-black .tabs.is-boxed a:hover,html.theme--catppuccin-mocha .hero.is-black .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-mocha .hero.is-black .tabs.is-boxed li.is-active a,html.theme--catppuccin-mocha .hero.is-black .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-mocha .hero.is-black .tabs.is-toggle li.is-active a,html.theme--catppuccin-mocha .hero.is-black .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#0a0a0a}html.theme--catppuccin-mocha .hero.is-black.is-bold{background-image:linear-gradient(141deg, #000 0%, #0a0a0a 71%, #181616 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .hero.is-black.is-bold .navbar-menu{background-image:linear-gradient(141deg, #000 0%, #0a0a0a 71%, #181616 100%)}}html.theme--catppuccin-mocha .hero.is-light{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-light a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-mocha .hero.is-light strong{color:inherit}html.theme--catppuccin-mocha .hero.is-light .title{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-light .subtitle{color:rgba(0,0,0,0.9)}html.theme--catppuccin-mocha .hero.is-light .subtitle a:not(.button),html.theme--catppuccin-mocha .hero.is-light .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .hero.is-light .navbar-menu{background-color:#f5f5f5}}html.theme--catppuccin-mocha .hero.is-light .navbar-item,html.theme--catppuccin-mocha .hero.is-light .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-light a.navbar-item:hover,html.theme--catppuccin-mocha .hero.is-light a.navbar-item.is-active,html.theme--catppuccin-mocha .hero.is-light .navbar-link:hover,html.theme--catppuccin-mocha .hero.is-light .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-light .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}html.theme--catppuccin-mocha .hero.is-light .tabs a:hover{opacity:1}html.theme--catppuccin-mocha .hero.is-light .tabs li.is-active a{color:#f5f5f5 !important;opacity:1}html.theme--catppuccin-mocha .hero.is-light .tabs.is-boxed a,html.theme--catppuccin-mocha .hero.is-light .tabs.is-toggle a{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-light .tabs.is-boxed a:hover,html.theme--catppuccin-mocha .hero.is-light .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-mocha .hero.is-light .tabs.is-boxed li.is-active a,html.theme--catppuccin-mocha .hero.is-light .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-mocha .hero.is-light .tabs.is-toggle li.is-active a,html.theme--catppuccin-mocha .hero.is-light .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#f5f5f5}html.theme--catppuccin-mocha .hero.is-light.is-bold{background-image:linear-gradient(141deg, #dfd8d9 0%, #f5f5f5 71%, #fff 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .hero.is-light.is-bold .navbar-menu{background-image:linear-gradient(141deg, #dfd8d9 0%, #f5f5f5 71%, #fff 100%)}}html.theme--catppuccin-mocha .hero.is-dark,html.theme--catppuccin-mocha .content kbd.hero{background-color:#313244;color:#fff}html.theme--catppuccin-mocha .hero.is-dark a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-mocha .content kbd.hero a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-mocha .hero.is-dark strong,html.theme--catppuccin-mocha .content kbd.hero strong{color:inherit}html.theme--catppuccin-mocha .hero.is-dark .title,html.theme--catppuccin-mocha .content kbd.hero .title{color:#fff}html.theme--catppuccin-mocha .hero.is-dark .subtitle,html.theme--catppuccin-mocha .content kbd.hero .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-mocha .hero.is-dark .subtitle a:not(.button),html.theme--catppuccin-mocha .content kbd.hero .subtitle a:not(.button),html.theme--catppuccin-mocha .hero.is-dark .subtitle strong,html.theme--catppuccin-mocha .content kbd.hero .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .hero.is-dark .navbar-menu,html.theme--catppuccin-mocha .content kbd.hero .navbar-menu{background-color:#313244}}html.theme--catppuccin-mocha .hero.is-dark .navbar-item,html.theme--catppuccin-mocha .content kbd.hero .navbar-item,html.theme--catppuccin-mocha .hero.is-dark .navbar-link,html.theme--catppuccin-mocha .content kbd.hero .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-mocha .hero.is-dark a.navbar-item:hover,html.theme--catppuccin-mocha .content kbd.hero a.navbar-item:hover,html.theme--catppuccin-mocha .hero.is-dark a.navbar-item.is-active,html.theme--catppuccin-mocha .content kbd.hero a.navbar-item.is-active,html.theme--catppuccin-mocha .hero.is-dark .navbar-link:hover,html.theme--catppuccin-mocha .content kbd.hero .navbar-link:hover,html.theme--catppuccin-mocha .hero.is-dark .navbar-link.is-active,html.theme--catppuccin-mocha .content kbd.hero .navbar-link.is-active{background-color:#262735;color:#fff}html.theme--catppuccin-mocha .hero.is-dark .tabs a,html.theme--catppuccin-mocha .content kbd.hero .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-mocha .hero.is-dark .tabs a:hover,html.theme--catppuccin-mocha .content kbd.hero .tabs a:hover{opacity:1}html.theme--catppuccin-mocha .hero.is-dark .tabs li.is-active a,html.theme--catppuccin-mocha .content kbd.hero .tabs li.is-active a{color:#313244 !important;opacity:1}html.theme--catppuccin-mocha .hero.is-dark .tabs.is-boxed a,html.theme--catppuccin-mocha .content kbd.hero .tabs.is-boxed a,html.theme--catppuccin-mocha .hero.is-dark .tabs.is-toggle a,html.theme--catppuccin-mocha .content kbd.hero .tabs.is-toggle a{color:#fff}html.theme--catppuccin-mocha .hero.is-dark .tabs.is-boxed a:hover,html.theme--catppuccin-mocha .content kbd.hero .tabs.is-boxed a:hover,html.theme--catppuccin-mocha .hero.is-dark .tabs.is-toggle a:hover,html.theme--catppuccin-mocha .content kbd.hero .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-mocha .hero.is-dark .tabs.is-boxed li.is-active a,html.theme--catppuccin-mocha .content kbd.hero .tabs.is-boxed li.is-active a,html.theme--catppuccin-mocha .hero.is-dark .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-mocha .hero.is-dark .tabs.is-toggle li.is-active a,html.theme--catppuccin-mocha .content kbd.hero .tabs.is-toggle li.is-active a,html.theme--catppuccin-mocha .hero.is-dark .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#313244}html.theme--catppuccin-mocha .hero.is-dark.is-bold,html.theme--catppuccin-mocha .content kbd.hero.is-bold{background-image:linear-gradient(141deg, #181c2a 0%, #313244 71%, #3c3856 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .hero.is-dark.is-bold .navbar-menu,html.theme--catppuccin-mocha .content kbd.hero.is-bold .navbar-menu{background-image:linear-gradient(141deg, #181c2a 0%, #313244 71%, #3c3856 100%)}}html.theme--catppuccin-mocha .hero.is-primary,html.theme--catppuccin-mocha .docstring>section>a.hero.docs-sourcelink{background-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .hero.is-primary a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-mocha .docstring>section>a.hero.docs-sourcelink a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-mocha .hero.is-primary strong,html.theme--catppuccin-mocha .docstring>section>a.hero.docs-sourcelink strong{color:inherit}html.theme--catppuccin-mocha .hero.is-primary .title,html.theme--catppuccin-mocha .docstring>section>a.hero.docs-sourcelink .title{color:#fff}html.theme--catppuccin-mocha .hero.is-primary .subtitle,html.theme--catppuccin-mocha .docstring>section>a.hero.docs-sourcelink .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-mocha .hero.is-primary .subtitle a:not(.button),html.theme--catppuccin-mocha .docstring>section>a.hero.docs-sourcelink .subtitle a:not(.button),html.theme--catppuccin-mocha .hero.is-primary .subtitle strong,html.theme--catppuccin-mocha .docstring>section>a.hero.docs-sourcelink .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .hero.is-primary .navbar-menu,html.theme--catppuccin-mocha .docstring>section>a.hero.docs-sourcelink .navbar-menu{background-color:#89b4fa}}html.theme--catppuccin-mocha .hero.is-primary .navbar-item,html.theme--catppuccin-mocha .docstring>section>a.hero.docs-sourcelink .navbar-item,html.theme--catppuccin-mocha .hero.is-primary .navbar-link,html.theme--catppuccin-mocha .docstring>section>a.hero.docs-sourcelink .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-mocha .hero.is-primary a.navbar-item:hover,html.theme--catppuccin-mocha .docstring>section>a.hero.docs-sourcelink a.navbar-item:hover,html.theme--catppuccin-mocha .hero.is-primary a.navbar-item.is-active,html.theme--catppuccin-mocha .docstring>section>a.hero.docs-sourcelink a.navbar-item.is-active,html.theme--catppuccin-mocha .hero.is-primary .navbar-link:hover,html.theme--catppuccin-mocha .docstring>section>a.hero.docs-sourcelink .navbar-link:hover,html.theme--catppuccin-mocha .hero.is-primary .navbar-link.is-active,html.theme--catppuccin-mocha .docstring>section>a.hero.docs-sourcelink .navbar-link.is-active{background-color:#71a4f9;color:#fff}html.theme--catppuccin-mocha .hero.is-primary .tabs a,html.theme--catppuccin-mocha .docstring>section>a.hero.docs-sourcelink .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-mocha .hero.is-primary .tabs a:hover,html.theme--catppuccin-mocha .docstring>section>a.hero.docs-sourcelink .tabs a:hover{opacity:1}html.theme--catppuccin-mocha .hero.is-primary .tabs li.is-active a,html.theme--catppuccin-mocha .docstring>section>a.hero.docs-sourcelink .tabs li.is-active a{color:#89b4fa !important;opacity:1}html.theme--catppuccin-mocha .hero.is-primary .tabs.is-boxed a,html.theme--catppuccin-mocha .docstring>section>a.hero.docs-sourcelink .tabs.is-boxed a,html.theme--catppuccin-mocha .hero.is-primary .tabs.is-toggle a,html.theme--catppuccin-mocha .docstring>section>a.hero.docs-sourcelink .tabs.is-toggle a{color:#fff}html.theme--catppuccin-mocha .hero.is-primary .tabs.is-boxed a:hover,html.theme--catppuccin-mocha .docstring>section>a.hero.docs-sourcelink .tabs.is-boxed a:hover,html.theme--catppuccin-mocha .hero.is-primary .tabs.is-toggle a:hover,html.theme--catppuccin-mocha .docstring>section>a.hero.docs-sourcelink .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-mocha .hero.is-primary .tabs.is-boxed li.is-active a,html.theme--catppuccin-mocha .docstring>section>a.hero.docs-sourcelink .tabs.is-boxed li.is-active a,html.theme--catppuccin-mocha .hero.is-primary .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-mocha .hero.is-primary .tabs.is-toggle li.is-active a,html.theme--catppuccin-mocha .docstring>section>a.hero.docs-sourcelink .tabs.is-toggle li.is-active a,html.theme--catppuccin-mocha .hero.is-primary .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#89b4fa}html.theme--catppuccin-mocha .hero.is-primary.is-bold,html.theme--catppuccin-mocha .docstring>section>a.hero.is-bold.docs-sourcelink{background-image:linear-gradient(141deg, #51b0ff 0%, #89b4fa 71%, #9fb3fd 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .hero.is-primary.is-bold .navbar-menu,html.theme--catppuccin-mocha .docstring>section>a.hero.is-bold.docs-sourcelink .navbar-menu{background-image:linear-gradient(141deg, #51b0ff 0%, #89b4fa 71%, #9fb3fd 100%)}}html.theme--catppuccin-mocha .hero.is-link{background-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .hero.is-link a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-mocha .hero.is-link strong{color:inherit}html.theme--catppuccin-mocha .hero.is-link .title{color:#fff}html.theme--catppuccin-mocha .hero.is-link .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-mocha .hero.is-link .subtitle a:not(.button),html.theme--catppuccin-mocha .hero.is-link .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .hero.is-link .navbar-menu{background-color:#89b4fa}}html.theme--catppuccin-mocha .hero.is-link .navbar-item,html.theme--catppuccin-mocha .hero.is-link .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-mocha .hero.is-link a.navbar-item:hover,html.theme--catppuccin-mocha .hero.is-link a.navbar-item.is-active,html.theme--catppuccin-mocha .hero.is-link .navbar-link:hover,html.theme--catppuccin-mocha .hero.is-link .navbar-link.is-active{background-color:#71a4f9;color:#fff}html.theme--catppuccin-mocha .hero.is-link .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-mocha .hero.is-link .tabs a:hover{opacity:1}html.theme--catppuccin-mocha .hero.is-link .tabs li.is-active a{color:#89b4fa !important;opacity:1}html.theme--catppuccin-mocha .hero.is-link .tabs.is-boxed a,html.theme--catppuccin-mocha .hero.is-link .tabs.is-toggle a{color:#fff}html.theme--catppuccin-mocha .hero.is-link .tabs.is-boxed a:hover,html.theme--catppuccin-mocha .hero.is-link .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-mocha .hero.is-link .tabs.is-boxed li.is-active a,html.theme--catppuccin-mocha .hero.is-link .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-mocha .hero.is-link .tabs.is-toggle li.is-active a,html.theme--catppuccin-mocha .hero.is-link .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#89b4fa}html.theme--catppuccin-mocha .hero.is-link.is-bold{background-image:linear-gradient(141deg, #51b0ff 0%, #89b4fa 71%, #9fb3fd 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .hero.is-link.is-bold .navbar-menu{background-image:linear-gradient(141deg, #51b0ff 0%, #89b4fa 71%, #9fb3fd 100%)}}html.theme--catppuccin-mocha .hero.is-info{background-color:#94e2d5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-info a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-mocha .hero.is-info strong{color:inherit}html.theme--catppuccin-mocha .hero.is-info .title{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-info .subtitle{color:rgba(0,0,0,0.9)}html.theme--catppuccin-mocha .hero.is-info .subtitle a:not(.button),html.theme--catppuccin-mocha .hero.is-info .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .hero.is-info .navbar-menu{background-color:#94e2d5}}html.theme--catppuccin-mocha .hero.is-info .navbar-item,html.theme--catppuccin-mocha .hero.is-info .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-info a.navbar-item:hover,html.theme--catppuccin-mocha .hero.is-info a.navbar-item.is-active,html.theme--catppuccin-mocha .hero.is-info .navbar-link:hover,html.theme--catppuccin-mocha .hero.is-info .navbar-link.is-active{background-color:#80ddcd;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-info .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}html.theme--catppuccin-mocha .hero.is-info .tabs a:hover{opacity:1}html.theme--catppuccin-mocha .hero.is-info .tabs li.is-active a{color:#94e2d5 !important;opacity:1}html.theme--catppuccin-mocha .hero.is-info .tabs.is-boxed a,html.theme--catppuccin-mocha .hero.is-info .tabs.is-toggle a{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-info .tabs.is-boxed a:hover,html.theme--catppuccin-mocha .hero.is-info .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-mocha .hero.is-info .tabs.is-boxed li.is-active a,html.theme--catppuccin-mocha .hero.is-info .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-mocha .hero.is-info .tabs.is-toggle li.is-active a,html.theme--catppuccin-mocha .hero.is-info .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#94e2d5}html.theme--catppuccin-mocha .hero.is-info.is-bold{background-image:linear-gradient(141deg, #63e0b6 0%, #94e2d5 71%, #a5eaea 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .hero.is-info.is-bold .navbar-menu{background-image:linear-gradient(141deg, #63e0b6 0%, #94e2d5 71%, #a5eaea 100%)}}html.theme--catppuccin-mocha .hero.is-success{background-color:#a6e3a1;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-success a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-mocha .hero.is-success strong{color:inherit}html.theme--catppuccin-mocha .hero.is-success .title{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-success .subtitle{color:rgba(0,0,0,0.9)}html.theme--catppuccin-mocha .hero.is-success .subtitle a:not(.button),html.theme--catppuccin-mocha .hero.is-success .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .hero.is-success .navbar-menu{background-color:#a6e3a1}}html.theme--catppuccin-mocha .hero.is-success .navbar-item,html.theme--catppuccin-mocha .hero.is-success .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-success a.navbar-item:hover,html.theme--catppuccin-mocha .hero.is-success a.navbar-item.is-active,html.theme--catppuccin-mocha .hero.is-success .navbar-link:hover,html.theme--catppuccin-mocha .hero.is-success .navbar-link.is-active{background-color:#93dd8d;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-success .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}html.theme--catppuccin-mocha .hero.is-success .tabs a:hover{opacity:1}html.theme--catppuccin-mocha .hero.is-success .tabs li.is-active a{color:#a6e3a1 !important;opacity:1}html.theme--catppuccin-mocha .hero.is-success .tabs.is-boxed a,html.theme--catppuccin-mocha .hero.is-success .tabs.is-toggle a{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-success .tabs.is-boxed a:hover,html.theme--catppuccin-mocha .hero.is-success .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-mocha .hero.is-success .tabs.is-boxed li.is-active a,html.theme--catppuccin-mocha .hero.is-success .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-mocha .hero.is-success .tabs.is-toggle li.is-active a,html.theme--catppuccin-mocha .hero.is-success .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#a6e3a1}html.theme--catppuccin-mocha .hero.is-success.is-bold{background-image:linear-gradient(141deg, #8ce071 0%, #a6e3a1 71%, #b2ebb7 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .hero.is-success.is-bold .navbar-menu{background-image:linear-gradient(141deg, #8ce071 0%, #a6e3a1 71%, #b2ebb7 100%)}}html.theme--catppuccin-mocha .hero.is-warning{background-color:#f9e2af;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-warning a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-mocha .hero.is-warning strong{color:inherit}html.theme--catppuccin-mocha .hero.is-warning .title{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-warning .subtitle{color:rgba(0,0,0,0.9)}html.theme--catppuccin-mocha .hero.is-warning .subtitle a:not(.button),html.theme--catppuccin-mocha .hero.is-warning .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .hero.is-warning .navbar-menu{background-color:#f9e2af}}html.theme--catppuccin-mocha .hero.is-warning .navbar-item,html.theme--catppuccin-mocha .hero.is-warning .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-warning a.navbar-item:hover,html.theme--catppuccin-mocha .hero.is-warning a.navbar-item.is-active,html.theme--catppuccin-mocha .hero.is-warning .navbar-link:hover,html.theme--catppuccin-mocha .hero.is-warning .navbar-link.is-active{background-color:#f7d997;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-warning .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}html.theme--catppuccin-mocha .hero.is-warning .tabs a:hover{opacity:1}html.theme--catppuccin-mocha .hero.is-warning .tabs li.is-active a{color:#f9e2af !important;opacity:1}html.theme--catppuccin-mocha .hero.is-warning .tabs.is-boxed a,html.theme--catppuccin-mocha .hero.is-warning .tabs.is-toggle a{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-warning .tabs.is-boxed a:hover,html.theme--catppuccin-mocha .hero.is-warning .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-mocha .hero.is-warning .tabs.is-boxed li.is-active a,html.theme--catppuccin-mocha .hero.is-warning .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-mocha .hero.is-warning .tabs.is-toggle li.is-active a,html.theme--catppuccin-mocha .hero.is-warning .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#f9e2af}html.theme--catppuccin-mocha .hero.is-warning.is-bold{background-image:linear-gradient(141deg, #fcbd79 0%, #f9e2af 71%, #fcf4c5 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .hero.is-warning.is-bold .navbar-menu{background-image:linear-gradient(141deg, #fcbd79 0%, #f9e2af 71%, #fcf4c5 100%)}}html.theme--catppuccin-mocha .hero.is-danger{background-color:#f38ba8;color:#fff}html.theme--catppuccin-mocha .hero.is-danger a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-mocha .hero.is-danger strong{color:inherit}html.theme--catppuccin-mocha .hero.is-danger .title{color:#fff}html.theme--catppuccin-mocha .hero.is-danger .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-mocha .hero.is-danger .subtitle a:not(.button),html.theme--catppuccin-mocha .hero.is-danger .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .hero.is-danger .navbar-menu{background-color:#f38ba8}}html.theme--catppuccin-mocha .hero.is-danger .navbar-item,html.theme--catppuccin-mocha .hero.is-danger .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-mocha .hero.is-danger a.navbar-item:hover,html.theme--catppuccin-mocha .hero.is-danger a.navbar-item.is-active,html.theme--catppuccin-mocha .hero.is-danger .navbar-link:hover,html.theme--catppuccin-mocha .hero.is-danger .navbar-link.is-active{background-color:#f17497;color:#fff}html.theme--catppuccin-mocha .hero.is-danger .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-mocha .hero.is-danger .tabs a:hover{opacity:1}html.theme--catppuccin-mocha .hero.is-danger .tabs li.is-active a{color:#f38ba8 !important;opacity:1}html.theme--catppuccin-mocha .hero.is-danger .tabs.is-boxed a,html.theme--catppuccin-mocha .hero.is-danger .tabs.is-toggle a{color:#fff}html.theme--catppuccin-mocha .hero.is-danger .tabs.is-boxed a:hover,html.theme--catppuccin-mocha .hero.is-danger .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-mocha .hero.is-danger .tabs.is-boxed li.is-active a,html.theme--catppuccin-mocha .hero.is-danger .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-mocha .hero.is-danger .tabs.is-toggle li.is-active a,html.theme--catppuccin-mocha .hero.is-danger .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#f38ba8}html.theme--catppuccin-mocha .hero.is-danger.is-bold{background-image:linear-gradient(141deg, #f7549d 0%, #f38ba8 71%, #f8a0a9 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .hero.is-danger.is-bold .navbar-menu{background-image:linear-gradient(141deg, #f7549d 0%, #f38ba8 71%, #f8a0a9 100%)}}html.theme--catppuccin-mocha .hero.is-small .hero-body,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.hero .hero-body{padding:1.5rem}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .hero.is-medium .hero-body{padding:9rem 4.5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .hero.is-large .hero-body{padding:18rem 6rem}}html.theme--catppuccin-mocha .hero.is-halfheight .hero-body,html.theme--catppuccin-mocha .hero.is-fullheight .hero-body,html.theme--catppuccin-mocha .hero.is-fullheight-with-navbar .hero-body{align-items:center;display:flex}html.theme--catppuccin-mocha .hero.is-halfheight .hero-body>.container,html.theme--catppuccin-mocha .hero.is-fullheight .hero-body>.container,html.theme--catppuccin-mocha .hero.is-fullheight-with-navbar .hero-body>.container{flex-grow:1;flex-shrink:1}html.theme--catppuccin-mocha .hero.is-halfheight{min-height:50vh}html.theme--catppuccin-mocha .hero.is-fullheight{min-height:100vh}html.theme--catppuccin-mocha .hero-video{overflow:hidden}html.theme--catppuccin-mocha .hero-video video{left:50%;min-height:100%;min-width:100%;position:absolute;top:50%;transform:translate3d(-50%, -50%, 0)}html.theme--catppuccin-mocha .hero-video.is-transparent{opacity:0.3}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .hero-video{display:none}}html.theme--catppuccin-mocha .hero-buttons{margin-top:1.5rem}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .hero-buttons .button{display:flex}html.theme--catppuccin-mocha .hero-buttons .button:not(:last-child){margin-bottom:0.75rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .hero-buttons{display:flex;justify-content:center}html.theme--catppuccin-mocha .hero-buttons .button:not(:last-child){margin-right:1.5rem}}html.theme--catppuccin-mocha .hero-head,html.theme--catppuccin-mocha .hero-foot{flex-grow:0;flex-shrink:0}html.theme--catppuccin-mocha .hero-body{flex-grow:1;flex-shrink:0;padding:3rem 1.5rem}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .hero-body{padding:3rem 3rem}}html.theme--catppuccin-mocha .section{padding:3rem 1.5rem}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .section{padding:3rem 3rem}html.theme--catppuccin-mocha .section.is-medium{padding:9rem 4.5rem}html.theme--catppuccin-mocha .section.is-large{padding:18rem 6rem}}html.theme--catppuccin-mocha .footer{background-color:#181825;padding:3rem 1.5rem 6rem}html.theme--catppuccin-mocha h1 .docs-heading-anchor,html.theme--catppuccin-mocha h1 .docs-heading-anchor:hover,html.theme--catppuccin-mocha h1 .docs-heading-anchor:visited,html.theme--catppuccin-mocha h2 .docs-heading-anchor,html.theme--catppuccin-mocha h2 .docs-heading-anchor:hover,html.theme--catppuccin-mocha h2 .docs-heading-anchor:visited,html.theme--catppuccin-mocha h3 .docs-heading-anchor,html.theme--catppuccin-mocha h3 .docs-heading-anchor:hover,html.theme--catppuccin-mocha h3 .docs-heading-anchor:visited,html.theme--catppuccin-mocha h4 .docs-heading-anchor,html.theme--catppuccin-mocha h4 .docs-heading-anchor:hover,html.theme--catppuccin-mocha h4 .docs-heading-anchor:visited,html.theme--catppuccin-mocha h5 .docs-heading-anchor,html.theme--catppuccin-mocha h5 .docs-heading-anchor:hover,html.theme--catppuccin-mocha h5 .docs-heading-anchor:visited,html.theme--catppuccin-mocha h6 .docs-heading-anchor,html.theme--catppuccin-mocha h6 .docs-heading-anchor:hover,html.theme--catppuccin-mocha h6 .docs-heading-anchor:visited{color:#cdd6f4}html.theme--catppuccin-mocha h1 .docs-heading-anchor-permalink,html.theme--catppuccin-mocha h2 .docs-heading-anchor-permalink,html.theme--catppuccin-mocha h3 .docs-heading-anchor-permalink,html.theme--catppuccin-mocha h4 .docs-heading-anchor-permalink,html.theme--catppuccin-mocha h5 .docs-heading-anchor-permalink,html.theme--catppuccin-mocha h6 .docs-heading-anchor-permalink{visibility:hidden;vertical-align:middle;margin-left:0.5em;font-size:0.7rem}html.theme--catppuccin-mocha h1 .docs-heading-anchor-permalink::before,html.theme--catppuccin-mocha h2 .docs-heading-anchor-permalink::before,html.theme--catppuccin-mocha h3 .docs-heading-anchor-permalink::before,html.theme--catppuccin-mocha h4 .docs-heading-anchor-permalink::before,html.theme--catppuccin-mocha h5 .docs-heading-anchor-permalink::before,html.theme--catppuccin-mocha h6 .docs-heading-anchor-permalink::before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f0c1"}html.theme--catppuccin-mocha h1:hover .docs-heading-anchor-permalink,html.theme--catppuccin-mocha h2:hover .docs-heading-anchor-permalink,html.theme--catppuccin-mocha h3:hover .docs-heading-anchor-permalink,html.theme--catppuccin-mocha h4:hover .docs-heading-anchor-permalink,html.theme--catppuccin-mocha h5:hover .docs-heading-anchor-permalink,html.theme--catppuccin-mocha h6:hover .docs-heading-anchor-permalink{visibility:visible}html.theme--catppuccin-mocha .docs-light-only{display:none !important}html.theme--catppuccin-mocha pre{position:relative;overflow:hidden}html.theme--catppuccin-mocha pre code,html.theme--catppuccin-mocha pre code.hljs{padding:0 .75rem !important;overflow:auto;display:block}html.theme--catppuccin-mocha pre code:first-of-type,html.theme--catppuccin-mocha pre code.hljs:first-of-type{padding-top:0.5rem !important}html.theme--catppuccin-mocha pre code:last-of-type,html.theme--catppuccin-mocha pre code.hljs:last-of-type{padding-bottom:0.5rem !important}html.theme--catppuccin-mocha pre .copy-button{opacity:0.2;transition:opacity 0.2s;position:absolute;right:0em;top:0em;padding:0.5em;width:2.5em;height:2.5em;background:transparent;border:none;font-family:"Font Awesome 6 Free";color:#cdd6f4;cursor:pointer;text-align:center}html.theme--catppuccin-mocha pre .copy-button:focus,html.theme--catppuccin-mocha pre .copy-button:hover{opacity:1;background:rgba(205,214,244,0.1);color:#89b4fa}html.theme--catppuccin-mocha pre .copy-button.success{color:#a6e3a1;opacity:1}html.theme--catppuccin-mocha pre .copy-button.error{color:#f38ba8;opacity:1}html.theme--catppuccin-mocha pre:hover .copy-button{opacity:1}html.theme--catppuccin-mocha .admonition{background-color:#181825;border-style:solid;border-width:2px;border-color:#bac2de;border-radius:4px;font-size:1rem}html.theme--catppuccin-mocha .admonition strong{color:currentColor}html.theme--catppuccin-mocha .admonition.is-small,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.admonition{font-size:.75rem}html.theme--catppuccin-mocha .admonition.is-medium{font-size:1.25rem}html.theme--catppuccin-mocha .admonition.is-large{font-size:1.5rem}html.theme--catppuccin-mocha .admonition.is-default{background-color:#181825;border-color:#bac2de}html.theme--catppuccin-mocha .admonition.is-default>.admonition-header{background-color:rgba(0,0,0,0);color:#bac2de}html.theme--catppuccin-mocha .admonition.is-default>.admonition-body{color:#cdd6f4}html.theme--catppuccin-mocha .admonition.is-info{background-color:#181825;border-color:#94e2d5}html.theme--catppuccin-mocha .admonition.is-info>.admonition-header{background-color:rgba(0,0,0,0);color:#94e2d5}html.theme--catppuccin-mocha .admonition.is-info>.admonition-body{color:#cdd6f4}html.theme--catppuccin-mocha .admonition.is-success{background-color:#181825;border-color:#a6e3a1}html.theme--catppuccin-mocha .admonition.is-success>.admonition-header{background-color:rgba(0,0,0,0);color:#a6e3a1}html.theme--catppuccin-mocha .admonition.is-success>.admonition-body{color:#cdd6f4}html.theme--catppuccin-mocha .admonition.is-warning{background-color:#181825;border-color:#f9e2af}html.theme--catppuccin-mocha .admonition.is-warning>.admonition-header{background-color:rgba(0,0,0,0);color:#f9e2af}html.theme--catppuccin-mocha .admonition.is-warning>.admonition-body{color:#cdd6f4}html.theme--catppuccin-mocha .admonition.is-danger{background-color:#181825;border-color:#f38ba8}html.theme--catppuccin-mocha .admonition.is-danger>.admonition-header{background-color:rgba(0,0,0,0);color:#f38ba8}html.theme--catppuccin-mocha .admonition.is-danger>.admonition-body{color:#cdd6f4}html.theme--catppuccin-mocha .admonition.is-compat{background-color:#181825;border-color:#89dceb}html.theme--catppuccin-mocha .admonition.is-compat>.admonition-header{background-color:rgba(0,0,0,0);color:#89dceb}html.theme--catppuccin-mocha .admonition.is-compat>.admonition-body{color:#cdd6f4}html.theme--catppuccin-mocha .admonition.is-todo{background-color:#181825;border-color:#cba6f7}html.theme--catppuccin-mocha .admonition.is-todo>.admonition-header{background-color:rgba(0,0,0,0);color:#cba6f7}html.theme--catppuccin-mocha .admonition.is-todo>.admonition-body{color:#cdd6f4}html.theme--catppuccin-mocha .admonition-header{color:#bac2de;background-color:rgba(0,0,0,0);align-items:center;font-weight:700;justify-content:space-between;line-height:1.25;padding:0.5rem .75rem;position:relative}html.theme--catppuccin-mocha .admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;margin-right:.75rem;content:"\f06a"}html.theme--catppuccin-mocha details.admonition.is-details>.admonition-header{list-style:none}html.theme--catppuccin-mocha details.admonition.is-details>.admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f055"}html.theme--catppuccin-mocha details.admonition.is-details[open]>.admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f056"}html.theme--catppuccin-mocha .admonition-body{color:#cdd6f4;padding:0.5rem .75rem}html.theme--catppuccin-mocha .admonition-body pre{background-color:#181825}html.theme--catppuccin-mocha .admonition-body code{background-color:#181825}html.theme--catppuccin-mocha .docstring{margin-bottom:1em;background-color:rgba(0,0,0,0);border:2px solid #585b70;border-radius:4px;box-shadow:none;max-width:100%}html.theme--catppuccin-mocha .docstring>header{cursor:pointer;display:flex;flex-grow:1;align-items:stretch;padding:0.5rem .75rem;background-color:#181825;box-shadow:0 0.125em 0.25em rgba(10,10,10,0.1);box-shadow:none;border-bottom:1px solid #585b70;overflow:auto}html.theme--catppuccin-mocha .docstring>header code{background-color:transparent}html.theme--catppuccin-mocha .docstring>header .docstring-article-toggle-button{min-width:1.1rem;padding:0.2rem 0.2rem 0.2rem 0}html.theme--catppuccin-mocha .docstring>header .docstring-binding{margin-right:0.3em}html.theme--catppuccin-mocha .docstring>header .docstring-category{margin-left:0.3em}html.theme--catppuccin-mocha .docstring>section{position:relative;padding:.75rem .75rem;border-bottom:1px solid #585b70}html.theme--catppuccin-mocha .docstring>section:last-child{border-bottom:none}html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink{transition:opacity 0.3s;opacity:0;position:absolute;right:.375rem;bottom:.375rem}html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink:focus{opacity:1 !important}html.theme--catppuccin-mocha .docstring:hover>section>a.docs-sourcelink{opacity:0.2}html.theme--catppuccin-mocha .docstring:focus-within>section>a.docs-sourcelink{opacity:0.2}html.theme--catppuccin-mocha .docstring>section:hover a.docs-sourcelink{opacity:1}html.theme--catppuccin-mocha .documenter-example-output{background-color:#1e1e2e}html.theme--catppuccin-mocha .outdated-warning-overlay{position:fixed;top:0;left:0;right:0;box-shadow:0 0 10px rgba(0,0,0,0.3);z-index:999;background-color:#181825;color:#cdd6f4;border-bottom:3px solid rgba(0,0,0,0);padding:10px 35px;text-align:center;font-size:15px}html.theme--catppuccin-mocha .outdated-warning-overlay .outdated-warning-closer{position:absolute;top:calc(50% - 10px);right:18px;cursor:pointer;width:12px}html.theme--catppuccin-mocha .outdated-warning-overlay a{color:#89b4fa}html.theme--catppuccin-mocha .outdated-warning-overlay a:hover{color:#89dceb}html.theme--catppuccin-mocha .content pre{border:2px solid #585b70;border-radius:4px}html.theme--catppuccin-mocha .content code{font-weight:inherit}html.theme--catppuccin-mocha .content a code{color:#89b4fa}html.theme--catppuccin-mocha .content a:hover code{color:#89dceb}html.theme--catppuccin-mocha .content h1 code,html.theme--catppuccin-mocha .content h2 code,html.theme--catppuccin-mocha .content h3 code,html.theme--catppuccin-mocha .content h4 code,html.theme--catppuccin-mocha .content h5 code,html.theme--catppuccin-mocha .content h6 code{color:#cdd6f4}html.theme--catppuccin-mocha .content table{display:block;width:initial;max-width:100%;overflow-x:auto}html.theme--catppuccin-mocha .content blockquote>ul:first-child,html.theme--catppuccin-mocha .content blockquote>ol:first-child,html.theme--catppuccin-mocha .content .admonition-body>ul:first-child,html.theme--catppuccin-mocha .content .admonition-body>ol:first-child{margin-top:0}html.theme--catppuccin-mocha pre,html.theme--catppuccin-mocha code{font-variant-ligatures:no-contextual}html.theme--catppuccin-mocha .breadcrumb a.is-disabled{cursor:default;pointer-events:none}html.theme--catppuccin-mocha .breadcrumb a.is-disabled,html.theme--catppuccin-mocha .breadcrumb a.is-disabled:hover{color:#b8c5ef}html.theme--catppuccin-mocha .hljs{background:initial !important}html.theme--catppuccin-mocha .katex .katex-mathml{top:0;right:0}html.theme--catppuccin-mocha .katex-display,html.theme--catppuccin-mocha mjx-container,html.theme--catppuccin-mocha .MathJax_Display{margin:0.5em 0 !important}html.theme--catppuccin-mocha html{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto}html.theme--catppuccin-mocha li.no-marker{list-style:none}html.theme--catppuccin-mocha #documenter .docs-main>article{overflow-wrap:break-word}html.theme--catppuccin-mocha #documenter .docs-main>article .math-container{overflow-x:auto;overflow-y:hidden}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha #documenter .docs-main{max-width:52rem;margin-left:20rem;padding-right:1rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha #documenter .docs-main{width:100%}html.theme--catppuccin-mocha #documenter .docs-main>article{max-width:52rem;margin-left:auto;margin-right:auto;margin-bottom:1rem;padding:0 1rem}html.theme--catppuccin-mocha #documenter .docs-main>header,html.theme--catppuccin-mocha #documenter .docs-main>nav{max-width:100%;width:100%;margin:0}}html.theme--catppuccin-mocha #documenter .docs-main header.docs-navbar{background-color:#1e1e2e;border-bottom:1px solid #585b70;z-index:2;min-height:4rem;margin-bottom:1rem;display:flex}html.theme--catppuccin-mocha #documenter .docs-main header.docs-navbar .breadcrumb{flex-grow:1;overflow-x:hidden}html.theme--catppuccin-mocha #documenter .docs-main header.docs-navbar .docs-sidebar-button{display:block;font-size:1.5rem;padding-bottom:0.1rem;margin-right:1rem}html.theme--catppuccin-mocha #documenter .docs-main header.docs-navbar .docs-right{display:flex;white-space:nowrap;gap:1rem;align-items:center}html.theme--catppuccin-mocha #documenter .docs-main header.docs-navbar .docs-right .docs-icon,html.theme--catppuccin-mocha #documenter .docs-main header.docs-navbar .docs-right .docs-label{display:inline-block}html.theme--catppuccin-mocha #documenter .docs-main header.docs-navbar .docs-right .docs-label{padding:0;margin-left:0.3em}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha #documenter .docs-main header.docs-navbar .docs-right .docs-navbar-link{margin-left:0.4rem;margin-right:0.4rem}}html.theme--catppuccin-mocha #documenter .docs-main header.docs-navbar>*{margin:auto 0}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha #documenter .docs-main header.docs-navbar{position:sticky;top:0;padding:0 1rem;transition-property:top, box-shadow;-webkit-transition-property:top, box-shadow;transition-duration:0.3s;-webkit-transition-duration:0.3s}html.theme--catppuccin-mocha #documenter .docs-main header.docs-navbar.headroom--not-top{box-shadow:.2rem 0rem .4rem #171717;transition-duration:0.7s;-webkit-transition-duration:0.7s}html.theme--catppuccin-mocha #documenter .docs-main header.docs-navbar.headroom--unpinned.headroom--not-top.headroom--not-bottom{top:-4.5rem;transition-duration:0.7s;-webkit-transition-duration:0.7s}}html.theme--catppuccin-mocha #documenter .docs-main section.footnotes{border-top:1px solid #585b70}html.theme--catppuccin-mocha #documenter .docs-main section.footnotes li .tag:first-child,html.theme--catppuccin-mocha #documenter .docs-main section.footnotes li .docstring>section>a.docs-sourcelink:first-child,html.theme--catppuccin-mocha #documenter .docs-main section.footnotes li .content kbd:first-child,html.theme--catppuccin-mocha .content #documenter .docs-main section.footnotes li kbd:first-child{margin-right:1em;margin-bottom:0.4em}html.theme--catppuccin-mocha #documenter .docs-main .docs-footer{display:flex;flex-wrap:wrap;margin-left:0;margin-right:0;border-top:1px solid #585b70;padding-top:1rem;padding-bottom:1rem}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha #documenter .docs-main .docs-footer{padding-left:1rem;padding-right:1rem}}html.theme--catppuccin-mocha #documenter .docs-main .docs-footer .docs-footer-nextpage,html.theme--catppuccin-mocha #documenter .docs-main .docs-footer .docs-footer-prevpage{flex-grow:1}html.theme--catppuccin-mocha #documenter .docs-main .docs-footer .docs-footer-nextpage{text-align:right}html.theme--catppuccin-mocha #documenter .docs-main .docs-footer .flexbox-break{flex-basis:100%;height:0}html.theme--catppuccin-mocha #documenter .docs-main .docs-footer .footer-message{font-size:0.8em;margin:0.5em auto 0 auto;text-align:center}html.theme--catppuccin-mocha #documenter .docs-sidebar{display:flex;flex-direction:column;color:#cdd6f4;background-color:#181825;border-right:1px solid #585b70;padding:0;flex:0 0 18rem;z-index:5;font-size:1rem;position:fixed;left:-18rem;width:18rem;height:100%;transition:left 0.3s}html.theme--catppuccin-mocha #documenter .docs-sidebar.visible{left:0;box-shadow:.4rem 0rem .8rem #171717}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha #documenter .docs-sidebar.visible{box-shadow:none}}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha #documenter .docs-sidebar{left:0;top:0}}html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo{margin-top:1rem;padding:0 1rem}html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img{max-height:6rem;margin:auto}html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-package-name{flex-shrink:0;font-size:1.5rem;font-weight:700;text-align:center;white-space:nowrap;overflow:hidden;padding:0.5rem 0}html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-package-name .docs-autofit{max-width:16.2rem}html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-package-name a,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-package-name a:hover{color:#cdd6f4}html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-version-selector{border-top:1px solid #585b70;display:none;padding:0.5rem}html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-version-selector.visible{display:flex}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu{flex-grow:1;user-select:none;border-top:1px solid #585b70;padding-bottom:1.5rem}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu>li>.tocitem{font-weight:bold}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu>li li{font-size:.95rem;margin-left:1em;border-left:1px solid #585b70}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu input.collapse-toggle{display:none}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu ul.collapsed{display:none}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu input:checked~ul.collapsed{display:block}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu label.tocitem{display:flex}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu label.tocitem .docs-label{flex-grow:2}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu label.tocitem .docs-chevron{display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1;font-size:.75rem;margin-left:1rem;margin-top:auto;margin-bottom:auto}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu label.tocitem .docs-chevron::before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f054"}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu input:checked~label.tocitem .docs-chevron::before{content:"\f078"}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu .tocitem{display:block;padding:0.5rem 0.5rem}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu .tocitem,html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu .tocitem:hover{color:#cdd6f4;background:#181825}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu a.tocitem:hover,html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu label.tocitem:hover{color:#cdd6f4;background-color:#202031}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu li.is-active{border-top:1px solid #585b70;border-bottom:1px solid #585b70;background-color:#11111b}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu li.is-active .tocitem,html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu li.is-active .tocitem:hover{background-color:#11111b;color:#cdd6f4}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu li.is-active ul.internal .tocitem:hover{background-color:#202031;color:#cdd6f4}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu>li.is-active:first-child{border-top:none}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu ul.internal{margin:0 0.5rem 0.5rem;border-top:1px solid #585b70}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu ul.internal li{font-size:.85rem;border-left:none;margin-left:0;margin-top:0.5rem}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu ul.internal .tocitem{width:100%;padding:0}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu ul.internal .tocitem::before{content:"⚬";margin-right:0.4em}html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search{margin:auto;margin-top:0.5rem;margin-bottom:0.5rem}html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input{width:14.4rem}html.theme--catppuccin-mocha #documenter .docs-sidebar #documenter-search-query{color:#868c98;width:14.4rem;box-shadow:inset 0 1px 2px rgba(10,10,10,0.1)}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu{overflow-y:auto;-webkit-overflow-scroll:touch}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar{width:.3rem;background:none}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar-thumb{border-radius:5px 0px 0px 5px;background:#28283e}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar-thumb:hover{background:#383856}}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha #documenter .docs-sidebar{overflow-y:auto;-webkit-overflow-scroll:touch}html.theme--catppuccin-mocha #documenter .docs-sidebar::-webkit-scrollbar{width:.3rem;background:none}html.theme--catppuccin-mocha #documenter .docs-sidebar::-webkit-scrollbar-thumb{border-radius:5px 0px 0px 5px;background:#28283e}html.theme--catppuccin-mocha #documenter .docs-sidebar::-webkit-scrollbar-thumb:hover{background:#383856}}html.theme--catppuccin-mocha kbd.search-modal-key-hints{border-radius:0.25rem;border:1px solid rgba(245,245,245,0.6);box-shadow:0 2px 0 1px rgba(245,245,245,0.6);cursor:default;font-size:0.9rem;line-height:1.5;min-width:0.75rem;text-align:center;padding:0.1rem 0.3rem;position:relative;top:-1px}html.theme--catppuccin-mocha .search-min-width-50{min-width:50%}html.theme--catppuccin-mocha .search-min-height-100{min-height:100%}html.theme--catppuccin-mocha .search-modal-card-body{max-height:calc(100vh - 15rem)}html.theme--catppuccin-mocha .search-result-link{border-radius:0.7em;transition:all 300ms}html.theme--catppuccin-mocha .search-result-link:hover,html.theme--catppuccin-mocha .search-result-link:focus{background-color:rgba(0,128,128,0.1)}html.theme--catppuccin-mocha .search-result-link .property-search-result-badge,html.theme--catppuccin-mocha .search-result-link .search-filter{transition:all 300ms}html.theme--catppuccin-mocha .property-search-result-badge,html.theme--catppuccin-mocha .search-filter{padding:0.15em 0.5em;font-size:0.8em;font-style:italic;text-transform:none !important;line-height:1.5;color:#f5f5f5;background-color:rgba(51,65,85,0.501961);border-radius:0.6rem}html.theme--catppuccin-mocha .search-result-link:hover .property-search-result-badge,html.theme--catppuccin-mocha .search-result-link:hover .search-filter,html.theme--catppuccin-mocha .search-result-link:focus .property-search-result-badge,html.theme--catppuccin-mocha .search-result-link:focus .search-filter{color:#333;background-color:#f1f5f9}html.theme--catppuccin-mocha .search-filter{color:#333;background-color:#f5f5f5;transition:all 300ms}html.theme--catppuccin-mocha .search-filter:hover,html.theme--catppuccin-mocha .search-filter:focus{color:#333}html.theme--catppuccin-mocha .search-filter-selected{color:#313244;background-color:#b4befe}html.theme--catppuccin-mocha .search-filter-selected:hover,html.theme--catppuccin-mocha .search-filter-selected:focus{color:#313244}html.theme--catppuccin-mocha .search-result-highlight{background-color:#ffdd57;color:black}html.theme--catppuccin-mocha .search-divider{border-bottom:1px solid #585b70}html.theme--catppuccin-mocha .search-result-title{width:85%;color:#f5f5f5}html.theme--catppuccin-mocha .search-result-code-title{font-size:0.875rem;font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace}html.theme--catppuccin-mocha #search-modal .modal-card-body::-webkit-scrollbar,html.theme--catppuccin-mocha #search-modal .filter-tabs::-webkit-scrollbar{height:10px;width:10px;background-color:transparent}html.theme--catppuccin-mocha #search-modal .modal-card-body::-webkit-scrollbar-thumb,html.theme--catppuccin-mocha #search-modal .filter-tabs::-webkit-scrollbar-thumb{background-color:gray;border-radius:1rem}html.theme--catppuccin-mocha #search-modal .modal-card-body::-webkit-scrollbar-track,html.theme--catppuccin-mocha #search-modal .filter-tabs::-webkit-scrollbar-track{-webkit-box-shadow:inset 0 0 6px rgba(0,0,0,0.6);background-color:transparent}html.theme--catppuccin-mocha .w-100{width:100%}html.theme--catppuccin-mocha .gap-2{gap:0.5rem}html.theme--catppuccin-mocha .gap-4{gap:1rem}html.theme--catppuccin-mocha .gap-8{gap:2rem}html.theme--catppuccin-mocha{background-color:#1e1e2e;font-size:16px;min-width:300px;overflow-x:auto;overflow-y:scroll;text-rendering:optimizeLegibility;text-size-adjust:100%}html.theme--catppuccin-mocha a{transition:all 200ms ease}html.theme--catppuccin-mocha .label{color:#cdd6f4}html.theme--catppuccin-mocha .button,html.theme--catppuccin-mocha .control.has-icons-left .icon,html.theme--catppuccin-mocha .control.has-icons-right .icon,html.theme--catppuccin-mocha .input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-mocha .pagination-ellipsis,html.theme--catppuccin-mocha .pagination-link,html.theme--catppuccin-mocha .pagination-next,html.theme--catppuccin-mocha .pagination-previous,html.theme--catppuccin-mocha .select,html.theme--catppuccin-mocha .select select,html.theme--catppuccin-mocha .textarea{height:2.5em;color:#cdd6f4}html.theme--catppuccin-mocha .input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-mocha .textarea{transition:all 200ms ease;box-shadow:none;border-width:1px;padding-left:1em;padding-right:1em;color:#cdd6f4}html.theme--catppuccin-mocha .select:after,html.theme--catppuccin-mocha .select select{border-width:1px}html.theme--catppuccin-mocha .menu-list a{transition:all 300ms ease}html.theme--catppuccin-mocha .modal-card-foot,html.theme--catppuccin-mocha .modal-card-head{border-color:#585b70}html.theme--catppuccin-mocha .navbar{border-radius:.4em}html.theme--catppuccin-mocha .navbar.is-transparent{background:none}html.theme--catppuccin-mocha .navbar.is-primary .navbar-dropdown a.navbar-item.is-active,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-dropdown a.navbar-item.is-active{background-color:#89b4fa}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .navbar .navbar-menu{background-color:#89b4fa;border-radius:0 0 .4em .4em}}html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink:not(body){color:#313244}html.theme--catppuccin-mocha .tag.is-link:not(body),html.theme--catppuccin-mocha .docstring>section>a.is-link.docs-sourcelink:not(body),html.theme--catppuccin-mocha .content kbd.is-link:not(body){color:#313244}html.theme--catppuccin-mocha .ansi span.sgr1{font-weight:bolder}html.theme--catppuccin-mocha .ansi span.sgr2{font-weight:lighter}html.theme--catppuccin-mocha .ansi span.sgr3{font-style:italic}html.theme--catppuccin-mocha .ansi span.sgr4{text-decoration:underline}html.theme--catppuccin-mocha .ansi span.sgr7{color:#1e1e2e;background-color:#cdd6f4}html.theme--catppuccin-mocha .ansi span.sgr8{color:transparent}html.theme--catppuccin-mocha .ansi span.sgr8 span{color:transparent}html.theme--catppuccin-mocha .ansi span.sgr9{text-decoration:line-through}html.theme--catppuccin-mocha .ansi span.sgr30{color:#45475a}html.theme--catppuccin-mocha .ansi span.sgr31{color:#f38ba8}html.theme--catppuccin-mocha .ansi span.sgr32{color:#a6e3a1}html.theme--catppuccin-mocha .ansi span.sgr33{color:#f9e2af}html.theme--catppuccin-mocha .ansi span.sgr34{color:#89b4fa}html.theme--catppuccin-mocha .ansi span.sgr35{color:#f5c2e7}html.theme--catppuccin-mocha .ansi span.sgr36{color:#94e2d5}html.theme--catppuccin-mocha .ansi span.sgr37{color:#bac2de}html.theme--catppuccin-mocha .ansi span.sgr40{background-color:#45475a}html.theme--catppuccin-mocha .ansi span.sgr41{background-color:#f38ba8}html.theme--catppuccin-mocha .ansi span.sgr42{background-color:#a6e3a1}html.theme--catppuccin-mocha .ansi span.sgr43{background-color:#f9e2af}html.theme--catppuccin-mocha .ansi span.sgr44{background-color:#89b4fa}html.theme--catppuccin-mocha .ansi span.sgr45{background-color:#f5c2e7}html.theme--catppuccin-mocha .ansi span.sgr46{background-color:#94e2d5}html.theme--catppuccin-mocha .ansi span.sgr47{background-color:#bac2de}html.theme--catppuccin-mocha .ansi span.sgr90{color:#585b70}html.theme--catppuccin-mocha .ansi span.sgr91{color:#f38ba8}html.theme--catppuccin-mocha .ansi span.sgr92{color:#a6e3a1}html.theme--catppuccin-mocha .ansi span.sgr93{color:#f9e2af}html.theme--catppuccin-mocha .ansi span.sgr94{color:#89b4fa}html.theme--catppuccin-mocha .ansi span.sgr95{color:#f5c2e7}html.theme--catppuccin-mocha .ansi span.sgr96{color:#94e2d5}html.theme--catppuccin-mocha .ansi span.sgr97{color:#a6adc8}html.theme--catppuccin-mocha .ansi span.sgr100{background-color:#585b70}html.theme--catppuccin-mocha .ansi span.sgr101{background-color:#f38ba8}html.theme--catppuccin-mocha .ansi span.sgr102{background-color:#a6e3a1}html.theme--catppuccin-mocha .ansi span.sgr103{background-color:#f9e2af}html.theme--catppuccin-mocha .ansi span.sgr104{background-color:#89b4fa}html.theme--catppuccin-mocha .ansi span.sgr105{background-color:#f5c2e7}html.theme--catppuccin-mocha .ansi span.sgr106{background-color:#94e2d5}html.theme--catppuccin-mocha .ansi span.sgr107{background-color:#a6adc8}html.theme--catppuccin-mocha code.language-julia-repl>span.hljs-meta{color:#a6e3a1;font-weight:bolder}html.theme--catppuccin-mocha code .hljs{color:#cdd6f4;background:#1e1e2e}html.theme--catppuccin-mocha code .hljs-keyword{color:#cba6f7}html.theme--catppuccin-mocha code .hljs-built_in{color:#f38ba8}html.theme--catppuccin-mocha code .hljs-type{color:#f9e2af}html.theme--catppuccin-mocha code .hljs-literal{color:#fab387}html.theme--catppuccin-mocha code .hljs-number{color:#fab387}html.theme--catppuccin-mocha code .hljs-operator{color:#94e2d5}html.theme--catppuccin-mocha code .hljs-punctuation{color:#bac2de}html.theme--catppuccin-mocha code .hljs-property{color:#94e2d5}html.theme--catppuccin-mocha code .hljs-regexp{color:#f5c2e7}html.theme--catppuccin-mocha code .hljs-string{color:#a6e3a1}html.theme--catppuccin-mocha code .hljs-char.escape_{color:#a6e3a1}html.theme--catppuccin-mocha code .hljs-subst{color:#a6adc8}html.theme--catppuccin-mocha code .hljs-symbol{color:#f2cdcd}html.theme--catppuccin-mocha code .hljs-variable{color:#cba6f7}html.theme--catppuccin-mocha code .hljs-variable.language_{color:#cba6f7}html.theme--catppuccin-mocha code .hljs-variable.constant_{color:#fab387}html.theme--catppuccin-mocha code .hljs-title{color:#89b4fa}html.theme--catppuccin-mocha code .hljs-title.class_{color:#f9e2af}html.theme--catppuccin-mocha code .hljs-title.function_{color:#89b4fa}html.theme--catppuccin-mocha code .hljs-params{color:#cdd6f4}html.theme--catppuccin-mocha code .hljs-comment{color:#585b70}html.theme--catppuccin-mocha code .hljs-doctag{color:#f38ba8}html.theme--catppuccin-mocha code .hljs-meta{color:#fab387}html.theme--catppuccin-mocha code .hljs-section{color:#89b4fa}html.theme--catppuccin-mocha code .hljs-tag{color:#a6adc8}html.theme--catppuccin-mocha code .hljs-name{color:#cba6f7}html.theme--catppuccin-mocha code .hljs-attr{color:#89b4fa}html.theme--catppuccin-mocha code .hljs-attribute{color:#a6e3a1}html.theme--catppuccin-mocha code .hljs-bullet{color:#94e2d5}html.theme--catppuccin-mocha code .hljs-code{color:#a6e3a1}html.theme--catppuccin-mocha code .hljs-emphasis{color:#f38ba8;font-style:italic}html.theme--catppuccin-mocha code .hljs-strong{color:#f38ba8;font-weight:bold}html.theme--catppuccin-mocha code .hljs-formula{color:#94e2d5}html.theme--catppuccin-mocha code .hljs-link{color:#74c7ec;font-style:italic}html.theme--catppuccin-mocha code .hljs-quote{color:#a6e3a1;font-style:italic}html.theme--catppuccin-mocha code .hljs-selector-tag{color:#f9e2af}html.theme--catppuccin-mocha code .hljs-selector-id{color:#89b4fa}html.theme--catppuccin-mocha code .hljs-selector-class{color:#94e2d5}html.theme--catppuccin-mocha code .hljs-selector-attr{color:#cba6f7}html.theme--catppuccin-mocha code .hljs-selector-pseudo{color:#94e2d5}html.theme--catppuccin-mocha code .hljs-template-tag{color:#f2cdcd}html.theme--catppuccin-mocha code .hljs-template-variable{color:#f2cdcd}html.theme--catppuccin-mocha code .hljs-addition{color:#a6e3a1;background:rgba(166,227,161,0.15)}html.theme--catppuccin-mocha code .hljs-deletion{color:#f38ba8;background:rgba(243,139,168,0.15)}html.theme--catppuccin-mocha .search-result-link{border-radius:0.7em;transition:all 300ms}html.theme--catppuccin-mocha .search-result-link:hover,html.theme--catppuccin-mocha .search-result-link:focus{background-color:#313244}html.theme--catppuccin-mocha .search-result-link .property-search-result-badge,html.theme--catppuccin-mocha .search-result-link .search-filter{transition:all 300ms}html.theme--catppuccin-mocha .search-result-link:hover .property-search-result-badge,html.theme--catppuccin-mocha .search-result-link:hover .search-filter,html.theme--catppuccin-mocha .search-result-link:focus .property-search-result-badge,html.theme--catppuccin-mocha .search-result-link:focus .search-filter{color:#313244 !important;background-color:#b4befe !important}html.theme--catppuccin-mocha .search-result-title{color:#cdd6f4}html.theme--catppuccin-mocha .search-result-highlight{background-color:#f38ba8;color:#181825}html.theme--catppuccin-mocha .search-divider{border-bottom:1px solid #5e6d6f50}html.theme--catppuccin-mocha .w-100{width:100%}html.theme--catppuccin-mocha .gap-2{gap:0.5rem}html.theme--catppuccin-mocha .gap-4{gap:1rem} diff --git a/previews/PR798/assets/themes/documenter-dark.css b/previews/PR798/assets/themes/documenter-dark.css new file mode 100644 index 0000000000..c41c82f25a --- /dev/null +++ b/previews/PR798/assets/themes/documenter-dark.css @@ -0,0 +1,7 @@ +html.theme--documenter-dark .pagination-previous,html.theme--documenter-dark .pagination-next,html.theme--documenter-dark .pagination-link,html.theme--documenter-dark .pagination-ellipsis,html.theme--documenter-dark .file-cta,html.theme--documenter-dark .file-name,html.theme--documenter-dark .select select,html.theme--documenter-dark .textarea,html.theme--documenter-dark .input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input,html.theme--documenter-dark .button{-moz-appearance:none;-webkit-appearance:none;align-items:center;border:1px solid transparent;border-radius:.4em;box-shadow:none;display:inline-flex;font-size:1rem;height:2.5em;justify-content:flex-start;line-height:1.5;padding-bottom:calc(0.5em - 1px);padding-left:calc(0.75em - 1px);padding-right:calc(0.75em - 1px);padding-top:calc(0.5em - 1px);position:relative;vertical-align:top}html.theme--documenter-dark .pagination-previous:focus,html.theme--documenter-dark .pagination-next:focus,html.theme--documenter-dark .pagination-link:focus,html.theme--documenter-dark .pagination-ellipsis:focus,html.theme--documenter-dark .file-cta:focus,html.theme--documenter-dark .file-name:focus,html.theme--documenter-dark .select select:focus,html.theme--documenter-dark .textarea:focus,html.theme--documenter-dark .input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input:focus,html.theme--documenter-dark .button:focus,html.theme--documenter-dark .is-focused.pagination-previous,html.theme--documenter-dark .is-focused.pagination-next,html.theme--documenter-dark .is-focused.pagination-link,html.theme--documenter-dark .is-focused.pagination-ellipsis,html.theme--documenter-dark .is-focused.file-cta,html.theme--documenter-dark .is-focused.file-name,html.theme--documenter-dark .select select.is-focused,html.theme--documenter-dark .is-focused.textarea,html.theme--documenter-dark .is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .is-focused.button,html.theme--documenter-dark .pagination-previous:active,html.theme--documenter-dark .pagination-next:active,html.theme--documenter-dark .pagination-link:active,html.theme--documenter-dark .pagination-ellipsis:active,html.theme--documenter-dark .file-cta:active,html.theme--documenter-dark .file-name:active,html.theme--documenter-dark .select select:active,html.theme--documenter-dark .textarea:active,html.theme--documenter-dark .input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input:active,html.theme--documenter-dark .button:active,html.theme--documenter-dark .is-active.pagination-previous,html.theme--documenter-dark .is-active.pagination-next,html.theme--documenter-dark .is-active.pagination-link,html.theme--documenter-dark .is-active.pagination-ellipsis,html.theme--documenter-dark .is-active.file-cta,html.theme--documenter-dark .is-active.file-name,html.theme--documenter-dark .select select.is-active,html.theme--documenter-dark .is-active.textarea,html.theme--documenter-dark .is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active,html.theme--documenter-dark .is-active.button{outline:none}html.theme--documenter-dark .pagination-previous[disabled],html.theme--documenter-dark .pagination-next[disabled],html.theme--documenter-dark .pagination-link[disabled],html.theme--documenter-dark .pagination-ellipsis[disabled],html.theme--documenter-dark .file-cta[disabled],html.theme--documenter-dark .file-name[disabled],html.theme--documenter-dark .select select[disabled],html.theme--documenter-dark .textarea[disabled],html.theme--documenter-dark .input[disabled],html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input[disabled],html.theme--documenter-dark .button[disabled],fieldset[disabled] html.theme--documenter-dark .pagination-previous,html.theme--documenter-dark fieldset[disabled] .pagination-previous,fieldset[disabled] html.theme--documenter-dark .pagination-next,html.theme--documenter-dark fieldset[disabled] .pagination-next,fieldset[disabled] html.theme--documenter-dark .pagination-link,html.theme--documenter-dark fieldset[disabled] .pagination-link,fieldset[disabled] html.theme--documenter-dark .pagination-ellipsis,html.theme--documenter-dark fieldset[disabled] .pagination-ellipsis,fieldset[disabled] html.theme--documenter-dark .file-cta,html.theme--documenter-dark fieldset[disabled] .file-cta,fieldset[disabled] html.theme--documenter-dark .file-name,html.theme--documenter-dark fieldset[disabled] .file-name,fieldset[disabled] html.theme--documenter-dark .select select,fieldset[disabled] html.theme--documenter-dark .textarea,fieldset[disabled] html.theme--documenter-dark .input,fieldset[disabled] html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input,html.theme--documenter-dark fieldset[disabled] .select select,html.theme--documenter-dark .select fieldset[disabled] select,html.theme--documenter-dark fieldset[disabled] .textarea,html.theme--documenter-dark fieldset[disabled] .input,html.theme--documenter-dark fieldset[disabled] #documenter .docs-sidebar form.docs-search>input,html.theme--documenter-dark #documenter .docs-sidebar fieldset[disabled] form.docs-search>input,fieldset[disabled] html.theme--documenter-dark .button,html.theme--documenter-dark fieldset[disabled] .button{cursor:not-allowed}html.theme--documenter-dark .tabs,html.theme--documenter-dark .pagination-previous,html.theme--documenter-dark .pagination-next,html.theme--documenter-dark .pagination-link,html.theme--documenter-dark .pagination-ellipsis,html.theme--documenter-dark .breadcrumb,html.theme--documenter-dark .file,html.theme--documenter-dark .button,.is-unselectable{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}html.theme--documenter-dark .navbar-link:not(.is-arrowless)::after,html.theme--documenter-dark .select:not(.is-multiple):not(.is-loading)::after{border:3px solid rgba(0,0,0,0);border-radius:2px;border-right:0;border-top:0;content:" ";display:block;height:0.625em;margin-top:-0.4375em;pointer-events:none;position:absolute;top:50%;transform:rotate(-45deg);transform-origin:center;width:0.625em}html.theme--documenter-dark .admonition:not(:last-child),html.theme--documenter-dark .tabs:not(:last-child),html.theme--documenter-dark .pagination:not(:last-child),html.theme--documenter-dark .message:not(:last-child),html.theme--documenter-dark .level:not(:last-child),html.theme--documenter-dark .breadcrumb:not(:last-child),html.theme--documenter-dark .block:not(:last-child),html.theme--documenter-dark .title:not(:last-child),html.theme--documenter-dark .subtitle:not(:last-child),html.theme--documenter-dark .table-container:not(:last-child),html.theme--documenter-dark .table:not(:last-child),html.theme--documenter-dark .progress:not(:last-child),html.theme--documenter-dark .notification:not(:last-child),html.theme--documenter-dark .content:not(:last-child),html.theme--documenter-dark .box:not(:last-child){margin-bottom:1.5rem}html.theme--documenter-dark .modal-close,html.theme--documenter-dark .delete{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-moz-appearance:none;-webkit-appearance:none;background-color:rgba(10,10,10,0.2);border:none;border-radius:9999px;cursor:pointer;pointer-events:auto;display:inline-block;flex-grow:0;flex-shrink:0;font-size:0;height:20px;max-height:20px;max-width:20px;min-height:20px;min-width:20px;outline:none;position:relative;vertical-align:top;width:20px}html.theme--documenter-dark .modal-close::before,html.theme--documenter-dark .delete::before,html.theme--documenter-dark .modal-close::after,html.theme--documenter-dark .delete::after{background-color:#fff;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}html.theme--documenter-dark .modal-close::before,html.theme--documenter-dark .delete::before{height:2px;width:50%}html.theme--documenter-dark .modal-close::after,html.theme--documenter-dark .delete::after{height:50%;width:2px}html.theme--documenter-dark .modal-close:hover,html.theme--documenter-dark .delete:hover,html.theme--documenter-dark .modal-close:focus,html.theme--documenter-dark .delete:focus{background-color:rgba(10,10,10,0.3)}html.theme--documenter-dark .modal-close:active,html.theme--documenter-dark .delete:active{background-color:rgba(10,10,10,0.4)}html.theme--documenter-dark .is-small.modal-close,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.modal-close,html.theme--documenter-dark .is-small.delete,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.delete{height:16px;max-height:16px;max-width:16px;min-height:16px;min-width:16px;width:16px}html.theme--documenter-dark .is-medium.modal-close,html.theme--documenter-dark .is-medium.delete{height:24px;max-height:24px;max-width:24px;min-height:24px;min-width:24px;width:24px}html.theme--documenter-dark .is-large.modal-close,html.theme--documenter-dark .is-large.delete{height:32px;max-height:32px;max-width:32px;min-height:32px;min-width:32px;width:32px}html.theme--documenter-dark .control.is-loading::after,html.theme--documenter-dark .select.is-loading::after,html.theme--documenter-dark .loader,html.theme--documenter-dark .button.is-loading::after{animation:spinAround 500ms infinite linear;border:2px solid #dbdee0;border-radius:9999px;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:1em;position:relative;width:1em}html.theme--documenter-dark .hero-video,html.theme--documenter-dark .modal-background,html.theme--documenter-dark .modal,html.theme--documenter-dark .image.is-square img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-square img,html.theme--documenter-dark .image.is-square .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-square .has-ratio,html.theme--documenter-dark .image.is-1by1 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by1 img,html.theme--documenter-dark .image.is-1by1 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by1 .has-ratio,html.theme--documenter-dark .image.is-5by4 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-5by4 img,html.theme--documenter-dark .image.is-5by4 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-5by4 .has-ratio,html.theme--documenter-dark .image.is-4by3 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-4by3 img,html.theme--documenter-dark .image.is-4by3 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-4by3 .has-ratio,html.theme--documenter-dark .image.is-3by2 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by2 img,html.theme--documenter-dark .image.is-3by2 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by2 .has-ratio,html.theme--documenter-dark .image.is-5by3 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-5by3 img,html.theme--documenter-dark .image.is-5by3 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-5by3 .has-ratio,html.theme--documenter-dark .image.is-16by9 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-16by9 img,html.theme--documenter-dark .image.is-16by9 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-16by9 .has-ratio,html.theme--documenter-dark .image.is-2by1 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-2by1 img,html.theme--documenter-dark .image.is-2by1 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-2by1 .has-ratio,html.theme--documenter-dark .image.is-3by1 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by1 img,html.theme--documenter-dark .image.is-3by1 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by1 .has-ratio,html.theme--documenter-dark .image.is-4by5 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-4by5 img,html.theme--documenter-dark .image.is-4by5 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-4by5 .has-ratio,html.theme--documenter-dark .image.is-3by4 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by4 img,html.theme--documenter-dark .image.is-3by4 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by4 .has-ratio,html.theme--documenter-dark .image.is-2by3 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-2by3 img,html.theme--documenter-dark .image.is-2by3 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-2by3 .has-ratio,html.theme--documenter-dark .image.is-3by5 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by5 img,html.theme--documenter-dark .image.is-3by5 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by5 .has-ratio,html.theme--documenter-dark .image.is-9by16 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-9by16 img,html.theme--documenter-dark .image.is-9by16 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-9by16 .has-ratio,html.theme--documenter-dark .image.is-1by2 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by2 img,html.theme--documenter-dark .image.is-1by2 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by2 .has-ratio,html.theme--documenter-dark .image.is-1by3 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by3 img,html.theme--documenter-dark .image.is-1by3 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by3 .has-ratio,.is-overlay{bottom:0;left:0;position:absolute;right:0;top:0}html.theme--documenter-dark .navbar-burger{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;color:currentColor;font-family:inherit;font-size:1em;margin:0;padding:0}/*! minireset.css v0.0.6 | MIT License | github.com/jgthms/minireset.css */html,body,p,ol,ul,li,dl,dt,dd,blockquote,figure,fieldset,legend,textarea,pre,iframe,hr,h1,h2,h3,h4,h5,h6{margin:0;padding:0}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal}ul{list-style:none}button,input,select,textarea{margin:0}html{box-sizing:border-box}*,*::before,*::after{box-sizing:inherit}img,video{height:auto;max-width:100%}iframe{border:0}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}td:not([align]),th:not([align]){text-align:inherit}.has-text-white{color:#fff !important}a.has-text-white:hover,a.has-text-white:focus{color:#e6e6e6 !important}.has-background-white{background-color:#fff !important}.has-text-black{color:#0a0a0a !important}a.has-text-black:hover,a.has-text-black:focus{color:#000 !important}.has-background-black{background-color:#0a0a0a !important}.has-text-light{color:#ecf0f1 !important}a.has-text-light:hover,a.has-text-light:focus{color:#cfd9db !important}.has-background-light{background-color:#ecf0f1 !important}.has-text-dark{color:#282f2f !important}a.has-text-dark:hover,a.has-text-dark:focus{color:#111414 !important}.has-background-dark{background-color:#282f2f !important}.has-text-primary{color:#375a7f !important}a.has-text-primary:hover,a.has-text-primary:focus{color:#28415b !important}.has-background-primary{background-color:#375a7f !important}.has-text-primary-light{color:#f1f5f9 !important}a.has-text-primary-light:hover,a.has-text-primary-light:focus{color:#cddbe9 !important}.has-background-primary-light{background-color:#f1f5f9 !important}.has-text-primary-dark{color:#4d7eb2 !important}a.has-text-primary-dark:hover,a.has-text-primary-dark:focus{color:#7198c1 !important}.has-background-primary-dark{background-color:#4d7eb2 !important}.has-text-link{color:#1abc9c !important}a.has-text-link:hover,a.has-text-link:focus{color:#148f77 !important}.has-background-link{background-color:#1abc9c !important}.has-text-link-light{color:#edfdf9 !important}a.has-text-link-light:hover,a.has-text-link-light:focus{color:#c0f6ec !important}.has-background-link-light{background-color:#edfdf9 !important}.has-text-link-dark{color:#15987e !important}a.has-text-link-dark:hover,a.has-text-link-dark:focus{color:#1bc5a4 !important}.has-background-link-dark{background-color:#15987e !important}.has-text-info{color:#3c5dcd !important}a.has-text-info:hover,a.has-text-info:focus{color:#2c48aa !important}.has-background-info{background-color:#3c5dcd !important}.has-text-info-light{color:#eff2fb !important}a.has-text-info-light:hover,a.has-text-info-light:focus{color:#c6d0f0 !important}.has-background-info-light{background-color:#eff2fb !important}.has-text-info-dark{color:#3253c3 !important}a.has-text-info-dark:hover,a.has-text-info-dark:focus{color:#5571d3 !important}.has-background-info-dark{background-color:#3253c3 !important}.has-text-success{color:#259a12 !important}a.has-text-success:hover,a.has-text-success:focus{color:#1a6c0d !important}.has-background-success{background-color:#259a12 !important}.has-text-success-light{color:#effded !important}a.has-text-success-light:hover,a.has-text-success-light:focus{color:#c7f8bf !important}.has-background-success-light{background-color:#effded !important}.has-text-success-dark{color:#2ec016 !important}a.has-text-success-dark:hover,a.has-text-success-dark:focus{color:#3fe524 !important}.has-background-success-dark{background-color:#2ec016 !important}.has-text-warning{color:#f4c72f !important}a.has-text-warning:hover,a.has-text-warning:focus{color:#e4b30c !important}.has-background-warning{background-color:#f4c72f !important}.has-text-warning-light{color:#fefaec !important}a.has-text-warning-light:hover,a.has-text-warning-light:focus{color:#fbedbb !important}.has-background-warning-light{background-color:#fefaec !important}.has-text-warning-dark{color:#8c6e07 !important}a.has-text-warning-dark:hover,a.has-text-warning-dark:focus{color:#bd940a !important}.has-background-warning-dark{background-color:#8c6e07 !important}.has-text-danger{color:#cb3c33 !important}a.has-text-danger:hover,a.has-text-danger:focus{color:#a23029 !important}.has-background-danger{background-color:#cb3c33 !important}.has-text-danger-light{color:#fbefef !important}a.has-text-danger-light:hover,a.has-text-danger-light:focus{color:#f1c8c6 !important}.has-background-danger-light{background-color:#fbefef !important}.has-text-danger-dark{color:#c03930 !important}a.has-text-danger-dark:hover,a.has-text-danger-dark:focus{color:#d35850 !important}.has-background-danger-dark{background-color:#c03930 !important}.has-text-black-bis{color:#121212 !important}.has-background-black-bis{background-color:#121212 !important}.has-text-black-ter{color:#242424 !important}.has-background-black-ter{background-color:#242424 !important}.has-text-grey-darker{color:#282f2f !important}.has-background-grey-darker{background-color:#282f2f !important}.has-text-grey-dark{color:#343c3d !important}.has-background-grey-dark{background-color:#343c3d !important}.has-text-grey{color:#5e6d6f !important}.has-background-grey{background-color:#5e6d6f !important}.has-text-grey-light{color:#8c9b9d !important}.has-background-grey-light{background-color:#8c9b9d !important}.has-text-grey-lighter{color:#dbdee0 !important}.has-background-grey-lighter{background-color:#dbdee0 !important}.has-text-white-ter{color:#ecf0f1 !important}.has-background-white-ter{background-color:#ecf0f1 !important}.has-text-white-bis{color:#fafafa !important}.has-background-white-bis{background-color:#fafafa !important}.is-flex-direction-row{flex-direction:row !important}.is-flex-direction-row-reverse{flex-direction:row-reverse !important}.is-flex-direction-column{flex-direction:column !important}.is-flex-direction-column-reverse{flex-direction:column-reverse !important}.is-flex-wrap-nowrap{flex-wrap:nowrap !important}.is-flex-wrap-wrap{flex-wrap:wrap !important}.is-flex-wrap-wrap-reverse{flex-wrap:wrap-reverse !important}.is-justify-content-flex-start{justify-content:flex-start !important}.is-justify-content-flex-end{justify-content:flex-end !important}.is-justify-content-center{justify-content:center !important}.is-justify-content-space-between{justify-content:space-between !important}.is-justify-content-space-around{justify-content:space-around !important}.is-justify-content-space-evenly{justify-content:space-evenly !important}.is-justify-content-start{justify-content:start !important}.is-justify-content-end{justify-content:end !important}.is-justify-content-left{justify-content:left !important}.is-justify-content-right{justify-content:right !important}.is-align-content-flex-start{align-content:flex-start !important}.is-align-content-flex-end{align-content:flex-end !important}.is-align-content-center{align-content:center !important}.is-align-content-space-between{align-content:space-between !important}.is-align-content-space-around{align-content:space-around !important}.is-align-content-space-evenly{align-content:space-evenly !important}.is-align-content-stretch{align-content:stretch !important}.is-align-content-start{align-content:start !important}.is-align-content-end{align-content:end !important}.is-align-content-baseline{align-content:baseline !important}.is-align-items-stretch{align-items:stretch !important}.is-align-items-flex-start{align-items:flex-start !important}.is-align-items-flex-end{align-items:flex-end !important}.is-align-items-center{align-items:center !important}.is-align-items-baseline{align-items:baseline !important}.is-align-items-start{align-items:start !important}.is-align-items-end{align-items:end !important}.is-align-items-self-start{align-items:self-start !important}.is-align-items-self-end{align-items:self-end !important}.is-align-self-auto{align-self:auto !important}.is-align-self-flex-start{align-self:flex-start !important}.is-align-self-flex-end{align-self:flex-end !important}.is-align-self-center{align-self:center !important}.is-align-self-baseline{align-self:baseline !important}.is-align-self-stretch{align-self:stretch !important}.is-flex-grow-0{flex-grow:0 !important}.is-flex-grow-1{flex-grow:1 !important}.is-flex-grow-2{flex-grow:2 !important}.is-flex-grow-3{flex-grow:3 !important}.is-flex-grow-4{flex-grow:4 !important}.is-flex-grow-5{flex-grow:5 !important}.is-flex-shrink-0{flex-shrink:0 !important}.is-flex-shrink-1{flex-shrink:1 !important}.is-flex-shrink-2{flex-shrink:2 !important}.is-flex-shrink-3{flex-shrink:3 !important}.is-flex-shrink-4{flex-shrink:4 !important}.is-flex-shrink-5{flex-shrink:5 !important}.is-clearfix::after{clear:both;content:" ";display:table}.is-pulled-left{float:left !important}.is-pulled-right{float:right !important}.is-radiusless{border-radius:0 !important}.is-shadowless{box-shadow:none !important}.is-clickable{cursor:pointer !important;pointer-events:all !important}.is-clipped{overflow:hidden !important}.is-relative{position:relative !important}.is-marginless{margin:0 !important}.is-paddingless{padding:0 !important}.m-0{margin:0 !important}.mt-0{margin-top:0 !important}.mr-0{margin-right:0 !important}.mb-0{margin-bottom:0 !important}.ml-0{margin-left:0 !important}.mx-0{margin-left:0 !important;margin-right:0 !important}.my-0{margin-top:0 !important;margin-bottom:0 !important}.m-1{margin:.25rem !important}.mt-1{margin-top:.25rem !important}.mr-1{margin-right:.25rem !important}.mb-1{margin-bottom:.25rem !important}.ml-1{margin-left:.25rem !important}.mx-1{margin-left:.25rem !important;margin-right:.25rem !important}.my-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.m-2{margin:.5rem !important}.mt-2{margin-top:.5rem !important}.mr-2{margin-right:.5rem !important}.mb-2{margin-bottom:.5rem !important}.ml-2{margin-left:.5rem !important}.mx-2{margin-left:.5rem !important;margin-right:.5rem !important}.my-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.m-3{margin:.75rem !important}.mt-3{margin-top:.75rem !important}.mr-3{margin-right:.75rem !important}.mb-3{margin-bottom:.75rem !important}.ml-3{margin-left:.75rem !important}.mx-3{margin-left:.75rem !important;margin-right:.75rem !important}.my-3{margin-top:.75rem !important;margin-bottom:.75rem !important}.m-4{margin:1rem !important}.mt-4{margin-top:1rem !important}.mr-4{margin-right:1rem !important}.mb-4{margin-bottom:1rem !important}.ml-4{margin-left:1rem !important}.mx-4{margin-left:1rem !important;margin-right:1rem !important}.my-4{margin-top:1rem !important;margin-bottom:1rem !important}.m-5{margin:1.5rem !important}.mt-5{margin-top:1.5rem !important}.mr-5{margin-right:1.5rem !important}.mb-5{margin-bottom:1.5rem !important}.ml-5{margin-left:1.5rem !important}.mx-5{margin-left:1.5rem !important;margin-right:1.5rem !important}.my-5{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.m-6{margin:3rem !important}.mt-6{margin-top:3rem !important}.mr-6{margin-right:3rem !important}.mb-6{margin-bottom:3rem !important}.ml-6{margin-left:3rem !important}.mx-6{margin-left:3rem !important;margin-right:3rem !important}.my-6{margin-top:3rem !important;margin-bottom:3rem !important}.m-auto{margin:auto !important}.mt-auto{margin-top:auto !important}.mr-auto{margin-right:auto !important}.mb-auto{margin-bottom:auto !important}.ml-auto{margin-left:auto !important}.mx-auto{margin-left:auto !important;margin-right:auto !important}.my-auto{margin-top:auto !important;margin-bottom:auto !important}.p-0{padding:0 !important}.pt-0{padding-top:0 !important}.pr-0{padding-right:0 !important}.pb-0{padding-bottom:0 !important}.pl-0{padding-left:0 !important}.px-0{padding-left:0 !important;padding-right:0 !important}.py-0{padding-top:0 !important;padding-bottom:0 !important}.p-1{padding:.25rem !important}.pt-1{padding-top:.25rem !important}.pr-1{padding-right:.25rem !important}.pb-1{padding-bottom:.25rem !important}.pl-1{padding-left:.25rem !important}.px-1{padding-left:.25rem !important;padding-right:.25rem !important}.py-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.p-2{padding:.5rem !important}.pt-2{padding-top:.5rem !important}.pr-2{padding-right:.5rem !important}.pb-2{padding-bottom:.5rem !important}.pl-2{padding-left:.5rem !important}.px-2{padding-left:.5rem !important;padding-right:.5rem !important}.py-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.p-3{padding:.75rem !important}.pt-3{padding-top:.75rem !important}.pr-3{padding-right:.75rem !important}.pb-3{padding-bottom:.75rem !important}.pl-3{padding-left:.75rem !important}.px-3{padding-left:.75rem !important;padding-right:.75rem !important}.py-3{padding-top:.75rem !important;padding-bottom:.75rem !important}.p-4{padding:1rem !important}.pt-4{padding-top:1rem !important}.pr-4{padding-right:1rem !important}.pb-4{padding-bottom:1rem !important}.pl-4{padding-left:1rem !important}.px-4{padding-left:1rem !important;padding-right:1rem !important}.py-4{padding-top:1rem !important;padding-bottom:1rem !important}.p-5{padding:1.5rem !important}.pt-5{padding-top:1.5rem !important}.pr-5{padding-right:1.5rem !important}.pb-5{padding-bottom:1.5rem !important}.pl-5{padding-left:1.5rem !important}.px-5{padding-left:1.5rem !important;padding-right:1.5rem !important}.py-5{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.p-6{padding:3rem !important}.pt-6{padding-top:3rem !important}.pr-6{padding-right:3rem !important}.pb-6{padding-bottom:3rem !important}.pl-6{padding-left:3rem !important}.px-6{padding-left:3rem !important;padding-right:3rem !important}.py-6{padding-top:3rem !important;padding-bottom:3rem !important}.p-auto{padding:auto !important}.pt-auto{padding-top:auto !important}.pr-auto{padding-right:auto !important}.pb-auto{padding-bottom:auto !important}.pl-auto{padding-left:auto !important}.px-auto{padding-left:auto !important;padding-right:auto !important}.py-auto{padding-top:auto !important;padding-bottom:auto !important}.is-size-1{font-size:3rem !important}.is-size-2{font-size:2.5rem !important}.is-size-3{font-size:2rem !important}.is-size-4{font-size:1.5rem !important}.is-size-5{font-size:1.25rem !important}.is-size-6{font-size:1rem !important}.is-size-7,html.theme--documenter-dark .docstring>section>a.docs-sourcelink{font-size:.75rem !important}@media screen and (max-width: 768px){.is-size-1-mobile{font-size:3rem !important}.is-size-2-mobile{font-size:2.5rem !important}.is-size-3-mobile{font-size:2rem !important}.is-size-4-mobile{font-size:1.5rem !important}.is-size-5-mobile{font-size:1.25rem !important}.is-size-6-mobile{font-size:1rem !important}.is-size-7-mobile{font-size:.75rem !important}}@media screen and (min-width: 769px),print{.is-size-1-tablet{font-size:3rem !important}.is-size-2-tablet{font-size:2.5rem !important}.is-size-3-tablet{font-size:2rem !important}.is-size-4-tablet{font-size:1.5rem !important}.is-size-5-tablet{font-size:1.25rem !important}.is-size-6-tablet{font-size:1rem !important}.is-size-7-tablet{font-size:.75rem !important}}@media screen and (max-width: 1055px){.is-size-1-touch{font-size:3rem !important}.is-size-2-touch{font-size:2.5rem !important}.is-size-3-touch{font-size:2rem !important}.is-size-4-touch{font-size:1.5rem !important}.is-size-5-touch{font-size:1.25rem !important}.is-size-6-touch{font-size:1rem !important}.is-size-7-touch{font-size:.75rem !important}}@media screen and (min-width: 1056px){.is-size-1-desktop{font-size:3rem !important}.is-size-2-desktop{font-size:2.5rem !important}.is-size-3-desktop{font-size:2rem !important}.is-size-4-desktop{font-size:1.5rem !important}.is-size-5-desktop{font-size:1.25rem !important}.is-size-6-desktop{font-size:1rem !important}.is-size-7-desktop{font-size:.75rem !important}}@media screen and (min-width: 1216px){.is-size-1-widescreen{font-size:3rem !important}.is-size-2-widescreen{font-size:2.5rem !important}.is-size-3-widescreen{font-size:2rem !important}.is-size-4-widescreen{font-size:1.5rem !important}.is-size-5-widescreen{font-size:1.25rem !important}.is-size-6-widescreen{font-size:1rem !important}.is-size-7-widescreen{font-size:.75rem !important}}@media screen and (min-width: 1408px){.is-size-1-fullhd{font-size:3rem !important}.is-size-2-fullhd{font-size:2.5rem !important}.is-size-3-fullhd{font-size:2rem !important}.is-size-4-fullhd{font-size:1.5rem !important}.is-size-5-fullhd{font-size:1.25rem !important}.is-size-6-fullhd{font-size:1rem !important}.is-size-7-fullhd{font-size:.75rem !important}}.has-text-centered{text-align:center !important}.has-text-justified{text-align:justify !important}.has-text-left{text-align:left !important}.has-text-right{text-align:right !important}@media screen and (max-width: 768px){.has-text-centered-mobile{text-align:center !important}}@media screen and (min-width: 769px),print{.has-text-centered-tablet{text-align:center !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-centered-tablet-only{text-align:center !important}}@media screen and (max-width: 1055px){.has-text-centered-touch{text-align:center !important}}@media screen and (min-width: 1056px){.has-text-centered-desktop{text-align:center !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-centered-desktop-only{text-align:center !important}}@media screen and (min-width: 1216px){.has-text-centered-widescreen{text-align:center !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-centered-widescreen-only{text-align:center !important}}@media screen and (min-width: 1408px){.has-text-centered-fullhd{text-align:center !important}}@media screen and (max-width: 768px){.has-text-justified-mobile{text-align:justify !important}}@media screen and (min-width: 769px),print{.has-text-justified-tablet{text-align:justify !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-justified-tablet-only{text-align:justify !important}}@media screen and (max-width: 1055px){.has-text-justified-touch{text-align:justify !important}}@media screen and (min-width: 1056px){.has-text-justified-desktop{text-align:justify !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-justified-desktop-only{text-align:justify !important}}@media screen and (min-width: 1216px){.has-text-justified-widescreen{text-align:justify !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-justified-widescreen-only{text-align:justify !important}}@media screen and (min-width: 1408px){.has-text-justified-fullhd{text-align:justify !important}}@media screen and (max-width: 768px){.has-text-left-mobile{text-align:left !important}}@media screen and (min-width: 769px),print{.has-text-left-tablet{text-align:left !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-left-tablet-only{text-align:left !important}}@media screen and (max-width: 1055px){.has-text-left-touch{text-align:left !important}}@media screen and (min-width: 1056px){.has-text-left-desktop{text-align:left !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-left-desktop-only{text-align:left !important}}@media screen and (min-width: 1216px){.has-text-left-widescreen{text-align:left !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-left-widescreen-only{text-align:left !important}}@media screen and (min-width: 1408px){.has-text-left-fullhd{text-align:left !important}}@media screen and (max-width: 768px){.has-text-right-mobile{text-align:right !important}}@media screen and (min-width: 769px),print{.has-text-right-tablet{text-align:right !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-right-tablet-only{text-align:right !important}}@media screen and (max-width: 1055px){.has-text-right-touch{text-align:right !important}}@media screen and (min-width: 1056px){.has-text-right-desktop{text-align:right !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-right-desktop-only{text-align:right !important}}@media screen and (min-width: 1216px){.has-text-right-widescreen{text-align:right !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-right-widescreen-only{text-align:right !important}}@media screen and (min-width: 1408px){.has-text-right-fullhd{text-align:right !important}}.is-capitalized{text-transform:capitalize !important}.is-lowercase{text-transform:lowercase !important}.is-uppercase{text-transform:uppercase !important}.is-italic{font-style:italic !important}.is-underlined{text-decoration:underline !important}.has-text-weight-light{font-weight:300 !important}.has-text-weight-normal{font-weight:400 !important}.has-text-weight-medium{font-weight:500 !important}.has-text-weight-semibold{font-weight:600 !important}.has-text-weight-bold{font-weight:700 !important}.is-family-primary{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-secondary{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-sans-serif{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-monospace{font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace !important}.is-family-code{font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace !important}.is-block{display:block !important}@media screen and (max-width: 768px){.is-block-mobile{display:block !important}}@media screen and (min-width: 769px),print{.is-block-tablet{display:block !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-block-tablet-only{display:block !important}}@media screen and (max-width: 1055px){.is-block-touch{display:block !important}}@media screen and (min-width: 1056px){.is-block-desktop{display:block !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-block-desktop-only{display:block !important}}@media screen and (min-width: 1216px){.is-block-widescreen{display:block !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-block-widescreen-only{display:block !important}}@media screen and (min-width: 1408px){.is-block-fullhd{display:block !important}}.is-flex{display:flex !important}@media screen and (max-width: 768px){.is-flex-mobile{display:flex !important}}@media screen and (min-width: 769px),print{.is-flex-tablet{display:flex !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-flex-tablet-only{display:flex !important}}@media screen and (max-width: 1055px){.is-flex-touch{display:flex !important}}@media screen and (min-width: 1056px){.is-flex-desktop{display:flex !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-flex-desktop-only{display:flex !important}}@media screen and (min-width: 1216px){.is-flex-widescreen{display:flex !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-flex-widescreen-only{display:flex !important}}@media screen and (min-width: 1408px){.is-flex-fullhd{display:flex !important}}.is-inline{display:inline !important}@media screen and (max-width: 768px){.is-inline-mobile{display:inline !important}}@media screen and (min-width: 769px),print{.is-inline-tablet{display:inline !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-tablet-only{display:inline !important}}@media screen and (max-width: 1055px){.is-inline-touch{display:inline !important}}@media screen and (min-width: 1056px){.is-inline-desktop{display:inline !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-desktop-only{display:inline !important}}@media screen and (min-width: 1216px){.is-inline-widescreen{display:inline !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-widescreen-only{display:inline !important}}@media screen and (min-width: 1408px){.is-inline-fullhd{display:inline !important}}.is-inline-block{display:inline-block !important}@media screen and (max-width: 768px){.is-inline-block-mobile{display:inline-block !important}}@media screen and (min-width: 769px),print{.is-inline-block-tablet{display:inline-block !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-block-tablet-only{display:inline-block !important}}@media screen and (max-width: 1055px){.is-inline-block-touch{display:inline-block !important}}@media screen and (min-width: 1056px){.is-inline-block-desktop{display:inline-block !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-block-desktop-only{display:inline-block !important}}@media screen and (min-width: 1216px){.is-inline-block-widescreen{display:inline-block !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-block-widescreen-only{display:inline-block !important}}@media screen and (min-width: 1408px){.is-inline-block-fullhd{display:inline-block !important}}.is-inline-flex{display:inline-flex !important}@media screen and (max-width: 768px){.is-inline-flex-mobile{display:inline-flex !important}}@media screen and (min-width: 769px),print{.is-inline-flex-tablet{display:inline-flex !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-flex-tablet-only{display:inline-flex !important}}@media screen and (max-width: 1055px){.is-inline-flex-touch{display:inline-flex !important}}@media screen and (min-width: 1056px){.is-inline-flex-desktop{display:inline-flex !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-flex-desktop-only{display:inline-flex !important}}@media screen and (min-width: 1216px){.is-inline-flex-widescreen{display:inline-flex !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-flex-widescreen-only{display:inline-flex !important}}@media screen and (min-width: 1408px){.is-inline-flex-fullhd{display:inline-flex !important}}.is-hidden{display:none !important}.is-sr-only{border:none !important;clip:rect(0, 0, 0, 0) !important;height:0.01em !important;overflow:hidden !important;padding:0 !important;position:absolute !important;white-space:nowrap !important;width:0.01em !important}@media screen and (max-width: 768px){.is-hidden-mobile{display:none !important}}@media screen and (min-width: 769px),print{.is-hidden-tablet{display:none !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-hidden-tablet-only{display:none !important}}@media screen and (max-width: 1055px){.is-hidden-touch{display:none !important}}@media screen and (min-width: 1056px){.is-hidden-desktop{display:none !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-hidden-desktop-only{display:none !important}}@media screen and (min-width: 1216px){.is-hidden-widescreen{display:none !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-hidden-widescreen-only{display:none !important}}@media screen and (min-width: 1408px){.is-hidden-fullhd{display:none !important}}.is-invisible{visibility:hidden !important}@media screen and (max-width: 768px){.is-invisible-mobile{visibility:hidden !important}}@media screen and (min-width: 769px),print{.is-invisible-tablet{visibility:hidden !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-invisible-tablet-only{visibility:hidden !important}}@media screen and (max-width: 1055px){.is-invisible-touch{visibility:hidden !important}}@media screen and (min-width: 1056px){.is-invisible-desktop{visibility:hidden !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-invisible-desktop-only{visibility:hidden !important}}@media screen and (min-width: 1216px){.is-invisible-widescreen{visibility:hidden !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-invisible-widescreen-only{visibility:hidden !important}}@media screen and (min-width: 1408px){.is-invisible-fullhd{visibility:hidden !important}}html.theme--documenter-dark{/*! + Theme: a11y-dark + Author: @ericwbailey + Maintainer: @ericwbailey + + Based on the Tomorrow Night Eighties theme: https://github.com/isagalaev/highlight.js/blob/master/src/styles/tomorrow-night-eighties.css +*/}html.theme--documenter-dark html{background-color:#1f2424;font-size:16px;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;min-width:300px;overflow-x:auto;overflow-y:scroll;text-rendering:optimizeLegibility;text-size-adjust:100%}html.theme--documenter-dark article,html.theme--documenter-dark aside,html.theme--documenter-dark figure,html.theme--documenter-dark footer,html.theme--documenter-dark header,html.theme--documenter-dark hgroup,html.theme--documenter-dark section{display:block}html.theme--documenter-dark body,html.theme--documenter-dark button,html.theme--documenter-dark input,html.theme--documenter-dark optgroup,html.theme--documenter-dark select,html.theme--documenter-dark textarea{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif}html.theme--documenter-dark code,html.theme--documenter-dark pre{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto;font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace}html.theme--documenter-dark body{color:#fff;font-size:1em;font-weight:400;line-height:1.5}html.theme--documenter-dark a{color:#1abc9c;cursor:pointer;text-decoration:none}html.theme--documenter-dark a strong{color:currentColor}html.theme--documenter-dark a:hover{color:#1dd2af}html.theme--documenter-dark code{background-color:rgba(255,255,255,0.05);color:#ececec;font-size:.875em;font-weight:normal;padding:.1em}html.theme--documenter-dark hr{background-color:#282f2f;border:none;display:block;height:2px;margin:1.5rem 0}html.theme--documenter-dark img{height:auto;max-width:100%}html.theme--documenter-dark input[type="checkbox"],html.theme--documenter-dark input[type="radio"]{vertical-align:baseline}html.theme--documenter-dark small{font-size:.875em}html.theme--documenter-dark span{font-style:inherit;font-weight:inherit}html.theme--documenter-dark strong{color:#f2f2f2;font-weight:700}html.theme--documenter-dark fieldset{border:none}html.theme--documenter-dark pre{-webkit-overflow-scrolling:touch;background-color:#282f2f;color:#fff;font-size:.875em;overflow-x:auto;padding:1.25rem 1.5rem;white-space:pre;word-wrap:normal}html.theme--documenter-dark pre code{background-color:transparent;color:currentColor;font-size:1em;padding:0}html.theme--documenter-dark table td,html.theme--documenter-dark table th{vertical-align:top}html.theme--documenter-dark table td:not([align]),html.theme--documenter-dark table th:not([align]){text-align:inherit}html.theme--documenter-dark table th{color:#f2f2f2}html.theme--documenter-dark .box{background-color:#343c3d;border-radius:8px;box-shadow:none;color:#fff;display:block;padding:1.25rem}html.theme--documenter-dark a.box:hover,html.theme--documenter-dark a.box:focus{box-shadow:0 0.5em 1em -0.125em rgba(10,10,10,0.1),0 0 0 1px #1abc9c}html.theme--documenter-dark a.box:active{box-shadow:inset 0 1px 2px rgba(10,10,10,0.2),0 0 0 1px #1abc9c}html.theme--documenter-dark .button{background-color:#282f2f;border-color:#4c5759;border-width:1px;color:#375a7f;cursor:pointer;justify-content:center;padding-bottom:calc(0.5em - 1px);padding-left:1em;padding-right:1em;padding-top:calc(0.5em - 1px);text-align:center;white-space:nowrap}html.theme--documenter-dark .button strong{color:inherit}html.theme--documenter-dark .button .icon,html.theme--documenter-dark .button .icon.is-small,html.theme--documenter-dark .button #documenter .docs-sidebar form.docs-search>input.icon,html.theme--documenter-dark #documenter .docs-sidebar .button form.docs-search>input.icon,html.theme--documenter-dark .button .icon.is-medium,html.theme--documenter-dark .button .icon.is-large{height:1.5em;width:1.5em}html.theme--documenter-dark .button .icon:first-child:not(:last-child){margin-left:calc(-0.5em - 1px);margin-right:.25em}html.theme--documenter-dark .button .icon:last-child:not(:first-child){margin-left:.25em;margin-right:calc(-0.5em - 1px)}html.theme--documenter-dark .button .icon:first-child:last-child{margin-left:calc(-0.5em - 1px);margin-right:calc(-0.5em - 1px)}html.theme--documenter-dark .button:hover,html.theme--documenter-dark .button.is-hovered{border-color:#8c9b9d;color:#f2f2f2}html.theme--documenter-dark .button:focus,html.theme--documenter-dark .button.is-focused{border-color:#8c9b9d;color:#17a689}html.theme--documenter-dark .button:focus:not(:active),html.theme--documenter-dark .button.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(26,188,156,0.25)}html.theme--documenter-dark .button:active,html.theme--documenter-dark .button.is-active{border-color:#343c3d;color:#f2f2f2}html.theme--documenter-dark .button.is-text{background-color:transparent;border-color:transparent;color:#fff;text-decoration:underline}html.theme--documenter-dark .button.is-text:hover,html.theme--documenter-dark .button.is-text.is-hovered,html.theme--documenter-dark .button.is-text:focus,html.theme--documenter-dark .button.is-text.is-focused{background-color:#282f2f;color:#f2f2f2}html.theme--documenter-dark .button.is-text:active,html.theme--documenter-dark .button.is-text.is-active{background-color:#1d2122;color:#f2f2f2}html.theme--documenter-dark .button.is-text[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-text{background-color:transparent;border-color:transparent;box-shadow:none}html.theme--documenter-dark .button.is-ghost{background:none;border-color:rgba(0,0,0,0);color:#1abc9c;text-decoration:none}html.theme--documenter-dark .button.is-ghost:hover,html.theme--documenter-dark .button.is-ghost.is-hovered{color:#1abc9c;text-decoration:underline}html.theme--documenter-dark .button.is-white{background-color:#fff;border-color:transparent;color:#0a0a0a}html.theme--documenter-dark .button.is-white:hover,html.theme--documenter-dark .button.is-white.is-hovered{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}html.theme--documenter-dark .button.is-white:focus,html.theme--documenter-dark .button.is-white.is-focused{border-color:transparent;color:#0a0a0a}html.theme--documenter-dark .button.is-white:focus:not(:active),html.theme--documenter-dark .button.is-white.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}html.theme--documenter-dark .button.is-white:active,html.theme--documenter-dark .button.is-white.is-active{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}html.theme--documenter-dark .button.is-white[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-white{background-color:#fff;border-color:#fff;box-shadow:none}html.theme--documenter-dark .button.is-white.is-inverted{background-color:#0a0a0a;color:#fff}html.theme--documenter-dark .button.is-white.is-inverted:hover,html.theme--documenter-dark .button.is-white.is-inverted.is-hovered{background-color:#000}html.theme--documenter-dark .button.is-white.is-inverted[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-white.is-inverted{background-color:#0a0a0a;border-color:transparent;box-shadow:none;color:#fff}html.theme--documenter-dark .button.is-white.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--documenter-dark .button.is-white.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--documenter-dark .button.is-white.is-outlined:hover,html.theme--documenter-dark .button.is-white.is-outlined.is-hovered,html.theme--documenter-dark .button.is-white.is-outlined:focus,html.theme--documenter-dark .button.is-white.is-outlined.is-focused{background-color:#fff;border-color:#fff;color:#0a0a0a}html.theme--documenter-dark .button.is-white.is-outlined.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-white.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-white.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-white.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-white.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--documenter-dark .button.is-white.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-white.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--documenter-dark .button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}html.theme--documenter-dark .button.is-white.is-inverted.is-outlined:hover,html.theme--documenter-dark .button.is-white.is-inverted.is-outlined.is-hovered,html.theme--documenter-dark .button.is-white.is-inverted.is-outlined:focus,html.theme--documenter-dark .button.is-white.is-inverted.is-outlined.is-focused{background-color:#0a0a0a;color:#fff}html.theme--documenter-dark .button.is-white.is-inverted.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-white.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-white.is-inverted.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-white.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-white.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}html.theme--documenter-dark .button.is-black{background-color:#0a0a0a;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-black:hover,html.theme--documenter-dark .button.is-black.is-hovered{background-color:#040404;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-black:focus,html.theme--documenter-dark .button.is-black.is-focused{border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-black:focus:not(:active),html.theme--documenter-dark .button.is-black.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}html.theme--documenter-dark .button.is-black:active,html.theme--documenter-dark .button.is-black.is-active{background-color:#000;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-black[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-black{background-color:#0a0a0a;border-color:#0a0a0a;box-shadow:none}html.theme--documenter-dark .button.is-black.is-inverted{background-color:#fff;color:#0a0a0a}html.theme--documenter-dark .button.is-black.is-inverted:hover,html.theme--documenter-dark .button.is-black.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--documenter-dark .button.is-black.is-inverted[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-black.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#0a0a0a}html.theme--documenter-dark .button.is-black.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}html.theme--documenter-dark .button.is-black.is-outlined:hover,html.theme--documenter-dark .button.is-black.is-outlined.is-hovered,html.theme--documenter-dark .button.is-black.is-outlined:focus,html.theme--documenter-dark .button.is-black.is-outlined.is-focused{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}html.theme--documenter-dark .button.is-black.is-outlined.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--documenter-dark .button.is-black.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-black.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-black.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-black.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-black.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}html.theme--documenter-dark .button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--documenter-dark .button.is-black.is-inverted.is-outlined:hover,html.theme--documenter-dark .button.is-black.is-inverted.is-outlined.is-hovered,html.theme--documenter-dark .button.is-black.is-inverted.is-outlined:focus,html.theme--documenter-dark .button.is-black.is-inverted.is-outlined.is-focused{background-color:#fff;color:#0a0a0a}html.theme--documenter-dark .button.is-black.is-inverted.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-black.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-black.is-inverted.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-black.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--documenter-dark .button.is-black.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--documenter-dark .button.is-light{background-color:#ecf0f1;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-light:hover,html.theme--documenter-dark .button.is-light.is-hovered{background-color:#e5eaec;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-light:focus,html.theme--documenter-dark .button.is-light.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-light:focus:not(:active),html.theme--documenter-dark .button.is-light.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(236,240,241,0.25)}html.theme--documenter-dark .button.is-light:active,html.theme--documenter-dark .button.is-light.is-active{background-color:#dde4e6;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-light[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-light{background-color:#ecf0f1;border-color:#ecf0f1;box-shadow:none}html.theme--documenter-dark .button.is-light.is-inverted{background-color:rgba(0,0,0,0.7);color:#ecf0f1}html.theme--documenter-dark .button.is-light.is-inverted:hover,html.theme--documenter-dark .button.is-light.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-light.is-inverted[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-light.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#ecf0f1}html.theme--documenter-dark .button.is-light.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--documenter-dark .button.is-light.is-outlined{background-color:transparent;border-color:#ecf0f1;color:#ecf0f1}html.theme--documenter-dark .button.is-light.is-outlined:hover,html.theme--documenter-dark .button.is-light.is-outlined.is-hovered,html.theme--documenter-dark .button.is-light.is-outlined:focus,html.theme--documenter-dark .button.is-light.is-outlined.is-focused{background-color:#ecf0f1;border-color:#ecf0f1;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-light.is-outlined.is-loading::after{border-color:transparent transparent #ecf0f1 #ecf0f1 !important}html.theme--documenter-dark .button.is-light.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-light.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-light.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-light.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--documenter-dark .button.is-light.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-light.is-outlined{background-color:transparent;border-color:#ecf0f1;box-shadow:none;color:#ecf0f1}html.theme--documenter-dark .button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-light.is-inverted.is-outlined:hover,html.theme--documenter-dark .button.is-light.is-inverted.is-outlined.is-hovered,html.theme--documenter-dark .button.is-light.is-inverted.is-outlined:focus,html.theme--documenter-dark .button.is-light.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#ecf0f1}html.theme--documenter-dark .button.is-light.is-inverted.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-light.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-light.is-inverted.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-light.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #ecf0f1 #ecf0f1 !important}html.theme--documenter-dark .button.is-light.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-dark,html.theme--documenter-dark .content kbd.button{background-color:#282f2f;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-dark:hover,html.theme--documenter-dark .content kbd.button:hover,html.theme--documenter-dark .button.is-dark.is-hovered,html.theme--documenter-dark .content kbd.button.is-hovered{background-color:#232829;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-dark:focus,html.theme--documenter-dark .content kbd.button:focus,html.theme--documenter-dark .button.is-dark.is-focused,html.theme--documenter-dark .content kbd.button.is-focused{border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-dark:focus:not(:active),html.theme--documenter-dark .content kbd.button:focus:not(:active),html.theme--documenter-dark .button.is-dark.is-focused:not(:active),html.theme--documenter-dark .content kbd.button.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(40,47,47,0.25)}html.theme--documenter-dark .button.is-dark:active,html.theme--documenter-dark .content kbd.button:active,html.theme--documenter-dark .button.is-dark.is-active,html.theme--documenter-dark .content kbd.button.is-active{background-color:#1d2122;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-dark[disabled],html.theme--documenter-dark .content kbd.button[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-dark,fieldset[disabled] html.theme--documenter-dark .content kbd.button{background-color:#282f2f;border-color:#282f2f;box-shadow:none}html.theme--documenter-dark .button.is-dark.is-inverted,html.theme--documenter-dark .content kbd.button.is-inverted{background-color:#fff;color:#282f2f}html.theme--documenter-dark .button.is-dark.is-inverted:hover,html.theme--documenter-dark .content kbd.button.is-inverted:hover,html.theme--documenter-dark .button.is-dark.is-inverted.is-hovered,html.theme--documenter-dark .content kbd.button.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--documenter-dark .button.is-dark.is-inverted[disabled],html.theme--documenter-dark .content kbd.button.is-inverted[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-dark.is-inverted,fieldset[disabled] html.theme--documenter-dark .content kbd.button.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#282f2f}html.theme--documenter-dark .button.is-dark.is-loading::after,html.theme--documenter-dark .content kbd.button.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-dark.is-outlined,html.theme--documenter-dark .content kbd.button.is-outlined{background-color:transparent;border-color:#282f2f;color:#282f2f}html.theme--documenter-dark .button.is-dark.is-outlined:hover,html.theme--documenter-dark .content kbd.button.is-outlined:hover,html.theme--documenter-dark .button.is-dark.is-outlined.is-hovered,html.theme--documenter-dark .content kbd.button.is-outlined.is-hovered,html.theme--documenter-dark .button.is-dark.is-outlined:focus,html.theme--documenter-dark .content kbd.button.is-outlined:focus,html.theme--documenter-dark .button.is-dark.is-outlined.is-focused,html.theme--documenter-dark .content kbd.button.is-outlined.is-focused{background-color:#282f2f;border-color:#282f2f;color:#fff}html.theme--documenter-dark .button.is-dark.is-outlined.is-loading::after,html.theme--documenter-dark .content kbd.button.is-outlined.is-loading::after{border-color:transparent transparent #282f2f #282f2f !important}html.theme--documenter-dark .button.is-dark.is-outlined.is-loading:hover::after,html.theme--documenter-dark .content kbd.button.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-dark.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .content kbd.button.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-dark.is-outlined.is-loading:focus::after,html.theme--documenter-dark .content kbd.button.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-dark.is-outlined.is-loading.is-focused::after,html.theme--documenter-dark .content kbd.button.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-dark.is-outlined[disabled],html.theme--documenter-dark .content kbd.button.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-dark.is-outlined,fieldset[disabled] html.theme--documenter-dark .content kbd.button.is-outlined{background-color:transparent;border-color:#282f2f;box-shadow:none;color:#282f2f}html.theme--documenter-dark .button.is-dark.is-inverted.is-outlined,html.theme--documenter-dark .content kbd.button.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--documenter-dark .button.is-dark.is-inverted.is-outlined:hover,html.theme--documenter-dark .content kbd.button.is-inverted.is-outlined:hover,html.theme--documenter-dark .button.is-dark.is-inverted.is-outlined.is-hovered,html.theme--documenter-dark .content kbd.button.is-inverted.is-outlined.is-hovered,html.theme--documenter-dark .button.is-dark.is-inverted.is-outlined:focus,html.theme--documenter-dark .content kbd.button.is-inverted.is-outlined:focus,html.theme--documenter-dark .button.is-dark.is-inverted.is-outlined.is-focused,html.theme--documenter-dark .content kbd.button.is-inverted.is-outlined.is-focused{background-color:#fff;color:#282f2f}html.theme--documenter-dark .button.is-dark.is-inverted.is-outlined.is-loading:hover::after,html.theme--documenter-dark .content kbd.button.is-inverted.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-dark.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .content kbd.button.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-dark.is-inverted.is-outlined.is-loading:focus::after,html.theme--documenter-dark .content kbd.button.is-inverted.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-dark.is-inverted.is-outlined.is-loading.is-focused::after,html.theme--documenter-dark .content kbd.button.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #282f2f #282f2f !important}html.theme--documenter-dark .button.is-dark.is-inverted.is-outlined[disabled],html.theme--documenter-dark .content kbd.button.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-dark.is-inverted.is-outlined,fieldset[disabled] html.theme--documenter-dark .content kbd.button.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--documenter-dark .button.is-primary,html.theme--documenter-dark .docstring>section>a.button.docs-sourcelink{background-color:#375a7f;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-primary:hover,html.theme--documenter-dark .docstring>section>a.button.docs-sourcelink:hover,html.theme--documenter-dark .button.is-primary.is-hovered,html.theme--documenter-dark .docstring>section>a.button.is-hovered.docs-sourcelink{background-color:#335476;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-primary:focus,html.theme--documenter-dark .docstring>section>a.button.docs-sourcelink:focus,html.theme--documenter-dark .button.is-primary.is-focused,html.theme--documenter-dark .docstring>section>a.button.is-focused.docs-sourcelink{border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-primary:focus:not(:active),html.theme--documenter-dark .docstring>section>a.button.docs-sourcelink:focus:not(:active),html.theme--documenter-dark .button.is-primary.is-focused:not(:active),html.theme--documenter-dark .docstring>section>a.button.is-focused.docs-sourcelink:not(:active){box-shadow:0 0 0 0.125em rgba(55,90,127,0.25)}html.theme--documenter-dark .button.is-primary:active,html.theme--documenter-dark .docstring>section>a.button.docs-sourcelink:active,html.theme--documenter-dark .button.is-primary.is-active,html.theme--documenter-dark .docstring>section>a.button.is-active.docs-sourcelink{background-color:#2f4d6d;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-primary[disabled],html.theme--documenter-dark .docstring>section>a.button.docs-sourcelink[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-primary,fieldset[disabled] html.theme--documenter-dark .docstring>section>a.button.docs-sourcelink{background-color:#375a7f;border-color:#375a7f;box-shadow:none}html.theme--documenter-dark .button.is-primary.is-inverted,html.theme--documenter-dark .docstring>section>a.button.is-inverted.docs-sourcelink{background-color:#fff;color:#375a7f}html.theme--documenter-dark .button.is-primary.is-inverted:hover,html.theme--documenter-dark .docstring>section>a.button.is-inverted.docs-sourcelink:hover,html.theme--documenter-dark .button.is-primary.is-inverted.is-hovered,html.theme--documenter-dark .docstring>section>a.button.is-inverted.is-hovered.docs-sourcelink{background-color:#f2f2f2}html.theme--documenter-dark .button.is-primary.is-inverted[disabled],html.theme--documenter-dark .docstring>section>a.button.is-inverted.docs-sourcelink[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-primary.is-inverted,fieldset[disabled] html.theme--documenter-dark .docstring>section>a.button.is-inverted.docs-sourcelink{background-color:#fff;border-color:transparent;box-shadow:none;color:#375a7f}html.theme--documenter-dark .button.is-primary.is-loading::after,html.theme--documenter-dark .docstring>section>a.button.is-loading.docs-sourcelink::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-primary.is-outlined,html.theme--documenter-dark .docstring>section>a.button.is-outlined.docs-sourcelink{background-color:transparent;border-color:#375a7f;color:#375a7f}html.theme--documenter-dark .button.is-primary.is-outlined:hover,html.theme--documenter-dark .docstring>section>a.button.is-outlined.docs-sourcelink:hover,html.theme--documenter-dark .button.is-primary.is-outlined.is-hovered,html.theme--documenter-dark .docstring>section>a.button.is-outlined.is-hovered.docs-sourcelink,html.theme--documenter-dark .button.is-primary.is-outlined:focus,html.theme--documenter-dark .docstring>section>a.button.is-outlined.docs-sourcelink:focus,html.theme--documenter-dark .button.is-primary.is-outlined.is-focused,html.theme--documenter-dark .docstring>section>a.button.is-outlined.is-focused.docs-sourcelink{background-color:#375a7f;border-color:#375a7f;color:#fff}html.theme--documenter-dark .button.is-primary.is-outlined.is-loading::after,html.theme--documenter-dark .docstring>section>a.button.is-outlined.is-loading.docs-sourcelink::after{border-color:transparent transparent #375a7f #375a7f !important}html.theme--documenter-dark .button.is-primary.is-outlined.is-loading:hover::after,html.theme--documenter-dark .docstring>section>a.button.is-outlined.is-loading.docs-sourcelink:hover::after,html.theme--documenter-dark .button.is-primary.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .docstring>section>a.button.is-outlined.is-loading.is-hovered.docs-sourcelink::after,html.theme--documenter-dark .button.is-primary.is-outlined.is-loading:focus::after,html.theme--documenter-dark .docstring>section>a.button.is-outlined.is-loading.docs-sourcelink:focus::after,html.theme--documenter-dark .button.is-primary.is-outlined.is-loading.is-focused::after,html.theme--documenter-dark .docstring>section>a.button.is-outlined.is-loading.is-focused.docs-sourcelink::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-primary.is-outlined[disabled],html.theme--documenter-dark .docstring>section>a.button.is-outlined.docs-sourcelink[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-primary.is-outlined,fieldset[disabled] html.theme--documenter-dark .docstring>section>a.button.is-outlined.docs-sourcelink{background-color:transparent;border-color:#375a7f;box-shadow:none;color:#375a7f}html.theme--documenter-dark .button.is-primary.is-inverted.is-outlined,html.theme--documenter-dark .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink{background-color:transparent;border-color:#fff;color:#fff}html.theme--documenter-dark .button.is-primary.is-inverted.is-outlined:hover,html.theme--documenter-dark .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink:hover,html.theme--documenter-dark .button.is-primary.is-inverted.is-outlined.is-hovered,html.theme--documenter-dark .docstring>section>a.button.is-inverted.is-outlined.is-hovered.docs-sourcelink,html.theme--documenter-dark .button.is-primary.is-inverted.is-outlined:focus,html.theme--documenter-dark .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink:focus,html.theme--documenter-dark .button.is-primary.is-inverted.is-outlined.is-focused,html.theme--documenter-dark .docstring>section>a.button.is-inverted.is-outlined.is-focused.docs-sourcelink{background-color:#fff;color:#375a7f}html.theme--documenter-dark .button.is-primary.is-inverted.is-outlined.is-loading:hover::after,html.theme--documenter-dark .docstring>section>a.button.is-inverted.is-outlined.is-loading.docs-sourcelink:hover::after,html.theme--documenter-dark .button.is-primary.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .docstring>section>a.button.is-inverted.is-outlined.is-loading.is-hovered.docs-sourcelink::after,html.theme--documenter-dark .button.is-primary.is-inverted.is-outlined.is-loading:focus::after,html.theme--documenter-dark .docstring>section>a.button.is-inverted.is-outlined.is-loading.docs-sourcelink:focus::after,html.theme--documenter-dark .button.is-primary.is-inverted.is-outlined.is-loading.is-focused::after,html.theme--documenter-dark .docstring>section>a.button.is-inverted.is-outlined.is-loading.is-focused.docs-sourcelink::after{border-color:transparent transparent #375a7f #375a7f !important}html.theme--documenter-dark .button.is-primary.is-inverted.is-outlined[disabled],html.theme--documenter-dark .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-primary.is-inverted.is-outlined,fieldset[disabled] html.theme--documenter-dark .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--documenter-dark .button.is-primary.is-light,html.theme--documenter-dark .docstring>section>a.button.is-light.docs-sourcelink{background-color:#f1f5f9;color:#4d7eb2}html.theme--documenter-dark .button.is-primary.is-light:hover,html.theme--documenter-dark .docstring>section>a.button.is-light.docs-sourcelink:hover,html.theme--documenter-dark .button.is-primary.is-light.is-hovered,html.theme--documenter-dark .docstring>section>a.button.is-light.is-hovered.docs-sourcelink{background-color:#e8eef5;border-color:transparent;color:#4d7eb2}html.theme--documenter-dark .button.is-primary.is-light:active,html.theme--documenter-dark .docstring>section>a.button.is-light.docs-sourcelink:active,html.theme--documenter-dark .button.is-primary.is-light.is-active,html.theme--documenter-dark .docstring>section>a.button.is-light.is-active.docs-sourcelink{background-color:#dfe8f1;border-color:transparent;color:#4d7eb2}html.theme--documenter-dark .button.is-link{background-color:#1abc9c;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-link:hover,html.theme--documenter-dark .button.is-link.is-hovered{background-color:#18b193;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-link:focus,html.theme--documenter-dark .button.is-link.is-focused{border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-link:focus:not(:active),html.theme--documenter-dark .button.is-link.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(26,188,156,0.25)}html.theme--documenter-dark .button.is-link:active,html.theme--documenter-dark .button.is-link.is-active{background-color:#17a689;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-link[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-link{background-color:#1abc9c;border-color:#1abc9c;box-shadow:none}html.theme--documenter-dark .button.is-link.is-inverted{background-color:#fff;color:#1abc9c}html.theme--documenter-dark .button.is-link.is-inverted:hover,html.theme--documenter-dark .button.is-link.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--documenter-dark .button.is-link.is-inverted[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-link.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#1abc9c}html.theme--documenter-dark .button.is-link.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-link.is-outlined{background-color:transparent;border-color:#1abc9c;color:#1abc9c}html.theme--documenter-dark .button.is-link.is-outlined:hover,html.theme--documenter-dark .button.is-link.is-outlined.is-hovered,html.theme--documenter-dark .button.is-link.is-outlined:focus,html.theme--documenter-dark .button.is-link.is-outlined.is-focused{background-color:#1abc9c;border-color:#1abc9c;color:#fff}html.theme--documenter-dark .button.is-link.is-outlined.is-loading::after{border-color:transparent transparent #1abc9c #1abc9c !important}html.theme--documenter-dark .button.is-link.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-link.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-link.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-link.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-link.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-link.is-outlined{background-color:transparent;border-color:#1abc9c;box-shadow:none;color:#1abc9c}html.theme--documenter-dark .button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--documenter-dark .button.is-link.is-inverted.is-outlined:hover,html.theme--documenter-dark .button.is-link.is-inverted.is-outlined.is-hovered,html.theme--documenter-dark .button.is-link.is-inverted.is-outlined:focus,html.theme--documenter-dark .button.is-link.is-inverted.is-outlined.is-focused{background-color:#fff;color:#1abc9c}html.theme--documenter-dark .button.is-link.is-inverted.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-link.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-link.is-inverted.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-link.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #1abc9c #1abc9c !important}html.theme--documenter-dark .button.is-link.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--documenter-dark .button.is-link.is-light{background-color:#edfdf9;color:#15987e}html.theme--documenter-dark .button.is-link.is-light:hover,html.theme--documenter-dark .button.is-link.is-light.is-hovered{background-color:#e2fbf6;border-color:transparent;color:#15987e}html.theme--documenter-dark .button.is-link.is-light:active,html.theme--documenter-dark .button.is-link.is-light.is-active{background-color:#d7f9f3;border-color:transparent;color:#15987e}html.theme--documenter-dark .button.is-info{background-color:#3c5dcd;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-info:hover,html.theme--documenter-dark .button.is-info.is-hovered{background-color:#3355c9;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-info:focus,html.theme--documenter-dark .button.is-info.is-focused{border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-info:focus:not(:active),html.theme--documenter-dark .button.is-info.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(60,93,205,0.25)}html.theme--documenter-dark .button.is-info:active,html.theme--documenter-dark .button.is-info.is-active{background-color:#3151bf;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-info[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-info{background-color:#3c5dcd;border-color:#3c5dcd;box-shadow:none}html.theme--documenter-dark .button.is-info.is-inverted{background-color:#fff;color:#3c5dcd}html.theme--documenter-dark .button.is-info.is-inverted:hover,html.theme--documenter-dark .button.is-info.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--documenter-dark .button.is-info.is-inverted[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-info.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#3c5dcd}html.theme--documenter-dark .button.is-info.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-info.is-outlined{background-color:transparent;border-color:#3c5dcd;color:#3c5dcd}html.theme--documenter-dark .button.is-info.is-outlined:hover,html.theme--documenter-dark .button.is-info.is-outlined.is-hovered,html.theme--documenter-dark .button.is-info.is-outlined:focus,html.theme--documenter-dark .button.is-info.is-outlined.is-focused{background-color:#3c5dcd;border-color:#3c5dcd;color:#fff}html.theme--documenter-dark .button.is-info.is-outlined.is-loading::after{border-color:transparent transparent #3c5dcd #3c5dcd !important}html.theme--documenter-dark .button.is-info.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-info.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-info.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-info.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-info.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-info.is-outlined{background-color:transparent;border-color:#3c5dcd;box-shadow:none;color:#3c5dcd}html.theme--documenter-dark .button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--documenter-dark .button.is-info.is-inverted.is-outlined:hover,html.theme--documenter-dark .button.is-info.is-inverted.is-outlined.is-hovered,html.theme--documenter-dark .button.is-info.is-inverted.is-outlined:focus,html.theme--documenter-dark .button.is-info.is-inverted.is-outlined.is-focused{background-color:#fff;color:#3c5dcd}html.theme--documenter-dark .button.is-info.is-inverted.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-info.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-info.is-inverted.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-info.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #3c5dcd #3c5dcd !important}html.theme--documenter-dark .button.is-info.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--documenter-dark .button.is-info.is-light{background-color:#eff2fb;color:#3253c3}html.theme--documenter-dark .button.is-info.is-light:hover,html.theme--documenter-dark .button.is-info.is-light.is-hovered{background-color:#e5e9f8;border-color:transparent;color:#3253c3}html.theme--documenter-dark .button.is-info.is-light:active,html.theme--documenter-dark .button.is-info.is-light.is-active{background-color:#dae1f6;border-color:transparent;color:#3253c3}html.theme--documenter-dark .button.is-success{background-color:#259a12;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-success:hover,html.theme--documenter-dark .button.is-success.is-hovered{background-color:#228f11;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-success:focus,html.theme--documenter-dark .button.is-success.is-focused{border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-success:focus:not(:active),html.theme--documenter-dark .button.is-success.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(37,154,18,0.25)}html.theme--documenter-dark .button.is-success:active,html.theme--documenter-dark .button.is-success.is-active{background-color:#20830f;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-success[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-success{background-color:#259a12;border-color:#259a12;box-shadow:none}html.theme--documenter-dark .button.is-success.is-inverted{background-color:#fff;color:#259a12}html.theme--documenter-dark .button.is-success.is-inverted:hover,html.theme--documenter-dark .button.is-success.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--documenter-dark .button.is-success.is-inverted[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-success.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#259a12}html.theme--documenter-dark .button.is-success.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-success.is-outlined{background-color:transparent;border-color:#259a12;color:#259a12}html.theme--documenter-dark .button.is-success.is-outlined:hover,html.theme--documenter-dark .button.is-success.is-outlined.is-hovered,html.theme--documenter-dark .button.is-success.is-outlined:focus,html.theme--documenter-dark .button.is-success.is-outlined.is-focused{background-color:#259a12;border-color:#259a12;color:#fff}html.theme--documenter-dark .button.is-success.is-outlined.is-loading::after{border-color:transparent transparent #259a12 #259a12 !important}html.theme--documenter-dark .button.is-success.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-success.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-success.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-success.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-success.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-success.is-outlined{background-color:transparent;border-color:#259a12;box-shadow:none;color:#259a12}html.theme--documenter-dark .button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--documenter-dark .button.is-success.is-inverted.is-outlined:hover,html.theme--documenter-dark .button.is-success.is-inverted.is-outlined.is-hovered,html.theme--documenter-dark .button.is-success.is-inverted.is-outlined:focus,html.theme--documenter-dark .button.is-success.is-inverted.is-outlined.is-focused{background-color:#fff;color:#259a12}html.theme--documenter-dark .button.is-success.is-inverted.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-success.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-success.is-inverted.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-success.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #259a12 #259a12 !important}html.theme--documenter-dark .button.is-success.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--documenter-dark .button.is-success.is-light{background-color:#effded;color:#2ec016}html.theme--documenter-dark .button.is-success.is-light:hover,html.theme--documenter-dark .button.is-success.is-light.is-hovered{background-color:#e5fce1;border-color:transparent;color:#2ec016}html.theme--documenter-dark .button.is-success.is-light:active,html.theme--documenter-dark .button.is-success.is-light.is-active{background-color:#dbfad6;border-color:transparent;color:#2ec016}html.theme--documenter-dark .button.is-warning{background-color:#f4c72f;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-warning:hover,html.theme--documenter-dark .button.is-warning.is-hovered{background-color:#f3c423;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-warning:focus,html.theme--documenter-dark .button.is-warning.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-warning:focus:not(:active),html.theme--documenter-dark .button.is-warning.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(244,199,47,0.25)}html.theme--documenter-dark .button.is-warning:active,html.theme--documenter-dark .button.is-warning.is-active{background-color:#f3c017;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-warning[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-warning{background-color:#f4c72f;border-color:#f4c72f;box-shadow:none}html.theme--documenter-dark .button.is-warning.is-inverted{background-color:rgba(0,0,0,0.7);color:#f4c72f}html.theme--documenter-dark .button.is-warning.is-inverted:hover,html.theme--documenter-dark .button.is-warning.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-warning.is-inverted[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-warning.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#f4c72f}html.theme--documenter-dark .button.is-warning.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--documenter-dark .button.is-warning.is-outlined{background-color:transparent;border-color:#f4c72f;color:#f4c72f}html.theme--documenter-dark .button.is-warning.is-outlined:hover,html.theme--documenter-dark .button.is-warning.is-outlined.is-hovered,html.theme--documenter-dark .button.is-warning.is-outlined:focus,html.theme--documenter-dark .button.is-warning.is-outlined.is-focused{background-color:#f4c72f;border-color:#f4c72f;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-warning.is-outlined.is-loading::after{border-color:transparent transparent #f4c72f #f4c72f !important}html.theme--documenter-dark .button.is-warning.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-warning.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-warning.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-warning.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--documenter-dark .button.is-warning.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-warning.is-outlined{background-color:transparent;border-color:#f4c72f;box-shadow:none;color:#f4c72f}html.theme--documenter-dark .button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-warning.is-inverted.is-outlined:hover,html.theme--documenter-dark .button.is-warning.is-inverted.is-outlined.is-hovered,html.theme--documenter-dark .button.is-warning.is-inverted.is-outlined:focus,html.theme--documenter-dark .button.is-warning.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#f4c72f}html.theme--documenter-dark .button.is-warning.is-inverted.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-warning.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-warning.is-inverted.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-warning.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #f4c72f #f4c72f !important}html.theme--documenter-dark .button.is-warning.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-warning.is-light{background-color:#fefaec;color:#8c6e07}html.theme--documenter-dark .button.is-warning.is-light:hover,html.theme--documenter-dark .button.is-warning.is-light.is-hovered{background-color:#fdf7e0;border-color:transparent;color:#8c6e07}html.theme--documenter-dark .button.is-warning.is-light:active,html.theme--documenter-dark .button.is-warning.is-light.is-active{background-color:#fdf3d3;border-color:transparent;color:#8c6e07}html.theme--documenter-dark .button.is-danger{background-color:#cb3c33;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-danger:hover,html.theme--documenter-dark .button.is-danger.is-hovered{background-color:#c13930;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-danger:focus,html.theme--documenter-dark .button.is-danger.is-focused{border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-danger:focus:not(:active),html.theme--documenter-dark .button.is-danger.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(203,60,51,0.25)}html.theme--documenter-dark .button.is-danger:active,html.theme--documenter-dark .button.is-danger.is-active{background-color:#b7362e;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-danger[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-danger{background-color:#cb3c33;border-color:#cb3c33;box-shadow:none}html.theme--documenter-dark .button.is-danger.is-inverted{background-color:#fff;color:#cb3c33}html.theme--documenter-dark .button.is-danger.is-inverted:hover,html.theme--documenter-dark .button.is-danger.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--documenter-dark .button.is-danger.is-inverted[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-danger.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#cb3c33}html.theme--documenter-dark .button.is-danger.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-danger.is-outlined{background-color:transparent;border-color:#cb3c33;color:#cb3c33}html.theme--documenter-dark .button.is-danger.is-outlined:hover,html.theme--documenter-dark .button.is-danger.is-outlined.is-hovered,html.theme--documenter-dark .button.is-danger.is-outlined:focus,html.theme--documenter-dark .button.is-danger.is-outlined.is-focused{background-color:#cb3c33;border-color:#cb3c33;color:#fff}html.theme--documenter-dark .button.is-danger.is-outlined.is-loading::after{border-color:transparent transparent #cb3c33 #cb3c33 !important}html.theme--documenter-dark .button.is-danger.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-danger.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-danger.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-danger.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-danger.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-danger.is-outlined{background-color:transparent;border-color:#cb3c33;box-shadow:none;color:#cb3c33}html.theme--documenter-dark .button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--documenter-dark .button.is-danger.is-inverted.is-outlined:hover,html.theme--documenter-dark .button.is-danger.is-inverted.is-outlined.is-hovered,html.theme--documenter-dark .button.is-danger.is-inverted.is-outlined:focus,html.theme--documenter-dark .button.is-danger.is-inverted.is-outlined.is-focused{background-color:#fff;color:#cb3c33}html.theme--documenter-dark .button.is-danger.is-inverted.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-danger.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-danger.is-inverted.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-danger.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #cb3c33 #cb3c33 !important}html.theme--documenter-dark .button.is-danger.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--documenter-dark .button.is-danger.is-light{background-color:#fbefef;color:#c03930}html.theme--documenter-dark .button.is-danger.is-light:hover,html.theme--documenter-dark .button.is-danger.is-light.is-hovered{background-color:#f8e6e5;border-color:transparent;color:#c03930}html.theme--documenter-dark .button.is-danger.is-light:active,html.theme--documenter-dark .button.is-danger.is-light.is-active{background-color:#f6dcda;border-color:transparent;color:#c03930}html.theme--documenter-dark .button.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.button{font-size:.75rem}html.theme--documenter-dark .button.is-small:not(.is-rounded),html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.button:not(.is-rounded){border-radius:3px}html.theme--documenter-dark .button.is-normal{font-size:1rem}html.theme--documenter-dark .button.is-medium{font-size:1.25rem}html.theme--documenter-dark .button.is-large{font-size:1.5rem}html.theme--documenter-dark .button[disabled],fieldset[disabled] html.theme--documenter-dark .button{background-color:#8c9b9d;border-color:#5e6d6f;box-shadow:none;opacity:.5}html.theme--documenter-dark .button.is-fullwidth{display:flex;width:100%}html.theme--documenter-dark .button.is-loading{color:transparent !important;pointer-events:none}html.theme--documenter-dark .button.is-loading::after{position:absolute;left:calc(50% - (1em * 0.5));top:calc(50% - (1em * 0.5));position:absolute !important}html.theme--documenter-dark .button.is-static{background-color:#282f2f;border-color:#5e6d6f;color:#dbdee0;box-shadow:none;pointer-events:none}html.theme--documenter-dark .button.is-rounded,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.button{border-radius:9999px;padding-left:calc(1em + 0.25em);padding-right:calc(1em + 0.25em)}html.theme--documenter-dark .buttons{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}html.theme--documenter-dark .buttons .button{margin-bottom:0.5rem}html.theme--documenter-dark .buttons .button:not(:last-child):not(.is-fullwidth){margin-right:.5rem}html.theme--documenter-dark .buttons:last-child{margin-bottom:-0.5rem}html.theme--documenter-dark .buttons:not(:last-child){margin-bottom:1rem}html.theme--documenter-dark .buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large){font-size:.75rem}html.theme--documenter-dark .buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large):not(.is-rounded){border-radius:3px}html.theme--documenter-dark .buttons.are-medium .button:not(.is-small):not(.is-normal):not(.is-large){font-size:1.25rem}html.theme--documenter-dark .buttons.are-large .button:not(.is-small):not(.is-normal):not(.is-medium){font-size:1.5rem}html.theme--documenter-dark .buttons.has-addons .button:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}html.theme--documenter-dark .buttons.has-addons .button:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0;margin-right:-1px}html.theme--documenter-dark .buttons.has-addons .button:last-child{margin-right:0}html.theme--documenter-dark .buttons.has-addons .button:hover,html.theme--documenter-dark .buttons.has-addons .button.is-hovered{z-index:2}html.theme--documenter-dark .buttons.has-addons .button:focus,html.theme--documenter-dark .buttons.has-addons .button.is-focused,html.theme--documenter-dark .buttons.has-addons .button:active,html.theme--documenter-dark .buttons.has-addons .button.is-active,html.theme--documenter-dark .buttons.has-addons .button.is-selected{z-index:3}html.theme--documenter-dark .buttons.has-addons .button:focus:hover,html.theme--documenter-dark .buttons.has-addons .button.is-focused:hover,html.theme--documenter-dark .buttons.has-addons .button:active:hover,html.theme--documenter-dark .buttons.has-addons .button.is-active:hover,html.theme--documenter-dark .buttons.has-addons .button.is-selected:hover{z-index:4}html.theme--documenter-dark .buttons.has-addons .button.is-expanded{flex-grow:1;flex-shrink:1}html.theme--documenter-dark .buttons.is-centered{justify-content:center}html.theme--documenter-dark .buttons.is-centered:not(.has-addons) .button:not(.is-fullwidth){margin-left:0.25rem;margin-right:0.25rem}html.theme--documenter-dark .buttons.is-right{justify-content:flex-end}html.theme--documenter-dark .buttons.is-right:not(.has-addons) .button:not(.is-fullwidth){margin-left:0.25rem;margin-right:0.25rem}@media screen and (max-width: 768px){html.theme--documenter-dark .button.is-responsive.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-responsive{font-size:.5625rem}html.theme--documenter-dark .button.is-responsive,html.theme--documenter-dark .button.is-responsive.is-normal{font-size:.65625rem}html.theme--documenter-dark .button.is-responsive.is-medium{font-size:.75rem}html.theme--documenter-dark .button.is-responsive.is-large{font-size:1rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--documenter-dark .button.is-responsive.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-responsive{font-size:.65625rem}html.theme--documenter-dark .button.is-responsive,html.theme--documenter-dark .button.is-responsive.is-normal{font-size:.75rem}html.theme--documenter-dark .button.is-responsive.is-medium{font-size:1rem}html.theme--documenter-dark .button.is-responsive.is-large{font-size:1.25rem}}html.theme--documenter-dark .container{flex-grow:1;margin:0 auto;position:relative;width:auto}html.theme--documenter-dark .container.is-fluid{max-width:none !important;padding-left:32px;padding-right:32px;width:100%}@media screen and (min-width: 1056px){html.theme--documenter-dark .container{max-width:992px}}@media screen and (max-width: 1215px){html.theme--documenter-dark .container.is-widescreen:not(.is-max-desktop){max-width:1152px}}@media screen and (max-width: 1407px){html.theme--documenter-dark .container.is-fullhd:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}@media screen and (min-width: 1216px){html.theme--documenter-dark .container:not(.is-max-desktop){max-width:1152px}}@media screen and (min-width: 1408px){html.theme--documenter-dark .container:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}html.theme--documenter-dark .content li+li{margin-top:0.25em}html.theme--documenter-dark .content p:not(:last-child),html.theme--documenter-dark .content dl:not(:last-child),html.theme--documenter-dark .content ol:not(:last-child),html.theme--documenter-dark .content ul:not(:last-child),html.theme--documenter-dark .content blockquote:not(:last-child),html.theme--documenter-dark .content pre:not(:last-child),html.theme--documenter-dark .content table:not(:last-child){margin-bottom:1em}html.theme--documenter-dark .content h1,html.theme--documenter-dark .content h2,html.theme--documenter-dark .content h3,html.theme--documenter-dark .content h4,html.theme--documenter-dark .content h5,html.theme--documenter-dark .content h6{color:#f2f2f2;font-weight:600;line-height:1.125}html.theme--documenter-dark .content h1{font-size:2em;margin-bottom:0.5em}html.theme--documenter-dark .content h1:not(:first-child){margin-top:1em}html.theme--documenter-dark .content h2{font-size:1.75em;margin-bottom:0.5714em}html.theme--documenter-dark .content h2:not(:first-child){margin-top:1.1428em}html.theme--documenter-dark .content h3{font-size:1.5em;margin-bottom:0.6666em}html.theme--documenter-dark .content h3:not(:first-child){margin-top:1.3333em}html.theme--documenter-dark .content h4{font-size:1.25em;margin-bottom:0.8em}html.theme--documenter-dark .content h5{font-size:1.125em;margin-bottom:0.8888em}html.theme--documenter-dark .content h6{font-size:1em;margin-bottom:1em}html.theme--documenter-dark .content blockquote{background-color:#282f2f;border-left:5px solid #5e6d6f;padding:1.25em 1.5em}html.theme--documenter-dark .content ol{list-style-position:outside;margin-left:2em;margin-top:1em}html.theme--documenter-dark .content ol:not([type]){list-style-type:decimal}html.theme--documenter-dark .content ol.is-lower-alpha:not([type]){list-style-type:lower-alpha}html.theme--documenter-dark .content ol.is-lower-roman:not([type]){list-style-type:lower-roman}html.theme--documenter-dark .content ol.is-upper-alpha:not([type]){list-style-type:upper-alpha}html.theme--documenter-dark .content ol.is-upper-roman:not([type]){list-style-type:upper-roman}html.theme--documenter-dark .content ul{list-style:disc outside;margin-left:2em;margin-top:1em}html.theme--documenter-dark .content ul ul{list-style-type:circle;margin-top:0.5em}html.theme--documenter-dark .content ul ul ul{list-style-type:square}html.theme--documenter-dark .content dd{margin-left:2em}html.theme--documenter-dark .content figure{margin-left:2em;margin-right:2em;text-align:center}html.theme--documenter-dark .content figure:not(:first-child){margin-top:2em}html.theme--documenter-dark .content figure:not(:last-child){margin-bottom:2em}html.theme--documenter-dark .content figure img{display:inline-block}html.theme--documenter-dark .content figure figcaption{font-style:italic}html.theme--documenter-dark .content pre{-webkit-overflow-scrolling:touch;overflow-x:auto;padding:0;white-space:pre;word-wrap:normal}html.theme--documenter-dark .content sup,html.theme--documenter-dark .content sub{font-size:75%}html.theme--documenter-dark .content table{width:100%}html.theme--documenter-dark .content table td,html.theme--documenter-dark .content table th{border:1px solid #5e6d6f;border-width:0 0 1px;padding:0.5em 0.75em;vertical-align:top}html.theme--documenter-dark .content table th{color:#f2f2f2}html.theme--documenter-dark .content table th:not([align]){text-align:inherit}html.theme--documenter-dark .content table thead td,html.theme--documenter-dark .content table thead th{border-width:0 0 2px;color:#f2f2f2}html.theme--documenter-dark .content table tfoot td,html.theme--documenter-dark .content table tfoot th{border-width:2px 0 0;color:#f2f2f2}html.theme--documenter-dark .content table tbody tr:last-child td,html.theme--documenter-dark .content table tbody tr:last-child th{border-bottom-width:0}html.theme--documenter-dark .content .tabs li+li{margin-top:0}html.theme--documenter-dark .content.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.content{font-size:.75rem}html.theme--documenter-dark .content.is-normal{font-size:1rem}html.theme--documenter-dark .content.is-medium{font-size:1.25rem}html.theme--documenter-dark .content.is-large{font-size:1.5rem}html.theme--documenter-dark .icon{align-items:center;display:inline-flex;justify-content:center;height:1.5rem;width:1.5rem}html.theme--documenter-dark .icon.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.icon{height:1rem;width:1rem}html.theme--documenter-dark .icon.is-medium{height:2rem;width:2rem}html.theme--documenter-dark .icon.is-large{height:3rem;width:3rem}html.theme--documenter-dark .icon-text{align-items:flex-start;color:inherit;display:inline-flex;flex-wrap:wrap;line-height:1.5rem;vertical-align:top}html.theme--documenter-dark .icon-text .icon{flex-grow:0;flex-shrink:0}html.theme--documenter-dark .icon-text .icon:not(:last-child){margin-right:.25em}html.theme--documenter-dark .icon-text .icon:not(:first-child){margin-left:.25em}html.theme--documenter-dark div.icon-text{display:flex}html.theme--documenter-dark .image,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img{display:block;position:relative}html.theme--documenter-dark .image img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img img{display:block;height:auto;width:100%}html.theme--documenter-dark .image img.is-rounded,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img img.is-rounded{border-radius:9999px}html.theme--documenter-dark .image.is-fullwidth,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-fullwidth{width:100%}html.theme--documenter-dark .image.is-square img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-square img,html.theme--documenter-dark .image.is-square .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-square .has-ratio,html.theme--documenter-dark .image.is-1by1 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by1 img,html.theme--documenter-dark .image.is-1by1 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by1 .has-ratio,html.theme--documenter-dark .image.is-5by4 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-5by4 img,html.theme--documenter-dark .image.is-5by4 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-5by4 .has-ratio,html.theme--documenter-dark .image.is-4by3 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-4by3 img,html.theme--documenter-dark .image.is-4by3 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-4by3 .has-ratio,html.theme--documenter-dark .image.is-3by2 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by2 img,html.theme--documenter-dark .image.is-3by2 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by2 .has-ratio,html.theme--documenter-dark .image.is-5by3 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-5by3 img,html.theme--documenter-dark .image.is-5by3 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-5by3 .has-ratio,html.theme--documenter-dark .image.is-16by9 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-16by9 img,html.theme--documenter-dark .image.is-16by9 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-16by9 .has-ratio,html.theme--documenter-dark .image.is-2by1 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-2by1 img,html.theme--documenter-dark .image.is-2by1 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-2by1 .has-ratio,html.theme--documenter-dark .image.is-3by1 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by1 img,html.theme--documenter-dark .image.is-3by1 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by1 .has-ratio,html.theme--documenter-dark .image.is-4by5 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-4by5 img,html.theme--documenter-dark .image.is-4by5 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-4by5 .has-ratio,html.theme--documenter-dark .image.is-3by4 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by4 img,html.theme--documenter-dark .image.is-3by4 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by4 .has-ratio,html.theme--documenter-dark .image.is-2by3 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-2by3 img,html.theme--documenter-dark .image.is-2by3 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-2by3 .has-ratio,html.theme--documenter-dark .image.is-3by5 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by5 img,html.theme--documenter-dark .image.is-3by5 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by5 .has-ratio,html.theme--documenter-dark .image.is-9by16 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-9by16 img,html.theme--documenter-dark .image.is-9by16 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-9by16 .has-ratio,html.theme--documenter-dark .image.is-1by2 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by2 img,html.theme--documenter-dark .image.is-1by2 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by2 .has-ratio,html.theme--documenter-dark .image.is-1by3 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by3 img,html.theme--documenter-dark .image.is-1by3 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by3 .has-ratio{height:100%;width:100%}html.theme--documenter-dark .image.is-square,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-square,html.theme--documenter-dark .image.is-1by1,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by1{padding-top:100%}html.theme--documenter-dark .image.is-5by4,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-5by4{padding-top:80%}html.theme--documenter-dark .image.is-4by3,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-4by3{padding-top:75%}html.theme--documenter-dark .image.is-3by2,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by2{padding-top:66.6666%}html.theme--documenter-dark .image.is-5by3,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-5by3{padding-top:60%}html.theme--documenter-dark .image.is-16by9,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-16by9{padding-top:56.25%}html.theme--documenter-dark .image.is-2by1,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-2by1{padding-top:50%}html.theme--documenter-dark .image.is-3by1,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by1{padding-top:33.3333%}html.theme--documenter-dark .image.is-4by5,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-4by5{padding-top:125%}html.theme--documenter-dark .image.is-3by4,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by4{padding-top:133.3333%}html.theme--documenter-dark .image.is-2by3,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-2by3{padding-top:150%}html.theme--documenter-dark .image.is-3by5,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by5{padding-top:166.6666%}html.theme--documenter-dark .image.is-9by16,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-9by16{padding-top:177.7777%}html.theme--documenter-dark .image.is-1by2,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by2{padding-top:200%}html.theme--documenter-dark .image.is-1by3,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by3{padding-top:300%}html.theme--documenter-dark .image.is-16x16,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-16x16{height:16px;width:16px}html.theme--documenter-dark .image.is-24x24,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-24x24{height:24px;width:24px}html.theme--documenter-dark .image.is-32x32,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-32x32{height:32px;width:32px}html.theme--documenter-dark .image.is-48x48,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-48x48{height:48px;width:48px}html.theme--documenter-dark .image.is-64x64,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-64x64{height:64px;width:64px}html.theme--documenter-dark .image.is-96x96,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-96x96{height:96px;width:96px}html.theme--documenter-dark .image.is-128x128,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-128x128{height:128px;width:128px}html.theme--documenter-dark .notification{background-color:#282f2f;border-radius:.4em;position:relative;padding:1.25rem 2.5rem 1.25rem 1.5rem}html.theme--documenter-dark .notification a:not(.button):not(.dropdown-item){color:currentColor;text-decoration:underline}html.theme--documenter-dark .notification strong{color:currentColor}html.theme--documenter-dark .notification code,html.theme--documenter-dark .notification pre{background:#fff}html.theme--documenter-dark .notification pre code{background:transparent}html.theme--documenter-dark .notification>.delete{right:.5rem;position:absolute;top:0.5rem}html.theme--documenter-dark .notification .title,html.theme--documenter-dark .notification .subtitle,html.theme--documenter-dark .notification .content{color:currentColor}html.theme--documenter-dark .notification.is-white{background-color:#fff;color:#0a0a0a}html.theme--documenter-dark .notification.is-black{background-color:#0a0a0a;color:#fff}html.theme--documenter-dark .notification.is-light{background-color:#ecf0f1;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .notification.is-dark,html.theme--documenter-dark .content kbd.notification{background-color:#282f2f;color:#fff}html.theme--documenter-dark .notification.is-primary,html.theme--documenter-dark .docstring>section>a.notification.docs-sourcelink{background-color:#375a7f;color:#fff}html.theme--documenter-dark .notification.is-primary.is-light,html.theme--documenter-dark .docstring>section>a.notification.is-light.docs-sourcelink{background-color:#f1f5f9;color:#4d7eb2}html.theme--documenter-dark .notification.is-link{background-color:#1abc9c;color:#fff}html.theme--documenter-dark .notification.is-link.is-light{background-color:#edfdf9;color:#15987e}html.theme--documenter-dark .notification.is-info{background-color:#3c5dcd;color:#fff}html.theme--documenter-dark .notification.is-info.is-light{background-color:#eff2fb;color:#3253c3}html.theme--documenter-dark .notification.is-success{background-color:#259a12;color:#fff}html.theme--documenter-dark .notification.is-success.is-light{background-color:#effded;color:#2ec016}html.theme--documenter-dark .notification.is-warning{background-color:#f4c72f;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .notification.is-warning.is-light{background-color:#fefaec;color:#8c6e07}html.theme--documenter-dark .notification.is-danger{background-color:#cb3c33;color:#fff}html.theme--documenter-dark .notification.is-danger.is-light{background-color:#fbefef;color:#c03930}html.theme--documenter-dark .progress{-moz-appearance:none;-webkit-appearance:none;border:none;border-radius:9999px;display:block;height:1rem;overflow:hidden;padding:0;width:100%}html.theme--documenter-dark .progress::-webkit-progress-bar{background-color:#343c3d}html.theme--documenter-dark .progress::-webkit-progress-value{background-color:#dbdee0}html.theme--documenter-dark .progress::-moz-progress-bar{background-color:#dbdee0}html.theme--documenter-dark .progress::-ms-fill{background-color:#dbdee0;border:none}html.theme--documenter-dark .progress.is-white::-webkit-progress-value{background-color:#fff}html.theme--documenter-dark .progress.is-white::-moz-progress-bar{background-color:#fff}html.theme--documenter-dark .progress.is-white::-ms-fill{background-color:#fff}html.theme--documenter-dark .progress.is-white:indeterminate{background-image:linear-gradient(to right, #fff 30%, #343c3d 30%)}html.theme--documenter-dark .progress.is-black::-webkit-progress-value{background-color:#0a0a0a}html.theme--documenter-dark .progress.is-black::-moz-progress-bar{background-color:#0a0a0a}html.theme--documenter-dark .progress.is-black::-ms-fill{background-color:#0a0a0a}html.theme--documenter-dark .progress.is-black:indeterminate{background-image:linear-gradient(to right, #0a0a0a 30%, #343c3d 30%)}html.theme--documenter-dark .progress.is-light::-webkit-progress-value{background-color:#ecf0f1}html.theme--documenter-dark .progress.is-light::-moz-progress-bar{background-color:#ecf0f1}html.theme--documenter-dark .progress.is-light::-ms-fill{background-color:#ecf0f1}html.theme--documenter-dark .progress.is-light:indeterminate{background-image:linear-gradient(to right, #ecf0f1 30%, #343c3d 30%)}html.theme--documenter-dark .progress.is-dark::-webkit-progress-value,html.theme--documenter-dark .content kbd.progress::-webkit-progress-value{background-color:#282f2f}html.theme--documenter-dark .progress.is-dark::-moz-progress-bar,html.theme--documenter-dark .content kbd.progress::-moz-progress-bar{background-color:#282f2f}html.theme--documenter-dark .progress.is-dark::-ms-fill,html.theme--documenter-dark .content kbd.progress::-ms-fill{background-color:#282f2f}html.theme--documenter-dark .progress.is-dark:indeterminate,html.theme--documenter-dark .content kbd.progress:indeterminate{background-image:linear-gradient(to right, #282f2f 30%, #343c3d 30%)}html.theme--documenter-dark .progress.is-primary::-webkit-progress-value,html.theme--documenter-dark .docstring>section>a.progress.docs-sourcelink::-webkit-progress-value{background-color:#375a7f}html.theme--documenter-dark .progress.is-primary::-moz-progress-bar,html.theme--documenter-dark .docstring>section>a.progress.docs-sourcelink::-moz-progress-bar{background-color:#375a7f}html.theme--documenter-dark .progress.is-primary::-ms-fill,html.theme--documenter-dark .docstring>section>a.progress.docs-sourcelink::-ms-fill{background-color:#375a7f}html.theme--documenter-dark .progress.is-primary:indeterminate,html.theme--documenter-dark .docstring>section>a.progress.docs-sourcelink:indeterminate{background-image:linear-gradient(to right, #375a7f 30%, #343c3d 30%)}html.theme--documenter-dark .progress.is-link::-webkit-progress-value{background-color:#1abc9c}html.theme--documenter-dark .progress.is-link::-moz-progress-bar{background-color:#1abc9c}html.theme--documenter-dark .progress.is-link::-ms-fill{background-color:#1abc9c}html.theme--documenter-dark .progress.is-link:indeterminate{background-image:linear-gradient(to right, #1abc9c 30%, #343c3d 30%)}html.theme--documenter-dark .progress.is-info::-webkit-progress-value{background-color:#3c5dcd}html.theme--documenter-dark .progress.is-info::-moz-progress-bar{background-color:#3c5dcd}html.theme--documenter-dark .progress.is-info::-ms-fill{background-color:#3c5dcd}html.theme--documenter-dark .progress.is-info:indeterminate{background-image:linear-gradient(to right, #3c5dcd 30%, #343c3d 30%)}html.theme--documenter-dark .progress.is-success::-webkit-progress-value{background-color:#259a12}html.theme--documenter-dark .progress.is-success::-moz-progress-bar{background-color:#259a12}html.theme--documenter-dark .progress.is-success::-ms-fill{background-color:#259a12}html.theme--documenter-dark .progress.is-success:indeterminate{background-image:linear-gradient(to right, #259a12 30%, #343c3d 30%)}html.theme--documenter-dark .progress.is-warning::-webkit-progress-value{background-color:#f4c72f}html.theme--documenter-dark .progress.is-warning::-moz-progress-bar{background-color:#f4c72f}html.theme--documenter-dark .progress.is-warning::-ms-fill{background-color:#f4c72f}html.theme--documenter-dark .progress.is-warning:indeterminate{background-image:linear-gradient(to right, #f4c72f 30%, #343c3d 30%)}html.theme--documenter-dark .progress.is-danger::-webkit-progress-value{background-color:#cb3c33}html.theme--documenter-dark .progress.is-danger::-moz-progress-bar{background-color:#cb3c33}html.theme--documenter-dark .progress.is-danger::-ms-fill{background-color:#cb3c33}html.theme--documenter-dark .progress.is-danger:indeterminate{background-image:linear-gradient(to right, #cb3c33 30%, #343c3d 30%)}html.theme--documenter-dark .progress:indeterminate{animation-duration:1.5s;animation-iteration-count:infinite;animation-name:moveIndeterminate;animation-timing-function:linear;background-color:#343c3d;background-image:linear-gradient(to right, #fff 30%, #343c3d 30%);background-position:top left;background-repeat:no-repeat;background-size:150% 150%}html.theme--documenter-dark .progress:indeterminate::-webkit-progress-bar{background-color:transparent}html.theme--documenter-dark .progress:indeterminate::-moz-progress-bar{background-color:transparent}html.theme--documenter-dark .progress:indeterminate::-ms-fill{animation-name:none}html.theme--documenter-dark .progress.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.progress{height:.75rem}html.theme--documenter-dark .progress.is-medium{height:1.25rem}html.theme--documenter-dark .progress.is-large{height:1.5rem}@keyframes moveIndeterminate{from{background-position:200% 0}to{background-position:-200% 0}}html.theme--documenter-dark .table{background-color:#343c3d;color:#fff}html.theme--documenter-dark .table td,html.theme--documenter-dark .table th{border:1px solid #5e6d6f;border-width:0 0 1px;padding:0.5em 0.75em;vertical-align:top}html.theme--documenter-dark .table td.is-white,html.theme--documenter-dark .table th.is-white{background-color:#fff;border-color:#fff;color:#0a0a0a}html.theme--documenter-dark .table td.is-black,html.theme--documenter-dark .table th.is-black{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}html.theme--documenter-dark .table td.is-light,html.theme--documenter-dark .table th.is-light{background-color:#ecf0f1;border-color:#ecf0f1;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .table td.is-dark,html.theme--documenter-dark .table th.is-dark{background-color:#282f2f;border-color:#282f2f;color:#fff}html.theme--documenter-dark .table td.is-primary,html.theme--documenter-dark .table th.is-primary{background-color:#375a7f;border-color:#375a7f;color:#fff}html.theme--documenter-dark .table td.is-link,html.theme--documenter-dark .table th.is-link{background-color:#1abc9c;border-color:#1abc9c;color:#fff}html.theme--documenter-dark .table td.is-info,html.theme--documenter-dark .table th.is-info{background-color:#3c5dcd;border-color:#3c5dcd;color:#fff}html.theme--documenter-dark .table td.is-success,html.theme--documenter-dark .table th.is-success{background-color:#259a12;border-color:#259a12;color:#fff}html.theme--documenter-dark .table td.is-warning,html.theme--documenter-dark .table th.is-warning{background-color:#f4c72f;border-color:#f4c72f;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .table td.is-danger,html.theme--documenter-dark .table th.is-danger{background-color:#cb3c33;border-color:#cb3c33;color:#fff}html.theme--documenter-dark .table td.is-narrow,html.theme--documenter-dark .table th.is-narrow{white-space:nowrap;width:1%}html.theme--documenter-dark .table td.is-selected,html.theme--documenter-dark .table th.is-selected{background-color:#375a7f;color:#fff}html.theme--documenter-dark .table td.is-selected a,html.theme--documenter-dark .table td.is-selected strong,html.theme--documenter-dark .table th.is-selected a,html.theme--documenter-dark .table th.is-selected strong{color:currentColor}html.theme--documenter-dark .table td.is-vcentered,html.theme--documenter-dark .table th.is-vcentered{vertical-align:middle}html.theme--documenter-dark .table th{color:#f2f2f2}html.theme--documenter-dark .table th:not([align]){text-align:left}html.theme--documenter-dark .table tr.is-selected{background-color:#375a7f;color:#fff}html.theme--documenter-dark .table tr.is-selected a,html.theme--documenter-dark .table tr.is-selected strong{color:currentColor}html.theme--documenter-dark .table tr.is-selected td,html.theme--documenter-dark .table tr.is-selected th{border-color:#fff;color:currentColor}html.theme--documenter-dark .table thead{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .table thead td,html.theme--documenter-dark .table thead th{border-width:0 0 2px;color:#f2f2f2}html.theme--documenter-dark .table tfoot{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .table tfoot td,html.theme--documenter-dark .table tfoot th{border-width:2px 0 0;color:#f2f2f2}html.theme--documenter-dark .table tbody{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .table tbody tr:last-child td,html.theme--documenter-dark .table tbody tr:last-child th{border-bottom-width:0}html.theme--documenter-dark .table.is-bordered td,html.theme--documenter-dark .table.is-bordered th{border-width:1px}html.theme--documenter-dark .table.is-bordered tr:last-child td,html.theme--documenter-dark .table.is-bordered tr:last-child th{border-bottom-width:1px}html.theme--documenter-dark .table.is-fullwidth{width:100%}html.theme--documenter-dark .table.is-hoverable tbody tr:not(.is-selected):hover{background-color:#282f2f}html.theme--documenter-dark .table.is-hoverable.is-striped tbody tr:not(.is-selected):hover{background-color:#282f2f}html.theme--documenter-dark .table.is-hoverable.is-striped tbody tr:not(.is-selected):hover:nth-child(even){background-color:#2d3435}html.theme--documenter-dark .table.is-narrow td,html.theme--documenter-dark .table.is-narrow th{padding:0.25em 0.5em}html.theme--documenter-dark .table.is-striped tbody tr:not(.is-selected):nth-child(even){background-color:#282f2f}html.theme--documenter-dark .table-container{-webkit-overflow-scrolling:touch;overflow:auto;overflow-y:hidden;max-width:100%}html.theme--documenter-dark .tags{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}html.theme--documenter-dark .tags .tag,html.theme--documenter-dark .tags .content kbd,html.theme--documenter-dark .content .tags kbd,html.theme--documenter-dark .tags .docstring>section>a.docs-sourcelink{margin-bottom:0.5rem}html.theme--documenter-dark .tags .tag:not(:last-child),html.theme--documenter-dark .tags .content kbd:not(:last-child),html.theme--documenter-dark .content .tags kbd:not(:last-child),html.theme--documenter-dark .tags .docstring>section>a.docs-sourcelink:not(:last-child){margin-right:.5rem}html.theme--documenter-dark .tags:last-child{margin-bottom:-0.5rem}html.theme--documenter-dark .tags:not(:last-child){margin-bottom:1rem}html.theme--documenter-dark .tags.are-medium .tag:not(.is-normal):not(.is-large),html.theme--documenter-dark .tags.are-medium .content kbd:not(.is-normal):not(.is-large),html.theme--documenter-dark .content .tags.are-medium kbd:not(.is-normal):not(.is-large),html.theme--documenter-dark .tags.are-medium .docstring>section>a.docs-sourcelink:not(.is-normal):not(.is-large){font-size:1rem}html.theme--documenter-dark .tags.are-large .tag:not(.is-normal):not(.is-medium),html.theme--documenter-dark .tags.are-large .content kbd:not(.is-normal):not(.is-medium),html.theme--documenter-dark .content .tags.are-large kbd:not(.is-normal):not(.is-medium),html.theme--documenter-dark .tags.are-large .docstring>section>a.docs-sourcelink:not(.is-normal):not(.is-medium){font-size:1.25rem}html.theme--documenter-dark .tags.is-centered{justify-content:center}html.theme--documenter-dark .tags.is-centered .tag,html.theme--documenter-dark .tags.is-centered .content kbd,html.theme--documenter-dark .content .tags.is-centered kbd,html.theme--documenter-dark .tags.is-centered .docstring>section>a.docs-sourcelink{margin-right:0.25rem;margin-left:0.25rem}html.theme--documenter-dark .tags.is-right{justify-content:flex-end}html.theme--documenter-dark .tags.is-right .tag:not(:first-child),html.theme--documenter-dark .tags.is-right .content kbd:not(:first-child),html.theme--documenter-dark .content .tags.is-right kbd:not(:first-child),html.theme--documenter-dark .tags.is-right .docstring>section>a.docs-sourcelink:not(:first-child){margin-left:0.5rem}html.theme--documenter-dark .tags.is-right .tag:not(:last-child),html.theme--documenter-dark .tags.is-right .content kbd:not(:last-child),html.theme--documenter-dark .content .tags.is-right kbd:not(:last-child),html.theme--documenter-dark .tags.is-right .docstring>section>a.docs-sourcelink:not(:last-child){margin-right:0}html.theme--documenter-dark .tags.has-addons .tag,html.theme--documenter-dark .tags.has-addons .content kbd,html.theme--documenter-dark .content .tags.has-addons kbd,html.theme--documenter-dark .tags.has-addons .docstring>section>a.docs-sourcelink{margin-right:0}html.theme--documenter-dark .tags.has-addons .tag:not(:first-child),html.theme--documenter-dark .tags.has-addons .content kbd:not(:first-child),html.theme--documenter-dark .content .tags.has-addons kbd:not(:first-child),html.theme--documenter-dark .tags.has-addons .docstring>section>a.docs-sourcelink:not(:first-child){margin-left:0;border-top-left-radius:0;border-bottom-left-radius:0}html.theme--documenter-dark .tags.has-addons .tag:not(:last-child),html.theme--documenter-dark .tags.has-addons .content kbd:not(:last-child),html.theme--documenter-dark .content .tags.has-addons kbd:not(:last-child),html.theme--documenter-dark .tags.has-addons .docstring>section>a.docs-sourcelink:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}html.theme--documenter-dark .tag:not(body),html.theme--documenter-dark .content kbd:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink:not(body){align-items:center;background-color:#282f2f;border-radius:.4em;color:#fff;display:inline-flex;font-size:.75rem;height:2em;justify-content:center;line-height:1.5;padding-left:0.75em;padding-right:0.75em;white-space:nowrap}html.theme--documenter-dark .tag:not(body) .delete,html.theme--documenter-dark .content kbd:not(body) .delete,html.theme--documenter-dark .docstring>section>a.docs-sourcelink:not(body) .delete{margin-left:.25rem;margin-right:-.375rem}html.theme--documenter-dark .tag.is-white:not(body),html.theme--documenter-dark .content kbd.is-white:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-white:not(body){background-color:#fff;color:#0a0a0a}html.theme--documenter-dark .tag.is-black:not(body),html.theme--documenter-dark .content kbd.is-black:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-black:not(body){background-color:#0a0a0a;color:#fff}html.theme--documenter-dark .tag.is-light:not(body),html.theme--documenter-dark .content kbd.is-light:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-light:not(body){background-color:#ecf0f1;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .tag.is-dark:not(body),html.theme--documenter-dark .content kbd:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-dark:not(body),html.theme--documenter-dark .content .docstring>section>kbd:not(body){background-color:#282f2f;color:#fff}html.theme--documenter-dark .tag.is-primary:not(body),html.theme--documenter-dark .content kbd.is-primary:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink:not(body){background-color:#375a7f;color:#fff}html.theme--documenter-dark .tag.is-primary.is-light:not(body),html.theme--documenter-dark .content kbd.is-primary.is-light:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-light:not(body){background-color:#f1f5f9;color:#4d7eb2}html.theme--documenter-dark .tag.is-link:not(body),html.theme--documenter-dark .content kbd.is-link:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-link:not(body){background-color:#1abc9c;color:#fff}html.theme--documenter-dark .tag.is-link.is-light:not(body),html.theme--documenter-dark .content kbd.is-link.is-light:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-link.is-light:not(body){background-color:#edfdf9;color:#15987e}html.theme--documenter-dark .tag.is-info:not(body),html.theme--documenter-dark .content kbd.is-info:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-info:not(body){background-color:#3c5dcd;color:#fff}html.theme--documenter-dark .tag.is-info.is-light:not(body),html.theme--documenter-dark .content kbd.is-info.is-light:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-info.is-light:not(body){background-color:#eff2fb;color:#3253c3}html.theme--documenter-dark .tag.is-success:not(body),html.theme--documenter-dark .content kbd.is-success:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-success:not(body){background-color:#259a12;color:#fff}html.theme--documenter-dark .tag.is-success.is-light:not(body),html.theme--documenter-dark .content kbd.is-success.is-light:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-success.is-light:not(body){background-color:#effded;color:#2ec016}html.theme--documenter-dark .tag.is-warning:not(body),html.theme--documenter-dark .content kbd.is-warning:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-warning:not(body){background-color:#f4c72f;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .tag.is-warning.is-light:not(body),html.theme--documenter-dark .content kbd.is-warning.is-light:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-warning.is-light:not(body){background-color:#fefaec;color:#8c6e07}html.theme--documenter-dark .tag.is-danger:not(body),html.theme--documenter-dark .content kbd.is-danger:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-danger:not(body){background-color:#cb3c33;color:#fff}html.theme--documenter-dark .tag.is-danger.is-light:not(body),html.theme--documenter-dark .content kbd.is-danger.is-light:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-danger.is-light:not(body){background-color:#fbefef;color:#c03930}html.theme--documenter-dark .tag.is-normal:not(body),html.theme--documenter-dark .content kbd.is-normal:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-normal:not(body){font-size:.75rem}html.theme--documenter-dark .tag.is-medium:not(body),html.theme--documenter-dark .content kbd.is-medium:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-medium:not(body){font-size:1rem}html.theme--documenter-dark .tag.is-large:not(body),html.theme--documenter-dark .content kbd.is-large:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-large:not(body){font-size:1.25rem}html.theme--documenter-dark .tag:not(body) .icon:first-child:not(:last-child),html.theme--documenter-dark .content kbd:not(body) .icon:first-child:not(:last-child),html.theme--documenter-dark .docstring>section>a.docs-sourcelink:not(body) .icon:first-child:not(:last-child){margin-left:-.375em;margin-right:.1875em}html.theme--documenter-dark .tag:not(body) .icon:last-child:not(:first-child),html.theme--documenter-dark .content kbd:not(body) .icon:last-child:not(:first-child),html.theme--documenter-dark .docstring>section>a.docs-sourcelink:not(body) .icon:last-child:not(:first-child){margin-left:.1875em;margin-right:-.375em}html.theme--documenter-dark .tag:not(body) .icon:first-child:last-child,html.theme--documenter-dark .content kbd:not(body) .icon:first-child:last-child,html.theme--documenter-dark .docstring>section>a.docs-sourcelink:not(body) .icon:first-child:last-child{margin-left:-.375em;margin-right:-.375em}html.theme--documenter-dark .tag.is-delete:not(body),html.theme--documenter-dark .content kbd.is-delete:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-delete:not(body){margin-left:1px;padding:0;position:relative;width:2em}html.theme--documenter-dark .tag.is-delete:not(body)::before,html.theme--documenter-dark .content kbd.is-delete:not(body)::before,html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-delete:not(body)::before,html.theme--documenter-dark .tag.is-delete:not(body)::after,html.theme--documenter-dark .content kbd.is-delete:not(body)::after,html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-delete:not(body)::after{background-color:currentColor;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}html.theme--documenter-dark .tag.is-delete:not(body)::before,html.theme--documenter-dark .content kbd.is-delete:not(body)::before,html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-delete:not(body)::before{height:1px;width:50%}html.theme--documenter-dark .tag.is-delete:not(body)::after,html.theme--documenter-dark .content kbd.is-delete:not(body)::after,html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-delete:not(body)::after{height:50%;width:1px}html.theme--documenter-dark .tag.is-delete:not(body):hover,html.theme--documenter-dark .content kbd.is-delete:not(body):hover,html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-delete:not(body):hover,html.theme--documenter-dark .tag.is-delete:not(body):focus,html.theme--documenter-dark .content kbd.is-delete:not(body):focus,html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-delete:not(body):focus{background-color:#1d2122}html.theme--documenter-dark .tag.is-delete:not(body):active,html.theme--documenter-dark .content kbd.is-delete:not(body):active,html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-delete:not(body):active{background-color:#111414}html.theme--documenter-dark .tag.is-rounded:not(body),html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input:not(body),html.theme--documenter-dark .content kbd.is-rounded:not(body),html.theme--documenter-dark #documenter .docs-sidebar .content form.docs-search>input:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-rounded:not(body){border-radius:9999px}html.theme--documenter-dark a.tag:hover,html.theme--documenter-dark .docstring>section>a.docs-sourcelink:hover{text-decoration:underline}html.theme--documenter-dark .title,html.theme--documenter-dark .subtitle{word-break:break-word}html.theme--documenter-dark .title em,html.theme--documenter-dark .title span,html.theme--documenter-dark .subtitle em,html.theme--documenter-dark .subtitle span{font-weight:inherit}html.theme--documenter-dark .title sub,html.theme--documenter-dark .subtitle sub{font-size:.75em}html.theme--documenter-dark .title sup,html.theme--documenter-dark .subtitle sup{font-size:.75em}html.theme--documenter-dark .title .tag,html.theme--documenter-dark .title .content kbd,html.theme--documenter-dark .content .title kbd,html.theme--documenter-dark .title .docstring>section>a.docs-sourcelink,html.theme--documenter-dark .subtitle .tag,html.theme--documenter-dark .subtitle .content kbd,html.theme--documenter-dark .content .subtitle kbd,html.theme--documenter-dark .subtitle .docstring>section>a.docs-sourcelink{vertical-align:middle}html.theme--documenter-dark .title{color:#fff;font-size:2rem;font-weight:500;line-height:1.125}html.theme--documenter-dark .title strong{color:inherit;font-weight:inherit}html.theme--documenter-dark .title:not(.is-spaced)+.subtitle{margin-top:-1.25rem}html.theme--documenter-dark .title.is-1{font-size:3rem}html.theme--documenter-dark .title.is-2{font-size:2.5rem}html.theme--documenter-dark .title.is-3{font-size:2rem}html.theme--documenter-dark .title.is-4{font-size:1.5rem}html.theme--documenter-dark .title.is-5{font-size:1.25rem}html.theme--documenter-dark .title.is-6{font-size:1rem}html.theme--documenter-dark .title.is-7{font-size:.75rem}html.theme--documenter-dark .subtitle{color:#8c9b9d;font-size:1.25rem;font-weight:400;line-height:1.25}html.theme--documenter-dark .subtitle strong{color:#8c9b9d;font-weight:600}html.theme--documenter-dark .subtitle:not(.is-spaced)+.title{margin-top:-1.25rem}html.theme--documenter-dark .subtitle.is-1{font-size:3rem}html.theme--documenter-dark .subtitle.is-2{font-size:2.5rem}html.theme--documenter-dark .subtitle.is-3{font-size:2rem}html.theme--documenter-dark .subtitle.is-4{font-size:1.5rem}html.theme--documenter-dark .subtitle.is-5{font-size:1.25rem}html.theme--documenter-dark .subtitle.is-6{font-size:1rem}html.theme--documenter-dark .subtitle.is-7{font-size:.75rem}html.theme--documenter-dark .heading{display:block;font-size:11px;letter-spacing:1px;margin-bottom:5px;text-transform:uppercase}html.theme--documenter-dark .number{align-items:center;background-color:#282f2f;border-radius:9999px;display:inline-flex;font-size:1.25rem;height:2em;justify-content:center;margin-right:1.5rem;min-width:2.5em;padding:0.25rem 0.5rem;text-align:center;vertical-align:top}html.theme--documenter-dark .select select,html.theme--documenter-dark .textarea,html.theme--documenter-dark .input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input{background-color:#1f2424;border-color:#5e6d6f;border-radius:.4em;color:#dbdee0}html.theme--documenter-dark .select select::-moz-placeholder,html.theme--documenter-dark .textarea::-moz-placeholder,html.theme--documenter-dark .input::-moz-placeholder,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input::-moz-placeholder{color:#868c98}html.theme--documenter-dark .select select::-webkit-input-placeholder,html.theme--documenter-dark .textarea::-webkit-input-placeholder,html.theme--documenter-dark .input::-webkit-input-placeholder,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input::-webkit-input-placeholder{color:#868c98}html.theme--documenter-dark .select select:-moz-placeholder,html.theme--documenter-dark .textarea:-moz-placeholder,html.theme--documenter-dark .input:-moz-placeholder,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input:-moz-placeholder{color:#868c98}html.theme--documenter-dark .select select:-ms-input-placeholder,html.theme--documenter-dark .textarea:-ms-input-placeholder,html.theme--documenter-dark .input:-ms-input-placeholder,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input:-ms-input-placeholder{color:#868c98}html.theme--documenter-dark .select select:hover,html.theme--documenter-dark .textarea:hover,html.theme--documenter-dark .input:hover,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input:hover,html.theme--documenter-dark .select select.is-hovered,html.theme--documenter-dark .is-hovered.textarea,html.theme--documenter-dark .is-hovered.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-hovered{border-color:#8c9b9d}html.theme--documenter-dark .select select:focus,html.theme--documenter-dark .textarea:focus,html.theme--documenter-dark .input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input:focus,html.theme--documenter-dark .select select.is-focused,html.theme--documenter-dark .is-focused.textarea,html.theme--documenter-dark .is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .select select:active,html.theme--documenter-dark .textarea:active,html.theme--documenter-dark .input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input:active,html.theme--documenter-dark .select select.is-active,html.theme--documenter-dark .is-active.textarea,html.theme--documenter-dark .is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active{border-color:#1abc9c;box-shadow:0 0 0 0.125em rgba(26,188,156,0.25)}html.theme--documenter-dark .select select[disabled],html.theme--documenter-dark .textarea[disabled],html.theme--documenter-dark .input[disabled],html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input[disabled],fieldset[disabled] html.theme--documenter-dark .select select,fieldset[disabled] html.theme--documenter-dark .textarea,fieldset[disabled] html.theme--documenter-dark .input,fieldset[disabled] html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input{background-color:#8c9b9d;border-color:#282f2f;box-shadow:none;color:#fff}html.theme--documenter-dark .select select[disabled]::-moz-placeholder,html.theme--documenter-dark .textarea[disabled]::-moz-placeholder,html.theme--documenter-dark .input[disabled]::-moz-placeholder,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input[disabled]::-moz-placeholder,fieldset[disabled] html.theme--documenter-dark .select select::-moz-placeholder,fieldset[disabled] html.theme--documenter-dark .textarea::-moz-placeholder,fieldset[disabled] html.theme--documenter-dark .input::-moz-placeholder,fieldset[disabled] html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input::-moz-placeholder{color:rgba(255,255,255,0.3)}html.theme--documenter-dark .select select[disabled]::-webkit-input-placeholder,html.theme--documenter-dark .textarea[disabled]::-webkit-input-placeholder,html.theme--documenter-dark .input[disabled]::-webkit-input-placeholder,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input[disabled]::-webkit-input-placeholder,fieldset[disabled] html.theme--documenter-dark .select select::-webkit-input-placeholder,fieldset[disabled] html.theme--documenter-dark .textarea::-webkit-input-placeholder,fieldset[disabled] html.theme--documenter-dark .input::-webkit-input-placeholder,fieldset[disabled] html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input::-webkit-input-placeholder{color:rgba(255,255,255,0.3)}html.theme--documenter-dark .select select[disabled]:-moz-placeholder,html.theme--documenter-dark .textarea[disabled]:-moz-placeholder,html.theme--documenter-dark .input[disabled]:-moz-placeholder,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input[disabled]:-moz-placeholder,fieldset[disabled] html.theme--documenter-dark .select select:-moz-placeholder,fieldset[disabled] html.theme--documenter-dark .textarea:-moz-placeholder,fieldset[disabled] html.theme--documenter-dark .input:-moz-placeholder,fieldset[disabled] html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input:-moz-placeholder{color:rgba(255,255,255,0.3)}html.theme--documenter-dark .select select[disabled]:-ms-input-placeholder,html.theme--documenter-dark .textarea[disabled]:-ms-input-placeholder,html.theme--documenter-dark .input[disabled]:-ms-input-placeholder,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input[disabled]:-ms-input-placeholder,fieldset[disabled] html.theme--documenter-dark .select select:-ms-input-placeholder,fieldset[disabled] html.theme--documenter-dark .textarea:-ms-input-placeholder,fieldset[disabled] html.theme--documenter-dark .input:-ms-input-placeholder,fieldset[disabled] html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input:-ms-input-placeholder{color:rgba(255,255,255,0.3)}html.theme--documenter-dark .textarea,html.theme--documenter-dark .input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input{box-shadow:inset 0 0.0625em 0.125em rgba(10,10,10,0.05);max-width:100%;width:100%}html.theme--documenter-dark .textarea[readonly],html.theme--documenter-dark .input[readonly],html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input[readonly]{box-shadow:none}html.theme--documenter-dark .is-white.textarea,html.theme--documenter-dark .is-white.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-white{border-color:#fff}html.theme--documenter-dark .is-white.textarea:focus,html.theme--documenter-dark .is-white.input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-white:focus,html.theme--documenter-dark .is-white.is-focused.textarea,html.theme--documenter-dark .is-white.is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .is-white.textarea:active,html.theme--documenter-dark .is-white.input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-white:active,html.theme--documenter-dark .is-white.is-active.textarea,html.theme--documenter-dark .is-white.is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}html.theme--documenter-dark .is-black.textarea,html.theme--documenter-dark .is-black.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-black{border-color:#0a0a0a}html.theme--documenter-dark .is-black.textarea:focus,html.theme--documenter-dark .is-black.input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-black:focus,html.theme--documenter-dark .is-black.is-focused.textarea,html.theme--documenter-dark .is-black.is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .is-black.textarea:active,html.theme--documenter-dark .is-black.input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-black:active,html.theme--documenter-dark .is-black.is-active.textarea,html.theme--documenter-dark .is-black.is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}html.theme--documenter-dark .is-light.textarea,html.theme--documenter-dark .is-light.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-light{border-color:#ecf0f1}html.theme--documenter-dark .is-light.textarea:focus,html.theme--documenter-dark .is-light.input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-light:focus,html.theme--documenter-dark .is-light.is-focused.textarea,html.theme--documenter-dark .is-light.is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .is-light.textarea:active,html.theme--documenter-dark .is-light.input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-light:active,html.theme--documenter-dark .is-light.is-active.textarea,html.theme--documenter-dark .is-light.is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(236,240,241,0.25)}html.theme--documenter-dark .is-dark.textarea,html.theme--documenter-dark .content kbd.textarea,html.theme--documenter-dark .is-dark.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-dark,html.theme--documenter-dark .content kbd.input{border-color:#282f2f}html.theme--documenter-dark .is-dark.textarea:focus,html.theme--documenter-dark .content kbd.textarea:focus,html.theme--documenter-dark .is-dark.input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-dark:focus,html.theme--documenter-dark .content kbd.input:focus,html.theme--documenter-dark .is-dark.is-focused.textarea,html.theme--documenter-dark .content kbd.is-focused.textarea,html.theme--documenter-dark .is-dark.is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .content kbd.is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar .content form.docs-search>input.is-focused,html.theme--documenter-dark .is-dark.textarea:active,html.theme--documenter-dark .content kbd.textarea:active,html.theme--documenter-dark .is-dark.input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-dark:active,html.theme--documenter-dark .content kbd.input:active,html.theme--documenter-dark .is-dark.is-active.textarea,html.theme--documenter-dark .content kbd.is-active.textarea,html.theme--documenter-dark .is-dark.is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active,html.theme--documenter-dark .content kbd.is-active.input,html.theme--documenter-dark #documenter .docs-sidebar .content form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(40,47,47,0.25)}html.theme--documenter-dark .is-primary.textarea,html.theme--documenter-dark .docstring>section>a.textarea.docs-sourcelink,html.theme--documenter-dark .is-primary.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-primary,html.theme--documenter-dark .docstring>section>a.input.docs-sourcelink{border-color:#375a7f}html.theme--documenter-dark .is-primary.textarea:focus,html.theme--documenter-dark .docstring>section>a.textarea.docs-sourcelink:focus,html.theme--documenter-dark .is-primary.input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-primary:focus,html.theme--documenter-dark .docstring>section>a.input.docs-sourcelink:focus,html.theme--documenter-dark .is-primary.is-focused.textarea,html.theme--documenter-dark .docstring>section>a.is-focused.textarea.docs-sourcelink,html.theme--documenter-dark .is-primary.is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .docstring>section>a.is-focused.input.docs-sourcelink,html.theme--documenter-dark .is-primary.textarea:active,html.theme--documenter-dark .docstring>section>a.textarea.docs-sourcelink:active,html.theme--documenter-dark .is-primary.input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-primary:active,html.theme--documenter-dark .docstring>section>a.input.docs-sourcelink:active,html.theme--documenter-dark .is-primary.is-active.textarea,html.theme--documenter-dark .docstring>section>a.is-active.textarea.docs-sourcelink,html.theme--documenter-dark .is-primary.is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active,html.theme--documenter-dark .docstring>section>a.is-active.input.docs-sourcelink{box-shadow:0 0 0 0.125em rgba(55,90,127,0.25)}html.theme--documenter-dark .is-link.textarea,html.theme--documenter-dark .is-link.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-link{border-color:#1abc9c}html.theme--documenter-dark .is-link.textarea:focus,html.theme--documenter-dark .is-link.input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-link:focus,html.theme--documenter-dark .is-link.is-focused.textarea,html.theme--documenter-dark .is-link.is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .is-link.textarea:active,html.theme--documenter-dark .is-link.input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-link:active,html.theme--documenter-dark .is-link.is-active.textarea,html.theme--documenter-dark .is-link.is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(26,188,156,0.25)}html.theme--documenter-dark .is-info.textarea,html.theme--documenter-dark .is-info.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-info{border-color:#3c5dcd}html.theme--documenter-dark .is-info.textarea:focus,html.theme--documenter-dark .is-info.input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-info:focus,html.theme--documenter-dark .is-info.is-focused.textarea,html.theme--documenter-dark .is-info.is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .is-info.textarea:active,html.theme--documenter-dark .is-info.input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-info:active,html.theme--documenter-dark .is-info.is-active.textarea,html.theme--documenter-dark .is-info.is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(60,93,205,0.25)}html.theme--documenter-dark .is-success.textarea,html.theme--documenter-dark .is-success.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-success{border-color:#259a12}html.theme--documenter-dark .is-success.textarea:focus,html.theme--documenter-dark .is-success.input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-success:focus,html.theme--documenter-dark .is-success.is-focused.textarea,html.theme--documenter-dark .is-success.is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .is-success.textarea:active,html.theme--documenter-dark .is-success.input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-success:active,html.theme--documenter-dark .is-success.is-active.textarea,html.theme--documenter-dark .is-success.is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(37,154,18,0.25)}html.theme--documenter-dark .is-warning.textarea,html.theme--documenter-dark .is-warning.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-warning{border-color:#f4c72f}html.theme--documenter-dark .is-warning.textarea:focus,html.theme--documenter-dark .is-warning.input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-warning:focus,html.theme--documenter-dark .is-warning.is-focused.textarea,html.theme--documenter-dark .is-warning.is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .is-warning.textarea:active,html.theme--documenter-dark .is-warning.input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-warning:active,html.theme--documenter-dark .is-warning.is-active.textarea,html.theme--documenter-dark .is-warning.is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(244,199,47,0.25)}html.theme--documenter-dark .is-danger.textarea,html.theme--documenter-dark .is-danger.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-danger{border-color:#cb3c33}html.theme--documenter-dark .is-danger.textarea:focus,html.theme--documenter-dark .is-danger.input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-danger:focus,html.theme--documenter-dark .is-danger.is-focused.textarea,html.theme--documenter-dark .is-danger.is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .is-danger.textarea:active,html.theme--documenter-dark .is-danger.input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-danger:active,html.theme--documenter-dark .is-danger.is-active.textarea,html.theme--documenter-dark .is-danger.is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(203,60,51,0.25)}html.theme--documenter-dark .is-small.textarea,html.theme--documenter-dark .is-small.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input{border-radius:3px;font-size:.75rem}html.theme--documenter-dark .is-medium.textarea,html.theme--documenter-dark .is-medium.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-medium{font-size:1.25rem}html.theme--documenter-dark .is-large.textarea,html.theme--documenter-dark .is-large.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-large{font-size:1.5rem}html.theme--documenter-dark .is-fullwidth.textarea,html.theme--documenter-dark .is-fullwidth.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-fullwidth{display:block;width:100%}html.theme--documenter-dark .is-inline.textarea,html.theme--documenter-dark .is-inline.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-inline{display:inline;width:auto}html.theme--documenter-dark .input.is-rounded,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input{border-radius:9999px;padding-left:calc(calc(0.75em - 1px) + 0.375em);padding-right:calc(calc(0.75em - 1px) + 0.375em)}html.theme--documenter-dark .input.is-static,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-static{background-color:transparent;border-color:transparent;box-shadow:none;padding-left:0;padding-right:0}html.theme--documenter-dark .textarea{display:block;max-width:100%;min-width:100%;padding:calc(0.75em - 1px);resize:vertical}html.theme--documenter-dark .textarea:not([rows]){max-height:40em;min-height:8em}html.theme--documenter-dark .textarea[rows]{height:initial}html.theme--documenter-dark .textarea.has-fixed-size{resize:none}html.theme--documenter-dark .radio,html.theme--documenter-dark .checkbox{cursor:pointer;display:inline-block;line-height:1.25;position:relative}html.theme--documenter-dark .radio input,html.theme--documenter-dark .checkbox input{cursor:pointer}html.theme--documenter-dark .radio:hover,html.theme--documenter-dark .checkbox:hover{color:#8c9b9d}html.theme--documenter-dark .radio[disabled],html.theme--documenter-dark .checkbox[disabled],fieldset[disabled] html.theme--documenter-dark .radio,fieldset[disabled] html.theme--documenter-dark .checkbox,html.theme--documenter-dark .radio input[disabled],html.theme--documenter-dark .checkbox input[disabled]{color:#fff;cursor:not-allowed}html.theme--documenter-dark .radio+.radio{margin-left:.5em}html.theme--documenter-dark .select{display:inline-block;max-width:100%;position:relative;vertical-align:top}html.theme--documenter-dark .select:not(.is-multiple){height:2.5em}html.theme--documenter-dark .select:not(.is-multiple):not(.is-loading)::after{border-color:#1abc9c;right:1.125em;z-index:4}html.theme--documenter-dark .select.is-rounded select,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.select select{border-radius:9999px;padding-left:1em}html.theme--documenter-dark .select select{cursor:pointer;display:block;font-size:1em;max-width:100%;outline:none}html.theme--documenter-dark .select select::-ms-expand{display:none}html.theme--documenter-dark .select select[disabled]:hover,fieldset[disabled] html.theme--documenter-dark .select select:hover{border-color:#282f2f}html.theme--documenter-dark .select select:not([multiple]){padding-right:2.5em}html.theme--documenter-dark .select select[multiple]{height:auto;padding:0}html.theme--documenter-dark .select select[multiple] option{padding:0.5em 1em}html.theme--documenter-dark .select:not(.is-multiple):not(.is-loading):hover::after{border-color:#8c9b9d}html.theme--documenter-dark .select.is-white:not(:hover)::after{border-color:#fff}html.theme--documenter-dark .select.is-white select{border-color:#fff}html.theme--documenter-dark .select.is-white select:hover,html.theme--documenter-dark .select.is-white select.is-hovered{border-color:#f2f2f2}html.theme--documenter-dark .select.is-white select:focus,html.theme--documenter-dark .select.is-white select.is-focused,html.theme--documenter-dark .select.is-white select:active,html.theme--documenter-dark .select.is-white select.is-active{box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}html.theme--documenter-dark .select.is-black:not(:hover)::after{border-color:#0a0a0a}html.theme--documenter-dark .select.is-black select{border-color:#0a0a0a}html.theme--documenter-dark .select.is-black select:hover,html.theme--documenter-dark .select.is-black select.is-hovered{border-color:#000}html.theme--documenter-dark .select.is-black select:focus,html.theme--documenter-dark .select.is-black select.is-focused,html.theme--documenter-dark .select.is-black select:active,html.theme--documenter-dark .select.is-black select.is-active{box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}html.theme--documenter-dark .select.is-light:not(:hover)::after{border-color:#ecf0f1}html.theme--documenter-dark .select.is-light select{border-color:#ecf0f1}html.theme--documenter-dark .select.is-light select:hover,html.theme--documenter-dark .select.is-light select.is-hovered{border-color:#dde4e6}html.theme--documenter-dark .select.is-light select:focus,html.theme--documenter-dark .select.is-light select.is-focused,html.theme--documenter-dark .select.is-light select:active,html.theme--documenter-dark .select.is-light select.is-active{box-shadow:0 0 0 0.125em rgba(236,240,241,0.25)}html.theme--documenter-dark .select.is-dark:not(:hover)::after,html.theme--documenter-dark .content kbd.select:not(:hover)::after{border-color:#282f2f}html.theme--documenter-dark .select.is-dark select,html.theme--documenter-dark .content kbd.select select{border-color:#282f2f}html.theme--documenter-dark .select.is-dark select:hover,html.theme--documenter-dark .content kbd.select select:hover,html.theme--documenter-dark .select.is-dark select.is-hovered,html.theme--documenter-dark .content kbd.select select.is-hovered{border-color:#1d2122}html.theme--documenter-dark .select.is-dark select:focus,html.theme--documenter-dark .content kbd.select select:focus,html.theme--documenter-dark .select.is-dark select.is-focused,html.theme--documenter-dark .content kbd.select select.is-focused,html.theme--documenter-dark .select.is-dark select:active,html.theme--documenter-dark .content kbd.select select:active,html.theme--documenter-dark .select.is-dark select.is-active,html.theme--documenter-dark .content kbd.select select.is-active{box-shadow:0 0 0 0.125em rgba(40,47,47,0.25)}html.theme--documenter-dark .select.is-primary:not(:hover)::after,html.theme--documenter-dark .docstring>section>a.select.docs-sourcelink:not(:hover)::after{border-color:#375a7f}html.theme--documenter-dark .select.is-primary select,html.theme--documenter-dark .docstring>section>a.select.docs-sourcelink select{border-color:#375a7f}html.theme--documenter-dark .select.is-primary select:hover,html.theme--documenter-dark .docstring>section>a.select.docs-sourcelink select:hover,html.theme--documenter-dark .select.is-primary select.is-hovered,html.theme--documenter-dark .docstring>section>a.select.docs-sourcelink select.is-hovered{border-color:#2f4d6d}html.theme--documenter-dark .select.is-primary select:focus,html.theme--documenter-dark .docstring>section>a.select.docs-sourcelink select:focus,html.theme--documenter-dark .select.is-primary select.is-focused,html.theme--documenter-dark .docstring>section>a.select.docs-sourcelink select.is-focused,html.theme--documenter-dark .select.is-primary select:active,html.theme--documenter-dark .docstring>section>a.select.docs-sourcelink select:active,html.theme--documenter-dark .select.is-primary select.is-active,html.theme--documenter-dark .docstring>section>a.select.docs-sourcelink select.is-active{box-shadow:0 0 0 0.125em rgba(55,90,127,0.25)}html.theme--documenter-dark .select.is-link:not(:hover)::after{border-color:#1abc9c}html.theme--documenter-dark .select.is-link select{border-color:#1abc9c}html.theme--documenter-dark .select.is-link select:hover,html.theme--documenter-dark .select.is-link select.is-hovered{border-color:#17a689}html.theme--documenter-dark .select.is-link select:focus,html.theme--documenter-dark .select.is-link select.is-focused,html.theme--documenter-dark .select.is-link select:active,html.theme--documenter-dark .select.is-link select.is-active{box-shadow:0 0 0 0.125em rgba(26,188,156,0.25)}html.theme--documenter-dark .select.is-info:not(:hover)::after{border-color:#3c5dcd}html.theme--documenter-dark .select.is-info select{border-color:#3c5dcd}html.theme--documenter-dark .select.is-info select:hover,html.theme--documenter-dark .select.is-info select.is-hovered{border-color:#3151bf}html.theme--documenter-dark .select.is-info select:focus,html.theme--documenter-dark .select.is-info select.is-focused,html.theme--documenter-dark .select.is-info select:active,html.theme--documenter-dark .select.is-info select.is-active{box-shadow:0 0 0 0.125em rgba(60,93,205,0.25)}html.theme--documenter-dark .select.is-success:not(:hover)::after{border-color:#259a12}html.theme--documenter-dark .select.is-success select{border-color:#259a12}html.theme--documenter-dark .select.is-success select:hover,html.theme--documenter-dark .select.is-success select.is-hovered{border-color:#20830f}html.theme--documenter-dark .select.is-success select:focus,html.theme--documenter-dark .select.is-success select.is-focused,html.theme--documenter-dark .select.is-success select:active,html.theme--documenter-dark .select.is-success select.is-active{box-shadow:0 0 0 0.125em rgba(37,154,18,0.25)}html.theme--documenter-dark .select.is-warning:not(:hover)::after{border-color:#f4c72f}html.theme--documenter-dark .select.is-warning select{border-color:#f4c72f}html.theme--documenter-dark .select.is-warning select:hover,html.theme--documenter-dark .select.is-warning select.is-hovered{border-color:#f3c017}html.theme--documenter-dark .select.is-warning select:focus,html.theme--documenter-dark .select.is-warning select.is-focused,html.theme--documenter-dark .select.is-warning select:active,html.theme--documenter-dark .select.is-warning select.is-active{box-shadow:0 0 0 0.125em rgba(244,199,47,0.25)}html.theme--documenter-dark .select.is-danger:not(:hover)::after{border-color:#cb3c33}html.theme--documenter-dark .select.is-danger select{border-color:#cb3c33}html.theme--documenter-dark .select.is-danger select:hover,html.theme--documenter-dark .select.is-danger select.is-hovered{border-color:#b7362e}html.theme--documenter-dark .select.is-danger select:focus,html.theme--documenter-dark .select.is-danger select.is-focused,html.theme--documenter-dark .select.is-danger select:active,html.theme--documenter-dark .select.is-danger select.is-active{box-shadow:0 0 0 0.125em rgba(203,60,51,0.25)}html.theme--documenter-dark .select.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.select{border-radius:3px;font-size:.75rem}html.theme--documenter-dark .select.is-medium{font-size:1.25rem}html.theme--documenter-dark .select.is-large{font-size:1.5rem}html.theme--documenter-dark .select.is-disabled::after{border-color:#fff !important;opacity:0.5}html.theme--documenter-dark .select.is-fullwidth{width:100%}html.theme--documenter-dark .select.is-fullwidth select{width:100%}html.theme--documenter-dark .select.is-loading::after{margin-top:0;position:absolute;right:.625em;top:0.625em;transform:none}html.theme--documenter-dark .select.is-loading.is-small:after,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-loading:after{font-size:.75rem}html.theme--documenter-dark .select.is-loading.is-medium:after{font-size:1.25rem}html.theme--documenter-dark .select.is-loading.is-large:after{font-size:1.5rem}html.theme--documenter-dark .file{align-items:stretch;display:flex;justify-content:flex-start;position:relative}html.theme--documenter-dark .file.is-white .file-cta{background-color:#fff;border-color:transparent;color:#0a0a0a}html.theme--documenter-dark .file.is-white:hover .file-cta,html.theme--documenter-dark .file.is-white.is-hovered .file-cta{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}html.theme--documenter-dark .file.is-white:focus .file-cta,html.theme--documenter-dark .file.is-white.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(255,255,255,0.25);color:#0a0a0a}html.theme--documenter-dark .file.is-white:active .file-cta,html.theme--documenter-dark .file.is-white.is-active .file-cta{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}html.theme--documenter-dark .file.is-black .file-cta{background-color:#0a0a0a;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-black:hover .file-cta,html.theme--documenter-dark .file.is-black.is-hovered .file-cta{background-color:#040404;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-black:focus .file-cta,html.theme--documenter-dark .file.is-black.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(10,10,10,0.25);color:#fff}html.theme--documenter-dark .file.is-black:active .file-cta,html.theme--documenter-dark .file.is-black.is-active .file-cta{background-color:#000;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-light .file-cta{background-color:#ecf0f1;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .file.is-light:hover .file-cta,html.theme--documenter-dark .file.is-light.is-hovered .file-cta{background-color:#e5eaec;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .file.is-light:focus .file-cta,html.theme--documenter-dark .file.is-light.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(236,240,241,0.25);color:rgba(0,0,0,0.7)}html.theme--documenter-dark .file.is-light:active .file-cta,html.theme--documenter-dark .file.is-light.is-active .file-cta{background-color:#dde4e6;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .file.is-dark .file-cta,html.theme--documenter-dark .content kbd.file .file-cta{background-color:#282f2f;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-dark:hover .file-cta,html.theme--documenter-dark .content kbd.file:hover .file-cta,html.theme--documenter-dark .file.is-dark.is-hovered .file-cta,html.theme--documenter-dark .content kbd.file.is-hovered .file-cta{background-color:#232829;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-dark:focus .file-cta,html.theme--documenter-dark .content kbd.file:focus .file-cta,html.theme--documenter-dark .file.is-dark.is-focused .file-cta,html.theme--documenter-dark .content kbd.file.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(40,47,47,0.25);color:#fff}html.theme--documenter-dark .file.is-dark:active .file-cta,html.theme--documenter-dark .content kbd.file:active .file-cta,html.theme--documenter-dark .file.is-dark.is-active .file-cta,html.theme--documenter-dark .content kbd.file.is-active .file-cta{background-color:#1d2122;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-primary .file-cta,html.theme--documenter-dark .docstring>section>a.file.docs-sourcelink .file-cta{background-color:#375a7f;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-primary:hover .file-cta,html.theme--documenter-dark .docstring>section>a.file.docs-sourcelink:hover .file-cta,html.theme--documenter-dark .file.is-primary.is-hovered .file-cta,html.theme--documenter-dark .docstring>section>a.file.is-hovered.docs-sourcelink .file-cta{background-color:#335476;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-primary:focus .file-cta,html.theme--documenter-dark .docstring>section>a.file.docs-sourcelink:focus .file-cta,html.theme--documenter-dark .file.is-primary.is-focused .file-cta,html.theme--documenter-dark .docstring>section>a.file.is-focused.docs-sourcelink .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(55,90,127,0.25);color:#fff}html.theme--documenter-dark .file.is-primary:active .file-cta,html.theme--documenter-dark .docstring>section>a.file.docs-sourcelink:active .file-cta,html.theme--documenter-dark .file.is-primary.is-active .file-cta,html.theme--documenter-dark .docstring>section>a.file.is-active.docs-sourcelink .file-cta{background-color:#2f4d6d;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-link .file-cta{background-color:#1abc9c;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-link:hover .file-cta,html.theme--documenter-dark .file.is-link.is-hovered .file-cta{background-color:#18b193;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-link:focus .file-cta,html.theme--documenter-dark .file.is-link.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(26,188,156,0.25);color:#fff}html.theme--documenter-dark .file.is-link:active .file-cta,html.theme--documenter-dark .file.is-link.is-active .file-cta{background-color:#17a689;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-info .file-cta{background-color:#3c5dcd;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-info:hover .file-cta,html.theme--documenter-dark .file.is-info.is-hovered .file-cta{background-color:#3355c9;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-info:focus .file-cta,html.theme--documenter-dark .file.is-info.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(60,93,205,0.25);color:#fff}html.theme--documenter-dark .file.is-info:active .file-cta,html.theme--documenter-dark .file.is-info.is-active .file-cta{background-color:#3151bf;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-success .file-cta{background-color:#259a12;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-success:hover .file-cta,html.theme--documenter-dark .file.is-success.is-hovered .file-cta{background-color:#228f11;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-success:focus .file-cta,html.theme--documenter-dark .file.is-success.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(37,154,18,0.25);color:#fff}html.theme--documenter-dark .file.is-success:active .file-cta,html.theme--documenter-dark .file.is-success.is-active .file-cta{background-color:#20830f;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-warning .file-cta{background-color:#f4c72f;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .file.is-warning:hover .file-cta,html.theme--documenter-dark .file.is-warning.is-hovered .file-cta{background-color:#f3c423;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .file.is-warning:focus .file-cta,html.theme--documenter-dark .file.is-warning.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(244,199,47,0.25);color:rgba(0,0,0,0.7)}html.theme--documenter-dark .file.is-warning:active .file-cta,html.theme--documenter-dark .file.is-warning.is-active .file-cta{background-color:#f3c017;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .file.is-danger .file-cta{background-color:#cb3c33;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-danger:hover .file-cta,html.theme--documenter-dark .file.is-danger.is-hovered .file-cta{background-color:#c13930;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-danger:focus .file-cta,html.theme--documenter-dark .file.is-danger.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(203,60,51,0.25);color:#fff}html.theme--documenter-dark .file.is-danger:active .file-cta,html.theme--documenter-dark .file.is-danger.is-active .file-cta{background-color:#b7362e;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.file{font-size:.75rem}html.theme--documenter-dark .file.is-normal{font-size:1rem}html.theme--documenter-dark .file.is-medium{font-size:1.25rem}html.theme--documenter-dark .file.is-medium .file-icon .fa{font-size:21px}html.theme--documenter-dark .file.is-large{font-size:1.5rem}html.theme--documenter-dark .file.is-large .file-icon .fa{font-size:28px}html.theme--documenter-dark .file.has-name .file-cta{border-bottom-right-radius:0;border-top-right-radius:0}html.theme--documenter-dark .file.has-name .file-name{border-bottom-left-radius:0;border-top-left-radius:0}html.theme--documenter-dark .file.has-name.is-empty .file-cta{border-radius:.4em}html.theme--documenter-dark .file.has-name.is-empty .file-name{display:none}html.theme--documenter-dark .file.is-boxed .file-label{flex-direction:column}html.theme--documenter-dark .file.is-boxed .file-cta{flex-direction:column;height:auto;padding:1em 3em}html.theme--documenter-dark .file.is-boxed .file-name{border-width:0 1px 1px}html.theme--documenter-dark .file.is-boxed .file-icon{height:1.5em;width:1.5em}html.theme--documenter-dark .file.is-boxed .file-icon .fa{font-size:21px}html.theme--documenter-dark .file.is-boxed.is-small .file-icon .fa,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-boxed .file-icon .fa{font-size:14px}html.theme--documenter-dark .file.is-boxed.is-medium .file-icon .fa{font-size:28px}html.theme--documenter-dark .file.is-boxed.is-large .file-icon .fa{font-size:35px}html.theme--documenter-dark .file.is-boxed.has-name .file-cta{border-radius:.4em .4em 0 0}html.theme--documenter-dark .file.is-boxed.has-name .file-name{border-radius:0 0 .4em .4em;border-width:0 1px 1px}html.theme--documenter-dark .file.is-centered{justify-content:center}html.theme--documenter-dark .file.is-fullwidth .file-label{width:100%}html.theme--documenter-dark .file.is-fullwidth .file-name{flex-grow:1;max-width:none}html.theme--documenter-dark .file.is-right{justify-content:flex-end}html.theme--documenter-dark .file.is-right .file-cta{border-radius:0 .4em .4em 0}html.theme--documenter-dark .file.is-right .file-name{border-radius:.4em 0 0 .4em;border-width:1px 0 1px 1px;order:-1}html.theme--documenter-dark .file-label{align-items:stretch;display:flex;cursor:pointer;justify-content:flex-start;overflow:hidden;position:relative}html.theme--documenter-dark .file-label:hover .file-cta{background-color:#232829;color:#f2f2f2}html.theme--documenter-dark .file-label:hover .file-name{border-color:#596668}html.theme--documenter-dark .file-label:active .file-cta{background-color:#1d2122;color:#f2f2f2}html.theme--documenter-dark .file-label:active .file-name{border-color:#535f61}html.theme--documenter-dark .file-input{height:100%;left:0;opacity:0;outline:none;position:absolute;top:0;width:100%}html.theme--documenter-dark .file-cta,html.theme--documenter-dark .file-name{border-color:#5e6d6f;border-radius:.4em;font-size:1em;padding-left:1em;padding-right:1em;white-space:nowrap}html.theme--documenter-dark .file-cta{background-color:#282f2f;color:#fff}html.theme--documenter-dark .file-name{border-color:#5e6d6f;border-style:solid;border-width:1px 1px 1px 0;display:block;max-width:16em;overflow:hidden;text-align:inherit;text-overflow:ellipsis}html.theme--documenter-dark .file-icon{align-items:center;display:flex;height:1em;justify-content:center;margin-right:.5em;width:1em}html.theme--documenter-dark .file-icon .fa{font-size:14px}html.theme--documenter-dark .label{color:#f2f2f2;display:block;font-size:1rem;font-weight:700}html.theme--documenter-dark .label:not(:last-child){margin-bottom:0.5em}html.theme--documenter-dark .label.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.label{font-size:.75rem}html.theme--documenter-dark .label.is-medium{font-size:1.25rem}html.theme--documenter-dark .label.is-large{font-size:1.5rem}html.theme--documenter-dark .help{display:block;font-size:.75rem;margin-top:0.25rem}html.theme--documenter-dark .help.is-white{color:#fff}html.theme--documenter-dark .help.is-black{color:#0a0a0a}html.theme--documenter-dark .help.is-light{color:#ecf0f1}html.theme--documenter-dark .help.is-dark,html.theme--documenter-dark .content kbd.help{color:#282f2f}html.theme--documenter-dark .help.is-primary,html.theme--documenter-dark .docstring>section>a.help.docs-sourcelink{color:#375a7f}html.theme--documenter-dark .help.is-link{color:#1abc9c}html.theme--documenter-dark .help.is-info{color:#3c5dcd}html.theme--documenter-dark .help.is-success{color:#259a12}html.theme--documenter-dark .help.is-warning{color:#f4c72f}html.theme--documenter-dark .help.is-danger{color:#cb3c33}html.theme--documenter-dark .field:not(:last-child){margin-bottom:0.75rem}html.theme--documenter-dark .field.has-addons{display:flex;justify-content:flex-start}html.theme--documenter-dark .field.has-addons .control:not(:last-child){margin-right:-1px}html.theme--documenter-dark .field.has-addons .control:not(:first-child):not(:last-child) .button,html.theme--documenter-dark .field.has-addons .control:not(:first-child):not(:last-child) .input,html.theme--documenter-dark .field.has-addons .control:not(:first-child):not(:last-child) #documenter .docs-sidebar form.docs-search>input,html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control:not(:first-child):not(:last-child) form.docs-search>input,html.theme--documenter-dark .field.has-addons .control:not(:first-child):not(:last-child) .select select{border-radius:0}html.theme--documenter-dark .field.has-addons .control:first-child:not(:only-child) .button,html.theme--documenter-dark .field.has-addons .control:first-child:not(:only-child) .input,html.theme--documenter-dark .field.has-addons .control:first-child:not(:only-child) #documenter .docs-sidebar form.docs-search>input,html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control:first-child:not(:only-child) form.docs-search>input,html.theme--documenter-dark .field.has-addons .control:first-child:not(:only-child) .select select{border-bottom-right-radius:0;border-top-right-radius:0}html.theme--documenter-dark .field.has-addons .control:last-child:not(:only-child) .button,html.theme--documenter-dark .field.has-addons .control:last-child:not(:only-child) .input,html.theme--documenter-dark .field.has-addons .control:last-child:not(:only-child) #documenter .docs-sidebar form.docs-search>input,html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control:last-child:not(:only-child) form.docs-search>input,html.theme--documenter-dark .field.has-addons .control:last-child:not(:only-child) .select select{border-bottom-left-radius:0;border-top-left-radius:0}html.theme--documenter-dark .field.has-addons .control .button:not([disabled]):hover,html.theme--documenter-dark .field.has-addons .control .button.is-hovered:not([disabled]),html.theme--documenter-dark .field.has-addons .control .input:not([disabled]):hover,html.theme--documenter-dark .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):hover,html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):hover,html.theme--documenter-dark .field.has-addons .control .input.is-hovered:not([disabled]),html.theme--documenter-dark .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-hovered:not([disabled]),html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-hovered:not([disabled]),html.theme--documenter-dark .field.has-addons .control .select select:not([disabled]):hover,html.theme--documenter-dark .field.has-addons .control .select select.is-hovered:not([disabled]){z-index:2}html.theme--documenter-dark .field.has-addons .control .button:not([disabled]):focus,html.theme--documenter-dark .field.has-addons .control .button.is-focused:not([disabled]),html.theme--documenter-dark .field.has-addons .control .button:not([disabled]):active,html.theme--documenter-dark .field.has-addons .control .button.is-active:not([disabled]),html.theme--documenter-dark .field.has-addons .control .input:not([disabled]):focus,html.theme--documenter-dark .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):focus,html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):focus,html.theme--documenter-dark .field.has-addons .control .input.is-focused:not([disabled]),html.theme--documenter-dark .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-focused:not([disabled]),html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-focused:not([disabled]),html.theme--documenter-dark .field.has-addons .control .input:not([disabled]):active,html.theme--documenter-dark .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):active,html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):active,html.theme--documenter-dark .field.has-addons .control .input.is-active:not([disabled]),html.theme--documenter-dark .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-active:not([disabled]),html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-active:not([disabled]),html.theme--documenter-dark .field.has-addons .control .select select:not([disabled]):focus,html.theme--documenter-dark .field.has-addons .control .select select.is-focused:not([disabled]),html.theme--documenter-dark .field.has-addons .control .select select:not([disabled]):active,html.theme--documenter-dark .field.has-addons .control .select select.is-active:not([disabled]){z-index:3}html.theme--documenter-dark .field.has-addons .control .button:not([disabled]):focus:hover,html.theme--documenter-dark .field.has-addons .control .button.is-focused:not([disabled]):hover,html.theme--documenter-dark .field.has-addons .control .button:not([disabled]):active:hover,html.theme--documenter-dark .field.has-addons .control .button.is-active:not([disabled]):hover,html.theme--documenter-dark .field.has-addons .control .input:not([disabled]):focus:hover,html.theme--documenter-dark .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):focus:hover,html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):focus:hover,html.theme--documenter-dark .field.has-addons .control .input.is-focused:not([disabled]):hover,html.theme--documenter-dark .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-focused:not([disabled]):hover,html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-focused:not([disabled]):hover,html.theme--documenter-dark .field.has-addons .control .input:not([disabled]):active:hover,html.theme--documenter-dark .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):active:hover,html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):active:hover,html.theme--documenter-dark .field.has-addons .control .input.is-active:not([disabled]):hover,html.theme--documenter-dark .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-active:not([disabled]):hover,html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-active:not([disabled]):hover,html.theme--documenter-dark .field.has-addons .control .select select:not([disabled]):focus:hover,html.theme--documenter-dark .field.has-addons .control .select select.is-focused:not([disabled]):hover,html.theme--documenter-dark .field.has-addons .control .select select:not([disabled]):active:hover,html.theme--documenter-dark .field.has-addons .control .select select.is-active:not([disabled]):hover{z-index:4}html.theme--documenter-dark .field.has-addons .control.is-expanded{flex-grow:1;flex-shrink:1}html.theme--documenter-dark .field.has-addons.has-addons-centered{justify-content:center}html.theme--documenter-dark .field.has-addons.has-addons-right{justify-content:flex-end}html.theme--documenter-dark .field.has-addons.has-addons-fullwidth .control{flex-grow:1;flex-shrink:0}html.theme--documenter-dark .field.is-grouped{display:flex;justify-content:flex-start}html.theme--documenter-dark .field.is-grouped>.control{flex-shrink:0}html.theme--documenter-dark .field.is-grouped>.control:not(:last-child){margin-bottom:0;margin-right:.75rem}html.theme--documenter-dark .field.is-grouped>.control.is-expanded{flex-grow:1;flex-shrink:1}html.theme--documenter-dark .field.is-grouped.is-grouped-centered{justify-content:center}html.theme--documenter-dark .field.is-grouped.is-grouped-right{justify-content:flex-end}html.theme--documenter-dark .field.is-grouped.is-grouped-multiline{flex-wrap:wrap}html.theme--documenter-dark .field.is-grouped.is-grouped-multiline>.control:last-child,html.theme--documenter-dark .field.is-grouped.is-grouped-multiline>.control:not(:last-child){margin-bottom:0.75rem}html.theme--documenter-dark .field.is-grouped.is-grouped-multiline:last-child{margin-bottom:-0.75rem}html.theme--documenter-dark .field.is-grouped.is-grouped-multiline:not(:last-child){margin-bottom:0}@media screen and (min-width: 769px),print{html.theme--documenter-dark .field.is-horizontal{display:flex}}html.theme--documenter-dark .field-label .label{font-size:inherit}@media screen and (max-width: 768px){html.theme--documenter-dark .field-label{margin-bottom:0.5rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .field-label{flex-basis:0;flex-grow:1;flex-shrink:0;margin-right:1.5rem;text-align:right}html.theme--documenter-dark .field-label.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.field-label{font-size:.75rem;padding-top:0.375em}html.theme--documenter-dark .field-label.is-normal{padding-top:0.375em}html.theme--documenter-dark .field-label.is-medium{font-size:1.25rem;padding-top:0.375em}html.theme--documenter-dark .field-label.is-large{font-size:1.5rem;padding-top:0.375em}}html.theme--documenter-dark .field-body .field .field{margin-bottom:0}@media screen and (min-width: 769px),print{html.theme--documenter-dark .field-body{display:flex;flex-basis:0;flex-grow:5;flex-shrink:1}html.theme--documenter-dark .field-body .field{margin-bottom:0}html.theme--documenter-dark .field-body>.field{flex-shrink:1}html.theme--documenter-dark .field-body>.field:not(.is-narrow){flex-grow:1}html.theme--documenter-dark .field-body>.field:not(:last-child){margin-right:.75rem}}html.theme--documenter-dark .control{box-sizing:border-box;clear:both;font-size:1rem;position:relative;text-align:inherit}html.theme--documenter-dark .control.has-icons-left .input:focus~.icon,html.theme--documenter-dark .control.has-icons-left #documenter .docs-sidebar form.docs-search>input:focus~.icon,html.theme--documenter-dark #documenter .docs-sidebar .control.has-icons-left form.docs-search>input:focus~.icon,html.theme--documenter-dark .control.has-icons-left .select:focus~.icon,html.theme--documenter-dark .control.has-icons-right .input:focus~.icon,html.theme--documenter-dark .control.has-icons-right #documenter .docs-sidebar form.docs-search>input:focus~.icon,html.theme--documenter-dark #documenter .docs-sidebar .control.has-icons-right form.docs-search>input:focus~.icon,html.theme--documenter-dark .control.has-icons-right .select:focus~.icon{color:#282f2f}html.theme--documenter-dark .control.has-icons-left .input.is-small~.icon,html.theme--documenter-dark .control.has-icons-left #documenter .docs-sidebar form.docs-search>input~.icon,html.theme--documenter-dark #documenter .docs-sidebar .control.has-icons-left form.docs-search>input~.icon,html.theme--documenter-dark .control.has-icons-left .select.is-small~.icon,html.theme--documenter-dark .control.has-icons-right .input.is-small~.icon,html.theme--documenter-dark .control.has-icons-right #documenter .docs-sidebar form.docs-search>input~.icon,html.theme--documenter-dark #documenter .docs-sidebar .control.has-icons-right form.docs-search>input~.icon,html.theme--documenter-dark .control.has-icons-right .select.is-small~.icon{font-size:.75rem}html.theme--documenter-dark .control.has-icons-left .input.is-medium~.icon,html.theme--documenter-dark .control.has-icons-left #documenter .docs-sidebar form.docs-search>input.is-medium~.icon,html.theme--documenter-dark #documenter .docs-sidebar .control.has-icons-left form.docs-search>input.is-medium~.icon,html.theme--documenter-dark .control.has-icons-left .select.is-medium~.icon,html.theme--documenter-dark .control.has-icons-right .input.is-medium~.icon,html.theme--documenter-dark .control.has-icons-right #documenter .docs-sidebar form.docs-search>input.is-medium~.icon,html.theme--documenter-dark #documenter .docs-sidebar .control.has-icons-right form.docs-search>input.is-medium~.icon,html.theme--documenter-dark .control.has-icons-right .select.is-medium~.icon{font-size:1.25rem}html.theme--documenter-dark .control.has-icons-left .input.is-large~.icon,html.theme--documenter-dark .control.has-icons-left #documenter .docs-sidebar form.docs-search>input.is-large~.icon,html.theme--documenter-dark #documenter .docs-sidebar .control.has-icons-left form.docs-search>input.is-large~.icon,html.theme--documenter-dark .control.has-icons-left .select.is-large~.icon,html.theme--documenter-dark .control.has-icons-right .input.is-large~.icon,html.theme--documenter-dark .control.has-icons-right #documenter .docs-sidebar form.docs-search>input.is-large~.icon,html.theme--documenter-dark #documenter .docs-sidebar .control.has-icons-right form.docs-search>input.is-large~.icon,html.theme--documenter-dark .control.has-icons-right .select.is-large~.icon{font-size:1.5rem}html.theme--documenter-dark .control.has-icons-left .icon,html.theme--documenter-dark .control.has-icons-right .icon{color:#5e6d6f;height:2.5em;pointer-events:none;position:absolute;top:0;width:2.5em;z-index:4}html.theme--documenter-dark .control.has-icons-left .input,html.theme--documenter-dark .control.has-icons-left #documenter .docs-sidebar form.docs-search>input,html.theme--documenter-dark #documenter .docs-sidebar .control.has-icons-left form.docs-search>input,html.theme--documenter-dark .control.has-icons-left .select select{padding-left:2.5em}html.theme--documenter-dark .control.has-icons-left .icon.is-left{left:0}html.theme--documenter-dark .control.has-icons-right .input,html.theme--documenter-dark .control.has-icons-right #documenter .docs-sidebar form.docs-search>input,html.theme--documenter-dark #documenter .docs-sidebar .control.has-icons-right form.docs-search>input,html.theme--documenter-dark .control.has-icons-right .select select{padding-right:2.5em}html.theme--documenter-dark .control.has-icons-right .icon.is-right{right:0}html.theme--documenter-dark .control.is-loading::after{position:absolute !important;right:.625em;top:0.625em;z-index:4}html.theme--documenter-dark .control.is-loading.is-small:after,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-loading:after{font-size:.75rem}html.theme--documenter-dark .control.is-loading.is-medium:after{font-size:1.25rem}html.theme--documenter-dark .control.is-loading.is-large:after{font-size:1.5rem}html.theme--documenter-dark .breadcrumb{font-size:1rem;white-space:nowrap}html.theme--documenter-dark .breadcrumb a{align-items:center;color:#1abc9c;display:flex;justify-content:center;padding:0 .75em}html.theme--documenter-dark .breadcrumb a:hover{color:#1dd2af}html.theme--documenter-dark .breadcrumb li{align-items:center;display:flex}html.theme--documenter-dark .breadcrumb li:first-child a{padding-left:0}html.theme--documenter-dark .breadcrumb li.is-active a{color:#f2f2f2;cursor:default;pointer-events:none}html.theme--documenter-dark .breadcrumb li+li::before{color:#8c9b9d;content:"\0002f"}html.theme--documenter-dark .breadcrumb ul,html.theme--documenter-dark .breadcrumb ol{align-items:flex-start;display:flex;flex-wrap:wrap;justify-content:flex-start}html.theme--documenter-dark .breadcrumb .icon:first-child{margin-right:.5em}html.theme--documenter-dark .breadcrumb .icon:last-child{margin-left:.5em}html.theme--documenter-dark .breadcrumb.is-centered ol,html.theme--documenter-dark .breadcrumb.is-centered ul{justify-content:center}html.theme--documenter-dark .breadcrumb.is-right ol,html.theme--documenter-dark .breadcrumb.is-right ul{justify-content:flex-end}html.theme--documenter-dark .breadcrumb.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.breadcrumb{font-size:.75rem}html.theme--documenter-dark .breadcrumb.is-medium{font-size:1.25rem}html.theme--documenter-dark .breadcrumb.is-large{font-size:1.5rem}html.theme--documenter-dark .breadcrumb.has-arrow-separator li+li::before{content:"\02192"}html.theme--documenter-dark .breadcrumb.has-bullet-separator li+li::before{content:"\02022"}html.theme--documenter-dark .breadcrumb.has-dot-separator li+li::before{content:"\000b7"}html.theme--documenter-dark .breadcrumb.has-succeeds-separator li+li::before{content:"\0227B"}html.theme--documenter-dark .card{background-color:#fff;border-radius:.25rem;box-shadow:#171717;color:#fff;max-width:100%;position:relative}html.theme--documenter-dark .card-footer:first-child,html.theme--documenter-dark .card-content:first-child,html.theme--documenter-dark .card-header:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}html.theme--documenter-dark .card-footer:last-child,html.theme--documenter-dark .card-content:last-child,html.theme--documenter-dark .card-header:last-child{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}html.theme--documenter-dark .card-header{background-color:rgba(0,0,0,0);align-items:stretch;box-shadow:0 0.125em 0.25em rgba(10,10,10,0.1);display:flex}html.theme--documenter-dark .card-header-title{align-items:center;color:#f2f2f2;display:flex;flex-grow:1;font-weight:700;padding:0.75rem 1rem}html.theme--documenter-dark .card-header-title.is-centered{justify-content:center}html.theme--documenter-dark .card-header-icon{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;color:currentColor;font-family:inherit;font-size:1em;margin:0;padding:0;align-items:center;cursor:pointer;display:flex;justify-content:center;padding:0.75rem 1rem}html.theme--documenter-dark .card-image{display:block;position:relative}html.theme--documenter-dark .card-image:first-child img{border-top-left-radius:.25rem;border-top-right-radius:.25rem}html.theme--documenter-dark .card-image:last-child img{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}html.theme--documenter-dark .card-content{background-color:rgba(0,0,0,0);padding:1.5rem}html.theme--documenter-dark .card-footer{background-color:rgba(0,0,0,0);border-top:1px solid #ededed;align-items:stretch;display:flex}html.theme--documenter-dark .card-footer-item{align-items:center;display:flex;flex-basis:0;flex-grow:1;flex-shrink:0;justify-content:center;padding:.75rem}html.theme--documenter-dark .card-footer-item:not(:last-child){border-right:1px solid #ededed}html.theme--documenter-dark .card .media:not(:last-child){margin-bottom:1.5rem}html.theme--documenter-dark .dropdown{display:inline-flex;position:relative;vertical-align:top}html.theme--documenter-dark .dropdown.is-active .dropdown-menu,html.theme--documenter-dark .dropdown.is-hoverable:hover .dropdown-menu{display:block}html.theme--documenter-dark .dropdown.is-right .dropdown-menu{left:auto;right:0}html.theme--documenter-dark .dropdown.is-up .dropdown-menu{bottom:100%;padding-bottom:4px;padding-top:initial;top:auto}html.theme--documenter-dark .dropdown-menu{display:none;left:0;min-width:12rem;padding-top:4px;position:absolute;top:100%;z-index:20}html.theme--documenter-dark .dropdown-content{background-color:#282f2f;border-radius:.4em;box-shadow:#171717;padding-bottom:.5rem;padding-top:.5rem}html.theme--documenter-dark .dropdown-item{color:#fff;display:block;font-size:0.875rem;line-height:1.5;padding:0.375rem 1rem;position:relative}html.theme--documenter-dark a.dropdown-item,html.theme--documenter-dark button.dropdown-item{padding-right:3rem;text-align:inherit;white-space:nowrap;width:100%}html.theme--documenter-dark a.dropdown-item:hover,html.theme--documenter-dark button.dropdown-item:hover{background-color:#282f2f;color:#0a0a0a}html.theme--documenter-dark a.dropdown-item.is-active,html.theme--documenter-dark button.dropdown-item.is-active{background-color:#1abc9c;color:#fff}html.theme--documenter-dark .dropdown-divider{background-color:#ededed;border:none;display:block;height:1px;margin:0.5rem 0}html.theme--documenter-dark .level{align-items:center;justify-content:space-between}html.theme--documenter-dark .level code{border-radius:.4em}html.theme--documenter-dark .level img{display:inline-block;vertical-align:top}html.theme--documenter-dark .level.is-mobile{display:flex}html.theme--documenter-dark .level.is-mobile .level-left,html.theme--documenter-dark .level.is-mobile .level-right{display:flex}html.theme--documenter-dark .level.is-mobile .level-left+.level-right{margin-top:0}html.theme--documenter-dark .level.is-mobile .level-item:not(:last-child){margin-bottom:0;margin-right:.75rem}html.theme--documenter-dark .level.is-mobile .level-item:not(.is-narrow){flex-grow:1}@media screen and (min-width: 769px),print{html.theme--documenter-dark .level{display:flex}html.theme--documenter-dark .level>.level-item:not(.is-narrow){flex-grow:1}}html.theme--documenter-dark .level-item{align-items:center;display:flex;flex-basis:auto;flex-grow:0;flex-shrink:0;justify-content:center}html.theme--documenter-dark .level-item .title,html.theme--documenter-dark .level-item .subtitle{margin-bottom:0}@media screen and (max-width: 768px){html.theme--documenter-dark .level-item:not(:last-child){margin-bottom:.75rem}}html.theme--documenter-dark .level-left,html.theme--documenter-dark .level-right{flex-basis:auto;flex-grow:0;flex-shrink:0}html.theme--documenter-dark .level-left .level-item.is-flexible,html.theme--documenter-dark .level-right .level-item.is-flexible{flex-grow:1}@media screen and (min-width: 769px),print{html.theme--documenter-dark .level-left .level-item:not(:last-child),html.theme--documenter-dark .level-right .level-item:not(:last-child){margin-right:.75rem}}html.theme--documenter-dark .level-left{align-items:center;justify-content:flex-start}@media screen and (max-width: 768px){html.theme--documenter-dark .level-left+.level-right{margin-top:1.5rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .level-left{display:flex}}html.theme--documenter-dark .level-right{align-items:center;justify-content:flex-end}@media screen and (min-width: 769px),print{html.theme--documenter-dark .level-right{display:flex}}html.theme--documenter-dark .media{align-items:flex-start;display:flex;text-align:inherit}html.theme--documenter-dark .media .content:not(:last-child){margin-bottom:.75rem}html.theme--documenter-dark .media .media{border-top:1px solid rgba(94,109,111,0.5);display:flex;padding-top:.75rem}html.theme--documenter-dark .media .media .content:not(:last-child),html.theme--documenter-dark .media .media .control:not(:last-child){margin-bottom:.5rem}html.theme--documenter-dark .media .media .media{padding-top:.5rem}html.theme--documenter-dark .media .media .media+.media{margin-top:.5rem}html.theme--documenter-dark .media+.media{border-top:1px solid rgba(94,109,111,0.5);margin-top:1rem;padding-top:1rem}html.theme--documenter-dark .media.is-large+.media{margin-top:1.5rem;padding-top:1.5rem}html.theme--documenter-dark .media-left,html.theme--documenter-dark .media-right{flex-basis:auto;flex-grow:0;flex-shrink:0}html.theme--documenter-dark .media-left{margin-right:1rem}html.theme--documenter-dark .media-right{margin-left:1rem}html.theme--documenter-dark .media-content{flex-basis:auto;flex-grow:1;flex-shrink:1;text-align:inherit}@media screen and (max-width: 768px){html.theme--documenter-dark .media-content{overflow-x:auto}}html.theme--documenter-dark .menu{font-size:1rem}html.theme--documenter-dark .menu.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.menu{font-size:.75rem}html.theme--documenter-dark .menu.is-medium{font-size:1.25rem}html.theme--documenter-dark .menu.is-large{font-size:1.5rem}html.theme--documenter-dark .menu-list{line-height:1.25}html.theme--documenter-dark .menu-list a{border-radius:3px;color:#fff;display:block;padding:0.5em 0.75em}html.theme--documenter-dark .menu-list a:hover{background-color:#282f2f;color:#f2f2f2}html.theme--documenter-dark .menu-list a.is-active{background-color:#1abc9c;color:#fff}html.theme--documenter-dark .menu-list li ul{border-left:1px solid #5e6d6f;margin:.75em;padding-left:.75em}html.theme--documenter-dark .menu-label{color:#fff;font-size:.75em;letter-spacing:.1em;text-transform:uppercase}html.theme--documenter-dark .menu-label:not(:first-child){margin-top:1em}html.theme--documenter-dark .menu-label:not(:last-child){margin-bottom:1em}html.theme--documenter-dark .message{background-color:#282f2f;border-radius:.4em;font-size:1rem}html.theme--documenter-dark .message strong{color:currentColor}html.theme--documenter-dark .message a:not(.button):not(.tag):not(.dropdown-item){color:currentColor;text-decoration:underline}html.theme--documenter-dark .message.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.message{font-size:.75rem}html.theme--documenter-dark .message.is-medium{font-size:1.25rem}html.theme--documenter-dark .message.is-large{font-size:1.5rem}html.theme--documenter-dark .message.is-white{background-color:#fff}html.theme--documenter-dark .message.is-white .message-header{background-color:#fff;color:#0a0a0a}html.theme--documenter-dark .message.is-white .message-body{border-color:#fff}html.theme--documenter-dark .message.is-black{background-color:#fafafa}html.theme--documenter-dark .message.is-black .message-header{background-color:#0a0a0a;color:#fff}html.theme--documenter-dark .message.is-black .message-body{border-color:#0a0a0a}html.theme--documenter-dark .message.is-light{background-color:#f9fafb}html.theme--documenter-dark .message.is-light .message-header{background-color:#ecf0f1;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .message.is-light .message-body{border-color:#ecf0f1}html.theme--documenter-dark .message.is-dark,html.theme--documenter-dark .content kbd.message{background-color:#f9fafa}html.theme--documenter-dark .message.is-dark .message-header,html.theme--documenter-dark .content kbd.message .message-header{background-color:#282f2f;color:#fff}html.theme--documenter-dark .message.is-dark .message-body,html.theme--documenter-dark .content kbd.message .message-body{border-color:#282f2f}html.theme--documenter-dark .message.is-primary,html.theme--documenter-dark .docstring>section>a.message.docs-sourcelink{background-color:#f1f5f9}html.theme--documenter-dark .message.is-primary .message-header,html.theme--documenter-dark .docstring>section>a.message.docs-sourcelink .message-header{background-color:#375a7f;color:#fff}html.theme--documenter-dark .message.is-primary .message-body,html.theme--documenter-dark .docstring>section>a.message.docs-sourcelink .message-body{border-color:#375a7f;color:#4d7eb2}html.theme--documenter-dark .message.is-link{background-color:#edfdf9}html.theme--documenter-dark .message.is-link .message-header{background-color:#1abc9c;color:#fff}html.theme--documenter-dark .message.is-link .message-body{border-color:#1abc9c;color:#15987e}html.theme--documenter-dark .message.is-info{background-color:#eff2fb}html.theme--documenter-dark .message.is-info .message-header{background-color:#3c5dcd;color:#fff}html.theme--documenter-dark .message.is-info .message-body{border-color:#3c5dcd;color:#3253c3}html.theme--documenter-dark .message.is-success{background-color:#effded}html.theme--documenter-dark .message.is-success .message-header{background-color:#259a12;color:#fff}html.theme--documenter-dark .message.is-success .message-body{border-color:#259a12;color:#2ec016}html.theme--documenter-dark .message.is-warning{background-color:#fefaec}html.theme--documenter-dark .message.is-warning .message-header{background-color:#f4c72f;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .message.is-warning .message-body{border-color:#f4c72f;color:#8c6e07}html.theme--documenter-dark .message.is-danger{background-color:#fbefef}html.theme--documenter-dark .message.is-danger .message-header{background-color:#cb3c33;color:#fff}html.theme--documenter-dark .message.is-danger .message-body{border-color:#cb3c33;color:#c03930}html.theme--documenter-dark .message-header{align-items:center;background-color:#fff;border-radius:.4em .4em 0 0;color:rgba(0,0,0,0.7);display:flex;font-weight:700;justify-content:space-between;line-height:1.25;padding:0.75em 1em;position:relative}html.theme--documenter-dark .message-header .delete{flex-grow:0;flex-shrink:0;margin-left:.75em}html.theme--documenter-dark .message-header+.message-body{border-width:0;border-top-left-radius:0;border-top-right-radius:0}html.theme--documenter-dark .message-body{border-color:#5e6d6f;border-radius:.4em;border-style:solid;border-width:0 0 0 4px;color:#fff;padding:1.25em 1.5em}html.theme--documenter-dark .message-body code,html.theme--documenter-dark .message-body pre{background-color:#fff}html.theme--documenter-dark .message-body pre code{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .modal{align-items:center;display:none;flex-direction:column;justify-content:center;overflow:hidden;position:fixed;z-index:40}html.theme--documenter-dark .modal.is-active{display:flex}html.theme--documenter-dark .modal-background{background-color:rgba(10,10,10,0.86)}html.theme--documenter-dark .modal-content,html.theme--documenter-dark .modal-card{margin:0 20px;max-height:calc(100vh - 160px);overflow:auto;position:relative;width:100%}@media screen and (min-width: 769px){html.theme--documenter-dark .modal-content,html.theme--documenter-dark .modal-card{margin:0 auto;max-height:calc(100vh - 40px);width:640px}}html.theme--documenter-dark .modal-close{background:none;height:40px;position:fixed;right:20px;top:20px;width:40px}html.theme--documenter-dark .modal-card{display:flex;flex-direction:column;max-height:calc(100vh - 40px);overflow:hidden;-ms-overflow-y:visible}html.theme--documenter-dark .modal-card-head,html.theme--documenter-dark .modal-card-foot{align-items:center;background-color:#282f2f;display:flex;flex-shrink:0;justify-content:flex-start;padding:20px;position:relative}html.theme--documenter-dark .modal-card-head{border-bottom:1px solid #5e6d6f;border-top-left-radius:8px;border-top-right-radius:8px}html.theme--documenter-dark .modal-card-title{color:#f2f2f2;flex-grow:1;flex-shrink:0;font-size:1.5rem;line-height:1}html.theme--documenter-dark .modal-card-foot{border-bottom-left-radius:8px;border-bottom-right-radius:8px;border-top:1px solid #5e6d6f}html.theme--documenter-dark .modal-card-foot .button:not(:last-child){margin-right:.5em}html.theme--documenter-dark .modal-card-body{-webkit-overflow-scrolling:touch;background-color:#fff;flex-grow:1;flex-shrink:1;overflow:auto;padding:20px}html.theme--documenter-dark .navbar{background-color:#375a7f;min-height:4rem;position:relative;z-index:30}html.theme--documenter-dark .navbar.is-white{background-color:#fff;color:#0a0a0a}html.theme--documenter-dark .navbar.is-white .navbar-brand>.navbar-item,html.theme--documenter-dark .navbar.is-white .navbar-brand .navbar-link{color:#0a0a0a}html.theme--documenter-dark .navbar.is-white .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-white .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-white .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-white .navbar-brand .navbar-link:focus,html.theme--documenter-dark .navbar.is-white .navbar-brand .navbar-link:hover,html.theme--documenter-dark .navbar.is-white .navbar-brand .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}html.theme--documenter-dark .navbar.is-white .navbar-brand .navbar-link::after{border-color:#0a0a0a}html.theme--documenter-dark .navbar.is-white .navbar-burger{color:#0a0a0a}@media screen and (min-width: 1056px){html.theme--documenter-dark .navbar.is-white .navbar-start>.navbar-item,html.theme--documenter-dark .navbar.is-white .navbar-start .navbar-link,html.theme--documenter-dark .navbar.is-white .navbar-end>.navbar-item,html.theme--documenter-dark .navbar.is-white .navbar-end .navbar-link{color:#0a0a0a}html.theme--documenter-dark .navbar.is-white .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-white .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-white .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-white .navbar-start .navbar-link:focus,html.theme--documenter-dark .navbar.is-white .navbar-start .navbar-link:hover,html.theme--documenter-dark .navbar.is-white .navbar-start .navbar-link.is-active,html.theme--documenter-dark .navbar.is-white .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-white .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-white .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-white .navbar-end .navbar-link:focus,html.theme--documenter-dark .navbar.is-white .navbar-end .navbar-link:hover,html.theme--documenter-dark .navbar.is-white .navbar-end .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}html.theme--documenter-dark .navbar.is-white .navbar-start .navbar-link::after,html.theme--documenter-dark .navbar.is-white .navbar-end .navbar-link::after{border-color:#0a0a0a}html.theme--documenter-dark .navbar.is-white .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .navbar.is-white .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .navbar.is-white .navbar-item.has-dropdown.is-active .navbar-link{background-color:#f2f2f2;color:#0a0a0a}html.theme--documenter-dark .navbar.is-white .navbar-dropdown a.navbar-item.is-active{background-color:#fff;color:#0a0a0a}}html.theme--documenter-dark .navbar.is-black{background-color:#0a0a0a;color:#fff}html.theme--documenter-dark .navbar.is-black .navbar-brand>.navbar-item,html.theme--documenter-dark .navbar.is-black .navbar-brand .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-black .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-black .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-black .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-black .navbar-brand .navbar-link:focus,html.theme--documenter-dark .navbar.is-black .navbar-brand .navbar-link:hover,html.theme--documenter-dark .navbar.is-black .navbar-brand .navbar-link.is-active{background-color:#000;color:#fff}html.theme--documenter-dark .navbar.is-black .navbar-brand .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-black .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--documenter-dark .navbar.is-black .navbar-start>.navbar-item,html.theme--documenter-dark .navbar.is-black .navbar-start .navbar-link,html.theme--documenter-dark .navbar.is-black .navbar-end>.navbar-item,html.theme--documenter-dark .navbar.is-black .navbar-end .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-black .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-black .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-black .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-black .navbar-start .navbar-link:focus,html.theme--documenter-dark .navbar.is-black .navbar-start .navbar-link:hover,html.theme--documenter-dark .navbar.is-black .navbar-start .navbar-link.is-active,html.theme--documenter-dark .navbar.is-black .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-black .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-black .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-black .navbar-end .navbar-link:focus,html.theme--documenter-dark .navbar.is-black .navbar-end .navbar-link:hover,html.theme--documenter-dark .navbar.is-black .navbar-end .navbar-link.is-active{background-color:#000;color:#fff}html.theme--documenter-dark .navbar.is-black .navbar-start .navbar-link::after,html.theme--documenter-dark .navbar.is-black .navbar-end .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-black .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .navbar.is-black .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .navbar.is-black .navbar-item.has-dropdown.is-active .navbar-link{background-color:#000;color:#fff}html.theme--documenter-dark .navbar.is-black .navbar-dropdown a.navbar-item.is-active{background-color:#0a0a0a;color:#fff}}html.theme--documenter-dark .navbar.is-light{background-color:#ecf0f1;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-light .navbar-brand>.navbar-item,html.theme--documenter-dark .navbar.is-light .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-light .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-light .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-light .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-light .navbar-brand .navbar-link:focus,html.theme--documenter-dark .navbar.is-light .navbar-brand .navbar-link:hover,html.theme--documenter-dark .navbar.is-light .navbar-brand .navbar-link.is-active{background-color:#dde4e6;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-light .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-light .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){html.theme--documenter-dark .navbar.is-light .navbar-start>.navbar-item,html.theme--documenter-dark .navbar.is-light .navbar-start .navbar-link,html.theme--documenter-dark .navbar.is-light .navbar-end>.navbar-item,html.theme--documenter-dark .navbar.is-light .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-light .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-light .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-light .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-light .navbar-start .navbar-link:focus,html.theme--documenter-dark .navbar.is-light .navbar-start .navbar-link:hover,html.theme--documenter-dark .navbar.is-light .navbar-start .navbar-link.is-active,html.theme--documenter-dark .navbar.is-light .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-light .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-light .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-light .navbar-end .navbar-link:focus,html.theme--documenter-dark .navbar.is-light .navbar-end .navbar-link:hover,html.theme--documenter-dark .navbar.is-light .navbar-end .navbar-link.is-active{background-color:#dde4e6;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-light .navbar-start .navbar-link::after,html.theme--documenter-dark .navbar.is-light .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-light .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .navbar.is-light .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .navbar.is-light .navbar-item.has-dropdown.is-active .navbar-link{background-color:#dde4e6;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-light .navbar-dropdown a.navbar-item.is-active{background-color:#ecf0f1;color:rgba(0,0,0,0.7)}}html.theme--documenter-dark .navbar.is-dark,html.theme--documenter-dark .content kbd.navbar{background-color:#282f2f;color:#fff}html.theme--documenter-dark .navbar.is-dark .navbar-brand>.navbar-item,html.theme--documenter-dark .content kbd.navbar .navbar-brand>.navbar-item,html.theme--documenter-dark .navbar.is-dark .navbar-brand .navbar-link,html.theme--documenter-dark .content kbd.navbar .navbar-brand .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-dark .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .content kbd.navbar .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-dark .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .content kbd.navbar .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-dark .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .content kbd.navbar .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-dark .navbar-brand .navbar-link:focus,html.theme--documenter-dark .content kbd.navbar .navbar-brand .navbar-link:focus,html.theme--documenter-dark .navbar.is-dark .navbar-brand .navbar-link:hover,html.theme--documenter-dark .content kbd.navbar .navbar-brand .navbar-link:hover,html.theme--documenter-dark .navbar.is-dark .navbar-brand .navbar-link.is-active,html.theme--documenter-dark .content kbd.navbar .navbar-brand .navbar-link.is-active{background-color:#1d2122;color:#fff}html.theme--documenter-dark .navbar.is-dark .navbar-brand .navbar-link::after,html.theme--documenter-dark .content kbd.navbar .navbar-brand .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-dark .navbar-burger,html.theme--documenter-dark .content kbd.navbar .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--documenter-dark .navbar.is-dark .navbar-start>.navbar-item,html.theme--documenter-dark .content kbd.navbar .navbar-start>.navbar-item,html.theme--documenter-dark .navbar.is-dark .navbar-start .navbar-link,html.theme--documenter-dark .content kbd.navbar .navbar-start .navbar-link,html.theme--documenter-dark .navbar.is-dark .navbar-end>.navbar-item,html.theme--documenter-dark .content kbd.navbar .navbar-end>.navbar-item,html.theme--documenter-dark .navbar.is-dark .navbar-end .navbar-link,html.theme--documenter-dark .content kbd.navbar .navbar-end .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-dark .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .content kbd.navbar .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-dark .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .content kbd.navbar .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-dark .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .content kbd.navbar .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-dark .navbar-start .navbar-link:focus,html.theme--documenter-dark .content kbd.navbar .navbar-start .navbar-link:focus,html.theme--documenter-dark .navbar.is-dark .navbar-start .navbar-link:hover,html.theme--documenter-dark .content kbd.navbar .navbar-start .navbar-link:hover,html.theme--documenter-dark .navbar.is-dark .navbar-start .navbar-link.is-active,html.theme--documenter-dark .content kbd.navbar .navbar-start .navbar-link.is-active,html.theme--documenter-dark .navbar.is-dark .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .content kbd.navbar .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-dark .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .content kbd.navbar .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-dark .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .content kbd.navbar .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-dark .navbar-end .navbar-link:focus,html.theme--documenter-dark .content kbd.navbar .navbar-end .navbar-link:focus,html.theme--documenter-dark .navbar.is-dark .navbar-end .navbar-link:hover,html.theme--documenter-dark .content kbd.navbar .navbar-end .navbar-link:hover,html.theme--documenter-dark .navbar.is-dark .navbar-end .navbar-link.is-active,html.theme--documenter-dark .content kbd.navbar .navbar-end .navbar-link.is-active{background-color:#1d2122;color:#fff}html.theme--documenter-dark .navbar.is-dark .navbar-start .navbar-link::after,html.theme--documenter-dark .content kbd.navbar .navbar-start .navbar-link::after,html.theme--documenter-dark .navbar.is-dark .navbar-end .navbar-link::after,html.theme--documenter-dark .content kbd.navbar .navbar-end .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-dark .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .content kbd.navbar .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .navbar.is-dark .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .content kbd.navbar .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .navbar.is-dark .navbar-item.has-dropdown.is-active .navbar-link,html.theme--documenter-dark .content kbd.navbar .navbar-item.has-dropdown.is-active .navbar-link{background-color:#1d2122;color:#fff}html.theme--documenter-dark .navbar.is-dark .navbar-dropdown a.navbar-item.is-active,html.theme--documenter-dark .content kbd.navbar .navbar-dropdown a.navbar-item.is-active{background-color:#282f2f;color:#fff}}html.theme--documenter-dark .navbar.is-primary,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink{background-color:#375a7f;color:#fff}html.theme--documenter-dark .navbar.is-primary .navbar-brand>.navbar-item,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-brand>.navbar-item,html.theme--documenter-dark .navbar.is-primary .navbar-brand .navbar-link,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-primary .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-primary .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-primary .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-primary .navbar-brand .navbar-link:focus,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link:focus,html.theme--documenter-dark .navbar.is-primary .navbar-brand .navbar-link:hover,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link:hover,html.theme--documenter-dark .navbar.is-primary .navbar-brand .navbar-link.is-active,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link.is-active{background-color:#2f4d6d;color:#fff}html.theme--documenter-dark .navbar.is-primary .navbar-brand .navbar-link::after,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-primary .navbar-burger,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--documenter-dark .navbar.is-primary .navbar-start>.navbar-item,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-start>.navbar-item,html.theme--documenter-dark .navbar.is-primary .navbar-start .navbar-link,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link,html.theme--documenter-dark .navbar.is-primary .navbar-end>.navbar-item,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-end>.navbar-item,html.theme--documenter-dark .navbar.is-primary .navbar-end .navbar-link,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-primary .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-primary .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-primary .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-primary .navbar-start .navbar-link:focus,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link:focus,html.theme--documenter-dark .navbar.is-primary .navbar-start .navbar-link:hover,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link:hover,html.theme--documenter-dark .navbar.is-primary .navbar-start .navbar-link.is-active,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link.is-active,html.theme--documenter-dark .navbar.is-primary .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-primary .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-primary .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-primary .navbar-end .navbar-link:focus,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link:focus,html.theme--documenter-dark .navbar.is-primary .navbar-end .navbar-link:hover,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link:hover,html.theme--documenter-dark .navbar.is-primary .navbar-end .navbar-link.is-active,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link.is-active{background-color:#2f4d6d;color:#fff}html.theme--documenter-dark .navbar.is-primary .navbar-start .navbar-link::after,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link::after,html.theme--documenter-dark .navbar.is-primary .navbar-end .navbar-link::after,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-primary .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .navbar.is-primary .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .navbar.is-primary .navbar-item.has-dropdown.is-active .navbar-link,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown.is-active .navbar-link{background-color:#2f4d6d;color:#fff}html.theme--documenter-dark .navbar.is-primary .navbar-dropdown a.navbar-item.is-active,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-dropdown a.navbar-item.is-active{background-color:#375a7f;color:#fff}}html.theme--documenter-dark .navbar.is-link{background-color:#1abc9c;color:#fff}html.theme--documenter-dark .navbar.is-link .navbar-brand>.navbar-item,html.theme--documenter-dark .navbar.is-link .navbar-brand .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-link .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-link .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-link .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-link .navbar-brand .navbar-link:focus,html.theme--documenter-dark .navbar.is-link .navbar-brand .navbar-link:hover,html.theme--documenter-dark .navbar.is-link .navbar-brand .navbar-link.is-active{background-color:#17a689;color:#fff}html.theme--documenter-dark .navbar.is-link .navbar-brand .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-link .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--documenter-dark .navbar.is-link .navbar-start>.navbar-item,html.theme--documenter-dark .navbar.is-link .navbar-start .navbar-link,html.theme--documenter-dark .navbar.is-link .navbar-end>.navbar-item,html.theme--documenter-dark .navbar.is-link .navbar-end .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-link .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-link .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-link .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-link .navbar-start .navbar-link:focus,html.theme--documenter-dark .navbar.is-link .navbar-start .navbar-link:hover,html.theme--documenter-dark .navbar.is-link .navbar-start .navbar-link.is-active,html.theme--documenter-dark .navbar.is-link .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-link .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-link .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-link .navbar-end .navbar-link:focus,html.theme--documenter-dark .navbar.is-link .navbar-end .navbar-link:hover,html.theme--documenter-dark .navbar.is-link .navbar-end .navbar-link.is-active{background-color:#17a689;color:#fff}html.theme--documenter-dark .navbar.is-link .navbar-start .navbar-link::after,html.theme--documenter-dark .navbar.is-link .navbar-end .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-link .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .navbar.is-link .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .navbar.is-link .navbar-item.has-dropdown.is-active .navbar-link{background-color:#17a689;color:#fff}html.theme--documenter-dark .navbar.is-link .navbar-dropdown a.navbar-item.is-active{background-color:#1abc9c;color:#fff}}html.theme--documenter-dark .navbar.is-info{background-color:#3c5dcd;color:#fff}html.theme--documenter-dark .navbar.is-info .navbar-brand>.navbar-item,html.theme--documenter-dark .navbar.is-info .navbar-brand .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-info .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-info .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-info .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-info .navbar-brand .navbar-link:focus,html.theme--documenter-dark .navbar.is-info .navbar-brand .navbar-link:hover,html.theme--documenter-dark .navbar.is-info .navbar-brand .navbar-link.is-active{background-color:#3151bf;color:#fff}html.theme--documenter-dark .navbar.is-info .navbar-brand .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-info .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--documenter-dark .navbar.is-info .navbar-start>.navbar-item,html.theme--documenter-dark .navbar.is-info .navbar-start .navbar-link,html.theme--documenter-dark .navbar.is-info .navbar-end>.navbar-item,html.theme--documenter-dark .navbar.is-info .navbar-end .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-info .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-info .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-info .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-info .navbar-start .navbar-link:focus,html.theme--documenter-dark .navbar.is-info .navbar-start .navbar-link:hover,html.theme--documenter-dark .navbar.is-info .navbar-start .navbar-link.is-active,html.theme--documenter-dark .navbar.is-info .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-info .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-info .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-info .navbar-end .navbar-link:focus,html.theme--documenter-dark .navbar.is-info .navbar-end .navbar-link:hover,html.theme--documenter-dark .navbar.is-info .navbar-end .navbar-link.is-active{background-color:#3151bf;color:#fff}html.theme--documenter-dark .navbar.is-info .navbar-start .navbar-link::after,html.theme--documenter-dark .navbar.is-info .navbar-end .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-info .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .navbar.is-info .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .navbar.is-info .navbar-item.has-dropdown.is-active .navbar-link{background-color:#3151bf;color:#fff}html.theme--documenter-dark .navbar.is-info .navbar-dropdown a.navbar-item.is-active{background-color:#3c5dcd;color:#fff}}html.theme--documenter-dark .navbar.is-success{background-color:#259a12;color:#fff}html.theme--documenter-dark .navbar.is-success .navbar-brand>.navbar-item,html.theme--documenter-dark .navbar.is-success .navbar-brand .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-success .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-success .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-success .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-success .navbar-brand .navbar-link:focus,html.theme--documenter-dark .navbar.is-success .navbar-brand .navbar-link:hover,html.theme--documenter-dark .navbar.is-success .navbar-brand .navbar-link.is-active{background-color:#20830f;color:#fff}html.theme--documenter-dark .navbar.is-success .navbar-brand .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-success .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--documenter-dark .navbar.is-success .navbar-start>.navbar-item,html.theme--documenter-dark .navbar.is-success .navbar-start .navbar-link,html.theme--documenter-dark .navbar.is-success .navbar-end>.navbar-item,html.theme--documenter-dark .navbar.is-success .navbar-end .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-success .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-success .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-success .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-success .navbar-start .navbar-link:focus,html.theme--documenter-dark .navbar.is-success .navbar-start .navbar-link:hover,html.theme--documenter-dark .navbar.is-success .navbar-start .navbar-link.is-active,html.theme--documenter-dark .navbar.is-success .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-success .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-success .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-success .navbar-end .navbar-link:focus,html.theme--documenter-dark .navbar.is-success .navbar-end .navbar-link:hover,html.theme--documenter-dark .navbar.is-success .navbar-end .navbar-link.is-active{background-color:#20830f;color:#fff}html.theme--documenter-dark .navbar.is-success .navbar-start .navbar-link::after,html.theme--documenter-dark .navbar.is-success .navbar-end .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-success .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .navbar.is-success .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .navbar.is-success .navbar-item.has-dropdown.is-active .navbar-link{background-color:#20830f;color:#fff}html.theme--documenter-dark .navbar.is-success .navbar-dropdown a.navbar-item.is-active{background-color:#259a12;color:#fff}}html.theme--documenter-dark .navbar.is-warning{background-color:#f4c72f;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-warning .navbar-brand>.navbar-item,html.theme--documenter-dark .navbar.is-warning .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-warning .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-warning .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-warning .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-warning .navbar-brand .navbar-link:focus,html.theme--documenter-dark .navbar.is-warning .navbar-brand .navbar-link:hover,html.theme--documenter-dark .navbar.is-warning .navbar-brand .navbar-link.is-active{background-color:#f3c017;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-warning .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-warning .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){html.theme--documenter-dark .navbar.is-warning .navbar-start>.navbar-item,html.theme--documenter-dark .navbar.is-warning .navbar-start .navbar-link,html.theme--documenter-dark .navbar.is-warning .navbar-end>.navbar-item,html.theme--documenter-dark .navbar.is-warning .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-warning .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-warning .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-warning .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-warning .navbar-start .navbar-link:focus,html.theme--documenter-dark .navbar.is-warning .navbar-start .navbar-link:hover,html.theme--documenter-dark .navbar.is-warning .navbar-start .navbar-link.is-active,html.theme--documenter-dark .navbar.is-warning .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-warning .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-warning .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-warning .navbar-end .navbar-link:focus,html.theme--documenter-dark .navbar.is-warning .navbar-end .navbar-link:hover,html.theme--documenter-dark .navbar.is-warning .navbar-end .navbar-link.is-active{background-color:#f3c017;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-warning .navbar-start .navbar-link::after,html.theme--documenter-dark .navbar.is-warning .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-warning .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .navbar.is-warning .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .navbar.is-warning .navbar-item.has-dropdown.is-active .navbar-link{background-color:#f3c017;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-warning .navbar-dropdown a.navbar-item.is-active{background-color:#f4c72f;color:rgba(0,0,0,0.7)}}html.theme--documenter-dark .navbar.is-danger{background-color:#cb3c33;color:#fff}html.theme--documenter-dark .navbar.is-danger .navbar-brand>.navbar-item,html.theme--documenter-dark .navbar.is-danger .navbar-brand .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-danger .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-danger .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-danger .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-danger .navbar-brand .navbar-link:focus,html.theme--documenter-dark .navbar.is-danger .navbar-brand .navbar-link:hover,html.theme--documenter-dark .navbar.is-danger .navbar-brand .navbar-link.is-active{background-color:#b7362e;color:#fff}html.theme--documenter-dark .navbar.is-danger .navbar-brand .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-danger .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--documenter-dark .navbar.is-danger .navbar-start>.navbar-item,html.theme--documenter-dark .navbar.is-danger .navbar-start .navbar-link,html.theme--documenter-dark .navbar.is-danger .navbar-end>.navbar-item,html.theme--documenter-dark .navbar.is-danger .navbar-end .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-danger .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-danger .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-danger .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-danger .navbar-start .navbar-link:focus,html.theme--documenter-dark .navbar.is-danger .navbar-start .navbar-link:hover,html.theme--documenter-dark .navbar.is-danger .navbar-start .navbar-link.is-active,html.theme--documenter-dark .navbar.is-danger .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-danger .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-danger .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-danger .navbar-end .navbar-link:focus,html.theme--documenter-dark .navbar.is-danger .navbar-end .navbar-link:hover,html.theme--documenter-dark .navbar.is-danger .navbar-end .navbar-link.is-active{background-color:#b7362e;color:#fff}html.theme--documenter-dark .navbar.is-danger .navbar-start .navbar-link::after,html.theme--documenter-dark .navbar.is-danger .navbar-end .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-danger .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .navbar.is-danger .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .navbar.is-danger .navbar-item.has-dropdown.is-active .navbar-link{background-color:#b7362e;color:#fff}html.theme--documenter-dark .navbar.is-danger .navbar-dropdown a.navbar-item.is-active{background-color:#cb3c33;color:#fff}}html.theme--documenter-dark .navbar>.container{align-items:stretch;display:flex;min-height:4rem;width:100%}html.theme--documenter-dark .navbar.has-shadow{box-shadow:0 2px 0 0 #282f2f}html.theme--documenter-dark .navbar.is-fixed-bottom,html.theme--documenter-dark .navbar.is-fixed-top{left:0;position:fixed;right:0;z-index:30}html.theme--documenter-dark .navbar.is-fixed-bottom{bottom:0}html.theme--documenter-dark .navbar.is-fixed-bottom.has-shadow{box-shadow:0 -2px 0 0 #282f2f}html.theme--documenter-dark .navbar.is-fixed-top{top:0}html.theme--documenter-dark html.has-navbar-fixed-top,html.theme--documenter-dark body.has-navbar-fixed-top{padding-top:4rem}html.theme--documenter-dark html.has-navbar-fixed-bottom,html.theme--documenter-dark body.has-navbar-fixed-bottom{padding-bottom:4rem}html.theme--documenter-dark .navbar-brand,html.theme--documenter-dark .navbar-tabs{align-items:stretch;display:flex;flex-shrink:0;min-height:4rem}html.theme--documenter-dark .navbar-brand a.navbar-item:focus,html.theme--documenter-dark .navbar-brand a.navbar-item:hover{background-color:transparent}html.theme--documenter-dark .navbar-tabs{-webkit-overflow-scrolling:touch;max-width:100vw;overflow-x:auto;overflow-y:hidden}html.theme--documenter-dark .navbar-burger{color:#fff;-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;cursor:pointer;display:block;height:4rem;position:relative;width:4rem;margin-left:auto}html.theme--documenter-dark .navbar-burger span{background-color:currentColor;display:block;height:1px;left:calc(50% - 8px);position:absolute;transform-origin:center;transition-duration:86ms;transition-property:background-color, opacity, transform;transition-timing-function:ease-out;width:16px}html.theme--documenter-dark .navbar-burger span:nth-child(1){top:calc(50% - 6px)}html.theme--documenter-dark .navbar-burger span:nth-child(2){top:calc(50% - 1px)}html.theme--documenter-dark .navbar-burger span:nth-child(3){top:calc(50% + 4px)}html.theme--documenter-dark .navbar-burger:hover{background-color:rgba(0,0,0,0.05)}html.theme--documenter-dark .navbar-burger.is-active span:nth-child(1){transform:translateY(5px) rotate(45deg)}html.theme--documenter-dark .navbar-burger.is-active span:nth-child(2){opacity:0}html.theme--documenter-dark .navbar-burger.is-active span:nth-child(3){transform:translateY(-5px) rotate(-45deg)}html.theme--documenter-dark .navbar-menu{display:none}html.theme--documenter-dark .navbar-item,html.theme--documenter-dark .navbar-link{color:#fff;display:block;line-height:1.5;padding:0.5rem 0.75rem;position:relative}html.theme--documenter-dark .navbar-item .icon:only-child,html.theme--documenter-dark .navbar-link .icon:only-child{margin-left:-0.25rem;margin-right:-0.25rem}html.theme--documenter-dark a.navbar-item,html.theme--documenter-dark .navbar-link{cursor:pointer}html.theme--documenter-dark a.navbar-item:focus,html.theme--documenter-dark a.navbar-item:focus-within,html.theme--documenter-dark a.navbar-item:hover,html.theme--documenter-dark a.navbar-item.is-active,html.theme--documenter-dark .navbar-link:focus,html.theme--documenter-dark .navbar-link:focus-within,html.theme--documenter-dark .navbar-link:hover,html.theme--documenter-dark .navbar-link.is-active{background-color:rgba(0,0,0,0);color:#1abc9c}html.theme--documenter-dark .navbar-item{flex-grow:0;flex-shrink:0}html.theme--documenter-dark .navbar-item img{max-height:1.75rem}html.theme--documenter-dark .navbar-item.has-dropdown{padding:0}html.theme--documenter-dark .navbar-item.is-expanded{flex-grow:1;flex-shrink:1}html.theme--documenter-dark .navbar-item.is-tab{border-bottom:1px solid transparent;min-height:4rem;padding-bottom:calc(0.5rem - 1px)}html.theme--documenter-dark .navbar-item.is-tab:focus,html.theme--documenter-dark .navbar-item.is-tab:hover{background-color:rgba(0,0,0,0);border-bottom-color:#1abc9c}html.theme--documenter-dark .navbar-item.is-tab.is-active{background-color:rgba(0,0,0,0);border-bottom-color:#1abc9c;border-bottom-style:solid;border-bottom-width:3px;color:#1abc9c;padding-bottom:calc(0.5rem - 3px)}html.theme--documenter-dark .navbar-content{flex-grow:1;flex-shrink:1}html.theme--documenter-dark .navbar-link:not(.is-arrowless){padding-right:2.5em}html.theme--documenter-dark .navbar-link:not(.is-arrowless)::after{border-color:#fff;margin-top:-0.375em;right:1.125em}html.theme--documenter-dark .navbar-dropdown{font-size:0.875rem;padding-bottom:0.5rem;padding-top:0.5rem}html.theme--documenter-dark .navbar-dropdown .navbar-item{padding-left:1.5rem;padding-right:1.5rem}html.theme--documenter-dark .navbar-divider{background-color:rgba(0,0,0,0.2);border:none;display:none;height:2px;margin:0.5rem 0}@media screen and (max-width: 1055px){html.theme--documenter-dark .navbar>.container{display:block}html.theme--documenter-dark .navbar-brand .navbar-item,html.theme--documenter-dark .navbar-tabs .navbar-item{align-items:center;display:flex}html.theme--documenter-dark .navbar-link::after{display:none}html.theme--documenter-dark .navbar-menu{background-color:#375a7f;box-shadow:0 8px 16px rgba(10,10,10,0.1);padding:0.5rem 0}html.theme--documenter-dark .navbar-menu.is-active{display:block}html.theme--documenter-dark .navbar.is-fixed-bottom-touch,html.theme--documenter-dark .navbar.is-fixed-top-touch{left:0;position:fixed;right:0;z-index:30}html.theme--documenter-dark .navbar.is-fixed-bottom-touch{bottom:0}html.theme--documenter-dark .navbar.is-fixed-bottom-touch.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,0.1)}html.theme--documenter-dark .navbar.is-fixed-top-touch{top:0}html.theme--documenter-dark .navbar.is-fixed-top .navbar-menu,html.theme--documenter-dark .navbar.is-fixed-top-touch .navbar-menu{-webkit-overflow-scrolling:touch;max-height:calc(100vh - 4rem);overflow:auto}html.theme--documenter-dark html.has-navbar-fixed-top-touch,html.theme--documenter-dark body.has-navbar-fixed-top-touch{padding-top:4rem}html.theme--documenter-dark html.has-navbar-fixed-bottom-touch,html.theme--documenter-dark body.has-navbar-fixed-bottom-touch{padding-bottom:4rem}}@media screen and (min-width: 1056px){html.theme--documenter-dark .navbar,html.theme--documenter-dark .navbar-menu,html.theme--documenter-dark .navbar-start,html.theme--documenter-dark .navbar-end{align-items:stretch;display:flex}html.theme--documenter-dark .navbar{min-height:4rem}html.theme--documenter-dark .navbar.is-spaced{padding:1rem 2rem}html.theme--documenter-dark .navbar.is-spaced .navbar-start,html.theme--documenter-dark .navbar.is-spaced .navbar-end{align-items:center}html.theme--documenter-dark .navbar.is-spaced a.navbar-item,html.theme--documenter-dark .navbar.is-spaced .navbar-link{border-radius:.4em}html.theme--documenter-dark .navbar.is-transparent a.navbar-item:focus,html.theme--documenter-dark .navbar.is-transparent a.navbar-item:hover,html.theme--documenter-dark .navbar.is-transparent a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-transparent .navbar-link:focus,html.theme--documenter-dark .navbar.is-transparent .navbar-link:hover,html.theme--documenter-dark .navbar.is-transparent .navbar-link.is-active{background-color:transparent !important}html.theme--documenter-dark .navbar.is-transparent .navbar-item.has-dropdown.is-active .navbar-link,html.theme--documenter-dark .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus .navbar-link,html.theme--documenter-dark .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus-within .navbar-link,html.theme--documenter-dark .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:hover .navbar-link{background-color:transparent !important}html.theme--documenter-dark .navbar.is-transparent .navbar-dropdown a.navbar-item:focus,html.theme--documenter-dark .navbar.is-transparent .navbar-dropdown a.navbar-item:hover{background-color:rgba(0,0,0,0);color:#dbdee0}html.theme--documenter-dark .navbar.is-transparent .navbar-dropdown a.navbar-item.is-active{background-color:rgba(0,0,0,0);color:#1abc9c}html.theme--documenter-dark .navbar-burger{display:none}html.theme--documenter-dark .navbar-item,html.theme--documenter-dark .navbar-link{align-items:center;display:flex}html.theme--documenter-dark .navbar-item.has-dropdown{align-items:stretch}html.theme--documenter-dark .navbar-item.has-dropdown-up .navbar-link::after{transform:rotate(135deg) translate(0.25em, -0.25em)}html.theme--documenter-dark .navbar-item.has-dropdown-up .navbar-dropdown{border-bottom:1px solid rgba(0,0,0,0.2);border-radius:8px 8px 0 0;border-top:none;bottom:100%;box-shadow:0 -8px 8px rgba(10,10,10,0.1);top:auto}html.theme--documenter-dark .navbar-item.is-active .navbar-dropdown,html.theme--documenter-dark .navbar-item.is-hoverable:focus .navbar-dropdown,html.theme--documenter-dark .navbar-item.is-hoverable:focus-within .navbar-dropdown,html.theme--documenter-dark .navbar-item.is-hoverable:hover .navbar-dropdown{display:block}.navbar.is-spaced html.theme--documenter-dark .navbar-item.is-active .navbar-dropdown,html.theme--documenter-dark .navbar-item.is-active .navbar-dropdown.is-boxed,.navbar.is-spaced html.theme--documenter-dark .navbar-item.is-hoverable:focus .navbar-dropdown,html.theme--documenter-dark .navbar-item.is-hoverable:focus .navbar-dropdown.is-boxed,.navbar.is-spaced html.theme--documenter-dark .navbar-item.is-hoverable:focus-within .navbar-dropdown,html.theme--documenter-dark .navbar-item.is-hoverable:focus-within .navbar-dropdown.is-boxed,.navbar.is-spaced html.theme--documenter-dark .navbar-item.is-hoverable:hover .navbar-dropdown,html.theme--documenter-dark .navbar-item.is-hoverable:hover .navbar-dropdown.is-boxed{opacity:1;pointer-events:auto;transform:translateY(0)}html.theme--documenter-dark .navbar-menu{flex-grow:1;flex-shrink:0}html.theme--documenter-dark .navbar-start{justify-content:flex-start;margin-right:auto}html.theme--documenter-dark .navbar-end{justify-content:flex-end;margin-left:auto}html.theme--documenter-dark .navbar-dropdown{background-color:#375a7f;border-bottom-left-radius:8px;border-bottom-right-radius:8px;border-top:1px solid rgba(0,0,0,0.2);box-shadow:0 8px 8px rgba(10,10,10,0.1);display:none;font-size:0.875rem;left:0;min-width:100%;position:absolute;top:100%;z-index:20}html.theme--documenter-dark .navbar-dropdown .navbar-item{padding:0.375rem 1rem;white-space:nowrap}html.theme--documenter-dark .navbar-dropdown a.navbar-item{padding-right:3rem}html.theme--documenter-dark .navbar-dropdown a.navbar-item:focus,html.theme--documenter-dark .navbar-dropdown a.navbar-item:hover{background-color:rgba(0,0,0,0);color:#dbdee0}html.theme--documenter-dark .navbar-dropdown a.navbar-item.is-active{background-color:rgba(0,0,0,0);color:#1abc9c}.navbar.is-spaced html.theme--documenter-dark .navbar-dropdown,html.theme--documenter-dark .navbar-dropdown.is-boxed{border-radius:8px;border-top:none;box-shadow:0 8px 8px rgba(10,10,10,0.1), 0 0 0 1px rgba(10,10,10,0.1);display:block;opacity:0;pointer-events:none;top:calc(100% + (-4px));transform:translateY(-5px);transition-duration:86ms;transition-property:opacity, transform}html.theme--documenter-dark .navbar-dropdown.is-right{left:auto;right:0}html.theme--documenter-dark .navbar-divider{display:block}html.theme--documenter-dark .navbar>.container .navbar-brand,html.theme--documenter-dark .container>.navbar .navbar-brand{margin-left:-.75rem}html.theme--documenter-dark .navbar>.container .navbar-menu,html.theme--documenter-dark .container>.navbar .navbar-menu{margin-right:-.75rem}html.theme--documenter-dark .navbar.is-fixed-bottom-desktop,html.theme--documenter-dark .navbar.is-fixed-top-desktop{left:0;position:fixed;right:0;z-index:30}html.theme--documenter-dark .navbar.is-fixed-bottom-desktop{bottom:0}html.theme--documenter-dark .navbar.is-fixed-bottom-desktop.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,0.1)}html.theme--documenter-dark .navbar.is-fixed-top-desktop{top:0}html.theme--documenter-dark html.has-navbar-fixed-top-desktop,html.theme--documenter-dark body.has-navbar-fixed-top-desktop{padding-top:4rem}html.theme--documenter-dark html.has-navbar-fixed-bottom-desktop,html.theme--documenter-dark body.has-navbar-fixed-bottom-desktop{padding-bottom:4rem}html.theme--documenter-dark html.has-spaced-navbar-fixed-top,html.theme--documenter-dark body.has-spaced-navbar-fixed-top{padding-top:6rem}html.theme--documenter-dark html.has-spaced-navbar-fixed-bottom,html.theme--documenter-dark body.has-spaced-navbar-fixed-bottom{padding-bottom:6rem}html.theme--documenter-dark a.navbar-item.is-active,html.theme--documenter-dark .navbar-link.is-active{color:#1abc9c}html.theme--documenter-dark a.navbar-item.is-active:not(:focus):not(:hover),html.theme--documenter-dark .navbar-link.is-active:not(:focus):not(:hover){background-color:rgba(0,0,0,0)}html.theme--documenter-dark .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .navbar-item.has-dropdown.is-active .navbar-link{background-color:rgba(0,0,0,0)}}html.theme--documenter-dark .hero.is-fullheight-with-navbar{min-height:calc(100vh - 4rem)}html.theme--documenter-dark .pagination{font-size:1rem;margin:-.25rem}html.theme--documenter-dark .pagination.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.pagination{font-size:.75rem}html.theme--documenter-dark .pagination.is-medium{font-size:1.25rem}html.theme--documenter-dark .pagination.is-large{font-size:1.5rem}html.theme--documenter-dark .pagination.is-rounded .pagination-previous,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.pagination .pagination-previous,html.theme--documenter-dark .pagination.is-rounded .pagination-next,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.pagination .pagination-next{padding-left:1em;padding-right:1em;border-radius:9999px}html.theme--documenter-dark .pagination.is-rounded .pagination-link,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.pagination .pagination-link{border-radius:9999px}html.theme--documenter-dark .pagination,html.theme--documenter-dark .pagination-list{align-items:center;display:flex;justify-content:center;text-align:center}html.theme--documenter-dark .pagination-previous,html.theme--documenter-dark .pagination-next,html.theme--documenter-dark .pagination-link,html.theme--documenter-dark .pagination-ellipsis{font-size:1em;justify-content:center;margin:.25rem;padding-left:.5em;padding-right:.5em;text-align:center}html.theme--documenter-dark .pagination-previous,html.theme--documenter-dark .pagination-next,html.theme--documenter-dark .pagination-link{border-color:#5e6d6f;color:#1abc9c;min-width:2.5em}html.theme--documenter-dark .pagination-previous:hover,html.theme--documenter-dark .pagination-next:hover,html.theme--documenter-dark .pagination-link:hover{border-color:#8c9b9d;color:#1dd2af}html.theme--documenter-dark .pagination-previous:focus,html.theme--documenter-dark .pagination-next:focus,html.theme--documenter-dark .pagination-link:focus{border-color:#8c9b9d}html.theme--documenter-dark .pagination-previous:active,html.theme--documenter-dark .pagination-next:active,html.theme--documenter-dark .pagination-link:active{box-shadow:inset 0 1px 2px rgba(10,10,10,0.2)}html.theme--documenter-dark .pagination-previous[disabled],html.theme--documenter-dark .pagination-previous.is-disabled,html.theme--documenter-dark .pagination-next[disabled],html.theme--documenter-dark .pagination-next.is-disabled,html.theme--documenter-dark .pagination-link[disabled],html.theme--documenter-dark .pagination-link.is-disabled{background-color:#5e6d6f;border-color:#5e6d6f;box-shadow:none;color:#fff;opacity:0.5}html.theme--documenter-dark .pagination-previous,html.theme--documenter-dark .pagination-next{padding-left:.75em;padding-right:.75em;white-space:nowrap}html.theme--documenter-dark .pagination-link.is-current{background-color:#1abc9c;border-color:#1abc9c;color:#fff}html.theme--documenter-dark .pagination-ellipsis{color:#8c9b9d;pointer-events:none}html.theme--documenter-dark .pagination-list{flex-wrap:wrap}html.theme--documenter-dark .pagination-list li{list-style:none}@media screen and (max-width: 768px){html.theme--documenter-dark .pagination{flex-wrap:wrap}html.theme--documenter-dark .pagination-previous,html.theme--documenter-dark .pagination-next{flex-grow:1;flex-shrink:1}html.theme--documenter-dark .pagination-list li{flex-grow:1;flex-shrink:1}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .pagination-list{flex-grow:1;flex-shrink:1;justify-content:flex-start;order:1}html.theme--documenter-dark .pagination-previous,html.theme--documenter-dark .pagination-next,html.theme--documenter-dark .pagination-link,html.theme--documenter-dark .pagination-ellipsis{margin-bottom:0;margin-top:0}html.theme--documenter-dark .pagination-previous{order:2}html.theme--documenter-dark .pagination-next{order:3}html.theme--documenter-dark .pagination{justify-content:space-between;margin-bottom:0;margin-top:0}html.theme--documenter-dark .pagination.is-centered .pagination-previous{order:1}html.theme--documenter-dark .pagination.is-centered .pagination-list{justify-content:center;order:2}html.theme--documenter-dark .pagination.is-centered .pagination-next{order:3}html.theme--documenter-dark .pagination.is-right .pagination-previous{order:1}html.theme--documenter-dark .pagination.is-right .pagination-next{order:2}html.theme--documenter-dark .pagination.is-right .pagination-list{justify-content:flex-end;order:3}}html.theme--documenter-dark .panel{border-radius:8px;box-shadow:#171717;font-size:1rem}html.theme--documenter-dark .panel:not(:last-child){margin-bottom:1.5rem}html.theme--documenter-dark .panel.is-white .panel-heading{background-color:#fff;color:#0a0a0a}html.theme--documenter-dark .panel.is-white .panel-tabs a.is-active{border-bottom-color:#fff}html.theme--documenter-dark .panel.is-white .panel-block.is-active .panel-icon{color:#fff}html.theme--documenter-dark .panel.is-black .panel-heading{background-color:#0a0a0a;color:#fff}html.theme--documenter-dark .panel.is-black .panel-tabs a.is-active{border-bottom-color:#0a0a0a}html.theme--documenter-dark .panel.is-black .panel-block.is-active .panel-icon{color:#0a0a0a}html.theme--documenter-dark .panel.is-light .panel-heading{background-color:#ecf0f1;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .panel.is-light .panel-tabs a.is-active{border-bottom-color:#ecf0f1}html.theme--documenter-dark .panel.is-light .panel-block.is-active .panel-icon{color:#ecf0f1}html.theme--documenter-dark .panel.is-dark .panel-heading,html.theme--documenter-dark .content kbd.panel .panel-heading{background-color:#282f2f;color:#fff}html.theme--documenter-dark .panel.is-dark .panel-tabs a.is-active,html.theme--documenter-dark .content kbd.panel .panel-tabs a.is-active{border-bottom-color:#282f2f}html.theme--documenter-dark .panel.is-dark .panel-block.is-active .panel-icon,html.theme--documenter-dark .content kbd.panel .panel-block.is-active .panel-icon{color:#282f2f}html.theme--documenter-dark .panel.is-primary .panel-heading,html.theme--documenter-dark .docstring>section>a.panel.docs-sourcelink .panel-heading{background-color:#375a7f;color:#fff}html.theme--documenter-dark .panel.is-primary .panel-tabs a.is-active,html.theme--documenter-dark .docstring>section>a.panel.docs-sourcelink .panel-tabs a.is-active{border-bottom-color:#375a7f}html.theme--documenter-dark .panel.is-primary .panel-block.is-active .panel-icon,html.theme--documenter-dark .docstring>section>a.panel.docs-sourcelink .panel-block.is-active .panel-icon{color:#375a7f}html.theme--documenter-dark .panel.is-link .panel-heading{background-color:#1abc9c;color:#fff}html.theme--documenter-dark .panel.is-link .panel-tabs a.is-active{border-bottom-color:#1abc9c}html.theme--documenter-dark .panel.is-link .panel-block.is-active .panel-icon{color:#1abc9c}html.theme--documenter-dark .panel.is-info .panel-heading{background-color:#3c5dcd;color:#fff}html.theme--documenter-dark .panel.is-info .panel-tabs a.is-active{border-bottom-color:#3c5dcd}html.theme--documenter-dark .panel.is-info .panel-block.is-active .panel-icon{color:#3c5dcd}html.theme--documenter-dark .panel.is-success .panel-heading{background-color:#259a12;color:#fff}html.theme--documenter-dark .panel.is-success .panel-tabs a.is-active{border-bottom-color:#259a12}html.theme--documenter-dark .panel.is-success .panel-block.is-active .panel-icon{color:#259a12}html.theme--documenter-dark .panel.is-warning .panel-heading{background-color:#f4c72f;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .panel.is-warning .panel-tabs a.is-active{border-bottom-color:#f4c72f}html.theme--documenter-dark .panel.is-warning .panel-block.is-active .panel-icon{color:#f4c72f}html.theme--documenter-dark .panel.is-danger .panel-heading{background-color:#cb3c33;color:#fff}html.theme--documenter-dark .panel.is-danger .panel-tabs a.is-active{border-bottom-color:#cb3c33}html.theme--documenter-dark .panel.is-danger .panel-block.is-active .panel-icon{color:#cb3c33}html.theme--documenter-dark .panel-tabs:not(:last-child),html.theme--documenter-dark .panel-block:not(:last-child){border-bottom:1px solid #ededed}html.theme--documenter-dark .panel-heading{background-color:#343c3d;border-radius:8px 8px 0 0;color:#f2f2f2;font-size:1.25em;font-weight:700;line-height:1.25;padding:0.75em 1em}html.theme--documenter-dark .panel-tabs{align-items:flex-end;display:flex;font-size:.875em;justify-content:center}html.theme--documenter-dark .panel-tabs a{border-bottom:1px solid #5e6d6f;margin-bottom:-1px;padding:0.5em}html.theme--documenter-dark .panel-tabs a.is-active{border-bottom-color:#343c3d;color:#17a689}html.theme--documenter-dark .panel-list a{color:#fff}html.theme--documenter-dark .panel-list a:hover{color:#1abc9c}html.theme--documenter-dark .panel-block{align-items:center;color:#f2f2f2;display:flex;justify-content:flex-start;padding:0.5em 0.75em}html.theme--documenter-dark .panel-block input[type="checkbox"]{margin-right:.75em}html.theme--documenter-dark .panel-block>.control{flex-grow:1;flex-shrink:1;width:100%}html.theme--documenter-dark .panel-block.is-wrapped{flex-wrap:wrap}html.theme--documenter-dark .panel-block.is-active{border-left-color:#1abc9c;color:#17a689}html.theme--documenter-dark .panel-block.is-active .panel-icon{color:#1abc9c}html.theme--documenter-dark .panel-block:last-child{border-bottom-left-radius:8px;border-bottom-right-radius:8px}html.theme--documenter-dark a.panel-block,html.theme--documenter-dark label.panel-block{cursor:pointer}html.theme--documenter-dark a.panel-block:hover,html.theme--documenter-dark label.panel-block:hover{background-color:#282f2f}html.theme--documenter-dark .panel-icon{display:inline-block;font-size:14px;height:1em;line-height:1em;text-align:center;vertical-align:top;width:1em;color:#fff;margin-right:.75em}html.theme--documenter-dark .panel-icon .fa{font-size:inherit;line-height:inherit}html.theme--documenter-dark .tabs{-webkit-overflow-scrolling:touch;align-items:stretch;display:flex;font-size:1rem;justify-content:space-between;overflow:hidden;overflow-x:auto;white-space:nowrap}html.theme--documenter-dark .tabs a{align-items:center;border-bottom-color:#5e6d6f;border-bottom-style:solid;border-bottom-width:1px;color:#fff;display:flex;justify-content:center;margin-bottom:-1px;padding:0.5em 1em;vertical-align:top}html.theme--documenter-dark .tabs a:hover{border-bottom-color:#f2f2f2;color:#f2f2f2}html.theme--documenter-dark .tabs li{display:block}html.theme--documenter-dark .tabs li.is-active a{border-bottom-color:#1abc9c;color:#1abc9c}html.theme--documenter-dark .tabs ul{align-items:center;border-bottom-color:#5e6d6f;border-bottom-style:solid;border-bottom-width:1px;display:flex;flex-grow:1;flex-shrink:0;justify-content:flex-start}html.theme--documenter-dark .tabs ul.is-left{padding-right:0.75em}html.theme--documenter-dark .tabs ul.is-center{flex:none;justify-content:center;padding-left:0.75em;padding-right:0.75em}html.theme--documenter-dark .tabs ul.is-right{justify-content:flex-end;padding-left:0.75em}html.theme--documenter-dark .tabs .icon:first-child{margin-right:.5em}html.theme--documenter-dark .tabs .icon:last-child{margin-left:.5em}html.theme--documenter-dark .tabs.is-centered ul{justify-content:center}html.theme--documenter-dark .tabs.is-right ul{justify-content:flex-end}html.theme--documenter-dark .tabs.is-boxed a{border:1px solid transparent;border-radius:.4em .4em 0 0}html.theme--documenter-dark .tabs.is-boxed a:hover{background-color:#282f2f;border-bottom-color:#5e6d6f}html.theme--documenter-dark .tabs.is-boxed li.is-active a{background-color:#fff;border-color:#5e6d6f;border-bottom-color:rgba(0,0,0,0) !important}html.theme--documenter-dark .tabs.is-fullwidth li{flex-grow:1;flex-shrink:0}html.theme--documenter-dark .tabs.is-toggle a{border-color:#5e6d6f;border-style:solid;border-width:1px;margin-bottom:0;position:relative}html.theme--documenter-dark .tabs.is-toggle a:hover{background-color:#282f2f;border-color:#8c9b9d;z-index:2}html.theme--documenter-dark .tabs.is-toggle li+li{margin-left:-1px}html.theme--documenter-dark .tabs.is-toggle li:first-child a{border-top-left-radius:.4em;border-bottom-left-radius:.4em}html.theme--documenter-dark .tabs.is-toggle li:last-child a{border-top-right-radius:.4em;border-bottom-right-radius:.4em}html.theme--documenter-dark .tabs.is-toggle li.is-active a{background-color:#1abc9c;border-color:#1abc9c;color:#fff;z-index:1}html.theme--documenter-dark .tabs.is-toggle ul{border-bottom:none}html.theme--documenter-dark .tabs.is-toggle.is-toggle-rounded li:first-child a{border-bottom-left-radius:9999px;border-top-left-radius:9999px;padding-left:1.25em}html.theme--documenter-dark .tabs.is-toggle.is-toggle-rounded li:last-child a{border-bottom-right-radius:9999px;border-top-right-radius:9999px;padding-right:1.25em}html.theme--documenter-dark .tabs.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.tabs{font-size:.75rem}html.theme--documenter-dark .tabs.is-medium{font-size:1.25rem}html.theme--documenter-dark .tabs.is-large{font-size:1.5rem}html.theme--documenter-dark .column{display:block;flex-basis:0;flex-grow:1;flex-shrink:1;padding:.75rem}.columns.is-mobile>html.theme--documenter-dark .column.is-narrow{flex:none;width:unset}.columns.is-mobile>html.theme--documenter-dark .column.is-full{flex:none;width:100%}.columns.is-mobile>html.theme--documenter-dark .column.is-three-quarters{flex:none;width:75%}.columns.is-mobile>html.theme--documenter-dark .column.is-two-thirds{flex:none;width:66.6666%}.columns.is-mobile>html.theme--documenter-dark .column.is-half{flex:none;width:50%}.columns.is-mobile>html.theme--documenter-dark .column.is-one-third{flex:none;width:33.3333%}.columns.is-mobile>html.theme--documenter-dark .column.is-one-quarter{flex:none;width:25%}.columns.is-mobile>html.theme--documenter-dark .column.is-one-fifth{flex:none;width:20%}.columns.is-mobile>html.theme--documenter-dark .column.is-two-fifths{flex:none;width:40%}.columns.is-mobile>html.theme--documenter-dark .column.is-three-fifths{flex:none;width:60%}.columns.is-mobile>html.theme--documenter-dark .column.is-four-fifths{flex:none;width:80%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-three-quarters{margin-left:75%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-two-thirds{margin-left:66.6666%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-half{margin-left:50%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-one-third{margin-left:33.3333%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-one-quarter{margin-left:25%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-one-fifth{margin-left:20%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-two-fifths{margin-left:40%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-three-fifths{margin-left:60%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-four-fifths{margin-left:80%}.columns.is-mobile>html.theme--documenter-dark .column.is-0{flex:none;width:0%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-0{margin-left:0%}.columns.is-mobile>html.theme--documenter-dark .column.is-1{flex:none;width:8.33333337%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-1{margin-left:8.33333337%}.columns.is-mobile>html.theme--documenter-dark .column.is-2{flex:none;width:16.66666674%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-2{margin-left:16.66666674%}.columns.is-mobile>html.theme--documenter-dark .column.is-3{flex:none;width:25%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-3{margin-left:25%}.columns.is-mobile>html.theme--documenter-dark .column.is-4{flex:none;width:33.33333337%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-4{margin-left:33.33333337%}.columns.is-mobile>html.theme--documenter-dark .column.is-5{flex:none;width:41.66666674%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-5{margin-left:41.66666674%}.columns.is-mobile>html.theme--documenter-dark .column.is-6{flex:none;width:50%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-6{margin-left:50%}.columns.is-mobile>html.theme--documenter-dark .column.is-7{flex:none;width:58.33333337%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-7{margin-left:58.33333337%}.columns.is-mobile>html.theme--documenter-dark .column.is-8{flex:none;width:66.66666674%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-8{margin-left:66.66666674%}.columns.is-mobile>html.theme--documenter-dark .column.is-9{flex:none;width:75%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-9{margin-left:75%}.columns.is-mobile>html.theme--documenter-dark .column.is-10{flex:none;width:83.33333337%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-10{margin-left:83.33333337%}.columns.is-mobile>html.theme--documenter-dark .column.is-11{flex:none;width:91.66666674%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-11{margin-left:91.66666674%}.columns.is-mobile>html.theme--documenter-dark .column.is-12{flex:none;width:100%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-12{margin-left:100%}@media screen and (max-width: 768px){html.theme--documenter-dark .column.is-narrow-mobile{flex:none;width:unset}html.theme--documenter-dark .column.is-full-mobile{flex:none;width:100%}html.theme--documenter-dark .column.is-three-quarters-mobile{flex:none;width:75%}html.theme--documenter-dark .column.is-two-thirds-mobile{flex:none;width:66.6666%}html.theme--documenter-dark .column.is-half-mobile{flex:none;width:50%}html.theme--documenter-dark .column.is-one-third-mobile{flex:none;width:33.3333%}html.theme--documenter-dark .column.is-one-quarter-mobile{flex:none;width:25%}html.theme--documenter-dark .column.is-one-fifth-mobile{flex:none;width:20%}html.theme--documenter-dark .column.is-two-fifths-mobile{flex:none;width:40%}html.theme--documenter-dark .column.is-three-fifths-mobile{flex:none;width:60%}html.theme--documenter-dark .column.is-four-fifths-mobile{flex:none;width:80%}html.theme--documenter-dark .column.is-offset-three-quarters-mobile{margin-left:75%}html.theme--documenter-dark .column.is-offset-two-thirds-mobile{margin-left:66.6666%}html.theme--documenter-dark .column.is-offset-half-mobile{margin-left:50%}html.theme--documenter-dark .column.is-offset-one-third-mobile{margin-left:33.3333%}html.theme--documenter-dark .column.is-offset-one-quarter-mobile{margin-left:25%}html.theme--documenter-dark .column.is-offset-one-fifth-mobile{margin-left:20%}html.theme--documenter-dark .column.is-offset-two-fifths-mobile{margin-left:40%}html.theme--documenter-dark .column.is-offset-three-fifths-mobile{margin-left:60%}html.theme--documenter-dark .column.is-offset-four-fifths-mobile{margin-left:80%}html.theme--documenter-dark .column.is-0-mobile{flex:none;width:0%}html.theme--documenter-dark .column.is-offset-0-mobile{margin-left:0%}html.theme--documenter-dark .column.is-1-mobile{flex:none;width:8.33333337%}html.theme--documenter-dark .column.is-offset-1-mobile{margin-left:8.33333337%}html.theme--documenter-dark .column.is-2-mobile{flex:none;width:16.66666674%}html.theme--documenter-dark .column.is-offset-2-mobile{margin-left:16.66666674%}html.theme--documenter-dark .column.is-3-mobile{flex:none;width:25%}html.theme--documenter-dark .column.is-offset-3-mobile{margin-left:25%}html.theme--documenter-dark .column.is-4-mobile{flex:none;width:33.33333337%}html.theme--documenter-dark .column.is-offset-4-mobile{margin-left:33.33333337%}html.theme--documenter-dark .column.is-5-mobile{flex:none;width:41.66666674%}html.theme--documenter-dark .column.is-offset-5-mobile{margin-left:41.66666674%}html.theme--documenter-dark .column.is-6-mobile{flex:none;width:50%}html.theme--documenter-dark .column.is-offset-6-mobile{margin-left:50%}html.theme--documenter-dark .column.is-7-mobile{flex:none;width:58.33333337%}html.theme--documenter-dark .column.is-offset-7-mobile{margin-left:58.33333337%}html.theme--documenter-dark .column.is-8-mobile{flex:none;width:66.66666674%}html.theme--documenter-dark .column.is-offset-8-mobile{margin-left:66.66666674%}html.theme--documenter-dark .column.is-9-mobile{flex:none;width:75%}html.theme--documenter-dark .column.is-offset-9-mobile{margin-left:75%}html.theme--documenter-dark .column.is-10-mobile{flex:none;width:83.33333337%}html.theme--documenter-dark .column.is-offset-10-mobile{margin-left:83.33333337%}html.theme--documenter-dark .column.is-11-mobile{flex:none;width:91.66666674%}html.theme--documenter-dark .column.is-offset-11-mobile{margin-left:91.66666674%}html.theme--documenter-dark .column.is-12-mobile{flex:none;width:100%}html.theme--documenter-dark .column.is-offset-12-mobile{margin-left:100%}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .column.is-narrow,html.theme--documenter-dark .column.is-narrow-tablet{flex:none;width:unset}html.theme--documenter-dark .column.is-full,html.theme--documenter-dark .column.is-full-tablet{flex:none;width:100%}html.theme--documenter-dark .column.is-three-quarters,html.theme--documenter-dark .column.is-three-quarters-tablet{flex:none;width:75%}html.theme--documenter-dark .column.is-two-thirds,html.theme--documenter-dark .column.is-two-thirds-tablet{flex:none;width:66.6666%}html.theme--documenter-dark .column.is-half,html.theme--documenter-dark .column.is-half-tablet{flex:none;width:50%}html.theme--documenter-dark .column.is-one-third,html.theme--documenter-dark .column.is-one-third-tablet{flex:none;width:33.3333%}html.theme--documenter-dark .column.is-one-quarter,html.theme--documenter-dark .column.is-one-quarter-tablet{flex:none;width:25%}html.theme--documenter-dark .column.is-one-fifth,html.theme--documenter-dark .column.is-one-fifth-tablet{flex:none;width:20%}html.theme--documenter-dark .column.is-two-fifths,html.theme--documenter-dark .column.is-two-fifths-tablet{flex:none;width:40%}html.theme--documenter-dark .column.is-three-fifths,html.theme--documenter-dark .column.is-three-fifths-tablet{flex:none;width:60%}html.theme--documenter-dark .column.is-four-fifths,html.theme--documenter-dark .column.is-four-fifths-tablet{flex:none;width:80%}html.theme--documenter-dark .column.is-offset-three-quarters,html.theme--documenter-dark .column.is-offset-three-quarters-tablet{margin-left:75%}html.theme--documenter-dark .column.is-offset-two-thirds,html.theme--documenter-dark .column.is-offset-two-thirds-tablet{margin-left:66.6666%}html.theme--documenter-dark .column.is-offset-half,html.theme--documenter-dark .column.is-offset-half-tablet{margin-left:50%}html.theme--documenter-dark .column.is-offset-one-third,html.theme--documenter-dark .column.is-offset-one-third-tablet{margin-left:33.3333%}html.theme--documenter-dark .column.is-offset-one-quarter,html.theme--documenter-dark .column.is-offset-one-quarter-tablet{margin-left:25%}html.theme--documenter-dark .column.is-offset-one-fifth,html.theme--documenter-dark .column.is-offset-one-fifth-tablet{margin-left:20%}html.theme--documenter-dark .column.is-offset-two-fifths,html.theme--documenter-dark .column.is-offset-two-fifths-tablet{margin-left:40%}html.theme--documenter-dark .column.is-offset-three-fifths,html.theme--documenter-dark .column.is-offset-three-fifths-tablet{margin-left:60%}html.theme--documenter-dark .column.is-offset-four-fifths,html.theme--documenter-dark .column.is-offset-four-fifths-tablet{margin-left:80%}html.theme--documenter-dark .column.is-0,html.theme--documenter-dark .column.is-0-tablet{flex:none;width:0%}html.theme--documenter-dark .column.is-offset-0,html.theme--documenter-dark .column.is-offset-0-tablet{margin-left:0%}html.theme--documenter-dark .column.is-1,html.theme--documenter-dark .column.is-1-tablet{flex:none;width:8.33333337%}html.theme--documenter-dark .column.is-offset-1,html.theme--documenter-dark .column.is-offset-1-tablet{margin-left:8.33333337%}html.theme--documenter-dark .column.is-2,html.theme--documenter-dark .column.is-2-tablet{flex:none;width:16.66666674%}html.theme--documenter-dark .column.is-offset-2,html.theme--documenter-dark .column.is-offset-2-tablet{margin-left:16.66666674%}html.theme--documenter-dark .column.is-3,html.theme--documenter-dark .column.is-3-tablet{flex:none;width:25%}html.theme--documenter-dark .column.is-offset-3,html.theme--documenter-dark .column.is-offset-3-tablet{margin-left:25%}html.theme--documenter-dark .column.is-4,html.theme--documenter-dark .column.is-4-tablet{flex:none;width:33.33333337%}html.theme--documenter-dark .column.is-offset-4,html.theme--documenter-dark .column.is-offset-4-tablet{margin-left:33.33333337%}html.theme--documenter-dark .column.is-5,html.theme--documenter-dark .column.is-5-tablet{flex:none;width:41.66666674%}html.theme--documenter-dark .column.is-offset-5,html.theme--documenter-dark .column.is-offset-5-tablet{margin-left:41.66666674%}html.theme--documenter-dark .column.is-6,html.theme--documenter-dark .column.is-6-tablet{flex:none;width:50%}html.theme--documenter-dark .column.is-offset-6,html.theme--documenter-dark .column.is-offset-6-tablet{margin-left:50%}html.theme--documenter-dark .column.is-7,html.theme--documenter-dark .column.is-7-tablet{flex:none;width:58.33333337%}html.theme--documenter-dark .column.is-offset-7,html.theme--documenter-dark .column.is-offset-7-tablet{margin-left:58.33333337%}html.theme--documenter-dark .column.is-8,html.theme--documenter-dark .column.is-8-tablet{flex:none;width:66.66666674%}html.theme--documenter-dark .column.is-offset-8,html.theme--documenter-dark .column.is-offset-8-tablet{margin-left:66.66666674%}html.theme--documenter-dark .column.is-9,html.theme--documenter-dark .column.is-9-tablet{flex:none;width:75%}html.theme--documenter-dark .column.is-offset-9,html.theme--documenter-dark .column.is-offset-9-tablet{margin-left:75%}html.theme--documenter-dark .column.is-10,html.theme--documenter-dark .column.is-10-tablet{flex:none;width:83.33333337%}html.theme--documenter-dark .column.is-offset-10,html.theme--documenter-dark .column.is-offset-10-tablet{margin-left:83.33333337%}html.theme--documenter-dark .column.is-11,html.theme--documenter-dark .column.is-11-tablet{flex:none;width:91.66666674%}html.theme--documenter-dark .column.is-offset-11,html.theme--documenter-dark .column.is-offset-11-tablet{margin-left:91.66666674%}html.theme--documenter-dark .column.is-12,html.theme--documenter-dark .column.is-12-tablet{flex:none;width:100%}html.theme--documenter-dark .column.is-offset-12,html.theme--documenter-dark .column.is-offset-12-tablet{margin-left:100%}}@media screen and (max-width: 1055px){html.theme--documenter-dark .column.is-narrow-touch{flex:none;width:unset}html.theme--documenter-dark .column.is-full-touch{flex:none;width:100%}html.theme--documenter-dark .column.is-three-quarters-touch{flex:none;width:75%}html.theme--documenter-dark .column.is-two-thirds-touch{flex:none;width:66.6666%}html.theme--documenter-dark .column.is-half-touch{flex:none;width:50%}html.theme--documenter-dark .column.is-one-third-touch{flex:none;width:33.3333%}html.theme--documenter-dark .column.is-one-quarter-touch{flex:none;width:25%}html.theme--documenter-dark .column.is-one-fifth-touch{flex:none;width:20%}html.theme--documenter-dark .column.is-two-fifths-touch{flex:none;width:40%}html.theme--documenter-dark .column.is-three-fifths-touch{flex:none;width:60%}html.theme--documenter-dark .column.is-four-fifths-touch{flex:none;width:80%}html.theme--documenter-dark .column.is-offset-three-quarters-touch{margin-left:75%}html.theme--documenter-dark .column.is-offset-two-thirds-touch{margin-left:66.6666%}html.theme--documenter-dark .column.is-offset-half-touch{margin-left:50%}html.theme--documenter-dark .column.is-offset-one-third-touch{margin-left:33.3333%}html.theme--documenter-dark .column.is-offset-one-quarter-touch{margin-left:25%}html.theme--documenter-dark .column.is-offset-one-fifth-touch{margin-left:20%}html.theme--documenter-dark .column.is-offset-two-fifths-touch{margin-left:40%}html.theme--documenter-dark .column.is-offset-three-fifths-touch{margin-left:60%}html.theme--documenter-dark .column.is-offset-four-fifths-touch{margin-left:80%}html.theme--documenter-dark .column.is-0-touch{flex:none;width:0%}html.theme--documenter-dark .column.is-offset-0-touch{margin-left:0%}html.theme--documenter-dark .column.is-1-touch{flex:none;width:8.33333337%}html.theme--documenter-dark .column.is-offset-1-touch{margin-left:8.33333337%}html.theme--documenter-dark .column.is-2-touch{flex:none;width:16.66666674%}html.theme--documenter-dark .column.is-offset-2-touch{margin-left:16.66666674%}html.theme--documenter-dark .column.is-3-touch{flex:none;width:25%}html.theme--documenter-dark .column.is-offset-3-touch{margin-left:25%}html.theme--documenter-dark .column.is-4-touch{flex:none;width:33.33333337%}html.theme--documenter-dark .column.is-offset-4-touch{margin-left:33.33333337%}html.theme--documenter-dark .column.is-5-touch{flex:none;width:41.66666674%}html.theme--documenter-dark .column.is-offset-5-touch{margin-left:41.66666674%}html.theme--documenter-dark .column.is-6-touch{flex:none;width:50%}html.theme--documenter-dark .column.is-offset-6-touch{margin-left:50%}html.theme--documenter-dark .column.is-7-touch{flex:none;width:58.33333337%}html.theme--documenter-dark .column.is-offset-7-touch{margin-left:58.33333337%}html.theme--documenter-dark .column.is-8-touch{flex:none;width:66.66666674%}html.theme--documenter-dark .column.is-offset-8-touch{margin-left:66.66666674%}html.theme--documenter-dark .column.is-9-touch{flex:none;width:75%}html.theme--documenter-dark .column.is-offset-9-touch{margin-left:75%}html.theme--documenter-dark .column.is-10-touch{flex:none;width:83.33333337%}html.theme--documenter-dark .column.is-offset-10-touch{margin-left:83.33333337%}html.theme--documenter-dark .column.is-11-touch{flex:none;width:91.66666674%}html.theme--documenter-dark .column.is-offset-11-touch{margin-left:91.66666674%}html.theme--documenter-dark .column.is-12-touch{flex:none;width:100%}html.theme--documenter-dark .column.is-offset-12-touch{margin-left:100%}}@media screen and (min-width: 1056px){html.theme--documenter-dark .column.is-narrow-desktop{flex:none;width:unset}html.theme--documenter-dark .column.is-full-desktop{flex:none;width:100%}html.theme--documenter-dark .column.is-three-quarters-desktop{flex:none;width:75%}html.theme--documenter-dark .column.is-two-thirds-desktop{flex:none;width:66.6666%}html.theme--documenter-dark .column.is-half-desktop{flex:none;width:50%}html.theme--documenter-dark .column.is-one-third-desktop{flex:none;width:33.3333%}html.theme--documenter-dark .column.is-one-quarter-desktop{flex:none;width:25%}html.theme--documenter-dark .column.is-one-fifth-desktop{flex:none;width:20%}html.theme--documenter-dark .column.is-two-fifths-desktop{flex:none;width:40%}html.theme--documenter-dark .column.is-three-fifths-desktop{flex:none;width:60%}html.theme--documenter-dark .column.is-four-fifths-desktop{flex:none;width:80%}html.theme--documenter-dark .column.is-offset-three-quarters-desktop{margin-left:75%}html.theme--documenter-dark .column.is-offset-two-thirds-desktop{margin-left:66.6666%}html.theme--documenter-dark .column.is-offset-half-desktop{margin-left:50%}html.theme--documenter-dark .column.is-offset-one-third-desktop{margin-left:33.3333%}html.theme--documenter-dark .column.is-offset-one-quarter-desktop{margin-left:25%}html.theme--documenter-dark .column.is-offset-one-fifth-desktop{margin-left:20%}html.theme--documenter-dark .column.is-offset-two-fifths-desktop{margin-left:40%}html.theme--documenter-dark .column.is-offset-three-fifths-desktop{margin-left:60%}html.theme--documenter-dark .column.is-offset-four-fifths-desktop{margin-left:80%}html.theme--documenter-dark .column.is-0-desktop{flex:none;width:0%}html.theme--documenter-dark .column.is-offset-0-desktop{margin-left:0%}html.theme--documenter-dark .column.is-1-desktop{flex:none;width:8.33333337%}html.theme--documenter-dark .column.is-offset-1-desktop{margin-left:8.33333337%}html.theme--documenter-dark .column.is-2-desktop{flex:none;width:16.66666674%}html.theme--documenter-dark .column.is-offset-2-desktop{margin-left:16.66666674%}html.theme--documenter-dark .column.is-3-desktop{flex:none;width:25%}html.theme--documenter-dark .column.is-offset-3-desktop{margin-left:25%}html.theme--documenter-dark .column.is-4-desktop{flex:none;width:33.33333337%}html.theme--documenter-dark .column.is-offset-4-desktop{margin-left:33.33333337%}html.theme--documenter-dark .column.is-5-desktop{flex:none;width:41.66666674%}html.theme--documenter-dark .column.is-offset-5-desktop{margin-left:41.66666674%}html.theme--documenter-dark .column.is-6-desktop{flex:none;width:50%}html.theme--documenter-dark .column.is-offset-6-desktop{margin-left:50%}html.theme--documenter-dark .column.is-7-desktop{flex:none;width:58.33333337%}html.theme--documenter-dark .column.is-offset-7-desktop{margin-left:58.33333337%}html.theme--documenter-dark .column.is-8-desktop{flex:none;width:66.66666674%}html.theme--documenter-dark .column.is-offset-8-desktop{margin-left:66.66666674%}html.theme--documenter-dark .column.is-9-desktop{flex:none;width:75%}html.theme--documenter-dark .column.is-offset-9-desktop{margin-left:75%}html.theme--documenter-dark .column.is-10-desktop{flex:none;width:83.33333337%}html.theme--documenter-dark .column.is-offset-10-desktop{margin-left:83.33333337%}html.theme--documenter-dark .column.is-11-desktop{flex:none;width:91.66666674%}html.theme--documenter-dark .column.is-offset-11-desktop{margin-left:91.66666674%}html.theme--documenter-dark .column.is-12-desktop{flex:none;width:100%}html.theme--documenter-dark .column.is-offset-12-desktop{margin-left:100%}}@media screen and (min-width: 1216px){html.theme--documenter-dark .column.is-narrow-widescreen{flex:none;width:unset}html.theme--documenter-dark .column.is-full-widescreen{flex:none;width:100%}html.theme--documenter-dark .column.is-three-quarters-widescreen{flex:none;width:75%}html.theme--documenter-dark .column.is-two-thirds-widescreen{flex:none;width:66.6666%}html.theme--documenter-dark .column.is-half-widescreen{flex:none;width:50%}html.theme--documenter-dark .column.is-one-third-widescreen{flex:none;width:33.3333%}html.theme--documenter-dark .column.is-one-quarter-widescreen{flex:none;width:25%}html.theme--documenter-dark .column.is-one-fifth-widescreen{flex:none;width:20%}html.theme--documenter-dark .column.is-two-fifths-widescreen{flex:none;width:40%}html.theme--documenter-dark .column.is-three-fifths-widescreen{flex:none;width:60%}html.theme--documenter-dark .column.is-four-fifths-widescreen{flex:none;width:80%}html.theme--documenter-dark .column.is-offset-three-quarters-widescreen{margin-left:75%}html.theme--documenter-dark .column.is-offset-two-thirds-widescreen{margin-left:66.6666%}html.theme--documenter-dark .column.is-offset-half-widescreen{margin-left:50%}html.theme--documenter-dark .column.is-offset-one-third-widescreen{margin-left:33.3333%}html.theme--documenter-dark .column.is-offset-one-quarter-widescreen{margin-left:25%}html.theme--documenter-dark .column.is-offset-one-fifth-widescreen{margin-left:20%}html.theme--documenter-dark .column.is-offset-two-fifths-widescreen{margin-left:40%}html.theme--documenter-dark .column.is-offset-three-fifths-widescreen{margin-left:60%}html.theme--documenter-dark .column.is-offset-four-fifths-widescreen{margin-left:80%}html.theme--documenter-dark .column.is-0-widescreen{flex:none;width:0%}html.theme--documenter-dark .column.is-offset-0-widescreen{margin-left:0%}html.theme--documenter-dark .column.is-1-widescreen{flex:none;width:8.33333337%}html.theme--documenter-dark .column.is-offset-1-widescreen{margin-left:8.33333337%}html.theme--documenter-dark .column.is-2-widescreen{flex:none;width:16.66666674%}html.theme--documenter-dark .column.is-offset-2-widescreen{margin-left:16.66666674%}html.theme--documenter-dark .column.is-3-widescreen{flex:none;width:25%}html.theme--documenter-dark .column.is-offset-3-widescreen{margin-left:25%}html.theme--documenter-dark .column.is-4-widescreen{flex:none;width:33.33333337%}html.theme--documenter-dark .column.is-offset-4-widescreen{margin-left:33.33333337%}html.theme--documenter-dark .column.is-5-widescreen{flex:none;width:41.66666674%}html.theme--documenter-dark .column.is-offset-5-widescreen{margin-left:41.66666674%}html.theme--documenter-dark .column.is-6-widescreen{flex:none;width:50%}html.theme--documenter-dark .column.is-offset-6-widescreen{margin-left:50%}html.theme--documenter-dark .column.is-7-widescreen{flex:none;width:58.33333337%}html.theme--documenter-dark .column.is-offset-7-widescreen{margin-left:58.33333337%}html.theme--documenter-dark .column.is-8-widescreen{flex:none;width:66.66666674%}html.theme--documenter-dark .column.is-offset-8-widescreen{margin-left:66.66666674%}html.theme--documenter-dark .column.is-9-widescreen{flex:none;width:75%}html.theme--documenter-dark .column.is-offset-9-widescreen{margin-left:75%}html.theme--documenter-dark .column.is-10-widescreen{flex:none;width:83.33333337%}html.theme--documenter-dark .column.is-offset-10-widescreen{margin-left:83.33333337%}html.theme--documenter-dark .column.is-11-widescreen{flex:none;width:91.66666674%}html.theme--documenter-dark .column.is-offset-11-widescreen{margin-left:91.66666674%}html.theme--documenter-dark .column.is-12-widescreen{flex:none;width:100%}html.theme--documenter-dark .column.is-offset-12-widescreen{margin-left:100%}}@media screen and (min-width: 1408px){html.theme--documenter-dark .column.is-narrow-fullhd{flex:none;width:unset}html.theme--documenter-dark .column.is-full-fullhd{flex:none;width:100%}html.theme--documenter-dark .column.is-three-quarters-fullhd{flex:none;width:75%}html.theme--documenter-dark .column.is-two-thirds-fullhd{flex:none;width:66.6666%}html.theme--documenter-dark .column.is-half-fullhd{flex:none;width:50%}html.theme--documenter-dark .column.is-one-third-fullhd{flex:none;width:33.3333%}html.theme--documenter-dark .column.is-one-quarter-fullhd{flex:none;width:25%}html.theme--documenter-dark .column.is-one-fifth-fullhd{flex:none;width:20%}html.theme--documenter-dark .column.is-two-fifths-fullhd{flex:none;width:40%}html.theme--documenter-dark .column.is-three-fifths-fullhd{flex:none;width:60%}html.theme--documenter-dark .column.is-four-fifths-fullhd{flex:none;width:80%}html.theme--documenter-dark .column.is-offset-three-quarters-fullhd{margin-left:75%}html.theme--documenter-dark .column.is-offset-two-thirds-fullhd{margin-left:66.6666%}html.theme--documenter-dark .column.is-offset-half-fullhd{margin-left:50%}html.theme--documenter-dark .column.is-offset-one-third-fullhd{margin-left:33.3333%}html.theme--documenter-dark .column.is-offset-one-quarter-fullhd{margin-left:25%}html.theme--documenter-dark .column.is-offset-one-fifth-fullhd{margin-left:20%}html.theme--documenter-dark .column.is-offset-two-fifths-fullhd{margin-left:40%}html.theme--documenter-dark .column.is-offset-three-fifths-fullhd{margin-left:60%}html.theme--documenter-dark .column.is-offset-four-fifths-fullhd{margin-left:80%}html.theme--documenter-dark .column.is-0-fullhd{flex:none;width:0%}html.theme--documenter-dark .column.is-offset-0-fullhd{margin-left:0%}html.theme--documenter-dark .column.is-1-fullhd{flex:none;width:8.33333337%}html.theme--documenter-dark .column.is-offset-1-fullhd{margin-left:8.33333337%}html.theme--documenter-dark .column.is-2-fullhd{flex:none;width:16.66666674%}html.theme--documenter-dark .column.is-offset-2-fullhd{margin-left:16.66666674%}html.theme--documenter-dark .column.is-3-fullhd{flex:none;width:25%}html.theme--documenter-dark .column.is-offset-3-fullhd{margin-left:25%}html.theme--documenter-dark .column.is-4-fullhd{flex:none;width:33.33333337%}html.theme--documenter-dark .column.is-offset-4-fullhd{margin-left:33.33333337%}html.theme--documenter-dark .column.is-5-fullhd{flex:none;width:41.66666674%}html.theme--documenter-dark .column.is-offset-5-fullhd{margin-left:41.66666674%}html.theme--documenter-dark .column.is-6-fullhd{flex:none;width:50%}html.theme--documenter-dark .column.is-offset-6-fullhd{margin-left:50%}html.theme--documenter-dark .column.is-7-fullhd{flex:none;width:58.33333337%}html.theme--documenter-dark .column.is-offset-7-fullhd{margin-left:58.33333337%}html.theme--documenter-dark .column.is-8-fullhd{flex:none;width:66.66666674%}html.theme--documenter-dark .column.is-offset-8-fullhd{margin-left:66.66666674%}html.theme--documenter-dark .column.is-9-fullhd{flex:none;width:75%}html.theme--documenter-dark .column.is-offset-9-fullhd{margin-left:75%}html.theme--documenter-dark .column.is-10-fullhd{flex:none;width:83.33333337%}html.theme--documenter-dark .column.is-offset-10-fullhd{margin-left:83.33333337%}html.theme--documenter-dark .column.is-11-fullhd{flex:none;width:91.66666674%}html.theme--documenter-dark .column.is-offset-11-fullhd{margin-left:91.66666674%}html.theme--documenter-dark .column.is-12-fullhd{flex:none;width:100%}html.theme--documenter-dark .column.is-offset-12-fullhd{margin-left:100%}}html.theme--documenter-dark .columns{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}html.theme--documenter-dark .columns:last-child{margin-bottom:-.75rem}html.theme--documenter-dark .columns:not(:last-child){margin-bottom:calc(1.5rem - .75rem)}html.theme--documenter-dark .columns.is-centered{justify-content:center}html.theme--documenter-dark .columns.is-gapless{margin-left:0;margin-right:0;margin-top:0}html.theme--documenter-dark .columns.is-gapless>.column{margin:0;padding:0 !important}html.theme--documenter-dark .columns.is-gapless:not(:last-child){margin-bottom:1.5rem}html.theme--documenter-dark .columns.is-gapless:last-child{margin-bottom:0}html.theme--documenter-dark .columns.is-mobile{display:flex}html.theme--documenter-dark .columns.is-multiline{flex-wrap:wrap}html.theme--documenter-dark .columns.is-vcentered{align-items:center}@media screen and (min-width: 769px),print{html.theme--documenter-dark .columns:not(.is-desktop){display:flex}}@media screen and (min-width: 1056px){html.theme--documenter-dark .columns.is-desktop{display:flex}}html.theme--documenter-dark .columns.is-variable{--columnGap: 0.75rem;margin-left:calc(-1 * var(--columnGap));margin-right:calc(-1 * var(--columnGap))}html.theme--documenter-dark .columns.is-variable>.column{padding-left:var(--columnGap);padding-right:var(--columnGap)}html.theme--documenter-dark .columns.is-variable.is-0{--columnGap: 0rem}@media screen and (max-width: 768px){html.theme--documenter-dark .columns.is-variable.is-0-mobile{--columnGap: 0rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .columns.is-variable.is-0-tablet{--columnGap: 0rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-0-tablet-only{--columnGap: 0rem}}@media screen and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-0-touch{--columnGap: 0rem}}@media screen and (min-width: 1056px){html.theme--documenter-dark .columns.is-variable.is-0-desktop{--columnGap: 0rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--documenter-dark .columns.is-variable.is-0-desktop-only{--columnGap: 0rem}}@media screen and (min-width: 1216px){html.theme--documenter-dark .columns.is-variable.is-0-widescreen{--columnGap: 0rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--documenter-dark .columns.is-variable.is-0-widescreen-only{--columnGap: 0rem}}@media screen and (min-width: 1408px){html.theme--documenter-dark .columns.is-variable.is-0-fullhd{--columnGap: 0rem}}html.theme--documenter-dark .columns.is-variable.is-1{--columnGap: .25rem}@media screen and (max-width: 768px){html.theme--documenter-dark .columns.is-variable.is-1-mobile{--columnGap: .25rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .columns.is-variable.is-1-tablet{--columnGap: .25rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-1-tablet-only{--columnGap: .25rem}}@media screen and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-1-touch{--columnGap: .25rem}}@media screen and (min-width: 1056px){html.theme--documenter-dark .columns.is-variable.is-1-desktop{--columnGap: .25rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--documenter-dark .columns.is-variable.is-1-desktop-only{--columnGap: .25rem}}@media screen and (min-width: 1216px){html.theme--documenter-dark .columns.is-variable.is-1-widescreen{--columnGap: .25rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--documenter-dark .columns.is-variable.is-1-widescreen-only{--columnGap: .25rem}}@media screen and (min-width: 1408px){html.theme--documenter-dark .columns.is-variable.is-1-fullhd{--columnGap: .25rem}}html.theme--documenter-dark .columns.is-variable.is-2{--columnGap: .5rem}@media screen and (max-width: 768px){html.theme--documenter-dark .columns.is-variable.is-2-mobile{--columnGap: .5rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .columns.is-variable.is-2-tablet{--columnGap: .5rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-2-tablet-only{--columnGap: .5rem}}@media screen and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-2-touch{--columnGap: .5rem}}@media screen and (min-width: 1056px){html.theme--documenter-dark .columns.is-variable.is-2-desktop{--columnGap: .5rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--documenter-dark .columns.is-variable.is-2-desktop-only{--columnGap: .5rem}}@media screen and (min-width: 1216px){html.theme--documenter-dark .columns.is-variable.is-2-widescreen{--columnGap: .5rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--documenter-dark .columns.is-variable.is-2-widescreen-only{--columnGap: .5rem}}@media screen and (min-width: 1408px){html.theme--documenter-dark .columns.is-variable.is-2-fullhd{--columnGap: .5rem}}html.theme--documenter-dark .columns.is-variable.is-3{--columnGap: .75rem}@media screen and (max-width: 768px){html.theme--documenter-dark .columns.is-variable.is-3-mobile{--columnGap: .75rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .columns.is-variable.is-3-tablet{--columnGap: .75rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-3-tablet-only{--columnGap: .75rem}}@media screen and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-3-touch{--columnGap: .75rem}}@media screen and (min-width: 1056px){html.theme--documenter-dark .columns.is-variable.is-3-desktop{--columnGap: .75rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--documenter-dark .columns.is-variable.is-3-desktop-only{--columnGap: .75rem}}@media screen and (min-width: 1216px){html.theme--documenter-dark .columns.is-variable.is-3-widescreen{--columnGap: .75rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--documenter-dark .columns.is-variable.is-3-widescreen-only{--columnGap: .75rem}}@media screen and (min-width: 1408px){html.theme--documenter-dark .columns.is-variable.is-3-fullhd{--columnGap: .75rem}}html.theme--documenter-dark .columns.is-variable.is-4{--columnGap: 1rem}@media screen and (max-width: 768px){html.theme--documenter-dark .columns.is-variable.is-4-mobile{--columnGap: 1rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .columns.is-variable.is-4-tablet{--columnGap: 1rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-4-tablet-only{--columnGap: 1rem}}@media screen and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-4-touch{--columnGap: 1rem}}@media screen and (min-width: 1056px){html.theme--documenter-dark .columns.is-variable.is-4-desktop{--columnGap: 1rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--documenter-dark .columns.is-variable.is-4-desktop-only{--columnGap: 1rem}}@media screen and (min-width: 1216px){html.theme--documenter-dark .columns.is-variable.is-4-widescreen{--columnGap: 1rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--documenter-dark .columns.is-variable.is-4-widescreen-only{--columnGap: 1rem}}@media screen and (min-width: 1408px){html.theme--documenter-dark .columns.is-variable.is-4-fullhd{--columnGap: 1rem}}html.theme--documenter-dark .columns.is-variable.is-5{--columnGap: 1.25rem}@media screen and (max-width: 768px){html.theme--documenter-dark .columns.is-variable.is-5-mobile{--columnGap: 1.25rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .columns.is-variable.is-5-tablet{--columnGap: 1.25rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-5-tablet-only{--columnGap: 1.25rem}}@media screen and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-5-touch{--columnGap: 1.25rem}}@media screen and (min-width: 1056px){html.theme--documenter-dark .columns.is-variable.is-5-desktop{--columnGap: 1.25rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--documenter-dark .columns.is-variable.is-5-desktop-only{--columnGap: 1.25rem}}@media screen and (min-width: 1216px){html.theme--documenter-dark .columns.is-variable.is-5-widescreen{--columnGap: 1.25rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--documenter-dark .columns.is-variable.is-5-widescreen-only{--columnGap: 1.25rem}}@media screen and (min-width: 1408px){html.theme--documenter-dark .columns.is-variable.is-5-fullhd{--columnGap: 1.25rem}}html.theme--documenter-dark .columns.is-variable.is-6{--columnGap: 1.5rem}@media screen and (max-width: 768px){html.theme--documenter-dark .columns.is-variable.is-6-mobile{--columnGap: 1.5rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .columns.is-variable.is-6-tablet{--columnGap: 1.5rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-6-tablet-only{--columnGap: 1.5rem}}@media screen and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-6-touch{--columnGap: 1.5rem}}@media screen and (min-width: 1056px){html.theme--documenter-dark .columns.is-variable.is-6-desktop{--columnGap: 1.5rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--documenter-dark .columns.is-variable.is-6-desktop-only{--columnGap: 1.5rem}}@media screen and (min-width: 1216px){html.theme--documenter-dark .columns.is-variable.is-6-widescreen{--columnGap: 1.5rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--documenter-dark .columns.is-variable.is-6-widescreen-only{--columnGap: 1.5rem}}@media screen and (min-width: 1408px){html.theme--documenter-dark .columns.is-variable.is-6-fullhd{--columnGap: 1.5rem}}html.theme--documenter-dark .columns.is-variable.is-7{--columnGap: 1.75rem}@media screen and (max-width: 768px){html.theme--documenter-dark .columns.is-variable.is-7-mobile{--columnGap: 1.75rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .columns.is-variable.is-7-tablet{--columnGap: 1.75rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-7-tablet-only{--columnGap: 1.75rem}}@media screen and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-7-touch{--columnGap: 1.75rem}}@media screen and (min-width: 1056px){html.theme--documenter-dark .columns.is-variable.is-7-desktop{--columnGap: 1.75rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--documenter-dark .columns.is-variable.is-7-desktop-only{--columnGap: 1.75rem}}@media screen and (min-width: 1216px){html.theme--documenter-dark .columns.is-variable.is-7-widescreen{--columnGap: 1.75rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--documenter-dark .columns.is-variable.is-7-widescreen-only{--columnGap: 1.75rem}}@media screen and (min-width: 1408px){html.theme--documenter-dark .columns.is-variable.is-7-fullhd{--columnGap: 1.75rem}}html.theme--documenter-dark .columns.is-variable.is-8{--columnGap: 2rem}@media screen and (max-width: 768px){html.theme--documenter-dark .columns.is-variable.is-8-mobile{--columnGap: 2rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .columns.is-variable.is-8-tablet{--columnGap: 2rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-8-tablet-only{--columnGap: 2rem}}@media screen and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-8-touch{--columnGap: 2rem}}@media screen and (min-width: 1056px){html.theme--documenter-dark .columns.is-variable.is-8-desktop{--columnGap: 2rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--documenter-dark .columns.is-variable.is-8-desktop-only{--columnGap: 2rem}}@media screen and (min-width: 1216px){html.theme--documenter-dark .columns.is-variable.is-8-widescreen{--columnGap: 2rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--documenter-dark .columns.is-variable.is-8-widescreen-only{--columnGap: 2rem}}@media screen and (min-width: 1408px){html.theme--documenter-dark .columns.is-variable.is-8-fullhd{--columnGap: 2rem}}html.theme--documenter-dark .tile{align-items:stretch;display:block;flex-basis:0;flex-grow:1;flex-shrink:1;min-height:min-content}html.theme--documenter-dark .tile.is-ancestor{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}html.theme--documenter-dark .tile.is-ancestor:last-child{margin-bottom:-.75rem}html.theme--documenter-dark .tile.is-ancestor:not(:last-child){margin-bottom:.75rem}html.theme--documenter-dark .tile.is-child{margin:0 !important}html.theme--documenter-dark .tile.is-parent{padding:.75rem}html.theme--documenter-dark .tile.is-vertical{flex-direction:column}html.theme--documenter-dark .tile.is-vertical>.tile.is-child:not(:last-child){margin-bottom:1.5rem !important}@media screen and (min-width: 769px),print{html.theme--documenter-dark .tile:not(.is-child){display:flex}html.theme--documenter-dark .tile.is-1{flex:none;width:8.33333337%}html.theme--documenter-dark .tile.is-2{flex:none;width:16.66666674%}html.theme--documenter-dark .tile.is-3{flex:none;width:25%}html.theme--documenter-dark .tile.is-4{flex:none;width:33.33333337%}html.theme--documenter-dark .tile.is-5{flex:none;width:41.66666674%}html.theme--documenter-dark .tile.is-6{flex:none;width:50%}html.theme--documenter-dark .tile.is-7{flex:none;width:58.33333337%}html.theme--documenter-dark .tile.is-8{flex:none;width:66.66666674%}html.theme--documenter-dark .tile.is-9{flex:none;width:75%}html.theme--documenter-dark .tile.is-10{flex:none;width:83.33333337%}html.theme--documenter-dark .tile.is-11{flex:none;width:91.66666674%}html.theme--documenter-dark .tile.is-12{flex:none;width:100%}}html.theme--documenter-dark .hero{align-items:stretch;display:flex;flex-direction:column;justify-content:space-between}html.theme--documenter-dark .hero .navbar{background:none}html.theme--documenter-dark .hero .tabs ul{border-bottom:none}html.theme--documenter-dark .hero.is-white{background-color:#fff;color:#0a0a0a}html.theme--documenter-dark .hero.is-white a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .hero.is-white strong{color:inherit}html.theme--documenter-dark .hero.is-white .title{color:#0a0a0a}html.theme--documenter-dark .hero.is-white .subtitle{color:rgba(10,10,10,0.9)}html.theme--documenter-dark .hero.is-white .subtitle a:not(.button),html.theme--documenter-dark .hero.is-white .subtitle strong{color:#0a0a0a}@media screen and (max-width: 1055px){html.theme--documenter-dark .hero.is-white .navbar-menu{background-color:#fff}}html.theme--documenter-dark .hero.is-white .navbar-item,html.theme--documenter-dark .hero.is-white .navbar-link{color:rgba(10,10,10,0.7)}html.theme--documenter-dark .hero.is-white a.navbar-item:hover,html.theme--documenter-dark .hero.is-white a.navbar-item.is-active,html.theme--documenter-dark .hero.is-white .navbar-link:hover,html.theme--documenter-dark .hero.is-white .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}html.theme--documenter-dark .hero.is-white .tabs a{color:#0a0a0a;opacity:0.9}html.theme--documenter-dark .hero.is-white .tabs a:hover{opacity:1}html.theme--documenter-dark .hero.is-white .tabs li.is-active a{color:#fff !important;opacity:1}html.theme--documenter-dark .hero.is-white .tabs.is-boxed a,html.theme--documenter-dark .hero.is-white .tabs.is-toggle a{color:#0a0a0a}html.theme--documenter-dark .hero.is-white .tabs.is-boxed a:hover,html.theme--documenter-dark .hero.is-white .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--documenter-dark .hero.is-white .tabs.is-boxed li.is-active a,html.theme--documenter-dark .hero.is-white .tabs.is-boxed li.is-active a:hover,html.theme--documenter-dark .hero.is-white .tabs.is-toggle li.is-active a,html.theme--documenter-dark .hero.is-white .tabs.is-toggle li.is-active a:hover{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}html.theme--documenter-dark .hero.is-white.is-bold{background-image:linear-gradient(141deg, #e8e3e4 0%, #fff 71%, #fff 100%)}@media screen and (max-width: 768px){html.theme--documenter-dark .hero.is-white.is-bold .navbar-menu{background-image:linear-gradient(141deg, #e8e3e4 0%, #fff 71%, #fff 100%)}}html.theme--documenter-dark .hero.is-black{background-color:#0a0a0a;color:#fff}html.theme--documenter-dark .hero.is-black a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .hero.is-black strong{color:inherit}html.theme--documenter-dark .hero.is-black .title{color:#fff}html.theme--documenter-dark .hero.is-black .subtitle{color:rgba(255,255,255,0.9)}html.theme--documenter-dark .hero.is-black .subtitle a:not(.button),html.theme--documenter-dark .hero.is-black .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--documenter-dark .hero.is-black .navbar-menu{background-color:#0a0a0a}}html.theme--documenter-dark .hero.is-black .navbar-item,html.theme--documenter-dark .hero.is-black .navbar-link{color:rgba(255,255,255,0.7)}html.theme--documenter-dark .hero.is-black a.navbar-item:hover,html.theme--documenter-dark .hero.is-black a.navbar-item.is-active,html.theme--documenter-dark .hero.is-black .navbar-link:hover,html.theme--documenter-dark .hero.is-black .navbar-link.is-active{background-color:#000;color:#fff}html.theme--documenter-dark .hero.is-black .tabs a{color:#fff;opacity:0.9}html.theme--documenter-dark .hero.is-black .tabs a:hover{opacity:1}html.theme--documenter-dark .hero.is-black .tabs li.is-active a{color:#0a0a0a !important;opacity:1}html.theme--documenter-dark .hero.is-black .tabs.is-boxed a,html.theme--documenter-dark .hero.is-black .tabs.is-toggle a{color:#fff}html.theme--documenter-dark .hero.is-black .tabs.is-boxed a:hover,html.theme--documenter-dark .hero.is-black .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--documenter-dark .hero.is-black .tabs.is-boxed li.is-active a,html.theme--documenter-dark .hero.is-black .tabs.is-boxed li.is-active a:hover,html.theme--documenter-dark .hero.is-black .tabs.is-toggle li.is-active a,html.theme--documenter-dark .hero.is-black .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#0a0a0a}html.theme--documenter-dark .hero.is-black.is-bold{background-image:linear-gradient(141deg, #000 0%, #0a0a0a 71%, #181616 100%)}@media screen and (max-width: 768px){html.theme--documenter-dark .hero.is-black.is-bold .navbar-menu{background-image:linear-gradient(141deg, #000 0%, #0a0a0a 71%, #181616 100%)}}html.theme--documenter-dark .hero.is-light{background-color:#ecf0f1;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .hero.is-light a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .hero.is-light strong{color:inherit}html.theme--documenter-dark .hero.is-light .title{color:rgba(0,0,0,0.7)}html.theme--documenter-dark .hero.is-light .subtitle{color:rgba(0,0,0,0.9)}html.theme--documenter-dark .hero.is-light .subtitle a:not(.button),html.theme--documenter-dark .hero.is-light .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){html.theme--documenter-dark .hero.is-light .navbar-menu{background-color:#ecf0f1}}html.theme--documenter-dark .hero.is-light .navbar-item,html.theme--documenter-dark .hero.is-light .navbar-link{color:rgba(0,0,0,0.7)}html.theme--documenter-dark .hero.is-light a.navbar-item:hover,html.theme--documenter-dark .hero.is-light a.navbar-item.is-active,html.theme--documenter-dark .hero.is-light .navbar-link:hover,html.theme--documenter-dark .hero.is-light .navbar-link.is-active{background-color:#dde4e6;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .hero.is-light .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}html.theme--documenter-dark .hero.is-light .tabs a:hover{opacity:1}html.theme--documenter-dark .hero.is-light .tabs li.is-active a{color:#ecf0f1 !important;opacity:1}html.theme--documenter-dark .hero.is-light .tabs.is-boxed a,html.theme--documenter-dark .hero.is-light .tabs.is-toggle a{color:rgba(0,0,0,0.7)}html.theme--documenter-dark .hero.is-light .tabs.is-boxed a:hover,html.theme--documenter-dark .hero.is-light .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--documenter-dark .hero.is-light .tabs.is-boxed li.is-active a,html.theme--documenter-dark .hero.is-light .tabs.is-boxed li.is-active a:hover,html.theme--documenter-dark .hero.is-light .tabs.is-toggle li.is-active a,html.theme--documenter-dark .hero.is-light .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#ecf0f1}html.theme--documenter-dark .hero.is-light.is-bold{background-image:linear-gradient(141deg, #cadfe0 0%, #ecf0f1 71%, #fafbfc 100%)}@media screen and (max-width: 768px){html.theme--documenter-dark .hero.is-light.is-bold .navbar-menu{background-image:linear-gradient(141deg, #cadfe0 0%, #ecf0f1 71%, #fafbfc 100%)}}html.theme--documenter-dark .hero.is-dark,html.theme--documenter-dark .content kbd.hero{background-color:#282f2f;color:#fff}html.theme--documenter-dark .hero.is-dark a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .content kbd.hero a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .hero.is-dark strong,html.theme--documenter-dark .content kbd.hero strong{color:inherit}html.theme--documenter-dark .hero.is-dark .title,html.theme--documenter-dark .content kbd.hero .title{color:#fff}html.theme--documenter-dark .hero.is-dark .subtitle,html.theme--documenter-dark .content kbd.hero .subtitle{color:rgba(255,255,255,0.9)}html.theme--documenter-dark .hero.is-dark .subtitle a:not(.button),html.theme--documenter-dark .content kbd.hero .subtitle a:not(.button),html.theme--documenter-dark .hero.is-dark .subtitle strong,html.theme--documenter-dark .content kbd.hero .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--documenter-dark .hero.is-dark .navbar-menu,html.theme--documenter-dark .content kbd.hero .navbar-menu{background-color:#282f2f}}html.theme--documenter-dark .hero.is-dark .navbar-item,html.theme--documenter-dark .content kbd.hero .navbar-item,html.theme--documenter-dark .hero.is-dark .navbar-link,html.theme--documenter-dark .content kbd.hero .navbar-link{color:rgba(255,255,255,0.7)}html.theme--documenter-dark .hero.is-dark a.navbar-item:hover,html.theme--documenter-dark .content kbd.hero a.navbar-item:hover,html.theme--documenter-dark .hero.is-dark a.navbar-item.is-active,html.theme--documenter-dark .content kbd.hero a.navbar-item.is-active,html.theme--documenter-dark .hero.is-dark .navbar-link:hover,html.theme--documenter-dark .content kbd.hero .navbar-link:hover,html.theme--documenter-dark .hero.is-dark .navbar-link.is-active,html.theme--documenter-dark .content kbd.hero .navbar-link.is-active{background-color:#1d2122;color:#fff}html.theme--documenter-dark .hero.is-dark .tabs a,html.theme--documenter-dark .content kbd.hero .tabs a{color:#fff;opacity:0.9}html.theme--documenter-dark .hero.is-dark .tabs a:hover,html.theme--documenter-dark .content kbd.hero .tabs a:hover{opacity:1}html.theme--documenter-dark .hero.is-dark .tabs li.is-active a,html.theme--documenter-dark .content kbd.hero .tabs li.is-active a{color:#282f2f !important;opacity:1}html.theme--documenter-dark .hero.is-dark .tabs.is-boxed a,html.theme--documenter-dark .content kbd.hero .tabs.is-boxed a,html.theme--documenter-dark .hero.is-dark .tabs.is-toggle a,html.theme--documenter-dark .content kbd.hero .tabs.is-toggle a{color:#fff}html.theme--documenter-dark .hero.is-dark .tabs.is-boxed a:hover,html.theme--documenter-dark .content kbd.hero .tabs.is-boxed a:hover,html.theme--documenter-dark .hero.is-dark .tabs.is-toggle a:hover,html.theme--documenter-dark .content kbd.hero .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--documenter-dark .hero.is-dark .tabs.is-boxed li.is-active a,html.theme--documenter-dark .content kbd.hero .tabs.is-boxed li.is-active a,html.theme--documenter-dark .hero.is-dark .tabs.is-boxed li.is-active a:hover,html.theme--documenter-dark .hero.is-dark .tabs.is-toggle li.is-active a,html.theme--documenter-dark .content kbd.hero .tabs.is-toggle li.is-active a,html.theme--documenter-dark .hero.is-dark .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#282f2f}html.theme--documenter-dark .hero.is-dark.is-bold,html.theme--documenter-dark .content kbd.hero.is-bold{background-image:linear-gradient(141deg, #0f1615 0%, #282f2f 71%, #313c40 100%)}@media screen and (max-width: 768px){html.theme--documenter-dark .hero.is-dark.is-bold .navbar-menu,html.theme--documenter-dark .content kbd.hero.is-bold .navbar-menu{background-image:linear-gradient(141deg, #0f1615 0%, #282f2f 71%, #313c40 100%)}}html.theme--documenter-dark .hero.is-primary,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink{background-color:#375a7f;color:#fff}html.theme--documenter-dark .hero.is-primary a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .hero.is-primary strong,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink strong{color:inherit}html.theme--documenter-dark .hero.is-primary .title,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .title{color:#fff}html.theme--documenter-dark .hero.is-primary .subtitle,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .subtitle{color:rgba(255,255,255,0.9)}html.theme--documenter-dark .hero.is-primary .subtitle a:not(.button),html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .subtitle a:not(.button),html.theme--documenter-dark .hero.is-primary .subtitle strong,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--documenter-dark .hero.is-primary .navbar-menu,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .navbar-menu{background-color:#375a7f}}html.theme--documenter-dark .hero.is-primary .navbar-item,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .navbar-item,html.theme--documenter-dark .hero.is-primary .navbar-link,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .navbar-link{color:rgba(255,255,255,0.7)}html.theme--documenter-dark .hero.is-primary a.navbar-item:hover,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink a.navbar-item:hover,html.theme--documenter-dark .hero.is-primary a.navbar-item.is-active,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink a.navbar-item.is-active,html.theme--documenter-dark .hero.is-primary .navbar-link:hover,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .navbar-link:hover,html.theme--documenter-dark .hero.is-primary .navbar-link.is-active,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .navbar-link.is-active{background-color:#2f4d6d;color:#fff}html.theme--documenter-dark .hero.is-primary .tabs a,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .tabs a{color:#fff;opacity:0.9}html.theme--documenter-dark .hero.is-primary .tabs a:hover,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .tabs a:hover{opacity:1}html.theme--documenter-dark .hero.is-primary .tabs li.is-active a,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .tabs li.is-active a{color:#375a7f !important;opacity:1}html.theme--documenter-dark .hero.is-primary .tabs.is-boxed a,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .tabs.is-boxed a,html.theme--documenter-dark .hero.is-primary .tabs.is-toggle a,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .tabs.is-toggle a{color:#fff}html.theme--documenter-dark .hero.is-primary .tabs.is-boxed a:hover,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .tabs.is-boxed a:hover,html.theme--documenter-dark .hero.is-primary .tabs.is-toggle a:hover,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--documenter-dark .hero.is-primary .tabs.is-boxed li.is-active a,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .tabs.is-boxed li.is-active a,html.theme--documenter-dark .hero.is-primary .tabs.is-boxed li.is-active a:hover,html.theme--documenter-dark .hero.is-primary .tabs.is-toggle li.is-active a,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .tabs.is-toggle li.is-active a,html.theme--documenter-dark .hero.is-primary .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#375a7f}html.theme--documenter-dark .hero.is-primary.is-bold,html.theme--documenter-dark .docstring>section>a.hero.is-bold.docs-sourcelink{background-image:linear-gradient(141deg, #214b62 0%, #375a7f 71%, #3a5796 100%)}@media screen and (max-width: 768px){html.theme--documenter-dark .hero.is-primary.is-bold .navbar-menu,html.theme--documenter-dark .docstring>section>a.hero.is-bold.docs-sourcelink .navbar-menu{background-image:linear-gradient(141deg, #214b62 0%, #375a7f 71%, #3a5796 100%)}}html.theme--documenter-dark .hero.is-link{background-color:#1abc9c;color:#fff}html.theme--documenter-dark .hero.is-link a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .hero.is-link strong{color:inherit}html.theme--documenter-dark .hero.is-link .title{color:#fff}html.theme--documenter-dark .hero.is-link .subtitle{color:rgba(255,255,255,0.9)}html.theme--documenter-dark .hero.is-link .subtitle a:not(.button),html.theme--documenter-dark .hero.is-link .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--documenter-dark .hero.is-link .navbar-menu{background-color:#1abc9c}}html.theme--documenter-dark .hero.is-link .navbar-item,html.theme--documenter-dark .hero.is-link .navbar-link{color:rgba(255,255,255,0.7)}html.theme--documenter-dark .hero.is-link a.navbar-item:hover,html.theme--documenter-dark .hero.is-link a.navbar-item.is-active,html.theme--documenter-dark .hero.is-link .navbar-link:hover,html.theme--documenter-dark .hero.is-link .navbar-link.is-active{background-color:#17a689;color:#fff}html.theme--documenter-dark .hero.is-link .tabs a{color:#fff;opacity:0.9}html.theme--documenter-dark .hero.is-link .tabs a:hover{opacity:1}html.theme--documenter-dark .hero.is-link .tabs li.is-active a{color:#1abc9c !important;opacity:1}html.theme--documenter-dark .hero.is-link .tabs.is-boxed a,html.theme--documenter-dark .hero.is-link .tabs.is-toggle a{color:#fff}html.theme--documenter-dark .hero.is-link .tabs.is-boxed a:hover,html.theme--documenter-dark .hero.is-link .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--documenter-dark .hero.is-link .tabs.is-boxed li.is-active a,html.theme--documenter-dark .hero.is-link .tabs.is-boxed li.is-active a:hover,html.theme--documenter-dark .hero.is-link .tabs.is-toggle li.is-active a,html.theme--documenter-dark .hero.is-link .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#1abc9c}html.theme--documenter-dark .hero.is-link.is-bold{background-image:linear-gradient(141deg, #0c9764 0%, #1abc9c 71%, #17d8d2 100%)}@media screen and (max-width: 768px){html.theme--documenter-dark .hero.is-link.is-bold .navbar-menu{background-image:linear-gradient(141deg, #0c9764 0%, #1abc9c 71%, #17d8d2 100%)}}html.theme--documenter-dark .hero.is-info{background-color:#3c5dcd;color:#fff}html.theme--documenter-dark .hero.is-info a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .hero.is-info strong{color:inherit}html.theme--documenter-dark .hero.is-info .title{color:#fff}html.theme--documenter-dark .hero.is-info .subtitle{color:rgba(255,255,255,0.9)}html.theme--documenter-dark .hero.is-info .subtitle a:not(.button),html.theme--documenter-dark .hero.is-info .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--documenter-dark .hero.is-info .navbar-menu{background-color:#3c5dcd}}html.theme--documenter-dark .hero.is-info .navbar-item,html.theme--documenter-dark .hero.is-info .navbar-link{color:rgba(255,255,255,0.7)}html.theme--documenter-dark .hero.is-info a.navbar-item:hover,html.theme--documenter-dark .hero.is-info a.navbar-item.is-active,html.theme--documenter-dark .hero.is-info .navbar-link:hover,html.theme--documenter-dark .hero.is-info .navbar-link.is-active{background-color:#3151bf;color:#fff}html.theme--documenter-dark .hero.is-info .tabs a{color:#fff;opacity:0.9}html.theme--documenter-dark .hero.is-info .tabs a:hover{opacity:1}html.theme--documenter-dark .hero.is-info .tabs li.is-active a{color:#3c5dcd !important;opacity:1}html.theme--documenter-dark .hero.is-info .tabs.is-boxed a,html.theme--documenter-dark .hero.is-info .tabs.is-toggle a{color:#fff}html.theme--documenter-dark .hero.is-info .tabs.is-boxed a:hover,html.theme--documenter-dark .hero.is-info .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--documenter-dark .hero.is-info .tabs.is-boxed li.is-active a,html.theme--documenter-dark .hero.is-info .tabs.is-boxed li.is-active a:hover,html.theme--documenter-dark .hero.is-info .tabs.is-toggle li.is-active a,html.theme--documenter-dark .hero.is-info .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#3c5dcd}html.theme--documenter-dark .hero.is-info.is-bold{background-image:linear-gradient(141deg, #215bb5 0%, #3c5dcd 71%, #4b53d8 100%)}@media screen and (max-width: 768px){html.theme--documenter-dark .hero.is-info.is-bold .navbar-menu{background-image:linear-gradient(141deg, #215bb5 0%, #3c5dcd 71%, #4b53d8 100%)}}html.theme--documenter-dark .hero.is-success{background-color:#259a12;color:#fff}html.theme--documenter-dark .hero.is-success a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .hero.is-success strong{color:inherit}html.theme--documenter-dark .hero.is-success .title{color:#fff}html.theme--documenter-dark .hero.is-success .subtitle{color:rgba(255,255,255,0.9)}html.theme--documenter-dark .hero.is-success .subtitle a:not(.button),html.theme--documenter-dark .hero.is-success .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--documenter-dark .hero.is-success .navbar-menu{background-color:#259a12}}html.theme--documenter-dark .hero.is-success .navbar-item,html.theme--documenter-dark .hero.is-success .navbar-link{color:rgba(255,255,255,0.7)}html.theme--documenter-dark .hero.is-success a.navbar-item:hover,html.theme--documenter-dark .hero.is-success a.navbar-item.is-active,html.theme--documenter-dark .hero.is-success .navbar-link:hover,html.theme--documenter-dark .hero.is-success .navbar-link.is-active{background-color:#20830f;color:#fff}html.theme--documenter-dark .hero.is-success .tabs a{color:#fff;opacity:0.9}html.theme--documenter-dark .hero.is-success .tabs a:hover{opacity:1}html.theme--documenter-dark .hero.is-success .tabs li.is-active a{color:#259a12 !important;opacity:1}html.theme--documenter-dark .hero.is-success .tabs.is-boxed a,html.theme--documenter-dark .hero.is-success .tabs.is-toggle a{color:#fff}html.theme--documenter-dark .hero.is-success .tabs.is-boxed a:hover,html.theme--documenter-dark .hero.is-success .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--documenter-dark .hero.is-success .tabs.is-boxed li.is-active a,html.theme--documenter-dark .hero.is-success .tabs.is-boxed li.is-active a:hover,html.theme--documenter-dark .hero.is-success .tabs.is-toggle li.is-active a,html.theme--documenter-dark .hero.is-success .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#259a12}html.theme--documenter-dark .hero.is-success.is-bold{background-image:linear-gradient(141deg, #287207 0%, #259a12 71%, #10b614 100%)}@media screen and (max-width: 768px){html.theme--documenter-dark .hero.is-success.is-bold .navbar-menu{background-image:linear-gradient(141deg, #287207 0%, #259a12 71%, #10b614 100%)}}html.theme--documenter-dark .hero.is-warning{background-color:#f4c72f;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .hero.is-warning a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .hero.is-warning strong{color:inherit}html.theme--documenter-dark .hero.is-warning .title{color:rgba(0,0,0,0.7)}html.theme--documenter-dark .hero.is-warning .subtitle{color:rgba(0,0,0,0.9)}html.theme--documenter-dark .hero.is-warning .subtitle a:not(.button),html.theme--documenter-dark .hero.is-warning .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){html.theme--documenter-dark .hero.is-warning .navbar-menu{background-color:#f4c72f}}html.theme--documenter-dark .hero.is-warning .navbar-item,html.theme--documenter-dark .hero.is-warning .navbar-link{color:rgba(0,0,0,0.7)}html.theme--documenter-dark .hero.is-warning a.navbar-item:hover,html.theme--documenter-dark .hero.is-warning a.navbar-item.is-active,html.theme--documenter-dark .hero.is-warning .navbar-link:hover,html.theme--documenter-dark .hero.is-warning .navbar-link.is-active{background-color:#f3c017;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .hero.is-warning .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}html.theme--documenter-dark .hero.is-warning .tabs a:hover{opacity:1}html.theme--documenter-dark .hero.is-warning .tabs li.is-active a{color:#f4c72f !important;opacity:1}html.theme--documenter-dark .hero.is-warning .tabs.is-boxed a,html.theme--documenter-dark .hero.is-warning .tabs.is-toggle a{color:rgba(0,0,0,0.7)}html.theme--documenter-dark .hero.is-warning .tabs.is-boxed a:hover,html.theme--documenter-dark .hero.is-warning .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--documenter-dark .hero.is-warning .tabs.is-boxed li.is-active a,html.theme--documenter-dark .hero.is-warning .tabs.is-boxed li.is-active a:hover,html.theme--documenter-dark .hero.is-warning .tabs.is-toggle li.is-active a,html.theme--documenter-dark .hero.is-warning .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#f4c72f}html.theme--documenter-dark .hero.is-warning.is-bold{background-image:linear-gradient(141deg, #f09100 0%, #f4c72f 71%, #faef42 100%)}@media screen and (max-width: 768px){html.theme--documenter-dark .hero.is-warning.is-bold .navbar-menu{background-image:linear-gradient(141deg, #f09100 0%, #f4c72f 71%, #faef42 100%)}}html.theme--documenter-dark .hero.is-danger{background-color:#cb3c33;color:#fff}html.theme--documenter-dark .hero.is-danger a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .hero.is-danger strong{color:inherit}html.theme--documenter-dark .hero.is-danger .title{color:#fff}html.theme--documenter-dark .hero.is-danger .subtitle{color:rgba(255,255,255,0.9)}html.theme--documenter-dark .hero.is-danger .subtitle a:not(.button),html.theme--documenter-dark .hero.is-danger .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--documenter-dark .hero.is-danger .navbar-menu{background-color:#cb3c33}}html.theme--documenter-dark .hero.is-danger .navbar-item,html.theme--documenter-dark .hero.is-danger .navbar-link{color:rgba(255,255,255,0.7)}html.theme--documenter-dark .hero.is-danger a.navbar-item:hover,html.theme--documenter-dark .hero.is-danger a.navbar-item.is-active,html.theme--documenter-dark .hero.is-danger .navbar-link:hover,html.theme--documenter-dark .hero.is-danger .navbar-link.is-active{background-color:#b7362e;color:#fff}html.theme--documenter-dark .hero.is-danger .tabs a{color:#fff;opacity:0.9}html.theme--documenter-dark .hero.is-danger .tabs a:hover{opacity:1}html.theme--documenter-dark .hero.is-danger .tabs li.is-active a{color:#cb3c33 !important;opacity:1}html.theme--documenter-dark .hero.is-danger .tabs.is-boxed a,html.theme--documenter-dark .hero.is-danger .tabs.is-toggle a{color:#fff}html.theme--documenter-dark .hero.is-danger .tabs.is-boxed a:hover,html.theme--documenter-dark .hero.is-danger .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--documenter-dark .hero.is-danger .tabs.is-boxed li.is-active a,html.theme--documenter-dark .hero.is-danger .tabs.is-boxed li.is-active a:hover,html.theme--documenter-dark .hero.is-danger .tabs.is-toggle li.is-active a,html.theme--documenter-dark .hero.is-danger .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#cb3c33}html.theme--documenter-dark .hero.is-danger.is-bold{background-image:linear-gradient(141deg, #ac1f2e 0%, #cb3c33 71%, #d66341 100%)}@media screen and (max-width: 768px){html.theme--documenter-dark .hero.is-danger.is-bold .navbar-menu{background-image:linear-gradient(141deg, #ac1f2e 0%, #cb3c33 71%, #d66341 100%)}}html.theme--documenter-dark .hero.is-small .hero-body,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.hero .hero-body{padding:1.5rem}@media screen and (min-width: 769px),print{html.theme--documenter-dark .hero.is-medium .hero-body{padding:9rem 4.5rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .hero.is-large .hero-body{padding:18rem 6rem}}html.theme--documenter-dark .hero.is-halfheight .hero-body,html.theme--documenter-dark .hero.is-fullheight .hero-body,html.theme--documenter-dark .hero.is-fullheight-with-navbar .hero-body{align-items:center;display:flex}html.theme--documenter-dark .hero.is-halfheight .hero-body>.container,html.theme--documenter-dark .hero.is-fullheight .hero-body>.container,html.theme--documenter-dark .hero.is-fullheight-with-navbar .hero-body>.container{flex-grow:1;flex-shrink:1}html.theme--documenter-dark .hero.is-halfheight{min-height:50vh}html.theme--documenter-dark .hero.is-fullheight{min-height:100vh}html.theme--documenter-dark .hero-video{overflow:hidden}html.theme--documenter-dark .hero-video video{left:50%;min-height:100%;min-width:100%;position:absolute;top:50%;transform:translate3d(-50%, -50%, 0)}html.theme--documenter-dark .hero-video.is-transparent{opacity:0.3}@media screen and (max-width: 768px){html.theme--documenter-dark .hero-video{display:none}}html.theme--documenter-dark .hero-buttons{margin-top:1.5rem}@media screen and (max-width: 768px){html.theme--documenter-dark .hero-buttons .button{display:flex}html.theme--documenter-dark .hero-buttons .button:not(:last-child){margin-bottom:0.75rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .hero-buttons{display:flex;justify-content:center}html.theme--documenter-dark .hero-buttons .button:not(:last-child){margin-right:1.5rem}}html.theme--documenter-dark .hero-head,html.theme--documenter-dark .hero-foot{flex-grow:0;flex-shrink:0}html.theme--documenter-dark .hero-body{flex-grow:1;flex-shrink:0;padding:3rem 1.5rem}@media screen and (min-width: 769px),print{html.theme--documenter-dark .hero-body{padding:3rem 3rem}}html.theme--documenter-dark .section{padding:3rem 1.5rem}@media screen and (min-width: 1056px){html.theme--documenter-dark .section{padding:3rem 3rem}html.theme--documenter-dark .section.is-medium{padding:9rem 4.5rem}html.theme--documenter-dark .section.is-large{padding:18rem 6rem}}html.theme--documenter-dark .footer{background-color:#282f2f;padding:3rem 1.5rem 6rem}html.theme--documenter-dark hr{height:1px}html.theme--documenter-dark h6{text-transform:uppercase;letter-spacing:0.5px}html.theme--documenter-dark .hero{background-color:#343c3d}html.theme--documenter-dark a{transition:all 200ms ease}html.theme--documenter-dark .button{transition:all 200ms ease;border-width:1px;color:#fff}html.theme--documenter-dark .button.is-active,html.theme--documenter-dark .button.is-focused,html.theme--documenter-dark .button:active,html.theme--documenter-dark .button:focus{box-shadow:0 0 0 2px rgba(140,155,157,0.5)}html.theme--documenter-dark .button.is-white.is-hovered,html.theme--documenter-dark .button.is-white:hover{background-color:#fff}html.theme--documenter-dark .button.is-white.is-active,html.theme--documenter-dark .button.is-white.is-focused,html.theme--documenter-dark .button.is-white:active,html.theme--documenter-dark .button.is-white:focus{border-color:#fff;box-shadow:0 0 0 2px rgba(255,255,255,0.5)}html.theme--documenter-dark .button.is-black.is-hovered,html.theme--documenter-dark .button.is-black:hover{background-color:#1d1d1d}html.theme--documenter-dark .button.is-black.is-active,html.theme--documenter-dark .button.is-black.is-focused,html.theme--documenter-dark .button.is-black:active,html.theme--documenter-dark .button.is-black:focus{border-color:#0a0a0a;box-shadow:0 0 0 2px rgba(10,10,10,0.5)}html.theme--documenter-dark .button.is-light.is-hovered,html.theme--documenter-dark .button.is-light:hover{background-color:#fff}html.theme--documenter-dark .button.is-light.is-active,html.theme--documenter-dark .button.is-light.is-focused,html.theme--documenter-dark .button.is-light:active,html.theme--documenter-dark .button.is-light:focus{border-color:#ecf0f1;box-shadow:0 0 0 2px rgba(236,240,241,0.5)}html.theme--documenter-dark .button.is-dark.is-hovered,html.theme--documenter-dark .content kbd.button.is-hovered,html.theme--documenter-dark .button.is-dark:hover,html.theme--documenter-dark .content kbd.button:hover{background-color:#3a4344}html.theme--documenter-dark .button.is-dark.is-active,html.theme--documenter-dark .content kbd.button.is-active,html.theme--documenter-dark .button.is-dark.is-focused,html.theme--documenter-dark .content kbd.button.is-focused,html.theme--documenter-dark .button.is-dark:active,html.theme--documenter-dark .content kbd.button:active,html.theme--documenter-dark .button.is-dark:focus,html.theme--documenter-dark .content kbd.button:focus{border-color:#282f2f;box-shadow:0 0 0 2px rgba(40,47,47,0.5)}html.theme--documenter-dark .button.is-primary.is-hovered,html.theme--documenter-dark .docstring>section>a.button.is-hovered.docs-sourcelink,html.theme--documenter-dark .button.is-primary:hover,html.theme--documenter-dark .docstring>section>a.button.docs-sourcelink:hover{background-color:#436d9a}html.theme--documenter-dark .button.is-primary.is-active,html.theme--documenter-dark .docstring>section>a.button.is-active.docs-sourcelink,html.theme--documenter-dark .button.is-primary.is-focused,html.theme--documenter-dark .docstring>section>a.button.is-focused.docs-sourcelink,html.theme--documenter-dark .button.is-primary:active,html.theme--documenter-dark .docstring>section>a.button.docs-sourcelink:active,html.theme--documenter-dark .button.is-primary:focus,html.theme--documenter-dark .docstring>section>a.button.docs-sourcelink:focus{border-color:#375a7f;box-shadow:0 0 0 2px rgba(55,90,127,0.5)}html.theme--documenter-dark .button.is-link.is-hovered,html.theme--documenter-dark .button.is-link:hover{background-color:#1fdeb8}html.theme--documenter-dark .button.is-link.is-active,html.theme--documenter-dark .button.is-link.is-focused,html.theme--documenter-dark .button.is-link:active,html.theme--documenter-dark .button.is-link:focus{border-color:#1abc9c;box-shadow:0 0 0 2px rgba(26,188,156,0.5)}html.theme--documenter-dark .button.is-info.is-hovered,html.theme--documenter-dark .button.is-info:hover{background-color:#5a76d5}html.theme--documenter-dark .button.is-info.is-active,html.theme--documenter-dark .button.is-info.is-focused,html.theme--documenter-dark .button.is-info:active,html.theme--documenter-dark .button.is-info:focus{border-color:#3c5dcd;box-shadow:0 0 0 2px rgba(60,93,205,0.5)}html.theme--documenter-dark .button.is-success.is-hovered,html.theme--documenter-dark .button.is-success:hover{background-color:#2dbc16}html.theme--documenter-dark .button.is-success.is-active,html.theme--documenter-dark .button.is-success.is-focused,html.theme--documenter-dark .button.is-success:active,html.theme--documenter-dark .button.is-success:focus{border-color:#259a12;box-shadow:0 0 0 2px rgba(37,154,18,0.5)}html.theme--documenter-dark .button.is-warning.is-hovered,html.theme--documenter-dark .button.is-warning:hover{background-color:#f6d153}html.theme--documenter-dark .button.is-warning.is-active,html.theme--documenter-dark .button.is-warning.is-focused,html.theme--documenter-dark .button.is-warning:active,html.theme--documenter-dark .button.is-warning:focus{border-color:#f4c72f;box-shadow:0 0 0 2px rgba(244,199,47,0.5)}html.theme--documenter-dark .button.is-danger.is-hovered,html.theme--documenter-dark .button.is-danger:hover{background-color:#d35951}html.theme--documenter-dark .button.is-danger.is-active,html.theme--documenter-dark .button.is-danger.is-focused,html.theme--documenter-dark .button.is-danger:active,html.theme--documenter-dark .button.is-danger:focus{border-color:#cb3c33;box-shadow:0 0 0 2px rgba(203,60,51,0.5)}html.theme--documenter-dark .label{color:#dbdee0}html.theme--documenter-dark .button,html.theme--documenter-dark .control.has-icons-left .icon,html.theme--documenter-dark .control.has-icons-right .icon,html.theme--documenter-dark .input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input,html.theme--documenter-dark .pagination-ellipsis,html.theme--documenter-dark .pagination-link,html.theme--documenter-dark .pagination-next,html.theme--documenter-dark .pagination-previous,html.theme--documenter-dark .select,html.theme--documenter-dark .select select,html.theme--documenter-dark .textarea{height:2.5em}html.theme--documenter-dark .input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input,html.theme--documenter-dark .textarea{transition:all 200ms ease;box-shadow:none;border-width:1px;padding-left:1em;padding-right:1em}html.theme--documenter-dark .select:after,html.theme--documenter-dark .select select{border-width:1px}html.theme--documenter-dark .control.has-addons .button,html.theme--documenter-dark .control.has-addons .input,html.theme--documenter-dark .control.has-addons #documenter .docs-sidebar form.docs-search>input,html.theme--documenter-dark #documenter .docs-sidebar .control.has-addons form.docs-search>input,html.theme--documenter-dark .control.has-addons .select{margin-right:-1px}html.theme--documenter-dark .notification{background-color:#343c3d}html.theme--documenter-dark .card{box-shadow:none;border:1px solid #343c3d;background-color:#282f2f;border-radius:.4em}html.theme--documenter-dark .card .card-image img{border-radius:.4em .4em 0 0}html.theme--documenter-dark .card .card-header{box-shadow:none;background-color:rgba(18,18,18,0.2);border-radius:.4em .4em 0 0}html.theme--documenter-dark .card .card-footer{background-color:rgba(18,18,18,0.2)}html.theme--documenter-dark .card .card-footer,html.theme--documenter-dark .card .card-footer-item{border-width:1px;border-color:#343c3d}html.theme--documenter-dark .notification.is-white a:not(.button){color:#0a0a0a;text-decoration:underline}html.theme--documenter-dark .notification.is-black a:not(.button){color:#fff;text-decoration:underline}html.theme--documenter-dark .notification.is-light a:not(.button){color:rgba(0,0,0,0.7);text-decoration:underline}html.theme--documenter-dark .notification.is-dark a:not(.button),html.theme--documenter-dark .content kbd.notification a:not(.button){color:#fff;text-decoration:underline}html.theme--documenter-dark .notification.is-primary a:not(.button),html.theme--documenter-dark .docstring>section>a.notification.docs-sourcelink a:not(.button){color:#fff;text-decoration:underline}html.theme--documenter-dark .notification.is-link a:not(.button){color:#fff;text-decoration:underline}html.theme--documenter-dark .notification.is-info a:not(.button){color:#fff;text-decoration:underline}html.theme--documenter-dark .notification.is-success a:not(.button){color:#fff;text-decoration:underline}html.theme--documenter-dark .notification.is-warning a:not(.button){color:rgba(0,0,0,0.7);text-decoration:underline}html.theme--documenter-dark .notification.is-danger a:not(.button){color:#fff;text-decoration:underline}html.theme--documenter-dark .tag,html.theme--documenter-dark .content kbd,html.theme--documenter-dark .docstring>section>a.docs-sourcelink{border-radius:.4em}html.theme--documenter-dark .menu-list a{transition:all 300ms ease}html.theme--documenter-dark .modal-card-body{background-color:#282f2f}html.theme--documenter-dark .modal-card-foot,html.theme--documenter-dark .modal-card-head{border-color:#343c3d}html.theme--documenter-dark .message-header{font-weight:700;background-color:#343c3d;color:#fff}html.theme--documenter-dark .message-body{border-width:1px;border-color:#343c3d}html.theme--documenter-dark .navbar{border-radius:.4em}html.theme--documenter-dark .navbar.is-transparent{background:none}html.theme--documenter-dark .navbar.is-primary .navbar-dropdown a.navbar-item.is-active,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-dropdown a.navbar-item.is-active{background-color:#1abc9c}@media screen and (max-width: 1055px){html.theme--documenter-dark .navbar .navbar-menu{background-color:#375a7f;border-radius:0 0 .4em .4em}}html.theme--documenter-dark .hero .navbar,html.theme--documenter-dark body>.navbar{border-radius:0}html.theme--documenter-dark .pagination-link,html.theme--documenter-dark .pagination-next,html.theme--documenter-dark .pagination-previous{border-width:1px}html.theme--documenter-dark .panel-block,html.theme--documenter-dark .panel-heading,html.theme--documenter-dark .panel-tabs{border-width:1px}html.theme--documenter-dark .panel-block:first-child,html.theme--documenter-dark .panel-heading:first-child,html.theme--documenter-dark .panel-tabs:first-child{border-top-width:1px}html.theme--documenter-dark .panel-heading{font-weight:700}html.theme--documenter-dark .panel-tabs a{border-width:1px;margin-bottom:-1px}html.theme--documenter-dark .panel-tabs a.is-active{border-bottom-color:#17a689}html.theme--documenter-dark .panel-block:hover{color:#1dd2af}html.theme--documenter-dark .panel-block:hover .panel-icon{color:#1dd2af}html.theme--documenter-dark .panel-block.is-active .panel-icon{color:#17a689}html.theme--documenter-dark .tabs a{border-bottom-width:1px;margin-bottom:-1px}html.theme--documenter-dark .tabs ul{border-bottom-width:1px}html.theme--documenter-dark .tabs.is-boxed a{border-width:1px}html.theme--documenter-dark .tabs.is-boxed li.is-active a{background-color:#1f2424}html.theme--documenter-dark .tabs.is-toggle li a{border-width:1px;margin-bottom:0}html.theme--documenter-dark .tabs.is-toggle li+li{margin-left:-1px}html.theme--documenter-dark .hero.is-white .navbar .navbar-dropdown .navbar-item:hover{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .hero.is-black .navbar .navbar-dropdown .navbar-item:hover{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .hero.is-light .navbar .navbar-dropdown .navbar-item:hover{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .hero.is-dark .navbar .navbar-dropdown .navbar-item:hover,html.theme--documenter-dark .content kbd.hero .navbar .navbar-dropdown .navbar-item:hover{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .hero.is-primary .navbar .navbar-dropdown .navbar-item:hover,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .navbar .navbar-dropdown .navbar-item:hover{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .hero.is-link .navbar .navbar-dropdown .navbar-item:hover{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .hero.is-info .navbar .navbar-dropdown .navbar-item:hover{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .hero.is-success .navbar .navbar-dropdown .navbar-item:hover{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .hero.is-warning .navbar .navbar-dropdown .navbar-item:hover{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .hero.is-danger .navbar .navbar-dropdown .navbar-item:hover{background-color:rgba(0,0,0,0)}html.theme--documenter-dark h1 .docs-heading-anchor,html.theme--documenter-dark h1 .docs-heading-anchor:hover,html.theme--documenter-dark h1 .docs-heading-anchor:visited,html.theme--documenter-dark h2 .docs-heading-anchor,html.theme--documenter-dark h2 .docs-heading-anchor:hover,html.theme--documenter-dark h2 .docs-heading-anchor:visited,html.theme--documenter-dark h3 .docs-heading-anchor,html.theme--documenter-dark h3 .docs-heading-anchor:hover,html.theme--documenter-dark h3 .docs-heading-anchor:visited,html.theme--documenter-dark h4 .docs-heading-anchor,html.theme--documenter-dark h4 .docs-heading-anchor:hover,html.theme--documenter-dark h4 .docs-heading-anchor:visited,html.theme--documenter-dark h5 .docs-heading-anchor,html.theme--documenter-dark h5 .docs-heading-anchor:hover,html.theme--documenter-dark h5 .docs-heading-anchor:visited,html.theme--documenter-dark h6 .docs-heading-anchor,html.theme--documenter-dark h6 .docs-heading-anchor:hover,html.theme--documenter-dark h6 .docs-heading-anchor:visited{color:#f2f2f2}html.theme--documenter-dark h1 .docs-heading-anchor-permalink,html.theme--documenter-dark h2 .docs-heading-anchor-permalink,html.theme--documenter-dark h3 .docs-heading-anchor-permalink,html.theme--documenter-dark h4 .docs-heading-anchor-permalink,html.theme--documenter-dark h5 .docs-heading-anchor-permalink,html.theme--documenter-dark h6 .docs-heading-anchor-permalink{visibility:hidden;vertical-align:middle;margin-left:0.5em;font-size:0.7rem}html.theme--documenter-dark h1 .docs-heading-anchor-permalink::before,html.theme--documenter-dark h2 .docs-heading-anchor-permalink::before,html.theme--documenter-dark h3 .docs-heading-anchor-permalink::before,html.theme--documenter-dark h4 .docs-heading-anchor-permalink::before,html.theme--documenter-dark h5 .docs-heading-anchor-permalink::before,html.theme--documenter-dark h6 .docs-heading-anchor-permalink::before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f0c1"}html.theme--documenter-dark h1:hover .docs-heading-anchor-permalink,html.theme--documenter-dark h2:hover .docs-heading-anchor-permalink,html.theme--documenter-dark h3:hover .docs-heading-anchor-permalink,html.theme--documenter-dark h4:hover .docs-heading-anchor-permalink,html.theme--documenter-dark h5:hover .docs-heading-anchor-permalink,html.theme--documenter-dark h6:hover .docs-heading-anchor-permalink{visibility:visible}html.theme--documenter-dark .docs-light-only{display:none !important}html.theme--documenter-dark pre{position:relative;overflow:hidden}html.theme--documenter-dark pre code,html.theme--documenter-dark pre code.hljs{padding:0 .75rem !important;overflow:auto;display:block}html.theme--documenter-dark pre code:first-of-type,html.theme--documenter-dark pre code.hljs:first-of-type{padding-top:0.5rem !important}html.theme--documenter-dark pre code:last-of-type,html.theme--documenter-dark pre code.hljs:last-of-type{padding-bottom:0.5rem !important}html.theme--documenter-dark pre .copy-button{opacity:0.2;transition:opacity 0.2s;position:absolute;right:0em;top:0em;padding:0.5em;width:2.5em;height:2.5em;background:transparent;border:none;font-family:"Font Awesome 6 Free";color:#fff;cursor:pointer;text-align:center}html.theme--documenter-dark pre .copy-button:focus,html.theme--documenter-dark pre .copy-button:hover{opacity:1;background:rgba(255,255,255,0.1);color:#1abc9c}html.theme--documenter-dark pre .copy-button.success{color:#259a12;opacity:1}html.theme--documenter-dark pre .copy-button.error{color:#cb3c33;opacity:1}html.theme--documenter-dark pre:hover .copy-button{opacity:1}html.theme--documenter-dark .admonition{background-color:#282f2f;border-style:solid;border-width:2px;border-color:#dbdee0;border-radius:4px;font-size:1rem}html.theme--documenter-dark .admonition strong{color:currentColor}html.theme--documenter-dark .admonition.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.admonition{font-size:.75rem}html.theme--documenter-dark .admonition.is-medium{font-size:1.25rem}html.theme--documenter-dark .admonition.is-large{font-size:1.5rem}html.theme--documenter-dark .admonition.is-default{background-color:#282f2f;border-color:#dbdee0}html.theme--documenter-dark .admonition.is-default>.admonition-header{background-color:rgba(0,0,0,0);color:#dbdee0}html.theme--documenter-dark .admonition.is-default>.admonition-body{color:#fff}html.theme--documenter-dark .admonition.is-info{background-color:#282f2f;border-color:#3c5dcd}html.theme--documenter-dark .admonition.is-info>.admonition-header{background-color:rgba(0,0,0,0);color:#3c5dcd}html.theme--documenter-dark .admonition.is-info>.admonition-body{color:#fff}html.theme--documenter-dark .admonition.is-success{background-color:#282f2f;border-color:#259a12}html.theme--documenter-dark .admonition.is-success>.admonition-header{background-color:rgba(0,0,0,0);color:#259a12}html.theme--documenter-dark .admonition.is-success>.admonition-body{color:#fff}html.theme--documenter-dark .admonition.is-warning{background-color:#282f2f;border-color:#f4c72f}html.theme--documenter-dark .admonition.is-warning>.admonition-header{background-color:rgba(0,0,0,0);color:#f4c72f}html.theme--documenter-dark .admonition.is-warning>.admonition-body{color:#fff}html.theme--documenter-dark .admonition.is-danger{background-color:#282f2f;border-color:#cb3c33}html.theme--documenter-dark .admonition.is-danger>.admonition-header{background-color:rgba(0,0,0,0);color:#cb3c33}html.theme--documenter-dark .admonition.is-danger>.admonition-body{color:#fff}html.theme--documenter-dark .admonition.is-compat{background-color:#282f2f;border-color:#3489da}html.theme--documenter-dark .admonition.is-compat>.admonition-header{background-color:rgba(0,0,0,0);color:#3489da}html.theme--documenter-dark .admonition.is-compat>.admonition-body{color:#fff}html.theme--documenter-dark .admonition.is-todo{background-color:#282f2f;border-color:#9558b2}html.theme--documenter-dark .admonition.is-todo>.admonition-header{background-color:rgba(0,0,0,0);color:#9558b2}html.theme--documenter-dark .admonition.is-todo>.admonition-body{color:#fff}html.theme--documenter-dark .admonition-header{color:#dbdee0;background-color:rgba(0,0,0,0);align-items:center;font-weight:700;justify-content:space-between;line-height:1.25;padding:0.5rem .75rem;position:relative}html.theme--documenter-dark .admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;margin-right:.75rem;content:"\f06a"}html.theme--documenter-dark details.admonition.is-details>.admonition-header{list-style:none}html.theme--documenter-dark details.admonition.is-details>.admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f055"}html.theme--documenter-dark details.admonition.is-details[open]>.admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f056"}html.theme--documenter-dark .admonition-body{color:#fff;padding:0.5rem .75rem}html.theme--documenter-dark .admonition-body pre{background-color:#282f2f}html.theme--documenter-dark .admonition-body code{background-color:rgba(255,255,255,0.05)}html.theme--documenter-dark .docstring{margin-bottom:1em;background-color:rgba(0,0,0,0);border:2px solid #5e6d6f;border-radius:4px;box-shadow:none;max-width:100%}html.theme--documenter-dark .docstring>header{cursor:pointer;display:flex;flex-grow:1;align-items:stretch;padding:0.5rem .75rem;background-color:#282f2f;box-shadow:0 0.125em 0.25em rgba(10,10,10,0.1);box-shadow:none;border-bottom:1px solid #5e6d6f;overflow:auto}html.theme--documenter-dark .docstring>header code{background-color:transparent}html.theme--documenter-dark .docstring>header .docstring-article-toggle-button{min-width:1.1rem;padding:0.2rem 0.2rem 0.2rem 0}html.theme--documenter-dark .docstring>header .docstring-binding{margin-right:0.3em}html.theme--documenter-dark .docstring>header .docstring-category{margin-left:0.3em}html.theme--documenter-dark .docstring>section{position:relative;padding:.75rem .75rem;border-bottom:1px solid #5e6d6f}html.theme--documenter-dark .docstring>section:last-child{border-bottom:none}html.theme--documenter-dark .docstring>section>a.docs-sourcelink{transition:opacity 0.3s;opacity:0;position:absolute;right:.375rem;bottom:.375rem}html.theme--documenter-dark .docstring>section>a.docs-sourcelink:focus{opacity:1 !important}html.theme--documenter-dark .docstring:hover>section>a.docs-sourcelink{opacity:0.2}html.theme--documenter-dark .docstring:focus-within>section>a.docs-sourcelink{opacity:0.2}html.theme--documenter-dark .docstring>section:hover a.docs-sourcelink{opacity:1}html.theme--documenter-dark .documenter-example-output{background-color:#1f2424}html.theme--documenter-dark .outdated-warning-overlay{position:fixed;top:0;left:0;right:0;box-shadow:0 0 10px rgba(0,0,0,0.3);z-index:999;background-color:#282f2f;color:#fff;border-bottom:3px solid rgba(0,0,0,0);padding:10px 35px;text-align:center;font-size:15px}html.theme--documenter-dark .outdated-warning-overlay .outdated-warning-closer{position:absolute;top:calc(50% - 10px);right:18px;cursor:pointer;width:12px}html.theme--documenter-dark .outdated-warning-overlay a{color:#1abc9c}html.theme--documenter-dark .outdated-warning-overlay a:hover{color:#1dd2af}html.theme--documenter-dark .content pre{border:2px solid #5e6d6f;border-radius:4px}html.theme--documenter-dark .content code{font-weight:inherit}html.theme--documenter-dark .content a code{color:#1abc9c}html.theme--documenter-dark .content a:hover code{color:#1dd2af}html.theme--documenter-dark .content h1 code,html.theme--documenter-dark .content h2 code,html.theme--documenter-dark .content h3 code,html.theme--documenter-dark .content h4 code,html.theme--documenter-dark .content h5 code,html.theme--documenter-dark .content h6 code{color:#f2f2f2}html.theme--documenter-dark .content table{display:block;width:initial;max-width:100%;overflow-x:auto}html.theme--documenter-dark .content blockquote>ul:first-child,html.theme--documenter-dark .content blockquote>ol:first-child,html.theme--documenter-dark .content .admonition-body>ul:first-child,html.theme--documenter-dark .content .admonition-body>ol:first-child{margin-top:0}html.theme--documenter-dark pre,html.theme--documenter-dark code{font-variant-ligatures:no-contextual}html.theme--documenter-dark .breadcrumb a.is-disabled{cursor:default;pointer-events:none}html.theme--documenter-dark .breadcrumb a.is-disabled,html.theme--documenter-dark .breadcrumb a.is-disabled:hover{color:#f2f2f2}html.theme--documenter-dark .hljs{background:initial !important}html.theme--documenter-dark .katex .katex-mathml{top:0;right:0}html.theme--documenter-dark .katex-display,html.theme--documenter-dark mjx-container,html.theme--documenter-dark .MathJax_Display{margin:0.5em 0 !important}html.theme--documenter-dark html{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto}html.theme--documenter-dark li.no-marker{list-style:none}html.theme--documenter-dark #documenter .docs-main>article{overflow-wrap:break-word}html.theme--documenter-dark #documenter .docs-main>article .math-container{overflow-x:auto;overflow-y:hidden}@media screen and (min-width: 1056px){html.theme--documenter-dark #documenter .docs-main{max-width:52rem;margin-left:20rem;padding-right:1rem}}@media screen and (max-width: 1055px){html.theme--documenter-dark #documenter .docs-main{width:100%}html.theme--documenter-dark #documenter .docs-main>article{max-width:52rem;margin-left:auto;margin-right:auto;margin-bottom:1rem;padding:0 1rem}html.theme--documenter-dark #documenter .docs-main>header,html.theme--documenter-dark #documenter .docs-main>nav{max-width:100%;width:100%;margin:0}}html.theme--documenter-dark #documenter .docs-main header.docs-navbar{background-color:#1f2424;border-bottom:1px solid #5e6d6f;z-index:2;min-height:4rem;margin-bottom:1rem;display:flex}html.theme--documenter-dark #documenter .docs-main header.docs-navbar .breadcrumb{flex-grow:1;overflow-x:hidden}html.theme--documenter-dark #documenter .docs-main header.docs-navbar .docs-sidebar-button{display:block;font-size:1.5rem;padding-bottom:0.1rem;margin-right:1rem}html.theme--documenter-dark #documenter .docs-main header.docs-navbar .docs-right{display:flex;white-space:nowrap;gap:1rem;align-items:center}html.theme--documenter-dark #documenter .docs-main header.docs-navbar .docs-right .docs-icon,html.theme--documenter-dark #documenter .docs-main header.docs-navbar .docs-right .docs-label{display:inline-block}html.theme--documenter-dark #documenter .docs-main header.docs-navbar .docs-right .docs-label{padding:0;margin-left:0.3em}@media screen and (max-width: 1055px){html.theme--documenter-dark #documenter .docs-main header.docs-navbar .docs-right .docs-navbar-link{margin-left:0.4rem;margin-right:0.4rem}}html.theme--documenter-dark #documenter .docs-main header.docs-navbar>*{margin:auto 0}@media screen and (max-width: 1055px){html.theme--documenter-dark #documenter .docs-main header.docs-navbar{position:sticky;top:0;padding:0 1rem;transition-property:top, box-shadow;-webkit-transition-property:top, box-shadow;transition-duration:0.3s;-webkit-transition-duration:0.3s}html.theme--documenter-dark #documenter .docs-main header.docs-navbar.headroom--not-top{box-shadow:.2rem 0rem .4rem #171717;transition-duration:0.7s;-webkit-transition-duration:0.7s}html.theme--documenter-dark #documenter .docs-main header.docs-navbar.headroom--unpinned.headroom--not-top.headroom--not-bottom{top:-4.5rem;transition-duration:0.7s;-webkit-transition-duration:0.7s}}html.theme--documenter-dark #documenter .docs-main section.footnotes{border-top:1px solid #5e6d6f}html.theme--documenter-dark #documenter .docs-main section.footnotes li .tag:first-child,html.theme--documenter-dark #documenter .docs-main section.footnotes li .docstring>section>a.docs-sourcelink:first-child,html.theme--documenter-dark #documenter .docs-main section.footnotes li .content kbd:first-child,html.theme--documenter-dark .content #documenter .docs-main section.footnotes li kbd:first-child{margin-right:1em;margin-bottom:0.4em}html.theme--documenter-dark #documenter .docs-main .docs-footer{display:flex;flex-wrap:wrap;margin-left:0;margin-right:0;border-top:1px solid #5e6d6f;padding-top:1rem;padding-bottom:1rem}@media screen and (max-width: 1055px){html.theme--documenter-dark #documenter .docs-main .docs-footer{padding-left:1rem;padding-right:1rem}}html.theme--documenter-dark #documenter .docs-main .docs-footer .docs-footer-nextpage,html.theme--documenter-dark #documenter .docs-main .docs-footer .docs-footer-prevpage{flex-grow:1}html.theme--documenter-dark #documenter .docs-main .docs-footer .docs-footer-nextpage{text-align:right}html.theme--documenter-dark #documenter .docs-main .docs-footer .flexbox-break{flex-basis:100%;height:0}html.theme--documenter-dark #documenter .docs-main .docs-footer .footer-message{font-size:0.8em;margin:0.5em auto 0 auto;text-align:center}html.theme--documenter-dark #documenter .docs-sidebar{display:flex;flex-direction:column;color:#fff;background-color:#282f2f;border-right:1px solid #5e6d6f;padding:0;flex:0 0 18rem;z-index:5;font-size:1rem;position:fixed;left:-18rem;width:18rem;height:100%;transition:left 0.3s}html.theme--documenter-dark #documenter .docs-sidebar.visible{left:0;box-shadow:.4rem 0rem .8rem #171717}@media screen and (min-width: 1056px){html.theme--documenter-dark #documenter .docs-sidebar.visible{box-shadow:none}}@media screen and (min-width: 1056px){html.theme--documenter-dark #documenter .docs-sidebar{left:0;top:0}}html.theme--documenter-dark #documenter .docs-sidebar .docs-logo{margin-top:1rem;padding:0 1rem}html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img{max-height:6rem;margin:auto}html.theme--documenter-dark #documenter .docs-sidebar .docs-package-name{flex-shrink:0;font-size:1.5rem;font-weight:700;text-align:center;white-space:nowrap;overflow:hidden;padding:0.5rem 0}html.theme--documenter-dark #documenter .docs-sidebar .docs-package-name .docs-autofit{max-width:16.2rem}html.theme--documenter-dark #documenter .docs-sidebar .docs-package-name a,html.theme--documenter-dark #documenter .docs-sidebar .docs-package-name a:hover{color:#fff}html.theme--documenter-dark #documenter .docs-sidebar .docs-version-selector{border-top:1px solid #5e6d6f;display:none;padding:0.5rem}html.theme--documenter-dark #documenter .docs-sidebar .docs-version-selector.visible{display:flex}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu{flex-grow:1;user-select:none;border-top:1px solid #5e6d6f;padding-bottom:1.5rem}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu>li>.tocitem{font-weight:bold}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu>li li{font-size:.95rem;margin-left:1em;border-left:1px solid #5e6d6f}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu input.collapse-toggle{display:none}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu ul.collapsed{display:none}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu input:checked~ul.collapsed{display:block}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu label.tocitem{display:flex}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu label.tocitem .docs-label{flex-grow:2}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu label.tocitem .docs-chevron{display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1;font-size:.75rem;margin-left:1rem;margin-top:auto;margin-bottom:auto}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu label.tocitem .docs-chevron::before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f054"}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu input:checked~label.tocitem .docs-chevron::before{content:"\f078"}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu .tocitem{display:block;padding:0.5rem 0.5rem}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu .tocitem,html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu .tocitem:hover{color:#fff;background:#282f2f}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu a.tocitem:hover,html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu label.tocitem:hover{color:#fff;background-color:#32393a}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu li.is-active{border-top:1px solid #5e6d6f;border-bottom:1px solid #5e6d6f;background-color:#1f2424}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu li.is-active .tocitem,html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu li.is-active .tocitem:hover{background-color:#1f2424;color:#fff}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu li.is-active ul.internal .tocitem:hover{background-color:#32393a;color:#fff}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu>li.is-active:first-child{border-top:none}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu ul.internal{margin:0 0.5rem 0.5rem;border-top:1px solid #5e6d6f}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu ul.internal li{font-size:.85rem;border-left:none;margin-left:0;margin-top:0.5rem}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu ul.internal .tocitem{width:100%;padding:0}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu ul.internal .tocitem::before{content:"⚬";margin-right:0.4em}html.theme--documenter-dark #documenter .docs-sidebar form.docs-search{margin:auto;margin-top:0.5rem;margin-bottom:0.5rem}html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input{width:14.4rem}html.theme--documenter-dark #documenter .docs-sidebar #documenter-search-query{color:#868c98;width:14.4rem;box-shadow:inset 0 1px 2px rgba(10,10,10,0.1)}@media screen and (min-width: 1056px){html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu{overflow-y:auto;-webkit-overflow-scroll:touch}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar{width:.3rem;background:none}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar-thumb{border-radius:5px 0px 0px 5px;background:#3b4445}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar-thumb:hover{background:#4e5a5c}}@media screen and (max-width: 1055px){html.theme--documenter-dark #documenter .docs-sidebar{overflow-y:auto;-webkit-overflow-scroll:touch}html.theme--documenter-dark #documenter .docs-sidebar::-webkit-scrollbar{width:.3rem;background:none}html.theme--documenter-dark #documenter .docs-sidebar::-webkit-scrollbar-thumb{border-radius:5px 0px 0px 5px;background:#3b4445}html.theme--documenter-dark #documenter .docs-sidebar::-webkit-scrollbar-thumb:hover{background:#4e5a5c}}html.theme--documenter-dark kbd.search-modal-key-hints{border-radius:0.25rem;border:1px solid rgba(245,245,245,0.6);box-shadow:0 2px 0 1px rgba(245,245,245,0.6);cursor:default;font-size:0.9rem;line-height:1.5;min-width:0.75rem;text-align:center;padding:0.1rem 0.3rem;position:relative;top:-1px}html.theme--documenter-dark .search-min-width-50{min-width:50%}html.theme--documenter-dark .search-min-height-100{min-height:100%}html.theme--documenter-dark .search-modal-card-body{max-height:calc(100vh - 15rem)}html.theme--documenter-dark .search-result-link{border-radius:0.7em;transition:all 300ms}html.theme--documenter-dark .search-result-link:hover,html.theme--documenter-dark .search-result-link:focus{background-color:rgba(0,128,128,0.1)}html.theme--documenter-dark .search-result-link .property-search-result-badge,html.theme--documenter-dark .search-result-link .search-filter{transition:all 300ms}html.theme--documenter-dark .property-search-result-badge,html.theme--documenter-dark .search-filter{padding:0.15em 0.5em;font-size:0.8em;font-style:italic;text-transform:none !important;line-height:1.5;color:#f5f5f5;background-color:rgba(51,65,85,0.501961);border-radius:0.6rem}html.theme--documenter-dark .search-result-link:hover .property-search-result-badge,html.theme--documenter-dark .search-result-link:hover .search-filter,html.theme--documenter-dark .search-result-link:focus .property-search-result-badge,html.theme--documenter-dark .search-result-link:focus .search-filter{color:#333;background-color:#f1f5f9}html.theme--documenter-dark .search-filter{color:#333;background-color:#f5f5f5;transition:all 300ms}html.theme--documenter-dark .search-filter:hover,html.theme--documenter-dark .search-filter:focus{color:#333}html.theme--documenter-dark .search-filter-selected{color:#f5f5f5;background-color:rgba(139,0,139,0.5)}html.theme--documenter-dark .search-filter-selected:hover,html.theme--documenter-dark .search-filter-selected:focus{color:#f5f5f5}html.theme--documenter-dark .search-result-highlight{background-color:#ffdd57;color:black}html.theme--documenter-dark .search-divider{border-bottom:1px solid #5e6d6f}html.theme--documenter-dark .search-result-title{width:85%;color:#f5f5f5}html.theme--documenter-dark .search-result-code-title{font-size:0.875rem;font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace}html.theme--documenter-dark #search-modal .modal-card-body::-webkit-scrollbar,html.theme--documenter-dark #search-modal .filter-tabs::-webkit-scrollbar{height:10px;width:10px;background-color:transparent}html.theme--documenter-dark #search-modal .modal-card-body::-webkit-scrollbar-thumb,html.theme--documenter-dark #search-modal .filter-tabs::-webkit-scrollbar-thumb{background-color:gray;border-radius:1rem}html.theme--documenter-dark #search-modal .modal-card-body::-webkit-scrollbar-track,html.theme--documenter-dark #search-modal .filter-tabs::-webkit-scrollbar-track{-webkit-box-shadow:inset 0 0 6px rgba(0,0,0,0.6);background-color:transparent}html.theme--documenter-dark .w-100{width:100%}html.theme--documenter-dark .gap-2{gap:0.5rem}html.theme--documenter-dark .gap-4{gap:1rem}html.theme--documenter-dark .gap-8{gap:2rem}html.theme--documenter-dark{background-color:#1f2424;font-size:16px;min-width:300px;overflow-x:auto;overflow-y:scroll;text-rendering:optimizeLegibility;text-size-adjust:100%}html.theme--documenter-dark .ansi span.sgr1{font-weight:bolder}html.theme--documenter-dark .ansi span.sgr2{font-weight:lighter}html.theme--documenter-dark .ansi span.sgr3{font-style:italic}html.theme--documenter-dark .ansi span.sgr4{text-decoration:underline}html.theme--documenter-dark .ansi span.sgr7{color:#1f2424;background-color:#fff}html.theme--documenter-dark .ansi span.sgr8{color:transparent}html.theme--documenter-dark .ansi span.sgr8 span{color:transparent}html.theme--documenter-dark .ansi span.sgr9{text-decoration:line-through}html.theme--documenter-dark .ansi span.sgr30{color:#242424}html.theme--documenter-dark .ansi span.sgr31{color:#f6705f}html.theme--documenter-dark .ansi span.sgr32{color:#4fb43a}html.theme--documenter-dark .ansi span.sgr33{color:#f4c72f}html.theme--documenter-dark .ansi span.sgr34{color:#7587f0}html.theme--documenter-dark .ansi span.sgr35{color:#bc89d3}html.theme--documenter-dark .ansi span.sgr36{color:#49b6ca}html.theme--documenter-dark .ansi span.sgr37{color:#b3bdbe}html.theme--documenter-dark .ansi span.sgr40{background-color:#242424}html.theme--documenter-dark .ansi span.sgr41{background-color:#f6705f}html.theme--documenter-dark .ansi span.sgr42{background-color:#4fb43a}html.theme--documenter-dark .ansi span.sgr43{background-color:#f4c72f}html.theme--documenter-dark .ansi span.sgr44{background-color:#7587f0}html.theme--documenter-dark .ansi span.sgr45{background-color:#bc89d3}html.theme--documenter-dark .ansi span.sgr46{background-color:#49b6ca}html.theme--documenter-dark .ansi span.sgr47{background-color:#b3bdbe}html.theme--documenter-dark .ansi span.sgr90{color:#92a0a2}html.theme--documenter-dark .ansi span.sgr91{color:#ff8674}html.theme--documenter-dark .ansi span.sgr92{color:#79d462}html.theme--documenter-dark .ansi span.sgr93{color:#ffe76b}html.theme--documenter-dark .ansi span.sgr94{color:#8a98ff}html.theme--documenter-dark .ansi span.sgr95{color:#d2a4e6}html.theme--documenter-dark .ansi span.sgr96{color:#6bc8db}html.theme--documenter-dark .ansi span.sgr97{color:#ecf0f1}html.theme--documenter-dark .ansi span.sgr100{background-color:#92a0a2}html.theme--documenter-dark .ansi span.sgr101{background-color:#ff8674}html.theme--documenter-dark .ansi span.sgr102{background-color:#79d462}html.theme--documenter-dark .ansi span.sgr103{background-color:#ffe76b}html.theme--documenter-dark .ansi span.sgr104{background-color:#8a98ff}html.theme--documenter-dark .ansi span.sgr105{background-color:#d2a4e6}html.theme--documenter-dark .ansi span.sgr106{background-color:#6bc8db}html.theme--documenter-dark .ansi span.sgr107{background-color:#ecf0f1}html.theme--documenter-dark code.language-julia-repl>span.hljs-meta{color:#4fb43a;font-weight:bolder}html.theme--documenter-dark .hljs{background:#2b2b2b;color:#f8f8f2}html.theme--documenter-dark .hljs-comment,html.theme--documenter-dark .hljs-quote{color:#d4d0ab}html.theme--documenter-dark .hljs-variable,html.theme--documenter-dark .hljs-template-variable,html.theme--documenter-dark .hljs-tag,html.theme--documenter-dark .hljs-name,html.theme--documenter-dark .hljs-selector-id,html.theme--documenter-dark .hljs-selector-class,html.theme--documenter-dark .hljs-regexp,html.theme--documenter-dark .hljs-deletion{color:#ffa07a}html.theme--documenter-dark .hljs-number,html.theme--documenter-dark .hljs-built_in,html.theme--documenter-dark .hljs-literal,html.theme--documenter-dark .hljs-type,html.theme--documenter-dark .hljs-params,html.theme--documenter-dark .hljs-meta,html.theme--documenter-dark .hljs-link{color:#f5ab35}html.theme--documenter-dark .hljs-attribute{color:#ffd700}html.theme--documenter-dark .hljs-string,html.theme--documenter-dark .hljs-symbol,html.theme--documenter-dark .hljs-bullet,html.theme--documenter-dark .hljs-addition{color:#abe338}html.theme--documenter-dark .hljs-title,html.theme--documenter-dark .hljs-section{color:#00e0e0}html.theme--documenter-dark .hljs-keyword,html.theme--documenter-dark .hljs-selector-tag{color:#dcc6e0}html.theme--documenter-dark .hljs-emphasis{font-style:italic}html.theme--documenter-dark .hljs-strong{font-weight:bold}@media screen and (-ms-high-contrast: active){html.theme--documenter-dark .hljs-addition,html.theme--documenter-dark .hljs-attribute,html.theme--documenter-dark .hljs-built_in,html.theme--documenter-dark .hljs-bullet,html.theme--documenter-dark .hljs-comment,html.theme--documenter-dark .hljs-link,html.theme--documenter-dark .hljs-literal,html.theme--documenter-dark .hljs-meta,html.theme--documenter-dark .hljs-number,html.theme--documenter-dark .hljs-params,html.theme--documenter-dark .hljs-string,html.theme--documenter-dark .hljs-symbol,html.theme--documenter-dark .hljs-type,html.theme--documenter-dark .hljs-quote{color:highlight}html.theme--documenter-dark .hljs-keyword,html.theme--documenter-dark .hljs-selector-tag{font-weight:bold}}html.theme--documenter-dark .hljs-subst{color:#f8f8f2}html.theme--documenter-dark .search-result-link{border-radius:0.7em;transition:all 300ms}html.theme--documenter-dark .search-result-link:hover,html.theme--documenter-dark .search-result-link:focus{background-color:rgba(0,128,128,0.1)}html.theme--documenter-dark .search-result-link .property-search-result-badge,html.theme--documenter-dark .search-result-link .search-filter{transition:all 300ms}html.theme--documenter-dark .search-result-link:hover .property-search-result-badge,html.theme--documenter-dark .search-result-link:hover .search-filter,html.theme--documenter-dark .search-result-link:focus .property-search-result-badge,html.theme--documenter-dark .search-result-link:focus .search-filter{color:#333 !important;background-color:#f1f5f9 !important}html.theme--documenter-dark .search-result-title{color:whitesmoke}html.theme--documenter-dark .search-result-highlight{background-color:greenyellow;color:black}html.theme--documenter-dark .search-divider{border-bottom:1px solid #5e6d6f50}html.theme--documenter-dark .w-100{width:100%}html.theme--documenter-dark .gap-2{gap:0.5rem}html.theme--documenter-dark .gap-4{gap:1rem} diff --git a/previews/PR798/assets/themes/documenter-light.css b/previews/PR798/assets/themes/documenter-light.css new file mode 100644 index 0000000000..e000447e60 --- /dev/null +++ b/previews/PR798/assets/themes/documenter-light.css @@ -0,0 +1,9 @@ +.pagination-previous,.pagination-next,.pagination-link,.pagination-ellipsis,.file-cta,.file-name,.select select,.textarea,.input,#documenter .docs-sidebar form.docs-search>input,.button{-moz-appearance:none;-webkit-appearance:none;align-items:center;border:1px solid transparent;border-radius:4px;box-shadow:none;display:inline-flex;font-size:1rem;height:2.5em;justify-content:flex-start;line-height:1.5;padding-bottom:calc(0.5em - 1px);padding-left:calc(0.75em - 1px);padding-right:calc(0.75em - 1px);padding-top:calc(0.5em - 1px);position:relative;vertical-align:top}.pagination-previous:focus,.pagination-next:focus,.pagination-link:focus,.pagination-ellipsis:focus,.file-cta:focus,.file-name:focus,.select select:focus,.textarea:focus,.input:focus,#documenter .docs-sidebar form.docs-search>input:focus,.button:focus,.is-focused.pagination-previous,.is-focused.pagination-next,.is-focused.pagination-link,.is-focused.pagination-ellipsis,.is-focused.file-cta,.is-focused.file-name,.select select.is-focused,.is-focused.textarea,.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.is-focused.button,.pagination-previous:active,.pagination-next:active,.pagination-link:active,.pagination-ellipsis:active,.file-cta:active,.file-name:active,.select select:active,.textarea:active,.input:active,#documenter .docs-sidebar form.docs-search>input:active,.button:active,.is-active.pagination-previous,.is-active.pagination-next,.is-active.pagination-link,.is-active.pagination-ellipsis,.is-active.file-cta,.is-active.file-name,.select select.is-active,.is-active.textarea,.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active,.is-active.button{outline:none}.pagination-previous[disabled],.pagination-next[disabled],.pagination-link[disabled],.pagination-ellipsis[disabled],.file-cta[disabled],.file-name[disabled],.select select[disabled],.textarea[disabled],.input[disabled],#documenter .docs-sidebar form.docs-search>input[disabled],.button[disabled],fieldset[disabled] .pagination-previous,fieldset[disabled] .pagination-next,fieldset[disabled] .pagination-link,fieldset[disabled] .pagination-ellipsis,fieldset[disabled] .file-cta,fieldset[disabled] .file-name,fieldset[disabled] .select select,.select fieldset[disabled] select,fieldset[disabled] .textarea,fieldset[disabled] .input,fieldset[disabled] #documenter .docs-sidebar form.docs-search>input,#documenter .docs-sidebar fieldset[disabled] form.docs-search>input,fieldset[disabled] .button{cursor:not-allowed}.tabs,.pagination-previous,.pagination-next,.pagination-link,.pagination-ellipsis,.breadcrumb,.file,.button,.is-unselectable{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.navbar-link:not(.is-arrowless)::after,.select:not(.is-multiple):not(.is-loading)::after{border:3px solid rgba(0,0,0,0);border-radius:2px;border-right:0;border-top:0;content:" ";display:block;height:0.625em;margin-top:-0.4375em;pointer-events:none;position:absolute;top:50%;transform:rotate(-45deg);transform-origin:center;width:0.625em}.admonition:not(:last-child),.tabs:not(:last-child),.pagination:not(:last-child),.message:not(:last-child),.level:not(:last-child),.breadcrumb:not(:last-child),.block:not(:last-child),.title:not(:last-child),.subtitle:not(:last-child),.table-container:not(:last-child),.table:not(:last-child),.progress:not(:last-child),.notification:not(:last-child),.content:not(:last-child),.box:not(:last-child){margin-bottom:1.5rem}.modal-close,.delete{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-moz-appearance:none;-webkit-appearance:none;background-color:rgba(10,10,10,0.2);border:none;border-radius:9999px;cursor:pointer;pointer-events:auto;display:inline-block;flex-grow:0;flex-shrink:0;font-size:0;height:20px;max-height:20px;max-width:20px;min-height:20px;min-width:20px;outline:none;position:relative;vertical-align:top;width:20px}.modal-close::before,.delete::before,.modal-close::after,.delete::after{background-color:#fff;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}.modal-close::before,.delete::before{height:2px;width:50%}.modal-close::after,.delete::after{height:50%;width:2px}.modal-close:hover,.delete:hover,.modal-close:focus,.delete:focus{background-color:rgba(10,10,10,0.3)}.modal-close:active,.delete:active{background-color:rgba(10,10,10,0.4)}.is-small.modal-close,#documenter .docs-sidebar form.docs-search>input.modal-close,.is-small.delete,#documenter .docs-sidebar form.docs-search>input.delete{height:16px;max-height:16px;max-width:16px;min-height:16px;min-width:16px;width:16px}.is-medium.modal-close,.is-medium.delete{height:24px;max-height:24px;max-width:24px;min-height:24px;min-width:24px;width:24px}.is-large.modal-close,.is-large.delete{height:32px;max-height:32px;max-width:32px;min-height:32px;min-width:32px;width:32px}.control.is-loading::after,.select.is-loading::after,.loader,.button.is-loading::after{animation:spinAround 500ms infinite linear;border:2px solid #dbdbdb;border-radius:9999px;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:1em;position:relative;width:1em}.hero-video,.modal-background,.modal,.image.is-square img,#documenter .docs-sidebar .docs-logo>img.is-square img,.image.is-square .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-square .has-ratio,.image.is-1by1 img,#documenter .docs-sidebar .docs-logo>img.is-1by1 img,.image.is-1by1 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-1by1 .has-ratio,.image.is-5by4 img,#documenter .docs-sidebar .docs-logo>img.is-5by4 img,.image.is-5by4 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-5by4 .has-ratio,.image.is-4by3 img,#documenter .docs-sidebar .docs-logo>img.is-4by3 img,.image.is-4by3 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-4by3 .has-ratio,.image.is-3by2 img,#documenter .docs-sidebar .docs-logo>img.is-3by2 img,.image.is-3by2 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-3by2 .has-ratio,.image.is-5by3 img,#documenter .docs-sidebar .docs-logo>img.is-5by3 img,.image.is-5by3 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-5by3 .has-ratio,.image.is-16by9 img,#documenter .docs-sidebar .docs-logo>img.is-16by9 img,.image.is-16by9 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-16by9 .has-ratio,.image.is-2by1 img,#documenter .docs-sidebar .docs-logo>img.is-2by1 img,.image.is-2by1 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-2by1 .has-ratio,.image.is-3by1 img,#documenter .docs-sidebar .docs-logo>img.is-3by1 img,.image.is-3by1 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-3by1 .has-ratio,.image.is-4by5 img,#documenter .docs-sidebar .docs-logo>img.is-4by5 img,.image.is-4by5 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-4by5 .has-ratio,.image.is-3by4 img,#documenter .docs-sidebar .docs-logo>img.is-3by4 img,.image.is-3by4 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-3by4 .has-ratio,.image.is-2by3 img,#documenter .docs-sidebar .docs-logo>img.is-2by3 img,.image.is-2by3 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-2by3 .has-ratio,.image.is-3by5 img,#documenter .docs-sidebar .docs-logo>img.is-3by5 img,.image.is-3by5 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-3by5 .has-ratio,.image.is-9by16 img,#documenter .docs-sidebar .docs-logo>img.is-9by16 img,.image.is-9by16 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-9by16 .has-ratio,.image.is-1by2 img,#documenter .docs-sidebar .docs-logo>img.is-1by2 img,.image.is-1by2 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-1by2 .has-ratio,.image.is-1by3 img,#documenter .docs-sidebar .docs-logo>img.is-1by3 img,.image.is-1by3 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-1by3 .has-ratio,.is-overlay{bottom:0;left:0;position:absolute;right:0;top:0}.navbar-burger{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;color:currentColor;font-family:inherit;font-size:1em;margin:0;padding:0}.has-text-white{color:#fff !important}a.has-text-white:hover,a.has-text-white:focus{color:#e6e6e6 !important}.has-background-white{background-color:#fff !important}.has-text-black{color:#0a0a0a !important}a.has-text-black:hover,a.has-text-black:focus{color:#000 !important}.has-background-black{background-color:#0a0a0a !important}.has-text-light{color:#f5f5f5 !important}a.has-text-light:hover,a.has-text-light:focus{color:#dbdbdb !important}.has-background-light{background-color:#f5f5f5 !important}.has-text-dark{color:#363636 !important}a.has-text-dark:hover,a.has-text-dark:focus{color:#1c1c1c !important}.has-background-dark{background-color:#363636 !important}.has-text-primary{color:#4eb5de !important}a.has-text-primary:hover,a.has-text-primary:focus{color:#27a1d2 !important}.has-background-primary{background-color:#4eb5de !important}.has-text-primary-light{color:#eef8fc !important}a.has-text-primary-light:hover,a.has-text-primary-light:focus{color:#c3e6f4 !important}.has-background-primary-light{background-color:#eef8fc !important}.has-text-primary-dark{color:#1a6d8e !important}a.has-text-primary-dark:hover,a.has-text-primary-dark:focus{color:#228eb9 !important}.has-background-primary-dark{background-color:#1a6d8e !important}.has-text-link{color:#2e63b8 !important}a.has-text-link:hover,a.has-text-link:focus{color:#244d8f !important}.has-background-link{background-color:#2e63b8 !important}.has-text-link-light{color:#eff3fb !important}a.has-text-link-light:hover,a.has-text-link-light:focus{color:#c6d6f1 !important}.has-background-link-light{background-color:#eff3fb !important}.has-text-link-dark{color:#3169c4 !important}a.has-text-link-dark:hover,a.has-text-link-dark:focus{color:#5485d4 !important}.has-background-link-dark{background-color:#3169c4 !important}.has-text-info{color:#3c5dcd !important}a.has-text-info:hover,a.has-text-info:focus{color:#2c48aa !important}.has-background-info{background-color:#3c5dcd !important}.has-text-info-light{color:#eff2fb !important}a.has-text-info-light:hover,a.has-text-info-light:focus{color:#c6d0f0 !important}.has-background-info-light{background-color:#eff2fb !important}.has-text-info-dark{color:#3253c3 !important}a.has-text-info-dark:hover,a.has-text-info-dark:focus{color:#5571d3 !important}.has-background-info-dark{background-color:#3253c3 !important}.has-text-success{color:#259a12 !important}a.has-text-success:hover,a.has-text-success:focus{color:#1a6c0d !important}.has-background-success{background-color:#259a12 !important}.has-text-success-light{color:#effded !important}a.has-text-success-light:hover,a.has-text-success-light:focus{color:#c7f8bf !important}.has-background-success-light{background-color:#effded !important}.has-text-success-dark{color:#2ec016 !important}a.has-text-success-dark:hover,a.has-text-success-dark:focus{color:#3fe524 !important}.has-background-success-dark{background-color:#2ec016 !important}.has-text-warning{color:#a98800 !important}a.has-text-warning:hover,a.has-text-warning:focus{color:#765f00 !important}.has-background-warning{background-color:#a98800 !important}.has-text-warning-light{color:#fffbeb !important}a.has-text-warning-light:hover,a.has-text-warning-light:focus{color:#fff1b8 !important}.has-background-warning-light{background-color:#fffbeb !important}.has-text-warning-dark{color:#cca400 !important}a.has-text-warning-dark:hover,a.has-text-warning-dark:focus{color:#ffcd00 !important}.has-background-warning-dark{background-color:#cca400 !important}.has-text-danger{color:#cb3c33 !important}a.has-text-danger:hover,a.has-text-danger:focus{color:#a23029 !important}.has-background-danger{background-color:#cb3c33 !important}.has-text-danger-light{color:#fbefef !important}a.has-text-danger-light:hover,a.has-text-danger-light:focus{color:#f1c8c6 !important}.has-background-danger-light{background-color:#fbefef !important}.has-text-danger-dark{color:#c03930 !important}a.has-text-danger-dark:hover,a.has-text-danger-dark:focus{color:#d35850 !important}.has-background-danger-dark{background-color:#c03930 !important}.has-text-black-bis{color:#121212 !important}.has-background-black-bis{background-color:#121212 !important}.has-text-black-ter{color:#242424 !important}.has-background-black-ter{background-color:#242424 !important}.has-text-grey-darker{color:#363636 !important}.has-background-grey-darker{background-color:#363636 !important}.has-text-grey-dark{color:#4a4a4a !important}.has-background-grey-dark{background-color:#4a4a4a !important}.has-text-grey{color:#6b6b6b !important}.has-background-grey{background-color:#6b6b6b !important}.has-text-grey-light{color:#b5b5b5 !important}.has-background-grey-light{background-color:#b5b5b5 !important}.has-text-grey-lighter{color:#dbdbdb !important}.has-background-grey-lighter{background-color:#dbdbdb !important}.has-text-white-ter{color:#f5f5f5 !important}.has-background-white-ter{background-color:#f5f5f5 !important}.has-text-white-bis{color:#fafafa !important}.has-background-white-bis{background-color:#fafafa !important}.is-flex-direction-row{flex-direction:row !important}.is-flex-direction-row-reverse{flex-direction:row-reverse !important}.is-flex-direction-column{flex-direction:column !important}.is-flex-direction-column-reverse{flex-direction:column-reverse !important}.is-flex-wrap-nowrap{flex-wrap:nowrap !important}.is-flex-wrap-wrap{flex-wrap:wrap !important}.is-flex-wrap-wrap-reverse{flex-wrap:wrap-reverse !important}.is-justify-content-flex-start{justify-content:flex-start !important}.is-justify-content-flex-end{justify-content:flex-end !important}.is-justify-content-center{justify-content:center !important}.is-justify-content-space-between{justify-content:space-between !important}.is-justify-content-space-around{justify-content:space-around !important}.is-justify-content-space-evenly{justify-content:space-evenly !important}.is-justify-content-start{justify-content:start !important}.is-justify-content-end{justify-content:end !important}.is-justify-content-left{justify-content:left !important}.is-justify-content-right{justify-content:right !important}.is-align-content-flex-start{align-content:flex-start !important}.is-align-content-flex-end{align-content:flex-end !important}.is-align-content-center{align-content:center !important}.is-align-content-space-between{align-content:space-between !important}.is-align-content-space-around{align-content:space-around !important}.is-align-content-space-evenly{align-content:space-evenly !important}.is-align-content-stretch{align-content:stretch !important}.is-align-content-start{align-content:start !important}.is-align-content-end{align-content:end !important}.is-align-content-baseline{align-content:baseline !important}.is-align-items-stretch{align-items:stretch !important}.is-align-items-flex-start{align-items:flex-start !important}.is-align-items-flex-end{align-items:flex-end !important}.is-align-items-center{align-items:center !important}.is-align-items-baseline{align-items:baseline !important}.is-align-items-start{align-items:start !important}.is-align-items-end{align-items:end !important}.is-align-items-self-start{align-items:self-start !important}.is-align-items-self-end{align-items:self-end !important}.is-align-self-auto{align-self:auto !important}.is-align-self-flex-start{align-self:flex-start !important}.is-align-self-flex-end{align-self:flex-end !important}.is-align-self-center{align-self:center !important}.is-align-self-baseline{align-self:baseline !important}.is-align-self-stretch{align-self:stretch !important}.is-flex-grow-0{flex-grow:0 !important}.is-flex-grow-1{flex-grow:1 !important}.is-flex-grow-2{flex-grow:2 !important}.is-flex-grow-3{flex-grow:3 !important}.is-flex-grow-4{flex-grow:4 !important}.is-flex-grow-5{flex-grow:5 !important}.is-flex-shrink-0{flex-shrink:0 !important}.is-flex-shrink-1{flex-shrink:1 !important}.is-flex-shrink-2{flex-shrink:2 !important}.is-flex-shrink-3{flex-shrink:3 !important}.is-flex-shrink-4{flex-shrink:4 !important}.is-flex-shrink-5{flex-shrink:5 !important}.is-clearfix::after{clear:both;content:" ";display:table}.is-pulled-left{float:left !important}.is-pulled-right{float:right !important}.is-radiusless{border-radius:0 !important}.is-shadowless{box-shadow:none !important}.is-clickable{cursor:pointer !important;pointer-events:all !important}.is-clipped{overflow:hidden !important}.is-relative{position:relative !important}.is-marginless{margin:0 !important}.is-paddingless{padding:0 !important}.m-0{margin:0 !important}.mt-0{margin-top:0 !important}.mr-0{margin-right:0 !important}.mb-0{margin-bottom:0 !important}.ml-0{margin-left:0 !important}.mx-0{margin-left:0 !important;margin-right:0 !important}.my-0{margin-top:0 !important;margin-bottom:0 !important}.m-1{margin:.25rem !important}.mt-1{margin-top:.25rem !important}.mr-1{margin-right:.25rem !important}.mb-1{margin-bottom:.25rem !important}.ml-1{margin-left:.25rem !important}.mx-1{margin-left:.25rem !important;margin-right:.25rem !important}.my-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.m-2{margin:.5rem !important}.mt-2{margin-top:.5rem !important}.mr-2{margin-right:.5rem !important}.mb-2{margin-bottom:.5rem !important}.ml-2{margin-left:.5rem !important}.mx-2{margin-left:.5rem !important;margin-right:.5rem !important}.my-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.m-3{margin:.75rem !important}.mt-3{margin-top:.75rem !important}.mr-3{margin-right:.75rem !important}.mb-3{margin-bottom:.75rem !important}.ml-3{margin-left:.75rem !important}.mx-3{margin-left:.75rem !important;margin-right:.75rem !important}.my-3{margin-top:.75rem !important;margin-bottom:.75rem !important}.m-4{margin:1rem !important}.mt-4{margin-top:1rem !important}.mr-4{margin-right:1rem !important}.mb-4{margin-bottom:1rem !important}.ml-4{margin-left:1rem !important}.mx-4{margin-left:1rem !important;margin-right:1rem !important}.my-4{margin-top:1rem !important;margin-bottom:1rem !important}.m-5{margin:1.5rem !important}.mt-5{margin-top:1.5rem !important}.mr-5{margin-right:1.5rem !important}.mb-5{margin-bottom:1.5rem !important}.ml-5{margin-left:1.5rem !important}.mx-5{margin-left:1.5rem !important;margin-right:1.5rem !important}.my-5{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.m-6{margin:3rem !important}.mt-6{margin-top:3rem !important}.mr-6{margin-right:3rem !important}.mb-6{margin-bottom:3rem !important}.ml-6{margin-left:3rem !important}.mx-6{margin-left:3rem !important;margin-right:3rem !important}.my-6{margin-top:3rem !important;margin-bottom:3rem !important}.m-auto{margin:auto !important}.mt-auto{margin-top:auto !important}.mr-auto{margin-right:auto !important}.mb-auto{margin-bottom:auto !important}.ml-auto{margin-left:auto !important}.mx-auto{margin-left:auto !important;margin-right:auto !important}.my-auto{margin-top:auto !important;margin-bottom:auto !important}.p-0{padding:0 !important}.pt-0{padding-top:0 !important}.pr-0{padding-right:0 !important}.pb-0{padding-bottom:0 !important}.pl-0{padding-left:0 !important}.px-0{padding-left:0 !important;padding-right:0 !important}.py-0{padding-top:0 !important;padding-bottom:0 !important}.p-1{padding:.25rem !important}.pt-1{padding-top:.25rem !important}.pr-1{padding-right:.25rem !important}.pb-1{padding-bottom:.25rem !important}.pl-1{padding-left:.25rem !important}.px-1{padding-left:.25rem !important;padding-right:.25rem !important}.py-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.p-2{padding:.5rem !important}.pt-2{padding-top:.5rem !important}.pr-2{padding-right:.5rem !important}.pb-2{padding-bottom:.5rem !important}.pl-2{padding-left:.5rem !important}.px-2{padding-left:.5rem !important;padding-right:.5rem !important}.py-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.p-3{padding:.75rem !important}.pt-3{padding-top:.75rem !important}.pr-3{padding-right:.75rem !important}.pb-3{padding-bottom:.75rem !important}.pl-3{padding-left:.75rem !important}.px-3{padding-left:.75rem !important;padding-right:.75rem !important}.py-3{padding-top:.75rem !important;padding-bottom:.75rem !important}.p-4{padding:1rem !important}.pt-4{padding-top:1rem !important}.pr-4{padding-right:1rem !important}.pb-4{padding-bottom:1rem !important}.pl-4{padding-left:1rem !important}.px-4{padding-left:1rem !important;padding-right:1rem !important}.py-4{padding-top:1rem !important;padding-bottom:1rem !important}.p-5{padding:1.5rem !important}.pt-5{padding-top:1.5rem !important}.pr-5{padding-right:1.5rem !important}.pb-5{padding-bottom:1.5rem !important}.pl-5{padding-left:1.5rem !important}.px-5{padding-left:1.5rem !important;padding-right:1.5rem !important}.py-5{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.p-6{padding:3rem !important}.pt-6{padding-top:3rem !important}.pr-6{padding-right:3rem !important}.pb-6{padding-bottom:3rem !important}.pl-6{padding-left:3rem !important}.px-6{padding-left:3rem !important;padding-right:3rem !important}.py-6{padding-top:3rem !important;padding-bottom:3rem !important}.p-auto{padding:auto !important}.pt-auto{padding-top:auto !important}.pr-auto{padding-right:auto !important}.pb-auto{padding-bottom:auto !important}.pl-auto{padding-left:auto !important}.px-auto{padding-left:auto !important;padding-right:auto !important}.py-auto{padding-top:auto !important;padding-bottom:auto !important}.is-size-1{font-size:3rem !important}.is-size-2{font-size:2.5rem !important}.is-size-3{font-size:2rem !important}.is-size-4{font-size:1.5rem !important}.is-size-5{font-size:1.25rem !important}.is-size-6{font-size:1rem !important}.is-size-7,.docstring>section>a.docs-sourcelink{font-size:.75rem !important}@media screen and (max-width: 768px){.is-size-1-mobile{font-size:3rem !important}.is-size-2-mobile{font-size:2.5rem !important}.is-size-3-mobile{font-size:2rem !important}.is-size-4-mobile{font-size:1.5rem !important}.is-size-5-mobile{font-size:1.25rem !important}.is-size-6-mobile{font-size:1rem !important}.is-size-7-mobile{font-size:.75rem !important}}@media screen and (min-width: 769px),print{.is-size-1-tablet{font-size:3rem !important}.is-size-2-tablet{font-size:2.5rem !important}.is-size-3-tablet{font-size:2rem !important}.is-size-4-tablet{font-size:1.5rem !important}.is-size-5-tablet{font-size:1.25rem !important}.is-size-6-tablet{font-size:1rem !important}.is-size-7-tablet{font-size:.75rem !important}}@media screen and (max-width: 1055px){.is-size-1-touch{font-size:3rem !important}.is-size-2-touch{font-size:2.5rem !important}.is-size-3-touch{font-size:2rem !important}.is-size-4-touch{font-size:1.5rem !important}.is-size-5-touch{font-size:1.25rem !important}.is-size-6-touch{font-size:1rem !important}.is-size-7-touch{font-size:.75rem !important}}@media screen and (min-width: 1056px){.is-size-1-desktop{font-size:3rem !important}.is-size-2-desktop{font-size:2.5rem !important}.is-size-3-desktop{font-size:2rem !important}.is-size-4-desktop{font-size:1.5rem !important}.is-size-5-desktop{font-size:1.25rem !important}.is-size-6-desktop{font-size:1rem !important}.is-size-7-desktop{font-size:.75rem !important}}@media screen and (min-width: 1216px){.is-size-1-widescreen{font-size:3rem !important}.is-size-2-widescreen{font-size:2.5rem !important}.is-size-3-widescreen{font-size:2rem !important}.is-size-4-widescreen{font-size:1.5rem !important}.is-size-5-widescreen{font-size:1.25rem !important}.is-size-6-widescreen{font-size:1rem !important}.is-size-7-widescreen{font-size:.75rem !important}}@media screen and (min-width: 1408px){.is-size-1-fullhd{font-size:3rem !important}.is-size-2-fullhd{font-size:2.5rem !important}.is-size-3-fullhd{font-size:2rem !important}.is-size-4-fullhd{font-size:1.5rem !important}.is-size-5-fullhd{font-size:1.25rem !important}.is-size-6-fullhd{font-size:1rem !important}.is-size-7-fullhd{font-size:.75rem !important}}.has-text-centered{text-align:center !important}.has-text-justified{text-align:justify !important}.has-text-left{text-align:left !important}.has-text-right{text-align:right !important}@media screen and (max-width: 768px){.has-text-centered-mobile{text-align:center !important}}@media screen and (min-width: 769px),print{.has-text-centered-tablet{text-align:center !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-centered-tablet-only{text-align:center !important}}@media screen and (max-width: 1055px){.has-text-centered-touch{text-align:center !important}}@media screen and (min-width: 1056px){.has-text-centered-desktop{text-align:center !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-centered-desktop-only{text-align:center !important}}@media screen and (min-width: 1216px){.has-text-centered-widescreen{text-align:center !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-centered-widescreen-only{text-align:center !important}}@media screen and (min-width: 1408px){.has-text-centered-fullhd{text-align:center !important}}@media screen and (max-width: 768px){.has-text-justified-mobile{text-align:justify !important}}@media screen and (min-width: 769px),print{.has-text-justified-tablet{text-align:justify !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-justified-tablet-only{text-align:justify !important}}@media screen and (max-width: 1055px){.has-text-justified-touch{text-align:justify !important}}@media screen and (min-width: 1056px){.has-text-justified-desktop{text-align:justify !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-justified-desktop-only{text-align:justify !important}}@media screen and (min-width: 1216px){.has-text-justified-widescreen{text-align:justify !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-justified-widescreen-only{text-align:justify !important}}@media screen and (min-width: 1408px){.has-text-justified-fullhd{text-align:justify !important}}@media screen and (max-width: 768px){.has-text-left-mobile{text-align:left !important}}@media screen and (min-width: 769px),print{.has-text-left-tablet{text-align:left !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-left-tablet-only{text-align:left !important}}@media screen and (max-width: 1055px){.has-text-left-touch{text-align:left !important}}@media screen and (min-width: 1056px){.has-text-left-desktop{text-align:left !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-left-desktop-only{text-align:left !important}}@media screen and (min-width: 1216px){.has-text-left-widescreen{text-align:left !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-left-widescreen-only{text-align:left !important}}@media screen and (min-width: 1408px){.has-text-left-fullhd{text-align:left !important}}@media screen and (max-width: 768px){.has-text-right-mobile{text-align:right !important}}@media screen and (min-width: 769px),print{.has-text-right-tablet{text-align:right !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-right-tablet-only{text-align:right !important}}@media screen and (max-width: 1055px){.has-text-right-touch{text-align:right !important}}@media screen and (min-width: 1056px){.has-text-right-desktop{text-align:right !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-right-desktop-only{text-align:right !important}}@media screen and (min-width: 1216px){.has-text-right-widescreen{text-align:right !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-right-widescreen-only{text-align:right !important}}@media screen and (min-width: 1408px){.has-text-right-fullhd{text-align:right !important}}.is-capitalized{text-transform:capitalize !important}.is-lowercase{text-transform:lowercase !important}.is-uppercase{text-transform:uppercase !important}.is-italic{font-style:italic !important}.is-underlined{text-decoration:underline !important}.has-text-weight-light{font-weight:300 !important}.has-text-weight-normal{font-weight:400 !important}.has-text-weight-medium{font-weight:500 !important}.has-text-weight-semibold{font-weight:600 !important}.has-text-weight-bold{font-weight:700 !important}.is-family-primary{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-secondary{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-sans-serif{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-monospace{font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace !important}.is-family-code{font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace !important}.is-block{display:block !important}@media screen and (max-width: 768px){.is-block-mobile{display:block !important}}@media screen and (min-width: 769px),print{.is-block-tablet{display:block !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-block-tablet-only{display:block !important}}@media screen and (max-width: 1055px){.is-block-touch{display:block !important}}@media screen and (min-width: 1056px){.is-block-desktop{display:block !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-block-desktop-only{display:block !important}}@media screen and (min-width: 1216px){.is-block-widescreen{display:block !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-block-widescreen-only{display:block !important}}@media screen and (min-width: 1408px){.is-block-fullhd{display:block !important}}.is-flex{display:flex !important}@media screen and (max-width: 768px){.is-flex-mobile{display:flex !important}}@media screen and (min-width: 769px),print{.is-flex-tablet{display:flex !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-flex-tablet-only{display:flex !important}}@media screen and (max-width: 1055px){.is-flex-touch{display:flex !important}}@media screen and (min-width: 1056px){.is-flex-desktop{display:flex !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-flex-desktop-only{display:flex !important}}@media screen and (min-width: 1216px){.is-flex-widescreen{display:flex !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-flex-widescreen-only{display:flex !important}}@media screen and (min-width: 1408px){.is-flex-fullhd{display:flex !important}}.is-inline{display:inline !important}@media screen and (max-width: 768px){.is-inline-mobile{display:inline !important}}@media screen and (min-width: 769px),print{.is-inline-tablet{display:inline !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-tablet-only{display:inline !important}}@media screen and (max-width: 1055px){.is-inline-touch{display:inline !important}}@media screen and (min-width: 1056px){.is-inline-desktop{display:inline !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-desktop-only{display:inline !important}}@media screen and (min-width: 1216px){.is-inline-widescreen{display:inline !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-widescreen-only{display:inline !important}}@media screen and (min-width: 1408px){.is-inline-fullhd{display:inline !important}}.is-inline-block{display:inline-block !important}@media screen and (max-width: 768px){.is-inline-block-mobile{display:inline-block !important}}@media screen and (min-width: 769px),print{.is-inline-block-tablet{display:inline-block !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-block-tablet-only{display:inline-block !important}}@media screen and (max-width: 1055px){.is-inline-block-touch{display:inline-block !important}}@media screen and (min-width: 1056px){.is-inline-block-desktop{display:inline-block !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-block-desktop-only{display:inline-block !important}}@media screen and (min-width: 1216px){.is-inline-block-widescreen{display:inline-block !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-block-widescreen-only{display:inline-block !important}}@media screen and (min-width: 1408px){.is-inline-block-fullhd{display:inline-block !important}}.is-inline-flex{display:inline-flex !important}@media screen and (max-width: 768px){.is-inline-flex-mobile{display:inline-flex !important}}@media screen and (min-width: 769px),print{.is-inline-flex-tablet{display:inline-flex !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-flex-tablet-only{display:inline-flex !important}}@media screen and (max-width: 1055px){.is-inline-flex-touch{display:inline-flex !important}}@media screen and (min-width: 1056px){.is-inline-flex-desktop{display:inline-flex !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-flex-desktop-only{display:inline-flex !important}}@media screen and (min-width: 1216px){.is-inline-flex-widescreen{display:inline-flex !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-flex-widescreen-only{display:inline-flex !important}}@media screen and (min-width: 1408px){.is-inline-flex-fullhd{display:inline-flex !important}}.is-hidden{display:none !important}.is-sr-only{border:none !important;clip:rect(0, 0, 0, 0) !important;height:0.01em !important;overflow:hidden !important;padding:0 !important;position:absolute !important;white-space:nowrap !important;width:0.01em !important}@media screen and (max-width: 768px){.is-hidden-mobile{display:none !important}}@media screen and (min-width: 769px),print{.is-hidden-tablet{display:none !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-hidden-tablet-only{display:none !important}}@media screen and (max-width: 1055px){.is-hidden-touch{display:none !important}}@media screen and (min-width: 1056px){.is-hidden-desktop{display:none !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-hidden-desktop-only{display:none !important}}@media screen and (min-width: 1216px){.is-hidden-widescreen{display:none !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-hidden-widescreen-only{display:none !important}}@media screen and (min-width: 1408px){.is-hidden-fullhd{display:none !important}}.is-invisible{visibility:hidden !important}@media screen and (max-width: 768px){.is-invisible-mobile{visibility:hidden !important}}@media screen and (min-width: 769px),print{.is-invisible-tablet{visibility:hidden !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-invisible-tablet-only{visibility:hidden !important}}@media screen and (max-width: 1055px){.is-invisible-touch{visibility:hidden !important}}@media screen and (min-width: 1056px){.is-invisible-desktop{visibility:hidden !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-invisible-desktop-only{visibility:hidden !important}}@media screen and (min-width: 1216px){.is-invisible-widescreen{visibility:hidden !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-invisible-widescreen-only{visibility:hidden !important}}@media screen and (min-width: 1408px){.is-invisible-fullhd{visibility:hidden !important}}/*! minireset.css v0.0.6 | MIT License | github.com/jgthms/minireset.css */html,body,p,ol,ul,li,dl,dt,dd,blockquote,figure,fieldset,legend,textarea,pre,iframe,hr,h1,h2,h3,h4,h5,h6{margin:0;padding:0}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal}ul{list-style:none}button,input,select,textarea{margin:0}html{box-sizing:border-box}*,*::before,*::after{box-sizing:inherit}img,video{height:auto;max-width:100%}iframe{border:0}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}td:not([align]),th:not([align]){text-align:inherit}html{background-color:#fff;font-size:16px;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;min-width:300px;overflow-x:auto;overflow-y:scroll;text-rendering:optimizeLegibility;text-size-adjust:100%}article,aside,figure,footer,header,hgroup,section{display:block}body,button,input,optgroup,select,textarea{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif}code,pre{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto;font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace}body{color:#222;font-size:1em;font-weight:400;line-height:1.5}a{color:#2e63b8;cursor:pointer;text-decoration:none}a strong{color:currentColor}a:hover{color:#363636}code{background-color:rgba(0,0,0,0.05);color:#000;font-size:.875em;font-weight:normal;padding:.1em}hr{background-color:#f5f5f5;border:none;display:block;height:2px;margin:1.5rem 0}img{height:auto;max-width:100%}input[type="checkbox"],input[type="radio"]{vertical-align:baseline}small{font-size:.875em}span{font-style:inherit;font-weight:inherit}strong{color:#222;font-weight:700}fieldset{border:none}pre{-webkit-overflow-scrolling:touch;background-color:#f5f5f5;color:#222;font-size:.875em;overflow-x:auto;padding:1.25rem 1.5rem;white-space:pre;word-wrap:normal}pre code{background-color:transparent;color:currentColor;font-size:1em;padding:0}table td,table th{vertical-align:top}table td:not([align]),table th:not([align]){text-align:inherit}table th{color:#222}@keyframes spinAround{from{transform:rotate(0deg)}to{transform:rotate(359deg)}}.box{background-color:#fff;border-radius:6px;box-shadow:#bbb;color:#222;display:block;padding:1.25rem}a.box:hover,a.box:focus{box-shadow:0 0.5em 1em -0.125em rgba(10,10,10,0.1),0 0 0 1px #2e63b8}a.box:active{box-shadow:inset 0 1px 2px rgba(10,10,10,0.2),0 0 0 1px #2e63b8}.button{background-color:#fff;border-color:#dbdbdb;border-width:1px;color:#222;cursor:pointer;justify-content:center;padding-bottom:calc(0.5em - 1px);padding-left:1em;padding-right:1em;padding-top:calc(0.5em - 1px);text-align:center;white-space:nowrap}.button strong{color:inherit}.button .icon,.button .icon.is-small,.button #documenter .docs-sidebar form.docs-search>input.icon,#documenter .docs-sidebar .button form.docs-search>input.icon,.button .icon.is-medium,.button .icon.is-large{height:1.5em;width:1.5em}.button .icon:first-child:not(:last-child){margin-left:calc(-0.5em - 1px);margin-right:.25em}.button .icon:last-child:not(:first-child){margin-left:.25em;margin-right:calc(-0.5em - 1px)}.button .icon:first-child:last-child{margin-left:calc(-0.5em - 1px);margin-right:calc(-0.5em - 1px)}.button:hover,.button.is-hovered{border-color:#b5b5b5;color:#363636}.button:focus,.button.is-focused{border-color:#3c5dcd;color:#363636}.button:focus:not(:active),.button.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(46,99,184,0.25)}.button:active,.button.is-active{border-color:#4a4a4a;color:#363636}.button.is-text{background-color:transparent;border-color:transparent;color:#222;text-decoration:underline}.button.is-text:hover,.button.is-text.is-hovered,.button.is-text:focus,.button.is-text.is-focused{background-color:#f5f5f5;color:#222}.button.is-text:active,.button.is-text.is-active{background-color:#e8e8e8;color:#222}.button.is-text[disabled],fieldset[disabled] .button.is-text{background-color:transparent;border-color:transparent;box-shadow:none}.button.is-ghost{background:none;border-color:rgba(0,0,0,0);color:#2e63b8;text-decoration:none}.button.is-ghost:hover,.button.is-ghost.is-hovered{color:#2e63b8;text-decoration:underline}.button.is-white{background-color:#fff;border-color:transparent;color:#0a0a0a}.button.is-white:hover,.button.is-white.is-hovered{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}.button.is-white:focus,.button.is-white.is-focused{border-color:transparent;color:#0a0a0a}.button.is-white:focus:not(:active),.button.is-white.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}.button.is-white:active,.button.is-white.is-active{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}.button.is-white[disabled],fieldset[disabled] .button.is-white{background-color:#fff;border-color:#fff;box-shadow:none}.button.is-white.is-inverted{background-color:#0a0a0a;color:#fff}.button.is-white.is-inverted:hover,.button.is-white.is-inverted.is-hovered{background-color:#000}.button.is-white.is-inverted[disabled],fieldset[disabled] .button.is-white.is-inverted{background-color:#0a0a0a;border-color:transparent;box-shadow:none;color:#fff}.button.is-white.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}.button.is-white.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-white.is-outlined:hover,.button.is-white.is-outlined.is-hovered,.button.is-white.is-outlined:focus,.button.is-white.is-outlined.is-focused{background-color:#fff;border-color:#fff;color:#0a0a0a}.button.is-white.is-outlined.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-white.is-outlined.is-loading:hover::after,.button.is-white.is-outlined.is-loading.is-hovered::after,.button.is-white.is-outlined.is-loading:focus::after,.button.is-white.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}.button.is-white.is-outlined[disabled],fieldset[disabled] .button.is-white.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}.button.is-white.is-inverted.is-outlined:hover,.button.is-white.is-inverted.is-outlined.is-hovered,.button.is-white.is-inverted.is-outlined:focus,.button.is-white.is-inverted.is-outlined.is-focused{background-color:#0a0a0a;color:#fff}.button.is-white.is-inverted.is-outlined.is-loading:hover::after,.button.is-white.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-white.is-inverted.is-outlined.is-loading:focus::after,.button.is-white.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-white.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}.button.is-black{background-color:#0a0a0a;border-color:transparent;color:#fff}.button.is-black:hover,.button.is-black.is-hovered{background-color:#040404;border-color:transparent;color:#fff}.button.is-black:focus,.button.is-black.is-focused{border-color:transparent;color:#fff}.button.is-black:focus:not(:active),.button.is-black.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}.button.is-black:active,.button.is-black.is-active{background-color:#000;border-color:transparent;color:#fff}.button.is-black[disabled],fieldset[disabled] .button.is-black{background-color:#0a0a0a;border-color:#0a0a0a;box-shadow:none}.button.is-black.is-inverted{background-color:#fff;color:#0a0a0a}.button.is-black.is-inverted:hover,.button.is-black.is-inverted.is-hovered{background-color:#f2f2f2}.button.is-black.is-inverted[disabled],fieldset[disabled] .button.is-black.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#0a0a0a}.button.is-black.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}.button.is-black.is-outlined:hover,.button.is-black.is-outlined.is-hovered,.button.is-black.is-outlined:focus,.button.is-black.is-outlined.is-focused{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}.button.is-black.is-outlined.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}.button.is-black.is-outlined.is-loading:hover::after,.button.is-black.is-outlined.is-loading.is-hovered::after,.button.is-black.is-outlined.is-loading:focus::after,.button.is-black.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-black.is-outlined[disabled],fieldset[disabled] .button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}.button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-black.is-inverted.is-outlined:hover,.button.is-black.is-inverted.is-outlined.is-hovered,.button.is-black.is-inverted.is-outlined:focus,.button.is-black.is-inverted.is-outlined.is-focused{background-color:#fff;color:#0a0a0a}.button.is-black.is-inverted.is-outlined.is-loading:hover::after,.button.is-black.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-black.is-inverted.is-outlined.is-loading:focus::after,.button.is-black.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}.button.is-black.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-light{background-color:#f5f5f5;border-color:transparent;color:rgba(0,0,0,0.7)}.button.is-light:hover,.button.is-light.is-hovered{background-color:#eee;border-color:transparent;color:rgba(0,0,0,0.7)}.button.is-light:focus,.button.is-light.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}.button.is-light:focus:not(:active),.button.is-light.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(245,245,245,0.25)}.button.is-light:active,.button.is-light.is-active{background-color:#e8e8e8;border-color:transparent;color:rgba(0,0,0,0.7)}.button.is-light[disabled],fieldset[disabled] .button.is-light{background-color:#f5f5f5;border-color:#f5f5f5;box-shadow:none}.button.is-light.is-inverted{background-color:rgba(0,0,0,0.7);color:#f5f5f5}.button.is-light.is-inverted:hover,.button.is-light.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}.button.is-light.is-inverted[disabled],fieldset[disabled] .button.is-light.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#f5f5f5}.button.is-light.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}.button.is-light.is-outlined{background-color:transparent;border-color:#f5f5f5;color:#f5f5f5}.button.is-light.is-outlined:hover,.button.is-light.is-outlined.is-hovered,.button.is-light.is-outlined:focus,.button.is-light.is-outlined.is-focused{background-color:#f5f5f5;border-color:#f5f5f5;color:rgba(0,0,0,0.7)}.button.is-light.is-outlined.is-loading::after{border-color:transparent transparent #f5f5f5 #f5f5f5 !important}.button.is-light.is-outlined.is-loading:hover::after,.button.is-light.is-outlined.is-loading.is-hovered::after,.button.is-light.is-outlined.is-loading:focus::after,.button.is-light.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}.button.is-light.is-outlined[disabled],fieldset[disabled] .button.is-light.is-outlined{background-color:transparent;border-color:#f5f5f5;box-shadow:none;color:#f5f5f5}.button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}.button.is-light.is-inverted.is-outlined:hover,.button.is-light.is-inverted.is-outlined.is-hovered,.button.is-light.is-inverted.is-outlined:focus,.button.is-light.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#f5f5f5}.button.is-light.is-inverted.is-outlined.is-loading:hover::after,.button.is-light.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-light.is-inverted.is-outlined.is-loading:focus::after,.button.is-light.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #f5f5f5 #f5f5f5 !important}.button.is-light.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}.button.is-dark,.content kbd.button{background-color:#363636;border-color:transparent;color:#fff}.button.is-dark:hover,.content kbd.button:hover,.button.is-dark.is-hovered,.content kbd.button.is-hovered{background-color:#2f2f2f;border-color:transparent;color:#fff}.button.is-dark:focus,.content kbd.button:focus,.button.is-dark.is-focused,.content kbd.button.is-focused{border-color:transparent;color:#fff}.button.is-dark:focus:not(:active),.content kbd.button:focus:not(:active),.button.is-dark.is-focused:not(:active),.content kbd.button.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(54,54,54,0.25)}.button.is-dark:active,.content kbd.button:active,.button.is-dark.is-active,.content kbd.button.is-active{background-color:#292929;border-color:transparent;color:#fff}.button.is-dark[disabled],.content kbd.button[disabled],fieldset[disabled] .button.is-dark,fieldset[disabled] .content kbd.button,.content fieldset[disabled] kbd.button{background-color:#363636;border-color:#363636;box-shadow:none}.button.is-dark.is-inverted,.content kbd.button.is-inverted{background-color:#fff;color:#363636}.button.is-dark.is-inverted:hover,.content kbd.button.is-inverted:hover,.button.is-dark.is-inverted.is-hovered,.content kbd.button.is-inverted.is-hovered{background-color:#f2f2f2}.button.is-dark.is-inverted[disabled],.content kbd.button.is-inverted[disabled],fieldset[disabled] .button.is-dark.is-inverted,fieldset[disabled] .content kbd.button.is-inverted,.content fieldset[disabled] kbd.button.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#363636}.button.is-dark.is-loading::after,.content kbd.button.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-dark.is-outlined,.content kbd.button.is-outlined{background-color:transparent;border-color:#363636;color:#363636}.button.is-dark.is-outlined:hover,.content kbd.button.is-outlined:hover,.button.is-dark.is-outlined.is-hovered,.content kbd.button.is-outlined.is-hovered,.button.is-dark.is-outlined:focus,.content kbd.button.is-outlined:focus,.button.is-dark.is-outlined.is-focused,.content kbd.button.is-outlined.is-focused{background-color:#363636;border-color:#363636;color:#fff}.button.is-dark.is-outlined.is-loading::after,.content kbd.button.is-outlined.is-loading::after{border-color:transparent transparent #363636 #363636 !important}.button.is-dark.is-outlined.is-loading:hover::after,.content kbd.button.is-outlined.is-loading:hover::after,.button.is-dark.is-outlined.is-loading.is-hovered::after,.content kbd.button.is-outlined.is-loading.is-hovered::after,.button.is-dark.is-outlined.is-loading:focus::after,.content kbd.button.is-outlined.is-loading:focus::after,.button.is-dark.is-outlined.is-loading.is-focused::after,.content kbd.button.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-dark.is-outlined[disabled],.content kbd.button.is-outlined[disabled],fieldset[disabled] .button.is-dark.is-outlined,fieldset[disabled] .content kbd.button.is-outlined,.content fieldset[disabled] kbd.button.is-outlined{background-color:transparent;border-color:#363636;box-shadow:none;color:#363636}.button.is-dark.is-inverted.is-outlined,.content kbd.button.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-dark.is-inverted.is-outlined:hover,.content kbd.button.is-inverted.is-outlined:hover,.button.is-dark.is-inverted.is-outlined.is-hovered,.content kbd.button.is-inverted.is-outlined.is-hovered,.button.is-dark.is-inverted.is-outlined:focus,.content kbd.button.is-inverted.is-outlined:focus,.button.is-dark.is-inverted.is-outlined.is-focused,.content kbd.button.is-inverted.is-outlined.is-focused{background-color:#fff;color:#363636}.button.is-dark.is-inverted.is-outlined.is-loading:hover::after,.content kbd.button.is-inverted.is-outlined.is-loading:hover::after,.button.is-dark.is-inverted.is-outlined.is-loading.is-hovered::after,.content kbd.button.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-dark.is-inverted.is-outlined.is-loading:focus::after,.content kbd.button.is-inverted.is-outlined.is-loading:focus::after,.button.is-dark.is-inverted.is-outlined.is-loading.is-focused::after,.content kbd.button.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #363636 #363636 !important}.button.is-dark.is-inverted.is-outlined[disabled],.content kbd.button.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-dark.is-inverted.is-outlined,fieldset[disabled] .content kbd.button.is-inverted.is-outlined,.content fieldset[disabled] kbd.button.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-primary,.docstring>section>a.button.docs-sourcelink{background-color:#4eb5de;border-color:transparent;color:#fff}.button.is-primary:hover,.docstring>section>a.button.docs-sourcelink:hover,.button.is-primary.is-hovered,.docstring>section>a.button.is-hovered.docs-sourcelink{background-color:#43b1dc;border-color:transparent;color:#fff}.button.is-primary:focus,.docstring>section>a.button.docs-sourcelink:focus,.button.is-primary.is-focused,.docstring>section>a.button.is-focused.docs-sourcelink{border-color:transparent;color:#fff}.button.is-primary:focus:not(:active),.docstring>section>a.button.docs-sourcelink:focus:not(:active),.button.is-primary.is-focused:not(:active),.docstring>section>a.button.is-focused.docs-sourcelink:not(:active){box-shadow:0 0 0 0.125em rgba(78,181,222,0.25)}.button.is-primary:active,.docstring>section>a.button.docs-sourcelink:active,.button.is-primary.is-active,.docstring>section>a.button.is-active.docs-sourcelink{background-color:#39acda;border-color:transparent;color:#fff}.button.is-primary[disabled],.docstring>section>a.button.docs-sourcelink[disabled],fieldset[disabled] .button.is-primary,fieldset[disabled] .docstring>section>a.button.docs-sourcelink{background-color:#4eb5de;border-color:#4eb5de;box-shadow:none}.button.is-primary.is-inverted,.docstring>section>a.button.is-inverted.docs-sourcelink{background-color:#fff;color:#4eb5de}.button.is-primary.is-inverted:hover,.docstring>section>a.button.is-inverted.docs-sourcelink:hover,.button.is-primary.is-inverted.is-hovered,.docstring>section>a.button.is-inverted.is-hovered.docs-sourcelink{background-color:#f2f2f2}.button.is-primary.is-inverted[disabled],.docstring>section>a.button.is-inverted.docs-sourcelink[disabled],fieldset[disabled] .button.is-primary.is-inverted,fieldset[disabled] .docstring>section>a.button.is-inverted.docs-sourcelink{background-color:#fff;border-color:transparent;box-shadow:none;color:#4eb5de}.button.is-primary.is-loading::after,.docstring>section>a.button.is-loading.docs-sourcelink::after{border-color:transparent transparent #fff #fff !important}.button.is-primary.is-outlined,.docstring>section>a.button.is-outlined.docs-sourcelink{background-color:transparent;border-color:#4eb5de;color:#4eb5de}.button.is-primary.is-outlined:hover,.docstring>section>a.button.is-outlined.docs-sourcelink:hover,.button.is-primary.is-outlined.is-hovered,.docstring>section>a.button.is-outlined.is-hovered.docs-sourcelink,.button.is-primary.is-outlined:focus,.docstring>section>a.button.is-outlined.docs-sourcelink:focus,.button.is-primary.is-outlined.is-focused,.docstring>section>a.button.is-outlined.is-focused.docs-sourcelink{background-color:#4eb5de;border-color:#4eb5de;color:#fff}.button.is-primary.is-outlined.is-loading::after,.docstring>section>a.button.is-outlined.is-loading.docs-sourcelink::after{border-color:transparent transparent #4eb5de #4eb5de !important}.button.is-primary.is-outlined.is-loading:hover::after,.docstring>section>a.button.is-outlined.is-loading.docs-sourcelink:hover::after,.button.is-primary.is-outlined.is-loading.is-hovered::after,.docstring>section>a.button.is-outlined.is-loading.is-hovered.docs-sourcelink::after,.button.is-primary.is-outlined.is-loading:focus::after,.docstring>section>a.button.is-outlined.is-loading.docs-sourcelink:focus::after,.button.is-primary.is-outlined.is-loading.is-focused::after,.docstring>section>a.button.is-outlined.is-loading.is-focused.docs-sourcelink::after{border-color:transparent transparent #fff #fff !important}.button.is-primary.is-outlined[disabled],.docstring>section>a.button.is-outlined.docs-sourcelink[disabled],fieldset[disabled] .button.is-primary.is-outlined,fieldset[disabled] .docstring>section>a.button.is-outlined.docs-sourcelink{background-color:transparent;border-color:#4eb5de;box-shadow:none;color:#4eb5de}.button.is-primary.is-inverted.is-outlined,.docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink{background-color:transparent;border-color:#fff;color:#fff}.button.is-primary.is-inverted.is-outlined:hover,.docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink:hover,.button.is-primary.is-inverted.is-outlined.is-hovered,.docstring>section>a.button.is-inverted.is-outlined.is-hovered.docs-sourcelink,.button.is-primary.is-inverted.is-outlined:focus,.docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink:focus,.button.is-primary.is-inverted.is-outlined.is-focused,.docstring>section>a.button.is-inverted.is-outlined.is-focused.docs-sourcelink{background-color:#fff;color:#4eb5de}.button.is-primary.is-inverted.is-outlined.is-loading:hover::after,.docstring>section>a.button.is-inverted.is-outlined.is-loading.docs-sourcelink:hover::after,.button.is-primary.is-inverted.is-outlined.is-loading.is-hovered::after,.docstring>section>a.button.is-inverted.is-outlined.is-loading.is-hovered.docs-sourcelink::after,.button.is-primary.is-inverted.is-outlined.is-loading:focus::after,.docstring>section>a.button.is-inverted.is-outlined.is-loading.docs-sourcelink:focus::after,.button.is-primary.is-inverted.is-outlined.is-loading.is-focused::after,.docstring>section>a.button.is-inverted.is-outlined.is-loading.is-focused.docs-sourcelink::after{border-color:transparent transparent #4eb5de #4eb5de !important}.button.is-primary.is-inverted.is-outlined[disabled],.docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink[disabled],fieldset[disabled] .button.is-primary.is-inverted.is-outlined,fieldset[disabled] .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-primary.is-light,.docstring>section>a.button.is-light.docs-sourcelink{background-color:#eef8fc;color:#1a6d8e}.button.is-primary.is-light:hover,.docstring>section>a.button.is-light.docs-sourcelink:hover,.button.is-primary.is-light.is-hovered,.docstring>section>a.button.is-light.is-hovered.docs-sourcelink{background-color:#e3f3fa;border-color:transparent;color:#1a6d8e}.button.is-primary.is-light:active,.docstring>section>a.button.is-light.docs-sourcelink:active,.button.is-primary.is-light.is-active,.docstring>section>a.button.is-light.is-active.docs-sourcelink{background-color:#d8eff8;border-color:transparent;color:#1a6d8e}.button.is-link{background-color:#2e63b8;border-color:transparent;color:#fff}.button.is-link:hover,.button.is-link.is-hovered{background-color:#2b5eae;border-color:transparent;color:#fff}.button.is-link:focus,.button.is-link.is-focused{border-color:transparent;color:#fff}.button.is-link:focus:not(:active),.button.is-link.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(46,99,184,0.25)}.button.is-link:active,.button.is-link.is-active{background-color:#2958a4;border-color:transparent;color:#fff}.button.is-link[disabled],fieldset[disabled] .button.is-link{background-color:#2e63b8;border-color:#2e63b8;box-shadow:none}.button.is-link.is-inverted{background-color:#fff;color:#2e63b8}.button.is-link.is-inverted:hover,.button.is-link.is-inverted.is-hovered{background-color:#f2f2f2}.button.is-link.is-inverted[disabled],fieldset[disabled] .button.is-link.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#2e63b8}.button.is-link.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-link.is-outlined{background-color:transparent;border-color:#2e63b8;color:#2e63b8}.button.is-link.is-outlined:hover,.button.is-link.is-outlined.is-hovered,.button.is-link.is-outlined:focus,.button.is-link.is-outlined.is-focused{background-color:#2e63b8;border-color:#2e63b8;color:#fff}.button.is-link.is-outlined.is-loading::after{border-color:transparent transparent #2e63b8 #2e63b8 !important}.button.is-link.is-outlined.is-loading:hover::after,.button.is-link.is-outlined.is-loading.is-hovered::after,.button.is-link.is-outlined.is-loading:focus::after,.button.is-link.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-link.is-outlined[disabled],fieldset[disabled] .button.is-link.is-outlined{background-color:transparent;border-color:#2e63b8;box-shadow:none;color:#2e63b8}.button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-link.is-inverted.is-outlined:hover,.button.is-link.is-inverted.is-outlined.is-hovered,.button.is-link.is-inverted.is-outlined:focus,.button.is-link.is-inverted.is-outlined.is-focused{background-color:#fff;color:#2e63b8}.button.is-link.is-inverted.is-outlined.is-loading:hover::after,.button.is-link.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-link.is-inverted.is-outlined.is-loading:focus::after,.button.is-link.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #2e63b8 #2e63b8 !important}.button.is-link.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-link.is-light{background-color:#eff3fb;color:#3169c4}.button.is-link.is-light:hover,.button.is-link.is-light.is-hovered{background-color:#e4ecf8;border-color:transparent;color:#3169c4}.button.is-link.is-light:active,.button.is-link.is-light.is-active{background-color:#dae5f6;border-color:transparent;color:#3169c4}.button.is-info{background-color:#3c5dcd;border-color:transparent;color:#fff}.button.is-info:hover,.button.is-info.is-hovered{background-color:#3355c9;border-color:transparent;color:#fff}.button.is-info:focus,.button.is-info.is-focused{border-color:transparent;color:#fff}.button.is-info:focus:not(:active),.button.is-info.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(60,93,205,0.25)}.button.is-info:active,.button.is-info.is-active{background-color:#3151bf;border-color:transparent;color:#fff}.button.is-info[disabled],fieldset[disabled] .button.is-info{background-color:#3c5dcd;border-color:#3c5dcd;box-shadow:none}.button.is-info.is-inverted{background-color:#fff;color:#3c5dcd}.button.is-info.is-inverted:hover,.button.is-info.is-inverted.is-hovered{background-color:#f2f2f2}.button.is-info.is-inverted[disabled],fieldset[disabled] .button.is-info.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#3c5dcd}.button.is-info.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-info.is-outlined{background-color:transparent;border-color:#3c5dcd;color:#3c5dcd}.button.is-info.is-outlined:hover,.button.is-info.is-outlined.is-hovered,.button.is-info.is-outlined:focus,.button.is-info.is-outlined.is-focused{background-color:#3c5dcd;border-color:#3c5dcd;color:#fff}.button.is-info.is-outlined.is-loading::after{border-color:transparent transparent #3c5dcd #3c5dcd !important}.button.is-info.is-outlined.is-loading:hover::after,.button.is-info.is-outlined.is-loading.is-hovered::after,.button.is-info.is-outlined.is-loading:focus::after,.button.is-info.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-info.is-outlined[disabled],fieldset[disabled] .button.is-info.is-outlined{background-color:transparent;border-color:#3c5dcd;box-shadow:none;color:#3c5dcd}.button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-info.is-inverted.is-outlined:hover,.button.is-info.is-inverted.is-outlined.is-hovered,.button.is-info.is-inverted.is-outlined:focus,.button.is-info.is-inverted.is-outlined.is-focused{background-color:#fff;color:#3c5dcd}.button.is-info.is-inverted.is-outlined.is-loading:hover::after,.button.is-info.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-info.is-inverted.is-outlined.is-loading:focus::after,.button.is-info.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #3c5dcd #3c5dcd !important}.button.is-info.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-info.is-light{background-color:#eff2fb;color:#3253c3}.button.is-info.is-light:hover,.button.is-info.is-light.is-hovered{background-color:#e5e9f8;border-color:transparent;color:#3253c3}.button.is-info.is-light:active,.button.is-info.is-light.is-active{background-color:#dae1f6;border-color:transparent;color:#3253c3}.button.is-success{background-color:#259a12;border-color:transparent;color:#fff}.button.is-success:hover,.button.is-success.is-hovered{background-color:#228f11;border-color:transparent;color:#fff}.button.is-success:focus,.button.is-success.is-focused{border-color:transparent;color:#fff}.button.is-success:focus:not(:active),.button.is-success.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(37,154,18,0.25)}.button.is-success:active,.button.is-success.is-active{background-color:#20830f;border-color:transparent;color:#fff}.button.is-success[disabled],fieldset[disabled] .button.is-success{background-color:#259a12;border-color:#259a12;box-shadow:none}.button.is-success.is-inverted{background-color:#fff;color:#259a12}.button.is-success.is-inverted:hover,.button.is-success.is-inverted.is-hovered{background-color:#f2f2f2}.button.is-success.is-inverted[disabled],fieldset[disabled] .button.is-success.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#259a12}.button.is-success.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-success.is-outlined{background-color:transparent;border-color:#259a12;color:#259a12}.button.is-success.is-outlined:hover,.button.is-success.is-outlined.is-hovered,.button.is-success.is-outlined:focus,.button.is-success.is-outlined.is-focused{background-color:#259a12;border-color:#259a12;color:#fff}.button.is-success.is-outlined.is-loading::after{border-color:transparent transparent #259a12 #259a12 !important}.button.is-success.is-outlined.is-loading:hover::after,.button.is-success.is-outlined.is-loading.is-hovered::after,.button.is-success.is-outlined.is-loading:focus::after,.button.is-success.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-success.is-outlined[disabled],fieldset[disabled] .button.is-success.is-outlined{background-color:transparent;border-color:#259a12;box-shadow:none;color:#259a12}.button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-success.is-inverted.is-outlined:hover,.button.is-success.is-inverted.is-outlined.is-hovered,.button.is-success.is-inverted.is-outlined:focus,.button.is-success.is-inverted.is-outlined.is-focused{background-color:#fff;color:#259a12}.button.is-success.is-inverted.is-outlined.is-loading:hover::after,.button.is-success.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-success.is-inverted.is-outlined.is-loading:focus::after,.button.is-success.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #259a12 #259a12 !important}.button.is-success.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-success.is-light{background-color:#effded;color:#2ec016}.button.is-success.is-light:hover,.button.is-success.is-light.is-hovered{background-color:#e5fce1;border-color:transparent;color:#2ec016}.button.is-success.is-light:active,.button.is-success.is-light.is-active{background-color:#dbfad6;border-color:transparent;color:#2ec016}.button.is-warning{background-color:#a98800;border-color:transparent;color:#fff}.button.is-warning:hover,.button.is-warning.is-hovered{background-color:#9c7d00;border-color:transparent;color:#fff}.button.is-warning:focus,.button.is-warning.is-focused{border-color:transparent;color:#fff}.button.is-warning:focus:not(:active),.button.is-warning.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(169,136,0,0.25)}.button.is-warning:active,.button.is-warning.is-active{background-color:#8f7300;border-color:transparent;color:#fff}.button.is-warning[disabled],fieldset[disabled] .button.is-warning{background-color:#a98800;border-color:#a98800;box-shadow:none}.button.is-warning.is-inverted{background-color:#fff;color:#a98800}.button.is-warning.is-inverted:hover,.button.is-warning.is-inverted.is-hovered{background-color:#f2f2f2}.button.is-warning.is-inverted[disabled],fieldset[disabled] .button.is-warning.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#a98800}.button.is-warning.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-warning.is-outlined{background-color:transparent;border-color:#a98800;color:#a98800}.button.is-warning.is-outlined:hover,.button.is-warning.is-outlined.is-hovered,.button.is-warning.is-outlined:focus,.button.is-warning.is-outlined.is-focused{background-color:#a98800;border-color:#a98800;color:#fff}.button.is-warning.is-outlined.is-loading::after{border-color:transparent transparent #a98800 #a98800 !important}.button.is-warning.is-outlined.is-loading:hover::after,.button.is-warning.is-outlined.is-loading.is-hovered::after,.button.is-warning.is-outlined.is-loading:focus::after,.button.is-warning.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-warning.is-outlined[disabled],fieldset[disabled] .button.is-warning.is-outlined{background-color:transparent;border-color:#a98800;box-shadow:none;color:#a98800}.button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-warning.is-inverted.is-outlined:hover,.button.is-warning.is-inverted.is-outlined.is-hovered,.button.is-warning.is-inverted.is-outlined:focus,.button.is-warning.is-inverted.is-outlined.is-focused{background-color:#fff;color:#a98800}.button.is-warning.is-inverted.is-outlined.is-loading:hover::after,.button.is-warning.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-warning.is-inverted.is-outlined.is-loading:focus::after,.button.is-warning.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #a98800 #a98800 !important}.button.is-warning.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-warning.is-light{background-color:#fffbeb;color:#cca400}.button.is-warning.is-light:hover,.button.is-warning.is-light.is-hovered{background-color:#fff9de;border-color:transparent;color:#cca400}.button.is-warning.is-light:active,.button.is-warning.is-light.is-active{background-color:#fff6d1;border-color:transparent;color:#cca400}.button.is-danger{background-color:#cb3c33;border-color:transparent;color:#fff}.button.is-danger:hover,.button.is-danger.is-hovered{background-color:#c13930;border-color:transparent;color:#fff}.button.is-danger:focus,.button.is-danger.is-focused{border-color:transparent;color:#fff}.button.is-danger:focus:not(:active),.button.is-danger.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(203,60,51,0.25)}.button.is-danger:active,.button.is-danger.is-active{background-color:#b7362e;border-color:transparent;color:#fff}.button.is-danger[disabled],fieldset[disabled] .button.is-danger{background-color:#cb3c33;border-color:#cb3c33;box-shadow:none}.button.is-danger.is-inverted{background-color:#fff;color:#cb3c33}.button.is-danger.is-inverted:hover,.button.is-danger.is-inverted.is-hovered{background-color:#f2f2f2}.button.is-danger.is-inverted[disabled],fieldset[disabled] .button.is-danger.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#cb3c33}.button.is-danger.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-danger.is-outlined{background-color:transparent;border-color:#cb3c33;color:#cb3c33}.button.is-danger.is-outlined:hover,.button.is-danger.is-outlined.is-hovered,.button.is-danger.is-outlined:focus,.button.is-danger.is-outlined.is-focused{background-color:#cb3c33;border-color:#cb3c33;color:#fff}.button.is-danger.is-outlined.is-loading::after{border-color:transparent transparent #cb3c33 #cb3c33 !important}.button.is-danger.is-outlined.is-loading:hover::after,.button.is-danger.is-outlined.is-loading.is-hovered::after,.button.is-danger.is-outlined.is-loading:focus::after,.button.is-danger.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-danger.is-outlined[disabled],fieldset[disabled] .button.is-danger.is-outlined{background-color:transparent;border-color:#cb3c33;box-shadow:none;color:#cb3c33}.button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-danger.is-inverted.is-outlined:hover,.button.is-danger.is-inverted.is-outlined.is-hovered,.button.is-danger.is-inverted.is-outlined:focus,.button.is-danger.is-inverted.is-outlined.is-focused{background-color:#fff;color:#cb3c33}.button.is-danger.is-inverted.is-outlined.is-loading:hover::after,.button.is-danger.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-danger.is-inverted.is-outlined.is-loading:focus::after,.button.is-danger.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #cb3c33 #cb3c33 !important}.button.is-danger.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-danger.is-light{background-color:#fbefef;color:#c03930}.button.is-danger.is-light:hover,.button.is-danger.is-light.is-hovered{background-color:#f8e6e5;border-color:transparent;color:#c03930}.button.is-danger.is-light:active,.button.is-danger.is-light.is-active{background-color:#f6dcda;border-color:transparent;color:#c03930}.button.is-small,#documenter .docs-sidebar form.docs-search>input.button{font-size:.75rem}.button.is-small:not(.is-rounded),#documenter .docs-sidebar form.docs-search>input.button:not(.is-rounded){border-radius:2px}.button.is-normal{font-size:1rem}.button.is-medium{font-size:1.25rem}.button.is-large{font-size:1.5rem}.button[disabled],fieldset[disabled] .button{background-color:#fff;border-color:#dbdbdb;box-shadow:none;opacity:.5}.button.is-fullwidth{display:flex;width:100%}.button.is-loading{color:transparent !important;pointer-events:none}.button.is-loading::after{position:absolute;left:calc(50% - (1em * 0.5));top:calc(50% - (1em * 0.5));position:absolute !important}.button.is-static{background-color:#f5f5f5;border-color:#dbdbdb;color:#6b6b6b;box-shadow:none;pointer-events:none}.button.is-rounded,#documenter .docs-sidebar form.docs-search>input.button{border-radius:9999px;padding-left:calc(1em + 0.25em);padding-right:calc(1em + 0.25em)}.buttons{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}.buttons .button{margin-bottom:0.5rem}.buttons .button:not(:last-child):not(.is-fullwidth){margin-right:.5rem}.buttons:last-child{margin-bottom:-0.5rem}.buttons:not(:last-child){margin-bottom:1rem}.buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large){font-size:.75rem}.buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large):not(.is-rounded){border-radius:2px}.buttons.are-medium .button:not(.is-small):not(.is-normal):not(.is-large){font-size:1.25rem}.buttons.are-large .button:not(.is-small):not(.is-normal):not(.is-medium){font-size:1.5rem}.buttons.has-addons .button:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.buttons.has-addons .button:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0;margin-right:-1px}.buttons.has-addons .button:last-child{margin-right:0}.buttons.has-addons .button:hover,.buttons.has-addons .button.is-hovered{z-index:2}.buttons.has-addons .button:focus,.buttons.has-addons .button.is-focused,.buttons.has-addons .button:active,.buttons.has-addons .button.is-active,.buttons.has-addons .button.is-selected{z-index:3}.buttons.has-addons .button:focus:hover,.buttons.has-addons .button.is-focused:hover,.buttons.has-addons .button:active:hover,.buttons.has-addons .button.is-active:hover,.buttons.has-addons .button.is-selected:hover{z-index:4}.buttons.has-addons .button.is-expanded{flex-grow:1;flex-shrink:1}.buttons.is-centered{justify-content:center}.buttons.is-centered:not(.has-addons) .button:not(.is-fullwidth){margin-left:0.25rem;margin-right:0.25rem}.buttons.is-right{justify-content:flex-end}.buttons.is-right:not(.has-addons) .button:not(.is-fullwidth){margin-left:0.25rem;margin-right:0.25rem}@media screen and (max-width: 768px){.button.is-responsive.is-small,#documenter .docs-sidebar form.docs-search>input.is-responsive{font-size:.5625rem}.button.is-responsive,.button.is-responsive.is-normal{font-size:.65625rem}.button.is-responsive.is-medium{font-size:.75rem}.button.is-responsive.is-large{font-size:1rem}}@media screen and (min-width: 769px) and (max-width: 1055px){.button.is-responsive.is-small,#documenter .docs-sidebar form.docs-search>input.is-responsive{font-size:.65625rem}.button.is-responsive,.button.is-responsive.is-normal{font-size:.75rem}.button.is-responsive.is-medium{font-size:1rem}.button.is-responsive.is-large{font-size:1.25rem}}.container{flex-grow:1;margin:0 auto;position:relative;width:auto}.container.is-fluid{max-width:none !important;padding-left:32px;padding-right:32px;width:100%}@media screen and (min-width: 1056px){.container{max-width:992px}}@media screen and (max-width: 1215px){.container.is-widescreen:not(.is-max-desktop){max-width:1152px}}@media screen and (max-width: 1407px){.container.is-fullhd:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}@media screen and (min-width: 1216px){.container:not(.is-max-desktop){max-width:1152px}}@media screen and (min-width: 1408px){.container:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}.content li+li{margin-top:0.25em}.content p:not(:last-child),.content dl:not(:last-child),.content ol:not(:last-child),.content ul:not(:last-child),.content blockquote:not(:last-child),.content pre:not(:last-child),.content table:not(:last-child){margin-bottom:1em}.content h1,.content h2,.content h3,.content h4,.content h5,.content h6{color:#222;font-weight:600;line-height:1.125}.content h1{font-size:2em;margin-bottom:0.5em}.content h1:not(:first-child){margin-top:1em}.content h2{font-size:1.75em;margin-bottom:0.5714em}.content h2:not(:first-child){margin-top:1.1428em}.content h3{font-size:1.5em;margin-bottom:0.6666em}.content h3:not(:first-child){margin-top:1.3333em}.content h4{font-size:1.25em;margin-bottom:0.8em}.content h5{font-size:1.125em;margin-bottom:0.8888em}.content h6{font-size:1em;margin-bottom:1em}.content blockquote{background-color:#f5f5f5;border-left:5px solid #dbdbdb;padding:1.25em 1.5em}.content ol{list-style-position:outside;margin-left:2em;margin-top:1em}.content ol:not([type]){list-style-type:decimal}.content ol.is-lower-alpha:not([type]){list-style-type:lower-alpha}.content ol.is-lower-roman:not([type]){list-style-type:lower-roman}.content ol.is-upper-alpha:not([type]){list-style-type:upper-alpha}.content ol.is-upper-roman:not([type]){list-style-type:upper-roman}.content ul{list-style:disc outside;margin-left:2em;margin-top:1em}.content ul ul{list-style-type:circle;margin-top:0.5em}.content ul ul ul{list-style-type:square}.content dd{margin-left:2em}.content figure{margin-left:2em;margin-right:2em;text-align:center}.content figure:not(:first-child){margin-top:2em}.content figure:not(:last-child){margin-bottom:2em}.content figure img{display:inline-block}.content figure figcaption{font-style:italic}.content pre{-webkit-overflow-scrolling:touch;overflow-x:auto;padding:0;white-space:pre;word-wrap:normal}.content sup,.content sub{font-size:75%}.content table{width:100%}.content table td,.content table th{border:1px solid #dbdbdb;border-width:0 0 1px;padding:0.5em 0.75em;vertical-align:top}.content table th{color:#222}.content table th:not([align]){text-align:inherit}.content table thead td,.content table thead th{border-width:0 0 2px;color:#222}.content table tfoot td,.content table tfoot th{border-width:2px 0 0;color:#222}.content table tbody tr:last-child td,.content table tbody tr:last-child th{border-bottom-width:0}.content .tabs li+li{margin-top:0}.content.is-small,#documenter .docs-sidebar form.docs-search>input.content{font-size:.75rem}.content.is-normal{font-size:1rem}.content.is-medium{font-size:1.25rem}.content.is-large{font-size:1.5rem}.icon{align-items:center;display:inline-flex;justify-content:center;height:1.5rem;width:1.5rem}.icon.is-small,#documenter .docs-sidebar form.docs-search>input.icon{height:1rem;width:1rem}.icon.is-medium{height:2rem;width:2rem}.icon.is-large{height:3rem;width:3rem}.icon-text{align-items:flex-start;color:inherit;display:inline-flex;flex-wrap:wrap;line-height:1.5rem;vertical-align:top}.icon-text .icon{flex-grow:0;flex-shrink:0}.icon-text .icon:not(:last-child){margin-right:.25em}.icon-text .icon:not(:first-child){margin-left:.25em}div.icon-text{display:flex}.image,#documenter .docs-sidebar .docs-logo>img{display:block;position:relative}.image img,#documenter .docs-sidebar .docs-logo>img img{display:block;height:auto;width:100%}.image img.is-rounded,#documenter .docs-sidebar .docs-logo>img img.is-rounded{border-radius:9999px}.image.is-fullwidth,#documenter .docs-sidebar .docs-logo>img.is-fullwidth{width:100%}.image.is-square img,#documenter .docs-sidebar .docs-logo>img.is-square img,.image.is-square .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-square .has-ratio,.image.is-1by1 img,#documenter .docs-sidebar .docs-logo>img.is-1by1 img,.image.is-1by1 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-1by1 .has-ratio,.image.is-5by4 img,#documenter .docs-sidebar .docs-logo>img.is-5by4 img,.image.is-5by4 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-5by4 .has-ratio,.image.is-4by3 img,#documenter .docs-sidebar .docs-logo>img.is-4by3 img,.image.is-4by3 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-4by3 .has-ratio,.image.is-3by2 img,#documenter .docs-sidebar .docs-logo>img.is-3by2 img,.image.is-3by2 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-3by2 .has-ratio,.image.is-5by3 img,#documenter .docs-sidebar .docs-logo>img.is-5by3 img,.image.is-5by3 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-5by3 .has-ratio,.image.is-16by9 img,#documenter .docs-sidebar .docs-logo>img.is-16by9 img,.image.is-16by9 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-16by9 .has-ratio,.image.is-2by1 img,#documenter .docs-sidebar .docs-logo>img.is-2by1 img,.image.is-2by1 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-2by1 .has-ratio,.image.is-3by1 img,#documenter .docs-sidebar .docs-logo>img.is-3by1 img,.image.is-3by1 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-3by1 .has-ratio,.image.is-4by5 img,#documenter .docs-sidebar .docs-logo>img.is-4by5 img,.image.is-4by5 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-4by5 .has-ratio,.image.is-3by4 img,#documenter .docs-sidebar .docs-logo>img.is-3by4 img,.image.is-3by4 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-3by4 .has-ratio,.image.is-2by3 img,#documenter .docs-sidebar .docs-logo>img.is-2by3 img,.image.is-2by3 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-2by3 .has-ratio,.image.is-3by5 img,#documenter .docs-sidebar .docs-logo>img.is-3by5 img,.image.is-3by5 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-3by5 .has-ratio,.image.is-9by16 img,#documenter .docs-sidebar .docs-logo>img.is-9by16 img,.image.is-9by16 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-9by16 .has-ratio,.image.is-1by2 img,#documenter .docs-sidebar .docs-logo>img.is-1by2 img,.image.is-1by2 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-1by2 .has-ratio,.image.is-1by3 img,#documenter .docs-sidebar .docs-logo>img.is-1by3 img,.image.is-1by3 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-1by3 .has-ratio{height:100%;width:100%}.image.is-square,#documenter .docs-sidebar .docs-logo>img.is-square,.image.is-1by1,#documenter .docs-sidebar .docs-logo>img.is-1by1{padding-top:100%}.image.is-5by4,#documenter .docs-sidebar .docs-logo>img.is-5by4{padding-top:80%}.image.is-4by3,#documenter .docs-sidebar .docs-logo>img.is-4by3{padding-top:75%}.image.is-3by2,#documenter .docs-sidebar .docs-logo>img.is-3by2{padding-top:66.6666%}.image.is-5by3,#documenter .docs-sidebar .docs-logo>img.is-5by3{padding-top:60%}.image.is-16by9,#documenter .docs-sidebar .docs-logo>img.is-16by9{padding-top:56.25%}.image.is-2by1,#documenter .docs-sidebar .docs-logo>img.is-2by1{padding-top:50%}.image.is-3by1,#documenter .docs-sidebar .docs-logo>img.is-3by1{padding-top:33.3333%}.image.is-4by5,#documenter .docs-sidebar .docs-logo>img.is-4by5{padding-top:125%}.image.is-3by4,#documenter .docs-sidebar .docs-logo>img.is-3by4{padding-top:133.3333%}.image.is-2by3,#documenter .docs-sidebar .docs-logo>img.is-2by3{padding-top:150%}.image.is-3by5,#documenter .docs-sidebar .docs-logo>img.is-3by5{padding-top:166.6666%}.image.is-9by16,#documenter .docs-sidebar .docs-logo>img.is-9by16{padding-top:177.7777%}.image.is-1by2,#documenter .docs-sidebar .docs-logo>img.is-1by2{padding-top:200%}.image.is-1by3,#documenter .docs-sidebar .docs-logo>img.is-1by3{padding-top:300%}.image.is-16x16,#documenter .docs-sidebar .docs-logo>img.is-16x16{height:16px;width:16px}.image.is-24x24,#documenter .docs-sidebar .docs-logo>img.is-24x24{height:24px;width:24px}.image.is-32x32,#documenter .docs-sidebar .docs-logo>img.is-32x32{height:32px;width:32px}.image.is-48x48,#documenter .docs-sidebar .docs-logo>img.is-48x48{height:48px;width:48px}.image.is-64x64,#documenter .docs-sidebar .docs-logo>img.is-64x64{height:64px;width:64px}.image.is-96x96,#documenter .docs-sidebar .docs-logo>img.is-96x96{height:96px;width:96px}.image.is-128x128,#documenter .docs-sidebar .docs-logo>img.is-128x128{height:128px;width:128px}.notification{background-color:#f5f5f5;border-radius:4px;position:relative;padding:1.25rem 2.5rem 1.25rem 1.5rem}.notification a:not(.button):not(.dropdown-item){color:currentColor;text-decoration:underline}.notification strong{color:currentColor}.notification code,.notification pre{background:#fff}.notification pre code{background:transparent}.notification>.delete{right:.5rem;position:absolute;top:0.5rem}.notification .title,.notification .subtitle,.notification .content{color:currentColor}.notification.is-white{background-color:#fff;color:#0a0a0a}.notification.is-black{background-color:#0a0a0a;color:#fff}.notification.is-light{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}.notification.is-dark,.content kbd.notification{background-color:#363636;color:#fff}.notification.is-primary,.docstring>section>a.notification.docs-sourcelink{background-color:#4eb5de;color:#fff}.notification.is-primary.is-light,.docstring>section>a.notification.is-light.docs-sourcelink{background-color:#eef8fc;color:#1a6d8e}.notification.is-link{background-color:#2e63b8;color:#fff}.notification.is-link.is-light{background-color:#eff3fb;color:#3169c4}.notification.is-info{background-color:#3c5dcd;color:#fff}.notification.is-info.is-light{background-color:#eff2fb;color:#3253c3}.notification.is-success{background-color:#259a12;color:#fff}.notification.is-success.is-light{background-color:#effded;color:#2ec016}.notification.is-warning{background-color:#a98800;color:#fff}.notification.is-warning.is-light{background-color:#fffbeb;color:#cca400}.notification.is-danger{background-color:#cb3c33;color:#fff}.notification.is-danger.is-light{background-color:#fbefef;color:#c03930}.progress{-moz-appearance:none;-webkit-appearance:none;border:none;border-radius:9999px;display:block;height:1rem;overflow:hidden;padding:0;width:100%}.progress::-webkit-progress-bar{background-color:#ededed}.progress::-webkit-progress-value{background-color:#222}.progress::-moz-progress-bar{background-color:#222}.progress::-ms-fill{background-color:#222;border:none}.progress.is-white::-webkit-progress-value{background-color:#fff}.progress.is-white::-moz-progress-bar{background-color:#fff}.progress.is-white::-ms-fill{background-color:#fff}.progress.is-white:indeterminate{background-image:linear-gradient(to right, #fff 30%, #ededed 30%)}.progress.is-black::-webkit-progress-value{background-color:#0a0a0a}.progress.is-black::-moz-progress-bar{background-color:#0a0a0a}.progress.is-black::-ms-fill{background-color:#0a0a0a}.progress.is-black:indeterminate{background-image:linear-gradient(to right, #0a0a0a 30%, #ededed 30%)}.progress.is-light::-webkit-progress-value{background-color:#f5f5f5}.progress.is-light::-moz-progress-bar{background-color:#f5f5f5}.progress.is-light::-ms-fill{background-color:#f5f5f5}.progress.is-light:indeterminate{background-image:linear-gradient(to right, #f5f5f5 30%, #ededed 30%)}.progress.is-dark::-webkit-progress-value,.content kbd.progress::-webkit-progress-value{background-color:#363636}.progress.is-dark::-moz-progress-bar,.content kbd.progress::-moz-progress-bar{background-color:#363636}.progress.is-dark::-ms-fill,.content kbd.progress::-ms-fill{background-color:#363636}.progress.is-dark:indeterminate,.content kbd.progress:indeterminate{background-image:linear-gradient(to right, #363636 30%, #ededed 30%)}.progress.is-primary::-webkit-progress-value,.docstring>section>a.progress.docs-sourcelink::-webkit-progress-value{background-color:#4eb5de}.progress.is-primary::-moz-progress-bar,.docstring>section>a.progress.docs-sourcelink::-moz-progress-bar{background-color:#4eb5de}.progress.is-primary::-ms-fill,.docstring>section>a.progress.docs-sourcelink::-ms-fill{background-color:#4eb5de}.progress.is-primary:indeterminate,.docstring>section>a.progress.docs-sourcelink:indeterminate{background-image:linear-gradient(to right, #4eb5de 30%, #ededed 30%)}.progress.is-link::-webkit-progress-value{background-color:#2e63b8}.progress.is-link::-moz-progress-bar{background-color:#2e63b8}.progress.is-link::-ms-fill{background-color:#2e63b8}.progress.is-link:indeterminate{background-image:linear-gradient(to right, #2e63b8 30%, #ededed 30%)}.progress.is-info::-webkit-progress-value{background-color:#3c5dcd}.progress.is-info::-moz-progress-bar{background-color:#3c5dcd}.progress.is-info::-ms-fill{background-color:#3c5dcd}.progress.is-info:indeterminate{background-image:linear-gradient(to right, #3c5dcd 30%, #ededed 30%)}.progress.is-success::-webkit-progress-value{background-color:#259a12}.progress.is-success::-moz-progress-bar{background-color:#259a12}.progress.is-success::-ms-fill{background-color:#259a12}.progress.is-success:indeterminate{background-image:linear-gradient(to right, #259a12 30%, #ededed 30%)}.progress.is-warning::-webkit-progress-value{background-color:#a98800}.progress.is-warning::-moz-progress-bar{background-color:#a98800}.progress.is-warning::-ms-fill{background-color:#a98800}.progress.is-warning:indeterminate{background-image:linear-gradient(to right, #a98800 30%, #ededed 30%)}.progress.is-danger::-webkit-progress-value{background-color:#cb3c33}.progress.is-danger::-moz-progress-bar{background-color:#cb3c33}.progress.is-danger::-ms-fill{background-color:#cb3c33}.progress.is-danger:indeterminate{background-image:linear-gradient(to right, #cb3c33 30%, #ededed 30%)}.progress:indeterminate{animation-duration:1.5s;animation-iteration-count:infinite;animation-name:moveIndeterminate;animation-timing-function:linear;background-color:#ededed;background-image:linear-gradient(to right, #222 30%, #ededed 30%);background-position:top left;background-repeat:no-repeat;background-size:150% 150%}.progress:indeterminate::-webkit-progress-bar{background-color:transparent}.progress:indeterminate::-moz-progress-bar{background-color:transparent}.progress:indeterminate::-ms-fill{animation-name:none}.progress.is-small,#documenter .docs-sidebar form.docs-search>input.progress{height:.75rem}.progress.is-medium{height:1.25rem}.progress.is-large{height:1.5rem}@keyframes moveIndeterminate{from{background-position:200% 0}to{background-position:-200% 0}}.table{background-color:#fff;color:#222}.table td,.table th{border:1px solid #dbdbdb;border-width:0 0 1px;padding:0.5em 0.75em;vertical-align:top}.table td.is-white,.table th.is-white{background-color:#fff;border-color:#fff;color:#0a0a0a}.table td.is-black,.table th.is-black{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}.table td.is-light,.table th.is-light{background-color:#f5f5f5;border-color:#f5f5f5;color:rgba(0,0,0,0.7)}.table td.is-dark,.table th.is-dark{background-color:#363636;border-color:#363636;color:#fff}.table td.is-primary,.table th.is-primary{background-color:#4eb5de;border-color:#4eb5de;color:#fff}.table td.is-link,.table th.is-link{background-color:#2e63b8;border-color:#2e63b8;color:#fff}.table td.is-info,.table th.is-info{background-color:#3c5dcd;border-color:#3c5dcd;color:#fff}.table td.is-success,.table th.is-success{background-color:#259a12;border-color:#259a12;color:#fff}.table td.is-warning,.table th.is-warning{background-color:#a98800;border-color:#a98800;color:#fff}.table td.is-danger,.table th.is-danger{background-color:#cb3c33;border-color:#cb3c33;color:#fff}.table td.is-narrow,.table th.is-narrow{white-space:nowrap;width:1%}.table td.is-selected,.table th.is-selected{background-color:#4eb5de;color:#fff}.table td.is-selected a,.table td.is-selected strong,.table th.is-selected a,.table th.is-selected strong{color:currentColor}.table td.is-vcentered,.table th.is-vcentered{vertical-align:middle}.table th{color:#222}.table th:not([align]){text-align:left}.table tr.is-selected{background-color:#4eb5de;color:#fff}.table tr.is-selected a,.table tr.is-selected strong{color:currentColor}.table tr.is-selected td,.table tr.is-selected th{border-color:#fff;color:currentColor}.table thead{background-color:rgba(0,0,0,0)}.table thead td,.table thead th{border-width:0 0 2px;color:#222}.table tfoot{background-color:rgba(0,0,0,0)}.table tfoot td,.table tfoot th{border-width:2px 0 0;color:#222}.table tbody{background-color:rgba(0,0,0,0)}.table tbody tr:last-child td,.table tbody tr:last-child th{border-bottom-width:0}.table.is-bordered td,.table.is-bordered th{border-width:1px}.table.is-bordered tr:last-child td,.table.is-bordered tr:last-child th{border-bottom-width:1px}.table.is-fullwidth{width:100%}.table.is-hoverable tbody tr:not(.is-selected):hover{background-color:#fafafa}.table.is-hoverable.is-striped tbody tr:not(.is-selected):hover{background-color:#fafafa}.table.is-hoverable.is-striped tbody tr:not(.is-selected):hover:nth-child(even){background-color:#f5f5f5}.table.is-narrow td,.table.is-narrow th{padding:0.25em 0.5em}.table.is-striped tbody tr:not(.is-selected):nth-child(even){background-color:#fafafa}.table-container{-webkit-overflow-scrolling:touch;overflow:auto;overflow-y:hidden;max-width:100%}.tags{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}.tags .tag,.tags .content kbd,.content .tags kbd,.tags .docstring>section>a.docs-sourcelink{margin-bottom:0.5rem}.tags .tag:not(:last-child),.tags .content kbd:not(:last-child),.content .tags kbd:not(:last-child),.tags .docstring>section>a.docs-sourcelink:not(:last-child){margin-right:.5rem}.tags:last-child{margin-bottom:-0.5rem}.tags:not(:last-child){margin-bottom:1rem}.tags.are-medium .tag:not(.is-normal):not(.is-large),.tags.are-medium .content kbd:not(.is-normal):not(.is-large),.content .tags.are-medium kbd:not(.is-normal):not(.is-large),.tags.are-medium .docstring>section>a.docs-sourcelink:not(.is-normal):not(.is-large){font-size:1rem}.tags.are-large .tag:not(.is-normal):not(.is-medium),.tags.are-large .content kbd:not(.is-normal):not(.is-medium),.content .tags.are-large kbd:not(.is-normal):not(.is-medium),.tags.are-large .docstring>section>a.docs-sourcelink:not(.is-normal):not(.is-medium){font-size:1.25rem}.tags.is-centered{justify-content:center}.tags.is-centered .tag,.tags.is-centered .content kbd,.content .tags.is-centered kbd,.tags.is-centered .docstring>section>a.docs-sourcelink{margin-right:0.25rem;margin-left:0.25rem}.tags.is-right{justify-content:flex-end}.tags.is-right .tag:not(:first-child),.tags.is-right .content kbd:not(:first-child),.content .tags.is-right kbd:not(:first-child),.tags.is-right .docstring>section>a.docs-sourcelink:not(:first-child){margin-left:0.5rem}.tags.is-right .tag:not(:last-child),.tags.is-right .content kbd:not(:last-child),.content .tags.is-right kbd:not(:last-child),.tags.is-right .docstring>section>a.docs-sourcelink:not(:last-child){margin-right:0}.tags.has-addons .tag,.tags.has-addons .content kbd,.content .tags.has-addons kbd,.tags.has-addons .docstring>section>a.docs-sourcelink{margin-right:0}.tags.has-addons .tag:not(:first-child),.tags.has-addons .content kbd:not(:first-child),.content .tags.has-addons kbd:not(:first-child),.tags.has-addons .docstring>section>a.docs-sourcelink:not(:first-child){margin-left:0;border-top-left-radius:0;border-bottom-left-radius:0}.tags.has-addons .tag:not(:last-child),.tags.has-addons .content kbd:not(:last-child),.content .tags.has-addons kbd:not(:last-child),.tags.has-addons .docstring>section>a.docs-sourcelink:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.tag:not(body),.content kbd:not(body),.docstring>section>a.docs-sourcelink:not(body){align-items:center;background-color:#f5f5f5;border-radius:4px;color:#222;display:inline-flex;font-size:.75rem;height:2em;justify-content:center;line-height:1.5;padding-left:0.75em;padding-right:0.75em;white-space:nowrap}.tag:not(body) .delete,.content kbd:not(body) .delete,.docstring>section>a.docs-sourcelink:not(body) .delete{margin-left:.25rem;margin-right:-.375rem}.tag.is-white:not(body),.content kbd.is-white:not(body),.docstring>section>a.docs-sourcelink.is-white:not(body){background-color:#fff;color:#0a0a0a}.tag.is-black:not(body),.content kbd.is-black:not(body),.docstring>section>a.docs-sourcelink.is-black:not(body){background-color:#0a0a0a;color:#fff}.tag.is-light:not(body),.content kbd.is-light:not(body),.docstring>section>a.docs-sourcelink.is-light:not(body){background-color:#f5f5f5;color:rgba(0,0,0,0.7)}.tag.is-dark:not(body),.content kbd:not(body),.docstring>section>a.docs-sourcelink.is-dark:not(body),.content .docstring>section>kbd:not(body){background-color:#363636;color:#fff}.tag.is-primary:not(body),.content kbd.is-primary:not(body),.docstring>section>a.docs-sourcelink:not(body){background-color:#4eb5de;color:#fff}.tag.is-primary.is-light:not(body),.content kbd.is-primary.is-light:not(body),.docstring>section>a.docs-sourcelink.is-light:not(body){background-color:#eef8fc;color:#1a6d8e}.tag.is-link:not(body),.content kbd.is-link:not(body),.docstring>section>a.docs-sourcelink.is-link:not(body){background-color:#2e63b8;color:#fff}.tag.is-link.is-light:not(body),.content kbd.is-link.is-light:not(body),.docstring>section>a.docs-sourcelink.is-link.is-light:not(body){background-color:#eff3fb;color:#3169c4}.tag.is-info:not(body),.content kbd.is-info:not(body),.docstring>section>a.docs-sourcelink.is-info:not(body){background-color:#3c5dcd;color:#fff}.tag.is-info.is-light:not(body),.content kbd.is-info.is-light:not(body),.docstring>section>a.docs-sourcelink.is-info.is-light:not(body){background-color:#eff2fb;color:#3253c3}.tag.is-success:not(body),.content kbd.is-success:not(body),.docstring>section>a.docs-sourcelink.is-success:not(body){background-color:#259a12;color:#fff}.tag.is-success.is-light:not(body),.content kbd.is-success.is-light:not(body),.docstring>section>a.docs-sourcelink.is-success.is-light:not(body){background-color:#effded;color:#2ec016}.tag.is-warning:not(body),.content kbd.is-warning:not(body),.docstring>section>a.docs-sourcelink.is-warning:not(body){background-color:#a98800;color:#fff}.tag.is-warning.is-light:not(body),.content kbd.is-warning.is-light:not(body),.docstring>section>a.docs-sourcelink.is-warning.is-light:not(body){background-color:#fffbeb;color:#cca400}.tag.is-danger:not(body),.content kbd.is-danger:not(body),.docstring>section>a.docs-sourcelink.is-danger:not(body){background-color:#cb3c33;color:#fff}.tag.is-danger.is-light:not(body),.content kbd.is-danger.is-light:not(body),.docstring>section>a.docs-sourcelink.is-danger.is-light:not(body){background-color:#fbefef;color:#c03930}.tag.is-normal:not(body),.content kbd.is-normal:not(body),.docstring>section>a.docs-sourcelink.is-normal:not(body){font-size:.75rem}.tag.is-medium:not(body),.content kbd.is-medium:not(body),.docstring>section>a.docs-sourcelink.is-medium:not(body){font-size:1rem}.tag.is-large:not(body),.content kbd.is-large:not(body),.docstring>section>a.docs-sourcelink.is-large:not(body){font-size:1.25rem}.tag:not(body) .icon:first-child:not(:last-child),.content kbd:not(body) .icon:first-child:not(:last-child),.docstring>section>a.docs-sourcelink:not(body) .icon:first-child:not(:last-child){margin-left:-.375em;margin-right:.1875em}.tag:not(body) .icon:last-child:not(:first-child),.content kbd:not(body) .icon:last-child:not(:first-child),.docstring>section>a.docs-sourcelink:not(body) .icon:last-child:not(:first-child){margin-left:.1875em;margin-right:-.375em}.tag:not(body) .icon:first-child:last-child,.content kbd:not(body) .icon:first-child:last-child,.docstring>section>a.docs-sourcelink:not(body) .icon:first-child:last-child{margin-left:-.375em;margin-right:-.375em}.tag.is-delete:not(body),.content kbd.is-delete:not(body),.docstring>section>a.docs-sourcelink.is-delete:not(body){margin-left:1px;padding:0;position:relative;width:2em}.tag.is-delete:not(body)::before,.content kbd.is-delete:not(body)::before,.docstring>section>a.docs-sourcelink.is-delete:not(body)::before,.tag.is-delete:not(body)::after,.content kbd.is-delete:not(body)::after,.docstring>section>a.docs-sourcelink.is-delete:not(body)::after{background-color:currentColor;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}.tag.is-delete:not(body)::before,.content kbd.is-delete:not(body)::before,.docstring>section>a.docs-sourcelink.is-delete:not(body)::before{height:1px;width:50%}.tag.is-delete:not(body)::after,.content kbd.is-delete:not(body)::after,.docstring>section>a.docs-sourcelink.is-delete:not(body)::after{height:50%;width:1px}.tag.is-delete:not(body):hover,.content kbd.is-delete:not(body):hover,.docstring>section>a.docs-sourcelink.is-delete:not(body):hover,.tag.is-delete:not(body):focus,.content kbd.is-delete:not(body):focus,.docstring>section>a.docs-sourcelink.is-delete:not(body):focus{background-color:#e8e8e8}.tag.is-delete:not(body):active,.content kbd.is-delete:not(body):active,.docstring>section>a.docs-sourcelink.is-delete:not(body):active{background-color:#dbdbdb}.tag.is-rounded:not(body),#documenter .docs-sidebar form.docs-search>input:not(body),.content kbd.is-rounded:not(body),#documenter .docs-sidebar .content form.docs-search>input:not(body),.docstring>section>a.docs-sourcelink.is-rounded:not(body){border-radius:9999px}a.tag:hover,.docstring>section>a.docs-sourcelink:hover{text-decoration:underline}.title,.subtitle{word-break:break-word}.title em,.title span,.subtitle em,.subtitle span{font-weight:inherit}.title sub,.subtitle sub{font-size:.75em}.title sup,.subtitle sup{font-size:.75em}.title .tag,.title .content kbd,.content .title kbd,.title .docstring>section>a.docs-sourcelink,.subtitle .tag,.subtitle .content kbd,.content .subtitle kbd,.subtitle .docstring>section>a.docs-sourcelink{vertical-align:middle}.title{color:#222;font-size:2rem;font-weight:600;line-height:1.125}.title strong{color:inherit;font-weight:inherit}.title:not(.is-spaced)+.subtitle{margin-top:-1.25rem}.title.is-1{font-size:3rem}.title.is-2{font-size:2.5rem}.title.is-3{font-size:2rem}.title.is-4{font-size:1.5rem}.title.is-5{font-size:1.25rem}.title.is-6{font-size:1rem}.title.is-7{font-size:.75rem}.subtitle{color:#222;font-size:1.25rem;font-weight:400;line-height:1.25}.subtitle strong{color:#222;font-weight:600}.subtitle:not(.is-spaced)+.title{margin-top:-1.25rem}.subtitle.is-1{font-size:3rem}.subtitle.is-2{font-size:2.5rem}.subtitle.is-3{font-size:2rem}.subtitle.is-4{font-size:1.5rem}.subtitle.is-5{font-size:1.25rem}.subtitle.is-6{font-size:1rem}.subtitle.is-7{font-size:.75rem}.heading{display:block;font-size:11px;letter-spacing:1px;margin-bottom:5px;text-transform:uppercase}.number{align-items:center;background-color:#f5f5f5;border-radius:9999px;display:inline-flex;font-size:1.25rem;height:2em;justify-content:center;margin-right:1.5rem;min-width:2.5em;padding:0.25rem 0.5rem;text-align:center;vertical-align:top}.select select,.textarea,.input,#documenter .docs-sidebar form.docs-search>input{background-color:#fff;border-color:#dbdbdb;border-radius:4px;color:#222}.select select::-moz-placeholder,.textarea::-moz-placeholder,.input::-moz-placeholder,#documenter .docs-sidebar form.docs-search>input::-moz-placeholder{color:#707070}.select select::-webkit-input-placeholder,.textarea::-webkit-input-placeholder,.input::-webkit-input-placeholder,#documenter .docs-sidebar form.docs-search>input::-webkit-input-placeholder{color:#707070}.select select:-moz-placeholder,.textarea:-moz-placeholder,.input:-moz-placeholder,#documenter .docs-sidebar form.docs-search>input:-moz-placeholder{color:#707070}.select select:-ms-input-placeholder,.textarea:-ms-input-placeholder,.input:-ms-input-placeholder,#documenter .docs-sidebar form.docs-search>input:-ms-input-placeholder{color:#707070}.select select:hover,.textarea:hover,.input:hover,#documenter .docs-sidebar form.docs-search>input:hover,.select select.is-hovered,.is-hovered.textarea,.is-hovered.input,#documenter .docs-sidebar form.docs-search>input.is-hovered{border-color:#b5b5b5}.select select:focus,.textarea:focus,.input:focus,#documenter .docs-sidebar form.docs-search>input:focus,.select select.is-focused,.is-focused.textarea,.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.select select:active,.textarea:active,.input:active,#documenter .docs-sidebar form.docs-search>input:active,.select select.is-active,.is-active.textarea,.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active{border-color:#2e63b8;box-shadow:0 0 0 0.125em rgba(46,99,184,0.25)}.select select[disabled],.textarea[disabled],.input[disabled],#documenter .docs-sidebar form.docs-search>input[disabled],fieldset[disabled] .select select,.select fieldset[disabled] select,fieldset[disabled] .textarea,fieldset[disabled] .input,fieldset[disabled] #documenter .docs-sidebar form.docs-search>input,#documenter .docs-sidebar fieldset[disabled] form.docs-search>input{background-color:#f5f5f5;border-color:#f5f5f5;box-shadow:none;color:#6b6b6b}.select select[disabled]::-moz-placeholder,.textarea[disabled]::-moz-placeholder,.input[disabled]::-moz-placeholder,#documenter .docs-sidebar form.docs-search>input[disabled]::-moz-placeholder,fieldset[disabled] .select select::-moz-placeholder,.select fieldset[disabled] select::-moz-placeholder,fieldset[disabled] .textarea::-moz-placeholder,fieldset[disabled] .input::-moz-placeholder,fieldset[disabled] #documenter .docs-sidebar form.docs-search>input::-moz-placeholder,#documenter .docs-sidebar fieldset[disabled] form.docs-search>input::-moz-placeholder{color:rgba(107,107,107,0.3)}.select select[disabled]::-webkit-input-placeholder,.textarea[disabled]::-webkit-input-placeholder,.input[disabled]::-webkit-input-placeholder,#documenter .docs-sidebar form.docs-search>input[disabled]::-webkit-input-placeholder,fieldset[disabled] .select select::-webkit-input-placeholder,.select fieldset[disabled] select::-webkit-input-placeholder,fieldset[disabled] .textarea::-webkit-input-placeholder,fieldset[disabled] .input::-webkit-input-placeholder,fieldset[disabled] #documenter .docs-sidebar form.docs-search>input::-webkit-input-placeholder,#documenter .docs-sidebar fieldset[disabled] form.docs-search>input::-webkit-input-placeholder{color:rgba(107,107,107,0.3)}.select select[disabled]:-moz-placeholder,.textarea[disabled]:-moz-placeholder,.input[disabled]:-moz-placeholder,#documenter .docs-sidebar form.docs-search>input[disabled]:-moz-placeholder,fieldset[disabled] .select select:-moz-placeholder,.select fieldset[disabled] select:-moz-placeholder,fieldset[disabled] .textarea:-moz-placeholder,fieldset[disabled] .input:-moz-placeholder,fieldset[disabled] #documenter .docs-sidebar form.docs-search>input:-moz-placeholder,#documenter .docs-sidebar fieldset[disabled] form.docs-search>input:-moz-placeholder{color:rgba(107,107,107,0.3)}.select select[disabled]:-ms-input-placeholder,.textarea[disabled]:-ms-input-placeholder,.input[disabled]:-ms-input-placeholder,#documenter .docs-sidebar form.docs-search>input[disabled]:-ms-input-placeholder,fieldset[disabled] .select select:-ms-input-placeholder,.select fieldset[disabled] select:-ms-input-placeholder,fieldset[disabled] .textarea:-ms-input-placeholder,fieldset[disabled] .input:-ms-input-placeholder,fieldset[disabled] #documenter .docs-sidebar form.docs-search>input:-ms-input-placeholder,#documenter .docs-sidebar fieldset[disabled] form.docs-search>input:-ms-input-placeholder{color:rgba(107,107,107,0.3)}.textarea,.input,#documenter .docs-sidebar form.docs-search>input{box-shadow:inset 0 0.0625em 0.125em rgba(10,10,10,0.05);max-width:100%;width:100%}.textarea[readonly],.input[readonly],#documenter .docs-sidebar form.docs-search>input[readonly]{box-shadow:none}.is-white.textarea,.is-white.input,#documenter .docs-sidebar form.docs-search>input.is-white{border-color:#fff}.is-white.textarea:focus,.is-white.input:focus,#documenter .docs-sidebar form.docs-search>input.is-white:focus,.is-white.is-focused.textarea,.is-white.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.is-white.textarea:active,.is-white.input:active,#documenter .docs-sidebar form.docs-search>input.is-white:active,.is-white.is-active.textarea,.is-white.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}.is-black.textarea,.is-black.input,#documenter .docs-sidebar form.docs-search>input.is-black{border-color:#0a0a0a}.is-black.textarea:focus,.is-black.input:focus,#documenter .docs-sidebar form.docs-search>input.is-black:focus,.is-black.is-focused.textarea,.is-black.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.is-black.textarea:active,.is-black.input:active,#documenter .docs-sidebar form.docs-search>input.is-black:active,.is-black.is-active.textarea,.is-black.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}.is-light.textarea,.is-light.input,#documenter .docs-sidebar form.docs-search>input.is-light{border-color:#f5f5f5}.is-light.textarea:focus,.is-light.input:focus,#documenter .docs-sidebar form.docs-search>input.is-light:focus,.is-light.is-focused.textarea,.is-light.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.is-light.textarea:active,.is-light.input:active,#documenter .docs-sidebar form.docs-search>input.is-light:active,.is-light.is-active.textarea,.is-light.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(245,245,245,0.25)}.is-dark.textarea,.content kbd.textarea,.is-dark.input,#documenter .docs-sidebar form.docs-search>input.is-dark,.content kbd.input{border-color:#363636}.is-dark.textarea:focus,.content kbd.textarea:focus,.is-dark.input:focus,#documenter .docs-sidebar form.docs-search>input.is-dark:focus,.content kbd.input:focus,.is-dark.is-focused.textarea,.content kbd.is-focused.textarea,.is-dark.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.content kbd.is-focused.input,#documenter .docs-sidebar .content form.docs-search>input.is-focused,.is-dark.textarea:active,.content kbd.textarea:active,.is-dark.input:active,#documenter .docs-sidebar form.docs-search>input.is-dark:active,.content kbd.input:active,.is-dark.is-active.textarea,.content kbd.is-active.textarea,.is-dark.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active,.content kbd.is-active.input,#documenter .docs-sidebar .content form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(54,54,54,0.25)}.is-primary.textarea,.docstring>section>a.textarea.docs-sourcelink,.is-primary.input,#documenter .docs-sidebar form.docs-search>input.is-primary,.docstring>section>a.input.docs-sourcelink{border-color:#4eb5de}.is-primary.textarea:focus,.docstring>section>a.textarea.docs-sourcelink:focus,.is-primary.input:focus,#documenter .docs-sidebar form.docs-search>input.is-primary:focus,.docstring>section>a.input.docs-sourcelink:focus,.is-primary.is-focused.textarea,.docstring>section>a.is-focused.textarea.docs-sourcelink,.is-primary.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.docstring>section>a.is-focused.input.docs-sourcelink,.is-primary.textarea:active,.docstring>section>a.textarea.docs-sourcelink:active,.is-primary.input:active,#documenter .docs-sidebar form.docs-search>input.is-primary:active,.docstring>section>a.input.docs-sourcelink:active,.is-primary.is-active.textarea,.docstring>section>a.is-active.textarea.docs-sourcelink,.is-primary.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active,.docstring>section>a.is-active.input.docs-sourcelink{box-shadow:0 0 0 0.125em rgba(78,181,222,0.25)}.is-link.textarea,.is-link.input,#documenter .docs-sidebar form.docs-search>input.is-link{border-color:#2e63b8}.is-link.textarea:focus,.is-link.input:focus,#documenter .docs-sidebar form.docs-search>input.is-link:focus,.is-link.is-focused.textarea,.is-link.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.is-link.textarea:active,.is-link.input:active,#documenter .docs-sidebar form.docs-search>input.is-link:active,.is-link.is-active.textarea,.is-link.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(46,99,184,0.25)}.is-info.textarea,.is-info.input,#documenter .docs-sidebar form.docs-search>input.is-info{border-color:#3c5dcd}.is-info.textarea:focus,.is-info.input:focus,#documenter .docs-sidebar form.docs-search>input.is-info:focus,.is-info.is-focused.textarea,.is-info.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.is-info.textarea:active,.is-info.input:active,#documenter .docs-sidebar form.docs-search>input.is-info:active,.is-info.is-active.textarea,.is-info.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(60,93,205,0.25)}.is-success.textarea,.is-success.input,#documenter .docs-sidebar form.docs-search>input.is-success{border-color:#259a12}.is-success.textarea:focus,.is-success.input:focus,#documenter .docs-sidebar form.docs-search>input.is-success:focus,.is-success.is-focused.textarea,.is-success.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.is-success.textarea:active,.is-success.input:active,#documenter .docs-sidebar form.docs-search>input.is-success:active,.is-success.is-active.textarea,.is-success.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(37,154,18,0.25)}.is-warning.textarea,.is-warning.input,#documenter .docs-sidebar form.docs-search>input.is-warning{border-color:#a98800}.is-warning.textarea:focus,.is-warning.input:focus,#documenter .docs-sidebar form.docs-search>input.is-warning:focus,.is-warning.is-focused.textarea,.is-warning.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.is-warning.textarea:active,.is-warning.input:active,#documenter .docs-sidebar form.docs-search>input.is-warning:active,.is-warning.is-active.textarea,.is-warning.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(169,136,0,0.25)}.is-danger.textarea,.is-danger.input,#documenter .docs-sidebar form.docs-search>input.is-danger{border-color:#cb3c33}.is-danger.textarea:focus,.is-danger.input:focus,#documenter .docs-sidebar form.docs-search>input.is-danger:focus,.is-danger.is-focused.textarea,.is-danger.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.is-danger.textarea:active,.is-danger.input:active,#documenter .docs-sidebar form.docs-search>input.is-danger:active,.is-danger.is-active.textarea,.is-danger.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(203,60,51,0.25)}.is-small.textarea,.is-small.input,#documenter .docs-sidebar form.docs-search>input{border-radius:2px;font-size:.75rem}.is-medium.textarea,.is-medium.input,#documenter .docs-sidebar form.docs-search>input.is-medium{font-size:1.25rem}.is-large.textarea,.is-large.input,#documenter .docs-sidebar form.docs-search>input.is-large{font-size:1.5rem}.is-fullwidth.textarea,.is-fullwidth.input,#documenter .docs-sidebar form.docs-search>input.is-fullwidth{display:block;width:100%}.is-inline.textarea,.is-inline.input,#documenter .docs-sidebar form.docs-search>input.is-inline{display:inline;width:auto}.input.is-rounded,#documenter .docs-sidebar form.docs-search>input{border-radius:9999px;padding-left:calc(calc(0.75em - 1px) + 0.375em);padding-right:calc(calc(0.75em - 1px) + 0.375em)}.input.is-static,#documenter .docs-sidebar form.docs-search>input.is-static{background-color:transparent;border-color:transparent;box-shadow:none;padding-left:0;padding-right:0}.textarea{display:block;max-width:100%;min-width:100%;padding:calc(0.75em - 1px);resize:vertical}.textarea:not([rows]){max-height:40em;min-height:8em}.textarea[rows]{height:initial}.textarea.has-fixed-size{resize:none}.radio,.checkbox{cursor:pointer;display:inline-block;line-height:1.25;position:relative}.radio input,.checkbox input{cursor:pointer}.radio:hover,.checkbox:hover{color:#222}.radio[disabled],.checkbox[disabled],fieldset[disabled] .radio,fieldset[disabled] .checkbox,.radio input[disabled],.checkbox input[disabled]{color:#6b6b6b;cursor:not-allowed}.radio+.radio{margin-left:.5em}.select{display:inline-block;max-width:100%;position:relative;vertical-align:top}.select:not(.is-multiple){height:2.5em}.select:not(.is-multiple):not(.is-loading)::after{border-color:#2e63b8;right:1.125em;z-index:4}.select.is-rounded select,#documenter .docs-sidebar form.docs-search>input.select select{border-radius:9999px;padding-left:1em}.select select{cursor:pointer;display:block;font-size:1em;max-width:100%;outline:none}.select select::-ms-expand{display:none}.select select[disabled]:hover,fieldset[disabled] .select select:hover{border-color:#f5f5f5}.select select:not([multiple]){padding-right:2.5em}.select select[multiple]{height:auto;padding:0}.select select[multiple] option{padding:0.5em 1em}.select:not(.is-multiple):not(.is-loading):hover::after{border-color:#222}.select.is-white:not(:hover)::after{border-color:#fff}.select.is-white select{border-color:#fff}.select.is-white select:hover,.select.is-white select.is-hovered{border-color:#f2f2f2}.select.is-white select:focus,.select.is-white select.is-focused,.select.is-white select:active,.select.is-white select.is-active{box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}.select.is-black:not(:hover)::after{border-color:#0a0a0a}.select.is-black select{border-color:#0a0a0a}.select.is-black select:hover,.select.is-black select.is-hovered{border-color:#000}.select.is-black select:focus,.select.is-black select.is-focused,.select.is-black select:active,.select.is-black select.is-active{box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}.select.is-light:not(:hover)::after{border-color:#f5f5f5}.select.is-light select{border-color:#f5f5f5}.select.is-light select:hover,.select.is-light select.is-hovered{border-color:#e8e8e8}.select.is-light select:focus,.select.is-light select.is-focused,.select.is-light select:active,.select.is-light select.is-active{box-shadow:0 0 0 0.125em rgba(245,245,245,0.25)}.select.is-dark:not(:hover)::after,.content kbd.select:not(:hover)::after{border-color:#363636}.select.is-dark select,.content kbd.select select{border-color:#363636}.select.is-dark select:hover,.content kbd.select select:hover,.select.is-dark select.is-hovered,.content kbd.select select.is-hovered{border-color:#292929}.select.is-dark select:focus,.content kbd.select select:focus,.select.is-dark select.is-focused,.content kbd.select select.is-focused,.select.is-dark select:active,.content kbd.select select:active,.select.is-dark select.is-active,.content kbd.select select.is-active{box-shadow:0 0 0 0.125em rgba(54,54,54,0.25)}.select.is-primary:not(:hover)::after,.docstring>section>a.select.docs-sourcelink:not(:hover)::after{border-color:#4eb5de}.select.is-primary select,.docstring>section>a.select.docs-sourcelink select{border-color:#4eb5de}.select.is-primary select:hover,.docstring>section>a.select.docs-sourcelink select:hover,.select.is-primary select.is-hovered,.docstring>section>a.select.docs-sourcelink select.is-hovered{border-color:#39acda}.select.is-primary select:focus,.docstring>section>a.select.docs-sourcelink select:focus,.select.is-primary select.is-focused,.docstring>section>a.select.docs-sourcelink select.is-focused,.select.is-primary select:active,.docstring>section>a.select.docs-sourcelink select:active,.select.is-primary select.is-active,.docstring>section>a.select.docs-sourcelink select.is-active{box-shadow:0 0 0 0.125em rgba(78,181,222,0.25)}.select.is-link:not(:hover)::after{border-color:#2e63b8}.select.is-link select{border-color:#2e63b8}.select.is-link select:hover,.select.is-link select.is-hovered{border-color:#2958a4}.select.is-link select:focus,.select.is-link select.is-focused,.select.is-link select:active,.select.is-link select.is-active{box-shadow:0 0 0 0.125em rgba(46,99,184,0.25)}.select.is-info:not(:hover)::after{border-color:#3c5dcd}.select.is-info select{border-color:#3c5dcd}.select.is-info select:hover,.select.is-info select.is-hovered{border-color:#3151bf}.select.is-info select:focus,.select.is-info select.is-focused,.select.is-info select:active,.select.is-info select.is-active{box-shadow:0 0 0 0.125em rgba(60,93,205,0.25)}.select.is-success:not(:hover)::after{border-color:#259a12}.select.is-success select{border-color:#259a12}.select.is-success select:hover,.select.is-success select.is-hovered{border-color:#20830f}.select.is-success select:focus,.select.is-success select.is-focused,.select.is-success select:active,.select.is-success select.is-active{box-shadow:0 0 0 0.125em rgba(37,154,18,0.25)}.select.is-warning:not(:hover)::after{border-color:#a98800}.select.is-warning select{border-color:#a98800}.select.is-warning select:hover,.select.is-warning select.is-hovered{border-color:#8f7300}.select.is-warning select:focus,.select.is-warning select.is-focused,.select.is-warning select:active,.select.is-warning select.is-active{box-shadow:0 0 0 0.125em rgba(169,136,0,0.25)}.select.is-danger:not(:hover)::after{border-color:#cb3c33}.select.is-danger select{border-color:#cb3c33}.select.is-danger select:hover,.select.is-danger select.is-hovered{border-color:#b7362e}.select.is-danger select:focus,.select.is-danger select.is-focused,.select.is-danger select:active,.select.is-danger select.is-active{box-shadow:0 0 0 0.125em rgba(203,60,51,0.25)}.select.is-small,#documenter .docs-sidebar form.docs-search>input.select{border-radius:2px;font-size:.75rem}.select.is-medium{font-size:1.25rem}.select.is-large{font-size:1.5rem}.select.is-disabled::after{border-color:#6b6b6b !important;opacity:0.5}.select.is-fullwidth{width:100%}.select.is-fullwidth select{width:100%}.select.is-loading::after{margin-top:0;position:absolute;right:.625em;top:0.625em;transform:none}.select.is-loading.is-small:after,#documenter .docs-sidebar form.docs-search>input.is-loading:after{font-size:.75rem}.select.is-loading.is-medium:after{font-size:1.25rem}.select.is-loading.is-large:after{font-size:1.5rem}.file{align-items:stretch;display:flex;justify-content:flex-start;position:relative}.file.is-white .file-cta{background-color:#fff;border-color:transparent;color:#0a0a0a}.file.is-white:hover .file-cta,.file.is-white.is-hovered .file-cta{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}.file.is-white:focus .file-cta,.file.is-white.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(255,255,255,0.25);color:#0a0a0a}.file.is-white:active .file-cta,.file.is-white.is-active .file-cta{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}.file.is-black .file-cta{background-color:#0a0a0a;border-color:transparent;color:#fff}.file.is-black:hover .file-cta,.file.is-black.is-hovered .file-cta{background-color:#040404;border-color:transparent;color:#fff}.file.is-black:focus .file-cta,.file.is-black.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(10,10,10,0.25);color:#fff}.file.is-black:active .file-cta,.file.is-black.is-active .file-cta{background-color:#000;border-color:transparent;color:#fff}.file.is-light .file-cta{background-color:#f5f5f5;border-color:transparent;color:rgba(0,0,0,0.7)}.file.is-light:hover .file-cta,.file.is-light.is-hovered .file-cta{background-color:#eee;border-color:transparent;color:rgba(0,0,0,0.7)}.file.is-light:focus .file-cta,.file.is-light.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(245,245,245,0.25);color:rgba(0,0,0,0.7)}.file.is-light:active .file-cta,.file.is-light.is-active .file-cta{background-color:#e8e8e8;border-color:transparent;color:rgba(0,0,0,0.7)}.file.is-dark .file-cta,.content kbd.file .file-cta{background-color:#363636;border-color:transparent;color:#fff}.file.is-dark:hover .file-cta,.content kbd.file:hover .file-cta,.file.is-dark.is-hovered .file-cta,.content kbd.file.is-hovered .file-cta{background-color:#2f2f2f;border-color:transparent;color:#fff}.file.is-dark:focus .file-cta,.content kbd.file:focus .file-cta,.file.is-dark.is-focused .file-cta,.content kbd.file.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(54,54,54,0.25);color:#fff}.file.is-dark:active .file-cta,.content kbd.file:active .file-cta,.file.is-dark.is-active .file-cta,.content kbd.file.is-active .file-cta{background-color:#292929;border-color:transparent;color:#fff}.file.is-primary .file-cta,.docstring>section>a.file.docs-sourcelink .file-cta{background-color:#4eb5de;border-color:transparent;color:#fff}.file.is-primary:hover .file-cta,.docstring>section>a.file.docs-sourcelink:hover .file-cta,.file.is-primary.is-hovered .file-cta,.docstring>section>a.file.is-hovered.docs-sourcelink .file-cta{background-color:#43b1dc;border-color:transparent;color:#fff}.file.is-primary:focus .file-cta,.docstring>section>a.file.docs-sourcelink:focus .file-cta,.file.is-primary.is-focused .file-cta,.docstring>section>a.file.is-focused.docs-sourcelink .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(78,181,222,0.25);color:#fff}.file.is-primary:active .file-cta,.docstring>section>a.file.docs-sourcelink:active .file-cta,.file.is-primary.is-active .file-cta,.docstring>section>a.file.is-active.docs-sourcelink .file-cta{background-color:#39acda;border-color:transparent;color:#fff}.file.is-link .file-cta{background-color:#2e63b8;border-color:transparent;color:#fff}.file.is-link:hover .file-cta,.file.is-link.is-hovered .file-cta{background-color:#2b5eae;border-color:transparent;color:#fff}.file.is-link:focus .file-cta,.file.is-link.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(46,99,184,0.25);color:#fff}.file.is-link:active .file-cta,.file.is-link.is-active .file-cta{background-color:#2958a4;border-color:transparent;color:#fff}.file.is-info .file-cta{background-color:#3c5dcd;border-color:transparent;color:#fff}.file.is-info:hover .file-cta,.file.is-info.is-hovered .file-cta{background-color:#3355c9;border-color:transparent;color:#fff}.file.is-info:focus .file-cta,.file.is-info.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(60,93,205,0.25);color:#fff}.file.is-info:active .file-cta,.file.is-info.is-active .file-cta{background-color:#3151bf;border-color:transparent;color:#fff}.file.is-success .file-cta{background-color:#259a12;border-color:transparent;color:#fff}.file.is-success:hover .file-cta,.file.is-success.is-hovered .file-cta{background-color:#228f11;border-color:transparent;color:#fff}.file.is-success:focus .file-cta,.file.is-success.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(37,154,18,0.25);color:#fff}.file.is-success:active .file-cta,.file.is-success.is-active .file-cta{background-color:#20830f;border-color:transparent;color:#fff}.file.is-warning .file-cta{background-color:#a98800;border-color:transparent;color:#fff}.file.is-warning:hover .file-cta,.file.is-warning.is-hovered .file-cta{background-color:#9c7d00;border-color:transparent;color:#fff}.file.is-warning:focus .file-cta,.file.is-warning.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(169,136,0,0.25);color:#fff}.file.is-warning:active .file-cta,.file.is-warning.is-active .file-cta{background-color:#8f7300;border-color:transparent;color:#fff}.file.is-danger .file-cta{background-color:#cb3c33;border-color:transparent;color:#fff}.file.is-danger:hover .file-cta,.file.is-danger.is-hovered .file-cta{background-color:#c13930;border-color:transparent;color:#fff}.file.is-danger:focus .file-cta,.file.is-danger.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(203,60,51,0.25);color:#fff}.file.is-danger:active .file-cta,.file.is-danger.is-active .file-cta{background-color:#b7362e;border-color:transparent;color:#fff}.file.is-small,#documenter .docs-sidebar form.docs-search>input.file{font-size:.75rem}.file.is-normal{font-size:1rem}.file.is-medium{font-size:1.25rem}.file.is-medium .file-icon .fa{font-size:21px}.file.is-large{font-size:1.5rem}.file.is-large .file-icon .fa{font-size:28px}.file.has-name .file-cta{border-bottom-right-radius:0;border-top-right-radius:0}.file.has-name .file-name{border-bottom-left-radius:0;border-top-left-radius:0}.file.has-name.is-empty .file-cta{border-radius:4px}.file.has-name.is-empty .file-name{display:none}.file.is-boxed .file-label{flex-direction:column}.file.is-boxed .file-cta{flex-direction:column;height:auto;padding:1em 3em}.file.is-boxed .file-name{border-width:0 1px 1px}.file.is-boxed .file-icon{height:1.5em;width:1.5em}.file.is-boxed .file-icon .fa{font-size:21px}.file.is-boxed.is-small .file-icon .fa,#documenter .docs-sidebar form.docs-search>input.is-boxed .file-icon .fa{font-size:14px}.file.is-boxed.is-medium .file-icon .fa{font-size:28px}.file.is-boxed.is-large .file-icon .fa{font-size:35px}.file.is-boxed.has-name .file-cta{border-radius:4px 4px 0 0}.file.is-boxed.has-name .file-name{border-radius:0 0 4px 4px;border-width:0 1px 1px}.file.is-centered{justify-content:center}.file.is-fullwidth .file-label{width:100%}.file.is-fullwidth .file-name{flex-grow:1;max-width:none}.file.is-right{justify-content:flex-end}.file.is-right .file-cta{border-radius:0 4px 4px 0}.file.is-right .file-name{border-radius:4px 0 0 4px;border-width:1px 0 1px 1px;order:-1}.file-label{align-items:stretch;display:flex;cursor:pointer;justify-content:flex-start;overflow:hidden;position:relative}.file-label:hover .file-cta{background-color:#eee;color:#222}.file-label:hover .file-name{border-color:#d5d5d5}.file-label:active .file-cta{background-color:#e8e8e8;color:#222}.file-label:active .file-name{border-color:#cfcfcf}.file-input{height:100%;left:0;opacity:0;outline:none;position:absolute;top:0;width:100%}.file-cta,.file-name{border-color:#dbdbdb;border-radius:4px;font-size:1em;padding-left:1em;padding-right:1em;white-space:nowrap}.file-cta{background-color:#f5f5f5;color:#222}.file-name{border-color:#dbdbdb;border-style:solid;border-width:1px 1px 1px 0;display:block;max-width:16em;overflow:hidden;text-align:inherit;text-overflow:ellipsis}.file-icon{align-items:center;display:flex;height:1em;justify-content:center;margin-right:.5em;width:1em}.file-icon .fa{font-size:14px}.label{color:#222;display:block;font-size:1rem;font-weight:700}.label:not(:last-child){margin-bottom:0.5em}.label.is-small,#documenter .docs-sidebar form.docs-search>input.label{font-size:.75rem}.label.is-medium{font-size:1.25rem}.label.is-large{font-size:1.5rem}.help{display:block;font-size:.75rem;margin-top:0.25rem}.help.is-white{color:#fff}.help.is-black{color:#0a0a0a}.help.is-light{color:#f5f5f5}.help.is-dark,.content kbd.help{color:#363636}.help.is-primary,.docstring>section>a.help.docs-sourcelink{color:#4eb5de}.help.is-link{color:#2e63b8}.help.is-info{color:#3c5dcd}.help.is-success{color:#259a12}.help.is-warning{color:#a98800}.help.is-danger{color:#cb3c33}.field:not(:last-child){margin-bottom:0.75rem}.field.has-addons{display:flex;justify-content:flex-start}.field.has-addons .control:not(:last-child){margin-right:-1px}.field.has-addons .control:not(:first-child):not(:last-child) .button,.field.has-addons .control:not(:first-child):not(:last-child) .input,.field.has-addons .control:not(:first-child):not(:last-child) #documenter .docs-sidebar form.docs-search>input,#documenter .docs-sidebar .field.has-addons .control:not(:first-child):not(:last-child) form.docs-search>input,.field.has-addons .control:not(:first-child):not(:last-child) .select select{border-radius:0}.field.has-addons .control:first-child:not(:only-child) .button,.field.has-addons .control:first-child:not(:only-child) .input,.field.has-addons .control:first-child:not(:only-child) #documenter .docs-sidebar form.docs-search>input,#documenter .docs-sidebar .field.has-addons .control:first-child:not(:only-child) form.docs-search>input,.field.has-addons .control:first-child:not(:only-child) .select select{border-bottom-right-radius:0;border-top-right-radius:0}.field.has-addons .control:last-child:not(:only-child) .button,.field.has-addons .control:last-child:not(:only-child) .input,.field.has-addons .control:last-child:not(:only-child) #documenter .docs-sidebar form.docs-search>input,#documenter .docs-sidebar .field.has-addons .control:last-child:not(:only-child) form.docs-search>input,.field.has-addons .control:last-child:not(:only-child) .select select{border-bottom-left-radius:0;border-top-left-radius:0}.field.has-addons .control .button:not([disabled]):hover,.field.has-addons .control .button.is-hovered:not([disabled]),.field.has-addons .control .input:not([disabled]):hover,.field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):hover,#documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):hover,.field.has-addons .control .input.is-hovered:not([disabled]),.field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-hovered:not([disabled]),#documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-hovered:not([disabled]),.field.has-addons .control .select select:not([disabled]):hover,.field.has-addons .control .select select.is-hovered:not([disabled]){z-index:2}.field.has-addons .control .button:not([disabled]):focus,.field.has-addons .control .button.is-focused:not([disabled]),.field.has-addons .control .button:not([disabled]):active,.field.has-addons .control .button.is-active:not([disabled]),.field.has-addons .control .input:not([disabled]):focus,.field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):focus,#documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):focus,.field.has-addons .control .input.is-focused:not([disabled]),.field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-focused:not([disabled]),#documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-focused:not([disabled]),.field.has-addons .control .input:not([disabled]):active,.field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):active,#documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):active,.field.has-addons .control .input.is-active:not([disabled]),.field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-active:not([disabled]),#documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-active:not([disabled]),.field.has-addons .control .select select:not([disabled]):focus,.field.has-addons .control .select select.is-focused:not([disabled]),.field.has-addons .control .select select:not([disabled]):active,.field.has-addons .control .select select.is-active:not([disabled]){z-index:3}.field.has-addons .control .button:not([disabled]):focus:hover,.field.has-addons .control .button.is-focused:not([disabled]):hover,.field.has-addons .control .button:not([disabled]):active:hover,.field.has-addons .control .button.is-active:not([disabled]):hover,.field.has-addons .control .input:not([disabled]):focus:hover,.field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):focus:hover,#documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):focus:hover,.field.has-addons .control .input.is-focused:not([disabled]):hover,.field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-focused:not([disabled]):hover,#documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-focused:not([disabled]):hover,.field.has-addons .control .input:not([disabled]):active:hover,.field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):active:hover,#documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):active:hover,.field.has-addons .control .input.is-active:not([disabled]):hover,.field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-active:not([disabled]):hover,#documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-active:not([disabled]):hover,.field.has-addons .control .select select:not([disabled]):focus:hover,.field.has-addons .control .select select.is-focused:not([disabled]):hover,.field.has-addons .control .select select:not([disabled]):active:hover,.field.has-addons .control .select select.is-active:not([disabled]):hover{z-index:4}.field.has-addons .control.is-expanded{flex-grow:1;flex-shrink:1}.field.has-addons.has-addons-centered{justify-content:center}.field.has-addons.has-addons-right{justify-content:flex-end}.field.has-addons.has-addons-fullwidth .control{flex-grow:1;flex-shrink:0}.field.is-grouped{display:flex;justify-content:flex-start}.field.is-grouped>.control{flex-shrink:0}.field.is-grouped>.control:not(:last-child){margin-bottom:0;margin-right:.75rem}.field.is-grouped>.control.is-expanded{flex-grow:1;flex-shrink:1}.field.is-grouped.is-grouped-centered{justify-content:center}.field.is-grouped.is-grouped-right{justify-content:flex-end}.field.is-grouped.is-grouped-multiline{flex-wrap:wrap}.field.is-grouped.is-grouped-multiline>.control:last-child,.field.is-grouped.is-grouped-multiline>.control:not(:last-child){margin-bottom:0.75rem}.field.is-grouped.is-grouped-multiline:last-child{margin-bottom:-0.75rem}.field.is-grouped.is-grouped-multiline:not(:last-child){margin-bottom:0}@media screen and (min-width: 769px),print{.field.is-horizontal{display:flex}}.field-label .label{font-size:inherit}@media screen and (max-width: 768px){.field-label{margin-bottom:0.5rem}}@media screen and (min-width: 769px),print{.field-label{flex-basis:0;flex-grow:1;flex-shrink:0;margin-right:1.5rem;text-align:right}.field-label.is-small,#documenter .docs-sidebar form.docs-search>input.field-label{font-size:.75rem;padding-top:0.375em}.field-label.is-normal{padding-top:0.375em}.field-label.is-medium{font-size:1.25rem;padding-top:0.375em}.field-label.is-large{font-size:1.5rem;padding-top:0.375em}}.field-body .field .field{margin-bottom:0}@media screen and (min-width: 769px),print{.field-body{display:flex;flex-basis:0;flex-grow:5;flex-shrink:1}.field-body .field{margin-bottom:0}.field-body>.field{flex-shrink:1}.field-body>.field:not(.is-narrow){flex-grow:1}.field-body>.field:not(:last-child){margin-right:.75rem}}.control{box-sizing:border-box;clear:both;font-size:1rem;position:relative;text-align:inherit}.control.has-icons-left .input:focus~.icon,.control.has-icons-left #documenter .docs-sidebar form.docs-search>input:focus~.icon,#documenter .docs-sidebar .control.has-icons-left form.docs-search>input:focus~.icon,.control.has-icons-left .select:focus~.icon,.control.has-icons-right .input:focus~.icon,.control.has-icons-right #documenter .docs-sidebar form.docs-search>input:focus~.icon,#documenter .docs-sidebar .control.has-icons-right form.docs-search>input:focus~.icon,.control.has-icons-right .select:focus~.icon{color:#222}.control.has-icons-left .input.is-small~.icon,.control.has-icons-left #documenter .docs-sidebar form.docs-search>input~.icon,#documenter .docs-sidebar .control.has-icons-left form.docs-search>input~.icon,.control.has-icons-left .select.is-small~.icon,.control.has-icons-right .input.is-small~.icon,.control.has-icons-right #documenter .docs-sidebar form.docs-search>input~.icon,#documenter .docs-sidebar .control.has-icons-right form.docs-search>input~.icon,.control.has-icons-right .select.is-small~.icon{font-size:.75rem}.control.has-icons-left .input.is-medium~.icon,.control.has-icons-left #documenter .docs-sidebar form.docs-search>input.is-medium~.icon,#documenter .docs-sidebar .control.has-icons-left form.docs-search>input.is-medium~.icon,.control.has-icons-left .select.is-medium~.icon,.control.has-icons-right .input.is-medium~.icon,.control.has-icons-right #documenter .docs-sidebar form.docs-search>input.is-medium~.icon,#documenter .docs-sidebar .control.has-icons-right form.docs-search>input.is-medium~.icon,.control.has-icons-right .select.is-medium~.icon{font-size:1.25rem}.control.has-icons-left .input.is-large~.icon,.control.has-icons-left #documenter .docs-sidebar form.docs-search>input.is-large~.icon,#documenter .docs-sidebar .control.has-icons-left form.docs-search>input.is-large~.icon,.control.has-icons-left .select.is-large~.icon,.control.has-icons-right .input.is-large~.icon,.control.has-icons-right #documenter .docs-sidebar form.docs-search>input.is-large~.icon,#documenter .docs-sidebar .control.has-icons-right form.docs-search>input.is-large~.icon,.control.has-icons-right .select.is-large~.icon{font-size:1.5rem}.control.has-icons-left .icon,.control.has-icons-right .icon{color:#dbdbdb;height:2.5em;pointer-events:none;position:absolute;top:0;width:2.5em;z-index:4}.control.has-icons-left .input,.control.has-icons-left #documenter .docs-sidebar form.docs-search>input,#documenter .docs-sidebar .control.has-icons-left form.docs-search>input,.control.has-icons-left .select select{padding-left:2.5em}.control.has-icons-left .icon.is-left{left:0}.control.has-icons-right .input,.control.has-icons-right #documenter .docs-sidebar form.docs-search>input,#documenter .docs-sidebar .control.has-icons-right form.docs-search>input,.control.has-icons-right .select select{padding-right:2.5em}.control.has-icons-right .icon.is-right{right:0}.control.is-loading::after{position:absolute !important;right:.625em;top:0.625em;z-index:4}.control.is-loading.is-small:after,#documenter .docs-sidebar form.docs-search>input.is-loading:after{font-size:.75rem}.control.is-loading.is-medium:after{font-size:1.25rem}.control.is-loading.is-large:after{font-size:1.5rem}.breadcrumb{font-size:1rem;white-space:nowrap}.breadcrumb a{align-items:center;color:#2e63b8;display:flex;justify-content:center;padding:0 .75em}.breadcrumb a:hover{color:#363636}.breadcrumb li{align-items:center;display:flex}.breadcrumb li:first-child a{padding-left:0}.breadcrumb li.is-active a{color:#222;cursor:default;pointer-events:none}.breadcrumb li+li::before{color:#b5b5b5;content:"\0002f"}.breadcrumb ul,.breadcrumb ol{align-items:flex-start;display:flex;flex-wrap:wrap;justify-content:flex-start}.breadcrumb .icon:first-child{margin-right:.5em}.breadcrumb .icon:last-child{margin-left:.5em}.breadcrumb.is-centered ol,.breadcrumb.is-centered ul{justify-content:center}.breadcrumb.is-right ol,.breadcrumb.is-right ul{justify-content:flex-end}.breadcrumb.is-small,#documenter .docs-sidebar form.docs-search>input.breadcrumb{font-size:.75rem}.breadcrumb.is-medium{font-size:1.25rem}.breadcrumb.is-large{font-size:1.5rem}.breadcrumb.has-arrow-separator li+li::before{content:"\02192"}.breadcrumb.has-bullet-separator li+li::before{content:"\02022"}.breadcrumb.has-dot-separator li+li::before{content:"\000b7"}.breadcrumb.has-succeeds-separator li+li::before{content:"\0227B"}.card{background-color:#fff;border-radius:.25rem;box-shadow:#bbb;color:#222;max-width:100%;position:relative}.card-footer:first-child,.card-content:first-child,.card-header:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.card-footer:last-child,.card-content:last-child,.card-header:last-child{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}.card-header{background-color:rgba(0,0,0,0);align-items:stretch;box-shadow:0 0.125em 0.25em rgba(10,10,10,0.1);display:flex}.card-header-title{align-items:center;color:#222;display:flex;flex-grow:1;font-weight:700;padding:0.75rem 1rem}.card-header-title.is-centered{justify-content:center}.card-header-icon{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;color:currentColor;font-family:inherit;font-size:1em;margin:0;padding:0;align-items:center;cursor:pointer;display:flex;justify-content:center;padding:0.75rem 1rem}.card-image{display:block;position:relative}.card-image:first-child img{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.card-image:last-child img{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}.card-content{background-color:rgba(0,0,0,0);padding:1.5rem}.card-footer{background-color:rgba(0,0,0,0);border-top:1px solid #ededed;align-items:stretch;display:flex}.card-footer-item{align-items:center;display:flex;flex-basis:0;flex-grow:1;flex-shrink:0;justify-content:center;padding:.75rem}.card-footer-item:not(:last-child){border-right:1px solid #ededed}.card .media:not(:last-child){margin-bottom:1.5rem}.dropdown{display:inline-flex;position:relative;vertical-align:top}.dropdown.is-active .dropdown-menu,.dropdown.is-hoverable:hover .dropdown-menu{display:block}.dropdown.is-right .dropdown-menu{left:auto;right:0}.dropdown.is-up .dropdown-menu{bottom:100%;padding-bottom:4px;padding-top:initial;top:auto}.dropdown-menu{display:none;left:0;min-width:12rem;padding-top:4px;position:absolute;top:100%;z-index:20}.dropdown-content{background-color:#fff;border-radius:4px;box-shadow:#bbb;padding-bottom:.5rem;padding-top:.5rem}.dropdown-item{color:#222;display:block;font-size:0.875rem;line-height:1.5;padding:0.375rem 1rem;position:relative}a.dropdown-item,button.dropdown-item{padding-right:3rem;text-align:inherit;white-space:nowrap;width:100%}a.dropdown-item:hover,button.dropdown-item:hover{background-color:#f5f5f5;color:#0a0a0a}a.dropdown-item.is-active,button.dropdown-item.is-active{background-color:#2e63b8;color:#fff}.dropdown-divider{background-color:#ededed;border:none;display:block;height:1px;margin:0.5rem 0}.level{align-items:center;justify-content:space-between}.level code{border-radius:4px}.level img{display:inline-block;vertical-align:top}.level.is-mobile{display:flex}.level.is-mobile .level-left,.level.is-mobile .level-right{display:flex}.level.is-mobile .level-left+.level-right{margin-top:0}.level.is-mobile .level-item:not(:last-child){margin-bottom:0;margin-right:.75rem}.level.is-mobile .level-item:not(.is-narrow){flex-grow:1}@media screen and (min-width: 769px),print{.level{display:flex}.level>.level-item:not(.is-narrow){flex-grow:1}}.level-item{align-items:center;display:flex;flex-basis:auto;flex-grow:0;flex-shrink:0;justify-content:center}.level-item .title,.level-item .subtitle{margin-bottom:0}@media screen and (max-width: 768px){.level-item:not(:last-child){margin-bottom:.75rem}}.level-left,.level-right{flex-basis:auto;flex-grow:0;flex-shrink:0}.level-left .level-item.is-flexible,.level-right .level-item.is-flexible{flex-grow:1}@media screen and (min-width: 769px),print{.level-left .level-item:not(:last-child),.level-right .level-item:not(:last-child){margin-right:.75rem}}.level-left{align-items:center;justify-content:flex-start}@media screen and (max-width: 768px){.level-left+.level-right{margin-top:1.5rem}}@media screen and (min-width: 769px),print{.level-left{display:flex}}.level-right{align-items:center;justify-content:flex-end}@media screen and (min-width: 769px),print{.level-right{display:flex}}.media{align-items:flex-start;display:flex;text-align:inherit}.media .content:not(:last-child){margin-bottom:.75rem}.media .media{border-top:1px solid rgba(219,219,219,0.5);display:flex;padding-top:.75rem}.media .media .content:not(:last-child),.media .media .control:not(:last-child){margin-bottom:.5rem}.media .media .media{padding-top:.5rem}.media .media .media+.media{margin-top:.5rem}.media+.media{border-top:1px solid rgba(219,219,219,0.5);margin-top:1rem;padding-top:1rem}.media.is-large+.media{margin-top:1.5rem;padding-top:1.5rem}.media-left,.media-right{flex-basis:auto;flex-grow:0;flex-shrink:0}.media-left{margin-right:1rem}.media-right{margin-left:1rem}.media-content{flex-basis:auto;flex-grow:1;flex-shrink:1;text-align:inherit}@media screen and (max-width: 768px){.media-content{overflow-x:auto}}.menu{font-size:1rem}.menu.is-small,#documenter .docs-sidebar form.docs-search>input.menu{font-size:.75rem}.menu.is-medium{font-size:1.25rem}.menu.is-large{font-size:1.5rem}.menu-list{line-height:1.25}.menu-list a{border-radius:2px;color:#222;display:block;padding:0.5em 0.75em}.menu-list a:hover{background-color:#f5f5f5;color:#222}.menu-list a.is-active{background-color:#2e63b8;color:#fff}.menu-list li ul{border-left:1px solid #dbdbdb;margin:.75em;padding-left:.75em}.menu-label{color:#6b6b6b;font-size:.75em;letter-spacing:.1em;text-transform:uppercase}.menu-label:not(:first-child){margin-top:1em}.menu-label:not(:last-child){margin-bottom:1em}.message{background-color:#f5f5f5;border-radius:4px;font-size:1rem}.message strong{color:currentColor}.message a:not(.button):not(.tag):not(.dropdown-item){color:currentColor;text-decoration:underline}.message.is-small,#documenter .docs-sidebar form.docs-search>input.message{font-size:.75rem}.message.is-medium{font-size:1.25rem}.message.is-large{font-size:1.5rem}.message.is-white{background-color:#fff}.message.is-white .message-header{background-color:#fff;color:#0a0a0a}.message.is-white .message-body{border-color:#fff}.message.is-black{background-color:#fafafa}.message.is-black .message-header{background-color:#0a0a0a;color:#fff}.message.is-black .message-body{border-color:#0a0a0a}.message.is-light{background-color:#fafafa}.message.is-light .message-header{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}.message.is-light .message-body{border-color:#f5f5f5}.message.is-dark,.content kbd.message{background-color:#fafafa}.message.is-dark .message-header,.content kbd.message .message-header{background-color:#363636;color:#fff}.message.is-dark .message-body,.content kbd.message .message-body{border-color:#363636}.message.is-primary,.docstring>section>a.message.docs-sourcelink{background-color:#eef8fc}.message.is-primary .message-header,.docstring>section>a.message.docs-sourcelink .message-header{background-color:#4eb5de;color:#fff}.message.is-primary .message-body,.docstring>section>a.message.docs-sourcelink .message-body{border-color:#4eb5de;color:#1a6d8e}.message.is-link{background-color:#eff3fb}.message.is-link .message-header{background-color:#2e63b8;color:#fff}.message.is-link .message-body{border-color:#2e63b8;color:#3169c4}.message.is-info{background-color:#eff2fb}.message.is-info .message-header{background-color:#3c5dcd;color:#fff}.message.is-info .message-body{border-color:#3c5dcd;color:#3253c3}.message.is-success{background-color:#effded}.message.is-success .message-header{background-color:#259a12;color:#fff}.message.is-success .message-body{border-color:#259a12;color:#2ec016}.message.is-warning{background-color:#fffbeb}.message.is-warning .message-header{background-color:#a98800;color:#fff}.message.is-warning .message-body{border-color:#a98800;color:#cca400}.message.is-danger{background-color:#fbefef}.message.is-danger .message-header{background-color:#cb3c33;color:#fff}.message.is-danger .message-body{border-color:#cb3c33;color:#c03930}.message-header{align-items:center;background-color:#222;border-radius:4px 4px 0 0;color:#fff;display:flex;font-weight:700;justify-content:space-between;line-height:1.25;padding:0.75em 1em;position:relative}.message-header .delete{flex-grow:0;flex-shrink:0;margin-left:.75em}.message-header+.message-body{border-width:0;border-top-left-radius:0;border-top-right-radius:0}.message-body{border-color:#dbdbdb;border-radius:4px;border-style:solid;border-width:0 0 0 4px;color:#222;padding:1.25em 1.5em}.message-body code,.message-body pre{background-color:#fff}.message-body pre code{background-color:rgba(0,0,0,0)}.modal{align-items:center;display:none;flex-direction:column;justify-content:center;overflow:hidden;position:fixed;z-index:40}.modal.is-active{display:flex}.modal-background{background-color:rgba(10,10,10,0.86)}.modal-content,.modal-card{margin:0 20px;max-height:calc(100vh - 160px);overflow:auto;position:relative;width:100%}@media screen and (min-width: 769px){.modal-content,.modal-card{margin:0 auto;max-height:calc(100vh - 40px);width:640px}}.modal-close{background:none;height:40px;position:fixed;right:20px;top:20px;width:40px}.modal-card{display:flex;flex-direction:column;max-height:calc(100vh - 40px);overflow:hidden;-ms-overflow-y:visible}.modal-card-head,.modal-card-foot{align-items:center;background-color:#f5f5f5;display:flex;flex-shrink:0;justify-content:flex-start;padding:20px;position:relative}.modal-card-head{border-bottom:1px solid #dbdbdb;border-top-left-radius:6px;border-top-right-radius:6px}.modal-card-title{color:#222;flex-grow:1;flex-shrink:0;font-size:1.5rem;line-height:1}.modal-card-foot{border-bottom-left-radius:6px;border-bottom-right-radius:6px;border-top:1px solid #dbdbdb}.modal-card-foot .button:not(:last-child){margin-right:.5em}.modal-card-body{-webkit-overflow-scrolling:touch;background-color:#fff;flex-grow:1;flex-shrink:1;overflow:auto;padding:20px}.navbar{background-color:#fff;min-height:3.25rem;position:relative;z-index:30}.navbar.is-white{background-color:#fff;color:#0a0a0a}.navbar.is-white .navbar-brand>.navbar-item,.navbar.is-white .navbar-brand .navbar-link{color:#0a0a0a}.navbar.is-white .navbar-brand>a.navbar-item:focus,.navbar.is-white .navbar-brand>a.navbar-item:hover,.navbar.is-white .navbar-brand>a.navbar-item.is-active,.navbar.is-white .navbar-brand .navbar-link:focus,.navbar.is-white .navbar-brand .navbar-link:hover,.navbar.is-white .navbar-brand .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}.navbar.is-white .navbar-brand .navbar-link::after{border-color:#0a0a0a}.navbar.is-white .navbar-burger{color:#0a0a0a}@media screen and (min-width: 1056px){.navbar.is-white .navbar-start>.navbar-item,.navbar.is-white .navbar-start .navbar-link,.navbar.is-white .navbar-end>.navbar-item,.navbar.is-white .navbar-end .navbar-link{color:#0a0a0a}.navbar.is-white .navbar-start>a.navbar-item:focus,.navbar.is-white .navbar-start>a.navbar-item:hover,.navbar.is-white .navbar-start>a.navbar-item.is-active,.navbar.is-white .navbar-start .navbar-link:focus,.navbar.is-white .navbar-start .navbar-link:hover,.navbar.is-white .navbar-start .navbar-link.is-active,.navbar.is-white .navbar-end>a.navbar-item:focus,.navbar.is-white .navbar-end>a.navbar-item:hover,.navbar.is-white .navbar-end>a.navbar-item.is-active,.navbar.is-white .navbar-end .navbar-link:focus,.navbar.is-white .navbar-end .navbar-link:hover,.navbar.is-white .navbar-end .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}.navbar.is-white .navbar-start .navbar-link::after,.navbar.is-white .navbar-end .navbar-link::after{border-color:#0a0a0a}.navbar.is-white .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-white .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-white .navbar-item.has-dropdown.is-active .navbar-link{background-color:#f2f2f2;color:#0a0a0a}.navbar.is-white .navbar-dropdown a.navbar-item.is-active{background-color:#fff;color:#0a0a0a}}.navbar.is-black{background-color:#0a0a0a;color:#fff}.navbar.is-black .navbar-brand>.navbar-item,.navbar.is-black .navbar-brand .navbar-link{color:#fff}.navbar.is-black .navbar-brand>a.navbar-item:focus,.navbar.is-black .navbar-brand>a.navbar-item:hover,.navbar.is-black .navbar-brand>a.navbar-item.is-active,.navbar.is-black .navbar-brand .navbar-link:focus,.navbar.is-black .navbar-brand .navbar-link:hover,.navbar.is-black .navbar-brand .navbar-link.is-active{background-color:#000;color:#fff}.navbar.is-black .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-black .navbar-burger{color:#fff}@media screen and (min-width: 1056px){.navbar.is-black .navbar-start>.navbar-item,.navbar.is-black .navbar-start .navbar-link,.navbar.is-black .navbar-end>.navbar-item,.navbar.is-black .navbar-end .navbar-link{color:#fff}.navbar.is-black .navbar-start>a.navbar-item:focus,.navbar.is-black .navbar-start>a.navbar-item:hover,.navbar.is-black .navbar-start>a.navbar-item.is-active,.navbar.is-black .navbar-start .navbar-link:focus,.navbar.is-black .navbar-start .navbar-link:hover,.navbar.is-black .navbar-start .navbar-link.is-active,.navbar.is-black .navbar-end>a.navbar-item:focus,.navbar.is-black .navbar-end>a.navbar-item:hover,.navbar.is-black .navbar-end>a.navbar-item.is-active,.navbar.is-black .navbar-end .navbar-link:focus,.navbar.is-black .navbar-end .navbar-link:hover,.navbar.is-black .navbar-end .navbar-link.is-active{background-color:#000;color:#fff}.navbar.is-black .navbar-start .navbar-link::after,.navbar.is-black .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-black .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-black .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-black .navbar-item.has-dropdown.is-active .navbar-link{background-color:#000;color:#fff}.navbar.is-black .navbar-dropdown a.navbar-item.is-active{background-color:#0a0a0a;color:#fff}}.navbar.is-light{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}.navbar.is-light .navbar-brand>.navbar-item,.navbar.is-light .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}.navbar.is-light .navbar-brand>a.navbar-item:focus,.navbar.is-light .navbar-brand>a.navbar-item:hover,.navbar.is-light .navbar-brand>a.navbar-item.is-active,.navbar.is-light .navbar-brand .navbar-link:focus,.navbar.is-light .navbar-brand .navbar-link:hover,.navbar.is-light .navbar-brand .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}.navbar.is-light .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}.navbar.is-light .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){.navbar.is-light .navbar-start>.navbar-item,.navbar.is-light .navbar-start .navbar-link,.navbar.is-light .navbar-end>.navbar-item,.navbar.is-light .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}.navbar.is-light .navbar-start>a.navbar-item:focus,.navbar.is-light .navbar-start>a.navbar-item:hover,.navbar.is-light .navbar-start>a.navbar-item.is-active,.navbar.is-light .navbar-start .navbar-link:focus,.navbar.is-light .navbar-start .navbar-link:hover,.navbar.is-light .navbar-start .navbar-link.is-active,.navbar.is-light .navbar-end>a.navbar-item:focus,.navbar.is-light .navbar-end>a.navbar-item:hover,.navbar.is-light .navbar-end>a.navbar-item.is-active,.navbar.is-light .navbar-end .navbar-link:focus,.navbar.is-light .navbar-end .navbar-link:hover,.navbar.is-light .navbar-end .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}.navbar.is-light .navbar-start .navbar-link::after,.navbar.is-light .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}.navbar.is-light .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-light .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-light .navbar-item.has-dropdown.is-active .navbar-link{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}.navbar.is-light .navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}}.navbar.is-dark,.content kbd.navbar{background-color:#363636;color:#fff}.navbar.is-dark .navbar-brand>.navbar-item,.content kbd.navbar .navbar-brand>.navbar-item,.navbar.is-dark .navbar-brand .navbar-link,.content kbd.navbar .navbar-brand .navbar-link{color:#fff}.navbar.is-dark .navbar-brand>a.navbar-item:focus,.content kbd.navbar .navbar-brand>a.navbar-item:focus,.navbar.is-dark .navbar-brand>a.navbar-item:hover,.content kbd.navbar .navbar-brand>a.navbar-item:hover,.navbar.is-dark .navbar-brand>a.navbar-item.is-active,.content kbd.navbar .navbar-brand>a.navbar-item.is-active,.navbar.is-dark .navbar-brand .navbar-link:focus,.content kbd.navbar .navbar-brand .navbar-link:focus,.navbar.is-dark .navbar-brand .navbar-link:hover,.content kbd.navbar .navbar-brand .navbar-link:hover,.navbar.is-dark .navbar-brand .navbar-link.is-active,.content kbd.navbar .navbar-brand .navbar-link.is-active{background-color:#292929;color:#fff}.navbar.is-dark .navbar-brand .navbar-link::after,.content kbd.navbar .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-dark .navbar-burger,.content kbd.navbar .navbar-burger{color:#fff}@media screen and (min-width: 1056px){.navbar.is-dark .navbar-start>.navbar-item,.content kbd.navbar .navbar-start>.navbar-item,.navbar.is-dark .navbar-start .navbar-link,.content kbd.navbar .navbar-start .navbar-link,.navbar.is-dark .navbar-end>.navbar-item,.content kbd.navbar .navbar-end>.navbar-item,.navbar.is-dark .navbar-end .navbar-link,.content kbd.navbar .navbar-end .navbar-link{color:#fff}.navbar.is-dark .navbar-start>a.navbar-item:focus,.content kbd.navbar .navbar-start>a.navbar-item:focus,.navbar.is-dark .navbar-start>a.navbar-item:hover,.content kbd.navbar .navbar-start>a.navbar-item:hover,.navbar.is-dark .navbar-start>a.navbar-item.is-active,.content kbd.navbar .navbar-start>a.navbar-item.is-active,.navbar.is-dark .navbar-start .navbar-link:focus,.content kbd.navbar .navbar-start .navbar-link:focus,.navbar.is-dark .navbar-start .navbar-link:hover,.content kbd.navbar .navbar-start .navbar-link:hover,.navbar.is-dark .navbar-start .navbar-link.is-active,.content kbd.navbar .navbar-start .navbar-link.is-active,.navbar.is-dark .navbar-end>a.navbar-item:focus,.content kbd.navbar .navbar-end>a.navbar-item:focus,.navbar.is-dark .navbar-end>a.navbar-item:hover,.content kbd.navbar .navbar-end>a.navbar-item:hover,.navbar.is-dark .navbar-end>a.navbar-item.is-active,.content kbd.navbar .navbar-end>a.navbar-item.is-active,.navbar.is-dark .navbar-end .navbar-link:focus,.content kbd.navbar .navbar-end .navbar-link:focus,.navbar.is-dark .navbar-end .navbar-link:hover,.content kbd.navbar .navbar-end .navbar-link:hover,.navbar.is-dark .navbar-end .navbar-link.is-active,.content kbd.navbar .navbar-end .navbar-link.is-active{background-color:#292929;color:#fff}.navbar.is-dark .navbar-start .navbar-link::after,.content kbd.navbar .navbar-start .navbar-link::after,.navbar.is-dark .navbar-end .navbar-link::after,.content kbd.navbar .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-dark .navbar-item.has-dropdown:focus .navbar-link,.content kbd.navbar .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-dark .navbar-item.has-dropdown:hover .navbar-link,.content kbd.navbar .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-dark .navbar-item.has-dropdown.is-active .navbar-link,.content kbd.navbar .navbar-item.has-dropdown.is-active .navbar-link{background-color:#292929;color:#fff}.navbar.is-dark .navbar-dropdown a.navbar-item.is-active,.content kbd.navbar .navbar-dropdown a.navbar-item.is-active{background-color:#363636;color:#fff}}.navbar.is-primary,.docstring>section>a.navbar.docs-sourcelink{background-color:#4eb5de;color:#fff}.navbar.is-primary .navbar-brand>.navbar-item,.docstring>section>a.navbar.docs-sourcelink .navbar-brand>.navbar-item,.navbar.is-primary .navbar-brand .navbar-link,.docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link{color:#fff}.navbar.is-primary .navbar-brand>a.navbar-item:focus,.docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item:focus,.navbar.is-primary .navbar-brand>a.navbar-item:hover,.docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item:hover,.navbar.is-primary .navbar-brand>a.navbar-item.is-active,.docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item.is-active,.navbar.is-primary .navbar-brand .navbar-link:focus,.docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link:focus,.navbar.is-primary .navbar-brand .navbar-link:hover,.docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link:hover,.navbar.is-primary .navbar-brand .navbar-link.is-active,.docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link.is-active{background-color:#39acda;color:#fff}.navbar.is-primary .navbar-brand .navbar-link::after,.docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-primary .navbar-burger,.docstring>section>a.navbar.docs-sourcelink .navbar-burger{color:#fff}@media screen and (min-width: 1056px){.navbar.is-primary .navbar-start>.navbar-item,.docstring>section>a.navbar.docs-sourcelink .navbar-start>.navbar-item,.navbar.is-primary .navbar-start .navbar-link,.docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link,.navbar.is-primary .navbar-end>.navbar-item,.docstring>section>a.navbar.docs-sourcelink .navbar-end>.navbar-item,.navbar.is-primary .navbar-end .navbar-link,.docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link{color:#fff}.navbar.is-primary .navbar-start>a.navbar-item:focus,.docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item:focus,.navbar.is-primary .navbar-start>a.navbar-item:hover,.docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item:hover,.navbar.is-primary .navbar-start>a.navbar-item.is-active,.docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item.is-active,.navbar.is-primary .navbar-start .navbar-link:focus,.docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link:focus,.navbar.is-primary .navbar-start .navbar-link:hover,.docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link:hover,.navbar.is-primary .navbar-start .navbar-link.is-active,.docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link.is-active,.navbar.is-primary .navbar-end>a.navbar-item:focus,.docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item:focus,.navbar.is-primary .navbar-end>a.navbar-item:hover,.docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item:hover,.navbar.is-primary .navbar-end>a.navbar-item.is-active,.docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item.is-active,.navbar.is-primary .navbar-end .navbar-link:focus,.docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link:focus,.navbar.is-primary .navbar-end .navbar-link:hover,.docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link:hover,.navbar.is-primary .navbar-end .navbar-link.is-active,.docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link.is-active{background-color:#39acda;color:#fff}.navbar.is-primary .navbar-start .navbar-link::after,.docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link::after,.navbar.is-primary .navbar-end .navbar-link::after,.docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-primary .navbar-item.has-dropdown:focus .navbar-link,.docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-primary .navbar-item.has-dropdown:hover .navbar-link,.docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-primary .navbar-item.has-dropdown.is-active .navbar-link,.docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown.is-active .navbar-link{background-color:#39acda;color:#fff}.navbar.is-primary .navbar-dropdown a.navbar-item.is-active,.docstring>section>a.navbar.docs-sourcelink .navbar-dropdown a.navbar-item.is-active{background-color:#4eb5de;color:#fff}}.navbar.is-link{background-color:#2e63b8;color:#fff}.navbar.is-link .navbar-brand>.navbar-item,.navbar.is-link .navbar-brand .navbar-link{color:#fff}.navbar.is-link .navbar-brand>a.navbar-item:focus,.navbar.is-link .navbar-brand>a.navbar-item:hover,.navbar.is-link .navbar-brand>a.navbar-item.is-active,.navbar.is-link .navbar-brand .navbar-link:focus,.navbar.is-link .navbar-brand .navbar-link:hover,.navbar.is-link .navbar-brand .navbar-link.is-active{background-color:#2958a4;color:#fff}.navbar.is-link .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-link .navbar-burger{color:#fff}@media screen and (min-width: 1056px){.navbar.is-link .navbar-start>.navbar-item,.navbar.is-link .navbar-start .navbar-link,.navbar.is-link .navbar-end>.navbar-item,.navbar.is-link .navbar-end .navbar-link{color:#fff}.navbar.is-link .navbar-start>a.navbar-item:focus,.navbar.is-link .navbar-start>a.navbar-item:hover,.navbar.is-link .navbar-start>a.navbar-item.is-active,.navbar.is-link .navbar-start .navbar-link:focus,.navbar.is-link .navbar-start .navbar-link:hover,.navbar.is-link .navbar-start .navbar-link.is-active,.navbar.is-link .navbar-end>a.navbar-item:focus,.navbar.is-link .navbar-end>a.navbar-item:hover,.navbar.is-link .navbar-end>a.navbar-item.is-active,.navbar.is-link .navbar-end .navbar-link:focus,.navbar.is-link .navbar-end .navbar-link:hover,.navbar.is-link .navbar-end .navbar-link.is-active{background-color:#2958a4;color:#fff}.navbar.is-link .navbar-start .navbar-link::after,.navbar.is-link .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-link .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-link .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-link .navbar-item.has-dropdown.is-active .navbar-link{background-color:#2958a4;color:#fff}.navbar.is-link .navbar-dropdown a.navbar-item.is-active{background-color:#2e63b8;color:#fff}}.navbar.is-info{background-color:#3c5dcd;color:#fff}.navbar.is-info .navbar-brand>.navbar-item,.navbar.is-info .navbar-brand .navbar-link{color:#fff}.navbar.is-info .navbar-brand>a.navbar-item:focus,.navbar.is-info .navbar-brand>a.navbar-item:hover,.navbar.is-info .navbar-brand>a.navbar-item.is-active,.navbar.is-info .navbar-brand .navbar-link:focus,.navbar.is-info .navbar-brand .navbar-link:hover,.navbar.is-info .navbar-brand .navbar-link.is-active{background-color:#3151bf;color:#fff}.navbar.is-info .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-info .navbar-burger{color:#fff}@media screen and (min-width: 1056px){.navbar.is-info .navbar-start>.navbar-item,.navbar.is-info .navbar-start .navbar-link,.navbar.is-info .navbar-end>.navbar-item,.navbar.is-info .navbar-end .navbar-link{color:#fff}.navbar.is-info .navbar-start>a.navbar-item:focus,.navbar.is-info .navbar-start>a.navbar-item:hover,.navbar.is-info .navbar-start>a.navbar-item.is-active,.navbar.is-info .navbar-start .navbar-link:focus,.navbar.is-info .navbar-start .navbar-link:hover,.navbar.is-info .navbar-start .navbar-link.is-active,.navbar.is-info .navbar-end>a.navbar-item:focus,.navbar.is-info .navbar-end>a.navbar-item:hover,.navbar.is-info .navbar-end>a.navbar-item.is-active,.navbar.is-info .navbar-end .navbar-link:focus,.navbar.is-info .navbar-end .navbar-link:hover,.navbar.is-info .navbar-end .navbar-link.is-active{background-color:#3151bf;color:#fff}.navbar.is-info .navbar-start .navbar-link::after,.navbar.is-info .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-info .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-info .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-info .navbar-item.has-dropdown.is-active .navbar-link{background-color:#3151bf;color:#fff}.navbar.is-info .navbar-dropdown a.navbar-item.is-active{background-color:#3c5dcd;color:#fff}}.navbar.is-success{background-color:#259a12;color:#fff}.navbar.is-success .navbar-brand>.navbar-item,.navbar.is-success .navbar-brand .navbar-link{color:#fff}.navbar.is-success .navbar-brand>a.navbar-item:focus,.navbar.is-success .navbar-brand>a.navbar-item:hover,.navbar.is-success .navbar-brand>a.navbar-item.is-active,.navbar.is-success .navbar-brand .navbar-link:focus,.navbar.is-success .navbar-brand .navbar-link:hover,.navbar.is-success .navbar-brand .navbar-link.is-active{background-color:#20830f;color:#fff}.navbar.is-success .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-success .navbar-burger{color:#fff}@media screen and (min-width: 1056px){.navbar.is-success .navbar-start>.navbar-item,.navbar.is-success .navbar-start .navbar-link,.navbar.is-success .navbar-end>.navbar-item,.navbar.is-success .navbar-end .navbar-link{color:#fff}.navbar.is-success .navbar-start>a.navbar-item:focus,.navbar.is-success .navbar-start>a.navbar-item:hover,.navbar.is-success .navbar-start>a.navbar-item.is-active,.navbar.is-success .navbar-start .navbar-link:focus,.navbar.is-success .navbar-start .navbar-link:hover,.navbar.is-success .navbar-start .navbar-link.is-active,.navbar.is-success .navbar-end>a.navbar-item:focus,.navbar.is-success .navbar-end>a.navbar-item:hover,.navbar.is-success .navbar-end>a.navbar-item.is-active,.navbar.is-success .navbar-end .navbar-link:focus,.navbar.is-success .navbar-end .navbar-link:hover,.navbar.is-success .navbar-end .navbar-link.is-active{background-color:#20830f;color:#fff}.navbar.is-success .navbar-start .navbar-link::after,.navbar.is-success .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-success .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-success .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-success .navbar-item.has-dropdown.is-active .navbar-link{background-color:#20830f;color:#fff}.navbar.is-success .navbar-dropdown a.navbar-item.is-active{background-color:#259a12;color:#fff}}.navbar.is-warning{background-color:#a98800;color:#fff}.navbar.is-warning .navbar-brand>.navbar-item,.navbar.is-warning .navbar-brand .navbar-link{color:#fff}.navbar.is-warning .navbar-brand>a.navbar-item:focus,.navbar.is-warning .navbar-brand>a.navbar-item:hover,.navbar.is-warning .navbar-brand>a.navbar-item.is-active,.navbar.is-warning .navbar-brand .navbar-link:focus,.navbar.is-warning .navbar-brand .navbar-link:hover,.navbar.is-warning .navbar-brand .navbar-link.is-active{background-color:#8f7300;color:#fff}.navbar.is-warning .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-warning .navbar-burger{color:#fff}@media screen and (min-width: 1056px){.navbar.is-warning .navbar-start>.navbar-item,.navbar.is-warning .navbar-start .navbar-link,.navbar.is-warning .navbar-end>.navbar-item,.navbar.is-warning .navbar-end .navbar-link{color:#fff}.navbar.is-warning .navbar-start>a.navbar-item:focus,.navbar.is-warning .navbar-start>a.navbar-item:hover,.navbar.is-warning .navbar-start>a.navbar-item.is-active,.navbar.is-warning .navbar-start .navbar-link:focus,.navbar.is-warning .navbar-start .navbar-link:hover,.navbar.is-warning .navbar-start .navbar-link.is-active,.navbar.is-warning .navbar-end>a.navbar-item:focus,.navbar.is-warning .navbar-end>a.navbar-item:hover,.navbar.is-warning .navbar-end>a.navbar-item.is-active,.navbar.is-warning .navbar-end .navbar-link:focus,.navbar.is-warning .navbar-end .navbar-link:hover,.navbar.is-warning .navbar-end .navbar-link.is-active{background-color:#8f7300;color:#fff}.navbar.is-warning .navbar-start .navbar-link::after,.navbar.is-warning .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-warning .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-warning .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-warning .navbar-item.has-dropdown.is-active .navbar-link{background-color:#8f7300;color:#fff}.navbar.is-warning .navbar-dropdown a.navbar-item.is-active{background-color:#a98800;color:#fff}}.navbar.is-danger{background-color:#cb3c33;color:#fff}.navbar.is-danger .navbar-brand>.navbar-item,.navbar.is-danger .navbar-brand .navbar-link{color:#fff}.navbar.is-danger .navbar-brand>a.navbar-item:focus,.navbar.is-danger .navbar-brand>a.navbar-item:hover,.navbar.is-danger .navbar-brand>a.navbar-item.is-active,.navbar.is-danger .navbar-brand .navbar-link:focus,.navbar.is-danger .navbar-brand .navbar-link:hover,.navbar.is-danger .navbar-brand .navbar-link.is-active{background-color:#b7362e;color:#fff}.navbar.is-danger .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-danger .navbar-burger{color:#fff}@media screen and (min-width: 1056px){.navbar.is-danger .navbar-start>.navbar-item,.navbar.is-danger .navbar-start .navbar-link,.navbar.is-danger .navbar-end>.navbar-item,.navbar.is-danger .navbar-end .navbar-link{color:#fff}.navbar.is-danger .navbar-start>a.navbar-item:focus,.navbar.is-danger .navbar-start>a.navbar-item:hover,.navbar.is-danger .navbar-start>a.navbar-item.is-active,.navbar.is-danger .navbar-start .navbar-link:focus,.navbar.is-danger .navbar-start .navbar-link:hover,.navbar.is-danger .navbar-start .navbar-link.is-active,.navbar.is-danger .navbar-end>a.navbar-item:focus,.navbar.is-danger .navbar-end>a.navbar-item:hover,.navbar.is-danger .navbar-end>a.navbar-item.is-active,.navbar.is-danger .navbar-end .navbar-link:focus,.navbar.is-danger .navbar-end .navbar-link:hover,.navbar.is-danger .navbar-end .navbar-link.is-active{background-color:#b7362e;color:#fff}.navbar.is-danger .navbar-start .navbar-link::after,.navbar.is-danger .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-danger .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-danger .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-danger .navbar-item.has-dropdown.is-active .navbar-link{background-color:#b7362e;color:#fff}.navbar.is-danger .navbar-dropdown a.navbar-item.is-active{background-color:#cb3c33;color:#fff}}.navbar>.container{align-items:stretch;display:flex;min-height:3.25rem;width:100%}.navbar.has-shadow{box-shadow:0 2px 0 0 #f5f5f5}.navbar.is-fixed-bottom,.navbar.is-fixed-top{left:0;position:fixed;right:0;z-index:30}.navbar.is-fixed-bottom{bottom:0}.navbar.is-fixed-bottom.has-shadow{box-shadow:0 -2px 0 0 #f5f5f5}.navbar.is-fixed-top{top:0}html.has-navbar-fixed-top,body.has-navbar-fixed-top{padding-top:3.25rem}html.has-navbar-fixed-bottom,body.has-navbar-fixed-bottom{padding-bottom:3.25rem}.navbar-brand,.navbar-tabs{align-items:stretch;display:flex;flex-shrink:0;min-height:3.25rem}.navbar-brand a.navbar-item:focus,.navbar-brand a.navbar-item:hover{background-color:transparent}.navbar-tabs{-webkit-overflow-scrolling:touch;max-width:100vw;overflow-x:auto;overflow-y:hidden}.navbar-burger{color:#222;-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;cursor:pointer;display:block;height:3.25rem;position:relative;width:3.25rem;margin-left:auto}.navbar-burger span{background-color:currentColor;display:block;height:1px;left:calc(50% - 8px);position:absolute;transform-origin:center;transition-duration:86ms;transition-property:background-color, opacity, transform;transition-timing-function:ease-out;width:16px}.navbar-burger span:nth-child(1){top:calc(50% - 6px)}.navbar-burger span:nth-child(2){top:calc(50% - 1px)}.navbar-burger span:nth-child(3){top:calc(50% + 4px)}.navbar-burger:hover{background-color:rgba(0,0,0,0.05)}.navbar-burger.is-active span:nth-child(1){transform:translateY(5px) rotate(45deg)}.navbar-burger.is-active span:nth-child(2){opacity:0}.navbar-burger.is-active span:nth-child(3){transform:translateY(-5px) rotate(-45deg)}.navbar-menu{display:none}.navbar-item,.navbar-link{color:#222;display:block;line-height:1.5;padding:0.5rem 0.75rem;position:relative}.navbar-item .icon:only-child,.navbar-link .icon:only-child{margin-left:-0.25rem;margin-right:-0.25rem}a.navbar-item,.navbar-link{cursor:pointer}a.navbar-item:focus,a.navbar-item:focus-within,a.navbar-item:hover,a.navbar-item.is-active,.navbar-link:focus,.navbar-link:focus-within,.navbar-link:hover,.navbar-link.is-active{background-color:#fafafa;color:#2e63b8}.navbar-item{flex-grow:0;flex-shrink:0}.navbar-item img{max-height:1.75rem}.navbar-item.has-dropdown{padding:0}.navbar-item.is-expanded{flex-grow:1;flex-shrink:1}.navbar-item.is-tab{border-bottom:1px solid transparent;min-height:3.25rem;padding-bottom:calc(0.5rem - 1px)}.navbar-item.is-tab:focus,.navbar-item.is-tab:hover{background-color:rgba(0,0,0,0);border-bottom-color:#2e63b8}.navbar-item.is-tab.is-active{background-color:rgba(0,0,0,0);border-bottom-color:#2e63b8;border-bottom-style:solid;border-bottom-width:3px;color:#2e63b8;padding-bottom:calc(0.5rem - 3px)}.navbar-content{flex-grow:1;flex-shrink:1}.navbar-link:not(.is-arrowless){padding-right:2.5em}.navbar-link:not(.is-arrowless)::after{border-color:#2e63b8;margin-top:-0.375em;right:1.125em}.navbar-dropdown{font-size:0.875rem;padding-bottom:0.5rem;padding-top:0.5rem}.navbar-dropdown .navbar-item{padding-left:1.5rem;padding-right:1.5rem}.navbar-divider{background-color:#f5f5f5;border:none;display:none;height:2px;margin:0.5rem 0}@media screen and (max-width: 1055px){.navbar>.container{display:block}.navbar-brand .navbar-item,.navbar-tabs .navbar-item{align-items:center;display:flex}.navbar-link::after{display:none}.navbar-menu{background-color:#fff;box-shadow:0 8px 16px rgba(10,10,10,0.1);padding:0.5rem 0}.navbar-menu.is-active{display:block}.navbar.is-fixed-bottom-touch,.navbar.is-fixed-top-touch{left:0;position:fixed;right:0;z-index:30}.navbar.is-fixed-bottom-touch{bottom:0}.navbar.is-fixed-bottom-touch.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,0.1)}.navbar.is-fixed-top-touch{top:0}.navbar.is-fixed-top .navbar-menu,.navbar.is-fixed-top-touch .navbar-menu{-webkit-overflow-scrolling:touch;max-height:calc(100vh - 3.25rem);overflow:auto}html.has-navbar-fixed-top-touch,body.has-navbar-fixed-top-touch{padding-top:3.25rem}html.has-navbar-fixed-bottom-touch,body.has-navbar-fixed-bottom-touch{padding-bottom:3.25rem}}@media screen and (min-width: 1056px){.navbar,.navbar-menu,.navbar-start,.navbar-end{align-items:stretch;display:flex}.navbar{min-height:3.25rem}.navbar.is-spaced{padding:1rem 2rem}.navbar.is-spaced .navbar-start,.navbar.is-spaced .navbar-end{align-items:center}.navbar.is-spaced a.navbar-item,.navbar.is-spaced .navbar-link{border-radius:4px}.navbar.is-transparent a.navbar-item:focus,.navbar.is-transparent a.navbar-item:hover,.navbar.is-transparent a.navbar-item.is-active,.navbar.is-transparent .navbar-link:focus,.navbar.is-transparent .navbar-link:hover,.navbar.is-transparent .navbar-link.is-active{background-color:transparent !important}.navbar.is-transparent .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus .navbar-link,.navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus-within .navbar-link,.navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:hover .navbar-link{background-color:transparent !important}.navbar.is-transparent .navbar-dropdown a.navbar-item:focus,.navbar.is-transparent .navbar-dropdown a.navbar-item:hover{background-color:#f5f5f5;color:#0a0a0a}.navbar.is-transparent .navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:#2e63b8}.navbar-burger{display:none}.navbar-item,.navbar-link{align-items:center;display:flex}.navbar-item.has-dropdown{align-items:stretch}.navbar-item.has-dropdown-up .navbar-link::after{transform:rotate(135deg) translate(0.25em, -0.25em)}.navbar-item.has-dropdown-up .navbar-dropdown{border-bottom:2px solid #dbdbdb;border-radius:6px 6px 0 0;border-top:none;bottom:100%;box-shadow:0 -8px 8px rgba(10,10,10,0.1);top:auto}.navbar-item.is-active .navbar-dropdown,.navbar-item.is-hoverable:focus .navbar-dropdown,.navbar-item.is-hoverable:focus-within .navbar-dropdown,.navbar-item.is-hoverable:hover .navbar-dropdown{display:block}.navbar.is-spaced .navbar-item.is-active .navbar-dropdown,.navbar-item.is-active .navbar-dropdown.is-boxed,.navbar.is-spaced .navbar-item.is-hoverable:focus .navbar-dropdown,.navbar-item.is-hoverable:focus .navbar-dropdown.is-boxed,.navbar.is-spaced .navbar-item.is-hoverable:focus-within .navbar-dropdown,.navbar-item.is-hoverable:focus-within .navbar-dropdown.is-boxed,.navbar.is-spaced .navbar-item.is-hoverable:hover .navbar-dropdown,.navbar-item.is-hoverable:hover .navbar-dropdown.is-boxed{opacity:1;pointer-events:auto;transform:translateY(0)}.navbar-menu{flex-grow:1;flex-shrink:0}.navbar-start{justify-content:flex-start;margin-right:auto}.navbar-end{justify-content:flex-end;margin-left:auto}.navbar-dropdown{background-color:#fff;border-bottom-left-radius:6px;border-bottom-right-radius:6px;border-top:2px solid #dbdbdb;box-shadow:0 8px 8px rgba(10,10,10,0.1);display:none;font-size:0.875rem;left:0;min-width:100%;position:absolute;top:100%;z-index:20}.navbar-dropdown .navbar-item{padding:0.375rem 1rem;white-space:nowrap}.navbar-dropdown a.navbar-item{padding-right:3rem}.navbar-dropdown a.navbar-item:focus,.navbar-dropdown a.navbar-item:hover{background-color:#f5f5f5;color:#0a0a0a}.navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:#2e63b8}.navbar.is-spaced .navbar-dropdown,.navbar-dropdown.is-boxed{border-radius:6px;border-top:none;box-shadow:0 8px 8px rgba(10,10,10,0.1), 0 0 0 1px rgba(10,10,10,0.1);display:block;opacity:0;pointer-events:none;top:calc(100% + (-4px));transform:translateY(-5px);transition-duration:86ms;transition-property:opacity, transform}.navbar-dropdown.is-right{left:auto;right:0}.navbar-divider{display:block}.navbar>.container .navbar-brand,.container>.navbar .navbar-brand{margin-left:-.75rem}.navbar>.container .navbar-menu,.container>.navbar .navbar-menu{margin-right:-.75rem}.navbar.is-fixed-bottom-desktop,.navbar.is-fixed-top-desktop{left:0;position:fixed;right:0;z-index:30}.navbar.is-fixed-bottom-desktop{bottom:0}.navbar.is-fixed-bottom-desktop.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,0.1)}.navbar.is-fixed-top-desktop{top:0}html.has-navbar-fixed-top-desktop,body.has-navbar-fixed-top-desktop{padding-top:3.25rem}html.has-navbar-fixed-bottom-desktop,body.has-navbar-fixed-bottom-desktop{padding-bottom:3.25rem}html.has-spaced-navbar-fixed-top,body.has-spaced-navbar-fixed-top{padding-top:5.25rem}html.has-spaced-navbar-fixed-bottom,body.has-spaced-navbar-fixed-bottom{padding-bottom:5.25rem}a.navbar-item.is-active,.navbar-link.is-active{color:#0a0a0a}a.navbar-item.is-active:not(:focus):not(:hover),.navbar-link.is-active:not(:focus):not(:hover){background-color:rgba(0,0,0,0)}.navbar-item.has-dropdown:focus .navbar-link,.navbar-item.has-dropdown:hover .navbar-link,.navbar-item.has-dropdown.is-active .navbar-link{background-color:#fafafa}}.hero.is-fullheight-with-navbar{min-height:calc(100vh - 3.25rem)}.pagination{font-size:1rem;margin:-.25rem}.pagination.is-small,#documenter .docs-sidebar form.docs-search>input.pagination{font-size:.75rem}.pagination.is-medium{font-size:1.25rem}.pagination.is-large{font-size:1.5rem}.pagination.is-rounded .pagination-previous,#documenter .docs-sidebar form.docs-search>input.pagination .pagination-previous,.pagination.is-rounded .pagination-next,#documenter .docs-sidebar form.docs-search>input.pagination .pagination-next{padding-left:1em;padding-right:1em;border-radius:9999px}.pagination.is-rounded .pagination-link,#documenter .docs-sidebar form.docs-search>input.pagination .pagination-link{border-radius:9999px}.pagination,.pagination-list{align-items:center;display:flex;justify-content:center;text-align:center}.pagination-previous,.pagination-next,.pagination-link,.pagination-ellipsis{font-size:1em;justify-content:center;margin:.25rem;padding-left:.5em;padding-right:.5em;text-align:center}.pagination-previous,.pagination-next,.pagination-link{border-color:#dbdbdb;color:#222;min-width:2.5em}.pagination-previous:hover,.pagination-next:hover,.pagination-link:hover{border-color:#b5b5b5;color:#363636}.pagination-previous:focus,.pagination-next:focus,.pagination-link:focus{border-color:#3c5dcd}.pagination-previous:active,.pagination-next:active,.pagination-link:active{box-shadow:inset 0 1px 2px rgba(10,10,10,0.2)}.pagination-previous[disabled],.pagination-previous.is-disabled,.pagination-next[disabled],.pagination-next.is-disabled,.pagination-link[disabled],.pagination-link.is-disabled{background-color:#dbdbdb;border-color:#dbdbdb;box-shadow:none;color:#6b6b6b;opacity:0.5}.pagination-previous,.pagination-next{padding-left:.75em;padding-right:.75em;white-space:nowrap}.pagination-link.is-current{background-color:#2e63b8;border-color:#2e63b8;color:#fff}.pagination-ellipsis{color:#b5b5b5;pointer-events:none}.pagination-list{flex-wrap:wrap}.pagination-list li{list-style:none}@media screen and (max-width: 768px){.pagination{flex-wrap:wrap}.pagination-previous,.pagination-next{flex-grow:1;flex-shrink:1}.pagination-list li{flex-grow:1;flex-shrink:1}}@media screen and (min-width: 769px),print{.pagination-list{flex-grow:1;flex-shrink:1;justify-content:flex-start;order:1}.pagination-previous,.pagination-next,.pagination-link,.pagination-ellipsis{margin-bottom:0;margin-top:0}.pagination-previous{order:2}.pagination-next{order:3}.pagination{justify-content:space-between;margin-bottom:0;margin-top:0}.pagination.is-centered .pagination-previous{order:1}.pagination.is-centered .pagination-list{justify-content:center;order:2}.pagination.is-centered .pagination-next{order:3}.pagination.is-right .pagination-previous{order:1}.pagination.is-right .pagination-next{order:2}.pagination.is-right .pagination-list{justify-content:flex-end;order:3}}.panel{border-radius:6px;box-shadow:#bbb;font-size:1rem}.panel:not(:last-child){margin-bottom:1.5rem}.panel.is-white .panel-heading{background-color:#fff;color:#0a0a0a}.panel.is-white .panel-tabs a.is-active{border-bottom-color:#fff}.panel.is-white .panel-block.is-active .panel-icon{color:#fff}.panel.is-black .panel-heading{background-color:#0a0a0a;color:#fff}.panel.is-black .panel-tabs a.is-active{border-bottom-color:#0a0a0a}.panel.is-black .panel-block.is-active .panel-icon{color:#0a0a0a}.panel.is-light .panel-heading{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}.panel.is-light .panel-tabs a.is-active{border-bottom-color:#f5f5f5}.panel.is-light .panel-block.is-active .panel-icon{color:#f5f5f5}.panel.is-dark .panel-heading,.content kbd.panel .panel-heading{background-color:#363636;color:#fff}.panel.is-dark .panel-tabs a.is-active,.content kbd.panel .panel-tabs a.is-active{border-bottom-color:#363636}.panel.is-dark .panel-block.is-active .panel-icon,.content kbd.panel .panel-block.is-active .panel-icon{color:#363636}.panel.is-primary .panel-heading,.docstring>section>a.panel.docs-sourcelink .panel-heading{background-color:#4eb5de;color:#fff}.panel.is-primary .panel-tabs a.is-active,.docstring>section>a.panel.docs-sourcelink .panel-tabs a.is-active{border-bottom-color:#4eb5de}.panel.is-primary .panel-block.is-active .panel-icon,.docstring>section>a.panel.docs-sourcelink .panel-block.is-active .panel-icon{color:#4eb5de}.panel.is-link .panel-heading{background-color:#2e63b8;color:#fff}.panel.is-link .panel-tabs a.is-active{border-bottom-color:#2e63b8}.panel.is-link .panel-block.is-active .panel-icon{color:#2e63b8}.panel.is-info .panel-heading{background-color:#3c5dcd;color:#fff}.panel.is-info .panel-tabs a.is-active{border-bottom-color:#3c5dcd}.panel.is-info .panel-block.is-active .panel-icon{color:#3c5dcd}.panel.is-success .panel-heading{background-color:#259a12;color:#fff}.panel.is-success .panel-tabs a.is-active{border-bottom-color:#259a12}.panel.is-success .panel-block.is-active .panel-icon{color:#259a12}.panel.is-warning .panel-heading{background-color:#a98800;color:#fff}.panel.is-warning .panel-tabs a.is-active{border-bottom-color:#a98800}.panel.is-warning .panel-block.is-active .panel-icon{color:#a98800}.panel.is-danger .panel-heading{background-color:#cb3c33;color:#fff}.panel.is-danger .panel-tabs a.is-active{border-bottom-color:#cb3c33}.panel.is-danger .panel-block.is-active .panel-icon{color:#cb3c33}.panel-tabs:not(:last-child),.panel-block:not(:last-child){border-bottom:1px solid #ededed}.panel-heading{background-color:#ededed;border-radius:6px 6px 0 0;color:#222;font-size:1.25em;font-weight:700;line-height:1.25;padding:0.75em 1em}.panel-tabs{align-items:flex-end;display:flex;font-size:.875em;justify-content:center}.panel-tabs a{border-bottom:1px solid #dbdbdb;margin-bottom:-1px;padding:0.5em}.panel-tabs a.is-active{border-bottom-color:#4a4a4a;color:#363636}.panel-list a{color:#222}.panel-list a:hover{color:#2e63b8}.panel-block{align-items:center;color:#222;display:flex;justify-content:flex-start;padding:0.5em 0.75em}.panel-block input[type="checkbox"]{margin-right:.75em}.panel-block>.control{flex-grow:1;flex-shrink:1;width:100%}.panel-block.is-wrapped{flex-wrap:wrap}.panel-block.is-active{border-left-color:#2e63b8;color:#363636}.panel-block.is-active .panel-icon{color:#2e63b8}.panel-block:last-child{border-bottom-left-radius:6px;border-bottom-right-radius:6px}a.panel-block,label.panel-block{cursor:pointer}a.panel-block:hover,label.panel-block:hover{background-color:#f5f5f5}.panel-icon{display:inline-block;font-size:14px;height:1em;line-height:1em;text-align:center;vertical-align:top;width:1em;color:#6b6b6b;margin-right:.75em}.panel-icon .fa{font-size:inherit;line-height:inherit}.tabs{-webkit-overflow-scrolling:touch;align-items:stretch;display:flex;font-size:1rem;justify-content:space-between;overflow:hidden;overflow-x:auto;white-space:nowrap}.tabs a{align-items:center;border-bottom-color:#dbdbdb;border-bottom-style:solid;border-bottom-width:1px;color:#222;display:flex;justify-content:center;margin-bottom:-1px;padding:0.5em 1em;vertical-align:top}.tabs a:hover{border-bottom-color:#222;color:#222}.tabs li{display:block}.tabs li.is-active a{border-bottom-color:#2e63b8;color:#2e63b8}.tabs ul{align-items:center;border-bottom-color:#dbdbdb;border-bottom-style:solid;border-bottom-width:1px;display:flex;flex-grow:1;flex-shrink:0;justify-content:flex-start}.tabs ul.is-left{padding-right:0.75em}.tabs ul.is-center{flex:none;justify-content:center;padding-left:0.75em;padding-right:0.75em}.tabs ul.is-right{justify-content:flex-end;padding-left:0.75em}.tabs .icon:first-child{margin-right:.5em}.tabs .icon:last-child{margin-left:.5em}.tabs.is-centered ul{justify-content:center}.tabs.is-right ul{justify-content:flex-end}.tabs.is-boxed a{border:1px solid transparent;border-radius:4px 4px 0 0}.tabs.is-boxed a:hover{background-color:#f5f5f5;border-bottom-color:#dbdbdb}.tabs.is-boxed li.is-active a{background-color:#fff;border-color:#dbdbdb;border-bottom-color:rgba(0,0,0,0) !important}.tabs.is-fullwidth li{flex-grow:1;flex-shrink:0}.tabs.is-toggle a{border-color:#dbdbdb;border-style:solid;border-width:1px;margin-bottom:0;position:relative}.tabs.is-toggle a:hover{background-color:#f5f5f5;border-color:#b5b5b5;z-index:2}.tabs.is-toggle li+li{margin-left:-1px}.tabs.is-toggle li:first-child a{border-top-left-radius:4px;border-bottom-left-radius:4px}.tabs.is-toggle li:last-child a{border-top-right-radius:4px;border-bottom-right-radius:4px}.tabs.is-toggle li.is-active a{background-color:#2e63b8;border-color:#2e63b8;color:#fff;z-index:1}.tabs.is-toggle ul{border-bottom:none}.tabs.is-toggle.is-toggle-rounded li:first-child a{border-bottom-left-radius:9999px;border-top-left-radius:9999px;padding-left:1.25em}.tabs.is-toggle.is-toggle-rounded li:last-child a{border-bottom-right-radius:9999px;border-top-right-radius:9999px;padding-right:1.25em}.tabs.is-small,#documenter .docs-sidebar form.docs-search>input.tabs{font-size:.75rem}.tabs.is-medium{font-size:1.25rem}.tabs.is-large{font-size:1.5rem}.column{display:block;flex-basis:0;flex-grow:1;flex-shrink:1;padding:.75rem}.columns.is-mobile>.column.is-narrow{flex:none;width:unset}.columns.is-mobile>.column.is-full{flex:none;width:100%}.columns.is-mobile>.column.is-three-quarters{flex:none;width:75%}.columns.is-mobile>.column.is-two-thirds{flex:none;width:66.6666%}.columns.is-mobile>.column.is-half{flex:none;width:50%}.columns.is-mobile>.column.is-one-third{flex:none;width:33.3333%}.columns.is-mobile>.column.is-one-quarter{flex:none;width:25%}.columns.is-mobile>.column.is-one-fifth{flex:none;width:20%}.columns.is-mobile>.column.is-two-fifths{flex:none;width:40%}.columns.is-mobile>.column.is-three-fifths{flex:none;width:60%}.columns.is-mobile>.column.is-four-fifths{flex:none;width:80%}.columns.is-mobile>.column.is-offset-three-quarters{margin-left:75%}.columns.is-mobile>.column.is-offset-two-thirds{margin-left:66.6666%}.columns.is-mobile>.column.is-offset-half{margin-left:50%}.columns.is-mobile>.column.is-offset-one-third{margin-left:33.3333%}.columns.is-mobile>.column.is-offset-one-quarter{margin-left:25%}.columns.is-mobile>.column.is-offset-one-fifth{margin-left:20%}.columns.is-mobile>.column.is-offset-two-fifths{margin-left:40%}.columns.is-mobile>.column.is-offset-three-fifths{margin-left:60%}.columns.is-mobile>.column.is-offset-four-fifths{margin-left:80%}.columns.is-mobile>.column.is-0{flex:none;width:0%}.columns.is-mobile>.column.is-offset-0{margin-left:0%}.columns.is-mobile>.column.is-1{flex:none;width:8.33333337%}.columns.is-mobile>.column.is-offset-1{margin-left:8.33333337%}.columns.is-mobile>.column.is-2{flex:none;width:16.66666674%}.columns.is-mobile>.column.is-offset-2{margin-left:16.66666674%}.columns.is-mobile>.column.is-3{flex:none;width:25%}.columns.is-mobile>.column.is-offset-3{margin-left:25%}.columns.is-mobile>.column.is-4{flex:none;width:33.33333337%}.columns.is-mobile>.column.is-offset-4{margin-left:33.33333337%}.columns.is-mobile>.column.is-5{flex:none;width:41.66666674%}.columns.is-mobile>.column.is-offset-5{margin-left:41.66666674%}.columns.is-mobile>.column.is-6{flex:none;width:50%}.columns.is-mobile>.column.is-offset-6{margin-left:50%}.columns.is-mobile>.column.is-7{flex:none;width:58.33333337%}.columns.is-mobile>.column.is-offset-7{margin-left:58.33333337%}.columns.is-mobile>.column.is-8{flex:none;width:66.66666674%}.columns.is-mobile>.column.is-offset-8{margin-left:66.66666674%}.columns.is-mobile>.column.is-9{flex:none;width:75%}.columns.is-mobile>.column.is-offset-9{margin-left:75%}.columns.is-mobile>.column.is-10{flex:none;width:83.33333337%}.columns.is-mobile>.column.is-offset-10{margin-left:83.33333337%}.columns.is-mobile>.column.is-11{flex:none;width:91.66666674%}.columns.is-mobile>.column.is-offset-11{margin-left:91.66666674%}.columns.is-mobile>.column.is-12{flex:none;width:100%}.columns.is-mobile>.column.is-offset-12{margin-left:100%}@media screen and (max-width: 768px){.column.is-narrow-mobile{flex:none;width:unset}.column.is-full-mobile{flex:none;width:100%}.column.is-three-quarters-mobile{flex:none;width:75%}.column.is-two-thirds-mobile{flex:none;width:66.6666%}.column.is-half-mobile{flex:none;width:50%}.column.is-one-third-mobile{flex:none;width:33.3333%}.column.is-one-quarter-mobile{flex:none;width:25%}.column.is-one-fifth-mobile{flex:none;width:20%}.column.is-two-fifths-mobile{flex:none;width:40%}.column.is-three-fifths-mobile{flex:none;width:60%}.column.is-four-fifths-mobile{flex:none;width:80%}.column.is-offset-three-quarters-mobile{margin-left:75%}.column.is-offset-two-thirds-mobile{margin-left:66.6666%}.column.is-offset-half-mobile{margin-left:50%}.column.is-offset-one-third-mobile{margin-left:33.3333%}.column.is-offset-one-quarter-mobile{margin-left:25%}.column.is-offset-one-fifth-mobile{margin-left:20%}.column.is-offset-two-fifths-mobile{margin-left:40%}.column.is-offset-three-fifths-mobile{margin-left:60%}.column.is-offset-four-fifths-mobile{margin-left:80%}.column.is-0-mobile{flex:none;width:0%}.column.is-offset-0-mobile{margin-left:0%}.column.is-1-mobile{flex:none;width:8.33333337%}.column.is-offset-1-mobile{margin-left:8.33333337%}.column.is-2-mobile{flex:none;width:16.66666674%}.column.is-offset-2-mobile{margin-left:16.66666674%}.column.is-3-mobile{flex:none;width:25%}.column.is-offset-3-mobile{margin-left:25%}.column.is-4-mobile{flex:none;width:33.33333337%}.column.is-offset-4-mobile{margin-left:33.33333337%}.column.is-5-mobile{flex:none;width:41.66666674%}.column.is-offset-5-mobile{margin-left:41.66666674%}.column.is-6-mobile{flex:none;width:50%}.column.is-offset-6-mobile{margin-left:50%}.column.is-7-mobile{flex:none;width:58.33333337%}.column.is-offset-7-mobile{margin-left:58.33333337%}.column.is-8-mobile{flex:none;width:66.66666674%}.column.is-offset-8-mobile{margin-left:66.66666674%}.column.is-9-mobile{flex:none;width:75%}.column.is-offset-9-mobile{margin-left:75%}.column.is-10-mobile{flex:none;width:83.33333337%}.column.is-offset-10-mobile{margin-left:83.33333337%}.column.is-11-mobile{flex:none;width:91.66666674%}.column.is-offset-11-mobile{margin-left:91.66666674%}.column.is-12-mobile{flex:none;width:100%}.column.is-offset-12-mobile{margin-left:100%}}@media screen and (min-width: 769px),print{.column.is-narrow,.column.is-narrow-tablet{flex:none;width:unset}.column.is-full,.column.is-full-tablet{flex:none;width:100%}.column.is-three-quarters,.column.is-three-quarters-tablet{flex:none;width:75%}.column.is-two-thirds,.column.is-two-thirds-tablet{flex:none;width:66.6666%}.column.is-half,.column.is-half-tablet{flex:none;width:50%}.column.is-one-third,.column.is-one-third-tablet{flex:none;width:33.3333%}.column.is-one-quarter,.column.is-one-quarter-tablet{flex:none;width:25%}.column.is-one-fifth,.column.is-one-fifth-tablet{flex:none;width:20%}.column.is-two-fifths,.column.is-two-fifths-tablet{flex:none;width:40%}.column.is-three-fifths,.column.is-three-fifths-tablet{flex:none;width:60%}.column.is-four-fifths,.column.is-four-fifths-tablet{flex:none;width:80%}.column.is-offset-three-quarters,.column.is-offset-three-quarters-tablet{margin-left:75%}.column.is-offset-two-thirds,.column.is-offset-two-thirds-tablet{margin-left:66.6666%}.column.is-offset-half,.column.is-offset-half-tablet{margin-left:50%}.column.is-offset-one-third,.column.is-offset-one-third-tablet{margin-left:33.3333%}.column.is-offset-one-quarter,.column.is-offset-one-quarter-tablet{margin-left:25%}.column.is-offset-one-fifth,.column.is-offset-one-fifth-tablet{margin-left:20%}.column.is-offset-two-fifths,.column.is-offset-two-fifths-tablet{margin-left:40%}.column.is-offset-three-fifths,.column.is-offset-three-fifths-tablet{margin-left:60%}.column.is-offset-four-fifths,.column.is-offset-four-fifths-tablet{margin-left:80%}.column.is-0,.column.is-0-tablet{flex:none;width:0%}.column.is-offset-0,.column.is-offset-0-tablet{margin-left:0%}.column.is-1,.column.is-1-tablet{flex:none;width:8.33333337%}.column.is-offset-1,.column.is-offset-1-tablet{margin-left:8.33333337%}.column.is-2,.column.is-2-tablet{flex:none;width:16.66666674%}.column.is-offset-2,.column.is-offset-2-tablet{margin-left:16.66666674%}.column.is-3,.column.is-3-tablet{flex:none;width:25%}.column.is-offset-3,.column.is-offset-3-tablet{margin-left:25%}.column.is-4,.column.is-4-tablet{flex:none;width:33.33333337%}.column.is-offset-4,.column.is-offset-4-tablet{margin-left:33.33333337%}.column.is-5,.column.is-5-tablet{flex:none;width:41.66666674%}.column.is-offset-5,.column.is-offset-5-tablet{margin-left:41.66666674%}.column.is-6,.column.is-6-tablet{flex:none;width:50%}.column.is-offset-6,.column.is-offset-6-tablet{margin-left:50%}.column.is-7,.column.is-7-tablet{flex:none;width:58.33333337%}.column.is-offset-7,.column.is-offset-7-tablet{margin-left:58.33333337%}.column.is-8,.column.is-8-tablet{flex:none;width:66.66666674%}.column.is-offset-8,.column.is-offset-8-tablet{margin-left:66.66666674%}.column.is-9,.column.is-9-tablet{flex:none;width:75%}.column.is-offset-9,.column.is-offset-9-tablet{margin-left:75%}.column.is-10,.column.is-10-tablet{flex:none;width:83.33333337%}.column.is-offset-10,.column.is-offset-10-tablet{margin-left:83.33333337%}.column.is-11,.column.is-11-tablet{flex:none;width:91.66666674%}.column.is-offset-11,.column.is-offset-11-tablet{margin-left:91.66666674%}.column.is-12,.column.is-12-tablet{flex:none;width:100%}.column.is-offset-12,.column.is-offset-12-tablet{margin-left:100%}}@media screen and (max-width: 1055px){.column.is-narrow-touch{flex:none;width:unset}.column.is-full-touch{flex:none;width:100%}.column.is-three-quarters-touch{flex:none;width:75%}.column.is-two-thirds-touch{flex:none;width:66.6666%}.column.is-half-touch{flex:none;width:50%}.column.is-one-third-touch{flex:none;width:33.3333%}.column.is-one-quarter-touch{flex:none;width:25%}.column.is-one-fifth-touch{flex:none;width:20%}.column.is-two-fifths-touch{flex:none;width:40%}.column.is-three-fifths-touch{flex:none;width:60%}.column.is-four-fifths-touch{flex:none;width:80%}.column.is-offset-three-quarters-touch{margin-left:75%}.column.is-offset-two-thirds-touch{margin-left:66.6666%}.column.is-offset-half-touch{margin-left:50%}.column.is-offset-one-third-touch{margin-left:33.3333%}.column.is-offset-one-quarter-touch{margin-left:25%}.column.is-offset-one-fifth-touch{margin-left:20%}.column.is-offset-two-fifths-touch{margin-left:40%}.column.is-offset-three-fifths-touch{margin-left:60%}.column.is-offset-four-fifths-touch{margin-left:80%}.column.is-0-touch{flex:none;width:0%}.column.is-offset-0-touch{margin-left:0%}.column.is-1-touch{flex:none;width:8.33333337%}.column.is-offset-1-touch{margin-left:8.33333337%}.column.is-2-touch{flex:none;width:16.66666674%}.column.is-offset-2-touch{margin-left:16.66666674%}.column.is-3-touch{flex:none;width:25%}.column.is-offset-3-touch{margin-left:25%}.column.is-4-touch{flex:none;width:33.33333337%}.column.is-offset-4-touch{margin-left:33.33333337%}.column.is-5-touch{flex:none;width:41.66666674%}.column.is-offset-5-touch{margin-left:41.66666674%}.column.is-6-touch{flex:none;width:50%}.column.is-offset-6-touch{margin-left:50%}.column.is-7-touch{flex:none;width:58.33333337%}.column.is-offset-7-touch{margin-left:58.33333337%}.column.is-8-touch{flex:none;width:66.66666674%}.column.is-offset-8-touch{margin-left:66.66666674%}.column.is-9-touch{flex:none;width:75%}.column.is-offset-9-touch{margin-left:75%}.column.is-10-touch{flex:none;width:83.33333337%}.column.is-offset-10-touch{margin-left:83.33333337%}.column.is-11-touch{flex:none;width:91.66666674%}.column.is-offset-11-touch{margin-left:91.66666674%}.column.is-12-touch{flex:none;width:100%}.column.is-offset-12-touch{margin-left:100%}}@media screen and (min-width: 1056px){.column.is-narrow-desktop{flex:none;width:unset}.column.is-full-desktop{flex:none;width:100%}.column.is-three-quarters-desktop{flex:none;width:75%}.column.is-two-thirds-desktop{flex:none;width:66.6666%}.column.is-half-desktop{flex:none;width:50%}.column.is-one-third-desktop{flex:none;width:33.3333%}.column.is-one-quarter-desktop{flex:none;width:25%}.column.is-one-fifth-desktop{flex:none;width:20%}.column.is-two-fifths-desktop{flex:none;width:40%}.column.is-three-fifths-desktop{flex:none;width:60%}.column.is-four-fifths-desktop{flex:none;width:80%}.column.is-offset-three-quarters-desktop{margin-left:75%}.column.is-offset-two-thirds-desktop{margin-left:66.6666%}.column.is-offset-half-desktop{margin-left:50%}.column.is-offset-one-third-desktop{margin-left:33.3333%}.column.is-offset-one-quarter-desktop{margin-left:25%}.column.is-offset-one-fifth-desktop{margin-left:20%}.column.is-offset-two-fifths-desktop{margin-left:40%}.column.is-offset-three-fifths-desktop{margin-left:60%}.column.is-offset-four-fifths-desktop{margin-left:80%}.column.is-0-desktop{flex:none;width:0%}.column.is-offset-0-desktop{margin-left:0%}.column.is-1-desktop{flex:none;width:8.33333337%}.column.is-offset-1-desktop{margin-left:8.33333337%}.column.is-2-desktop{flex:none;width:16.66666674%}.column.is-offset-2-desktop{margin-left:16.66666674%}.column.is-3-desktop{flex:none;width:25%}.column.is-offset-3-desktop{margin-left:25%}.column.is-4-desktop{flex:none;width:33.33333337%}.column.is-offset-4-desktop{margin-left:33.33333337%}.column.is-5-desktop{flex:none;width:41.66666674%}.column.is-offset-5-desktop{margin-left:41.66666674%}.column.is-6-desktop{flex:none;width:50%}.column.is-offset-6-desktop{margin-left:50%}.column.is-7-desktop{flex:none;width:58.33333337%}.column.is-offset-7-desktop{margin-left:58.33333337%}.column.is-8-desktop{flex:none;width:66.66666674%}.column.is-offset-8-desktop{margin-left:66.66666674%}.column.is-9-desktop{flex:none;width:75%}.column.is-offset-9-desktop{margin-left:75%}.column.is-10-desktop{flex:none;width:83.33333337%}.column.is-offset-10-desktop{margin-left:83.33333337%}.column.is-11-desktop{flex:none;width:91.66666674%}.column.is-offset-11-desktop{margin-left:91.66666674%}.column.is-12-desktop{flex:none;width:100%}.column.is-offset-12-desktop{margin-left:100%}}@media screen and (min-width: 1216px){.column.is-narrow-widescreen{flex:none;width:unset}.column.is-full-widescreen{flex:none;width:100%}.column.is-three-quarters-widescreen{flex:none;width:75%}.column.is-two-thirds-widescreen{flex:none;width:66.6666%}.column.is-half-widescreen{flex:none;width:50%}.column.is-one-third-widescreen{flex:none;width:33.3333%}.column.is-one-quarter-widescreen{flex:none;width:25%}.column.is-one-fifth-widescreen{flex:none;width:20%}.column.is-two-fifths-widescreen{flex:none;width:40%}.column.is-three-fifths-widescreen{flex:none;width:60%}.column.is-four-fifths-widescreen{flex:none;width:80%}.column.is-offset-three-quarters-widescreen{margin-left:75%}.column.is-offset-two-thirds-widescreen{margin-left:66.6666%}.column.is-offset-half-widescreen{margin-left:50%}.column.is-offset-one-third-widescreen{margin-left:33.3333%}.column.is-offset-one-quarter-widescreen{margin-left:25%}.column.is-offset-one-fifth-widescreen{margin-left:20%}.column.is-offset-two-fifths-widescreen{margin-left:40%}.column.is-offset-three-fifths-widescreen{margin-left:60%}.column.is-offset-four-fifths-widescreen{margin-left:80%}.column.is-0-widescreen{flex:none;width:0%}.column.is-offset-0-widescreen{margin-left:0%}.column.is-1-widescreen{flex:none;width:8.33333337%}.column.is-offset-1-widescreen{margin-left:8.33333337%}.column.is-2-widescreen{flex:none;width:16.66666674%}.column.is-offset-2-widescreen{margin-left:16.66666674%}.column.is-3-widescreen{flex:none;width:25%}.column.is-offset-3-widescreen{margin-left:25%}.column.is-4-widescreen{flex:none;width:33.33333337%}.column.is-offset-4-widescreen{margin-left:33.33333337%}.column.is-5-widescreen{flex:none;width:41.66666674%}.column.is-offset-5-widescreen{margin-left:41.66666674%}.column.is-6-widescreen{flex:none;width:50%}.column.is-offset-6-widescreen{margin-left:50%}.column.is-7-widescreen{flex:none;width:58.33333337%}.column.is-offset-7-widescreen{margin-left:58.33333337%}.column.is-8-widescreen{flex:none;width:66.66666674%}.column.is-offset-8-widescreen{margin-left:66.66666674%}.column.is-9-widescreen{flex:none;width:75%}.column.is-offset-9-widescreen{margin-left:75%}.column.is-10-widescreen{flex:none;width:83.33333337%}.column.is-offset-10-widescreen{margin-left:83.33333337%}.column.is-11-widescreen{flex:none;width:91.66666674%}.column.is-offset-11-widescreen{margin-left:91.66666674%}.column.is-12-widescreen{flex:none;width:100%}.column.is-offset-12-widescreen{margin-left:100%}}@media screen and (min-width: 1408px){.column.is-narrow-fullhd{flex:none;width:unset}.column.is-full-fullhd{flex:none;width:100%}.column.is-three-quarters-fullhd{flex:none;width:75%}.column.is-two-thirds-fullhd{flex:none;width:66.6666%}.column.is-half-fullhd{flex:none;width:50%}.column.is-one-third-fullhd{flex:none;width:33.3333%}.column.is-one-quarter-fullhd{flex:none;width:25%}.column.is-one-fifth-fullhd{flex:none;width:20%}.column.is-two-fifths-fullhd{flex:none;width:40%}.column.is-three-fifths-fullhd{flex:none;width:60%}.column.is-four-fifths-fullhd{flex:none;width:80%}.column.is-offset-three-quarters-fullhd{margin-left:75%}.column.is-offset-two-thirds-fullhd{margin-left:66.6666%}.column.is-offset-half-fullhd{margin-left:50%}.column.is-offset-one-third-fullhd{margin-left:33.3333%}.column.is-offset-one-quarter-fullhd{margin-left:25%}.column.is-offset-one-fifth-fullhd{margin-left:20%}.column.is-offset-two-fifths-fullhd{margin-left:40%}.column.is-offset-three-fifths-fullhd{margin-left:60%}.column.is-offset-four-fifths-fullhd{margin-left:80%}.column.is-0-fullhd{flex:none;width:0%}.column.is-offset-0-fullhd{margin-left:0%}.column.is-1-fullhd{flex:none;width:8.33333337%}.column.is-offset-1-fullhd{margin-left:8.33333337%}.column.is-2-fullhd{flex:none;width:16.66666674%}.column.is-offset-2-fullhd{margin-left:16.66666674%}.column.is-3-fullhd{flex:none;width:25%}.column.is-offset-3-fullhd{margin-left:25%}.column.is-4-fullhd{flex:none;width:33.33333337%}.column.is-offset-4-fullhd{margin-left:33.33333337%}.column.is-5-fullhd{flex:none;width:41.66666674%}.column.is-offset-5-fullhd{margin-left:41.66666674%}.column.is-6-fullhd{flex:none;width:50%}.column.is-offset-6-fullhd{margin-left:50%}.column.is-7-fullhd{flex:none;width:58.33333337%}.column.is-offset-7-fullhd{margin-left:58.33333337%}.column.is-8-fullhd{flex:none;width:66.66666674%}.column.is-offset-8-fullhd{margin-left:66.66666674%}.column.is-9-fullhd{flex:none;width:75%}.column.is-offset-9-fullhd{margin-left:75%}.column.is-10-fullhd{flex:none;width:83.33333337%}.column.is-offset-10-fullhd{margin-left:83.33333337%}.column.is-11-fullhd{flex:none;width:91.66666674%}.column.is-offset-11-fullhd{margin-left:91.66666674%}.column.is-12-fullhd{flex:none;width:100%}.column.is-offset-12-fullhd{margin-left:100%}}.columns{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}.columns:last-child{margin-bottom:-.75rem}.columns:not(:last-child){margin-bottom:calc(1.5rem - .75rem)}.columns.is-centered{justify-content:center}.columns.is-gapless{margin-left:0;margin-right:0;margin-top:0}.columns.is-gapless>.column{margin:0;padding:0 !important}.columns.is-gapless:not(:last-child){margin-bottom:1.5rem}.columns.is-gapless:last-child{margin-bottom:0}.columns.is-mobile{display:flex}.columns.is-multiline{flex-wrap:wrap}.columns.is-vcentered{align-items:center}@media screen and (min-width: 769px),print{.columns:not(.is-desktop){display:flex}}@media screen and (min-width: 1056px){.columns.is-desktop{display:flex}}.columns.is-variable{--columnGap: 0.75rem;margin-left:calc(-1 * var(--columnGap));margin-right:calc(-1 * var(--columnGap))}.columns.is-variable>.column{padding-left:var(--columnGap);padding-right:var(--columnGap)}.columns.is-variable.is-0{--columnGap: 0rem}@media screen and (max-width: 768px){.columns.is-variable.is-0-mobile{--columnGap: 0rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-0-tablet{--columnGap: 0rem}}@media screen and (min-width: 769px) and (max-width: 1055px){.columns.is-variable.is-0-tablet-only{--columnGap: 0rem}}@media screen and (max-width: 1055px){.columns.is-variable.is-0-touch{--columnGap: 0rem}}@media screen and (min-width: 1056px){.columns.is-variable.is-0-desktop{--columnGap: 0rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){.columns.is-variable.is-0-desktop-only{--columnGap: 0rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-0-widescreen{--columnGap: 0rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){.columns.is-variable.is-0-widescreen-only{--columnGap: 0rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-0-fullhd{--columnGap: 0rem}}.columns.is-variable.is-1{--columnGap: .25rem}@media screen and (max-width: 768px){.columns.is-variable.is-1-mobile{--columnGap: .25rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-1-tablet{--columnGap: .25rem}}@media screen and (min-width: 769px) and (max-width: 1055px){.columns.is-variable.is-1-tablet-only{--columnGap: .25rem}}@media screen and (max-width: 1055px){.columns.is-variable.is-1-touch{--columnGap: .25rem}}@media screen and (min-width: 1056px){.columns.is-variable.is-1-desktop{--columnGap: .25rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){.columns.is-variable.is-1-desktop-only{--columnGap: .25rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-1-widescreen{--columnGap: .25rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){.columns.is-variable.is-1-widescreen-only{--columnGap: .25rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-1-fullhd{--columnGap: .25rem}}.columns.is-variable.is-2{--columnGap: .5rem}@media screen and (max-width: 768px){.columns.is-variable.is-2-mobile{--columnGap: .5rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-2-tablet{--columnGap: .5rem}}@media screen and (min-width: 769px) and (max-width: 1055px){.columns.is-variable.is-2-tablet-only{--columnGap: .5rem}}@media screen and (max-width: 1055px){.columns.is-variable.is-2-touch{--columnGap: .5rem}}@media screen and (min-width: 1056px){.columns.is-variable.is-2-desktop{--columnGap: .5rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){.columns.is-variable.is-2-desktop-only{--columnGap: .5rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-2-widescreen{--columnGap: .5rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){.columns.is-variable.is-2-widescreen-only{--columnGap: .5rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-2-fullhd{--columnGap: .5rem}}.columns.is-variable.is-3{--columnGap: .75rem}@media screen and (max-width: 768px){.columns.is-variable.is-3-mobile{--columnGap: .75rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-3-tablet{--columnGap: .75rem}}@media screen and (min-width: 769px) and (max-width: 1055px){.columns.is-variable.is-3-tablet-only{--columnGap: .75rem}}@media screen and (max-width: 1055px){.columns.is-variable.is-3-touch{--columnGap: .75rem}}@media screen and (min-width: 1056px){.columns.is-variable.is-3-desktop{--columnGap: .75rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){.columns.is-variable.is-3-desktop-only{--columnGap: .75rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-3-widescreen{--columnGap: .75rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){.columns.is-variable.is-3-widescreen-only{--columnGap: .75rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-3-fullhd{--columnGap: .75rem}}.columns.is-variable.is-4{--columnGap: 1rem}@media screen and (max-width: 768px){.columns.is-variable.is-4-mobile{--columnGap: 1rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-4-tablet{--columnGap: 1rem}}@media screen and (min-width: 769px) and (max-width: 1055px){.columns.is-variable.is-4-tablet-only{--columnGap: 1rem}}@media screen and (max-width: 1055px){.columns.is-variable.is-4-touch{--columnGap: 1rem}}@media screen and (min-width: 1056px){.columns.is-variable.is-4-desktop{--columnGap: 1rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){.columns.is-variable.is-4-desktop-only{--columnGap: 1rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-4-widescreen{--columnGap: 1rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){.columns.is-variable.is-4-widescreen-only{--columnGap: 1rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-4-fullhd{--columnGap: 1rem}}.columns.is-variable.is-5{--columnGap: 1.25rem}@media screen and (max-width: 768px){.columns.is-variable.is-5-mobile{--columnGap: 1.25rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-5-tablet{--columnGap: 1.25rem}}@media screen and (min-width: 769px) and (max-width: 1055px){.columns.is-variable.is-5-tablet-only{--columnGap: 1.25rem}}@media screen and (max-width: 1055px){.columns.is-variable.is-5-touch{--columnGap: 1.25rem}}@media screen and (min-width: 1056px){.columns.is-variable.is-5-desktop{--columnGap: 1.25rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){.columns.is-variable.is-5-desktop-only{--columnGap: 1.25rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-5-widescreen{--columnGap: 1.25rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){.columns.is-variable.is-5-widescreen-only{--columnGap: 1.25rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-5-fullhd{--columnGap: 1.25rem}}.columns.is-variable.is-6{--columnGap: 1.5rem}@media screen and (max-width: 768px){.columns.is-variable.is-6-mobile{--columnGap: 1.5rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-6-tablet{--columnGap: 1.5rem}}@media screen and (min-width: 769px) and (max-width: 1055px){.columns.is-variable.is-6-tablet-only{--columnGap: 1.5rem}}@media screen and (max-width: 1055px){.columns.is-variable.is-6-touch{--columnGap: 1.5rem}}@media screen and (min-width: 1056px){.columns.is-variable.is-6-desktop{--columnGap: 1.5rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){.columns.is-variable.is-6-desktop-only{--columnGap: 1.5rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-6-widescreen{--columnGap: 1.5rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){.columns.is-variable.is-6-widescreen-only{--columnGap: 1.5rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-6-fullhd{--columnGap: 1.5rem}}.columns.is-variable.is-7{--columnGap: 1.75rem}@media screen and (max-width: 768px){.columns.is-variable.is-7-mobile{--columnGap: 1.75rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-7-tablet{--columnGap: 1.75rem}}@media screen and (min-width: 769px) and (max-width: 1055px){.columns.is-variable.is-7-tablet-only{--columnGap: 1.75rem}}@media screen and (max-width: 1055px){.columns.is-variable.is-7-touch{--columnGap: 1.75rem}}@media screen and (min-width: 1056px){.columns.is-variable.is-7-desktop{--columnGap: 1.75rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){.columns.is-variable.is-7-desktop-only{--columnGap: 1.75rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-7-widescreen{--columnGap: 1.75rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){.columns.is-variable.is-7-widescreen-only{--columnGap: 1.75rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-7-fullhd{--columnGap: 1.75rem}}.columns.is-variable.is-8{--columnGap: 2rem}@media screen and (max-width: 768px){.columns.is-variable.is-8-mobile{--columnGap: 2rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-8-tablet{--columnGap: 2rem}}@media screen and (min-width: 769px) and (max-width: 1055px){.columns.is-variable.is-8-tablet-only{--columnGap: 2rem}}@media screen and (max-width: 1055px){.columns.is-variable.is-8-touch{--columnGap: 2rem}}@media screen and (min-width: 1056px){.columns.is-variable.is-8-desktop{--columnGap: 2rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){.columns.is-variable.is-8-desktop-only{--columnGap: 2rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-8-widescreen{--columnGap: 2rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){.columns.is-variable.is-8-widescreen-only{--columnGap: 2rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-8-fullhd{--columnGap: 2rem}}.tile{align-items:stretch;display:block;flex-basis:0;flex-grow:1;flex-shrink:1;min-height:min-content}.tile.is-ancestor{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}.tile.is-ancestor:last-child{margin-bottom:-.75rem}.tile.is-ancestor:not(:last-child){margin-bottom:.75rem}.tile.is-child{margin:0 !important}.tile.is-parent{padding:.75rem}.tile.is-vertical{flex-direction:column}.tile.is-vertical>.tile.is-child:not(:last-child){margin-bottom:1.5rem !important}@media screen and (min-width: 769px),print{.tile:not(.is-child){display:flex}.tile.is-1{flex:none;width:8.33333337%}.tile.is-2{flex:none;width:16.66666674%}.tile.is-3{flex:none;width:25%}.tile.is-4{flex:none;width:33.33333337%}.tile.is-5{flex:none;width:41.66666674%}.tile.is-6{flex:none;width:50%}.tile.is-7{flex:none;width:58.33333337%}.tile.is-8{flex:none;width:66.66666674%}.tile.is-9{flex:none;width:75%}.tile.is-10{flex:none;width:83.33333337%}.tile.is-11{flex:none;width:91.66666674%}.tile.is-12{flex:none;width:100%}}.hero{align-items:stretch;display:flex;flex-direction:column;justify-content:space-between}.hero .navbar{background:none}.hero .tabs ul{border-bottom:none}.hero.is-white{background-color:#fff;color:#0a0a0a}.hero.is-white a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-white strong{color:inherit}.hero.is-white .title{color:#0a0a0a}.hero.is-white .subtitle{color:rgba(10,10,10,0.9)}.hero.is-white .subtitle a:not(.button),.hero.is-white .subtitle strong{color:#0a0a0a}@media screen and (max-width: 1055px){.hero.is-white .navbar-menu{background-color:#fff}}.hero.is-white .navbar-item,.hero.is-white .navbar-link{color:rgba(10,10,10,0.7)}.hero.is-white a.navbar-item:hover,.hero.is-white a.navbar-item.is-active,.hero.is-white .navbar-link:hover,.hero.is-white .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}.hero.is-white .tabs a{color:#0a0a0a;opacity:0.9}.hero.is-white .tabs a:hover{opacity:1}.hero.is-white .tabs li.is-active a{color:#fff !important;opacity:1}.hero.is-white .tabs.is-boxed a,.hero.is-white .tabs.is-toggle a{color:#0a0a0a}.hero.is-white .tabs.is-boxed a:hover,.hero.is-white .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-white .tabs.is-boxed li.is-active a,.hero.is-white .tabs.is-boxed li.is-active a:hover,.hero.is-white .tabs.is-toggle li.is-active a,.hero.is-white .tabs.is-toggle li.is-active a:hover{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}.hero.is-white.is-bold{background-image:linear-gradient(141deg, #e8e3e4 0%, #fff 71%, #fff 100%)}@media screen and (max-width: 768px){.hero.is-white.is-bold .navbar-menu{background-image:linear-gradient(141deg, #e8e3e4 0%, #fff 71%, #fff 100%)}}.hero.is-black{background-color:#0a0a0a;color:#fff}.hero.is-black a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-black strong{color:inherit}.hero.is-black .title{color:#fff}.hero.is-black .subtitle{color:rgba(255,255,255,0.9)}.hero.is-black .subtitle a:not(.button),.hero.is-black .subtitle strong{color:#fff}@media screen and (max-width: 1055px){.hero.is-black .navbar-menu{background-color:#0a0a0a}}.hero.is-black .navbar-item,.hero.is-black .navbar-link{color:rgba(255,255,255,0.7)}.hero.is-black a.navbar-item:hover,.hero.is-black a.navbar-item.is-active,.hero.is-black .navbar-link:hover,.hero.is-black .navbar-link.is-active{background-color:#000;color:#fff}.hero.is-black .tabs a{color:#fff;opacity:0.9}.hero.is-black .tabs a:hover{opacity:1}.hero.is-black .tabs li.is-active a{color:#0a0a0a !important;opacity:1}.hero.is-black .tabs.is-boxed a,.hero.is-black .tabs.is-toggle a{color:#fff}.hero.is-black .tabs.is-boxed a:hover,.hero.is-black .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-black .tabs.is-boxed li.is-active a,.hero.is-black .tabs.is-boxed li.is-active a:hover,.hero.is-black .tabs.is-toggle li.is-active a,.hero.is-black .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#0a0a0a}.hero.is-black.is-bold{background-image:linear-gradient(141deg, #000 0%, #0a0a0a 71%, #181616 100%)}@media screen and (max-width: 768px){.hero.is-black.is-bold .navbar-menu{background-image:linear-gradient(141deg, #000 0%, #0a0a0a 71%, #181616 100%)}}.hero.is-light{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}.hero.is-light a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-light strong{color:inherit}.hero.is-light .title{color:rgba(0,0,0,0.7)}.hero.is-light .subtitle{color:rgba(0,0,0,0.9)}.hero.is-light .subtitle a:not(.button),.hero.is-light .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){.hero.is-light .navbar-menu{background-color:#f5f5f5}}.hero.is-light .navbar-item,.hero.is-light .navbar-link{color:rgba(0,0,0,0.7)}.hero.is-light a.navbar-item:hover,.hero.is-light a.navbar-item.is-active,.hero.is-light .navbar-link:hover,.hero.is-light .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}.hero.is-light .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}.hero.is-light .tabs a:hover{opacity:1}.hero.is-light .tabs li.is-active a{color:#f5f5f5 !important;opacity:1}.hero.is-light .tabs.is-boxed a,.hero.is-light .tabs.is-toggle a{color:rgba(0,0,0,0.7)}.hero.is-light .tabs.is-boxed a:hover,.hero.is-light .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-light .tabs.is-boxed li.is-active a,.hero.is-light .tabs.is-boxed li.is-active a:hover,.hero.is-light .tabs.is-toggle li.is-active a,.hero.is-light .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#f5f5f5}.hero.is-light.is-bold{background-image:linear-gradient(141deg, #dfd8d9 0%, #f5f5f5 71%, #fff 100%)}@media screen and (max-width: 768px){.hero.is-light.is-bold .navbar-menu{background-image:linear-gradient(141deg, #dfd8d9 0%, #f5f5f5 71%, #fff 100%)}}.hero.is-dark,.content kbd.hero{background-color:#363636;color:#fff}.hero.is-dark a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.content kbd.hero a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-dark strong,.content kbd.hero strong{color:inherit}.hero.is-dark .title,.content kbd.hero .title{color:#fff}.hero.is-dark .subtitle,.content kbd.hero .subtitle{color:rgba(255,255,255,0.9)}.hero.is-dark .subtitle a:not(.button),.content kbd.hero .subtitle a:not(.button),.hero.is-dark .subtitle strong,.content kbd.hero .subtitle strong{color:#fff}@media screen and (max-width: 1055px){.hero.is-dark .navbar-menu,.content kbd.hero .navbar-menu{background-color:#363636}}.hero.is-dark .navbar-item,.content kbd.hero .navbar-item,.hero.is-dark .navbar-link,.content kbd.hero .navbar-link{color:rgba(255,255,255,0.7)}.hero.is-dark a.navbar-item:hover,.content kbd.hero a.navbar-item:hover,.hero.is-dark a.navbar-item.is-active,.content kbd.hero a.navbar-item.is-active,.hero.is-dark .navbar-link:hover,.content kbd.hero .navbar-link:hover,.hero.is-dark .navbar-link.is-active,.content kbd.hero .navbar-link.is-active{background-color:#292929;color:#fff}.hero.is-dark .tabs a,.content kbd.hero .tabs a{color:#fff;opacity:0.9}.hero.is-dark .tabs a:hover,.content kbd.hero .tabs a:hover{opacity:1}.hero.is-dark .tabs li.is-active a,.content kbd.hero .tabs li.is-active a{color:#363636 !important;opacity:1}.hero.is-dark .tabs.is-boxed a,.content kbd.hero .tabs.is-boxed a,.hero.is-dark .tabs.is-toggle a,.content kbd.hero .tabs.is-toggle a{color:#fff}.hero.is-dark .tabs.is-boxed a:hover,.content kbd.hero .tabs.is-boxed a:hover,.hero.is-dark .tabs.is-toggle a:hover,.content kbd.hero .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-dark .tabs.is-boxed li.is-active a,.content kbd.hero .tabs.is-boxed li.is-active a,.hero.is-dark .tabs.is-boxed li.is-active a:hover,.hero.is-dark .tabs.is-toggle li.is-active a,.content kbd.hero .tabs.is-toggle li.is-active a,.hero.is-dark .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#363636}.hero.is-dark.is-bold,.content kbd.hero.is-bold{background-image:linear-gradient(141deg, #1f191a 0%, #363636 71%, #46403f 100%)}@media screen and (max-width: 768px){.hero.is-dark.is-bold .navbar-menu,.content kbd.hero.is-bold .navbar-menu{background-image:linear-gradient(141deg, #1f191a 0%, #363636 71%, #46403f 100%)}}.hero.is-primary,.docstring>section>a.hero.docs-sourcelink{background-color:#4eb5de;color:#fff}.hero.is-primary a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.docstring>section>a.hero.docs-sourcelink a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-primary strong,.docstring>section>a.hero.docs-sourcelink strong{color:inherit}.hero.is-primary .title,.docstring>section>a.hero.docs-sourcelink .title{color:#fff}.hero.is-primary .subtitle,.docstring>section>a.hero.docs-sourcelink .subtitle{color:rgba(255,255,255,0.9)}.hero.is-primary .subtitle a:not(.button),.docstring>section>a.hero.docs-sourcelink .subtitle a:not(.button),.hero.is-primary .subtitle strong,.docstring>section>a.hero.docs-sourcelink .subtitle strong{color:#fff}@media screen and (max-width: 1055px){.hero.is-primary .navbar-menu,.docstring>section>a.hero.docs-sourcelink .navbar-menu{background-color:#4eb5de}}.hero.is-primary .navbar-item,.docstring>section>a.hero.docs-sourcelink .navbar-item,.hero.is-primary .navbar-link,.docstring>section>a.hero.docs-sourcelink .navbar-link{color:rgba(255,255,255,0.7)}.hero.is-primary a.navbar-item:hover,.docstring>section>a.hero.docs-sourcelink a.navbar-item:hover,.hero.is-primary a.navbar-item.is-active,.docstring>section>a.hero.docs-sourcelink a.navbar-item.is-active,.hero.is-primary .navbar-link:hover,.docstring>section>a.hero.docs-sourcelink .navbar-link:hover,.hero.is-primary .navbar-link.is-active,.docstring>section>a.hero.docs-sourcelink .navbar-link.is-active{background-color:#39acda;color:#fff}.hero.is-primary .tabs a,.docstring>section>a.hero.docs-sourcelink .tabs a{color:#fff;opacity:0.9}.hero.is-primary .tabs a:hover,.docstring>section>a.hero.docs-sourcelink .tabs a:hover{opacity:1}.hero.is-primary .tabs li.is-active a,.docstring>section>a.hero.docs-sourcelink .tabs li.is-active a{color:#4eb5de !important;opacity:1}.hero.is-primary .tabs.is-boxed a,.docstring>section>a.hero.docs-sourcelink .tabs.is-boxed a,.hero.is-primary .tabs.is-toggle a,.docstring>section>a.hero.docs-sourcelink .tabs.is-toggle a{color:#fff}.hero.is-primary .tabs.is-boxed a:hover,.docstring>section>a.hero.docs-sourcelink .tabs.is-boxed a:hover,.hero.is-primary .tabs.is-toggle a:hover,.docstring>section>a.hero.docs-sourcelink .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-primary .tabs.is-boxed li.is-active a,.docstring>section>a.hero.docs-sourcelink .tabs.is-boxed li.is-active a,.hero.is-primary .tabs.is-boxed li.is-active a:hover,.hero.is-primary .tabs.is-toggle li.is-active a,.docstring>section>a.hero.docs-sourcelink .tabs.is-toggle li.is-active a,.hero.is-primary .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#4eb5de}.hero.is-primary.is-bold,.docstring>section>a.hero.is-bold.docs-sourcelink{background-image:linear-gradient(141deg, #1bc7de 0%, #4eb5de 71%, #5fa9e7 100%)}@media screen and (max-width: 768px){.hero.is-primary.is-bold .navbar-menu,.docstring>section>a.hero.is-bold.docs-sourcelink .navbar-menu{background-image:linear-gradient(141deg, #1bc7de 0%, #4eb5de 71%, #5fa9e7 100%)}}.hero.is-link{background-color:#2e63b8;color:#fff}.hero.is-link a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-link strong{color:inherit}.hero.is-link .title{color:#fff}.hero.is-link .subtitle{color:rgba(255,255,255,0.9)}.hero.is-link .subtitle a:not(.button),.hero.is-link .subtitle strong{color:#fff}@media screen and (max-width: 1055px){.hero.is-link .navbar-menu{background-color:#2e63b8}}.hero.is-link .navbar-item,.hero.is-link .navbar-link{color:rgba(255,255,255,0.7)}.hero.is-link a.navbar-item:hover,.hero.is-link a.navbar-item.is-active,.hero.is-link .navbar-link:hover,.hero.is-link .navbar-link.is-active{background-color:#2958a4;color:#fff}.hero.is-link .tabs a{color:#fff;opacity:0.9}.hero.is-link .tabs a:hover{opacity:1}.hero.is-link .tabs li.is-active a{color:#2e63b8 !important;opacity:1}.hero.is-link .tabs.is-boxed a,.hero.is-link .tabs.is-toggle a{color:#fff}.hero.is-link .tabs.is-boxed a:hover,.hero.is-link .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-link .tabs.is-boxed li.is-active a,.hero.is-link .tabs.is-boxed li.is-active a:hover,.hero.is-link .tabs.is-toggle li.is-active a,.hero.is-link .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#2e63b8}.hero.is-link.is-bold{background-image:linear-gradient(141deg, #1b6098 0%, #2e63b8 71%, #2d51d2 100%)}@media screen and (max-width: 768px){.hero.is-link.is-bold .navbar-menu{background-image:linear-gradient(141deg, #1b6098 0%, #2e63b8 71%, #2d51d2 100%)}}.hero.is-info{background-color:#3c5dcd;color:#fff}.hero.is-info a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-info strong{color:inherit}.hero.is-info .title{color:#fff}.hero.is-info .subtitle{color:rgba(255,255,255,0.9)}.hero.is-info .subtitle a:not(.button),.hero.is-info .subtitle strong{color:#fff}@media screen and (max-width: 1055px){.hero.is-info .navbar-menu{background-color:#3c5dcd}}.hero.is-info .navbar-item,.hero.is-info .navbar-link{color:rgba(255,255,255,0.7)}.hero.is-info a.navbar-item:hover,.hero.is-info a.navbar-item.is-active,.hero.is-info .navbar-link:hover,.hero.is-info .navbar-link.is-active{background-color:#3151bf;color:#fff}.hero.is-info .tabs a{color:#fff;opacity:0.9}.hero.is-info .tabs a:hover{opacity:1}.hero.is-info .tabs li.is-active a{color:#3c5dcd !important;opacity:1}.hero.is-info .tabs.is-boxed a,.hero.is-info .tabs.is-toggle a{color:#fff}.hero.is-info .tabs.is-boxed a:hover,.hero.is-info .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-info .tabs.is-boxed li.is-active a,.hero.is-info .tabs.is-boxed li.is-active a:hover,.hero.is-info .tabs.is-toggle li.is-active a,.hero.is-info .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#3c5dcd}.hero.is-info.is-bold{background-image:linear-gradient(141deg, #215bb5 0%, #3c5dcd 71%, #4b53d8 100%)}@media screen and (max-width: 768px){.hero.is-info.is-bold .navbar-menu{background-image:linear-gradient(141deg, #215bb5 0%, #3c5dcd 71%, #4b53d8 100%)}}.hero.is-success{background-color:#259a12;color:#fff}.hero.is-success a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-success strong{color:inherit}.hero.is-success .title{color:#fff}.hero.is-success .subtitle{color:rgba(255,255,255,0.9)}.hero.is-success .subtitle a:not(.button),.hero.is-success .subtitle strong{color:#fff}@media screen and (max-width: 1055px){.hero.is-success .navbar-menu{background-color:#259a12}}.hero.is-success .navbar-item,.hero.is-success .navbar-link{color:rgba(255,255,255,0.7)}.hero.is-success a.navbar-item:hover,.hero.is-success a.navbar-item.is-active,.hero.is-success .navbar-link:hover,.hero.is-success .navbar-link.is-active{background-color:#20830f;color:#fff}.hero.is-success .tabs a{color:#fff;opacity:0.9}.hero.is-success .tabs a:hover{opacity:1}.hero.is-success .tabs li.is-active a{color:#259a12 !important;opacity:1}.hero.is-success .tabs.is-boxed a,.hero.is-success .tabs.is-toggle a{color:#fff}.hero.is-success .tabs.is-boxed a:hover,.hero.is-success .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-success .tabs.is-boxed li.is-active a,.hero.is-success .tabs.is-boxed li.is-active a:hover,.hero.is-success .tabs.is-toggle li.is-active a,.hero.is-success .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#259a12}.hero.is-success.is-bold{background-image:linear-gradient(141deg, #287207 0%, #259a12 71%, #10b614 100%)}@media screen and (max-width: 768px){.hero.is-success.is-bold .navbar-menu{background-image:linear-gradient(141deg, #287207 0%, #259a12 71%, #10b614 100%)}}.hero.is-warning{background-color:#a98800;color:#fff}.hero.is-warning a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-warning strong{color:inherit}.hero.is-warning .title{color:#fff}.hero.is-warning .subtitle{color:rgba(255,255,255,0.9)}.hero.is-warning .subtitle a:not(.button),.hero.is-warning .subtitle strong{color:#fff}@media screen and (max-width: 1055px){.hero.is-warning .navbar-menu{background-color:#a98800}}.hero.is-warning .navbar-item,.hero.is-warning .navbar-link{color:rgba(255,255,255,0.7)}.hero.is-warning a.navbar-item:hover,.hero.is-warning a.navbar-item.is-active,.hero.is-warning .navbar-link:hover,.hero.is-warning .navbar-link.is-active{background-color:#8f7300;color:#fff}.hero.is-warning .tabs a{color:#fff;opacity:0.9}.hero.is-warning .tabs a:hover{opacity:1}.hero.is-warning .tabs li.is-active a{color:#a98800 !important;opacity:1}.hero.is-warning .tabs.is-boxed a,.hero.is-warning .tabs.is-toggle a{color:#fff}.hero.is-warning .tabs.is-boxed a:hover,.hero.is-warning .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-warning .tabs.is-boxed li.is-active a,.hero.is-warning .tabs.is-boxed li.is-active a:hover,.hero.is-warning .tabs.is-toggle li.is-active a,.hero.is-warning .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#a98800}.hero.is-warning.is-bold{background-image:linear-gradient(141deg, #764b00 0%, #a98800 71%, #c2bd00 100%)}@media screen and (max-width: 768px){.hero.is-warning.is-bold .navbar-menu{background-image:linear-gradient(141deg, #764b00 0%, #a98800 71%, #c2bd00 100%)}}.hero.is-danger{background-color:#cb3c33;color:#fff}.hero.is-danger a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-danger strong{color:inherit}.hero.is-danger .title{color:#fff}.hero.is-danger .subtitle{color:rgba(255,255,255,0.9)}.hero.is-danger .subtitle a:not(.button),.hero.is-danger .subtitle strong{color:#fff}@media screen and (max-width: 1055px){.hero.is-danger .navbar-menu{background-color:#cb3c33}}.hero.is-danger .navbar-item,.hero.is-danger .navbar-link{color:rgba(255,255,255,0.7)}.hero.is-danger a.navbar-item:hover,.hero.is-danger a.navbar-item.is-active,.hero.is-danger .navbar-link:hover,.hero.is-danger .navbar-link.is-active{background-color:#b7362e;color:#fff}.hero.is-danger .tabs a{color:#fff;opacity:0.9}.hero.is-danger .tabs a:hover{opacity:1}.hero.is-danger .tabs li.is-active a{color:#cb3c33 !important;opacity:1}.hero.is-danger .tabs.is-boxed a,.hero.is-danger .tabs.is-toggle a{color:#fff}.hero.is-danger .tabs.is-boxed a:hover,.hero.is-danger .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-danger .tabs.is-boxed li.is-active a,.hero.is-danger .tabs.is-boxed li.is-active a:hover,.hero.is-danger .tabs.is-toggle li.is-active a,.hero.is-danger .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#cb3c33}.hero.is-danger.is-bold{background-image:linear-gradient(141deg, #ac1f2e 0%, #cb3c33 71%, #d66341 100%)}@media screen and (max-width: 768px){.hero.is-danger.is-bold .navbar-menu{background-image:linear-gradient(141deg, #ac1f2e 0%, #cb3c33 71%, #d66341 100%)}}.hero.is-small .hero-body,#documenter .docs-sidebar form.docs-search>input.hero .hero-body{padding:1.5rem}@media screen and (min-width: 769px),print{.hero.is-medium .hero-body{padding:9rem 4.5rem}}@media screen and (min-width: 769px),print{.hero.is-large .hero-body{padding:18rem 6rem}}.hero.is-halfheight .hero-body,.hero.is-fullheight .hero-body,.hero.is-fullheight-with-navbar .hero-body{align-items:center;display:flex}.hero.is-halfheight .hero-body>.container,.hero.is-fullheight .hero-body>.container,.hero.is-fullheight-with-navbar .hero-body>.container{flex-grow:1;flex-shrink:1}.hero.is-halfheight{min-height:50vh}.hero.is-fullheight{min-height:100vh}.hero-video{overflow:hidden}.hero-video video{left:50%;min-height:100%;min-width:100%;position:absolute;top:50%;transform:translate3d(-50%, -50%, 0)}.hero-video.is-transparent{opacity:0.3}@media screen and (max-width: 768px){.hero-video{display:none}}.hero-buttons{margin-top:1.5rem}@media screen and (max-width: 768px){.hero-buttons .button{display:flex}.hero-buttons .button:not(:last-child){margin-bottom:0.75rem}}@media screen and (min-width: 769px),print{.hero-buttons{display:flex;justify-content:center}.hero-buttons .button:not(:last-child){margin-right:1.5rem}}.hero-head,.hero-foot{flex-grow:0;flex-shrink:0}.hero-body{flex-grow:1;flex-shrink:0;padding:3rem 1.5rem}@media screen and (min-width: 769px),print{.hero-body{padding:3rem 3rem}}.section{padding:3rem 1.5rem}@media screen and (min-width: 1056px){.section{padding:3rem 3rem}.section.is-medium{padding:9rem 4.5rem}.section.is-large{padding:18rem 6rem}}.footer{background-color:#fafafa;padding:3rem 1.5rem 6rem}h1 .docs-heading-anchor,h1 .docs-heading-anchor:hover,h1 .docs-heading-anchor:visited,h2 .docs-heading-anchor,h2 .docs-heading-anchor:hover,h2 .docs-heading-anchor:visited,h3 .docs-heading-anchor,h3 .docs-heading-anchor:hover,h3 .docs-heading-anchor:visited,h4 .docs-heading-anchor,h4 .docs-heading-anchor:hover,h4 .docs-heading-anchor:visited,h5 .docs-heading-anchor,h5 .docs-heading-anchor:hover,h5 .docs-heading-anchor:visited,h6 .docs-heading-anchor,h6 .docs-heading-anchor:hover,h6 .docs-heading-anchor:visited{color:#222}h1 .docs-heading-anchor-permalink,h2 .docs-heading-anchor-permalink,h3 .docs-heading-anchor-permalink,h4 .docs-heading-anchor-permalink,h5 .docs-heading-anchor-permalink,h6 .docs-heading-anchor-permalink{visibility:hidden;vertical-align:middle;margin-left:0.5em;font-size:0.7rem}h1 .docs-heading-anchor-permalink::before,h2 .docs-heading-anchor-permalink::before,h3 .docs-heading-anchor-permalink::before,h4 .docs-heading-anchor-permalink::before,h5 .docs-heading-anchor-permalink::before,h6 .docs-heading-anchor-permalink::before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f0c1"}h1:hover .docs-heading-anchor-permalink,h2:hover .docs-heading-anchor-permalink,h3:hover .docs-heading-anchor-permalink,h4:hover .docs-heading-anchor-permalink,h5:hover .docs-heading-anchor-permalink,h6:hover .docs-heading-anchor-permalink{visibility:visible}.docs-dark-only{display:none !important}pre{position:relative;overflow:hidden}pre code,pre code.hljs{padding:0 .75rem !important;overflow:auto;display:block}pre code:first-of-type,pre code.hljs:first-of-type{padding-top:0.5rem !important}pre code:last-of-type,pre code.hljs:last-of-type{padding-bottom:0.5rem !important}pre .copy-button{opacity:0.2;transition:opacity 0.2s;position:absolute;right:0em;top:0em;padding:0.5em;width:2.5em;height:2.5em;background:transparent;border:none;font-family:"Font Awesome 6 Free";color:#222;cursor:pointer;text-align:center}pre .copy-button:focus,pre .copy-button:hover{opacity:1;background:rgba(34,34,34,0.1);color:#2e63b8}pre .copy-button.success{color:#259a12;opacity:1}pre .copy-button.error{color:#cb3c33;opacity:1}pre:hover .copy-button{opacity:1}.admonition{background-color:#f5f5f5;border-style:solid;border-width:2px;border-color:#4a4a4a;border-radius:4px;font-size:1rem}.admonition strong{color:currentColor}.admonition.is-small,#documenter .docs-sidebar form.docs-search>input.admonition{font-size:.75rem}.admonition.is-medium{font-size:1.25rem}.admonition.is-large{font-size:1.5rem}.admonition.is-default{background-color:#f5f5f5;border-color:#4a4a4a}.admonition.is-default>.admonition-header{background-color:rgba(0,0,0,0);color:#4a4a4a}.admonition.is-default>.admonition-body{color:rgba(0,0,0,0.7)}.admonition.is-info{background-color:#f5f5f5;border-color:#3c5dcd}.admonition.is-info>.admonition-header{background-color:rgba(0,0,0,0);color:#3c5dcd}.admonition.is-info>.admonition-body{color:rgba(0,0,0,0.7)}.admonition.is-success{background-color:#f5f5f5;border-color:#259a12}.admonition.is-success>.admonition-header{background-color:rgba(0,0,0,0);color:#259a12}.admonition.is-success>.admonition-body{color:rgba(0,0,0,0.7)}.admonition.is-warning{background-color:#f5f5f5;border-color:#a98800}.admonition.is-warning>.admonition-header{background-color:rgba(0,0,0,0);color:#a98800}.admonition.is-warning>.admonition-body{color:rgba(0,0,0,0.7)}.admonition.is-danger{background-color:#f5f5f5;border-color:#cb3c33}.admonition.is-danger>.admonition-header{background-color:rgba(0,0,0,0);color:#cb3c33}.admonition.is-danger>.admonition-body{color:rgba(0,0,0,0.7)}.admonition.is-compat{background-color:#f5f5f5;border-color:#3489da}.admonition.is-compat>.admonition-header{background-color:rgba(0,0,0,0);color:#3489da}.admonition.is-compat>.admonition-body{color:rgba(0,0,0,0.7)}.admonition.is-todo{background-color:#f5f5f5;border-color:#9558b2}.admonition.is-todo>.admonition-header{background-color:rgba(0,0,0,0);color:#9558b2}.admonition.is-todo>.admonition-body{color:rgba(0,0,0,0.7)}.admonition-header{color:#4a4a4a;background-color:rgba(0,0,0,0);align-items:center;font-weight:700;justify-content:space-between;line-height:1.25;padding:0.5rem .75rem;position:relative}.admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;margin-right:.75rem;content:"\f06a"}details.admonition.is-details>.admonition-header{list-style:none}details.admonition.is-details>.admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f055"}details.admonition.is-details[open]>.admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f056"}.admonition-body{color:#222;padding:0.5rem .75rem}.admonition-body pre{background-color:#f5f5f5}.admonition-body code{background-color:rgba(0,0,0,0.05)}.docstring{margin-bottom:1em;background-color:rgba(0,0,0,0);border:2px solid #dbdbdb;border-radius:4px;box-shadow:2px 2px 3px rgba(10,10,10,0.1);max-width:100%}.docstring>header{cursor:pointer;display:flex;flex-grow:1;align-items:stretch;padding:0.5rem .75rem;background-color:#f5f5f5;box-shadow:0 0.125em 0.25em rgba(10,10,10,0.1);box-shadow:none;border-bottom:1px solid #dbdbdb;overflow:auto}.docstring>header code{background-color:transparent}.docstring>header .docstring-article-toggle-button{min-width:1.1rem;padding:0.2rem 0.2rem 0.2rem 0}.docstring>header .docstring-binding{margin-right:0.3em}.docstring>header .docstring-category{margin-left:0.3em}.docstring>section{position:relative;padding:.75rem .75rem;border-bottom:1px solid #dbdbdb}.docstring>section:last-child{border-bottom:none}.docstring>section>a.docs-sourcelink{transition:opacity 0.3s;opacity:0;position:absolute;right:.375rem;bottom:.375rem}.docstring>section>a.docs-sourcelink:focus{opacity:1 !important}.docstring:hover>section>a.docs-sourcelink{opacity:0.2}.docstring:focus-within>section>a.docs-sourcelink{opacity:0.2}.docstring>section:hover a.docs-sourcelink{opacity:1}.documenter-example-output{background-color:#fff}.outdated-warning-overlay{position:fixed;top:0;left:0;right:0;box-shadow:0 0 10px rgba(0,0,0,0.3);z-index:999;background-color:#f5f5f5;color:rgba(0,0,0,0.7);border-bottom:3px solid rgba(0,0,0,0);padding:10px 35px;text-align:center;font-size:15px}.outdated-warning-overlay .outdated-warning-closer{position:absolute;top:calc(50% - 10px);right:18px;cursor:pointer;width:12px}.outdated-warning-overlay a{color:#2e63b8}.outdated-warning-overlay a:hover{color:#363636}.content pre{border:2px solid #dbdbdb;border-radius:4px}.content code{font-weight:inherit}.content a code{color:#2e63b8}.content a:hover code{color:#363636}.content h1 code,.content h2 code,.content h3 code,.content h4 code,.content h5 code,.content h6 code{color:#222}.content table{display:block;width:initial;max-width:100%;overflow-x:auto}.content blockquote>ul:first-child,.content blockquote>ol:first-child,.content .admonition-body>ul:first-child,.content .admonition-body>ol:first-child{margin-top:0}pre,code{font-variant-ligatures:no-contextual}.breadcrumb a.is-disabled{cursor:default;pointer-events:none}.breadcrumb a.is-disabled,.breadcrumb a.is-disabled:hover{color:#222}.hljs{background:initial !important}.katex .katex-mathml{top:0;right:0}.katex-display,mjx-container,.MathJax_Display{margin:0.5em 0 !important}html{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto}li.no-marker{list-style:none}#documenter .docs-main>article{overflow-wrap:break-word}#documenter .docs-main>article .math-container{overflow-x:auto;overflow-y:hidden}@media screen and (min-width: 1056px){#documenter .docs-main{max-width:52rem;margin-left:20rem;padding-right:1rem}}@media screen and (max-width: 1055px){#documenter .docs-main{width:100%}#documenter .docs-main>article{max-width:52rem;margin-left:auto;margin-right:auto;margin-bottom:1rem;padding:0 1rem}#documenter .docs-main>header,#documenter .docs-main>nav{max-width:100%;width:100%;margin:0}}#documenter .docs-main header.docs-navbar{background-color:#fff;border-bottom:1px solid #dbdbdb;z-index:2;min-height:4rem;margin-bottom:1rem;display:flex}#documenter .docs-main header.docs-navbar .breadcrumb{flex-grow:1;overflow-x:hidden}#documenter .docs-main header.docs-navbar .docs-sidebar-button{display:block;font-size:1.5rem;padding-bottom:0.1rem;margin-right:1rem}#documenter .docs-main header.docs-navbar .docs-right{display:flex;white-space:nowrap;gap:1rem;align-items:center}#documenter .docs-main header.docs-navbar .docs-right .docs-icon,#documenter .docs-main header.docs-navbar .docs-right .docs-label{display:inline-block}#documenter .docs-main header.docs-navbar .docs-right .docs-label{padding:0;margin-left:0.3em}@media screen and (max-width: 1055px){#documenter .docs-main header.docs-navbar .docs-right .docs-navbar-link{margin-left:0.4rem;margin-right:0.4rem}}#documenter .docs-main header.docs-navbar>*{margin:auto 0}@media screen and (max-width: 1055px){#documenter .docs-main header.docs-navbar{position:sticky;top:0;padding:0 1rem;transition-property:top, box-shadow;-webkit-transition-property:top, box-shadow;transition-duration:0.3s;-webkit-transition-duration:0.3s}#documenter .docs-main header.docs-navbar.headroom--not-top{box-shadow:.2rem 0rem .4rem #bbb;transition-duration:0.7s;-webkit-transition-duration:0.7s}#documenter .docs-main header.docs-navbar.headroom--unpinned.headroom--not-top.headroom--not-bottom{top:-4.5rem;transition-duration:0.7s;-webkit-transition-duration:0.7s}}#documenter .docs-main section.footnotes{border-top:1px solid #dbdbdb}#documenter .docs-main section.footnotes li .tag:first-child,#documenter .docs-main section.footnotes li .docstring>section>a.docs-sourcelink:first-child,#documenter .docs-main section.footnotes li .content kbd:first-child,.content #documenter .docs-main section.footnotes li kbd:first-child{margin-right:1em;margin-bottom:0.4em}#documenter .docs-main .docs-footer{display:flex;flex-wrap:wrap;margin-left:0;margin-right:0;border-top:1px solid #dbdbdb;padding-top:1rem;padding-bottom:1rem}@media screen and (max-width: 1055px){#documenter .docs-main .docs-footer{padding-left:1rem;padding-right:1rem}}#documenter .docs-main .docs-footer .docs-footer-nextpage,#documenter .docs-main .docs-footer .docs-footer-prevpage{flex-grow:1}#documenter .docs-main .docs-footer .docs-footer-nextpage{text-align:right}#documenter .docs-main .docs-footer .flexbox-break{flex-basis:100%;height:0}#documenter .docs-main .docs-footer .footer-message{font-size:0.8em;margin:0.5em auto 0 auto;text-align:center}#documenter .docs-sidebar{display:flex;flex-direction:column;color:#0a0a0a;background-color:#f5f5f5;border-right:1px solid #dbdbdb;padding:0;flex:0 0 18rem;z-index:5;font-size:1rem;position:fixed;left:-18rem;width:18rem;height:100%;transition:left 0.3s}#documenter .docs-sidebar.visible{left:0;box-shadow:.4rem 0rem .8rem #bbb}@media screen and (min-width: 1056px){#documenter .docs-sidebar.visible{box-shadow:none}}@media screen and (min-width: 1056px){#documenter .docs-sidebar{left:0;top:0}}#documenter .docs-sidebar .docs-logo{margin-top:1rem;padding:0 1rem}#documenter .docs-sidebar .docs-logo>img{max-height:6rem;margin:auto}#documenter .docs-sidebar .docs-package-name{flex-shrink:0;font-size:1.5rem;font-weight:700;text-align:center;white-space:nowrap;overflow:hidden;padding:0.5rem 0}#documenter .docs-sidebar .docs-package-name .docs-autofit{max-width:16.2rem}#documenter .docs-sidebar .docs-package-name a,#documenter .docs-sidebar .docs-package-name a:hover{color:#0a0a0a}#documenter .docs-sidebar .docs-version-selector{border-top:1px solid #dbdbdb;display:none;padding:0.5rem}#documenter .docs-sidebar .docs-version-selector.visible{display:flex}#documenter .docs-sidebar ul.docs-menu{flex-grow:1;user-select:none;border-top:1px solid #dbdbdb;padding-bottom:1.5rem}#documenter .docs-sidebar ul.docs-menu>li>.tocitem{font-weight:bold}#documenter .docs-sidebar ul.docs-menu>li li{font-size:.95rem;margin-left:1em;border-left:1px solid #dbdbdb}#documenter .docs-sidebar ul.docs-menu input.collapse-toggle{display:none}#documenter .docs-sidebar ul.docs-menu ul.collapsed{display:none}#documenter .docs-sidebar ul.docs-menu input:checked~ul.collapsed{display:block}#documenter .docs-sidebar ul.docs-menu label.tocitem{display:flex}#documenter .docs-sidebar ul.docs-menu label.tocitem .docs-label{flex-grow:2}#documenter .docs-sidebar ul.docs-menu label.tocitem .docs-chevron{display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1;font-size:.75rem;margin-left:1rem;margin-top:auto;margin-bottom:auto}#documenter .docs-sidebar ul.docs-menu label.tocitem .docs-chevron::before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f054"}#documenter .docs-sidebar ul.docs-menu input:checked~label.tocitem .docs-chevron::before{content:"\f078"}#documenter .docs-sidebar ul.docs-menu .tocitem{display:block;padding:0.5rem 0.5rem}#documenter .docs-sidebar ul.docs-menu .tocitem,#documenter .docs-sidebar ul.docs-menu .tocitem:hover{color:#0a0a0a;background:#f5f5f5}#documenter .docs-sidebar ul.docs-menu a.tocitem:hover,#documenter .docs-sidebar ul.docs-menu label.tocitem:hover{color:#0a0a0a;background-color:#ebebeb}#documenter .docs-sidebar ul.docs-menu li.is-active{border-top:1px solid #dbdbdb;border-bottom:1px solid #dbdbdb;background-color:#fff}#documenter .docs-sidebar ul.docs-menu li.is-active .tocitem,#documenter .docs-sidebar ul.docs-menu li.is-active .tocitem:hover{background-color:#fff;color:#0a0a0a}#documenter .docs-sidebar ul.docs-menu li.is-active ul.internal .tocitem:hover{background-color:#ebebeb;color:#0a0a0a}#documenter .docs-sidebar ul.docs-menu>li.is-active:first-child{border-top:none}#documenter .docs-sidebar ul.docs-menu ul.internal{margin:0 0.5rem 0.5rem;border-top:1px solid #dbdbdb}#documenter .docs-sidebar ul.docs-menu ul.internal li{font-size:.85rem;border-left:none;margin-left:0;margin-top:0.5rem}#documenter .docs-sidebar ul.docs-menu ul.internal .tocitem{width:100%;padding:0}#documenter .docs-sidebar ul.docs-menu ul.internal .tocitem::before{content:"⚬";margin-right:0.4em}#documenter .docs-sidebar form.docs-search{margin:auto;margin-top:0.5rem;margin-bottom:0.5rem}#documenter .docs-sidebar form.docs-search>input{width:14.4rem}#documenter .docs-sidebar #documenter-search-query{color:#707070;width:14.4rem;box-shadow:inset 0 1px 2px rgba(10,10,10,0.1)}@media screen and (min-width: 1056px){#documenter .docs-sidebar ul.docs-menu{overflow-y:auto;-webkit-overflow-scroll:touch}#documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar{width:.3rem;background:none}#documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar-thumb{border-radius:5px 0px 0px 5px;background:#e0e0e0}#documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar-thumb:hover{background:#ccc}}@media screen and (max-width: 1055px){#documenter .docs-sidebar{overflow-y:auto;-webkit-overflow-scroll:touch}#documenter .docs-sidebar::-webkit-scrollbar{width:.3rem;background:none}#documenter .docs-sidebar::-webkit-scrollbar-thumb{border-radius:5px 0px 0px 5px;background:#e0e0e0}#documenter .docs-sidebar::-webkit-scrollbar-thumb:hover{background:#ccc}}kbd.search-modal-key-hints{border-radius:0.25rem;border:1px solid rgba(0,0,0,0.6);box-shadow:0 2px 0 1px rgba(0,0,0,0.6);cursor:default;font-size:0.9rem;line-height:1.5;min-width:0.75rem;text-align:center;padding:0.1rem 0.3rem;position:relative;top:-1px}.search-min-width-50{min-width:50%}.search-min-height-100{min-height:100%}.search-modal-card-body{max-height:calc(100vh - 15rem)}.search-result-link{border-radius:0.7em;transition:all 300ms}.search-result-link:hover,.search-result-link:focus{background-color:rgba(0,128,128,0.1)}.search-result-link .property-search-result-badge,.search-result-link .search-filter{transition:all 300ms}.property-search-result-badge,.search-filter{padding:0.15em 0.5em;font-size:0.8em;font-style:italic;text-transform:none !important;line-height:1.5;color:#f5f5f5;background-color:rgba(51,65,85,0.501961);border-radius:0.6rem}.search-result-link:hover .property-search-result-badge,.search-result-link:hover .search-filter,.search-result-link:focus .property-search-result-badge,.search-result-link:focus .search-filter{color:#f1f5f9;background-color:#333}.search-filter{color:#333;background-color:#f5f5f5;transition:all 300ms}.search-filter:hover,.search-filter:focus{color:#333}.search-filter-selected{color:#f5f5f5;background-color:rgba(139,0,139,0.5)}.search-filter-selected:hover,.search-filter-selected:focus{color:#f5f5f5}.search-result-highlight{background-color:#ffdd57;color:black}.search-divider{border-bottom:1px solid #dbdbdb}.search-result-title{width:85%;color:#333}.search-result-code-title{font-size:0.875rem;font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace}#search-modal .modal-card-body::-webkit-scrollbar,#search-modal .filter-tabs::-webkit-scrollbar{height:10px;width:10px;background-color:transparent}#search-modal .modal-card-body::-webkit-scrollbar-thumb,#search-modal .filter-tabs::-webkit-scrollbar-thumb{background-color:gray;border-radius:1rem}#search-modal .modal-card-body::-webkit-scrollbar-track,#search-modal .filter-tabs::-webkit-scrollbar-track{-webkit-box-shadow:inset 0 0 6px rgba(0,0,0,0.6);background-color:transparent}.w-100{width:100%}.gap-2{gap:0.5rem}.gap-4{gap:1rem}.gap-8{gap:2rem}.ansi span.sgr1{font-weight:bolder}.ansi span.sgr2{font-weight:lighter}.ansi span.sgr3{font-style:italic}.ansi span.sgr4{text-decoration:underline}.ansi span.sgr7{color:#fff;background-color:#222}.ansi span.sgr8{color:transparent}.ansi span.sgr8 span{color:transparent}.ansi span.sgr9{text-decoration:line-through}.ansi span.sgr30{color:#242424}.ansi span.sgr31{color:#a7201f}.ansi span.sgr32{color:#066f00}.ansi span.sgr33{color:#856b00}.ansi span.sgr34{color:#2149b0}.ansi span.sgr35{color:#7d4498}.ansi span.sgr36{color:#007989}.ansi span.sgr37{color:gray}.ansi span.sgr40{background-color:#242424}.ansi span.sgr41{background-color:#a7201f}.ansi span.sgr42{background-color:#066f00}.ansi span.sgr43{background-color:#856b00}.ansi span.sgr44{background-color:#2149b0}.ansi span.sgr45{background-color:#7d4498}.ansi span.sgr46{background-color:#007989}.ansi span.sgr47{background-color:gray}.ansi span.sgr90{color:#616161}.ansi span.sgr91{color:#cb3c33}.ansi span.sgr92{color:#0e8300}.ansi span.sgr93{color:#a98800}.ansi span.sgr94{color:#3c5dcd}.ansi span.sgr95{color:#9256af}.ansi span.sgr96{color:#008fa3}.ansi span.sgr97{color:#f5f5f5}.ansi span.sgr100{background-color:#616161}.ansi span.sgr101{background-color:#cb3c33}.ansi span.sgr102{background-color:#0e8300}.ansi span.sgr103{background-color:#a98800}.ansi span.sgr104{background-color:#3c5dcd}.ansi span.sgr105{background-color:#9256af}.ansi span.sgr106{background-color:#008fa3}.ansi span.sgr107{background-color:#f5f5f5}code.language-julia-repl>span.hljs-meta{color:#066f00;font-weight:bolder}/*! + Theme: Default + Description: Original highlight.js style + Author: (c) Ivan Sagalaev + Maintainer: @highlightjs/core-team + Website: https://highlightjs.org/ + License: see project LICENSE + Touched: 2021 +*/pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#F3F3F3;color:#444}.hljs-comment{color:#697070}.hljs-tag,.hljs-punctuation{color:#444a}.hljs-tag .hljs-name,.hljs-tag .hljs-attr{color:#444}.hljs-keyword,.hljs-attribute,.hljs-selector-tag,.hljs-meta .hljs-keyword,.hljs-doctag,.hljs-name{font-weight:bold}.hljs-type,.hljs-string,.hljs-number,.hljs-selector-id,.hljs-selector-class,.hljs-quote,.hljs-template-tag,.hljs-deletion{color:#880000}.hljs-title,.hljs-section{color:#880000;font-weight:bold}.hljs-regexp,.hljs-symbol,.hljs-variable,.hljs-template-variable,.hljs-link,.hljs-selector-attr,.hljs-operator,.hljs-selector-pseudo{color:#ab5656}.hljs-literal{color:#695}.hljs-built_in,.hljs-bullet,.hljs-code,.hljs-addition{color:#397300}.hljs-meta{color:#1f7199}.hljs-meta .hljs-string{color:#38a}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:bold}.gap-4{gap:1rem} diff --git a/previews/PR798/assets/themeswap.js b/previews/PR798/assets/themeswap.js new file mode 100644 index 0000000000..9f5eebe6aa --- /dev/null +++ b/previews/PR798/assets/themeswap.js @@ -0,0 +1,84 @@ +// Small function to quickly swap out themes. Gets put into the tag.. +function set_theme_from_local_storage() { + // Initialize the theme to null, which means default + var theme = null; + // If the browser supports the localstorage and is not disabled then try to get the + // documenter theme + if (window.localStorage != null) { + // Get the user-picked theme from localStorage. May be `null`, which means the default + // theme. + theme = window.localStorage.getItem("documenter-theme"); + } + // Check if the users preference is for dark color scheme + var darkPreference = + window.matchMedia("(prefers-color-scheme: dark)").matches === true; + // Initialize a few variables for the loop: + // + // - active: will contain the index of the theme that should be active. Note that there + // is no guarantee that localStorage contains sane values. If `active` stays `null` + // we either could not find the theme or it is the default (primary) theme anyway. + // Either way, we then need to stick to the primary theme. + // + // - disabled: style sheets that should be disabled (i.e. all the theme style sheets + // that are not the currently active theme) + var active = null; + var disabled = []; + var primaryLightTheme = null; + var primaryDarkTheme = null; + for (var i = 0; i < document.styleSheets.length; i++) { + var ss = document.styleSheets[i]; + // The tag of each style sheet is expected to have a data-theme-name attribute + // which must contain the name of the theme. The names in localStorage much match this. + var themename = ss.ownerNode.getAttribute("data-theme-name"); + // attribute not set => non-theme stylesheet => ignore + if (themename === null) continue; + // To distinguish the default (primary) theme, it needs to have the data-theme-primary + // attribute set. + if (ss.ownerNode.getAttribute("data-theme-primary") !== null) { + primaryLightTheme = themename; + } + // Check if the theme is primary dark theme so that we could store its name in darkTheme + if (ss.ownerNode.getAttribute("data-theme-primary-dark") !== null) { + primaryDarkTheme = themename; + } + // If we find a matching theme (and it's not the default), we'll set active to non-null + if (themename === theme) active = i; + // Store the style sheets of inactive themes so that we could disable them + if (themename !== theme) disabled.push(ss); + } + var activeTheme = null; + if (active !== null) { + // If we did find an active theme, we'll (1) add the theme--$(theme) class to + document.getElementsByTagName("html")[0].className = "theme--" + theme; + activeTheme = theme; + } else { + // If we did _not_ find an active theme, then we need to fall back to the primary theme + // which can either be dark or light, depending on the user's OS preference. + var activeTheme = darkPreference ? primaryDarkTheme : primaryLightTheme; + // In case it somehow happens that the relevant primary theme was not found in the + // preceding loop, we abort without doing anything. + if (activeTheme === null) { + console.error("Unable to determine primary theme."); + return; + } + // When switching to the primary light theme, then we must not have a class name + // for the tag. That's only for non-primary or the primary dark theme. + if (darkPreference) { + document.getElementsByTagName("html")[0].className = + "theme--" + activeTheme; + } else { + document.getElementsByTagName("html")[0].className = ""; + } + } + for (var i = 0; i < document.styleSheets.length; i++) { + var ss = document.styleSheets[i]; + // The tag of each style sheet is expected to have a data-theme-name attribute + // which must contain the name of the theme. The names in localStorage much match this. + var themename = ss.ownerNode.getAttribute("data-theme-name"); + // attribute not set => non-theme stylesheet => ignore + if (themename === null) continue; + // we'll disable all the stylesheets, except for the active one + ss.disabled = !(themename == activeTheme); + } +} +set_theme_from_local_storage(); diff --git a/previews/PR798/assets/warner.js b/previews/PR798/assets/warner.js new file mode 100644 index 0000000000..3f6f5d0083 --- /dev/null +++ b/previews/PR798/assets/warner.js @@ -0,0 +1,52 @@ +function maybeAddWarning() { + // DOCUMENTER_NEWEST is defined in versions.js, DOCUMENTER_CURRENT_VERSION and DOCUMENTER_STABLE + // in siteinfo.js. + // If either of these are undefined something went horribly wrong, so we abort. + if ( + window.DOCUMENTER_NEWEST === undefined || + window.DOCUMENTER_CURRENT_VERSION === undefined || + window.DOCUMENTER_STABLE === undefined + ) { + return; + } + + // Current version is not a version number, so we can't tell if it's the newest version. Abort. + if (!/v(\d+\.)*\d+/.test(window.DOCUMENTER_CURRENT_VERSION)) { + return; + } + + // Current version is newest version, so no need to add a warning. + if (window.DOCUMENTER_NEWEST === window.DOCUMENTER_CURRENT_VERSION) { + return; + } + + // Add a noindex meta tag (unless one exists) so that search engines don't index this version of the docs. + if (document.body.querySelector('meta[name="robots"]') === null) { + const meta = document.createElement("meta"); + meta.name = "robots"; + meta.content = "noindex"; + + document.getElementsByTagName("head")[0].appendChild(meta); + } + + const div = document.createElement("div"); + div.classList.add("outdated-warning-overlay"); + const closer = document.createElement("button"); + closer.classList.add("outdated-warning-closer", "delete"); + closer.addEventListener("click", function () { + document.body.removeChild(div); + }); + const href = window.documenterBaseURL + "/../" + window.DOCUMENTER_STABLE; + div.innerHTML = + 'This documentation is not for the latest stable release, but for either the development version or an older release.
Click here to go to the documentation for the latest stable release.'; + div.appendChild(closer); + document.body.appendChild(div); +} + +if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", maybeAddWarning); +} else { + maybeAddWarning(); +} diff --git a/previews/PR798/changelog/index.html b/previews/PR798/changelog/index.html new file mode 100644 index 0000000000..dd0d18b996 --- /dev/null +++ b/previews/PR798/changelog/index.html @@ -0,0 +1,114 @@ + +Changelog · Ferrite.jl

Ferrite changelog

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

[Unreleased]

Removed

  • The deprecated third type parameter for interpolations have been removed. Old code which tries to use three parameters will now throw the somewhat cryptic error:
    julia> Lagrange{2, RefCube, 1}()
    +ERROR: too many parameters for type
    (#1083)

Other

v1.0.0 - 2024-09-30

Ferrite version 1.0 is a relatively large release, with a lot of new features, improvements, deprecations and some removals. These changes are made to make the code base more consistent and more suitable for future improvements. With this 1.0 release we are aiming for long time stability, and there is no breaking release 2.0 on the horizon.

Unfortunately this means that code written for Ferrite version 0.3 will have to be updated. All changes, with upgrade paths, are listed in the sections below. Since these sections include a lot of other information as well (new features, internal changes, ...) there is also a dedicated section about upgrading code from Ferrite 0.3 to 1.0 (see below) which include the most common changes that are required. In addition, in all cases where possible, you will be presented with a descriptive error message telling you what needs to change.

Deprecations for 1.0 will be removed during the 1.x release series. When upgrading old code it is therefore recommended to use Ferrite 1.0 as a first stepping stone since this release contain descriptive deprecation error messages that might not exist in e.g. Ferrite version 1.2.

Upgrading code from Ferrite 0.3 to 1.0

This section give a short overview of the most common required changes. More details and motivation are included in the following sections (with links to issues/pull request for more discussion).

  • Interpolations: remove the first parameter (the reference dimension) and use new reference shapes.

    Examples:

    # Linear Lagrange interpolation for a line
    +- Lagrange{1, RefCube, 1}()
    ++ Lagrange{RefLine, 1}()
    +
    +# Linear Lagrange interpolation for a quadrilateral
    +- Lagrange{2, RefCube, 1}()
    ++ Lagrange{RefQuadrilateral, 1}()
    +
    +# Quadratic Lagrange interpolation for a triangle
    +- Lagrange{2, RefTetrahedron, 2}()
    ++ Lagrange{RefTriangle, 2}()

    For vector valued problems it is now required to explicitly vectorize the interpolation using the new VectorizedInterpolation. This is required when passing the interpolation to CellValues and when adding fields to the DofHandler using add!. In both of these places the interpolation was implicitly vectorized in Ferrite 0.3.

    Examples:

    # Linear Lagrange interpolation for a vector problem on the triangle (vector dimension
    +# same as the reference dimension)
    +ip_scalar = Lagrange{RefTriangle, 1}()
    +ip_vector = ip_scalar ^ 2 # or VectorizedInterpolation{2}(ip_scalar)
  • Quadrature: remove the first parameter (the reference dimension) and use new reference shapes.

    Examples:

    # Quadrature for a line
    +- QuadratureRule{1, RefCube}(quadrature_order)
    ++ QuadratureRule{RefLine}(quadrature_order)
    +
    +# Quadrature for a quadrilateral
    +- QuadratureRule{2, RefCube}(quadrature_order)
    ++ QuadratureRule{RefQuadrilateral}(quadrature_order)
    +
    +# Quadrature for a tetrahedron
    +- QuadratureRule{3, RefTetrahedron}(quadrature_order)
    ++ QuadratureRule{RefTetrahedron}(quadrature_order)
  • Quadrature for face integration (FacetValues): replace QuadratureRule{dim-1, reference_shape}(quadrature_order) with FacetQuadratureRule{reference_shape}(quadrature_order).

    Examples:

    # Quadrature for the facets of a quadrilateral
    +- QuadratureRule{1, RefCube}(quadrature_order)
    ++ FacetQuadratureRule{RefQuadrilateral}(quadrature_order)
    +
    +# Quadrature for the facets of a triangle
    +- QuadratureRule{1, RefTetrahedron}(quadrature_order)
    ++ FacetQuadratureRule{RefTriangle}(quadrature_order)
    +
    +# Quadrature for the facets of a hexhedron
    +- QuadratureRule{2, RefCube}(quadrature_order)
    ++ FacetQuadratureRule{RefHexahedron}(quadrature_order)
  • CellValues: replace usage of CellScalarValues and CellVectorValues with CellValues. For vector valued problems the interpolation passed to CellValues should be vectorized to a VectorizedInterpolation (see above).

    Examples:

    # CellValues for a scalar problem with triangle elements
    +- qr = QuadratureRule{2, RefTetrahedron}(quadrature_order)
    +- ip = Lagrange{2, RefTetrahedron, 1}()
    +- cv = CellScalarValues(qr, ip)
    ++ qr = QuadratureRule{RefTriangle}(quadrature_order)
    ++ ip = Lagrange{RefTriangle, 1}()
    ++ cv = CellValues(qr, ip)
    +
    +# CellValues for a vector problem with hexahedronal elements
    +- qr = QuadratureRule{3, RefCube}(quadrature_order)
    +- ip = Lagrange{3, RefCube, 1}()
    +- cv = CellVectorValues(qr, ip)
    ++ qr = QuadratureRule{RefHexahedron}(quadrature_order)
    ++ ip = Lagrange{RefHexahedron, 1}() ^ 3
    ++ cv = CellValues(qr, ip)

    If you use CellScalarValues or CellVectorValues in method signature you must replace them with CellValues. Note that the type parameters are different.

    Examples:

    - function do_something(cvs::CellScalarValues, cvv::CellVectorValues)
    ++ function do_something(cvs::CellValues, cvv::CellValues)

    The default geometric interpolation have changed from the function interpolation to always use linear Lagrange interpolation. If you use linear elements in the grid, and a higher order interpolation for the function you can now rely on the new default:

    qr = QuadratureRule(...)
    +- ip_function = Lagrange{2, RefTetrahedron, 2}()
    +- ip_geometry = Lagrange{2, RefTetrahedron, 1}()
    +- cv = CellScalarValues(qr, ip_function, ip_geometry)
    ++ ip_function = Lagrange{2, RefTetrahedron, 2}()
    ++ cv = CellValues(qr, ip_function)

    and if you have quadratic (or higher order) elements in the grid you must now pass the corresponding interpolation to the constructor:

    qr = QuadratureRule(...)
    +- ip_function = Lagrange{2, RefTetrahedron, 2}()
    +- cv = CellScalarValues(qr, ip_function)
    ++ ip_function = Lagrange{2, RefTetrahedron, 2}()
    ++ ip_geometry = Lagrange{2, RefTetrahedron, 1}()
    ++ cv = CellValues(qr, ip_function, ip_geometry)
  • FacetValues: replace usage of FaceScalarValues and FaceVectorValues with FacetValues. For vector valued problems the interpolation passed to FacetValues should be vectorized to a VectorizedInterpolation (see above). The input quadrature rule should be a FacetQuadratureRule instead of a QuadratureRule.

    Examples:

    # FacetValues for a scalar problem with triangle elements
    +- qr = QuadratureRule{1, RefTetrahedron}(quadrature_order)
    +- ip = Lagrange{2, RefTetrahedron, 1}()
    +- cv = FaceScalarValues(qr, ip)
    ++ qr = FacetQuadratureRule{RefTriangle}(quadrature_order)
    ++ ip = Lagrange{RefTriangle, 1}()
    ++ cv = FacetValues(qr, ip)
    +
    +# FaceValues for a vector problem with hexahedronal elements
    +- qr = QuadratureRule{2, RefCube}(quadrature_order)
    +- ip = Lagrange{3, RefCube, 1}()
    +- cv = FaceVectorValues(qr, ip)
    ++ qr = FacetQuadratureRule{RefHexahedron}(quadrature_order)
    ++ ip = Lagrange{RefHexahedron, 1}() ^ 3
    ++ cv = FacetValues(qr, ip)
  • DofHandler construction: it is now required to pass the interpolation explicitly when adding new fields using add! (previously it was optional, defaulting to the default interpolation of the elements in the grid). For vector-valued fields the interpolation should be vectorized, instead of passing the number of components to add! as an integer.

    Examples:

    dh = DofHandler(grid) # grid with triangles
    +
    +# Vector field :u
    +- add!(dh, :u, 2)
    ++ add!(dh, :u, Lagrange{RefTriangle, 1}()^2)
    +
    +# Scalar field :p
    +- add!(dh, :u, 1)
    ++ add!(dh, :u, Lagrange{RefTriangle, 1}())
  • Boundary conditions: The entity enclosing a cell was previously called face, but is now denoted a facet. When applying boundary conditions, rename getfaceset to getfacetset and addfaceset! is now addfacetset!. These sets are now described by FacetIndex instead of FaceIndex. When looping over the facets of a cell, change nfaces to nfacets.

    Examples:

    # Dirichlet boundary conditions
    +- addfaceset!(grid, "dbc", x -> x[1] ≈ 1.0)
    ++ addfacetset!(grid, "dbc", x -> x[1] ≈ 1.0)
    +
    +- dbc = Dirichlet(:u, getfaceset(grid, "dbc"), Returns(0.0))
    ++ dbc = Dirichlet(:u, getfacetset(grid, "dbc"), Returns(0.0))
    +
    +# Neumann boundary conditions
    +- for facet in 1:nfaces(cell)
    +-     if (cellid(cell), facet) ∈ getfaceset(grid, "Neumann Boundary")
    ++ for facet in 1:nfacets(cell)
    ++     if (cellid(cell), facet) ∈ getfacetset(grid, "Neumann Boundary")
    +          # ...
  • VTK Export: The VTK export has been changed #692.

    - vtk_grid(name, dh) do vtk
    +-     vtk_point_data(vtk, dh, a)
    +-     vtk_point_data(vtk, nodal_data, "my node data")
    +-     vtk_point_data(vtk, proj, projected_data, "my projected data")
    +-     vtk_cell_data(vtk, proj, projected_data, "my projected data")
    ++ VTKGridFile(name, dh) do vtk
    ++     write_solution(vtk, dh, a)
    ++     write_node_data(vtk, nodal_data, "my node data")
    ++     write_projection(vtk, proj, projected_data, "my projected data")
    ++     write_cell_data(vtk, cell_data, "my projected data")
    +end

    When using a paraview_collection collection for e.g. multiple timesteps the VTKGridFile object can be used instead of the previous type returned from vtk_grid.

  • Sparsity pattern and global matrix construction: since there is now explicit support for working with the sparsity pattern before instantiating a matrix the function create_sparsity_pattern has been removed. To recover the old functionality that return a sparse matrix from the DofHandler directly use allocate_matrix instead.

    Examples:

    # Create sparse matrix from DofHandler
    +- K = create_sparsity_pattern(dh)
    ++ K = allocate_matrix(dh)
    +
    +# Create condensed sparse matrix from DofHandler + ConstraintHandler
    +- K = create_sparsity_pattern(dh, ch)
    ++ K = allocate_matrix(dh, ch)

Added

  • InterfaceValues for computing jumps and averages over interfaces. (#743)

  • InterfaceIterator and InterfaceCache for iterating over interfaces. (#747)

  • FacetQuadratureRule implementation for RefPrism and RefPyramid. (#779)

  • The DofHandler now support selectively adding fields on sub-domains (rather than the full domain). This new functionality is included with the new SubDofHandler struct, which, as the name suggest, is a DofHandler for a subdomain. (#624, #667, #735)

  • New reference shape structs RefLine, RefTriangle, RefQuadrilateral, RefTetrahedron, RefHexahedron, and RefPrism have been added. These encode the reference dimension, and will thus replace the old reference shapes for which it was necessary to always pair with an explicit dimension (i.e. RefLine replaces (RefCube, 1), RefTriangle replaces (RefTetrahedron, 2), etc.). For writing "dimension independent code" it is possible to use Ferrite.RefHypercube{dim} and Ferrite.RefSimplex{dim}. (#679)

  • New methods for adding entitysets that are located on the boundary of the grid: addboundaryfacetset! and addboundaryvertexset!. These work similar to addfacetset! and addvertexset!, but filters out all instances not on the boundary (this can be used to avoid accidental inclusion of internal entities in sets used for boundary conditions, for example). (#606)

  • New interpolation VectorizedInterpolation which vectorizes scalar interpolations for vector-valued problems. A VectorizedInterpolation is created from a (scalar) interpolation ip using either ip ^ dim or VectorizedInterpolation{dim}(ip). For convenience, the method VectorizedInterpolation(ip) vectorizes the interpolation to the reference dimension of the interpolation. (#694, #736)

  • New (scalar) interpolation Lagrange{RefQuadrilateral, 3}(), i.e. third order Lagrange interpolation for 2D quadrilaterals. (#701, #731)

  • CellValues now support embedded elements. Specifically you can now embed elements with reference dimension 1 into spatial dimension 2 or 3, and elements with reference dimension 2 in to spatial dimension 3. (#651)

  • CellValues now support (vector) interpolations with dimension different from the spatial dimension. (#651)

  • FacetQuadratureRule have been added and should be used for FacetValues. A FacetQuadratureRule for integration of the facets of e.g. a triangle can be constructed by FacetQuadratureRule{RefTriangle}(order) (similar to how QuadratureRule is constructed). (#716)

  • New functions Ferrite.reference_shape_value(::Interpolation, ξ::Vec, i::Int) and Ferrite.reference_shape_gradient(::Interpolation, ξ::Vec, i::Int) for evaluating the value/gradient of the ith shape function of an interpolation in local reference coordinate ξ. These methods are public but not exported. (Note that these methods return the value/gradient wrt. the reference coordinate ξ, whereas the corresponding methods for CellValues etc return the value/gradient wrt the spatial coordinate x.) (#721)

  • FacetIterator and FacetCache have been added. These work similarly to CellIterator and CellCache but are used to iterate over (boundary) face sets instead. These simplify boundary integrals in general, and in particular Neumann boundary conditions are more convenient to implement now that you can loop directly over the face set instead of checking all faces of a cell inside the element routine. (#495)

  • The ConstraintHandler now support adding Dirichlet boundary conditions on discontinuous interpolations. (#729)

  • collect_periodic_faces now have a keyword argument tol that can be used to relax the default tolerance when necessary. (#749)

  • VTK export now work with QuadraticHexahedron elements. (#714)

  • The function bounding_box(::AbstractGrid) has been added. It computes the bounding box for a given grid (based on its node coordinates), and returns the minimum and maximum vertices of the bounding box. (#880)

  • Support for working with sparsity patterns has been added. This means that Ferrite exposes the intermediate "state" between the DofHandler and the instantiated matrix as the new struct SparsityPattern. This make it possible to insert custom equations or couplings in the pattern before instantiating the matrix. The function create_sparsity_pattern have been removed. The new function allocate_matrix is instead used to instantiate the matrix. Refer to the documentation for more details. (#888)

    To upgrade: if you want to recover the old functionality and don't need to work with the pattern, replace any usage of create_sparsity_pattern with allocate_matrix.

  • A new function, geometric_interpolation, is exported, which gives the geometric interpolation for each cell type. This is equivalent to the deprecated Ferrite.default_interpolation function. (#953)

  • CellValues and FacetValues can now store and map second order gradients (Hessians). The number of gradients computed in CellValues/FacetValues is specified using the keyword arguments update_gradients::Bool (default true) and update_hessians::Bool (default false) in the constructors, i.e. CellValues(...; update_hessians=true). (#953)

  • L2Projector supports projecting on grids with mixed celltypes. (#949)

Changed

  • It is now possible to create sparsity patterns with interface couplings, see the new function add_interface_entries! and the rework of sparsity pattern construction. (#710)

  • The AbstractCell interface has been reworked. This change should not affect user code, but may in some cases be relevant for code parsing external mesh files. In particular, the generic Cell struct have been removed in favor of concrete cell implementations (Line, Triangle, ...). (#679, #712)

    To upgrade replace any usage of Cell{...}(...) with calls to the concrete implementations.

  • The default geometric mapping in CellValues and FacetValues have changed. The new default is to always use Lagrange{refshape, 1}(), i.e. linear Lagrange polynomials, for the geometric interpolation. Previously, the function interpolation was (re) used also for the geometry interpolation. (#695)

    To upgrade, if you relied on the previous default, simply pass the function interpolation also as the third argument (the geometric interpolation).

  • All interpolations are now categorized as either scalar or vector interpolations. All (previously) existing interpolations are scalar. (Scalar) interpolations must now be explicitly vectorized, using the new VectorizedInterpolation, when used for vector problems. (Previously implicit vectorization happened in the CellValues constructor, and when adding fields to the DofHandler). (#694)

  • It is now required to explicitly pass the interpolation to the DofHandler when adding a new field using add!. For vector fields the interpolation should be vectorized, instead of passing number of components as an integer. (#694)

    To upgrade don't pass the dimension as an integer, and pass the interpolation explicitly. See more details in Upgrading code from Ferrite 0.3 to 1.0.

  • Interpolations should now be constructed using the new reference shapes. Since the new reference shapes encode the reference dimension the first type parameter of interpolations have been removed. (#711) To upgrade replace e.g. Lagrange{1, RefCube, 1}() with Lagrange{RefLine, 1}(), and Lagrange{2, RefTetrahedron, 1}() with Lagrange{RefTriangle, 1}(), etc.

  • QuadratureRules should now be constructed using the new reference shapes. Since the new reference shapes encode the reference dimension the first type parameter of QuadratureRule have been removed. (#711, #716) To upgrade replace e.g. QuadratureRule{1, RefCube}(order) with QuadratureRule{RefLine}(order), and QuadratureRule{2, RefTetrahedron}(1) with Lagrange{RefTriangle}(order), etc.

  • CellScalarValues and CellVectorValues have been merged into CellValues, FaceScalarValues and FaceVectorValues have been merged into FacetValues, and PointScalarValues and PointVectorValues have been merged into PointValues. The differentiation between scalar and vector have thus been moved to the interpolation (see above). Note that previously CellValues, FaceValues, and PointValues where abstract types, but they are now concrete implementations with different type parameters, except FaceValues which is now FacetValues (#708) To upgrade, for scalar problems, it is enough to replace CellScalarValues with CellValues, FaceScalarValues with FacetValues and PointScalarValues with PointValues, respectively. For vector problems, make sure to vectorize the interpolation (see above) and then replace CellVectorValues with CellValues, FaceVectorValues with FacetValues, and PointVectorValues with PointValues.

  • The quadrature rule passed to FacetValues should now be of type FacetQuadratureRule rather than of type QuadratureRule. (#716) To upgrade replace the quadrature rule passed to FacetValues with a FacetQuadratureRule.

  • Checking if a face (ele_id, local_face_id) ∈ faceset has been previously implemented by type piracy. In order to be invariant to the underlying Set datatype as well as omitting type piracy, (#835) implemented isequal and hash for BoundaryIndex datatypes.

  • VTK export: Ferrite no longer extends WriteVTK.vtk_grid and associated functions, instead the new type VTKGridFile should be used instead. New methods exists for writing to a VTKGridFile, e.g. write_solution, write_cell_data, write_node_data, and write_projection. See #692.

  • Definitions: Previously, face and edge referred to codimension 1 relative reference shape. In Ferrite v1, volume, face, edge, and vertex refer to 3, 2, 1, and 0 dimensional entities, and facet replaces the old definition of face. No direct replacement for edges exits. See #914 and #914. The main implications of this change are

    • FaceIndex -> FacetIndex (FaceIndex still exists, but has a different meaning)
    • FaceValues -> FacetValues
    • nfaces -> nfacets (nfaces is now an internal method with different meaning)
    • addfaceset! -> addfacetset
    • getfaceset -> getfacetset

    Furthermore, subtypes of Interpolation should now define vertexdof_indices, edgedof_indices, facedof_indices, volumedof_indices (and similar) according to these definitions.

  • Ferrite.getdim has been changed into Ferrite.getrefdim for getting the dimension of the reference shape and Ferrite.getspatialdim to get the spatial dimension (of the grid). (#943)

  • Ferrite.getfielddim(::AbstractDofHandler, args...) has been renamed to Ferrite.n_components. (#943)

  • The constructor for ExclusiveTopology only accept an AbstractGrid as input, removing the alternative of providing a Vector{<:AbstractCell}, as knowing the spatial dimension is required for correct code paths. Furthermore, it uses a new internal data structure, ArrayOfVectorViews, to store the neighborhood information more efficiently The datatype for the neighborhood has thus changed to a view of a vector, instead of the now removed EntityNeighborhood container. This also applies to vertex_star_stencils. (#974).

  • project(::L2Projector, data, qr_rhs) now expects data to be indexed by the cellid, as opposed to the index in the vector of cellids passed to the L2Projector. The data may be passed as an AbstractDict{Int, <:AbstractVector}, as an alternative to AbstractArray{<:AbstractVector}. (#949)

Deprecated

  • The rarely (if ever) used methods of function_value, function_gradient, function_divergence, and function_curl taking vectorized dof values as in put have been deprecated. (#698)

  • The function reshape_to_nodes have been deprecated in favor of evaluate_at_grid_nodes. (#703)

  • start_assemble([n::Int]) has been deprecated in favor of calling Ferrite.COOAssembler() directly (#916, #1058).

  • start_assemble(f, K) have been deprecated in favor of the "canonical" start_assemble(K, f). (#707)

  • assemble!(assembler, dofs, fe, Ke) have been deprecated in favor of the "canonical" assemble!(assembler, dofs, Ke, fe). (#1059)

  • end_assemble have been deprecated in favor of finish_assemble. (#754)

  • get_point_values have been deprecated in favor of evaluate_at_points. (#754)

  • transform! have been deprecated in favor of transform_coordinates!. (#754)

  • Ferrite.default_interpolation has been deprecated in favor of geometric_interpolation. (#953)

Removed

  • MixedDofHandler + FieldHandler have been removed in favor of DofHandler + SubDofHandler. Note that the syntax has changed, and note that SubDofHandler is much more capable compared to FieldHandler. Previously it was often required to pass both the MixedDofHandler and the FieldHandler to e.g. the assembly routine, but now it is enough to pass the SubDofHandler since it can be used for e.g. DoF queries etc. (#624, #667, #735)

  • Some old methods to construct the L2Projector have been removed after being deprecated for several releases. (#697)

  • The option project_to_nodes have been removed from project(::L2Projector, ...). The returned values are now always ordered according to the projectors internal DofHandler. (#699)

  • The function compute_vertex_values have been removed. (#700)

  • The names getweights, getpoints, getcellsets, getnodesets, getfacesets, getedgesets, and getvertexsets have been removed from the list of exported names. (For now you can still use them by prefixing Ferrite., e.g. Ferrite.getweights.) (#754)

  • The onboundary function (and the associated boundary_matrix property of the Grid datastructure) have been removed (#924). Instead of first checking onboundary and then check whether a facet belong to a specific facetset, check the facetset directly. For example:

    - if onboundary(cell, local_face_id) && (cell_id, local_face_id) in getfacesets(grid, "traction_boundary")
    ++ if (cell_id, local_face_id) in getfacesets(grid, "traction_boundary")
    +     # integrate the "traction_boundary" boundary
    +  end

Fixed

  • Benchmarks now work with master branch. (#751, #855)

  • Topology construction have been generalized to, in particular, fix construction for 1D and for wedge elements. (#641, #670, #684)

Other improvements

  • Documentation:

    • The documentation is now structured according to the Diataxis framework. There is now also clear separation between tutorials (for teaching) and code gallery (for showing off). (#737, #756)
    • New section in the developer documentation that describes the (new) reference shapes and their numbering scheme. (#688)
  • Performance:

    • Ferrite.transform!(grid, f) (for transforming the node coordinates in the grid according to a function f) is now faster and allocates less. (#675)
    • Slight performance improvement in construction of PointEvalHandler (faster reverse coordinate lookup). (#719)
    • Various performance improvements to topology construction. (#753, #759)
  • Internal improvements:

    • The dof distribution interface have been updated to support higher order elements (future work). (#627, #732, #733)
    • The AbstractGrid and AbstractDofHandler interfaces are now used more consistently internally. This will help with the implementation of distributed grids and DofHandlers. (#655)
    • VTK export now uses the (geometric) interpolation directly when evaluating the finite element field instead of trying to work backwards how DoFs map to nodes. (#703)
    • Improved bounds checking in assemble!. (#706)
    • Internal methods Ferrite.value and Ferrite.derivative for computing the value/gradient of all shape functions have been removed. (#720)
    • Ferrite.create_incidence_matrix now work with any AbstractGrid (not just Grid). (#726)

v0.3.14 - 2023-04-03

Added

  • Support reordering dofs of a MixedDofHandler by the built-in orderings FieldWise and ComponentWise. This includes support for reordering dofs of fields on subdomains. (#645)
  • Support specifying the coupling between fields in a MixedDofHandler when creating the sparsity pattern. (#650)
  • Support Metis dof reordering with coupling information for MixedDofHandler. (#650)
  • Pretty printing for MixedDofHandler and L2Projector. (#465)

Other improvements

  • The MixedDofHandler have gone through a performance review (see #629) and now performs the same as DofHandler. This was part of the push to merge the two DoF handlers. Since MixedDofHandler is strictly more flexible, and now equally performant, it will replace DofHandler in the next breaking release. (#637, #639, #642, #643, #656, #660)

Internal changes

Changes listed here should not affect regular usage, but listed here in case you have been poking into Ferrite internals:

  • Ferrite.ndim(dh, fieldname) has been removed, use Ferrite.getfielddim(dh, fieldname) instead. (#658)
  • Ferrite.nfields(dh) has been removed, use length(Ferrite.getfieldnames(dh)) instead. (#444, #653)
  • getfielddims(::FieldHandler) and getfieldinterpolations(::FieldHandler) have been removed (#647, #659)

v0.3.13 - 2023-03-23

Added

  • Support for classical trilinear and triquadratic wedge elements. (#581)
  • Symmetric quadrature rules up to order 10 for prismatic elements. (#581)
  • Finer granulation of dof distribution, allowing to distribute different amounts of dofs per entity. (#581)

Fixed

  • Dof distribution for embedded elements. (#581)
  • Improve numerical accuracy in shape function evaluation for the Lagrange{2,Tetrahedron,(3|4|5)} interpolations. (#582, #633)

Other improvements

  • Documentation:
    • New "Developer documentation" section in the manual for documenting Ferrite.jl internals and developer tools. (#611)
    • Fix a bug in constraint computation in Stoke's flow example. (#614)
  • Performance:
    • Benchmarking infrastructure to help tracking performance changes. (#388)
    • Performance improvements for various accessor functions for MixedDofHandler. (#621)

Internal changes

  • To clarify the dof management vertices(ip), edges(ip) and faces(ip) has been deprecated in favor of vertexdof_indices(ip), edgedof_indices(ip) and facedof_indices(ip). (#581)
  • Duplicate grid representation has been removed from the MixedDofHandler. (#577)

v0.3.12 - 2023-02-28

Added

  • Added a basic show method for assemblers. (#598)

Fixed

  • Fix an issue in constraint application of Symmetric-wrapped sparse matrices (i.e. obtained from create_symmatric_sparsity_pattern). In particular, apply!(K::Symmetric, f, ch) would incorrectly modify f if any of the constraints were inhomogeneous. (#592)
  • Properly disable the Metis extension on Julia 1.9 instead of causing precompilation errors. (#588)
  • Fix adding Dirichlet boundary conditions on nodes when using MixedDofHandler. (#593, #594)
  • Fix accidentally slow implementation of show for Grids. (#599)
  • Fixes to topology functionality. (#453, #518, #455)
  • Fix grid coloring for cell sets with 0 or 1 cells. (#600)

Other improvements

  • Documentation improvements:
    • Simplications and clarifications to hyperelasticity example. (#591)
    • Remove duplicate docstring entry for vtk_point_data. (#602)
    • Update documentation about initial conditions. (#601, #604)

v0.3.11 - 2023-01-17

Added

  • Metis.jl extension for fill-reducing DoF permutation. This uses Julias new package extension mechanism (requires Julia 1.10) to support a new DoF renumbering order DofOrder.Ext{Metis}() that can be passed to renumber! to renumber DoFs using the Metis.jl library. (#393, #549)
  • BlockArrays.jl extension for creating a globally blocked system matrix. create_sparsity_pattern(BlockMatrix, dh, ch; kwargs...) return a matrix that is blocked by field (requires DoFs to be (re)numbered by field, i.e. renumber!(dh, DofOrder.FieldWise())). For custom blocking it is possible to pass an uninitialized BlockMatrix with the correct block sizes (see BlockArrays.jl docs). This functionality is useful for e.g. special solvers where individual blocks need to be extracted. Requires Julia version 1.9 or above. (#567)
  • New function apply_analytical! for setting the values of the degrees of freedom for a specific field according to a spatial function f(x). (#532)
  • New cache struct CellCache to be used when iterating over the cells in a grid or DoF handler. CellCache caches nodes, coordinates, and DoFs, for the cell. The cache cc can be re-initialized for a new cell index ci by calling reinit!(cc, ci). This can be used as an alternative to CellIterator when more control over which element to loop over is needed. See documentation for CellCache for more information. (#546)
  • It is now possible to create the sparsity pattern without constrained entries (they will be zeroed out later anyway) by passing keep_constrained=false to create_sparsity_pattern. This naturally only works together with local condensation of constraints since there won't be space allocated in the global matrix for the full (i.e. "non-condensed") element matrix. Creating the matrix without constrained entries reduces the memory footprint, but unless a significant amount of DoFs are constrained (e.g. high mesh resolution at a boundary) the savings are negligible. (#539)

Changed

  • ConstraintHandler: update! is now called implicitly in close!. This was easy to miss, and somewhat of a strange requirement when solving problems without time stepping. (#459)
  • The function for computing the inhomogeneity in a Dirichlet constraint can now be specified as either f(x) or f(x, t), where x is the spatial coordinate and t the time. (#459)
  • The elements of a CellIterator are now CellCache instead of the iterator itself, which was confusing in some cases. This change does not affect typical user code. (#546)

Deprecated

  • Adding fields to a DoF handler with push!(dh, ...) has been deprecated in favor of add!(dh, ...). This is to make it consistent with how constraints are added to a constraint handler. (#578)

Fixed

  • Fix shape_value for the linear, discontinuous Lagrange interpolation. (#553)
  • Fix reference_coordinate dispatch for discontinuous Lagrange interpolations. (#559)
  • Fix show(::Grid) for custom cell types. (#570)
  • Fix apply_zero!(Δa, ch) when using inhomogeneous affine constraints (#575)

Other improvements

  • Internal changes defining a new global matrix/vector "interface". These changes make it easy to enable more array types (e.g. BlockMatrix support added in this release) and solvers in the future. (#562, #571)
  • Performance improvements:
    • Reduced time and memory allocations for global sparse matrix creation (Julia >= 1.10). (#563)
  • Documentation improvements:
    • Added an overview of the Examples section. (#531)
    • Added an example showing topology optimization. (#531)
    • Various typo fixes. (#574)
    • Fix broken links. (#583)

v0.3.10 - 2022-12-11

Added

  • New functions apply_local! and apply_assemble! for applying constraints locally on the element level before assembling to the global system. (#528)
  • New functionality to renumber DoFs by fields or by components. This is useful when you need the global matrix to be blocked. (#378, #545)
  • Functionality to renumber DoFs in DofHandler and ConstraintHandler simultaneously: renumber!(dh::DofHandler, ch::ConstraintHandler, order). Previously renumbering had to be done before creating the ConstraintHandler since otherwise DoF numbers would be inconsistent. However, this was inconvenient in cases where the constraints impact the new DoF order permutation. (#542)
  • The coupling between fields can now be specified when creating the global matrix with create_sparsity_pattern by passing a Matrix{Bool}. For example, in a problem with unknowns (u, p) and corresponding test functions (v, q), if there is no coupling between p and q it is unnecessary to allocate entries in the global matrix corresponding to these DoFs. This can now be communicated to create_sparsity_pattern by passing the coupling matrix [true true; true false] in the keyword argument coupling. (#544)

Changed

  • Runtime and allocations for application of boundary conditions in apply! and apply_zero! have been improved. As a result, the strategy keyword argument is obsolete and thus ignored. (#489)
  • The internal representation of Dirichlet boundary conditions and AffineConstraints in the ConstraintHandler have been unified. As a result, conflicting constraints on DoFs are handled more consistently: the constraint added last to the ConstraintHandler now always override any previous constraints. Conflicting constraints could previously cause problems when a DoF where prescribed by both Dirichlet and AffineConstraint. (#529)
  • Entries in local matrix/vector are now ignored in the assembly procedure. This allows, for example, using a dense local matrix [a b; c d] even if no entries exist in the global matrix for the d block, i.e. in [A B; C D] the D block is zero, and these global entries might not exist in the sparse matrix. (Such sparsity patterns can now be created by create_sparsity_pattern, see #544.) (#543)

Fixed

  • Fix affine constraints with prescribed DoFs in the right-hand-side. In particular, DoFs that are prescribed by just an inhomogeneity are now handled correctly, and nested affine constraints now give an error instead of silently giving the wrong result. (#530, #535)
  • Fixed internal inconsistency in edge ordering for 2nd order RefTetrahedron and RefCube. (#520, #523)

Other improvements

  • Performance improvements:
    • Reduced time and memory allocations in DoF distribution for MixedDofHandler. (#533)
    • Reduced time and memory allocations reductions in getcoordinates!. (#536)
    • Reduced time and memory allocations in affine constraint condensation. (#537, #541, #550)
  • Documentation improvements:
    • Use :static scheduling for threaded for-loop (#534)
    • Remove use of @inbounds (#547)
  • Unification of create_sparsity_pattern methods to remove code duplication between DofHandler and MixedDofHandler. (#538, #540)

v0.3.9 - 2022-10-19

Added

  • New higher order function interpolations for triangles (Lagrange{2,RefTetrahedron,3}, Lagrange{2,RefTetrahedron,4}, and Lagrange{2,RefTetrahedron,5}). (#482, #512)
  • New Gaussian quadrature formula for triangles up to order 15. (#514)
  • Add debug mode for working with Ferrite internals. (#524)

Changed

  • The default components to constrain in Dirichlet and PeriodicDirichlet have changed from component 1 to all components of the field. For scalar problems this has no effect. (#506, #509)

v0.3.8 - 2022-10-05

Added

  • Ferrite.jl now has a logo! (#464)
  • New keyword argument search_nneighbors::Int in PointEvalHandler for specifying how many neighboring elements to consider in the kNN search. The default is still 3 (usually sufficient). (#466)
  • The IJV-assembler now support assembling non-square matrices. (#471)
  • Periodic boundary conditions have been reworked and generalized. It now supports arbitrary relations between the mirror and image boundaries (e.g. not only translations in x/y/z direction). (#478, #481, #496, #501)

Fixed

  • Fix PointEvalHandler when the first point is missing. (#466)
  • Fix the ordering of nodes on the face for (Quadratic)Tetrahedron cells. (#475)

Other improvements

  • Many improvements to the documentation. (#467, #473, #487, #494, #500)
  • Improved error messages in reinit! when number of geometric base functions and number of element coordinates mismatch. (#469)
  • Remove some unnecessary function parametrizations. (#503)
  • Remove some unnecessary allocations in grid coloring. (#505)
  • More efficient way of creating the sparsity pattern when using AffineConstraints and/or PeriodicDirichlet. (#436)

v0.3.7 - 2022-07-05

Fixed

  • Fix tests for newer version of WriteVTK (no functional change). (#462)

Other improvements

  • Various improvements to the heat equation example and the hyperelasticity example in the documentation. (#460, #461)

v0.3.6 - 2022-06-30

Fixed

  • Fix a bug with L2Projection of mixed grid. (#456)

Other improvements

  • Expanded manual section of Dirichlet BCs. (#458)

v0.3.5 - 2022-05-30

Added

  • Functionality for querying information about the grid topology (e.g. neighboring cells, boundaries, ...). (#363)

Fixed

  • Fix application of boundary conditions when combining RHSData and affine constraints. (#431)

v0.3.4 - 2022-02-25

Added

  • Affine (linear) constraints between degrees-of-freedom. (#401)
  • Periodic Dirichlet boundary conditions. (#418)
  • Evaluation of arbitrary quantities in FE space. (#425)

Changed

  • Interpolation(s) and the quadrature rule are now stored as part of the CellValues structs (cv.func_interp, cv.geo_interp, and cv.qr). (#428)

v0.3.3 - 2022-02-04

Changed

  • Verify user input in various functions to eliminate possible out-of-bounds accesses. (#407, #411)

v0.3.2 - 2022-01-18

Added

  • Support for new interpolation types: DiscontinuousLagrange, BubbleEnrichedLagrange, and CrouzeixRaviart. (#352, #392)

Changed

  • Julia version 1.0 is no longer supported for Ferrite versions >= 0.3.2. Use Julia version >= 1.6. (#385)
  • Quadrature data for L2 projection can now be given as a matrix of size "number of elements" x "number of quadrature points per element". (#386)
  • Projected values from L2 projection can now be exported directly to VTK. (#390)
  • Grid coloring can now act on a subset of cells. (#402)
  • Various functions related to cell values now use traits to make it easier to extend and reuse functionality in external code. (#404)

Fixed

  • Exporting tensors to VTK now use correct names for the components. (#406)
diff --git a/previews/PR798/cited-literature/index.html b/previews/PR798/cited-literature/index.html new file mode 100644 index 0000000000..0322147269 --- /dev/null +++ b/previews/PR798/cited-literature/index.html @@ -0,0 +1,2 @@ + +Cited literature · Ferrite.jl

Cited literature

[1]
[2]
[3]
G. A. Holzapfel. Nonlinear Solid Mechanics: A Continuum Approach for Engineering (Wiley, Chichester ; New York, 2000).
[4]
[5]
[6]
D. N. Arnold, F. Brezzi, B. Cockburn and L. D. Marini. Unified Analysis of Discontinuous Galerkin Methods for Elliptic Problems. SIAM Journal on Numerical Analysis 39, 1749–1779 (2002). Accessed on Dec 20, 2023.
[7]
[8]
[9]
[10]
F. D. Witherden and P. E. Vincent. On the identification of symmetric quadrature rules for finite element methods. Computers & Mathematics with Applications 69, 1232–1241 (2015).
[11]
M. Crouzeix and P.-A. Raviart. Conforming and nonconforming finite element methods for solving the stationary Stokes equations I. Revue française d'automatique informatique recherche opérationnelle. Mathématique 7, 33–75 (1973).
[12]
R. Rannacher and S. Turek. Simple nonconforming quadrilateral Stokes element. Numerical Methods for Partial Differential Equations 8, 97–111 (1992).
[13]
[14]
M. Cenanovic. Finite element methods for surface problems. Ph.D. Thesis, Jönköping University, School of Engineering (2017).
[15]
[16]
[17]
diff --git a/previews/PR798/devdocs/FEValues/index.html b/previews/PR798/devdocs/FEValues/index.html new file mode 100644 index 0000000000..b46833f523 --- /dev/null +++ b/previews/PR798/devdocs/FEValues/index.html @@ -0,0 +1,2 @@ + +FEValues · Ferrite.jl

FEValues

Type definitions

Internal types

Ferrite.GeometryMappingType
GeometryMapping{DiffOrder}(::Type{T}, ip_geo, qr::QuadratureRule)

Create a GeometryMapping object which contains the geometric

  • shape values
  • gradient values (if DiffOrder ≥ 1)
  • hessians values (if DiffOrder ≥ 2)

T<:AbstractFloat gives the numeric type of the values.

source
Ferrite.MappingValuesType
MappingValues(J, H)

The mapping values are calculated based on a geometric_mapping::GeometryMapping along with the cell coordinates, and the stored jacobian, J, and potentially hessian, H, are used when mapping the FunctionValues to the current cell during reinit!.

source
Ferrite.FunctionValuesType
FunctionValues{DiffOrder}(::Type{T}, ip_fun, qr::QuadratureRule, ip_geo::VectorizedInterpolation)

Create a FunctionValues object containing the shape values and gradients (up to order DiffOrder) for both the reference cell (precalculated) and the real cell (updated in reinit!).

source
Ferrite.BCValuesType
BCValues(func_interpol::Interpolation, geom_interpol::Interpolation, boundary_type::Union{Type{<:BoundaryIndex}})

BCValues stores the shape values at all facet/faces/edges/vertices (depending on boundary_type) for the geometric interpolation (geom_interpol), for each dof-position determined by the func_interpol. Used mainly by the ConstraintHandler.

source

Internal utilities

Ferrite.embedding_detFunction
embedding_det(J::SMatrix{3, 2})

Embedding determinant for surfaces in 3D.

TLDR: "det(J) =" ||∂x/∂ξ₁ × ∂x/∂ξ₂||₂

The transformation theorem for some function f on a 2D surface in 3D space leads to ∫ f ⋅ dS = ∫ f ⋅ (∂x/∂ξ₁ × ∂x/∂ξ₂) dξ₁dξ₂ = ∫ f ⋅ n ||∂x/∂ξ₁ × ∂x/∂ξ₂||₂ dξ₁dξ₂ where ||∂x/∂ξ₁ × ∂x/∂ξ₂||₂ is "detJ" and n is the unit normal. See e.g. https://scicomp.stackexchange.com/questions/41741/integration-of-d-1-dimensional-functions-on-finite-element-surfaces for simple explanation. For more details see e.g. the doctoral thesis by Mirza Cenanovic Tangential Calculus [14].

source
embedding_det(J::Union{SMatrix{2, 1}, SMatrix{3, 1}})

Embedding determinant for curves in 2D and 3D.

TLDR: "det(J) =" ||∂x/∂ξ||₂

The transformation theorem for some function f on a 1D curve in 2D and 3D space leads to ∫ f ⋅ dE = ∫ f ⋅ ∂x/∂ξ dξ = ∫ f ⋅ t ||∂x/∂ξ||₂ dξ where ||∂x/∂ξ||₂ is "detJ" and t is "the unit tangent". See e.g. https://scicomp.stackexchange.com/questions/41741/integration-of-d-1-dimensional-functions-on-finite-element-surfaces for simple explanation.

source
Ferrite.ValuesUpdateFlagsType
ValuesUpdateFlags(ip_fun::Interpolation; update_gradients = Val(true), update_hessians = Val(false), update_detJdV = Val(true))

Creates a singleton type for specifying what parts of the AbstractValues should be updated. Note that this is internal API used to get type-stable construction. Keyword arguments in AbstractValues constructors are forwarded, and the public API is passing these as Bool, while the ValuesUpdateFlags method supports both boolean and Val(::Bool) keyword args.

source

Custom FEValues

Custom FEValues, fe_v::AbstractValues, should normally implement the reinit! method. Subtypes of AbstractValues have default implementations for some functions, but require some lower-level access functions, specifically

Array bounds

  • Asking for the nth quadrature point must be inside array bounds if 1 <= n <= getnquadpoints(fe_v). (checkquadpoint can, alternatively, be dispatched to check that n is inbounds.)
  • Asking for the ith shape value or gradient must be inside array bounds if 1 <= i <= getnbasefunctions(fe_v)
  • Asking for the ith geometric value must be inside array bounds if 1 <= i <= getngeobasefunctions(fe_v)
diff --git a/previews/PR798/devdocs/assembly/index.html b/previews/PR798/devdocs/assembly/index.html new file mode 100644 index 0000000000..69e073f65c --- /dev/null +++ b/previews/PR798/devdocs/assembly/index.html @@ -0,0 +1,4 @@ + +Assembly · Ferrite.jl

Assembly

Type definitions

Ferrite.COOAssemblerType
struct COOAssembler{Tv, Ti}

This assembler creates a COO (coordinate format) representation of a sparse matrix during assembly and converts it into a SparseMatrixCSC{Tv, Ti} on finalization.

source

Utility functions

Ferrite.matrix_handleFunction
matrix_handle(a::AbstractAssembler)
+vector_handle(a::AbstractAssembler)

Return a reference to the underlying matrix/vector of the assembler used during assembly operations.

source
Ferrite.vector_handleFunction
matrix_handle(a::AbstractAssembler)
+vector_handle(a::AbstractAssembler)

Return a reference to the underlying matrix/vector of the assembler used during assembly operations.

source
Ferrite._sortdofs_for_assembly!Function
_sortdofs_for_assembly!(permutation::Vector{Int}, sorteddofs::Vector{Int}, dofs::AbstractVector)

Sorts the dofs into a separate buffer and returns it together with a permutation vector.

source
Ferrite.sortperm2!Function
sortperm2!(data::AbstractVector, permutation::AbstractVector)

Sort the input vector inplace and compute the corresponding permutation.

source
diff --git a/previews/PR798/devdocs/dofhandler/index.html b/previews/PR798/devdocs/dofhandler/index.html new file mode 100644 index 0000000000..c5722526e3 --- /dev/null +++ b/previews/PR798/devdocs/dofhandler/index.html @@ -0,0 +1,16 @@ + +Dof Handler · Ferrite.jl

Dof Handler

Type definitions

Dof handlers are subtypes of AbstractDofhandler{sdim}, i.e. they are parametrized by the spatial dimension. Internally a helper struct InterpolationInfo is utilized to enforce type stability during dof distribution, because the interpolations are not available as concrete types.

Ferrite.InterpolationInfoType
InterpolationInfo

Gathers all the information needed to distribute dofs for a given interpolation. Note that this cache is of the same type no matter the interpolation: the purpose is to make dof-distribution type-stable.

source
Ferrite.PathOrientationInfoType
PathOrientationInfo

Orientation information for 1D entities.

The orientation for 1D entities is defined by the indices of the grid nodes associated to the vertices. To give an example, the oriented path

1 ---> 2

is called regular, indicated by regular=true, while the oriented path

2 ---> 1

is called inverted, indicated by regular=false.

source
Ferrite.SurfaceOrientationInfoType
SurfaceOrientationInfo

Orientation information for 2D entities. Such an entity can be possibly flipped (i.e. the defining vertex order is reverse to the spanning vertex order) and the vertices can be rotated against each other. Take for example the faces

1---2 2---3
+| A | | B |
+4---3 1---4

which are rotated against each other by 90° (shift index is 1) or the faces

1---2 2---1
+| A | | B |
+4---3 3---4

which are flipped against each other. Any combination of these can happen. The combination to map this local face to the defining face is encoded with this data structure via $rotate \circ flip$ where the rotation is indiced by the shift index. !!!NOTE TODO implement me.

source

Internal API

The main entry point for dof distribution is __close!.

Ferrite.__close!Function
__close!(dh::DofHandler)

Internal entry point for dof distribution.

Dofs are distributed as follows: For the DofHandler each SubDofHandler is visited in the order they were added. For each field in the SubDofHandler create dofs for the cell. This means that dofs on a particular cell will be numbered in groups for each field, so first the dofs for field 1 are distributed, then field 2, etc. For each cell dofs are first distributed on its vertices, then on the interior of edges (if applicable), then on the interior of faces (if applicable), and finally on the cell interior. The entity ordering follows the geometrical ordering found in vertices, faces and edges.

source
Ferrite.get_gridFunction
get_grid(dh::AbstractDofHandler)

Access some grid representation for the dof handler.

Note

This API function is currently not well-defined. It acts as the interface between distributed assembly and assembly on a single process, because most parts of the functionality can be handled by only acting on the locally owned cell set.

source
Ferrite.find_fieldFunction
find_field(dh::DofHandler, field_name::Symbol)::NTuple{2,Int}

Return the index of the field with name field_name in a DofHandler. The index is a NTuple{2,Int}, where the 1st entry is the index of the SubDofHandler within which the field was found and the 2nd entry is the index of the field within the SubDofHandler.

Note

Always finds the 1st occurrence of a field within DofHandler.

See also: find_field(sdh::SubDofHandler, field_name::Symbol), Ferrite._find_field(sdh::SubDofHandler, field_name::Symbol).

source
find_field(sdh::SubDofHandler, field_name::Symbol)::Int

Return the index of the field with name field_name in a SubDofHandler. Throw an error if the field is not found.

See also: find_field(dh::DofHandler, field_name::Symbol), _find_field(sdh::SubDofHandler, field_name::Symbol).

source
Ferrite._close_subdofhandler!Function
_close_subdofhandler!(dh::DofHandler{sdim}, sdh::SubDofHandler, sdh_index::Int, nextdof::Int, vertexdicts, edgedicts, facedicts) where {sdim}

Main entry point to distribute dofs for a single SubDofHandler on its subdomain.

source
Ferrite._distribute_dofs_for_cell!Function
_distribute_dofs_for_cell!(dh::DofHandler{sdim}, cell::AbstractCell, ip_info::InterpolationInfo, nextdof::Int, vertexdict, edgedict, facedict) where {sdim}

Main entry point to distribute dofs for a single cell.

source
Ferrite.permute_and_push!Function
permute_and_push!

For interpolations with more than one interior dof per edge it may be necessary to adjust the dofs. Since dofs are (initially) enumerated according to the local edge direction there can be a direction mismatch with the neighboring element. For example, in the following nodal interpolation example, with three interior dofs on each edge, the initial pass have distributed dofs 4, 5, 6 according to the local edge directions:

+-----------+
+|     A     |
++--4--5--6->+    local edge on element A
+
+ ---------->     global edge
+
++<-6--5--4--+    local edge on element B
+|     B     |
++-----------+

For most scalar-valued interpolations we can simply compensate for this by reversing the numbering on all edges that do not match the global edge direction, i.e. for the edge on element B in the example.

In addition, we also have to preserve the ordering at each dof location.

For more details we refer to Scroggs et al. [15] as we follow the methodology described therein.

References

  • [15] Scroggs et al. ACM Trans. Math. Softw. 48 (2022).
source
!!!NOTE TODO implement me.

For more details we refer to [1] as we follow the methodology described therein.

[1] Scroggs, M. W., Dokken, J. S., Richardson, C. N., & Wells, G. N. (2022). Construction of arbitrary order finite element degree-of-freedom maps on polygonal and polyhedral cell meshes. ACM Transactions on Mathematical Software (TOMS), 48(2), 1-23.

!!!TODO citation via software.
+
+!!!TODO Investigate if we can somehow pass the interpolation into this function in a typestable way.
source
diff --git a/previews/PR798/devdocs/elements/index.html b/previews/PR798/devdocs/elements/index.html new file mode 100644 index 0000000000..c107e6c043 --- /dev/null +++ b/previews/PR798/devdocs/elements/index.html @@ -0,0 +1,46 @@ + +Elements and cells · Ferrite.jl

Elements and cells

Type definitions

Elements or cells are subtypes of AbstractCell{<:AbstractRefShape}. As shown, they are parametrized by the associated reference element.

Required methods to implement for all subtypes of AbstractCell to define a new element

Ferrite.get_node_idsFunction
Ferrite.get_node_ids(c::AbstractCell)

Return the node id's for cell c in the order determined by the cell's reference cell.

Default implementation: c.nodes.

source

Common utilities and definitions when working with grids internally.

First we have some topological queries on the element

Ferrite.verticesMethod
Ferrite.vertices(::AbstractCell)

Returns a tuple with the node indices (of the nodes in a grid) for each vertex in a given cell. This function induces the VertexIndex, where the second index corresponds to the local index into this tuple.

source
Ferrite.edgesMethod
Ferrite.edges(::AbstractCell)

Returns a tuple of 2-tuples containing the ordered node indices (of the nodes in a grid) corresponding to the vertices that define an oriented edge. This function induces the EdgeIndex, where the second index corresponds to the local index into this tuple.

Note that the vertices are sufficient to define an edge uniquely.

source
Ferrite.facesMethod
Ferrite.faces(::AbstractCell)

Returns a tuple of n-tuples containing the ordered node indices (of the nodes in a grid) corresponding to the vertices that define an oriented face. This function induces the FaceIndex, where the second index corresponds to the local index into this tuple.

An oriented face is a face with the first node having the local index and the other nodes spanning such that the normal to the face is pointing outwards.

Note that the vertices are sufficient to define a face uniquely.

source
Ferrite.facetsMethod
Ferrite.facets(::AbstractCell)

Returns a tuple of n-tuples containing the ordered node indices (of the nodes in a grid) corresponding to the vertices that define an oriented facet. This function induces the FacetIndex, where the second index corresponds to the local index into this tuple.

See also vertices, edges, and faces

source
Ferrite.boundaryfunctionMethod
boundaryfunction(::Type{<:BoundaryIndex})

Helper function to dispatch on the correct entity from a given boundary index.

source
Ferrite.reference_verticesMethod
reference_vertices(::Type{<:AbstractRefShape})
+reference_vertices(::AbstractCell)

Returns a tuple of integers containing the local node indices corresponding to the vertices (i.e. corners or endpoints) of the cell.

source
Ferrite.reference_edgesMethod
reference_edges(::Type{<:AbstractRefShape})
+reference_edges(::AbstractCell)

Returns a tuple of 2-tuples containing the ordered local node indices (corresponding to the vertices) that define an edge.

source
Ferrite.reference_facesMethod
reference_faces(::Type{<:AbstractRefShape})
+reference_faces(::AbstractCell)

Returns a tuple of n-tuples containing the ordered local node indices (corresponding to the vertices) that define a face.

source

and some generic utils which are commonly found in finite element codes

Ferrite.toglobalFunction
toglobal(grid::AbstractGrid, vertexidx::VertexIndex) -> Int
+toglobal(grid::AbstractGrid, vertexidx::Vector{VertexIndex}) -> Vector{Int}

This function takes the local vertex representation (a VertexIndex) and looks up the unique global id (an Int).

source
Ferrite.sortfaceFunction
sortface(face::Tuple{Int})
+sortface(face::Tuple{Int,Int})
+sortface(face::Tuple{Int,Int,Int})
+sortface(face::Tuple{Int,Int,Int,Int})

Returns the unique representation of a face. Here the unique representation is the sorted node index tuple. Note that in 3D we only need indices to uniquely identify a face, so the unique representation is always a tuple length 3.

source
Ferrite.sortface_fastFunction
sortface_fast(face::Tuple{Int})
+sortface_fast(face::Tuple{Int,Int})
+sortface_fast(face::Tuple{Int,Int,Int})
+sortface_fast(face::Tuple{Int,Int,Int,Int})

Returns the unique representation of a face. Here the unique representation is the sorted node index tuple. Note that in 3D we only need indices to uniquely identify a face, so the unique representation is always a tuple length 3.

source
Ferrite.sortedgeFunction
sortedge(edge::Tuple{Int,Int})

Returns the unique representation of an edge and its orientation. Here the unique representation is the sorted node index tuple. The orientation is true if the edge is not flipped, where it is false if the edge is flipped.

source
Ferrite.sortedge_fastFunction

sortedge_fast(edge::Tuple{Int,Int})

Returns the unique representation of an edge. Here the unique representation is the sorted node index tuple.

source
Ferrite.element_to_facet_transformationFunction
element_to_facet_transformation(point::AbstractVector, ::Type{<:AbstractRefShape}, facet::Int)

Transform quadrature point from the cell's coordinates to the facet's reference coordinates, decreasing the number of dimensions by one. This is the inverse of facet_to_element_transformation.

source
Ferrite.facet_to_element_transformationFunction
facet_to_element_transformation(point::Vec, ::Type{<:AbstractRefShape}, facet::Int)

Transform quadrature point from the facet's reference coordinates to coordinates on the cell's facet, increasing the number of dimensions by one.

source
Ferrite.InterfaceOrientationInfoType
InterfaceOrientationInfo

Relative orientation information for 1D and 2D interfaces in 2D and 3D elements respectively. This information is used to construct the transformation matrix to transform the quadrature points from faceta to facetb achieving synced spatial coordinates. Face B's orientation relative to Face A's can possibly be flipped (i.e. the vertices indices order is reversed) and the vertices can be rotated against each other. The reference orientation of face B is such that the first node has the lowest vertex index. Thus, this structure also stores the shift of the lowest vertex index which is used to reorient the face in case of flipping transform_interface_points!.

source
Ferrite.transform_interface_points!Function
transform_interface_points!(dst::AbstractVector{Vec{3, Float64}}, points::AbstractVector{Vec{3, Float64}}, interface_transformation::InterfaceOrientationInfo)

Transform the points from face A to face B using the orientation information of the interface and store it in the vector dst. For 3D, the faces are transformed into regular polygons such that the rotation angle is the shift in reference node index × 2π ÷ number of edges in face. If the face is flipped then the flipping is about the axis that preserves the position of the first node (which is the reference node after being rotated to be in the first position, it's rotated back in the opposite direction after flipping). Take for example the interface

        2           3
+        | \         | \
+        |  \        |  \
+y       | A \       | B \
+↑       |    \      |    \
+→  x    1-----3     1-----2

Transforming A to an equilateral triangle and translating it such that {0,0} is equidistant to all nodes

        3
+        +
+       / \
+      /   \
+     /  x  \
+    /   ↑   \
+   /  ←      \
+  /  y        \
+2+-------------+1

Rotating it -270° (or 120°) such that the reference node (the node with the smallest index) is at index 1

        1
+        +
+       / \
+      /   \
+     /  x  \
+    /   ↑   \
+   /  ←      \
+  /  y        \
+3+-------------+2

Flipping about the x axis (such that the position of the reference node doesn't change) and rotating 270° (or -120°)

        2
+        +
+       / \
+      /   \
+     /  x  \
+    /   ↑   \
+   /  ←      \
+  /  y        \
+3+-------------+1

Transforming back to triangle B

       3
+       | \
+       |  \
+y      |   \
+↑      |    \
+→ x    1-----2
source
Ferrite.get_transformation_matrixFunction
get_transformation_matrix(interface_transformation::InterfaceOrientationInfo)

Returns the transformation matrix corresponding to the interface orientation information stored in InterfaceOrientationInfo. The transformation matrix is constructed using a combination of affine transformations defined for each interface reference shape. The transformation for a flipped face is a function of both relative orientation and the orientation of the second face. If the face is not flipped then the transformation is a function of relative orientation only.

source
diff --git a/previews/PR798/devdocs/index.html b/previews/PR798/devdocs/index.html new file mode 100644 index 0000000000..89af29ed38 --- /dev/null +++ b/previews/PR798/devdocs/index.html @@ -0,0 +1,2 @@ + +Developer documentation · Ferrite.jl
diff --git a/previews/PR798/devdocs/interpolations/index.html b/previews/PR798/devdocs/interpolations/index.html new file mode 100644 index 0000000000..bf174ae334 --- /dev/null +++ b/previews/PR798/devdocs/interpolations/index.html @@ -0,0 +1,5 @@ + +Interpolations · Ferrite.jl

Interpolations

Type definitions

Interpolations are subtypes of Interpolation{shape, order}, i.e. they are parametrized by the reference element and its characteristic order.

Fallback methods applicable for all subtypes of Interpolation

Ferrite.getrefshapeMethod
Ferrite.getrefshape(::Interpolation)::AbstractRefShape

Return the reference element shape of the interpolation.

source
Ferrite.reference_shape_gradientMethod
reference_shape_gradient(ip::Interpolation, ξ::Vec, i::Int)

Evaluate the gradient of the ith shape function of the interpolation ip in reference coordinate ξ.

source
Ferrite.boundarydof_indicesFunction
boundarydof_indices(::Type{<:BoundaryIndex})

Helper function to generically dispatch on the correct dof sets of a boundary entity.

source
Ferrite.reference_shape_values!Function
reference_shape_values!(values::AbstractArray{T}, ip::Interpolation, ξ::Vec)

Evaluate all shape functions of ip at once at the reference point ξ and store them in values.

source
Ferrite.reference_shape_gradients!Function
reference_shape_gradients!(gradients::AbstractArray, ip::Interpolation, ξ::Vec)

Evaluate all shape function gradients of ip at once at the reference point ξ and store them in gradients.

source
Ferrite.reference_shape_gradients_and_values!Function
reference_shape_gradients_and_values!(gradients::AbstractArray, values::AbstractArray, ip::Interpolation, ξ::Vec)

Evaluate all shape function gradients and values of ip at once at the reference point ξ and store them in values.

source
Ferrite.reference_shape_hessians_gradients_and_values!Function
reference_shape_hessians_gradients_and_values!(hessians::AbstractVector, gradients::AbstractVector, values::AbstractVector, ip::Interpolation, ξ::Vec)

Evaluate all shape function hessians, gradients and values of ip at once at the reference point ξ and store them in hessians, gradients, and values.

source
Ferrite.shape_value_typeMethod
shape_value_type(ip::Interpolation, ::Type{T}) where T<:Number

Return the type of shape_value(ip::Interpolation, ξ::Vec, ib::Int).

source

Required methods to implement for all subtypes of Interpolation to define a new finite element

Depending on the dimension of the reference element the following functions have to be implemented

Ferrite.vertexdof_indicesMethod
vertexdof_indices(ip::Interpolation)

A tuple containing tuples of local dof indices for the respective vertex in local enumeration on a cell defined by vertices(::Cell). The vertex enumeration must match the vertex enumeration of the corresponding geometrical cell.

Note

The dofs appearing in the tuple must be continuous and increasing! The first dof must be the 1, as vertex dofs are enumerated first.

source
Ferrite.dirichlet_vertexdof_indicesMethod
dirichlet_vertexdof_indices(ip::Interpolation)

A tuple containing tuples of local dof indices for the respective vertex in local enumeration on a cell defined by vertices(::Cell). The vertex enumeration must match the vertex enumeration of the corresponding geometrical cell. Used internally in ConstraintHandler and defaults to vertexdof_indices(ip::Interpolation) for continuous interpolation.

Note

The dofs appearing in the tuple must be continuous and increasing! The first dof must be the 1, as vertex dofs are enumerated first.

source
Ferrite.facedof_indicesMethod
facedof_indices(ip::Interpolation)

A tuple containing tuples of all local dof indices for the respective face in local enumeration on a cell defined by faces(::Cell). The face enumeration must match the face enumeration of the corresponding geometrical cell.

source
Ferrite.facedof_interior_indicesMethod
facedof_interior_indices(ip::Interpolation)

A tuple containing tuples of the local dof indices on the interior of the respective face in local enumeration on a cell defined by faces(::Cell). The face enumeration must match the face enumeration of the corresponding geometrical cell. Note that the vertex and edge dofs are included here.

Note

The dofs appearing in the tuple must be continuous and increasing! The first dof must be the computed via "last edge interior dof index + 1", if face dofs exist.

source
Ferrite.edgedof_indicesMethod
edgedof_indices(ip::Interpolation)

A tuple containing tuples of local dof indices for the respective edge in local enumeration on a cell defined by edges(::Cell). The edge enumeration must match the edge enumeration of the corresponding geometrical cell.

The dofs are guaranteed to be aligned with the local ordering of the entities on the oriented edge. Here the first entries are the vertex dofs, followed by the edge interior dofs.

source
Ferrite.dirichlet_edgedof_indicesMethod
dirichlet_edgedof_indices(ip::Interpolation)

A tuple containing tuples of local dof indices for the respective edge in local enumeration on a cell defined by edges(::Cell). The edge enumeration must match the edge enumeration of the corresponding geometrical cell. Used internally in ConstraintHandler and defaults to edgedof_indices(ip::Interpolation) for continuous interpolation.

The dofs are guaranteed to be aligned with the local ordering of the entities on the oriented edge. Here the first entries are the vertex dofs, followed by the edge interior dofs.

source
Ferrite.edgedof_interior_indicesMethod
edgedof_interior_indices(ip::Interpolation)

A tuple containing tuples of the local dof indices on the interior of the respective edge in local enumeration on a cell defined by edges(::Cell). The edge enumeration must match the edge enumeration of the corresponding geometrical cell. Note that the vertex dofs are included here.

Note

The dofs appearing in the tuple must be continuous and increasing! The first dof must be computed via "last vertex dof index + 1", if edge dofs exist.

source
Ferrite.volumedof_interior_indicesMethod
volumedof_interior_indices(ip::Interpolation)

Tuple containing the dof indices associated with the interior of a volume.

Note

The dofs appearing in the tuple must be continuous and increasing, volumedofs are enumerated last.

source
Ferrite.is_discontinuousMethod
is_discontinuous(::Interpolation)
+is_discontinuous(::Type{<:Interpolation})

Checks whether the interpolation is discontinuous (i.e. DiscontinuousLagrange)

source
Ferrite.adjust_dofs_during_distributionMethod
adjust_dofs_during_distribution(::Interpolation)

This function must return true if the dofs should be adjusted (i.e. permuted) during dof distribution. This is in contrast to i) adjusting the dofs during reinit! in the assembly loop, or ii) not adjusting at all (which is not needed for low order interpolations, generally).

source
Ferrite.mapping_typeFunction
mapping_type(ip::Interpolation)

Get the type of mapping from the reference cell to the real cell for an interpolation ip. Subtypes of ScalarInterpolation and VectorizedInterpolation return IdentityMapping(), but other non-scalar interpolations may request different mapping types.

source

for all entities which exist on that reference element. The dof functions default to having no dofs defined on a specific entity. Hence, not overloading of the dof functions will result in an element with zero dofs. Also, it should always be double checked that everything is consistent as specified in the docstring of the corresponding function, as inconsistent implementations can lead to bugs which are really difficult to track down.

diff --git a/previews/PR798/devdocs/performance/index.html b/previews/PR798/devdocs/performance/index.html new file mode 100644 index 0000000000..405cb90838 --- /dev/null +++ b/previews/PR798/devdocs/performance/index.html @@ -0,0 +1,2 @@ + +Performance analysis · Ferrite.jl

Performance analysis

In the benchmark folder we provide basic infrastructure to analyze the performance of Ferrite to help tracking down performance regression issues. Two basic tools can be directly executed via make: A basic benchmark for the current branch and a comparison between two commits. To execute the benchmark on the current branch only open a shell in the benchmark folder and call

make benchmark

whereas for the comparison of two commits simply call

make compare target=<target-commit> baseline=<baseline-commit>

If you have a custom julia executable that is not accessible via the julia command, then you can pass the executable via

JULIA_CMD=<path-to-julia-executable> make compare target=<target-commit> baseline=<baseline-commit>
Note

For the performance comparison between two commits you must not have any uncommitted or untracked files in your Ferrite.jl folder! Otherwise the PkgBenchmark.jl will fail to setup the comparison.

For more fine grained control you can run subsets of the benchmarks via by appending -<subset> to compare or benchmark, e.g.

make benchmark-mesh

to benchmark only the mesh functionality. The following subsets are currently available:

  • assembly
  • boundary-conditions
  • dofs
  • mesh
Note

It is recommended to run all benchmarks before running subsets to get the correct tuning parameters for each benchmark.

diff --git a/previews/PR798/devdocs/reference_cells/index.html b/previews/PR798/devdocs/reference_cells/index.html new file mode 100644 index 0000000000..6d5a71bcab --- /dev/null +++ b/previews/PR798/devdocs/reference_cells/index.html @@ -0,0 +1,128 @@ + +Reference cells · Ferrite.jl

Reference cells

The reference cells are used to i) define grid cells, ii) define shape functions, and iii) define quadrature rules. The numbering of vertices, edges, faces are visualized below. See also FerriteViz.elementinfo.

AbstractRefShape subtypes

Ferrite.RefLineType
RefLine <: AbstractRefShape{1}

Reference line/interval, reference dimension 1.

----------------+--------------------
+Vertex numbers: | Vertex coordinates:
+  1-------2     | v1: 𝛏 = (-1.0,)
+    --> ξ₁      | v2: 𝛏 = ( 1.0,)
+----------------+--------------------
+Face numbers:   | Face identifiers:
+  1-------2     | f1: (v1,)
+                | f2: (v2,)
+----------------+--------------------
source
Ferrite.RefTriangleType
RefTriangle <: AbstractRefShape{2}

Reference triangle, reference dimension 2.

----------------+--------------------
+Vertex numbers: | Vertex coordinates:
+    2           |
+    | \         | v1: 𝛏 = (1.0, 0.0)
+    |   \       | v2: 𝛏 = (0.0, 1.0)
+ξ₂^ |     \     | v3: 𝛏 = (0.0, 0.0)
+  | 3-------1   |
+  +--> ξ₁       |
+----------------+--------------------
+Face numbers:   | Face identifiers:
+    +           |
+    | \         | f1: (v1, v2)
+    2   1       | f2: (v2, v3)
+    |     \     | f3: (v3, v1)
+    +---3---+   |
+----------------+--------------------
source
Ferrite.RefQuadrilateralType
RefQuadrilateral <: AbstractRefShape{2}

Reference quadrilateral, reference dimension 2.

----------------+---------------------
+Vertex numbers: | Vertex coordinates:
+    4-------3   |
+    |       |   | v1: 𝛏 = (-1.0, -1.0)
+    |       |   | v2: 𝛏 = ( 1.0, -1.0)
+ξ₂^ |       |   | v3: 𝛏 = ( 1.0,  1.0)
+  | 1-------2   | v4: 𝛏 = (-1.0,  1.0)
+  +--> ξ₁       |
+----------------+---------------------
+Face numbers:   | Face identifiers:
+    +---3---+   | f1: (v1, v2)
+    |       |   | f2: (v2, v3)
+    4       2   | f3: (v3, v4)
+    |       |   | f4: (v4, v1)
+    +---1---+   |
+----------------+---------------------
source
Ferrite.RefTetrahedronType
RefTetrahedron <: AbstractRefShape{3}

Reference tetrahedron, reference dimension 3.

---------------------------------------+-------------------------
+Vertex numbers:                        | Vertex coordinates:
+             4                4        |
+  ^ ξ₃      /  \             /| \      |  v1: 𝛏 = (0.0, 0.0, 0.0)
+  |        /     \          / |   \    |  v2: 𝛏 = (1.0, 0.0, 0.0)
+  +-> ξ₂  /        \       /  1___  \  |  v3: 𝛏 = (0.0, 1.0, 0.0)
+ /       /      __--3     / /    __‾-3 |  v4: 𝛏 = (0.0, 0.0, 1.0)
+ξ₁      2 __--‾‾         2/__--‾‾      |
+---------------------------------------+-------------------------
+Edge numbers:                          | Edge identifiers:
+             +                +        | e1: (v1, v2)
+            /  \             /| \      | e2: (v2, v3)
+         5 /     \ 6      5 / |4  \ 6  | e3: (v3, v1)
+          /        \       /  +__3  \  | e4: (v1, v4)
+         /      __--+     / /1   __‾-+ | e5: (v2, v4)
+        + __--‾‾2        +/__--‾‾2     | e6: (v3, v4)
+---------------------------------------+-------------------------
+Face numbers:                          | Face identifiers:
+             +                +        |
+            /  \             /| \      | f1: (v1, v3, v2)
+           /     \          / | 4 \    | f2: (v1, v2, v4)
+          /   3    \       /2 +___  \  | f3: (v2, v3, v4)
+         /      __--+     / /  1 __‾-+ | f4: (v1, v4, v3)
+        + __--‾‾         +/__--‾‾      |
+---------------------------------------+-------------------------
source
Ferrite.RefHexahedronType
RefHexahedron <: AbstractRefShape{3}

Reference hexahedron, reference dimension 3.

-----------------------------------------+----------------------------
+Vertex numbers:                          | Vertex coordinates:
+            5--------8        5--------8 | v1: 𝛏 = (-1.0, -1.0, -1.0)
+           /        /|       /|        | | v2: 𝛏 = ( 1.0, -1.0, -1.0)
+          /        / |      / |        | | v3: 𝛏 = ( 1.0,  1.0, -1.0)
+  ^ ξ₃   6--------7  |     6  |        | | v4: 𝛏 = (-1.0,  1.0, -1.0)
+  |      |        |  4     |  1--------4 | v5: 𝛏 = (-1.0, -1.0,  1.0)
+  +-> ξ₂ |        | /      | /        /  | v6: 𝛏 = ( 1.0, -1.0,  1.0)
+ /       |        |/       |/        /   | v7: 𝛏 = ( 1.0,  1.0,  1.0)
+ξ₁       2--------3        2--------3    | v8: 𝛏 = (-1.0,  1.0,  1.0)
+-----------------------------------------+-----------------------------
+Edge numbers:                            | Edge identifiers:
+            +----8---+        +----8---+ |
+          5/        /|      5/|        | |  e1: (v1, v2),  e2: (v2, v3)
+          /       7/ |12    / |9     12| |  e3: (v3, v4),  e4: (v4, v1)
+         +----6---+  |     +  |        | |  e5: (v5, v6),  e6: (v6, v7)
+         |        |  +     |  +---4----+ |  e7: (v7, v8),  e8: (v8, v5)
+       10|      11| /    10| /1       /  |  e9: (v1, v5), e10: (v2, v6)
+         |        |/3      |/        /3  | e11: (v3, v7), e12: (v4, v8)
+         +---2----+        +---2----+    |
+-----------------------------------------+-----------------------------
+Face numbers:                            | Face identifiers:
+            +--------+        +--------+ |
+           /   6    /|       /|        | |  f1: (v1, v4, v3, v2)
+          /        / |      / |   5    | |  f2: (v1, v2, v6, v5)
+         +--------+ 4|     +  |        | |  f3: (v2, v3, v7, v6)
+         |        |  +     |2 +--------+ |  f4: (v3, v4, v8, v7)
+         |    3   | /      | /        /  |  f5: (v1, v5, v8, v4)
+         |        |/       |/    1   /   |  f6: (v5, v6, v7, v8)
+         +--------+        +--------+    |
+-----------------------------------------+-----------------------------
source
Ferrite.RefPrismType
RefPrism <: AbstractRefShape{3}

Reference prism, reference dimension 3.

-----------------------------------------+----------------------------
+Vertex numbers:                          | Vertex coordinates:
+            4-------/6       4--------6  |
+           /     /   |      /|        |  |  v1: 𝛏 = (0.0, 0.0, 0.0)
+          /   /      |     / |        |  |  v2: 𝛏 = (1.0, 0.0, 0.0)
+  ^ ξ₃   5 /         |    5  |        |  |  v3: 𝛏 = (0.0, 1.0, 0.0)
+  |      |          /3    |  1-------/3  |  v4: 𝛏 = (0.0, 0.0, 1.0)
+  +-> ξ₂ |       /        | /     /      |  v5: 𝛏 = (1.0, 0.0, 1.0)
+ /       |    /           |/   /         |  v6: 𝛏 = (0.0, 1.0, 1.0)
+ξ₁       2 /              2 /            |
+-----------------------------------------+----------------------------
+Edge numbers:                            | Edge identifiers:
+            +---8---/+       +---8----+  |
+          7/     /   |     7/|        |  | e1: (v2, v1),  e2: (v1, v3)
+          /   / 9    |6    / |3       |6 | e3: (v1, v4),  e4: (v3, v2)
+         + /         |    +  |        |  | e5: (v2, v5),  e6: (v3, v6)
+         |          /+    |  +--2----/+  | e7: (v4, v5),  e8: (v4, v6)
+        5|       /       5| /1    /      | e9: (v6, v5)
+         |    / 4         |/   / 4       |
+         + /              + /            |
+-----------------------------------------+----------------------------
+Face numbers:                            | Face identifiers:
+            +-------/+       +--------+  |
+           /  5  /   |      /|        |  | f1: (v1, v3, v2)
+          /   /      |     / |    3   |  | f2: (v1, v2, v5, v4)
+         + /         |    +  |        |  | f3: (v3, v1, v4, v6)
+         |     4    /+    |2 +-------/+  | f4: (v2, v3, v6, v5)
+         |       /        | /  1  /      | f5: (v4, v5, v6)
+         |    /           |/   /         |
+         + /              + /            |
+-----------------------------------------+----------------------------
source

Required methods to implement for all subtypes of AbstractRefShape to define a new reference shape

Ferrite.reference_verticesMethod
reference_vertices(::Type{<:AbstractRefShape})
+reference_vertices(::AbstractCell)

Returns a tuple of integers containing the local node indices corresponding to the vertices (i.e. corners or endpoints) of the cell.

source
Ferrite.reference_edgesMethod
reference_edges(::Type{<:AbstractRefShape})
+reference_edges(::AbstractCell)

Returns a tuple of 2-tuples containing the ordered local node indices (corresponding to the vertices) that define an edge.

source
Ferrite.reference_facesMethod
reference_faces(::Type{<:AbstractRefShape})
+reference_faces(::AbstractCell)

Returns a tuple of n-tuples containing the ordered local node indices (corresponding to the vertices) that define a face.

source

which automatically defines

Applicable methods to AbstractRefShapes

Ferrite.getrefdimMethod
Ferrite.getrefdim(RefShape::Type{<:AbstractRefShape})

Get the dimension of the reference shape

source
diff --git a/previews/PR798/devdocs/special_datastructures/index.html b/previews/PR798/devdocs/special_datastructures/index.html new file mode 100644 index 0000000000..e06b576e25 --- /dev/null +++ b/previews/PR798/devdocs/special_datastructures/index.html @@ -0,0 +1,6 @@ + +Special data structures · Ferrite.jl

Special data structures

ArrayOfVectorViews

ArrayOfVectorViews is a data structure representing an Array of vector views (specifically SubArray{T, 1} where T). By arranging all data (of type T) continuously in memory, this will significantly reduce the garbage collection time compared to using an Array{AbstractVector{T}}. While the data in each view can be mutated, the length of each view is fixed after construction. This data structure provides two features not provided by ArraysOfArrays.jl: Support of matrices and higher order arrays for storing vectors of different dimensions and efficient construction when the number of elements in each view is not known in advance.

Ferrite.CollectionsOfViews.ArrayOfVectorViewsType
ArrayOfVectorViews(f!::Function, data::Vector{T}, dims::NTuple{N, Int}; sizehint)

Create an ArrayOfVectorViews to store many vector views of potentially different sizes, emulating an Array{Vector{T}, N} with size dims. However, it avoids allocating each vector individually by storing all data in data, and instead of Vector{T}, the each element is a typeof(view(data, 1:2)).

When the length of each vector is unknown, the ArrayOfVectorViews can be created reasonably efficient with the following do-block, which creates an intermediate buffer::ConstructionBuffer supporting the push_at_index! function.

vector_views = ArrayOfVectorViews(data, dims; sizehint) do buffer
+    for (ind, val) in some_data
+        push_at_index!(buffer, val, ind)
+    end
+end

sizehint tells how much space to allocate for the index ind if no val has been added to that index before, or how much more space to allocate in case all previously allocated space for ind has been used up.

source
ArrayOfVectorViews(b::CollectionsOfViews.ConstructionBuffer)

Creates the ArrayOfVectorViews directly from the ConstructionBuffer that was manually created and filled.

source
ArrayOfVectorViews(indices::Vector{Int}, data::Vector{T}, lin_idx::LinearIndices{N}; checkargs = true)

Creates the ArrayOfVectorViews directly where the user is responsible for having the correct input data. Checking of the argument dimensions can be elided by setting checkargs = false, but incorrect dimensions may lead to illegal out of bounds access later.

data is indexed by indices[i]:indices[i+1], where i = lin_idx[idx...] and idx... are the user-provided indices to the ArrayOfVectorViews.

source
Ferrite.CollectionsOfViews.ConstructionBufferType
ConstructionBuffer(data::Vector, dims::NTuple{N, Int}, sizehint)

Create a buffer for creating an ArrayOfVectorViews, representing an array with N axes. sizehint sets the number of elements in data allocated when a new index is added via push_at_index!, or when the current storage for the index is full, how much many additional elements are reserved for that index. Any content in data is overwritten, but performance is improved by pre-allocating it to a reasonable size or by sizehint!ing it.

source
diff --git a/previews/PR798/gallery/bending.png b/previews/PR798/gallery/bending.png new file mode 100644 index 0000000000..727b3cba64 Binary files /dev/null and b/previews/PR798/gallery/bending.png differ diff --git a/previews/PR798/gallery/bending_animation.gif b/previews/PR798/gallery/bending_animation.gif new file mode 100644 index 0000000000..2649b897fd Binary files /dev/null and b/previews/PR798/gallery/bending_animation.gif differ diff --git a/previews/PR798/gallery/helmholtz.ipynb b/previews/PR798/gallery/helmholtz.ipynb new file mode 100644 index 0000000000..f58c1ef2b0 --- /dev/null +++ b/previews/PR798/gallery/helmholtz.ipynb @@ -0,0 +1,257 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Helmholtz equation\n", + "\n", + "In this example, we want to solve a (variant of) of the [Helmholtz equation](https://en.wikipedia.org/wiki/Helmholtz_equation).\n", + "The example is inspired by an [dealii step_7](https://www.dealii.org/8.4.1/doxygen/deal.II/step_7.html) on the standard square.\n", + "\n", + "$$\n", + " - \\Delta u + u = f\n", + "$$\n", + "\n", + "With boundary conditions given by\n", + "$$\n", + "u = g_1 \\quad x \\in \\Gamma_1\n", + "$$\n", + "and\n", + "$$\n", + "n \\cdot \\nabla u = g_2 \\quad x \\in \\Gamma_2\n", + "$$\n", + "\n", + "Here Γ₁ is the union of the top and the right boundary of the square,\n", + "while Γ₂ is the union of the bottom and the left boundary.\n", + "\n", + "![](helmholtz.png)\n", + "\n", + "We will use the following weak formulation:\n", + "$$\n", + "\\int_\\Omega \\nabla δu \\cdot \\nabla u \\, d\\Omega\n", + "+ \\int_\\Omega δu \\cdot u \\, d\\Omega\n", + "- \\int_\\Omega δu \\cdot f \\, d\\Omega\n", + "- \\int_{\\Gamma_2} δu g_2 \\, d\\Gamma = 0 \\quad \\forall δu\n", + "$$\n", + "\n", + "where $δu$ is a suitable test function that satisfies:\n", + "$$\n", + "δu = 0 \\quad x \\in \\Gamma_1\n", + "$$\n", + "and $u$ is a suitable function that satisfies:\n", + "$$\n", + "u = g_1 \\quad x \\in \\Gamma_1\n", + "$$\n", + "The example highlights the following interesting features:\n", + "\n", + "* There are two kinds of boundary conditions, \"Dirichlet\" and \"Von Neumann\"\n", + "* The example contains boundary integrals\n", + "* The Dirichlet condition is imposed strongly and the Von Neumann condition is imposed weakly." + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "DofHandler{2, Grid{2, Quadrilateral, Float64}}\n Fields:\n :u, Lagrange{RefQuadrilateral, 1}()\n Dofs per cell: 4\n Total dofs: 22801" + }, + "metadata": {}, + "execution_count": 1 + } + ], + "cell_type": "code", + "source": [ + "using Ferrite\n", + "using Tensors\n", + "using SparseArrays\n", + "using LinearAlgebra\n", + "\n", + "const ∇ = Tensors.gradient\n", + "const Δ = Tensors.hessian;\n", + "\n", + "grid = generate_grid(Quadrilateral, (150, 150))\n", + "\n", + "ip = Lagrange{RefQuadrilateral, 1}()\n", + "qr = QuadratureRule{RefQuadrilateral}(2)\n", + "qr_facet = FacetQuadratureRule{RefQuadrilateral}(2)\n", + "cellvalues = CellValues(qr, ip);\n", + "facetvalues = FacetValues(qr_facet, ip);\n", + "\n", + "dh = DofHandler(grid)\n", + "add!(dh, :u, ip)\n", + "close!(dh)" + ], + "metadata": {}, + "execution_count": 1 + }, + { + "cell_type": "markdown", + "source": [ + "We will set things up, so that a known analytic solution is approximately reproduced.\n", + "This is a good testing strategy for PDE codes and known as the method of manufactured solutions." + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "ConstraintHandler:\n Not closed!" + }, + "metadata": {}, + "execution_count": 2 + } + ], + "cell_type": "code", + "source": [ + "function u_ana(x::Vec{2, T}) where {T}\n", + " xs = (\n", + " Vec{2}((-0.5, 0.5)),\n", + " Vec{2}((-0.5, -0.5)),\n", + " Vec{2}((0.5, -0.5)),\n", + " )\n", + " σ = 1 / 8\n", + " s = zero(eltype(x))\n", + " for i in 1:3\n", + " s += exp(- norm(x - xs[i])^2 / σ^2)\n", + " end\n", + " return max(1.0e-15 * one(T), s) # Denormals, be gone\n", + "end;\n", + "\n", + "dbcs = ConstraintHandler(dh)" + ], + "metadata": {}, + "execution_count": 2 + }, + { + "cell_type": "markdown", + "source": [ + "The (strong) Dirichlet boundary condition can be handled automatically by the Ferrite library." + ], + "metadata": {} + }, + { + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Helmholtz successful\n" + ] + } + ], + "cell_type": "code", + "source": [ + "dbc = Dirichlet(:u, union(getfacetset(grid, \"top\"), getfacetset(grid, \"right\")), (x, t) -> u_ana(x))\n", + "add!(dbcs, dbc)\n", + "close!(dbcs)\n", + "update!(dbcs, 0.0)\n", + "\n", + "K = allocate_matrix(dh);\n", + "\n", + "function doassemble(\n", + " cellvalues::CellValues, facetvalues::FacetValues, K::SparseMatrixCSC, dh::DofHandler\n", + " )\n", + " b = 1.0\n", + " f = zeros(ndofs(dh))\n", + " assembler = start_assemble(K, f)\n", + "\n", + " n_basefuncs = getnbasefunctions(cellvalues)\n", + "\n", + " fe = zeros(n_basefuncs) # Local force vector\n", + " Ke = zeros(n_basefuncs, n_basefuncs) # Local stiffness mastrix\n", + "\n", + " for (cellcount, cell) in enumerate(CellIterator(dh))\n", + " fill!(Ke, 0)\n", + " fill!(fe, 0)\n", + " coords = getcoordinates(cell)\n", + "\n", + " reinit!(cellvalues, cell)\n", + " # First we derive the non boundary part of the variation problem from the destined solution `u_ana`\n", + " # $$\n", + " # \\int_\\Omega \\nabla δu \\cdot \\nabla u \\, d\\Omega\n", + " # + \\int_\\Omega δu \\cdot u \\, d\\Omega\n", + " # - \\int_\\Omega δu \\cdot f \\, d\\Omega\n", + " # $$\n", + " for q_point in 1:getnquadpoints(cellvalues)\n", + " dΩ = getdetJdV(cellvalues, q_point)\n", + " coords_qp = spatial_coordinate(cellvalues, q_point, coords)\n", + " f_true = -LinearAlgebra.tr(hessian(u_ana, coords_qp)) + u_ana(coords_qp)\n", + " for i in 1:n_basefuncs\n", + " δu = shape_value(cellvalues, q_point, i)\n", + " ∇δu = shape_gradient(cellvalues, q_point, i)\n", + " fe[i] += (δu * f_true) * dΩ\n", + " for j in 1:n_basefuncs\n", + " u = shape_value(cellvalues, q_point, j)\n", + " ∇u = shape_gradient(cellvalues, q_point, j)\n", + " Ke[i, j] += (∇δu ⋅ ∇u + δu * u) * dΩ\n", + " end\n", + " end\n", + " end\n", + " # Now we manually add the von Neumann boundary terms\n", + " # $$\n", + " # \\int_{\\Gamma_2} δu g_2 \\, d\\Gamma\n", + " # $$\n", + " for facet in 1:nfacets(cell)\n", + " if (cellcount, facet) ∈ getfacetset(grid, \"left\") ||\n", + " (cellcount, facet) ∈ getfacetset(grid, \"bottom\")\n", + " reinit!(facetvalues, cell, facet)\n", + " for q_point in 1:getnquadpoints(facetvalues)\n", + " coords_qp = spatial_coordinate(facetvalues, q_point, coords)\n", + " n = getnormal(facetvalues, q_point)\n", + " g_2 = gradient(u_ana, coords_qp) ⋅ n\n", + " dΓ = getdetJdV(facetvalues, q_point)\n", + " for i in 1:n_basefuncs\n", + " δu = shape_value(facetvalues, q_point, i)\n", + " fe[i] += (δu * g_2) * dΓ\n", + " end\n", + " end\n", + " end\n", + " end\n", + "\n", + " assemble!(assembler, celldofs(cell), Ke, fe)\n", + " end\n", + " return K, f\n", + "end;\n", + "\n", + "K, f = doassemble(cellvalues, facetvalues, K, dh);\n", + "apply!(K, f, dbcs)\n", + "u = Symmetric(K) \\ f;\n", + "\n", + "vtk = VTKGridFile(\"helmholtz\", dh)\n", + "write_solution(vtk, dh, u)\n", + "close(vtk)\n", + "println(\"Helmholtz successful\")" + ], + "metadata": {}, + "execution_count": 3 + }, + { + "cell_type": "markdown", + "source": [ + "---\n", + "\n", + "*This notebook was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).*" + ], + "metadata": {} + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.1" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.1", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR798/gallery/helmholtz.jl b/previews/PR798/gallery/helmholtz.jl new file mode 100644 index 0000000000..b2d4e31d1f --- /dev/null +++ b/previews/PR798/gallery/helmholtz.jl @@ -0,0 +1,110 @@ +using Ferrite +using Tensors +using SparseArrays +using LinearAlgebra + +const ∇ = Tensors.gradient +const Δ = Tensors.hessian; + +grid = generate_grid(Quadrilateral, (150, 150)) + +ip = Lagrange{RefQuadrilateral, 1}() +qr = QuadratureRule{RefQuadrilateral}(2) +qr_facet = FacetQuadratureRule{RefQuadrilateral}(2) +cellvalues = CellValues(qr, ip); +facetvalues = FacetValues(qr_facet, ip); + +dh = DofHandler(grid) +add!(dh, :u, ip) +close!(dh) + +function u_ana(x::Vec{2, T}) where {T} + xs = ( + Vec{2}((-0.5, 0.5)), + Vec{2}((-0.5, -0.5)), + Vec{2}((0.5, -0.5)), + ) + σ = 1 / 8 + s = zero(eltype(x)) + for i in 1:3 + s += exp(- norm(x - xs[i])^2 / σ^2) + end + return max(1.0e-15 * one(T), s) # Denormals, be gone +end; + +dbcs = ConstraintHandler(dh) + +dbc = Dirichlet(:u, union(getfacetset(grid, "top"), getfacetset(grid, "right")), (x, t) -> u_ana(x)) +add!(dbcs, dbc) +close!(dbcs) +update!(dbcs, 0.0) + +K = allocate_matrix(dh); + +function doassemble( + cellvalues::CellValues, facetvalues::FacetValues, K::SparseMatrixCSC, dh::DofHandler + ) + b = 1.0 + f = zeros(ndofs(dh)) + assembler = start_assemble(K, f) + + n_basefuncs = getnbasefunctions(cellvalues) + + fe = zeros(n_basefuncs) # Local force vector + Ke = zeros(n_basefuncs, n_basefuncs) # Local stiffness mastrix + + for (cellcount, cell) in enumerate(CellIterator(dh)) + fill!(Ke, 0) + fill!(fe, 0) + coords = getcoordinates(cell) + + reinit!(cellvalues, cell) + + for q_point in 1:getnquadpoints(cellvalues) + dΩ = getdetJdV(cellvalues, q_point) + coords_qp = spatial_coordinate(cellvalues, q_point, coords) + f_true = -LinearAlgebra.tr(hessian(u_ana, coords_qp)) + u_ana(coords_qp) + for i in 1:n_basefuncs + δu = shape_value(cellvalues, q_point, i) + ∇δu = shape_gradient(cellvalues, q_point, i) + fe[i] += (δu * f_true) * dΩ + for j in 1:n_basefuncs + u = shape_value(cellvalues, q_point, j) + ∇u = shape_gradient(cellvalues, q_point, j) + Ke[i, j] += (∇δu ⋅ ∇u + δu * u) * dΩ + end + end + end + + for facet in 1:nfacets(cell) + if (cellcount, facet) ∈ getfacetset(grid, "left") || + (cellcount, facet) ∈ getfacetset(grid, "bottom") + reinit!(facetvalues, cell, facet) + for q_point in 1:getnquadpoints(facetvalues) + coords_qp = spatial_coordinate(facetvalues, q_point, coords) + n = getnormal(facetvalues, q_point) + g_2 = gradient(u_ana, coords_qp) ⋅ n + dΓ = getdetJdV(facetvalues, q_point) + for i in 1:n_basefuncs + δu = shape_value(facetvalues, q_point, i) + fe[i] += (δu * g_2) * dΓ + end + end + end + end + + assemble!(assembler, celldofs(cell), Ke, fe) + end + return K, f +end; + +K, f = doassemble(cellvalues, facetvalues, K, dh); +apply!(K, f, dbcs) +u = Symmetric(K) \ f; + +vtk = VTKGridFile("helmholtz", dh) +write_solution(vtk, dh, u) +close(vtk) +println("Helmholtz successful") + +# This file was generated using Literate.jl, https://github.com/fredrikekre/Literate.jl diff --git a/previews/PR798/gallery/helmholtz.png b/previews/PR798/gallery/helmholtz.png new file mode 100644 index 0000000000..100d3e0306 Binary files /dev/null and b/previews/PR798/gallery/helmholtz.png differ diff --git a/previews/PR798/gallery/helmholtz.vtu b/previews/PR798/gallery/helmholtz.vtu new file mode 100644 index 0000000000..6665b95138 Binary files /dev/null and b/previews/PR798/gallery/helmholtz.vtu differ diff --git a/previews/PR798/gallery/helmholtz/index.html b/previews/PR798/gallery/helmholtz/index.html new file mode 100644 index 0000000000..93c6ea40b4 --- /dev/null +++ b/previews/PR798/gallery/helmholtz/index.html @@ -0,0 +1,111 @@ + +Helmholtz equation · Ferrite.jl

Helmholtz equation

In this example, we want to solve a (variant of) of the Helmholtz equation. The example is inspired by an dealii step_7 on the standard square.

\[ - \Delta u + u = f\]

With boundary conditions given by

\[u = g_1 \quad x \in \Gamma_1\]

and

\[n \cdot \nabla u = g_2 \quad x \in \Gamma_2\]

Here Γ₁ is the union of the top and the right boundary of the square, while Γ₂ is the union of the bottom and the left boundary.

We will use the following weak formulation:

\[\int_\Omega \nabla δu \cdot \nabla u \, d\Omega ++ \int_\Omega δu \cdot u \, d\Omega +- \int_\Omega δu \cdot f \, d\Omega +- \int_{\Gamma_2} δu g_2 \, d\Gamma = 0 \quad \forall δu\]

where $δu$ is a suitable test function that satisfies:

\[δu = 0 \quad x \in \Gamma_1\]

and $u$ is a suitable function that satisfies:

\[u = g_1 \quad x \in \Gamma_1\]

The example highlights the following interesting features:

  • There are two kinds of boundary conditions, "Dirichlet" and "Von Neumann"
  • The example contains boundary integrals
  • The Dirichlet condition is imposed strongly and the Von Neumann condition is imposed weakly.
using Ferrite
+using Tensors
+using SparseArrays
+using LinearAlgebra
+
+const ∇ = Tensors.gradient
+const Δ = Tensors.hessian;
+
+grid = generate_grid(Quadrilateral, (150, 150))
+
+ip = Lagrange{RefQuadrilateral, 1}()
+qr = QuadratureRule{RefQuadrilateral}(2)
+qr_facet = FacetQuadratureRule{RefQuadrilateral}(2)
+cellvalues = CellValues(qr, ip);
+facetvalues = FacetValues(qr_facet, ip);
+
+dh = DofHandler(grid)
+add!(dh, :u, ip)
+close!(dh)
DofHandler{2, Grid{2, Quadrilateral, Float64}}
+  Fields:
+    :u, Lagrange{RefQuadrilateral, 1}()
+  Dofs per cell: 4
+  Total dofs: 22801

We will set things up, so that a known analytic solution is approximately reproduced. This is a good testing strategy for PDE codes and known as the method of manufactured solutions.

function u_ana(x::Vec{2, T}) where {T}
+    xs = (
+        Vec{2}((-0.5, 0.5)),
+        Vec{2}((-0.5, -0.5)),
+        Vec{2}((0.5, -0.5)),
+    )
+    σ = 1 / 8
+    s = zero(eltype(x))
+    for i in 1:3
+        s += exp(- norm(x - xs[i])^2 / σ^2)
+    end
+    return max(1.0e-15 * one(T), s) # Denormals, be gone
+end;
+
+dbcs = ConstraintHandler(dh)
ConstraintHandler:
+  Not closed!

The (strong) Dirichlet boundary condition can be handled automatically by the Ferrite library.

dbc = Dirichlet(:u, union(getfacetset(grid, "top"), getfacetset(grid, "right")), (x, t) -> u_ana(x))
+add!(dbcs, dbc)
+close!(dbcs)
+update!(dbcs, 0.0)
+
+K = allocate_matrix(dh);
+
+function doassemble(
+        cellvalues::CellValues, facetvalues::FacetValues, K::SparseMatrixCSC, dh::DofHandler
+    )
+    b = 1.0
+    f = zeros(ndofs(dh))
+    assembler = start_assemble(K, f)
+
+    n_basefuncs = getnbasefunctions(cellvalues)
+
+    fe = zeros(n_basefuncs) # Local force vector
+    Ke = zeros(n_basefuncs, n_basefuncs) # Local stiffness mastrix
+
+    for (cellcount, cell) in enumerate(CellIterator(dh))
+        fill!(Ke, 0)
+        fill!(fe, 0)
+        coords = getcoordinates(cell)
+
+        reinit!(cellvalues, cell)

First we derive the non boundary part of the variation problem from the destined solution u_ana

\[\int_\Omega \nabla δu \cdot \nabla u \, d\Omega ++ \int_\Omega δu \cdot u \, d\Omega +- \int_\Omega δu \cdot f \, d\Omega\]

        for q_point in 1:getnquadpoints(cellvalues)
+            dΩ = getdetJdV(cellvalues, q_point)
+            coords_qp = spatial_coordinate(cellvalues, q_point, coords)
+            f_true = -LinearAlgebra.tr(hessian(u_ana, coords_qp)) + u_ana(coords_qp)
+            for i in 1:n_basefuncs
+                δu = shape_value(cellvalues, q_point, i)
+                ∇δu = shape_gradient(cellvalues, q_point, i)
+                fe[i] += (δu * f_true) * dΩ
+                for j in 1:n_basefuncs
+                    u = shape_value(cellvalues, q_point, j)
+                    ∇u = shape_gradient(cellvalues, q_point, j)
+                    Ke[i, j] += (∇δu ⋅ ∇u + δu * u) * dΩ
+                end
+            end
+        end

Now we manually add the von Neumann boundary terms

\[\int_{\Gamma_2} δu g_2 \, d\Gamma\]

        for facet in 1:nfacets(cell)
+            if (cellcount, facet) ∈ getfacetset(grid, "left") ||
+                    (cellcount, facet) ∈ getfacetset(grid, "bottom")
+                reinit!(facetvalues, cell, facet)
+                for q_point in 1:getnquadpoints(facetvalues)
+                    coords_qp = spatial_coordinate(facetvalues, q_point, coords)
+                    n = getnormal(facetvalues, q_point)
+                    g_2 = gradient(u_ana, coords_qp) ⋅ n
+                    dΓ = getdetJdV(facetvalues, q_point)
+                    for i in 1:n_basefuncs
+                        δu = shape_value(facetvalues, q_point, i)
+                        fe[i] += (δu * g_2) * dΓ
+                    end
+                end
+            end
+        end
+
+        assemble!(assembler, celldofs(cell), Ke, fe)
+    end
+    return K, f
+end;
+
+K, f = doassemble(cellvalues, facetvalues, K, dh);
+apply!(K, f, dbcs)
+u = Symmetric(K) \ f;
+
+vtk = VTKGridFile("helmholtz", dh)
+write_solution(vtk, dh, u)
+close(vtk)
+println("Helmholtz successful")
Helmholtz successful

This page was generated using Literate.jl.

diff --git a/previews/PR798/gallery/hyperelasticity_incomp_mixed.pvd b/previews/PR798/gallery/hyperelasticity_incomp_mixed.pvd new file mode 100755 index 0000000000..c8b9ad5b56 --- /dev/null +++ b/previews/PR798/gallery/hyperelasticity_incomp_mixed.pvd @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/previews/PR798/gallery/hyperelasticity_incomp_mixed_1.vtu b/previews/PR798/gallery/hyperelasticity_incomp_mixed_1.vtu new file mode 100644 index 0000000000..676daa2c8f Binary files /dev/null and b/previews/PR798/gallery/hyperelasticity_incomp_mixed_1.vtu differ diff --git a/previews/PR798/gallery/hyperelasticity_incomp_mixed_10.vtu b/previews/PR798/gallery/hyperelasticity_incomp_mixed_10.vtu new file mode 100644 index 0000000000..41c2b14be2 Binary files /dev/null and b/previews/PR798/gallery/hyperelasticity_incomp_mixed_10.vtu differ diff --git a/previews/PR798/gallery/hyperelasticity_incomp_mixed_11.vtu b/previews/PR798/gallery/hyperelasticity_incomp_mixed_11.vtu new file mode 100644 index 0000000000..66ffde3b46 Binary files /dev/null and b/previews/PR798/gallery/hyperelasticity_incomp_mixed_11.vtu differ diff --git a/previews/PR798/gallery/hyperelasticity_incomp_mixed_12.vtu b/previews/PR798/gallery/hyperelasticity_incomp_mixed_12.vtu new file mode 100644 index 0000000000..d11dce50c8 Binary files /dev/null and b/previews/PR798/gallery/hyperelasticity_incomp_mixed_12.vtu differ diff --git a/previews/PR798/gallery/hyperelasticity_incomp_mixed_13.vtu b/previews/PR798/gallery/hyperelasticity_incomp_mixed_13.vtu new file mode 100644 index 0000000000..84f2aae6d8 Binary files /dev/null and b/previews/PR798/gallery/hyperelasticity_incomp_mixed_13.vtu differ diff --git a/previews/PR798/gallery/hyperelasticity_incomp_mixed_14.vtu b/previews/PR798/gallery/hyperelasticity_incomp_mixed_14.vtu new file mode 100644 index 0000000000..9cbd7ed250 Binary files /dev/null and b/previews/PR798/gallery/hyperelasticity_incomp_mixed_14.vtu differ diff --git a/previews/PR798/gallery/hyperelasticity_incomp_mixed_15.vtu b/previews/PR798/gallery/hyperelasticity_incomp_mixed_15.vtu new file mode 100644 index 0000000000..ee40ea3e70 Binary files /dev/null and b/previews/PR798/gallery/hyperelasticity_incomp_mixed_15.vtu differ diff --git a/previews/PR798/gallery/hyperelasticity_incomp_mixed_16.vtu b/previews/PR798/gallery/hyperelasticity_incomp_mixed_16.vtu new file mode 100644 index 0000000000..ee92a545f0 Binary files /dev/null and b/previews/PR798/gallery/hyperelasticity_incomp_mixed_16.vtu differ diff --git a/previews/PR798/gallery/hyperelasticity_incomp_mixed_17.vtu b/previews/PR798/gallery/hyperelasticity_incomp_mixed_17.vtu new file mode 100644 index 0000000000..f018be627a Binary files /dev/null and b/previews/PR798/gallery/hyperelasticity_incomp_mixed_17.vtu differ diff --git a/previews/PR798/gallery/hyperelasticity_incomp_mixed_18.vtu b/previews/PR798/gallery/hyperelasticity_incomp_mixed_18.vtu new file mode 100644 index 0000000000..d6bbe9bda2 Binary files /dev/null and b/previews/PR798/gallery/hyperelasticity_incomp_mixed_18.vtu differ diff --git a/previews/PR798/gallery/hyperelasticity_incomp_mixed_19.vtu b/previews/PR798/gallery/hyperelasticity_incomp_mixed_19.vtu new file mode 100644 index 0000000000..f50461b3b3 Binary files /dev/null and b/previews/PR798/gallery/hyperelasticity_incomp_mixed_19.vtu differ diff --git a/previews/PR798/gallery/hyperelasticity_incomp_mixed_2.vtu b/previews/PR798/gallery/hyperelasticity_incomp_mixed_2.vtu new file mode 100644 index 0000000000..2854e5f69a Binary files /dev/null and b/previews/PR798/gallery/hyperelasticity_incomp_mixed_2.vtu differ diff --git a/previews/PR798/gallery/hyperelasticity_incomp_mixed_20.vtu b/previews/PR798/gallery/hyperelasticity_incomp_mixed_20.vtu new file mode 100644 index 0000000000..d121c7e0be Binary files /dev/null and b/previews/PR798/gallery/hyperelasticity_incomp_mixed_20.vtu differ diff --git a/previews/PR798/gallery/hyperelasticity_incomp_mixed_21.vtu b/previews/PR798/gallery/hyperelasticity_incomp_mixed_21.vtu new file mode 100644 index 0000000000..0dc1675742 Binary files /dev/null and b/previews/PR798/gallery/hyperelasticity_incomp_mixed_21.vtu differ diff --git a/previews/PR798/gallery/hyperelasticity_incomp_mixed_3.vtu b/previews/PR798/gallery/hyperelasticity_incomp_mixed_3.vtu new file mode 100644 index 0000000000..bb14f21dc4 Binary files /dev/null and b/previews/PR798/gallery/hyperelasticity_incomp_mixed_3.vtu differ diff --git a/previews/PR798/gallery/hyperelasticity_incomp_mixed_4.vtu b/previews/PR798/gallery/hyperelasticity_incomp_mixed_4.vtu new file mode 100644 index 0000000000..1d69de77c2 Binary files /dev/null and b/previews/PR798/gallery/hyperelasticity_incomp_mixed_4.vtu differ diff --git a/previews/PR798/gallery/hyperelasticity_incomp_mixed_5.vtu b/previews/PR798/gallery/hyperelasticity_incomp_mixed_5.vtu new file mode 100644 index 0000000000..57dd91cdb6 Binary files /dev/null and b/previews/PR798/gallery/hyperelasticity_incomp_mixed_5.vtu differ diff --git a/previews/PR798/gallery/hyperelasticity_incomp_mixed_6.vtu b/previews/PR798/gallery/hyperelasticity_incomp_mixed_6.vtu new file mode 100644 index 0000000000..8ceb22e2c6 Binary files /dev/null and b/previews/PR798/gallery/hyperelasticity_incomp_mixed_6.vtu differ diff --git a/previews/PR798/gallery/hyperelasticity_incomp_mixed_7.vtu b/previews/PR798/gallery/hyperelasticity_incomp_mixed_7.vtu new file mode 100644 index 0000000000..9fe4db646e Binary files /dev/null and b/previews/PR798/gallery/hyperelasticity_incomp_mixed_7.vtu differ diff --git a/previews/PR798/gallery/hyperelasticity_incomp_mixed_8.vtu b/previews/PR798/gallery/hyperelasticity_incomp_mixed_8.vtu new file mode 100644 index 0000000000..3a6c20184a Binary files /dev/null and b/previews/PR798/gallery/hyperelasticity_incomp_mixed_8.vtu differ diff --git a/previews/PR798/gallery/hyperelasticity_incomp_mixed_9.vtu b/previews/PR798/gallery/hyperelasticity_incomp_mixed_9.vtu new file mode 100644 index 0000000000..10169211ab Binary files /dev/null and b/previews/PR798/gallery/hyperelasticity_incomp_mixed_9.vtu differ diff --git a/previews/PR798/gallery/index.html b/previews/PR798/gallery/index.html new file mode 100644 index 0000000000..56b9232e4d --- /dev/null +++ b/previews/PR798/gallery/index.html @@ -0,0 +1,2 @@ + +Code gallery · Ferrite.jl

Code gallery

This page gives an overview of the code gallery. Compared to the tutorials, these programs do not focus on teaching Ferrite, but rather focus on showing how Ferrite can be used "in the wild".

Contribute to the gallery!

Most of the gallery is user contributed. If you use Ferrite, and have something you want to share, please contribute to the gallery! This could, for example, be your research code for a published paper, some interesting application, or just some nice trick.


Helmholtz equation

Solves the Helmholtz equation on the unit square using a combination of Dirichlet and Neumann boundary conditions and the method of manufactured solutions.

Contributed by: Kristoffer Carlsson (@KristofferC).


Nearly incompressible hyperelasticity

This program combines the ideas from Tutorial 3: Incompressible elasticity and Tutorial 4: Hyperelasticity to construct a mixed element solving three-dimensional displacement-pressure equations.

Contributed by: Bhavesh Shrimali (@bhaveshshrimali).


Ginzburg-Landau model energy minimization

A basic Ginzburg-Landau model is solved. ForwardDiff.jl is used to compute the gradient and hessian of the energy function. Multi-threading is used to parallelize the assembly procedure.

Contributed by: Louis Ponet (@louisponet).


Topology optimization

Topology optimization is shown for the bending problem by using a SIMP material model. To avoid numerical instabilities, a regularization scheme requiring the calculation of the Laplacian is imposed, which is done by using the grid topology functionalities.

Contributed by: Mischa Blaszczyk (@blaszm).

diff --git a/previews/PR798/gallery/landau.ipynb b/previews/PR798/gallery/landau.ipynb new file mode 100644 index 0000000000..b00b3c369b --- /dev/null +++ b/previews/PR798/gallery/landau.ipynb @@ -0,0 +1,694 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Ginzburg-Landau model energy minimization" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "![landau_orig.png](landau_orig.png)" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Original" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "![landau_opt.png](landau_opt.png)" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Optimized" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "In this example a basic Ginzburg-Landau model is solved.\n", + "This example gives an idea of how the API together with ForwardDiff can be leveraged to\n", + "performantly solve non standard problems on a FEM grid.\n", + "A large portion of the code is there only for performance reasons,\n", + "but since this usually really matters and is what takes the most time to optimize,\n", + "it is included." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The key to using a method like this for minimizing a free energy function directly,\n", + "rather than the weak form, as is usually done with FEM, is to split up the\n", + "gradient and Hessian calculations.\n", + "This means that they are performed for each cell separately instead of for the\n", + "grid as a whole." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using ForwardDiff\n", + "import ForwardDiff: GradientConfig, HessianConfig, Chunk\n", + "using Ferrite\n", + "using Optim, LineSearches\n", + "using SparseArrays\n", + "using Tensors\n", + "using Base.Threads" + ], + "metadata": {}, + "execution_count": 1 + }, + { + "cell_type": "markdown", + "source": [ + "## Energy terms\n", + "### 4th order Landau free energy" + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "Fl (generic function with 1 method)" + }, + "metadata": {}, + "execution_count": 2 + } + ], + "cell_type": "code", + "source": [ + "function Fl(P::Vec{3, T}, α::Vec{3}) where {T}\n", + " P2 = Vec{3, T}((P[1]^2, P[2]^2, P[3]^2))\n", + " return α[1] * sum(P2) +\n", + " α[2] * (P[1]^4 + P[2]^4 + P[3]^4) +\n", + " α[3] * ((P2[1] * P2[2] + P2[2] * P2[3]) + P2[1] * P2[3])\n", + "end" + ], + "metadata": {}, + "execution_count": 2 + }, + { + "cell_type": "markdown", + "source": [ + "### Ginzburg free energy" + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "Fg (generic function with 1 method)" + }, + "metadata": {}, + "execution_count": 3 + } + ], + "cell_type": "code", + "source": [ + "@inline Fg(∇P, G) = 0.5(∇P ⊡ G) ⊡ ∇P" + ], + "metadata": {}, + "execution_count": 3 + }, + { + "cell_type": "markdown", + "source": [ + "### GL free energy" + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "F (generic function with 1 method)" + }, + "metadata": {}, + "execution_count": 4 + } + ], + "cell_type": "code", + "source": [ + "F(P, ∇P, params) = Fl(P, params.α) + Fg(∇P, params.G)" + ], + "metadata": {}, + "execution_count": 4 + }, + { + "cell_type": "markdown", + "source": [ + "### Parameters that characterize the model" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "struct ModelParams{V, T}\n", + " α::V\n", + " G::T\n", + "end" + ], + "metadata": {}, + "execution_count": 5 + }, + { + "cell_type": "markdown", + "source": [ + "### ThreadCache\n", + "This holds the values that each thread will use during the assembly." + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "Main.var\"##347\".ThreadCache" + }, + "metadata": {}, + "execution_count": 6 + } + ], + "cell_type": "code", + "source": [ + "struct ThreadCache{CV, T, DIM, F <: Function, GC <: GradientConfig, HC <: HessianConfig}\n", + " cvP::CV\n", + " element_indices::Vector{Int}\n", + " element_dofs::Vector{T}\n", + " element_gradient::Vector{T}\n", + " element_hessian::Matrix{T}\n", + " element_coords::Vector{Vec{DIM, T}}\n", + " element_potential::F\n", + " gradconf::GC\n", + " hessconf::HC\n", + "end\n", + "function ThreadCache(dpc::Int, nodespercell, cvP::CellValues, modelparams, elpotential)\n", + " element_indices = zeros(Int, dpc)\n", + " element_dofs = zeros(dpc)\n", + " element_gradient = zeros(dpc)\n", + " element_hessian = zeros(dpc, dpc)\n", + " element_coords = zeros(Vec{3, Float64}, nodespercell)\n", + " potfunc = x -> elpotential(x, cvP, modelparams)\n", + " gradconf = GradientConfig(potfunc, zeros(dpc), Chunk{12}())\n", + " hessconf = HessianConfig(potfunc, zeros(dpc), Chunk{4}())\n", + " return ThreadCache(cvP, element_indices, element_dofs, element_gradient, element_hessian, element_coords, potfunc, gradconf, hessconf)\n", + "end" + ], + "metadata": {}, + "execution_count": 6 + }, + { + "cell_type": "markdown", + "source": [ + "## The Model\n", + "everything is combined into a model." + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "Main.var\"##347\".LandauModel" + }, + "metadata": {}, + "execution_count": 7 + } + ], + "cell_type": "code", + "source": [ + "mutable struct LandauModel{T, DH <: DofHandler, CH <: ConstraintHandler, TC <: ThreadCache}\n", + " dofs::Vector{T}\n", + " dofhandler::DH\n", + " boundaryconds::CH\n", + " threadindices::Vector{Vector{Int}}\n", + " threadcaches::Vector{TC}\n", + "end\n", + "\n", + "function LandauModel(α, G, gridsize, left::Vec{DIM, T}, right::Vec{DIM, T}, elpotential) where {DIM, T}\n", + " grid = generate_grid(Tetrahedron, gridsize, left, right)\n", + " threadindices = Ferrite.create_coloring(grid)\n", + "\n", + " qr = QuadratureRule{RefTetrahedron}(2)\n", + " ipP = Lagrange{RefTetrahedron, 1}()^3\n", + " cvP = CellValues(qr, ipP)\n", + "\n", + " dofhandler = DofHandler(grid)\n", + " add!(dofhandler, :P, ipP)\n", + " close!(dofhandler)\n", + "\n", + " dofvector = zeros(ndofs(dofhandler))\n", + " startingconditions!(dofvector, dofhandler)\n", + " boundaryconds = ConstraintHandler(dofhandler)\n", + " #boundary conditions can be added but aren't necessary for optimization\n", + " #add!(boundaryconds, Dirichlet(:P, getfacetset(grid, \"left\"), (x, t) -> [0.0,0.0,0.53], [1,2,3]))\n", + " #add!(boundaryconds, Dirichlet(:P, getfacetset(grid, \"right\"), (x, t) -> [0.0,0.0,-0.53], [1,2,3]))\n", + " close!(boundaryconds)\n", + " update!(boundaryconds, 0.0)\n", + "\n", + " apply!(dofvector, boundaryconds)\n", + "\n", + " hessian = allocate_matrix(dofhandler)\n", + " dpc = ndofs_per_cell(dofhandler)\n", + " cpc = length(grid.cells[1].nodes)\n", + " caches = [ThreadCache(dpc, cpc, copy(cvP), ModelParams(α, G), elpotential) for t in 1:nthreads()]\n", + " return LandauModel(dofvector, dofhandler, boundaryconds, threadindices, caches)\n", + "end" + ], + "metadata": {}, + "execution_count": 7 + }, + { + "cell_type": "markdown", + "source": [ + "utility to quickly save a model" + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "save_landau (generic function with 2 methods)" + }, + "metadata": {}, + "execution_count": 8 + } + ], + "cell_type": "code", + "source": [ + "function save_landau(path, model, dofs = model.dofs)\n", + " VTKGridFile(path, model.dofhandler) do vtk\n", + " write_solution(vtk, model.dofhandler, dofs)\n", + " end\n", + " return\n", + "end" + ], + "metadata": {}, + "execution_count": 8 + }, + { + "cell_type": "markdown", + "source": [ + "## Assembly\n", + "This macro defines most of the assembly step, since the structure is the same for\n", + "the energy, gradient and Hessian calculations." + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "@assemble! (macro with 1 method)" + }, + "metadata": {}, + "execution_count": 9 + } + ], + "cell_type": "code", + "source": [ + "macro assemble!(innerbody)\n", + " return esc(\n", + " quote\n", + " dofhandler = model.dofhandler\n", + " for indices in model.threadindices\n", + " @threads for i in indices\n", + " cache = model.threadcaches[threadid()]\n", + " eldofs = cache.element_dofs\n", + " nodeids = dofhandler.grid.cells[i].nodes\n", + " for j in 1:length(cache.element_coords)\n", + " cache.element_coords[j] = dofhandler.grid.nodes[nodeids[j]].x\n", + " end\n", + " reinit!(cache.cvP, cache.element_coords)\n", + "\n", + " celldofs!(cache.element_indices, dofhandler, i)\n", + " for j in 1:length(cache.element_dofs)\n", + " eldofs[j] = dofvector[cache.element_indices[j]]\n", + " end\n", + " $innerbody\n", + " end\n", + " end\n", + " end\n", + " )\n", + "end" + ], + "metadata": {}, + "execution_count": 9 + }, + { + "cell_type": "markdown", + "source": [ + "This calculates the total energy calculation of the grid" + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "F (generic function with 2 methods)" + }, + "metadata": {}, + "execution_count": 10 + } + ], + "cell_type": "code", + "source": [ + "function F(dofvector::Vector{T}, model) where {T}\n", + " outs = fill(zero(T), nthreads())\n", + " @assemble! begin\n", + " outs[threadid()] += cache.element_potential(eldofs)\n", + " end\n", + " return sum(outs)\n", + "end" + ], + "metadata": {}, + "execution_count": 10 + }, + { + "cell_type": "markdown", + "source": [ + "The gradient calculation for each dof" + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "∇F! (generic function with 1 method)" + }, + "metadata": {}, + "execution_count": 11 + } + ], + "cell_type": "code", + "source": [ + "function ∇F!(∇f::Vector{T}, dofvector::Vector{T}, model::LandauModel{T}) where {T}\n", + " fill!(∇f, zero(T))\n", + " @assemble! begin\n", + " ForwardDiff.gradient!(cache.element_gradient, cache.element_potential, eldofs, cache.gradconf)\n", + " @inbounds assemble!(∇f, cache.element_indices, cache.element_gradient)\n", + " end\n", + " return\n", + "end" + ], + "metadata": {}, + "execution_count": 11 + }, + { + "cell_type": "markdown", + "source": [ + "The Hessian calculation for the whole grid" + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "∇²F! (generic function with 1 method)" + }, + "metadata": {}, + "execution_count": 12 + } + ], + "cell_type": "code", + "source": [ + "function ∇²F!(∇²f::SparseMatrixCSC, dofvector::Vector{T}, model::LandauModel{T}) where {T}\n", + " assemblers = [start_assemble(∇²f) for t in 1:nthreads()]\n", + " @assemble! begin\n", + " ForwardDiff.hessian!(cache.element_hessian, cache.element_potential, eldofs, cache.hessconf)\n", + " @inbounds assemble!(assemblers[threadid()], cache.element_indices, cache.element_hessian)\n", + " end\n", + " return\n", + "end" + ], + "metadata": {}, + "execution_count": 12 + }, + { + "cell_type": "markdown", + "source": [ + "We can also calculate all things in one go!" + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "calcall (generic function with 1 method)" + }, + "metadata": {}, + "execution_count": 13 + } + ], + "cell_type": "code", + "source": [ + "function calcall(∇²f::SparseMatrixCSC, ∇f::Vector{T}, dofvector::Vector{T}, model::LandauModel{T}) where {T}\n", + " outs = fill(zero(T), nthreads())\n", + " fill!(∇f, zero(T))\n", + " assemblers = [start_assemble(∇²f, ∇f) for t in 1:nthreads()]\n", + " @assemble! begin\n", + " outs[threadid()] += cache.element_potential(eldofs)\n", + " ForwardDiff.hessian!(cache.element_hessian, cache.element_potential, eldofs, cache.hessconf)\n", + " ForwardDiff.gradient!(cache.element_gradient, cache.element_potential, eldofs, cache.gradconf)\n", + " @inbounds assemble!(assemblers[threadid()], cache.element_indices, cache.element_gradient, cache.element_hessian)\n", + " end\n", + " return sum(outs)\n", + "end" + ], + "metadata": {}, + "execution_count": 13 + }, + { + "cell_type": "markdown", + "source": [ + "## Minimization\n", + "Now everything can be combined to minimize the energy, and find the equilibrium\n", + "configuration." + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "minimize! (generic function with 1 method)" + }, + "metadata": {}, + "execution_count": 14 + } + ], + "cell_type": "code", + "source": [ + "function minimize!(model; kwargs...)\n", + " dh = model.dofhandler\n", + " dofs = model.dofs\n", + " ∇f = fill(0.0, length(dofs))\n", + " ∇²f = allocate_matrix(dh)\n", + " function g!(storage, x)\n", + " ∇F!(storage, x, model)\n", + " return apply_zero!(storage, model.boundaryconds)\n", + " end\n", + " function h!(storage, x)\n", + " return ∇²F!(storage, x, model)\n", + " # apply!(storage, model.boundaryconds)\n", + " end\n", + " f(x) = F(x, model)\n", + "\n", + " od = TwiceDifferentiable(f, g!, h!, model.dofs, 0.0, ∇f, ∇²f)\n", + "\n", + " # this way of minimizing is only beneficial when the initial guess is completely off,\n", + " # then a quick couple of ConjuageGradient steps brings us easily closer to the minimum.\n", + " # res = optimize(od, model.dofs, ConjugateGradient(linesearch=BackTracking()), Optim.Options(show_trace=true, show_every=1, g_tol=1e-20, iterations=10))\n", + " # model.dofs .= res.minimizer\n", + " # to get the final convergence, Newton's method is more ideal since the energy landscape should be almost parabolic\n", + " ##+\n", + " res = optimize(od, model.dofs, Newton(linesearch = BackTracking()), Optim.Options(show_trace = true, show_every = 1, g_tol = 1.0e-20))\n", + " model.dofs .= res.minimizer\n", + " return res\n", + "end" + ], + "metadata": {}, + "execution_count": 14 + }, + { + "cell_type": "markdown", + "source": [ + "## Testing it\n", + "This calculates the contribution of each element to the total energy,\n", + "it is also the function that will be put through ForwardDiff for the gradient and Hessian." + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "element_potential (generic function with 1 method)" + }, + "metadata": {}, + "execution_count": 15 + } + ], + "cell_type": "code", + "source": [ + "function element_potential(eldofs::AbstractVector{T}, cvP, params) where {T}\n", + " energy = zero(T)\n", + " for qp in 1:getnquadpoints(cvP)\n", + " P = function_value(cvP, qp, eldofs)\n", + " ∇P = function_gradient(cvP, qp, eldofs)\n", + " energy += F(P, ∇P, params) * getdetJdV(cvP, qp)\n", + " end\n", + " return energy\n", + "end" + ], + "metadata": {}, + "execution_count": 15 + }, + { + "cell_type": "markdown", + "source": [ + "now we define some starting conditions" + ], + "metadata": {} + }, + { + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Iter Function value Gradient norm \n", + " 0 2.127588e+06 3.597094e+02\n", + " * time: 0.028335094451904297\n", + " 1 3.786155e+05 1.047687e+02\n", + " * time: 3.7368240356445312\n", + " 2 5.306125e+04 2.978953e+01\n", + " * time: 6.726307153701782\n", + " 3 -2.642320e+03 7.943136e+00\n", + " * time: 9.658858060836792\n", + " 4 -1.027484e+04 1.752693e+00\n", + " * time: 12.59636902809143\n", + " 5 -1.084925e+04 2.157295e-01\n", + " * time: 15.55314302444458\n", + " 6 -1.085880e+04 5.288877e-03\n", + " * time: 18.519394159317017\n", + " 7 -1.085881e+04 3.478176e-06\n", + " * time: 21.498953104019165\n", + " 8 -1.085881e+04 1.596091e-12\n", + " * time: 24.434569120407104\n", + " 9 -1.085881e+04 1.444882e-12\n", + " * time: 27.415829181671143\n", + " 10 -1.085881e+04 1.548831e-13\n", + " * time: 30.352124214172363\n", + " 11 -1.085881e+04 1.548831e-13\n", + " * time: 33.39971709251404\n", + " 12 -1.085881e+04 1.508030e-13\n", + " * time: 33.82969617843628\n", + " 41.169691 seconds (14.91 M allocations: 4.061 GiB, 1.42% gc time, 12.11% compilation time)\n" + ] + } + ], + "cell_type": "code", + "source": [ + "function startingconditions!(dofvector, dofhandler)\n", + " for cell in CellIterator(dofhandler)\n", + " globaldofs = celldofs(cell)\n", + " it = 1\n", + " for i in 1:3:length(globaldofs)\n", + " dofvector[globaldofs[i]] = -2.0\n", + " dofvector[globaldofs[i + 1]] = 2.0\n", + " dofvector[globaldofs[i + 2]] = -2.0tanh(cell.coords[it][1] / 20)\n", + " it += 1\n", + " end\n", + " end\n", + " return\n", + "end\n", + "\n", + "δ(i, j) = i == j ? one(i) : zero(i)\n", + "V2T(p11, p12, p44) = Tensor{4, 3}((i, j, k, l) -> p11 * δ(i, j) * δ(k, l) * δ(i, k) + p12 * δ(i, j) * δ(k, l) * (1 - δ(i, k)) + p44 * δ(i, k) * δ(j, l) * (1 - δ(i, j)))\n", + "\n", + "G = V2T(1.0e2, 0.0, 1.0e2)\n", + "α = Vec{3}((-1.0, 1.0, 1.0))\n", + "left = Vec{3}((-75.0, -25.0, -2.0))\n", + "right = Vec{3}((75.0, 25.0, 2.0))\n", + "model = LandauModel(α, G, (50, 50, 2), left, right, element_potential)\n", + "\n", + "save_landau(\"landauorig\", model)\n", + "@time minimize!(model)\n", + "save_landau(\"landaufinal\", model)" + ], + "metadata": {}, + "execution_count": 16 + }, + { + "cell_type": "markdown", + "source": [ + "as we can see this runs very quickly even for relatively large gridsizes.\n", + "The key to get high performance like this is to minimize the allocations inside the threaded loops,\n", + "ideally to 0." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "---\n", + "\n", + "*This notebook was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).*" + ], + "metadata": {} + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.1" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.1", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR798/gallery/landau.jl b/previews/PR798/gallery/landau.jl new file mode 100644 index 0000000000..7ffc65c2e9 --- /dev/null +++ b/previews/PR798/gallery/landau.jl @@ -0,0 +1,222 @@ +using ForwardDiff +import ForwardDiff: GradientConfig, HessianConfig, Chunk +using Ferrite +using Optim, LineSearches +using SparseArrays +using Tensors +using Base.Threads + +function Fl(P::Vec{3, T}, α::Vec{3}) where {T} + P2 = Vec{3, T}((P[1]^2, P[2]^2, P[3]^2)) + return α[1] * sum(P2) + + α[2] * (P[1]^4 + P[2]^4 + P[3]^4) + + α[3] * ((P2[1] * P2[2] + P2[2] * P2[3]) + P2[1] * P2[3]) +end + +@inline Fg(∇P, G) = 0.5(∇P ⊡ G) ⊡ ∇P + +F(P, ∇P, params) = Fl(P, params.α) + Fg(∇P, params.G) + +struct ModelParams{V, T} + α::V + G::T +end + +struct ThreadCache{CV, T, DIM, F <: Function, GC <: GradientConfig, HC <: HessianConfig} + cvP::CV + element_indices::Vector{Int} + element_dofs::Vector{T} + element_gradient::Vector{T} + element_hessian::Matrix{T} + element_coords::Vector{Vec{DIM, T}} + element_potential::F + gradconf::GC + hessconf::HC +end +function ThreadCache(dpc::Int, nodespercell, cvP::CellValues, modelparams, elpotential) + element_indices = zeros(Int, dpc) + element_dofs = zeros(dpc) + element_gradient = zeros(dpc) + element_hessian = zeros(dpc, dpc) + element_coords = zeros(Vec{3, Float64}, nodespercell) + potfunc = x -> elpotential(x, cvP, modelparams) + gradconf = GradientConfig(potfunc, zeros(dpc), Chunk{12}()) + hessconf = HessianConfig(potfunc, zeros(dpc), Chunk{4}()) + return ThreadCache(cvP, element_indices, element_dofs, element_gradient, element_hessian, element_coords, potfunc, gradconf, hessconf) +end + +mutable struct LandauModel{T, DH <: DofHandler, CH <: ConstraintHandler, TC <: ThreadCache} + dofs::Vector{T} + dofhandler::DH + boundaryconds::CH + threadindices::Vector{Vector{Int}} + threadcaches::Vector{TC} +end + +function LandauModel(α, G, gridsize, left::Vec{DIM, T}, right::Vec{DIM, T}, elpotential) where {DIM, T} + grid = generate_grid(Tetrahedron, gridsize, left, right) + threadindices = Ferrite.create_coloring(grid) + + qr = QuadratureRule{RefTetrahedron}(2) + ipP = Lagrange{RefTetrahedron, 1}()^3 + cvP = CellValues(qr, ipP) + + dofhandler = DofHandler(grid) + add!(dofhandler, :P, ipP) + close!(dofhandler) + + dofvector = zeros(ndofs(dofhandler)) + startingconditions!(dofvector, dofhandler) + boundaryconds = ConstraintHandler(dofhandler) + #boundary conditions can be added but aren't necessary for optimization + #add!(boundaryconds, Dirichlet(:P, getfacetset(grid, "left"), (x, t) -> [0.0,0.0,0.53], [1,2,3])) + #add!(boundaryconds, Dirichlet(:P, getfacetset(grid, "right"), (x, t) -> [0.0,0.0,-0.53], [1,2,3])) + close!(boundaryconds) + update!(boundaryconds, 0.0) + + apply!(dofvector, boundaryconds) + + hessian = allocate_matrix(dofhandler) + dpc = ndofs_per_cell(dofhandler) + cpc = length(grid.cells[1].nodes) + caches = [ThreadCache(dpc, cpc, copy(cvP), ModelParams(α, G), elpotential) for t in 1:nthreads()] + return LandauModel(dofvector, dofhandler, boundaryconds, threadindices, caches) +end + +function save_landau(path, model, dofs = model.dofs) + VTKGridFile(path, model.dofhandler) do vtk + write_solution(vtk, model.dofhandler, dofs) + end + return +end + +macro assemble!(innerbody) + return esc( + quote + dofhandler = model.dofhandler + for indices in model.threadindices + @threads for i in indices + cache = model.threadcaches[threadid()] + eldofs = cache.element_dofs + nodeids = dofhandler.grid.cells[i].nodes + for j in 1:length(cache.element_coords) + cache.element_coords[j] = dofhandler.grid.nodes[nodeids[j]].x + end + reinit!(cache.cvP, cache.element_coords) + + celldofs!(cache.element_indices, dofhandler, i) + for j in 1:length(cache.element_dofs) + eldofs[j] = dofvector[cache.element_indices[j]] + end + $innerbody + end + end + end + ) +end + +function F(dofvector::Vector{T}, model) where {T} + outs = fill(zero(T), nthreads()) + @assemble! begin + outs[threadid()] += cache.element_potential(eldofs) + end + return sum(outs) +end + +function ∇F!(∇f::Vector{T}, dofvector::Vector{T}, model::LandauModel{T}) where {T} + fill!(∇f, zero(T)) + @assemble! begin + ForwardDiff.gradient!(cache.element_gradient, cache.element_potential, eldofs, cache.gradconf) + @inbounds assemble!(∇f, cache.element_indices, cache.element_gradient) + end + return +end + +function ∇²F!(∇²f::SparseMatrixCSC, dofvector::Vector{T}, model::LandauModel{T}) where {T} + assemblers = [start_assemble(∇²f) for t in 1:nthreads()] + @assemble! begin + ForwardDiff.hessian!(cache.element_hessian, cache.element_potential, eldofs, cache.hessconf) + @inbounds assemble!(assemblers[threadid()], cache.element_indices, cache.element_hessian) + end + return +end + +function calcall(∇²f::SparseMatrixCSC, ∇f::Vector{T}, dofvector::Vector{T}, model::LandauModel{T}) where {T} + outs = fill(zero(T), nthreads()) + fill!(∇f, zero(T)) + assemblers = [start_assemble(∇²f, ∇f) for t in 1:nthreads()] + @assemble! begin + outs[threadid()] += cache.element_potential(eldofs) + ForwardDiff.hessian!(cache.element_hessian, cache.element_potential, eldofs, cache.hessconf) + ForwardDiff.gradient!(cache.element_gradient, cache.element_potential, eldofs, cache.gradconf) + @inbounds assemble!(assemblers[threadid()], cache.element_indices, cache.element_gradient, cache.element_hessian) + end + return sum(outs) +end + +function minimize!(model; kwargs...) + dh = model.dofhandler + dofs = model.dofs + ∇f = fill(0.0, length(dofs)) + ∇²f = allocate_matrix(dh) + function g!(storage, x) + ∇F!(storage, x, model) + return apply_zero!(storage, model.boundaryconds) + end + function h!(storage, x) + return ∇²F!(storage, x, model) + # apply!(storage, model.boundaryconds) + end + f(x) = F(x, model) + + od = TwiceDifferentiable(f, g!, h!, model.dofs, 0.0, ∇f, ∇²f) + + # this way of minimizing is only beneficial when the initial guess is completely off, + # then a quick couple of ConjuageGradient steps brings us easily closer to the minimum. + # res = optimize(od, model.dofs, ConjugateGradient(linesearch=BackTracking()), Optim.Options(show_trace=true, show_every=1, g_tol=1e-20, iterations=10)) + # model.dofs .= res.minimizer + # to get the final convergence, Newton's method is more ideal since the energy landscape should be almost parabolic + ##+ + res = optimize(od, model.dofs, Newton(linesearch = BackTracking()), Optim.Options(show_trace = true, show_every = 1, g_tol = 1.0e-20)) + model.dofs .= res.minimizer + return res +end + +function element_potential(eldofs::AbstractVector{T}, cvP, params) where {T} + energy = zero(T) + for qp in 1:getnquadpoints(cvP) + P = function_value(cvP, qp, eldofs) + ∇P = function_gradient(cvP, qp, eldofs) + energy += F(P, ∇P, params) * getdetJdV(cvP, qp) + end + return energy +end + +function startingconditions!(dofvector, dofhandler) + for cell in CellIterator(dofhandler) + globaldofs = celldofs(cell) + it = 1 + for i in 1:3:length(globaldofs) + dofvector[globaldofs[i]] = -2.0 + dofvector[globaldofs[i + 1]] = 2.0 + dofvector[globaldofs[i + 2]] = -2.0tanh(cell.coords[it][1] / 20) + it += 1 + end + end + return +end + +δ(i, j) = i == j ? one(i) : zero(i) +V2T(p11, p12, p44) = Tensor{4, 3}((i, j, k, l) -> p11 * δ(i, j) * δ(k, l) * δ(i, k) + p12 * δ(i, j) * δ(k, l) * (1 - δ(i, k)) + p44 * δ(i, k) * δ(j, l) * (1 - δ(i, j))) + +G = V2T(1.0e2, 0.0, 1.0e2) +α = Vec{3}((-1.0, 1.0, 1.0)) +left = Vec{3}((-75.0, -25.0, -2.0)) +right = Vec{3}((75.0, 25.0, 2.0)) +model = LandauModel(α, G, (50, 50, 2), left, right, element_potential) + +save_landau("landauorig", model) +@time minimize!(model) +save_landau("landaufinal", model) + +# This file was generated using Literate.jl, https://github.com/fredrikekre/Literate.jl diff --git a/previews/PR798/gallery/landau/index.html b/previews/PR798/gallery/landau/index.html new file mode 100644 index 0000000000..60f9a91a9f --- /dev/null +++ b/previews/PR798/gallery/landau/index.html @@ -0,0 +1,218 @@ + +Ginzburg-Landau model energy minimization · Ferrite.jl

Ginzburg-Landau model energy minimization

landau_orig.png

Original

landau_opt.png

Optimized

In this example a basic Ginzburg-Landau model is solved. This example gives an idea of how the API together with ForwardDiff can be leveraged to performantly solve non standard problems on a FEM grid. A large portion of the code is there only for performance reasons, but since this usually really matters and is what takes the most time to optimize, it is included.

The key to using a method like this for minimizing a free energy function directly, rather than the weak form, as is usually done with FEM, is to split up the gradient and Hessian calculations. This means that they are performed for each cell separately instead of for the grid as a whole.

using ForwardDiff
+import ForwardDiff: GradientConfig, HessianConfig, Chunk
+using Ferrite
+using Optim, LineSearches
+using SparseArrays
+using Tensors
+using Base.Threads

Energy terms

4th order Landau free energy

function Fl(P::Vec{3, T}, α::Vec{3}) where {T}
+    P2 = Vec{3, T}((P[1]^2, P[2]^2, P[3]^2))
+    return α[1] * sum(P2) +
+        α[2] * (P[1]^4 + P[2]^4 + P[3]^4) +
+        α[3] * ((P2[1] * P2[2] + P2[2] * P2[3]) + P2[1] * P2[3])
+end
Fl (generic function with 1 method)

Ginzburg free energy

@inline Fg(∇P, G) = 0.5(∇P ⊡ G) ⊡ ∇P
Fg (generic function with 1 method)

GL free energy

F(P, ∇P, params) = Fl(P, params.α) + Fg(∇P, params.G)
F (generic function with 1 method)

Parameters that characterize the model

struct ModelParams{V, T}
+    α::V
+    G::T
+end

ThreadCache

This holds the values that each thread will use during the assembly.

struct ThreadCache{CV, T, DIM, F <: Function, GC <: GradientConfig, HC <: HessianConfig}
+    cvP::CV
+    element_indices::Vector{Int}
+    element_dofs::Vector{T}
+    element_gradient::Vector{T}
+    element_hessian::Matrix{T}
+    element_coords::Vector{Vec{DIM, T}}
+    element_potential::F
+    gradconf::GC
+    hessconf::HC
+end
+function ThreadCache(dpc::Int, nodespercell, cvP::CellValues, modelparams, elpotential)
+    element_indices = zeros(Int, dpc)
+    element_dofs = zeros(dpc)
+    element_gradient = zeros(dpc)
+    element_hessian = zeros(dpc, dpc)
+    element_coords = zeros(Vec{3, Float64}, nodespercell)
+    potfunc = x -> elpotential(x, cvP, modelparams)
+    gradconf = GradientConfig(potfunc, zeros(dpc), Chunk{12}())
+    hessconf = HessianConfig(potfunc, zeros(dpc), Chunk{4}())
+    return ThreadCache(cvP, element_indices, element_dofs, element_gradient, element_hessian, element_coords, potfunc, gradconf, hessconf)
+end
Main.ThreadCache

The Model

everything is combined into a model.

mutable struct LandauModel{T, DH <: DofHandler, CH <: ConstraintHandler, TC <: ThreadCache}
+    dofs::Vector{T}
+    dofhandler::DH
+    boundaryconds::CH
+    threadindices::Vector{Vector{Int}}
+    threadcaches::Vector{TC}
+end
+
+function LandauModel(α, G, gridsize, left::Vec{DIM, T}, right::Vec{DIM, T}, elpotential) where {DIM, T}
+    grid = generate_grid(Tetrahedron, gridsize, left, right)
+    threadindices = Ferrite.create_coloring(grid)
+
+    qr = QuadratureRule{RefTetrahedron}(2)
+    ipP = Lagrange{RefTetrahedron, 1}()^3
+    cvP = CellValues(qr, ipP)
+
+    dofhandler = DofHandler(grid)
+    add!(dofhandler, :P, ipP)
+    close!(dofhandler)
+
+    dofvector = zeros(ndofs(dofhandler))
+    startingconditions!(dofvector, dofhandler)
+    boundaryconds = ConstraintHandler(dofhandler)
+    #boundary conditions can be added but aren't necessary for optimization
+    #add!(boundaryconds, Dirichlet(:P, getfacetset(grid, "left"), (x, t) -> [0.0,0.0,0.53], [1,2,3]))
+    #add!(boundaryconds, Dirichlet(:P, getfacetset(grid, "right"), (x, t) -> [0.0,0.0,-0.53], [1,2,3]))
+    close!(boundaryconds)
+    update!(boundaryconds, 0.0)
+
+    apply!(dofvector, boundaryconds)
+
+    hessian = allocate_matrix(dofhandler)
+    dpc = ndofs_per_cell(dofhandler)
+    cpc = length(grid.cells[1].nodes)
+    caches = [ThreadCache(dpc, cpc, copy(cvP), ModelParams(α, G), elpotential) for t in 1:nthreads()]
+    return LandauModel(dofvector, dofhandler, boundaryconds, threadindices, caches)
+end
Main.LandauModel

utility to quickly save a model

function save_landau(path, model, dofs = model.dofs)
+    VTKGridFile(path, model.dofhandler) do vtk
+        write_solution(vtk, model.dofhandler, dofs)
+    end
+    return
+end
save_landau (generic function with 2 methods)

Assembly

This macro defines most of the assembly step, since the structure is the same for the energy, gradient and Hessian calculations.

macro assemble!(innerbody)
+    return esc(
+        quote
+            dofhandler = model.dofhandler
+            for indices in model.threadindices
+                @threads for i in indices
+                    cache = model.threadcaches[threadid()]
+                    eldofs = cache.element_dofs
+                    nodeids = dofhandler.grid.cells[i].nodes
+                    for j in 1:length(cache.element_coords)
+                        cache.element_coords[j] = dofhandler.grid.nodes[nodeids[j]].x
+                    end
+                    reinit!(cache.cvP, cache.element_coords)
+
+                    celldofs!(cache.element_indices, dofhandler, i)
+                    for j in 1:length(cache.element_dofs)
+                        eldofs[j] = dofvector[cache.element_indices[j]]
+                    end
+                    $innerbody
+                end
+            end
+        end
+    )
+end
@assemble! (macro with 1 method)

This calculates the total energy calculation of the grid

function F(dofvector::Vector{T}, model) where {T}
+    outs = fill(zero(T), nthreads())
+    @assemble! begin
+        outs[threadid()] += cache.element_potential(eldofs)
+    end
+    return sum(outs)
+end
F (generic function with 2 methods)

The gradient calculation for each dof

function ∇F!(∇f::Vector{T}, dofvector::Vector{T}, model::LandauModel{T}) where {T}
+    fill!(∇f, zero(T))
+    @assemble! begin
+        ForwardDiff.gradient!(cache.element_gradient, cache.element_potential, eldofs, cache.gradconf)
+        @inbounds assemble!(∇f, cache.element_indices, cache.element_gradient)
+    end
+    return
+end
∇F! (generic function with 1 method)

The Hessian calculation for the whole grid

function ∇²F!(∇²f::SparseMatrixCSC, dofvector::Vector{T}, model::LandauModel{T}) where {T}
+    assemblers = [start_assemble(∇²f) for t in 1:nthreads()]
+    @assemble! begin
+        ForwardDiff.hessian!(cache.element_hessian, cache.element_potential, eldofs, cache.hessconf)
+        @inbounds assemble!(assemblers[threadid()], cache.element_indices, cache.element_hessian)
+    end
+    return
+end
∇²F! (generic function with 1 method)

We can also calculate all things in one go!

function calcall(∇²f::SparseMatrixCSC, ∇f::Vector{T}, dofvector::Vector{T}, model::LandauModel{T}) where {T}
+    outs = fill(zero(T), nthreads())
+    fill!(∇f, zero(T))
+    assemblers = [start_assemble(∇²f, ∇f) for t in 1:nthreads()]
+    @assemble! begin
+        outs[threadid()] += cache.element_potential(eldofs)
+        ForwardDiff.hessian!(cache.element_hessian, cache.element_potential, eldofs, cache.hessconf)
+        ForwardDiff.gradient!(cache.element_gradient, cache.element_potential, eldofs, cache.gradconf)
+        @inbounds assemble!(assemblers[threadid()], cache.element_indices, cache.element_gradient, cache.element_hessian)
+    end
+    return sum(outs)
+end
calcall (generic function with 1 method)

Minimization

Now everything can be combined to minimize the energy, and find the equilibrium configuration.

function minimize!(model; kwargs...)
+    dh = model.dofhandler
+    dofs = model.dofs
+    ∇f = fill(0.0, length(dofs))
+    ∇²f = allocate_matrix(dh)
+    function g!(storage, x)
+        ∇F!(storage, x, model)
+        return apply_zero!(storage, model.boundaryconds)
+    end
+    function h!(storage, x)
+        return ∇²F!(storage, x, model)
+        # apply!(storage, model.boundaryconds)
+    end
+    f(x) = F(x, model)
+
+    od = TwiceDifferentiable(f, g!, h!, model.dofs, 0.0, ∇f, ∇²f)
+
+    # this way of minimizing is only beneficial when the initial guess is completely off,
+    # then a quick couple of ConjuageGradient steps brings us easily closer to the minimum.
+    # res = optimize(od, model.dofs, ConjugateGradient(linesearch=BackTracking()), Optim.Options(show_trace=true, show_every=1, g_tol=1e-20, iterations=10))
+    # model.dofs .= res.minimizer
+    # to get the final convergence, Newton's method is more ideal since the energy landscape should be almost parabolic
+    ##+
+    res = optimize(od, model.dofs, Newton(linesearch = BackTracking()), Optim.Options(show_trace = true, show_every = 1, g_tol = 1.0e-20))
+    model.dofs .= res.minimizer
+    return res
+end
minimize! (generic function with 1 method)

Testing it

This calculates the contribution of each element to the total energy, it is also the function that will be put through ForwardDiff for the gradient and Hessian.

function element_potential(eldofs::AbstractVector{T}, cvP, params) where {T}
+    energy = zero(T)
+    for qp in 1:getnquadpoints(cvP)
+        P = function_value(cvP, qp, eldofs)
+        ∇P = function_gradient(cvP, qp, eldofs)
+        energy += F(P, ∇P, params) * getdetJdV(cvP, qp)
+    end
+    return energy
+end
element_potential (generic function with 1 method)

now we define some starting conditions

function startingconditions!(dofvector, dofhandler)
+    for cell in CellIterator(dofhandler)
+        globaldofs = celldofs(cell)
+        it = 1
+        for i in 1:3:length(globaldofs)
+            dofvector[globaldofs[i]] = -2.0
+            dofvector[globaldofs[i + 1]] = 2.0
+            dofvector[globaldofs[i + 2]] = -2.0tanh(cell.coords[it][1] / 20)
+            it += 1
+        end
+    end
+    return
+end
+
+δ(i, j) = i == j ? one(i) : zero(i)
+V2T(p11, p12, p44) = Tensor{4, 3}((i, j, k, l) -> p11 * δ(i, j) * δ(k, l) * δ(i, k) + p12 * δ(i, j) * δ(k, l) * (1 - δ(i, k)) + p44 * δ(i, k) * δ(j, l) * (1 - δ(i, j)))
+
+G = V2T(1.0e2, 0.0, 1.0e2)
+α = Vec{3}((-1.0, 1.0, 1.0))
+left = Vec{3}((-75.0, -25.0, -2.0))
+right = Vec{3}((75.0, 25.0, 2.0))
+model = LandauModel(α, G, (50, 50, 2), left, right, element_potential)
+
+save_landau("landauorig", model)
+@time minimize!(model)
+save_landau("landaufinal", model)
Iter     Function value   Gradient norm
+     0     2.127588e+06     3.597094e+02
+ * time: 8.416175842285156e-5
+     1     3.786155e+05     1.047687e+02
+ * time: 3.3889400959014893
+     2     5.306125e+04     2.978953e+01
+ * time: 6.372174024581909
+     3    -2.642320e+03     7.943136e+00
+ * time: 9.31834602355957
+     4    -1.027484e+04     1.752693e+00
+ * time: 12.265552997589111
+     5    -1.084925e+04     2.157295e-01
+ * time: 15.208797216415405
+     6    -1.085880e+04     5.288877e-03
+ * time: 18.16419005393982
+     7    -1.085881e+04     3.478176e-06
+ * time: 21.129767179489136
+     8    -1.085881e+04     1.596091e-12
+ * time: 24.07312512397766
+     9    -1.085881e+04     1.444882e-12
+ * time: 27.05501914024353
+    10    -1.085881e+04     1.548831e-13
+ * time: 30.043122053146362
+    11    -1.085881e+04     1.548831e-13
+ * time: 33.16480302810669
+    12    -1.085881e+04     1.508030e-13
+ * time: 33.60310912132263
+ 39.673375 seconds (11.20 M allocations: 3.879 GiB, 1.45% gc time, 8.07% compilation time)

as we can see this runs very quickly even for relatively large gridsizes. The key to get high performance like this is to minimize the allocations inside the threaded loops, ideally to 0.


This page was generated using Literate.jl.

diff --git a/previews/PR798/gallery/landau_opt.png b/previews/PR798/gallery/landau_opt.png new file mode 100644 index 0000000000..66c329f7a2 Binary files /dev/null and b/previews/PR798/gallery/landau_opt.png differ diff --git a/previews/PR798/gallery/landau_orig.png b/previews/PR798/gallery/landau_orig.png new file mode 100644 index 0000000000..2075e884c7 Binary files /dev/null and b/previews/PR798/gallery/landau_orig.png differ diff --git a/previews/PR798/gallery/landaufinal.vtu b/previews/PR798/gallery/landaufinal.vtu new file mode 100644 index 0000000000..48c3584f80 Binary files /dev/null and b/previews/PR798/gallery/landaufinal.vtu differ diff --git a/previews/PR798/gallery/landauorig.vtu b/previews/PR798/gallery/landauorig.vtu new file mode 100644 index 0000000000..3aad274c5c Binary files /dev/null and b/previews/PR798/gallery/landauorig.vtu differ diff --git a/previews/PR798/gallery/large_radius.vtu b/previews/PR798/gallery/large_radius.vtu new file mode 100644 index 0000000000..9879906a35 Binary files /dev/null and b/previews/PR798/gallery/large_radius.vtu differ diff --git a/previews/PR798/gallery/quasi_incompressible_hyperelasticity.gif b/previews/PR798/gallery/quasi_incompressible_hyperelasticity.gif new file mode 100644 index 0000000000..15f3aa97e9 Binary files /dev/null and b/previews/PR798/gallery/quasi_incompressible_hyperelasticity.gif differ diff --git a/previews/PR798/gallery/quasi_incompressible_hyperelasticity.ipynb b/previews/PR798/gallery/quasi_incompressible_hyperelasticity.ipynb new file mode 100644 index 0000000000..8719abefbe --- /dev/null +++ b/previews/PR798/gallery/quasi_incompressible_hyperelasticity.ipynb @@ -0,0 +1,622 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Nearly Incompressible Hyperelasticity\n", + "\n", + "![](quasi_incompressible_hyperelasticity.gif)" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Introduction\n", + "\n", + "In this example we study quasi- or nearly-incompressible hyperelasticity using the stable Taylor-Hood approximation. In spirit, this example is the nonlinear analogue of\n", + "[`incompressible_elasticity`](https://nbviewer.jupyter.org/github/Ferrite-FEM/Ferrite.jl/blob/gh-pages/previews/PR798/tutorials/incompressible_elasticity.ipynb) and the incompressible analogue of\n", + "[`hyperelasticity`](https://nbviewer.jupyter.org/github/Ferrite-FEM/Ferrite.jl/blob/gh-pages/previews/PR798/tutorials/hyperelasticity.ipynb). Much of the code therefore follows from the above two examples.\n", + "The problem is formulated in the undeformed or reference configuration with the displacement $\\mathbf{u}$ and pressure $p$ being the unknown fields. We now briefly outline\n", + "the formulation. Consider the standard hyperelasticity problem\n", + "\n", + "$$\n", + " \\mathbf{u} = \\argmin_{\\mathbf{v}\\in\\mathcal{K}(\\Omega)}\\Pi(\\mathbf{v}),\\quad \\text{where}\\quad \\Pi(\\mathbf{v}) = \\int_\\Omega \\Psi(\\mathbf{v}) \\ \\mathrm{d}\\Omega\\ .\n", + "$$\n", + "\n", + "where $\\mathcal{K}(\\Omega)$ is a suitable function space.\n", + "\n", + "For clarity of presentation we ignore any non-zero surface tractions and body forces and instead consider only\n", + "applied displacements (i.e. non-homogeneous dirichlet boundary conditions). Moreover we stick our attention to the standard Neo-Hookean stored energy density\n", + "\n", + "$$\n", + " \\Psi(\\mathbf{u}) = \\frac{\\mu}{2}\\left(I_1 - 3 \\right) - \\mu \\log(J) + \\frac{\\lambda}{2}\\left( J - 1\\right){}^2,\n", + "$$\n", + "where $I_1 = \\mathrm{tr}(\\mathbf{C}) = \\mathrm{tr}(\\mathbf{F}^\\mathrm{T} \\mathbf{F}) = F_{ij}F_{ij}$ and $J = \\det(\\mathbf{F})$ denote the standard invariants of the deformation gradient tensor $\\mathbf{F} = \\mathbf{I}+\\nabla_{\\mathbf{X}} \\mathbf{u}$.\n", + "The above problem is ill-posed in the limit of incompressibility (or near-incompressibility), namely when\n", + "$$\n", + " \\lambda/\\mu \\rightarrow +\\infty.\n", + "$$\n", + "In order to alleviate the problem, we consider the partial legendre transform of the strain energy density $\\Psi$ with respect to $J = \\det(\\mathbf{F})$, namely\n", + "$$\n", + " \\widehat{\\Psi}(\\mathbf{u}, p) = \\sup_{J} \\left[ p(J - 1) - \\frac{\\mu}{2}\\left(I_1 - 3 \\right) + \\mu \\log(J) - \\frac{\\lambda}{2}\\left( J - 1\\right){}^2 \\right].\n", + "$$\n", + "The supremum, say $J^\\star$, can be calculated in closed form by the first order optimailty condition $\\partial\\widehat{\\Psi}/\\partial J = 0$. This gives\n", + "$$\n", + " J^\\star(p) = \\frac{\\lambda + p + \\sqrt{(\\lambda + p){}^2 + 4 \\lambda \\mu }}{(2 \\lambda)}.\n", + "$$\n", + "Furthermore, taking the partial legendre transform of $\\widehat{\\Psi}$ once again, gives us back the original problem, i.e.\n", + "$$\n", + " \\Psi(\\mathbf{u}) = \\Psi^\\star(\\mathbf{u}, p) = \\sup_{p} \\left[ p(J - 1) - p(J^\\star - 1) + \\frac{\\mu}{2}\\left(I_1 - 3 \\right) - \\mu \\log(J^\\star) + \\frac{\\lambda}{2}\\left( J^\\star - 1\\right){}^2 \\right].\n", + "$$\n", + "Therefore our original hyperelasticity problem can now be reformulated as\n", + "$$\n", + " \\inf_{\\mathbf{u}\\in\\mathcal{K}(\\Omega)}\\sup_{p} \\int_\\Omega\\Psi^{\\star} (\\mathbf{u}, p) \\, \\mathrm{d}\\Omega.\n", + "$$\n", + "The total (modified) energy $\\Pi^\\star$ can then be written as\n", + "$$\n", + " \\Pi^\\star(\\mathbf{u}, p) = \\int_\\Omega p (J - J^\\star) \\ \\mathrm{d}\\Omega + \\int_\\Omega \\frac{\\mu}{2} \\left( I_1 - 3\\right) \\ \\mathrm{d}\\Omega - \\int_\\Omega \\mu\\log(J^\\star)\\ \\mathrm{d}\\Omega + \\int_\\Omega \\frac{\\lambda}{2}\\left( J^\\star - 1 \\right){}^2\\ \\mathrm{d}\\Omega\n", + "$$\n", + "The Euler-Lagrange equations corresponding to the above energy give us our governing PDEs in the weak form, namely\n", + "$$\n", + " \\int_\\Omega \\frac{\\partial\\Psi^\\star}{\\partial \\mathbf{F}}:\\delta \\mathbf{F} \\ \\mathrm{d}\\Omega = 0\n", + "$$\n", + "and\n", + "$$\n", + " \\int_\\Omega \\frac{\\partial \\Psi^\\star}{\\partial p}\\delta p \\ \\mathrm{d}\\Omega = 0,\n", + "$$\n", + "where $\\delta \\mathrm{F} = \\delta \\mathrm{grad}_0(\\mathbf{u}) = \\mathrm{grad}_0(\\delta \\mathbf{u})$ and $\\delta \\mathbf{u}$ and $\\delta p$ denote arbitrary variations with respect to displacement and pressure (or the test functions). See the references\n", + "below for a more detailed explanation of the above mathematical trick. Now, in order to apply Newton's method to the\n", + "above problem, we further need to linearize the above equations and calculate the respective hessians (or tangents), namely, $\\partial^2\\Psi^\\star/\\partial \\mathbf{F}^2$, $\\partial^2\\Psi^\\star/\\partial p^2$ and $\\partial^2\\Psi^\\star/\\partial \\mathbf{F}\\partial p$\n", + "which, using `Tensors.jl`, can be determined conveniently using automatic differentiation (see the code below). Hence we only need to define the above potential.\n", + "The remaineder of the example follows similarly.\n", + "## References\n", + "1. [A paradigm for higher-order polygonal elements in finite elasticity using a gradient correction scheme, CMAME 2016, 306, 216–251](http://pamies.cee.illinois.edu/Publications_files/CMAME_2016.pdf)\n", + "2. [Approximation of incompressible large deformation elastic problems: some unresolved issues, Computational Mechanics, 2013](https://link.springer.com/content/pdf/10.1007/s00466-013-0869-0.pdf)" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Implementation\n", + "We now get to the actual code. First, we import the respective packages" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using Ferrite, Tensors, ProgressMeter, WriteVTK\n", + "using BlockArrays, SparseArrays, LinearAlgebra" + ], + "metadata": {}, + "execution_count": 1 + }, + { + "cell_type": "markdown", + "source": [ + "and the corresponding `struct` to store our material properties." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "struct NeoHooke\n", + " μ::Float64\n", + " λ::Float64\n", + "end" + ], + "metadata": {}, + "execution_count": 2 + }, + { + "cell_type": "markdown", + "source": [ + "We then create a function to generate a simple test mesh on which to compute FE solution. We also mark the boundaries\n", + "to later assign Dirichlet boundary conditions" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function importTestGrid()\n", + " grid = generate_grid(Tetrahedron, (5, 5, 5), zero(Vec{3}), ones(Vec{3}))\n", + " addfacetset!(grid, \"myBottom\", x -> norm(x[2]) ≈ 0.0)\n", + " addfacetset!(grid, \"myBack\", x -> norm(x[3]) ≈ 0.0)\n", + " addfacetset!(grid, \"myRight\", x -> norm(x[1]) ≈ 1.0)\n", + " addfacetset!(grid, \"myLeft\", x -> norm(x[1]) ≈ 0.0)\n", + " return grid\n", + "end;" + ], + "metadata": {}, + "execution_count": 3 + }, + { + "cell_type": "markdown", + "source": [ + "The function to create corresponding cellvalues for the displacement field `u` and pressure `p`\n", + "follows in a similar fashion from the `incompressible_elasticity` example" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function create_values(interpolation_u, interpolation_p)\n", + " # quadrature rules\n", + " qr = QuadratureRule{RefTetrahedron}(4)\n", + " facet_qr = FacetQuadratureRule{RefTetrahedron}(4)\n", + "\n", + " # cell and facetvalues for u\n", + " cellvalues_u = CellValues(qr, interpolation_u)\n", + " facetvalues_u = FacetValues(facet_qr, interpolation_u)\n", + "\n", + " # cellvalues for p\n", + " cellvalues_p = CellValues(qr, interpolation_p)\n", + "\n", + " return cellvalues_u, cellvalues_p, facetvalues_u\n", + "end;" + ], + "metadata": {}, + "execution_count": 4 + }, + { + "cell_type": "markdown", + "source": [ + "We now create the function for Ψ*" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function Ψ(F, p, mp::NeoHooke)\n", + " μ = mp.μ\n", + " λ = mp.λ\n", + " Ic = tr(tdot(F))\n", + " J = det(F)\n", + " Js = (λ + p + sqrt((λ + p)^2.0 + 4.0 * λ * μ)) / (2.0 * λ)\n", + " return p * (Js - J) + μ / 2 * (Ic - 3) - μ * log(Js) + λ / 2 * (Js - 1)^2\n", + "end;" + ], + "metadata": {}, + "execution_count": 5 + }, + { + "cell_type": "markdown", + "source": [ + "and it's derivatives (required in computing the jacobian and hessian respectively)" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function constitutive_driver(F, p, mp::NeoHooke)\n", + " # Compute all derivatives in one function call\n", + " ∂²Ψ∂F², ∂Ψ∂F = Tensors.hessian(y -> Ψ(y, p, mp), F, :all)\n", + " ∂²Ψ∂p², ∂Ψ∂p = Tensors.hessian(y -> Ψ(F, y, mp), p, :all)\n", + " ∂²Ψ∂F∂p = Tensors.gradient(q -> Tensors.gradient(y -> Ψ(y, q, mp), F), p)\n", + " return ∂Ψ∂F, ∂²Ψ∂F², ∂Ψ∂p, ∂²Ψ∂p², ∂²Ψ∂F∂p\n", + "end;" + ], + "metadata": {}, + "execution_count": 6 + }, + { + "cell_type": "markdown", + "source": [ + "The functions to create the `DofHandler` and `ConstraintHandler` (to assign corresponding boundary conditions) follow\n", + "likewise from the incompressible elasticity example, namely" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function create_dofhandler(grid, ipu, ipp)\n", + " dh = DofHandler(grid)\n", + " add!(dh, :u, ipu) # displacement dim = 3\n", + " add!(dh, :p, ipp) # pressure dim = 1\n", + " close!(dh)\n", + " return dh\n", + "end;" + ], + "metadata": {}, + "execution_count": 7 + }, + { + "cell_type": "markdown", + "source": [ + "We are simulating a uniaxial tensile loading of a unit cube. Hence we apply a displacement field (`:u`) in `x` direction on the right face.\n", + "The left, bottom and back facets are fixed in the `x`, `y` and `z` components of the displacement so as to emulate the uniaxial nature\n", + "of the loading." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function create_bc(dh)\n", + " dbc = ConstraintHandler(dh)\n", + " add!(dbc, Dirichlet(:u, getfacetset(dh.grid, \"myLeft\"), (x, t) -> zero(Vec{1}), [1]))\n", + " add!(dbc, Dirichlet(:u, getfacetset(dh.grid, \"myBottom\"), (x, t) -> zero(Vec{1}), [2]))\n", + " add!(dbc, Dirichlet(:u, getfacetset(dh.grid, \"myBack\"), (x, t) -> zero(Vec{1}), [3]))\n", + " add!(dbc, Dirichlet(:u, getfacetset(dh.grid, \"myRight\"), (x, t) -> t * ones(Vec{1}), [1]))\n", + " close!(dbc)\n", + " Ferrite.update!(dbc, 0.0)\n", + " return dbc\n", + "end;" + ], + "metadata": {}, + "execution_count": 8 + }, + { + "cell_type": "markdown", + "source": [ + "Also, since we are considering incompressible hyperelasticity, an interesting quantity that we can compute is the deformed volume of the solid.\n", + "It is easy to show that this is equal to ∫J*dΩ where J=det(F). This can be done at the level of each element (cell)" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function calculate_element_volume(cell, cellvalues_u, ue)\n", + " reinit!(cellvalues_u, cell)\n", + " evol::Float64 = 0.0\n", + " for qp in 1:getnquadpoints(cellvalues_u)\n", + " dΩ = getdetJdV(cellvalues_u, qp)\n", + " ∇u = function_gradient(cellvalues_u, qp, ue)\n", + " F = one(∇u) + ∇u\n", + " J = det(F)\n", + " evol += J * dΩ\n", + " end\n", + " return evol\n", + "end;" + ], + "metadata": {}, + "execution_count": 9 + }, + { + "cell_type": "markdown", + "source": [ + "and then assembled over all the cells (elements)" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function calculate_volume_deformed_mesh(w, dh::DofHandler, cellvalues_u)\n", + " evol::Float64 = 0.0\n", + " for cell in CellIterator(dh)\n", + " global_dofs = celldofs(cell)\n", + " nu = getnbasefunctions(cellvalues_u)\n", + " global_dofs_u = global_dofs[1:nu]\n", + " ue = w[global_dofs_u]\n", + " δevol = calculate_element_volume(cell, cellvalues_u, ue)\n", + " evol += δevol\n", + " end\n", + " return evol\n", + "end;" + ], + "metadata": {}, + "execution_count": 10 + }, + { + "cell_type": "markdown", + "source": [ + "The function to assemble the element stiffness matrix for each element in the mesh now has a block structure like in\n", + "`incompressible_elasticity`." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function assemble_element!(Ke, fe, cell, cellvalues_u, cellvalues_p, mp, ue, pe)\n", + " # Reinitialize cell values, and reset output arrays\n", + " ublock, pblock = 1, 2\n", + " reinit!(cellvalues_u, cell)\n", + " reinit!(cellvalues_p, cell)\n", + " fill!(Ke, 0.0)\n", + " fill!(fe, 0.0)\n", + "\n", + " n_basefuncs_u = getnbasefunctions(cellvalues_u)\n", + " n_basefuncs_p = getnbasefunctions(cellvalues_p)\n", + "\n", + " for qp in 1:getnquadpoints(cellvalues_u)\n", + " dΩ = getdetJdV(cellvalues_u, qp)\n", + " # Compute deformation gradient F\n", + " ∇u = function_gradient(cellvalues_u, qp, ue)\n", + " p = function_value(cellvalues_p, qp, pe)\n", + " F = one(∇u) + ∇u\n", + "\n", + " # Compute first Piola-Kirchhoff stress and tangent modulus\n", + " ∂Ψ∂F, ∂²Ψ∂F², ∂Ψ∂p, ∂²Ψ∂p², ∂²Ψ∂F∂p = constitutive_driver(F, p, mp)\n", + "\n", + " # Loop over the `u`-test functions to calculate the `u`-`u` and `u`-`p` blocks\n", + " for i in 1:n_basefuncs_u\n", + " # gradient of the test function\n", + " ∇δui = shape_gradient(cellvalues_u, qp, i)\n", + " # Add contribution to the residual from this test function\n", + " fe[BlockIndex((ublock), (i))] += (∇δui ⊡ ∂Ψ∂F) * dΩ\n", + "\n", + " ∇δui∂S∂F = ∇δui ⊡ ∂²Ψ∂F²\n", + " for j in 1:n_basefuncs_u\n", + " ∇δuj = shape_gradient(cellvalues_u, qp, j)\n", + "\n", + " # Add contribution to the tangent\n", + " Ke[BlockIndex((ublock, ublock), (i, j))] += (∇δui∂S∂F ⊡ ∇δuj) * dΩ\n", + " end\n", + " # Loop over the `p`-test functions\n", + " for j in 1:n_basefuncs_p\n", + " δp = shape_value(cellvalues_p, qp, j)\n", + " # Add contribution to the tangent\n", + " Ke[BlockIndex((ublock, pblock), (i, j))] += (∂²Ψ∂F∂p ⊡ ∇δui) * δp * dΩ\n", + " end\n", + " end\n", + " # Loop over the `p`-test functions to calculate the `p-`u` and `p`-`p` blocks\n", + " for i in 1:n_basefuncs_p\n", + " δp = shape_value(cellvalues_p, qp, i)\n", + " fe[BlockIndex((pblock), (i))] += (δp * ∂Ψ∂p) * dΩ\n", + "\n", + " for j in 1:n_basefuncs_u\n", + " ∇δuj = shape_gradient(cellvalues_u, qp, j)\n", + " Ke[BlockIndex((pblock, ublock), (i, j))] += ∇δuj ⊡ ∂²Ψ∂F∂p * δp * dΩ\n", + " end\n", + " for j in 1:n_basefuncs_p\n", + " δp = shape_value(cellvalues_p, qp, j)\n", + " Ke[BlockIndex((pblock, pblock), (i, j))] += δp * ∂²Ψ∂p² * δp * dΩ\n", + " end\n", + " end\n", + " end\n", + " return\n", + "end;" + ], + "metadata": {}, + "execution_count": 11 + }, + { + "cell_type": "markdown", + "source": [ + "The only thing that changes in the assembly of the global stiffness matrix is slicing the corresponding element\n", + "dofs for the displacement (see `global_dofsu`) and pressure (`global_dofsp`)." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function assemble_global!(\n", + " K::SparseMatrixCSC, f, cellvalues_u::CellValues,\n", + " cellvalues_p::CellValues, dh::DofHandler, mp::NeoHooke, w\n", + " )\n", + " nu = getnbasefunctions(cellvalues_u)\n", + " np = getnbasefunctions(cellvalues_p)\n", + "\n", + " # start_assemble resets K and f\n", + " fe = BlockedArray(zeros(nu + np), [nu, np]) # local force vector\n", + " ke = BlockedArray(zeros(nu + np, nu + np), [nu, np], [nu, np]) # local stiffness matrix\n", + "\n", + " assembler = start_assemble(K, f)\n", + " # Loop over all cells in the grid\n", + " for cell in CellIterator(dh)\n", + " global_dofs = celldofs(cell)\n", + " global_dofsu = global_dofs[1:nu] # first nu dofs are displacement\n", + " global_dofsp = global_dofs[(nu + 1):end] # last np dofs are pressure\n", + " @assert size(global_dofs, 1) == nu + np # sanity check\n", + " ue = w[global_dofsu] # displacement dofs for the current cell\n", + " pe = w[global_dofsp] # pressure dofs for the current cell\n", + " assemble_element!(ke, fe, cell, cellvalues_u, cellvalues_p, mp, ue, pe)\n", + " assemble!(assembler, global_dofs, ke, fe)\n", + " end\n", + " return\n", + "end;" + ], + "metadata": {}, + "execution_count": 12 + }, + { + "cell_type": "markdown", + "source": [ + "We now define a main function `solve`. For nonlinear quasistatic problems we often like to parameterize the\n", + "solution in terms of a pseudo time like parameter, which in this case is used to gradually apply the boundary\n", + "displacement on the right face. Also for definitenessm we consider λ/μ = 10⁴" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function solve(interpolation_u, interpolation_p)\n", + "\n", + " # import the mesh\n", + " grid = importTestGrid()\n", + "\n", + " # Material parameters\n", + " μ = 1.0\n", + " λ = 1.0e4 * μ\n", + " mp = NeoHooke(μ, λ)\n", + "\n", + " # Create the DofHandler and CellValues\n", + " dh = create_dofhandler(grid, interpolation_u, interpolation_p)\n", + " cellvalues_u, cellvalues_p, facetvalues_u = create_values(interpolation_u, interpolation_p)\n", + "\n", + " # Create the DirichletBCs\n", + " dbc = create_bc(dh)\n", + "\n", + " # Pre-allocation of vectors for the solution and Newton increments\n", + " _ndofs = ndofs(dh)\n", + " w = zeros(_ndofs)\n", + " ΔΔw = zeros(_ndofs)\n", + " apply!(w, dbc)\n", + "\n", + " # Create the sparse matrix and residual vector\n", + " K = allocate_matrix(dh)\n", + " f = zeros(_ndofs)\n", + "\n", + " # We run the simulation parameterized by a time like parameter. `Tf` denotes the final value\n", + " # of this parameter, and Δt denotes its increment in each step\n", + " Tf = 2.0\n", + " Δt = 0.1\n", + " NEWTON_TOL = 1.0e-8\n", + "\n", + " pvd = paraview_collection(\"hyperelasticity_incomp_mixed\")\n", + " for (step, t) in enumerate(0.0:Δt:Tf)\n", + " # Perform Newton iterations\n", + " Ferrite.update!(dbc, t)\n", + " apply!(w, dbc)\n", + " newton_itr = -1\n", + " prog = ProgressMeter.ProgressThresh(NEWTON_TOL; desc = \"Solving @ time $t of $Tf;\")\n", + " fill!(ΔΔw, 0.0)\n", + " while true\n", + " newton_itr += 1\n", + " assemble_global!(K, f, cellvalues_u, cellvalues_p, dh, mp, w)\n", + " norm_res = norm(f[Ferrite.free_dofs(dbc)])\n", + " apply_zero!(K, f, dbc)\n", + " # Only display output at specific load steps\n", + " if t % (5 * Δt) == 0\n", + " ProgressMeter.update!(prog, norm_res; showvalues = [(:iter, newton_itr)])\n", + " end\n", + " if norm_res < NEWTON_TOL\n", + " break\n", + " elseif newton_itr > 30\n", + " error(\"Reached maximum Newton iterations, aborting\")\n", + " end\n", + " # Compute the incremental `dof`-vector (both displacement and pressure)\n", + " ΔΔw .= K \\ f\n", + "\n", + " apply_zero!(ΔΔw, dbc)\n", + " w .-= ΔΔw\n", + " end\n", + "\n", + " # Save the solution fields\n", + " VTKGridFile(\"hyperelasticity_incomp_mixed_$step\", grid) do vtk\n", + " write_solution(vtk, dh, w)\n", + " pvd[t] = vtk\n", + " end\n", + " end\n", + " vtk_save(pvd)\n", + " vol_def = calculate_volume_deformed_mesh(w, dh, cellvalues_u)\n", + " print(\"Deformed volume is $vol_def\")\n", + " return vol_def\n", + "end;" + ], + "metadata": {}, + "execution_count": 13 + }, + { + "cell_type": "markdown", + "source": [ + "We can now test the solution using the Taylor-Hood approximation" + ], + "metadata": {} + }, + { + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\rSolving @ time 0.0 of 2.0; (thresh = 1e-08, value = 4.78011e-05)\u001b[K\r\n", + " iter: 1\u001b[K\r\u001b[A\n", + "\r\u001b[K\u001b[A\rSolving @ time 0.0 of 2.0; Time: 0:00:00 (3 iterations)\u001b[K\r\n", + " iter: 2\u001b[K\n", + "\rSolving @ time 0.5 of 2.0; (thresh = 1e-08, value = 0.00164904)\u001b[K\r\n", + " iter: 1\u001b[K\r\u001b[A\n", + "\r\u001b[K\u001b[A\rSolving @ time 0.5 of 2.0; (thresh = 1e-08, value = 5.74317e-05)\u001b[K\r\n", + " iter: 2\u001b[K\r\u001b[A\n", + "\r\u001b[K\u001b[A\rSolving @ time 0.5 of 2.0; (thresh = 1e-08, value = 1.07293e-08)\u001b[K\r\n", + " iter: 3\u001b[K\r\u001b[A\n", + "\r\u001b[K\u001b[A\rSolving @ time 0.5 of 2.0; Time: 0:00:00 (5 iterations)\u001b[K\r\n", + " iter: 4\u001b[K\n", + "\rSolving @ time 1.0 of 2.0; (thresh = 1e-08, value = 0.000819569)\u001b[K\r\n", + " iter: 1\u001b[K\r\u001b[A\n", + "\r\u001b[K\u001b[A\rSolving @ time 1.0 of 2.0; (thresh = 1e-08, value = 2.60479e-05)\u001b[K\r\n", + " iter: 2\u001b[K\r\u001b[A\n", + "\r\u001b[K\u001b[A\rSolving @ time 1.0 of 2.0; Time: 0:00:00 (4 iterations)\u001b[K\r\n", + " iter: 3\u001b[K\n", + "\rSolving @ time 1.5 of 2.0; (thresh = 1e-08, value = 0.000500654)\u001b[K\r\n", + " iter: 1\u001b[K\r\u001b[A\n", + "\r\u001b[K\u001b[A\rSolving @ time 1.5 of 2.0; (thresh = 1e-08, value = 1.30232e-05)\u001b[K\r\n", + " iter: 2\u001b[K\r\u001b[A\n", + "\r\u001b[K\u001b[A\rSolving @ time 1.5 of 2.0; Time: 0:00:00 (4 iterations)\u001b[K\r\n", + " iter: 3\u001b[K\n", + "\rSolving @ time 2.0 of 2.0; (thresh = 1e-08, value = 0.000339016)\u001b[K\r\n", + " iter: 1\u001b[K\r\u001b[A\n", + "\r\u001b[K\u001b[A\rSolving @ time 2.0 of 2.0; (thresh = 1e-08, value = 6.94365e-06)\u001b[K\r\n", + " iter: 2\u001b[K\r\u001b[A\n", + "\r\u001b[K\u001b[A\rSolving @ time 2.0 of 2.0; Time: 0:00:00 (4 iterations)\u001b[K\r\n", + " iter: 3\u001b[K\n", + "Deformed volume is 1.000199977169989" + ] + }, + { + "output_type": "execute_result", + "data": { + "text/plain": "1.000199977169989" + }, + "metadata": {}, + "execution_count": 14 + } + ], + "cell_type": "code", + "source": [ + "quadratic_u = Lagrange{RefTetrahedron, 2}()^3\n", + "linear_p = Lagrange{RefTetrahedron, 1}()\n", + "vol_def = solve(quadratic_u, linear_p)" + ], + "metadata": {}, + "execution_count": 14 + }, + { + "cell_type": "markdown", + "source": [ + "The deformed volume is indeed close to 1 (as should be for a nearly incompressible material)." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "---\n", + "\n", + "*This notebook was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).*" + ], + "metadata": {} + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.1" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.1", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR798/gallery/quasi_incompressible_hyperelasticity.jl b/previews/PR798/gallery/quasi_incompressible_hyperelasticity.jl new file mode 100644 index 0000000000..3cfb5ff3b5 --- /dev/null +++ b/previews/PR798/gallery/quasi_incompressible_hyperelasticity.jl @@ -0,0 +1,259 @@ +using Ferrite, Tensors, ProgressMeter, WriteVTK +using BlockArrays, SparseArrays, LinearAlgebra + +struct NeoHooke + μ::Float64 + λ::Float64 +end + +function importTestGrid() + grid = generate_grid(Tetrahedron, (5, 5, 5), zero(Vec{3}), ones(Vec{3})) + addfacetset!(grid, "myBottom", x -> norm(x[2]) ≈ 0.0) + addfacetset!(grid, "myBack", x -> norm(x[3]) ≈ 0.0) + addfacetset!(grid, "myRight", x -> norm(x[1]) ≈ 1.0) + addfacetset!(grid, "myLeft", x -> norm(x[1]) ≈ 0.0) + return grid +end; + +function create_values(interpolation_u, interpolation_p) + # quadrature rules + qr = QuadratureRule{RefTetrahedron}(4) + facet_qr = FacetQuadratureRule{RefTetrahedron}(4) + + # cell and facetvalues for u + cellvalues_u = CellValues(qr, interpolation_u) + facetvalues_u = FacetValues(facet_qr, interpolation_u) + + # cellvalues for p + cellvalues_p = CellValues(qr, interpolation_p) + + return cellvalues_u, cellvalues_p, facetvalues_u +end; + +function Ψ(F, p, mp::NeoHooke) + μ = mp.μ + λ = mp.λ + Ic = tr(tdot(F)) + J = det(F) + Js = (λ + p + sqrt((λ + p)^2.0 + 4.0 * λ * μ)) / (2.0 * λ) + return p * (Js - J) + μ / 2 * (Ic - 3) - μ * log(Js) + λ / 2 * (Js - 1)^2 +end; + +function constitutive_driver(F, p, mp::NeoHooke) + # Compute all derivatives in one function call + ∂²Ψ∂F², ∂Ψ∂F = Tensors.hessian(y -> Ψ(y, p, mp), F, :all) + ∂²Ψ∂p², ∂Ψ∂p = Tensors.hessian(y -> Ψ(F, y, mp), p, :all) + ∂²Ψ∂F∂p = Tensors.gradient(q -> Tensors.gradient(y -> Ψ(y, q, mp), F), p) + return ∂Ψ∂F, ∂²Ψ∂F², ∂Ψ∂p, ∂²Ψ∂p², ∂²Ψ∂F∂p +end; + +function create_dofhandler(grid, ipu, ipp) + dh = DofHandler(grid) + add!(dh, :u, ipu) # displacement dim = 3 + add!(dh, :p, ipp) # pressure dim = 1 + close!(dh) + return dh +end; + +function create_bc(dh) + dbc = ConstraintHandler(dh) + add!(dbc, Dirichlet(:u, getfacetset(dh.grid, "myLeft"), (x, t) -> zero(Vec{1}), [1])) + add!(dbc, Dirichlet(:u, getfacetset(dh.grid, "myBottom"), (x, t) -> zero(Vec{1}), [2])) + add!(dbc, Dirichlet(:u, getfacetset(dh.grid, "myBack"), (x, t) -> zero(Vec{1}), [3])) + add!(dbc, Dirichlet(:u, getfacetset(dh.grid, "myRight"), (x, t) -> t * ones(Vec{1}), [1])) + close!(dbc) + Ferrite.update!(dbc, 0.0) + return dbc +end; + +function calculate_element_volume(cell, cellvalues_u, ue) + reinit!(cellvalues_u, cell) + evol::Float64 = 0.0 + for qp in 1:getnquadpoints(cellvalues_u) + dΩ = getdetJdV(cellvalues_u, qp) + ∇u = function_gradient(cellvalues_u, qp, ue) + F = one(∇u) + ∇u + J = det(F) + evol += J * dΩ + end + return evol +end; + +function calculate_volume_deformed_mesh(w, dh::DofHandler, cellvalues_u) + evol::Float64 = 0.0 + for cell in CellIterator(dh) + global_dofs = celldofs(cell) + nu = getnbasefunctions(cellvalues_u) + global_dofs_u = global_dofs[1:nu] + ue = w[global_dofs_u] + δevol = calculate_element_volume(cell, cellvalues_u, ue) + evol += δevol + end + return evol +end; + +function assemble_element!(Ke, fe, cell, cellvalues_u, cellvalues_p, mp, ue, pe) + # Reinitialize cell values, and reset output arrays + ublock, pblock = 1, 2 + reinit!(cellvalues_u, cell) + reinit!(cellvalues_p, cell) + fill!(Ke, 0.0) + fill!(fe, 0.0) + + n_basefuncs_u = getnbasefunctions(cellvalues_u) + n_basefuncs_p = getnbasefunctions(cellvalues_p) + + for qp in 1:getnquadpoints(cellvalues_u) + dΩ = getdetJdV(cellvalues_u, qp) + # Compute deformation gradient F + ∇u = function_gradient(cellvalues_u, qp, ue) + p = function_value(cellvalues_p, qp, pe) + F = one(∇u) + ∇u + + # Compute first Piola-Kirchhoff stress and tangent modulus + ∂Ψ∂F, ∂²Ψ∂F², ∂Ψ∂p, ∂²Ψ∂p², ∂²Ψ∂F∂p = constitutive_driver(F, p, mp) + + # Loop over the `u`-test functions to calculate the `u`-`u` and `u`-`p` blocks + for i in 1:n_basefuncs_u + # gradient of the test function + ∇δui = shape_gradient(cellvalues_u, qp, i) + # Add contribution to the residual from this test function + fe[BlockIndex((ublock), (i))] += (∇δui ⊡ ∂Ψ∂F) * dΩ + + ∇δui∂S∂F = ∇δui ⊡ ∂²Ψ∂F² + for j in 1:n_basefuncs_u + ∇δuj = shape_gradient(cellvalues_u, qp, j) + + # Add contribution to the tangent + Ke[BlockIndex((ublock, ublock), (i, j))] += (∇δui∂S∂F ⊡ ∇δuj) * dΩ + end + # Loop over the `p`-test functions + for j in 1:n_basefuncs_p + δp = shape_value(cellvalues_p, qp, j) + # Add contribution to the tangent + Ke[BlockIndex((ublock, pblock), (i, j))] += (∂²Ψ∂F∂p ⊡ ∇δui) * δp * dΩ + end + end + # Loop over the `p`-test functions to calculate the `p-`u` and `p`-`p` blocks + for i in 1:n_basefuncs_p + δp = shape_value(cellvalues_p, qp, i) + fe[BlockIndex((pblock), (i))] += (δp * ∂Ψ∂p) * dΩ + + for j in 1:n_basefuncs_u + ∇δuj = shape_gradient(cellvalues_u, qp, j) + Ke[BlockIndex((pblock, ublock), (i, j))] += ∇δuj ⊡ ∂²Ψ∂F∂p * δp * dΩ + end + for j in 1:n_basefuncs_p + δp = shape_value(cellvalues_p, qp, j) + Ke[BlockIndex((pblock, pblock), (i, j))] += δp * ∂²Ψ∂p² * δp * dΩ + end + end + end + return +end; + +function assemble_global!( + K::SparseMatrixCSC, f, cellvalues_u::CellValues, + cellvalues_p::CellValues, dh::DofHandler, mp::NeoHooke, w + ) + nu = getnbasefunctions(cellvalues_u) + np = getnbasefunctions(cellvalues_p) + + # start_assemble resets K and f + fe = BlockedArray(zeros(nu + np), [nu, np]) # local force vector + ke = BlockedArray(zeros(nu + np, nu + np), [nu, np], [nu, np]) # local stiffness matrix + + assembler = start_assemble(K, f) + # Loop over all cells in the grid + for cell in CellIterator(dh) + global_dofs = celldofs(cell) + global_dofsu = global_dofs[1:nu] # first nu dofs are displacement + global_dofsp = global_dofs[(nu + 1):end] # last np dofs are pressure + @assert size(global_dofs, 1) == nu + np # sanity check + ue = w[global_dofsu] # displacement dofs for the current cell + pe = w[global_dofsp] # pressure dofs for the current cell + assemble_element!(ke, fe, cell, cellvalues_u, cellvalues_p, mp, ue, pe) + assemble!(assembler, global_dofs, ke, fe) + end + return +end; + +function solve(interpolation_u, interpolation_p) + + # import the mesh + grid = importTestGrid() + + # Material parameters + μ = 1.0 + λ = 1.0e4 * μ + mp = NeoHooke(μ, λ) + + # Create the DofHandler and CellValues + dh = create_dofhandler(grid, interpolation_u, interpolation_p) + cellvalues_u, cellvalues_p, facetvalues_u = create_values(interpolation_u, interpolation_p) + + # Create the DirichletBCs + dbc = create_bc(dh) + + # Pre-allocation of vectors for the solution and Newton increments + _ndofs = ndofs(dh) + w = zeros(_ndofs) + ΔΔw = zeros(_ndofs) + apply!(w, dbc) + + # Create the sparse matrix and residual vector + K = allocate_matrix(dh) + f = zeros(_ndofs) + + # We run the simulation parameterized by a time like parameter. `Tf` denotes the final value + # of this parameter, and Δt denotes its increment in each step + Tf = 2.0 + Δt = 0.1 + NEWTON_TOL = 1.0e-8 + + pvd = paraview_collection("hyperelasticity_incomp_mixed") + for (step, t) in enumerate(0.0:Δt:Tf) + # Perform Newton iterations + Ferrite.update!(dbc, t) + apply!(w, dbc) + newton_itr = -1 + prog = ProgressMeter.ProgressThresh(NEWTON_TOL; desc = "Solving @ time $t of $Tf;") + fill!(ΔΔw, 0.0) + while true + newton_itr += 1 + assemble_global!(K, f, cellvalues_u, cellvalues_p, dh, mp, w) + norm_res = norm(f[Ferrite.free_dofs(dbc)]) + apply_zero!(K, f, dbc) + # Only display output at specific load steps + if t % (5 * Δt) == 0 + ProgressMeter.update!(prog, norm_res; showvalues = [(:iter, newton_itr)]) + end + if norm_res < NEWTON_TOL + break + elseif newton_itr > 30 + error("Reached maximum Newton iterations, aborting") + end + # Compute the incremental `dof`-vector (both displacement and pressure) + ΔΔw .= K \ f + + apply_zero!(ΔΔw, dbc) + w .-= ΔΔw + end + + # Save the solution fields + VTKGridFile("hyperelasticity_incomp_mixed_$step", grid) do vtk + write_solution(vtk, dh, w) + pvd[t] = vtk + end + end + vtk_save(pvd) + vol_def = calculate_volume_deformed_mesh(w, dh, cellvalues_u) + print("Deformed volume is $vol_def") + return vol_def +end; + +quadratic_u = Lagrange{RefTetrahedron, 2}()^3 +linear_p = Lagrange{RefTetrahedron, 1}() +vol_def = solve(quadratic_u, linear_p) + +# This file was generated using Literate.jl, https://github.com/fredrikekre/Literate.jl diff --git a/previews/PR798/gallery/quasi_incompressible_hyperelasticity/index.html b/previews/PR798/gallery/quasi_incompressible_hyperelasticity/index.html new file mode 100644 index 0000000000..4c2d72acde --- /dev/null +++ b/previews/PR798/gallery/quasi_incompressible_hyperelasticity/index.html @@ -0,0 +1,488 @@ + +Nearly Incompressible Hyperelasticity · Ferrite.jl

Nearly Incompressible Hyperelasticity

Tip

This example is also available as a Jupyter notebook: quasi_incompressible_hyperelasticity.ipynb

Introduction

In this example we study quasi- or nearly-incompressible hyperelasticity using the stable Taylor-Hood approximation. In spirit, this example is the nonlinear analogue of incompressible_elasticity and the incompressible analogue of hyperelasticity. Much of the code therefore follows from the above two examples. The problem is formulated in the undeformed or reference configuration with the displacement $\mathbf{u}$ and pressure $p$ being the unknown fields. We now briefly outline the formulation. Consider the standard hyperelasticity problem

\[ \mathbf{u} = \argmin_{\mathbf{v}\in\mathcal{K}(\Omega)}\Pi(\mathbf{v}),\quad \text{where}\quad \Pi(\mathbf{v}) = \int_\Omega \Psi(\mathbf{v}) \ \mathrm{d}\Omega\ .\]

where $\mathcal{K}(\Omega)$ is a suitable function space.

For clarity of presentation we ignore any non-zero surface tractions and body forces and instead consider only applied displacements (i.e. non-homogeneous dirichlet boundary conditions). Moreover we stick our attention to the standard Neo-Hookean stored energy density

\[ \Psi(\mathbf{u}) = \frac{\mu}{2}\left(I_1 - 3 \right) - \mu \log(J) + \frac{\lambda}{2}\left( J - 1\right){}^2,\]

where $I_1 = \mathrm{tr}(\mathbf{C}) = \mathrm{tr}(\mathbf{F}^\mathrm{T} \mathbf{F}) = F_{ij}F_{ij}$ and $J = \det(\mathbf{F})$ denote the standard invariants of the deformation gradient tensor $\mathbf{F} = \mathbf{I}+\nabla_{\mathbf{X}} \mathbf{u}$. The above problem is ill-posed in the limit of incompressibility (or near-incompressibility), namely when

\[ \lambda/\mu \rightarrow +\infty.\]

In order to alleviate the problem, we consider the partial legendre transform of the strain energy density $\Psi$ with respect to $J = \det(\mathbf{F})$, namely

\[ \widehat{\Psi}(\mathbf{u}, p) = \sup_{J} \left[ p(J - 1) - \frac{\mu}{2}\left(I_1 - 3 \right) + \mu \log(J) - \frac{\lambda}{2}\left( J - 1\right){}^2 \right].\]

The supremum, say $J^\star$, can be calculated in closed form by the first order optimailty condition $\partial\widehat{\Psi}/\partial J = 0$. This gives

\[ J^\star(p) = \frac{\lambda + p + \sqrt{(\lambda + p){}^2 + 4 \lambda \mu }}{(2 \lambda)}.\]

Furthermore, taking the partial legendre transform of $\widehat{\Psi}$ once again, gives us back the original problem, i.e.

\[ \Psi(\mathbf{u}) = \Psi^\star(\mathbf{u}, p) = \sup_{p} \left[ p(J - 1) - p(J^\star - 1) + \frac{\mu}{2}\left(I_1 - 3 \right) - \mu \log(J^\star) + \frac{\lambda}{2}\left( J^\star - 1\right){}^2 \right].\]

Therefore our original hyperelasticity problem can now be reformulated as

\[ \inf_{\mathbf{u}\in\mathcal{K}(\Omega)}\sup_{p} \int_\Omega\Psi^{\star} (\mathbf{u}, p) \, \mathrm{d}\Omega.\]

The total (modified) energy $\Pi^\star$ can then be written as

\[ \Pi^\star(\mathbf{u}, p) = \int_\Omega p (J - J^\star) \ \mathrm{d}\Omega + \int_\Omega \frac{\mu}{2} \left( I_1 - 3\right) \ \mathrm{d}\Omega - \int_\Omega \mu\log(J^\star)\ \mathrm{d}\Omega + \int_\Omega \frac{\lambda}{2}\left( J^\star - 1 \right){}^2\ \mathrm{d}\Omega\]

The Euler-Lagrange equations corresponding to the above energy give us our governing PDEs in the weak form, namely

\[ \int_\Omega \frac{\partial\Psi^\star}{\partial \mathbf{F}}:\delta \mathbf{F} \ \mathrm{d}\Omega = 0\]

and

\[ \int_\Omega \frac{\partial \Psi^\star}{\partial p}\delta p \ \mathrm{d}\Omega = 0,\]

where $\delta \mathrm{F} = \delta \mathrm{grad}_0(\mathbf{u}) = \mathrm{grad}_0(\delta \mathbf{u})$ and $\delta \mathbf{u}$ and $\delta p$ denote arbitrary variations with respect to displacement and pressure (or the test functions). See the references below for a more detailed explanation of the above mathematical trick. Now, in order to apply Newton's method to the above problem, we further need to linearize the above equations and calculate the respective hessians (or tangents), namely, $\partial^2\Psi^\star/\partial \mathbf{F}^2$, $\partial^2\Psi^\star/\partial p^2$ and $\partial^2\Psi^\star/\partial \mathbf{F}\partial p$ which, using Tensors.jl, can be determined conveniently using automatic differentiation (see the code below). Hence we only need to define the above potential. The remaineder of the example follows similarly.

References

  1. A paradigm for higher-order polygonal elements in finite elasticity using a gradient correction scheme, CMAME 2016, 306, 216–251
  2. Approximation of incompressible large deformation elastic problems: some unresolved issues, Computational Mechanics, 2013

Implementation

We now get to the actual code. First, we import the respective packages

using Ferrite, Tensors, ProgressMeter, WriteVTK
+using BlockArrays, SparseArrays, LinearAlgebra

and the corresponding struct to store our material properties.

struct NeoHooke
+    μ::Float64
+    λ::Float64
+end

We then create a function to generate a simple test mesh on which to compute FE solution. We also mark the boundaries to later assign Dirichlet boundary conditions

function importTestGrid()
+    grid = generate_grid(Tetrahedron, (5, 5, 5), zero(Vec{3}), ones(Vec{3}))
+    addfacetset!(grid, "myBottom", x -> norm(x[2]) ≈ 0.0)
+    addfacetset!(grid, "myBack", x -> norm(x[3]) ≈ 0.0)
+    addfacetset!(grid, "myRight", x -> norm(x[1]) ≈ 1.0)
+    addfacetset!(grid, "myLeft", x -> norm(x[1]) ≈ 0.0)
+    return grid
+end;

The function to create corresponding cellvalues for the displacement field u and pressure p follows in a similar fashion from the incompressible_elasticity example

function create_values(interpolation_u, interpolation_p)
+    # quadrature rules
+    qr = QuadratureRule{RefTetrahedron}(4)
+    facet_qr = FacetQuadratureRule{RefTetrahedron}(4)
+
+    # cell and facetvalues for u
+    cellvalues_u = CellValues(qr, interpolation_u)
+    facetvalues_u = FacetValues(facet_qr, interpolation_u)
+
+    # cellvalues for p
+    cellvalues_p = CellValues(qr, interpolation_p)
+
+    return cellvalues_u, cellvalues_p, facetvalues_u
+end;

We now create the function for Ψ*

function Ψ(F, p, mp::NeoHooke)
+    μ = mp.μ
+    λ = mp.λ
+    Ic = tr(tdot(F))
+    J = det(F)
+    Js = (λ + p + sqrt((λ + p)^2.0 + 4.0 * λ * μ)) / (2.0 * λ)
+    return p * (Js - J) + μ / 2 * (Ic - 3) - μ * log(Js) + λ / 2 * (Js - 1)^2
+end;

and it's derivatives (required in computing the jacobian and hessian respectively)

function constitutive_driver(F, p, mp::NeoHooke)
+    # Compute all derivatives in one function call
+    ∂²Ψ∂F², ∂Ψ∂F = Tensors.hessian(y -> Ψ(y, p, mp), F, :all)
+    ∂²Ψ∂p², ∂Ψ∂p = Tensors.hessian(y -> Ψ(F, y, mp), p, :all)
+    ∂²Ψ∂F∂p = Tensors.gradient(q -> Tensors.gradient(y -> Ψ(y, q, mp), F), p)
+    return ∂Ψ∂F, ∂²Ψ∂F², ∂Ψ∂p, ∂²Ψ∂p², ∂²Ψ∂F∂p
+end;

The functions to create the DofHandler and ConstraintHandler (to assign corresponding boundary conditions) follow likewise from the incompressible elasticity example, namely

function create_dofhandler(grid, ipu, ipp)
+    dh = DofHandler(grid)
+    add!(dh, :u, ipu) # displacement dim = 3
+    add!(dh, :p, ipp) # pressure dim = 1
+    close!(dh)
+    return dh
+end;

We are simulating a uniaxial tensile loading of a unit cube. Hence we apply a displacement field (:u) in x direction on the right face. The left, bottom and back facets are fixed in the x, y and z components of the displacement so as to emulate the uniaxial nature of the loading.

function create_bc(dh)
+    dbc = ConstraintHandler(dh)
+    add!(dbc, Dirichlet(:u, getfacetset(dh.grid, "myLeft"), (x, t) -> zero(Vec{1}), [1]))
+    add!(dbc, Dirichlet(:u, getfacetset(dh.grid, "myBottom"), (x, t) -> zero(Vec{1}), [2]))
+    add!(dbc, Dirichlet(:u, getfacetset(dh.grid, "myBack"), (x, t) -> zero(Vec{1}), [3]))
+    add!(dbc, Dirichlet(:u, getfacetset(dh.grid, "myRight"), (x, t) -> t * ones(Vec{1}), [1]))
+    close!(dbc)
+    Ferrite.update!(dbc, 0.0)
+    return dbc
+end;

Also, since we are considering incompressible hyperelasticity, an interesting quantity that we can compute is the deformed volume of the solid. It is easy to show that this is equal to ∫J*dΩ where J=det(F). This can be done at the level of each element (cell)

function calculate_element_volume(cell, cellvalues_u, ue)
+    reinit!(cellvalues_u, cell)
+    evol::Float64 = 0.0
+    for qp in 1:getnquadpoints(cellvalues_u)
+        dΩ = getdetJdV(cellvalues_u, qp)
+        ∇u = function_gradient(cellvalues_u, qp, ue)
+        F = one(∇u) + ∇u
+        J = det(F)
+        evol += J * dΩ
+    end
+    return evol
+end;

and then assembled over all the cells (elements)

function calculate_volume_deformed_mesh(w, dh::DofHandler, cellvalues_u)
+    evol::Float64 = 0.0
+    for cell in CellIterator(dh)
+        global_dofs = celldofs(cell)
+        nu = getnbasefunctions(cellvalues_u)
+        global_dofs_u = global_dofs[1:nu]
+        ue = w[global_dofs_u]
+        δevol = calculate_element_volume(cell, cellvalues_u, ue)
+        evol += δevol
+    end
+    return evol
+end;

The function to assemble the element stiffness matrix for each element in the mesh now has a block structure like in incompressible_elasticity.

function assemble_element!(Ke, fe, cell, cellvalues_u, cellvalues_p, mp, ue, pe)
+    # Reinitialize cell values, and reset output arrays
+    ublock, pblock = 1, 2
+    reinit!(cellvalues_u, cell)
+    reinit!(cellvalues_p, cell)
+    fill!(Ke, 0.0)
+    fill!(fe, 0.0)
+
+    n_basefuncs_u = getnbasefunctions(cellvalues_u)
+    n_basefuncs_p = getnbasefunctions(cellvalues_p)
+
+    for qp in 1:getnquadpoints(cellvalues_u)
+        dΩ = getdetJdV(cellvalues_u, qp)
+        # Compute deformation gradient F
+        ∇u = function_gradient(cellvalues_u, qp, ue)
+        p = function_value(cellvalues_p, qp, pe)
+        F = one(∇u) + ∇u
+
+        # Compute first Piola-Kirchhoff stress and tangent modulus
+        ∂Ψ∂F, ∂²Ψ∂F², ∂Ψ∂p, ∂²Ψ∂p², ∂²Ψ∂F∂p = constitutive_driver(F, p, mp)
+
+        # Loop over the `u`-test functions to calculate the `u`-`u` and `u`-`p` blocks
+        for i in 1:n_basefuncs_u
+            # gradient of the test function
+            ∇δui = shape_gradient(cellvalues_u, qp, i)
+            # Add contribution to the residual from this test function
+            fe[BlockIndex((ublock), (i))] += (∇δui ⊡ ∂Ψ∂F) * dΩ
+
+            ∇δui∂S∂F = ∇δui ⊡ ∂²Ψ∂F²
+            for j in 1:n_basefuncs_u
+                ∇δuj = shape_gradient(cellvalues_u, qp, j)
+
+                # Add contribution to the tangent
+                Ke[BlockIndex((ublock, ublock), (i, j))] += (∇δui∂S∂F ⊡ ∇δuj) * dΩ
+            end
+            # Loop over the `p`-test functions
+            for j in 1:n_basefuncs_p
+                δp = shape_value(cellvalues_p, qp, j)
+                # Add contribution to the tangent
+                Ke[BlockIndex((ublock, pblock), (i, j))] += (∂²Ψ∂F∂p ⊡ ∇δui) * δp * dΩ
+            end
+        end
+        # Loop over the `p`-test functions to calculate the `p-`u` and `p`-`p` blocks
+        for i in 1:n_basefuncs_p
+            δp = shape_value(cellvalues_p, qp, i)
+            fe[BlockIndex((pblock), (i))] += (δp * ∂Ψ∂p) * dΩ
+
+            for j in 1:n_basefuncs_u
+                ∇δuj = shape_gradient(cellvalues_u, qp, j)
+                Ke[BlockIndex((pblock, ublock), (i, j))] += ∇δuj ⊡ ∂²Ψ∂F∂p * δp * dΩ
+            end
+            for j in 1:n_basefuncs_p
+                δp = shape_value(cellvalues_p, qp, j)
+                Ke[BlockIndex((pblock, pblock), (i, j))] += δp * ∂²Ψ∂p² * δp * dΩ
+            end
+        end
+    end
+    return
+end;

The only thing that changes in the assembly of the global stiffness matrix is slicing the corresponding element dofs for the displacement (see global_dofsu) and pressure (global_dofsp).

function assemble_global!(
+        K::SparseMatrixCSC, f, cellvalues_u::CellValues,
+        cellvalues_p::CellValues, dh::DofHandler, mp::NeoHooke, w
+    )
+    nu = getnbasefunctions(cellvalues_u)
+    np = getnbasefunctions(cellvalues_p)
+
+    # start_assemble resets K and f
+    fe = BlockedArray(zeros(nu + np), [nu, np]) # local force vector
+    ke = BlockedArray(zeros(nu + np, nu + np), [nu, np], [nu, np]) # local stiffness matrix
+
+    assembler = start_assemble(K, f)
+    # Loop over all cells in the grid
+    for cell in CellIterator(dh)
+        global_dofs = celldofs(cell)
+        global_dofsu = global_dofs[1:nu] # first nu dofs are displacement
+        global_dofsp = global_dofs[(nu + 1):end] # last np dofs are pressure
+        @assert size(global_dofs, 1) == nu + np # sanity check
+        ue = w[global_dofsu] # displacement dofs for the current cell
+        pe = w[global_dofsp] # pressure dofs for the current cell
+        assemble_element!(ke, fe, cell, cellvalues_u, cellvalues_p, mp, ue, pe)
+        assemble!(assembler, global_dofs, ke, fe)
+    end
+    return
+end;

We now define a main function solve. For nonlinear quasistatic problems we often like to parameterize the solution in terms of a pseudo time like parameter, which in this case is used to gradually apply the boundary displacement on the right face. Also for definitenessm we consider λ/μ = 10⁴

function solve(interpolation_u, interpolation_p)
+
+    # import the mesh
+    grid = importTestGrid()
+
+    # Material parameters
+    μ = 1.0
+    λ = 1.0e4 * μ
+    mp = NeoHooke(μ, λ)
+
+    # Create the DofHandler and CellValues
+    dh = create_dofhandler(grid, interpolation_u, interpolation_p)
+    cellvalues_u, cellvalues_p, facetvalues_u = create_values(interpolation_u, interpolation_p)
+
+    # Create the DirichletBCs
+    dbc = create_bc(dh)
+
+    # Pre-allocation of vectors for the solution and Newton increments
+    _ndofs = ndofs(dh)
+    w = zeros(_ndofs)
+    ΔΔw = zeros(_ndofs)
+    apply!(w, dbc)
+
+    # Create the sparse matrix and residual vector
+    K = allocate_matrix(dh)
+    f = zeros(_ndofs)
+
+    # We run the simulation parameterized by a time like parameter. `Tf` denotes the final value
+    # of this parameter, and Δt denotes its increment in each step
+    Tf = 2.0
+    Δt = 0.1
+    NEWTON_TOL = 1.0e-8
+
+    pvd = paraview_collection("hyperelasticity_incomp_mixed")
+    for (step, t) in enumerate(0.0:Δt:Tf)
+        # Perform Newton iterations
+        Ferrite.update!(dbc, t)
+        apply!(w, dbc)
+        newton_itr = -1
+        prog = ProgressMeter.ProgressThresh(NEWTON_TOL; desc = "Solving @ time $t of $Tf;")
+        fill!(ΔΔw, 0.0)
+        while true
+            newton_itr += 1
+            assemble_global!(K, f, cellvalues_u, cellvalues_p, dh, mp, w)
+            norm_res = norm(f[Ferrite.free_dofs(dbc)])
+            apply_zero!(K, f, dbc)
+            # Only display output at specific load steps
+            if t % (5 * Δt) == 0
+                ProgressMeter.update!(prog, norm_res; showvalues = [(:iter, newton_itr)])
+            end
+            if norm_res < NEWTON_TOL
+                break
+            elseif newton_itr > 30
+                error("Reached maximum Newton iterations, aborting")
+            end
+            # Compute the incremental `dof`-vector (both displacement and pressure)
+            ΔΔw .= K \ f
+
+            apply_zero!(ΔΔw, dbc)
+            w .-= ΔΔw
+        end
+
+        # Save the solution fields
+        VTKGridFile("hyperelasticity_incomp_mixed_$step", grid) do vtk
+            write_solution(vtk, dh, w)
+            pvd[t] = vtk
+        end
+    end
+    vtk_save(pvd)
+    vol_def = calculate_volume_deformed_mesh(w, dh, cellvalues_u)
+    print("Deformed volume is $vol_def")
+    return vol_def
+end;

We can now test the solution using the Taylor-Hood approximation

quadratic_u = Lagrange{RefTetrahedron, 2}()^3
+linear_p = Lagrange{RefTetrahedron, 1}()
+vol_def = solve(quadratic_u, linear_p)
1.000199977169989

The deformed volume is indeed close to 1 (as should be for a nearly incompressible material).

Plain program

Here follows a version of the program without any comments. The file is also available here: quasi_incompressible_hyperelasticity.jl.

using Ferrite, Tensors, ProgressMeter, WriteVTK
+using BlockArrays, SparseArrays, LinearAlgebra
+
+struct NeoHooke
+    μ::Float64
+    λ::Float64
+end
+
+function importTestGrid()
+    grid = generate_grid(Tetrahedron, (5, 5, 5), zero(Vec{3}), ones(Vec{3}))
+    addfacetset!(grid, "myBottom", x -> norm(x[2]) ≈ 0.0)
+    addfacetset!(grid, "myBack", x -> norm(x[3]) ≈ 0.0)
+    addfacetset!(grid, "myRight", x -> norm(x[1]) ≈ 1.0)
+    addfacetset!(grid, "myLeft", x -> norm(x[1]) ≈ 0.0)
+    return grid
+end;
+
+function create_values(interpolation_u, interpolation_p)
+    # quadrature rules
+    qr = QuadratureRule{RefTetrahedron}(4)
+    facet_qr = FacetQuadratureRule{RefTetrahedron}(4)
+
+    # cell and facetvalues for u
+    cellvalues_u = CellValues(qr, interpolation_u)
+    facetvalues_u = FacetValues(facet_qr, interpolation_u)
+
+    # cellvalues for p
+    cellvalues_p = CellValues(qr, interpolation_p)
+
+    return cellvalues_u, cellvalues_p, facetvalues_u
+end;
+
+function Ψ(F, p, mp::NeoHooke)
+    μ = mp.μ
+    λ = mp.λ
+    Ic = tr(tdot(F))
+    J = det(F)
+    Js = (λ + p + sqrt((λ + p)^2.0 + 4.0 * λ * μ)) / (2.0 * λ)
+    return p * (Js - J) + μ / 2 * (Ic - 3) - μ * log(Js) + λ / 2 * (Js - 1)^2
+end;
+
+function constitutive_driver(F, p, mp::NeoHooke)
+    # Compute all derivatives in one function call
+    ∂²Ψ∂F², ∂Ψ∂F = Tensors.hessian(y -> Ψ(y, p, mp), F, :all)
+    ∂²Ψ∂p², ∂Ψ∂p = Tensors.hessian(y -> Ψ(F, y, mp), p, :all)
+    ∂²Ψ∂F∂p = Tensors.gradient(q -> Tensors.gradient(y -> Ψ(y, q, mp), F), p)
+    return ∂Ψ∂F, ∂²Ψ∂F², ∂Ψ∂p, ∂²Ψ∂p², ∂²Ψ∂F∂p
+end;
+
+function create_dofhandler(grid, ipu, ipp)
+    dh = DofHandler(grid)
+    add!(dh, :u, ipu) # displacement dim = 3
+    add!(dh, :p, ipp) # pressure dim = 1
+    close!(dh)
+    return dh
+end;
+
+function create_bc(dh)
+    dbc = ConstraintHandler(dh)
+    add!(dbc, Dirichlet(:u, getfacetset(dh.grid, "myLeft"), (x, t) -> zero(Vec{1}), [1]))
+    add!(dbc, Dirichlet(:u, getfacetset(dh.grid, "myBottom"), (x, t) -> zero(Vec{1}), [2]))
+    add!(dbc, Dirichlet(:u, getfacetset(dh.grid, "myBack"), (x, t) -> zero(Vec{1}), [3]))
+    add!(dbc, Dirichlet(:u, getfacetset(dh.grid, "myRight"), (x, t) -> t * ones(Vec{1}), [1]))
+    close!(dbc)
+    Ferrite.update!(dbc, 0.0)
+    return dbc
+end;
+
+function calculate_element_volume(cell, cellvalues_u, ue)
+    reinit!(cellvalues_u, cell)
+    evol::Float64 = 0.0
+    for qp in 1:getnquadpoints(cellvalues_u)
+        dΩ = getdetJdV(cellvalues_u, qp)
+        ∇u = function_gradient(cellvalues_u, qp, ue)
+        F = one(∇u) + ∇u
+        J = det(F)
+        evol += J * dΩ
+    end
+    return evol
+end;
+
+function calculate_volume_deformed_mesh(w, dh::DofHandler, cellvalues_u)
+    evol::Float64 = 0.0
+    for cell in CellIterator(dh)
+        global_dofs = celldofs(cell)
+        nu = getnbasefunctions(cellvalues_u)
+        global_dofs_u = global_dofs[1:nu]
+        ue = w[global_dofs_u]
+        δevol = calculate_element_volume(cell, cellvalues_u, ue)
+        evol += δevol
+    end
+    return evol
+end;
+
+function assemble_element!(Ke, fe, cell, cellvalues_u, cellvalues_p, mp, ue, pe)
+    # Reinitialize cell values, and reset output arrays
+    ublock, pblock = 1, 2
+    reinit!(cellvalues_u, cell)
+    reinit!(cellvalues_p, cell)
+    fill!(Ke, 0.0)
+    fill!(fe, 0.0)
+
+    n_basefuncs_u = getnbasefunctions(cellvalues_u)
+    n_basefuncs_p = getnbasefunctions(cellvalues_p)
+
+    for qp in 1:getnquadpoints(cellvalues_u)
+        dΩ = getdetJdV(cellvalues_u, qp)
+        # Compute deformation gradient F
+        ∇u = function_gradient(cellvalues_u, qp, ue)
+        p = function_value(cellvalues_p, qp, pe)
+        F = one(∇u) + ∇u
+
+        # Compute first Piola-Kirchhoff stress and tangent modulus
+        ∂Ψ∂F, ∂²Ψ∂F², ∂Ψ∂p, ∂²Ψ∂p², ∂²Ψ∂F∂p = constitutive_driver(F, p, mp)
+
+        # Loop over the `u`-test functions to calculate the `u`-`u` and `u`-`p` blocks
+        for i in 1:n_basefuncs_u
+            # gradient of the test function
+            ∇δui = shape_gradient(cellvalues_u, qp, i)
+            # Add contribution to the residual from this test function
+            fe[BlockIndex((ublock), (i))] += (∇δui ⊡ ∂Ψ∂F) * dΩ
+
+            ∇δui∂S∂F = ∇δui ⊡ ∂²Ψ∂F²
+            for j in 1:n_basefuncs_u
+                ∇δuj = shape_gradient(cellvalues_u, qp, j)
+
+                # Add contribution to the tangent
+                Ke[BlockIndex((ublock, ublock), (i, j))] += (∇δui∂S∂F ⊡ ∇δuj) * dΩ
+            end
+            # Loop over the `p`-test functions
+            for j in 1:n_basefuncs_p
+                δp = shape_value(cellvalues_p, qp, j)
+                # Add contribution to the tangent
+                Ke[BlockIndex((ublock, pblock), (i, j))] += (∂²Ψ∂F∂p ⊡ ∇δui) * δp * dΩ
+            end
+        end
+        # Loop over the `p`-test functions to calculate the `p-`u` and `p`-`p` blocks
+        for i in 1:n_basefuncs_p
+            δp = shape_value(cellvalues_p, qp, i)
+            fe[BlockIndex((pblock), (i))] += (δp * ∂Ψ∂p) * dΩ
+
+            for j in 1:n_basefuncs_u
+                ∇δuj = shape_gradient(cellvalues_u, qp, j)
+                Ke[BlockIndex((pblock, ublock), (i, j))] += ∇δuj ⊡ ∂²Ψ∂F∂p * δp * dΩ
+            end
+            for j in 1:n_basefuncs_p
+                δp = shape_value(cellvalues_p, qp, j)
+                Ke[BlockIndex((pblock, pblock), (i, j))] += δp * ∂²Ψ∂p² * δp * dΩ
+            end
+        end
+    end
+    return
+end;
+
+function assemble_global!(
+        K::SparseMatrixCSC, f, cellvalues_u::CellValues,
+        cellvalues_p::CellValues, dh::DofHandler, mp::NeoHooke, w
+    )
+    nu = getnbasefunctions(cellvalues_u)
+    np = getnbasefunctions(cellvalues_p)
+
+    # start_assemble resets K and f
+    fe = BlockedArray(zeros(nu + np), [nu, np]) # local force vector
+    ke = BlockedArray(zeros(nu + np, nu + np), [nu, np], [nu, np]) # local stiffness matrix
+
+    assembler = start_assemble(K, f)
+    # Loop over all cells in the grid
+    for cell in CellIterator(dh)
+        global_dofs = celldofs(cell)
+        global_dofsu = global_dofs[1:nu] # first nu dofs are displacement
+        global_dofsp = global_dofs[(nu + 1):end] # last np dofs are pressure
+        @assert size(global_dofs, 1) == nu + np # sanity check
+        ue = w[global_dofsu] # displacement dofs for the current cell
+        pe = w[global_dofsp] # pressure dofs for the current cell
+        assemble_element!(ke, fe, cell, cellvalues_u, cellvalues_p, mp, ue, pe)
+        assemble!(assembler, global_dofs, ke, fe)
+    end
+    return
+end;
+
+function solve(interpolation_u, interpolation_p)
+
+    # import the mesh
+    grid = importTestGrid()
+
+    # Material parameters
+    μ = 1.0
+    λ = 1.0e4 * μ
+    mp = NeoHooke(μ, λ)
+
+    # Create the DofHandler and CellValues
+    dh = create_dofhandler(grid, interpolation_u, interpolation_p)
+    cellvalues_u, cellvalues_p, facetvalues_u = create_values(interpolation_u, interpolation_p)
+
+    # Create the DirichletBCs
+    dbc = create_bc(dh)
+
+    # Pre-allocation of vectors for the solution and Newton increments
+    _ndofs = ndofs(dh)
+    w = zeros(_ndofs)
+    ΔΔw = zeros(_ndofs)
+    apply!(w, dbc)
+
+    # Create the sparse matrix and residual vector
+    K = allocate_matrix(dh)
+    f = zeros(_ndofs)
+
+    # We run the simulation parameterized by a time like parameter. `Tf` denotes the final value
+    # of this parameter, and Δt denotes its increment in each step
+    Tf = 2.0
+    Δt = 0.1
+    NEWTON_TOL = 1.0e-8
+
+    pvd = paraview_collection("hyperelasticity_incomp_mixed")
+    for (step, t) in enumerate(0.0:Δt:Tf)
+        # Perform Newton iterations
+        Ferrite.update!(dbc, t)
+        apply!(w, dbc)
+        newton_itr = -1
+        prog = ProgressMeter.ProgressThresh(NEWTON_TOL; desc = "Solving @ time $t of $Tf;")
+        fill!(ΔΔw, 0.0)
+        while true
+            newton_itr += 1
+            assemble_global!(K, f, cellvalues_u, cellvalues_p, dh, mp, w)
+            norm_res = norm(f[Ferrite.free_dofs(dbc)])
+            apply_zero!(K, f, dbc)
+            # Only display output at specific load steps
+            if t % (5 * Δt) == 0
+                ProgressMeter.update!(prog, norm_res; showvalues = [(:iter, newton_itr)])
+            end
+            if norm_res < NEWTON_TOL
+                break
+            elseif newton_itr > 30
+                error("Reached maximum Newton iterations, aborting")
+            end
+            # Compute the incremental `dof`-vector (both displacement and pressure)
+            ΔΔw .= K \ f
+
+            apply_zero!(ΔΔw, dbc)
+            w .-= ΔΔw
+        end
+
+        # Save the solution fields
+        VTKGridFile("hyperelasticity_incomp_mixed_$step", grid) do vtk
+            write_solution(vtk, dh, w)
+            pvd[t] = vtk
+        end
+    end
+    vtk_save(pvd)
+    vol_def = calculate_volume_deformed_mesh(w, dh, cellvalues_u)
+    print("Deformed volume is $vol_def")
+    return vol_def
+end;
+
+quadratic_u = Lagrange{RefTetrahedron, 2}()^3
+linear_p = Lagrange{RefTetrahedron, 1}()
+vol_def = solve(quadratic_u, linear_p)

This page was generated using Literate.jl.

diff --git a/previews/PR798/gallery/topology_optimization.ipynb b/previews/PR798/gallery/topology_optimization.ipynb new file mode 100644 index 0000000000..f55bd49289 --- /dev/null +++ b/previews/PR798/gallery/topology_optimization.ipynb @@ -0,0 +1,881 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Topology optimization\n", + "\n", + "**Keywords**: *Topology optimization*, *weak and strong form*, *non-linear problem*, *Laplacian*, *grid topology*\n", + "\n", + "![](bending_animation.gif)\n", + "\n", + "*Figure 1*: Optimization of the bending beam. Evolution of the density for fixed total mass." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Introduction\n", + "\n", + "Topology optimization is the task of finding structures that are mechanically ideal.\n", + "In this example we cover the bending beam, where we specify a load, boundary conditions and the total mass. Then, our\n", + "objective is to find the most suitable geometry within the design space minimizing the compliance (i.e. the inverse stiffness) of the structure.\n", + "We shortly introduce our simplified model for regular meshes. A detailed derivation of the method and advanced techniques\n", + "can be found in [JanHacJun2019regularizedthermotopopt](@cite) and\n", + "[BlaJanJun2022taylorwlsthermotopopt](@cite).\n", + "\n", + "We start by introducing the local, elementwise density $\\chi \\in [\\chi_{\\text{min}}, 1]$ of the material, where we choose\n", + "$\\chi_{\\text{min}}$ slightly above zero to prevent numerical instabilities. Here, $\\chi = \\chi_{\\text{min}}$ means void and $\\chi=1$\n", + "means bulk material. Then, we use a SIMP ansatz (solid isotropic material with penalization) for the stiffness tensor\n", + "$C(\\chi) = \\chi^p C_0$, where $C_0$ is the stiffness of the bulk material. The SIMP exponent $p>1$ ensures that the\n", + "model prefers the density values void and bulk before the intermediate values. The variational formulation then yields\n", + "the modified Gibbs energy\n", + "$$\n", + "G = \\int_{\\Omega} \\frac{1}{2} \\chi^p \\varepsilon : C : \\varepsilon \\; \\text{d}V - \\int_{\\Omega} \\boldsymbol{f} \\cdot \\boldsymbol{u} \\; \\text{d}V - \\int_{\\partial\\Omega} \\boldsymbol{t} \\cdot \\boldsymbol{u} \\; \\text{d}A.\n", + "$$\n", + "Furthermore, we receive the evolution equation of the density\n", + "and the additional Neumann boundary condition in the strong form\n", + "$$\n", + "p_\\chi + \\eta \\dot{\\chi} + \\lambda + \\gamma - \\beta \\nabla^2 \\chi \\ni 0 \\quad \\forall \\textbf{x} \\in \\Omega,\n", + "$$\n", + "$$\n", + "\\beta \\nabla \\chi \\cdot \\textbf{n} = 0 \\quad \\forall \\textbf{x} \\in \\partial \\Omega,\n", + "$$\n", + "with the thermodynamic driving force\n", + "$$\n", + "p_\\chi = \\frac{1}{2} p \\chi^{p-1} \\varepsilon : C : \\varepsilon.\n", + "$$\n", + "We obtain the mechanical displacement field by applying the Finite Element Method to the weak form\n", + "of the Gibbs energy using Ferrite. In contrast, we use the evolution equation (i.e. the strong form) to calculate\n", + "the value of the density field $\\chi$. The advantage of this \"split\" approach is the very high computation speed.\n", + "The evolution equation consists of the driving force, the damping parameter $\\eta$, the regularization parameter $\\beta$ times the Laplacian,\n", + "which is necessary to avoid numerical issues like mesh dependence or checkerboarding, and the constraint parameters $\\lambda$, to keep the mass constant,\n", + "and $\\gamma$, to avoid leaving the set $[\\chi_{\\text{min}}, 1]$. By including gradient regularization, it becomes necessary to calculate the Laplacian.\n", + "The Finite Difference Method for square meshes with the edge length $\\Delta h$ approximates the Laplacian as follows:\n", + "$$\n", + "\\nabla^2 \\chi_p = \\frac{1}{(\\Delta h)^2} (\\chi_n + \\chi_s + \\chi_w + \\chi_e - 4 \\chi_p)\n", + "$$\n", + "Here, the indices refer to the different cardinal directions. Boundary element do not have neighbors in each direction. However, we can calculate\n", + "the central difference to fulfill Neumann boundary condition. For example, if the element is on the left boundary, we have to fulfill\n", + "$$\n", + "\\nabla \\chi_p \\cdot \\textbf{n} = \\frac{1}{\\Delta h} (\\chi_w - \\chi_e) = 0\n", + "$$\n", + "from which follows $\\chi_w = \\chi_e$. Thus for boundary elements we can replace the value for the missing neighbor by the value of the opposite neighbor.\n", + "In order to find the corresponding neighbor elements, we will make use of Ferrites grid topology funcionalities.\n", + "\n", + "## Commented Program\n", + "We now solve the problem in Ferrite. What follows is a program spliced with comments.\n", + "\n", + "First we load all necessary packages." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using Ferrite, SparseArrays, LinearAlgebra, Tensors, Printf" + ], + "metadata": {}, + "execution_count": 1 + }, + { + "cell_type": "markdown", + "source": [ + "Next, we create a simple square grid of the size 2x1. We apply a fixed Dirichlet boundary condition\n", + "to the left facet set, called `clamped`. On the right facet, we create a small set `traction`, where we\n", + "will later apply a force in negative y-direction." + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "create_grid (generic function with 1 method)" + }, + "metadata": {}, + "execution_count": 2 + } + ], + "cell_type": "code", + "source": [ + "function create_grid(n)\n", + " corners = [\n", + " Vec{2}((0.0, 0.0)),\n", + " Vec{2}((2.0, 0.0)),\n", + " Vec{2}((2.0, 1.0)),\n", + " Vec{2}((0.0, 1.0)),\n", + " ]\n", + " grid = generate_grid(Quadrilateral, (2 * n, n), corners)\n", + "\n", + " # node-/facesets for boundary conditions\n", + " addnodeset!(grid, \"clamped\", x -> x[1] ≈ 0.0)\n", + " addfacetset!(grid, \"traction\", x -> x[1] ≈ 2.0 && norm(x[2] - 0.5) <= 0.05)\n", + " return grid\n", + "end" + ], + "metadata": {}, + "execution_count": 2 + }, + { + "cell_type": "markdown", + "source": [ + "Next, we create the FE values, the DofHandler and the Dirichlet boundary condition." + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "create_bc (generic function with 1 method)" + }, + "metadata": {}, + "execution_count": 3 + } + ], + "cell_type": "code", + "source": [ + "function create_values()\n", + " # quadrature rules\n", + " qr = QuadratureRule{RefQuadrilateral}(2)\n", + " facet_qr = FacetQuadratureRule{RefQuadrilateral}(2)\n", + "\n", + " # cell and facetvalues for u\n", + " ip = Lagrange{RefQuadrilateral, 1}()^2\n", + " cellvalues = CellValues(qr, ip)\n", + " facetvalues = FacetValues(facet_qr, ip)\n", + "\n", + " return cellvalues, facetvalues\n", + "end\n", + "\n", + "function create_dofhandler(grid)\n", + " dh = DofHandler(grid)\n", + " add!(dh, :u, Lagrange{RefQuadrilateral, 1}()^2) # displacement\n", + " close!(dh)\n", + " return dh\n", + "end\n", + "\n", + "function create_bc(dh)\n", + " dbc = ConstraintHandler(dh)\n", + " add!(dbc, Dirichlet(:u, getnodeset(dh.grid, \"clamped\"), (x, t) -> zero(Vec{2}), [1, 2]))\n", + " close!(dbc)\n", + " t = 0.0\n", + " update!(dbc, t)\n", + " return dbc\n", + "end" + ], + "metadata": {}, + "execution_count": 3 + }, + { + "cell_type": "markdown", + "source": [ + "Now, we define a struct to store all necessary material parameters (stiffness tensor of the bulk material\n", + "and the parameters for topology optimization) and add a constructor to the struct to\n", + "initialize it by using the common material parameters Young's modulus and Poisson number." + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "Main.var\"##351\".MaterialParameters" + }, + "metadata": {}, + "execution_count": 4 + } + ], + "cell_type": "code", + "source": [ + "struct MaterialParameters{T, S <: SymmetricTensor{4, 2, T}}\n", + " C::S\n", + " χ_min::T\n", + " p::T\n", + " β::T\n", + " η::T\n", + "end\n", + "\n", + "function MaterialParameters(E, ν, χ_min, p, β, η)\n", + " δ(i, j) = i == j ? 1.0 : 0.0 # helper function\n", + "\n", + " G = E / 2(1 + ν) # =μ\n", + " λ = E * ν / (1 - ν^2) # correction for plane stress included\n", + "\n", + " C = SymmetricTensor{4, 2}((i, j, k, l) -> λ * δ(i, j) * δ(k, l) + G * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k)))\n", + " return MaterialParameters(C, χ_min, p, β, η)\n", + "end" + ], + "metadata": {}, + "execution_count": 4 + }, + { + "cell_type": "markdown", + "source": [ + "To store the density and the strain required to calculate the driving forces, we create the struct\n", + "`MaterialState`. We add a constructor to initialize the struct. The function `update_material_states!`\n", + "updates the density values once we calculated the new values." + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "update_material_states! (generic function with 1 method)" + }, + "metadata": {}, + "execution_count": 5 + } + ], + "cell_type": "code", + "source": [ + "mutable struct MaterialState{T, S <: AbstractArray{SymmetricTensor{2, 2, T, 3}, 1}}\n", + " χ::T # density\n", + " ε::S # strain in each quadrature point\n", + "end\n", + "\n", + "function MaterialState(ρ, n_qp)\n", + " return MaterialState(ρ, Array{SymmetricTensor{2, 2, Float64, 3}, 1}(undef, n_qp))\n", + "end\n", + "\n", + "function update_material_states!(χn1, states, dh)\n", + " for (element, state) in zip(CellIterator(dh), states)\n", + " state.χ = χn1[cellid(element)]\n", + " end\n", + " return\n", + "end" + ], + "metadata": {}, + "execution_count": 5 + }, + { + "cell_type": "markdown", + "source": [ + "Next, we define a function to calculate the driving forces for all elements.\n", + "For this purpose, we iterate through all elements and calculate the average strain in each\n", + "element. Then, we compute the driving force from the formula introduced at the beginning.\n", + "We create a second function to collect the density in each element." + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "compute_densities (generic function with 1 method)" + }, + "metadata": {}, + "execution_count": 6 + } + ], + "cell_type": "code", + "source": [ + "function compute_driving_forces(states, mp, dh, χn)\n", + " pΨ = zeros(length(states))\n", + " for (element, state) in zip(CellIterator(dh), states)\n", + " i = cellid(element)\n", + " ε = sum(state.ε) / length(state.ε) # average element strain\n", + " pΨ[i] = 1 / 2 * mp.p * χn[i]^(mp.p - 1) * (ε ⊡ mp.C ⊡ ε)\n", + " end\n", + " return pΨ\n", + "end\n", + "\n", + "function compute_densities(states, dh)\n", + " χn = zeros(length(states))\n", + " for (element, state) in zip(CellIterator(dh), states)\n", + " i = cellid(element)\n", + " χn[i] = state.χ\n", + " end\n", + " return χn\n", + "end" + ], + "metadata": {}, + "execution_count": 6 + }, + { + "cell_type": "markdown", + "source": [ + "For the Laplacian we need some neighboorhood information which is constant throughout the analysis so we compute it once and cache it.\n", + "We iterate through each facet of each element,\n", + "obtaining the neighboring element by using the `getneighborhood` function. For boundary facets,\n", + "the function call will return an empty object. In that case we use the dictionary to instead find the opposite\n", + "facet, as discussed in the introduction." + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "cache_neighborhood (generic function with 1 method)" + }, + "metadata": {}, + "execution_count": 7 + } + ], + "cell_type": "code", + "source": [ + "function cache_neighborhood(dh, topology)\n", + " nbgs = Vector{Vector{Int}}(undef, getncells(dh.grid))\n", + " _nfacets = nfacets(dh.grid.cells[1])\n", + " opp = Dict(1 => 3, 2 => 4, 3 => 1, 4 => 2)\n", + "\n", + " for element in CellIterator(dh)\n", + " nbg = zeros(Int, _nfacets)\n", + " i = cellid(element)\n", + " for j in 1:_nfacets\n", + " nbg_cellid = getneighborhood(topology, dh.grid, FacetIndex(i, j))\n", + " if !isempty(nbg_cellid)\n", + " nbg[j] = first(nbg_cellid)[1] # assuming only one facet neighbor per cell\n", + " else # boundary facet\n", + " nbg[j] = first(getneighborhood(topology, dh.grid, FacetIndex(i, opp[j])))[1]\n", + " end\n", + " end\n", + "\n", + " nbgs[i] = nbg\n", + " end\n", + "\n", + " return nbgs\n", + "end" + ], + "metadata": {}, + "execution_count": 7 + }, + { + "cell_type": "markdown", + "source": [ + "Now we calculate the Laplacian using the previously cached neighboorhood information." + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "approximate_laplacian (generic function with 1 method)" + }, + "metadata": {}, + "execution_count": 8 + } + ], + "cell_type": "code", + "source": [ + "function approximate_laplacian(nbgs, χn, Δh)\n", + " ∇²χ = zeros(length(nbgs))\n", + " for i in 1:length(nbgs)\n", + " nbg = nbgs[i]\n", + " ∇²χ[i] = (χn[nbg[1]] + χn[nbg[2]] + χn[nbg[3]] + χn[nbg[4]] - 4 * χn[i]) / (Δh^2)\n", + " end\n", + "\n", + " return ∇²χ\n", + "end" + ], + "metadata": {}, + "execution_count": 8 + }, + { + "cell_type": "markdown", + "source": [ + "For the iterative computation of the solution, a function is needed to update the densities in each element.\n", + "To ensure that the mass is kept constant, we have to calculate the constraint\n", + "parameter $\\lambda$, which we do via the bisection method. We repeat the calculation\n", + "until the difference between the average density (calculated from the element-wise trial densities) and the target density nearly vanishes.\n", + "By using the extremal values of $\\Delta \\chi$ as the starting interval, we guarantee that the method converges eventually." + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "compute_χn1 (generic function with 1 method)" + }, + "metadata": {}, + "execution_count": 9 + } + ], + "cell_type": "code", + "source": [ + "function compute_χn1(χn, Δχ, ρ, ηs, χ_min)\n", + " n_el = length(χn)\n", + "\n", + " χ_trial = zeros(n_el)\n", + " ρ_trial = 0.0\n", + "\n", + " λ_lower = minimum(Δχ) - ηs\n", + " λ_upper = maximum(Δχ) + ηs\n", + " λ_trial = 0.0\n", + "\n", + " while abs(ρ - ρ_trial) > 1.0e-7\n", + " for i in 1:n_el\n", + " Δχt = 1 / ηs * (Δχ[i] - λ_trial)\n", + " χ_trial[i] = max(χ_min, min(1.0, χn[i] + Δχt))\n", + " end\n", + "\n", + " ρ_trial = 0.0\n", + " for i in 1:n_el\n", + " ρ_trial += χ_trial[i] / n_el\n", + " end\n", + "\n", + " if ρ_trial > ρ\n", + " λ_lower = λ_trial\n", + " elseif ρ_trial < ρ\n", + " λ_upper = λ_trial\n", + " end\n", + " λ_trial = 1 / 2 * (λ_upper + λ_lower)\n", + " end\n", + "\n", + " return χ_trial\n", + "end" + ], + "metadata": {}, + "execution_count": 9 + }, + { + "cell_type": "markdown", + "source": [ + "Lastly, we use the following helper function to compute the average driving force, which is later\n", + "used to normalize the driving forces. This makes the used material parameters and numerical parameters independent\n", + "of the problem." + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "compute_average_driving_force (generic function with 1 method)" + }, + "metadata": {}, + "execution_count": 10 + } + ], + "cell_type": "code", + "source": [ + "function compute_average_driving_force(mp, pΨ, χn)\n", + " n = length(pΨ)\n", + " w = zeros(n)\n", + "\n", + " for i in 1:n\n", + " w[i] = (χn[i] - mp.χ_min) * (1 - χn[i])\n", + " end\n", + "\n", + " p_Ω = sum(w .* pΨ) / sum(w) # average driving force\n", + "\n", + " return p_Ω\n", + "end" + ], + "metadata": {}, + "execution_count": 10 + }, + { + "cell_type": "markdown", + "source": [ + "Finally, we put everything together to update the density. The loop ensures the stability of the\n", + "updated solution." + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "update_density (generic function with 1 method)" + }, + "metadata": {}, + "execution_count": 11 + } + ], + "cell_type": "code", + "source": [ + "function update_density(dh, states, mp, ρ, neighboorhoods, Δh)\n", + " n_j = Int(ceil(6 * mp.β / (mp.η * Δh^2))) # iterations needed for stability\n", + " χn = compute_densities(states, dh) # old density field\n", + " χn1 = zeros(length(χn))\n", + "\n", + " for j in 1:n_j\n", + " ∇²χ = approximate_laplacian(neighboorhoods, χn, Δh) # Laplacian\n", + " pΨ = compute_driving_forces(states, mp, dh, χn) # driving forces\n", + " p_Ω = compute_average_driving_force(mp, pΨ, χn) # average driving force\n", + "\n", + " Δχ = pΨ / p_Ω + mp.β * ∇²χ\n", + "\n", + " χn1 = compute_χn1(χn, Δχ, ρ, mp.η, mp.χ_min)\n", + "\n", + " if j < n_j\n", + " χn[:] = χn1[:]\n", + " end\n", + " end\n", + "\n", + " return χn1\n", + "end" + ], + "metadata": {}, + "execution_count": 11 + }, + { + "cell_type": "markdown", + "source": [ + "Now, we move on to the Finite Element part of the program. We use the following function to assemble our linear system." + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "doassemble! (generic function with 1 method)" + }, + "metadata": {}, + "execution_count": 12 + } + ], + "cell_type": "code", + "source": [ + "function doassemble!(cellvalues::CellValues, facetvalues::FacetValues, K::SparseMatrixCSC, grid::Grid, dh::DofHandler, mp::MaterialParameters, u, states)\n", + " r = zeros(ndofs(dh))\n", + " assembler = start_assemble(K, r)\n", + " nu = getnbasefunctions(cellvalues)\n", + "\n", + " re = zeros(nu) # local residual vector\n", + " Ke = zeros(nu, nu) # local stiffness matrix\n", + "\n", + " for (element, state) in zip(CellIterator(dh), states)\n", + " fill!(Ke, 0)\n", + " fill!(re, 0)\n", + "\n", + " eldofs = celldofs(element)\n", + " ue = u[eldofs]\n", + "\n", + " elmt!(Ke, re, element, cellvalues, facetvalues, grid, mp, ue, state)\n", + " assemble!(assembler, celldofs(element), Ke, re)\n", + " end\n", + "\n", + " return K, r\n", + "end" + ], + "metadata": {}, + "execution_count": 12 + }, + { + "cell_type": "markdown", + "source": [ + "The element routine is used to calculate the elementwise stiffness matrix and the residual. In contrast to a purely\n", + "elastomechanic problem, for topology optimization we additionally use our material state to receive the density value of\n", + "the element and to store the strain at each quadrature point." + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "symmetrize_lower! (generic function with 1 method)" + }, + "metadata": {}, + "execution_count": 13 + } + ], + "cell_type": "code", + "source": [ + "function elmt!(Ke, re, element, cellvalues, facetvalues, grid, mp, ue, state)\n", + " n_basefuncs = getnbasefunctions(cellvalues)\n", + " reinit!(cellvalues, element)\n", + " χ = state.χ\n", + "\n", + " # We only assemble lower half triangle of the stiffness matrix and then symmetrize it.\n", + " @inbounds for q_point in 1:getnquadpoints(cellvalues)\n", + " dΩ = getdetJdV(cellvalues, q_point)\n", + " state.ε[q_point] = function_symmetric_gradient(cellvalues, q_point, ue)\n", + "\n", + " for i in 1:n_basefuncs\n", + " δεi = shape_symmetric_gradient(cellvalues, q_point, i)\n", + " for j in 1:i\n", + " δεj = shape_symmetric_gradient(cellvalues, q_point, j)\n", + " Ke[i, j] += (χ)^(mp.p) * (δεi ⊡ mp.C ⊡ δεj) * dΩ\n", + " end\n", + " re[i] += (-δεi ⊡ ((χ)^(mp.p) * mp.C ⊡ state.ε[q_point])) * dΩ\n", + " end\n", + " end\n", + "\n", + " symmetrize_lower!(Ke)\n", + "\n", + " @inbounds for facet in 1:nfacets(getcells(grid, cellid(element)))\n", + " if (cellid(element), facet) ∈ getfacetset(grid, \"traction\")\n", + " reinit!(facetvalues, element, facet)\n", + " t = Vec((0.0, -1.0)) # force pointing downwards\n", + " for q_point in 1:getnquadpoints(facetvalues)\n", + " dΓ = getdetJdV(facetvalues, q_point)\n", + " for i in 1:n_basefuncs\n", + " δu = shape_value(facetvalues, q_point, i)\n", + " re[i] += (δu ⋅ t) * dΓ\n", + " end\n", + " end\n", + " end\n", + " end\n", + " return\n", + "end\n", + "\n", + "function symmetrize_lower!(K)\n", + " for i in 1:size(K, 1)\n", + " for j in (i + 1):size(K, 1)\n", + " K[i, j] = K[j, i]\n", + " end\n", + " end\n", + " return\n", + "end" + ], + "metadata": {}, + "execution_count": 13 + }, + { + "cell_type": "markdown", + "source": [ + "We put everything together in the main function. Here the user may choose the radius parameter, which\n", + "is related to the regularization parameter as $\\beta = ra^2$, the starting density, the number of elements in vertical direction and finally the\n", + "name of the output. Additionally, the user may choose whether only the final design (default)\n", + "or every iteration step is saved.\n", + "\n", + "First, we compute the material parameters and create the grid, DofHandler, boundary condition and FE values.\n", + "Then we prepare the iterative Newton-Raphson method by pre-allocating all important vectors. Furthermore,\n", + "we create material states for each element and construct the topology of the grid.\n", + "\n", + "During each iteration step, first we solve our FE problem in the Newton-Raphson loop. With the solution of the\n", + "elastomechanic problem, we check for convergence of our topology design. The criteria has to be fulfilled twice in\n", + "a row to avoid oscillations. If no convergence is reached yet, we update our design and prepare the next iteration step.\n", + "Finally, we output the results in paraview and calculate the relative stiffness of the final design, i.e. how much how\n", + "the stiffness increased compared to the starting point." + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "topopt (generic function with 1 method)" + }, + "metadata": {}, + "execution_count": 14 + } + ], + "cell_type": "code", + "source": [ + "function topopt(ra, ρ, n, filename; output = :false)\n", + " # material\n", + " mp = MaterialParameters(210.0e3, 0.3, 1.0e-3, 3.0, ra^2, 15.0)\n", + "\n", + " # grid, dofhandler, boundary condition\n", + " grid = create_grid(n)\n", + " dh = create_dofhandler(grid)\n", + " Δh = 1 / n # element edge length\n", + " dbc = create_bc(dh)\n", + "\n", + " # cellvalues\n", + " cellvalues, facetvalues = create_values()\n", + "\n", + " # Pre-allocate solution vectors, etc.\n", + " n_dofs = ndofs(dh) # total number of dofs\n", + " u = zeros(n_dofs) # solution vector\n", + " un = zeros(n_dofs) # previous solution vector\n", + "\n", + " Δu = zeros(n_dofs) # previous displacement correction\n", + " ΔΔu = zeros(n_dofs) # new displacement correction\n", + "\n", + " # create material states\n", + " states = [MaterialState(ρ, getnquadpoints(cellvalues)) for _ in 1:getncells(dh.grid)]\n", + "\n", + " χ = zeros(getncells(dh.grid))\n", + "\n", + " r = zeros(n_dofs) # residual\n", + " K = allocate_matrix(dh) # stiffness matrix\n", + "\n", + " i_max = 300 ## maximum number of iteration steps\n", + " tol = 1.0e-4\n", + " compliance = 0.0\n", + " compliance_0 = 0.0\n", + " compliance_n = 0.0\n", + " conv = :false\n", + "\n", + " topology = ExclusiveTopology(grid)\n", + " neighboorhoods = cache_neighborhood(dh, topology)\n", + "\n", + " # Newton-Raphson loop\n", + " NEWTON_TOL = 1.0e-8\n", + " print(\"\\n Starting Newton iterations\\n\")\n", + "\n", + " for it in 1:i_max\n", + " apply_zero!(u, dbc)\n", + " newton_itr = -1\n", + "\n", + " while true\n", + " newton_itr += 1\n", + "\n", + " if newton_itr > 10\n", + " error(\"Reached maximum Newton iterations, aborting\")\n", + " break\n", + " end\n", + "\n", + " # current guess\n", + " u .= un .+ Δu\n", + " K, r = doassemble!(cellvalues, facetvalues, K, grid, dh, mp, u, states)\n", + " norm_r = norm(r[Ferrite.free_dofs(dbc)])\n", + "\n", + " if (norm_r) < NEWTON_TOL\n", + " break\n", + " end\n", + "\n", + " apply_zero!(K, r, dbc)\n", + " ΔΔu = Symmetric(K) \\ r\n", + "\n", + " apply_zero!(ΔΔu, dbc)\n", + " Δu .+= ΔΔu\n", + " end # of loop while NR-Iteration\n", + "\n", + " # calculate compliance\n", + " compliance = 1 / 2 * u' * K * u\n", + "\n", + " if it == 1\n", + " compliance_0 = compliance\n", + " end\n", + "\n", + " # check convergence criterium (twice!)\n", + " if abs(compliance - compliance_n) / compliance < tol\n", + " if conv\n", + " println(\"Converged at iteration number: \", it)\n", + " break\n", + " else\n", + " conv = :true\n", + " end\n", + " else\n", + " conv = :false\n", + " end\n", + "\n", + " # update density\n", + " χ = update_density(dh, states, mp, ρ, neighboorhoods, Δh)\n", + "\n", + " # update old displacement, density and compliance\n", + " un .= u\n", + " Δu .= 0.0\n", + " update_material_states!(χ, states, dh)\n", + " compliance_n = compliance\n", + "\n", + " # output during calculation\n", + " if output\n", + " i = @sprintf(\"%3.3i\", it)\n", + " filename_it = string(filename, \"_\", i)\n", + "\n", + " VTKGridFile(filename_it, grid) do vtk\n", + " write_cell_data(vtk, χ, \"density\")\n", + " end\n", + " end\n", + " end\n", + "\n", + " # export converged results\n", + " if !output\n", + " VTKGridFile(filename, grid) do vtk\n", + " write_cell_data(vtk, χ, \"density\")\n", + " end\n", + " end\n", + " @printf \"Rel. stiffness: %.4f \\n\" compliance^(-1) / compliance_0^(-1)\n", + "\n", + " return\n", + "end" + ], + "metadata": {}, + "execution_count": 14 + }, + { + "cell_type": "markdown", + "source": [ + "Lastly, we call our main function and compare the results. To create the\n", + "complete output with all iteration steps, it is possible to set the output\n", + "parameter to `true`." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "grid, χ =topopt(0.02, 0.5, 60, \"small_radius\"; output=:false);" + ], + "metadata": {} + }, + { + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + " Starting Newton iterations\n", + "Converged at iteration number: 65\n", + "Rel. stiffness: 4.8466 \n", + " 4.538163 seconds (2.44 M allocations: 2.015 GiB, 2.12% gc time, 6.46% compilation time)\n" + ] + } + ], + "cell_type": "code", + "source": [ + "@time topopt(0.03, 0.5, 60, \"large_radius\"; output = :false);\n", + "#topopt(0.02, 0.5, 60, \"topopt_animation\"; output=:true); # can be used to create animations" + ], + "metadata": {}, + "execution_count": 15 + }, + { + "cell_type": "markdown", + "source": [ + "We observe, that the stiffness for the lower value of $ra$ is higher,\n", + "but also requires more iterations until convergence and finer structures to be manufactured, as can be seen in Figure 2:\n", + "\n", + "![](bending.png)\n", + "\n", + "*Figure 2*: Optimization results of the bending beam for smaller (left) and larger (right) value of the regularization parameter $\\beta$.\n", + "\n", + "To prove mesh independence, the user could vary the mesh resolution and compare the results." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "---\n", + "\n", + "*This notebook was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).*" + ], + "metadata": {} + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.1" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.1", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR798/gallery/topology_optimization.jl b/previews/PR798/gallery/topology_optimization.jl new file mode 100644 index 0000000000..30252d38b8 --- /dev/null +++ b/previews/PR798/gallery/topology_optimization.jl @@ -0,0 +1,393 @@ +using Ferrite, SparseArrays, LinearAlgebra, Tensors, Printf + +function create_grid(n) + corners = [ + Vec{2}((0.0, 0.0)), + Vec{2}((2.0, 0.0)), + Vec{2}((2.0, 1.0)), + Vec{2}((0.0, 1.0)), + ] + grid = generate_grid(Quadrilateral, (2 * n, n), corners) + + # node-/facesets for boundary conditions + addnodeset!(grid, "clamped", x -> x[1] ≈ 0.0) + addfacetset!(grid, "traction", x -> x[1] ≈ 2.0 && norm(x[2] - 0.5) <= 0.05) + return grid +end + +function create_values() + # quadrature rules + qr = QuadratureRule{RefQuadrilateral}(2) + facet_qr = FacetQuadratureRule{RefQuadrilateral}(2) + + # cell and facetvalues for u + ip = Lagrange{RefQuadrilateral, 1}()^2 + cellvalues = CellValues(qr, ip) + facetvalues = FacetValues(facet_qr, ip) + + return cellvalues, facetvalues +end + +function create_dofhandler(grid) + dh = DofHandler(grid) + add!(dh, :u, Lagrange{RefQuadrilateral, 1}()^2) # displacement + close!(dh) + return dh +end + +function create_bc(dh) + dbc = ConstraintHandler(dh) + add!(dbc, Dirichlet(:u, getnodeset(dh.grid, "clamped"), (x, t) -> zero(Vec{2}), [1, 2])) + close!(dbc) + t = 0.0 + update!(dbc, t) + return dbc +end + +struct MaterialParameters{T, S <: SymmetricTensor{4, 2, T}} + C::S + χ_min::T + p::T + β::T + η::T +end + +function MaterialParameters(E, ν, χ_min, p, β, η) + δ(i, j) = i == j ? 1.0 : 0.0 # helper function + + G = E / 2(1 + ν) # =μ + λ = E * ν / (1 - ν^2) # correction for plane stress included + + C = SymmetricTensor{4, 2}((i, j, k, l) -> λ * δ(i, j) * δ(k, l) + G * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k))) + return MaterialParameters(C, χ_min, p, β, η) +end + +mutable struct MaterialState{T, S <: AbstractArray{SymmetricTensor{2, 2, T, 3}, 1}} + χ::T # density + ε::S # strain in each quadrature point +end + +function MaterialState(ρ, n_qp) + return MaterialState(ρ, Array{SymmetricTensor{2, 2, Float64, 3}, 1}(undef, n_qp)) +end + +function update_material_states!(χn1, states, dh) + for (element, state) in zip(CellIterator(dh), states) + state.χ = χn1[cellid(element)] + end + return +end + +function compute_driving_forces(states, mp, dh, χn) + pΨ = zeros(length(states)) + for (element, state) in zip(CellIterator(dh), states) + i = cellid(element) + ε = sum(state.ε) / length(state.ε) # average element strain + pΨ[i] = 1 / 2 * mp.p * χn[i]^(mp.p - 1) * (ε ⊡ mp.C ⊡ ε) + end + return pΨ +end + +function compute_densities(states, dh) + χn = zeros(length(states)) + for (element, state) in zip(CellIterator(dh), states) + i = cellid(element) + χn[i] = state.χ + end + return χn +end + +function cache_neighborhood(dh, topology) + nbgs = Vector{Vector{Int}}(undef, getncells(dh.grid)) + _nfacets = nfacets(dh.grid.cells[1]) + opp = Dict(1 => 3, 2 => 4, 3 => 1, 4 => 2) + + for element in CellIterator(dh) + nbg = zeros(Int, _nfacets) + i = cellid(element) + for j in 1:_nfacets + nbg_cellid = getneighborhood(topology, dh.grid, FacetIndex(i, j)) + if !isempty(nbg_cellid) + nbg[j] = first(nbg_cellid)[1] # assuming only one facet neighbor per cell + else # boundary facet + nbg[j] = first(getneighborhood(topology, dh.grid, FacetIndex(i, opp[j])))[1] + end + end + + nbgs[i] = nbg + end + + return nbgs +end + +function approximate_laplacian(nbgs, χn, Δh) + ∇²χ = zeros(length(nbgs)) + for i in 1:length(nbgs) + nbg = nbgs[i] + ∇²χ[i] = (χn[nbg[1]] + χn[nbg[2]] + χn[nbg[3]] + χn[nbg[4]] - 4 * χn[i]) / (Δh^2) + end + + return ∇²χ +end + +function compute_χn1(χn, Δχ, ρ, ηs, χ_min) + n_el = length(χn) + + χ_trial = zeros(n_el) + ρ_trial = 0.0 + + λ_lower = minimum(Δχ) - ηs + λ_upper = maximum(Δχ) + ηs + λ_trial = 0.0 + + while abs(ρ - ρ_trial) > 1.0e-7 + for i in 1:n_el + Δχt = 1 / ηs * (Δχ[i] - λ_trial) + χ_trial[i] = max(χ_min, min(1.0, χn[i] + Δχt)) + end + + ρ_trial = 0.0 + for i in 1:n_el + ρ_trial += χ_trial[i] / n_el + end + + if ρ_trial > ρ + λ_lower = λ_trial + elseif ρ_trial < ρ + λ_upper = λ_trial + end + λ_trial = 1 / 2 * (λ_upper + λ_lower) + end + + return χ_trial +end + +function compute_average_driving_force(mp, pΨ, χn) + n = length(pΨ) + w = zeros(n) + + for i in 1:n + w[i] = (χn[i] - mp.χ_min) * (1 - χn[i]) + end + + p_Ω = sum(w .* pΨ) / sum(w) # average driving force + + return p_Ω +end + +function update_density(dh, states, mp, ρ, neighboorhoods, Δh) + n_j = Int(ceil(6 * mp.β / (mp.η * Δh^2))) # iterations needed for stability + χn = compute_densities(states, dh) # old density field + χn1 = zeros(length(χn)) + + for j in 1:n_j + ∇²χ = approximate_laplacian(neighboorhoods, χn, Δh) # Laplacian + pΨ = compute_driving_forces(states, mp, dh, χn) # driving forces + p_Ω = compute_average_driving_force(mp, pΨ, χn) # average driving force + + Δχ = pΨ / p_Ω + mp.β * ∇²χ + + χn1 = compute_χn1(χn, Δχ, ρ, mp.η, mp.χ_min) + + if j < n_j + χn[:] = χn1[:] + end + end + + return χn1 +end + +function doassemble!(cellvalues::CellValues, facetvalues::FacetValues, K::SparseMatrixCSC, grid::Grid, dh::DofHandler, mp::MaterialParameters, u, states) + r = zeros(ndofs(dh)) + assembler = start_assemble(K, r) + nu = getnbasefunctions(cellvalues) + + re = zeros(nu) # local residual vector + Ke = zeros(nu, nu) # local stiffness matrix + + for (element, state) in zip(CellIterator(dh), states) + fill!(Ke, 0) + fill!(re, 0) + + eldofs = celldofs(element) + ue = u[eldofs] + + elmt!(Ke, re, element, cellvalues, facetvalues, grid, mp, ue, state) + assemble!(assembler, celldofs(element), Ke, re) + end + + return K, r +end + +function elmt!(Ke, re, element, cellvalues, facetvalues, grid, mp, ue, state) + n_basefuncs = getnbasefunctions(cellvalues) + reinit!(cellvalues, element) + χ = state.χ + + # We only assemble lower half triangle of the stiffness matrix and then symmetrize it. + @inbounds for q_point in 1:getnquadpoints(cellvalues) + dΩ = getdetJdV(cellvalues, q_point) + state.ε[q_point] = function_symmetric_gradient(cellvalues, q_point, ue) + + for i in 1:n_basefuncs + δεi = shape_symmetric_gradient(cellvalues, q_point, i) + for j in 1:i + δεj = shape_symmetric_gradient(cellvalues, q_point, j) + Ke[i, j] += (χ)^(mp.p) * (δεi ⊡ mp.C ⊡ δεj) * dΩ + end + re[i] += (-δεi ⊡ ((χ)^(mp.p) * mp.C ⊡ state.ε[q_point])) * dΩ + end + end + + symmetrize_lower!(Ke) + + @inbounds for facet in 1:nfacets(getcells(grid, cellid(element))) + if (cellid(element), facet) ∈ getfacetset(grid, "traction") + reinit!(facetvalues, element, facet) + t = Vec((0.0, -1.0)) # force pointing downwards + for q_point in 1:getnquadpoints(facetvalues) + dΓ = getdetJdV(facetvalues, q_point) + for i in 1:n_basefuncs + δu = shape_value(facetvalues, q_point, i) + re[i] += (δu ⋅ t) * dΓ + end + end + end + end + return +end + +function symmetrize_lower!(K) + for i in 1:size(K, 1) + for j in (i + 1):size(K, 1) + K[i, j] = K[j, i] + end + end + return +end + +function topopt(ra, ρ, n, filename; output = :false) + # material + mp = MaterialParameters(210.0e3, 0.3, 1.0e-3, 3.0, ra^2, 15.0) + + # grid, dofhandler, boundary condition + grid = create_grid(n) + dh = create_dofhandler(grid) + Δh = 1 / n # element edge length + dbc = create_bc(dh) + + # cellvalues + cellvalues, facetvalues = create_values() + + # Pre-allocate solution vectors, etc. + n_dofs = ndofs(dh) # total number of dofs + u = zeros(n_dofs) # solution vector + un = zeros(n_dofs) # previous solution vector + + Δu = zeros(n_dofs) # previous displacement correction + ΔΔu = zeros(n_dofs) # new displacement correction + + # create material states + states = [MaterialState(ρ, getnquadpoints(cellvalues)) for _ in 1:getncells(dh.grid)] + + χ = zeros(getncells(dh.grid)) + + r = zeros(n_dofs) # residual + K = allocate_matrix(dh) # stiffness matrix + + i_max = 300 ## maximum number of iteration steps + tol = 1.0e-4 + compliance = 0.0 + compliance_0 = 0.0 + compliance_n = 0.0 + conv = :false + + topology = ExclusiveTopology(grid) + neighboorhoods = cache_neighborhood(dh, topology) + + # Newton-Raphson loop + NEWTON_TOL = 1.0e-8 + print("\n Starting Newton iterations\n") + + for it in 1:i_max + apply_zero!(u, dbc) + newton_itr = -1 + + while true + newton_itr += 1 + + if newton_itr > 10 + error("Reached maximum Newton iterations, aborting") + break + end + + # current guess + u .= un .+ Δu + K, r = doassemble!(cellvalues, facetvalues, K, grid, dh, mp, u, states) + norm_r = norm(r[Ferrite.free_dofs(dbc)]) + + if (norm_r) < NEWTON_TOL + break + end + + apply_zero!(K, r, dbc) + ΔΔu = Symmetric(K) \ r + + apply_zero!(ΔΔu, dbc) + Δu .+= ΔΔu + end # of loop while NR-Iteration + + # calculate compliance + compliance = 1 / 2 * u' * K * u + + if it == 1 + compliance_0 = compliance + end + + # check convergence criterium (twice!) + if abs(compliance - compliance_n) / compliance < tol + if conv + println("Converged at iteration number: ", it) + break + else + conv = :true + end + else + conv = :false + end + + # update density + χ = update_density(dh, states, mp, ρ, neighboorhoods, Δh) + + # update old displacement, density and compliance + un .= u + Δu .= 0.0 + update_material_states!(χ, states, dh) + compliance_n = compliance + + # output during calculation + if output + i = @sprintf("%3.3i", it) + filename_it = string(filename, "_", i) + + VTKGridFile(filename_it, grid) do vtk + write_cell_data(vtk, χ, "density") + end + end + end + + # export converged results + if !output + VTKGridFile(filename, grid) do vtk + write_cell_data(vtk, χ, "density") + end + end + @printf "Rel. stiffness: %.4f \n" compliance^(-1) / compliance_0^(-1) + + return +end + +@time topopt(0.03, 0.5, 60, "large_radius"; output = :false); +#topopt(0.02, 0.5, 60, "topopt_animation"; output=:true); # can be used to create animations + +# This file was generated using Literate.jl, https://github.com/fredrikekre/Literate.jl diff --git a/previews/PR798/gallery/topology_optimization/index.html b/previews/PR798/gallery/topology_optimization/index.html new file mode 100644 index 0000000000..ccf0a89427 --- /dev/null +++ b/previews/PR798/gallery/topology_optimization/index.html @@ -0,0 +1,758 @@ + +Topology optimization · Ferrite.jl

Topology optimization

Keywords: Topology optimization, weak and strong form, non-linear problem, Laplacian, grid topology

Figure 1: Optimization of the bending beam. Evolution of the density for fixed total mass.

Tip

This example is also available as a Jupyter notebook: topology_optimization.ipynb.

Introduction

Topology optimization is the task of finding structures that are mechanically ideal. In this example we cover the bending beam, where we specify a load, boundary conditions and the total mass. Then, our objective is to find the most suitable geometry within the design space minimizing the compliance (i.e. the inverse stiffness) of the structure. We shortly introduce our simplified model for regular meshes. A detailed derivation of the method and advanced techniques can be found in [16] and [17].

We start by introducing the local, elementwise density $\chi \in [\chi_{\text{min}}, 1]$ of the material, where we choose $\chi_{\text{min}}$ slightly above zero to prevent numerical instabilities. Here, $\chi = \chi_{\text{min}}$ means void and $\chi=1$ means bulk material. Then, we use a SIMP ansatz (solid isotropic material with penalization) for the stiffness tensor $C(\chi) = \chi^p C_0$, where $C_0$ is the stiffness of the bulk material. The SIMP exponent $p>1$ ensures that the model prefers the density values void and bulk before the intermediate values. The variational formulation then yields the modified Gibbs energy

\[G = \int_{\Omega} \frac{1}{2} \chi^p \varepsilon : C : \varepsilon \; \text{d}V - \int_{\Omega} \boldsymbol{f} \cdot \boldsymbol{u} \; \text{d}V - \int_{\partial\Omega} \boldsymbol{t} \cdot \boldsymbol{u} \; \text{d}A.\]

Furthermore, we receive the evolution equation of the density and the additional Neumann boundary condition in the strong form

\[p_\chi + \eta \dot{\chi} + \lambda + \gamma - \beta \nabla^2 \chi \ni 0 \quad \forall \textbf{x} \in \Omega,\]

\[\beta \nabla \chi \cdot \textbf{n} = 0 \quad \forall \textbf{x} \in \partial \Omega,\]

with the thermodynamic driving force

\[p_\chi = \frac{1}{2} p \chi^{p-1} \varepsilon : C : \varepsilon.\]

We obtain the mechanical displacement field by applying the Finite Element Method to the weak form of the Gibbs energy using Ferrite. In contrast, we use the evolution equation (i.e. the strong form) to calculate the value of the density field $\chi$. The advantage of this "split" approach is the very high computation speed. The evolution equation consists of the driving force, the damping parameter $\eta$, the regularization parameter $\beta$ times the Laplacian, which is necessary to avoid numerical issues like mesh dependence or checkerboarding, and the constraint parameters $\lambda$, to keep the mass constant, and $\gamma$, to avoid leaving the set $[\chi_{\text{min}}, 1]$. By including gradient regularization, it becomes necessary to calculate the Laplacian. The Finite Difference Method for square meshes with the edge length $\Delta h$ approximates the Laplacian as follows:

\[\nabla^2 \chi_p = \frac{1}{(\Delta h)^2} (\chi_n + \chi_s + \chi_w + \chi_e - 4 \chi_p)\]

Here, the indices refer to the different cardinal directions. Boundary element do not have neighbors in each direction. However, we can calculate the central difference to fulfill Neumann boundary condition. For example, if the element is on the left boundary, we have to fulfill

\[\nabla \chi_p \cdot \textbf{n} = \frac{1}{\Delta h} (\chi_w - \chi_e) = 0\]

from which follows $\chi_w = \chi_e$. Thus for boundary elements we can replace the value for the missing neighbor by the value of the opposite neighbor. In order to find the corresponding neighbor elements, we will make use of Ferrites grid topology funcionalities.

Commented Program

We now solve the problem in Ferrite. What follows is a program spliced with comments. The full program, without comments, can be found in the next section.

First we load all necessary packages.

using Ferrite, SparseArrays, LinearAlgebra, Tensors, Printf

Next, we create a simple square grid of the size 2x1. We apply a fixed Dirichlet boundary condition to the left facet set, called clamped. On the right facet, we create a small set traction, where we will later apply a force in negative y-direction.

function create_grid(n)
+    corners = [
+        Vec{2}((0.0, 0.0)),
+        Vec{2}((2.0, 0.0)),
+        Vec{2}((2.0, 1.0)),
+        Vec{2}((0.0, 1.0)),
+    ]
+    grid = generate_grid(Quadrilateral, (2 * n, n), corners)
+
+    # node-/facesets for boundary conditions
+    addnodeset!(grid, "clamped", x -> x[1] ≈ 0.0)
+    addfacetset!(grid, "traction", x -> x[1] ≈ 2.0 && norm(x[2] - 0.5) <= 0.05)
+    return grid
+end

Next, we create the FE values, the DofHandler and the Dirichlet boundary condition.

function create_values()
+    # quadrature rules
+    qr = QuadratureRule{RefQuadrilateral}(2)
+    facet_qr = FacetQuadratureRule{RefQuadrilateral}(2)
+
+    # cell and facetvalues for u
+    ip = Lagrange{RefQuadrilateral, 1}()^2
+    cellvalues = CellValues(qr, ip)
+    facetvalues = FacetValues(facet_qr, ip)
+
+    return cellvalues, facetvalues
+end
+
+function create_dofhandler(grid)
+    dh = DofHandler(grid)
+    add!(dh, :u, Lagrange{RefQuadrilateral, 1}()^2) # displacement
+    close!(dh)
+    return dh
+end
+
+function create_bc(dh)
+    dbc = ConstraintHandler(dh)
+    add!(dbc, Dirichlet(:u, getnodeset(dh.grid, "clamped"), (x, t) -> zero(Vec{2}), [1, 2]))
+    close!(dbc)
+    t = 0.0
+    update!(dbc, t)
+    return dbc
+end

Now, we define a struct to store all necessary material parameters (stiffness tensor of the bulk material and the parameters for topology optimization) and add a constructor to the struct to initialize it by using the common material parameters Young's modulus and Poisson number.

struct MaterialParameters{T, S <: SymmetricTensor{4, 2, T}}
+    C::S
+    χ_min::T
+    p::T
+    β::T
+    η::T
+end
+
+function MaterialParameters(E, ν, χ_min, p, β, η)
+    δ(i, j) = i == j ? 1.0 : 0.0 # helper function
+
+    G = E / 2(1 + ν) # =μ
+    λ = E * ν / (1 - ν^2) # correction for plane stress included
+
+    C = SymmetricTensor{4, 2}((i, j, k, l) -> λ * δ(i, j) * δ(k, l) + G * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k)))
+    return MaterialParameters(C, χ_min, p, β, η)
+end

To store the density and the strain required to calculate the driving forces, we create the struct MaterialState. We add a constructor to initialize the struct. The function update_material_states! updates the density values once we calculated the new values.

mutable struct MaterialState{T, S <: AbstractArray{SymmetricTensor{2, 2, T, 3}, 1}}
+    χ::T # density
+    ε::S # strain in each quadrature point
+end
+
+function MaterialState(ρ, n_qp)
+    return MaterialState(ρ, Array{SymmetricTensor{2, 2, Float64, 3}, 1}(undef, n_qp))
+end
+
+function update_material_states!(χn1, states, dh)
+    for (element, state) in zip(CellIterator(dh), states)
+        state.χ = χn1[cellid(element)]
+    end
+    return
+end

Next, we define a function to calculate the driving forces for all elements. For this purpose, we iterate through all elements and calculate the average strain in each element. Then, we compute the driving force from the formula introduced at the beginning. We create a second function to collect the density in each element.

function compute_driving_forces(states, mp, dh, χn)
+    pΨ = zeros(length(states))
+    for (element, state) in zip(CellIterator(dh), states)
+        i = cellid(element)
+        ε = sum(state.ε) / length(state.ε) # average element strain
+        pΨ[i] = 1 / 2 * mp.p * χn[i]^(mp.p - 1) * (ε ⊡ mp.C ⊡ ε)
+    end
+    return pΨ
+end
+
+function compute_densities(states, dh)
+    χn = zeros(length(states))
+    for (element, state) in zip(CellIterator(dh), states)
+        i = cellid(element)
+        χn[i] = state.χ
+    end
+    return χn
+end

For the Laplacian we need some neighboorhood information which is constant throughout the analysis so we compute it once and cache it. We iterate through each facet of each element, obtaining the neighboring element by using the getneighborhood function. For boundary facets, the function call will return an empty object. In that case we use the dictionary to instead find the opposite facet, as discussed in the introduction.

function cache_neighborhood(dh, topology)
+    nbgs = Vector{Vector{Int}}(undef, getncells(dh.grid))
+    _nfacets = nfacets(dh.grid.cells[1])
+    opp = Dict(1 => 3, 2 => 4, 3 => 1, 4 => 2)
+
+    for element in CellIterator(dh)
+        nbg = zeros(Int, _nfacets)
+        i = cellid(element)
+        for j in 1:_nfacets
+            nbg_cellid = getneighborhood(topology, dh.grid, FacetIndex(i, j))
+            if !isempty(nbg_cellid)
+                nbg[j] = first(nbg_cellid)[1] # assuming only one facet neighbor per cell
+            else # boundary facet
+                nbg[j] = first(getneighborhood(topology, dh.grid, FacetIndex(i, opp[j])))[1]
+            end
+        end
+
+        nbgs[i] = nbg
+    end
+
+    return nbgs
+end

Now we calculate the Laplacian using the previously cached neighboorhood information.

function approximate_laplacian(nbgs, χn, Δh)
+    ∇²χ = zeros(length(nbgs))
+    for i in 1:length(nbgs)
+        nbg = nbgs[i]
+        ∇²χ[i] = (χn[nbg[1]] + χn[nbg[2]] + χn[nbg[3]] + χn[nbg[4]] - 4 * χn[i]) / (Δh^2)
+    end
+
+    return ∇²χ
+end

For the iterative computation of the solution, a function is needed to update the densities in each element. To ensure that the mass is kept constant, we have to calculate the constraint parameter $\lambda$, which we do via the bisection method. We repeat the calculation until the difference between the average density (calculated from the element-wise trial densities) and the target density nearly vanishes. By using the extremal values of $\Delta \chi$ as the starting interval, we guarantee that the method converges eventually.

function compute_χn1(χn, Δχ, ρ, ηs, χ_min)
+    n_el = length(χn)
+
+    χ_trial = zeros(n_el)
+    ρ_trial = 0.0
+
+    λ_lower = minimum(Δχ) - ηs
+    λ_upper = maximum(Δχ) + ηs
+    λ_trial = 0.0
+
+    while abs(ρ - ρ_trial) > 1.0e-7
+        for i in 1:n_el
+            Δχt = 1 / ηs * (Δχ[i] - λ_trial)
+            χ_trial[i] = max(χ_min, min(1.0, χn[i] + Δχt))
+        end
+
+        ρ_trial = 0.0
+        for i in 1:n_el
+            ρ_trial += χ_trial[i] / n_el
+        end
+
+        if ρ_trial > ρ
+            λ_lower = λ_trial
+        elseif ρ_trial < ρ
+            λ_upper = λ_trial
+        end
+        λ_trial = 1 / 2 * (λ_upper + λ_lower)
+    end
+
+    return χ_trial
+end

Lastly, we use the following helper function to compute the average driving force, which is later used to normalize the driving forces. This makes the used material parameters and numerical parameters independent of the problem.

function compute_average_driving_force(mp, pΨ, χn)
+    n = length(pΨ)
+    w = zeros(n)
+
+    for i in 1:n
+        w[i] = (χn[i] - mp.χ_min) * (1 - χn[i])
+    end
+
+    p_Ω = sum(w .* pΨ) / sum(w) # average driving force
+
+    return p_Ω
+end

Finally, we put everything together to update the density. The loop ensures the stability of the updated solution.

function update_density(dh, states, mp, ρ, neighboorhoods, Δh)
+    n_j = Int(ceil(6 * mp.β / (mp.η * Δh^2))) # iterations needed for stability
+    χn = compute_densities(states, dh) # old density field
+    χn1 = zeros(length(χn))
+
+    for j in 1:n_j
+        ∇²χ = approximate_laplacian(neighboorhoods, χn, Δh) # Laplacian
+        pΨ = compute_driving_forces(states, mp, dh, χn) # driving forces
+        p_Ω = compute_average_driving_force(mp, pΨ, χn) # average driving force
+
+        Δχ = pΨ / p_Ω + mp.β * ∇²χ
+
+        χn1 = compute_χn1(χn, Δχ, ρ, mp.η, mp.χ_min)
+
+        if j < n_j
+            χn[:] = χn1[:]
+        end
+    end
+
+    return χn1
+end

Now, we move on to the Finite Element part of the program. We use the following function to assemble our linear system.

function doassemble!(cellvalues::CellValues, facetvalues::FacetValues, K::SparseMatrixCSC, grid::Grid, dh::DofHandler, mp::MaterialParameters, u, states)
+    r = zeros(ndofs(dh))
+    assembler = start_assemble(K, r)
+    nu = getnbasefunctions(cellvalues)
+
+    re = zeros(nu) # local residual vector
+    Ke = zeros(nu, nu) # local stiffness matrix
+
+    for (element, state) in zip(CellIterator(dh), states)
+        fill!(Ke, 0)
+        fill!(re, 0)
+
+        eldofs = celldofs(element)
+        ue = u[eldofs]
+
+        elmt!(Ke, re, element, cellvalues, facetvalues, grid, mp, ue, state)
+        assemble!(assembler, celldofs(element), Ke, re)
+    end
+
+    return K, r
+end

The element routine is used to calculate the elementwise stiffness matrix and the residual. In contrast to a purely elastomechanic problem, for topology optimization we additionally use our material state to receive the density value of the element and to store the strain at each quadrature point.

function elmt!(Ke, re, element, cellvalues, facetvalues, grid, mp, ue, state)
+    n_basefuncs = getnbasefunctions(cellvalues)
+    reinit!(cellvalues, element)
+    χ = state.χ
+
+    # We only assemble lower half triangle of the stiffness matrix and then symmetrize it.
+    @inbounds for q_point in 1:getnquadpoints(cellvalues)
+        dΩ = getdetJdV(cellvalues, q_point)
+        state.ε[q_point] = function_symmetric_gradient(cellvalues, q_point, ue)
+
+        for i in 1:n_basefuncs
+            δεi = shape_symmetric_gradient(cellvalues, q_point, i)
+            for j in 1:i
+                δεj = shape_symmetric_gradient(cellvalues, q_point, j)
+                Ke[i, j] += (χ)^(mp.p) * (δεi ⊡ mp.C ⊡ δεj) * dΩ
+            end
+            re[i] += (-δεi ⊡ ((χ)^(mp.p) * mp.C ⊡ state.ε[q_point])) * dΩ
+        end
+    end
+
+    symmetrize_lower!(Ke)
+
+    @inbounds for facet in 1:nfacets(getcells(grid, cellid(element)))
+        if (cellid(element), facet) ∈ getfacetset(grid, "traction")
+            reinit!(facetvalues, element, facet)
+            t = Vec((0.0, -1.0)) # force pointing downwards
+            for q_point in 1:getnquadpoints(facetvalues)
+                dΓ = getdetJdV(facetvalues, q_point)
+                for i in 1:n_basefuncs
+                    δu = shape_value(facetvalues, q_point, i)
+                    re[i] += (δu ⋅ t) * dΓ
+                end
+            end
+        end
+    end
+    return
+end
+
+function symmetrize_lower!(K)
+    for i in 1:size(K, 1)
+        for j in (i + 1):size(K, 1)
+            K[i, j] = K[j, i]
+        end
+    end
+    return
+end

We put everything together in the main function. Here the user may choose the radius parameter, which is related to the regularization parameter as $\beta = ra^2$, the starting density, the number of elements in vertical direction and finally the name of the output. Additionally, the user may choose whether only the final design (default) or every iteration step is saved.

First, we compute the material parameters and create the grid, DofHandler, boundary condition and FE values. Then we prepare the iterative Newton-Raphson method by pre-allocating all important vectors. Furthermore, we create material states for each element and construct the topology of the grid.

During each iteration step, first we solve our FE problem in the Newton-Raphson loop. With the solution of the elastomechanic problem, we check for convergence of our topology design. The criteria has to be fulfilled twice in a row to avoid oscillations. If no convergence is reached yet, we update our design and prepare the next iteration step. Finally, we output the results in paraview and calculate the relative stiffness of the final design, i.e. how much how the stiffness increased compared to the starting point.

function topopt(ra, ρ, n, filename; output = :false)
+    # material
+    mp = MaterialParameters(210.0e3, 0.3, 1.0e-3, 3.0, ra^2, 15.0)
+
+    # grid, dofhandler, boundary condition
+    grid = create_grid(n)
+    dh = create_dofhandler(grid)
+    Δh = 1 / n # element edge length
+    dbc = create_bc(dh)
+
+    # cellvalues
+    cellvalues, facetvalues = create_values()
+
+    # Pre-allocate solution vectors, etc.
+    n_dofs = ndofs(dh) # total number of dofs
+    u = zeros(n_dofs) # solution vector
+    un = zeros(n_dofs) # previous solution vector
+
+    Δu = zeros(n_dofs)  # previous displacement correction
+    ΔΔu = zeros(n_dofs) # new displacement correction
+
+    # create material states
+    states = [MaterialState(ρ, getnquadpoints(cellvalues)) for _ in 1:getncells(dh.grid)]
+
+    χ = zeros(getncells(dh.grid))
+
+    r = zeros(n_dofs) # residual
+    K = allocate_matrix(dh) # stiffness matrix
+
+    i_max = 300 ## maximum number of iteration steps
+    tol = 1.0e-4
+    compliance = 0.0
+    compliance_0 = 0.0
+    compliance_n = 0.0
+    conv = :false
+
+    topology = ExclusiveTopology(grid)
+    neighboorhoods = cache_neighborhood(dh, topology)
+
+    # Newton-Raphson loop
+    NEWTON_TOL = 1.0e-8
+    print("\n Starting Newton iterations\n")
+
+    for it in 1:i_max
+        apply_zero!(u, dbc)
+        newton_itr = -1
+
+        while true
+            newton_itr += 1
+
+            if newton_itr > 10
+                error("Reached maximum Newton iterations, aborting")
+                break
+            end
+
+            # current guess
+            u .= un .+ Δu
+            K, r = doassemble!(cellvalues, facetvalues, K, grid, dh, mp, u, states)
+            norm_r = norm(r[Ferrite.free_dofs(dbc)])
+
+            if (norm_r) < NEWTON_TOL
+                break
+            end
+
+            apply_zero!(K, r, dbc)
+            ΔΔu = Symmetric(K) \ r
+
+            apply_zero!(ΔΔu, dbc)
+            Δu .+= ΔΔu
+        end # of loop while NR-Iteration
+
+        # calculate compliance
+        compliance = 1 / 2 * u' * K * u
+
+        if it == 1
+            compliance_0 = compliance
+        end
+
+        # check convergence criterium (twice!)
+        if abs(compliance - compliance_n) / compliance < tol
+            if conv
+                println("Converged at iteration number: ", it)
+                break
+            else
+                conv = :true
+            end
+        else
+            conv = :false
+        end
+
+        # update density
+        χ = update_density(dh, states, mp, ρ, neighboorhoods, Δh)
+
+        # update old displacement, density and compliance
+        un .= u
+        Δu .= 0.0
+        update_material_states!(χ, states, dh)
+        compliance_n = compliance
+
+        # output during calculation
+        if output
+            i = @sprintf("%3.3i", it)
+            filename_it = string(filename, "_", i)
+
+            VTKGridFile(filename_it, grid) do vtk
+                write_cell_data(vtk, χ, "density")
+            end
+        end
+    end
+
+    # export converged results
+    if !output
+        VTKGridFile(filename, grid) do vtk
+            write_cell_data(vtk, χ, "density")
+        end
+    end
+    @printf "Rel. stiffness: %.4f \n" compliance^(-1) / compliance_0^(-1)
+
+    return
+end

Lastly, we call our main function and compare the results. To create the complete output with all iteration steps, it is possible to set the output parameter to true.

grid, χ =topopt(0.02, 0.5, 60, "small_radius"; output=:false);

@time topopt(0.03, 0.5, 60, "large_radius"; output = :false);
+#topopt(0.02, 0.5, 60, "topopt_animation"; output=:true); # can be used to create animations

+ Starting Newton iterations
+Converged at iteration number: 65
+Rel. stiffness: 4.8466
+  4.431934 seconds (2.24 M allocations: 2.005 GiB, 2.36% gc time, 1.86% compilation time)

We observe, that the stiffness for the lower value of $ra$ is higher, but also requires more iterations until convergence and finer structures to be manufactured, as can be seen in Figure 2:

Figure 2: Optimization results of the bending beam for smaller (left) and larger (right) value of the regularization parameter $\beta$.

To prove mesh independence, the user could vary the mesh resolution and compare the results.

References

Plain program

Here follows a version of the program without any comments. The file is also available here: topology_optimization.jl.

using Ferrite, SparseArrays, LinearAlgebra, Tensors, Printf
+
+function create_grid(n)
+    corners = [
+        Vec{2}((0.0, 0.0)),
+        Vec{2}((2.0, 0.0)),
+        Vec{2}((2.0, 1.0)),
+        Vec{2}((0.0, 1.0)),
+    ]
+    grid = generate_grid(Quadrilateral, (2 * n, n), corners)
+
+    # node-/facesets for boundary conditions
+    addnodeset!(grid, "clamped", x -> x[1] ≈ 0.0)
+    addfacetset!(grid, "traction", x -> x[1] ≈ 2.0 && norm(x[2] - 0.5) <= 0.05)
+    return grid
+end
+
+function create_values()
+    # quadrature rules
+    qr = QuadratureRule{RefQuadrilateral}(2)
+    facet_qr = FacetQuadratureRule{RefQuadrilateral}(2)
+
+    # cell and facetvalues for u
+    ip = Lagrange{RefQuadrilateral, 1}()^2
+    cellvalues = CellValues(qr, ip)
+    facetvalues = FacetValues(facet_qr, ip)
+
+    return cellvalues, facetvalues
+end
+
+function create_dofhandler(grid)
+    dh = DofHandler(grid)
+    add!(dh, :u, Lagrange{RefQuadrilateral, 1}()^2) # displacement
+    close!(dh)
+    return dh
+end
+
+function create_bc(dh)
+    dbc = ConstraintHandler(dh)
+    add!(dbc, Dirichlet(:u, getnodeset(dh.grid, "clamped"), (x, t) -> zero(Vec{2}), [1, 2]))
+    close!(dbc)
+    t = 0.0
+    update!(dbc, t)
+    return dbc
+end
+
+struct MaterialParameters{T, S <: SymmetricTensor{4, 2, T}}
+    C::S
+    χ_min::T
+    p::T
+    β::T
+    η::T
+end
+
+function MaterialParameters(E, ν, χ_min, p, β, η)
+    δ(i, j) = i == j ? 1.0 : 0.0 # helper function
+
+    G = E / 2(1 + ν) # =μ
+    λ = E * ν / (1 - ν^2) # correction for plane stress included
+
+    C = SymmetricTensor{4, 2}((i, j, k, l) -> λ * δ(i, j) * δ(k, l) + G * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k)))
+    return MaterialParameters(C, χ_min, p, β, η)
+end
+
+mutable struct MaterialState{T, S <: AbstractArray{SymmetricTensor{2, 2, T, 3}, 1}}
+    χ::T # density
+    ε::S # strain in each quadrature point
+end
+
+function MaterialState(ρ, n_qp)
+    return MaterialState(ρ, Array{SymmetricTensor{2, 2, Float64, 3}, 1}(undef, n_qp))
+end
+
+function update_material_states!(χn1, states, dh)
+    for (element, state) in zip(CellIterator(dh), states)
+        state.χ = χn1[cellid(element)]
+    end
+    return
+end
+
+function compute_driving_forces(states, mp, dh, χn)
+    pΨ = zeros(length(states))
+    for (element, state) in zip(CellIterator(dh), states)
+        i = cellid(element)
+        ε = sum(state.ε) / length(state.ε) # average element strain
+        pΨ[i] = 1 / 2 * mp.p * χn[i]^(mp.p - 1) * (ε ⊡ mp.C ⊡ ε)
+    end
+    return pΨ
+end
+
+function compute_densities(states, dh)
+    χn = zeros(length(states))
+    for (element, state) in zip(CellIterator(dh), states)
+        i = cellid(element)
+        χn[i] = state.χ
+    end
+    return χn
+end
+
+function cache_neighborhood(dh, topology)
+    nbgs = Vector{Vector{Int}}(undef, getncells(dh.grid))
+    _nfacets = nfacets(dh.grid.cells[1])
+    opp = Dict(1 => 3, 2 => 4, 3 => 1, 4 => 2)
+
+    for element in CellIterator(dh)
+        nbg = zeros(Int, _nfacets)
+        i = cellid(element)
+        for j in 1:_nfacets
+            nbg_cellid = getneighborhood(topology, dh.grid, FacetIndex(i, j))
+            if !isempty(nbg_cellid)
+                nbg[j] = first(nbg_cellid)[1] # assuming only one facet neighbor per cell
+            else # boundary facet
+                nbg[j] = first(getneighborhood(topology, dh.grid, FacetIndex(i, opp[j])))[1]
+            end
+        end
+
+        nbgs[i] = nbg
+    end
+
+    return nbgs
+end
+
+function approximate_laplacian(nbgs, χn, Δh)
+    ∇²χ = zeros(length(nbgs))
+    for i in 1:length(nbgs)
+        nbg = nbgs[i]
+        ∇²χ[i] = (χn[nbg[1]] + χn[nbg[2]] + χn[nbg[3]] + χn[nbg[4]] - 4 * χn[i]) / (Δh^2)
+    end
+
+    return ∇²χ
+end
+
+function compute_χn1(χn, Δχ, ρ, ηs, χ_min)
+    n_el = length(χn)
+
+    χ_trial = zeros(n_el)
+    ρ_trial = 0.0
+
+    λ_lower = minimum(Δχ) - ηs
+    λ_upper = maximum(Δχ) + ηs
+    λ_trial = 0.0
+
+    while abs(ρ - ρ_trial) > 1.0e-7
+        for i in 1:n_el
+            Δχt = 1 / ηs * (Δχ[i] - λ_trial)
+            χ_trial[i] = max(χ_min, min(1.0, χn[i] + Δχt))
+        end
+
+        ρ_trial = 0.0
+        for i in 1:n_el
+            ρ_trial += χ_trial[i] / n_el
+        end
+
+        if ρ_trial > ρ
+            λ_lower = λ_trial
+        elseif ρ_trial < ρ
+            λ_upper = λ_trial
+        end
+        λ_trial = 1 / 2 * (λ_upper + λ_lower)
+    end
+
+    return χ_trial
+end
+
+function compute_average_driving_force(mp, pΨ, χn)
+    n = length(pΨ)
+    w = zeros(n)
+
+    for i in 1:n
+        w[i] = (χn[i] - mp.χ_min) * (1 - χn[i])
+    end
+
+    p_Ω = sum(w .* pΨ) / sum(w) # average driving force
+
+    return p_Ω
+end
+
+function update_density(dh, states, mp, ρ, neighboorhoods, Δh)
+    n_j = Int(ceil(6 * mp.β / (mp.η * Δh^2))) # iterations needed for stability
+    χn = compute_densities(states, dh) # old density field
+    χn1 = zeros(length(χn))
+
+    for j in 1:n_j
+        ∇²χ = approximate_laplacian(neighboorhoods, χn, Δh) # Laplacian
+        pΨ = compute_driving_forces(states, mp, dh, χn) # driving forces
+        p_Ω = compute_average_driving_force(mp, pΨ, χn) # average driving force
+
+        Δχ = pΨ / p_Ω + mp.β * ∇²χ
+
+        χn1 = compute_χn1(χn, Δχ, ρ, mp.η, mp.χ_min)
+
+        if j < n_j
+            χn[:] = χn1[:]
+        end
+    end
+
+    return χn1
+end
+
+function doassemble!(cellvalues::CellValues, facetvalues::FacetValues, K::SparseMatrixCSC, grid::Grid, dh::DofHandler, mp::MaterialParameters, u, states)
+    r = zeros(ndofs(dh))
+    assembler = start_assemble(K, r)
+    nu = getnbasefunctions(cellvalues)
+
+    re = zeros(nu) # local residual vector
+    Ke = zeros(nu, nu) # local stiffness matrix
+
+    for (element, state) in zip(CellIterator(dh), states)
+        fill!(Ke, 0)
+        fill!(re, 0)
+
+        eldofs = celldofs(element)
+        ue = u[eldofs]
+
+        elmt!(Ke, re, element, cellvalues, facetvalues, grid, mp, ue, state)
+        assemble!(assembler, celldofs(element), Ke, re)
+    end
+
+    return K, r
+end
+
+function elmt!(Ke, re, element, cellvalues, facetvalues, grid, mp, ue, state)
+    n_basefuncs = getnbasefunctions(cellvalues)
+    reinit!(cellvalues, element)
+    χ = state.χ
+
+    # We only assemble lower half triangle of the stiffness matrix and then symmetrize it.
+    @inbounds for q_point in 1:getnquadpoints(cellvalues)
+        dΩ = getdetJdV(cellvalues, q_point)
+        state.ε[q_point] = function_symmetric_gradient(cellvalues, q_point, ue)
+
+        for i in 1:n_basefuncs
+            δεi = shape_symmetric_gradient(cellvalues, q_point, i)
+            for j in 1:i
+                δεj = shape_symmetric_gradient(cellvalues, q_point, j)
+                Ke[i, j] += (χ)^(mp.p) * (δεi ⊡ mp.C ⊡ δεj) * dΩ
+            end
+            re[i] += (-δεi ⊡ ((χ)^(mp.p) * mp.C ⊡ state.ε[q_point])) * dΩ
+        end
+    end
+
+    symmetrize_lower!(Ke)
+
+    @inbounds for facet in 1:nfacets(getcells(grid, cellid(element)))
+        if (cellid(element), facet) ∈ getfacetset(grid, "traction")
+            reinit!(facetvalues, element, facet)
+            t = Vec((0.0, -1.0)) # force pointing downwards
+            for q_point in 1:getnquadpoints(facetvalues)
+                dΓ = getdetJdV(facetvalues, q_point)
+                for i in 1:n_basefuncs
+                    δu = shape_value(facetvalues, q_point, i)
+                    re[i] += (δu ⋅ t) * dΓ
+                end
+            end
+        end
+    end
+    return
+end
+
+function symmetrize_lower!(K)
+    for i in 1:size(K, 1)
+        for j in (i + 1):size(K, 1)
+            K[i, j] = K[j, i]
+        end
+    end
+    return
+end
+
+function topopt(ra, ρ, n, filename; output = :false)
+    # material
+    mp = MaterialParameters(210.0e3, 0.3, 1.0e-3, 3.0, ra^2, 15.0)
+
+    # grid, dofhandler, boundary condition
+    grid = create_grid(n)
+    dh = create_dofhandler(grid)
+    Δh = 1 / n # element edge length
+    dbc = create_bc(dh)
+
+    # cellvalues
+    cellvalues, facetvalues = create_values()
+
+    # Pre-allocate solution vectors, etc.
+    n_dofs = ndofs(dh) # total number of dofs
+    u = zeros(n_dofs) # solution vector
+    un = zeros(n_dofs) # previous solution vector
+
+    Δu = zeros(n_dofs)  # previous displacement correction
+    ΔΔu = zeros(n_dofs) # new displacement correction
+
+    # create material states
+    states = [MaterialState(ρ, getnquadpoints(cellvalues)) for _ in 1:getncells(dh.grid)]
+
+    χ = zeros(getncells(dh.grid))
+
+    r = zeros(n_dofs) # residual
+    K = allocate_matrix(dh) # stiffness matrix
+
+    i_max = 300 ## maximum number of iteration steps
+    tol = 1.0e-4
+    compliance = 0.0
+    compliance_0 = 0.0
+    compliance_n = 0.0
+    conv = :false
+
+    topology = ExclusiveTopology(grid)
+    neighboorhoods = cache_neighborhood(dh, topology)
+
+    # Newton-Raphson loop
+    NEWTON_TOL = 1.0e-8
+    print("\n Starting Newton iterations\n")
+
+    for it in 1:i_max
+        apply_zero!(u, dbc)
+        newton_itr = -1
+
+        while true
+            newton_itr += 1
+
+            if newton_itr > 10
+                error("Reached maximum Newton iterations, aborting")
+                break
+            end
+
+            # current guess
+            u .= un .+ Δu
+            K, r = doassemble!(cellvalues, facetvalues, K, grid, dh, mp, u, states)
+            norm_r = norm(r[Ferrite.free_dofs(dbc)])
+
+            if (norm_r) < NEWTON_TOL
+                break
+            end
+
+            apply_zero!(K, r, dbc)
+            ΔΔu = Symmetric(K) \ r
+
+            apply_zero!(ΔΔu, dbc)
+            Δu .+= ΔΔu
+        end # of loop while NR-Iteration
+
+        # calculate compliance
+        compliance = 1 / 2 * u' * K * u
+
+        if it == 1
+            compliance_0 = compliance
+        end
+
+        # check convergence criterium (twice!)
+        if abs(compliance - compliance_n) / compliance < tol
+            if conv
+                println("Converged at iteration number: ", it)
+                break
+            else
+                conv = :true
+            end
+        else
+            conv = :false
+        end
+
+        # update density
+        χ = update_density(dh, states, mp, ρ, neighboorhoods, Δh)
+
+        # update old displacement, density and compliance
+        un .= u
+        Δu .= 0.0
+        update_material_states!(χ, states, dh)
+        compliance_n = compliance
+
+        # output during calculation
+        if output
+            i = @sprintf("%3.3i", it)
+            filename_it = string(filename, "_", i)
+
+            VTKGridFile(filename_it, grid) do vtk
+                write_cell_data(vtk, χ, "density")
+            end
+        end
+    end
+
+    # export converged results
+    if !output
+        VTKGridFile(filename, grid) do vtk
+            write_cell_data(vtk, χ, "density")
+        end
+    end
+    @printf "Rel. stiffness: %.4f \n" compliance^(-1) / compliance_0^(-1)
+
+    return
+end
+
+@time topopt(0.03, 0.5, 60, "large_radius"; output = :false);
+#topopt(0.02, 0.5, 60, "topopt_animation"; output=:true); # can be used to create animations

This page was generated using Literate.jl.

diff --git a/previews/PR798/howto/colored.vtu b/previews/PR798/howto/colored.vtu new file mode 100644 index 0000000000..3e0ad85af2 Binary files /dev/null and b/previews/PR798/howto/colored.vtu differ diff --git a/previews/PR798/howto/coloring.png b/previews/PR798/howto/coloring.png new file mode 100644 index 0000000000..534528bba2 Binary files /dev/null and b/previews/PR798/howto/coloring.png differ diff --git a/previews/PR798/howto/heat_equation.vtu b/previews/PR798/howto/heat_equation.vtu new file mode 100644 index 0000000000..d743617ec3 Binary files /dev/null and b/previews/PR798/howto/heat_equation.vtu differ diff --git a/previews/PR798/howto/heat_equation_flux.vtu b/previews/PR798/howto/heat_equation_flux.vtu new file mode 100644 index 0000000000..2bc9e5550e Binary files /dev/null and b/previews/PR798/howto/heat_equation_flux.vtu differ diff --git a/previews/PR798/howto/heat_square_fluxes.png b/previews/PR798/howto/heat_square_fluxes.png new file mode 100644 index 0000000000..5bc6cb5b6c Binary files /dev/null and b/previews/PR798/howto/heat_square_fluxes.png differ diff --git a/previews/PR798/howto/heat_square_pointevaluation.png b/previews/PR798/howto/heat_square_pointevaluation.png new file mode 100644 index 0000000000..057c9bf551 Binary files /dev/null and b/previews/PR798/howto/heat_square_pointevaluation.png differ diff --git a/previews/PR798/howto/index.html b/previews/PR798/howto/index.html new file mode 100644 index 0000000000..e471477667 --- /dev/null +++ b/previews/PR798/howto/index.html @@ -0,0 +1,2 @@ + +How-to guide overview · Ferrite.jl

How-to guides

This page gives an overview of the how-to guides. How-to guides address various common tasks one might want to do in a finite element program. Many of the guides are extensions, or build on top of, the tutorials and, therefore, some familiarity with Ferrite is assumed.


Post processing and visualization

This guide builds on top of Tutorial 1: Heat equation and discusses various post processsing techniques with the goal of visualizing primary fields (the finite element solution) and secondary quantities (e.g. fluxes, stresses, etc.). Concretely, this guide answers:

  • How to visualize data from quadrature points?
  • How to evaluate the finite element solution, or secondary quantities, in arbitrary points of the domain?

Multi-threaded assembly

This guide modifies Tutorial 2: Linear elasticity such that the program is using multi-threading to parallelize the assembly procedure. Concretely this shows how to use grid coloring and "scratch values" in order to use multi-threading without running into race-conditions.


diff --git a/previews/PR798/howto/postprocessing.ipynb b/previews/PR798/howto/postprocessing.ipynb new file mode 100644 index 0000000000..f4be660389 --- /dev/null +++ b/previews/PR798/howto/postprocessing.ipynb @@ -0,0 +1,569 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Post processing and visualization\n", + "\n", + "![](heat_square_fluxes.png)\n", + "\n", + "*Figure 1*: Heat flux computed from the solution to the heat equation on\n", + "the unit square, see previous example: Heat equation." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Introduction\n", + "\n", + "After running a simulation, we usually want to visualize the results in different ways.\n", + "The `L2Projector` and the `PointEvalHandler` build a pipeline for doing so. With the `L2Projector`,\n", + "integration point quantities can be projected to the nodes. The `PointEvalHandler` enables evaluation of\n", + "the finite element approximated function in any coordinate in the domain. Thus with the combination of both functionalities,\n", + "both nodal quantities and integration point quantities can be evaluated in any coordinate, allowing for example\n", + "cut-planes through 3D structures or cut-lines through 2D-structures.\n", + "\n", + "This example continues from the Heat equation example, where the temperature field was\n", + "determined on a square domain. In this example, we first compute the heat flux in each\n", + "integration point (based on the solved temperature field) and then we do an L2-projection\n", + "of the fluxes to the nodes of the mesh. By doing this, we can more easily visualize\n", + "integration points quantities. Finally, we visualize the temperature field and the heat fluxes along a cut-line.\n", + "\n", + "The L2-projection is defined as follows: Find projection $q(\\boldsymbol{x}) \\in U_h(\\Omega)$ such that\n", + "$$\n", + "\\int v q \\ \\mathrm{d}\\Omega = \\int v d \\ \\mathrm{d}\\Omega \\quad \\forall v \\in U_h(\\Omega),\n", + "$$\n", + "where $d$ is the quadrature data to project. Since the flux is a vector the projection function\n", + "will be solved with multiple right hand sides, e.g. with $d = q_x$ and $d = q_y$ for this 2D problem.\n", + "In this example, we use standard Lagrange interpolations, and the finite element space $U_h$ is then\n", + "a subset of the $H^1$ space (continuous functions).\n", + "\n", + "Ferrite has functionality for doing much of this automatically, as displayed in the code below.\n", + "In particular `L2Projector` for assembling the left hand side, and\n", + "`project` for assembling the right hand sides and solving for the projection." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Implementation\n", + "\n", + "Start by simply running the Heat equation example to solve the problem" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "include(\"../tutorials/heat_equation.jl\");" + ], + "metadata": {}, + "execution_count": 1 + }, + { + "cell_type": "markdown", + "source": [ + "Next we define a function that computes the heat flux for each integration point in the domain.\n", + "Fourier's law is adopted, where the conductivity tensor is assumed to be isotropic with unit\n", + "conductivity $\\lambda = 1 ⇒ q = - \\nabla u$, where $u$ is the temperature." + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "compute_heat_fluxes (generic function with 1 method)" + }, + "metadata": {}, + "execution_count": 2 + } + ], + "cell_type": "code", + "source": [ + "function compute_heat_fluxes(cellvalues::CellValues, dh::DofHandler, a::AbstractVector{T}) where {T}\n", + "\n", + " n = getnbasefunctions(cellvalues)\n", + " cell_dofs = zeros(Int, n)\n", + " nqp = getnquadpoints(cellvalues)\n", + "\n", + " # Allocate storage for the fluxes to store\n", + " q = [Vec{2, T}[] for _ in 1:getncells(dh.grid)]\n", + "\n", + " for (cell_num, cell) in enumerate(CellIterator(dh))\n", + " q_cell = q[cell_num]\n", + " celldofs!(cell_dofs, dh, cell_num)\n", + " aᵉ = a[cell_dofs]\n", + " reinit!(cellvalues, cell)\n", + "\n", + " for q_point in 1:nqp\n", + " q_qp = - function_gradient(cellvalues, q_point, aᵉ)\n", + " push!(q_cell, q_qp)\n", + " end\n", + " end\n", + " return q\n", + "end" + ], + "metadata": {}, + "execution_count": 2 + }, + { + "cell_type": "markdown", + "source": [ + "Now call the function to get all the fluxes." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "q_gp = compute_heat_fluxes(cellvalues, dh, u);" + ], + "metadata": {}, + "execution_count": 3 + }, + { + "cell_type": "markdown", + "source": [ + "Next, create an `L2Projector` using the same interpolation as was used to approximate the\n", + "temperature field. On instantiation, the projector assembles the coefficient matrix `M` and\n", + "computes the Cholesky factorization of it. By doing so, the projector can be reused without\n", + "having to invert `M` every time." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "projector = L2Projector(ip, grid);" + ], + "metadata": {}, + "execution_count": 4 + }, + { + "cell_type": "markdown", + "source": [ + "Project the integration point values to the nodal values" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "q_projected = project(projector, q_gp, qr);" + ], + "metadata": {}, + "execution_count": 5 + }, + { + "cell_type": "markdown", + "source": [ + "## Exporting to VTK\n", + "To visualize the heat flux, we export the projected field `q_projected`\n", + "to a VTK-file, which can be viewed in e.g. [ParaView](https://www.paraview.org/).\n", + "The result is also visualized in *Figure 1*." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "VTKGridFile(\"heat_equation_flux\", grid) do vtk\n", + " write_projection(vtk, projector, q_projected, \"q\")\n", + "end;" + ], + "metadata": {}, + "execution_count": 6 + }, + { + "cell_type": "markdown", + "source": [ + "## Point evaluation\n", + "![](heat_square_pointevaluation.png)\n", + "\n", + "*Figure 2*: Visualization of the cut line where we want to compute\n", + "the temperature and heat flux." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Consider a cut-line through the domain like the black line in *Figure 2* above.\n", + "We will evaluate the temperature and the heat flux distribution along a horizontal line." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "points = [Vec((x, 0.75)) for x in range(-1.0, 1.0, length = 101)];" + ], + "metadata": {}, + "execution_count": 7 + }, + { + "cell_type": "markdown", + "source": [ + "First, we need to generate a `PointEvalHandler`. This will find and store the cells\n", + "containing the input points." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "ph = PointEvalHandler(grid, points);" + ], + "metadata": {}, + "execution_count": 8 + }, + { + "cell_type": "markdown", + "source": [ + "After the L2-Projection, the heat fluxes `q_projected` are stored in the DoF-ordering\n", + "determined by the projector's internal DoFHandler, so to evaluate the flux `q` at our points\n", + "we give the `PointEvalHandler`, the `L2Projector` and the values `q_projected`." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "q_points = evaluate_at_points(ph, projector, q_projected);" + ], + "metadata": {}, + "execution_count": 9 + }, + { + "cell_type": "markdown", + "source": [ + "We can also extract the field values, here the temperature, right away from the result\n", + "vector of the simulation, that is stored in `u`. These values are stored in the order of\n", + "our initial DofHandler so the input is not the `PointEvalHandler`, the original `DofHandler`,\n", + "the dof-vector `u`, and (optionally for single-field problems) the name of the field.\n", + "From the `L2Projection`, the values are stored in the order of the degrees of freedom." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "u_points = evaluate_at_points(ph, dh, u, :u);" + ], + "metadata": {}, + "execution_count": 10 + }, + { + "cell_type": "markdown", + "source": [ + "Now, we can plot the temperature and flux values with the help of any plotting library, e.g. Plots.jl.\n", + "To do this, we need to import the package:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "import Plots" + ], + "metadata": {}, + "execution_count": 11 + }, + { + "cell_type": "markdown", + "source": [ + "Firstly, we are going to plot the temperature values along the given line." + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "Plot{Plots.GRBackend() n=1}", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAlgAAAGQCAIAAAD9V4nPAAAABmJLR0QA/wD/AP+gvaeTAAAgAElEQVR4nOzdeUAU5f8H8GdmlmO5QeQ+5PZERUXNE/IEUREzz9IsNa/6amn1VSurb2Z2aZm3+bMyTU1FUFG8EEnxQDw4FJBDbrmXBXZn5vfHFhEuCrj3vl9/scOzy4dhed77zDzzDMXzPAEAANBXtLoLAAAAUCcEIQAA6DUEIQAA6DUEIQAA6DUEIQAA6DUEIQAA6DUEIQAA6DUEIQAA6DUEIQAA6DUEIQAA6DVdC8KDBw9evny5lY1ZllVqMdAUx3HqLkGP8DyPHa5K6ExUSeF7W9eC8OLFi9evX29l49raWqUWA02JxWJ0zSojlUobGhrUXYUeQWeiSgrf27oWhAAAAG2CIAQAAL2GIAQAAL2GIAQAAL2GIAQAAL2GIAQAAL2GIAQAAL2GIAQAAL0mUHcBANBOYikpEPMFtaRIzOfXkmIxnycixWI+v5YUignH0fYmUmdTYmdMOZkQBxPKQUgcTSh7IXE2pUzxrw/wN/w3AGiukjpSJOYLakmhmC+sJfm1fLGY5In44jqSL+IlPHEUUo4mxF5IOZkQOyE12IHYC2lHIbE1lPIcX8kxj0SkuI7PryUZVXx8Ecmv5YrEJF/EE0KcTSk7IXEyoRyExMGEcjIhdsaUsymxE1L2QkKp+3cHUBkEIYBGEEnJmUdcbD7/sPqfEZ6lIbEXUo4mxEFIOZgQdzOqX0fibELbCYmzKWVh0OKrSSSEZXl3Y8rfhhB5oSaSkkcivkhMCmr5QjEprOXPVZDiOu6RiBSJ+bJ60tGYcjAhjkLiYkoFOVGjnGlrI+X99gDqhCAEUKcHVXx0Lh+VwyUU84EdqbGu9AgnYi+kZSMzQ6WdxDcVEF9LyteSyI1JKUeK6/iCWlJQSx7W8D8/4ObFsb06UCGudIgr1cMGw0XQKQhCAFWrZ0lcIR+dy0Xl8jUSMtaVmt+FPjiCNm95hKdiApo4mVBOJrJH1OKutFhKLhTyx3O4iac5KU9CXKkQV+pFJ9oEXQhoP7yLAVTkkYiPzuWjc/lzBVxXKyrUjf4tmO7VgdKK4ZVQQMa4UGNcGEJIagUflct/e4ebcY59wZ4KdaVDXCkvC634PQDkQBACKBHLkz+L+agc7kQen1vDj3ahX/Kkdgw16KDN59s6W1GdrajlPegqCTmdx53I4z+/xVoYUCGuVIgrPdRRiUd0AZQBQQigeKV15GQeF53Lx+RxbmbUWFfqhxeY/nYUo1ujJgsDEuFBR3gQnjA3S/noXH7NdfZeBR/sRIe4UmNdKGdT3fqFQUchCAEUgydEFgZRuVzK32Gwob/AyUT3w4AiJMCWCrClVvWmS+vIqTwuKpd/7yrrakaFuFKhrrTufQgAXYIgBHheCcX8zjQuOpczN6BCXalP+jD6fHjQ1pjM8KZneBOWZ2SHhRddZmWHhV/1pUc5Iw9B4yAIAdovvohfe4NNryJLu9Hv9xRgwkhTDEUG2VOD7Jn/9SN5Ij4ql1/2J2tmQNb0ZkJcsaNAgyAIAdojvohfd4u9XUb+050+1oU2YtRdkGZzMaXmd6be8KOjcrnV19kPEsl/e9OTPWjkIWgCBCFA21wo4D++weaIyH970YdH0Ab6egi0HWiKhLnRoa70Hw+5T25yX9zi1vSmw9wRh6BmCEKA1rpUyH90g82sJu/1pF/zpQWIwHahKRLhQU/yoI/ncGtvcKuuc+/0oGd608hDUBcEIcCznX7Er73JlojJf3vT071oTIB8fhQhYW70ODc6Mptbe5P76ja3pjcd3glxCGqAIAR4mlN5/NqbbHk9+W8veioiUNEoQsa702HudFQOv/Ym+/FNblUverIH4hBUCkEIIF90Lv/JTbZaQlb1oqd4omtWIoqQcW7UODdBdC6/9ia79iaHfQ6qhCAEaC4ql//4BlvHEoxOVCzElQpxFchG4Wtvcqt60y97YhQOSocgBPjHmUf8f6+xtVLyrj89wxtdsHqMdqFGuwguFfIf3mBXX+NWYmoSKBmCEIDwhBzL5tbe5FiOfBhAT+yEQaD6DXagYkMEsfn8xzfYr25z/+1FT/dCHIJSIAhBr/GEyK5poymyujc9Ade0aZgXnagXnQTnCvi1N9hPbnL/7UXP9EYcgoIhCEF/HcvmVl/nDGmytg89zg0RqLmCHKmgUMHFQv7jG+wnN7k1AfQrPvh7gcIgCEEf1UjIkgT2chH/9QAmFOteaomhDlRsiOBSIb/sCnswi9s1VNDRWN01gU7AIQbQO9dK+YAjUpYj1ycKkIJaZ7ADdTlM0MeW6nlYEpXLq7sc0AUIQtAjPCHf3eFCTko/DqD/bzhjZqDugqBdBDT5KID5NUjw5iX2rQS2nlV3QaDlEISgL/JE/ItR0iPZ3PVwwTQvvPO13nBHKmmSIE9EBhyTplRgaAjth+4A9MIfD7m+R6RDHakzIQJXUxwO1RE2RuTQCOY/3emhx6Xf3eEQhtA+mCwDOk4sJe8lssdy+EMjBIPsEYE66BUfur8dNf0ce76A3zGU6WCk7oJA2yhxRFhXV3f58uW0tLSntCkvL8/JyWm6heO4lJSUhISE8vLyxo21tbWZTdTW1iqraNAtNx/zAUekFQ0keRJSUJf5WVIJ4wXeFqTXYenZfIwMoW2UFYSpqak+Pj7vv//+6NGjZ82axfPN35pnz5719vbu0KHD0KFDGzdmZGQ4OzuHh4evXLmyU6dOW7ZskW2PjY3t1q3byL9dvXpVSWWDzuAJ+eo2N+akdE1ves8wxhzzYnSdIU2+7M/sGsq8coF9L5GVcOouCLSHsoJw9erV06ZNu3DhQnJy8sWLF2NjY5s18PLy+u23344cOdJ0o7m5+cmTJ1NTUy9evHj48OElS5ZUVVXJvtWnT5+Mvw0fPlxJZYNuKBaTcaekv2dxl8MwL0a/jHSmksIFKRVk4DFpeiWGhtAqSukjGhoajh49OmfOHEKIhYVFeHj4wYMHm7Vxd3fv27evoaFh0412dnY9e/aUfd2nTx+pVFpRUSF7yHFcRkZGWVmZMgoGXXIqj+9zRNrDhoobJ/CywOFQvWNrTI6OZJZ2owdFSr+7g4EhPJtSJssUFhZKJJJOnTrJHnbq1On06dNtfZHNmzcHBga6ubnJHt65c2f8+PG5ubmBgYH79u3r2LGj3GfV1NSkpqY2/jiGYYYOHUrT8vOe4ziOw/+Jiqhgb4ul5N1E7mQe2TecesGeIkR//7zc39RdiNrM9CK9beiZF7hLhdyWQbS1kmfQ6PneVrE27e2W+v+mlBKEtbW1FEU1jvaMjY1FIlGbXiEyMvK77747f/687OGwYcNKSkqMjIxqamomT578zjvv7NmzR+4T8/PzMzMzU1JSZA+FQmGXLl3Mzc3lNhaLxQzDtKkwaDexWMzzfGvelO1zp4J6LcGghxV/aZTUwoDX8wlVEomEZVk975o9jEjsi2TNLUHAEWbbAMmgjkrcG+hMVKlNe9vY2FggeEbSKSUIHR0deZ4vKyuTjdtKS0sdHR1b//SYmJjXX389MjKyS5cusi0WFhayL8zMzBYtWvTWW2+19FxfX9/Q0NAlS5a05gfxPG9mZtb6wuB5UBQlFAqVEYQ8IdtTuTXX2bV9mHmdaUIwff6vIDQ21ve1OM0I2TyUnHnEz75IRXSiNvRnDJTzSQydiSopfG8r5U1haWnp5+d38eJF2cO4uLjAwMBWPvfSpUuzZs36/fffW3pKdnZ2hw4dFFMoaL8iMQk9Jd2VzsWHCeZ1xrwYkGOEM3V9ouB+FT/0uDSzGjNooDllXVC/bNmyd955hxBy9+7dmzdv7tu3jxCSnp4eGBiYnZ1taWlZVFT0008/paenV1ZWfvHFF46Ojq+88sr9+/fHjBkTHByckJCQkJBACJkxY4aLi8vnn39uYmLi7u6ekpKybt26zZs3K6ls0C4ncvnX49g5vtRHAQzuUQdPYS8kUaMFG+9wA45KvxrAzPLG2wX+oawgnDdvnqmp6aFDh6ysrOLi4mxsbAgh1tbWCxYsMDIyIoSwLFteXt6xY8f58+eXl5fLxrkMwyxevJgQ0ng1vVQqJYT07dv3jz/+iI+Pt7e3j4qKGjx4sJLKBm1Rz5IPb7C/ZfC/BjHDHDE1FJ6NIuSt7vRIF2raWfZYNr9tMKPsGTSgLagnL3XXakuXLvXx8WnlOcLq6uqW5tGAwolEIkWdI0yp4KefYz3Nqe1DGBv0ZfLgHOFT1LFk5VX2aDa/dzgzxEExn6LQmaiSwvc2jg+AlvnjITfsuHRJN/rQCKQgtIcxQ74byGx6gZ4SK/0xRa8n1oIMFt0GbbIvg1t+hT01VtC7Aw6HwnMJc6MTxlMvRrNiKVnWA0MCvYY/P2iNXx5w717lTo1BCoJidDKn4scLdqVz7yXi3r56DUEI2mFbKvffa9y5EKaHDVIQFMZBSM6GCE7k8shCfYYgBC3wwz3u81tcbAjjY4kUBAWzE5LzoYJz+fyiy6xOTR2EVkMQgqb7XxK38S4XN47BCtqgJNZG5NRYwfVSflE8slAfIQhBo310g/35AXculHExRQqCElkZktgQQVolP/McK8VMUj2DIAQNxROy/Ap7LJu/ME7gZIIUBKUzFZDIUYLSOn7medzXV78gCEET8YS8ncBeKOBPhwg64qJwUBUTAYkcLahnyaQz0jrMntEbCELQOCxP5l5kbz7mz4UKOuCSeVAtQ5rsf5ExYqgIZKHeQBCCZpFyZNZ5NlfEnxgjMDdQdzWglwxp8lsQY2VIjTslFUnVXQ0oH4IQNEgDR6aeYysa+GMjBaZY9QjUR0CTvcOZTubUmJPSKom6qwElQxCCpqhnyZRYtoElf4wQCJGCoG40RbYPYfrYUsFR0sf16q4GlAlBCBqhVkrCYqTGDDk0gjFi1F0NACGEEIqQbwcwY12pEdHSkjp1VwNKgyAE9auWkDEnpS6m1C9BjAHekqBhPunDjHOjgqKkhWJ1lwLKgV4H1KyigYw8Ie1uTe0cyjC4XBA00id9mBne9LDj0lwRVp7RQQhCUKfyejLqhLSvLfXDIIQgaLT3e9JvdaOHHmczqpCFugZBCGpTJCbDoqTBTtT3LyAFQQss7Eq/35Meepy9V4Es1CmYnAfqUVBLRkRLp3jSHwbg0xhojXmdaTMDMuoEe2I07gimOxCEoAZZ1fyIaHZRVxp3BgetM92LpgkZfVIaNRr3iNYR6IZA1dIr+eFR7FvdkYKgraZ60dsGC8aelCYU4xipLkBPBCqVWsGPiGY/DKCXdsN7D7TYODfqp2GC8THSs/nIQq2HzghUJ+kxHxQl/bQv/Zov3nig9ca4UAdfFEw/Jz39CFmo3dAfgYpce0yFxHCbBzGv+OBdBzpimCN18EXBzPPSmAKsh6TF0CWBKmTX8FPjBDuGMOGd8JYDnTLYgYocJVhwRXDzMcaF2gq9EihdHUsmn2H/04ULcVF3KQBKENiR+q6vZNIZFuuRaikEISjdwnjWz4pa5IebnILOCnPhpnpSU89KpZy6S4G2QxCCcn1zh7v5mN82GGdQQMd91pcRMmTFVXzg0z4IQlCis/n8F7fYwyMYE6zcALqOpsje4YLIHH53OkaFWgZBCMqSU8PPPC/dFyzwMMfqG6AXrI1I5CjmvUT2WikmzmgTBCEohVhKws+wK/yZIEekIOiRzlbU5heYl2LZUkyc0R4IQlCKhZfZLlbU293xBgO9E+FBT/eipsRi4ozWQD8Fivf1bS4JE2RAj33ShzERYOKM1kAQgoKdzee/SMYEGdBrmDijXRCEoEiyCTK/YYIM6D1MnNEiCEJQmDqWRJxhV2KCDAAhhJDOVtTWwcxkrDij8RCEoDCvx7FdrKi3MEEG4G8T3enpXlhxRtOhzwLF+Po2d7ec34IJMgD/9ilWnNF4CEJQgLP5/HpMkAGQBxNnNB+CEJ5XNlaQAXgqTJzRcAhCeC5iKYk4w77XExNkAJ4GE2c0GYIQnotsBZml3fBGAniGie70DG9q0hmpBIdINQz6L2i/r29zt8uwggxAa33ShzE3wMQZjYMghHaKzefXJ7O/v8gIMUEGoHVoivwWLDiVh4kzmgVBCO2RXcPPwgoyAG1nYUAOj2DeS2QTSzBxRlMgCKHNGifIDMcEGYC262xFbRvMvBTLFovVXQoQQhCE0A6YIAPwnCa40zO9qYhYaQMOkWoA9GXQNl/d5pIe81sxQQbg+aztw1hi4oxmUFYQ5uXlRUREeHl5jR07Ni0t7ckG8fHx7777bkhIyPr165tuT05OHjFihJeX1/Tp04uLi2UbeZ7//PPPu3bt2rNnz507dyqpZnim2Hz+q9vs0ZFYQQbgedEU+TlIEJ3L732AUaGaKSsIZ8yY4eTkdPHixYEDB06YMIHjmv+lb926ZWhoyDDMvXv3GjdKJJLQ0NCxY8devHjR2Nh47ty5su179+7duXPn77//vnXr1vfee+/ixYtKKhueQjZB5pcggZsZTg0CKICVITk2knnnCibOqJlSgvDevXuJiYnr1q1zdnZetWpVZWXl+fPnm7VZuHDhZ5991qNHj6Ybo6KijIyMli9f7uzsvH79+lOnTuXm5hJCtmzZsmLFim7dug0YMGDevHlbt25VRtnwFFhBBkAZMHFGEygrCP38/ExNTQkhNE337t37zp07rXni3bt3+/TpI/va1tbW1dVVNl68e/duQECAbHtAQMDdu3eVUTY8BSbIACgJJs6onVJO9ZSWllpYWDQ+tLKyKikpafcTGxoaqqqqGrc//dWSkpJ27NixZs0a2UNTU9MrV65YWlrKbSwSiSgK45tn25jK3ChhTr/YUFPT/hepra1lWZamEaWqIJFIWJaVSqXqLkRfPGdnssKPXCsSLIuXruuNP9mztWlvGxsbCwTPSDqlBKGVlZVIJGp8WFVVZWNj08onFhUVNXuioaGhqalp4wtWV1c/5dX8/f1DQ0PnzZsne8gwTNNkbYbneTMzs9YUps9i8/mNadKE8QI7c8PneR2KooRCIYJQNWRBaGxsrO5C9MXzdyYHRpEBR6W/5xvO8cX/yDMovOtWyh739PR88OCBRCKRPUxNTfXy8mrlE1NSUmRfi0SivLw8T09P2fbU1NTGV5NtlIumaRMTE+u/PSUFoTXya/mZ57CCDIDSWRiQ319k3ktk75Rj4oyqKSUI+/Xr5+TkJJvScvjw4erq6jFjxhBC4uPjv/zyS1mb+vr68vJysVjc+AUhZOLEiZmZmTExMYSQjRs3+vv7d+7cmRDyyiuvbNq0SSwWP378eOfOnbNmzVJG2fCktxK4eZ1prCADoALdrKnP+jLz4lgOUahaSglCiqL27t27adMmW1vbt99+e9++fYaGhoSQjIwMWcgRQnbv3u3l5bVnz55Tp055eXl98cUXhBBzc/Nffvll9uzZtra2+/bt2717t6zxkiVLPD09HR0dvby8wsLCIiIilFE2NHMil08u49/vhWvnAVRkrh8tFJCtqZg2o1IUzyvxs4dIJJLNHVXIE+vr6xmGefppz6VLl/r4+CxZsqQ1P6W6utrc3Lwd5emDWinpfki6YwgT7KSY4aBIJMI5QpXBOUIVU2Bnkl7JDzkuvTFR4GyKIzHyKbzrVm6v1L4UbOmJRkZGz5z8A4ry32tskCOlqBQEgFbytaQWdKbf/hODQtV5WhA2NDTk5+cXFxc/uS4M6LZbZfxvGdwXgTgoCqAGH/Ri7pbzx7LR8aqInCC8du3a0qVLu3XrZmRk5OzsbG9vb2RkNHDgwA8//PDhw4cqrxBUjeXJ3Ivshv6MLY6rAaiDEUO2DGaWJnA1EnWXoh/+daQxLi5uxYoVf/75p7Oz88CBAydNmmRjYyOVSouLi+/cubNx48ZPP/00PDx83bp13t7e6qoYlO27O5yFAZnujZN5AGoz1IEKdqLWXGe/HoADM0r3TxAePXp06tSps2fP/uabbwYMGPBkU4lEEhMTs3379u7du6enp7u5uamwTlCR7Bp+3S02YbwA5wYB1OvL/kyPQ5IZ3nQfW/w7Ktc/Qejn53f//n0XF5eWmhoYGISGhoaGht68eRMLsuiqpQncsh6MlwX+8QDUrIMRWR/IvHaRvTZRYIADNMr0z97t3LnzU1Kwqd69e7dyyTTQLr9ncZlV/PIe+J8D0AgzvWl7Idl0F7NmlOtpXV5lZWViYmJ0dLTKqgE1qpKQ5X9yWwcz+OwJoDm2DWbW3WKzqrHYjBLJ7/MaGhoWLlzYsWPHwMDAxgWsFyxYMHXqVBXWBiq18iob5k69YI+DogAapJM59Z8ezKLLrLoL0WXyg3DFihV79+793//+t3nz5saN4eHhx44dq6urU1VtoDpXivnIHP6zvpifBqBx3u1BF9SS37NwgFRZ5ARhQ0PD9u3bv/rqq3feeadr166N23v06CEWi2W3jAddIuXI/EvsdwNoq+e6zxIAKIWAJlsHM28ncOX16i5FR8kJwpKSktra2iFDhjTbLpspWlFRoYq6QIXWJ3NOpiTCA+cGATRUYEdqgjv1wTUcIFUKOX2flZUVwzBPLiJz48YNQkgrZ5aCtnhQxX97h90yCAdFATTa5/2Y4zn85SLMmlE8OUFoamo6YsSI1atXl5SUUNRfUycKCgqWL18+YMAAR0dH1VYIyrUwnn2/F+NmhjkyABrN0pB8M4Ced4ltwLlCRZN/M4eNGzcOGTLE19fXz8+voqJi3LhxcXFxhJDz58+rtDpQsr0PuGIxWdIVB0UBtMBkD3rvA35DMvdBL/zPKpL8venr65uUlPTqq69WV1cbGhreu3cvIiLi+vXrvXv3VnF9oDxl9WTlVXbnUEaA/ykALbFpIP31bTatEgdIFUnOiFAkEn3yySdTpkz59ttvVV8QqMzyK+w0LyxjCKBN3Myo//ZmFlxiz4ZiQWCFkTMWqK6u/uKLLyQS3P9Dl10o4M/l8x8HYI4MgJZZ2o2ulpCfH+BUocLICUI7Ozt7e/vMzEzVVwOqUc+SN+PZjQNpMwN1lwIAbcRQZOdQ5t0rbAlWN1EQOUFI0/T69etXr159584d1RcEKvBZEtvdmhrvjnODAFqppw013YtecRWXFSqG/Fmjx48fr66u7tmzp4eHh6urq0DwT7PTp0+rqjZQinsV/NZULikcg0EALba2D9P9kPRcAR/kiHOFz0t+EBJC/P39VVkHqAZPyOJ49uMAxtFE3aUAwHMwMyA/DmLmX2KTJwmMca7/+cgPwgMHDqi4DlCN7alcLUvmdcZBUQCtN9aV2pVO/S+JXdsHSfhc0CHqkSIxWXWN3TqYoXEoBUAnbHqB2ZrK3avAZYXPRf6IMDIysqXbLb300kvKrAeU6K0E9o3OdE8bxCCAjnAQko8CmPmX2IvjcFlh+8kPwjfeeKOoqEjut3geHz200sk8/nopv3toi2eFAUAbze9M//KA25HKvYFTHu0lv1u8cuUKy/4zMbeqqurChQtfffXVpk2bVFUYKFKtlCyKZ7cMZoTIQQDdQlNky2AmKEoa6kY5mWBY2B7y+0V3d/dmW3r16mVjY/Puu++GhYXRND53aJmPbrCD7KmRzvgnAdBB3a2peZ3p5Ve4fUGYNdMebYi0kSNH3r9/PyUlRXnVgDIkPeb/7z731QD8hwDorFW9mGsl/IlcnLpqjzYEYVpaGiHEyMhIacWA4nE8WRjPruvHdDRWdykAoDRCAdk+hFl4ma3BKtFt16pZo1KpNCMjY/PmzZ06dfL09FRVbaAA39/jDBnyqi+OZgPouOGO1GB7au1Ndn0gDv+0TWtnjdI0HRwc/O233+IEoRYpqCWfJbHncbsWAP3w7UCm+0HJNC+6dwf807dBq2aNCgQCBwcHQ0NDVVUFirEwnl3clelihX8JAL3QwYj8rx8z/xKbMF7A4P++1eQHoZWVlampadO1tgkhUqm0urra2tpaJYXB84rK5VMq+N+CcZAEQI/M9qV/ecBtvsct6Yajd60lf0/5+fldvXq12cbExEQbGxvllwQKUC0hb15ifxzMGCEHAfQJRciPg5hPbrJ5Iswgba02fGSQSqUGBrh3j3ZYe4Md7ULh/iwAesjHklrcjVl+Bbewb61/Hfysq6sTi8WEEI7jqqury8vLG78lEokOHTrk7Oys6gKh7YrEZFc6lzwJq8gA6KkV/rT3AWlyGe+PtYVb4V995Y8//rhs2TLZ12PGjHmy9RdffKGKouD5rE9mZ/nQzqb4BwDQU8YM+U93+tOb3IEXcXbk2f4VhCNGjNi6dSsh5N13312wYIGXl1fjt8zNzbt3796jRw9VFwhtVFpH9qRztzAcBNBvb3ahNyRLbpfRPTAofJZ/dZc9evSQRV1DQ0N4eDgOhGqjdbfY6d4YDgLoOxMBWdaD+SyJw9TxZ5I/bli8eLGK6wCFKK0je+5zN8MxHAQAsrAr7bUfg8Jna7HHzM3NPXbsWGZmZk1NTdPtsmOnoJnWJ7NTPWkXDAcBgBBTAflPd+Z/t3BXimeQH4SxsbETJkyQSqWGhoYMw3AcV1VVJRQKHR0dVVwftF5pHdmdjuEgAPxjcTfaa7/kTjnd3Rqfj1sk/zrCt99+u3///qWlpREREYsWLaqsrIyLi3N2dl61apWK64PW+zKZnYLhIAA0YSogb3VnPk/CNYVPIycI6+vrU1JS1qxZY2ZmRgiRSqWEkMGDB2/btu2tt96SXWgImuZxPdmRxq3wx6JKAPAvS7rSZ/O51AosNNMiOf1mRUUFy7Kyo6BWVlaNl9UPGDCgurpadldC0DQbktkpnrS7GYaDAPAvZgZkSTfmUwwKWyYnCDt27GhkZPTo0SNCiIeHx8WLF2V3okhKSiKEyIaJoFEe15PtqdxKDAcBQJ6l3egzj7i0SgwK5ZPTddI0PXTo0OjoaELI1KlTs7OzBw8ePH/+/PHjx/v7++PGvBro69vsZA+6kzmGgwAgh5kBWdyV+ewmBoXyyZ9h+MMPP0rym1IAACAASURBVMiOiNrZ2R07duzzzz8/ffr00KFDN2zY0Pob816/fj06OtrW1nb69OmWlpZPNiguLv71119ra2vDw8O7dOlCCKmsrNy/f3/TNoMHD+7atWt2dvapU6caN44dO9bV1bWVZei8snqyNYVLnIjJogDQoqXdae/9krRK2s8Sn5ibk5NqPM/b2tp27dpV9nDEiBGxsbGZmZmHDh3y8PBo5etGRUWNHDmS47hz584NHDjwySk2paWlAQEBycnJtbW1AwcOlN31qa6u7vrfLl++PH/+/IKCAkJIcnLyhx9+2PitysrK9v/GOufr2+wkD9oDw0EAaJmFAVncDdNH5ZMzjCgoKHB2do6JiRk5cmS7X/fTTz9dt27dvHnzeJ4PDAw8cODAq6++2rTB9u3be/XqtWvXLkKIkZHRunXrDh8+bG9v33jB/oEDBy5cuBAUFCR76OXlhWv5n1TRQLalclcmYDgIAM/wdnfaa78kvZL2xaDw3+SMCK2trQUCAcO0fyUCkUj0559/hoSEEEIoiho7duyZM2eatYmNjR07dqzs67Fjx8bGxjZrsHPnzjlz5jQeiS0vL9++ffvvv//++PHjdheme76+zY53x3AQAJ7NwoAs7sqsu4VBYXNyRhJCoXDKlCk//fRTcHBw+15UdjzTzs5O9tDBweHy5ctPtrG3t29sUFVVVVNT0zglNS8v79y5c9u2bZM9NDIycnd3T05OTk1NffPNN2NiYgICAuT+6Ly8vLt37+bk5Pz16wkEK1asMDExkdu4vr7e0NCwfb+jJqhsIJvvURfG8PX1UnXX8mz19fU0Tbf+HDM8D4lEwrIsReETkopoS2fypg/peoS6WyL1tlB3Kc+hTXu7NeM6+YfUQkNDly9fPnTo0PHjxzs7OwsE/zR76aWXnvmDZZ0dz/81VZdl2SfroGma4/76YCL7ommbnTt3BgUFubu7yx6OGjVq1KhRsq+XL1++cuXK06dPy/3RhoaGJiYm1tbWsocmJiaGhoYtdb7a3i9/n0bGuRJfK+3o7Oi/qbsQvUDTNM/z2Nsqoy3vbWtj8mZnsuEutW2Qukt5Dm3a2635OCg/CJctW1ZUVFRYWBgXF9fsW43x9hSOjo4URRUUFHTq1IkQUlBQ8OQipU5OToWFhbKvCwoKrK2thUJh44/Yu3fvZ599JvfFR4wYcfDgwZZ+tJ2dnY+Pz5IlS55ZJCHEwMDAwMCgNS01UGUD+TFVEh8mMDDQjiCU7W2t6Cx0A03T2vv21jpa1Jm805P4HJDk1gk8tfaUisL3tvxe6cqVKxktaM2LCoXC4cOHHzlyhBAilUqPHz8uO18omxQquzx/7NixR48elcXqkSNHGs8XEkJiY2PLy8snTJjQuEX2FJkzZ874+fm153fVLd/d5UJdcdIbANrG0pAs6EL/D9NHm5A/Imw8JtluH374YXh4+P3791NTU01MTCZOnEgIycrK6tu3b3l5uZWV1Zw5c7Zs2TJhwgR7e/vDhw9fuHCh8bm7du2aOXOmsbFx45Y5c+ZUVla6ubmlpKQkJyc3vaZQP1VJyPd32UthmCwKAG22rAfje0CS1Qvz7P5CtXSok2XZc+fO3blzp7a29oMPPiCEpKSkmJmZtf5K9qysrNjYWGtr63HjxhkZGRFCRCJRfHx8cHCw7KRjTU3N8ePHa2trx44d2/TYaVxcnJ+fX+NcG0JIQUFBfHz848eP7e3tg4ODLSxaPM+7dOnS1h8ara6uNjc3b+Wvo1E+uck9qOL3DNOme4yJRCKhUIhDo6ohmyzT9NMkKJXWdSarrrHFdWTbYG3qQxopfG/LD8KioqKQkJAbN24IBAJ7e/u8vDxCyH/+85/ExMRLly4p8McrnD4EYZWEeO+XxIUJtGuFCAShKiEIVUzrOpOyeuJ7QJI4UaCNg0KF7235vdIbb7xRXl5++fLlmJiYxo0vv/xyQkJCVVWVAn88tMOmu9wYF6yTBADtZ2NE5nehv8A1hYQQuUEoEomio6O/++67gQMHNv387uPjw3Fcbm6uCsuD5mokZNNd9r+9Ma4CgOeyrAdzMIt7WI1bUsgLwsrKSpZlvb29m22XXe1XX1+virqgBRvvciOcMRwEgOfVwYi80Zn+IhmDQnlBaGtra2Jicu3atWbbY2NjGYbx8vJSSWEgh0hKNt1lV/XCcBAAFOAdf+ZAJpddo++DQjldqqGh4dSpU1euXHnhwoXGa/JPnz69bNmy8PBwuTdUAtXYdJcLdqI7a8lSMgCg4WSDwvV6PyiUfyHaN998k5aWNnz4cFNT04aGhg4dOpSVlXXv3v2HH35QcX3QqEZCvr3DngvFtYMAoDDLezBdfpe835N2MdXfT9jye1ULC4vz588fPnw4JiamsLDQ2tp62LBhzS5yBxXbnMINd6S7YDgIAIrT0Zi85kevu8V9/4JWXlOoEC0OLwQCwZQpU6ZMmaLKaqAlIin5+jZ7eiyGgwCgYCv8mc6/S97T40Hh0zrWlJSUW7duPXr0yN7evnv37r169VJZWdDM5nvcMEe6h42evk0BQHlsjclsX3p9MrdxoJ4OCuUHYXV19ezZsw8fPtx0Y1BQ0L59+xpvIggqUyslX99mT2E4CADK8a4/0/WgZKU/7ayXg0L5E/Hnzp0bHR396aef3rt3r6ysLD09/bvvvktKSoqIiFBxfUAI+TGFG+JA+2M4CADKYS8kr/rQX97W0+mjcgYZ1dXVhw8f3rx587x582RbrK2tfXx8PD09w8LCHjx48OS19qA8dSz55g4XPVpPD1kAgGqs6Ml0OyhZ4U87mejdZ245I0KxWMyy7LBhw5ptHz58OCGkurpaBWVBo833uAF2FIaDAKBUDkIyy4feoJfXFMoJwo4dO3p7eyckJDTbnpCQYGlp2blzZ5UUBoQQUseSr+9wq7GyKAAo33s9mT33ufxavVtoRs6hUYqidu3aNWPGjOrq6vDwcEdHx+Li4pMnT65du/ann34SCoWqr1JvbUnhAjtSPTEcBADlcxCSmd7017e5Df3161yM/KHGSy+9lJubu3TpUldXV4FA4OTk9Nprrz18+DA8PJz6244dO1Rcq74RS8mXydwaDAcBQFVW9qR3p3NFYnXXoVryZ+SvWrVKJBI9/ZmBgYFKqAf+sT2N69eR6tUBw0EAUBEnE2q6F70hmf1SnwaF8oNw8eLFKq4DmqlnyZfJ3B8j9ei9CACa4INeTI9Dknf9GTu9OQ+Gw24aalsqF2BL9bXFcBAAVMrRhLzsRX91m1V3IarT4mIliYmJx44dy87Orqura7r9wIEDyq9K39WzZH0yd3gEhoMAoAbv9aR7HZYu76Evg0L5Qfjll1+uWLHC1NTUzc3NxMRExTXBjjSuVwfSryOGgwCgBq6m1Mue9Dd32M/76cXHcTlBKJVKP/zww9dee+3777/HxRKq18CRL5O5Ay/qxfsPADTT+73oXoely3owHfXg5ntyzhGWlZWJxeKFCxciBdViZxrXzZoEYjgIAOrjakq95EF/e0cvzhTKX1nG3d09MzNT9dWAhCPrk7k1vTEcBAA1+6AXvSWFK6l7dkttJycIKYravHnz6tWrr1y5ovqC9NyudK6zJelvh+EgAKiZmxkV4UF/pweDQvmTZYKDg/v16zdgwAALCwtbW9um38rIyFBJYfpIypF1t7h9QRgOAoBG+KAn3feI9B1/xspQ3aUok/wgnDNnzv79+4cOHert7S0Q4H6wKnIij3M2IQMwHAQAzdDJnBrpQv/ygFvUVZcvOpcTclVVVfv379+wYcOyZctUX5A+25HGz/XT5XcbAGidub708iusbgehnN9NIpHwPC+7+yCoTKGYxBVyL3no8rsNALTOi86USEqul+ryvZnkdLsdOnQIDAyMj49XfTX6bFcaN8WDNjNQdx0AAE1QhMz2oXem6fINe+Wf//vss8/mzp3b0NAwatQoU1PTpt/y9PRUSWH6hSdkdzr3K6bJAIDmmeNL+R9mN/RnTHR0xoj8X2vmzJlFRUXvvPPOk9/ieV0eIKvLuXxeKMCaagCgiZxNqRfsqd+zuFd9dPPcjfwg3L59e7O1tkGpdqZxb2CaDABoqrl+9Ne39SwIw8LCVFyHPqtoICfzuE0v4PQgAGioca70ongupYLvYqWDB66eFu81NTU3b96Mi4tTWTX6ae99bqwrbWOk7joAAFogoMkrPtRP6bo5ZUZ+EEokkrfeeqtDhw4BAQHTpk2TbZw3b9706dNVWJu+2JXO4fJBANBwb3Sm/+8+J9HFKJTf/65cuXLnzp0fffTRpk2bGjdOmjTpyJEjOHeoWIklfGUDGeagg0cbAECXeJpTflZUZI4OJqGcIGxoaNi2bduXX375/vvv9+jRo3G7v7+/WCzOzc1VYXm6b2ca97ofTSMHAUDjzfXTzQsK5QRhaWmpSCR6cmUZMzMzQkhFRYUKytITIin5PYub7YsYBAAtMLkTfaWYzxXp2kV0coLQ0tKSYZicnJxm25OSkgghzs7OqqhLPxzI5AY70E4mCEIA0AJCAZnqRe9O14MgNDU1DQ4OXr169ePHjynqrz5adn19YGCgk5OTaivUZTvTuLkYDgKA9pjXmd6RyrG6FYXyryPcuHHjkCFDfH19u3TpUlFRER4efv78eZZlz507p+L6dFhaJZ9ZzY91xXxRANAa/jaUnZCczedHOuvOh3j5vXDnzp2TkpKmTZtWVFRkYGBw/fr1sLCwxMTEPn36qLg+HbY9lZvjSxsgBwFAq+jelJkWl1B1dnb+/vvvVVmKXmngyM8PuEthOrqELQDorule9AeJkpI6pqOxuktREPnjkSlTpty7d6/Zxnv37o0cOVL5JemFY9lcN2vK20J3ji0AgJ6wNCTj3emfH+jOoFB+EF68ePHJyyQqKipiY2OVX5Je2JmG1WQAQFvN9aO3pep6EMqVnZ1ta2urvFL0R56ITyzhw90RhACglYY6UBQhfxbryOTRf52jOnTo0JYtWwgh5eXlb7/9tqWlZeO36urqrl+/HhIS0vqXlkgkubm5Tk5OxsYtHkjOy8szMTGxsbFpzQvm5+cbGBh07Nix9TVoph1p3HRvWojzgwCgtWb70jvTuAF2unA78dYOSqytrZctWyaLydY4d+6cm5tbaGioi4vL4cOHn2xQWlrav3//wYMH+/r6Ll68WHa/36KiIqqJjz76SNa4pqZmxIgRgYGB3bt3nzFjhlQqbWUZGojjyU/p/Gu+GA4CgBZ71Yc+9JCrlqi7DkX416gkIiIiIiKCEDJq1KgNGzb4+/u370U5jps7d+6GDRtmzJhx9uzZyZMnjxkzxsTEpGmbTz75xN3d/c8//ywvLw8ICBg3btyYMWMIIQYGBg0NDc1e8JtvvqEoKjs7u76+vn///vv27Zs1a1b7alO7mEe8nZD06oBpMgCgxeyFZLgjvT+Te137pzvI/wViYmLanYKEkISEhKqqqqlTpxJCgoODHRwcoqOjm7X59ddfFy1aRFGUjY3NtGnT9u3b1/it+vr6ZmO+X3/9dcGCBQzDmJiYzJkzp2ljrYNpMgCgG173o3foxAWF//TIDx8+bOWC2llZWZWVlU9p8PDhQ09PT4b569ixj4/Pw4cPmzaoqakpLS318fGRPfT29s7KypJ9LZFIHBwczM3NR44cmZmZKduYnZ3t7e39ZOMnNTQ05ObmXv9bSkpKa34jlSmpI7H53DQvBCEAaL3RLlS+iNwp1/opM/8cGr1y5cqbb765ePHi2bNne3p6ym199erV7du37927NyUlpelUmmaqq6uFQmHjQxMTk6qqqqYNZA8b25iamsq2WFpa3r9/39vbu6amZuHChVOnTr169apEIhGLxU82luv+/fvp6emnT5/+69cTCP7444+WSq2pqWnpdZRke5og1Imi6uqq9e+ujrW1tVKplKbxIUAVJBIJy7ISiU6cwNEGqu9MNMT0ToIfb0vW9VbpvI027W1jY2MDA4Ont/knCF9++WUDA4P33nvv008/7dmz58CBA/38/GxsbKRSaUlJyZ07d+Lj4zMzM4cNG3bp0iUPD4+nvKi9vX15eXnjw/Lycnt7+6YNOnbsSNN0RUWFtbU1IaSsrEzWwNjYWDbyMzMzW7dunbOzc1lZmY2NjbW1deNotbGxXN26dZs4ceKSJUue/ms3Mjc3b2VLhfjloXTbEMbcXFfWY2gLmqaFQiGCUDVkQfiUCdugcCruTDTEgu58v6PSrwYJjVU7e1Sxe/tfk2UmTZo0ceLEkydP7tmz59ChQ8XFxY3f8vLyGjly5BtvvNGa5Ub9/f3T09MrKystLS1Zlr127drq1aubNjAwMOjateuVK1dkgXrlypVevXo1e5GysjKGYWQDwZ49e165ciUwMLClxlohvohnefKCPabJAICO6GRO9e5AHc3mXvbU4s+4za9lo2k6JCQkJCSE5/nCwsKSkhKBQODg4NDKS/1kvLy8goODFy5c+O677+7atcvd3X3QoEGEkAMHDhw4cODgwYOEkMWLF69Zs8bd3T0rK+vIkSPXr18nhERFRRUXF3fp0qWkpGT16tXTpk2TBeGSJUuWLFnSo0ePqqqqnTt3xsTEKGwHqNDONO71zrgXPQDoFNka3DoVhI0oinJ0dHR0dGzf6/7888+rVq168803u3TpcuzYMdnGDh06+Pn5yb6eN29eXV3dypUrzc3Njxw5IjsiamNjs3fv3u3bt1tZWb3yyisLFy6UNZ40aVJFRcXHH39saGi4Z8+efv36ta8qNaqRkKPZ3Lp+zzhUDQCgXcI70UsT2Iwq3ktrF0+mZFey64ylS5f6+Pi08hxhdXW1yg7rb03lzjzif39RF1ZhaB+RSIRzhCqDc4QqpsrORAMt+5M1MyBr+6iof1P43kavpCK4fBAAdNW8zvTudF57b1uPrlkVbpfxhbVEl27oDADQqLMV5WJKTuZpaxIiCFVhRxr3mh/FIAcBQEdp9W3rEYRKV8+S3zK5OVhlGwB011RP+nwBV1Cr7jraBb2z0h16yPXuQLmbYTwIADrLzIBEdKL/775WDgrlXz4xZcqUpkvDNNW4ehm00s40bkEXfOAAAB33uh/9ygV2RU/tu1q6VR10ZWVlQkLC1atXlV2N7smq5m+X8ePdEIQAoOP621FChsQVat+UGfkjwgMHDjTbUlJSMnny5FGjRim/JJ2yI417xYc20t+rBwFAj8huWz/UQcu6vNaOVDp27Pj111+vWbPm6TdggqakHNlzn38Nlw8CgH6Y5UMfy+bK69VdRxu1oY92cnKqq6vLyMhQXjU65kQe18mMdLXSugPmAADt0cGIjHah92Vo2ZSZ1gYhz/Pbt28nhHTq1EmJ5eiWnWk8VpMBAL0y14/emqplQdiqWaNSqTQrKys7O3vmzJltug2FPisUk4uF3M/Dsco2AOiREc6USEpulPIBtlpzMKzFu080ZWRk9OKLL44ZMyYiIkLZBemM3encSx60GXIQAPQJRcirPvTOdC7AVmumzLR21ii0CU/I7nTu5+Fa8z4AAFCU13wp/8Psl4GMSauGWuqHM1hKcb6AN2ZIYEetOTIAAKAozqbUADvq0EOtOVOIIFSKnWnc65gmAwD6SrvW4EZnrXgVDSQqh5vuhX0LAHpqvBudXsmnVmjHKjPorBXv5wfcWFfaFvcGBwB9JaDJLG96d7p2DAoRhIqH46IAAHP96P+7z0m0IQrRXyvYtVK+soEMd8Q0GQDQa76WlK8lFZWrBUmIIFQw2XBQ+25DAgCgaNoyZQZBqEhiKTmYxc32RQwCAJCXPOiEIj5XpOlTZhCEirQ/kxtoRzuZIAgBAIhQQKZ40nvSEYT6ZGc6N9cPKQgA8Je5fvSudI7T7ChEECpMWiX/oJIPccUuBQD4Sx9bysqQnM3X6CREr60wO1K5Ob60AfYoAEATc/3onZp9QSG6bcWQcuSXDG6OL/YnAMC/zPSmT+ZypXXqrqNl6LgV42g219mS8rHECUIAgH+xNCRhbvQvDzR3UIggVIyd6RxuRg8AINdcP3qHBl9QiL5bAfJE/JViPrwTdiYAgBzDHCkpT64Ua+iUGfTdCrD3AT/Vi9aWW1ACAKjeqz70nvsaOihEECrA0Wxusgf2JABAi17yoI5m85o5JET3/byKxCS9kh9sj2kyAAAt8rKgLAzJjVJNjEIE4fOKzOHGuuDyQQCAZxjvRkXmaOLRUfTfzysyhw9zw3AQAOAZwtzoY9kYEeocsZRcKODGYFk1AIBnecGeyq/ls2s0LgvRgz+XM/lcX1vKylDddQAAaDyaImNc6KgcBKFuiczhw9ywDwEAWiVMI08TohNvP56Q6Fw+FCcIAQBaZ7QLfbmIr5aou45/QxC2X2IJb21IvC0QhAAArWJmQF6wp2LyNGtQiCBsv8gcbrw7UhAAoA3C3OhIDTtNiCBsv2PZOEEIANA2E9ypqFyO1aQoRD/eTjk1fKGYD+yIESEAQBs4m1JuZlRCkQYlIYKwnY5m82FuNI0cBABoI02bO4ogbKfIHA4LygAAtEOYG31Mk04TIgjbo0pCrpbwI5yx9wAA2izAlqqRkPRKTclCdOXtcSqPG2RPmeIGhAAAbUcRMs6N0py5owjC9jiWzU9wx64DAGin8W605pwmVNaghuO4b775JjIy0tbWdsWKFYGBgU+2OXPmzMaNG0Ui0bRp015//XVCSE1NzY4dOy5cuFBZWdm7d+8VK1bY29sTQpKTk3/44YfGJy5ZsqR79+5KqvyZWJ6czOM+74fxIABAOwU7UdPP8Y/rSQcjdZeivCD89ttvd+/evWXLljt37owePTolJcXBwaFpg5SUlEmTJv34449OTk6vvvqqqanptGnTsrKyEhISZsyYYWdnt3HjxpEjRyYlJdE0nZ2dfeHChU8++UT23A4dOiip7Na4VMh7mFMuppgpAwDQTkYMCXKiT+RyM73Vf3RNWUG4cePG77//fvDgwYMHD46Ojv7pp5/ee++9pg1+/PHHqVOnzpgxgxDy4Ycfbty4cdq0aT169Ni/f7+sQa9evSwtLR8+fOjp6UkIsbW1femll5RUbZtE5nC4jh4A4DmFuVGROfxMb3XXoaRzhOXl5dnZ2QMHDpQ9HDhw4M2bN5u1SUpKGjBgQGODpKQknv/XidOUlBShUNg4jszKypo6deqCBQvOnj2rjJpbD3fiBQB4fuPc6NOPuAYNOFGolBFhYWEhRVFWVlayhzY2NoWFhc3aFBUVWVtby762trauq6urrKxsfEpNTc0bb7zx8ccfm5iYEEKcnZ2XLVvm5eWVmpoaHh6+efNm2VDySXfv3v3jjz92794te8gwzJEjRywtLeU2FolEFNW2SEuvosRSQy8jUU1Nm54HpLa2lmVZmsZgWhUkEgnLslKpVN2F6It2dCYgJMTX3OBkZm2wQ9vCsE1729jYWCB4RtIpJQgtLCx4nq+rqzM1NSWEiESiJ6PI3NxcLBbLvq6traVpWtaYECIWi8PCwvr37//OO+/ItgQEBAQEBMi+NjMz+/bbb1sKQm9v7z59+rz88suyhwYGBs7Ozi3VyfO8mZlZm361M5ncBHfevI3PAkIIRVFCoRBBqBqyIDQ2NlZ3IfqiHZ0JEEImenBnSgTjvZk2PUvhe1spQWhvb29sbJyRkeHv708IycjIcHNza9bGzc0tIyND9nVGRoaTk5OBgQEhpK6ubuLEia6urlu3bpWb+e7u7o8fP27pRxsZGbm6uvbp00dhv8y/ReZwq3u37W8GAAByjXenxp7kNg5UcxlK+XguEAgmT578448/EkKKi4sPHz48bdo0QkhZWdn69evr6+sJIdOmTfvll19qamp4nt+6dausQUNDw5QpUywsLHbt2tV06JCamio7yFNTU/PDDz8MGTJEGWU/0+N6cqeMH+aAAyAAAArQ1YoyYkhymZqvrFfWcarPPvvs4sWLXbt27dat2/Tp0wcNGkQIKSoqWrlypeyI6KRJkwIDA729vX19ffPz82VzSi9fvhwZGXnw4EEDAwOKoiiKunTpEiFk48aNHTp06Nq1q6OjI0VRGzZsUFLZT3c8hxvpTBthQAgAoCDjXKlj2WoOQqrZXE0F4nk+IyPDysrK1ta2cSPLsgzzT5IUFRWJRCLZBRJPV1ZWVlpa6uDgYGFh8ZRmS5cu9fHxWbJkSWsqrK6uNjc3b01Lmcmx7AR3apYGXPWijUQiEc4RqgzOEapYWzsTaHSugH/vKntlQhvO0yl8bytxeRSKory9m18h0jQFCSGyhWNaw8bGxsbGRjGVtUs9S2IfcVsGGaixBgAAHTPUgcqs5h+JeGf1rVKCj+etda6A97ehbPEJGwBAcRiKjHKmT+Sp8+gogrC1InO4MCy0DQCgaGHqvhMFevbWisrhx2NBGQAARRvrSl8o4GrVt/wDgrBVkh7zRgzxtUQQAgAomKUh6WtLxearbbE1BGGrHM3mJ7gjBQEAlCLMjVbj0VEEYavgjhMAAMozsRN1LJvj1BSF6NyfLb+Wf1jND7TDiBAAQCnczaiOxlRiiXqSEEH4bMey+VA3WoBdBQCgNOPdqcgc9ZwmRO/+bJE5HG5ACACgVGo8TYggfAaRlMQX8aNdsKMAAJQosCNVLOazqtWQhejfnyEmjxtgR5ljYTUAAGWiKRLqRh9Xx6AQQfgMkTk85osCAKhAmJt6ThOii38ajifRuVyoK04QAgAo3Shn+moJX9Gg6p+LIHyaP4t5RxOqkzmCEABA6YQCMsSBislT9aAQQfg0kTkc1hcFAFAZtcwdRRA+zbFsHnecAABQmTA3+kQuJ1XtmBC9fIsyqviKBtLHFiNCAAAVcTQhnhbUpSKVDgoRhC06lsOHuVGIQQAAVQpzo1U8dxRB2KLIbNyJFwBA1ca7U0ezMSLUABUN5MZjPtgRA0IAAJXqaUNJOZJSobosTJToewAAGCpJREFURBDKF53LBTnSQoG66wAA0D9hbpQq544iCOWLzOHDcCdeAAB1CHNX6WlCBKEcEo7E5HEhrtg5AABqEORI3S3ni8Qq+nHo6+W4WMj7WVIOQnXXAQCglwxoMsKJPpGrokEhglCOyBzMFwUAUKcwd9WdJkR3L0dUDo878QIAqFGoK302n6tjVfGzEITN3SnnpTzpbo0gBABQGxsj4m9DnctXxaAQQdjcsWx+AuaLAgCom8rmjiIIm4vM4XAnXgAAtZvoTh3L4VUwJESP/y/FYpJWyQ9xwIgQAEDNvC0oMwG5War0KEQQ/ktkDjfahTbEXgEA0ADj3aljyj86ii7/XyIxXxQAQGOo5j69CMJ/iKXkfAE31gX7BABAI7xgT+XW8Hki5WYhOv1/xObzAR0oayN11wEAAIQQQhiKjHVV+qAQQfgPLCgDAKBpwtwoZV9EgX7/LzwhUbk4QQgAoFlGu9DxhXyNRIk/AkH4l+ulvIUB8bZAEAIAaBBzAzLQnop5pMRBIYLwL8eyOSwoAwCggZQ9dxRB+Jdj2TwWlAEA0EDj3anoXI5VWhSi6yeEkJwavkDM97fDiBAAQOO4mlJOJtSfxcpKQgQhIYQcy+FDXWkGOQgAoJHGK3PuKIKQEEIisznMFwUA0Fhh7nRkNkaESlMjIVdK+BHO2BUAABqqjy1VJSH3K5WShej9yYk8bpA9ZW6g7joAAKAFFCGhrtTxXAShckRivigAgMYLc6cjs5VymlDfA4Dlyck8bhxOEAIAaLYRTtTNx/zjesW/sr4HYXwR725GuZgiCAEANJoRQ4Y70qfyFD8oFCj8FRtdv379xo0bXbp0GTx4sNwG+fn5p0+fNjMzCwkJEQqFjdvPnj2bmZkZGBjo7+/fuLG0tPTkyZMGBgYhISHm5uaKKjIyGwttAwBohzB3KjKHD7NT8MsqKwO+/vrrCRMm3Lp1a/bs2StXrnyyQVJSUvfu3S9cuLBly5ZBgwaJxWLZ9gULFixatCgpKWn06NE7duyQbczIyOjWrdvJkyd//fXXgICAsrIyRdV5DHfiBQDQEuNc6VN5XIPCx4S8EtTU1FhaWiYmJvI8n52dbWxs/OjRo2ZtIiIiVq1axfM8y7L9+/ffsWMHz/P37983MTEpKirief7s2bMODg719fU8z8+fP3/BggWyJ44dO/bzzz9v6UcvWbJk48aNrazz+qNq518lXJt/P2iPmpoalmXVXYW+aGhoEIvF6q5Cj1RVVam7BH0x8Kjk2P0axb6mUkaEly5dsrKy6tu3LyHEzc2tV69eMTExzdI3KioqIiKCEELT9MSJE6Oioggh0dHRgwYNsrOzI4QMHz5cIpFcv36dEBIVFTVp0iTZcyMiImSNn1/0I2aCO4XxIACAtghzp0/kKzi5lHKOMD8/39nZufGhs7Pzo0ePmjYoKyurq6trbOPk5JSfn9/siRRFOTo6Pnr0iOO4wsLCJxvLVVJSkpWVVV1dLXtoYmLyxhtvGBoaym184hH1fi9OIlHuLR9BRiKRCAQCmsYZWVWQSCQsyzIMo+5C9IVEIpFIlHnHPPhbiBOZcI9p/d5mGOaZ3Y5SgpBlWarJQIumaZZlmzUghDS2YRhGKpW29ESO4ziOe7KxXPX19bW1tY0nEauqqurr61vqDsKdG4baGf67NFAWlmVlh0bVXYheYP+m7kL0Bfa2yviZk/meDSwrf3jzpNZ8+FZKEDo6OhYXFzc+LCoqGjVqVNMGtra2BgYGxcXFtra2sgZOTk6yJ6akpDR9opOTk0AgsLOzKy4u7tKlCyGksLBQ1lguFxeXoKCgJUuWtKbO+Z0l5ibGbfzloJ1YljU2NsaIUDUYhpHtcHUXoi8kEgn2tsos7qrgva2UXmngwIF5eXkPHjwghJSXlycmJg4fPpwQIhaLZWM1mqaHDx9+8uRJWfuTJ08GBwcTQoKCguLi4kQiESEkKSmprq4uICBAtv3UqVOyxqdOnZI1BgAAeH5KGRHa2NgsXLgwPDx89uzZBw8enDRpkre3NyHkp59+2rp1a1JSEiHk/fffDw8PF4lE2dnZaWlp+/fvJ4T07t172LBhoaGhYWFh27ZtW758uampKSHk3XffHT58OMMwVVVVp0+fvnHjhjLKBgAAPaSs41RffvnlRx99VFZWtnjx4j179sg2BgcHr127VvZ1UFDQ+fPnOY7r2rVrYmKitbW1bPvBgwdnz55dVla2YcOG1atXyzb27t37ypUrRkZGTk5ON27ccHFxUUiRMTExHIeZMipy9erV0tJSdVehL7Kzs+/evavuKvRFbW3thQsX1F2FHmk8mqgolI5NXli6dKmPj08rzxE6ODgkJSU5ODgouyoghEycOHHWrFmya2ZA2b766qvs7OyNGzequxC9cPXq1YULF167dk3dhegLQ0NDkUhkYKCwewZh5gKojo596tJk2NUArYcgBAAAvYYgBAAAvaZr5whDQkIyMzNdXV1b0zguLq5///4trTsDipWcnOzg4CBbPw+ULScnp66uztfXV92F6IWqqqrU1NTAwEB1F6Ivzp49GxQURLVufczw8PCFCxc+vY2uBWFiYmJeXl4r79OUlZXl4eGh7JJAJj8/38bGBhcdq0ZVVVVDQ4NswQpQNpZlHz165Obmpu5C9EWbum4PDw8vL6+nt9G1IAQAAGgTnCMEAAC9hiAEAAC9hiAEAAC9hiAEAAC9ppRFtzVfUVFRSkqKp6dnSxO9KioqTp48yTDMmDFjWjkHFVpSXV198uRJjuPGjBljaWnZ7LsikSghIaHxoZ+fXyuvfoFGcXFxDx486NevX/fu3eU2yMjIiIuLc3Z2fvHFF3EnrOeUk5Nz7tw5e3v7ESNGCATNu9Dc3Ny0tLTGhwMHDpTdOQDaJz8/PzU1tXPnzi3dfa+0tDQmJsbY2HjMmDEmJibt/DG8/hk1apRQKDQ1Nf3mm2/kNsjNzXV2do6IiBg/frynp2dxcbGKK9QlRUVFHh4e48ePj4iIcHFxyc3NbdYgJSXF0NBwxN8OHz6sljq116JFi3x8fObPn29vb79z584nGxw/frxDhw6vv/567969J06cqPoKdcnZs2dtbGzmzp0bGBg4atQo2b2mm9q0aZODg0Pj+/nhw4dqqVM3BAYGmpmZCYXC3bt3y22Qnp5uZ2c3ffr00aNHd+vWrbKysn0/SB+DMCsrSyKRjBkzpqUgXLZs2axZs2Rfh4eHf/TRRyqsTtesWbMmPDxc9vWsWbOWL1/erEFKSoqdnZ3K69IRGRkZQqGwoKCA5/nY2FgHB4eGhoZmbXr27PnTTz/xPF9dXe3o6BgXF6eGQnXFoEGDvv/+e57nxWKxp6fniRMnmjXYtGnT9OnT1VGaDsrMzJRKpQMGDGgpCF977bUlS5bwPM9xXFBQUEtd+jPp40GSTp06PXlAo6njx49PnjxZ9nVERMTx48dVUpduas3OZFk2NjY2Pj5edk9maL3o6OgXXnhBdgeVoKAgiUSSmJjYtEFOTk5ycrLsph9mZmajR4/G+7ndysrK4uPjZTvT2Ng4NDRU7s4sLy8/ceLEzZs3cZe35+Th4cEwzFMaHD9+XPbn+P/27j4oqqoNAPjZD5bvBZZguYCAS8sI8aVAQFhQYg3GgIGpxFROYsUIjGnaFOCAkWEwKoOZlTISSuQSKkQpaIpCTATxDYFIAUIssLvsALuwy+6+f5zpzg4siCLwAs/vr3ufc+69Z8/u3Gf33rP3UCiU+ZyrV2MifKi+vj4rKyu8bGVl1dvbu7TtWdZ6e3sf2plMJjMjIyMmJobL5arfLwQP1dvbS07PSaFQCIKY0sN9fX3GxsYGBgZ4FT7P89HX10en09lsNl7V2JkUCoXP53/11Vdbt2718fERCASL3szVQi6XDw0NkZ//+Xy2V+ZgmV27dtXV1U0Jbtmy5ejRo3PZfHJyknyKHY1Gm5ycfMLtW1kuXbqksWPxW6BQKMjRGRo7k8vldnZ24uXk5OQ9e/Y0NTUtZHtXFIVCof7ERTqdPqWHp1SAz/N84A/z7CeH999/f+/evQghmUwWFBR05MiRjIyMxW7o6qBQKJRK5RM5V6/MRHj48GGJRDIlaGxsPMfNCYIYHBzEy3w+f6bRSgALDAx0cnKaqZQgiIGBAbyssTPVL31EREQkJydPTExoa2svRFNXHoIg1Gein97DFhYWw8PDMpkMP1yez+cTBLHYrVwp8C3Y4eFhfDLR2Jnk55nBYGzbti0vL2+xW7lq6OjomJiYDA4OcjgcNL9z9cq8NMrhcJynIX9BaySXy0UiEV4OCAi4fv06Xi4pKQkICFjoBi9rLBZrem+T4/hn6kyRSCSXy6fs6s8//zQ3N4csOHcBAQHl5eX4a19DQ8PY2JiHhwdCSCKRjI6OIoTWrl1rY2Nz48YN9N+92BdffHFp27x8sdlsJyenkpIShJBKpSotLcWdqVAoNF4Crampgf8CPXH4uwheflLn6pX5i3B258+fr6ysbG5uFovFra2tUVFRXl5eJSUlb775plAoRAjt37/fz89PX19fJpPl5+dPGX0AHklcXJyXl5eJiQmDwcjKyqqoqMBxDoeTm5sbFBSUnp7e1tbm4ODQ29ublZV14sSJpW3w8rJ+/frnn38+ODg4JCTkm2++2bdvH74dmJCQ8ODBg0uXLlGp1I8//vjdd9/dv39/eXk5k8ncsmXLUrd6GYuPj4+Li+vu7q6pqZHJZGFhYQihuro6T09PmUympaUVGRlpaWnJZrOrqqpKSkrKy8uXusnL2OnTp+vr6zs7O7OzsysrK2NjY52dnfPz8xMSEvD9lEOHDr3yyisUCkUgEJSVlZ08efLxDkRLSkp6kg1fDoaHh7W1tV944QV3d3dLS8tnnnmGxWLp6uo6Ojq6ubkhhMzNzcPCwlpbW3V0dDIzMx86hQeYBYvF2r59e1tbG5VKzcjIcHR0xHFLS0sfHx8mk0kQxMjISH9/P5vNPnr0aFBQ0NI2eNkJDw9XKpUPHjx46623oqOjcdDIyMjV1RVfMvLw8HB1dW1paXFxcTl58iTMhDUfLi4uzz77bHNzM5fLPXXqFP7awWAwuFyup6cnhUKxtLQcHBwUiUSurq5nzpyBid7mQygU6unpvfTSSy4uLpaWls7OzkZGRnp6ek5OTviak7W19auvvtrS0mJkZPTll1+S4/IeFUzDBAAAYFVbmfcIAQAAgDmCRAgAAGBVg0QIAABgVYNECAAAYFWDRAgAAGBVg0QIAABgVYNECMCCEwqFPB5PJpMtdUNQQUHBzZs38XJXV1d2djZ+AM0iE4lEPB5vYmJi8Q8NwHSQCAFYcPHx8d9++y1+2ufSSklJOXv2LF7+448/du3axefz57/b6urqrKysuddnMpmJiYmnTp2a/6EBmD9IhAAsrPb29rNnzyYmJi51Q6Zydnb+9NNPTU1N57+rwsLC2NjYuden0WiHDh1KSUkRi8XzPzoA8wSJEICFdebMGVtb240bN06JKxQKPp+vcS5iqVTa398/06XU2UvJCuRDo8bHx/l8/vRJYtetW5eQkDB9VpbJyUly9pUplErlwMDAI012I5PJ+vv7pVLplPiOHTsmJycvXLgw910BsEAgEQLwaGJjY62trTs6OvDq5OTkpk2bNmzYoPFmm1KpzMnJee2119QnBRwfHz9w4ICZmZmFhYWBgYG9vT2eHQIhxOfzw8PDjYyMCIIwMTGJjo5Wn1Csr68vNDQUP6CVxWLFxsaOj4/jor///pvFYuXk5EREROAKQqFQoVB8+OGHLBbLwsLCysrq+++/V29bcXExQRBdXV141dPTMy4u7vjx42ZmZubm5iwWKzMzk6xcVVUVEBCgo6PDZrP19fX9/Pzq6+tx0cGDB9PS0qRSKYvFwsfCcbFYHBUVZWJiQhAEk8kMDw9Xz6/6+vqbN2/Ozs5+nPcAgCdLBQB4FKOjo46Ojq6urlKpVKVSffTRR3Q6vaKiQmPl2tpahFBBQYF6MDg4mMFgJCcnV1dXV1VVZWRkXLlyRaVSTUxMuLm5GRsbZ2dn19fXp6ena2lphYWF4a2kUqmTkxOLxbp48WJ9fX1qaiqdTn/jjTdw6b179xBC5ubmb7/99o0bN37++WeJRHL48GEKhZKYmNjY2HjlyhUbGxtjY+OdO3fiTXg8HkKoo6MDr3K5XAsLi40bN167du23334LDQ2l0WgtLS24tKioKCEh4fbt262trcXFxe7u7hYWFqOjoyqVqrW1NTIyUltbu7S0tLS09ObNmyqVSi6X+/r6EgSRnZ3d3Nx8+fJlW1tbb29vhUJB9sMXX3xBo9FEItGTeWMAeFyQCAF4ZI2Njbq6unFxcb/88guVSk1LS5up5nfffYcQampqIiN40GZ6evr0yjgz5ebmkpGEhASEUH19vUqlysnJQQj9+OOPZOnBgwcpFEpbW5vqv0QYGBhIlkqlUkNDw4iICDKCf3fOkgjNzc3FYjFeFQqFWlpaqampGl8XPlxRURFeTUxM1NPTU6+Qm5uLELp79y4ZKSsrQwjdunWLjFy9ehUhVF5ervEQACya1TgfIQDz5OzsfOLEiejo6PPnzwcFBR04cGCmmvhiIIvFIiOlpaUIod27d0+vXFdXR6PRtm3bRkZ27tyZkpJy584dV1fXuro6bW3trVu3kqU7duxIS0u7c+eOg4MDjoSEhJCl9+/fHxkZCQ8PJyObNm2afkdQnZ+fH5PJxMsmJiYWFhY9PT1kaU9PD4/H6+7uxjf8qFQqeX14uuvXrxsaGo6Pj5NXfRUKBY1Ga2pqImdPxeN0BgYGZmkSAIsAEiEAjyMyMvKTTz4RCoVJSUnq9/+moNPpCCH10SVDQ0P6+voaE1J3d/dTTz2lpaVFRiwtLRFCePbz7u5uNptNpVI1lmLk/TmEUG9v75QIQoggiFlelHrCRghpa2uTQ3IuXLjwzjvvODo6ent7m5iYUCgUKpU6y5hPPp8vkUi2b9+uHmQymSKRiFyVy+UIIfXXC8CSgEQIwOOIiYmRy+V2dnYxMTF3796d6WzOZrMRQgKBYM2aNThibGw8NjY2OjqK53RVp6+vLxQKlUolme3wD0ojIyNcOjQ0pF5fvRRTT8nm5uYIIY2bPIYjR44EBwcXFBTgVbFYfOzYsVnqGxkZmZqazv4nRZzCcRcBsIRg1CgAjywvLy87O/v06dM8Hq+2tjY+Pn6mml5eXgihxsZGMuLv748QwvfnpvD29pbL5SUlJWSkqKgIIeTr64sQ8vHxkUgkt27d0lg6HZfL1dHRuXbtGhmprq6ekhfnSKVS/fPPPxs2bCAjxcXF6hUMDAwmJibU/6Hh7+8/MDBw+/btWXbb0NCgo6Pj4uLyGE0C4Ela6puUACwz7e3thoaGUVFRePX48eMUCuXq1asz1edwOO+99x65qlAofH198dDQoaGhwcHBwsLCX3/9VaVSjY6O2tnZ2dnZlZWVicXi/Px8JpPp7++PNxSLxdbW1vb29hUVFcPDw3l5eQYGBi+//DIuxaNXeDye+qH37t3LYDDOnTs3MjJSW1vr4uKip6c3y2CZ3bt3q2/+9NNP79mzBy97eHhwudympiaJRFJYWGhra0uj0ZKSknDpTz/9hBBKTU2trKysqalRqVRjY2OOjo5WVlY8Hk8gEAiFwt9///2DDz5ob28n9x8YGLh58+ZH6HoAFgYkQgAegVQqdXd3d3JyGhsbwxGlUhkaGspisbq6ujRu8tlnn5mZmclkMjIiEAjCwsLI65+GhoaXL1/GRX/99ZeHhwf5PTUoKGhgYIDcsKmpyc3NDRdRKJSQkBCBQICLNCZCiUTy+uuv4/oMBuPYsWPr169/vERYXV1tZ2eHd2VqalpYWKirq0smQqVSuW/fPoIgKBQKg8HAQfyvR/JlUqnU5557rqenB5fy+Xw6nf7DDz/Mue8BWCgU1X+PnwAALITBwUF7e/tz586ROQkbGhrq7Ow0MDDgcDg6OjrqRffv3xcIBNbW1ng4zBQdHR1CoXDNmjWzj3whdXd39/f3Ozg4TB+hg0dyzvGFyOXytrY2pVK5bt26uT83VSQS3bt3T09Pz9raWr0BqampX3/9dVtb2//DI1jBKgeJEIAF9/nnn1+8eLGhoUF9zOdqNjY2xuFwMjMzpwwrBWBJQCIEYMHJ5fKenh4bGxv8bwoglUr//ffftWvXzvLPEwAWDSRCAAAAqxpcqAEAALCqQSIEAACwqkEiBAAAsKpBIgQAALCq/Q+q9CxlurG6fAAAAABJRU5ErkJggg==", + "text/html": [ + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ], + "image/svg+xml": [ + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ] + }, + "metadata": {}, + "execution_count": 12 + } + ], + "cell_type": "code", + "source": [ + "Plots.plot(getindex.(points, 1), u_points, xlabel = \"x (coordinate)\", ylabel = \"u (temperature)\", label = nothing)" + ], + "metadata": {}, + "execution_count": 12 + }, + { + "cell_type": "markdown", + "source": [ + "*Figure 3*: Temperature along the cut line from *Figure 2*." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Secondly, the horizontal heat flux (i.e. the first component of the heat flux vector) is plotted." + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "Plot{Plots.GRBackend() n=1}", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAlgAAAGQCAIAAAD9V4nPAAAABmJLR0QA/wD/AP+gvaeTAAAgAElEQVR4nOzdeXxMV/8H8DP3zmTf98WaIEEoUhoiCGLfIqhdG081bZ8+WqVapeqptbRFq4q2llDUvstCRO0iREJIJEEE2SP7ZOae8/tjnt/QZEJkmcnMfN5/eM3cOZn7zWTMZ865554rYowRAAAAfcVpugAAAABNQhACAIBeQxACAIBeQxACAIBeQxACAIBeQxACAIBeQxACAIBeQxACAIBeQxACAIBeQxACAIBe0+4gjIuL27p1aw0bM8awnpyaCYKg6RL0DqVU0yXoF7zg6lfvHyzaHYTx8fGRkZE1bFxRUSGXyxu0HqiktLRU0yXoF0ppeXm5pqvQL3iTq1+9v+baHYQAAAB1hCAEAAC9hiAEAAC9hiAEAAC9hiAEAAC9hiAEAAC9hiAEAAC9hiAEAAC9hiAEAACtsTOFTjxnUL/PKa7fpwMAAGggvybSb6/TvX4yQgzr8WkRhAAAoAVWxNENd2j0MN5RVM+rRiMIAQCgUWOEzL0iHHvI/h7Gu5qKiorq+fkRhAAA0HgJjMz4W0gsYOeGi63rc0D0OQQhAAA0UlKBTDojFFWwiCFi0wbLK8waBQCAxqhETkZGyOWUHBrQgClIEIQAANAI5UvJgBNyR2PR3n68Ed+w+0IQAgBA4/K0jPQ5Jn/TTrSlNy9u+JhCEAIAQCOSUsh8D8snunNruvMitewRk2UAAKCxiM9jg8OErztzMzzV109DEAIAQKNwNZuNjJCv9uHHual1tBJBCAAAmhf1hE04Ld/SWzyoiXoGRJ9DEAIAgIYdekBDzgn7+ot9HdWdggRBCAAAmvXbXfpNLD05WPyGjQZSkCAIAQBAg9beoqsTaNQQvrWlZlKQIAgBAEBTVsTRrcn07DC+ianGUpAgCAEAQP0oIx+eF67lsLPDxHZGGi4GQQgAAGpVQcnUM0JmGTs1VGwh0XQ1CEIAAFCnUjkZc0ouFolODBI39CKiNYQl1gAAQE0KKsjAk3JbQ9H+/g2+lHbNIQgBAEAdMsuI/zF5JxvRVrUspV1zjakWAADQUQ+Kmd9R+cAmop968Jwmp4iqoLFjhCUlJYQQU1PT6hoUFxfzPG9sbKzGogAAoP7lSUmfY8KcDtyH7Rpj70sDNcnl8mnTprm6ujZp0mTq1KkymaxSg0uXLrm4uDRr1szR0bFLly5xcXHqLxIAAOrLh+eF0S1EjTMFiUaCcPPmzXFxcRkZGRkZGbdu3frjjz8qNXB3d7969WpeXl5+fr6/v/+MGTPUXyQAANSL9Yk0uZAtfbPRzI2pQgNBGBoa+v7775uampqYmISEhISGhlZqYG9v7+rqSgjhed7f3z87O1v9RQIAQN3dLmALrwk7+vCGjTcHNXGMMCUlxdPTU3Hbw8MjNTW1apuKiootW7bk5+fv2rVr2bJl1T0VY6y4uFj5DBKJxNXVleMaae8bAECvlAtk4mlhRTfe06qRTY/5Jw0EYWFhoXKOjJmZWUFBQdU2jLHU1NRHjx6VlZWJRNW+gvfu3YuMjOzXr59yy6ZNm3x8fFQ2lkqlHMdJJI1gGQO9UVJS8pI/H9Q7Sml5eTmlVNOF6BHFvD9QadY1sZupaKxLeXFxfT7ta32wmJiYvLJ3pIEgtLe3V4ZfQUGBg4ND1TaGhobLly8nhJw7d27QoEHDhw9XOX20devWo0aNqjq4qpJEIkEQqhljzMzMTNNV6BFKqVgsNjEx0XQh+gVvcpWOp7PwJ8L1QLGZoWH9PnO9f7BoYBTRy8srJiZGcTsmJsbLy+sljd3d3UtKSkpLS9VSGgAA1INHJSz4rHxXX966nkOwQWigR/jBBx8EBwf37dtXJBKtXr1606ZNiu0BAQFLly7t2rXrgQMHxGJxmzZt8vLylixZ4u/vb2trq/46AQCgFigj06KF/7TnfRy048iIBoJw8ODBixYt+vjjjxlj33zzzdChQxXbra2tFeOWxsbGP/30U1pamrW1tZ+f3+eff67+IgEAoHaW3KACI3Pf0Jp5iyLGmKZrqL3t27eHhYXV8BghJsuoX1FRkbm5uaar0COKyTI4RqhOxcXFOEb4oivZbHi4/OpIcTOzhuoO1vsHi9YkNgAANHIFFWT8aWFjT77hUrAhIAgBAKB+fHheGN5MNLK5liULLswLAAD14Le79HY+uzRS+2JF+yoGAIDGJrGAzbsqRA9rLBedfy1a1oEFAIDGRiqQiVHC0q5828a9lFp1EIQAAFAnc64I7haif3loa6BgaBQAAGrvRDo79IBdD9TiNNHi0gEAQLMyy8h754TdfXkbbVhKrTra2pMFAADNooxMPiN/35PzddTKQ4NKCEIAAKiNFTepjJJ5nbQ+RzA0CgAAr+1SFluTIMSMEvPa3RskBD1CAAB4XcUyMjVa+LUn38RU+2MQQQgAAK8r5LwQ4CoapW1LqVUHQ6MAAPAaNifRG7nsqhYupVYd3flNAACgod0rZHOvCJFDxMY6lB460rEFAICGJqNkUpTwX2++o40uHBpUQhACAECNfHFVcDUVhbTVteDQoc4tAAA0mBPpbG+adi+lVh0d/JUAAKB+ZZWRf/0thPbR7qXUqqNrPVwAAKhfjJDgs/L3PEV9XXTq0KASghAAAF5m5U36TEbmd9LCS+7WDIZGAQCgWtdy2PfxwuURYrHu9pt09zcDAIC6KZaRSVHCGh++hbluDooqIAgBAECFEjl5+7S8t7NovLuOJ4WO/3oAAFALT0pJ76NyJ2PRzz109tCgEoIQAAD+IT6PdT8sH9ZM9HsvXqIHKYHJMgAA8Fx4BptyRr7ah5+g6yOiSghCAAD4n/WJdPF1eihA7OOgy7NjKkEQAgAAoYzMuSKcSGfnhvMtdXqOaFUIQgAAfVcukHeihexydmGE2MpA09Wonb4MAQMAgEpPy0ivo3JjMTkxSB9TkCAIAQD02a181v2wfEhT0eZevIG+BgKGRgEA9FRkBpt0Rv7DW/ykVvqagYQQBCEAgH76I4l+dVXY00/cy0m/psZUpbEgTEhIyMnJ8fb2Njc3r/qoVCqNj48vKyvz8vKytrZWf3kAALqKEbIoVthxj50ZJvaw1PcUJC8JQrlcnpaWlp2dLRaLnZ2dmzZtWo97nTZt2tmzZ9u0aRMXF3fixInOnTu/+OilS5eGDBnSsmVLMzOzuLi49evXT5gwoR73DgCgt8rkZGq0kF3OLo8U6+RVdmuhchDKZLKDBw9u27YtOjq6qKhIud3JySkgIGD69Om9e/eu4y7//vvviIiI27dvW1lZLV269Msvvzx58uSLDVxcXGJjY1u0aEEI2b179/vvv//2229znF4PYQMA1F1WGRkRIW9jIQofLNbbqTFVPQ9CSumOHTvmzZv35MkTHx+f999/39PT09raWi6X5+TkxMfHnzt3rk+fPt7e3t9//31d4nDfvn3Dhw+3srIihEydOnXBggWFhYUWFhbKBs2aNVPe7tixY3FxsVQqNTY2rvUeAQAg+RkbFi5McBct7MJjPPRFz4Nw9+7dc+bMmTVr1uTJk11cXFS2vnXr1m+//TZ48OD4+Hh3d/fa7TI9Pd3b21tx29XVleO4jIyMF4PwRWvWrBkxYkR1KVhWVvbw4cO//vrrf7+MWNy3b9/qnopSqvwX1INSihdcnej/03QhekRbXvDTT8jkM8J33bjJ7iJGKdN0PXXxWq95TUYTnwehn59fSkqKqanpS1q3b9/+xx9//Pzzz1/e7OXKy8slEonitkgkkkgkZWVlKltu2LAhLCzswoUL1T1VTk5OWlrarl27lFtcXFy8vLxUNpZKpRzHKXcNalBWVsbzun8Nl8aDUlpeXq7pKvRLWVlZ4z9wsyON/zqO39pD3tOBlpZqupo6e60PFhMTk1f+gZ4HYZMmTWr4vM7OzjVsqZKTk1Nubq7idklJSVlZmcon3Lp16+LFi6Oiol6yu6ZNm/bu3Ts0NLQm+5VIJAhCNWOMmZmZaboKPUIpFYvFJiYmmi5EvzTmNzkjZH6MsCeN/T2cb2OpI8vG1PsHiwa+yPj4+Jw9e1ZxOzo6umXLlo6OjpXa7NmzZ968eWFhYa1atVJ7gQAAukAqkClnhKjH7PxwcRucJlE91UFYWlq6fPlyHx8fFxcXm3+q+y4nTJjw8OHD2bNn//XXXzNnzpw1a5ai3xoYGLh48WJCyIULFyZMmNC/f/8jR46sWLFixYoV+fn5dd8vAID+yJWSASfkFQI5NURsb6Tpaho31ecRTpky5cCBA/369Rs3bpyRUT2/hGZmZufPn1+7du2RI0cWLVo0ceJExfagoCDFJB1TU9PZs2cTQpT5pxXHogEAGol7hWxomDCoiehHH55DV/BVRIxVnj1UUVFhZma2dOlSRRo1Ztu3bw8LC6vhMUJMllG/oqIilSsHQQNRTJbBMUJ1Ki4ubmzHCE+ks+Cz8uXd+GmtG/ssntqp9w8WFT3CgoICmUzWr1+/etwNAAA0tApKvrwq7E1jf/UT++n9CqI1p+L7gr29fZs2beLj49VfDQAA1E5aEet9VJ5aSGIDkYKvR0UQikSiP/74Y+nSpREREXK5XP01AQDAa9mWTLsdko934/YH8LZYQfQ1qZ4sExwc/ODBgwEDBkgkkkrD33l5eWopDAAAXq1QRkLOCfF5LGqo2MsaHcHaqHbWaHFxsZpLAQCA13I1m02MEro7iC6PFJvg8rK1pfqVmz9/vprrAACAmmOErE2gS24IP/Xg33bTzdmhavPqrxCNcHIwAIA+e1JKppyRyyi5Fihuaorh0Lqq9nvEqVOn/P39ra2tzc3NLSwsevTosX//fnVWBgAAVR1LZ94HZb2cudNDkYL1Q3WP8ODBg0FBQQ4ODuPHj3dycsrJyTl27FhQUND69etDQkLUXCIAABBCpAJZGCvsSmG7++IEifqkOghnz57dv3//Q4cOKddX+/HHH4ODg7/88svg4GADAx1ZwhwAQFvcfcYmnBZamItiA8U2OEGiXqkYGs3Ozk5JSVm4cOGLq4yKxeJvv/22oKAgMTFRjeUBAAD5/S71OyIPacvt788jBetdtZNlRKLK/e6qWwAAoEEVysgH54QbuezUEHEHG3wINwjVS6y5ubktXry4oqJCuZFS+t///tfS0tLT01ON5QEA6K8r2azLATkvIldHIQUbkOoe4XfffTd27Fh3d/dRo0a5uLhkZWUdP348KSnpp59+MjREtxwAoGFRRpbF0Z9vCRt68iOa4zTBhqU6CIOCgk6cOPHtt9/++uuvcrmc47jOnTvv3r173Lhxaq4PAEDfZJSwKWcEQkjMKLErTpBoeNUeIxw4cODAgQOlUmlpaamRkZGxsbE6ywIA0E+HH9AZ54SQttyCzjyPEFSLV6wsY2hoiLFQAAA1KBfI3CvCoQdsbz9xT5wmqEbPg/DmzZtHjx719/fv3r37mjVrSkpKVP7AvHnz1FUbAIC+OJ/JPjgvtLMSxY0WW+JUbfV6HoTXr19fsGDB0qVLu3fvvmLFiszMTJU/gCAEAKhHGSVs7lUa/YSt7MaNd8e8GA14HoTTpk2bNm2a4vbjx481VA8AgL6QUfLLbfrtdWFSK+72GLG5RNMF6SvVxwifPXtmamoqFv/jUblcXlRUZG1trZbCAAB02aEH9LPLtIO16MoosZs5jghqkupuuIeHx5UrVyptvHr1qo2NTcOXBACgyxIL2MAT8nlX6Xpf/kAAjxTUuNe4pLFcLpdI0HUHAKilYhlZFS+sT6SzvPhPO3AGOCDYOPwjCMvLy8vKygghlNKioqL8/HzlQ6Wlpfv27XN1dVV3gQAA2o8ysjmJzo8RhjXj4kdLHHBidmPyjyBcv379rFmzFLcHDRpUtfXy5cvVURQAgA65ms1mXhTkjBwIEPs4YCC00flHEPbv33/Dhg2EkDlz5oSEhLi7uysfMjMz69ChQ4cOHdRdIACA1npSSr6JFY6nsyVvclNac8jAxukfQaiMuoqKisDAQAyEAgDUjlQgqxPoqnjhPQ8ucYzYDPMrGjHVk2WCg4MFQai0sbi4WCQSmZqaNnxVAABaLDKD/eei4G5BLo0Qu1ugH9jYqZ60NHDgwK+//rrSxl9//fWNN95o+JIAALTVnQI26KT8PxeFNd35IwOQgtpBRRBWVFRcuHBh1KhRlbYHBgampKQ8fPhQLYUBAGiTZxXks8tCr6PyQU24uNHiAFdEoNZQEYQ5OTmUUkdHx0rbHRwcCCHVrUEKAKCfGCHbkmnbvbLsMhIfJPnEi5PgBEGtouIYoZWVlVgsTkhI8PT0fHF7fHw8IcTW1lZNpQEANHqXc7gvIuUGPDkyQOxth16gVlLxvcXExKRv375z5869f/++cmNWVtbMmTPbtWvn5uamvuoAABqrK9lsSJg8+KJkphd3bjhSUIupnjX6448/9uzZ09PTs2/fvq6urk+fPj1z5gylNDw8XM31AQA0NnF5bMl1ejmbzfLitvmU2lmaaboiqBPVQdiuXbvY2NilS5dGREScP3/e0tIyMDDwyy+/bNu2bb3stbCwcM+ePUVFRUOHDm3dunXVBlKpNC4u7sGDB/7+/nZ2dvWyUwCAOrqZxxYrI7APb8ST4mJN1wR1Vu2i2y1atNi4cWND7LK4uLhbt27t2rVr2bJl165dT5w40b179xcbMMYsLCyaN29+//79qKgoBCEAaFzVCASd8bKrT1RUVKSmpmZnZ/v5+dXjLkNDQ+3s7Pbt2ycSiezt7ZcsWXL06NEXG4hEoqysLEtLS0zMAQCNu5bDvokVbuaRL97gtvvzuGSE7lH9JxUEYe7cuVZWVm3btp0wYYJi4/Tp0ydPnlz3XYaHhw8bNkwkEhFChg8fHhERQSmt1MbS0rLuOwIAqIvYHDYiXAiMEAY34ZLGij9oiwsn6SbVPcKvvvpq7dq1c+fOtbCw+OGHHxQbR48e/fbbb0ulUkNDw7rsMiMjw9nZWXHbxcWloqIiJydHcZLi68rNzb1+/fqcOXMUd3menz59erNmzVQ2lkqlHMdVDV1oOFKp1MDAQNNV6BFKqVQq5XkM29VVQj5ZFs+dy2QftyWhPZkRLxA5kcpVtJRKpbhQq5q91geLgYGBot/1EiqCUCaTrVu37rvvvvv444+jo6OV2zt16lRSUpKent6qVauaV1yVSCRijCluK268ssqXkEgkVlZWLz55XWoDAD13I48sucnF5pLZXuwPX2aILxV6QEUQZmdnFxcX9+vXr9J2CwsLQsiLV+utHRcXF+XyNE+fPjUwMKj1sUBbW1svL6+vvvqqhu05jsN3N3WqqKio4/gBvBZKKWMMr3nt3Mhli2JpTA77vCP3V3+uhtNhZDIZXnA1q/cPFhUD3hYWFhzHZWRkVNp+8+ZNQoiLi0sddzlgwICjR48q+oJHjhwJCAjgOI4Qcv/+/YKCgjo+OQDA64rLY6MjhWHhQl8XUfI48cfta5qCoBtUBKGZmVnv3r2/+eabZ8+eKUca8/Ly5syZ4+3tXfeLFE6ePDk7O3vMmDGzZ89evny5sj8XGBj4559/Km7PmjVr3LhxxcXFCxYsGDdu3NOnT+u4UwCAqm7msaBIYWiY0MdZdA8RqK9UT5ZZu3atn5+fh4dHu3btCgsLJ06cGBERUVpaeurUqbrv0tzc/MqVK3v37i0qKrp69aryiOMPP/zQokULxe2BAwcWFhaOHTtWcdfMDAs3AEB9upXPVsTRiAz6iRe/vQ9v/LJTyUDHqf7je3l5xcbGLl68WJl//v7+X3/9tZeXV73s1cLCIjg4uNJGf39/5e2BAwfWy44AACqJesJWJ9CYbDanI7ehpwQRCCreAmVlZWvXrh06dOjvv/+u/oIAABqCVCC7UunqBFohkJle3C5/9ALhf1S8EQoKCr744os+ffqovRgAgPqXXU5+TaTrE4WONqLlXfkBTXCWFfyDiskyDg4OdnZ2uBI9AGi7W/nsvb8Fjz2y9BIWOUR8cpB4IFIQqlDRI+R5fvHixV9//bW3tzeuPggAWocREvaI/RgvxOezD9vySWMldkaargkaMdVj5KdOncrKyvL09Gzfvn2liz9ERESopTAAgNdWJieh9+iaBGrAk0+9uPHuWB0UXq3ag8VdunRRZx0AAHXxpJT8kihsvEN9HLiffXl/Z4yAQk2pDsK//vpLzXUAANTO9Vy2OoEefUgnuHPnholbWyIC4fVg+jAAaCXKyJGHdHUCTSkk/27PrfaRWGPJT6iV50GYlZWVnJzcokULV1fXq1evVlRUqPwBX19fddUGAKBCsYxsTqJrb1FbI/KpFxfUghPjQCDUwfMgPHbsWHBw8NKlS7/88sthw4ZlZWWp/AHlFZQAANTsSSnZcEdYd5v6OIjW+/L9XTEKCvXgeRAOHTr077//Vqz2eeTIkep6hAAAakYZCXvENt6hfz+l77ThYkaJm5shAqHePA9CBwcH5WXiu3XrpqF6AACee1pGtibRDXeojSGZ4cmF9pGY4YqiUN8wWQYAGh3KSEQG23iHRj2h41pye/vxXezQBYSG8jwIw8LCVq1a9cofwAn1ANBwMsvIliS68Q414snU1txvfpgLCg2u2h5hTEzMs2fPPD09XVxcsrKyEhMTJRIJpowCQENghJzKYBvu0FOPaVALbnc//k10AUFdngfhwIEDlVcBXLNmzaNHj86dO9e+fXvFlvv370+cOLFz584aqBEAdFe+lOxJo2sSKCNkWmtuQ0+JDbqAoF4qeoQymeybb77Zu3evMgUJIS1atPj99987duz4+eefV1p9FADgdTFCoh6zDXdoRAYNbM790Yt/ywFdQNAMFUGYk5NTUFDg5ORUabuTk5NcLr9//z6CEABqraCC/JVKf7pF5Yy805pb74suIGiYivUYbGxszMzMNm7cWGn7pk2bOI5r1qyZWgoDAJ3CCDnzhE2MElrukl3IZBv9+MQx4rlvcEhB0DgVPUJDQ8Mvvvhi/vz5CQkJgYGBTk5OOTk5J06cOHr06AcffKA81xAAoCYUXcCfb9MKgbzbhvuph8QW4QeNiepZo1999ZWtre2yZcs+/vhjxRZHR8fFixd//vnnaqwNALSYwEjYI7YliUZk0BHNufW+vK8jjgJCY1Tt6RMhISEhISF5eXmPHj1ycnJCRxAAaiixgG1NpqHJrJkZeacNt9FPYmWg6ZoAqveKlWWsrKxkMpmNjY16qgEA7fWsguxKpVuS6MNiMqW16NQQ3tMKXUDQAq8IwkePHjVv3jw2NhZnEAKASpSRU4/ZliR6PJ0GuHILOvMDm4h4JCBoD6w1CgC1lPyMbU2m25KZgzF5pw33Uw+cCAFaCUEIAK+nSEb2pNHNSTT5GZvUijs2kO9ggw4gaDEEIQDUCGUk+inbkkQPP6B9nLk5HbjBTTkJLg0P2u8VQejs7BwTE+Pp6ameagCgEUorYluT6dZkZikh77bhVr0lsTfSdE0A9Ud1EBYUFFhZWRFCJBKJt7e3cvvdu3c9PDzUVBoAaFSJnOxLo5uT6K18NsGd29+f72yLIVDQQarHNfz8/E6ePFlp4969e996662GLwkANIkycuYJCz4rNN0p25vGPm7PPZooWdMdKQg6S3UQtmrVaujQoQsWLBAEgRBSWloaHBw8duzYwMBA9ZYHAOqTWMC+ihFa7pZ/eknoYCO6PUZyeAA/ugVngAOBoNNUD43u27fvu+++W7BgwdmzZ+fPnz9z5sz09PRt27ZNmTJFzfUBQEPLLie7UmjoPfq4lEx0Fx0byHtZo/MHekR1EHIc98UXX/j5+Q0ePHjAgAEeHh4xMTE4OgigS6QCOfKQht5jZ5/Q4c24JW/y/VxEHBIQ9E+1s0aLiorWr19fVFTUtGnTBw8enD59GkEIoAMYIeefstB7dF8a7WwnmtKK29FHYibRdFkAmqN67P/27dvdu3c/cuTIzp07U1JSZs6c+dFHHwUGBubn59fLXg8ePDht2rSPP/74zp07KhskJiZ+/PHH06ZNO3z4cL3sEQDuFbJvYoVWu+Uh5wV3C9GN0eKIweKprTmkIOg51UE4YMAAIyOj2NjY8ePHSySS5cuX79+/Pzo6+sVTKWrtr7/+CgkJGTBggJ2dXc+ePTMzMys1ePr0ac+ePe3t7QcMGDBjxoy9e/fWfacAeitPSn5NpL5H5H5H5M8qyN7+fEKQ+POOXBNTDIMCEFLd0GhISMicOXMMDZ+vGzhq1KjOnTtPmDCh7rtcuXLlsmXLJk2aRAiJjY39/fff582b92KD3377zc/P7+uvvyaESKXSlStXjhkzpu77BdArFZScSKfbktnpx3RQU+6rTvwAV5EY8z8BqlAdhPPnz6+6sXnz5tHR0XXcn0wmi42N7d27t+Junz59oqKiKrW5dOlS//79Fbd79+793nvvyeVysRirwQHUyOUsFnqP/pVK21mLprbmNveWWGDwE6B6r5cuEkld/z9lZWVRSm1tbRV37e3tnzx5UqnN06dPlQ3s7OwopZmZma6urlWfLT09PTo6evTo0cotc+bM6dChg8pdS6VSjuPq/itAzZWUlIhEGH9Tk4clZGcav+s+LxLJJrSkZwLkzUwJIYRISbFUw7XpsJKSEk2XoHde64PFxMSE414xEqLubpaxsTEhpKKiQnG3vLzcxMSkahtlA6lUSgip2kbBzs6uZcuW48ePV9zleb5t27bVNeZ5HkGoZoIgVPfngPryrILsu89C77HEAja2BdnUXdarqWIlUFwVXh0opXiTq9lrfbC8MgWJ+oPQ2traxMTk4cOH9vb2hJCHDx9W7eq5uro+fPhQcfvhw4empqaKhU+rMjY2btas2bhx42qya+7/1aF8eD14wRuOnJKwDBaaTMMe0X6u3GcduEFNOTGh5eUyvObqhDe5+tX7a67uv59IJBo9enRoaCghpKysbM+ePUFBQYSQ0tLSnTt3lpWVEUKCgoL27NmjuB0aGhoUFDePT40AACAASURBVIThNQClaznsk0tCk52yZTcEfxdR6tuSvf34Ec2xEBpALWlgBsrChQv79u178+bNx48ft2nTZuTIkYSQ3NzciRMnPnr0yNXVNTAwcOvWrV26dHF2dk5JSTl9+rT6iwRobB6VsO33WGgylVIypRV3frjY3QJfEAHqgQaCsFWrVnfv3o2JiTE3N+/UqZNio4uLy/37952cnAghYrH4yJEjcXFxRUVFXbt2NTLCpc9AfxXJyP77dFsyjctlY924jX58D0eMkADUJ9VBSCndtWvXoUOH0tPTlfNWFGJiYuq+V2NjYz8/vxe38DzfvHlz5V2RSKTMSAA9JDASmcFC79FjD2lvZ+6jdtzQppwhr+myAHSR6iD84IMPNm7c2KZNm7Zt2xoYYO4ZgPrczGOhyfTPFNbUjExuxa3pLrE1fPVPAUCtqQhCuVy+bdu2OXPmrFixArNUANTjSSnZmUK3JdOCCjK5lej0UN7DEv/7ANRBRRDm5uaWl5ePHz8eKQjQ0MoFcuA+3ZZMr2SzUc25Nd35Xs74jwegViqC0N7e3tXVNS0trUuXLuovCEBPXM5iW5LpX6m0q71oWmtuf3/OGMsIAmiCiv95HMetW7du3rx5bdu2bdeunfprAtBhT0pJ6D26JYkKjExrzcWNFuMqEACapfor6Lp1654+fdqhQ4dmzZopl/1UqJdZowD6RiqQww/p1iR6IYsFteA2+fG+jsg/gEZBdRA2b968ulXNAOC1XMthW5LorlT6ho3onTbcX/04EwyBAjQmqv9Hbtq0Sc11AOiYrDKyI4VuTqIlMjKtDRczStzcDF1AgMYIX00B6pOMkuPpdHMSO/uUjmjG/YRZoACN3vMgfPDgwdWrVzt06ODh4XH06NHy8nKVP4CLxQOodDOPbU6if6ZQT0vRO2247X0kZrjkF4A2eB6Ep0+fDg4OXrp06Zdffjl9+vSsrCyVP8AYU1dtAFogV0r+vEc3J9FcKZnWWnQBa2EDaJvnQThmzJhevXop5oheuXJFLpdrriqAxk5g5OQjtjmJnsqgw5pxK9/i/Z1FHBIQQAs9D0Jzc3Nzc3PF7RfXvwaAF2WUsN+T2G93qIspmd6G+91PYonleAG0GSbLANQIZeTkI7bxDv37KR3vzh0ZyL9hgw4ggC5AEAK8wuNS9sdd9ttd6mhMZnhyO/wlpvh/A6BD8B8aQDXKSEQG23CHRj+h49y4AwF8Z1t0AQF0EIIQoLKnZeSPu/S3u9TGkMzw5Lb1xokQALoMQQjwP5SRU4/Zhjv09GM6piW3px/vbYcuIIDuUx2Eqampbm5uVbdHRUX5+/s3cEkA6pZZRrYk0U13qYWEzPDkNveSmKMLCKA3OJVb+/btu2bNmhe3UEpXrFgREBCglqoA1IERcuoxe/u00HavLLmQ/enPxwaKQ9pySEEAvaK6Rzhq1KhPPvnk0qVLGzZssLCwePz48aRJk86dO7do0SI11wfQELLL/9cFNObJ+57cxp44FxBAf6kOwtWrV/fs2fNf//qXt7f3rFmzFi5caGhoePr0aT8/PzXXB1C/zj1lvyTSE+l0VAtuW2/exwFHAQH0XbWTZcaMGdOxY8c333zzww8/7Nix46lTp+zs7NRZGUA9klGyJ43+GE8LZeTf7bhffCVW6AICACGkumOEhJD09PTg4GCpVOrr6xsfH79s2bKKigp1VgZQL3KlZFkcbblb/sddurALnzhG/HF7DikIAEqqg/DIkSOdO3dOT0+Pioo6d+7cli1b1q9f36NHj5SUFDXXB1Brd5+xD84Lrf+SJT9jxwfykUPEw5phXWwAqEx1EL733nu9evWKi4vr0aMHIWTq1Knnzp179uyZt7e3essDqI1zT9m4U0Kfo3JLAxI/WvxHL74j1gUFgGqoPka4atWqSZMmiV64sHaXLl2uXbv2/vvvq6swgNdWLpA/U+jqBEoImdme29ZHYsRruiYAaPRUB+HkyZOrbrSwsNi5c2cD1wNQG1llZHMS/fk29bQiS97khjXDCCgA1BSWWAPtdjOPrU6gBx/Qt924iMG8pxUSEABej+ogdHJyyszMVPkQY6wh6wGoEcrIiUfsx3jhzjPyUTsueZzE1lDTNQGAdlIdhPPnzy8pKVHeLS8vj46Ovnr16qeffqquwgBUK5GTbcl0TQI1k5BPvbhxbpyk2pOAAABeTXUQ/vvf/6668bPPPouPj2/gegCq9aiErbtNf7tLezlxm/x4PyeMggJAPXiNY4QfffSRu7t7enp606ZNG64ggKri89iyOBr2iE5tzV0eKXYzRwQCQL15jSCklBJC8vLyEISgNgn5bFEsPZ9JP+vA/9pTYoHrQgBAfavR0ZXy8vK4uLgPP/zQ2NjYw8Oj7ns9fPiwr69vhw4dFi1apMjXSjZt2hQSEhIQEHDr1q267w600e0CNvWMEHBc/qad6N44yWcdOKQgADSE15g1ampq+vPPPxsZGdVxl3fu3Jk8efKOHTtatWo1fvx4KyurmTNnvtiAMRYeHu7t7b1jx46CgoI67g60zq189u11Gv2EzunI/9pTYoJzfACgIdVo1qiRkVHz5s19fX3t7e3rvstNmzYFBQUNHz6cELJw4cJ58+ZVCkKRSLRnzx5CyMqVK+u+O9AiiQVs2Q0ankFD2nIb/TAQCgDq8BqzRutLQkLCiBEjFLfffPPNpKQkqVRqaIizwPTaixGY5IsIBAD1aZBRp5ycnGvXrlXd3qtXL2Nj46ysLCsrK8UWa2trxlh2dnaTJk1qsaOkpKSDBw+2bNlSuWXjxo0+Pj4qG0ulUo7jJBJ8xKpPcXHxK9vcLRR9f1t8OpOb3kqIHSKYixkpJ0XlaqhOB1FKpVKpIAiaLkSPlJSUYJkRNavJB4uSiYkJz79i0eHnQXj27NkNGza88kl37Njxyjb3799fu3Zt1e2dOnUyNja2srJS/hpFRUWEEGUuvq7WrVv379//+++/V9zleb5p06Ycp3oGkIGBAYJQ/czNzat7KLWIrYijhx7QkLbchj48eoF1RymVSCQmJiaaLkSPiEQiMzMzTVehd17ywVILz4MwPz8/ISGhXp70zTffPHbsWHWPurm5JSUlKW4nJSU5ODjU+m2keAu6ubnV7sdBU+4+Y99ep5EZdFYH/t44iRkiEAA053kQjhw5cuTIkWrY5eTJkydNmjR79mxHR8c1a9ZMmTJFsX3NmjXdunXr3r07IaSwsFAQBMZYUVFRfn6+paVldf080C5pRWz5//cC1/WQWOJK8QCgac/T5c8//1SEECFk8eLFDx48aKBd+vv7BwcHt23b1sHBobS0dP78+YrtBw4cSExMVNweOHCgu7s7IWTixInu7u4NVwyoTdIzNvWM4HNY3sJcdG+c5JsuPFIQABqD5z1CxlheXp7i9s8//9y3b9/mzZs30F4XL168YMECmUz24qDomTNnlLcvXrzYQLsG9Ut+xr69TsMy6Mz2/DpfiTkGQgGgMXkehG5ubmlpaXv37u3YsaMgCI8fP05NTa36A/V1QM7Q0BCnTOi8lCLRj7HCyUf0P4hAAGisngehj4/P0KFDx44dq7irvFEJJgpDTRRUkG9ihR3JBp90EP3UA+cFAkDj9TwIRSLRgQMH4uPjU1NTp02bNnv27HpZVhT0DWVk+z0694oQ4MpdHlThZo8jgQDQqFU+ob5Dhw4dOnQYMGDA6NGj27Vrp5GaQHudz2T/uSiYiMnxQeLOtqKiIowfAEBjp3plmb/++kvNdYC2yyhhc6/S6CdsRTdugjuHCwYCgLZ4fvrEjRs3anhCfVhYWFZWVoOVBFpGRsmaBPrGfrmtIbk9RjwRKQgAWuV5EGZmZnp7ewcFBR09elQul1dtWlRUtGXLlu7du48bN04mk6mxSGi8Dj2gbffKzzxhV0aJ13TnMS8UALTO86HRgQMHxsbGfvnllyNHjjQxMfH29m7Tpo2NjY1cLs/Ly7t58+bNmzd5nn/33XcPHTrk4OCgwaKhMUgsYJ9cFB6VkPW+fIArOoEAoK3+cYywffv2hw8fTk1NDQ0NjYqK2rdvX15eHs/z9vb2Xbt2nTp16uTJk21sbDRVKzQSBRVkeZywJYl+6sV/2oEzwOJ3AKDNVEyWcXNzW7hw4cKFCwkhMplMLBaLRPi+D4QQQhn5I4kuiBFGNOfigyT2RpouCACgzl5xPUJctAiUzmeymRcFI/5/p0ZouhwAgPrRIBfmBR3zuJR9cYWefsKWvslNaY1JoQCgU3B4B15GKpBlcbTjPnlTM3J3rHgqUhAAdA56hFCtww/orMvUy1p0eaTY3QIJCAC6CUEIKiQ9Y59eEu4VkrXd+SFNEYEAoMswNAr/UEHJl1eFnkfk/V25hCAxUhAAdJ7qIDxy5EjVjZTSFStWNHA9oEn3CpnvYfntApIQJPnUi5PgaxIA6AHVH3Xvvffeu+++W1JSotySlZU1ZMiQefPmqaswULc9abTnEfnkVtzBAN7BWNPVAACoi+og/P777/fu3du1a1fFMtxRUVGdOnWKi4sLCwtTb3mgDkUyMvWM8PU1enKQeKYX5oUCgH5RHYSTJk2KiYkxMDDo1q3blClTAgIC2rdvf+PGjf79+6u5PmhoV7NZlwNyYzG5NkrcCafJA4D+qfYokIeHx4EDB8Ri8fbt2728vI4dO+bo6KjOyqChMULWJNChYfJvvbkNPXkTzCAGAL1UbRCGh4f7+PhYWlrOnj379u3b/fr1e/TokTorgwaVVUaGhsl3ptJLI8Xj3TErBgD0l+pPwIULFw4ePLhr167Xr19fuXLl2bNn09PTO3XqdPToUTXXBw3hRDrrdEDW1U50bpjYzRzDoQCg11QH4R9//LFixYojR47Y2dkRQnx8fK5fv+7n5zdixAj1lgf1rIKSzy4LIeeFXX3Fi7x5MbqCAKD3VB8XOnHihJeX14tbrK2t9+/fv3btWrVUBQ0i6RmbECU0MxXFBoptDTVdDQBA46C6R1ApBRVEItHMmTMVt0+fPu3v79+AdUF925ZMfY/Ip7biDgTwSEEAAKVazhQsKyt78uRJ/ZYCDaRQRkLOCfF57MxQcXtrHBEEAPgHHCPScVezmfcBuVhELo9ECgIAqIBzx3QWI2RtAl1yQ/ipB/+2G77xAACohiDUTY9L2dQzgoySa4HipqboCAIAVAsdBR10LJ15H5D3cuZOD0UKAgC8AnqEOkUqkIWxwq4U9lc/sZ8TIhAA4NUQhLrjTgGbGCW0MBfFBoptcIIEAEDN1HJotEWLFpMnT67fUqAudqXQXkflH7Tl9vfnkYIAADWnukf47NkzSulLfszFxWXOnDl13Hd5ebmRkVF1jzLGZDKZgYFBHfeiD5bcoL/dpaeHir1wggQAwGtS3SP08PCweZXQ0NBa7/XXX3+1tbV1cnLq169fVlZWpUfv3r3br18/U1NTGxubt9566/r167Xekc6TUxJyTjj0gF4cgRQEAKgN1T3CVatWffbZZ66urqNHj3Z0dMzNzT1+/HhcXNzixYudnJwUbd58883a7TIpKenzzz+/cOFC27Ztp0+fPnfu3M2bN7/YoLy8/L333jt8+LCxsfH8+fPHjBmTkpJSu33ptnwpCYqUWxmKzgwV42qCAAC1I2KMVd06duxYc3PzP/7448WNCxYsiIyMvHjxYh13OX/+/Hv37u3atYsQcuvWra5du+bl5VU3Rnrv3r3WrVsXFxebmppWfXT79u1hYWE17JtKpVKO4yQSSV2KbzxSi9iwMGFIU9F33XiusXYFi4qKzM3NNV2FHqGUlpeXm5iYaLoQPVJcXGxmZqbpKvRLvX+wqBgaLS4u3r9//yeffFJp+8yZMy9dunTv3r067jIlJaVt27aK256enlKpNCMjo7rGhw4d8vb2VpmChBDGWEVFRf4LVOa67rmUxXoekf+nPbfqrcabggAAWkHFgFpxcTGl9NmzZ5W2K7YUFha+8kkzMzMrjXYqTJ061cXFpaCgQPkFiud5Y2Pj/Px8lc9z4cKFJUuWREREVLej5OTkgwcPhoeH/++XEYv//PPP7t27q2ysMz3CQ+ncp9fEv3QTBrlIi4s1Xc1LlZSUiEQIavVR9AhfPtMN6ldJSYmmS9A7r/XBYmJiwnGvOD9CRRA6Ojq2adPmk08+OXjwYNOmTRUbc3JyPvjgA1tb23bt2r1yx4IgFKv6hBYEgRBiZ2enTFOZTFZaWurg4FC18bVr1wIDA//8809vb+/qdtSmTZtx48bVcGhUIpHoQBCuSaCr4mnYYL6zrRbMp2WMYdRInSilYrEYQ6Nqhje5mtX7B4uKIBSJRL///vuQIUPc3d27du3q7OyclZUVExNDKd29e/dLTnhQcnFxWbx4cXWPtmvX7sKFC4rb169ft7KycnZ2rtQmLi5u2LBhmzZtGjRo0Ov8OrpMRslHF4RrOezySN7FBN0sAID6obrD2LNnz1u3bs2aNcvMzCwxMdHAwOD999+Pi4sbOXJk3Xc5bdq06OjoAwcOZGRkzJ8//91331X00r766qtt27YRQpKSkvr37x8YGGhiYhIZGRkZGVlaWlr3/Wq1ZxVkaJj8aSmJHipGCgIA1KNqJ903bdp0+fLlDbFLFxeXvXv3LliwICcnZ9CgQcq+I6VUMdUlIyOjU6dOycnJK1asUDy0detWfR7tyShhw8KFbvaidT14MZZJBwCoV6pPn9AW+nD6xJVsFhghzH2D+0977ctAnD6hZjh9Qv1w+oT61fsHC07DbtQO3Kch54Xf/PjhzbQvBQEAtAKCsPFaFU/XJtCTg8SdbXFQEACgoSAIGyOBkZkXhbNP2bnhfDMzpCAAQANCEDY6xTIy/rRcRsm54WILLTugCQCgfXDkqXHJKGG9jspdTEXHBiIFAQDUAUHYiFzLYT6HhamtuY09cZoEAICaYGi0sTj8gL53TtjYkx/ZHBkIAKA+CMJGYU0CXRlPjw0Uv2mHqTEAAGqFINQwgZFPLwlRj9n54XxzTBAFAFA7BKEmSQUSFCmXM3J+BKbGAABoBoJQk764Kog50cF+mBoDAKAxCEKNOfmI7Utj10eLkYIAABqEINSMzDIy/ayw3Z+3NdR0KQAA+g2dEQ2gjEw5I5/hyfk7Y3YMAICGIQg1YOVNWiaQ+Z3x4gMAaB6GRtUtJof9kCBcGSnm0RsEAGgE0ClRq0IZGX9aWO+LUwYBABoLBKFafXhe6O8iGt0CLzsAQGOBoVH12ZZMr+ewq6PwmgMANCL4UFaTlEI2+7IQMURsgpccAKAxwRidOsgomXRGWNiFf8MGhwYBABoXBKE6zLsq2BqSD9vh1QYAaHQwTtfgwjPY7lQWGyhGZxAAoBFCEDaszDLybrSww5+3M9J0KQAAoAoG6xoQI+Rff8uDPUR9sJQaAEBjhSBsQD/E06wy8nVnXtOFAABAtTA02lBic9h3N4VLI8QSfNkAAGjE8CHdIErkZGKUsNqHb2mOQVEAgEYNQdggPjov+DmJJrjj5QUAaOwwNFr/9qTRC1nsGpZSAwDQBviwrmepReyj80LYYLG5RNOlAABADWDsrj7JKZkcJXzVie9si0ODAADaAUFYnxZcE6wMyX+88KoCAGgNDI3Wm+gnbGsyvR4oQWcQAECLaCYIGWM3b95kjHXs2JHjVPSfnj17lpyczPO8h4eHiYmJ+it8XdnlZPIZYVtvsaOxpksBAIDXoYEgLCoqGjBgQHFxsUgkMjExCQ8Pt7CweLHBqVOnxo4d6+npWV5enp6evn379oEDB6q/zppjhPzrb2FKK1F/V/QGAQC0jAaOZq1fv97Y2DguLu7GjRvm5ua//PJLpQY9evTIzs6+cOFCbGzsvHnzZs2apf4iX8vaBPq4hH3jjaXUAAC0jwaCcM+ePdOmTeM4juO4adOm7dmzp1IDY2Njnv9fqLi4uIhEjbqbdSOXLY0TdvfjDTBFBgBAC2lgaPThw4ctW7ZU3G7ZsmV6enrVNlKpdOHChVlZWTdv3tywYUN1TyWVSp88eRIZGanc4uPjY2ZmVu81V0exlNoPb/FuWEoNAEA7NUgQnjhxYsuWLZU28jz/559/EkLKysoMDAwUG42MjEpKSqo+g0gkcnNzMzMzO3/+/Pnz5319fVXu6OnTpwkJCUuXLlVu+fbbb9944w2VjaVSKcdxEkl9nuj+0RVxF2sy0kleXFyPz6o7SkpKGnmHXsdQSsvLyymlmi5Ej6j8BIMG9VofLCYmJiqnZL6oQYKwTZs248aNq7RRWYqTk1NeXp7idm5urrOzc9VnMDAwmDFjBiEkMDCwU6dOISEhlSbUKDRv3jwgICA0NLQmVUkkkvoNwn1p9EIOjQ0Um2ERmWowxtTZQQdKqVgs1oqJ1roEb3I1q/cPlgYJQnd3d3d39+oe7dq167lz54YMGUII+fvvv7t27fqSpzI2Nm6cX2/TS9hHF4RDAVhKDQBAu2ngGOF//vOfAQMGeHh4cBz3008/nTx5UrG9RYsWW7du7d2794YNG4qKijw9PbOzs9euXfv222+r7A5qkJySCaeFOR35txww7gcAoN00EIRvvfXWgQMHfv/9d0LIvn37fHx8FNsnTZrk4uJCCOnatev27dvPnz9vaWk5c+bMSZMmqb/Il/v6mmAuIbM6YJ4oAIDW08zKMn379u3bt2+ljUuWLFHc6NKlS5cuXdReVE0tvCbsv8+ih4nRGQQA0AFYa/Q1MEI+uyREP2Vnh4kdsJQaAIBOQBDWlMDIe38LKYXs9BCxpYGmqwEAgHqCIKwRqUAmRglSgZ0cJDbGawYAoEMw3ePVimVkWLjciCcHApCCAAC6BkH4CnlS0v+EvLWFKLQPL8GrBQCgc/DR/jKPS1nvo/I+zqJffHkOk0QBAHQRgrBaqUWs11HhnTbc8q64vhIAgM7CIS/VYnPYsHD5sq78tNb4rgAAoMsQhCqcfcrGnpJv6MmPao4UBADQcQjCyo6ls3ej5Tv8xQGuOCoIAKD7EIT/sDOFfnJJODRA3B2raQMA6AcE4XPrE+mSGzRysLiDDVIQAEBfIAj/Z/F1ujWZnhvGtzBHCgIA6BEE4f+W0j79mP09XOyEpbQBAPSMvgehwMj754T4PHZqqNjWUNPVAACA2ul1EFZQMilKyJOyU0PEZhJNVwMAAJqgv+fJlcjJ8DC5nJLjA5GCAAD6S0+DMF9KAo7LnU1Ee/rxhlhADQBAj+ljED4tI32OyXs5izb35sX6+AIAAMBzepcDaUXM74h8UitueVce50kAAIB+TZa5XUCGRwoLOnPveerdNwAAAFBJj4LwWi4ZHUV+7sGNaYkUBACA/9GjIKygoh29SL+mSEEAAHhOj4Kwuz3jcJl5AAD4J3SPAABAryEIAQBAr+lREN65cyc1NVXTVeiXsLAwxpimq9Aj2dnZV69e1XQV+uX8+fOFhYWarkKPyGSyU6dO1e9z6lEQ7tixY9++fZquQr+89957BQUFmq5Cj0RHR//000+arkK/LF++HF8+1On+/fuzZ8+u3+fUoyAkhKB3AroN73CAWtCvIAQAAKgEQQgAAHpNu88jfPTo0enTpwMCAmrSODk5WSKRnDlzpoGLgudKSkpGjx4tFmv320yLZGZmZmVl1fB/BNSLuLi4uXPnWltba7oQfVFWVvbkyZOav8kDAwM//PDDl7cRafVBhSdPnpw+fdrR0bEmjfPy8niet7S0bOiqQCktLa1ly5aarkKPlJeX5+fnOzs7a7oQPfLo0SNHR0eJBBc1VRPG2IMHD1q0aFHD9i1btnR3d395G+0OQgAAgDrCMUIAANBrCEIAANBrCEIAANBrCEIAANBrejGvvby8/ObNm4yxt956S2UDxlh0dHRaWlqPHj08PDzUXJ5OevjwYVRUlIODQ0BAQNXTJx49enTnzh3lXR8fHzMzM/UWqAtiYmLi4+O9vLy6du2qssHjx49PnTplbW09YMAAAwMDNZene+RyeURERFZWVt++fZs2bVq1QVRUlCAIittOTk5eXl7qLVAHpaSkpKWldevWzcLCQmWDBw8enDlzxtHRMSAggOf5Wu6G6bqtW7caGBjY2tp26tSpujZTp05t167djBkz7O3td+3apc7ydFJUVJSNjU1wcHC3bt0CAgIEQajUYN26dY6Ojv3/X2pqqkbq1GrLli1r2rRpSEhIs2bNvv3226oNLl++bGNj8+677/r6+vr6+kqlUvUXqUvkcnnfvn3feuut4OBgGxub6Ojoqm3Mzc179uypeFcvW7ZM/UXqkoqKCmtra2tra57nL1++rLJNZGSkjY3N9OnTu3btOmjQoKofNTWk+0GYlZVVUFCwc+fO6oLw5s2blpaWubm5jLGDBw+6ubnV+tUEhZ49e65du5YxVlZW5ubmdvz48UoN1q1bN378eE2UpiPy8/NNTU0TEhIYY3fu3DExMcnLy6vUZvDgwUuWLGGMVVRUdOjQAd/w6ujIkSPu7u7l5eWMsTVr1vTq1atqG3Nz8/v376u9NN0kCEJKSgpjzMLCorog7N69+y+//MIYKy0tbdGiheJyN7Wg+8cI7e3tX34S/dGjR/39/W1sbAghQ4YMefLkya1bt9RVnQ4qKCg4d+7cmDFjCCFGRkbDhg07evSoymYnTpy4fv06pVTtNWq9qKiopk2btm/fnhDi4eHh7u4eGRn5YgOZTBYeHh4UFEQIkUgkI0eOVPlXgJo7evTo8OHDDQ0NCSFjxow5e/asyqsvXb169dSpUzk5OWovUNdwHOfm5vaSBjk5ORcvXlS8yY2NjYcMGVLrN7nuB+ErZWRkNGnSRHFbIpE4ODhkZGRotiSt9vjxY7FY7OTkpLjr6upa9fUUiURZWVnr168fNWqUj49Pbm6u2svUbi++aYmqF/np06eCICjbqPwrwGvJyMhwdXVV3HZycuJ5vupLamtru3nz5v/+978tW7bcQHXtZAAADVxJREFUunWr2mvUL48fP1Z8Yivu1uVNrguTZfbv3//f//636vaYmJiarHIpCMKLyyOJxWK5XF6f9emi2bNnV+qCEEK6deu2ceNGQRBEIpFyI8/zVV/PGTNmfPDBB4SQioqKoUOHLlq0aO3atQ1dsy6p9CJXfdMqpmwo26j8K8BrEQSB4/7XcxCJRBzHVX1J7927p5ivcfz48aCgoOHDhyuGmqAhvPgXIXV7k+tCEPr7+7dp06bq9hrOIHJ2dr59+7biNqU0KyvLxcWlPuvTRTNnznznnXcqbVTM/HRycpLJZPn5+YqPgMzMzKpLXyr/NAYGBkFBQTt27GjwinWLs7NzVlaW8m5mZmalN62iR56dnd28eXOVDeB1vfia5+XlyWSyqi+p8o09ZMgQQ0PDxMREX19ftVapT5ycnKRSaWFhoWJCqcqPmhrShaFRa2trL1Ve/MpcVX5+vkwmI4T06dPnzJkzFRUVhJCLFy8aGRkpDr3ASzRt2rTqC65YBtfe3t7Lyys8PJwQwhiLiIjw9/cnhAiCoHIINDY2VuVMdHiJnj173rlz58mTJ4SQrKys+Ph4Pz8/Qkh5ebniwJWRkZGPj4/ir0AICQ8P79Onj+bq1QV9+vQJDw9njBFCwsPDO3bsaGtrSwgpKioqKyur1PjevXuFhYV4YzeEsrKyoqIiQoiTk5Onp6fiTU4pjYyMVHzU1EYtJ/Rojzt37syYMaNfv362trYzZsxYvXq1Yru9vf2hQ4cUt3v16jVo0KAffvjB3d195cqVmitWR/z5558ODg4rV64cP35827ZtFRPtrl27RghR3J44ceLs2bNXrlw5btw4S0tLxVme8FpmzJjh7e29evXqbt26TZ8+XbFx1apV3bt3V9w+fPiwjY3NihUr3nnnnZYtWxYWFmquWF1QVlbm4eExYcKElStXOjg47N69W7E9ICBg0aJFjLHjx4+PHTt26dKl8+fPd3V1/eCDDzRary6YP3/+jBkzDAwMRo0aNWPGjJycHMbYV199NXToUEWD0NBQJyenVatWjRs3rn379rU+R0j3rz7x+PHjF6cSNWnSZMiQIYSQ7du39+rVq1mzZoSQsrKyLVu2pKen+/r6Dh06VGO16pCzZ8+Gh4fb2tq+++67VlZWhJDc3Nz9+/dPnz6d47hLly5FR0cXFBQ0adJk7NixysPdUHOU0p07dypOqJ8wYYJiUC4+Pj4tLW3EiBGKNpcuXTp27JilpeU777xjZ2en0Xp1QX5+/ubNm/Py8gYNGtSzZ0/FxhMnTjg5OXXu3DkvL+/gwYOpqalGRkbdu3fv16+fZqvVAbt373727Jny7sSJE83MzGJiYnJzcwcOHKjYeObMmYiICHt7+3fffbfWV9nT/SAEAAB4CV04RggAAFBrCEIAANBrCEIAANBrCEIAANBrCEIAANBrCEIAANBrCEKAhlJWVrZnz54XT4TSlJMnTx4+fFhxOzs7e+vWrZmZmeovo7y8fM+ePQUFBerfNcBLIAgBGsr333+/aNEic3NzTRdC1q1bt2rVKsXte/fuvfPOO3fv3q370yYmJm7cuFEqldawvZGR0bp161QukQ+gQQhCgAaRm5v73XffzZ8//8UF8hsDxRXtFQvD1tHZs2fff//90tLSmv/IvHnz1q1bd//+/brvHaC+NK7/ogA6Y/PmzSKRaOTIkZW2M8aysrJUXtNVKpU+ffq0vLxc5RMqHn1J90vRQHEBJkJIRUVFZmZm1QvTuLq6zp8/X7G44IsEQcjOzq5uqamsrCzFwvQ1JJfLnz59WlJSUml7//79HR0dN23aVPOnAmhoCEKAGlm6dKm9vf2VK1cUdxlj48ePb9WqVXUH27Zt2zZ06FBjY2PlFkEQvv32WycnJ0dHR0tLyyZNmuzatUvxUGFh4TvvvGNlZeXs7GxlZTVhwoS8vDzlDxYUFEyePNnS0lLx6JQpU5SH2crLy21sbNasWRMSEqJooLim2PLly+3s7JycnBwcHCpd6/HatWvOzs6XL19W3B0+fPiYMWNCQ0NdXV0dHBwsLCwWLFigbJycnDxw4EBjY2NHR0dTU9POnTtHR0crHvrxxx9nzZpFCHFzc7OxsbGxsVFcll0qlc6aNcvOzs7Z2dnCwiIgIODF/h/HcSNHjsRFa6FxqY8lwgF0n0wm8/X1bdasWW5uLmNs7dq1IpHowIEDKhtnZWWJRKK1a9e+uHHGjBkcx82aNevSpUuxsbEbN27csmWL4qGAgAAjI6Off/755s2bGzduNDU17dGjhyAIjDFKaa9evUxMTNavX3/z5s1ffvnF2Ni4d+/elFLGmGJY0sHBYfjw4SdPnoyMjMzOzt64cSMh5MMPP7xx40Z4eLinp6etra2fn59iXxcuXCCEREdHK+726dPHwcHhjTfeOHTo0OXLl4ODgwkhp06dUjx68eLFWbNmRUZGJiYmRkZG9u7d28zMLCMjgzGWlpb2ySefEEIOHDgQERERERGhWPt/1KhRVlZWv/zyS0JCwsmTJ728vFq1alVaWqp8HRSXn7xz5069/W0A6gZBCFBT6enptra2w4cPv3HjhpGR0aefflpdy8jISELIyZMnlVsSExNFIpHKH1Ek04vX//r1118JIcePH2eMRUVFEUJezNTVq1crs0oRhO3atVOkpkKLFi169eqlvHvr1i2RSPSSIDQ2Nk5PT1fclUql9vb2//73v1X+Xs+ePTMwMFi/fv2Ldebl5SkbKPqLO3fuVG5JSkoSiUTbt29XbomJiSGEKC9jBKBxunCFegD1aNKkyebNm0eOHBkdHd2hQ4fly5dX11IxSGhjY6Pcosit6dOnV21848YNQsjbb7+t3PL222+HhIScPXt28ODBVR8dP378J598cvbs2b59+yq2DB8+XDklp6Cg4P79+59++qmyfbt27dq1a/eS38vLy6tJkyaK2wYGBq1atUpPT1c+mp2dvXv37tTUVMUBP0NDw3v37lX3VGFhYSKRyNTUVPFVQMHGxiYhIUF5V3E9W+XV3gE0DkEI8BoGDx7cqlWr5OTkr776ysDAoLpmYrGYEPLiRBVFNCrz5kUPHz4UiUTOzs7KLVZWViYmJrm5uYpHxWKxvb298lEHh/9r7/5e2fvjOIC/t9mHHcJkP9BEKPNzkkTb/ChpUeIPsAsWV4rIhQtXI0pKKSUSd5QSk6aoFaWE5mplNltrOyxmfmbnOJ+Ld9/TifH19f36zDevx9Xp/XqfH9suntvO65wjjYqKwlVMJpOxyx6PByEkl8u5u0hJSXmny4Yb2Aih6Ohoti/GbDY3NzenpaVptdqkpCQ+ny8QCMJ2+mAkSfJ4PL1e/2KcezElfluEQuFbGwHgD4MgBOAfGBwcdDqdubm5fX19tbW1b10jiJOJm1X46cQkSb5+dihBEAzD+P1+Nr3u7u7u7+/xzNjYWIqiAoGAWCzG1cvLS4qiuNvh8XjsMn7KMc5d1sXFRXx8/Cde7/DwcHFxscViwdHOMMzk5OQ78xMSEvh8vsfj4XYJvYCP7UVUAxBB0DUKwEdtb2+PjIwYjUaTyXR+ft7e3v7WTJVKJRQKj4+P2RGtVosQWlxcfD25oqICIbS2tsaOrK6usuPl5eUIIZPJFLb6mkQikclkGxsb7IjL5cKtpJ/gcDhUKhVOQYSQxWK5vb1lq/h7wMPDAztSVVVFUdTy8vI727RarQihsrKyzx0SAP+9CJ+jBOB/wufzpaSk6HQ63K65tLSEEJqenn5rvkajaWxs5I60tLSIRKKJiQmv13t1dbW5ubmyssIwDE3TpaWlycnJJpMpGAyazWa5XK5UKp+enhiGCYVChYWFONuCweD6+rpEIikqKqIoivmrWWZ8fJy7o6GhIT6fPzo6GggEbDabWq0mCOKdZpn6+nru6tXV1TqdDi83NTVJpdK9vb3Hx8etra2srCyRSGQwGHD18PAQIdTb27uzs7O/vx8KhWiaVqvVYrF4ZmaGJMlgMHhwcDAwMLC7u8tuX6/X5+XlfeIjAOCLQBAC8Pdomq6rq5PJZD6fjx3s6OiIiYk5OjoKu8rc3NyvX7/8fj87cnd319bWxv66EolEU1NTuOTxeGpqativp5WVlQ6Hg13R5XJpNBq2qtVqXS4XLoUNQoqiOjs7cfuMQCDo7u5uaGj4XBCenJzk5+fj/cbFxc3OzmZnZ7NByDCM0WhUKBQCgQAhdH5+zjBMIBBobW1lXyZCqKSkxGq14vkPDw+JiYljY2Mfet8B+CN4zBs3kgAA/BuPj485OTk9PT3cBk6E0PX1tc1mIwgiIyMjLi6OW3K73V6vVyqVhr3/2dnZGUmScrn89U1hwvJ6vW63OzMzk9tog9E0jaPrI2iattvtt7e3SqXynTN/L9zc3NhstqioKIVCgdtEsYWFha6urtPTU/aUJwARB0EIwFeZn5/v7++32+0EQUT6WL4FmqYLCgoMBgO+JQ0A3wQEIQBf5fn52el0pqamxsTERPpYvoVQKOR2u9PT07l/nAIQcRCEAAAAfjS4fAIAAMCPBkEIAADgR4MgBAAA8KNBEAIAAPjRfgOlTdPWogMVFgAAAABJRU5ErkJggg==", + "text/html": [ + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ], + "image/svg+xml": [ + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ] + }, + "metadata": {}, + "execution_count": 13 + } + ], + "cell_type": "code", + "source": [ + "Plots.plot(getindex.(points, 1), getindex.(q_points, 1), xlabel = \"x (coordinate)\", ylabel = \"q_x (flux in x-direction)\", label = nothing)" + ], + "metadata": {}, + "execution_count": 13 + }, + { + "cell_type": "markdown", + "source": [ + "*Figure 4*: $x$-component of the flux along the cut line from *Figure 2*." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "---\n", + "\n", + "*This notebook was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).*" + ], + "metadata": {} + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.1" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.1", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR798/howto/postprocessing.jl b/previews/PR798/howto/postprocessing.jl new file mode 100644 index 0000000000..a424a02138 --- /dev/null +++ b/previews/PR798/howto/postprocessing.jl @@ -0,0 +1,50 @@ +include("../tutorials/heat_equation.jl"); + +function compute_heat_fluxes(cellvalues::CellValues, dh::DofHandler, a::AbstractVector{T}) where {T} + + n = getnbasefunctions(cellvalues) + cell_dofs = zeros(Int, n) + nqp = getnquadpoints(cellvalues) + + # Allocate storage for the fluxes to store + q = [Vec{2, T}[] for _ in 1:getncells(dh.grid)] + + for (cell_num, cell) in enumerate(CellIterator(dh)) + q_cell = q[cell_num] + celldofs!(cell_dofs, dh, cell_num) + aᵉ = a[cell_dofs] + reinit!(cellvalues, cell) + + for q_point in 1:nqp + q_qp = - function_gradient(cellvalues, q_point, aᵉ) + push!(q_cell, q_qp) + end + end + return q +end + +q_gp = compute_heat_fluxes(cellvalues, dh, u); + +projector = L2Projector(ip, grid); + +q_projected = project(projector, q_gp, qr); + +VTKGridFile("heat_equation_flux", grid) do vtk + write_projection(vtk, projector, q_projected, "q") +end; + +points = [Vec((x, 0.75)) for x in range(-1.0, 1.0, length = 101)]; + +ph = PointEvalHandler(grid, points); + +q_points = evaluate_at_points(ph, projector, q_projected); + +u_points = evaluate_at_points(ph, dh, u, :u); + +import Plots + +Plots.plot(getindex.(points, 1), u_points, xlabel = "x (coordinate)", ylabel = "u (temperature)", label = nothing) + +Plots.plot(getindex.(points, 1), getindex.(q_points, 1), xlabel = "x (coordinate)", ylabel = "q_x (flux in x-direction)", label = nothing) + +# This file was generated using Literate.jl, https://github.com/fredrikekre/Literate.jl diff --git a/previews/PR798/howto/postprocessing/93bea075.svg b/previews/PR798/howto/postprocessing/93bea075.svg new file mode 100644 index 0000000000..bcdef5af4d --- /dev/null +++ b/previews/PR798/howto/postprocessing/93bea075.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/previews/PR798/howto/postprocessing/e3095b42.svg b/previews/PR798/howto/postprocessing/e3095b42.svg new file mode 100644 index 0000000000..c71ebedb1f --- /dev/null +++ b/previews/PR798/howto/postprocessing/e3095b42.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/previews/PR798/howto/postprocessing/index.html b/previews/PR798/howto/postprocessing/index.html new file mode 100644 index 0000000000..4df3ce5e47 --- /dev/null +++ b/previews/PR798/howto/postprocessing/index.html @@ -0,0 +1,72 @@ + +Post processing and visualization · Ferrite.jl

Post processing and visualization

Figure 1: Heat flux computed from the solution to the heat equation on the unit square, see previous example: Heat equation.

Tip

This example is also available as a Jupyter notebook: postprocessing.ipynb.

Introduction

After running a simulation, we usually want to visualize the results in different ways. The L2Projector and the PointEvalHandler build a pipeline for doing so. With the L2Projector, integration point quantities can be projected to the nodes. The PointEvalHandler enables evaluation of the finite element approximated function in any coordinate in the domain. Thus with the combination of both functionalities, both nodal quantities and integration point quantities can be evaluated in any coordinate, allowing for example cut-planes through 3D structures or cut-lines through 2D-structures.

This example continues from the Heat equation example, where the temperature field was determined on a square domain. In this example, we first compute the heat flux in each integration point (based on the solved temperature field) and then we do an L2-projection of the fluxes to the nodes of the mesh. By doing this, we can more easily visualize integration points quantities. Finally, we visualize the temperature field and the heat fluxes along a cut-line.

The L2-projection is defined as follows: Find projection $q(\boldsymbol{x}) \in U_h(\Omega)$ such that

\[\int v q \ \mathrm{d}\Omega = \int v d \ \mathrm{d}\Omega \quad \forall v \in U_h(\Omega),\]

where $d$ is the quadrature data to project. Since the flux is a vector the projection function will be solved with multiple right hand sides, e.g. with $d = q_x$ and $d = q_y$ for this 2D problem. In this example, we use standard Lagrange interpolations, and the finite element space $U_h$ is then a subset of the $H^1$ space (continuous functions).

Ferrite has functionality for doing much of this automatically, as displayed in the code below. In particular L2Projector for assembling the left hand side, and project for assembling the right hand sides and solving for the projection.

Implementation

Start by simply running the Heat equation example to solve the problem

include("../tutorials/heat_equation.jl");

Next we define a function that computes the heat flux for each integration point in the domain. Fourier's law is adopted, where the conductivity tensor is assumed to be isotropic with unit conductivity $\lambda = 1 ⇒ q = - \nabla u$, where $u$ is the temperature.

function compute_heat_fluxes(cellvalues::CellValues, dh::DofHandler, a::AbstractVector{T}) where {T}
+
+    n = getnbasefunctions(cellvalues)
+    cell_dofs = zeros(Int, n)
+    nqp = getnquadpoints(cellvalues)
+
+    # Allocate storage for the fluxes to store
+    q = [Vec{2, T}[] for _ in 1:getncells(dh.grid)]
+
+    for (cell_num, cell) in enumerate(CellIterator(dh))
+        q_cell = q[cell_num]
+        celldofs!(cell_dofs, dh, cell_num)
+        aᵉ = a[cell_dofs]
+        reinit!(cellvalues, cell)
+
+        for q_point in 1:nqp
+            q_qp = - function_gradient(cellvalues, q_point, aᵉ)
+            push!(q_cell, q_qp)
+        end
+    end
+    return q
+end

Now call the function to get all the fluxes.

q_gp = compute_heat_fluxes(cellvalues, dh, u);

Next, create an L2Projector using the same interpolation as was used to approximate the temperature field. On instantiation, the projector assembles the coefficient matrix M and computes the Cholesky factorization of it. By doing so, the projector can be reused without having to invert M every time.

projector = L2Projector(ip, grid);

Project the integration point values to the nodal values

q_projected = project(projector, q_gp, qr);

Exporting to VTK

To visualize the heat flux, we export the projected field q_projected to a VTK-file, which can be viewed in e.g. ParaView. The result is also visualized in Figure 1.

VTKGridFile("heat_equation_flux", grid) do vtk
+    write_projection(vtk, projector, q_projected, "q")
+end;

Point evaluation

Figure 2: Visualization of the cut line where we want to compute the temperature and heat flux.

Consider a cut-line through the domain like the black line in Figure 2 above. We will evaluate the temperature and the heat flux distribution along a horizontal line.

points = [Vec((x, 0.75)) for x in range(-1.0, 1.0, length = 101)];

First, we need to generate a PointEvalHandler. This will find and store the cells containing the input points.

ph = PointEvalHandler(grid, points);

After the L2-Projection, the heat fluxes q_projected are stored in the DoF-ordering determined by the projector's internal DoFHandler, so to evaluate the flux q at our points we give the PointEvalHandler, the L2Projector and the values q_projected.

q_points = evaluate_at_points(ph, projector, q_projected);

We can also extract the field values, here the temperature, right away from the result vector of the simulation, that is stored in u. These values are stored in the order of our initial DofHandler so the input is not the PointEvalHandler, the original DofHandler, the dof-vector u, and (optionally for single-field problems) the name of the field. From the L2Projection, the values are stored in the order of the degrees of freedom.

u_points = evaluate_at_points(ph, dh, u, :u);

Now, we can plot the temperature and flux values with the help of any plotting library, e.g. Plots.jl. To do this, we need to import the package:

import Plots

Firstly, we are going to plot the temperature values along the given line.

Plots.plot(getindex.(points, 1), u_points, xlabel = "x (coordinate)", ylabel = "u (temperature)", label = nothing)
Example block output

Figure 3: Temperature along the cut line from Figure 2.

Secondly, the horizontal heat flux (i.e. the first component of the heat flux vector) is plotted.

Plots.plot(getindex.(points, 1), getindex.(q_points, 1), xlabel = "x (coordinate)", ylabel = "q_x (flux in x-direction)", label = nothing)
Example block output

Figure 4: $x$-component of the flux along the cut line from Figure 2.

Plain program

Here follows a version of the program without any comments. The file is also available here: postprocessing.jl.

include("../tutorials/heat_equation.jl");
+
+function compute_heat_fluxes(cellvalues::CellValues, dh::DofHandler, a::AbstractVector{T}) where {T}
+
+    n = getnbasefunctions(cellvalues)
+    cell_dofs = zeros(Int, n)
+    nqp = getnquadpoints(cellvalues)
+
+    # Allocate storage for the fluxes to store
+    q = [Vec{2, T}[] for _ in 1:getncells(dh.grid)]
+
+    for (cell_num, cell) in enumerate(CellIterator(dh))
+        q_cell = q[cell_num]
+        celldofs!(cell_dofs, dh, cell_num)
+        aᵉ = a[cell_dofs]
+        reinit!(cellvalues, cell)
+
+        for q_point in 1:nqp
+            q_qp = - function_gradient(cellvalues, q_point, aᵉ)
+            push!(q_cell, q_qp)
+        end
+    end
+    return q
+end
+
+q_gp = compute_heat_fluxes(cellvalues, dh, u);
+
+projector = L2Projector(ip, grid);
+
+q_projected = project(projector, q_gp, qr);
+
+VTKGridFile("heat_equation_flux", grid) do vtk
+    write_projection(vtk, projector, q_projected, "q")
+end;
+
+points = [Vec((x, 0.75)) for x in range(-1.0, 1.0, length = 101)];
+
+ph = PointEvalHandler(grid, points);
+
+q_points = evaluate_at_points(ph, projector, q_projected);
+
+u_points = evaluate_at_points(ph, dh, u, :u);
+
+import Plots
+
+Plots.plot(getindex.(points, 1), u_points, xlabel = "x (coordinate)", ylabel = "u (temperature)", label = nothing)
+
+Plots.plot(getindex.(points, 1), getindex.(q_points, 1), xlabel = "x (coordinate)", ylabel = "q_x (flux in x-direction)", label = nothing)

This page was generated using Literate.jl.

diff --git a/previews/PR798/howto/threaded_assembly.ipynb b/previews/PR798/howto/threaded_assembly.ipynb new file mode 100644 index 0000000000..291a7a2295 --- /dev/null +++ b/previews/PR798/howto/threaded_assembly.ipynb @@ -0,0 +1,441 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "```@meta\n", + "Draft = false\n", + "```\n", + "# Multi-threaded assembly" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Introduction\n", + "\n", + "In this howto we will explore how to use task based multithreading (shared memory\n", + "parallelism) to speed up the analysis. Some parts of a finite element simulation are\n", + "trivially parallelizable such as the computation of the local element contributions since\n", + "each element can be processed independently. However, two things need to be considered in\n", + "order to parallelize safely:\n", + "\n", + " - **Modification of shared data**: Although the contributions from all the elements can\n", + " be computed independently, eventually they need to be assembled into the global\n", + " matrix and vector. Letting each task assemble their own contribution would lead to\n", + " race conditions since elements share degrees of freedom with each other. There are\n", + " various ways to remedy this, for example:\n", + " - **Locking**: By using a lock around the call to `assemble!` we can ensure that only\n", + " one task assembles at a time. This is simple to implement but can lead to lock\n", + " contention and thus poor performance. Another drawback is that the results will not\n", + " be deterministic since floating point operations are neither associative nor\n", + " commutative.\n", + " - **Assembler task**: By using a designated task for the assembling we (obviously)\n", + " ensure that only a single task assembles. The worker tasks (the tasks computing the\n", + " element contributions) would then hand off their results to the assemly task. This\n", + " can be a useful approach if computing the element contributions is much slower than\n", + " the assembly -- otherwise the assembler task can't keep up with the worker tasks.\n", + " There might also be some extra overhead because of task switching in the scheduler.\n", + " The problem with non-deterministic results still remains.\n", + " - **Grid coloring**: By \"coloring\" the grid such that, within each color, no two\n", + " elements share degrees of freedom, we can safely assemble each color in parallel.\n", + " Even if concurrently running tasks will write to the global matrix and vector they\n", + " will not write to the same memory locations. Note also that this procedure gives\n", + " predictable results because for a memory location which, for example, a \"red\",\n", + " a \"blue\", and a \"green\" element will contribute to we will always add the red first,\n", + " then the blue, and finally the green.\n", + " - **Scratch data**: In order to speed up the computation of the element contributions we\n", + " typically pre-allocate some data structures that can be reused for every element. Such\n", + " scratch data include, for example, the local matrix and vector, and the CellValues.\n", + " Each task need their own copy of the scratch data since they will be modified for each\n", + " element." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Grid coloring\n", + "\n", + "Ferrite include functionality to color the grid with the `create_coloring`\n", + "function. Here we create a simple 2D grid, color it, and export the colors to a VTK file\n", + "to visualize the result (see *Figure 1*.). Note that no cells with the same color has any\n", + "shared nodes (dofs). This means that it is safe to assemble in parallel as long as we only\n", + "assemble one color at a time.\n", + "\n", + "There are two coloring algorithms implemented: the \"workstream\" algorithm (from Turcksin\n", + "et al. [Turcksin2016](@cite)) and a \"greedy\" algorithm. For this structured grid the\n", + "greedy algorithm uses fewer colors, but both algorithms result in colors that contain\n", + "roughly the same number of elements. The workstream algorithm is the default one since it\n", + "in general results in more balanced colors. For unstructured grids the greedy algorithm\n", + "can result in colors with very few elements, for example." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using Ferrite, SparseArrays\n", + "\n", + "function create_example_2d_grid()\n", + " grid = generate_grid(Quadrilateral, (10, 10), Vec{2}((0.0, 0.0)), Vec{2}((10.0, 10.0)))\n", + " colors_workstream = create_coloring(grid; alg = ColoringAlgorithm.WorkStream)\n", + " colors_greedy = create_coloring(grid; alg = ColoringAlgorithm.Greedy)\n", + " VTKGridFile(\"colored\", grid) do vtk\n", + " Ferrite.write_cell_colors(vtk, grid, colors_workstream, \"workstream-coloring\")\n", + " Ferrite.write_cell_colors(vtk, grid, colors_greedy, \"greedy-coloring\")\n", + " end\n", + " return\n", + "end\n", + "\n", + "create_example_2d_grid()" + ], + "metadata": {}, + "execution_count": 1 + }, + { + "cell_type": "markdown", + "source": [ + "![](coloring.png)\n", + "\n", + "*Figure 1*: Element coloring using the \"workstream\"-algorithm (left) and the \"greedy\"-\n", + "algorithm (right)." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Multithreaded assembly of a cantilever beam in 3D\n", + "\n", + "We will now look at an example where we assemble the stiffness matrix and right hand side\n", + "using multiple threads. The problem setup is a cantilever beam in 3D with a linear elastic\n", + "material behavior. For this exercise we only focus on the multithreading and are not\n", + "bothered with boundary conditions. For more details refer to the [tutorial on linear\n", + "elasticity](../tutorials/linear_elasticity.md)." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "### Setup\n", + "\n", + "We define the element routine, material stiffness, grid and DofHandler just like in the\n", + "[tutorial on linear elasticity](../tutorials/linear_elasticity.md) without discussing it\n", + "further here." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "# Element routine\n", + "function assemble_cell!(Ke::Matrix, fe::Vector, cellvalues::CellValues, C::SymmetricTensor, b::Vec)\n", + " fill!(Ke, 0)\n", + " fill!(fe, 0)\n", + " for q_point in 1:getnquadpoints(cellvalues)\n", + " dΩ = getdetJdV(cellvalues, q_point)\n", + " for i in 1:getnbasefunctions(cellvalues)\n", + " δui = shape_value(cellvalues, q_point, i)\n", + " fe[i] += (δui ⋅ b) * dΩ\n", + " ∇δui = shape_symmetric_gradient(cellvalues, q_point, i)\n", + " for j in 1:getnbasefunctions(cellvalues)\n", + " ∇uj = shape_symmetric_gradient(cellvalues, q_point, j)\n", + " Ke[i, j] += (∇δui ⊡ C ⊡ ∇uj) * dΩ\n", + " end\n", + " end\n", + " end\n", + " return Ke, fe\n", + "end\n", + "\n", + "# Material stiffness\n", + "function create_material_stiffness()\n", + " E = 200.0e9\n", + " ν = 0.3\n", + " λ = E * ν / ((1 + ν) * (1 - 2ν))\n", + " μ = E / (2(1 + ν))\n", + " δ(i, j) = i == j ? 1.0 : 0.0\n", + " C = SymmetricTensor{4, 3}() do i, j, k, l\n", + " return λ * δ(i, j) * δ(k, l) + μ * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k))\n", + " end\n", + " return C\n", + "end\n", + "\n", + "# Grid and grid coloring\n", + "function create_cantilever_grid(n::Int)\n", + " xmin = Vec{3}((0.0, 0.0, 0.0))\n", + " xmax = Vec{3}((10.0, 1.0, 1.0))\n", + " grid = generate_grid(Hexahedron, (10 * n, n, n), xmin, xmax)\n", + " colors = create_coloring(grid)\n", + " return grid, colors\n", + "end\n", + "\n", + "# DofHandler with displacement field u\n", + "function create_dofhandler(grid::Grid, interpolation::VectorInterpolation)\n", + " dh = DofHandler(grid)\n", + " add!(dh, :u, interpolation)\n", + " close!(dh)\n", + " return dh\n", + "end\n", + "nothing # hide" + ], + "metadata": {}, + "execution_count": 2 + }, + { + "cell_type": "markdown", + "source": [ + "### Task local scratch data\n", + "\n", + "We group everything that needs to be duplicated for each task in the struct\n", + "`ScratchData`:\n", + " - `cell_cache::CellCache`: contain buffers for coordinates and (global) dofs which will\n", + " be `reinit!`ed for each cell.\n", + " - `cellvalues::CellValues`: the cell values which will be `reinit!`ed for each cell using\n", + " the `cell_cache`\n", + " - `Ke::Matrix`: the local matrix\n", + " - `fe::Vector`: the local vector\n", + " - `assembler`: the assembler (which needs to be duplicated because it contains buffers\n", + " that are modified during the call to `assemble!`)" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "struct ScratchData{CC, CV, T, A}\n", + " cell_cache::CC\n", + " cellvalues::CV\n", + " Ke::Matrix{T}\n", + " fe::Vector{T}\n", + " assembler::A\n", + "end" + ], + "metadata": {}, + "execution_count": 3 + }, + { + "cell_type": "markdown", + "source": [ + "This constructor will be called within each task to create a independent `ScratchData`\n", + "object. For `cell_cache`, `Ke`, and `fe` we simply call the constructors to allocate\n", + "independent objects. For `cellvalues` we use `copy` which Ferrite defines for this\n", + "purpose. Finally, for the assembler we call `start_assemble` to create a new assembler but\n", + "note that we set `fillzero = false` because we don't want to risk that a task that starts\n", + "a bit later will zero out data that another task have already assembled." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function ScratchData(dh::DofHandler, K::SparseMatrixCSC, f::Vector, cellvalues::CellValues)\n", + " cell_cache = CellCache(dh)\n", + " n = ndofs_per_cell(dh)\n", + " Ke = zeros(n, n)\n", + " fe = zeros(n)\n", + " asm = start_assemble(K, f; fillzero = false)\n", + " return ScratchData(cell_cache, copy(cellvalues), Ke, fe, asm)\n", + "end\n", + "nothing # hide" + ], + "metadata": {}, + "execution_count": 4 + }, + { + "cell_type": "markdown", + "source": [ + "### Global assembly routine" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Finally we define the global assemble routine, which is where the parallelization happens.\n", + "The main difference from all previous `assemble_global!` functions is that we now have an\n", + "outer loop over the colors, and then the inner loop over the cells in each color, which\n", + "can be parallelized.\n", + "\n", + "For the scheduling of parallel tasks we use the\n", + "[OhMyThreads.jl](https://github.com/JuliaFolds2/OhMyThreads.jl) package. OhMyThreads\n", + "provides a macro based and a functional API. Here we use the macro based API because it is\n", + "slightly more convenient when using task local values since they can be defined with the\n", + "`@local` macro.\n", + "\n", + "> **Schedulers and load balancing**\n", + ">\n", + "> OhMyThreads provides a number of different\n", + "> [schedulers](https://juliafolds2.github.io/OhMyThreads.jl/stable/refs/api/#Schedulers).\n", + "> In this example we use the `DynamicScheduler` (which is the default one). The\n", + "> `DynamicScheduler` will spawn `ntasks` tasks where each task will process a chunk of\n", + "> (roughly) equal number of cells (i.e. `length(color) ÷ ntasks`). This should be a good\n", + "> choice for this example because we expect all cells to take the same time to process\n", + "> and we don't need any load balancing.\n", + "\n", + " For a different problem setup where some cells might take longer to process (perhaps\n", + " they experience plastic deformation and we need to solve a local problem) we might\n", + " benefit from load balancing. The `DynamicScheduler` can be used also for load\n", + " balancing by specifiying `nchunks` or `chunksize`. However, the `DynamicScheduler`\n", + " will always spawn `nchunks` tasks which can become costly since we are allocating\n", + " scratch data for every task. To limit the number of tasks, while allowing for more\n", + " than `ntasks` chunks, we can use the `GreedyScheduler` *with chunking*. For example,\n", + " `scheduler = OhMyThreads.GreedyScheduler(; ntasks = ntasks, nchunks = 10 * ntasks)`\n", + " will split the work into `10 * ntasks` chunks and spawn `ntasks` tasks to process\n", + " them. Refer to the [OhMyThreads\n", + " documentation](https://juliafolds2.github.io/OhMyThreads.jl/stable/) for details." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using OhMyThreads, TaskLocalValues\n", + "\n", + "function assemble_global!(\n", + " K::SparseMatrixCSC, f::Vector, dh::DofHandler, colors,\n", + " cellvalues_template::CellValues; ntasks = Threads.nthreads()\n", + " )\n", + " # Zero-out existing data in K and f\n", + " _ = start_assemble(K, f)\n", + " # Body force and material stiffness\n", + " b = Vec{3}((0.0, 0.0, -1.0))\n", + " C = create_material_stiffness()\n", + " # Loop over the colors\n", + " for color in colors\n", + " # Dynamic scheduler spawning `ntasks` tasks where each task will process a chunk of\n", + " # (roughly) equal number of cells (`length(color) ÷ ntasks`).\n", + " scheduler = OhMyThreads.DynamicScheduler(; ntasks)\n", + " # Parallelize the loop over the cells in this color\n", + " OhMyThreads.@tasks for cellidx in color\n", + " # Tell the @tasks loop to use the scheduler defined above\n", + " @set scheduler = scheduler\n", + " # Obtain a task local scratch and unpack it\n", + " @local scratch = ScratchData(dh, K, f, cellvalues_template)\n", + " (; cell_cache, cellvalues, Ke, fe, assembler) = scratch\n", + " # Reinitialize the cell cache and then the cellvalues\n", + " reinit!(cell_cache, cellidx)\n", + " reinit!(cellvalues, cell_cache)\n", + " # Compute the local contribution of the cell\n", + " assemble_cell!(Ke, fe, cellvalues, C, b)\n", + " # Assemble local contribution\n", + " assemble!(assembler, celldofs(cell_cache), Ke, fe)\n", + " end\n", + " end\n", + " return K, f\n", + "end\n", + "nothing # hide" + ], + "metadata": {}, + "execution_count": 5 + }, + { + "cell_type": "markdown", + "source": [ + "> **OhMyThreads functional API: OhMyThreads.tforeach**\n", + ">\n", + "> The `OhMyThreads.@tasks` block above corresponds to a call to `OhMyThreads.tforeach`.\n", + "> Using the functional API directly would look like below. The main difference is that\n", + "> we need to manually create a `TaskLocalValue` for the scratch data.\n", + "> ```julia\n", + "> # using TaskLocalValues\n", + "> scratches = TaskLocalValue() do\n", + "> ScratchData(dh, K, f, cellvalues)\n", + "> end\n", + "> OhMyThreads.tforeach(color; scheduler) do cellidx\n", + "> # Obtain a task local scratch and unpack it\n", + "> scratch = scratches[]\n", + "> (; cell_cache, cellvalues, Ke, fe, assembler) = scratch\n", + "> # Reinitialize the cell cache and then the cellvalues\n", + "> reinit!(cell_cache, cellidx)\n", + "> reinit!(cellvalues, cell_cache)\n", + "> # Compute the local contribution of the cell\n", + "> assemble_cell!(Ke, fe, cellvalues, C, b)\n", + "> # Assemble local contribution\n", + "> assemble!(assembler, celldofs(cell_cache), Ke, fe)\n", + "> end\n", + "> ```" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "We define the main function to setup everything and then time the call to\n", + "`assemble_global!`." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function main(; n = 20, ntasks = Threads.nthreads())\n", + " # Interpolation, quadrature and cellvalues\n", + " interpolation = Lagrange{RefHexahedron, 1}()^3\n", + " quadrature = QuadratureRule{RefHexahedron}(2)\n", + " cellvalues = CellValues(quadrature, interpolation)\n", + " # Grid, colors and DofHandler\n", + " grid, colors = create_cantilever_grid(n)\n", + " dh = create_dofhandler(grid, interpolation)\n", + " # Global matrix and vector\n", + " K = allocate_matrix(dh)\n", + " f = zeros(ndofs(dh))\n", + " # Compile it\n", + " assemble_global!(K, f, dh, colors, cellvalues; ntasks = ntasks)\n", + " # Time it\n", + " @time assemble_global!(K, f, dh, colors, cellvalues; ntasks = ntasks)\n", + " return\n", + "end\n", + "nothing # hide" + ], + "metadata": {}, + "execution_count": 6 + }, + { + "cell_type": "markdown", + "source": [ + "On a machine with 4 cores, starting julia with `--threads=auto`, we obtain the following\n", + "timings:\n", + "```julia\n", + "main(; ntasks = 1) # 1.970784 seconds (902 allocations: 816.172 KiB)\n", + "main(; ntasks = 2) # 1.025065 seconds (1.64 k allocations: 1.564 MiB)\n", + "main(; ntasks = 3) # 0.700423 seconds (2.38 k allocations: 2.332 MiB)\n", + "main(; ntasks = 4) # 0.548356 seconds (3.12 k allocations: 3.099 MiB)\n", + "```" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "---\n", + "\n", + "*This notebook was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).*" + ], + "metadata": {} + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.1" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.1", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR798/howto/threaded_assembly.jl b/previews/PR798/howto/threaded_assembly.jl new file mode 100644 index 0000000000..1887bb8dc0 --- /dev/null +++ b/previews/PR798/howto/threaded_assembly.jl @@ -0,0 +1,139 @@ +using Ferrite, SparseArrays + +function create_example_2d_grid() + grid = generate_grid(Quadrilateral, (10, 10), Vec{2}((0.0, 0.0)), Vec{2}((10.0, 10.0))) + colors_workstream = create_coloring(grid; alg = ColoringAlgorithm.WorkStream) + colors_greedy = create_coloring(grid; alg = ColoringAlgorithm.Greedy) + VTKGridFile("colored", grid) do vtk + Ferrite.write_cell_colors(vtk, grid, colors_workstream, "workstream-coloring") + Ferrite.write_cell_colors(vtk, grid, colors_greedy, "greedy-coloring") + end + return +end + +create_example_2d_grid() + +# Element routine +function assemble_cell!(Ke::Matrix, fe::Vector, cellvalues::CellValues, C::SymmetricTensor, b::Vec) + fill!(Ke, 0) + fill!(fe, 0) + for q_point in 1:getnquadpoints(cellvalues) + dΩ = getdetJdV(cellvalues, q_point) + for i in 1:getnbasefunctions(cellvalues) + δui = shape_value(cellvalues, q_point, i) + fe[i] += (δui ⋅ b) * dΩ + ∇δui = shape_symmetric_gradient(cellvalues, q_point, i) + for j in 1:getnbasefunctions(cellvalues) + ∇uj = shape_symmetric_gradient(cellvalues, q_point, j) + Ke[i, j] += (∇δui ⊡ C ⊡ ∇uj) * dΩ + end + end + end + return Ke, fe +end + +# Material stiffness +function create_material_stiffness() + E = 200.0e9 + ν = 0.3 + λ = E * ν / ((1 + ν) * (1 - 2ν)) + μ = E / (2(1 + ν)) + δ(i, j) = i == j ? 1.0 : 0.0 + C = SymmetricTensor{4, 3}() do i, j, k, l + return λ * δ(i, j) * δ(k, l) + μ * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k)) + end + return C +end + +# Grid and grid coloring +function create_cantilever_grid(n::Int) + xmin = Vec{3}((0.0, 0.0, 0.0)) + xmax = Vec{3}((10.0, 1.0, 1.0)) + grid = generate_grid(Hexahedron, (10 * n, n, n), xmin, xmax) + colors = create_coloring(grid) + return grid, colors +end + +# DofHandler with displacement field u +function create_dofhandler(grid::Grid, interpolation::VectorInterpolation) + dh = DofHandler(grid) + add!(dh, :u, interpolation) + close!(dh) + return dh +end +nothing # hide + +struct ScratchData{CC, CV, T, A} + cell_cache::CC + cellvalues::CV + Ke::Matrix{T} + fe::Vector{T} + assembler::A +end + +function ScratchData(dh::DofHandler, K::SparseMatrixCSC, f::Vector, cellvalues::CellValues) + cell_cache = CellCache(dh) + n = ndofs_per_cell(dh) + Ke = zeros(n, n) + fe = zeros(n) + asm = start_assemble(K, f; fillzero = false) + return ScratchData(cell_cache, copy(cellvalues), Ke, fe, asm) +end +nothing # hide + +using OhMyThreads, TaskLocalValues + +function assemble_global!( + K::SparseMatrixCSC, f::Vector, dh::DofHandler, colors, + cellvalues_template::CellValues; ntasks = Threads.nthreads() + ) + # Zero-out existing data in K and f + _ = start_assemble(K, f) + # Body force and material stiffness + b = Vec{3}((0.0, 0.0, -1.0)) + C = create_material_stiffness() + # Loop over the colors + for color in colors + # Dynamic scheduler spawning `ntasks` tasks where each task will process a chunk of + # (roughly) equal number of cells (`length(color) ÷ ntasks`). + scheduler = OhMyThreads.DynamicScheduler(; ntasks) + # Parallelize the loop over the cells in this color + OhMyThreads.@tasks for cellidx in color + # Tell the @tasks loop to use the scheduler defined above + @set scheduler = scheduler + # Obtain a task local scratch and unpack it + @local scratch = ScratchData(dh, K, f, cellvalues_template) + (; cell_cache, cellvalues, Ke, fe, assembler) = scratch + # Reinitialize the cell cache and then the cellvalues + reinit!(cell_cache, cellidx) + reinit!(cellvalues, cell_cache) + # Compute the local contribution of the cell + assemble_cell!(Ke, fe, cellvalues, C, b) + # Assemble local contribution + assemble!(assembler, celldofs(cell_cache), Ke, fe) + end + end + return K, f +end +nothing # hide + +function main(; n = 20, ntasks = Threads.nthreads()) + # Interpolation, quadrature and cellvalues + interpolation = Lagrange{RefHexahedron, 1}()^3 + quadrature = QuadratureRule{RefHexahedron}(2) + cellvalues = CellValues(quadrature, interpolation) + # Grid, colors and DofHandler + grid, colors = create_cantilever_grid(n) + dh = create_dofhandler(grid, interpolation) + # Global matrix and vector + K = allocate_matrix(dh) + f = zeros(ndofs(dh)) + # Compile it + assemble_global!(K, f, dh, colors, cellvalues; ntasks = ntasks) + # Time it + @time assemble_global!(K, f, dh, colors, cellvalues; ntasks = ntasks) + return +end +nothing # hide + +# This file was generated using Literate.jl, https://github.com/fredrikekre/Literate.jl diff --git a/previews/PR798/howto/threaded_assembly/index.html b/previews/PR798/howto/threaded_assembly/index.html new file mode 100644 index 0000000000..20684c30f6 --- /dev/null +++ b/previews/PR798/howto/threaded_assembly/index.html @@ -0,0 +1,278 @@ + +Multi-threaded assembly · Ferrite.jl

Multi-threaded assembly

Tip

This example is also available as a Jupyter notebook: threaded_assembly.ipynb.

Introduction

In this howto we will explore how to use task based multithreading (shared memory parallelism) to speed up the analysis. Some parts of a finite element simulation are trivially parallelizable such as the computation of the local element contributions since each element can be processed independently. However, two things need to be considered in order to parallelize safely:

  • Modification of shared data: Although the contributions from all the elements can be computed independently, eventually they need to be assembled into the global matrix and vector. Letting each task assemble their own contribution would lead to race conditions since elements share degrees of freedom with each other. There are various ways to remedy this, for example:
    • Locking: By using a lock around the call to assemble! we can ensure that only one task assembles at a time. This is simple to implement but can lead to lock contention and thus poor performance. Another drawback is that the results will not be deterministic since floating point operations are neither associative nor commutative.
    • Assembler task: By using a designated task for the assembling we (obviously) ensure that only a single task assembles. The worker tasks (the tasks computing the element contributions) would then hand off their results to the assemly task. This can be a useful approach if computing the element contributions is much slower than the assembly – otherwise the assembler task can't keep up with the worker tasks. There might also be some extra overhead because of task switching in the scheduler. The problem with non-deterministic results still remains.
    • Grid coloring: By "coloring" the grid such that, within each color, no two elements share degrees of freedom, we can safely assemble each color in parallel. Even if concurrently running tasks will write to the global matrix and vector they will not write to the same memory locations. Note also that this procedure gives predictable results because for a memory location which, for example, a "red", a "blue", and a "green" element will contribute to we will always add the red first, then the blue, and finally the green.
  • Scratch data: In order to speed up the computation of the element contributions we typically pre-allocate some data structures that can be reused for every element. Such scratch data include, for example, the local matrix and vector, and the CellValues. Each task need their own copy of the scratch data since they will be modified for each element.

Grid coloring

Ferrite include functionality to color the grid with the create_coloring function. Here we create a simple 2D grid, color it, and export the colors to a VTK file to visualize the result (see Figure 1.). Note that no cells with the same color has any shared nodes (dofs). This means that it is safe to assemble in parallel as long as we only assemble one color at a time.

There are two coloring algorithms implemented: the "workstream" algorithm (from Turcksin et al. [13]) and a "greedy" algorithm. For this structured grid the greedy algorithm uses fewer colors, but both algorithms result in colors that contain roughly the same number of elements. The workstream algorithm is the default one since it in general results in more balanced colors. For unstructured grids the greedy algorithm can result in colors with very few elements, for example.

using Ferrite, SparseArrays
+
+function create_example_2d_grid()
+    grid = generate_grid(Quadrilateral, (10, 10), Vec{2}((0.0, 0.0)), Vec{2}((10.0, 10.0)))
+    colors_workstream = create_coloring(grid; alg = ColoringAlgorithm.WorkStream)
+    colors_greedy = create_coloring(grid; alg = ColoringAlgorithm.Greedy)
+    VTKGridFile("colored", grid) do vtk
+        Ferrite.write_cell_colors(vtk, grid, colors_workstream, "workstream-coloring")
+        Ferrite.write_cell_colors(vtk, grid, colors_greedy, "greedy-coloring")
+    end
+    return
+end
+
+create_example_2d_grid()

Figure 1: Element coloring using the "workstream"-algorithm (left) and the "greedy"- algorithm (right).

Multithreaded assembly of a cantilever beam in 3D

We will now look at an example where we assemble the stiffness matrix and right hand side using multiple threads. The problem setup is a cantilever beam in 3D with a linear elastic material behavior. For this exercise we only focus on the multithreading and are not bothered with boundary conditions. For more details refer to the tutorial on linear elasticity.

Setup

We define the element routine, material stiffness, grid and DofHandler just like in the tutorial on linear elasticity without discussing it further here.

# Element routine
+function assemble_cell!(Ke::Matrix, fe::Vector, cellvalues::CellValues, C::SymmetricTensor, b::Vec)
+    fill!(Ke, 0)
+    fill!(fe, 0)
+    for q_point in 1:getnquadpoints(cellvalues)
+        dΩ = getdetJdV(cellvalues, q_point)
+        for i in 1:getnbasefunctions(cellvalues)
+            δui = shape_value(cellvalues, q_point, i)
+            fe[i] += (δui ⋅ b) * dΩ
+            ∇δui = shape_symmetric_gradient(cellvalues, q_point, i)
+            for j in 1:getnbasefunctions(cellvalues)
+                ∇uj = shape_symmetric_gradient(cellvalues, q_point, j)
+                Ke[i, j] += (∇δui ⊡ C ⊡ ∇uj) * dΩ
+            end
+        end
+    end
+    return Ke, fe
+end
+
+# Material stiffness
+function create_material_stiffness()
+    E = 200.0e9
+    ν = 0.3
+    λ = E * ν / ((1 + ν) * (1 - 2ν))
+    μ = E / (2(1 + ν))
+    δ(i, j) = i == j ? 1.0 : 0.0
+    C = SymmetricTensor{4, 3}() do i, j, k, l
+        return λ * δ(i, j) * δ(k, l) + μ * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k))
+    end
+    return C
+end
+
+# Grid and grid coloring
+function create_cantilever_grid(n::Int)
+    xmin = Vec{3}((0.0, 0.0, 0.0))
+    xmax = Vec{3}((10.0, 1.0, 1.0))
+    grid = generate_grid(Hexahedron, (10 * n, n, n), xmin, xmax)
+    colors = create_coloring(grid)
+    return grid, colors
+end
+
+# DofHandler with displacement field u
+function create_dofhandler(grid::Grid, interpolation::VectorInterpolation)
+    dh = DofHandler(grid)
+    add!(dh, :u, interpolation)
+    close!(dh)
+    return dh
+end

Task local scratch data

We group everything that needs to be duplicated for each task in the struct ScratchData:

  • cell_cache::CellCache: contain buffers for coordinates and (global) dofs which will be reinit!ed for each cell.
  • cellvalues::CellValues: the cell values which will be reinit!ed for each cell using the cell_cache
  • Ke::Matrix: the local matrix
  • fe::Vector: the local vector
  • assembler: the assembler (which needs to be duplicated because it contains buffers that are modified during the call to assemble!)
struct ScratchData{CC, CV, T, A}
+    cell_cache::CC
+    cellvalues::CV
+    Ke::Matrix{T}
+    fe::Vector{T}
+    assembler::A
+end

This constructor will be called within each task to create a independent ScratchData object. For cell_cache, Ke, and fe we simply call the constructors to allocate independent objects. For cellvalues we use copy which Ferrite defines for this purpose. Finally, for the assembler we call start_assemble to create a new assembler but note that we set fillzero = false because we don't want to risk that a task that starts a bit later will zero out data that another task have already assembled.

function ScratchData(dh::DofHandler, K::SparseMatrixCSC, f::Vector, cellvalues::CellValues)
+    cell_cache = CellCache(dh)
+    n = ndofs_per_cell(dh)
+    Ke = zeros(n, n)
+    fe = zeros(n)
+    asm = start_assemble(K, f; fillzero = false)
+    return ScratchData(cell_cache, copy(cellvalues), Ke, fe, asm)
+end

Global assembly routine

Finally we define the global assemble routine, which is where the parallelization happens. The main difference from all previous assemble_global! functions is that we now have an outer loop over the colors, and then the inner loop over the cells in each color, which can be parallelized.

For the scheduling of parallel tasks we use the OhMyThreads.jl package. OhMyThreads provides a macro based and a functional API. Here we use the macro based API because it is slightly more convenient when using task local values since they can be defined with the @local macro.

Schedulers and load balancing

OhMyThreads provides a number of different schedulers. In this example we use the DynamicScheduler (which is the default one). The DynamicScheduler will spawn ntasks tasks where each task will process a chunk of (roughly) equal number of cells (i.e. length(color) ÷ ntasks). This should be a good choice for this example because we expect all cells to take the same time to process and we don't need any load balancing.

For a different problem setup where some cells might take longer to process (perhaps they experience plastic deformation and we need to solve a local problem) we might benefit from load balancing. The DynamicScheduler can be used also for load balancing by specifiying nchunks or chunksize. However, the DynamicScheduler will always spawn nchunks tasks which can become costly since we are allocating scratch data for every task. To limit the number of tasks, while allowing for more than ntasks chunks, we can use the GreedyScheduler with chunking. For example, scheduler = OhMyThreads.GreedyScheduler(; ntasks = ntasks, nchunks = 10 * ntasks) will split the work into 10 * ntasks chunks and spawn ntasks tasks to process them. Refer to the OhMyThreads documentation for details.

using OhMyThreads, TaskLocalValues
+
+function assemble_global!(
+        K::SparseMatrixCSC, f::Vector, dh::DofHandler, colors,
+        cellvalues_template::CellValues; ntasks = Threads.nthreads()
+    )
+    # Zero-out existing data in K and f
+    _ = start_assemble(K, f)
+    # Body force and material stiffness
+    b = Vec{3}((0.0, 0.0, -1.0))
+    C = create_material_stiffness()
+    # Loop over the colors
+    for color in colors
+        # Dynamic scheduler spawning `ntasks` tasks where each task will process a chunk of
+        # (roughly) equal number of cells (`length(color) ÷ ntasks`).
+        scheduler = OhMyThreads.DynamicScheduler(; ntasks)
+        # Parallelize the loop over the cells in this color
+        OhMyThreads.@tasks for cellidx in color
+            # Tell the @tasks loop to use the scheduler defined above
+            @set scheduler = scheduler
+            # Obtain a task local scratch and unpack it
+            @local scratch = ScratchData(dh, K, f, cellvalues_template)
+            (; cell_cache, cellvalues, Ke, fe, assembler) = scratch
+            # Reinitialize the cell cache and then the cellvalues
+            reinit!(cell_cache, cellidx)
+            reinit!(cellvalues, cell_cache)
+            # Compute the local contribution of the cell
+            assemble_cell!(Ke, fe, cellvalues, C, b)
+            # Assemble local contribution
+            assemble!(assembler, celldofs(cell_cache), Ke, fe)
+        end
+    end
+    return K, f
+end
OhMyThreads functional API: OhMyThreads.tforeach

The OhMyThreads.@tasks block above corresponds to a call to OhMyThreads.tforeach. Using the functional API directly would look like below. The main difference is that we need to manually create a TaskLocalValue for the scratch data.

# using TaskLocalValues
+scratches = TaskLocalValue() do
+    ScratchData(dh, K, f, cellvalues)
+end
+OhMyThreads.tforeach(color; scheduler) do cellidx
+    # Obtain a task local scratch and unpack it
+    scratch = scratches[]
+    (; cell_cache, cellvalues, Ke, fe, assembler) = scratch
+    # Reinitialize the cell cache and then the cellvalues
+    reinit!(cell_cache, cellidx)
+    reinit!(cellvalues, cell_cache)
+    # Compute the local contribution of the cell
+    assemble_cell!(Ke, fe, cellvalues, C, b)
+    # Assemble local contribution
+    assemble!(assembler, celldofs(cell_cache), Ke, fe)
+end

We define the main function to setup everything and then time the call to assemble_global!.

function main(; n = 20, ntasks = Threads.nthreads())
+    # Interpolation, quadrature and cellvalues
+    interpolation = Lagrange{RefHexahedron, 1}()^3
+    quadrature = QuadratureRule{RefHexahedron}(2)
+    cellvalues = CellValues(quadrature, interpolation)
+    # Grid, colors and DofHandler
+    grid, colors = create_cantilever_grid(n)
+    dh = create_dofhandler(grid, interpolation)
+    # Global matrix and vector
+    K = allocate_matrix(dh)
+    f = zeros(ndofs(dh))
+    # Compile it
+    assemble_global!(K, f, dh, colors, cellvalues; ntasks = ntasks)
+    # Time it
+    @time assemble_global!(K, f, dh, colors, cellvalues; ntasks = ntasks)
+    return
+end

On a machine with 4 cores, starting julia with --threads=auto, we obtain the following timings:

main(; ntasks = 1) # 1.970784 seconds (902 allocations: 816.172 KiB)
+main(; ntasks = 2) # 1.025065 seconds (1.64 k allocations: 1.564 MiB)
+main(; ntasks = 3) # 0.700423 seconds (2.38 k allocations: 2.332 MiB)
+main(; ntasks = 4) # 0.548356 seconds (3.12 k allocations: 3.099 MiB)

Plain program

Here follows a version of the program without any comments. The file is also available here: threaded_assembly.jl.

using Ferrite, SparseArrays
+
+function create_example_2d_grid()
+    grid = generate_grid(Quadrilateral, (10, 10), Vec{2}((0.0, 0.0)), Vec{2}((10.0, 10.0)))
+    colors_workstream = create_coloring(grid; alg = ColoringAlgorithm.WorkStream)
+    colors_greedy = create_coloring(grid; alg = ColoringAlgorithm.Greedy)
+    VTKGridFile("colored", grid) do vtk
+        Ferrite.write_cell_colors(vtk, grid, colors_workstream, "workstream-coloring")
+        Ferrite.write_cell_colors(vtk, grid, colors_greedy, "greedy-coloring")
+    end
+    return
+end
+
+create_example_2d_grid()
+
+# Element routine
+function assemble_cell!(Ke::Matrix, fe::Vector, cellvalues::CellValues, C::SymmetricTensor, b::Vec)
+    fill!(Ke, 0)
+    fill!(fe, 0)
+    for q_point in 1:getnquadpoints(cellvalues)
+        dΩ = getdetJdV(cellvalues, q_point)
+        for i in 1:getnbasefunctions(cellvalues)
+            δui = shape_value(cellvalues, q_point, i)
+            fe[i] += (δui ⋅ b) * dΩ
+            ∇δui = shape_symmetric_gradient(cellvalues, q_point, i)
+            for j in 1:getnbasefunctions(cellvalues)
+                ∇uj = shape_symmetric_gradient(cellvalues, q_point, j)
+                Ke[i, j] += (∇δui ⊡ C ⊡ ∇uj) * dΩ
+            end
+        end
+    end
+    return Ke, fe
+end
+
+# Material stiffness
+function create_material_stiffness()
+    E = 200.0e9
+    ν = 0.3
+    λ = E * ν / ((1 + ν) * (1 - 2ν))
+    μ = E / (2(1 + ν))
+    δ(i, j) = i == j ? 1.0 : 0.0
+    C = SymmetricTensor{4, 3}() do i, j, k, l
+        return λ * δ(i, j) * δ(k, l) + μ * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k))
+    end
+    return C
+end
+
+# Grid and grid coloring
+function create_cantilever_grid(n::Int)
+    xmin = Vec{3}((0.0, 0.0, 0.0))
+    xmax = Vec{3}((10.0, 1.0, 1.0))
+    grid = generate_grid(Hexahedron, (10 * n, n, n), xmin, xmax)
+    colors = create_coloring(grid)
+    return grid, colors
+end
+
+# DofHandler with displacement field u
+function create_dofhandler(grid::Grid, interpolation::VectorInterpolation)
+    dh = DofHandler(grid)
+    add!(dh, :u, interpolation)
+    close!(dh)
+    return dh
+end
+nothing # hide
+
+struct ScratchData{CC, CV, T, A}
+    cell_cache::CC
+    cellvalues::CV
+    Ke::Matrix{T}
+    fe::Vector{T}
+    assembler::A
+end
+
+function ScratchData(dh::DofHandler, K::SparseMatrixCSC, f::Vector, cellvalues::CellValues)
+    cell_cache = CellCache(dh)
+    n = ndofs_per_cell(dh)
+    Ke = zeros(n, n)
+    fe = zeros(n)
+    asm = start_assemble(K, f; fillzero = false)
+    return ScratchData(cell_cache, copy(cellvalues), Ke, fe, asm)
+end
+nothing # hide
+
+using OhMyThreads, TaskLocalValues
+
+function assemble_global!(
+        K::SparseMatrixCSC, f::Vector, dh::DofHandler, colors,
+        cellvalues_template::CellValues; ntasks = Threads.nthreads()
+    )
+    # Zero-out existing data in K and f
+    _ = start_assemble(K, f)
+    # Body force and material stiffness
+    b = Vec{3}((0.0, 0.0, -1.0))
+    C = create_material_stiffness()
+    # Loop over the colors
+    for color in colors
+        # Dynamic scheduler spawning `ntasks` tasks where each task will process a chunk of
+        # (roughly) equal number of cells (`length(color) ÷ ntasks`).
+        scheduler = OhMyThreads.DynamicScheduler(; ntasks)
+        # Parallelize the loop over the cells in this color
+        OhMyThreads.@tasks for cellidx in color
+            # Tell the @tasks loop to use the scheduler defined above
+            @set scheduler = scheduler
+            # Obtain a task local scratch and unpack it
+            @local scratch = ScratchData(dh, K, f, cellvalues_template)
+            (; cell_cache, cellvalues, Ke, fe, assembler) = scratch
+            # Reinitialize the cell cache and then the cellvalues
+            reinit!(cell_cache, cellidx)
+            reinit!(cellvalues, cell_cache)
+            # Compute the local contribution of the cell
+            assemble_cell!(Ke, fe, cellvalues, C, b)
+            # Assemble local contribution
+            assemble!(assembler, celldofs(cell_cache), Ke, fe)
+        end
+    end
+    return K, f
+end
+nothing # hide
+
+function main(; n = 20, ntasks = Threads.nthreads())
+    # Interpolation, quadrature and cellvalues
+    interpolation = Lagrange{RefHexahedron, 1}()^3
+    quadrature = QuadratureRule{RefHexahedron}(2)
+    cellvalues = CellValues(quadrature, interpolation)
+    # Grid, colors and DofHandler
+    grid, colors = create_cantilever_grid(n)
+    dh = create_dofhandler(grid, interpolation)
+    # Global matrix and vector
+    K = allocate_matrix(dh)
+    f = zeros(ndofs(dh))
+    # Compile it
+    assemble_global!(K, f, dh, colors, cellvalues; ntasks = ntasks)
+    # Time it
+    @time assemble_global!(K, f, dh, colors, cellvalues; ntasks = ntasks)
+    return
+end
+nothing # hide

This page was generated using Literate.jl.

diff --git a/previews/PR798/index.html b/previews/PR798/index.html new file mode 100644 index 0000000000..a087534e56 --- /dev/null +++ b/previews/PR798/index.html @@ -0,0 +1,2 @@ + +Home · Ferrite.jl

Ferrite.jl

Welcome to the documentation for Ferrite.jl! Ferrite is a finite element toolbox that provides functionalities to implement finite element analysis in Julia. The aim is to be i) general, ii) performant, and iii) to keep mathematical abstractions.

Upgrading code from version 0.3.x to version 1.0

Ferrite version 1.0 contains a number of breaking changes compared to version 0.3.x. The Changelog documents all changes and there is also a section specifically for Upgrading code from Ferrite 0.3 to 1.0.

Note

Please help improve this documentation – if something confuses you, chances are you're not alone. It's easy to do as you read along: just click on the "Edit on GitHub" link at the top of each page, and then edit the files directly in your browser. Your changes will be vetted by developers before becoming permanent, so don't worry about whether you might say something wrong. See also Contributing to Ferrite for more details.

How the documentation is organized

This high level view of the documentation structure will help you find what you are looking for. The document is organized as follows[1]:

  • Tutorials are thoroughly documented examples which guides you through the process of solving partial differential equations using Ferrite.
  • Topic guides contains more in-depth explanations and discussions about finite element programming concepts and ideas, and specifically how these are realized in Ferrite.
  • Reference contains the technical API reference of functions and methods (e.g. the documentation strings).
  • How-to guides will guide you through the steps involved in addressing common tasks and use-cases. These usually build on top of the tutorials and thus assume basic knowledge of how Ferrite works.

The four sections above form the main user-facing parts of the documentation. In addition, the document also contain the following sections:

  • Code gallery contain user contributed example programs showcasing what can be done with Ferrite.
  • Changelog contain release notes and information about how to upgrade between releases.
  • Developer documentation contain documentation of Ferrite internal code and is mainly targeted at developers of Ferrite.

Getting started

As a new user of Ferrite it is suggested to start working with the tutorials before using Ferrite to tackle the specific equation you ultimately want to solve. The tutorials start with explaining the basic concepts and then increase in complexity. Understanding the first tutorial program, solving the heat equation, is essential in order to understand how Ferrite works. Already this rather simple program discusses many of the important concepts. See the tutorials overview for suggestion on how to progress to more advanced usage.

Getting help

If you have questions about Ferrite it is suggested to use the #ferrite-fem channel on the Julia Slack, or the #Ferrite.jl stream on Zulip. Alternatively you can use the discussion forum on the GitHub repository.

Installation

To use Ferrite you first need to install Julia, see https://julialang.org/ for details. Installing Ferrite can then be done from the Pkg REPL; press ] at the julia> promp to enter pkg> mode:

pkg> add Ferrite

This will install Ferrite and all necessary dependencies. Press backspace to get back to the julia> prompt. (See the documentation for Pkg, Julia's package manager, for more help regarding package installation and project management.)

Finally, to load Ferrite, use

using Ferrite

You are now all set to start using Ferrite!

Contributing to Ferrite

Ferrite is still under active development. If you find a bug, or have ideas for improvements, you are encouraged to interact with the developers on the Ferrite GitHub repository. There is also a thorough contributor guide which can be found in CONTRIBUTING.md.

diff --git a/previews/PR798/literate-gallery/bending.png b/previews/PR798/literate-gallery/bending.png new file mode 100644 index 0000000000..727b3cba64 Binary files /dev/null and b/previews/PR798/literate-gallery/bending.png differ diff --git a/previews/PR798/literate-gallery/bending_animation.gif b/previews/PR798/literate-gallery/bending_animation.gif new file mode 100644 index 0000000000..2649b897fd Binary files /dev/null and b/previews/PR798/literate-gallery/bending_animation.gif differ diff --git a/previews/PR798/literate-gallery/helmholtz.jl b/previews/PR798/literate-gallery/helmholtz.jl new file mode 100644 index 0000000000..8729c55426 --- /dev/null +++ b/previews/PR798/literate-gallery/helmholtz.jl @@ -0,0 +1,175 @@ +# # [Helmholtz equation](@id tutorial-helmholtz) +# +# In this example, we want to solve a (variant of) of the [Helmholtz equation](https://en.wikipedia.org/wiki/Helmholtz_equation). +# The example is inspired by an [dealii step_7](https://www.dealii.org/8.4.1/doxygen/deal.II/step_7.html) on the standard square. +# +# ```math +# - \Delta u + u = f +# ``` +# +# With boundary conditions given by +# ```math +# u = g_1 \quad x \in \Gamma_1 +# ``` +# and +# ```math +# n \cdot \nabla u = g_2 \quad x \in \Gamma_2 +# ``` +# +# Here Γ₁ is the union of the top and the right boundary of the square, +# while Γ₂ is the union of the bottom and the left boundary. +# +# ![](helmholtz.png) +# +# We will use the following weak formulation: +# ```math +# \int_\Omega \nabla δu \cdot \nabla u \, d\Omega +# + \int_\Omega δu \cdot u \, d\Omega +# - \int_\Omega δu \cdot f \, d\Omega +# - \int_{\Gamma_2} δu g_2 \, d\Gamma = 0 \quad \forall δu +# ``` +# +# where $δu$ is a suitable test function that satisfies: +# ```math +# δu = 0 \quad x \in \Gamma_1 +# ``` +# and $u$ is a suitable function that satisfies: +# ```math +# u = g_1 \quad x \in \Gamma_1 +# ``` +# The example highlights the following interesting features: +# +# * There are two kinds of boundary conditions, "Dirichlet" and "Von Neumann" +# * The example contains boundary integrals +# * The Dirichlet condition is imposed strongly and the Von Neumann condition is imposed weakly. +# +using Ferrite +using Tensors +using SparseArrays +using LinearAlgebra + +const ∇ = Tensors.gradient +const Δ = Tensors.hessian; + +grid = generate_grid(Quadrilateral, (150, 150)) + +ip = Lagrange{RefQuadrilateral, 1}() +qr = QuadratureRule{RefQuadrilateral}(2) +qr_facet = FacetQuadratureRule{RefQuadrilateral}(2) +cellvalues = CellValues(qr, ip); +facetvalues = FacetValues(qr_facet, ip); + +dh = DofHandler(grid) +add!(dh, :u, ip) +close!(dh) + +# We will set things up, so that a known analytic solution is approximately reproduced. +# This is a good testing strategy for PDE codes and known as the method of manufactured solutions. + +function u_ana(x::Vec{2, T}) where {T} + xs = ( + Vec{2}((-0.5, 0.5)), + Vec{2}((-0.5, -0.5)), + Vec{2}((0.5, -0.5)), + ) + σ = 1 / 8 + s = zero(eltype(x)) + for i in 1:3 + s += exp(- norm(x - xs[i])^2 / σ^2) + end + return max(1.0e-15 * one(T), s) # Denormals, be gone +end; + +dbcs = ConstraintHandler(dh) +# The (strong) Dirichlet boundary condition can be handled automatically by the Ferrite library. +dbc = Dirichlet(:u, union(getfacetset(grid, "top"), getfacetset(grid, "right")), (x, t) -> u_ana(x)) +add!(dbcs, dbc) +close!(dbcs) +update!(dbcs, 0.0) + +K = allocate_matrix(dh); + +function doassemble( + cellvalues::CellValues, facetvalues::FacetValues, K::SparseMatrixCSC, dh::DofHandler + ) + b = 1.0 + f = zeros(ndofs(dh)) + assembler = start_assemble(K, f) + + n_basefuncs = getnbasefunctions(cellvalues) + + fe = zeros(n_basefuncs) # Local force vector + Ke = zeros(n_basefuncs, n_basefuncs) # Local stiffness mastrix + + for (cellcount, cell) in enumerate(CellIterator(dh)) + fill!(Ke, 0) + fill!(fe, 0) + coords = getcoordinates(cell) + + reinit!(cellvalues, cell) + # First we derive the non boundary part of the variation problem from the destined solution `u_ana` + # ```math + # \int_\Omega \nabla δu \cdot \nabla u \, d\Omega + # + \int_\Omega δu \cdot u \, d\Omega + # - \int_\Omega δu \cdot f \, d\Omega + # ``` + #+ + + for q_point in 1:getnquadpoints(cellvalues) + dΩ = getdetJdV(cellvalues, q_point) + coords_qp = spatial_coordinate(cellvalues, q_point, coords) + f_true = -LinearAlgebra.tr(hessian(u_ana, coords_qp)) + u_ana(coords_qp) + for i in 1:n_basefuncs + δu = shape_value(cellvalues, q_point, i) + ∇δu = shape_gradient(cellvalues, q_point, i) + fe[i] += (δu * f_true) * dΩ + for j in 1:n_basefuncs + u = shape_value(cellvalues, q_point, j) + ∇u = shape_gradient(cellvalues, q_point, j) + Ke[i, j] += (∇δu ⋅ ∇u + δu * u) * dΩ + end + end + end + + # Now we manually add the von Neumann boundary terms + # ```math + # \int_{\Gamma_2} δu g_2 \, d\Gamma + # ``` + #+ + for facet in 1:nfacets(cell) + if (cellcount, facet) ∈ getfacetset(grid, "left") || + (cellcount, facet) ∈ getfacetset(grid, "bottom") + reinit!(facetvalues, cell, facet) + for q_point in 1:getnquadpoints(facetvalues) + coords_qp = spatial_coordinate(facetvalues, q_point, coords) + n = getnormal(facetvalues, q_point) + g_2 = gradient(u_ana, coords_qp) ⋅ n + dΓ = getdetJdV(facetvalues, q_point) + for i in 1:n_basefuncs + δu = shape_value(facetvalues, q_point, i) + fe[i] += (δu * g_2) * dΓ + end + end + end + end + + assemble!(assembler, celldofs(cell), Ke, fe) + end + return K, f +end; + +K, f = doassemble(cellvalues, facetvalues, K, dh); +apply!(K, f, dbcs) +u = Symmetric(K) \ f; + +vtk = VTKGridFile("helmholtz", dh) +write_solution(vtk, dh, u) +close(vtk) +using Test #src +#src this test catches unexpected changes in the result over time. +#src the true maximum is slightly bigger then 1.0 +@test maximum(u) ≈ 0.9952772469054607 #src +@test u_ana(Vec{2}((-0.5, -0.5))) ≈ 1 #src +@test u_ana(Vec{2}((0.5, -0.5))) ≈ 1 #src +@test u_ana(Vec{2}((-0.5, 0.5))) ≈ 1 #src +println("Helmholtz successful") diff --git a/previews/PR798/literate-gallery/helmholtz.png b/previews/PR798/literate-gallery/helmholtz.png new file mode 100644 index 0000000000..100d3e0306 Binary files /dev/null and b/previews/PR798/literate-gallery/helmholtz.png differ diff --git a/previews/PR798/literate-gallery/landau.jl b/previews/PR798/literate-gallery/landau.jl new file mode 100644 index 0000000000..e16ead420e --- /dev/null +++ b/previews/PR798/literate-gallery/landau.jl @@ -0,0 +1,268 @@ +# # [Ginzburg-Landau model energy minimization](@id tutorial-ginzburg-landau-minimizer) + +# ![landau_orig.png](landau_orig.png) + +# Original + +# ![landau_opt.png](landau_opt.png) + +# Optimized + +# In this example a basic Ginzburg-Landau model is solved. +# This example gives an idea of how the API together with ForwardDiff can be leveraged to +# performantly solve non standard problems on a FEM grid. +# A large portion of the code is there only for performance reasons, +# but since this usually really matters and is what takes the most time to optimize, +# it is included. + +# The key to using a method like this for minimizing a free energy function directly, +# rather than the weak form, as is usually done with FEM, is to split up the +# gradient and Hessian calculations. +# This means that they are performed for each cell separately instead of for the +# grid as a whole. + +using ForwardDiff +import ForwardDiff: GradientConfig, HessianConfig, Chunk +using Ferrite +using Optim, LineSearches +using SparseArrays +using Tensors +using Base.Threads +# ## Energy terms +# ### 4th order Landau free energy +function Fl(P::Vec{3, T}, α::Vec{3}) where {T} + P2 = Vec{3, T}((P[1]^2, P[2]^2, P[3]^2)) + return α[1] * sum(P2) + + α[2] * (P[1]^4 + P[2]^4 + P[3]^4) + + α[3] * ((P2[1] * P2[2] + P2[2] * P2[3]) + P2[1] * P2[3]) +end +# ### Ginzburg free energy +@inline Fg(∇P, G) = 0.5(∇P ⊡ G) ⊡ ∇P +# ### GL free energy +F(P, ∇P, params) = Fl(P, params.α) + Fg(∇P, params.G) + +# ### Parameters that characterize the model +struct ModelParams{V, T} + α::V + G::T +end + +# ### ThreadCache +# This holds the values that each thread will use during the assembly. +struct ThreadCache{CV, T, DIM, F <: Function, GC <: GradientConfig, HC <: HessianConfig} + cvP::CV + element_indices::Vector{Int} + element_dofs::Vector{T} + element_gradient::Vector{T} + element_hessian::Matrix{T} + element_coords::Vector{Vec{DIM, T}} + element_potential::F + gradconf::GC + hessconf::HC +end +function ThreadCache(dpc::Int, nodespercell, cvP::CellValues, modelparams, elpotential) + element_indices = zeros(Int, dpc) + element_dofs = zeros(dpc) + element_gradient = zeros(dpc) + element_hessian = zeros(dpc, dpc) + element_coords = zeros(Vec{3, Float64}, nodespercell) + potfunc = x -> elpotential(x, cvP, modelparams) + gradconf = GradientConfig(potfunc, zeros(dpc), Chunk{12}()) + hessconf = HessianConfig(potfunc, zeros(dpc), Chunk{4}()) + return ThreadCache(cvP, element_indices, element_dofs, element_gradient, element_hessian, element_coords, potfunc, gradconf, hessconf) +end + +# ## The Model +# everything is combined into a model. +mutable struct LandauModel{T, DH <: DofHandler, CH <: ConstraintHandler, TC <: ThreadCache} + dofs::Vector{T} + dofhandler::DH + boundaryconds::CH + threadindices::Vector{Vector{Int}} + threadcaches::Vector{TC} +end + +function LandauModel(α, G, gridsize, left::Vec{DIM, T}, right::Vec{DIM, T}, elpotential) where {DIM, T} + grid = generate_grid(Tetrahedron, gridsize, left, right) + threadindices = Ferrite.create_coloring(grid) + + qr = QuadratureRule{RefTetrahedron}(2) + ipP = Lagrange{RefTetrahedron, 1}()^3 + cvP = CellValues(qr, ipP) + + dofhandler = DofHandler(grid) + add!(dofhandler, :P, ipP) + close!(dofhandler) + + dofvector = zeros(ndofs(dofhandler)) + startingconditions!(dofvector, dofhandler) + boundaryconds = ConstraintHandler(dofhandler) + #boundary conditions can be added but aren't necessary for optimization + #add!(boundaryconds, Dirichlet(:P, getfacetset(grid, "left"), (x, t) -> [0.0,0.0,0.53], [1,2,3])) + #add!(boundaryconds, Dirichlet(:P, getfacetset(grid, "right"), (x, t) -> [0.0,0.0,-0.53], [1,2,3])) + close!(boundaryconds) + update!(boundaryconds, 0.0) + + apply!(dofvector, boundaryconds) + + hessian = allocate_matrix(dofhandler) + dpc = ndofs_per_cell(dofhandler) + cpc = length(grid.cells[1].nodes) + caches = [ThreadCache(dpc, cpc, copy(cvP), ModelParams(α, G), elpotential) for t in 1:nthreads()] + return LandauModel(dofvector, dofhandler, boundaryconds, threadindices, caches) +end + +# utility to quickly save a model +function save_landau(path, model, dofs = model.dofs) + VTKGridFile(path, model.dofhandler) do vtk + write_solution(vtk, model.dofhandler, dofs) + end + return +end + +# ## Assembly +# This macro defines most of the assembly step, since the structure is the same for +# the energy, gradient and Hessian calculations. +macro assemble!(innerbody) + return esc( + quote + dofhandler = model.dofhandler + for indices in model.threadindices + @threads for i in indices + cache = model.threadcaches[threadid()] + eldofs = cache.element_dofs + nodeids = dofhandler.grid.cells[i].nodes + for j in 1:length(cache.element_coords) + cache.element_coords[j] = dofhandler.grid.nodes[nodeids[j]].x + end + reinit!(cache.cvP, cache.element_coords) + + celldofs!(cache.element_indices, dofhandler, i) + for j in 1:length(cache.element_dofs) + eldofs[j] = dofvector[cache.element_indices[j]] + end + $innerbody + end + end + end + ) +end + +# This calculates the total energy calculation of the grid +function F(dofvector::Vector{T}, model) where {T} + outs = fill(zero(T), nthreads()) + @assemble! begin + outs[threadid()] += cache.element_potential(eldofs) + end + return sum(outs) +end + +# The gradient calculation for each dof +function ∇F!(∇f::Vector{T}, dofvector::Vector{T}, model::LandauModel{T}) where {T} + fill!(∇f, zero(T)) + @assemble! begin + ForwardDiff.gradient!(cache.element_gradient, cache.element_potential, eldofs, cache.gradconf) + @inbounds assemble!(∇f, cache.element_indices, cache.element_gradient) + end + return +end + +# The Hessian calculation for the whole grid +function ∇²F!(∇²f::SparseMatrixCSC, dofvector::Vector{T}, model::LandauModel{T}) where {T} + assemblers = [start_assemble(∇²f) for t in 1:nthreads()] + @assemble! begin + ForwardDiff.hessian!(cache.element_hessian, cache.element_potential, eldofs, cache.hessconf) + @inbounds assemble!(assemblers[threadid()], cache.element_indices, cache.element_hessian) + end + return +end + +# We can also calculate all things in one go! +function calcall(∇²f::SparseMatrixCSC, ∇f::Vector{T}, dofvector::Vector{T}, model::LandauModel{T}) where {T} + outs = fill(zero(T), nthreads()) + fill!(∇f, zero(T)) + assemblers = [start_assemble(∇²f, ∇f) for t in 1:nthreads()] + @assemble! begin + outs[threadid()] += cache.element_potential(eldofs) + ForwardDiff.hessian!(cache.element_hessian, cache.element_potential, eldofs, cache.hessconf) + ForwardDiff.gradient!(cache.element_gradient, cache.element_potential, eldofs, cache.gradconf) + @inbounds assemble!(assemblers[threadid()], cache.element_indices, cache.element_gradient, cache.element_hessian) + end + return sum(outs) +end + +# ## Minimization +# Now everything can be combined to minimize the energy, and find the equilibrium +# configuration. +function minimize!(model; kwargs...) + dh = model.dofhandler + dofs = model.dofs + ∇f = fill(0.0, length(dofs)) + ∇²f = allocate_matrix(dh) + function g!(storage, x) + ∇F!(storage, x, model) + return apply_zero!(storage, model.boundaryconds) + end + function h!(storage, x) + return ∇²F!(storage, x, model) + ## apply!(storage, model.boundaryconds) + end + f(x) = F(x, model) + + od = TwiceDifferentiable(f, g!, h!, model.dofs, 0.0, ∇f, ∇²f) + + ## this way of minimizing is only beneficial when the initial guess is completely off, + ## then a quick couple of ConjuageGradient steps brings us easily closer to the minimum. + ## res = optimize(od, model.dofs, ConjugateGradient(linesearch=BackTracking()), Optim.Options(show_trace=true, show_every=1, g_tol=1e-20, iterations=10)) + ## model.dofs .= res.minimizer + ## to get the final convergence, Newton's method is more ideal since the energy landscape should be almost parabolic + ##+ + res = optimize(od, model.dofs, Newton(linesearch = BackTracking()), Optim.Options(show_trace = true, show_every = 1, g_tol = 1.0e-20)) + model.dofs .= res.minimizer + return res +end + +# ## Testing it +# This calculates the contribution of each element to the total energy, +# it is also the function that will be put through ForwardDiff for the gradient and Hessian. +function element_potential(eldofs::AbstractVector{T}, cvP, params) where {T} + energy = zero(T) + for qp in 1:getnquadpoints(cvP) + P = function_value(cvP, qp, eldofs) + ∇P = function_gradient(cvP, qp, eldofs) + energy += F(P, ∇P, params) * getdetJdV(cvP, qp) + end + return energy +end + +# now we define some starting conditions +function startingconditions!(dofvector, dofhandler) + for cell in CellIterator(dofhandler) + globaldofs = celldofs(cell) + it = 1 + for i in 1:3:length(globaldofs) + dofvector[globaldofs[i]] = -2.0 + dofvector[globaldofs[i + 1]] = 2.0 + dofvector[globaldofs[i + 2]] = -2.0tanh(cell.coords[it][1] / 20) + it += 1 + end + end + return +end + +δ(i, j) = i == j ? one(i) : zero(i) +V2T(p11, p12, p44) = Tensor{4, 3}((i, j, k, l) -> p11 * δ(i, j) * δ(k, l) * δ(i, k) + p12 * δ(i, j) * δ(k, l) * (1 - δ(i, k)) + p44 * δ(i, k) * δ(j, l) * (1 - δ(i, j))) + +G = V2T(1.0e2, 0.0, 1.0e2) +α = Vec{3}((-1.0, 1.0, 1.0)) +left = Vec{3}((-75.0, -25.0, -2.0)) +right = Vec{3}((75.0, 25.0, 2.0)) +model = LandauModel(α, G, (50, 50, 2), left, right, element_potential) + +save_landau("landauorig", model) +@time minimize!(model) +save_landau("landaufinal", model) + +# as we can see this runs very quickly even for relatively large gridsizes. +# The key to get high performance like this is to minimize the allocations inside the threaded loops, +# ideally to 0. diff --git a/previews/PR798/literate-gallery/landau_opt.png b/previews/PR798/literate-gallery/landau_opt.png new file mode 100644 index 0000000000..66c329f7a2 Binary files /dev/null and b/previews/PR798/literate-gallery/landau_opt.png differ diff --git a/previews/PR798/literate-gallery/landau_orig.png b/previews/PR798/literate-gallery/landau_orig.png new file mode 100644 index 0000000000..2075e884c7 Binary files /dev/null and b/previews/PR798/literate-gallery/landau_orig.png differ diff --git a/previews/PR798/literate-gallery/quasi_incompressible_hyperelasticity.gif b/previews/PR798/literate-gallery/quasi_incompressible_hyperelasticity.gif new file mode 100644 index 0000000000..15f3aa97e9 Binary files /dev/null and b/previews/PR798/literate-gallery/quasi_incompressible_hyperelasticity.gif differ diff --git a/previews/PR798/literate-gallery/quasi_incompressible_hyperelasticity.jl b/previews/PR798/literate-gallery/quasi_incompressible_hyperelasticity.jl new file mode 100644 index 0000000000..2f19d4ada9 --- /dev/null +++ b/previews/PR798/literate-gallery/quasi_incompressible_hyperelasticity.jl @@ -0,0 +1,371 @@ +# # [Nearly Incompressible Hyperelasticity](@id tutorial-nearly-incompressible-hyperelasticity) +# +# ![](quasi_incompressible_hyperelasticity.gif) +#- +#md # !!! tip +#md # This example is also available as a Jupyter notebook: +#md # [`quasi_incompressible_hyperelasticity.ipynb`](@__NBVIEWER_ROOT_URL__/gallery/quasi_incompressible_hyperelasticity.ipynb) +#- +# ## Introduction +# +# In this example we study quasi- or nearly-incompressible hyperelasticity using the stable Taylor-Hood approximation. In spirit, this example is the nonlinear analogue of +# [`incompressible_elasticity`](@__NBVIEWER_ROOT_URL__/tutorials/incompressible_elasticity.ipynb) and the incompressible analogue of +# [`hyperelasticity`](@__NBVIEWER_ROOT_URL__/tutorials/hyperelasticity.ipynb). Much of the code therefore follows from the above two examples. +# The problem is formulated in the undeformed or reference configuration with the displacement $\mathbf{u}$ and pressure $p$ being the unknown fields. We now briefly outline +# the formulation. Consider the standard hyperelasticity problem +# +# ```math +# \mathbf{u} = \argmin_{\mathbf{v}\in\mathcal{K}(\Omega)}\Pi(\mathbf{v}),\quad \text{where}\quad \Pi(\mathbf{v}) = \int_\Omega \Psi(\mathbf{v}) \ \mathrm{d}\Omega\ . +# ``` +# +# where $\mathcal{K}(\Omega)$ is a suitable function space. +# +# For clarity of presentation we ignore any non-zero surface tractions and body forces and instead consider only +# applied displacements (i.e. non-homogeneous dirichlet boundary conditions). Moreover we stick our attention to the standard Neo-Hookean stored energy density +# +# ```math +# \Psi(\mathbf{u}) = \frac{\mu}{2}\left(I_1 - 3 \right) - \mu \log(J) + \frac{\lambda}{2}\left( J - 1\right){}^2, +# ``` +# where $I_1 = \mathrm{tr}(\mathbf{C}) = \mathrm{tr}(\mathbf{F}^\mathrm{T} \mathbf{F}) = F_{ij}F_{ij}$ and $J = \det(\mathbf{F})$ denote the standard invariants of the deformation gradient tensor $\mathbf{F} = \mathbf{I}+\nabla_{\mathbf{X}} \mathbf{u}$. +# The above problem is ill-posed in the limit of incompressibility (or near-incompressibility), namely when +# ```math +# \lambda/\mu \rightarrow +\infty. +# ``` +# In order to alleviate the problem, we consider the partial legendre transform of the strain energy density $\Psi$ with respect to $J = \det(\mathbf{F})$, namely +# ```math +# \widehat{\Psi}(\mathbf{u}, p) = \sup_{J} \left[ p(J - 1) - \frac{\mu}{2}\left(I_1 - 3 \right) + \mu \log(J) - \frac{\lambda}{2}\left( J - 1\right){}^2 \right]. +# ``` +# The supremum, say $J^\star$, can be calculated in closed form by the first order optimailty condition $\partial\widehat{\Psi}/\partial J = 0$. This gives +# ```math +# J^\star(p) = \frac{\lambda + p + \sqrt{(\lambda + p){}^2 + 4 \lambda \mu }}{(2 \lambda)}. +# ``` +# Furthermore, taking the partial legendre transform of $\widehat{\Psi}$ once again, gives us back the original problem, i.e. +# ```math +# \Psi(\mathbf{u}) = \Psi^\star(\mathbf{u}, p) = \sup_{p} \left[ p(J - 1) - p(J^\star - 1) + \frac{\mu}{2}\left(I_1 - 3 \right) - \mu \log(J^\star) + \frac{\lambda}{2}\left( J^\star - 1\right){}^2 \right]. +# ``` +# Therefore our original hyperelasticity problem can now be reformulated as +# ```math +# \inf_{\mathbf{u}\in\mathcal{K}(\Omega)}\sup_{p} \int_\Omega\Psi^{\star} (\mathbf{u}, p) \, \mathrm{d}\Omega. +# ``` +# The total (modified) energy $\Pi^\star$ can then be written as +# ```math +# \Pi^\star(\mathbf{u}, p) = \int_\Omega p (J - J^\star) \ \mathrm{d}\Omega + \int_\Omega \frac{\mu}{2} \left( I_1 - 3\right) \ \mathrm{d}\Omega - \int_\Omega \mu\log(J^\star)\ \mathrm{d}\Omega + \int_\Omega \frac{\lambda}{2}\left( J^\star - 1 \right){}^2\ \mathrm{d}\Omega +# ``` +# The Euler-Lagrange equations corresponding to the above energy give us our governing PDEs in the weak form, namely +# ```math +# \int_\Omega \frac{\partial\Psi^\star}{\partial \mathbf{F}}:\delta \mathbf{F} \ \mathrm{d}\Omega = 0 +# ``` +# and +# ```math +# \int_\Omega \frac{\partial \Psi^\star}{\partial p}\delta p \ \mathrm{d}\Omega = 0, +# ``` +# where $\delta \mathrm{F} = \delta \mathrm{grad}_0(\mathbf{u}) = \mathrm{grad}_0(\delta \mathbf{u})$ and $\delta \mathbf{u}$ and $\delta p$ denote arbitrary variations with respect to displacement and pressure (or the test functions). See the references +# below for a more detailed explanation of the above mathematical trick. Now, in order to apply Newton's method to the +# above problem, we further need to linearize the above equations and calculate the respective hessians (or tangents), namely, $\partial^2\Psi^\star/\partial \mathbf{F}^2$, $\partial^2\Psi^\star/\partial p^2$ and $\partial^2\Psi^\star/\partial \mathbf{F}\partial p$ +# which, using `Tensors.jl`, can be determined conveniently using automatic differentiation (see the code below). Hence we only need to define the above potential. +# The remaineder of the example follows similarly. +# ## References +# 1. [A paradigm for higher-order polygonal elements in finite elasticity using a gradient correction scheme, CMAME 2016, 306, 216–251](http://pamies.cee.illinois.edu/Publications_files/CMAME_2016.pdf) +# 2. [Approximation of incompressible large deformation elastic problems: some unresolved issues, Computational Mechanics, 2013](https://link.springer.com/content/pdf/10.1007/s00466-013-0869-0.pdf) +# + +# ## Implementation +# We now get to the actual code. First, we import the respective packages + +using Ferrite, Tensors, ProgressMeter, WriteVTK +using BlockArrays, SparseArrays, LinearAlgebra + +# and the corresponding `struct` to store our material properties. + +struct NeoHooke + μ::Float64 + λ::Float64 +end + +# We then create a function to generate a simple test mesh on which to compute FE solution. We also mark the boundaries +# to later assign Dirichlet boundary conditions +function importTestGrid() + grid = generate_grid(Tetrahedron, (5, 5, 5), zero(Vec{3}), ones(Vec{3})) + addfacetset!(grid, "myBottom", x -> norm(x[2]) ≈ 0.0) + addfacetset!(grid, "myBack", x -> norm(x[3]) ≈ 0.0) + addfacetset!(grid, "myRight", x -> norm(x[1]) ≈ 1.0) + addfacetset!(grid, "myLeft", x -> norm(x[1]) ≈ 0.0) + return grid +end; + +# The function to create corresponding cellvalues for the displacement field `u` and pressure `p` +# follows in a similar fashion from the `incompressible_elasticity` example +function create_values(interpolation_u, interpolation_p) + ## quadrature rules + qr = QuadratureRule{RefTetrahedron}(4) + facet_qr = FacetQuadratureRule{RefTetrahedron}(4) + + ## cell and facetvalues for u + cellvalues_u = CellValues(qr, interpolation_u) + facetvalues_u = FacetValues(facet_qr, interpolation_u) + + ## cellvalues for p + cellvalues_p = CellValues(qr, interpolation_p) + + return cellvalues_u, cellvalues_p, facetvalues_u +end; + +# We now create the function for Ψ* +function Ψ(F, p, mp::NeoHooke) + μ = mp.μ + λ = mp.λ + Ic = tr(tdot(F)) + J = det(F) + Js = (λ + p + sqrt((λ + p)^2.0 + 4.0 * λ * μ)) / (2.0 * λ) + return p * (Js - J) + μ / 2 * (Ic - 3) - μ * log(Js) + λ / 2 * (Js - 1)^2 +end; + +# and it's derivatives (required in computing the jacobian and hessian respectively) +function constitutive_driver(F, p, mp::NeoHooke) + ## Compute all derivatives in one function call + ∂²Ψ∂F², ∂Ψ∂F = Tensors.hessian(y -> Ψ(y, p, mp), F, :all) + ∂²Ψ∂p², ∂Ψ∂p = Tensors.hessian(y -> Ψ(F, y, mp), p, :all) + ∂²Ψ∂F∂p = Tensors.gradient(q -> Tensors.gradient(y -> Ψ(y, q, mp), F), p) + return ∂Ψ∂F, ∂²Ψ∂F², ∂Ψ∂p, ∂²Ψ∂p², ∂²Ψ∂F∂p +end; + +# The functions to create the `DofHandler` and `ConstraintHandler` (to assign corresponding boundary conditions) follow +# likewise from the incompressible elasticity example, namely + +function create_dofhandler(grid, ipu, ipp) + dh = DofHandler(grid) + add!(dh, :u, ipu) # displacement dim = 3 + add!(dh, :p, ipp) # pressure dim = 1 + close!(dh) + return dh +end; + +# We are simulating a uniaxial tensile loading of a unit cube. Hence we apply a displacement field (`:u`) in `x` direction on the right face. +# The left, bottom and back facets are fixed in the `x`, `y` and `z` components of the displacement so as to emulate the uniaxial nature +# of the loading. +function create_bc(dh) + dbc = ConstraintHandler(dh) + add!(dbc, Dirichlet(:u, getfacetset(dh.grid, "myLeft"), (x, t) -> zero(Vec{1}), [1])) + add!(dbc, Dirichlet(:u, getfacetset(dh.grid, "myBottom"), (x, t) -> zero(Vec{1}), [2])) + add!(dbc, Dirichlet(:u, getfacetset(dh.grid, "myBack"), (x, t) -> zero(Vec{1}), [3])) + add!(dbc, Dirichlet(:u, getfacetset(dh.grid, "myRight"), (x, t) -> t * ones(Vec{1}), [1])) + close!(dbc) + Ferrite.update!(dbc, 0.0) + return dbc +end; + +# Also, since we are considering incompressible hyperelasticity, an interesting quantity that we can compute is the deformed volume of the solid. +# It is easy to show that this is equal to ∫J*dΩ where J=det(F). This can be done at the level of each element (cell) +function calculate_element_volume(cell, cellvalues_u, ue) + reinit!(cellvalues_u, cell) + evol::Float64 = 0.0 + for qp in 1:getnquadpoints(cellvalues_u) + dΩ = getdetJdV(cellvalues_u, qp) + ∇u = function_gradient(cellvalues_u, qp, ue) + F = one(∇u) + ∇u + J = det(F) + evol += J * dΩ + end + return evol +end; + +# and then assembled over all the cells (elements) +function calculate_volume_deformed_mesh(w, dh::DofHandler, cellvalues_u) + evol::Float64 = 0.0 + for cell in CellIterator(dh) + global_dofs = celldofs(cell) + nu = getnbasefunctions(cellvalues_u) + global_dofs_u = global_dofs[1:nu] + ue = w[global_dofs_u] + δevol = calculate_element_volume(cell, cellvalues_u, ue) + evol += δevol + end + return evol +end; + +# The function to assemble the element stiffness matrix for each element in the mesh now has a block structure like in +# `incompressible_elasticity`. +function assemble_element!(Ke, fe, cell, cellvalues_u, cellvalues_p, mp, ue, pe) + ## Reinitialize cell values, and reset output arrays + ublock, pblock = 1, 2 + reinit!(cellvalues_u, cell) + reinit!(cellvalues_p, cell) + fill!(Ke, 0.0) + fill!(fe, 0.0) + + n_basefuncs_u = getnbasefunctions(cellvalues_u) + n_basefuncs_p = getnbasefunctions(cellvalues_p) + + for qp in 1:getnquadpoints(cellvalues_u) + dΩ = getdetJdV(cellvalues_u, qp) + ## Compute deformation gradient F + ∇u = function_gradient(cellvalues_u, qp, ue) + p = function_value(cellvalues_p, qp, pe) + F = one(∇u) + ∇u + + ## Compute first Piola-Kirchhoff stress and tangent modulus + ∂Ψ∂F, ∂²Ψ∂F², ∂Ψ∂p, ∂²Ψ∂p², ∂²Ψ∂F∂p = constitutive_driver(F, p, mp) + + ## Loop over the `u`-test functions to calculate the `u`-`u` and `u`-`p` blocks + for i in 1:n_basefuncs_u + ## gradient of the test function + ∇δui = shape_gradient(cellvalues_u, qp, i) + ## Add contribution to the residual from this test function + fe[BlockIndex((ublock), (i))] += (∇δui ⊡ ∂Ψ∂F) * dΩ + + ∇δui∂S∂F = ∇δui ⊡ ∂²Ψ∂F² + for j in 1:n_basefuncs_u + ∇δuj = shape_gradient(cellvalues_u, qp, j) + + ## Add contribution to the tangent + Ke[BlockIndex((ublock, ublock), (i, j))] += (∇δui∂S∂F ⊡ ∇δuj) * dΩ + end + ## Loop over the `p`-test functions + for j in 1:n_basefuncs_p + δp = shape_value(cellvalues_p, qp, j) + ## Add contribution to the tangent + Ke[BlockIndex((ublock, pblock), (i, j))] += (∂²Ψ∂F∂p ⊡ ∇δui) * δp * dΩ + end + end + ## Loop over the `p`-test functions to calculate the `p-`u` and `p`-`p` blocks + for i in 1:n_basefuncs_p + δp = shape_value(cellvalues_p, qp, i) + fe[BlockIndex((pblock), (i))] += (δp * ∂Ψ∂p) * dΩ + + for j in 1:n_basefuncs_u + ∇δuj = shape_gradient(cellvalues_u, qp, j) + Ke[BlockIndex((pblock, ublock), (i, j))] += ∇δuj ⊡ ∂²Ψ∂F∂p * δp * dΩ + end + for j in 1:n_basefuncs_p + δp = shape_value(cellvalues_p, qp, j) + Ke[BlockIndex((pblock, pblock), (i, j))] += δp * ∂²Ψ∂p² * δp * dΩ + end + end + end + return +end; + +# The only thing that changes in the assembly of the global stiffness matrix is slicing the corresponding element +# dofs for the displacement (see `global_dofsu`) and pressure (`global_dofsp`). +function assemble_global!( + K::SparseMatrixCSC, f, cellvalues_u::CellValues, + cellvalues_p::CellValues, dh::DofHandler, mp::NeoHooke, w + ) + nu = getnbasefunctions(cellvalues_u) + np = getnbasefunctions(cellvalues_p) + + ## start_assemble resets K and f + fe = BlockedArray(zeros(nu + np), [nu, np]) # local force vector + ke = BlockedArray(zeros(nu + np, nu + np), [nu, np], [nu, np]) # local stiffness matrix + + assembler = start_assemble(K, f) + ## Loop over all cells in the grid + for cell in CellIterator(dh) + global_dofs = celldofs(cell) + global_dofsu = global_dofs[1:nu] # first nu dofs are displacement + global_dofsp = global_dofs[(nu + 1):end] # last np dofs are pressure + @assert size(global_dofs, 1) == nu + np # sanity check + ue = w[global_dofsu] # displacement dofs for the current cell + pe = w[global_dofsp] # pressure dofs for the current cell + assemble_element!(ke, fe, cell, cellvalues_u, cellvalues_p, mp, ue, pe) + assemble!(assembler, global_dofs, ke, fe) + end + return +end; + +# We now define a main function `solve`. For nonlinear quasistatic problems we often like to parameterize the +# solution in terms of a pseudo time like parameter, which in this case is used to gradually apply the boundary +# displacement on the right face. Also for definitenessm we consider λ/μ = 10⁴ +function solve(interpolation_u, interpolation_p) + + ## import the mesh + grid = importTestGrid() + + ## Material parameters + μ = 1.0 + λ = 1.0e4 * μ + mp = NeoHooke(μ, λ) + + ## Create the DofHandler and CellValues + dh = create_dofhandler(grid, interpolation_u, interpolation_p) + cellvalues_u, cellvalues_p, facetvalues_u = create_values(interpolation_u, interpolation_p) + + ## Create the DirichletBCs + dbc = create_bc(dh) + + ## Pre-allocation of vectors for the solution and Newton increments + _ndofs = ndofs(dh) + w = zeros(_ndofs) + ΔΔw = zeros(_ndofs) + apply!(w, dbc) + + ## Create the sparse matrix and residual vector + K = allocate_matrix(dh) + f = zeros(_ndofs) + + ## We run the simulation parameterized by a time like parameter. `Tf` denotes the final value + ## of this parameter, and Δt denotes its increment in each step + Tf = 2.0 + Δt = 0.1 + NEWTON_TOL = 1.0e-8 + + pvd = paraview_collection("hyperelasticity_incomp_mixed") + for (step, t) in enumerate(0.0:Δt:Tf) + ## Perform Newton iterations + Ferrite.update!(dbc, t) + apply!(w, dbc) + newton_itr = -1 + prog = ProgressMeter.ProgressThresh(NEWTON_TOL; desc = "Solving @ time $t of $Tf;") + fill!(ΔΔw, 0.0) + while true + newton_itr += 1 + assemble_global!(K, f, cellvalues_u, cellvalues_p, dh, mp, w) + norm_res = norm(f[Ferrite.free_dofs(dbc)]) + apply_zero!(K, f, dbc) + ## Only display output at specific load steps + if t % (5 * Δt) == 0 + ProgressMeter.update!(prog, norm_res; showvalues = [(:iter, newton_itr)]) + end + if norm_res < NEWTON_TOL + break + elseif newton_itr > 30 + error("Reached maximum Newton iterations, aborting") + end + ## Compute the incremental `dof`-vector (both displacement and pressure) + ΔΔw .= K \ f + + apply_zero!(ΔΔw, dbc) + w .-= ΔΔw + end + + ## Save the solution fields + VTKGridFile("hyperelasticity_incomp_mixed_$step", grid) do vtk + write_solution(vtk, dh, w) + pvd[t] = vtk + end + end + vtk_save(pvd) + vol_def = calculate_volume_deformed_mesh(w, dh, cellvalues_u) + print("Deformed volume is $vol_def") + return vol_def +end; + +# We can now test the solution using the Taylor-Hood approximation +quadratic_u = Lagrange{RefTetrahedron, 2}()^3 +linear_p = Lagrange{RefTetrahedron, 1}() +vol_def = solve(quadratic_u, linear_p) + +# The deformed volume is indeed close to 1 (as should be for a nearly incompressible material). + +using Test #src +@test isapprox(vol_def, 1.0, atol = 1.0e-3) #src + +#md # ## Plain program +#md # +#md # Here follows a version of the program without any comments. +#md # The file is also available here: +#md # [`quasi_incompressible_hyperelasticity.jl`](quasi_incompressible_hyperelasticity.jl). +#md # +#md # ```julia +#md # @__CODE__ +#md # ``` diff --git a/previews/PR798/literate-gallery/topology_optimization.jl b/previews/PR798/literate-gallery/topology_optimization.jl new file mode 100644 index 0000000000..37e99e00d7 --- /dev/null +++ b/previews/PR798/literate-gallery/topology_optimization.jl @@ -0,0 +1,560 @@ +# # [Topology optimization](@id tutorial-topology-optimization) +# +# **Keywords**: *Topology optimization*, *weak and strong form*, *non-linear problem*, *Laplacian*, *grid topology* +# +# ![](bending_animation.gif) +# +# *Figure 1*: Optimization of the bending beam. Evolution of the density for fixed total mass. +# +#- +#md # !!! tip +#md # This example is also available as a Jupyter notebook: +#md # [`topology_optimization.ipynb`](@__NBVIEWER_ROOT_URL__/gallery/topology_optimization.ipynb). +#- +# +# ## Introduction +# +# Topology optimization is the task of finding structures that are mechanically ideal. +# In this example we cover the bending beam, where we specify a load, boundary conditions and the total mass. Then, our +# objective is to find the most suitable geometry within the design space minimizing the compliance (i.e. the inverse stiffness) of the structure. +# We shortly introduce our simplified model for regular meshes. A detailed derivation of the method and advanced techniques +# can be found in [JanHacJun2019regularizedthermotopopt](@cite) and +# [BlaJanJun2022taylorwlsthermotopopt](@cite). +# +# We start by introducing the local, elementwise density $\chi \in [\chi_{\text{min}}, 1]$ of the material, where we choose +# $\chi_{\text{min}}$ slightly above zero to prevent numerical instabilities. Here, $\chi = \chi_{\text{min}}$ means void and $\chi=1$ +# means bulk material. Then, we use a SIMP ansatz (solid isotropic material with penalization) for the stiffness tensor +# $C(\chi) = \chi^p C_0$, where $C_0$ is the stiffness of the bulk material. The SIMP exponent $p>1$ ensures that the +# model prefers the density values void and bulk before the intermediate values. The variational formulation then yields +# the modified Gibbs energy +# ```math +# G = \int_{\Omega} \frac{1}{2} \chi^p \varepsilon : C : \varepsilon \; \text{d}V - \int_{\Omega} \boldsymbol{f} \cdot \boldsymbol{u} \; \text{d}V - \int_{\partial\Omega} \boldsymbol{t} \cdot \boldsymbol{u} \; \text{d}A. +# ``` +# Furthermore, we receive the evolution equation of the density +# and the additional Neumann boundary condition in the strong form +# ```math +# p_\chi + \eta \dot{\chi} + \lambda + \gamma - \beta \nabla^2 \chi \ni 0 \quad \forall \textbf{x} \in \Omega, +# ``` +# ```math +# \beta \nabla \chi \cdot \textbf{n} = 0 \quad \forall \textbf{x} \in \partial \Omega, +# ``` +# with the thermodynamic driving force +# ```math +# p_\chi = \frac{1}{2} p \chi^{p-1} \varepsilon : C : \varepsilon. +# ``` +# We obtain the mechanical displacement field by applying the Finite Element Method to the weak form +# of the Gibbs energy using Ferrite. In contrast, we use the evolution equation (i.e. the strong form) to calculate +# the value of the density field $\chi$. The advantage of this "split" approach is the very high computation speed. +# The evolution equation consists of the driving force, the damping parameter $\eta$, the regularization parameter $\beta$ times the Laplacian, +# which is necessary to avoid numerical issues like mesh dependence or checkerboarding, and the constraint parameters $\lambda$, to keep the mass constant, +# and $\gamma$, to avoid leaving the set $[\chi_{\text{min}}, 1]$. By including gradient regularization, it becomes necessary to calculate the Laplacian. +# The Finite Difference Method for square meshes with the edge length $\Delta h$ approximates the Laplacian as follows: +# ```math +# \nabla^2 \chi_p = \frac{1}{(\Delta h)^2} (\chi_n + \chi_s + \chi_w + \chi_e - 4 \chi_p) +# ``` +# Here, the indices refer to the different cardinal directions. Boundary element do not have neighbors in each direction. However, we can calculate +# the central difference to fulfill Neumann boundary condition. For example, if the element is on the left boundary, we have to fulfill +# ```math +# \nabla \chi_p \cdot \textbf{n} = \frac{1}{\Delta h} (\chi_w - \chi_e) = 0 +# ``` +# from which follows $\chi_w = \chi_e$. Thus for boundary elements we can replace the value for the missing neighbor by the value of the opposite neighbor. +# In order to find the corresponding neighbor elements, we will make use of Ferrites grid topology funcionalities. +# +# ## Commented Program +# We now solve the problem in Ferrite. What follows is a program spliced with comments. +#md # The full program, without comments, can be found in the next [section](@ref topology_optimization-plain-program). +# +# First we load all necessary packages. +using Ferrite, SparseArrays, LinearAlgebra, Tensors, Printf +# Next, we create a simple square grid of the size 2x1. We apply a fixed Dirichlet boundary condition +# to the left facet set, called `clamped`. On the right facet, we create a small set `traction`, where we +# will later apply a force in negative y-direction. + +function create_grid(n) + corners = [ + Vec{2}((0.0, 0.0)), + Vec{2}((2.0, 0.0)), + Vec{2}((2.0, 1.0)), + Vec{2}((0.0, 1.0)), + ] + grid = generate_grid(Quadrilateral, (2 * n, n), corners) + + ## node-/facesets for boundary conditions + addnodeset!(grid, "clamped", x -> x[1] ≈ 0.0) + addfacetset!(grid, "traction", x -> x[1] ≈ 2.0 && norm(x[2] - 0.5) <= 0.05) + return grid +end +#md nothing # hide + +# Next, we create the FE values, the DofHandler and the Dirichlet boundary condition. + +function create_values() + ## quadrature rules + qr = QuadratureRule{RefQuadrilateral}(2) + facet_qr = FacetQuadratureRule{RefQuadrilateral}(2) + + ## cell and facetvalues for u + ip = Lagrange{RefQuadrilateral, 1}()^2 + cellvalues = CellValues(qr, ip) + facetvalues = FacetValues(facet_qr, ip) + + return cellvalues, facetvalues +end + +function create_dofhandler(grid) + dh = DofHandler(grid) + add!(dh, :u, Lagrange{RefQuadrilateral, 1}()^2) # displacement + close!(dh) + return dh +end + +function create_bc(dh) + dbc = ConstraintHandler(dh) + add!(dbc, Dirichlet(:u, getnodeset(dh.grid, "clamped"), (x, t) -> zero(Vec{2}), [1, 2])) + close!(dbc) + t = 0.0 + update!(dbc, t) + return dbc +end +#md nothing # hide + +# Now, we define a struct to store all necessary material parameters (stiffness tensor of the bulk material +# and the parameters for topology optimization) and add a constructor to the struct to +# initialize it by using the common material parameters Young's modulus and Poisson number. + +struct MaterialParameters{T, S <: SymmetricTensor{4, 2, T}} + C::S + χ_min::T + p::T + β::T + η::T +end +#md nothing # hide + +function MaterialParameters(E, ν, χ_min, p, β, η) + δ(i, j) = i == j ? 1.0 : 0.0 # helper function + + G = E / 2(1 + ν) # =μ + λ = E * ν / (1 - ν^2) # correction for plane stress included + + C = SymmetricTensor{4, 2}((i, j, k, l) -> λ * δ(i, j) * δ(k, l) + G * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k))) + return MaterialParameters(C, χ_min, p, β, η) +end +#md nothing # hide + +# To store the density and the strain required to calculate the driving forces, we create the struct +# `MaterialState`. We add a constructor to initialize the struct. The function `update_material_states!` +# updates the density values once we calculated the new values. + +mutable struct MaterialState{T, S <: AbstractArray{SymmetricTensor{2, 2, T, 3}, 1}} + χ::T # density + ε::S # strain in each quadrature point +end + +function MaterialState(ρ, n_qp) + return MaterialState(ρ, Array{SymmetricTensor{2, 2, Float64, 3}, 1}(undef, n_qp)) +end + +function update_material_states!(χn1, states, dh) + for (element, state) in zip(CellIterator(dh), states) + state.χ = χn1[cellid(element)] + end + return +end +#md nothing # hide + +# Next, we define a function to calculate the driving forces for all elements. +# For this purpose, we iterate through all elements and calculate the average strain in each +# element. Then, we compute the driving force from the formula introduced at the beginning. +# We create a second function to collect the density in each element. + +function compute_driving_forces(states, mp, dh, χn) + pΨ = zeros(length(states)) + for (element, state) in zip(CellIterator(dh), states) + i = cellid(element) + ε = sum(state.ε) / length(state.ε) # average element strain + pΨ[i] = 1 / 2 * mp.p * χn[i]^(mp.p - 1) * (ε ⊡ mp.C ⊡ ε) + end + return pΨ +end + +function compute_densities(states, dh) + χn = zeros(length(states)) + for (element, state) in zip(CellIterator(dh), states) + i = cellid(element) + χn[i] = state.χ + end + return χn +end +#md nothing # hide + +# For the Laplacian we need some neighboorhood information which is constant throughout the analysis so we compute it once and cache it. +# We iterate through each facet of each element, +# obtaining the neighboring element by using the `getneighborhood` function. For boundary facets, +# the function call will return an empty object. In that case we use the dictionary to instead find the opposite +# facet, as discussed in the introduction. + +function cache_neighborhood(dh, topology) + nbgs = Vector{Vector{Int}}(undef, getncells(dh.grid)) + _nfacets = nfacets(dh.grid.cells[1]) + opp = Dict(1 => 3, 2 => 4, 3 => 1, 4 => 2) + + for element in CellIterator(dh) + nbg = zeros(Int, _nfacets) + i = cellid(element) + for j in 1:_nfacets + nbg_cellid = getneighborhood(topology, dh.grid, FacetIndex(i, j)) + if !isempty(nbg_cellid) + nbg[j] = first(nbg_cellid)[1] # assuming only one facet neighbor per cell + else # boundary facet + nbg[j] = first(getneighborhood(topology, dh.grid, FacetIndex(i, opp[j])))[1] + end + end + + nbgs[i] = nbg + end + + return nbgs +end +#md nothing # hide + +# Now we calculate the Laplacian using the previously cached neighboorhood information. +function approximate_laplacian(nbgs, χn, Δh) + ∇²χ = zeros(length(nbgs)) + for i in 1:length(nbgs) + nbg = nbgs[i] + ∇²χ[i] = (χn[nbg[1]] + χn[nbg[2]] + χn[nbg[3]] + χn[nbg[4]] - 4 * χn[i]) / (Δh^2) + end + + return ∇²χ +end +#md nothing # hide + +# For the iterative computation of the solution, a function is needed to update the densities in each element. +# To ensure that the mass is kept constant, we have to calculate the constraint +# parameter $\lambda$, which we do via the bisection method. We repeat the calculation +# until the difference between the average density (calculated from the element-wise trial densities) and the target density nearly vanishes. +# By using the extremal values of $\Delta \chi$ as the starting interval, we guarantee that the method converges eventually. + +function compute_χn1(χn, Δχ, ρ, ηs, χ_min) + n_el = length(χn) + + χ_trial = zeros(n_el) + ρ_trial = 0.0 + + λ_lower = minimum(Δχ) - ηs + λ_upper = maximum(Δχ) + ηs + λ_trial = 0.0 + + while abs(ρ - ρ_trial) > 1.0e-7 + for i in 1:n_el + Δχt = 1 / ηs * (Δχ[i] - λ_trial) + χ_trial[i] = max(χ_min, min(1.0, χn[i] + Δχt)) + end + + ρ_trial = 0.0 + for i in 1:n_el + ρ_trial += χ_trial[i] / n_el + end + + if ρ_trial > ρ + λ_lower = λ_trial + elseif ρ_trial < ρ + λ_upper = λ_trial + end + λ_trial = 1 / 2 * (λ_upper + λ_lower) + end + + return χ_trial +end +#md nothing # hide + +# Lastly, we use the following helper function to compute the average driving force, which is later +# used to normalize the driving forces. This makes the used material parameters and numerical parameters independent +# of the problem. + +function compute_average_driving_force(mp, pΨ, χn) + n = length(pΨ) + w = zeros(n) + + for i in 1:n + w[i] = (χn[i] - mp.χ_min) * (1 - χn[i]) + end + + p_Ω = sum(w .* pΨ) / sum(w) # average driving force + + return p_Ω +end +#md nothing # hide + +# Finally, we put everything together to update the density. The loop ensures the stability of the +# updated solution. + +function update_density(dh, states, mp, ρ, neighboorhoods, Δh) + n_j = Int(ceil(6 * mp.β / (mp.η * Δh^2))) # iterations needed for stability + χn = compute_densities(states, dh) # old density field + χn1 = zeros(length(χn)) + + for j in 1:n_j + ∇²χ = approximate_laplacian(neighboorhoods, χn, Δh) # Laplacian + pΨ = compute_driving_forces(states, mp, dh, χn) # driving forces + p_Ω = compute_average_driving_force(mp, pΨ, χn) # average driving force + + Δχ = pΨ / p_Ω + mp.β * ∇²χ + + χn1 = compute_χn1(χn, Δχ, ρ, mp.η, mp.χ_min) + + if j < n_j + χn[:] = χn1[:] + end + end + + return χn1 +end +#md nothing # hide + +# Now, we move on to the Finite Element part of the program. We use the following function to assemble our linear system. + +function doassemble!(cellvalues::CellValues, facetvalues::FacetValues, K::SparseMatrixCSC, grid::Grid, dh::DofHandler, mp::MaterialParameters, u, states) + r = zeros(ndofs(dh)) + assembler = start_assemble(K, r) + nu = getnbasefunctions(cellvalues) + + re = zeros(nu) # local residual vector + Ke = zeros(nu, nu) # local stiffness matrix + + for (element, state) in zip(CellIterator(dh), states) + fill!(Ke, 0) + fill!(re, 0) + + eldofs = celldofs(element) + ue = u[eldofs] + + elmt!(Ke, re, element, cellvalues, facetvalues, grid, mp, ue, state) + assemble!(assembler, celldofs(element), Ke, re) + end + + return K, r +end +#md nothing # hide + +# The element routine is used to calculate the elementwise stiffness matrix and the residual. In contrast to a purely +# elastomechanic problem, for topology optimization we additionally use our material state to receive the density value of +# the element and to store the strain at each quadrature point. + +function elmt!(Ke, re, element, cellvalues, facetvalues, grid, mp, ue, state) + n_basefuncs = getnbasefunctions(cellvalues) + reinit!(cellvalues, element) + χ = state.χ + + ## We only assemble lower half triangle of the stiffness matrix and then symmetrize it. + @inbounds for q_point in 1:getnquadpoints(cellvalues) + dΩ = getdetJdV(cellvalues, q_point) + state.ε[q_point] = function_symmetric_gradient(cellvalues, q_point, ue) + + for i in 1:n_basefuncs + δεi = shape_symmetric_gradient(cellvalues, q_point, i) + for j in 1:i + δεj = shape_symmetric_gradient(cellvalues, q_point, j) + Ke[i, j] += (χ)^(mp.p) * (δεi ⊡ mp.C ⊡ δεj) * dΩ + end + re[i] += (-δεi ⊡ ((χ)^(mp.p) * mp.C ⊡ state.ε[q_point])) * dΩ + end + end + + symmetrize_lower!(Ke) + + @inbounds for facet in 1:nfacets(getcells(grid, cellid(element))) + if (cellid(element), facet) ∈ getfacetset(grid, "traction") + reinit!(facetvalues, element, facet) + t = Vec((0.0, -1.0)) # force pointing downwards + for q_point in 1:getnquadpoints(facetvalues) + dΓ = getdetJdV(facetvalues, q_point) + for i in 1:n_basefuncs + δu = shape_value(facetvalues, q_point, i) + re[i] += (δu ⋅ t) * dΓ + end + end + end + end + return +end + +function symmetrize_lower!(K) + for i in 1:size(K, 1) + for j in (i + 1):size(K, 1) + K[i, j] = K[j, i] + end + end + return +end +#md nothing # hide + +# We put everything together in the main function. Here the user may choose the radius parameter, which +# is related to the regularization parameter as $\beta = ra^2$, the starting density, the number of elements in vertical direction and finally the +# name of the output. Additionally, the user may choose whether only the final design (default) +# or every iteration step is saved. +# +# First, we compute the material parameters and create the grid, DofHandler, boundary condition and FE values. +# Then we prepare the iterative Newton-Raphson method by pre-allocating all important vectors. Furthermore, +# we create material states for each element and construct the topology of the grid. +# +# During each iteration step, first we solve our FE problem in the Newton-Raphson loop. With the solution of the +# elastomechanic problem, we check for convergence of our topology design. The criteria has to be fulfilled twice in +# a row to avoid oscillations. If no convergence is reached yet, we update our design and prepare the next iteration step. +# Finally, we output the results in paraview and calculate the relative stiffness of the final design, i.e. how much how +# the stiffness increased compared to the starting point. + +function topopt(ra, ρ, n, filename; output = :false) + ## material + mp = MaterialParameters(210.0e3, 0.3, 1.0e-3, 3.0, ra^2, 15.0) + + ## grid, dofhandler, boundary condition + grid = create_grid(n) + dh = create_dofhandler(grid) + Δh = 1 / n # element edge length + dbc = create_bc(dh) + + ## cellvalues + cellvalues, facetvalues = create_values() + + ## Pre-allocate solution vectors, etc. + n_dofs = ndofs(dh) # total number of dofs + u = zeros(n_dofs) # solution vector + un = zeros(n_dofs) # previous solution vector + + Δu = zeros(n_dofs) # previous displacement correction + ΔΔu = zeros(n_dofs) # new displacement correction + + ## create material states + states = [MaterialState(ρ, getnquadpoints(cellvalues)) for _ in 1:getncells(dh.grid)] + + χ = zeros(getncells(dh.grid)) + + r = zeros(n_dofs) # residual + K = allocate_matrix(dh) # stiffness matrix + + i_max = 300 ## maximum number of iteration steps + tol = 1.0e-4 + compliance = 0.0 + compliance_0 = 0.0 + compliance_n = 0.0 + conv = :false + + topology = ExclusiveTopology(grid) + neighboorhoods = cache_neighborhood(dh, topology) + + ## Newton-Raphson loop + NEWTON_TOL = 1.0e-8 + print("\n Starting Newton iterations\n") + + for it in 1:i_max + apply_zero!(u, dbc) + newton_itr = -1 + + while true + newton_itr += 1 + + if newton_itr > 10 + error("Reached maximum Newton iterations, aborting") + break + end + + ## current guess + u .= un .+ Δu + K, r = doassemble!(cellvalues, facetvalues, K, grid, dh, mp, u, states) + norm_r = norm(r[Ferrite.free_dofs(dbc)]) + + if (norm_r) < NEWTON_TOL + break + end + + apply_zero!(K, r, dbc) + ΔΔu = Symmetric(K) \ r + + apply_zero!(ΔΔu, dbc) + Δu .+= ΔΔu + end # of loop while NR-Iteration + + ## calculate compliance + compliance = 1 / 2 * u' * K * u + + if it == 1 + compliance_0 = compliance + end + + ## check convergence criterium (twice!) + if abs(compliance - compliance_n) / compliance < tol + if conv + println("Converged at iteration number: ", it) + break + else + conv = :true + end + else + conv = :false + end + + ## update density + χ = update_density(dh, states, mp, ρ, neighboorhoods, Δh) + + ## update old displacement, density and compliance + un .= u + Δu .= 0.0 + update_material_states!(χ, states, dh) + compliance_n = compliance + + ## output during calculation + if output + i = @sprintf("%3.3i", it) + filename_it = string(filename, "_", i) + + VTKGridFile(filename_it, grid) do vtk + write_cell_data(vtk, χ, "density") + end + end + end + + ## export converged results + if !output + VTKGridFile(filename, grid) do vtk + write_cell_data(vtk, χ, "density") + end + end + @printf "Rel. stiffness: %.4f \n" compliance^(-1) / compliance_0^(-1) + + return +end +#md nothing # hide + +# Lastly, we call our main function and compare the results. To create the +# complete output with all iteration steps, it is possible to set the output +# parameter to `true`. + +# grid, χ =topopt(0.02, 0.5, 60, "small_radius"; output=:false); +@time topopt(0.03, 0.5, 60, "large_radius"; output = :false); +#topopt(0.02, 0.5, 60, "topopt_animation"; output=:true); # can be used to create animations + +# We observe, that the stiffness for the lower value of $ra$ is higher, +# but also requires more iterations until convergence and finer structures to be manufactured, as can be seen in Figure 2: +# +# ![](bending.png) +# +# *Figure 2*: Optimization results of the bending beam for smaller (left) and larger (right) value of the regularization parameter $\beta$. +# +# To prove mesh independence, the user could vary the mesh resolution and compare the results. + +#md # ## References +#md # ```@bibliography +#md # Pages = ["topology_optimization.md"] +#md # Canonical = false +#md # ``` + +#md # ## [Plain program](@id topology_optimization-plain-program) +#md # +#md # Here follows a version of the program without any comments. +#md # The file is also available here: [`topology_optimization.jl`](topology_optimization.jl). +#md # +#md # ```julia +#md # @__CODE__ +#md # ``` diff --git a/previews/PR798/literate-howto/coloring.png b/previews/PR798/literate-howto/coloring.png new file mode 100644 index 0000000000..534528bba2 Binary files /dev/null and b/previews/PR798/literate-howto/coloring.png differ diff --git a/previews/PR798/literate-howto/heat_square_fluxes.png b/previews/PR798/literate-howto/heat_square_fluxes.png new file mode 100644 index 0000000000..5bc6cb5b6c Binary files /dev/null and b/previews/PR798/literate-howto/heat_square_fluxes.png differ diff --git a/previews/PR798/literate-howto/heat_square_pointevaluation.png b/previews/PR798/literate-howto/heat_square_pointevaluation.png new file mode 100644 index 0000000000..057c9bf551 Binary files /dev/null and b/previews/PR798/literate-howto/heat_square_pointevaluation.png differ diff --git a/previews/PR798/literate-howto/postprocessing.jl b/previews/PR798/literate-howto/postprocessing.jl new file mode 100644 index 0000000000..2eeb09b3c3 --- /dev/null +++ b/previews/PR798/literate-howto/postprocessing.jl @@ -0,0 +1,141 @@ +# # [Post processing and visualization](@id howto-postprocessing) +# +# ![](heat_square_fluxes.png) +# +# *Figure 1*: Heat flux computed from the solution to the heat equation on +# the unit square, see previous example: [Heat equation](@ref tutorial-heat-equation). +# +#- +#md # !!! tip +#md # This example is also available as a Jupyter notebook: +#md # [`postprocessing.ipynb`](@__NBVIEWER_ROOT_URL__/howto/postprocessing.ipynb). +#- +# +# ## Introduction +# +# After running a simulation, we usually want to visualize the results in different ways. +# The `L2Projector` and the `PointEvalHandler` build a pipeline for doing so. With the `L2Projector`, +# integration point quantities can be projected to the nodes. The `PointEvalHandler` enables evaluation of +# the finite element approximated function in any coordinate in the domain. Thus with the combination of both functionalities, +# both nodal quantities and integration point quantities can be evaluated in any coordinate, allowing for example +# cut-planes through 3D structures or cut-lines through 2D-structures. +# +# This example continues from the Heat equation example, where the temperature field was +# determined on a square domain. In this example, we first compute the heat flux in each +# integration point (based on the solved temperature field) and then we do an L2-projection +# of the fluxes to the nodes of the mesh. By doing this, we can more easily visualize +# integration points quantities. Finally, we visualize the temperature field and the heat fluxes along a cut-line. +# +# The L2-projection is defined as follows: Find projection ``q(\boldsymbol{x}) \in U_h(\Omega)`` such that +# ```math +# \int v q \ \mathrm{d}\Omega = \int v d \ \mathrm{d}\Omega \quad \forall v \in U_h(\Omega), +# ``` +# where ``d`` is the quadrature data to project. Since the flux is a vector the projection function +# will be solved with multiple right hand sides, e.g. with ``d = q_x`` and ``d = q_y`` for this 2D problem. +# In this example, we use standard Lagrange interpolations, and the finite element space ``U_h`` is then +# a subset of the ``H^1`` space (continuous functions). +# +# Ferrite has functionality for doing much of this automatically, as displayed in the code below. +# In particular [`L2Projector`](@ref) for assembling the left hand side, and +# [`project`](@ref) for assembling the right hand sides and solving for the projection. + +# ## Implementation +# +# Start by simply running the Heat equation example to solve the problem +include("../tutorials/heat_equation.jl"); + + +# Next we define a function that computes the heat flux for each integration point in the domain. +# Fourier's law is adopted, where the conductivity tensor is assumed to be isotropic with unit +# conductivity ``\lambda = 1 ⇒ q = - \nabla u``, where ``u`` is the temperature. +function compute_heat_fluxes(cellvalues::CellValues, dh::DofHandler, a::AbstractVector{T}) where {T} + + n = getnbasefunctions(cellvalues) + cell_dofs = zeros(Int, n) + nqp = getnquadpoints(cellvalues) + + ## Allocate storage for the fluxes to store + q = [Vec{2, T}[] for _ in 1:getncells(dh.grid)] + + for (cell_num, cell) in enumerate(CellIterator(dh)) + q_cell = q[cell_num] + celldofs!(cell_dofs, dh, cell_num) + aᵉ = a[cell_dofs] + reinit!(cellvalues, cell) + + for q_point in 1:nqp + q_qp = - function_gradient(cellvalues, q_point, aᵉ) + push!(q_cell, q_qp) + end + end + return q +end +#md nothing # hide + +# Now call the function to get all the fluxes. +q_gp = compute_heat_fluxes(cellvalues, dh, u); + +# Next, create an [`L2Projector`](@ref) using the same interpolation as was used to approximate the +# temperature field. On instantiation, the projector assembles the coefficient matrix `M` and +# computes the Cholesky factorization of it. By doing so, the projector can be reused without +# having to invert `M` every time. +projector = L2Projector(ip, grid); + +# Project the integration point values to the nodal values +q_projected = project(projector, q_gp, qr); + + +# ## Exporting to VTK +# To visualize the heat flux, we export the projected field `q_projected` +# to a VTK-file, which can be viewed in e.g. [ParaView](https://www.paraview.org/). +# The result is also visualized in *Figure 1*. +VTKGridFile("heat_equation_flux", grid) do vtk + write_projection(vtk, projector, q_projected, "q") +end; + +# ## Point evaluation +# ![](heat_square_pointevaluation.png) +# +# *Figure 2*: Visualization of the cut line where we want to compute +# the temperature and heat flux. + +# Consider a cut-line through the domain like the black line in *Figure 2* above. +# We will evaluate the temperature and the heat flux distribution along a horizontal line. +points = [Vec((x, 0.75)) for x in range(-1.0, 1.0, length = 101)]; + +# First, we need to generate a `PointEvalHandler`. This will find and store the cells +# containing the input points. +ph = PointEvalHandler(grid, points); + +# After the L2-Projection, the heat fluxes `q_projected` are stored in the DoF-ordering +# determined by the projector's internal DoFHandler, so to evaluate the flux `q` at our points +# we give the `PointEvalHandler`, the `L2Projector` and the values `q_projected`. +q_points = evaluate_at_points(ph, projector, q_projected); + +# We can also extract the field values, here the temperature, right away from the result +# vector of the simulation, that is stored in `u`. These values are stored in the order of +# our initial DofHandler so the input is not the `PointEvalHandler`, the original `DofHandler`, +# the dof-vector `u`, and (optionally for single-field problems) the name of the field. +# From the `L2Projection`, the values are stored in the order of the degrees of freedom. +u_points = evaluate_at_points(ph, dh, u, :u); + +# Now, we can plot the temperature and flux values with the help of any plotting library, e.g. Plots.jl. +# To do this, we need to import the package: +import Plots + +# Firstly, we are going to plot the temperature values along the given line. +Plots.plot(getindex.(points, 1), u_points, xlabel = "x (coordinate)", ylabel = "u (temperature)", label = nothing) +# *Figure 3*: Temperature along the cut line from *Figure 2*. + +# Secondly, the horizontal heat flux (i.e. the first component of the heat flux vector) is plotted. +Plots.plot(getindex.(points, 1), getindex.(q_points, 1), xlabel = "x (coordinate)", ylabel = "q_x (flux in x-direction)", label = nothing) +# *Figure 4*: ``x``-component of the flux along the cut line from *Figure 2*. + +#md # ## [Plain program](@id postprocessing-plain-program) +#md # +#md # Here follows a version of the program without any comments. +#md # The file is also available here: [`postprocessing.jl`](postprocessing.jl). +#md # +#md # ```julia +#md # @__CODE__ +#md # ``` diff --git a/previews/PR798/literate-howto/threaded_assembly.jl b/previews/PR798/literate-howto/threaded_assembly.jl new file mode 100644 index 0000000000..47cbd32297 --- /dev/null +++ b/previews/PR798/literate-howto/threaded_assembly.jl @@ -0,0 +1,324 @@ +# ```@meta +# Draft = false +# ``` +# # [Multi-threaded assembly](@id tutorial-threaded-assembly) +# +#- +#md # !!! tip +#md # This example is also available as a Jupyter notebook: +#md # [`threaded_assembly.ipynb`](@__NBVIEWER_ROOT_URL__/howto/threaded_assembly.ipynb). +#- + +# ## Introduction +# +# In this howto we will explore how to use task based multithreading (shared memory +# parallelism) to speed up the analysis. Some parts of a finite element simulation are +# trivially parallelizable such as the computation of the local element contributions since +# each element can be processed independently. However, two things need to be considered in +# order to parallelize safely: +# +# - **Modification of shared data**: Although the contributions from all the elements can +# be computed independently, eventually they need to be assembled into the global +# matrix and vector. Letting each task assemble their own contribution would lead to +# race conditions since elements share degrees of freedom with each other. There are +# various ways to remedy this, for example: +# - **Locking**: By using a lock around the call to `assemble!` we can ensure that only +# one task assembles at a time. This is simple to implement but can lead to lock +# contention and thus poor performance. Another drawback is that the results will not +# be deterministic since floating point operations are neither associative nor +# commutative. +# - **Assembler task**: By using a designated task for the assembling we (obviously) +# ensure that only a single task assembles. The worker tasks (the tasks computing the +# element contributions) would then hand off their results to the assemly task. This +# can be a useful approach if computing the element contributions is much slower than +# the assembly -- otherwise the assembler task can't keep up with the worker tasks. +# There might also be some extra overhead because of task switching in the scheduler. +# The problem with non-deterministic results still remains. +# - **Grid coloring**: By "coloring" the grid such that, within each color, no two +# elements share degrees of freedom, we can safely assemble each color in parallel. +# Even if concurrently running tasks will write to the global matrix and vector they +# will not write to the same memory locations. Note also that this procedure gives +# predictable results because for a memory location which, for example, a "red", +# a "blue", and a "green" element will contribute to we will always add the red first, +# then the blue, and finally the green. +# - **Scratch data**: In order to speed up the computation of the element contributions we +# typically pre-allocate some data structures that can be reused for every element. Such +# scratch data include, for example, the local matrix and vector, and the CellValues. +# Each task need their own copy of the scratch data since they will be modified for each +# element. + +# ## Grid coloring +# +# Ferrite include functionality to color the grid with the [`create_coloring`](@ref) +# function. Here we create a simple 2D grid, color it, and export the colors to a VTK file +# to visualize the result (see *Figure 1*.). Note that no cells with the same color has any +# shared nodes (dofs). This means that it is safe to assemble in parallel as long as we only +# assemble one color at a time. +# +# There are two coloring algorithms implemented: the "workstream" algorithm (from Turcksin +# et al. [Turcksin2016](@cite)) and a "greedy" algorithm. For this structured grid the +# greedy algorithm uses fewer colors, but both algorithms result in colors that contain +# roughly the same number of elements. The workstream algorithm is the default one since it +# in general results in more balanced colors. For unstructured grids the greedy algorithm +# can result in colors with very few elements, for example. + +using Ferrite, SparseArrays + +function create_example_2d_grid() + grid = generate_grid(Quadrilateral, (10, 10), Vec{2}((0.0, 0.0)), Vec{2}((10.0, 10.0))) + colors_workstream = create_coloring(grid; alg = ColoringAlgorithm.WorkStream) + colors_greedy = create_coloring(grid; alg = ColoringAlgorithm.Greedy) + VTKGridFile("colored", grid) do vtk + Ferrite.write_cell_colors(vtk, grid, colors_workstream, "workstream-coloring") + Ferrite.write_cell_colors(vtk, grid, colors_greedy, "greedy-coloring") + end + return +end + +create_example_2d_grid() + +# ![](coloring.png) +# +# *Figure 1*: Element coloring using the "workstream"-algorithm (left) and the "greedy"- +# algorithm (right). + +# ## Multithreaded assembly of a cantilever beam in 3D +# +# We will now look at an example where we assemble the stiffness matrix and right hand side +# using multiple threads. The problem setup is a cantilever beam in 3D with a linear elastic +# material behavior. For this exercise we only focus on the multithreading and are not +# bothered with boundary conditions. For more details refer to the [tutorial on linear +# elasticity](../tutorials/linear_elasticity.md). + +# ### Setup +# +# We define the element routine, material stiffness, grid and DofHandler just like in the +# [tutorial on linear elasticity](../tutorials/linear_elasticity.md) without discussing it +# further here. + +## Element routine +function assemble_cell!(Ke::Matrix, fe::Vector, cellvalues::CellValues, C::SymmetricTensor, b::Vec) + fill!(Ke, 0) + fill!(fe, 0) + for q_point in 1:getnquadpoints(cellvalues) + dΩ = getdetJdV(cellvalues, q_point) + for i in 1:getnbasefunctions(cellvalues) + δui = shape_value(cellvalues, q_point, i) + fe[i] += (δui ⋅ b) * dΩ + ∇δui = shape_symmetric_gradient(cellvalues, q_point, i) + for j in 1:getnbasefunctions(cellvalues) + ∇uj = shape_symmetric_gradient(cellvalues, q_point, j) + Ke[i, j] += (∇δui ⊡ C ⊡ ∇uj) * dΩ + end + end + end + return Ke, fe +end + +## Material stiffness +function create_material_stiffness() + E = 200.0e9 + ν = 0.3 + λ = E * ν / ((1 + ν) * (1 - 2ν)) + μ = E / (2(1 + ν)) + δ(i, j) = i == j ? 1.0 : 0.0 + C = SymmetricTensor{4, 3}() do i, j, k, l + return λ * δ(i, j) * δ(k, l) + μ * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k)) + end + return C +end + +## Grid and grid coloring +function create_cantilever_grid(n::Int) + xmin = Vec{3}((0.0, 0.0, 0.0)) + xmax = Vec{3}((10.0, 1.0, 1.0)) + grid = generate_grid(Hexahedron, (10 * n, n, n), xmin, xmax) + colors = create_coloring(grid) + return grid, colors +end + +## DofHandler with displacement field u +function create_dofhandler(grid::Grid, interpolation::VectorInterpolation) + dh = DofHandler(grid) + add!(dh, :u, interpolation) + close!(dh) + return dh +end +nothing # hide + +# ### Task local scratch data +# +# We group everything that needs to be duplicated for each task in the struct +# `ScratchData`: +# - `cell_cache::CellCache`: contain buffers for coordinates and (global) dofs which will +# be `reinit!`ed for each cell. +# - `cellvalues::CellValues`: the cell values which will be `reinit!`ed for each cell using +# the `cell_cache` +# - `Ke::Matrix`: the local matrix +# - `fe::Vector`: the local vector +# - `assembler`: the assembler (which needs to be duplicated because it contains buffers +# that are modified during the call to `assemble!`) +struct ScratchData{CC, CV, T, A} + cell_cache::CC + cellvalues::CV + Ke::Matrix{T} + fe::Vector{T} + assembler::A +end + +# This constructor will be called within each task to create a independent `ScratchData` +# object. For `cell_cache`, `Ke`, and `fe` we simply call the constructors to allocate +# independent objects. For `cellvalues` we use `copy` which Ferrite defines for this +# purpose. Finally, for the assembler we call `start_assemble` to create a new assembler but +# note that we set `fillzero = false` because we don't want to risk that a task that starts +# a bit later will zero out data that another task have already assembled. +function ScratchData(dh::DofHandler, K::SparseMatrixCSC, f::Vector, cellvalues::CellValues) + cell_cache = CellCache(dh) + n = ndofs_per_cell(dh) + Ke = zeros(n, n) + fe = zeros(n) + asm = start_assemble(K, f; fillzero = false) + return ScratchData(cell_cache, copy(cellvalues), Ke, fe, asm) +end +nothing # hide + +# ### Global assembly routine + +# Finally we define the global assemble routine, which is where the parallelization happens. +# The main difference from all previous `assemble_global!` functions is that we now have an +# outer loop over the colors, and then the inner loop over the cells in each color, which +# can be parallelized. +# +# For the scheduling of parallel tasks we use the +# [OhMyThreads.jl](https://github.com/JuliaFolds2/OhMyThreads.jl) package. OhMyThreads +# provides a macro based and a functional API. Here we use the macro based API because it is +# slightly more convenient when using task local values since they can be defined with the +# `@local` macro. +# +# !!! note "Schedulers and load balancing" +# OhMyThreads provides a number of different +# [schedulers](https://juliafolds2.github.io/OhMyThreads.jl/stable/refs/api/#Schedulers). +# In this example we use the `DynamicScheduler` (which is the default one). The +# `DynamicScheduler` will spawn `ntasks` tasks where each task will process a chunk of +# (roughly) equal number of cells (i.e. `length(color) ÷ ntasks`). This should be a good +# choice for this example because we expect all cells to take the same time to process +# and we don't need any load balancing. +# +# For a different problem setup where some cells might take longer to process (perhaps +# they experience plastic deformation and we need to solve a local problem) we might +# benefit from load balancing. The `DynamicScheduler` can be used also for load +# balancing by specifiying `nchunks` or `chunksize`. However, the `DynamicScheduler` +# will always spawn `nchunks` tasks which can become costly since we are allocating +# scratch data for every task. To limit the number of tasks, while allowing for more +# than `ntasks` chunks, we can use the `GreedyScheduler` *with chunking*. For example, +# `scheduler = OhMyThreads.GreedyScheduler(; ntasks = ntasks, nchunks = 10 * ntasks)` +# will split the work into `10 * ntasks` chunks and spawn `ntasks` tasks to process +# them. Refer to the [OhMyThreads +# documentation](https://juliafolds2.github.io/OhMyThreads.jl/stable/) for details. + +using OhMyThreads, TaskLocalValues + +function assemble_global!( + K::SparseMatrixCSC, f::Vector, dh::DofHandler, colors, + cellvalues_template::CellValues; ntasks = Threads.nthreads() + ) + ## Zero-out existing data in K and f + _ = start_assemble(K, f) + ## Body force and material stiffness + b = Vec{3}((0.0, 0.0, -1.0)) + C = create_material_stiffness() + ## Loop over the colors + for color in colors + ## Dynamic scheduler spawning `ntasks` tasks where each task will process a chunk of + ## (roughly) equal number of cells (`length(color) ÷ ntasks`). + scheduler = OhMyThreads.DynamicScheduler(; ntasks) + ## Parallelize the loop over the cells in this color + OhMyThreads.@tasks for cellidx in color + ## Tell the @tasks loop to use the scheduler defined above + @set scheduler = scheduler + ## Obtain a task local scratch and unpack it + @local scratch = ScratchData(dh, K, f, cellvalues_template) + (; cell_cache, cellvalues, Ke, fe, assembler) = scratch + ## Reinitialize the cell cache and then the cellvalues + reinit!(cell_cache, cellidx) + reinit!(cellvalues, cell_cache) + ## Compute the local contribution of the cell + assemble_cell!(Ke, fe, cellvalues, C, b) + ## Assemble local contribution + assemble!(assembler, celldofs(cell_cache), Ke, fe) + end + end + return K, f +end +nothing # hide + +# !!! details "OhMyThreads functional API: OhMyThreads.tforeach" +# The `OhMyThreads.@tasks` block above corresponds to a call to `OhMyThreads.tforeach`. +# Using the functional API directly would look like below. The main difference is that +# we need to manually create a `TaskLocalValue` for the scratch data. +# ```julia +# # using TaskLocalValues +# scratches = TaskLocalValue() do +# ScratchData(dh, K, f, cellvalues) +# end +# OhMyThreads.tforeach(color; scheduler) do cellidx +# # Obtain a task local scratch and unpack it +# scratch = scratches[] +# (; cell_cache, cellvalues, Ke, fe, assembler) = scratch +# # Reinitialize the cell cache and then the cellvalues +# reinit!(cell_cache, cellidx) +# reinit!(cellvalues, cell_cache) +# # Compute the local contribution of the cell +# assemble_cell!(Ke, fe, cellvalues, C, b) +# # Assemble local contribution +# assemble!(assembler, celldofs(cell_cache), Ke, fe) +# end +# ``` + +# We define the main function to setup everything and then time the call to +# `assemble_global!`. + +function main(; n = 20, ntasks = Threads.nthreads()) + ## Interpolation, quadrature and cellvalues + interpolation = Lagrange{RefHexahedron, 1}()^3 + quadrature = QuadratureRule{RefHexahedron}(2) + cellvalues = CellValues(quadrature, interpolation) + ## Grid, colors and DofHandler + grid, colors = create_cantilever_grid(n) + dh = create_dofhandler(grid, interpolation) + ## Global matrix and vector + K = allocate_matrix(dh) + f = zeros(ndofs(dh)) + ## Compile it + assemble_global!(K, f, dh, colors, cellvalues; ntasks = ntasks) + ## Time it + @time assemble_global!(K, f, dh, colors, cellvalues; ntasks = ntasks) + return norm(K.nzval), norm(f) #src + return +end +nothing # hide + +# On a machine with 4 cores, starting julia with `--threads=auto`, we obtain the following +# timings: +# ```julia +# main(; ntasks = 1) # 1.970784 seconds (902 allocations: 816.172 KiB) +# main(; ntasks = 2) # 1.025065 seconds (1.64 k allocations: 1.564 MiB) +# main(; ntasks = 3) # 0.700423 seconds (2.38 k allocations: 2.332 MiB) +# main(; ntasks = 4) # 0.548356 seconds (3.12 k allocations: 3.099 MiB) +# ``` + +using Test #src +nK1, nf1 = main(; n = 5, ntasks = 1) #src +nK2, nf2 = main(; n = 5, ntasks = 2) #src +nK4, nf4 = main(; n = 5, ntasks = 4) #src +@test nK1 == nK2 == nK4 #src +@test nf1 == nf2 == nf4 #src + +#md # ## [Plain program](@id threaded_assembly-plain-program) +#md # +#md # Here follows a version of the program without any comments. +#md # The file is also available here: [`threaded_assembly.jl`](threaded_assembly.jl). +#md # +#md # ```julia +#md # @__CODE__ +#md # ``` diff --git a/previews/PR798/literate-tutorials/computational_homogenization.jl b/previews/PR798/literate-tutorials/computational_homogenization.jl new file mode 100644 index 0000000000..ee7b6486e4 --- /dev/null +++ b/previews/PR798/literate-tutorials/computational_homogenization.jl @@ -0,0 +1,578 @@ +# # [Computational homogenization](@id tutorial-computational-homogenization) +# +# ![](rve_homogenization.png) +# +# *Figure 1*: von Mises stress in an RVE with 5 stiff inclusions embedded in a softer matrix +# material that is loaded in shear. The problem is solved by using homogeneous Dirichlet +# boundary conditions (left) and (strong) periodic boundary conditions (right). +# +#- +#md # !!! tip +#md # This example is also available as a Jupyter notebook: +#md # [`computational_homogenization.ipynb`](@__NBVIEWER_ROOT_URL__/tutorials/computational_homogenization.ipynb). +#- +# +# ## Introduction +# +# In this example we will solve the Representative Volume Element (RVE) problem for +# computational homogenization of linear elasticity and compute the effective/homogenized +# stiffness of an RVE with 5 stiff circular inclusions embedded in a softer matrix material +# (see Figure 1). +# +# It is possible to obtain upper and lower bounds on the stiffness analytically, see for +# example [Rule of mixtures](https://en.wikipedia.org/wiki/Rule_of_mixtures). An upper +# bound is obtained from the Voigt model, where the *strain* is assumed to be the same in +# the two constituents, +# +# ```math +# \mathsf{E}_\mathrm{Voigt} = v_\mathrm{m} \mathsf{E}_\mathrm{m} + +# (1 - v_\mathrm{m}) \mathsf{E}_\mathrm{i} +# ``` +# +# where ``v_\mathrm{m}`` is the volume fraction of the matrix material, and where +# ``\mathsf{E}_\mathrm{m}`` and ``\mathsf{E}_\mathrm{i}`` are the individual stiffness for +# the matrix material and the inclusions, respectively. The lower bound is obtained from +# the Reuss model, where the *stress* is assumed to be the same in the two constituents, +# +# ```math +# \mathsf{E}_\mathrm{Reuss} = \left(v_\mathrm{m} \mathsf{E}_\mathrm{m}^{-1} + +# (1 - v_\mathrm{m}) \mathsf{E}_\mathrm{i}^{-1} \right)^{-1}. +# ``` +# +# However, neither of these assumptions are, in general, very close to the "truth" which is +# why it is of interest to computationally find the homogenized properties for a given RVE. +# +# The canonical version of the RVE problem can be formulated as follows: +# For given homogenized field ``\bar{\boldsymbol{u}}``, ``\bar{\boldsymbol{\varepsilon}} = +# \boldsymbol{\varepsilon}[\bar{\boldsymbol{u}}]``, find ``\boldsymbol{u} \in +# \mathbb{U}_\Box``, ``\boldsymbol{t} \in \mathbb{T}_\Box`` such that +# +# ```math +# \frac{1}{|\Omega_\Box|} \int_{\Omega_\Box}\boldsymbol{\varepsilon}[\delta\boldsymbol{u}] +# : \mathsf{E} : \boldsymbol{\varepsilon}[\boldsymbol{u}]\ \mathrm{d}\Omega +# - \frac{1}{|\Omega_\Box|} \int_{\Gamma_\Box}\delta \boldsymbol{u} \cdot +# \boldsymbol{t}\ \mathrm{d}\Gamma = 0 \quad +# \forall \delta \boldsymbol{u} \in \mathbb{U}_\Box,\quad (1\mathrm{a})\\ +# - \frac{1}{|\Omega_\Box|} \int_{\Gamma_\Box}\delta \boldsymbol{t} \cdot +# \boldsymbol{u}\ \mathrm{d}\Gamma = - \bar{\boldsymbol{\varepsilon}} : +# \left[ \frac{1}{|\Omega_\Box|} \int_{\Gamma_\Box}\delta \boldsymbol{t} \otimes +# [\boldsymbol{x} - \bar{\boldsymbol{x}}]\ \mathrm{d}\Gamma \right] +# \quad \forall \delta \boldsymbol{t} \in \mathbb{T}_\Box, \quad (1\mathrm{b}) +# ``` +# +# where ``\boldsymbol{u} = \bar{\boldsymbol{\varepsilon}} \cdot [\boldsymbol{x} - +# \bar{\boldsymbol{x}}] + \boldsymbol{u}^\mu``, where ``\Omega_\Box`` and ``|\Omega_\Box|`` +# are the domain and volume of the RVE, where ``\Gamma_\Box`` is the boundary, and where +# ``\mathbb{U}_\Box``, ``\mathbb{T}_\Box`` are set of "sufficiently regular" functions +# defined on the RVE. +# +# This system is not solvable without introducing extra restrictions on ``\mathbb{U}_\Box``, +# ``\mathbb{T}_\Box``. In this example we will consider the common cases of Dirichlet +# boundary conditions and (strong) periodic boundary conditions. +# +# **Dirichlet boundary conditions** +# +# We can introduce the more restrictive sets of ``\mathbb{U}_\Box``: +# +# ```math +# \begin{align*} +# \mathbb{U}_\Box^\mathrm{D} &:= \left\{\boldsymbol{u} \in \mathbb{U}_\Box|\ \boldsymbol{u} +# = \bar{\boldsymbol{\varepsilon}} \cdot [\boldsymbol{x} - \bar{\boldsymbol{x}}] +# \ \mathrm{on}\ \Gamma_\Box\right\},\\ +# \mathbb{U}_\Box^{\mathrm{D},0} &:= \left\{\boldsymbol{u} \in \mathbb{U}_\Box|\ \boldsymbol{u} +# = \boldsymbol{0}\ \mathrm{on}\ \Gamma_\Box\right\}, +# \end{align*} +# ``` +# +# and use these as trial and test sets to obtain a solvable RVE problem pertaining to +# Dirichlet boundary conditions. Eq. ``(1\mathrm{b})`` is trivially fulfilled, the second +# term of Eq. ``(1\mathrm{a})`` vanishes, and we are left with the following problem: +# Find ``\boldsymbol{u} \in \mathbb{U}_\Box^\mathrm{D}`` that solve +# +# ```math +# \frac{1}{|\Omega_\Box|} \int_{\Omega_\Box}\boldsymbol{\varepsilon}[\delta\boldsymbol{u}] +# : \mathsf{E} : \boldsymbol{\varepsilon}[\boldsymbol{u}]\ \mathrm{d}\Omega = 0 +# \quad \forall \delta \boldsymbol{u} \in \mathbb{U}_\Box^{\mathrm{D},0}. +# ``` +# +# Note that, since ``\boldsymbol{u} = \bar{\boldsymbol{\varepsilon}} \cdot [\boldsymbol{x} - +# \bar{\boldsymbol{x}}] + \boldsymbol{u}^\mu``, this problem is equivalent to solving for +# ``\boldsymbol{u}^\mu \in \mathbb{U}_\Box^{\mathrm{D},0}``, which is what we will do in +# the implementation. +# +# **Periodic boundary conditions** +# +# The RVE problem pertaining to periodic boundary conditions is obtained by restricting +# ``\boldsymbol{u}^\mu`` to be periodic, and ``\boldsymbol{t}`` anti-periodic across the +# RVE. Similarly as for Dirichlet boundary conditions, Eq. ``(1\mathrm{b})`` is directly +# fulfilled, and the second term in Eq. ``(1\mathrm{a})`` vanishes, with these restrictions, +# and we are left with the following problem: +# Find ``\boldsymbol{u}^\mu \in \mathbb{U}_\Box^{\mathrm{P},0}`` such that +# +# ```math +# \frac{1}{|\Omega_\Box|} \int_{\Omega_\Box}\boldsymbol{\varepsilon}[\delta\boldsymbol{u}] +# : \mathsf{E} : (\bar{\boldsymbol{\varepsilon}} + \boldsymbol{\varepsilon} +# [\boldsymbol{u}^\mu])\ \mathrm{d}\Omega = 0 +# \quad \forall \delta \boldsymbol{u} \in \mathbb{U}_\Box^{\mathrm{P},0}, +# ``` +# +# where +# +# ```math +# \mathbb{U}_\Box^{\mathrm{P},0} := \left\{\boldsymbol{u} \in \mathbb{U}_\Box| +# \ \llbracket \boldsymbol{u} \rrbracket_\Box = \boldsymbol{0} +# \ \mathrm{on}\ \Gamma_\Box^+\right\} +# ``` +# +# where ``\llbracket \bullet \rrbracket_\Box = \bullet(\boldsymbol{x}^+) - +# \bullet(\boldsymbol{x}^-)`` defines the "jump" over the RVE, i.e. the difference between +# the value on the image part ``\Gamma_\Box^+`` (coordinate ``\boldsymbol{x}^+``) and the +# mirror part ``\Gamma_\Box^-`` (coordinate ``\boldsymbol{x}^-``) of the boundary. +# To make sure this restriction holds in a strong sense we need a periodic mesh. +# +# Note that it would be possible to solve for the total ``\boldsymbol{u}`` directly by +# instead enforcing the jump to be equal to the jump in the macroscopic part, +# ``\boldsymbol{u}^\mathrm{M}``, i.e. +# +# ```math +# \llbracket \boldsymbol{u} \rrbracket_\Box = +# \llbracket \boldsymbol{u}^\mathrm{M} \rrbracket_\Box = +# \llbracket \bar{\boldsymbol{\varepsilon}} \cdot [\boldsymbol{x} - \bar{\boldsymbol{x}}] +# \rrbracket_\Box = +# \bar{\boldsymbol{\varepsilon}} \cdot [\boldsymbol{x}^+ - \boldsymbol{x}^-]. +# ``` +# +# **Homogenization of effective properties** +# +# In general it is necessary to compute the homogenized stress and the stiffness on the fly, +# but since we in this example consider linear elasticity it is possible to compute the +# effective properties once and for all for a given RVE configuration. We do this by +# computing sensitivity fields for every independent strain component (6 in 3D, 3 in 2D). +# Thus, for a 2D problem, as in the implementation below, we compute sensitivities +# ``\hat{\boldsymbol{u}}_{11}``, ``\hat{\boldsymbol{u}}_{22}``, and +# ``\hat{\boldsymbol{u}}_{12} = \hat{\boldsymbol{u}}_{21}`` by using +# +# ```math +# \bar{\boldsymbol{\varepsilon}} = \begin{pmatrix}1 & 0\\ 0 & 0\end{pmatrix}, \quad +# \bar{\boldsymbol{\varepsilon}} = \begin{pmatrix}0 & 0\\ 0 & 1\end{pmatrix}, \quad +# \bar{\boldsymbol{\varepsilon}} = \begin{pmatrix}0 & 0.5\\ 0.5 & 0\end{pmatrix} +# ``` +# +# as the input to the RVE problem. When the sensitivies are solved we can compute the +# entries of the homogenized stiffness as follows +# +# ```math +# \mathsf{E}_{ijkl} = \frac{\partial\ \bar{\sigma}_{ij}}{\partial\ \bar{\varepsilon}_{kl}} +# = \bar{\sigma}_{ij}(\hat{\boldsymbol{u}}_{kl}), +# ``` +# +# where the homogenized stress, ``\bar{\boldsymbol{\sigma}}(\boldsymbol{u})``, is computed +# as the volume average of the stress in the RVE, i.e. +# +# ```math +# \bar{\boldsymbol{\sigma}}(\boldsymbol{u}) := +# \frac{1}{|\Omega_\Box|} \int_{\Omega_\Box} \boldsymbol{\sigma}\ \mathrm{d}\Omega = +# \frac{1}{|\Omega_\Box|} \int_{\Omega_\Box} +# \mathsf{E} : \boldsymbol{\varepsilon}[\boldsymbol{u}]\ \mathrm{d}\Omega. +# ``` + + +# ## Commented program +# +# Now we will see how this can be implemented in Ferrite. What follows is a program +# with comments in between which describe the different steps. +#md # You can also find the same program without comments at the end of the page, +#md # see [Plain program](@ref homogenization-plain-program). + +using Ferrite, SparseArrays, LinearAlgebra +using Test #src + +# We first load the mesh file [`periodic-rve.msh`](periodic-rve.msh) +# ([`periodic-rve-coarse.msh`](periodic-rve-coarse.msh) for a coarser mesh). The mesh is +# generated with [Gmsh](https://gmsh.info/), and we read it in as a Ferrite `Grid` using +# the [FerriteGmsh.jl](https://github.com/Ferrite-FEM/FerriteGmsh.jl) package: + +using FerriteGmsh + +#src notebook: use coarse mesh to decrease build time +#src script: use the fine mesh +#src markdown: use the coarse mesh to decrease build time, but make it look like the fine +#nb ## grid = togrid("periodic-rve.msh") +#nb grid = togrid("periodic-rve-coarse.msh") +#jl ## grid = togrid("periodic-rve-coarse.msh") +#jl grid = togrid("periodic-rve.msh") +#md grid = togrid("periodic-rve.msh") +#- +#md grid = redirect_stdout(devnull) do #hide +#md togrid("periodic-rve-coarse.msh") #hide +#md end #hide + +grid = togrid("periodic-rve.msh") #src + +# Next we construct the interpolation and quadrature rule, and combining them into +# cellvalues as usual: + +dim = 2 +ip = Lagrange{RefTriangle, 1}()^dim +qr = QuadratureRule{RefTriangle}(2) +cellvalues = CellValues(qr, ip); + +# We define a dof handler with a displacement field `:u`: +dh = DofHandler(grid) +add!(dh, :u, ip) +close!(dh); + +# Now we need to define boundary conditions. As discussed earlier we will solve the problem +# using (i) homogeneous Dirichlet boundary conditions, and (ii) periodic Dirichlet boundary +# conditions. We construct two different constraint handlers, one for each case. The +# [`Dirichlet`](@ref) boundary condition we have seen in many other examples. Here we simply +# define the condition that the field, `:u`, should have both components prescribed to `0` +# on the full boundary: + +ch_dirichlet = ConstraintHandler(dh) +dirichlet = Dirichlet( + :u, + union(getfacetset.(Ref(grid), ["left", "right", "top", "bottom"])...), + (x, t) -> [0, 0], + [1, 2] +) +add!(ch_dirichlet, dirichlet) +close!(ch_dirichlet) +update!(ch_dirichlet, 0.0) + +# For periodic boundary conditions we use the [`PeriodicDirichlet`](@ref) constraint type, +# which is very similar to the `Dirichlet` type, but instead of a passing a facetset we pass +# a vector with "facet pairs", i.e. the mapping between mirror and image parts of the +# boundary. In this example the `"left"` and `"bottom"` boundaries are mirrors, and the +# `"right"` and `"top"` boundaries are the mirrors. + +ch_periodic = ConstraintHandler(dh); +periodic = PeriodicDirichlet( + :u, + ["left" => "right", "bottom" => "top"], + [1, 2] +) +add!(ch_periodic, periodic) +close!(ch_periodic) +update!(ch_periodic, 0.0) + +# This will now constrain any degrees of freedom located on the mirror boundaries to +# the matching degree of freedom on the image boundaries. Internally this will create +# a number of `AffineConstraint`s of the form `u_i = 1 * u_j + 0`: +# ```julia +# a = AffineConstraint(u_m, [u_i => 1], 0) +# ``` +# where `u_m` is the degree of freedom on the mirror and `u_i` the matching one on the +# image part. `PeriodicDirichlet` is thus simply just a more convenient way of +# constructing such affine constraints since it computes the degree of freedom mapping +# automatically. +# +# To simplify things we group the constraint handlers into a named tuple + +ch = (dirichlet = ch_dirichlet, periodic = ch_periodic); + +# We can now construct the sparse matrix. Note that, since we are using affine constraints, +# which need to modify the matrix sparsity pattern in order to account for the constraint +# equations, we construct the matrix for the periodic case by passing both the dof handler +# and the constraint handler. + +K = ( + dirichlet = allocate_matrix(dh), + periodic = allocate_matrix(dh, ch.periodic), +); + +# We define the fourth order elasticity tensor for the matrix material, and define the +# inclusions to have 10 times higher stiffness + +λ, μ = 1.0e10, 7.0e9 # Lamé parameters +δ(i, j) = i == j ? 1.0 : 0.0 +Em = SymmetricTensor{4, 2}( + (i, j, k, l) -> λ * δ(i, j) * δ(k, l) + μ * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k)) +) +Ei = 10 * Em; + +# As mentioned above, in order to compute the apparent/homogenized stiffness we will solve +# the problem repeatedly with different macroscale strain tensors to compute the sensitvity +# of the homogenized stress, ``\bar{\boldsymbol{\sigma}}``, w.r.t. the macroscopic strain, +# ``\bar{\boldsymbol{\varepsilon}}``. The corresponding unit strains are defined below, +# and will result in three different right-hand-sides: + +εᴹ = [ + SymmetricTensor{2, 2}([1.0 0.0; 0.0 0.0]), # ε_11 loading + SymmetricTensor{2, 2}([0.0 0.0; 0.0 1.0]), # ε_22 loading + SymmetricTensor{2, 2}([0.0 0.5; 0.5 0.0]), # ε_12/ε_21 loading +]; + +# The assembly function is nothing strange, and in particular there is no impact from the +# choice of boundary conditions, so the same function can be used for both cases. Since +# we want to solve the system 3 times, once for each macroscopic strain component, we +# assemble 3 right-hand-sides. + +function doassemble!(cellvalues::CellValues, K::SparseMatrixCSC, dh::DofHandler, εᴹ) + + n_basefuncs = getnbasefunctions(cellvalues) + ndpc = ndofs_per_cell(dh) + Ke = zeros(ndpc, ndpc) + fe = zeros(ndpc, length(εᴹ)) + f = zeros(ndofs(dh), length(εᴹ)) + assembler = start_assemble(K) + + for cell in CellIterator(dh) + + E = cellid(cell) in getcellset(dh.grid, "inclusions") ? Ei : Em + reinit!(cellvalues, cell) + fill!(Ke, 0) + fill!(fe, 0) + + for q_point in 1:getnquadpoints(cellvalues) + dΩ = getdetJdV(cellvalues, q_point) + for i in 1:n_basefuncs + δεi = shape_symmetric_gradient(cellvalues, q_point, i) + for j in 1:n_basefuncs + δεj = shape_symmetric_gradient(cellvalues, q_point, j) + Ke[i, j] += (δεi ⊡ E ⊡ δεj) * dΩ + end + for (rhs, ε) in enumerate(εᴹ) + σᴹ = E ⊡ ε + fe[i, rhs] += (- δεi ⊡ σᴹ) * dΩ + end + end + end + + cdofs = celldofs(cell) + assemble!(assembler, cdofs, Ke) + f[cdofs, :] .+= fe + end + return f +end; + +# We can now assemble the system. The assembly function modifies the matrix in-place, but +# return the right hand side(s) which we collect in another named tuple. + +rhs = ( + dirichlet = doassemble!(cellvalues, K.dirichlet, dh, εᴹ), + periodic = doassemble!(cellvalues, K.periodic, dh, εᴹ), +); + +# The next step is to solve the systems. Since application of boundary conditions, using +# the [`apply!`](@ref) function, modifies both the matrix and the right hand sides we can +# not use it directly in this case since we want to reuse the matrix again for the next +# right hand sides. We could of course re-assemble the matrix for every right hand side, +# but that would not be very efficient. Instead we will use the [`get_rhs_data`](@ref) +# function, together with [`apply_rhs!`](@ref) in a later step. This will extract the +# necessary data from the matrix such that we can apply it for all the different right +# hand sides. Note that we call `apply!` with just the matrix and no right hand side. + +rhsdata = ( + dirichlet = get_rhs_data(ch.dirichlet, K.dirichlet), + periodic = get_rhs_data(ch.periodic, K.periodic), +) + +apply!(K.dirichlet, ch.dirichlet) +apply!(K.periodic, ch.periodic) + +# We can now solve the problem(s). Note that we only use `apply_rhs!` in the loops below. +# The boundary conditions are already applied to the matrix above, so we only need to +# modify the right hand side. + +u = ( + dirichlet = Vector{Float64}[], + periodic = Vector{Float64}[], +) + +for i in 1:size(rhs.dirichlet, 2) + rhs_i = @view rhs.dirichlet[:, i] # Extract this RHS + apply_rhs!(rhsdata.dirichlet, rhs_i, ch.dirichlet) # Apply BC + u_i = cholesky(Symmetric(K.dirichlet)) \ rhs_i # Solve + apply!(u_i, ch.dirichlet) # Apply BC on the solution + push!(u.dirichlet, u_i) # Save the solution vector +end + +for i in 1:size(rhs.periodic, 2) + rhs_i = @view rhs.periodic[:, i] # Extract this RHS + apply_rhs!(rhsdata.periodic, rhs_i, ch.periodic) # Apply BC + u_i = cholesky(Symmetric(K.periodic)) \ rhs_i # Solve + apply!(u_i, ch.periodic) # Apply BC on the solution + push!(u.periodic, u_i) # Save the solution vector +end + +# When the solution(s) are known we can compute the averaged stress, +# ``\bar{\boldsymbol{\sigma}}`` in the RVE. We define a function that does this, and also +# returns the von Mise stress in every quadrature point for visualization. + +function compute_stress(cellvalues::CellValues, dh::DofHandler, u, εᴹ) + σvM_qpdata = zeros(getnquadpoints(cellvalues), getncells(dh.grid)) + σ̄Ω = zero(SymmetricTensor{2, 2}) + Ω = 0.0 # Total volume + for cell in CellIterator(dh) + E = cellid(cell) in getcellset(dh.grid, "inclusions") ? Ei : Em + reinit!(cellvalues, cell) + for q_point in 1:getnquadpoints(cellvalues) + dΩ = getdetJdV(cellvalues, q_point) + εμ = function_symmetric_gradient(cellvalues, q_point, u[celldofs(cell)]) + σ = E ⊡ (εᴹ + εμ) + σvM_qpdata[q_point, cellid(cell)] = sqrt(3 / 2 * dev(σ) ⊡ dev(σ)) + Ω += dΩ # Update total volume + σ̄Ω += σ * dΩ # Update integrated stress + end + end + σ̄ = σ̄Ω / Ω + return σvM_qpdata, σ̄ +end; + +# We now compute the homogenized stress and von Mise stress for all cases + +σ̄ = ( + dirichlet = SymmetricTensor{2, 2}[], + periodic = SymmetricTensor{2, 2}[], +) +σ = ( + dirichlet = Vector{Float64}[], + periodic = Vector{Float64}[], +) + +projector = L2Projector(ip, grid) + +for i in 1:3 + σ_qp, σ̄_i = compute_stress(cellvalues, dh, u.dirichlet[i], εᴹ[i]) + proj = project(projector, σ_qp, qr) + push!(σ.dirichlet, proj) + push!(σ̄.dirichlet, σ̄_i) +end + +for i in 1:3 + σ_qp, σ̄_i = compute_stress(cellvalues, dh, u.periodic[i], εᴹ[i]) + proj = project(projector, σ_qp, qr) + push!(σ.periodic, proj) + push!(σ̄.periodic, σ̄_i) +end + +# The remaining thing is to compute the homogenized stiffness. As mentioned in the +# introduction we can find all the components from the average stress of the sensitivity +# fields that we have solved for +# +# ```math +# \mathsf{E}_{ijkl} = \bar{\sigma}_{ij}(\hat{\boldsymbol{u}}_{kl}). +# ``` +# +# So we have now already computed all the components, and just need to gather the data in +# a fourth order tensor: + +E_dirichlet = SymmetricTensor{4, 2}() do i, j, k, l + if k == l == 1 + σ̄.dirichlet[1][i, j] # ∂σ∂ε_**11 + elseif k == l == 2 + σ̄.dirichlet[2][i, j] # ∂σ∂ε_**22 + else + σ̄.dirichlet[3][i, j] # ∂σ∂ε_**12 and ∂σ∂ε_**21 + end +end + +E_periodic = SymmetricTensor{4, 2}() do i, j, k, l + if k == l == 1 + σ̄.periodic[1][i, j] + elseif k == l == 2 + σ̄.periodic[2][i, j] + else + σ̄.periodic[3][i, j] + end +end + +# We can check that the result are what we expect, namely that the stiffness with Dirichlet +# boundary conditions is higher than when using periodic boundary conditions, and that +# the Reuss assumption is an lower bound, and the Voigt assumption a upper bound. We first +# compute the volume fraction of the matrix, and then the Voigt and Reuss bounds: + +function matrix_volume_fraction(grid, cellvalues) + V = 0.0 # Total volume + Vm = 0.0 # Volume of the matrix + for c in CellIterator(grid) + reinit!(cellvalues, c) + is_matrix = !(cellid(c) in getcellset(grid, "inclusions")) + for qp in 1:getnquadpoints(cellvalues) + dΩ = getdetJdV(cellvalues, qp) + V += dΩ + if is_matrix + Vm += dΩ + end + end + end + return Vm / V +end + +vm = matrix_volume_fraction(grid, cellvalues) +#- +E_voigt = vm * Em + (1 - vm) * Ei +E_reuss = inv(vm * inv(Em) + (1 - vm) * inv(Ei)); + +# We can now compare the different computed stiffness tensors. We expect +# ``E_\mathrm{Reuss} \leq E_\mathrm{PeriodicBC} \leq E_\mathrm{DirichletBC} \leq +# E_\mathrm{Voigt}``. A simple thing to compare are the eigenvalues of the tensors. Here +# we look at the first eigenvalue: + +ev = (first ∘ eigvals).((E_reuss, E_periodic, E_dirichlet, E_voigt)) +@test issorted(ev) #src +round.(ev; digits = -8) + +# Finally, we export the solution and the stress field to a VTK file. For the export we +# also compute the macroscopic part of the displacement. + +uM = zeros(ndofs(dh)) + +VTKGridFile("homogenization", dh) do vtk + for i in 1:3 + ## Compute macroscopic solution + apply_analytical!(uM, dh, :u, x -> εᴹ[i] ⋅ x) + ## Dirichlet + write_solution(vtk, dh, uM + u.dirichlet[i], "_dirichlet_$i") + write_projection(vtk, projector, σ.dirichlet[i], "σvM_dirichlet_$i") + ## Periodic + write_solution(vtk, dh, uM + u.periodic[i], "_periodic_$i") + write_projection(vtk, projector, σ.periodic[i], "σvM_periodic_$i") + end +end; + +# Just another way to compute the stiffness for testing purposes #src +function homogenize_test(u::Matrix, dh, cv, E_incl, E_mat) #src + ĒΩ = zero(SymmetricTensor{4, 2}) #src + Ω = 0.0 #src + ue = zeros(ndofs_per_cell(dh), 3) #src + for cell in CellIterator(dh) #src + reinit!(cv, cell) #src + for (localdof, globaldof) in enumerate(celldofs(cell)) #src + for i in 1:3 #src + ue[localdof, i] = u[globaldof, i] #src + end #src + end #src + E = cellid(cell) in getcellset(dh.grid, "inclusions") ? E_incl : E_mat #src + for qp in 1:getnquadpoints(cv) #src + dΩ = getdetJdV(cv, qp) #src + Ω += dΩ #src + ## compute u^ij and u^kl #src + Ē′ = SymmetricTensor{4, 2}() do i, j, k, l #src + ij = i == j == 1 ? 1 : i == j == 2 ? 2 : 3 #src + kl = k == l == 1 ? 1 : k == l == 2 ? 2 : 3 #src + εij = function_symmetric_gradient(cv, qp, view(ue, :, ij)) + #src + symmetric((basevec(Vec{2}, i) ⊗ basevec(Vec{2}, j))) #src + εkl = function_symmetric_gradient(cv, qp, view(ue, :, kl)) + #src + symmetric((basevec(Vec{2}, k) ⊗ basevec(Vec{2}, l))) #src + return (εij ⊡ E ⊡ εkl) * dΩ #src + end #src + ĒΩ += Ē′ #src + end #src + end #src + return ĒΩ / Ω #src +end #src + +@test homogenize_test(reduce(hcat, u.dirichlet), dh, cellvalues, Ei, Em) ≈ E_dirichlet #src +@test homogenize_test(reduce(hcat, u.periodic), dh, cellvalues, Ei, Em) ≈ E_periodic #src + +#md # ## [Plain program](@id homogenization-plain-program) +#md # +#md # Here follows a version of the program without any comments. +#md # The file is also available here: +#md # [`computational_homogenization.jl`](computational_homogenization.jl). +#md # +#md # ```julia +#md # @__CODE__ +#md # ``` diff --git a/previews/PR798/literate-tutorials/dg_heat_equation.jl b/previews/PR798/literate-tutorials/dg_heat_equation.jl new file mode 100644 index 0000000000..407dc7ad5f --- /dev/null +++ b/previews/PR798/literate-tutorials/dg_heat_equation.jl @@ -0,0 +1,358 @@ +# # [Discontinuous Galerkin heat equation](@id tutorial-dg-heat-equation) +# +# ![](dg_heat_equation.png) +# +# *Figure 1*: Temperature field on the unit square with an internal uniform heat source +# solved with inhomogeneous Dirichlet boundary conditions on the left and right boundaries +# and flux on the top and bottom boundaries. +# +#- +#md # !!! tip +#md # This example is also available as a Jupyter notebook: +#md # [`dg_heat_equation.ipynb`](@__NBVIEWER_ROOT_URL__/tutorials/dg_heat_equation.ipynb). +#- +# +# This example was developed +# as part of the Google summer of code funded project +# ["Discontinuous Galerkin Infrastructure For the finite element toolbox Ferrite.jl"](https://summerofcode.withgoogle.com/programs/2023/projects/SLGbRNI5) +# +# ## Introduction +# +# This tutorial extends [Tutorial 1: Heat equation](heat_equation.md) by using the +# discontinuous Galerkin method. The reader is expected to have gone through [Tutorial 1: +# Heat equation](heat_equation.md) before proceeding with this tutorial. The main +# differences between the two tutorials are the interface integral terms in the weak form, +# the boundary conditions, and some implementation differences explained in the commented +# program below. +# +# The strong form considered in this tutorial is given as follows +# ```math +# -\boldsymbol{\nabla} \cdot [\boldsymbol{\nabla}(u)] = 1 \quad \textbf{x} \in \Omega, +# ``` +# +# with the inhomogeneous Dirichlet boundary conditions +# ```math +# u(\textbf{x}) = 1 \quad \textbf{x} \in \partial \Omega_D^+ = \lbrace\textbf{x} : x_1 = 1.0\rbrace, \\ +# u(\textbf{x}) = -1 \quad \textbf{x} \in \partial \Omega_D^- = \lbrace\textbf{x} : x_1 = -1.0\rbrace, +# ``` +# and Neumann boundary conditions +# ```math +# [\boldsymbol{\nabla} (u(\textbf{x}))] \cdot \boldsymbol{n} = 1 \quad \textbf{x} \in \partial \Omega_N^+ = \lbrace\textbf{x} : x_2 = 1.0\rbrace, \\ +# [\boldsymbol{\nabla} (u(\textbf{x}))] \cdot \boldsymbol{n} = -1 \quad \textbf{x} \in \partial \Omega_N^- = \lbrace\textbf{x} : x_2 = -1.0\rbrace, +# ``` +# +# The following definitions of average and jump on interfaces between elements are adopted +# in this tutorial: +# ```math +# \{u\} = \frac{1}{2}(u^+ + u^-),\quad \llbracket u\rrbracket = u^+ \boldsymbol{n}^+ + u^- \boldsymbol{n}^-\\ +# ``` +# where ``u^+`` and ``u^-`` are the temperature on the two sides of the interface. +# +# +# !!! details "Derivation of the weak form for homogeneous Dirichlet boundary condition" +# Defining $\boldsymbol{\sigma}$ as the gradient of the temperature field the equation can be expressed as +# ```math +# \boldsymbol{\sigma} = \boldsymbol{\nabla} (u),\\ +# -\boldsymbol{\nabla} \cdot \boldsymbol{\sigma} = 1, +# ``` +# Multiplying by test functions $ \boldsymbol{\tau} $ and $ \delta u $ respectively and integrating +# over the domain, +# ```math +# \int_\Omega \boldsymbol{\sigma} \cdot \boldsymbol{\tau} \,\mathrm{d}\Omega = \int_\Omega [\boldsymbol{\nabla} (u)] \cdot \boldsymbol{\tau} \,\mathrm{d}\Omega,\\ +# -\int_\Omega \boldsymbol{\nabla} \cdot \boldsymbol{\sigma} \delta u \,\mathrm{d}\Omega = \int_\Omega \delta u \,\mathrm{d}\Omega, +# ``` +# Integrating by parts and applying divergence theorem, +# ```math +# \int_\Omega \boldsymbol{\sigma} \cdot \boldsymbol{\tau} \,\mathrm{d}\Omega = -\int_\Omega u (\boldsymbol{\nabla} \cdot \boldsymbol{\tau}) \,\mathrm{d}\Omega + \int_\Gamma \hat{u} \boldsymbol{\tau} \cdot \boldsymbol{n} \,\mathrm{d}\Gamma,\\ +# \int_\Omega \boldsymbol{\sigma} \cdot [\boldsymbol{\nabla} (\delta u)] \,\mathrm{d}\Omega = \int_\Omega \delta u \,\mathrm{d}\Omega + \int_\Gamma \delta u \boldsymbol{\hat{\sigma}} \cdot \boldsymbol{n} \,\mathrm{d}\Gamma, +# ``` +# Where $\boldsymbol{n}$ is the outwards pointing normal, $\Gamma$ is the union of the elements' boundaries, and $\hat{u}, \, \hat{\sigma}$ are the numerical fluxes. +# Substituting the integrals of form +# ```math +# \int_\Gamma q \boldsymbol{\phi} \cdot \boldsymbol{n} \,\mathrm{d}\Gamma = \int_\Gamma \llbracket q\rrbracket \cdot \{\boldsymbol{\phi}\} \,\mathrm{d}\Gamma + \int_{\Gamma^0} \{q\} \llbracket \boldsymbol{\phi}\rrbracket \,\mathrm{d}\Gamma^0, +# ``` +# where $\Gamma^0 : \Gamma \setminus \partial \Omega$, and the jump of the vector-valued field $\boldsymbol{\phi}$ is defined as +# ```math +# \llbracket \boldsymbol{\phi}\rrbracket = \boldsymbol{\phi}^+ \cdot \boldsymbol{n}^+ + \boldsymbol{\phi}^- \cdot \boldsymbol{n}^-\\ +# ``` +# with the jumps and averages results in +# ```math +# \int_\Omega \boldsymbol{\sigma} \cdot \boldsymbol{\tau} \,\mathrm{d}\Omega = -\int_\Omega u (\boldsymbol{\nabla} \cdot \boldsymbol{\tau}) \,\mathrm{d}\Omega + \int_\Gamma \llbracket \hat{u}\rrbracket \cdot \{\boldsymbol{\tau}\} \,\mathrm{d}\Gamma + \int_{\Gamma^0} \{\hat{u}\} \llbracket \boldsymbol{\tau}\rrbracket \,\mathrm{d}\Gamma^0,\\ +# \int_\Omega \boldsymbol{\sigma} \cdot [\boldsymbol{\nabla} (\delta u)] \,\mathrm{d}\Omega = \int_\Omega \delta u \,\mathrm{d}\Omega + \int_\Gamma \llbracket \delta u\rrbracket \cdot \{\hat{\boldsymbol{\sigma}}\} \,\mathrm{d}\Gamma + \int_{\Gamma^0} \{\delta u\} \llbracket \hat{\boldsymbol{\sigma}}\rrbracket \,\mathrm{d}\Gamma^0, +# ``` +# Integrating $ \int_\Omega [\boldsymbol{\nabla} (u)] \cdot \boldsymbol{\tau} \,\mathrm{d}\Omega $ by parts and applying divergence theorem +# without using numerical flux, then substitute in the equation to obtain a weak form. +# ```math +# \int_\Omega \boldsymbol{\sigma} \cdot \boldsymbol{\tau} \,\mathrm{d}\Omega = \int_\Omega [\boldsymbol{\nabla} (u)] \cdot \boldsymbol{\tau} \,\mathrm{d}\Omega + \int_\Gamma \llbracket \hat{u} - u\rrbracket \cdot \{\boldsymbol{\tau}\} \,\mathrm{d}\Gamma + \int_{\Gamma^0} \{\hat{u} - u\} \llbracket \boldsymbol{\tau}\rrbracket \,\mathrm{d}\Gamma^0,\\ +# \int_\Omega \boldsymbol{\sigma} \cdot [\boldsymbol{\nabla} (\delta u)] \,\mathrm{d}\Omega = \int_\Omega \delta u \,\mathrm{d}\Omega + \int_\Gamma \llbracket \delta u\rrbracket \cdot \{\hat{\boldsymbol{\sigma}}\} \,\mathrm{d}\Gamma + \int_{\Gamma^0} \{\delta u\} \llbracket \hat{\boldsymbol{\sigma}}\rrbracket \,\mathrm{d}\Gamma^0, +# ``` +# Substituting +# ```math +# \boldsymbol{\tau} = \boldsymbol{\nabla} (\delta u),\\ +# ``` +# results in +# ```math +# \int_\Omega \boldsymbol{\sigma} \cdot [\boldsymbol{\nabla} (\delta u)] \,\mathrm{d}\Omega = \int_\Omega [\boldsymbol{\nabla} (u)] \cdot [\boldsymbol{\nabla} (\delta u)] \,\mathrm{d}\Omega + \int_\Gamma \llbracket \hat{u} - u\rrbracket \cdot \{\boldsymbol{\nabla} (\delta u)\} \,\mathrm{d}\Gamma + \int_{\Gamma^0} \{\hat{u} - u\} \llbracket \boldsymbol{\nabla} (\delta u)\rrbracket \,\mathrm{d}\Gamma^0,\\ +# \int_\Omega \boldsymbol{\sigma} \cdot [\boldsymbol{\nabla} (\delta u)] \,\mathrm{d}\Omega = \int_\Omega \delta u \,\mathrm{d}\Omega + \int_\Gamma \llbracket \delta u\rrbracket \cdot \{\hat{\boldsymbol{\sigma}}\} \,\mathrm{d}\Gamma + \int_{\Gamma^0} \{\delta u\} \llbracket \hat{\boldsymbol{\sigma}}\rrbracket \,\mathrm{d}\Gamma^0, +# ``` +# Combining the two equations, +# ```math +# \int_\Omega [\boldsymbol{\nabla} (u)] \cdot [\boldsymbol{\nabla} (\delta u)] \,\mathrm{d}\Omega + \int_\Gamma \llbracket \hat{u} - u\rrbracket \cdot \{\boldsymbol{\nabla} (\delta u)\} \,\mathrm{d}\Gamma + \int_{\Gamma^0} \{\hat{u} - u\} \llbracket \boldsymbol{\nabla} (\delta u)\rrbracket \,\mathrm{d}\Gamma^0 - \int_\Gamma \llbracket \delta u\rrbracket \cdot \{\hat{\boldsymbol{\sigma}}\} \,\mathrm{d}\Gamma - \int_{\Gamma^0} \{\delta u\} \llbracket \hat{\boldsymbol{\sigma}}\rrbracket \,\mathrm{d}\Gamma^0 = \int_\Omega \delta u \,\mathrm{d}\Omega,\\ +# ``` +# The numerical fluxes chosen for the interior penalty method are $\boldsymbol{\hat{\sigma}} = \{\boldsymbol{\nabla} (u)\} - \alpha(\llbracket u\rrbracket)$ on $\Gamma$, $\hat{u} = \{u\}$ on the interfaces between elements $\Gamma^0 : \Gamma \setminus \partial \Omega$, +# and $\hat{u} = 0$ on $\partial \Omega$. Such choice results in $\{\hat{\boldsymbol{\sigma}}\} = \{\boldsymbol{\nabla} (u)\} - \alpha(\llbracket u\rrbracket)$, $\llbracket \hat{u}\rrbracket = 0$, $\{\hat{u}\} = \{u\}$, $\llbracket \hat{\boldsymbol{\sigma}}\rrbracket = 0$ and the equation becomes +# ```math +# \int_\Omega [\boldsymbol{\nabla} (u)] \cdot [\boldsymbol{\nabla} (\delta u)] \,\mathrm{d}\Omega - \int_\Gamma \llbracket u\rrbracket \cdot \{\boldsymbol{\nabla} (\delta u)\} \,\mathrm{d}\Gamma - \int_\Gamma \llbracket \delta u\rrbracket \cdot \{\boldsymbol{\nabla} (u)\} - \llbracket \delta u\rrbracket \cdot \alpha(\llbracket u\rrbracket) \,\mathrm{d}\Gamma = \int_\Omega \delta u \,\mathrm{d}\Omega,\\ +# ``` +# Where +# ```math +# \alpha(\llbracket u\rrbracket) = \mu \llbracket u\rrbracket +# ``` +# Where $\mu = \eta h_e^{-1}$, the weak form becomes +# ```math +# \int_\Omega [\boldsymbol{\nabla} (u)] \cdot [\boldsymbol{\nabla}] (\delta u) \,\mathrm{d}\Omega - \int_\Gamma \llbracket u \rrbracket \cdot \{\boldsymbol{\nabla} (\delta u)\} + \llbracket \delta u \rrbracket \cdot \{\boldsymbol{\nabla} (u)\} \,\mathrm{d}\Gamma + \int_\Gamma \frac{\eta}{h_e} \llbracket u\rrbracket \cdot \llbracket \delta u\rrbracket \,\mathrm{d}\Gamma = \int_\Omega \delta u \,\mathrm{d}\Omega,\\ +# ``` +# Since $\partial \Omega$ is constrained with both Dirichlet and Neumann boundary conditions the term $\int_{\partial \Omega} [\boldsymbol{\nabla} (u)] \cdot \boldsymbol{n} \delta u \,\mathrm{d} \Omega$ can be expressed as an integral over $\partial \Omega_N$, where $\partial \Omega_N$ is the boundaries with only prescribed Neumann boundary condition, +# The resulting weak form is given given as follows: Find $u \in \mathbb{U}$ such that +# ```math +# \int_\Omega [\boldsymbol{\nabla} (u)] \cdot [\boldsymbol{\nabla} (\delta u)] \,\mathrm{d}\Omega - \int_{\Gamma^0} \llbracket u\rrbracket \cdot \{\boldsymbol{\nabla} (\delta u)\} + \llbracket \delta u\rrbracket \cdot \{\boldsymbol{\nabla} (u)\} \,\mathrm{d}\Gamma^0 + \int_{\Gamma^0} \frac{\eta}{h_e} \llbracket u\rrbracket \cdot \llbracket \delta u\rrbracket \,\mathrm{d}\Gamma^0 = \int_\Omega \delta u \,\mathrm{d}\Omega + \int_{\partial \Omega_N} ([\boldsymbol{\nabla} (u)] \cdot \boldsymbol{n}) \delta u \,\mathrm{d} \partial \Omega_N,\\ +# ``` +# where $h_e$ is the characteristic size (the diameter of the interface), and $\eta$ is a large enough positive number independent of $h_e$ [Mu:2014:IP](@cite), +# $\delta u \in \mathbb{T}$ is a test function, and where $\mathbb{U}$ and $\mathbb{T}$ are suitable +# trial and test function sets, respectively. +# We use the value ``\eta = (1 + O)^{D}``, where ``O`` is the polynomial order and ``D`` the +# dimension, in this tutorial. +# +# More details on DG formulations for elliptic problems can be found in [Cockburn:2002:unifiedanalysis](@cite). +#- +# ## Commented Program +# +# Now we solve the problem in Ferrite. What follows is a program spliced with comments. +#md # The full program, without comments, can be found in the next [section](@ref heat_equation-DG-plain-program). +# +# First we load Ferrite and other packages, and generate grid just like the [heat equation tutorial](heat_equation.md) +using Ferrite, SparseArrays +dim = 2; +grid = generate_grid(Quadrilateral, ntuple(_ -> 20, dim)); +# We construct the topology information which is used later for generating the sparsity pattern for stiffness matrix. +topology = ExclusiveTopology(grid); + +# ### Trial and test functions +# `CellValues`, `FacetValues`, and `InterfaceValues` facilitate the process of evaluating values and gradients of +# test and trial functions (among other things). To define +# these we need to specify an interpolation space for the shape functions. +# We use `DiscontinuousLagrange` functions +# based on the two-dimensional reference quadrilateral. We also define a quadrature rule based on +# the same reference element. We combine the interpolation and the quadrature rule +# to `CellValues` and `InterfaceValues` object. Note that `InterfaceValues` object contains two `FacetValues` objects which can be used individually. +order = 1; +ip = DiscontinuousLagrange{RefQuadrilateral, order}(); +qr = QuadratureRule{RefQuadrilateral}(2); +# For `FacetValues` and `InterfaceValues` we use `FacetQuadratureRule` +facet_qr = FacetQuadratureRule{RefQuadrilateral}(2); +cellvalues = CellValues(qr, ip); +facetvalues = FacetValues(facet_qr, ip); +interfacevalues = InterfaceValues(facet_qr, ip); +# ### Penalty term parameters +# We define functions to calculate the diameter of a set of points, used to calculate the characteristic size $h_e$ in the assembly routine. +getdistance(p1::Vec{N, T}, p2::Vec{N, T}) where {N, T} = norm(p1 - p2); +getdiameter(cell_coords::Vector{Vec{N, T}}) where {N, T} = maximum(getdistance.(cell_coords, reshape(cell_coords, (1, :)))); + +# ### Degrees of freedom +# Degrees of freedom distribution is handled using `DofHandler` as usual +dh = DofHandler(grid) +add!(dh, :u, ip) +close!(dh); + +# However, when generating the sparsity pattern we need to pass the topology and the cross-element coupling matrix when we're using +# discontinuous interpolations. The cross-element coupling matrix is of size [1,1] in this case as +# we have only one field and one DofHandler. +K = allocate_matrix(dh, topology = topology, interface_coupling = trues(1, 1)); + +# ### Boundary conditions +# The Dirichlet boundary conditions are treated +# as usual by a `ConstraintHandler`. +ch = ConstraintHandler(dh) +add!(ch, Dirichlet(:u, getfacetset(grid, "right"), (x, t) -> 1.0)) +add!(ch, Dirichlet(:u, getfacetset(grid, "left"), (x, t) -> -1.0)) +close!(ch); + +# Furthermore, we define $\partial \Omega_N$ as the `union` of the facet sets with Neumann boundary conditions for later use +∂Ωₙ = union( + getfacetset(grid, "top"), + getfacetset(grid, "bottom"), +); + + +# ### Assembling the linear system +# +# Now we have all the pieces needed to assemble the linear system, $K u = f$. Assembling of +# the global system is done by looping over i) all the elements in order to compute the +# element contributions ``K_e`` and ``f_e``, ii) all the interfaces to compute their +# contributions ``K_i``, and iii) all the Neumann boundary facets to compute their +# contributions ``f_e``. All these local contributions are then assembled into the +# appropriate place in the global ``K`` and ``f``. +# +# #### Local assembly +# We define the functions +# * `assemble_element!` to compute the contributions ``K_e`` and ``f_e`` of volume integrals +# over an element using `cellvalues`. +# * `assemble_interface!` to compute the contribution ``K_i`` of surface integrals over an +# interface using `interfacevalues`. +# * `assemble_boundary!` to compute the contribution ``f_e`` of surface integrals over a +# boundary facet using `FacetValues`. + +function assemble_element!(Ke::Matrix, fe::Vector, cellvalues::CellValues) + n_basefuncs = getnbasefunctions(cellvalues) + ## Reset to 0 + fill!(Ke, 0) + fill!(fe, 0) + ## Loop over quadrature points + for q_point in 1:getnquadpoints(cellvalues) + ## Quadrature weight + dΩ = getdetJdV(cellvalues, q_point) + ## Loop over test shape functions + for i in 1:n_basefuncs + δu = shape_value(cellvalues, q_point, i) + ∇δu = shape_gradient(cellvalues, q_point, i) + ## Add contribution to fe + fe[i] += δu * dΩ + ## Loop over trial shape functions + for j in 1:n_basefuncs + ∇u = shape_gradient(cellvalues, q_point, j) + ## Add contribution to Ke + Ke[i, j] += (∇δu ⋅ ∇u) * dΩ + end + end + end + return Ke, fe +end + +function assemble_interface!(Ki::Matrix, iv::InterfaceValues, μ::Float64) + ## Reset to 0 + fill!(Ki, 0) + ## Loop over quadrature points + for q_point in 1:getnquadpoints(iv) + ## Get the normal to facet A + normal = getnormal(iv, q_point) + ## Get the quadrature weight + dΓ = getdetJdV(iv, q_point) + ## Loop over test shape functions + for i in 1:getnbasefunctions(iv) + ## Multiply the jump by the negative normal to get the definition from the theory section. + δu_jump = shape_value_jump(iv, q_point, i) * (-normal) + ∇δu_avg = shape_gradient_average(iv, q_point, i) + ## Loop over trial shape functions + for j in 1:getnbasefunctions(iv) + ## Multiply the jump by the negative normal to get the definition from the theory section. + u_jump = shape_value_jump(iv, q_point, j) * (-normal) + ∇u_avg = shape_gradient_average(iv, q_point, j) + ## Add contribution to Ki + Ki[i, j] += -(δu_jump ⋅ ∇u_avg + ∇δu_avg ⋅ u_jump) * dΓ + μ * (δu_jump ⋅ u_jump) * dΓ + end + end + end + return Ki +end + +function assemble_boundary!(fe::Vector, fv::FacetValues) + ## Reset to 0 + fill!(fe, 0) + ## Loop over quadrature points + for q_point in 1:getnquadpoints(fv) + ## Get the normal to facet A + normal = getnormal(fv, q_point) + ## Get the quadrature weight + ∂Ω = getdetJdV(fv, q_point) + ## Loop over test shape functions + for i in 1:getnbasefunctions(fv) + δu = shape_value(fv, q_point, i) + boundary_flux = normal[2] + fe[i] = boundary_flux * δu * ∂Ω + end + end + return fe +end +#md nothing # hide + +# #### Global assembly +# +# We define the function `assemble_global` to loop over all elements and internal facets +# (interfaces), as well as the external facets involved in Neumann boundary conditions. + +function assemble_global(cellvalues::CellValues, facetvalues::FacetValues, interfacevalues::InterfaceValues, K::SparseMatrixCSC, dh::DofHandler, order::Int, dim::Int) + ## Allocate the element stiffness matrix and element force vector + n_basefuncs = getnbasefunctions(cellvalues) + Ke = zeros(n_basefuncs, n_basefuncs) + fe = zeros(n_basefuncs) + Ki = zeros(n_basefuncs * 2, n_basefuncs * 2) + ## Allocate global force vector f + f = zeros(ndofs(dh)) + ## Create an assembler + assembler = start_assemble(K, f) + ## Loop over all cells + for cell in CellIterator(dh) + ## Reinitialize cellvalues for this cell + reinit!(cellvalues, cell) + ## Compute volume integral contribution + assemble_element!(Ke, fe, cellvalues) + ## Assemble Ke and fe into K and f + assemble!(assembler, celldofs(cell), Ke, fe) + end + ## Loop over all interfaces + for ic in InterfaceIterator(dh) + ## Reinitialize interfacevalues for this interface + reinit!(interfacevalues, ic) + ## Calculate the characteristic size hₑ as the face diameter + interfacecoords = ∩(getcoordinates(ic)...) + hₑ = getdiameter(interfacecoords) + ## Calculate μ + μ = (1 + order)^dim / hₑ + ## Compute interface surface integrals contribution + assemble_interface!(Ki, interfacevalues, μ) + ## Assemble Ki into K + assemble!(assembler, interfacedofs(ic), Ki) + end + ## Loop over domain boundaries with Neumann boundary conditions + for fc in FacetIterator(dh, ∂Ωₙ) + ## Reinitialize facetvalues for this boundary facet + reinit!(facetvalues, fc) + ## Compute boundary facet surface integrals contribution + assemble_boundary!(fe, facetvalues) + ## Assemble fe into f + assemble!(f, celldofs(fc), fe) + end + return K, f +end +K, f = assemble_global(cellvalues, facetvalues, interfacevalues, K, dh, order, dim); +#md nothing # hide + +# ### Solution of the system +# +# The solution of the system is independent of the discontinuous discretization and the +# application of constraints, linear solve, and exporting is done as usual. + +apply!(K, f, ch) +u = K \ f; +VTKGridFile("dg_heat_equation", dh) do vtk + write_solution(vtk, dh, u) +end; + +## test the result #src +using Test #src +@test norm(u) ≈ 27.888811116291766 #src + +#md # ## References +#md # ```@bibliography +#md # Pages = ["dg_heat_equation.md"] +#md # Canonical = false +#md # ``` + +#md # ## [Plain program](@id heat_equation-DG-plain-program) +#md # +#md # Here follows a version of the program without any comments. +#md # The file is also available here: [`dg_heat_equation.jl`](dg_heat_equation.jl). +#md # +#md # ```julia +#md # @__CODE__ +#md # ``` diff --git a/previews/PR798/literate-tutorials/dg_heat_equation.png b/previews/PR798/literate-tutorials/dg_heat_equation.png new file mode 100644 index 0000000000..a3da16ad4a Binary files /dev/null and b/previews/PR798/literate-tutorials/dg_heat_equation.png differ diff --git a/previews/PR798/literate-tutorials/heat_equation.jl b/previews/PR798/literate-tutorials/heat_equation.jl new file mode 100644 index 0000000000..a81f8f75eb --- /dev/null +++ b/previews/PR798/literate-tutorials/heat_equation.jl @@ -0,0 +1,231 @@ +# # [Heat equation](@id tutorial-heat-equation) +# +# ![](heat_square.png) +# +# *Figure 1*: Temperature field on the unit square with an internal uniform heat source +# solved with homogeneous Dirichlet boundary conditions on the boundary. +# +#- +#md # !!! tip +#md # This example is also available as a Jupyter notebook: +#md # [`heat_equation.ipynb`](@__NBVIEWER_ROOT_URL__/tutorials/heat_equation.ipynb). +#- +# +# ## Introduction +# +# The heat equation is the "Hello, world!" equation of finite elements. +# Here we solve the equation on a unit square, with a uniform internal source. +# The strong form of the (linear) heat equation is given by +# +# ```math +# -\nabla \cdot (k \nabla u) = f \quad \textbf{x} \in \Omega, +# ``` +# +# where $u$ is the unknown temperature field, $k$ the heat conductivity, +# $f$ the heat source and $\Omega$ the domain. For simplicity we set $f = 1$ +# and $k = 1$. We will consider homogeneous Dirichlet boundary conditions such that +# ```math +# u(\textbf{x}) = 0 \quad \textbf{x} \in \partial \Omega, +# ``` +# where $\partial \Omega$ denotes the boundary of $\Omega$. +# The resulting weak form is given given as follows: Find ``u \in \mathbb{U}`` such that +# ```math +# \int_{\Omega} \nabla \delta u \cdot \nabla u \ d\Omega = \int_{\Omega} \delta u \ d\Omega \quad \forall \delta u \in \mathbb{T}, +# ``` +# where $\delta u$ is a test function, and where $\mathbb{U}$ and $\mathbb{T}$ are suitable +# trial and test function sets, respectively. +#- +# ## Commented Program +# +# Now we solve the problem in Ferrite. What follows is a program spliced with comments. +#md # The full program, without comments, can be found in the next [section](@ref heat_equation-plain-program). +# +# First we load Ferrite, and some other packages we need +using Ferrite, SparseArrays +# We start by generating a simple grid with 20x20 quadrilateral elements +# using `generate_grid`. The generator defaults to the unit square, +# so we don't need to specify the corners of the domain. +grid = generate_grid(Quadrilateral, (20, 20)); + +# ### Trial and test functions +# A `CellValues` facilitates the process of evaluating values and gradients of +# test and trial functions (among other things). To define +# this we need to specify an interpolation space for the shape functions. +# We use Lagrange functions +# based on the two-dimensional reference quadrilateral. We also define a quadrature rule based on +# the same reference element. We combine the interpolation and the quadrature rule +# to a `CellValues` object. +ip = Lagrange{RefQuadrilateral, 1}() +qr = QuadratureRule{RefQuadrilateral}(2) +cellvalues = CellValues(qr, ip); + +# ### Degrees of freedom +# Next we need to define a `DofHandler`, which will take care of numbering +# and distribution of degrees of freedom for our approximated fields. +# We create the `DofHandler` and then add a single scalar field called `:u` based on +# our interpolation `ip` defined above. +# Lastly we `close!` the `DofHandler`, it is now that the dofs are distributed +# for all the elements. +dh = DofHandler(grid) +add!(dh, :u, ip) +close!(dh); + +# Now that we have distributed all our dofs we can create our tangent matrix, +# using `allocate_matrix`. This function returns a sparse matrix +# with the correct entries stored. +K = allocate_matrix(dh) + +# ### Boundary conditions +# In Ferrite constraints like Dirichlet boundary conditions +# are handled by a `ConstraintHandler`. +ch = ConstraintHandler(dh); + +# Next we need to add constraints to `ch`. For this problem we define +# homogeneous Dirichlet boundary conditions on the whole boundary, i.e. +# the `union` of all the facet sets on the boundary. +∂Ω = union( + getfacetset(grid, "left"), + getfacetset(grid, "right"), + getfacetset(grid, "top"), + getfacetset(grid, "bottom"), +); + +# Now we are set up to define our constraint. We specify which field +# the condition is for, and our combined facet set `∂Ω`. The last +# argument is a function of the form $f(\textbf{x})$ or $f(\textbf{x}, t)$, +# where $\textbf{x}$ is the spatial coordinate and +# $t$ the current time, and returns the prescribed value. Since the boundary condition in +# this case do not depend on time we define our function as $f(\textbf{x}) = 0$, i.e. +# no matter what $\textbf{x}$ we return $0$. When we have +# specified our constraint we `add!` it to `ch`. +dbc = Dirichlet(:u, ∂Ω, (x, t) -> 0) +add!(ch, dbc); + +# Finally we also need to `close!` our constraint handler. When we call `close!` +# the dofs corresponding to our constraints are calculated and stored +# in our `ch` object. +close!(ch) + +# Note that if one or more of the constraints are time dependent we would use +# [`update!`](@ref) to recompute prescribed values in each new timestep. + +# ### Assembling the linear system +# +# Now we have all the pieces needed to assemble the linear system, $K u = f$. +# Assembling of the global system is done by looping over all the elements in order to +# compute the element contributions ``K_e`` and ``f_e``, which are then assembled to the +# appropriate place in the global ``K`` and ``f``. +# +# #### Element assembly +# We define the function `assemble_element!` (see below) which computes the contribution for +# an element. The function takes pre-allocated `ke` and `fe` (they are allocated once and +# then reused for all elements) so we first need to make sure that they are all zeroes at +# the start of the function by using `fill!`. Then we loop over all the quadrature points, +# and for each quadrature point we loop over all the (local) shape functions. We need the +# value and gradient of the test function, `δu` and also the gradient of the trial function +# `u`. We get all of these from `cellvalues`. +# +# !!! note "Notation" +# Comparing with the brief finite element introduction in [Introduction to FEM](@ref), +# the variables `δu`, `∇δu` and `∇u` are actually $\phi_i(\textbf{x}_q)$, $\nabla +# \phi_i(\textbf{x}_q)$ and $\nabla \phi_j(\textbf{x}_q)$, i.e. the evaluation of the +# trial and test functions in the quadrature point ``\textbf{x}_q``. However, to +# underline the strong parallel between the weak form and the implementation, this +# example uses the symbols appearing in the weak form. + +function assemble_element!(Ke::Matrix, fe::Vector, cellvalues::CellValues) + n_basefuncs = getnbasefunctions(cellvalues) + ## Reset to 0 + fill!(Ke, 0) + fill!(fe, 0) + ## Loop over quadrature points + for q_point in 1:getnquadpoints(cellvalues) + ## Get the quadrature weight + dΩ = getdetJdV(cellvalues, q_point) + ## Loop over test shape functions + for i in 1:n_basefuncs + δu = shape_value(cellvalues, q_point, i) + ∇δu = shape_gradient(cellvalues, q_point, i) + ## Add contribution to fe + fe[i] += δu * dΩ + ## Loop over trial shape functions + for j in 1:n_basefuncs + ∇u = shape_gradient(cellvalues, q_point, j) + ## Add contribution to Ke + Ke[i, j] += (∇δu ⋅ ∇u) * dΩ + end + end + end + return Ke, fe +end +#md nothing # hide + +# #### Global assembly +# We define the function `assemble_global` to loop over the elements and do the global +# assembly. The function takes our `cellvalues`, the sparse matrix `K`, and our DofHandler +# as input arguments and returns the assembled global stiffness matrix, and the assembled +# global force vector. We start by allocating `Ke`, `fe`, and the global force vector `f`. +# We also create an assembler by using `start_assemble`. The assembler lets us assemble into +# `K` and `f` efficiently. We then start the loop over all the elements. In each loop +# iteration we reinitialize `cellvalues` (to update derivatives of shape functions etc.), +# compute the element contribution with `assemble_element!`, and then assemble into the +# global `K` and `f` with `assemble!`. +# +# !!! note "Notation" +# Comparing again with [Introduction to FEM](@ref), `f` and `u` correspond to +# $\underline{\hat{f}}$ and $\underline{\hat{u}}$, since they represent the discretized +# versions. However, through the code we use `f` and `u` instead to reflect the strong +# connection between the weak form and the Ferrite implementation. + +function assemble_global(cellvalues::CellValues, K::SparseMatrixCSC, dh::DofHandler) + ## Allocate the element stiffness matrix and element force vector + n_basefuncs = getnbasefunctions(cellvalues) + Ke = zeros(n_basefuncs, n_basefuncs) + fe = zeros(n_basefuncs) + ## Allocate global force vector f + f = zeros(ndofs(dh)) + ## Create an assembler + assembler = start_assemble(K, f) + ## Loop over all cels + for cell in CellIterator(dh) + ## Reinitialize cellvalues for this cell + reinit!(cellvalues, cell) + ## Compute element contribution + assemble_element!(Ke, fe, cellvalues) + ## Assemble Ke and fe into K and f + assemble!(assembler, celldofs(cell), Ke, fe) + end + return K, f +end +#md nothing # hide + +# ### Solution of the system +# The last step is to solve the system. First we call `assemble_global` +# to obtain the global stiffness matrix `K` and force vector `f`. +K, f = assemble_global(cellvalues, K, dh); + +# To account for the boundary conditions we use the `apply!` function. +# This modifies elements in `K` and `f` respectively, such that +# we can get the correct solution vector `u` by using `\`. +apply!(K, f, ch) +u = K \ f; + +# ### Exporting to VTK +# To visualize the result we export the grid and our field `u` +# to a VTK-file, which can be viewed in e.g. [ParaView](https://www.paraview.org/). +VTKGridFile("heat_equation", dh) do vtk + write_solution(vtk, dh, u) +end + +## test the result #src +using Test #src +@test norm(u) ≈ 3.307743912641305 #src + +#md # ## [Plain program](@id heat_equation-plain-program) +#md # +#md # Here follows a version of the program without any comments. +#md # The file is also available here: [`heat_equation.jl`](heat_equation.jl). +#md # +#md # ```julia +#md # @__CODE__ +#md # ``` diff --git a/previews/PR798/literate-tutorials/heat_equation_hdiv.jl b/previews/PR798/literate-tutorials/heat_equation_hdiv.jl new file mode 100644 index 0000000000..7fceeb6ad2 --- /dev/null +++ b/previews/PR798/literate-tutorials/heat_equation_hdiv.jl @@ -0,0 +1,234 @@ +# # [Heat equation - Mixed H(div) conforming formulation)](@id tutorial-heat-equation-hdiv) +# As an alternative to the standard formulation for solving the heat equation used in +# the [heat equation tutorial](@ref tutorial-heat-equation), we can used a mixed formulation +# where both the temperature, $u(\mathbf{x})$, and the heat flux, $\boldsymbol{q}(\boldsymbol{x})$, +# are primary variables. From a theoretical standpoint, there are many details on e.g. which combinations +# of interpolations that are stable. See e.g. [Gatica2014](@cite) and [Boffi2013](@cite) for further reading. +# This tutorial is based on the theory in +# [Fenics' mixed poisson example](https://fenicsproject.org/olddocs/dolfin/1.4.0/python/demo/documented/mixed-poisson/python/documentation.html). +# +# ![Temperature solution](https://raw.githubusercontent.com/Ferrite-FEM/Ferrite.jl/refs/heads/gh-pages/assets/heat_equation_hdiv.png) +# **Figure:** Temperature distribution considering a central part with lower heat conductivity. +# +# The advantage with the mixed formulation is that the heat flux is approximated better. However, the +# temperature becomes discontinuous where the conductivity is discontinuous. +# +# ## Theory +# We start with the strong form of the heat equation: Find the temperature, $u(\boldsymbol{x})$, and heat flux, $\boldsymbol{q}(x)$, +# such that +# ```math +# \begin{align*} +# \boldsymbol{\nabla}\cdot \boldsymbol{q} &= h(\boldsymbol{x}), \quad \forall \boldsymbol{x} \in \Omega \\ +# \boldsymbol{q}(\boldsymbol{x}) &= - k\ \boldsymbol{\nabla} u(\boldsymbol{x}), \quad \forall \boldsymbol{x} \in \Omega \\ +# \boldsymbol{q}(\boldsymbol{x})\cdot \boldsymbol{n}(\boldsymbol{x}) &= q_n, \quad \forall \boldsymbol{x} \in \Gamma_\mathrm{N}\\ +# u(\boldsymbol{x}) &= u_\mathrm{D}, \quad \forall \boldsymbol{x} \in \Gamma_\mathrm{D} +# \end{align*} +# ``` +# +# From this strong form, we can formulate the weak form as a mixed formulation. +# Find $u \in \mathbb{U}$ and $\boldsymbol{q}\in\mathbb{Q}$ such that +# ```math +# \begin{align*} +# \int_{\Omega} \delta u [\boldsymbol{\nabla} \cdot \boldsymbol{q}]\ \mathrm{d}\Omega &= \int_\Omega \delta u h\ \mathrm{d}\Omega, \quad \forall\ \delta u \in \delta\mathbb{U} \\ +# \int_{\Omega} \boldsymbol{\delta q} \cdot \boldsymbol{q}\ \mathrm{d}\Omega &= -\int_\Omega \boldsymbol{\delta q} \cdot [k\ \boldsymbol{\nabla} u]\ \mathrm{d}\Omega \\ +# \int_{\Omega} \boldsymbol{\delta q} \cdot \boldsymbol{q}\ \mathrm{d}\Omega - \int_{\Omega} [\boldsymbol{\nabla} \cdot \boldsymbol{\delta q}] k u \ \mathrm{d}\Omega &= +# -\int_\Gamma \boldsymbol{\delta q} \cdot \boldsymbol{n} k\ u\ \mathrm{d}\Omega, \quad \forall\ \boldsymbol{\delta q} \in \delta\mathbb{Q} +# \end{align*} +# ``` +# where we have the function spaces, +# ```math +# \begin{align*} +# \mathbb{U} &= \delta\mathbb{U} = L^2 \\ +# \mathbb{Q} &= \lbrace \boldsymbol{q} \in H(\mathrm{div}) \text{ such that } \boldsymbol{q}\cdot\boldsymbol{n} = q_\mathrm{n} \text{ on } \Gamma_\mathrm{D}\rbrace \\ +# \delta\mathbb{Q} &= \lbrace \boldsymbol{q} \in H(\mathrm{div}) \text{ such that } \boldsymbol{q}\cdot\boldsymbol{n} = 0 \text{ on } \Gamma_\mathrm{D}\rbrace +# \end{align*} +# ``` +# A stable choice of finite element spaces for this problem on grid with triangles is using +# * `DiscontinuousLagrange{RefTriangle, k-1}` for approximating $L^2$ +# * `BrezziDouglasMarini{RefTriangle, k}` for approximating $H(\mathrm{div})$ +# +# We will also investigate the consequences of using $H^1$ `Lagrange` instead of $H(\mathrm{div})$ interpolations. +# +# ## Commented Program +# +# Now we solve the problem in Ferrite. What follows is a program spliced with comments. +# +# First we load Ferrite, +using Ferrite +# And define our grid, representing a channel with a central part having a lower +# conductivity, $k$, than the surrounding. +function create_grid(ny::Int) + width = 10.0 + length = 40.0 + center_width = 5.0 + center_length = 20.0 + upper_right = Vec((length / 2, width / 2)) + grid = generate_grid(Triangle, (round(Int, ny * length / width), ny), -upper_right, upper_right) + addcellset!(grid, "center", x -> abs(x[1]) < center_length / 2 && abs(x[2]) < center_width / 2) + addcellset!(grid, "around", setdiff(1:getncells(grid), getcellset(grid, "center"))) + return grid +end + +grid = create_grid(100) + +# ### Setup +# We define one `CellValues` for each field which share the same quadrature rule. +ip_geo = geometric_interpolation(getcelltype(grid)) +ipu = DiscontinuousLagrange{RefTriangle, 0}() +ipq = Ferrite.BrezziDouglasMarini{2, RefTriangle, 1}() +qr = QuadratureRule{RefTriangle}(2) +cellvalues = (u = CellValues(qr, ipu, ip_geo), q = CellValues(qr, ipq, ip_geo)) + +# Distribute the degrees of freedom +dh = DofHandler(grid) +add!(dh, :u, ipu) +add!(dh, :q, ipq) +close!(dh); + +# In this problem, we have a zero temperature on the boundary, Γ, which is enforced +# via zero Neumann boundary conditions. Hence, we don't need a `Constrainthandler`. +Γ = union((getfacetset(grid, name) for name in ("left", "right", "bottom", "top"))...) + +# ### Element implementation + +function assemble_element!(Ke::Matrix, fe::Vector, cv::NamedTuple, dr::NamedTuple, k::Number) + cvu = cv[:u] + cvq = cv[:q] + dru = dr[:u] + drq = dr[:q] + h = 1.0 # Heat source + ## Loop over quadrature points + for q_point in 1:getnquadpoints(cvu) + ## Get the quadrature weight + dΩ = getdetJdV(cvu, q_point) + ## Loop over test shape functions + for (iu, Iu) in pairs(dru) + δNu = shape_value(cvu, q_point, iu) + ## Add contribution to fe + fe[Iu] += δNu * h * dΩ + ## Loop over trial shape functions + for (jq, Jq) in pairs(drq) + div_Nq = shape_divergence(cvq, q_point, jq) + ## Add contribution to Ke + Ke[Iu, Jq] += (δNu * div_Nq) * dΩ + end + end + for (iq, Iq) in pairs(drq) + δNq = shape_value(cvq, q_point, iq) + div_δNq = shape_divergence(cvq, q_point, iq) + for (ju, Ju) in pairs(dru) + Nu = shape_value(cvu, q_point, ju) + Ke[Iq, Ju] -= div_δNq * k * Nu * dΩ + end + for (jq, Jq) in pairs(drq) + Nq = shape_value(cvq, q_point, jq) + Ke[Iq, Jq] += (δNq ⋅ Nq) * dΩ + end + end + end + return Ke, fe +end +#md nothing # hide + +# ### Global assembly + +function assemble_global(cellvalues, dh::DofHandler) + grid = dh.grid + ## Allocate the element stiffness matrix and element force vector + dofranges = (u = dof_range(dh, :u), q = dof_range(dh, :q)) + ncelldofs = ndofs_per_cell(dh) + Ke = zeros(ncelldofs, ncelldofs) + fe = zeros(ncelldofs) + ## Allocate global system matrix and vector + K = allocate_matrix(dh) + f = zeros(ndofs(dh)) + ## Create an assembler + assembler = start_assemble(K, f) + x = copy(getcoordinates(grid, 1)) + dofs = copy(celldofs(dh, 1)) + ## Loop over all cells + for (cells, k) in ( + (getcellset(grid, "center"), 0.1), + (getcellset(grid, "around"), 1.0), + ) + for cellnr in cells + ## Reinitialize cellvalues for this cell + cell = getcells(grid, cellnr) + getcoordinates!(x, grid, cell) + celldofs!(dofs, dh, cellnr) + reinit!(cellvalues[:u], cell, x) + reinit!(cellvalues[:q], cell, x) + ## Reset to 0 + fill!(Ke, 0) + fill!(fe, 0) + ## Compute element contribution + assemble_element!(Ke, fe, cellvalues, dofranges, k) + ## Assemble Ke and fe into K and f + assemble!(assembler, dofs, Ke, fe) + end + end + return K, f +end +#md nothing # hide + +# ### Solution of the system +K, f = assemble_global(cellvalues, dh); +u = K \ f; + +# ### Exporting to VTK +# Currently, exporting discontinuous interpolations is not supported. +# Since in this case, we have a single temperature degree of freedom +# per cell, we work around this by calculating the per-cell temperature. +temperature_dof = first(dof_range(dh, :u)) +u_cells = map(1:getncells(grid)) do i + u[celldofs(dh, i)[temperature_dof]] +end +VTKGridFile("heat_equation_hdiv", dh) do vtk + write_cell_data(vtk, u_cells, "temperature") +end + +# ## Postprocess the total flux +# We applied a constant unit heat source to the body, and the +# total heat flux exiting across the boundary should therefore +# match the area for the considered stationary case. +function calculate_flux(dh, boundary_facets, ip, a) + grid = dh.grid + qr = FacetQuadratureRule{RefTriangle}(4) + ip_geo = geometric_interpolation(getcelltype(grid)) + fv = FacetValues(qr, ip, ip_geo) + + dofrange = dof_range(dh, :q) + flux = 0.0 + dofs = celldofs(dh, 1) + ae = zeros(length(dofs)) + x = getcoordinates(grid, 1) + for (cellnr, facetnr) in boundary_facets + getcoordinates!(x, grid, cellnr) + cell = getcells(grid, cellnr) + celldofs!(dofs, dh, cellnr) + map!(i -> a[i], ae, dofs) + reinit!(fv, cell, x, facetnr) + for q_point in 1:getnquadpoints(fv) + dΓ = getdetJdV(fv, q_point) + n = getnormal(fv, q_point) + q = function_value(fv, q_point, ae, dofrange) + flux += (q ⋅ n) * dΓ + end + end + return flux +end + +println("Outward flux: ", calculate_flux(dh, Γ, ipq, u)) + +# Note that this is not the case for the standard [Heat equation](@ref tutorial-heat-equation), +# as the flux terms are less accurately approximated. A fine mesh is required to converge in that case. +# However, the present example gives a worse approximation of the temperature field. + +#md # ## [Plain program](@id tutorial-heat-equation-hdiv-plain) +#md # +#md # Here follows a version of the program without any comments. +#md # The file is also available here: [`heat_equation_hdiv.jl`](heat_equation_hdiv.jl). +#md # +#md # ```julia +#md # @__CODE__ +#md # ``` diff --git a/previews/PR798/literate-tutorials/heat_square.png b/previews/PR798/literate-tutorials/heat_square.png new file mode 100644 index 0000000000..1fc753e9ec Binary files /dev/null and b/previews/PR798/literate-tutorials/heat_square.png differ diff --git a/previews/PR798/literate-tutorials/hyperelasticity.jl b/previews/PR798/literate-tutorials/hyperelasticity.jl new file mode 100644 index 0000000000..6ab05b3138 --- /dev/null +++ b/previews/PR798/literate-tutorials/hyperelasticity.jl @@ -0,0 +1,449 @@ +# # [Hyperelasticity](@id tutorial-hyperelasticity) +# +# **Keywords**: *hyperelasticity*, *finite strain*, *large deformations*, *Newton's method*, +# *conjugate gradient*, *automatic differentiation* +# +# ![hyperelasticity.png](hyperelasticity.png) +# +# *Figure 1*: Cube loaded in torsion modeled with a hyperelastic material model and +# finite strain. +# +#- +#md # !!! tip +#md # This example is also available as a Jupyter notebook: +#md # [`hyperelasticity.ipynb`](@__NBVIEWER_ROOT_URL__/tutorials/hyperelasticity.ipynb). +#- +# ## Introduction +# +# In this example we will solve a problem in a finite strain setting using an +# hyperelastic material model. In order to compute the stress we will use automatic +# differentiation, to solve the non-linear system we use Newton's +# method, and for solving the Newton increment we use conjugate gradients. +# +# The weak form is expressed in terms of the first Piola-Kirchoff stress ``\mathbf{P}`` +# as follows: Find ``\mathbf{u} \in \mathbb{U}`` such that +# +# ```math +# \int_{\Omega} [\nabla_{\mathbf{X}} \delta \mathbf{u}] : \mathbf{P}(\mathbf{u})\ \mathrm{d}\Omega = +# \int_{\Omega} \delta \mathbf{u} \cdot \mathbf{b}\ \mathrm{d}\Omega + \int_{\Gamma_\mathrm{N}} +# \delta \mathbf{u} \cdot \mathbf{t}\ \mathrm{d}\Gamma +# \quad \forall \delta \mathbf{u} \in \mathbb{U}^0, +# ``` +# +# where ``\mathbf{u}`` is the unknown displacement field, ``\mathbf{b}`` is the body force acting +# on the reference domain, ``\mathbf{t}`` is the traction acting on the Neumann part of the reference +# domain's boundary, and where ``\mathbb{U}`` and ``\mathbb{U}^0`` are suitable trial and test sets. +# ``\Omega`` denotes the reference (sometimes also called *initial* or *material*) domain. +# Gradients are defined with respect to the reference domain, here denoted with an ``\mathbf{X}``. +# Formally this is expressed as ``(\nabla_{\mathbf{X}} \bullet)_{ij} := \frac{\partial(\bullet)_i}{\partial X_j}``. +# Note that for large deformation problems it is also possible that gradients and integrals +# are defined on the deformed (sometimes also called *current* or *spatial*) domain, depending +# on the specific formulation. +# +# The specific problem we will solve in this example is the cube from Figure 1: On one side +# we apply a rotation using Dirichlet boundary conditions, on the opposite side we fix the +# displacement with a homogeneous Dirichlet boundary condition, and on the remaining four +# sides we apply a traction in the normal direction of the surface. In addition, a body +# force is applied in one direction. +# +# In addition to Ferrite.jl and Tensors.jl, this examples uses +# [TimerOutputs.jl](https://github.com/KristofferC/TimerOutputs.jl) for timing the program +# and print a summary at the end, +# [ProgressMeter.jl](https://github.com/timholy/ProgressMeter.jl) for showing a simple +# progress bar, and +# [IterativeSolvers.jl](https://github.com/JuliaLinearAlgebra/IterativeSolvers.jl) for solving +# the linear system using conjugate gradients. + +using Ferrite, Tensors, TimerOutputs, ProgressMeter, IterativeSolvers + +# ## Hyperelastic material model +# +# The stress can be derived from an energy potential, defined in +# terms of the right Cauchy-Green tensor ``\mathbf{C} = \mathbf{F}^{\mathrm{T}} \cdot \mathbf{F}``, +# where ``\mathbf{F} = \mathbf{I} + \nabla_{\mathbf{X}} \mathbf{u}`` is the deformation gradient. +# We shall use the compressible neo-Hookean model from [Wikipedia](https://en.wikipedia.org/wiki/Neo-Hookean_solid) with the potential +# +# ```math +# \Psi(\mathbf{C}) = \underbrace{\frac{\mu}{2} (I_1 - 3)}_{W(\mathbf{C})} \underbrace{- {\mu} \ln(J) + \frac{\lambda}{2} (J - 1)^2}_{U(J)}, +# ``` +# +# where ``I_1 = \mathrm{tr}(\mathbf{C})`` is the first invariant, ``J = \sqrt{\det(\mathbf{C})}`` +# and ``\mu`` and ``\lambda`` material parameters. + +# !!! details "Extra details on compressible neo-Hookean formulations" +# The Neo-Hooke model is only a well defined terminology in the incompressible case. +# Thus, only $W(\mathbf{C})$ specifies the neo-Hookean behavior, the volume penalty $U(J)$ can vary in different formulations. +# In order to obtain a well-posed problem, it is crucial to choose a convex formulation of $U(J)$. +# Other examples for $U(J)$ can be found, e.g. in [Hol:2000:nsm; Eq. (6.138)](@cite) +# ```math +# \beta^{-2} (\beta \ln J + J^{-\beta} -1) +# ``` +# where [SimMie:1992:act; Eq. (2.37)](@cite) published a non-generalized version with $\beta=-2$. +# This shows the possible variety of $U(J)$ while all of them refer to compressible neo-Hookean models. +# Sometimes the modified first invariant $\overline{I}_1=\frac{I_1}{I_3^{1/3}}$ is used in $W(\mathbf{C})$ instead of $I_1$. + +# From the potential we obtain the second Piola-Kirchoff stress ``\mathbf{S}`` as +# +# ```math +# \mathbf{S} = 2 \frac{\partial \Psi}{\partial \mathbf{C}}, +# ``` +# +# and the tangent of ``\mathbf{S}`` as +# +# ```math +# \frac{\partial \mathbf{S}}{\partial \mathbf{C}} = 2 \frac{\partial^2 \Psi}{\partial \mathbf{C}^2}. +# ``` +# +# Finally, for the finite element problem we need ``\mathbf{P}`` and +# ``\frac{\partial \mathbf{P}}{\partial \mathbf{F}}``, which can be +# obtained by using the following relations: +# +# ```math +# \begin{align*} +# \mathbf{P} &= \mathbf{F} \cdot \mathbf{S},\\ +# \frac{\partial \mathbf{P}}{\partial \mathbf{F}} &= \mathbf{I} \bar{\otimes} \mathbf{S} + 2\, \mathbf{F} \cdot +# \frac{\partial \mathbf{S}}{\partial \mathbf{C}} : \mathbf{F}^\mathrm{T} \bar{\otimes} \mathbf{I}. +# \end{align*} +# ``` + +# !!! details "Derivation of $\partial \mathbf{P} / \partial \mathbf{F}$" +# *Tip:* See [knutam.github.io/tensors](https://knutam.github.io/tensors/Theory/IndexNotation/) for +# an explanation of the index notation used in this derivation. +#md # +# Using the product rule, the chain rule, and the relations ``\mathbf{P} = \mathbf{F} \cdot +# \mathbf{S}`` and ``\mathbf{C} = \mathbf{F}^\mathrm{T} \cdot \mathbf{F}``, we obtain the +# following: +# ```math +# \begin{aligned} +# \frac{\partial P_{ij}}{\partial F_{kl}} &= +# \frac{\partial (F_{im}S_{mj})}{\partial F_{kl}} \\ &= +# \frac{\partial F_{im}}{\partial F_{kl}}S_{mj} + +# F_{im}\frac{\partial S_{mj}}{\partial F_{kl}} \\ &= +# \delta_{ik}\delta_{ml} S_{mj} + +# F_{im}\frac{\partial S_{mj}}{\partial C_{no}}\frac{\partial C_{no}}{\partial F_{kl}} \\ &= +# \delta_{ik}S_{lj} + +# F_{im}\frac{\partial S_{mj}}{\partial C_{no}} +# \frac{\partial (F^\mathrm{T}_{np}F_{po})}{\partial F_{kl}} \\ &= +# \delta_{ik}S^\mathrm{T}_{jl} + +# F_{im}\frac{\partial S_{mj}}{\partial C_{no}} +# \left( +# \frac{\partial F^\mathrm{T}_{np}}{\partial F_{kl}}F_{po} + +# F^\mathrm{T}_{np}\frac{\partial F_{po}}{\partial F_{kl}} +# \right) \\ &= +# \delta_{ik}S_{jl} + +# F_{im}\frac{\partial S_{mj}}{\partial C_{no}} +# (\delta_{nl} \delta_{pk} F_{po} + F^\mathrm{T}_{np}\delta_{pk} \delta_{ol}) \\ &= +# \delta_{ik}S_{lj} + +# F_{im}\frac{\partial S_{mj}}{\partial C_{no}} +# (F^\mathrm{T}_{ok} \delta_{nl} + F^\mathrm{T}_{nk} \delta_{ol}) \\ &= +# \delta_{ik}S_{jl} + +# 2\, F_{im} \frac{\partial S_{mj}}{\partial C_{no}} +# F^\mathrm{T}_{nk} \delta_{ol} \\ +# \frac{\partial \mathbf{P}}{\partial \mathbf{F}} &= +# \mathbf{I}\bar{\otimes}\mathbf{S} + +# 2\, \mathbf{F} \cdot \frac{\partial \mathbf{S}}{\partial \mathbf{C}} +# : \mathbf{F}^\mathrm{T} \bar{\otimes} \mathbf{I}, +# \end{aligned} +# ``` +# where we used the fact that ``\mathbf{S}`` is symmetric (``S_{lj} = S_{jl}``) and that +# ``\frac{\partial \mathbf{S}}{\partial \mathbf{C}}`` is *minor* symmetric (``\frac{\partial +# S_{mj}}{\partial C_{no}} = \frac{\partial S_{mj}}{\partial C_{on}}``). + +# ### Implementation of material model using automatic differentiation +# We can implement the material model as follows, where we utilize automatic differentiation +# for the stress and the tangent, and thus only define the potential: + +struct NeoHooke + μ::Float64 + λ::Float64 +end + +function Ψ(C, mp::NeoHooke) + μ = mp.μ + λ = mp.λ + Ic = tr(C) + J = sqrt(det(C)) + return μ / 2 * (Ic - 3 - 2 * log(J)) + λ / 2 * (J - 1)^2 +end + +function constitutive_driver(C, mp::NeoHooke) + ## Compute all derivatives in one function call + ∂²Ψ∂C², ∂Ψ∂C = Tensors.hessian(y -> Ψ(y, mp), C, :all) + S = 2.0 * ∂Ψ∂C + ∂S∂C = 2.0 * ∂²Ψ∂C² + return S, ∂S∂C +end; + +## Test the derivation #src +using Test #src +F = rand(Tensor{2, 3}) #src +mp = NeoHooke(rand(2)...) #src +S, ∂S∂C = constitutive_driver(tdot(F), mp) #src +P = F ⋅ S #src +I = one(S) #src +∂P∂F = otimesu(I, S) + 2 * F ⋅ ∂S∂C ⊡ otimesu(F', I) #src +∂P∂F_ad, P_ad = Tensors.hessian(x -> Ψ(tdot(x), mp), F, :all) #src +@test P ≈ P_ad #src +@test ∂P∂F ≈ ∂P∂F_ad #src +nothing #src + +# ## Newton's method +# +# As mentioned above, to deal with the non-linear weak form we first linearize +# the problem such that we can apply Newton's method, and then apply the FEM to +# discretize the problem. Skipping a detailed derivation, Newton's method can +# be expressed as: +# Given some initial guess for the degrees of freedom ``\underline{u}^0``, find a sequence +# ``\underline{u}^{k}`` by iterating +# +# ```math +# \underline{u}^{k+1} = \underline{u}^{k} - \Delta \underline{u}^{k} +# ``` +# +# until some termination condition has been met. Therein we determine ``\Delta \underline{u}^{k}`` +# from the linearized problem +# +# ```math +# \underline{\underline{K}}(\underline{u}^{k}) \Delta \underline{u}^{k} = \underline{g}(\underline{u}^{k}) +# ``` +# +# where the global residual, $\underline{g}$, and the Jacobi matrix, +# $\underline{\underline{K}} = \frac{\partial \underline{g}}{\partial \underline{u}}$, are +# evaluated at the current guess $\underline{u}^k$. The entries of $\underline{g}$ are given +# by +# +# ```math +# (\underline{g})_{i} = \int_{\Omega} [\nabla_{\mathbf{X}} \delta \mathbf{u}_{i}] : +# \mathbf{P} \, \mathrm{d} \Omega - \int_{\Omega} \delta \mathbf{u}_{i} \cdot \mathbf{b} \, +# \mathrm{d} \Omega - \int_{\Gamma_\mathrm{N}} \delta \mathbf{u}_i \cdot \mathbf{t}\ +# \mathrm{d}\Gamma, +# ``` +# +# and the entries of $\underline{\underline{K}}$ are given by +# +# ```math +# (\underline{\underline{K}})_{ij} = \int_{\Omega} [\nabla_{\mathbf{X}} \delta +# \mathbf{u}_{i}] : \frac{\partial \mathbf{P}}{\partial \mathbf{F}} : [\nabla_{\mathbf{X}} +# \delta \mathbf{u}_{j}] \, \mathrm{d} \Omega. +# ``` +# +# +# A detailed derivation can be found in every continuum mechanics book, which has a +# chapter about finite elasticity theory. We used "Nonlinear solid mechanics: a continuum +# approach for engineering science." by [Hol:2000:nsm; Chapter 8](@citet) as a reference. +# +# ## Finite element assembly +# +# The element routine for assembling the residual and tangent stiffness is implemented +# as usual, with loops over quadrature points and shape functions: + +function assemble_element!(ke, ge, cell, cv, fv, mp, ue, ΓN) + ## Reinitialize cell values, and reset output arrays + reinit!(cv, cell) + fill!(ke, 0.0) + fill!(ge, 0.0) + + b = Vec{3}((0.0, -0.5, 0.0)) # Body force + tn = 0.1 # Traction (to be scaled with surface normal) + ndofs = getnbasefunctions(cv) + + for qp in 1:getnquadpoints(cv) + dΩ = getdetJdV(cv, qp) + ## Compute deformation gradient F and right Cauchy-Green tensor C + ∇u = function_gradient(cv, qp, ue) + F = one(∇u) + ∇u + C = tdot(F) # F' ⋅ F + ## Compute stress and tangent + S, ∂S∂C = constitutive_driver(C, mp) + P = F ⋅ S + I = one(S) + ∂P∂F = otimesu(I, S) + 2 * F ⋅ ∂S∂C ⊡ otimesu(F', I) + + ## Loop over test functions + for i in 1:ndofs + ## Test function and gradient + δui = shape_value(cv, qp, i) + ∇δui = shape_gradient(cv, qp, i) + ## Add contribution to the residual from this test function + ge[i] += (∇δui ⊡ P - δui ⋅ b) * dΩ + + ∇δui∂P∂F = ∇δui ⊡ ∂P∂F # Hoisted computation + for j in 1:ndofs + ∇δuj = shape_gradient(cv, qp, j) + ## Add contribution to the tangent + ke[i, j] += (∇δui∂P∂F ⊡ ∇δuj) * dΩ + end + end + end + + ## Surface integral for the traction + for facet in 1:nfacets(cell) + if (cellid(cell), facet) in ΓN + reinit!(fv, cell, facet) + for q_point in 1:getnquadpoints(fv) + t = tn * getnormal(fv, q_point) + dΓ = getdetJdV(fv, q_point) + for i in 1:ndofs + δui = shape_value(fv, q_point, i) + ge[i] -= (δui ⋅ t) * dΓ + end + end + end + end + return +end; + +# Assembling global residual and tangent is also done in the usual way, just looping over +# the elements, call the element routine and assemble in the the global matrix K and +# residual g. + +function assemble_global!(K, g, dh, cv, fv, mp, u, ΓN) + n = ndofs_per_cell(dh) + ke = zeros(n, n) + ge = zeros(n) + + ## start_assemble resets K and g + assembler = start_assemble(K, g) + + ## Loop over all cells in the grid + @timeit "assemble" for cell in CellIterator(dh) + global_dofs = celldofs(cell) + ue = u[global_dofs] # element dofs + @timeit "element assemble" assemble_element!(ke, ge, cell, cv, fv, mp, ue, ΓN) + assemble!(assembler, global_dofs, ke, ge) + end + return +end; + +# Finally, we define a main function which sets up everything and then performs Newton +# iterations until convergence. + +function solve() + reset_timer!() + + ## Generate a grid + N = 10 + L = 1.0 + left = zero(Vec{3}) + right = L * ones(Vec{3}) + grid = generate_grid(Tetrahedron, (N, N, N), left, right) + + ## Material parameters + E = 10.0 + ν = 0.3 + μ = E / (2(1 + ν)) + λ = (E * ν) / ((1 + ν) * (1 - 2ν)) + mp = NeoHooke(μ, λ) + + ## Finite element base + ip = Lagrange{RefTetrahedron, 1}()^3 + qr = QuadratureRule{RefTetrahedron}(1) + qr_facet = FacetQuadratureRule{RefTetrahedron}(1) + cv = CellValues(qr, ip) + fv = FacetValues(qr_facet, ip) + + ## DofHandler + dh = DofHandler(grid) + add!(dh, :u, ip) # Add a displacement field + close!(dh) + + function rotation(X, t) + θ = pi / 3 # 60° + x, y, z = X + return t * Vec{3}( + ( + 0.0, + L / 2 - y + (y - L / 2) * cos(θ) - (z - L / 2) * sin(θ), + L / 2 - z + (y - L / 2) * sin(θ) + (z - L / 2) * cos(θ), + ) + ) + end + + dbcs = ConstraintHandler(dh) + ## Add a homogeneous boundary condition on the "clamped" edge + dbc = Dirichlet(:u, getfacetset(grid, "right"), (x, t) -> [0.0, 0.0, 0.0], [1, 2, 3]) + add!(dbcs, dbc) + dbc = Dirichlet(:u, getfacetset(grid, "left"), (x, t) -> rotation(x, t), [1, 2, 3]) + add!(dbcs, dbc) + close!(dbcs) + t = 0.5 + Ferrite.update!(dbcs, t) + + ## Neumann part of the boundary + ΓN = union( + getfacetset(grid, "top"), + getfacetset(grid, "bottom"), + getfacetset(grid, "front"), + getfacetset(grid, "back"), + ) + + ## Pre-allocation of vectors for the solution and Newton increments + _ndofs = ndofs(dh) + un = zeros(_ndofs) # previous solution vector + u = zeros(_ndofs) + Δu = zeros(_ndofs) + ΔΔu = zeros(_ndofs) + apply!(un, dbcs) + + ## Create sparse matrix and residual vector + K = allocate_matrix(dh) + g = zeros(_ndofs) + + ## Perform Newton iterations + newton_itr = -1 + NEWTON_TOL = 1.0e-8 + NEWTON_MAXITER = 30 + prog = ProgressMeter.ProgressThresh(NEWTON_TOL; desc = "Solving:") + + while true + newton_itr += 1 + ## Construct the current guess + u .= un .+ Δu + ## Compute residual and tangent for current guess + assemble_global!(K, g, dh, cv, fv, mp, u, ΓN) + ## Apply boundary conditions + apply_zero!(K, g, dbcs) + ## Compute the residual norm and compare with tolerance + normg = norm(g) + ProgressMeter.update!(prog, normg; showvalues = [(:iter, newton_itr)]) + if normg < NEWTON_TOL + break + elseif newton_itr > NEWTON_MAXITER + error("Reached maximum Newton iterations, aborting") + end + + ## Compute increment using conjugate gradients + @timeit "linear solve" IterativeSolvers.cg!(ΔΔu, K, g; maxiter = 1000) + + apply_zero!(ΔΔu, dbcs) + Δu .-= ΔΔu + end + + ## Save the solution + @timeit "export" begin + VTKGridFile("hyperelasticity", dh) do vtk + write_solution(vtk, dh, u) + end + end + + print_timer(title = "Analysis with $(getncells(grid)) elements", linechars = :ascii) + return u +end + + +# Run the simulation + +u = solve(); + +## test the result #src +using Test #src +@test norm(u) ≈ 4.761404305083876 #src + +#md # ## Plain program +#md # +#md # Here follows a version of the program without any comments. +#md # The file is also available here: [`hyperelasticity.jl`](hyperelasticity.jl). +#md # +#md # ```julia +#md # @__CODE__ +#md # ``` diff --git a/previews/PR798/literate-tutorials/hyperelasticity.png b/previews/PR798/literate-tutorials/hyperelasticity.png new file mode 100644 index 0000000000..38713fc540 Binary files /dev/null and b/previews/PR798/literate-tutorials/hyperelasticity.png differ diff --git a/previews/PR798/literate-tutorials/incompressible_elasticity.jl b/previews/PR798/literate-tutorials/incompressible_elasticity.jl new file mode 100644 index 0000000000..eda174b964 --- /dev/null +++ b/previews/PR798/literate-tutorials/incompressible_elasticity.jl @@ -0,0 +1,323 @@ +# # [Incompressible elasticity](@id tutorial-incompressible-elasticity) +# +#- +#md # !!! tip +#md # This example is also available as a Jupyter notebook: +#md # [`incompressible_elasticity.ipynb`](@__NBVIEWER_ROOT_URL__/tutorials/incompressible_elasticity.ipynb). +#- +# +# ## Introduction +# +# Mixed elements can be used to overcome locking when the material becomes +# incompressible. However, for an element to be stable, it needs to fulfill +# the LBB condition. +# In this example we will consider two different element formulations +# - linear displacement with linear pressure approximation (does *not* fulfill LBB) +# - quadratic displacement with linear pressure approximation (does fulfill LBB) +# The quadratic/linear element is also known as the Taylor-Hood element. +# We will consider Cook's Membrane with an applied traction on the right hand side. +#- +# ## Commented program +# +# What follows is a program spliced with comments. +#md # The full program, without comments, can be found in the next +#md # [section](@ref incompressible_elasticity-plain-program). +using Ferrite, Tensors +using BlockArrays, SparseArrays, LinearAlgebra + +# First we generate a simple grid, specifying the 4 corners of Cooks membrane. +function create_cook_grid(nx, ny) + corners = [ + Vec{2}((0.0, 0.0)), + Vec{2}((48.0, 44.0)), + Vec{2}((48.0, 60.0)), + Vec{2}((0.0, 44.0)), + ] + grid = generate_grid(Triangle, (nx, ny), corners) + ## facesets for boundary conditions + addfacetset!(grid, "clamped", x -> norm(x[1]) ≈ 0.0) + addfacetset!(grid, "traction", x -> norm(x[1]) ≈ 48.0) + return grid +end; + +# Next we define a function to set up our cell- and FacetValues. +function create_values(interpolation_u, interpolation_p) + ## quadrature rules + qr = QuadratureRule{RefTriangle}(3) + facet_qr = FacetQuadratureRule{RefTriangle}(3) + + ## cell and FacetValues for u + cellvalues_u = CellValues(qr, interpolation_u) + facetvalues_u = FacetValues(facet_qr, interpolation_u) + + ## cellvalues for p + cellvalues_p = CellValues(qr, interpolation_p) + + return cellvalues_u, cellvalues_p, facetvalues_u +end; + + +# We create a DofHandler, with two fields, `:u` and `:p`, +# with possibly different interpolations +function create_dofhandler(grid, ipu, ipp) + dh = DofHandler(grid) + add!(dh, :u, ipu) # displacement + add!(dh, :p, ipp) # pressure + close!(dh) + return dh +end; + +# We also need to add Dirichlet boundary conditions on the `"clamped"` facetset. +# We specify a homogeneous Dirichlet bc on the displacement field, `:u`. +function create_bc(dh) + dbc = ConstraintHandler(dh) + add!(dbc, Dirichlet(:u, getfacetset(dh.grid, "clamped"), x -> zero(x), [1, 2])) + close!(dbc) + return dbc +end; + +# The material is linear elastic, which is here specified by the shear and bulk moduli +struct LinearElasticity{T} + G::T + K::T +end + +# Now to the assembling of the stiffness matrix. This mixed formulation leads to a blocked +# element matrix. Since Ferrite does not force us to use any particular matrix type we will +# use a `BlockedArray` from `BlockArrays.jl`. + +function doassemble( + cellvalues_u::CellValues, + cellvalues_p::CellValues, + facetvalues_u::FacetValues, + K::SparseMatrixCSC, grid::Grid, dh::DofHandler, mp::LinearElasticity + ) + f = zeros(ndofs(dh)) + assembler = start_assemble(K, f) + nu = getnbasefunctions(cellvalues_u) + np = getnbasefunctions(cellvalues_p) + + fe = BlockedArray(zeros(nu + np), [nu, np]) # local force vector + ke = BlockedArray(zeros(nu + np, nu + np), [nu, np], [nu, np]) # local stiffness matrix + + ## traction vector + t = Vec{2}((0.0, 1 / 16)) + ## cache ɛdev outside the element routine to avoid some unnecessary allocations + ɛdev = [zero(SymmetricTensor{2, 2}) for i in 1:getnbasefunctions(cellvalues_u)] + + for cell in CellIterator(dh) + fill!(ke, 0) + fill!(fe, 0) + assemble_up!(ke, fe, cell, cellvalues_u, cellvalues_p, facetvalues_u, grid, mp, ɛdev, t) + assemble!(assembler, celldofs(cell), ke, fe) + end + + return K, f +end; + +# The element routine integrates the local stiffness and force vector for all elements. +# Since the problem results in a symmetric matrix we choose to only assemble the lower part, +# and then symmetrize it after the loop over the quadrature points. +function assemble_up!(Ke, fe, cell, cellvalues_u, cellvalues_p, facetvalues_u, grid, mp, ɛdev, t) + + n_basefuncs_u = getnbasefunctions(cellvalues_u) + n_basefuncs_p = getnbasefunctions(cellvalues_p) + u▄, p▄ = 1, 2 + reinit!(cellvalues_u, cell) + reinit!(cellvalues_p, cell) + + ## We only assemble lower half triangle of the stiffness matrix and then symmetrize it. + for q_point in 1:getnquadpoints(cellvalues_u) + for i in 1:n_basefuncs_u + ɛdev[i] = dev(symmetric(shape_gradient(cellvalues_u, q_point, i))) + end + dΩ = getdetJdV(cellvalues_u, q_point) + for i in 1:n_basefuncs_u + divδu = shape_divergence(cellvalues_u, q_point, i) + δu = shape_value(cellvalues_u, q_point, i) + for j in 1:i + Ke[BlockIndex((u▄, u▄), (i, j))] += 2 * mp.G * ɛdev[i] ⊡ ɛdev[j] * dΩ + end + end + + for i in 1:n_basefuncs_p + δp = shape_value(cellvalues_p, q_point, i) + for j in 1:n_basefuncs_u + divδu = shape_divergence(cellvalues_u, q_point, j) + Ke[BlockIndex((p▄, u▄), (i, j))] += -δp * divδu * dΩ + end + for j in 1:i + p = shape_value(cellvalues_p, q_point, j) + Ke[BlockIndex((p▄, p▄), (i, j))] += - 1 / mp.K * δp * p * dΩ + end + + end + end + + symmetrize_lower!(Ke) + + ## We integrate the Neumann boundary using the FacetValues. + ## We loop over all the facets in the cell, then check if the facet + ## is in our `"traction"` facetset. + for facet in 1:nfacets(cell) + if (cellid(cell), facet) ∈ getfacetset(grid, "traction") + reinit!(facetvalues_u, cell, facet) + for q_point in 1:getnquadpoints(facetvalues_u) + dΓ = getdetJdV(facetvalues_u, q_point) + for i in 1:n_basefuncs_u + δu = shape_value(facetvalues_u, q_point, i) + fe[i] += (δu ⋅ t) * dΓ + end + end + end + end + return +end + +function symmetrize_lower!(Ke) + for i in 1:size(Ke, 1) + for j in (i + 1):size(Ke, 1) + Ke[i, j] = Ke[j, i] + end + end + return +end; + +# To evaluate the stresses after solving the problem we once again loop over the cells in +# the grid. Stresses are evaluated in the quadrature points, however, for +# export/visualization you typically want values in the nodes of the mesh, or as single data +# points per cell. For the former you can project the quadrature point data to a finite +# element space (see the example with the `L2Projector` in [Post processing and +# visualization](@ref howto-postprocessing)). In this example we choose to compute the mean +# value of the stress within each cell, and thus end up with one data point per cell. The +# mean value is computed as +# ```math +# \bar{\boldsymbol{\sigma}}_i = \frac{1}{ |\Omega_i|} +# \int_{\Omega_i} \boldsymbol{\sigma}\, \mathrm{d}\Omega, \quad +# |\Omega_i| = \int_{\Omega_i} 1\, \mathrm{d}\Omega +# ``` +# where $\Omega_i$ is the domain occupied by cell number $i$, and $|\Omega_i|$ the volume +# (area) of the cell. The integrals are evaluated using numerical quadrature with the help +# of cellvalues for u and p, just like in the assembly procedure. +# +# Note that even though all strain components in the out-of-plane direction are zero (plane +# strain) the stress components are not. Specifically, $\sigma_{33}$ will be non-zero in +# this formulation. Therefore we expand the strain to a 3D tensor, and then compute the (3D) +# stress tensor. + +function compute_stresses( + cellvalues_u::CellValues, cellvalues_p::CellValues, + dh::DofHandler, mp::LinearElasticity, a::Vector + ) + ae = zeros(ndofs_per_cell(dh)) # local solution vector + u_range = dof_range(dh, :u) # local range of dofs corresponding to u + p_range = dof_range(dh, :p) # local range of dofs corresponding to p + ## Allocate storage for the stresses + σ = zeros(SymmetricTensor{2, 3}, getncells(dh.grid)) + ## Loop over the cells and compute the cell-average stress + for cc in CellIterator(dh) + ## Update cellvalues + reinit!(cellvalues_u, cc) + reinit!(cellvalues_p, cc) + ## Extract the cell local part of the solution + for (i, I) in pairs(celldofs(cc)) + ae[i] = a[I] + end + ## Loop over the quadrature points + σΩi = zero(SymmetricTensor{2, 3}) # stress integrated over the cell + Ωi = 0.0 # cell volume (area) + for qp in 1:getnquadpoints(cellvalues_u) + dΩ = getdetJdV(cellvalues_u, qp) + ## Evaluate the strain and the pressure + ε = function_symmetric_gradient(cellvalues_u, qp, ae, u_range) + p = function_value(cellvalues_p, qp, ae, p_range) + ## Expand strain to 3D + ε3D = SymmetricTensor{2, 3}((i, j) -> i < 3 && j < 3 ? ε[i, j] : 0.0) + ## Compute the stress in this quadrature point + σqp = 2 * mp.G * dev(ε3D) - one(ε3D) * p + σΩi += σqp * dΩ + Ωi += dΩ + end + ## Store the value + σ[cellid(cc)] = σΩi / Ωi + end + return σ +end; + +# Now we have constructed all the necessary components, we just need a function +# to put it all together. + +function solve(ν, interpolation_u, interpolation_p) + ## material + Emod = 1.0 + Gmod = Emod / 2(1 + ν) + Kmod = Emod * ν / ((1 + ν) * (1 - 2ν)) + mp = LinearElasticity(Gmod, Kmod) + + ## Grid, dofhandler, boundary condition + n = 50 + grid = create_cook_grid(n, n) + dh = create_dofhandler(grid, interpolation_u, interpolation_p) + dbc = create_bc(dh) + + ## CellValues + cellvalues_u, cellvalues_p, facetvalues_u = create_values(interpolation_u, interpolation_p) + + ## Assembly and solve + K = allocate_matrix(dh) + K, f = doassemble(cellvalues_u, cellvalues_p, facetvalues_u, K, grid, dh, mp) + apply!(K, f, dbc) + u = K \ f + + ## Compute the stress + σ = compute_stresses(cellvalues_u, cellvalues_p, dh, mp, u) + σvM = map(x -> √(3 / 2 * dev(x) ⊡ dev(x)), σ) # von Mise effective stress + + ## Export the solution and the stress + filename = "cook_" * + (interpolation_u == Lagrange{RefTriangle, 1}()^2 ? "linear" : "quadratic") * + "_linear" + + VTKGridFile(filename, grid) do vtk + write_solution(vtk, dh, u) + for i in 1:3, j in 1:3 + σij = [x[i, j] for x in σ] + write_cell_data(vtk, σij, "sigma_$(i)$(j)") + end + write_cell_data(vtk, σvM, "sigma von Mises") + end + return u +end +#md nothing # hide + +# We now define the interpolation for displacement and pressure. We use (scalar) Lagrange +# interpolation as a basis for both, and for the displacement, which is a vector, we +# vectorize it to 2 dimensions such that we obtain vector shape functions (and 2nd order +# tensors for the gradients). + +linear_p = Lagrange{RefTriangle, 1}() +linear_u = Lagrange{RefTriangle, 1}()^2 +quadratic_u = Lagrange{RefTriangle, 2}()^2 +#md nothing # hide + +# All that is left is to solve the problem. We choose a value of Poissons +# ratio that results in incompressibility ($ν = 0.5$) and thus expect the +# linear/linear approximation to return garbage, and the quadratic/linear +# approximation to be stable. + +u1 = solve(0.5, linear_u, linear_p); +u2 = solve(0.5, quadratic_u, linear_p); + +## test the result #src +using Test #src +@test norm(u2) ≈ 919.2121476140703 #src + +#md # ## [Plain program](@id incompressible_elasticity-plain-program) +#md # +#md # Here follows a version of the program without any comments. +#md # The file is also available here: +#md # [`incompressible_elasticity.jl`](incompressible_elasticity.jl). +#md # +#md # ```julia +#md # @__CODE__ +#md # ``` diff --git a/previews/PR798/literate-tutorials/linear_elasticity.jl b/previews/PR798/literate-tutorials/linear_elasticity.jl new file mode 100644 index 0000000000..49181ee8b5 --- /dev/null +++ b/previews/PR798/literate-tutorials/linear_elasticity.jl @@ -0,0 +1,408 @@ +# # [Linear elasticity](@id tutorial-linear-elasticity) +# +# ![](linear_elasticity.svg) +# +# *Figure 1*: Linear elastically deformed 1mm $\times$ 1mm Ferrite logo. +# +#md # !!! tip +#md # This tutorial is also available as a Jupyter notebook: +#md # [`linear_elasticity.ipynb`](@__NBVIEWER_ROOT_URL__/tutorials/linear_elasticity.ipynb). +#- +# +# ## Introduction +# +# The classical first finite element problem to solve in solid mechanics is a linear balance +# of momentum problem. We will use this to introduce a vector valued field, the displacements +# $\boldsymbol{u}(\boldsymbol{x})$. In addition, some features of the +# [`Tensors.jl`](https://github.com/Ferrite-FEM/Tensors.jl) toolbox are demonstrated. +# +# ### Strong form +# The strong form of the balance of momentum for quasi-static loading is given by +# ```math +# \begin{alignat*}{2} +# \mathrm{div}(\boldsymbol{\sigma}) + \boldsymbol{b} &= 0 \quad &&\boldsymbol{x} \in \Omega \\ +# \boldsymbol{u} &= \boldsymbol{u}_\mathrm{D} \quad &&\boldsymbol{x} \in \Gamma_\mathrm{D} \\ +# \boldsymbol{n} \cdot \boldsymbol{\sigma} &= \boldsymbol{t}_\mathrm{N} \quad &&\boldsymbol{x} \in \Gamma_\mathrm{N} +# \end{alignat*} +# ``` +# where $\boldsymbol{\sigma}$ is the (Cauchy) stress tensor and $\boldsymbol{b}$ the body force. +# The domain, $\Omega$, has the boundary $\Gamma$, consisting of a Dirichlet part, $\Gamma_\mathrm{D}$, and +# a Neumann part, $\Gamma_\mathrm{N}$, with outward pointing normal vector $\boldsymbol{n}$. +# $\boldsymbol{u}_\mathrm{D}$ denotes prescribed displacements on $\Gamma_\mathrm{D}$, +# while $\boldsymbol{t}_\mathrm{N}$ the known tractions on $\Gamma_\mathrm{N}$. +# +# In this tutorial, we use linear elasticity, such that +# ```math +# \boldsymbol{\sigma} = \mathsf{C} : \boldsymbol{\varepsilon}, \quad +# \boldsymbol{\varepsilon} = \left[\mathrm{grad}(\boldsymbol{u})\right]^\mathrm{sym} +# ``` +# where $\mathsf{C}$ is the 4th order elastic stiffness tensor and +# $\boldsymbol{\varepsilon}$ the small strain tensor. +# The colon, $:$, represents the double contraction, +# $\sigma_{ij} = \mathsf{C}_{ijkl} \varepsilon_{kl}$, and the superscript $\mathrm{sym}$ +# denotes the symmetric part. +# +# ### Weak form +# The resulting weak form is given given as follows: Find ``\boldsymbol{u} \in \mathbb{U}`` such that +# ```math +# \int_\Omega +# \mathrm{grad}(\delta \boldsymbol{u}) : \boldsymbol{\sigma} +# \, \mathrm{d}\Omega +# = +# \int_{\Gamma} +# \delta \boldsymbol{u} \cdot \boldsymbol{t} +# \, \mathrm{d}\Gamma +# + +# \int_\Omega +# \delta \boldsymbol{u} \cdot \boldsymbol{b} +# \, \mathrm{d}\Omega +# \quad \forall \, \delta \boldsymbol{u} \in \mathbb{T}, +# ``` +# where $\mathbb{U}$ and $\mathbb{T}$ denote suitable trial and test function spaces. +# $\delta \boldsymbol{u}$ is a vector valued test function and +# $\boldsymbol{t} = \boldsymbol{\sigma}\cdot\boldsymbol{n}$ is the traction vector on +# the boundary. In this tutorial, we will neglect body forces (i.e. $\boldsymbol{b} = \boldsymbol{0}$) and the weak form reduces to +# ```math +# \int_\Omega +# \mathrm{grad}(\delta \boldsymbol{u}) : \boldsymbol{\sigma} +# \, \mathrm{d}\Omega +# = +# \int_{\Gamma} +# \delta \boldsymbol{u} \cdot \boldsymbol{t} +# \, \mathrm{d}\Gamma \,. +# ``` +# +# ### Finite element form +# Finally, the finite element form is obtained by introducing the finite element shape functions. +# Since the displacement field, $\boldsymbol{u}$, is vector valued, we use vector valued shape functions +# $\delta\boldsymbol{N}_i$ and $\boldsymbol{N}_i$ to approximate the test and trial functions: +# ```math +# \boldsymbol{u} \approx \sum_{i=1}^N \boldsymbol{N}_i (\boldsymbol{x}) \, \hat{u}_i +# \qquad +# \delta \boldsymbol{u} \approx \sum_{i=1}^N \delta\boldsymbol{N}_i (\boldsymbol{x}) \, \delta \hat{u}_i +# ``` +# Here $N$ is the number of nodal variables, with $\hat{u}_i$ and $\delta\hat{u}_i$ representing the $i$-th nodal value. +# Using the Einstein summation convention, we can write this in short form as +# $\boldsymbol{u} \approx \boldsymbol{N}_i \, \hat{u}_i$ and $\delta\boldsymbol{u} \approx \delta\boldsymbol{N}_i \, \delta\hat{u}_i$. +# +# Inserting the these into the weak form, and noting that that the equation should hold for all $\delta \hat{u}_i$, we get +# ```math +# \underbrace{\int_\Omega \mathrm{grad}(\delta \boldsymbol{N}_i) : \boldsymbol{\sigma}\ \mathrm{d}\Omega}_{f_i^\mathrm{int}} = \underbrace{\int_\Gamma \delta \boldsymbol{N}_i \cdot \boldsymbol{t}\ \mathrm{d}\Gamma}_{f_i^\mathrm{ext}} +# ``` +# Inserting the linear constitutive relationship, $\boldsymbol{\sigma} = \mathsf{C}:\boldsymbol{\varepsilon}$, +# in the internal force vector, $f_i^\mathrm{int}$, yields the linear equation +# ```math +# \underbrace{\left[\int_\Omega \mathrm{grad}(\delta \boldsymbol{N}_i) : \mathsf{C} : \left[\mathrm{grad}(\boldsymbol{N}_j)\right]^\mathrm{sym}\ \mathrm{d}\Omega\right]}_{K_{ij}}\ \hat{u}_j = f_i^\mathrm{ext} +# ``` +# +# ## Implementation +# First we load Ferrite, and some other packages we need. +using Ferrite, FerriteGmsh, SparseArrays +# As in the heat equation tutorial, we will use a unit square - but here we'll load the grid of the Ferrite logo! +# This is done by downloading [`logo.geo`](logo.geo) and loading it using [FerriteGmsh.jl](https://github.com/Ferrite-FEM/FerriteGmsh.jl), +using Downloads: download +logo_mesh = "logo.geo" +asset_url = "https://raw.githubusercontent.com/Ferrite-FEM/Ferrite.jl/gh-pages/assets/" +isfile(logo_mesh) || download(string(asset_url, logo_mesh), logo_mesh) + +FerriteGmsh.Gmsh.initialize() #hide +FerriteGmsh.Gmsh.gmsh.option.set_number("General.Verbosity", 2) #hide +grid = togrid(logo_mesh); +FerriteGmsh.Gmsh.finalize(); #hide +#md nothing #hide +# The generated grid lacks the facetsets for the boundaries, so we add them by using Ferrite's +# [`addfacetset!`](@ref). It allows us to add facetsets to the grid based on coordinates. +# Note that approximate comparison to 0.0 doesn't work well, so we use a tolerance instead. +addfacetset!(grid, "top", x -> x[2] ≈ 1.0) # facets for which x[2] ≈ 1.0 for all nodes +addfacetset!(grid, "left", x -> abs(x[1]) < 1.0e-6) +addfacetset!(grid, "bottom", x -> abs(x[2]) < 1.0e-6); + +# ### Trial and test functions +# In this tutorial, we use the same linear Lagrange shape functions to approximate both the +# test and trial spaces, i.e. $\delta\boldsymbol{N}_i = \boldsymbol{N}_i$. +# As our grid is composed of triangular elements, we need the Lagrange functions defined +# on a `RefTriangle`. All currently available interpolations can be found under +# [`Interpolation`](@ref reference-interpolation). +# +# Here we use linear triangular elements (also called constant strain triangles). +# The vector valued shape functions are constructed by raising the interpolation +# to the power `dim` (the dimension) since the displacement field has one component in each +# spatial dimension. +dim = 2 +order = 1 # linear interpolation +ip = Lagrange{RefTriangle, order}()^dim; # vector valued interpolation + +# In order to evaluate the integrals, we need to specify the quadrature rules to use. Due to the +# linear interpolation, a single quadrature point suffices, both inside the cell and on the facet. +# In 2d, a facet is the edge of the element. +qr = QuadratureRule{RefTriangle}(1) # 1 quadrature point +qr_face = FacetQuadratureRule{RefTriangle}(1); + +# Finally, we collect the interpolations and quadrature rules into the `CellValues` and `FacetValues` +# buffers, which we will later use to evaluate the integrals over the cells and facets. +cellvalues = CellValues(qr, ip) +facetvalues = FacetValues(qr_face, ip); + +# ### Degrees of freedom +# For distributing degrees of freedom, we define a `DofHandler`. The `DofHandler` knows that +# `u` has two degrees of freedom per node because we vectorized the interpolation above. +dh = DofHandler(grid) +add!(dh, :u, ip) +close!(dh); + +# ### Boundary conditions +# We set Dirichlet boundary conditions by fixing the motion normal to the bottom and left +# boundaries. The last argument to `Dirichlet` determines which components of the field should be +# constrained. If no argument is given, all components are constrained by default. +ch = ConstraintHandler(dh) +add!(ch, Dirichlet(:u, getfacetset(grid, "bottom"), (x, t) -> 0.0, 2)) +add!(ch, Dirichlet(:u, getfacetset(grid, "left"), (x, t) -> 0.0, 1)) +close!(ch); + +# In addition, we will use Neumann boundary conditions on the top surface, where +# we add a traction vector of the form +# ```math +# \boldsymbol{t}_\mathrm{N}(\boldsymbol{x}) = (20e3) x_1 \boldsymbol{e}_2\ \mathrm{N}/\mathrm{mm}^2 +# ``` +traction(x) = Vec(0.0, 20.0e3 * x[1]); + +# On the right boundary, we don't do anything, resulting in a zero traction Neumann boundary. +# In order to assemble the external forces, $f_i^\mathrm{ext}$, we need to iterate over all +# facets in the relevant facetset. We do this by using the [`FacetIterator`](@ref). + +function assemble_external_forces!(f_ext, dh, facetset, facetvalues, prescribed_traction) + ## Create a temporary array for the facet's local contributions to the external force vector + fe_ext = zeros(getnbasefunctions(facetvalues)) + for facet in FacetIterator(dh, facetset) + ## Update the facetvalues to the correct facet number + reinit!(facetvalues, facet) + ## Reset the temporary array for the next facet + fill!(fe_ext, 0.0) + ## Access the cell's coordinates + cell_coordinates = getcoordinates(facet) + for qp in 1:getnquadpoints(facetvalues) + ## Calculate the global coordinate of the quadrature point. + x = spatial_coordinate(facetvalues, qp, cell_coordinates) + tₚ = prescribed_traction(x) + ## Get the integration weight for the current quadrature point. + dΓ = getdetJdV(facetvalues, qp) + for i in 1:getnbasefunctions(facetvalues) + Nᵢ = shape_value(facetvalues, qp, i) + fe_ext[i] += tₚ ⋅ Nᵢ * dΓ + end + end + ## Add the local contributions to the correct indices in the global external force vector + assemble!(f_ext, celldofs(facet), fe_ext) + end + return f_ext +end +#md nothing #hide + +# ### Material behavior +# Next, we need to define the material behavior, specifically the elastic stiffness tensor, $\mathsf{C}$. +# In this tutorial, we use plane strain linear isotropic elasticity, with Hooke's law as +# ```math +# \boldsymbol{\sigma} = 2G \boldsymbol{\varepsilon}^\mathrm{dev} + 3K \boldsymbol{\varepsilon}^\mathrm{vol} +# ``` +# where $G$ is the shear modulus and $K$ the bulk modulus. This expression can be written as +# $\boldsymbol{\sigma} = \mathsf{C}:\boldsymbol{\varepsilon}$, with +# ```math +# \mathsf{C} := \frac{\partial \boldsymbol{\sigma}}{\partial \boldsymbol{\varepsilon}} +# ``` +# The volumetric, $\boldsymbol{\varepsilon}^\mathrm{vol}$, +# and deviatoric, $\boldsymbol{\varepsilon}^\mathrm{dev}$ strains, are defined as +# ```math +# \begin{align*} +# \boldsymbol{\varepsilon}^\mathrm{vol} &= \frac{\mathrm{tr}(\boldsymbol{\varepsilon})}{3}\boldsymbol{I}, \quad +# \boldsymbol{\varepsilon}^\mathrm{dev} &= \boldsymbol{\varepsilon} - \boldsymbol{\varepsilon}^\mathrm{vol} +# \end{align*} +# ``` +# +# Starting from Young's modulus, $E$, and Poisson's ratio, $\nu$, the shear and bulk modulus are +# ```math +# G = \frac{E}{2(1 + \nu)}, \quad K = \frac{E}{3(1 - 2\nu)} +# ``` +Emod = 200.0e3 # Young's modulus [MPa] +ν = 0.3 # Poisson's ratio [-] + +Gmod = Emod / (2(1 + ν)) # Shear modulus +Kmod = Emod / (3(1 - 2ν)) # Bulk modulus + +# Finally, we demonstrate `Tensors.jl`'s automatic differentiation capabilities when +# calculating the elastic stiffness tensor +C = gradient(ϵ -> 2 * Gmod * dev(ϵ) + 3 * Kmod * vol(ϵ), zero(SymmetricTensor{2, 2})); + +#md # !!! details "Plane stress instead of plane strain?" +#md # In order to change this tutorial to consider plane stress instead of plane strain, +#md # the elastic stiffness tensor should be changed to reflect this. The plane stress elasticity +#md # stiffness matrix in Voigt notation for engineering shear strains, is given as +#md # ```math +#md # \underline{\underline{\boldsymbol{E}}} = \frac{E}{1 - \nu^2}\begin{bmatrix} +#md # 1 & \nu & 0 \\ +#md # \nu & 1 & 0 \\ +#md # 0 & 0 & (1 - \nu)/2 +#md # \end{bmatrix} +#md # ``` +#md # This matrix can be converted into the 4th order elastic stiffness tensor as +#md # ```julia +#md # C_voigt = Emod * [1.0 ν 0.0; ν 1.0 0.0; 0.0 0.0 (1-ν)/2] / (1 - ν^2) +#md # C = fromvoigt(SymmetricTensor{4,2}, E_voigt) +#md # ``` +#md # +# ### Element routine +# To calculate the global stiffness matrix, $K_{ij}$, the element routine computes the +# local stiffness matrix `ke` for a single element and assembles it into the global matrix. +# `ke` is pre-allocated and reused for all elements. +# +# Note that the elastic stiffness tensor $\mathsf{C}$ is constant. +# Thus is needs to be computed and once and can then be used for all integration points. +function assemble_cell!(ke, cellvalues, C) + for q_point in 1:getnquadpoints(cellvalues) + ## Get the integration weight for the quadrature point + dΩ = getdetJdV(cellvalues, q_point) + for i in 1:getnbasefunctions(cellvalues) + ## Gradient of the test function + ∇Nᵢ = shape_gradient(cellvalues, q_point, i) + for j in 1:getnbasefunctions(cellvalues) + ## Symmetric gradient of the trial function + ∇ˢʸᵐNⱼ = shape_symmetric_gradient(cellvalues, q_point, j) + ke[i, j] += (∇Nᵢ ⊡ C ⊡ ∇ˢʸᵐNⱼ) * dΩ + end + end + end + return ke +end +#md nothing #hide +# +# ### Global assembly +# We define the function `assemble_global` to loop over the elements and do the global +# assembly. The function takes the preallocated sparse matrix `K`, our DofHandler `dh`, our +# `cellvalues` and the elastic stiffness tensor `C` as input arguments and computes the +# global stiffness matrix `K`. +function assemble_global!(K, dh, cellvalues, C) + ## Allocate the element stiffness matrix + n_basefuncs = getnbasefunctions(cellvalues) + ke = zeros(n_basefuncs, n_basefuncs) + ## Create an assembler + assembler = start_assemble(K) + ## Loop over all cells + for cell in CellIterator(dh) + ## Update the shape function gradients based on the cell coordinates + reinit!(cellvalues, cell) + ## Reset the element stiffness matrix + fill!(ke, 0.0) + ## Compute element contribution + assemble_cell!(ke, cellvalues, C) + ## Assemble ke into K + assemble!(assembler, celldofs(cell), ke) + end + return K +end +#md nothing #hide + +# ### Solution of the system +# The last step is to solve the system. First we allocate the global stiffness matrix `K` +# and assemble it. +K = allocate_matrix(dh) +assemble_global!(K, dh, cellvalues, C); +# Then we allocate and assemble the external force vector. +f_ext = zeros(ndofs(dh)) +assemble_external_forces!(f_ext, dh, getfacetset(grid, "top"), facetvalues, traction); + +# To account for the Dirichlet boundary conditions we use the `apply!` function. +# This modifies elements in `K` and `f`, such that we can get the +# correct solution vector `u` by using solving the linear equation system $K_{ij} \hat{u}_j = f^\mathrm{ext}_i$, +apply!(K, f_ext, ch) +u = K \ f_ext; + +# ### Postprocessing +# In this case, we want to analyze the displacements, as well as the stress field. +# We calculate the stress in each quadrature point, and then export it in two different +# ways: +# 1) Constant in each cell (matching the approximation of constant strains in each element). +# Note that a current limitation is that cell data for second order tensors must be exported +# component-wise (see issue #768) +# 2) Interpolated using the linear lagrange ansatz functions via the [`L2Projector`](@ref). + +function calculate_stresses(grid, dh, cv, u, C) + qp_stresses = [ + [zero(SymmetricTensor{2, 2}) for _ in 1:getnquadpoints(cv)] + for _ in 1:getncells(grid) + ] + avg_cell_stresses = tuple((zeros(getncells(grid)) for _ in 1:3)...) + for cell in CellIterator(dh) + reinit!(cv, cell) + cell_stresses = qp_stresses[cellid(cell)] + for q_point in 1:getnquadpoints(cv) + ε = function_symmetric_gradient(cv, q_point, u, celldofs(cell)) + cell_stresses[q_point] = C ⊡ ε + end + σ_avg = sum(cell_stresses) / getnquadpoints(cv) + avg_cell_stresses[1][cellid(cell)] = σ_avg[1, 1] + avg_cell_stresses[2][cellid(cell)] = σ_avg[2, 2] + avg_cell_stresses[3][cellid(cell)] = σ_avg[1, 2] + end + return qp_stresses, avg_cell_stresses +end + +qp_stresses, avg_cell_stresses = calculate_stresses(grid, dh, cellvalues, u, C); + +# We now use the the L2Projector to project the stress-field onto the piecewise linear +# finite element space that we used to solve the problem. +proj = L2Projector(Lagrange{RefTriangle, 1}(), grid) +stress_field = project(proj, qp_stresses, qr); + +# Export also values for each color in the logo. #src +color_data = zeros(Int, getncells(grid)) #hide +colors = [ #hide + "1" => 1, "5" => 1, # purple #hide + "2" => 2, "3" => 2, # red #hide + "4" => 3, # blue #hide + "6" => 4, # green #hide +] #hide +for (key, color) in colors #hide + for i in getcellset(grid, key) #hide + color_data[i] = color #hide + end #hide +end #hide +#md nothing #hide +# +# To visualize the result we export to a VTK-file. Specifically, an unstructured +# grid file, `.vtu`, is created, which can be viewed in e.g. +# [ParaView](https://www.paraview.org/). +VTKGridFile("linear_elasticity", dh) do vtk + write_solution(vtk, dh, u) + for (i, key) in enumerate(("11", "22", "12")) + write_cell_data(vtk, avg_cell_stresses[i], "sigma_" * key) + end + write_projection(vtk, proj, stress_field, "stress field") + Ferrite.write_cellset(vtk, grid) + write_cell_data(vtk, color_data, "colors") #hide +end + +# We used the displacement field to visualize the deformed logo in *Figure 1*, +# and in *Figure 2*, we demonstrate the difference between the interpolated stress +# field and the constant stress in each cell. + +# ![](linear_elasticity_stress.png) +# +# *Figure 2*: Vertical normal stresses (MPa) exported using the `L2Projector` (left) +# and constant stress in each cell (right). + +# The mesh produced by gmsh is not stable between different OS #src +# For linux, we therefore test carefully, and for other OS we provide #src +# a coarse test to indicate introduced errors before running CI. #src +using Test #hide +linux_result = 0.31742879147646924 #hide +@test abs(norm(u) - linux_result) < 0.01 #hide +Sys.islinux() && @test norm(u) ≈ linux_result #hide +nothing #hide + +#md # ## [Plain program](@id linear_elasticity-plain-program) +#md # +#md # Here follows a version of the program without any comments. +#md # The file is also available here: [`linear_elasticity.jl`](linear_elasticity.jl). +#md # +#md # ```julia +#md # @__CODE__ +#md # ``` diff --git a/previews/PR798/literate-tutorials/linear_shell.jl b/previews/PR798/literate-tutorials/linear_shell.jl new file mode 100644 index 0000000000..71a74c79c1 --- /dev/null +++ b/previews/PR798/literate-tutorials/linear_shell.jl @@ -0,0 +1,310 @@ +# runic: off +# # [Linear shell](@id tutorial-linear-shell) +# +# ![](linear_shell.png) +#- +# ## Introduction +# +# In this example we show how shell elements can be analyzed with Ferrite. The shell implemented here comes from the book +# "The finite element method - Linear static and dynamic finite element analysis" by Hughes (1987), and a brief description of it is +# given at the end of this tutorial. The first part of the tutorial explains how to set up the problem. + +# ## Setting up the problem +using Ferrite +using ForwardDiff + +function main() #wrap everything in a function... + +# First we generate a flat rectangular mesh. There is currently no built-in function for generating +# shell meshes in Ferrite, so we have to create our own simple mesh generator (see the +# function `generate_shell_grid` further down in this file). +#+ +nels = (10,10) +size = (10.0, 10.0) +grid = generate_shell_grid(nels, size) + +# Here we define the bi-linear interpolation used for the geometrical description of the shell. +# We also create two quadrature rules for the in-plane and out-of-plane directions. Note that we use +# under integration for the inplane integration, to avoid shear locking. +#+ +ip = Lagrange{RefQuadrilateral,1}() +qr_inplane = QuadratureRule{RefQuadrilateral}(1) +qr_ooplane = QuadratureRule{RefLine}(2) +cv = CellValues(qr_inplane, ip, ip^3) + +# Next we distribute displacement dofs,`:u = (x,y,z)` and rotational dofs, `:θ = (θ₁, θ₂)`. +#+ +dh = DofHandler(grid) +add!(dh, :u, ip^3) +add!(dh, :θ, ip^2) +close!(dh) + +# In order to apply our boundary conditions, we first need to create some facet- and vertex-sets. This +# is done with `addfacetset!` and `addvertexset!` +#+ +addfacetset!(grid, "left", (x) -> x[1] ≈ 0.0) +addfacetset!(grid, "right", (x) -> x[1] ≈ size[1]) +addvertexset!(grid, "corner", (x) -> x[1] ≈ 0.0 && x[2] ≈ 0.0 && x[3] ≈ 0.0) + +# Here we define the boundary conditions. On the left edge, we lock the displacements in the x- and z- directions, and all the rotations. +#+ +ch = ConstraintHandler(dh) +add!(ch, Dirichlet(:u, getfacetset(grid, "left"), (x, t) -> (0.0, 0.0), [1,3]) ) +add!(ch, Dirichlet(:θ, getfacetset(grid, "left"), (x, t) -> (0.0, 0.0), [1,2]) ) + +# On the right edge, we also lock the displacements in the x- and z- directions, but apply a precribed rotation. +#+ +add!(ch, Dirichlet(:u, getfacetset(grid, "right"), (x, t) -> (0.0, 0.0), [1,3]) ) +add!(ch, Dirichlet(:θ, getfacetset(grid, "right"), (x, t) -> (0.0, pi/10), [1,2]) ) + +# In order to not get rigid body motion, we lock the y-displacement in one of the corners. +#+ +add!(ch, Dirichlet(:θ, getvertexset(grid, "corner"), (x, t) -> (0.0), [2]) ) + +close!(ch) +update!(ch, 0.0) + +# Next we define relevant data for the shell, such as shear correction factor and stiffness matrix for the material. +# In this linear shell, plane stress is assumed, ie $\\sigma_{zz} = 0$. Therefor, the stiffness matrix is 5x5 (opposed to the normal 6x6). +#+ +κ = 5/6 # Shear correction factor +E = 210.0 +ν = 0.3 +a = (1-ν)/2 +C = E/(1-ν^2) * [1 ν 0 0 0; + ν 1 0 0 0; + 0 0 a*κ 0 0; + 0 0 0 a*κ 0; + 0 0 0 0 a*κ] + + +data = (thickness = 1.0, C = C); #Named tuple + +# We now assemble the problem in standard finite element fashion +#+ +nnodes = getnbasefunctions(ip) +ndofs_shell = ndofs_per_cell(dh) + +K = allocate_matrix(dh) +f = zeros(Float64, ndofs(dh)) + +ke = zeros(ndofs_shell, ndofs_shell) +fe = zeros(ndofs_shell) + +celldofs = zeros(Int, ndofs_shell) +cellcoords = zeros(Vec{3,Float64}, nnodes) + +assembler = start_assemble(K, f) +for cell in CellIterator(grid) + fill!(ke, 0.0) + reinit!(cv, cell) + celldofs!(celldofs, dh, cellid(cell)) + getcoordinates!(cellcoords, grid, cellid(cell)) + + #Call the element routine + integrate_shell!(ke, cv, qr_ooplane, cellcoords, data) + + assemble!(assembler, celldofs, ke, fe) +end + +# Apply BC and solve. +#+ +apply!(K, f, ch) +a = K\f + +# Output results. +#+ +VTKGridFile("linear_shell", dh) do vtk + write_solution(vtk, dh, a) +end + +end; #end main functions + +# Below is the function that creates the shell mesh. It simply generates a 2d-quadrature mesh, and appends +# a third coordinate (z-direction) to the node-positions. +function generate_shell_grid(nels, size) + _grid = generate_grid(Quadrilateral, nels, Vec((0.0,0.0)), Vec(size)) + nodes = [(n.x[1], n.x[2], 0.0) |> Vec{3} |> Node for n in _grid.nodes] + + grid = Grid(_grid.cells, nodes) + + return grid +end; + +# ## The shell element +# +# The shell presented here comes from the book "The finite element method - Linear static and dynamic finite element analysis" by Hughes (1987). +# The shell is a so called degenerate shell element, meaning it is based on a continuum element. +# A brief description of the shell is given here. + +#md # !!! note +#md # This element might experience various locking phenomenas, and should only be seen as a proof of concept. + +# ##### Fiber coordinate system +# The element uses two coordinate systems. The first coordianate system, called the fiber system, is created for each +# element node, and is used as a reference frame for the rotations. The function below implements an algorithm that return the +# fiber directions, $\boldsymbol{e}^{f}_{a1}$, $\boldsymbol{e}^{f}_{a2}$ and $\boldsymbol{e}^{f}_{a3}$, at each node $a$. +function fiber_coordsys(Ps::Vector{Vec{3,Float64}}) + + ef1 = Vec{3,Float64}[] + ef2 = Vec{3,Float64}[] + ef3 = Vec{3,Float64}[] + for P in Ps + a = abs.(P) + j = 1 + if a[1] > a[3]; a[3] = a[1]; j = 2; end + if a[2] > a[3]; j = 3; end + + e3 = P + e2 = Tensors.cross(P, basevec(Vec{3}, j)) + e2 /= norm(e2) + e1 = Tensors.cross(e2, P) + + push!(ef1, e1) + push!(ef2, e2) + push!(ef3, e3) + end + return ef1, ef2, ef3 + +end; + +# ##### Lamina coordinate system +# The second coordinate system is the so called Lamina Coordinate system. It is +# created for each integration point, and is defined to be tangent to the +# mid-surface. It is in this system that we enforce that plane stress assumption, +# i.e. $\sigma_{zz} = 0$. The function below returns the rotation matrix, $\boldsymbol{q}$, for this coordinate system. +function lamina_coordsys(dNdξ, ζ, x, p, h) + + e1 = zero(Vec{3}) + e2 = zero(Vec{3}) + + for i in 1:length(dNdξ) + e1 += dNdξ[i][1] * x[i] + 0.5*h*ζ * dNdξ[i][1] * p[i] + e2 += dNdξ[i][2] * x[i] + 0.5*h*ζ * dNdξ[i][1] * p[i] + end + + e1 /= norm(e1) + e2 /= norm(e2) + + ez = Tensors.cross(e1,e2) + ez /= norm(ez) + + a = 0.5*(e1 + e2) + a /= norm(a) + + b = Tensors.cross(ez,a) + b /= norm(b) + + ex = sqrt(2)/2 * (a - b) + ey = sqrt(2)/2 * (a + b) + + return Tensor{2,3}(hcat(ex,ey,ez)) +end; + + +# ##### Geometrical description +# A material point in the shell is defined as +# ```math +# \boldsymbol x(\xi, \eta, \zeta) = \sum_{a=1}^{N_{\text{nodes}}} N_a(\xi, \eta) \boldsymbol{\bar{x}}_{a} + ζ \frac{h}{2} \boldsymbol{\bar{p}_a} +# ``` +# where $\boldsymbol{\bar{x}}_{a}$ are nodal positions on the mid-surface, and $\boldsymbol{\bar{p}_a}$ is an vector that defines the fiber direction +# on the reference surface. $N_a$ arethe shape functions. +# +# Based on the definition of the position vector, we create an function for obtaining the Jacobian-matrix, +# ```math +# J_{ij} = \frac{\partial x_i}{\partial \xi_j}, +# ``` +function getjacobian(q, N, dNdξ, ζ, X, p, h) + J = zeros(3,3) + for a in 1:length(N) + for i in 1:3, j in 1:3 + _dNdξ = (j==3) ? 0.0 : dNdξ[a][j] + _dζdξ = (j==3) ? 1.0 : 0.0 + _N = N[a] + + J[i,j] += _dNdξ * X[a][i] + (_dNdξ*ζ + _N*_dζdξ) * h/2 * p[a][i] + end + end + + return (q' * J) |> Tensor{2,3,Float64} +end; + +# ##### Strains +# Small deformation is assumed, +# ```math +# \varepsilon_{ij}= \frac{1}{2}(\frac{\partial u_{i}}{\partial x_j} + \frac{\partial u_{j}}{\partial x_i}) +# ``` +# The displacement field is calculated as: +# ```math +# \boldsymbol u = \sum_{a=1}^{N_{\text{nodes}}} N_a \bar{\boldsymbol u}_{a} + +# N_a ζ\frac{h}{2}(\theta_{a2} \boldsymbol e^{f}_{a1} - \theta_{a1} \boldsymbol e^{f}_{a2}) +# +# ``` +# The gradient of the displacement (in the lamina coordinate system), then becomes: +# ```math +# \frac{\partial u_{i}}{\partial x_j} = \sum_{m=1}^3 q_{im} \sum_{a=1}^{N_{\text{nodes}}} \frac{\partial N_a}{\partial x_j} \bar{u}_{am} + +# \frac{\partial(N_a ζ)}{\partial x_j} \frac{h}{2} (\theta_{a2} e^{f}_{am1} - \theta_{a1} e^{f}_{am2}) +# ``` +function strain(dofvec::Vector{T}, N, dNdx, ζ, dζdx, q, ef1, ef2, h) where T + + u = reinterpret(Vec{3,T}, dofvec[1:12]) + θ = reinterpret(Vec{2,T}, dofvec[13:20]) + + dudx = zeros(T, 3, 3) + for m in 1:3, j in 1:3 + for a in 1:length(N) + dudx[m,j] += dNdx[a][j] * u[a][m] + h/2 * (dNdx[a][j]*ζ + N[a]*dζdx[j]) * (θ[a][2]*ef1[a][m] - θ[a][1]*ef2[a][m]) + end + end + + dudx = q*dudx + ε = [dudx[1,1], dudx[2,2], dudx[1,2]+dudx[2,1], dudx[2,3]+dudx[3,2], dudx[1,3]+dudx[3,1]] + return ε +end; + +# ##### Main element routine +# Below is the main routine that calculates the stiffness matrix of the shell element. +# Since it is a so called degenerate shell element, the code is similar to that for an standard continuum element. +shape_reference_gradient(cv::CellValues, q_point, i) = cv.fun_values.dNdξ[i, q_point] + +function integrate_shell!(ke, cv, qr_ooplane, X, data) + nnodes = getnbasefunctions(cv) + ndofs = nnodes*5 + h = data.thickness + + #Create the directors in each node. + #Note: For a more general case, the directors should + #be input parameters for the element routine. + p = zeros(Vec{3}, nnodes) + for i in 1:nnodes + a = Vec{3}((0.0, 0.0, 1.0)) + p[i] = a/norm(a) + end + + ef1, ef2, ef3 = fiber_coordsys(p) + + for iqp in 1:getnquadpoints(cv) + N = [shape_value(cv, iqp, i) for i in 1:nnodes] + dNdξ = [shape_reference_gradient(cv, iqp, i) for i in 1:nnodes] + dNdx = [shape_gradient(cv, iqp, i) for i in 1:nnodes] + + for oqp in 1:length(qr_ooplane.weights) + ζ = qr_ooplane.points[oqp][1] + q = lamina_coordsys(dNdξ, ζ, X, p, h) + + J = getjacobian(q, N, dNdξ, ζ, X, p, h) + Jinv = inv(J) + dζdx = Vec{3}((0.0, 0.0, 1.0)) ⋅ Jinv + + #For simplicity, use automatic differentiation to construct the B-matrix from the strain. + B = ForwardDiff.jacobian( + (a) -> strain(a, N, dNdx, ζ, dζdx, q, ef1, ef2, h), zeros(Float64, ndofs) ) + + dV = qr_ooplane.weights[oqp] * getdetJdV(cv, iqp) + ke .+= B'*data.C*B * dV + end + end +end; + +# Run everything: +main() diff --git a/previews/PR798/literate-tutorials/linear_shell.png b/previews/PR798/literate-tutorials/linear_shell.png new file mode 100644 index 0000000000..7b1d812ebb Binary files /dev/null and b/previews/PR798/literate-tutorials/linear_shell.png differ diff --git a/previews/PR798/literate-tutorials/ns_vs_diffeq.jl b/previews/PR798/literate-tutorials/ns_vs_diffeq.jl new file mode 100644 index 0000000000..67871b872e --- /dev/null +++ b/previews/PR798/literate-tutorials/ns_vs_diffeq.jl @@ -0,0 +1,645 @@ +# We check for a divergence free velocity field in the CI #src +if isdefined(Main, :is_ci) #hide + IS_CI = Main.is_ci #hide +else #hide + IS_CI = false #hide +end #hide +nothing #hide +# # [Incompressible Navier-Stokes equations via DifferentialEquations.jl](@id tutorial-ins-ordinarydiffeq) +# +# ![nsdiffeq](nsdiffeq.gif) +# +# +# In this example we focus on a simple but visually appealing problem from +# fluid dynamics, namely vortex shedding. This problem is also known as +# [von-Karman vortex streets](https://en.wikipedia.org/wiki/K%C3%A1rm%C3%A1n_vortex_street). Within this example, we show how to utilize [DifferentialEquations.jl](https://github.com/SciML/DifferentialEquations.jl) +# in tandem with Ferrite to solve this space-time problem. To keep things simple we use a naive approach +# to discretize the system. +# +# ## Remarks on DifferentialEquations.jl +# +# !!! note "Required Version" +# This example will only work with OrdinaryDiffEq@v6.80.1. or above +# +# Many "time step solvers" of [DifferentialEquations.jl](https://github.com/SciML/DifferentialEquations.jl) assume that that the +# problem is provided in mass matrix form. The incompressible Navier-Stokes +# equations as stated above yield a DAE in this form after applying a spatial +# discretization technique - in our case FEM. The mass matrix form of ODEs and DAEs +# is given as: +# ```math +# M(t) \mathrm{d}_t u = f(u,t) +# ``` +# where $M$ is a possibly time-dependent and not necessarily invertible mass matrix, +# $u$ the vector of unknowns and $f$ the right-hand-side (RHS). For us $f$ can be interpreted as +# the spatial discretization of all linear and nonlinear operators depending on $u$ and $t$, +# but not on the time derivative of $u$. +# +# ## Some theory on the incompressible Navier-Stokes equations +# +# ### Problem description in strong form +# +# The incompressible Navier-Stokes equations can be stated as the system +# ```math +# \begin{aligned} +# \partial_t v &= \underbrace{\nu \Delta v}_{\text{viscosity}} - \underbrace{(v \cdot \nabla) v}_{\text{advection}} - \underbrace{\nabla p}_{\text{pressure}} \\ +# 0 &= \underbrace{\nabla \cdot v}_{\text{incompressibility}} +# \end{aligned} +# ``` +# where $v$ is the unknown velocity field, $p$ the unknown pressure field, +# $\nu$ the dynamic viscosity and $\Delta$ the Laplacian. In the derivation we assumed +# a constant density of 1 for the fluid and negligible coupling between the velocity components. +# +# Our setup is derived from [Turek's DFG benchmark](http://www.mathematik.tu-dortmund.de/~featflow/en/benchmarks/cfdbenchmarking/flow/dfg_benchmark2_re100.html). +# We model a channel with size $0.41 \times 1.1$ and a hole of radius $0.05$ centered at $(0.2, 0.2)$. +# The left side has a parabolic inflow profile, which is ramped up over time, modeled as the time dependent +# Dirichlet condition +# ```math +# v(x,y,t) +# = +# \begin{bmatrix} +# 4 v_{in}(t) y (0.41-y)/0.41^2 \\ +# 0 +# \end{bmatrix} +# ``` +# where $v_{in}(t) = \text{clamp}(t, 0.0, 1.5)$. With a dynamic viscosity of $\nu = 0.001$ +# this is enough to induce turbulence behind the cylinder which leads to vortex shedding. The top and bottom of our +# channel have no-slip conditions, i.e. $v = [0,0]^{\textrm{T}}$, while the right boundary has the do-nothing boundary condition +# $\nu \partial_{\textrm{n}} v - p n = 0$ to model outflow. With these boundary conditions we can choose the zero solution as a +# feasible initial condition. +# +# ### Derivation of Semi-Discrete Weak Form +# +# By multiplying test functions $\varphi$ and $\psi$ from a suitable test function space on the strong form, +# followed by integrating over the domain and applying partial integration to the pressure and viscosity terms +# we can obtain the following weak form +# ```math +# \begin{aligned} +# \int_\Omega \partial_t v \cdot \varphi &= - \int_\Omega \nu \nabla v : \nabla \varphi - \int_\Omega (v \cdot \nabla) v \cdot \varphi + \int_\Omega p (\nabla \cdot \varphi) + \int_{\partial \Omega_{N}} \underbrace{(\nu \partial_n v - p n )}_{=0} \cdot \varphi \\ +# 0 &= \int_\Omega (\nabla \cdot v) \psi +# \end{aligned} +# ``` +# for all possible test functions from the suitable space. +# +# Now we can discretize the problem as usual with the finite element method +# utilizing Taylor-Hood elements (Q2Q1) to yield a stable discretization in +# mass matrix form: +# ```math +# \underbrace{\begin{bmatrix} +# M_v & 0 \\ +# 0 & 0 +# \end{bmatrix}}_{:=M} +# \begin{bmatrix} +# \mathrm{d}_t\hat{v} \\ +# \mathrm{d}_t\hat{p} +# \end{bmatrix} +# = +# \underbrace{\begin{bmatrix} +# A & B^{\textrm{T}} \\ +# B & 0 +# \end{bmatrix}}_{:=K} +# \begin{bmatrix} +# \hat{v} \\ +# \hat{p} +# \end{bmatrix} +# + +# \begin{bmatrix} +# N(\hat{v}, \hat{v}, \hat{\varphi}) \\ +# 0 +# \end{bmatrix} +# ``` +# Here $M$ is the singular block mass matrix, $K$ is the discretized Stokes operator and $N$ the nonlinear advection term, which +# is also called trilinear form. $\hat{v}$ and $\hat{p}$ represent the time-dependent vectors of nodal values of the discretizations +# of $v$ and $p$ respectively, while $\hat{\varphi}$ is the choice for the test function in the discretization. The hats are dropped +# in the implementation and only stated for clarity in this section. +# +# +# ## Commented implementation +# +# Now we solve the problem with Ferrite and [DifferentialEquations.jl](https://github.com/SciML/DifferentialEquations.jl). What follows is a program spliced with comments. +# The full program, without comments, can be found in the next [section](@ref ns_vs_diffeq-plain-program). +# +# First we load Ferrite and some other packages we need +using Ferrite, SparseArrays, BlockArrays, LinearAlgebra, UnPack, LinearSolve, WriteVTK +# Since we do not need the complete DifferentialEquations suite, we just load the required ODE infrastructure, which can also handle +# DAEs in mass matrix form. +using OrdinaryDiffEq + +# We start off by defining our only material parameter. +ν = 1.0 / 1000.0; #dynamic viscosity + +# Next a rectangular grid with a cylinder in it has to be generated. +# We use Gmsh.jl for the creation of the mesh and FerriteGmsh.jl to translate it to a `Ferrite.Grid`. +# Note that the mesh is pretty fine, leading to a high memory consumption when +# feeding the equation system to direct solvers. +using FerriteGmsh +using FerriteGmsh: Gmsh +Gmsh.initialize() +gmsh.option.set_number("General.Verbosity", 2) +dim = 2; +# We specify first the rectangle, the cylinder, the surface spanned by the cylinder +# and the boolean difference of rectangle and cylinder. +if !IS_CI #hide + # runic: off #src +rect_tag = gmsh.model.occ.add_rectangle(0, 0, 0, 1.1, 0.41) +circle_tag = gmsh.model.occ.add_circle(0.2, 0.2, 0, 0.05) +circle_curve_tag = gmsh.model.occ.add_curve_loop([circle_tag]) +circle_surf_tag = gmsh.model.occ.add_plane_surface([circle_curve_tag]) +gmsh.model.occ.cut([(dim, rect_tag)], [(dim, circle_surf_tag)]) + # runic: on #src +else #hide + rect_tag = gmsh.model.occ.add_rectangle(0, 0, 0, 0.55, 0.41) #hide +end #hide +nothing #hide +# Now, the geometrical entities need to be synchronized in order to be available outside +# of `gmsh.model.occ` +gmsh.model.occ.synchronize() +# In the next lines, we add the physical groups needed to define boundary conditions. +if !IS_CI #hide + # runic: off #src +bottomtag = gmsh.model.model.add_physical_group(dim - 1, [6], -1, "bottom") +lefttag = gmsh.model.model.add_physical_group(dim - 1, [7], -1, "left") +righttag = gmsh.model.model.add_physical_group(dim - 1, [8], -1, "right") +toptag = gmsh.model.model.add_physical_group(dim - 1, [9], -1, "top") +holetag = gmsh.model.model.add_physical_group(dim - 1, [5], -1, "hole") + # runic: on #src +else #hide + gmsh.model.model.add_physical_group(dim - 1, [4], 7, "left") #hide + gmsh.model.model.add_physical_group(dim - 1, [3], 8, "top") #hide + gmsh.model.model.add_physical_group(dim - 1, [2], 9, "right") #hide + gmsh.model.model.add_physical_group(dim - 1, [1], 10, "bottom") #hide +end #hide +nothing #hide +# Since we want a quad mesh, we specify the meshing algorithm to the quasi structured quad one. +# For a complete list, [see the Gmsh docs](https://gmsh.info/doc/texinfo/gmsh.html#Mesh-options-list). +gmsh.option.setNumber("Mesh.Algorithm", 11) +gmsh.option.setNumber("Mesh.MeshSizeFromCurvature", 20) +gmsh.option.setNumber("Mesh.MeshSizeMax", 0.05) +if IS_CI #hide + gmsh.option.setNumber("Mesh.MeshSizeFromCurvature", 20) #hide + gmsh.option.setNumber("Mesh.MeshSizeMax", 0.15) #hide +end #hide +# In the next step, the mesh is generated and finally translated. +gmsh.model.mesh.generate(dim) +grid = togrid() +Gmsh.finalize(); + +# ### Function Space +# To ensure stability we utilize the Taylor-Hood element pair Q2-Q1. +# We have to utilize the same quadrature rule for the pressure as for the velocity, because in the weak form the +# linear pressure term is tested against a quadratic function. +ip_v = Lagrange{RefQuadrilateral, 2}()^dim +qr = QuadratureRule{RefQuadrilateral}(4) +cellvalues_v = CellValues(qr, ip_v); + +ip_p = Lagrange{RefQuadrilateral, 1}() +cellvalues_p = CellValues(qr, ip_p); + +dh = DofHandler(grid) +add!(dh, :v, ip_v) +add!(dh, :p, ip_p) +close!(dh); + +# ### Boundary conditions +# As in the DFG benchmark we apply no-slip conditions to the top, bottom and +# cylinder boundary. The no-slip condition states that the velocity of the +# fluid on this portion of the boundary is fixed to be zero. +ch = ConstraintHandler(dh); + +nosplip_facet_names = ["top", "bottom", "hole"]; +# No hole for the test present #src +if IS_CI #hide + nosplip_facet_names = ["top", "bottom"] #hide +end #hide +∂Ω_noslip = union(getfacetset.((grid,), nosplip_facet_names)...); +noslip_bc = Dirichlet(:v, ∂Ω_noslip, (x, t) -> Vec((0.0, 0.0)), [1, 2]) +add!(ch, noslip_bc); + +# The left boundary has a parabolic inflow with peak velocity of 1.5. This +# ensures that for the given geometry the Reynolds number is 100, which +# is already enough to obtain some simple vortex streets. By increasing the +# velocity further we can obtain stronger vortices - which may need additional +# refinement of the grid. +∂Ω_inflow = getfacetset(grid, "left"); + +# !!! note +# The kink in the velocity profile will lead to a discontinuity in the pressure at $t=1$. +# This needs to be considered in the DiffEq `init` by providing the keyword argument `d_discontinuities=[1.0]`. +vᵢₙ(t) = min(t * 1.5, 1.5) #inflow velocity + +parabolic_inflow_profile(x, t) = Vec((4 * vᵢₙ(t) * x[2] * (0.41 - x[2]) / 0.41^2, 0.0)) +inflow_bc = Dirichlet(:v, ∂Ω_inflow, parabolic_inflow_profile, [1, 2]) +add!(ch, inflow_bc); + +# The outflow boundary condition has been applied on the right side of the +# cylinder when the weak form has been derived by setting the boundary integral +# to zero. It is also called the do-nothing condition. Other outflow conditions +# are also possible. +∂Ω_free = getfacetset(grid, "right"); + +close!(ch) +update!(ch, 0.0); + +# ### Linear System Assembly +# Next we describe how the block mass matrix and the Stokes matrix are assembled. +# +# For the block mass matrix $M$ we remember that only the first equation had a time derivative +# and that the block mass matrix corresponds to the term arising from discretizing the time +# derivatives. Hence, only the upper left block has non-zero components. +function assemble_mass_matrix(cellvalues_v::CellValues, cellvalues_p::CellValues, M::SparseMatrixCSC, dh::DofHandler) + ## Allocate a buffer for the local matrix and some helpers, together with the assembler. + n_basefuncs_v = getnbasefunctions(cellvalues_v) + n_basefuncs_p = getnbasefunctions(cellvalues_p) + n_basefuncs = n_basefuncs_v + n_basefuncs_p + v▄, p▄ = 1, 2 + Mₑ = BlockedArray(zeros(n_basefuncs, n_basefuncs), [n_basefuncs_v, n_basefuncs_p], [n_basefuncs_v, n_basefuncs_p]) + + ## It follows the assembly loop as explained in the basic tutorials. + mass_assembler = start_assemble(M) + for cell in CellIterator(dh) + fill!(Mₑ, 0) + Ferrite.reinit!(cellvalues_v, cell) + + for q_point in 1:getnquadpoints(cellvalues_v) + dΩ = getdetJdV(cellvalues_v, q_point) + ## Remember that we assemble a vector mass term, hence the dot product. + ## There is only one time derivative on the left hand side, so only one mass block is non-zero. + for i in 1:n_basefuncs_v + φᵢ = shape_value(cellvalues_v, q_point, i) + for j in 1:n_basefuncs_v + φⱼ = shape_value(cellvalues_v, q_point, j) + Mₑ[BlockIndex((v▄, v▄), (i, j))] += φᵢ ⋅ φⱼ * dΩ + end + end + end + assemble!(mass_assembler, celldofs(cell), Mₑ) + end + + return M +end; + +# Next we discuss the assembly of the Stokes matrix appearing on the right hand side. +# Remember that we use the same function spaces for trial and test, hence the +# matrix has the following block form +# ```math +# K = \begin{bmatrix} +# A & B^{\textrm{T}} \\ +# B & 0 +# \end{bmatrix} +# ``` +# which is also called saddle point matrix. These problems are known to have +# a non-trivial kernel, which is a reflection of the strong form as discussed +# in the theory portion if this example. +function assemble_stokes_matrix(cellvalues_v::CellValues, cellvalues_p::CellValues, ν, K::SparseMatrixCSC, dh::DofHandler) + ## Again, some buffers and helpers + n_basefuncs_v = getnbasefunctions(cellvalues_v) + n_basefuncs_p = getnbasefunctions(cellvalues_p) + n_basefuncs = n_basefuncs_v + n_basefuncs_p + v▄, p▄ = 1, 2 + Kₑ = BlockedArray(zeros(n_basefuncs, n_basefuncs), [n_basefuncs_v, n_basefuncs_p], [n_basefuncs_v, n_basefuncs_p]) + + ## Assembly loop + stiffness_assembler = start_assemble(K) + for cell in CellIterator(dh) + ## Don't forget to initialize everything + fill!(Kₑ, 0) + + Ferrite.reinit!(cellvalues_v, cell) + Ferrite.reinit!(cellvalues_p, cell) + + for q_point in 1:getnquadpoints(cellvalues_v) + dΩ = getdetJdV(cellvalues_v, q_point) + # Assemble local viscosity block of $A$ + #+ + for i in 1:n_basefuncs_v + ∇φᵢ = shape_gradient(cellvalues_v, q_point, i) + for j in 1:n_basefuncs_v + ∇φⱼ = shape_gradient(cellvalues_v, q_point, j) + Kₑ[BlockIndex((v▄, v▄), (i, j))] -= ν * ∇φᵢ ⊡ ∇φⱼ * dΩ + end + end + # Assemble local pressure and incompressibility blocks of $B^{\textrm{T}}$ and $B$. + #+ + for j in 1:n_basefuncs_p + ψ = shape_value(cellvalues_p, q_point, j) + for i in 1:n_basefuncs_v + divφ = shape_divergence(cellvalues_v, q_point, i) + Kₑ[BlockIndex((v▄, p▄), (i, j))] += (divφ * ψ) * dΩ + Kₑ[BlockIndex((p▄, v▄), (j, i))] += (ψ * divφ) * dΩ + end + end + end + + ## Assemble `Kₑ` into the Stokes matrix `K`. + assemble!(stiffness_assembler, celldofs(cell), Kₑ) + end + return K +end; + +# ### Solution of the semi-discretized system via DifferentialEquations.jl +# First we assemble the linear portions for efficiency. These matrices are +# assumed to be constant over time. +# !!! note +# To obtain the vortex street a small time step is important to resolve +# the small oscillation forming. The mesh size becomes important to +# "only" resolve the smaller vertices forming, but less important for +# the initial formation. +T = 6.0 +Δt₀ = 0.001 +if IS_CI #hide + Δt₀ = 0.1 #hide +end #hide +Δt_save = 0.1 + +M = allocate_matrix(dh); +M = assemble_mass_matrix(cellvalues_v, cellvalues_p, M, dh); + +K = allocate_matrix(dh); +K = assemble_stokes_matrix(cellvalues_v, cellvalues_p, ν, K, dh); + +# These are our initial conditions. We start from the zero solution, because it +# is trivially admissible if the Dirichlet conditions are zero everywhere on the +# Dirichlet boundary for $t=0$. Note that the time stepper is also doing fine if the +# Dirichlet condition is non-zero and not too pathological. +u₀ = zeros(ndofs(dh)) +apply!(u₀, ch); + +# DifferentialEquations assumes dense matrices by default, which is not +# feasible for semi-discretization of finite element models. We communicate +# that a sparse matrix with specified pattern should be utilized through the +# `jac_prototyp` argument. It is simple to see that the Jacobian and the +# stiffness matrix share the same sparsity pattern, since they share the +# same relation between trial and test functions. +jac_sparsity = sparse(K); + +# To apply the nonlinear portion of the Navier-Stokes problem we simply hand +# over the dof handler and cell values to the right-hand-side (RHS) as a parameter. +# Furthermore the pre-assembled linear part, our Stokes operator (which is time independent) +# is passed to save some additional runtime. To apply the time-dependent Dirichlet BCs, we +# also need to hand over the constraint handler. +# The basic idea to apply the Dirichlet BCs consistently is that we copy the +# current solution `u`, apply the Dirichlet BCs on the copy, evaluate the +# discretized RHS of the Navier-Stokes equations with this vector. +# Furthermore we pass down the Jacobian assembly manually. For the Jacobian we eliminate all +# rows and columns associated with constrained dofs. Also note that we eliminate the mass +# matrix beforehand in a similar fashion. This decouples the time evolution of the constrained +# dofs from the true unknowns. The correct solution is enforced by utilizing step and +# stage limiters. The correct norms are computed by passing down a custom norm which simply +# ignores all constrained dofs. +# +# !!! note +# An alternative strategy is to hook into the nonlinear and linear solvers and enforce +# the solution therein. However, this is not possible at the time of writing this tutorial. +# +apply!(M, ch) + +struct RHSparams + K::SparseMatrixCSC + ch::ConstraintHandler + dh::DofHandler + cellvalues_v::CellValues + u::Vector +end +p = RHSparams(K, ch, dh, cellvalues_v, copy(u₀)) + +function ferrite_limiter!(u, _, p, t) + update!(p.ch, t) + return apply!(u, p.ch) +end + +function navierstokes_rhs_element!(dvₑ, vₑ, cellvalues_v) + n_basefuncs = getnbasefunctions(cellvalues_v) + for q_point in 1:getnquadpoints(cellvalues_v) + dΩ = getdetJdV(cellvalues_v, q_point) + ∇v = function_gradient(cellvalues_v, q_point, vₑ) + v = function_value(cellvalues_v, q_point, vₑ) + for j in 1:n_basefuncs + φⱼ = shape_value(cellvalues_v, q_point, j) + # Note that in Tensors.jl the definition $\textrm{grad} v = \nabla v$ holds. + # With this information it can be quickly shown in index notation that + # ```math + # [(v \cdot \nabla) v]_{\textrm{i}} = v_{\textrm{j}} (\partial_{\textrm{j}} v_{\textrm{i}}) = [v (\nabla v)^{\textrm{T}}]_{\textrm{i}} + # ``` + # where we should pay attentation to the transpose of the gradient. + #+ + dvₑ[j] -= v ⋅ ∇v' ⋅ φⱼ * dΩ + end + end + return +end + +function navierstokes!(du, u_uc, p::RHSparams, t) + # Unpack the struct to save some allocations. + #+ + @unpack K, ch, dh, cellvalues_v, u = p + + # We start by applying the time-dependent Dirichlet BCs. Note that we are + # not allowed to mutate `u_uc`! Furthermore not that we also can not pre- + # allocate a buffer for this variable variable if we want to use AD to derive + # the Jacobian matrix, which appears in stiff solvers. + # Therefore, for efficiency reasons, we simply pass down the jacobian analytically. + #+ + u .= u_uc + update!(ch, t) + apply!(u, ch) + + # Now we apply the rhs of the Navier-Stokes equations + #+ + ## Linear contribution (Stokes operator) + mul!(du, K, u) # du .= K * u + + ## nonlinear contribution + v_range = dof_range(dh, :v) + n_basefuncs = getnbasefunctions(cellvalues_v) + vₑ = zeros(n_basefuncs) + duₑ = zeros(n_basefuncs) + for cell in CellIterator(dh) + Ferrite.reinit!(cellvalues_v, cell) + v_celldofs = @view celldofs(cell)[v_range] + vₑ .= @views u[v_celldofs] + fill!(duₑ, 0.0) + navierstokes_rhs_element!(duₑ, vₑ, cellvalues_v) + assemble!(du, v_celldofs, duₑ) + end + return +end; + +function navierstokes_jac_element!(Jₑ, vₑ, cellvalues_v) + n_basefuncs = getnbasefunctions(cellvalues_v) + for q_point in 1:getnquadpoints(cellvalues_v) + dΩ = getdetJdV(cellvalues_v, q_point) + ∇v = function_gradient(cellvalues_v, q_point, vₑ) + v = function_value(cellvalues_v, q_point, vₑ) + for j in 1:n_basefuncs + φⱼ = shape_value(cellvalues_v, q_point, j) + # Note that in Tensors.jl the definition $\textrm{grad} v = \nabla v$ holds. + # With this information it can be quickly shown in index notation that + # ```math + # [(v \cdot \nabla) v]_{\textrm{i}} = v_{\textrm{j}} (\partial_{\textrm{j}} v_{\textrm{i}}) = [v (\nabla v)^{\textrm{T}}]_{\textrm{i}} + # ``` + # where we should pay attentation to the transpose of the gradient. + #+ + for i in 1:n_basefuncs + φᵢ = shape_value(cellvalues_v, q_point, i) + ∇φᵢ = shape_gradient(cellvalues_v, q_point, i) + Jₑ[j, i] -= (φᵢ ⋅ ∇v' + v ⋅ ∇φᵢ') ⋅ φⱼ * dΩ + end + end + end + return +end + +function navierstokes_jac!(J, u_uc, p, t) + # Unpack the struct to save some allocations. + #+ + @unpack K, ch, dh, cellvalues_v, u = p + + # We start by applying the time-dependent Dirichlet BCs. Note that we are + # not allowed to mutate `u_uc`, so we use our buffer again. + #+ + u .= u_uc + update!(ch, t) + apply!(u, ch) + + # Now we apply the Jacobian of the Navier-Stokes equations. + #+ + ## Linear contribution (Stokes operator) + ## Here we assume that J has exactly the same structure as K by construction + nonzeros(J) .= nonzeros(K) + + assembler = start_assemble(J; fillzero = false) + + ## Assemble variation of the nonlinear term + n_basefuncs = getnbasefunctions(cellvalues_v) + Jₑ = zeros(n_basefuncs, n_basefuncs) + vₑ = zeros(n_basefuncs) + v_range = dof_range(dh, :v) + for cell in CellIterator(dh) + Ferrite.reinit!(cellvalues_v, cell) + v_celldofs = @view celldofs(cell)[v_range] + + vₑ .= @views u[v_celldofs] + fill!(Jₑ, 0.0) + navierstokes_jac_element!(Jₑ, vₑ, cellvalues_v) + assemble!(assembler, v_celldofs, Jₑ) + end + + # Finally we eliminate the constrained dofs from the Jacobian to + # decouple them in the nonlinear solver from the remaining system. + #+ + return apply!(J, ch) +end; + +# Finally, together with our pre-assembled mass matrix, we are now able to +# define our problem in mass matrix form. +rhs = ODEFunction(navierstokes!, mass_matrix = M; jac = navierstokes_jac!, jac_prototype = jac_sparsity) +problem = ODEProblem(rhs, u₀, (0.0, T), p); + +# All norms must not depend on constrained dofs. A problem with the presented implementation +# is that we are currently unable to strictly enforce constraint everywhere in the internal +# time integration process of [DifferentialEquations.jl](https://github.com/SciML/DifferentialEquations.jl), +# hence the values might differ, resulting in worse error estimates. +# We try to resolve this issue in the future. Volunteers are also welcome to take a look into this! +struct FreeDofErrorNorm + ch::ConstraintHandler +end +(fe_norm::FreeDofErrorNorm)(u::Union{AbstractFloat, Complex}, t) = DiffEqBase.ODE_DEFAULT_NORM(u, t) +(fe_norm::FreeDofErrorNorm)(u::AbstractArray, t) = DiffEqBase.ODE_DEFAULT_NORM(u[fe_norm.ch.free_dofs], t) + +# Now we can put everything together by specifying how to solve the problem. +# We want to use an adaptive variant of the implicit Euler method. Further we +# enable the progress bar with the `progress` and `progress_steps` arguments. +# Finally we have to communicate the time step length and initialization +# algorithm. Since we start with a valid initial state we do not use one of +# DifferentialEquations.jl initialization algorithms. +# !!! note "DAE initialization" +# At the time of writing this [no Hessenberg index 2 initialization is implemented](https://github.com/SciML/OrdinaryDiffEq.jl/issues/1019). +# +# To visualize the result we export the grid and our fields +# to VTK-files, which can be viewed in [ParaView](https://www.paraview.org/) +# by utilizing the corresponding pvd file. +timestepper = Rodas5P(autodiff = false, step_limiter! = ferrite_limiter!); +# timestepper = ImplicitEuler(nlsolve=NonlinearSolveAlg(OrdinaryDiffEq.NonlinearSolve.NewtonRaphson(autodiff=OrdinaryDiffEq.AutoFiniteDiff()); max_iter=50), step_limiter! = ferrite_limiter!) #src +#NOTE! This is left for future reference #src +# function algebraicmultigrid(W,du,u,p,t,newW,Plprev,Prprev,solverdata) #src +# if newW === nothing || newW #src +# Pl = aspreconditioner(ruge_stuben(convert(AbstractMatrix,W))) #src +# else #src +# Pl = Plprev #src +# end #src +# Pl,nothing #src +# end #src +# timestepper = ImplicitEuler(linsolve = IterativeSolversJL_GMRES(; abstol=1e-8, reltol=1e-6), precs=algebraicmultigrid, concrete_jac=true) #src + +# !!! info "Debugging convergence issues" +# We can obtain some debug information from OrdinaryDiffEq by wrapping the following section into a [debug logger](https://docs.julialang.org/en/v1/stdlib/Logging/#Example:-Enable-debug-level-messages). +integrator = init( + problem, timestepper; initializealg = NoInit(), dt = Δt₀, + adaptive = true, abstol = 1.0e-4, reltol = 1.0e-5, + progress = true, progress_steps = 1, + verbose = true, internalnorm = FreeDofErrorNorm(ch), d_discontinuities = [1.0] +); + + +# !!! note "Export of solution" +# Exporting interpolated solutions of problems containing mass matrices is currently broken. +# Thus, the `intervals` iterator is used. Note that `solve` holds all solutions in the memory. +pvd = paraview_collection("vortex-street") +for (step, (u, t)) in enumerate(intervals(integrator)) + VTKGridFile("vortex-street-$step", dh) do vtk + write_solution(vtk, dh, u) + pvd[t] = vtk + end +end +vtk_save(pvd); + + +using Test #hide +if IS_CI #hide + function compute_divergence(dh, u, cellvalues_v) #hide + divv = 0.0 #hide + for cell in CellIterator(dh) #hide + Ferrite.reinit!(cellvalues_v, cell) #hide + for q_point in 1:getnquadpoints(cellvalues_v) #hide + dΩ = getdetJdV(cellvalues_v, q_point) #hide + #hide + all_celldofs = celldofs(cell) #hide + v_celldofs = all_celldofs[dof_range(dh, :v)] #hide + v_cell = u[v_celldofs] #hide + #hide + divv += function_divergence(cellvalues_v, q_point, v_cell) * dΩ #hide + end #hide + end #hide + return divv #hide + end #hide + let #hide + u = copy(integrator.u) #hide + Δdivv = abs(compute_divergence(dh, u, cellvalues_v)) #hide + @test isapprox(Δdivv, 0.0, atol = 1.0e-12) #hide + #hide + Δv = 0.0 #hide + for cell in CellIterator(dh) #hide + Ferrite.reinit!(cellvalues_v, cell) #hide + all_celldofs = celldofs(cell) #hide + v_celldofs = all_celldofs[dof_range(dh, :v)] #hide + v_cell = u[v_celldofs] #hide + coords = getcoordinates(cell) #hide + for q_point in 1:getnquadpoints(cellvalues_v) #hide + dΩ = getdetJdV(cellvalues_v, q_point) #hide + coords_qp = spatial_coordinate(cellvalues_v, q_point, coords) #hide + v = function_value(cellvalues_v, q_point, v_cell) #hide + Δv += norm(v - parabolic_inflow_profile(coords_qp, T))^2 * dΩ #hide + end #hide + end #hide + @test isapprox(sqrt(Δv), 0.0, atol = 1.0e-3) #hide + end #hide + nothing #hide +end #hide + +#md # ## [Plain program](@id ns_vs_diffeq-plain-program) +#md # +#md # Here follows a version of the program without any comments. +#md # The file is also available here: [`ns_vs_diffeq.jl`](ns_vs_diffeq.jl). +#md # +#md # ```julia +#md # @__CODE__ +#md # ``` diff --git a/previews/PR798/literate-tutorials/plasticity.jl b/previews/PR798/literate-tutorials/plasticity.jl new file mode 100644 index 0000000000..b1e4cbf60d --- /dev/null +++ b/previews/PR798/literate-tutorials/plasticity.jl @@ -0,0 +1,391 @@ +# # [Von Mises plasticity](@id tutorial-plasticity) +# +# ![Shows the von Mises stress distribution in a cantilever beam.](plasticity.png) +# +# *Figure 1.* A coarse mesh solution of a cantilever beam subjected to a load +# causing plastic deformations. The initial yield limit is 200 MPa but due to +# hardening it increases up to approximately 240 MPa. +# +#- +#md # !!! tip +#md # This example is also available as a Jupyter notebook: +#md # [`plasticity.ipynb`](@__NBVIEWER_ROOT_URL__/tutorials/plasticity.ipynb). +#- +# +# ## Introduction +# +# This example illustrates the use of a nonlinear material model in Ferrite. +# The particular model is von Mises plasticity (also know as J₂-plasticity) with +# isotropic hardening. The model is fully 3D, meaning that no assumptions like *plane stress* +# or *plane strain* are introduced. +# +# Also note that the theory of the model is not described here, instead one is +# referred to standard textbooks on material modeling. +# +# To illustrate the use of the plasticity model, we setup and solve a FE-problem +# consisting of a cantilever beam loaded at its free end. But first, we shortly +# describe the parts of the implementation dealing with the material modeling. + +# ## Material modeling +# This section describes the `struct`s and methods used to implement the material +# model + +# ### Material parameters and state variables +# +# Start by loading some necessary packages +using Ferrite, Tensors, SparseArrays, LinearAlgebra, Printf + +# We define a J₂-plasticity-material, containing material parameters and the elastic +# stiffness Dᵉ (since it is constant) +struct J2Plasticity{T, S <: SymmetricTensor{4, 3, T}} + G::T # Shear modulus + K::T # Bulk modulus + σ₀::T # Initial yield limit + H::T # Hardening modulus + Dᵉ::S # Elastic stiffness tensor +end; + +# Next, we define a constructor for the material instance. +function J2Plasticity(E, ν, σ₀, H) + δ(i, j) = i == j ? 1.0 : 0.0 # helper function + G = E / 2(1 + ν) + K = E / 3(1 - 2ν) + + Isymdev(i, j, k, l) = 0.5 * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k)) - 1.0 / 3.0 * δ(i, j) * δ(k, l) + temp(i, j, k, l) = 2.0G * (0.5 * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k)) + ν / (1.0 - 2.0ν) * δ(i, j) * δ(k, l)) + Dᵉ = SymmetricTensor{4, 3}(temp) + return J2Plasticity(G, K, σ₀, H, Dᵉ) +end; + +#md # !!! note +#md # Above, we defined a constructor `J2Plasticity(E, ν, σ₀, H)` in terms of the more common +#md # material parameters ``E`` and ``ν`` - simply as a convenience for the user. +#md # + +# Define a `struct` to store the material state for a Gauss point. +struct MaterialState{T, S <: SecondOrderTensor{3, T}} + ## Store "converged" values + ϵᵖ::S # plastic strain + σ::S # stress + k::T # hardening variable +end + +# Constructor for initializing a material state. Every quantity is set to zero. +function MaterialState() + return MaterialState( + zero(SymmetricTensor{2, 3}), + zero(SymmetricTensor{2, 3}), + 0.0 + ) +end + +# For later use, during the post-processing step, we define a function to +# compute the von Mises effective stress. +function vonMises(σ) + s = dev(σ) + return sqrt(3.0 / 2.0 * s ⊡ s) +end; + +# ## Constitutive driver +# +# This is the actual method which computes the stress and material tangent +# stiffness in a given integration point. +# Input is the current strain and the material state from the previous timestep. +function compute_stress_tangent(ϵ::SymmetricTensor{2, 3}, material::J2Plasticity, state::MaterialState) + ## unpack some material parameters + G = material.G + H = material.H + + ## We use (•)ᵗ to denote *trial*-values + σᵗ = material.Dᵉ ⊡ (ϵ - state.ϵᵖ) # trial-stress + sᵗ = dev(σᵗ) # deviatoric part of trial-stress + J₂ = 0.5 * sᵗ ⊡ sᵗ # second invariant of sᵗ + σᵗₑ = sqrt(3.0 * J₂) # effective trial-stress (von Mises stress) + σʸ = material.σ₀ + H * state.k # Previous yield limit + + φᵗ = σᵗₑ - σʸ # Trial-value of the yield surface + + if φᵗ < 0.0 # elastic loading + return σᵗ, material.Dᵉ, MaterialState(state.ϵᵖ, σᵗ, state.k) + else # plastic loading + h = H + 3G + μ = φᵗ / h # plastic multiplier + + c1 = 1 - 3G * μ / σᵗₑ + s = c1 * sᵗ # updated deviatoric stress + σ = s + vol(σᵗ) # updated stress + + ## Compute algorithmic tangent stiffness ``D = \frac{\Delta \sigma }{\Delta \epsilon}`` + κ = H * (state.k + μ) # drag stress + σₑ = material.σ₀ + κ # updated yield surface + + δ(i, j) = i == j ? 1.0 : 0.0 + Isymdev(i, j, k, l) = 0.5 * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k)) - 1.0 / 3.0 * δ(i, j) * δ(k, l) + Q(i, j, k, l) = Isymdev(i, j, k, l) - 3.0 / (2.0 * σₑ^2) * s[i, j] * s[k, l] + b = (3G * μ / σₑ) / (1.0 + 3G * μ / σₑ) + + Dtemp(i, j, k, l) = -2G * b * Q(i, j, k, l) - 9G^2 / (h * σₑ^2) * s[i, j] * s[k, l] + D = material.Dᵉ + SymmetricTensor{4, 3}(Dtemp) + + ## Return new state + Δϵᵖ = 3 / 2 * μ / σₑ * s # plastic strain + ϵᵖ = state.ϵᵖ + Δϵᵖ # plastic strain + k = state.k + μ # hardening variable + return σ, D, MaterialState(ϵᵖ, σ, k) + end +end + +# ## FE-problem +# What follows are methods for assembling and and solving the FE-problem. +function create_values(interpolation) + ## setup quadrature rules + qr = QuadratureRule{RefTetrahedron}(2) + facet_qr = FacetQuadratureRule{RefTetrahedron}(3) + + ## cell and facetvalues for u + cellvalues_u = CellValues(qr, interpolation) + facetvalues_u = FacetValues(facet_qr, interpolation) + + return cellvalues_u, facetvalues_u +end; + +# ### Add degrees of freedom +function create_dofhandler(grid, interpolation) + dh = DofHandler(grid) + add!(dh, :u, interpolation) # add a displacement field with 3 components + close!(dh) + return dh +end + +# ### Boundary conditions +function create_bc(dh, grid) + dbcs = ConstraintHandler(dh) + ## Clamped on the left side + dofs = [1, 2, 3] + dbc = Dirichlet(:u, getfacetset(grid, "left"), (x, t) -> [0.0, 0.0, 0.0], dofs) + add!(dbcs, dbc) + close!(dbcs) + return dbcs +end; + + +# ### Assembling of element contributions +# +# * Residual vector `r` +# * Tangent stiffness `K` +function doassemble!( + K::SparseMatrixCSC, r::Vector, cellvalues::CellValues, dh::DofHandler, + material::J2Plasticity, u, states, states_old + ) + assembler = start_assemble(K, r) + nu = getnbasefunctions(cellvalues) + re = zeros(nu) # element residual vector + ke = zeros(nu, nu) # element tangent matrix + + for (i, cell) in enumerate(CellIterator(dh)) + fill!(ke, 0) + fill!(re, 0) + eldofs = celldofs(cell) + ue = u[eldofs] + state = @view states[:, i] + state_old = @view states_old[:, i] + assemble_cell!(ke, re, cell, cellvalues, material, ue, state, state_old) + assemble!(assembler, eldofs, ke, re) + end + return K, r +end + +# Compute element contribution to the residual and the tangent. +#md # !!! note +#md # Due to symmetry, we only compute the lower half of the tangent +#md # and then symmetrize it. +#md # +function assemble_cell!(Ke, re, cell, cellvalues, material, ue, state, state_old) + n_basefuncs = getnbasefunctions(cellvalues) + reinit!(cellvalues, cell) + + for q_point in 1:getnquadpoints(cellvalues) + ## For each integration point, compute stress and material stiffness + ϵ = function_symmetric_gradient(cellvalues, q_point, ue) # Total strain + σ, D, state[q_point] = compute_stress_tangent(ϵ, material, state_old[q_point]) + + dΩ = getdetJdV(cellvalues, q_point) + for i in 1:n_basefuncs + δϵ = shape_symmetric_gradient(cellvalues, q_point, i) + re[i] += (δϵ ⊡ σ) * dΩ # add internal force to residual + for j in 1:i # loop only over lower half + Δϵ = shape_symmetric_gradient(cellvalues, q_point, j) + Ke[i, j] += δϵ ⊡ D ⊡ Δϵ * dΩ + end + end + end + symmetrize_lower!(Ke) + return +end + +# Helper function to symmetrize the material tangent +function symmetrize_lower!(K) + for i in 1:size(K, 1) + for j in (i + 1):size(K, 1) + K[i, j] = K[j, i] + end + end + return +end; + +function doassemble_neumann!(r, dh, facetset, facetvalues, t) + n_basefuncs = getnbasefunctions(facetvalues) + re = zeros(n_basefuncs) # element residual vector + for fc in FacetIterator(dh, facetset) + ## Add traction as a negative contribution to the element residual `re`: + reinit!(facetvalues, fc) + fill!(re, 0) + for q_point in 1:getnquadpoints(facetvalues) + dΓ = getdetJdV(facetvalues, q_point) + for i in 1:n_basefuncs + δu = shape_value(facetvalues, q_point, i) + re[i] -= (δu ⋅ t) * dΓ + end + end + assemble!(r, celldofs(fc), re) + end + return r +end + +# Define a function which solves the FE-problem. +function solve() + ## Define material parameters + E = 200.0e9 # [Pa] + H = E / 20 # [Pa] + ν = 0.3 # [-] + σ₀ = 200.0e6 # [Pa] + material = J2Plasticity(E, ν, σ₀, H) + + L = 10.0 # beam length [m] + w = 1.0 # beam width [m] + h = 1.0 # beam height[m] + n_timesteps = 10 + u_max = zeros(n_timesteps) + traction_magnitude = 1.0e7 * range(0.5, 1.0, length = n_timesteps) + + ## Create geometry, dofs and boundary conditions + n = 2 + nels = (10n, n, 2n) # number of elements in each spatial direction + P1 = Vec((0.0, 0.0, 0.0)) # start point for geometry + P2 = Vec((L, w, h)) # end point for geometry + grid = generate_grid(Tetrahedron, nels, P1, P2) + interpolation = Lagrange{RefTetrahedron, 1}()^3 + + dh = create_dofhandler(grid, interpolation) # JuaFEM helper function + dbcs = create_bc(dh, grid) # create Dirichlet boundary-conditions + + cellvalues, facetvalues = create_values(interpolation) + + ## Pre-allocate solution vectors, etc. + n_dofs = ndofs(dh) # total number of dofs + u = zeros(n_dofs) # solution vector + Δu = zeros(n_dofs) # displacement correction + r = zeros(n_dofs) # residual + K = allocate_matrix(dh) # tangent stiffness matrix + + ## Create material states. One array for each cell, where each element is an array of material- + ## states - one for each integration point + nqp = getnquadpoints(cellvalues) + states = [MaterialState() for _ in 1:nqp, _ in 1:getncells(grid)] + states_old = [MaterialState() for _ in 1:nqp, _ in 1:getncells(grid)] + + ## Newton-Raphson loop + NEWTON_TOL = 1 # 1 N + print("\n Starting Netwon iterations:\n") + + for timestep in 1:n_timesteps + t = timestep # actual time (used for evaluating d-bndc) + traction = Vec((0.0, 0.0, traction_magnitude[timestep])) + newton_itr = -1 + print("\n Time step @time = $timestep:\n") + update!(dbcs, t) # evaluates the D-bndc at time t + apply!(u, dbcs) # set the prescribed values in the solution vector + + while true + newton_itr += 1 + if newton_itr > 8 + error("Reached maximum Newton iterations, aborting") + break + end + ## Tangent and residual contribution from the cells (volume integral) + doassemble!(K, r, cellvalues, dh, material, u, states, states_old) + ## Residual contribution from the Neumann boundary (surface integral) + doassemble_neumann!(r, dh, getfacetset(grid, "right"), facetvalues, traction) + norm_r = norm(r[Ferrite.free_dofs(dbcs)]) + + print("Iteration: $newton_itr \tresidual: $(@sprintf("%.8f", norm_r))\n") + if norm_r < NEWTON_TOL + break + end + + apply_zero!(K, r, dbcs) + Δu = Symmetric(K) \ r + u -= Δu + end + + ## Update the old states with the converged values for next timestep + states_old .= states + + u_max[timestep] = maximum(abs, u) # maximum displacement in current timestep + end + + ## ## Postprocessing + ## Only a vtu-file corresponding to the last time-step is exported. + ## + ## The following is a quick (and dirty) way of extracting average cell data for export. + mises_values = zeros(getncells(grid)) + κ_values = zeros(getncells(grid)) + for (el, cell_states) in enumerate(eachcol(states)) + for state in cell_states + mises_values[el] += vonMises(state.σ) + κ_values[el] += state.k * material.H + end + mises_values[el] /= length(cell_states) # average von Mises stress + κ_values[el] /= length(cell_states) # average drag stress + end + VTKGridFile("plasticity", dh) do vtk + write_solution(vtk, dh, u) # displacement field + write_cell_data(vtk, mises_values, "von Mises [Pa]") + write_cell_data(vtk, κ_values, "Drag stress [Pa]") + end + + return u_max, traction_magnitude +end + +# Solve the FE-problem and for each time-step extract maximum displacement and +# the corresponding traction load. Also compute the limit-traction-load +u_max, traction_magnitude = solve(); + +# Finally we plot the load-displacement curve. +using Plots +plot( + vcat(0.0, u_max), # add the origin as a point + vcat(0.0, traction_magnitude), + linewidth = 2, + title = "Traction-displacement", + label = nothing, + markershape = :auto +) +ylabel!("Traction [Pa]") +xlabel!("Maximum deflection [m]") + +# *Figure 2.* Load-displacement-curve for the beam, showing a clear decrease +# in stiffness as more material starts to yield. + +## test the result #src +using Test #src +@test norm(u_max[end]) ≈ 0.254452645 #src + +#md # ## [Plain program](@id plasticity-plain-program) +#md # +#md # Here follows a version of the program without any comments. +#md # The file is also available here: [`plasticity.jl`](plasticity.jl). +#md # +#md # ```julia +#md # @__CODE__ +#md # ``` diff --git a/previews/PR798/literate-tutorials/plasticity.png b/previews/PR798/literate-tutorials/plasticity.png new file mode 100644 index 0000000000..579e2a72d6 Binary files /dev/null and b/previews/PR798/literate-tutorials/plasticity.png differ diff --git a/previews/PR798/literate-tutorials/porous_media.jl b/previews/PR798/literate-tutorials/porous_media.jl new file mode 100644 index 0000000000..71c0837d62 --- /dev/null +++ b/previews/PR798/literate-tutorials/porous_media.jl @@ -0,0 +1,377 @@ +# # Porous media + +# Porous media is a two-phase material, consisting of solid parts and a liquid occupying +# the pores inbetween. +# Using the porous media theory, we can model such a material without explicitly +# resolving the microstructure, but by considering the interactions between the +# solid and liquid. In this example, we will additionally consider larger linear +# elastic solid aggregates that are impermeable. Hence, there is no liquids in +# these particles and the only unknown variable is the displacement field `:u`. +# In the porous media, denoted the matrix, we have both the displacement field, +# `:u`, as well as the liquid pressure, `:p`, as unknown. The simulation result +# is shown below +# +# ![Pressure evolution.](porous_media.gif) +# +# ## Theory of porous media +# The strong forms are given as +# ```math +# \begin{aligned} +# \boldsymbol{\sigma}(\boldsymbol{\epsilon}, p) \cdot \boldsymbol{\nabla} &= \boldsymbol{0} \\ +# \dot{\Phi}(\boldsymbol{\epsilon}, p) + \boldsymbol{w}(p) \cdot \boldsymbol{\nabla} &= 0 +# \end{aligned} +# ``` +# where +# ``\boldsymbol{\epsilon} = \left[\boldsymbol{u}\otimes\boldsymbol{\nabla}\right]^\mathrm{sym}`` +# The constitutive relationships are +# ```math +# \begin{aligned} +# \boldsymbol{\sigma} &= \boldsymbol{\mathsf{C}}:\boldsymbol{\epsilon} - \alpha p \boldsymbol{I} \\ +# \boldsymbol{w} &= - k \boldsymbol{\nabla} p \\ +# \Phi &= \phi + \alpha \mathrm{tr}(\boldsymbol{\epsilon}) + \beta p +# \end{aligned} +# ``` +# with +# ``\boldsymbol{\mathsf{C}}=2G \boldsymbol{\mathsf{I}}^\mathrm{dev} + 3K \boldsymbol{I}\otimes\boldsymbol{I}``. +# The material parameters are then the +# shear modulus, ``G``, +# bulk modulus, ``K``, +# permeability, ``k``, +# Biot's coefficient, ``\alpha``, and +# liquid compressibility, ``\beta``. +# The porosity, ``\phi``, doesn't enter into the equations +# (A different porosity leads to different skeleton stiffness and permeability). +# +# +# The variational (weak) form can then be derived for the variations ``\boldsymbol{\delta u}`` +# and ``\delta p`` as +# ```math +# \begin{aligned} +# \int_\Omega \left[\left[\boldsymbol{\delta u}\otimes\boldsymbol{\nabla}\right]^\mathrm{sym}: +# \boldsymbol{\mathsf{C}}:\boldsymbol{\epsilon} - \boldsymbol{\delta u} \cdot \boldsymbol{\nabla} \alpha p\right] \mathrm{d}\Omega +# &= \int_\Gamma \boldsymbol{\delta u} \cdot \boldsymbol{t} \mathrm{d} \Gamma \\ +# \int_\Omega \left[\delta p \left[\alpha \dot{\boldsymbol{u}} \cdot \boldsymbol{\nabla} + \beta \dot{p}\right] + +# \boldsymbol{\nabla}(\delta p) \cdot [k \boldsymbol{\nabla}]\right] \mathrm{d}\Omega +# &= \int_\Gamma \delta p w_\mathrm{n} \mathrm{d} \Gamma +# \end{aligned} +# ``` +# where ``\boldsymbol{t}=\boldsymbol{n}\cdot\boldsymbol{\sigma}`` is the traction and +# ``w_\mathrm{n} = \boldsymbol{n}\cdot\boldsymbol{w}`` is the normal flux. +# +# ### Finite element form +# Discretizing in space using finite elements, we obtain the vector equation +# ``r_i = f_i^\mathrm{int} - f_{i}^\mathrm{ext}`` where ``f^\mathrm{ext}`` are the external +# "forces", and ``f_i^\mathrm{int}`` are the internal "forces". We split this into the +# displacement part ``r_i^\mathrm{u} = f_i^\mathrm{int,u} - f_{i}^\mathrm{ext,u}`` and +# pressure part ``r_i^\mathrm{p} = f_i^\mathrm{int,p} - f_{i}^\mathrm{ext,p}`` +# to obtain the discretized equation system +# ```math +# \begin{aligned} +# f_i^\mathrm{int,u} &= \int_\Omega [\boldsymbol{\delta N}^\mathrm{u}_i\otimes\boldsymbol{\nabla}]^\mathrm{sym} : \boldsymbol{\mathsf{C}} : [\boldsymbol{u}\otimes\boldsymbol{\nabla}]^\mathrm{sym} \ +# - [\boldsymbol{\delta N}^\mathrm{u}_i \cdot \boldsymbol{\nabla}] \alpha p \mathrm{d}\Omega +# &= \int_\Gamma \boldsymbol{\delta N}^\mathrm{u}_i \cdot \boldsymbol{t} \mathrm{d} \Gamma \\ +# f_i^\mathrm{int,p} &= \int_\Omega \delta N_i^\mathrm{p} [\alpha [\dot{\boldsymbol{u}}\cdot\boldsymbol{\nabla}] + \beta\dot{p}] + \boldsymbol{\nabla}(\delta N_i^\mathrm{p}) \cdot [k \boldsymbol{\nabla}(p)] \mathrm{d}\Omega +# &= \int_\Gamma \delta N_i^\mathrm{p} w_\mathrm{n} \mathrm{d} \Gamma +# \end{aligned} +# ``` +# Approximating the time-derivatives, ``\dot{\boldsymbol{u}}\approx \left[\boldsymbol{u}-{}^n\boldsymbol{u}\right]/\Delta t`` +# and ``\dot{p}\approx \left[p-{}^np\right]/\Delta t``, we can implement the finite element equations in the residual form +# ``r_i(\boldsymbol{a}(t), t) = 0`` where the vector ``\boldsymbol{a}`` contains all unknown displacements ``u_i`` and pressures ``p_i``. +# +# The jacobian, ``K_{ij} = \partial r_i/\partial a_j``, is then split into four parts, +# ```math +# \begin{aligned} +# K_{ij}^\mathrm{uu} &= \frac{\partial r_i^\mathrm{u}}{\partial u_j} = \int_\Omega [\boldsymbol{\delta N}^\mathrm{u}_i\otimes\boldsymbol{\nabla}]^\mathrm{sym} : \boldsymbol{\mathsf{C}} : [\boldsymbol{N}_j^\mathrm{u}\otimes\boldsymbol{\nabla}]^\mathrm{sym}\ \mathrm{d}\Omega \\ +# K_{ij}^\mathrm{up} &= \frac{\partial r_i^\mathrm{u}}{\partial p_j} = - \int_\Omega [\boldsymbol{\delta N}^\mathrm{u}_i \cdot \boldsymbol{\nabla}] \alpha N_j^\mathrm{p}\ \mathrm{d}\Omega \\ +# K_{ij}^\mathrm{pu} &= \frac{\partial r_i^\mathrm{p}}{\partial u_j} = \int_\Omega \delta N_i^\mathrm{p} \frac{\alpha}{\Delta t} [\boldsymbol{N}_j^\mathrm{u} \cdot\boldsymbol{\nabla}]\ \mathrm{d}\Omega\\ +# K_{ij}^\mathrm{pp} &= \frac{\partial r_i^\mathrm{p}}{\partial p_j} = \int_\Omega \delta N_i^\mathrm{p} \frac{N_j^\mathrm{p}}{\Delta t} + \boldsymbol{\nabla}(\delta N_i^\mathrm{p}) \cdot [k \boldsymbol{\nabla}(N_j^\mathrm{p})] \mathrm{d}\Omega +# \end{aligned} +# ``` +# +# We could assemble one stiffness matrix and one mass matrix, which would be constant, but for simplicity we only consider a single +# system matrix that depends on the time step, and assemble this for each step. The equations are still linear, so no iterations are required. +# +# ## Implementation +# We now solve the problem step by step. The full program with fewer comments is found in +#md # the final [section](@ref porous-media-plain-program) +# +# Required packages +using Ferrite, FerriteMeshParser, Tensors, WriteVTK + +# ### Elasticity +# We start by defining the elastic material type, containing the elastic stiffness, +# for the linear elastic impermeable solid aggregates. +struct Elastic{T} + C::SymmetricTensor{4, 2, T, 9} +end +function Elastic(; E = 20.0e3, ν = 0.3) + G = E / 2(1 + ν) + K = E / 3(1 - 2ν) + I2 = one(SymmetricTensor{2, 2}) + I4vol = I2 ⊗ I2 + I4dev = minorsymmetric(otimesu(I2, I2)) - I4vol / 3 + return Elastic(2G * I4dev + K * I4vol) +end; + +# Next, we define the element routine for the solid aggregates, where we dispatch on the +# `Elastic` material struct. Note that the unused inputs here are used for the porous matrix below. +function element_routine!(Ke, re, material::Elastic, cv, cell, a, args...) + reinit!(cv, cell) + n_basefuncs = getnbasefunctions(cv) + + for q_point in 1:getnquadpoints(cv) + dΩ = getdetJdV(cv, q_point) + ϵ = function_symmetric_gradient(cv, q_point, a) + σ = material.C ⊡ ϵ + for i in 1:n_basefuncs + δ∇N = shape_symmetric_gradient(cv, q_point, i) + re[i] += (δ∇N ⊡ σ) * dΩ + for j in 1:n_basefuncs + ∇N = shape_symmetric_gradient(cv, q_point, j) + Ke[i, j] += (δ∇N ⊡ material.C ⊡ ∇N) * dΩ + end + end + end + return +end; + +# ### PoroElasticity +# To define the poroelastic material, we re-use the elastic part from above for +# the skeleton, and add the additional required material parameters. +struct PoroElastic{T} + elastic::Elastic{T} ## Skeleton stiffness + k::T ## Permeability of liquid [mm^4/(Ns)] + ϕ::T ## Porosity [-] + α::T ## Biot's coefficient [-] + β::T ## Liquid compressibility [1/MPa] +end +PoroElastic(; elastic, k, ϕ, α, β) = PoroElastic(elastic, k, ϕ, α, β); + +# The element routine requires a few more inputs since we have two fields, as well +# as the dependence on the rates of the displacements and pressure. +# Again, we dispatch on the material type. +function element_routine!(Ke, re, m::PoroElastic, cvs::Tuple, cell, a, a_old, Δt, sdh) + ## Setup cellvalues and give easier names + reinit!.(cvs, (cell,)) + cv_u, cv_p = cvs + dr_u = dof_range(sdh, :u) + dr_p = dof_range(sdh, :p) + + C = m.elastic.C ## Elastic stiffness + + ## Assemble stiffness and force vectors + for q_point in 1:getnquadpoints(cv_u) + dΩ = getdetJdV(cv_u, q_point) + p = function_value(cv_p, q_point, a, dr_p) + p_old = function_value(cv_p, q_point, a_old, dr_p) + pdot = (p - p_old) / Δt + ∇p = function_gradient(cv_p, q_point, a, dr_p) + ϵ = function_symmetric_gradient(cv_u, q_point, a, dr_u) + tr_ϵ_old = function_divergence(cv_u, q_point, a_old, dr_u) + tr_ϵ_dot = (tr(ϵ) - tr_ϵ_old) / Δt + σ_eff = C ⊡ ϵ + ## Variation of u_i + for (iᵤ, Iᵤ) in pairs(dr_u) + ∇δNu = shape_symmetric_gradient(cv_u, q_point, iᵤ) + div_δNu = shape_divergence(cv_u, q_point, iᵤ) + re[Iᵤ] += (∇δNu ⊡ σ_eff - div_δNu * p * m.α) * dΩ + for (jᵤ, Jᵤ) in pairs(dr_u) + ∇Nu = shape_symmetric_gradient(cv_u, q_point, jᵤ) + Ke[Iᵤ, Jᵤ] += (∇δNu ⊡ C ⊡ ∇Nu) * dΩ + end + for (jₚ, Jₚ) in pairs(dr_p) + Np = shape_value(cv_p, q_point, jₚ) + Ke[Iᵤ, Jₚ] -= (div_δNu * m.α * Np) * dΩ + end + end + ## Variation of p_i + for (iₚ, Iₚ) in pairs(dr_p) + δNp = shape_value(cv_p, q_point, iₚ) + ∇δNp = shape_gradient(cv_p, q_point, iₚ) + re[Iₚ] += (δNp * (m.α * tr_ϵ_dot + m.β * pdot) + m.k * (∇δNp ⋅ ∇p)) * dΩ + for (jᵤ, Jᵤ) in pairs(dr_u) + div_Nu = shape_divergence(cv_u, q_point, jᵤ) + Ke[Iₚ, Jᵤ] += δNp * (m.α / Δt) * div_Nu * dΩ + end + for (jₚ, Jₚ) in pairs(dr_p) + ∇Np = shape_gradient(cv_p, q_point, jₚ) + Np = shape_value(cv_p, q_point, jₚ) + Ke[Iₚ, Jₚ] += (δNp * m.β * Np / Δt + m.k * (∇δNp ⋅ ∇Np)) * dΩ + end + end + end + return +end; + +# ### Assembly +# To organize the different domains, we'll first define a container type +struct FEDomain{M, CV, SDH <: SubDofHandler} + material::M + cellvalues::CV + sdh::SDH +end; + +# And then we can loop over a vector of such domains, allowing us to +# loop over each domain, to assemble the contributions from each +# cell in that domain (given by the `SubDofHandler`'s cellset) +function doassemble!(K, r, domains::Vector{<:FEDomain}, a, a_old, Δt) + assembler = start_assemble(K, r) + for domain in domains + doassemble!(assembler, domain, a, a_old, Δt) + end + return +end; + +# For one domain (corresponding to a specific SubDofHandler), +# we can then loop over all cells in its cellset. Doing this +# in a separate function (instead of a nested loop), ensures +# that the calls to the `element_routine` are type stable, +# which can be important for good performance. +function doassemble!(assembler, domain::FEDomain, a, a_old, Δt) + material = domain.material + cv = domain.cellvalues + sdh = domain.sdh + n = ndofs_per_cell(sdh) + Ke = zeros(n, n) + re = zeros(n) + ae_old = zeros(n) + ae = zeros(n) + for cell in CellIterator(sdh) + ## copy values from a to ae + map!(i -> a[i], ae, celldofs(cell)) + map!(i -> a_old[i], ae_old, celldofs(cell)) + fill!(Ke, 0) + fill!(re, 0) + element_routine!(Ke, re, material, cv, cell, ae, ae_old, Δt, sdh) + assemble!(assembler, celldofs(cell), Ke, re) + end + return +end; + +# ### Mesh import +# In this example, we import the mesh from the Abaqus input file, [`porous_media_0p25.inp`](porous_media_0p25.inp) using FerriteMeshParser's +# `get_ferrite_grid` function. We then create one cellset for each phase (solid and porous) +# for each element type. These 4 sets will later be used in their own `SubDofHandler` +function get_grid() + ## Import grid from abaqus mesh + grid = get_ferrite_grid(joinpath(@__DIR__, "porous_media_0p25.inp")) + + ## Create cellsets for each fieldhandler + addcellset!(grid, "solid3", intersect(getcellset(grid, "solid"), getcellset(grid, "CPS3"))) + addcellset!(grid, "solid4", intersect(getcellset(grid, "solid"), getcellset(grid, "CPS4R"))) + addcellset!(grid, "porous3", intersect(getcellset(grid, "porous"), getcellset(grid, "CPS3"))) + addcellset!(grid, "porous4", intersect(getcellset(grid, "porous"), getcellset(grid, "CPS4R"))) + return grid +end; + +# ### Problem setup +# Define the finite element interpolation, integration, and boundary conditions. +function setup_problem(; t_rise = 0.1, u_max = -0.1) + + grid = get_grid() + + ## Define materials + m_solid = Elastic(; E = 20.0e3, ν = 0.3) + m_porous = PoroElastic(; elastic = Elastic(; E = 10.0e3, ν = 0.3), β = 1 / 15.0e3, α = 0.9, k = 5.0e-3, ϕ = 0.8) + + ## Define interpolations + ipu_quad = Lagrange{RefQuadrilateral, 2}()^2 + ipu_tri = Lagrange{RefTriangle, 2}()^2 + ipp_quad = Lagrange{RefQuadrilateral, 1}() + ipp_tri = Lagrange{RefTriangle, 1}() + + ## Quadrature rules + qr_quad = QuadratureRule{RefQuadrilateral}(2) + qr_tri = QuadratureRule{RefTriangle}(2) + + ## CellValues + cvu_quad = CellValues(qr_quad, ipu_quad) + cvu_tri = CellValues(qr_tri, ipu_tri) + cvp_quad = CellValues(qr_quad, ipp_quad) + cvp_tri = CellValues(qr_tri, ipp_tri) + + ## Setup the DofHandler + dh = DofHandler(grid) + ## Solid quads + sdh_solid_quad = SubDofHandler(dh, getcellset(grid, "solid4")) + add!(sdh_solid_quad, :u, ipu_quad) + ## Solid triangles + sdh_solid_tri = SubDofHandler(dh, getcellset(grid, "solid3")) + add!(sdh_solid_tri, :u, ipu_tri) + ## Porous quads + sdh_porous_quad = SubDofHandler(dh, getcellset(grid, "porous4")) + add!(sdh_porous_quad, :u, ipu_quad) + add!(sdh_porous_quad, :p, ipp_quad) + ## Porous triangles + sdh_porous_tri = SubDofHandler(dh, getcellset(grid, "porous3")) + add!(sdh_porous_tri, :u, ipu_tri) + add!(sdh_porous_tri, :p, ipp_tri) + + close!(dh) + + ## Setup the domains + domains = [ + FEDomain(m_solid, cvu_quad, sdh_solid_quad), + FEDomain(m_solid, cvu_tri, sdh_solid_tri), + FEDomain(m_porous, (cvu_quad, cvp_quad), sdh_porous_quad), + FEDomain(m_porous, (cvu_tri, cvp_tri), sdh_porous_tri), + ] + + ## Boundary conditions + ## Sliding for u, except top which is compressed + ## Sealed for p, except top with prescribed zero pressure + addfacetset!(dh.grid, "sides", x -> x[1] < 1.0e-6 || x[1] ≈ 5.0) + addfacetset!(dh.grid, "top", x -> x[2] ≈ 10.0) + ch = ConstraintHandler(dh) + add!(ch, Dirichlet(:u, getfacetset(grid, "bottom"), (x, t) -> zero(Vec{1}), [2])) + add!(ch, Dirichlet(:u, getfacetset(grid, "sides"), (x, t) -> zero(Vec{1}), [1])) + add!(ch, Dirichlet(:u, getfacetset(grid, "top"), (x, t) -> u_max * clamp(t / t_rise, 0, 1), [2])) + add!(ch, Dirichlet(:p, getfacetset(grid, "top_p"), (x, t) -> 0.0)) + close!(ch) + + return dh, ch, domains +end; + +# ### Solving +# Given the `DofHandler`, `ConstraintHandler`, and `CellValues`, +# we can solve the problem by stepping through the time history +function solve(dh, ch, domains; Δt = 0.025, t_total = 1.0) + K = allocate_matrix(dh) + r = zeros(ndofs(dh)) + a = zeros(ndofs(dh)) + a_old = copy(a) + pvd = paraview_collection("porous_media") + step = 0 + for t in 0:Δt:t_total + if t > 0 + update!(ch, t) + apply!(a, ch) + doassemble!(K, r, domains, a, a_old, Δt) + apply_zero!(K, r, ch) + Δa = -K \ r + apply_zero!(Δa, ch) + a .+= Δa + copyto!(a_old, a) + end + step += 1 + VTKGridFile("porous_media_$step", dh) do vtk + write_solution(vtk, dh, a) + pvd[t] = vtk + end + end + vtk_save(pvd) + return +end; + +# Finally we call the functions to actually run the code +dh, ch, domains = setup_problem() +solve(dh, ch, domains); + +#md # ## [Plain program](@id porous-media-plain-program) +#md # +#md # Here follows a version of the program without any comments. +#md # The file is also available here: [`porous_media.jl`](porous_media.jl). +#md # +#md # ```julia +#md # @__CODE__ +#md # ``` diff --git a/previews/PR798/literate-tutorials/reactive_surface.jl b/previews/PR798/literate-tutorials/reactive_surface.jl new file mode 100644 index 0000000000..723c50a756 --- /dev/null +++ b/previews/PR798/literate-tutorials/reactive_surface.jl @@ -0,0 +1,336 @@ +# Putting this flag to false reproduces the figure shown in the example #src +# We check for laminar flow development in the CI #src +if isdefined(Main, :is_ci) #hide + IS_CI = Main.is_ci #hide +else #hide + IS_CI = false #hide +end #hide +nothing #hide +# # [Reactive surface](@id tutorial-reactive-surface) +# +# ![](reactive_surface.gif) +# +# *Figure 1*: Reactant concentration field of the Gray-Scott model on the unit sphere. +# +#- +#md # !!! tip +#md # This example is also available as a Jupyter notebook: +#md # [`reactive_surface.ipynb`](@__NBVIEWER_ROOT_URL__/tutorials/reactive_surface.ipynb). +#- +# +# ## Introduction +# +# This tutorial gives a quick tutorial on how to assemble and solve time-dependent problems +# on embedded surfaces. +# +# For this showcase we use the well known Gray-Scott model, which is a well-known reaction-diffusion +# system to study pattern formation. The strong form is given by +# +# ```math +# \begin{aligned} +# \partial_t r_1 &= \nabla \cdot (D_1 \nabla r_1) - r_1*r_2^2 + F *(1 - r_1) \quad \textbf{x} \in \Omega, \\ +# \partial_t r_2 &= \nabla \cdot (D_2 \nabla r_2) + r_1*r_2^2 - r_2*(F + k ) \quad \textbf{x} \in \Omega, +# \end{aligned} +# ``` +# +# where $r_1$ and $r_2$ are the reaction fields, $D_1$ and $D_2$ the diffusion tensors, +# $k$ is the conversion rate, $F$ is the feed rate and $\Omega$ the domain. Depending on the choice of +# parameters a different pattern can be observed. Please also note that the domain does not have a +# boundary. The corresponding weak form can be derived as usual. +# +# For simplicity we will solve the problem with the Lie-Troter-Godunov operator splitting technique with +# the classical reaction-diffusion split. In this method we split our problem in two problems, i.e. a heat +# problem and a pointwise reaction problem, and solve them alternatingly to advance in time. +# +# ## Solver details +# +# The main idea for the Lie-Trotter-Godunov scheme is simple. We can write down the reaction diffusion +# problem in an abstract way as +# ```math +# \partial_t \mathbf{r} = \mathcal{D}\mathbf{r} + R(\mathbf{r}) \quad \textbf{x} \in \Omega +# ``` +# where $\mathcal{D}$ is the diffusion operator and $R$ is the reaction operator. Notice that the right +# hand side is just the sum of two operators. Now with our operator splitting scheme we can advance a +# solution $\mathbf{r}(t_1)$ to $\mathbf{r}(t_2)$ by first solving a heat problem +# ```math +# \partial_t \mathbf{r}^{\mathrm{\mathrm{A}}} = \mathcal{D}\mathbf{r}^{\mathrm{A}} \quad \textbf{x} \in \Omega +# ``` +# with $\mathbf{r}^{\mathrm{A}}(t_1) = \mathbf{r}(t_1)$ on the time interval $t_1$ to $t_2$ and use +# the solution as the initial condition to solve the reaction problem +# ```math +# \partial_t \mathbf{r}^{\mathrm{B}} = R(\mathbf{r}^{\mathrm{B}}) \quad \textbf{x} \in \Omega +# ``` +# with $\mathbf{r}^{\mathrm{B}}(t_1) = \mathbf{r}^{\mathrm{A}}(t_2)$. +# This way we obtain a solution approximation $\mathbf{r}(t_2) \approx \mathbf{r}^{\mathrm{B}}(t_2)$. +# +# !!! note +# The operator splitting itself is an approximation, so even if we solve the subproblems analytically +# we end up with having only a solution approximation. We also do not have a beginner friendly reference +# for the theory behind operator splitting and can only refer to the original papers for each method. +# +#- +# ## Commented Program +# +# Now we solve the problem in Ferrite. What follows is a program spliced with comments. +#md # The full program, without comments, can be found in the next [section](@ref reactive_surface-plain-program). +# +# First we load Ferrite, and some other packages we need + +using Ferrite, FerriteGmsh +using BlockArrays, SparseArrays, LinearAlgebra, WriteVTK + +# ### Assembly routines +# Before we head into the assembly, we define a helper struct to control the dispatches. +struct GrayScottMaterial{T} + D₁::T + D₂::T + F::T + k::T +end; + +# The following assembly routines are written analogue to these found in previous tutorials. +function assemble_element_mass!(Me::Matrix, cellvalues::CellValues) + n_basefuncs = getnbasefunctions(cellvalues) + ## The mass matrices between the reactions are not coupled, so we get a blocked-strided matrix. + num_reactants = 2 + r₁range = 1:num_reactants:(num_reactants * n_basefuncs) + r₂range = 2:num_reactants:(num_reactants * n_basefuncs) + Me₁ = @view Me[r₁range, r₁range] + Me₂ = @view Me[r₂range, r₂range] + ## Reset to 0 + fill!(Me, 0) + ## Loop over quadrature points + for q_point in 1:getnquadpoints(cellvalues) + ## Get the quadrature weight + dΩ = getdetJdV(cellvalues, q_point) + ## Loop over test shape functions + for i in 1:n_basefuncs + δuᵢ = shape_value(cellvalues, q_point, i) + ## Loop over trial shape functions + for j in 1:n_basefuncs + δuⱼ = shape_value(cellvalues, q_point, j) + ## Add contribution to Ke + Me₁[i, j] += (δuᵢ * δuⱼ) * dΩ + Me₂[i, j] += (δuᵢ * δuⱼ) * dΩ + end + end + end + return nothing +end + +function assemble_element_diffusion!(De::Matrix, cellvalues::CellValues, material::GrayScottMaterial) + n_basefuncs = getnbasefunctions(cellvalues) + D₁ = material.D₁ + D₂ = material.D₂ + ## The diffusion between the reactions is not coupled, so we get a blocked-strided matrix. + num_reactants = 2 + r₁range = 1:num_reactants:(num_reactants * n_basefuncs) + r₂range = 2:num_reactants:(num_reactants * n_basefuncs) + De₁ = @view De[r₁range, r₁range] + De₂ = @view De[r₂range, r₂range] + ## Reset to 0 + fill!(De, 0) + ## Loop over quadrature points + for q_point in 1:getnquadpoints(cellvalues) + ## Get the quadrature weight + dΩ = getdetJdV(cellvalues, q_point) + ## Loop over test shape functions + for i in 1:n_basefuncs + ∇δuᵢ = shape_gradient(cellvalues, q_point, i) + ## Loop over trial shape functions + for j in 1:n_basefuncs + ∇δuⱼ = shape_gradient(cellvalues, q_point, j) + ## Add contribution to Ke + De₁[i, j] += D₁ * (∇δuᵢ ⋅ ∇δuⱼ) * dΩ + De₂[i, j] += D₂ * (∇δuᵢ ⋅ ∇δuⱼ) * dΩ + end + end + end + return nothing +end + +function assemble_matrices!(M::SparseMatrixCSC, D::SparseMatrixCSC, cellvalues::CellValues, dh::DofHandler, material::GrayScottMaterial) + n_basefuncs = getnbasefunctions(cellvalues) + + ## Allocate the element stiffness matrix and element force vector + Me = zeros(2 * n_basefuncs, 2 * n_basefuncs) + De = zeros(2 * n_basefuncs, 2 * n_basefuncs) + + ## Create an assembler + M_assembler = start_assemble(M) + D_assembler = start_assemble(D) + ## Loop over all cels + for cell in CellIterator(dh) + ## Reinitialize cellvalues for this cell + reinit!(cellvalues, cell) + ## Compute element contribution + assemble_element_mass!(Me, cellvalues) + assemble!(M_assembler, celldofs(cell), Me) + + assemble_element_diffusion!(De, cellvalues, material) + assemble!(D_assembler, celldofs(cell), De) + end + return nothing +end; + +# ### Initial condition setup +# Time-dependent problems always need an initial condition from which the time evolution starts. +# In this tutorial we set the concentration of reactant 1 to $1$ and the concentration of reactant +# 2 to $0$ for all nodal dof with associated coordinate $z \leq 0.9$ on the sphere. Since the +# simulation would be pretty boring with a steady-state initial condition, we introduce some +# heterogeneity by setting the dofs associated to top part of the sphere (i.e. dofs with $z > 0.9$ +# to store the reactant concentrations of $0.5$ and $0.25$ for the reactants 1 and 2 respectively. +function setup_initial_conditions!(u₀::Vector, cellvalues::CellValues, dh::DofHandler) + u₀ .= ones(ndofs(dh)) + u₀[2:2:end] .= 0.0 + + n_basefuncs = getnbasefunctions(cellvalues) + + for cell in CellIterator(dh) + reinit!(cellvalues, cell) + + coords = getcoordinates(cell) + dofs = celldofs(cell) + uₑ = @view u₀[dofs] + rv₀ₑ = reshape(uₑ, (2, n_basefuncs)) + + for i in 1:n_basefuncs + if coords[i][3] > 0.9 + rv₀ₑ[1, i] = 0.5 + rv₀ₑ[2, i] = 0.25 + end + end + end + + u₀ .+= 0.01 * rand(ndofs(dh)) + return +end; + +# ### Mesh generation +# In this section we define a routine to create a surface mesh with the help of FerriteGmsh.jl. +function create_embedded_sphere(refinements) + gmsh.initialize() + + ## Add a unit sphere in 3D space + gmsh.model.occ.addSphere(0.0, 0.0, 0.0, 1.0) + gmsh.model.occ.synchronize() + + ## Generate nodes and surface elements only, hence we need to pass 2 into generate + gmsh.model.mesh.generate(2) + + ## To get good solution quality refine the elements several times + for _ in 1:refinements + gmsh.model.mesh.refine() + end + + ## Now we create a Ferrite grid out of it. Note that we also call toelements + ## with our surface element dimension to obtain these. + nodes = tonodes() + elements, _ = toelements(2) + gmsh.finalize() + return Grid(elements, nodes) +end + +# ### Simulation routines +# Now we define a function to setup and solve the problem with given feed and conversion rates +# $F$ and $k$, as well as the time step length and for how long we want to solve the model. +function gray_scott_on_sphere(material::GrayScottMaterial, Δt::Real, T::Real, refinements::Integer) + ## We start by setting up grid, dof handler and the matrices for the heat problem. + grid = create_embedded_sphere(refinements) + + ## Next we are creating our element assembly helper for surface elements. + ## The only change which we need to introduce here is to pass in a geometrical + ## interpolation with the same dimension as the physical space into which our + ## elements are embedded into, which is in this example 3. + ip = Lagrange{RefTriangle, 1}() + qr = QuadratureRule{RefTriangle}(2) + cellvalues = CellValues(qr, ip, ip^3) + + ## We have two options to add the reactants to the dof handler, which will give us slightly + ## different resulting dof distributions: + ## A) We can add a scalar-valued interpolation for each reactant. + ## B) We can add one vectorized interpolation whose dimension is the number of reactants + ## number of reactants. + ## In this tutorial we opt for B, because the dofs are distributed per cell entity -- or + ## to be specific for this tutorial, we use an isoparametric concept such that the nodes + ## of our grid and the nodes of our solution approximation coincide. This way a reaction + ## we can create simply reshape the solution vector u to a matrix where the inner index + ## corresponds to the index of the reactant. Note that we will still use the scalar + ## interpolation for the assembly procedure. + dh = DofHandler(grid) + add!(dh, :reactants, ip^2) + close!(dh) + + ## We can save some memory by telling the sparsity pattern that the matrices are not coupled. + M = allocate_matrix(dh; coupling = [true false; false true]) + D = allocate_matrix(dh; coupling = [true false; false true]) + + ## Since the heat problem is linear and has no time dependent parameters, we precompute the + ## decomposition of the system matrix to speed up the linear system solver. + assemble_matrices!(M, D, cellvalues, dh, material) + A = M + Δt .* D + cholA = cholesky(A) + + ## Now we setup buffers for the time dependent solution and fill the initial condition. + uₜ = zeros(ndofs(dh)) + uₜ₋₁ = ones(ndofs(dh)) + setup_initial_conditions!(uₜ₋₁, cellvalues, dh) + + ## And prepare output for visualization. + pvd = paraview_collection("reactive-surface") + VTKGridFile("reactive-surface-0", dh) do vtk + write_solution(vtk, dh, uₜ₋₁) + pvd[0.0] = vtk + end + + ## This is now the main solve loop. + F = material.F + k = material.k + for (iₜ, t) in enumerate(Δt:Δt:T) + ## First we solve the heat problem + uₜ .= cholA \ (M * uₜ₋₁) + + ## Then we solve the point-wise reaction problem with the solution of + ## the heat problem as initial guess. 2 is the number of reactants. + num_individual_reaction_dofs = ndofs(dh) ÷ 2 + rvₜ = reshape(uₜ, (2, num_individual_reaction_dofs)) + for i in 1:num_individual_reaction_dofs + r₁ = rvₜ[1, i] + r₂ = rvₜ[2, i] + rvₜ[1, i] += Δt * (-r₁ * r₂^2 + F * (1 - r₁)) + rvₜ[2, i] += Δt * (r₁ * r₂^2 - r₂ * (F + k)) + end + + ## The solution is then stored every 10th step to vtk files for + ## later visualization purposes. + if (iₜ % 10) == 0 + VTKGridFile("reactive-surface-$(iₜ)", dh) do vtk + write_solution(vtk, dh, uₜ₋₁) + pvd[t] = vtk + end + end + + ## Finally we totate the solution to initialize the next timestep. + uₜ₋₁ .= uₜ + end + vtk_save(pvd) + return +end + +## This parametrization gives the spot pattern shown in the gif above. +material = GrayScottMaterial(0.00016, 0.00008, 0.06, 0.062) +if !IS_CI #src + gray_scott_on_sphere(material, 10.0, 32000.0, 3) +else #src + gray_scott_on_sphere(material, 10.0, 20.0, 0) #src +end #src +nothing #src + +#md # ## [Plain program](@id reactive_surface-plain-program) +#md # +#md # Here follows a version of the program without any comments. +#md # The file is also available here: [`reactive_surface.jl`](reactive_surface.jl). +#md # +#md # ```julia +#md # @__CODE__ +#md # ``` diff --git a/previews/PR798/literate-tutorials/rve_homogenization.png b/previews/PR798/literate-tutorials/rve_homogenization.png new file mode 100644 index 0000000000..73aca2801e Binary files /dev/null and b/previews/PR798/literate-tutorials/rve_homogenization.png differ diff --git a/previews/PR798/literate-tutorials/stokes-flow.jl b/previews/PR798/literate-tutorials/stokes-flow.jl new file mode 100644 index 0000000000..b88baa3a4a --- /dev/null +++ b/previews/PR798/literate-tutorials/stokes-flow.jl @@ -0,0 +1,535 @@ +# # [Stokes flow](@id tutorial-stokes-flow) +# +# **Keywords**: *periodic boundary conditions, multiple fields, mean value constraint* +#- +#md # !!! tip +#md # This example is also available as a Jupyter notebook: +#md # [`stokes-flow.ipynb`](@__NBVIEWER_ROOT_URL__/tutorials/stokes-flow.ipynb). +#- +# +# ![](stokes-flow.png) +# *Figure 1*: Left: Computational domain ``\Omega`` with boundaries ``\Gamma_1``, +# ``\Gamma_3`` (periodic boundary conditions) and ``\Gamma_2``, ``\Gamma_4`` (homogeneous +# Dirichlet boundary conditions). Right: Magnitude of the resulting velocity field. + +# ## Introduction and problem formulation +# +# This example is a translation of the [step-45 example from +# deal.ii](https://www.dealii.org/current/doxygen/deal.II/step_45.html) which solves Stokes +# flow on a quarter circle. In particular it shows how to use periodic boundary conditions, +# how to solve a problem with multiple unknown fields, and how to enforce a specific mean +# value of the solution. For the mesh generation we use +# [Gmsh.jl](https://github.com/JuliaFEM/Gmsh.jl) and then use +# [FerriteGmsh.jl](https://github.com/Ferrite-FEM/FerriteGmsh.jl) to import the mesh into +# Ferrite's format. +# +# The strong form of Stokes flow with velocity ``\boldsymbol{u}`` and pressure ``p`` can be +# written as follows: +# ```math +# \begin{align*} +# -\Delta \boldsymbol{u} + \boldsymbol{\nabla} p &= \bigl(\exp(-100||\boldsymbol{x} - (0.75, 0.1)||^2), 0\bigr) =: +# \boldsymbol{b} \quad \forall \boldsymbol{x} \in \Omega,\\ +# -\boldsymbol{\nabla} \cdot \boldsymbol{u} &= 0 \quad \forall \boldsymbol{x} \in \Omega, +# \end{align*} +# ``` +# where the domain is defined as ``\Omega = \{\boldsymbol{x} \in (0, 1)^2: +# \ ||\boldsymbol{x}|| \in (0.5, 1)\}``, see *Figure 1*. For the velocity we use periodic +# boundary conditions on the inlet ``\Gamma_1`` and outlet ``\Gamma_3``: +# ```math +# \begin{align*} +# u_x(0,\nu) &= -u_y(\nu, 0) \quad & \nu\ \in\ [0.5, 1],\\ +# u_y(0,\nu) &= u_x(\nu, 0) \quad & \nu\ \in\ [0.5, 1], +# \end{align*} +# ``` +# and homogeneous Dirichlet boundary conditions for ``\Gamma_2`` and ``\Gamma_4``: +# ```math +# \boldsymbol{u} = \boldsymbol{0} \quad \forall \boldsymbol{x}\ \in\ +# \Gamma_2 \cup \Gamma_4 := \{ \boldsymbol{x}:\ ||\boldsymbol{x}|| \in \{0.5, 1\}\}. +# ``` +# +# The corresponding weak form reads as follows: Find ``(\boldsymbol{u}, p) \in \mathbb{U} +# \times \mathrm{L}_2`` s.t. +# ```math +# \begin{align*} +# \int_\Omega \Bigl[[\delta\boldsymbol{u}\otimes\boldsymbol{\nabla}]:[\boldsymbol{u}\otimes\boldsymbol{\nabla}] - +# (\boldsymbol{\nabla}\cdot\delta\boldsymbol{u})\ p\ \Bigr] \mathrm{d}\Omega &= +# \int_\Omega \delta\boldsymbol{u} \cdot \boldsymbol{b}\ \mathrm{d}\Omega \quad \forall +# \delta \boldsymbol{u} \in \mathbb{U},\\ +# \int_\Omega - (\boldsymbol{\nabla}\cdot\boldsymbol{u})\ \delta p\ \mathrm{d}\Omega &= 0 +# \quad \forall \delta p \in \mathrm{L}_2, +# \end{align*} +# ``` +# where ``\mathbb{U}`` is a suitable function space, that, in particular, enforces the +# Dirichlet boundary conditions, and the periodicity constraints. +# This formulation is a saddle point problem, and, just like the example with +# [Incompressible Elasticity](@ref tutorial-incompressible-elasticity), we need +# our formulation to fulfill the [LBB +# condition](https://en.wikipedia.org/wiki/Ladyzhenskaya%E2%80%93Babu%C5%A1ka%E2%80%93Brezzi_condition). +# We ensure this by using a quadratic approximation for the velocity field, and a linear +# approximation for the pressure. +# +# With this formulation and boundary conditions for ``\boldsymbol{u}`` the pressure will +# only be determined up to a constant. We will therefore add an additional constraint which +# fixes this constant (see [deal.ii +# step-11](https://www.dealii.org/current/doxygen/deal.II/step_11.html) for some more +# discussion around this). In particular, we will enforce the mean value of the pressure on +# the boundary to be 0, i.e. ``\int_{\Gamma} p\ \mathrm{d}\Gamma = 0``. One option is to +# enforce this using a Lagrange multiplier. This would give a contribution ``\lambda +# \int_{\Gamma} \delta p\ \mathrm{d}\Gamma`` to the second equation in the weak form above, +# and a third equation ``\delta\lambda \int_{\Gamma} p\ \mathrm{d}\Gamma = 0`` so that we +# can solve for ``\lambda``. However, since we in this case are not interested in computing +# ``\lambda``, and since the constraint is linear, we can directly embed this constraint +# using an `AffineConstraint` in Ferrite. +# +# After FE discretization we obtain a linear system of the form +# ``\underline{\underline{K}}\ \underline{a} = \underline{f}``, where +# ```math +# \underline{\underline{K}} = +# \begin{bmatrix} +# \underline{\underline{K}}_{uu} & \underline{\underline{K}}_{pu}^\textrm{T} \\ +# \underline{\underline{K}}_{pu} & \underline{\underline{0}} +# \end{bmatrix}, \quad +# \underline{a} = \begin{bmatrix} +# \underline{a}_{u} \\ +# \underline{a}_{p} +# \end{bmatrix}, \quad +# \underline{f} = \begin{bmatrix} +# \underline{f}_{u} \\ +# \underline{0} +# \end{bmatrix}, +# ``` +# and where +# ```math +# \begin{align*} +# (\underline{\underline{K}}_{uu})_{ij} &= \int_\Omega [\boldsymbol{\phi}^u_i\otimes\boldsymbol{\nabla}]:[\boldsymbol{\phi}^u_j\otimes\boldsymbol{\nabla}] \mathrm{d}\Omega, \\ +# (\underline{\underline{K}}_{pu})_{ij} &= \int_\Omega - (\boldsymbol{\nabla}\cdot\boldsymbol{\phi}^u_j)\ \phi^p_i\ \mathrm{d}\Omega, \\ +# (\underline{f}_{u})_{i} &= \int_\Omega \boldsymbol{\phi}^u_i \cdot \boldsymbol{b}\ \mathrm{d}\Omega. +# \end{align*} +# ``` +# +# The affine constraint to enforce zero mean pressure on the boundary is obtained from +# ``\underline{\underline{C}}_p\ \underline{a}_p = \underline{0}``, where +# ```math +# (\underline{\underline{C}}_p)_{1j} = \int_{\Gamma} \phi^p_j\ \mathrm{d}\Gamma. +# ``` +# +# !!! note +# The constraint matrix ``\underline{\underline{C}}_p`` is the same matrix we would have +# obtained when assembling the system with the Lagrange multiplier. In that case the +# full system would be +# ```math +# \underline{\underline{K}} = +# \begin{bmatrix} +# \underline{\underline{K}}_{uu} & \underline{\underline{K}}_{pu}^\textrm{T} & +# \underline{\underline{0}}\\ +# \underline{\underline{K}}_{pu} & \underline{\underline{0}} & \underline{\underline{C}}_p^\mathrm{T} \\ +# \underline{\underline{0}} & \underline{\underline{C}}_p & 0 \\ +# \end{bmatrix}, \quad +# \underline{a} = \begin{bmatrix} +# \underline{a}_{u} \\ +# \underline{a}_{p} \\ +# \underline{a}_{\lambda} +# \end{bmatrix}, \quad +# \underline{f} = \begin{bmatrix} +# \underline{f}_{u} \\ +# \underline{0} \\ +# \underline{0} +# \end{bmatrix}. +# ``` + +# ## Commented program +# +# What follows is a program spliced with comments. +#md # The full program, without comments, can be found in the next +#md # [section](@ref stokes-flow-plain-program). + +using Ferrite, FerriteGmsh, Gmsh, Tensors, LinearAlgebra, SparseArrays +using Test #src + +# ### Geometry and mesh generation with `Gmsh.jl` +# +# In the `setup_grid` function below we use the +# [`Gmsh.jl`](https://github.com/JuliaFEM/Gmsh.jl) package for setting up the geometry and +# performing the meshing. We will not discuss this part in much detail but refer to the +# [Gmsh API documentation](https://gmsh.info/doc/texinfo/gmsh.html#Gmsh-API) instead. The +# most important thing to note is the mesh periodicity constraint that is applied between +# the "inlet" and "outlet" parts using `gmsh.model.set_periodic`. This is necessary to later +# on apply a periodicity constraint for the approximated velocity field. + +function setup_grid(h = 0.05) + ## Initialize gmsh + Gmsh.initialize() + gmsh.option.set_number("General.Verbosity", 2) + + ## Add the points + o = gmsh.model.geo.add_point(0.0, 0.0, 0.0, h) + p1 = gmsh.model.geo.add_point(0.5, 0.0, 0.0, h) + p2 = gmsh.model.geo.add_point(1.0, 0.0, 0.0, h) + p3 = gmsh.model.geo.add_point(0.0, 1.0, 0.0, h) + p4 = gmsh.model.geo.add_point(0.0, 0.5, 0.0, h) + + ## Add the lines + l1 = gmsh.model.geo.add_line(p1, p2) + l2 = gmsh.model.geo.add_circle_arc(p2, o, p3) + l3 = gmsh.model.geo.add_line(p3, p4) + l4 = gmsh.model.geo.add_circle_arc(p4, o, p1) + + ## Create the closed curve loop and the surface + loop = gmsh.model.geo.add_curve_loop([l1, l2, l3, l4]) + surf = gmsh.model.geo.add_plane_surface([loop]) + + ## Synchronize the model + gmsh.model.geo.synchronize() + + ## Create the physical domains + gmsh.model.add_physical_group(1, [l1], -1, "Γ1") + gmsh.model.add_physical_group(1, [l2], -1, "Γ2") + gmsh.model.add_physical_group(1, [l3], -1, "Γ3") + gmsh.model.add_physical_group(1, [l4], -1, "Γ4") + gmsh.model.add_physical_group(2, [surf]) + + ## Add the periodicity constraint using 4x4 affine transformation matrix, + ## see https://en.wikipedia.org/wiki/Transformation_matrix#Affine_transformations + transformation_matrix = zeros(4, 4) + transformation_matrix[1, 2] = 1 # -sin(-pi/2) + transformation_matrix[2, 1] = -1 # cos(-pi/2) + transformation_matrix[3, 3] = 1 + transformation_matrix[4, 4] = 1 + transformation_matrix = vec(transformation_matrix') + gmsh.model.mesh.set_periodic(1, [l1], [l3], transformation_matrix) + + ## Generate a 2D mesh + gmsh.model.mesh.generate(2) + + ## Save the mesh, and read back in as a Ferrite Grid + grid = mktempdir() do dir + path = joinpath(dir, "mesh.msh") + gmsh.write(path) + togrid(path) + end + + ## Finalize the Gmsh library + Gmsh.finalize() + + return grid +end +#md nothing #hide + +# ### Degrees of freedom +# +# As mentioned in the introduction we will use a quadratic approximation for the velocity +# field and a linear approximation for the pressure to ensure that we fulfill the LBB +# condition. We create the corresponding FE values with interpolations `ipu` for the +# velocity and `ipp` for the pressure. Note that we specify linear geometric mapping +# (`ipg`) for both the velocity and pressure because our grid contains linear +# triangles. However, since linear mapping is default this could have been skipped. +# We also construct facet-values for the pressure since we need to integrate along +# the boundary when assembling the constraint matrix ``\underline{\underline{C}}``. + +function setup_fevalues(ipu, ipp, ipg) + qr = QuadratureRule{RefTriangle}(2) + cvu = CellValues(qr, ipu, ipg) + cvp = CellValues(qr, ipp, ipg) + qr_facet = FacetQuadratureRule{RefTriangle}(2) + fvp = FacetValues(qr_facet, ipp, ipg) + return cvu, cvp, fvp +end +#md nothing #hide + +# The `setup_dofs` function creates the `DofHandler`, and adds the two fields: a +# vector field `:u` with interpolation `ipu`, and a scalar field `:p` with interpolation +# `ipp`. + +function setup_dofs(grid, ipu, ipp) + dh = DofHandler(grid) + add!(dh, :u, ipu) + add!(dh, :p, ipp) + close!(dh) + return dh +end +#md nothing #hide + +# ### Boundary conditions and constraints +# +# Now it is time to setup the `ConstraintHandler` and add our boundary conditions and the +# mean value constraint. This is perhaps the most interesting section in this example, and +# deserves some attention. +# +# Let's first discuss the assembly of the constraint matrix ``\underline{\underline{C}}`` +# and how to create an `AffineConstraint` from it. This is done in the +# `setup_mean_constraint` function below. Assembling this is not so different from standard +# assembly in Ferrite: we loop over all the facets, loop over the quadrature points, and loop +# over the shape functions. Note that since there is only one constraint the matrix will +# only have one row. +# After assembling `C` we construct an `AffineConstraint` from it. We select the constrained +# dof to be the one with the highest weight (just to avoid selecting one with 0 or a very +# small weight), then move the remaining to the right hand side. As an example, consider the +# case where the constraint equation ``\underline{\underline{C}}_p\ \underline{a}_p`` is +# ```math +# w_{10} p_{10} + w_{23} p_{23} + w_{154} p_{154} = 0 +# ``` +# i.e. dofs 10, 23, and 154, are the ones located on the boundary (all other dofs naturally +# gives 0 contribution). If ``w_{23}`` is the largest weight, then we select ``p_{23}`` to +# be the constrained one, and thus reorder the constraint to the form +# ```math +# p_{23} = -\frac{w_{10}}{w_{23}} p_{10} -\frac{w_{154}}{w_{23}} p_{154} + 0, +# ``` +# which is the form the `AffineConstraint` constructor expects. +# +# !!! note +# If all nodes along the boundary are equidistant all the weights would be the same. In +# this case we can construct the constraint without having to do any integration by +# simply finding all degrees of freedom that are located along the boundary (and using 1 +# as the weight). This is what is done in the [deal.ii step-11 +# example](https://www.dealii.org/current/doxygen/deal.II/step_11.html). + +function setup_mean_constraint(dh, fvp) + assembler = Ferrite.COOAssembler() + ## All external boundaries + set = union( + getfacetset(dh.grid, "Γ1"), + getfacetset(dh.grid, "Γ2"), + getfacetset(dh.grid, "Γ3"), + getfacetset(dh.grid, "Γ4"), + ) + ## Allocate buffers + range_p = dof_range(dh, :p) + element_dofs = zeros(Int, ndofs_per_cell(dh)) + element_dofs_p = view(element_dofs, range_p) + element_coords = zeros(Vec{2}, 3) + Ce = zeros(1, length(range_p)) # Local constraint matrix (only 1 row) + ## Loop over all the boundaries + for (ci, fi) in set + Ce .= 0 + getcoordinates!(element_coords, dh.grid, ci) + reinit!(fvp, element_coords, fi) + celldofs!(element_dofs, dh, ci) + for qp in 1:getnquadpoints(fvp) + dΓ = getdetJdV(fvp, qp) + for i in 1:getnbasefunctions(fvp) + Ce[1, i] += shape_value(fvp, qp, i) * dΓ + end + end + ## Assemble to row 1 + assemble!(assembler, [1], element_dofs_p, Ce) + end + C, _ = finish_assemble(assembler) + ## Create an AffineConstraint from the C-matrix + _, J, V = findnz(C) + _, constrained_dof_idx = findmax(abs2, V) + constrained_dof = J[constrained_dof_idx] + V ./= V[constrained_dof_idx] + mean_value_constraint = AffineConstraint( + constrained_dof, + Pair{Int, Float64}[J[i] => -V[i] for i in 1:length(J) if J[i] != constrained_dof], + 0.0, + ) + return mean_value_constraint +end +#md nothing #hide + +# We now setup all the boundary conditions in the `setup_constraints` function below. +# Since the periodicity constraint for this example is between two boundaries which are not +# parallel to each other we need to i) compute the mapping between each mirror facet and the +# corresponding image facet (on the element level) and ii) describe the dof relation between +# dofs on these two facets. In Ferrite this is done by defining a transformation of entities +# on the image boundary such that they line up with the matching entities on the mirror +# boundary. In this example we consider the inlet ``\Gamma_1`` to be the image, and the +# outlet ``\Gamma_3`` to be the mirror. The necessary transformation to apply then becomes a +# rotation of ``\pi/2`` radians around the out-of-plane axis. We set up the rotation matrix +# `R`, and then compute the mapping between mirror and image facets using +# [`collect_periodic_facets`](@ref) where the rotation is applied to the coordinates. In the +# next step we construct the constraint using the [`PeriodicDirichlet`](@ref) constructor. +# We pass the constructor the computed mapping, and also the rotation matrix. This matrix is +# used to rotate the dofs on the mirror surface such that we properly constrain +# ``\boldsymbol{u}_x``-dofs on the mirror to ``-\boldsymbol{u}_y``-dofs on the image, and +# ``\boldsymbol{u}_y``-dofs on the mirror to ``\boldsymbol{u}_x``-dofs on the image. +# +# For the remaining part of the boundary we add a homogeneous Dirichlet boundary condition +# on both components of the velocity field. This is done using the [`Dirichlet`](@ref) +# constructor, which we have discussed in other tutorials. + +function setup_constraints(dh, fvp) + ch = ConstraintHandler(dh) + ## Periodic BC + R = rotation_tensor(π / 2) + periodic_faces = collect_periodic_facets(dh.grid, "Γ3", "Γ1", x -> R ⋅ x) + periodic = PeriodicDirichlet(:u, periodic_faces, R, [1, 2]) + add!(ch, periodic) + ## Dirichlet BC + Γ24 = union(getfacetset(dh.grid, "Γ2"), getfacetset(dh.grid, "Γ4")) + dbc = Dirichlet(:u, Γ24, (x, t) -> [0, 0], [1, 2]) + add!(ch, dbc) + ## Compute mean value constraint and add it + mean_value_constraint = setup_mean_constraint(dh, fvp) + add!(ch, mean_value_constraint) + ## Finalize + close!(ch) + update!(ch, 0) + return ch +end +#md nothing #hide + +# ### Global and local assembly +# +# Assembly of the global system is also something that we have seen in many previous +# tutorials. One interesting thing to note here is that, since we have two unknown fields, +# we use the [`dof_range`](@ref) function to make sure we assemble the element contributions +# to the correct block of the local stiffness matrix `ke`. + +function assemble_system!(K, f, dh, cvu, cvp) + assembler = start_assemble(K, f) + ke = zeros(ndofs_per_cell(dh), ndofs_per_cell(dh)) + fe = zeros(ndofs_per_cell(dh)) + range_u = dof_range(dh, :u) + ndofs_u = length(range_u) + range_p = dof_range(dh, :p) + ndofs_p = length(range_p) + ϕᵤ = Vector{Vec{2, Float64}}(undef, ndofs_u) + ∇ϕᵤ = Vector{Tensor{2, 2, Float64, 4}}(undef, ndofs_u) + divϕᵤ = Vector{Float64}(undef, ndofs_u) + ϕₚ = Vector{Float64}(undef, ndofs_p) + for cell in CellIterator(dh) + reinit!(cvu, cell) + reinit!(cvp, cell) + ke .= 0 + fe .= 0 + for qp in 1:getnquadpoints(cvu) + dΩ = getdetJdV(cvu, qp) + for i in 1:ndofs_u + ϕᵤ[i] = shape_value(cvu, qp, i) + ∇ϕᵤ[i] = shape_gradient(cvu, qp, i) + divϕᵤ[i] = shape_divergence(cvu, qp, i) + end + for i in 1:ndofs_p + ϕₚ[i] = shape_value(cvp, qp, i) + end + ## u-u + for (i, I) in pairs(range_u), (j, J) in pairs(range_u) + ke[I, J] += (∇ϕᵤ[i] ⊡ ∇ϕᵤ[j]) * dΩ + end + ## u-p + for (i, I) in pairs(range_u), (j, J) in pairs(range_p) + ke[I, J] += (-divϕᵤ[i] * ϕₚ[j]) * dΩ + end + ## p-u + for (i, I) in pairs(range_p), (j, J) in pairs(range_u) + ke[I, J] += (-divϕᵤ[j] * ϕₚ[i]) * dΩ + end + ## rhs + for (i, I) in pairs(range_u) + x = spatial_coordinate(cvu, qp, getcoordinates(cell)) + b = exp(-100 * norm(x - Vec{2}((0.75, 0.1)))^2) + bv = Vec{2}((b, 0.0)) + fe[I] += (ϕᵤ[i] ⋅ bv) * dΩ + end + end + assemble!(assembler, celldofs(cell), ke, fe) + end + return K, f +end +#md nothing #hide + +# ### Running the simulation +# +# We now have all the puzzle pieces, and just need to define the main function, which puts +# them all together. + +function check_mean_constraint(dh, fvp, u) #src + ## All external boundaries #src + set = union( #src + getfacetset(dh.grid, "Γ1"), getfacetset(dh.grid, "Γ2"), #src + getfacetset(dh.grid, "Γ3"), getfacetset(dh.grid, "Γ4"), #src + ) #src + range_p = dof_range(dh, :p) #src + cc = CellCache(dh) #src + ## Loop over all the boundaries and compute the integrated pressure #src + ∫pdΓ, Γ = 0.0, 0.0 #src + for (ci, fi) in set #src + reinit!(cc, ci) #src + reinit!(fvp, cc, fi) #src + ue = u[celldofs(cc)] #src + for qp in 1:getnquadpoints(fvp) #src + dΓ = getdetJdV(fvp, qp) #src + ∫pdΓ += function_value(fvp, qp, ue, range_p) * dΓ #src + Γ += dΓ #src + end #src + end #src + @test ∫pdΓ / Γ ≈ 0.0 atol = 1.0e-16 #src + return #src +end #src + +function check_L2(dh, cvu, cvp, u) #src + range_u = dof_range(dh, :u) #src + range_p = dof_range(dh, :p) #src + ## Loop over the domain and compute the integrals #src + ∫uudΩ, ∫ppdΩ, Ω = 0.0, 0.0, 0.0 #src + for cell in CellIterator(dh) #src + reinit!(cvu, cell) #src + reinit!(cvp, cell) #src + ue = u[celldofs(cell)] #src + for qp in 1:getnquadpoints(cvu) #src + dΩ = getdetJdV(cvu, qp) #src + uh = function_value(cvu, qp, ue, range_u) #src + ph = function_value(cvp, qp, ue, range_p) #src + ∫uudΩ += (uh ⋅ uh) * dΩ #src + ∫ppdΩ += (ph * ph) * dΩ #src + Ω += dΩ #src + end #src + end #src + @test √(∫uudΩ) / Ω ≈ 0.0007255988117907926 atol = 1.0e-7 #src + @test √(∫ppdΩ) / Ω ≈ 0.02169683180923709 atol = 1.0e-5 #src + return #src +end #src + +function main() + ## Grid + h = 0.05 # approximate element size + grid = setup_grid(h) + ## Interpolations + ipu = Lagrange{RefTriangle, 2}()^2 # quadratic + ipp = Lagrange{RefTriangle, 1}() # linear + ## Dofs + dh = setup_dofs(grid, ipu, ipp) + ## FE values + ipg = Lagrange{RefTriangle, 1}() # linear geometric interpolation + cvu, cvp, fvp = setup_fevalues(ipu, ipp, ipg) + ## Boundary conditions + ch = setup_constraints(dh, fvp) + ## Global tangent matrix and rhs + coupling = [true true; true false] # no coupling between pressure test/trial functions + K = allocate_matrix(dh, ch; coupling = coupling) + f = zeros(ndofs(dh)) + ## Assemble system + assemble_system!(K, f, dh, cvu, cvp) + ## Apply boundary conditions and solve + apply!(K, f, ch) + u = K \ f + apply!(u, ch) + ## Export the solution + VTKGridFile("stokes-flow", grid) do vtk + write_solution(vtk, dh, u) + end + + ## Check the result #src + check_L2(dh, cvu, cvp, u) #src + check_mean_constraint(dh, fvp, u) #src + + return +end +#md nothing #hide + +# Run it! + +main() + +# The resulting magnitude of the velocity field is visualized in *Figure 1*. + +#md # ## [Plain program](@id stokes-flow-plain-program) +#md # +#md # Here follows a version of the program without any comments. +#md # The file is also available here: [`stokes-flow.jl`](stokes-flow.jl). +#md # +#md # ```julia +#md # @__CODE__ +#md # ``` diff --git a/previews/PR798/literate-tutorials/stokes-flow.png b/previews/PR798/literate-tutorials/stokes-flow.png new file mode 100644 index 0000000000..08ae518a86 Binary files /dev/null and b/previews/PR798/literate-tutorials/stokes-flow.png differ diff --git a/previews/PR798/literate-tutorials/transient_heat_equation.jl b/previews/PR798/literate-tutorials/transient_heat_equation.jl new file mode 100644 index 0000000000..03c48aa696 --- /dev/null +++ b/previews/PR798/literate-tutorials/transient_heat_equation.jl @@ -0,0 +1,230 @@ +# # [Transient heat equation](@id tutorial-transient-heat-equation) +# +# ![](transient_heat.gif) +# ![](transient_heat_colorbar.svg) +# +# *Figure 1*: Visualization of the temperature time evolution on a unit +# square where the prescribed temperature on the upper and lower parts +# of the boundary increase with time. +# +#- +#md # !!! tip +#md # This example is also available as a Jupyter notebook: +#md # [`transient_heat_equation.ipynb`](@__NBVIEWER_ROOT_URL__/tutorials/transient_heat_equation.ipynb). +#- +# +# ## Introduction +# +# In this example we extend the heat equation by a time dependent term, i.e. +# ```math +# \frac{\partial u}{\partial t}-\nabla \cdot (k \nabla u) = f \quad x \in \Omega, +# ``` +# +# where $u$ is the unknown temperature field, $k$ the heat conductivity, +# $f$ the heat source and $\Omega$ the domain. For simplicity, we hard code $f = 0.1$ +# and $k = 10^{-3}$. We define homogeneous Dirichlet boundary conditions along the left and right edge of the domain. +# ```math +# u(x,t) = 0 \quad x \in \partial \Omega_1, +# ``` +# where $\partial \Omega_1$ denotes the left and right boundary of $\Omega$. +# +# Further, we define heterogeneous Dirichlet boundary conditions at the top and bottom edge $\partial \Omega_2$. +# We choose a linearly increasing function $a(t)$ that describes the temperature at this boundary +# ```math +# u(x,t) = a(t) \quad x \in \partial \Omega_2. +# ``` +# The semidiscrete weak form is given by +# ```math +# \int_{\Omega}v \frac{\partial u}{\partial t} \ \mathrm{d}\Omega + \int_{\Omega} k \nabla v \cdot \nabla u \ \mathrm{d}\Omega = \int_{\Omega} f v \ \mathrm{d}\Omega, +# ``` +# where $v$ is a suitable test function. Now, we still need to discretize the time derivative. An implicit Euler scheme is applied, +# which yields: +# ```math +# \int_{\Omega} v\, u_{n+1}\ \mathrm{d}\Omega + \Delta t\int_{\Omega} k \nabla v \cdot \nabla u_{n+1} \ \mathrm{d}\Omega = \Delta t\int_{\Omega} f v \ \mathrm{d}\Omega + \int_{\Omega} v \, u_{n} \ \mathrm{d}\Omega. +# ``` +# If we assemble the discrete operators, we get the following algebraic system: +# ```math +# \mathbf{M} \mathbf{u}_{n+1} + Δt \mathbf{K} \mathbf{u}_{n+1} = Δt \mathbf{f} + \mathbf{M} \mathbf{u}_{n} +# ``` +# In this example we apply the boundary conditions to the assembled discrete operators (mass matrix $\mathbf{M}$ and stiffnes matrix $\mathbf{K}$) +# only once. We utilize the fact that in finite element computations Dirichlet conditions can be applied by +# zero out rows and columns that correspond +# to a prescribed dof in the system matrix ($\mathbf{A} = Δt \mathbf{K} + \mathbf{M}$) and setting the value of the right-hand side vector to the value +# of the Dirichlet condition. Thus, we only need to apply in every time step the Dirichlet condition to the right-hand side of the problem. +#- +# ## Commented Program +# +# Now we solve the problem in Ferrite. What follows is a program spliced with comments. +#md # The full program, without comments, can be found in the next [section](@ref heat_equation-plain-program). +# +# First we load Ferrite, and some other packages we need. +using Ferrite, SparseArrays, WriteVTK +# We create the same grid as in the heat equation example. +grid = generate_grid(Quadrilateral, (100, 100)); + +# ### Trial and test functions +# Again, we define the structs that are responsible for the `shape_value` and `shape_gradient` evaluation. +ip = Lagrange{RefQuadrilateral, 1}() +qr = QuadratureRule{RefQuadrilateral}(2) +cellvalues = CellValues(qr, ip); + +# ### Degrees of freedom +# After this, we can define the `DofHandler` and distribute the DOFs of the problem. +dh = DofHandler(grid) +add!(dh, :u, ip) +close!(dh); + +# By means of the `DofHandler` we can allocate the needed `SparseMatrixCSC`. +# `M` refers here to the so called mass matrix, which always occurs in time related terms, i.e. +# ```math +# M_{ij} = \int_{\Omega} v_i \, u_j \ \mathrm{d}\Omega, +# ``` +# where $u_i$ and $v_j$ are trial and test functions, respectively. +K = allocate_matrix(dh); +M = allocate_matrix(dh); +# We also preallocate the right hand side +f = zeros(ndofs(dh)); + +# ### Boundary conditions +# In order to define the time dependent problem, we need some end time `T` and something that describes +# the linearly increasing Dirichlet boundary condition on $\partial \Omega_2$. +max_temp = 100 +Δt = 1 +T = 200 +t_rise = 100 +ch = ConstraintHandler(dh); + +# Here, we define the boundary condition related to $\partial \Omega_1$. +∂Ω₁ = union(getfacetset.((grid,), ["left", "right"])...) +dbc = Dirichlet(:u, ∂Ω₁, (x, t) -> 0) +add!(ch, dbc); +# While the next code block corresponds to the linearly increasing temperature description on $\partial \Omega_2$ +# until `t=t_rise`, and then keep constant +∂Ω₂ = union(getfacetset.((grid,), ["top", "bottom"])...) +dbc = Dirichlet(:u, ∂Ω₂, (x, t) -> max_temp * clamp(t / t_rise, 0, 1)) +add!(ch, dbc) +close!(ch) +update!(ch, 0.0); + +# ### Assembling the linear system +# As in the heat equation example we define a `doassemble!` function that assembles the diffusion parts of the equation: +function doassemble_K!(K::SparseMatrixCSC, f::Vector, cellvalues::CellValues, dh::DofHandler) + + n_basefuncs = getnbasefunctions(cellvalues) + Ke = zeros(n_basefuncs, n_basefuncs) + fe = zeros(n_basefuncs) + + assembler = start_assemble(K, f) + + for cell in CellIterator(dh) + + fill!(Ke, 0) + fill!(fe, 0) + + reinit!(cellvalues, cell) + + for q_point in 1:getnquadpoints(cellvalues) + dΩ = getdetJdV(cellvalues, q_point) + + for i in 1:n_basefuncs + v = shape_value(cellvalues, q_point, i) + ∇v = shape_gradient(cellvalues, q_point, i) + fe[i] += 0.1 * v * dΩ + for j in 1:n_basefuncs + ∇u = shape_gradient(cellvalues, q_point, j) + Ke[i, j] += 1.0e-3 * (∇v ⋅ ∇u) * dΩ + end + end + end + + assemble!(assembler, celldofs(cell), Ke, fe) + end + return K, f +end +#md nothing # hide +# In addition to the diffusive part, we also need a function that assembles the mass matrix `M`. +function doassemble_M!(M::SparseMatrixCSC, cellvalues::CellValues, dh::DofHandler) + + n_basefuncs = getnbasefunctions(cellvalues) + Me = zeros(n_basefuncs, n_basefuncs) + + assembler = start_assemble(M) + + for cell in CellIterator(dh) + + fill!(Me, 0) + + reinit!(cellvalues, cell) + + for q_point in 1:getnquadpoints(cellvalues) + dΩ = getdetJdV(cellvalues, q_point) + + for i in 1:n_basefuncs + v = shape_value(cellvalues, q_point, i) + for j in 1:n_basefuncs + u = shape_value(cellvalues, q_point, j) + Me[i, j] += (v * u) * dΩ + end + end + end + + assemble!(assembler, celldofs(cell), Me) + end + return M +end +#md nothing # hide +# ### Solution of the system +# We first assemble all parts in the prior allocated `SparseMatrixCSC`. +K, f = doassemble_K!(K, f, cellvalues, dh) +M = doassemble_M!(M, cellvalues, dh) +A = (Δt .* K) + M; +# Now, we need to save all boundary condition related values of the unaltered system matrix `A`, which is done +# by `get_rhs_data`. The function returns a `RHSData` struct, which contains all needed information to apply +# the boundary conditions solely on the right-hand-side vector of the problem. +rhsdata = get_rhs_data(ch, A); +# We set the values at initial time step, denoted by uₙ, to a bubble-shape described by +# $(x_1^2-1)(x_2^2-1)$, such that it is zero at the boundaries and the maximum temperature in the center. +uₙ = zeros(length(f)); +apply_analytical!(uₙ, dh, :u, x -> (x[1]^2 - 1) * (x[2]^2 - 1) * max_temp); +# Here, we apply **once** the boundary conditions to the system matrix `A`. +apply!(A, ch); + +# To store the solution, we initialize a paraview collection (.pvd) file, +pvd = paraview_collection("transient-heat") +VTKGridFile("transient-heat-0", dh) do vtk + write_solution(vtk, dh, uₙ) + pvd[0.0] = vtk +end + +# At this point everything is set up and we can finally approach the time loop. +for (step, t) in enumerate(Δt:Δt:T) + #First of all, we need to update the Dirichlet boundary condition values. + update!(ch, t) + + #Secondly, we compute the right-hand-side of the problem. + b = Δt .* f .+ M * uₙ + #Then, we can apply the boundary conditions of the current time step. + apply_rhs!(rhsdata, b, ch) + + #Finally, we can solve the time step and save the solution afterwards. + u = A \ b + + VTKGridFile("transient-heat-$step", dh) do vtk + write_solution(vtk, dh, u) + pvd[t] = vtk + end + #At the end of the time loop, we set the previous solution to the current one and go to the next time step. + uₙ .= u +end +# In order to use the .pvd file we need to store it to the disk, which is done by: +vtk_save(pvd); + +#md # ## [Plain program](@id transient_heat_equation-plain-program) +#md # +#md # Here follows a version of the program without any comments. +#md # The file is also available here: +#md # [`transient_heat_equation.jl`](transient_heat_equation.jl). +#md # +#md # ```julia +#md # @__CODE__ +#md # ``` diff --git a/previews/PR798/objects.inv b/previews/PR798/objects.inv new file mode 100644 index 0000000000..55c3989a5a Binary files /dev/null and b/previews/PR798/objects.inv differ diff --git a/previews/PR798/reference/assembly/index.html b/previews/PR798/reference/assembly/index.html new file mode 100644 index 0000000000..949e2a6578 --- /dev/null +++ b/previews/PR798/reference/assembly/index.html @@ -0,0 +1,6 @@ + +Assembly · Ferrite.jl

Assembly

Ferrite.start_assembleFunction
start_assemble(K::AbstractSparseMatrixCSC;            fillzero::Bool=true) -> CSCAssembler
+start_assemble(K::AbstractSparseMatrixCSC, f::Vector; fillzero::Bool=true) -> CSCAssembler

Create a CSCAssembler from the matrix K and optional vector f.

start_assemble(K::Symmetric{AbstractSparseMatrixCSC};                 fillzero::Bool=true) -> SymmetricCSCAssembler
+start_assemble(K::Symmetric{AbstractSparseMatrixCSC}, f::Vector=Td[]; fillzero::Bool=true) -> SymmetricCSCAssembler

Create a SymmetricCSCAssembler from the matrix K and optional vector f.

CSCAssembler and SymmetricCSCAssembler allocate workspace necessary for efficient matrix assembly. To assemble the contribution from an element, use assemble!.

The keyword argument fillzero can be set to false if K and f should not be zeroed out, but instead keep their current values.

source
Ferrite.assemble!Function
assemble!(a::COOAssembler, dofs, Ke)
+assemble!(a::COOAssembler, dofs, Ke, fe)

Assembles the element matrix Ke and element vector fe into a.

source
assemble!(a::COOAssembler, rowdofs, coldofs, Ke)

Assembles the matrix Ke into a according to the dofs specified by rowdofs and coldofs.

source
assemble!(g, dofs, ge)

Assembles the element residual ge into the global residual vector g.

source
assemble!(A::AbstractAssembler, dofs::AbstractVector{Int}, Ke::AbstractMatrix)
+assemble!(A::AbstractAssembler, dofs::AbstractVector{Int}, Ke::AbstractMatrix, fe::AbstractVector)

Assemble the element stiffness matrix Ke (and optional force vector fe) into the global stiffness (and force) in A, given the element degrees of freedom dofs.

This is equivalent to K[dofs, dofs] += Ke and f[dofs] += fe, where K is the global stiffness matrix and f the global force/residual vector, but more efficient.

source
Ferrite.finish_assembleFunction
finish_assemble(a::COOAssembler) -> K, f

Finalize the assembly and return the sparse matrix K::SparseMatrixCSC and vector f::Vector. If the assembler have not been used for vector assembly, f is an empty vector.

source
diff --git a/previews/PR798/reference/boundary_conditions/index.html b/previews/PR798/reference/boundary_conditions/index.html new file mode 100644 index 0000000000..34bf996fc8 --- /dev/null +++ b/previews/PR798/reference/boundary_conditions/index.html @@ -0,0 +1,34 @@ + +Boundary conditions · Ferrite.jl

Boundary conditions

Ferrite.ConstraintHandlerType
ConstraintHandler([T=Float64], dh::AbstractDofHandler)

A collection of constraints associated with the dof handler dh. T is the numeric type for stored values.

source
Ferrite.DirichletType
Dirichlet(u::Symbol, ∂Ω::AbstractVecOrSet, f::Function, components=nothing)

Create a Dirichlet boundary condition on u on the ∂Ω part of the boundary. f is a function of the form f(x) or f(x, t) where x is the spatial coordinate and t is the current time, and returns the prescribed value. components specify the components of u that are prescribed by this condition. By default all components of u are prescribed.

The set, ∂Ω, can be an AbstractSet or AbstractVector with elements of type FacetIndex, FaceIndex, EdgeIndex, VertexIndex, or Int. For most cases, the element type is FacetIndex, as shown below. To constrain a single point, using VertexIndex is recommended, but it is also possible to constrain a specific nodes by giving the node numbers via Int elements. To constrain e.g. an edge in 3d EdgeIndex elements can be given.

For example, here we create a Dirichlet condition for the :u field, on the facetset called ∂Ω and the value given by the sin function:

Examples

# Obtain the facetset from the grid
+∂Ω = getfacetset(grid, "boundary-1")
+
+# Prescribe scalar field :s on ∂Ω to sin(t)
+dbc = Dirichlet(:s, ∂Ω, (x, t) -> sin(t))
+
+# Prescribe all components of vector field :v on ∂Ω to 0
+dbc = Dirichlet(:v, ∂Ω, x -> 0 * x)
+
+# Prescribe component 2 and 3 of vector field :v on ∂Ω to [sin(t), cos(t)]
+dbc = Dirichlet(:v, ∂Ω, (x, t) -> [sin(t), cos(t)], [2, 3])

Dirichlet boundary conditions are added to a ConstraintHandler which applies the condition via apply! and/or apply_zero!.

source
Ferrite.PeriodicDirichletType
PeriodicDirichlet(u::Symbol, facet_mapping, components=nothing)
+PeriodicDirichlet(u::Symbol, facet_mapping, R::AbstractMatrix, components=nothing)
+PeriodicDirichlet(u::Symbol, facet_mapping, f::Function, components=nothing)

Create a periodic Dirichlet boundary condition for the field u on the facet-pairs given in facet_mapping. The mapping can be computed with collect_periodic_facets. The constraint ensures that degrees-of-freedom on the mirror facet are constrained to the corresponding degrees-of-freedom on the image facet. components specify the components of u that are prescribed by this condition. By default all components of u are prescribed.

If the mapping is not aligned with the coordinate axis (e.g. rotated) a rotation matrix R should be passed to the constructor. This matrix rotates dofs on the mirror facet to the image facet. Note that this is only applicable for vector-valued problems.

To construct an inhomogeneous periodic constraint it is possible to pass a function f. Note that this is currently only supported when the periodicity is aligned with the coordinate axes.

See the manual section on Periodic boundary conditions for more information.

source
Ferrite.collect_periodic_facetsFunction
collect_periodic_facets(grid::Grid, mset, iset, transform::Union{Function,Nothing}=nothing; tol=1e-12)

Match all mirror facets in mset with a corresponding image facet in iset. Return a dictionary which maps each mirror facet to a image facet. The result can then be passed to PeriodicDirichlet.

mset and iset can be given as a String (an existing facet set in the grid) or as a AbstractSet{FacetIndex} directly.

By default this function looks for a matching facet in the directions of the coordinate system. For other types of periodicities the transform function can be used. The transform function is applied on the coordinates of the image facet, and is expected to transform the coordinates to the matching locations in the mirror set.

The keyword tol specifies the tolerance (i.e. distance and deviation in facet-normals) between a image-facet and mirror-facet, for them to be considered matched.

See also: collect_periodic_facets!, PeriodicDirichlet.

source
collect_periodic_facets(grid::Grid, all_facets::Union{AbstractSet{FacetIndex},String,Nothing}=nothing; tol=1e-12)

Split all facets in all_facets into image and mirror sets. For each matching pair, the facet located further along the vector (1, 1, 1) becomes the image facet.

If no set is given, all facets on the outer boundary of the grid (i.e. all facets that do not have a neighbor) is used.

See also: collect_periodic_facets!, PeriodicDirichlet.

source
Ferrite.add!Function
add!(sdh::SubDofHandler, name::Symbol, ip::Interpolation)

Add a field called name approximated by ip to the SubDofHandler sdh.

source
add!(dh::DofHandler, name::Symbol, ip::Interpolation)

Add a field called name approximated by ip to the DofHandler dh.

The field is added to all cells of the underlying grid, use SubDofHandlers if the grid contains multiple cell types, or to add the field to subset of all the cells.

source
add!(ch::ConstraintHandler, ac::AffineConstraint)

Add the AffineConstraint to the ConstraintHandler.

source
add!(ch::ConstraintHandler, dbc::Dirichlet)

Add a Dirichlet boundary condition to the ConstraintHandler.

source
add!(proj::L2Projector, set::AbstractVecOrSet{Int}, ip::Interpolation;
+    qr_rhs, [qr_lhs])

Add an interpolation ip on the cells in set to the L2Projector proj.

  • qr_rhs sets the quadrature rule used to later integrate the right-hand-side of the projection equation, when calling project. It should match the quadrature points used when creating the quadrature-point variables to project.
  • The optional qr_lhs sets the quadrature rule used to integrate the left-hand-side of the projection equation, and defaults to a quadrature rule that integrates the mass-matrix exactly for the given interpolation ip.
source
Ferrite.close!Function
close!(dh::AbstractDofHandler)

Closes dh and creates degrees of freedom for each cell.

source
close!(ch::ConstraintHandler)

Close and finalize the ConstraintHandler.

source
close!(proj::L2Projector)

Close proj which assembles and calculates the left-hand-side of the projection equation, before doing a Cholesky factorization of the mass-matrix.

source
Ferrite.update!Function
update!(ch::ConstraintHandler, time::Real=0.0)

Update time-dependent inhomogeneities for the new time. This calls f(x) or f(x, t) when applicable, where f is the function(s) corresponding to the constraints in the handler, to compute the inhomogeneities.

Note that this is called implicitly in close!(::ConstraintHandler).

source
Ferrite.apply!Function
apply!(K::SparseMatrixCSC, rhs::AbstractVector, ch::ConstraintHandler)

Adjust the matrix K and right hand side rhs to account for the Dirichlet boundary conditions specified in ch such that K \ rhs gives the expected solution.

Note

apply!(K, rhs, ch) essentially calculates

rhs[free] = rhs[free] - K[constrained, constrained] * a[constrained]

where a[constrained] are the inhomogeneities. Consequently, the sign of rhs matters (in contrast with apply_zero!).

apply!(v::AbstractVector, ch::ConstraintHandler)

Apply Dirichlet boundary conditions and affine constraints, specified in ch, to the solution vector v.

Examples

K, f = assemble_system(...) # Assemble system
+apply!(K, f, ch)            # Adjust K and f to account for boundary conditions
+u = K \ f                   # Solve the system, u should be "approximately correct"
+apply!(u, ch)               # Explicitly make sure bcs are correct
Note

The last operation is not strictly necessary since the boundary conditions should already be fulfilled after apply!(K, f, ch). However, solvers of linear systems are not exact, and thus apply!(u, ch) can be used to make sure the boundary conditions are fulfilled exactly.

source
Ferrite.apply_zero!Function
apply_zero!(K::SparseMatrixCSC, rhs::AbstractVector, ch::ConstraintHandler)

Adjust the matrix K and the right hand side rhs to account for prescribed Dirichlet boundary conditions and affine constraints such that du = K \ rhs gives the expected result (e.g. du zero for all prescribed degrees of freedom).

apply_zero!(v::AbstractVector, ch::ConstraintHandler)

Zero-out values in v corresponding to prescribed degrees of freedom and update values prescribed by affine constraints, such that if a fulfills the constraints, a ± v also will.

These methods are typically used in e.g. a Newton solver where the increment, du, should be prescribed to zero even for non-homogeneouos boundary conditions.

See also: apply!.

Examples

u = un + Δu                 # Current guess
+K, g = assemble_system(...) # Assemble residual and tangent for current guess
+apply_zero!(K, g, ch)       # Adjust tangent and residual to take prescribed values into account
+ΔΔu = K \ g                # Compute the (negative) increment, prescribed values are "approximately" zero
+apply_zero!(ΔΔu, ch)        # Make sure values are exactly zero
+Δu .-= ΔΔu                  # Update current guess
Note

The last call to apply_zero! is only strictly necessary for affine constraints. However, even if the Dirichlet boundary conditions should be fulfilled after apply!(K, g, ch), solvers of linear systems are not exact. apply!(ΔΔu, ch) can be used to make sure the values for the prescribed degrees of freedom are fulfilled exactly.

source
Ferrite.apply_local!Function
apply_local!(
+    local_matrix::AbstractMatrix, local_vector::AbstractVector,
+    global_dofs::AbstractVector, ch::ConstraintHandler;
+    apply_zero::Bool = false
+)

Similar to apply! but perform condensation of constrained degrees-of-freedom locally in local_matrix and local_vector before they are to be assembled into the global system.

When the keyword argument apply_zero is true all inhomogeneities are set to 0 (cf. apply! vs apply_zero!).

This method can only be used if all constraints are "local", i.e. no constraint couples with dofs outside of the element dofs (global_dofs) since condensation of such constraints requires writing to entries in the global matrix/vector. For such a case, apply_assemble! can be used instead.

Note that this method is destructive since it, by definition, modifies local_matrix and local_vector.

source
Ferrite.apply_assemble!Function
apply_assemble!(
+    assembler::AbstractAssembler, ch::ConstraintHandler,
+    global_dofs::AbstractVector{Int},
+    local_matrix::AbstractMatrix, local_vector::AbstractVector;
+    apply_zero::Bool = false
+)

Assemble local_matrix and local_vector into the global system in assembler by first doing constraint condensation using apply_local!.

This is similar to using apply_local! followed by assemble! with the advantage that non-local constraints can be handled, since this method can write to entries of the global matrix and vector outside of the indices in global_dofs.

When the keyword argument apply_zero is true all inhomogeneities are set to 0 (cf. apply! vs apply_zero!).

Note that this method is destructive since it modifies local_matrix and local_vector.

source
Ferrite.get_rhs_dataFunction
get_rhs_data(ch::ConstraintHandler, A::SparseMatrixCSC) -> RHSData

Returns the needed RHSData for apply_rhs!.

This must be used when the same stiffness matrix is reused for multiple steps, for example when timestepping, with different non-homogeneouos Dirichlet boundary conditions.

source
Ferrite.apply_rhs!Function
apply_rhs!(data::RHSData, f::AbstractVector, ch::ConstraintHandler, applyzero::Bool=false)

Applies the boundary condition to the right-hand-side vector without modifying the stiffness matrix.

See also: get_rhs_data.

source
Ferrite.RHSDataType
RHSData

Stores the constrained columns and mean of the diagonal of stiffness matrix A.

source

Initial conditions

Ferrite.apply_analytical!Function
apply_analytical!(
+    a::AbstractVector, dh::AbstractDofHandler, fieldname::Symbol,
+    f::Function, cellset=1:getncells(get_grid(dh)))

Apply a solution f(x) by modifying the values in the degree of freedom vector a pertaining to the field fieldname for all cells in cellset. The function f(x) are given the spatial coordinate of the degree of freedom. For scalar fields, f(x)::Number, and for vector fields with dimension dim, f(x)::Vec{dim}.

This function can be used to apply initial conditions for time dependent problems.

Note

This function only works for standard nodal finite element interpolations when the function value at the (algebraic) node is equal to the corresponding degree of freedom value. This holds for e.g. Lagrange and Serendipity interpolations, including sub- and superparametric elements.

source
diff --git a/previews/PR798/reference/dofhandler/index.html b/previews/PR798/reference/dofhandler/index.html new file mode 100644 index 0000000000..56afd02e08 --- /dev/null +++ b/previews/PR798/reference/dofhandler/index.html @@ -0,0 +1,59 @@ + +Degrees of Freedom · Ferrite.jl

Degrees of Freedom

Degrees of freedom (dofs) are distributed by the DofHandler.

Ferrite.DofHandlerType
DofHandler(grid::Grid)

Construct a DofHandler based on the grid grid.

After construction any number of discrete fields can be added to the DofHandler using add!. Construction is finalized by calling close!.

By default fields are added to all elements of the grid. Refer to SubDofHandler for restricting fields to subdomains of the grid.

Examples

dh = DofHandler(grid)
+ip_u = Lagrange{RefTriangle, 2}()^2 # vector interpolation for a field u
+ip_p = Lagrange{RefTriangle, 1}()   # scalar interpolation for a field p
+add!(dh, :u, ip_u)
+add!(dh, :p, ip_p)
+close!(dh)
source
Ferrite.SubDofHandlerType
SubDofHandler(dh::AbstractDofHandler, cellset::AbstractVecOrSet{Int})

Create an sdh::SubDofHandler from the parent dh, pertaining to the cells in cellset. This allows you to add fields to parts of the domain, or using different interpolations or cell types (e.g. Triangles and Quadrilaterals). All fields and cell types must be the same in one SubDofHandler.

After construction any number of discrete fields can be added to the SubDofHandler using add!. Construction is finalized by calling close! on the parent dh.

Examples

We assume we have a grid containing "Triangle" and "Quadrilateral" cells, including the cellsets "triangles" and "quadilaterals" for to these cells.

dh = DofHandler(grid)
+
+sdh_tri = SubDofHandler(dh, getcellset(grid, "triangles"))
+ip_tri = Lagrange{RefTriangle, 2}()^2 # vector interpolation for a field u
+add!(sdh_tri, :u, ip_tri)
+
+sdh_quad = SubDofHandler(dh, getcellset(grid, "quadilaterals"))
+ip_quad = Lagrange{RefQuadrilateral, 2}()^2 # vector interpolation for a field u
+add!(sdh_quad, :u, ip_quad)
+
+close!(dh) # Finalize by closing the parent
source

Adding fields to the DofHandlers

Ferrite.add!Method
add!(dh::DofHandler, name::Symbol, ip::Interpolation)

Add a field called name approximated by ip to the DofHandler dh.

The field is added to all cells of the underlying grid, use SubDofHandlers if the grid contains multiple cell types, or to add the field to subset of all the cells.

source
Ferrite.add!Method
add!(sdh::SubDofHandler, name::Symbol, ip::Interpolation)

Add a field called name approximated by ip to the SubDofHandler sdh.

source
Ferrite.close!Method
close!(dh::AbstractDofHandler)

Closes dh and creates degrees of freedom for each cell.

source

Dof renumbering

Ferrite.renumber!Function
renumber!(dh::AbstractDofHandler, order)
+renumber!(dh::AbstractDofHandler, ch::ConstraintHandler, order)

Renumber the degrees of freedom in the DofHandler and/or ConstraintHandler according to the ordering order.

order can be given by one of the following options:

  • A permutation vector perm::AbstractVector{Int} such that dof i is renumbered to perm[i].
  • DofOrder.FieldWise() for renumbering dofs field wise.
  • DofOrder.ComponentWise() for renumbering dofs component wise.
  • DofOrder.Ext{T} for "external" renumber permutations, see documentation for DofOrder.Ext for details.
Warning

The dof numbering in the DofHandler and ConstraintHandler must always be consistent. It is therefore necessary to either renumber before creating the ConstraintHandler in the first place, or to renumber the DofHandler and the ConstraintHandler together.

source
Ferrite.DofOrder.FieldWiseType
DofOrder.FieldWise()
+DofOrder.FieldWise(target_blocks::Vector{Int})

Dof order passed to renumber! to renumber global dofs field wise resulting in a globally blocked system.

The default behavior is to group dofs of each field into their own block, with the same order as in the DofHandler. This can be customized by passing a vector of the same length as the total number of fields in the DofHandler (see getfieldnames(dh)) that maps each field to a "target block": to renumber a DofHandler with three fields :u, :v, :w such that dofs for :u and :w end up in the first global block, and dofs for :v in the second global block use DofOrder.FieldWise([1, 2, 1]).

This renumbering is stable such that the original relative ordering of dofs within each target block is maintained.

source
Ferrite.DofOrder.ComponentWiseType
DofOrder.ComponentWise()
+DofOrder.ComponentWise(target_blocks::Vector{Int})

Dof order passed to renumber! to renumber global dofs component wise resulting in a globally blocked system.

The default behavior is to group dofs of each component into their own block, with the same order as in the DofHandler. This can be customized by passing a vector of length ncomponents that maps each component to a "target block" (see DofOrder.FieldWise for details).

This renumbering is stable such that the original relative ordering of dofs within each target block is maintained.

source

Common methods

Ferrite.ndofsFunction
ndofs(dh::AbstractDofHandler)

Return the number of degrees of freedom in dh

source
Ferrite.ndofs_per_cellFunction
ndofs_per_cell(dh::AbstractDofHandler[, cell::Int=1])

Return the number of degrees of freedom for the cell with index cell.

See also ndofs.

source
Ferrite.dof_rangeFunction
dof_range(sdh::SubDofHandler, field_idx::Int)
+dof_range(sdh::SubDofHandler, field_name::Symbol)
+dof_range(dh:DofHandler, field_name::Symbol)

Return the local dof range for a given field. The field can be specified by its name or index, where field_idx represents the index of a field within a SubDofHandler and field_idxs is a tuple of the SubDofHandler-index within the DofHandler and the field_idx.

Note

The dof_range of a field can vary between different SubDofHandlers. Therefore, it is advised to use the field_idxs or refer to a given SubDofHandler directly in case several SubDofHandlers exist. Using the field_name will always refer to the first occurrence of field within the DofHandler.

Example:

julia> grid = generate_grid(Triangle, (3, 3))
+Grid{2, Triangle, Float64} with 18 Triangle cells and 16 nodes
+
+julia> dh = DofHandler(grid); add!(dh, :u, 3); add!(dh, :p, 1); close!(dh);
+
+julia> dof_range(dh, :u)
+1:9
+
+julia> dof_range(dh, :p)
+10:12
+
+julia> dof_range(dh, (1,1)) # field :u
+1:9
+
+julia> dof_range(dh.subdofhandlers[1], 2) # field :p
+10:12
source
Ferrite.celldofsFunction
celldofs(dh::AbstractDofHandler, i::Int)

Return a vector with the degrees of freedom that belong to cell i.

See also celldofs!.

source
Ferrite.celldofs!Function
celldofs!(global_dofs::Vector{Int}, dh::AbstractDofHandler, i::Int)

Store the degrees of freedom that belong to cell i in global_dofs.

See also celldofs.

source

Grid iterators

Ferrite.CellCacheType
CellCache(grid::Grid)
+CellCache(dh::AbstractDofHandler)

Create a cache object with pre-allocated memory for the nodes, coordinates, and dofs of a cell. The cache is updated for a new cell by calling reinit!(cache, cellid) where cellid::Int is the cell id.

Methods with CellCache

  • reinit!(cc, i): reinitialize the cache for cell i
  • cellid(cc): get the cell id of the currently cached cell
  • getnodes(cc): get the global node ids of the cell
  • getcoordinates(cc): get the coordinates of the cell
  • celldofs(cc): get the global dof ids of the cell
  • reinit!(fev, cc): reinitialize CellValues or FacetValues

See also CellIterator.

source
Ferrite.CellIteratorType
CellIterator(grid::Grid, cellset=1:getncells(grid))
+CellIterator(dh::AbstractDofHandler, cellset=1:getncells(dh))

Create a CellIterator to conveniently iterate over all, or a subset, of the cells in a grid. The elements of the iterator are CellCaches which are properly reinit!ialized. See CellCache for more details.

Looping over a CellIterator, i.e.:

for cc in CellIterator(grid, cellset)
+    # ...
+end

is thus simply convenience for the following equivalent snippet:

cc = CellCache(grid)
+for idx in cellset
+    reinit!(cc, idx)
+    # ...
+end
Warning

CellIterator is stateful and should not be used for things other than for-looping (e.g. broadcasting over, or collecting the iterator may yield unexpected results).

source
Ferrite.FacetCacheType
FacetCache(grid::Grid)
+FacetCache(dh::AbstractDofHandler)

Create a cache object with pre-allocated memory for the nodes, coordinates, and dofs of a cell suitable for looping over faces in a grid. The cache is updated for a new face by calling reinit!(cache, fi::FacetIndex).

Methods with fc::FacetCache

  • reinit!(fc, fi): reinitialize the cache for face fi::FacetIndex
  • cellid(fc): get the current cellid
  • getnodes(fc): get the global node ids of the cell
  • getcoordinates(fc): get the coordinates of the cell
  • celldofs(fc): get the global dof ids of the cell
  • reinit!(fv, fc): reinitialize FacetValues

See also FacetIterator.

source
Ferrite.FacetIteratorType
FacetIterator(gridordh::Union{Grid,AbstractDofHandler}, facetset::AbstractVecOrSet{FacetIndex})

Create a FacetIterator to conveniently iterate over the faces in facestet. The elements of the iterator are FacetCaches which are properly reinit!ialized. See FacetCache for more details.

Looping over a FacetIterator, i.e.:

for fc in FacetIterator(grid, facetset)
+    # ...
+end

is thus simply convenience for the following equivalent snippet: ```julia fc = FacetCache(grid) for faceindex in facetset reinit!(fc, faceindex) # ... end

source
Ferrite.InterfaceCacheType
InterfaceCache(grid::Grid)
+InterfaceCache(dh::AbstractDofHandler)

Create a cache object with pre-allocated memory for the nodes, coordinates, and dofs of an interface. The cache is updated for a new cell by calling reinit!(cache, facet_a, facet_b) where facet_a::FacetIndex and facet_b::FacetIndex are the two interface faces.

Struct fields of InterfaceCache

  • ic.a :: FacetCache: face cache for the first face of the interface
  • ic.b :: FacetCache: face cache for the second face of the interface
  • ic.dofs :: Vector{Int}: global dof ids for the interface (union of ic.a.dofs and ic.b.dofs)

Methods with InterfaceCache

  • reinit!(cache::InterfaceCache, facet_a::FacetIndex, facet_b::FacetIndex): reinitialize the cache for a new interface
  • interfacedofs(ic): get the global dof ids of the interface

See also InterfaceIterator.

source
Ferrite.InterfaceIteratorType
InterfaceIterator(grid::Grid, [topology::ExclusiveTopology])
+InterfaceIterator(dh::AbstractDofHandler, [topology::ExclusiveTopology])

Create an InterfaceIterator to conveniently iterate over all the interfaces in a grid. The elements of the iterator are InterfaceCaches which are properly reinit!ialized. See InterfaceCache for more details. Looping over an InterfaceIterator, i.e.:

for ic in InterfaceIterator(grid, topology)
+    # ...
+end

is thus simply convenience for the following equivalent snippet for grids of dimensions > 1:

ic = InterfaceCache(grid, topology)
+for face in topology.face_skeleton
+    neighborhood = topology.face_face_neighbor[face[1], face[2]]
+    isempty(neighborhood) && continue
+    neighbor_face = neighborhood[1]
+    reinit!(ic, face, neighbor_face)
+    # ...
+end
Warning

InterfaceIterator is stateful and should not be used for things other than for-looping (e.g. broadcasting over, or collecting the iterator may yield unexpected results).

source
diff --git a/previews/PR798/reference/export/index.html b/previews/PR798/reference/export/index.html new file mode 100644 index 0000000000..c1d5adc4d8 --- /dev/null +++ b/previews/PR798/reference/export/index.html @@ -0,0 +1,42 @@ + +Postprocessing · Ferrite.jl

Postprocessing

Projection of quadrature point data

Ferrite.L2ProjectorMethod
L2Projector(grid::AbstractGrid)

Initiate an L2Projector for projecting quadrature data onto a function space. To define the function space, add interpolations for different cell sets with add! before close!ing the projector, see the example below.

The L2Projector acts as the integrated left hand side of the projection equation: Find projection $u \in U_h(\Omega) \subset L_2(\Omega)$ such that

\[\int v u \ \mathrm{d}\Omega = \int v f \ \mathrm{d}\Omega \quad \forall v \in U_h(\Omega),\]

where $f \in L_2(\Omega)$ is the data to project. The function space $U_h(\Omega)$ is the finite element approximation given by the interpolations add!ed to the L2Projector.

Example

proj = L2Projector(grid)
+qr_quad = QuadratureRule{RefQuadrilateral}(2)
+add!(proj, quad_set, Lagrange{RefQuadrilateral, 1}(); qr_rhs = qr_quad)
+qr_tria = QuadratureRule{RefTriangle}(1)
+add!(proj, tria_set, Lagrange{RefTriangle, 1}(); qr_rhs = qr_tria)
+close!(proj)
+
+vals = Dict{Int, Vector{Float64}}() # Can also be Vector{Vector},
+                                    # indexed with cellnr
+for (set, qr) in ((quad_set, qr_quad), (tria_set, qr_tria))
+    nqp = getnquadpoints(qr)
+    for cellnr in set
+        vals[cellnr] = rand(nqp)
+    end
+end
+
+projected = project(proj, vals)

where projected can be used in e.g. evaluate_at_points with the PointEvalHandler, or with evaluate_at_grid_nodes.

source
Ferrite.add!Method
add!(proj::L2Projector, set::AbstractVecOrSet{Int}, ip::Interpolation;
+    qr_rhs, [qr_lhs])

Add an interpolation ip on the cells in set to the L2Projector proj.

  • qr_rhs sets the quadrature rule used to later integrate the right-hand-side of the projection equation, when calling project. It should match the quadrature points used when creating the quadrature-point variables to project.
  • The optional qr_lhs sets the quadrature rule used to integrate the left-hand-side of the projection equation, and defaults to a quadrature rule that integrates the mass-matrix exactly for the given interpolation ip.
source
Ferrite.close!Method
close!(proj::L2Projector)

Close proj which assembles and calculates the left-hand-side of the projection equation, before doing a Cholesky factorization of the mass-matrix.

source
Ferrite.L2ProjectorMethod
L2Projector(ip::Interpolation, grid::AbstractGrid; [qr_lhs], [set])

A quick way to initiate an L2Projector, add an interpolation ip on the set to it, and then close! it so that it can be used to project. The optional keyword argument set defaults to all cells in the grid, while qr_lhs defaults to a quadrature rule that integrates the mass matrix exactly for the interpolation ip.

source
Ferrite.projectFunction
project(proj::L2Projector, vals, [qr_rhs::QuadratureRule])

Makes a L2 projection of data vals to the nodes of the grid using the projector proj (see L2Projector).

project integrates the right hand side, and solves the projection $u$ from the following projection equation: Find projection $u \in U_h(\Omega) \subset L_2(\Omega)$ such that

\[\int v u \ \mathrm{d}\Omega = \int v f \ \mathrm{d}\Omega \quad \forall v \in U_h(\Omega),\]

where $f \in L_2(\Omega)$ is the data to project. The function space $U_h(\Omega)$ is the finite element approximation given by the interpolations in proj.

The data vals should be an AbstractVector or AbstractDict that is indexed by the cell number. Each index in vals should give an AbstractVector with one element for each cell quadrature point.

If proj was created by calling L2Projector(ip, grid, set), qr_rhs must be given. Otherwise, this is added for each domain when calling add!(proj, args...).

Alternatively, vals can be a matrix, with the column index referring the cell number, and the row index corresponding to quadrature point number. Example (scalar) input data:

vals = [
+    [0.44, 0.98, 0.32], # data for quadrature point 1, 2, 3 of element 1
+    [0.29, 0.48, 0.55], # data for quadrature point 1, 2, 3 of element 2
+    # ...
+]

or equivalent in matrix form:

vals = [
+    0.44 0.29 # ...
+    0.98 0.48 # ...
+    0.32 0.55 # ...
+]

Supported data types to project are Numbers and AbstractTensors.

Note

The order of the returned data correspond to the order of the L2Projector's internal DofHandler. The data can be further analyzed with evaluate_at_points and evaluate_at_grid_nodes. Use write_projection to export the result.

source

Evaluation at points

Ferrite.evaluate_at_grid_nodesFunction
evaluate_at_grid_nodes(dh::AbstractDofHandler, u::AbstractVector{T}, fieldname::Symbol) where T

Evaluate the approximated solution for field fieldname at the node coordinates of the grid given the Dof handler dh and the solution vector u.

Return a vector of length getnnodes(grid) where entry i contains the evaluation of the approximation in the coordinate of node i. If the field does not live on parts of the grid, the corresponding values for those nodes will be returned as NaNs.

source
Ferrite.PointEvalHandlerType
PointEvalHandler(grid::Grid, points::AbstractVector{Vec{dim,T}}; kwargs...) where {dim, T}

The PointEvalHandler can be used for function evaluation in arbitrary points in the domain – not just in quadrature points or nodes.

The constructor takes a grid and a vector of coordinates for the points. The PointEvalHandler computes i) the corresponding cell, and ii) the (local) coordinate within the cell, for each point. The fields of the PointEvalHandler are:

  • cells::Vector{Union{Int,Nothing}}: vector with cell IDs for the points, with nothing for points that could not be found.
  • local_coords::Vector{Union{Vec,Nothing}}: vector with the local coordinates (i.e. coordinates in the reference configuration) for the points, with nothing for points that could not be found.

There are two ways to use the PointEvalHandler to evaluate functions:

  • evaluate_at_points: can be used when the function is described by i) a dh::DofHandler + uh::Vector (for example the FE-solution), or ii) a p::L2Projector + ph::Vector (for projected data).
  • Iteration with PointIterator + PointValues: can be used for more flexible evaluation in the points, for example to compute gradients.
source
Ferrite.evaluate_at_pointsFunction
evaluate_at_points(ph::PointEvalHandler, dh::AbstractDofHandler, dof_values::Vector{T}, [fieldname::Symbol]) where T
+evaluate_at_points(ph::PointEvalHandler, proj::L2Projector, dof_values::Vector{T}) where T

Return a Vector{T} (for a 1-dimensional field) or a Vector{Vec{fielddim, T}} (for a vector field) with the field values of field fieldname in the points of the PointEvalHandler. The fieldname can be omitted if only one field is stored in dh. The field values are computed based on the dof_values and interpolated to the local coordinates by the function interpolation of the corresponding field stored in the AbstractDofHandler or the L2Projector.

Points that could not be found in the domain when constructing the PointEvalHandler will have NaNs for the corresponding entries in the output vector.

source
Ferrite.PointValuesType
PointValues(cv::CellValues)
+PointValues([::Type{T}], func_interpol::Interpolation, [geom_interpol::Interpolation])

Similar to CellValues but with a single updateable "quadrature point". PointValues are used for evaluation of functions/gradients in arbitrary points of the domain together with a PointEvalHandler.

PointValues can be created from CellValues, or from the interpolations directly.

PointValues are reinitialized like other CellValues, but since the local reference coordinate of the "quadrature point" changes this needs to be passed to reinit!, in addition to the element coordinates: reinit!(pv, coords, local_coord). Alternatively, it can be reinitialized with a PointLocation when iterating a PointEvalHandler with a PointIterator.

For function/gradient evaluation, PointValues are used in the same way as CellValues, i.e. by using function_value, function_gradient, etc, with the exception that there is no need to specify the quadrature point index (since PointValues only have 1, this is the default).

source
Ferrite.PointIteratorType
PointIterator(ph::PointEvalHandler)

Create an iterator over the points in the PointEvalHandler. The elements of the iterator are either a PointLocation, if the corresponding point could be found in the grid, or nothing, if the point was not found.

A PointLocation can be used to query the cell ID with the cellid function, and can be used to reinitialize PointValues with reinit!.

Examples

ph = PointEvalHandler(grid, points)
+
+for point in PointIterator(ph)
+    point === nothing && continue # Skip any points that weren't found
+    reinit!(pointvalues, point)   # Update pointvalues
+    # ...
+end
source
Ferrite.PointLocationType
PointLocation

Element of a PointIterator, typically used to reinitialize PointValues. Fields:

  • cid::Int: ID of the cell containing the point
  • local_coord::Vec: the local (reference) coordinate of the point
  • coords::Vector{Vec}: the coordinates of the cell
source

VTK export

Ferrite.VTKGridFileType
VTKGridFile(filename::AbstractString, grid::AbstractGrid; kwargs...)
+VTKGridFile(filename::AbstractString, dh::DofHandler; kwargs...)

Create a VTKGridFile that contains an unstructured VTK grid. The keyword arguments are forwarded to WriteVTK.vtk_grid, see Data Formatting Options

This file handler can be used to to write data with

It is necessary to call close(::VTKGridFile) to save the data after writing to the file handler. Using the supported do-block does this automatically:

VTKGridFile(filename, grid) do vtk
+    write_solution(vtk, dh, u)
+    write_cell_data(vtk, celldata)
+end
source
Ferrite.write_solutionFunction
write_solution(vtk::VTKGridFile, dh::AbstractDofHandler, u::Vector, suffix="")

Save the values at the nodes in the degree of freedom vector u to vtk. Each field in dh will be saved separately, and suffix can be used to append to the fieldname.

u can also contain tensorial values, but each entry in u must correspond to a degree of freedom in dh, see write_node_data for details. Use write_node_data directly when exporting values that are already sorted by the nodes in the grid.

source
Ferrite.write_projectionFunction
write_projection(vtk::VTKGridFile, proj::L2Projector, vals::Vector, name::AbstractString)

Project vals to the grid nodes with proj and save to vtk.

source
Ferrite.write_cell_dataFunction
write_cell_data(vtk::VTKGridFile, celldata::AbstractVector, name::String)

Write the celldata that is ordered by the cells in the grid to the vtk file.

source
Ferrite.write_node_dataFunction
write_node_data(vtk::VTKGridFile, nodedata::Vector{Real}, name)
+write_node_data(vtk::VTKGridFile, nodedata::Vector{<:AbstractTensor}, name)

Write the nodedata that is ordered by the nodes in the grid to vtk.

When nodedata contains Tensors.Vecs, each component is exported. Two-dimensional vectors are padded with zeros.

When nodedata contains second order tensors, the index order, [11, 22, 33, 23, 13, 12, 32, 31, 21], follows the default Voigt order in Tensors.jl.

source
Ferrite.write_cellsetFunction
write_cellset(vtk, grid::AbstractGrid)
+write_cellset(vtk, grid::AbstractGrid, cellset::String)
+write_cellset(vtk, grid::AbstractGrid, cellsets::Union{AbstractVector{String},AbstractSet{String})

Write all cell sets in the grid with name according to their keys and celldata 1 if the cell is in the set, and 0 otherwise. It is also possible to only export a single cellset, or multiple cellsets.

source
Ferrite.write_nodesetFunction
write_nodeset(vtk::VTKGridFile, grid::AbstractGrid, nodeset::String)

Write nodal values of 1 for nodes in nodeset, and 0 otherwise

source
Ferrite.write_constraintsFunction
write_constraints(vtk::VTKGridFile, ch::ConstraintHandler)

Saves the dirichlet boundary conditions to a vtkfile. Values will have a 1 where bcs are active and 0 otherwise

source
Ferrite.write_cell_colorsFunction
write_cell_colors(vtk::VTKGridFile, grid::AbstractGrid, cell_colors, name="coloring")

Write cell colors (see create_coloring) to a VTK file for visualization.

In case of coloring a subset, the cells which are not part of the subset are represented as color 0.

source
diff --git a/previews/PR798/reference/fevalues/index.html b/previews/PR798/reference/fevalues/index.html new file mode 100644 index 0000000000..2ae66f0087 --- /dev/null +++ b/previews/PR798/reference/fevalues/index.html @@ -0,0 +1,11 @@ + +FEValues · Ferrite.jl

FEValues

Main types

CellValues and FacetValues are the most common subtypes of Ferrite.AbstractValues. For more details about how these work, please see the related topic guide.

Ferrite.CellValuesType
CellValues([::Type{T},] quad_rule::QuadratureRule, func_interpol::Interpolation, [geom_interpol::Interpolation])

A CellValues object facilitates the process of evaluating values of shape functions, gradients of shape functions, values of nodal functions, gradients and divergences of nodal functions etc. in the finite element cell.

Arguments:

  • T: an optional argument (default to Float64) to determine the type the internal data is stored as.
  • quad_rule: an instance of a QuadratureRule
  • func_interpol: an instance of an Interpolation used to interpolate the approximated function
  • geom_interpol: an optional instance of a Interpolation which is used to interpolate the geometry. By default linear Lagrange interpolation is used. For embedded elements the geometric interpolations should be vectorized to the spatial dimension.

Keyword arguments: The following keyword arguments are experimental and may change in future minor releases

  • update_gradients: Specifies if the gradients of the shape functions should be updated (default true)
  • update_hessians: Specifies if the hessians of the shape functions should be updated (default false)
  • update_detJdV: Specifies if the volume associated with each quadrature point should be updated (default true)

Common methods:

source
Ferrite.FacetValuesType
FacetValues([::Type{T}], quad_rule::FacetQuadratureRule, func_interpol::Interpolation, [geom_interpol::Interpolation])

A FacetValues object facilitates the process of evaluating values of shape functions, gradients of shape functions, values of nodal functions, gradients and divergences of nodal functions etc. on the facets of finite elements.

Arguments:

  • T: an optional argument (default to Float64) to determine the type the internal data is stored as.
  • quad_rule: an instance of a FacetQuadratureRule
  • func_interpol: an instance of an Interpolation used to interpolate the approximated function
  • geom_interpol: an optional instance of an Interpolation which is used to interpolate the geometry. By default linear Lagrange interpolation is used.

Keyword arguments: The following keyword arguments are experimental and may change in future minor releases

  • update_gradients: Specifies if the gradients of the shape functions should be updated (default true)
  • update_hessians: Specifies if the hessians of the shape functions should be updated (default false)

Common methods:

source
Embedded API

Currently, embedded FEValues returns SArrays, which behave differently from the Tensors for normal value. In the future, we expect to return an AbstractTensor, this change may happen in a minor release, and the API for embedded FEValues should therefore be considered experimental.

Applicable functions

The following functions are applicable to both CellValues and FacetValues.

Ferrite.reinit!Function
reinit!(cv::CellValues, cell::AbstractCell, x::AbstractVector)
+reinit!(cv::CellValues, x::AbstractVector)
+reinit!(fv::FacetValues, cell::AbstractCell, x::AbstractVector, facet::Int)
+reinit!(fv::FacetValues, x::AbstractVector, function_gradient::Int)

Update the CellValues/FacetValues object for a cell or facet with cell coordinates x. The derivatives of the shape functions, and the new integration weights are computed. For interpolations with non-identity mappings, the current cell is also required.

source
Ferrite.getnquadpointsFunction
getnquadpoints(fe_v::AbstractValues)

Return the number of quadrature points. For FacetValues, this is the number for the current facet.

source
Ferrite.getdetJdVFunction
getdetJdV(fe_v::AbstractValues, q_point::Int)

Return the product between the determinant of the Jacobian and the quadrature point weight for the given quadrature point: $\det(J(\mathbf{x})) w_q$.

This value is typically used when one integrates a function on a finite element cell or facet as

$\int\limits_\Omega f(\mathbf{x}) d \Omega \approx \sum\limits_{q = 1}^{n_q} f(\mathbf{x}_q) \det(J(\mathbf{x})) w_q$ $\int\limits_\Gamma f(\mathbf{x}) d \Gamma \approx \sum\limits_{q = 1}^{n_q} f(\mathbf{x}_q) \det(J(\mathbf{x})) w_q$

source
Ferrite.shape_valueMethod
shape_value(fe_v::AbstractValues, q_point::Int, base_function::Int)

Return the value of shape function base_function evaluated in quadrature point q_point.

source
Ferrite.shape_gradientMethod
shape_gradient(fe_v::AbstractValues, q_point::Int, base_function::Int)

Return the gradient of shape function base_function evaluated in quadrature point q_point.

source
Ferrite.shape_symmetric_gradientFunction
shape_symmetric_gradient(fe_v::AbstractValues, q_point::Int, base_function::Int)

Return the symmetric gradient of shape function base_function evaluated in quadrature point q_point.

source
Ferrite.shape_divergenceFunction
shape_divergence(fe_v::AbstractValues, q_point::Int, base_function::Int)

Return the divergence of shape function base_function evaluated in quadrature point q_point.

source
Ferrite.shape_curlFunction
shape_curl(fe_v::AbstractValues, q_point::Int, base_function::Int)

Return the curl of shape function base_function evaluated in quadrature point q_point.

source
Ferrite.geometric_valueFunction
geometric_value(fe_v::AbstractValues, q_point, base_function::Int)

Return the value of the geometric shape function base_function evaluated in quadrature point q_point.

source
Ferrite.function_valueFunction
function_value(iv::InterfaceValues, q_point::Int, u; here::Bool)
+function_value(iv::InterfaceValues, q_point::Int, u, dof_range_here, dof_range_there; here::Bool)

Compute the value of the function in quadrature point q_point on the "here" (here=true) or "there" (here=false) side of the interface. u_here and u_there are the values of the degrees of freedom for the respective element.

u is a vector of scalar values for the degrees of freedom. This function can be used with a single u vector containing the dofs of both elements of the interface or two vectors (u_here and u_there) which contain the dofs of each cell of the interface respectively.

here determines which element to use for calculating function value. true uses the value on the first element's side of the interface, while false uses the value on the second element's side.

The value of a scalar valued function is computed as $u(\mathbf{x}) = \sum\limits_{i = 1}^n N_i (\mathbf{x}) u_i$ where $u_i$ are the value of $u$ in the nodes. For a vector valued function the value is calculated as $\mathbf{u}(\mathbf{x}) = \sum\limits_{i = 1}^n N_i (\mathbf{x}) \mathbf{u}_i$ where $\mathbf{u}_i$ are the nodal values of $\mathbf{u}$.

source
function_value(fe_v::AbstractValues, q_point::Int, u::AbstractVector, [dof_range])

Compute the value of the function in a quadrature point. u is a vector with values for the degrees of freedom. For a scalar valued function, u contains scalars. For a vector valued function, u can be a vector of scalars (for use of VectorValues) or u can be a vector of Vecs (for use with ScalarValues).

The value of a scalar valued function is computed as $u(\mathbf{x}) = \sum\limits_{i = 1}^n N_i (\mathbf{x}) u_i$ where $u_i$ are the value of $u$ in the nodes. For a vector valued function the value is calculated as $\mathbf{u}(\mathbf{x}) = \sum\limits_{i = 1}^n N_i (\mathbf{x}) \mathbf{u}_i$ where $\mathbf{u}_i$ are the nodal values of $\mathbf{u}$.

source
Ferrite.function_gradientFunction
function_gradient(iv::InterfaceValues, q_point::Int, u; here::Bool)
+function_gradient(iv::InterfaceValues, q_point::Int, u, dof_range_here, dof_range_there; here::Bool)

Compute the gradient of the function in a quadrature point. u is a vector of scalar values for the degrees of freedom. This function can be used with a single u vector containing the dofs of both elements of the interface or two vectors (u_here and u_there) which contain the dofs of each cell of the interface respectively.

here determines which element to use for calculating function value. true uses the value on the first element's side of the interface, while false uses the value on the second element's side.

The gradient of a scalar function or a vector valued function with use of VectorValues is computed as $\mathbf{\nabla} u(\mathbf{x}) = \sum\limits_{i = 1}^n \mathbf{\nabla} N_i (\mathbf{x}) u_i$ or $\mathbf{\nabla} u(\mathbf{x}) = \sum\limits_{i = 1}^n \mathbf{\nabla} \mathbf{N}_i (\mathbf{x}) u_i$ respectively, where $u_i$ are the nodal values of the function. For a vector valued function with use of ScalarValues the gradient is computed as $\mathbf{\nabla} \mathbf{u}(\mathbf{x}) = \sum\limits_{i = 1}^n \mathbf{u}_i \otimes \mathbf{\nabla} N_i (\mathbf{x})$ where $\mathbf{u}_i$ are the nodal values of $\mathbf{u}$.

source
function_gradient(fe_v::AbstractValues{dim}, q_point::Int, u::AbstractVector, [dof_range])

Compute the gradient of the function in a quadrature point. u is a vector with values for the degrees of freedom. For a scalar valued function, u contains scalars. For a vector valued function, u can be a vector of scalars (for use of VectorValues) or u can be a vector of Vecs (for use with ScalarValues).

The gradient of a scalar function or a vector valued function with use of VectorValues is computed as $\mathbf{\nabla} u(\mathbf{x}) = \sum\limits_{i = 1}^n \mathbf{\nabla} N_i (\mathbf{x}) u_i$ or $\mathbf{\nabla} u(\mathbf{x}) = \sum\limits_{i = 1}^n \mathbf{\nabla} \mathbf{N}_i (\mathbf{x}) u_i$ respectively, where $u_i$ are the nodal values of the function. For a vector valued function with use of ScalarValues the gradient is computed as $\mathbf{\nabla} \mathbf{u}(\mathbf{x}) = \sum\limits_{i = 1}^n \mathbf{u}_i \otimes \mathbf{\nabla} N_i (\mathbf{x})$ where $\mathbf{u}_i$ are the nodal values of $\mathbf{u}$.

source
Ferrite.function_symmetric_gradientFunction
function_symmetric_gradient(fe_v::AbstractValues, q_point::Int, u::AbstractVector, [dof_range])

Compute the symmetric gradient of the function, see function_gradient. Return a SymmetricTensor.

The symmetric gradient of a scalar function is computed as $\left[ \mathbf{\nabla} \mathbf{u}(\mathbf{x_q}) \right]^\text{sym} = \sum\limits_{i = 1}^n \frac{1}{2} \left[ \mathbf{\nabla} N_i (\mathbf{x}_q) \otimes \mathbf{u}_i + \mathbf{u}_i \otimes \mathbf{\nabla} N_i (\mathbf{x}_q) \right]$ where $\mathbf{u}_i$ are the nodal values of the function.

source
Ferrite.function_divergenceFunction
function_divergence(fe_v::AbstractValues, q_point::Int, u::AbstractVector, [dof_range])

Compute the divergence of the vector valued function in a quadrature point.

The divergence of a vector valued functions in the quadrature point $\mathbf{x}_q)$ is computed as $\mathbf{\nabla} \cdot \mathbf{u}(\mathbf{x_q}) = \sum\limits_{i = 1}^n \mathbf{\nabla} N_i (\mathbf{x_q}) \cdot \mathbf{u}_i$ where $\mathbf{u}_i$ are the nodal values of the function.

source
Ferrite.function_curlFunction
function_curl(fe_v::AbstractValues, q_point::Int, u::AbstractVector, [dof_range])

Compute the curl of the vector valued function in a quadrature point.

The curl of a vector valued functions in the quadrature point $\mathbf{x}_q)$ is computed as $\mathbf{\nabla} \times \mathbf{u}(\mathbf{x_q}) = \sum\limits_{i = 1}^n \mathbf{\nabla} N_i \times (\mathbf{x_q}) \cdot \mathbf{u}_i$ where $\mathbf{u}_i$ are the nodal values of the function.

source
Ferrite.spatial_coordinateFunction
spatial_coordinate(fe_v::AbstractValues, q_point::Int, x::AbstractVector)

Compute the spatial coordinate in a quadrature point. x contains the nodal coordinates of the cell.

The coordinate is computed, using the geometric interpolation, as $\mathbf{x} = \sum\limits_{i = 1}^n M_i (\mathbf{\xi}) \mathbf{\hat{x}}_i$.

where $\xi$is the coordinate of the given quadrature point q_point of the associated quadrature rule.

source
spatial_coordinate(ip::ScalarInterpolation, ξ::Vec, x::AbstractVector{<:Vec{sdim, T}})

Compute the spatial coordinate in a given quadrature point. x contains the nodal coordinates of the cell.

The coordinate is computed, using the geometric interpolation, as $\mathbf{x} = \sum\limits_{i = 1}^n M_i (\mathbf{\xi}) \mathbf{\hat{x}}_i$

source

In addition, there are some methods that are unique for FacetValues.

Ferrite.getcurrentfacetFunction
getcurrentfacet(fv::FacetValues)

Return the current active facet of the FacetValues object (from last reinit!).

source
Ferrite.getnormalFunction
getnormal(fv::FacetValues, qp::Int)

Return the normal at the quadrature point qp for the active facet of the FacetValues object(from last reinit!).

source
getnormal(iv::InterfaceValues, qp::Int; here::Bool=true)

Return the normal vector in the quadrature point qp on the interface. If here = true (default) the outward normal to the "here" element is returned, otherwise the outward normal to the "there" element.

source

InterfaceValues

All of the methods for FacetValues apply for InterfaceValues as well. In addition, there are some methods that are unique for InterfaceValues:

Ferrite.InterfaceValuesType
InterfaceValues

An InterfaceValues object facilitates the process of evaluating values, averages, jumps and gradients of shape functions and function on the interfaces between elements.

The first element of the interface is denoted "here" and the second element "there".

Constructors

  • InterfaceValues(qr::FacetQuadratureRule, ip::Interpolation): same quadrature rule and interpolation on both sides, default linear Lagrange geometric interpolation.
  • InterfaceValues(qr::FacetQuadratureRule, ip::Interpolation, ip_geo::Interpolation): same as above but with given geometric interpolation.
  • InterfaceValues(qr_here::FacetQuadratureRule, ip_here::Interpolation, qr_there::FacetQuadratureRule, ip_there::Interpolation): different quadrature rule and interpolation on the two sides, default linear Lagrange geometric interpolation.
  • InterfaceValues(qr_here::FacetQuadratureRule, ip_here::Interpolation, ip_geo_here::Interpolation, qr_there::FacetQuadratureRule, ip_there::Interpolation, ip_geo_there::Interpolation): same as above but with given geometric interpolation.
  • InterfaceValues(fv::FacetValues): quadrature rule and interpolations from face values (same on both sides).
  • InterfaceValues(fv_here::FacetValues, fv_there::FacetValues): quadrature rule and interpolations from the face values.

Associated methods:

Common methods:

source
Ferrite.shape_value_averageFunction
shape_value_average(iv::InterfaceValues, qp::Int, i::Int)

Compute the average of the value of shape function i at quadrature point qp across the interface.

source
Ferrite.shape_value_jumpFunction
shape_value_jump(iv::InterfaceValues, qp::Int, i::Int)

Compute the jump of the value of shape function i at quadrature point qp across the interface in the default normal direction.

This function uses the definition $\llbracket \vec{v} \rrbracket=\vec{v}^\text{there} -\vec{v}^\text{here}$. To obtain the form, $\llbracket \vec{v} \rrbracket=\vec{v}^\text{there} \cdot \vec{n}^\text{there} + \vec{v}^\text{here} \cdot \vec{n}^\text{here}$, multiply by minus the outward facing normal to the first element's side of the interface (which is the default normal for getnormal with InterfaceValues).

source
Ferrite.shape_gradient_averageFunction
shape_gradient_average(iv::InterfaceValues, qp::Int, i::Int)

Compute the average of the gradient of shape function i at quadrature point qp across the interface.

source
Ferrite.shape_gradient_jumpFunction
shape_gradient_jump(iv::InterfaceValues, qp::Int, i::Int)

Compute the jump of the gradient of shape function i at quadrature point qp across the interface in the default normal direction.

This function uses the definition $\llbracket \vec{v} \rrbracket=\vec{v}^\text{there} -\vec{v}^\text{here}$. To obtain the form, $\llbracket \vec{v} \rrbracket=\vec{v}^\text{there} ⋅ \vec{n}^\text{there} + \vec{v}^\text{here} ⋅ \vec{n}^\text{here}$, multiply by minus the outward facing normal to the first element's side of the interface (which is the default normal for getnormal with InterfaceValues).

source
Ferrite.function_value_averageFunction
function_value_average(iv::InterfaceValues, q_point::Int, u)
+function_value_average(iv::InterfaceValues, q_point::Int, u, dof_range_here, dof_range_there)

Compute the average of the function value at the quadrature point on the interface.

source
Ferrite.function_value_jumpFunction
function_value_jump(iv::InterfaceValues, q_point::Int, u)
+function_value_jump(iv::InterfaceValues, q_point::Int, u, dof_range_here, dof_range_there)

Compute the jump of the function value at the quadrature point over the interface along the default normal direction.

This function uses the definition $\llbracket \vec{v} \rrbracket=\vec{v}^\text{there} -\vec{v}^\text{here}$. To obtain the form, $\llbracket \vec{v} \rrbracket=\vec{v}^\text{there} ⋅ \vec{n}^\text{there} + \vec{v}^\text{here} ⋅ \vec{n}^\text{here}$, multiply by minus the outward facing normal to the first element's side of the interface (which is the default normal for getnormal with InterfaceValues).

source
Ferrite.function_gradient_averageFunction
function_gradient_average(iv::InterfaceValues, q_point::Int, u)
+function_gradient_average(iv::InterfaceValues, q_point::Int, u, dof_range_here, dof_range_there)

Compute the average of the function gradient at the quadrature point on the interface.

source
Ferrite.function_gradient_jumpFunction
function_gradient_jump(iv::InterfaceValues, q_point::Int, u)
+function_gradient_jump(iv::InterfaceValues, q_point::Int, u, dof_range_here, dof_range_there)

Compute the jump of the function gradient at the quadrature point over the interface along the default normal direction.

This function uses the definition $\llbracket \vec{v} \rrbracket=\vec{v}^\text{there} -\vec{v}^\text{here}$. To obtain the form, $\llbracket \vec{v} \rrbracket=\vec{v}^\text{there} ⋅ \vec{n}^\text{there} + \vec{v}^\text{here} ⋅ \vec{n}^\text{here}$, multiply by minus the outward facing normal to the first element's side of the interface (which is the default normal for getnormal with InterfaceValues).

source
diff --git a/previews/PR798/reference/grid/index.html b/previews/PR798/reference/grid/index.html new file mode 100644 index 0000000000..1fe83c3316 --- /dev/null +++ b/previews/PR798/reference/grid/index.html @@ -0,0 +1,26 @@ + +Grid & AbstractGrid · Ferrite.jl

Grid & AbstractGrid

Grid

Ferrite.generate_gridFunction
generate_grid(celltype::Cell, nel::NTuple, [left::Vec, right::Vec)

Return a Grid for a rectangle in 1, 2 or 3 dimensions. celltype defined the type of cells, e.g. Triangle or Hexahedron. nel is a tuple of the number of elements in each direction. left and right are optional endpoints of the domain. Defaults to -1 and 1 in all directions.

source
Ferrite.NodeType
Node{dim, T}

A Node is a point in space.

Fields

  • x::Vec{dim,T}: stores the coordinates
source
Ferrite.VertexIndexType

A VertexIndex wraps an (Int, Int) and defines a local vertex by pointing to a (cell, vert).

source
Ferrite.EdgeIndexType

A EdgeIndex wraps an (Int, Int) and defines a local edge by pointing to a (cell, edge).

source
Ferrite.FaceIndexType

A FaceIndex wraps an (Int, Int) and defines a local face by pointing to a (cell, face).

source
Ferrite.FacetIndexType

A FacetIndex wraps an (Int, Int) and defines a local facet by pointing to a (cell, facet).

source
Ferrite.GridType
Grid{dim, C<:AbstractCell, T<:Real} <: AbstractGrid}

A Grid is a collection of Ferrite.AbstractCells and Ferrite.Nodes which covers the computational domain. Helper structures for applying boundary conditions or define subdomains are gathered in cellsets, nodesets, facetsets, and vertexsets.

Fields

  • cells::Vector{C}: stores all cells of the grid
  • nodes::Vector{Node{dim,T}}: stores the dim dimensional nodes of the grid
  • cellsets::Dict{String, OrderedSet{Int}}: maps a String key to an OrderedSet of cell ids
  • nodesets::Dict{String, OrderedSet{Int}}: maps a String key to an OrderedSet of global node ids
  • facetsets::Dict{String, OrderedSet{FacetIndex}}: maps a String to an OrderedSet of FacetIndex
  • vertexsets::Dict{String, OrderedSet{VertexIndex}}: maps a String key to an OrderedSet of VertexIndex
source

Utility Functions

Ferrite.getcellsFunction
getcells(grid::AbstractGrid)
+getcells(grid::AbstractGrid, v::Union{Int,Vector{Int}}
+getcells(grid::AbstractGrid, setname::String)

Returns either all cells::Collection{C<:AbstractCell} of a <:AbstractGrid or a subset based on an Int, Vector{Int} or String. Whereas the last option tries to call a cellset of the grid. Collection can be any indexable type, for Grid it is Vector{C<:AbstractCell}.

source
Ferrite.getnodesFunction
getnodes(grid::AbstractGrid)
+getnodes(grid::AbstractGrid, v::Union{Int,Vector{Int}}
+getnodes(grid::AbstractGrid, setname::String)

Returns either all nodes::Collection{N} of a <:AbstractGrid or a subset based on an Int, Vector{Int} or String. The last option tries to call a nodeset of the <:AbstractGrid. Collection{N} refers to some indexable collection where each element corresponds to a Node.

source
Ferrite.getcellsetFunction
getcellset(grid::AbstractGrid, setname::String)

Returns all cells as cellid in the set with name setname.

source
Ferrite.getnodesetFunction
getnodeset(grid::AbstractGrid, setname::String)

Returns all nodes as nodeid in the set with name setname.

source
Ferrite.getfacetsetFunction
getfacetset(grid::AbstractGrid, setname::String)

Returns all faces as FacetIndex in the set with name setname.

source
Ferrite.getvertexsetFunction
getvertexset(grid::AbstractGrid, setname::String)

Returns all vertices as VertexIndex in the set with name setname.

source
Ferrite.transform_coordinates!Function
transform_coordinates!(grid::Abstractgrid, f::Function)

Transform the coordinates of all nodes of the grid based on some transformation function f(x).

source
Ferrite.getcoordinatesFunction
getcoordinates(grid::AbstractGrid, idx::Union{Int,CellIndex})
+getcoordinates(cache::CellCache)

Get a vector with the coordinates of the cell corresponding to idx or cache

source
Ferrite.getcoordinates!Function
getcoordinates!(x::Vector{<:Vec}, grid::AbstractGrid, idx::Union{Int,CellIndex})
+getcoordinates!(x::Vector{<:Vec}, grid::AbstractGrid, cell::AbstractCell)

Mutate x to the coordinates of the cell corresponding to idx or cell.

source
Ferrite.geometric_interpolationMethod
geometric_interpolation(::AbstractCell)::ScalarInterpolation
+geometric_interpolation(::Type{<:AbstractCell})::ScalarInterpolation

Each AbstractCell type has a unique geometric interpolation describing its geometry. This function returns that interpolation, which is always a scalar interpolation.

source
Ferrite.get_node_coordinateFunction
get_node_coordinate(::Node)

Get the value of the node coordinate.

source
get_node_coordinate(grid::AbstractGrid, n::Int)

Return the coordinate of the nth node in grid

source
Ferrite.getspatialdimMethod
Ferrite.getspatialdim(grid::AbstractGrid)

Get the spatial dimension of the grid, corresponding to the vector dimension of the grid's coordinates.

source
Ferrite.getrefdimMethod
Ferrite.getrefdim(cell::AbstractCell)
+Ferrite.getrefdim(::Type{<:AbstractCell})

Get the reference dimension of the cell, i.e. the dimension of the cell's reference shape.

source

Topology

Ferrite.ExclusiveTopologyType
ExclusiveTopology(grid::AbstractGrid)

The experimental feature ExclusiveTopology saves topological (connectivity/neighborhood) data of the grid. Only the highest dimensional neighborhood is saved. I.e., if something is connected by a face and an edge, only the face neighborhood is saved. The lower dimensional neighborhood is recomputed when calling getneighborhood if needed.

Fields

  • vertex_to_cell::AbstractArray{AbstractVector{Int}, 1}: global vertex id to all cells containing the vertex
  • cell_neighbor::AbstractArray{AbstractVector{Int}, 1}: cellid to all connected cells
  • face_neighbor::AbstractArray{AbstractVector{FaceIndex}, 2}: face_neighbor[cellid, local_face_id] -> neighboring faces
  • edge_neighbor::AbstractArray{AbstractVector{EdgeIndex}, 2}: edge_neighbor[cellid, local_edge_id] -> neighboring edges
  • vertex_neighbor::AbstractArray{AbstractVector{VertexIndex}, 2}: vertex_neighbor[cellid, local_vertex_id] -> neighboring vertices
  • face_skeleton::Union{Vector{FaceIndex}, Nothing}: List of unique faces in the grid given as FaceIndex
  • edge_skeleton::Union{Vector{EdgeIndex}, Nothing}: List of unique edges in the grid given as EdgeIndex
  • vertex_skeleton::Union{Vector{VertexIndex}, Nothing}: List of unique vertices in the grid given as VertexIndex
Limitations

The implementation only works with conforming grids, i.e. grids without "hanging nodes". Non-conforming grids will give unexpected results. Grids with embedded cells (different reference dimension compared to the spatial dimension) are not supported, and will error on construction.

source
Ferrite.getneighborhoodFunction
getneighborhood(topology, grid::AbstractGrid, cellidx::CellIndex, include_self=false)
+getneighborhood(topology, grid::AbstractGrid, faceidx::FaceIndex, include_self=false)
+getneighborhood(topology, grid::AbstractGrid, vertexidx::VertexIndex, include_self=false)
+getneighborhood(topology, grid::AbstractGrid, edgeidx::EdgeIndex, include_self=false)

Returns all connected entities of the same type as defined by the respective topology. If include_self is true, the given entity is included in the returned list as well.

source
Ferrite.facetskeletonFunction
facetskeleton(top::ExclusiveTopology, grid::AbstractGrid)

Materializes the skeleton from the neighborhood information by returning an iterable over the unique facets in the grid, described by FacetIndex.

source
Ferrite.vertex_star_stencilsFunction
vertex_star_stencils(top::ExclusiveTopology, grid::Grid) -> AbstractVector{AbstractVector{VertexIndex}}

Computes the stencils induced by the edge connectivity of the vertices.

source
Ferrite.getstencilFunction
getstencil(top::ArrayOfVectorViews{VertexIndex, 1}, grid::AbstractGrid, vertex_idx::VertexIndex) -> AbstractVector{VertexIndex}

Get an iterateable over the stencil members for a given local entity.

source

Grid Sets Utility

Ferrite.addcellset!Function
addcellset!(grid::AbstractGrid, name::String, cellid::AbstractVecOrSet{Int})
+addcellset!(grid::AbstractGrid, name::String, f::function; all::Bool=true)

Adds a cellset to the grid with key name. Cellsets are typically used to define subdomains of the problem, e.g. two materials in the computational domain. The DofHandler can construct different fields which live not on the whole domain, but rather on a cellset. all=true implies that f(x) must return true for all nodal coordinates x in the cell if the cell should be added to the set, otherwise it suffices that f(x) returns true for one node.

addcellset!(grid, "left", Set((1,3))) #add cells with id 1 and 3 to cellset left
+addcellset!(grid, "right", x -> norm(x[1]) < 2.0 ) #add cell to cellset right, if x[1] of each cell's node is smaller than 2.0
source
Ferrite.addfacetset!Function
addfacetset!(grid::AbstractGrid, name::String, faceid::AbstractVecOrSet{FacetIndex})
+addfacetset!(grid::AbstractGrid, name::String, f::Function; all::Bool=true)

Adds a facetset to the grid with key name. A facetset maps a String key to a OrderedSet of tuples corresponding to (global_cell_id, local_facet_id). Facetsets can be used to initialize Dirichlet boundary conditions for the ConstraintHandler. all=true implies that f(x) must return true for all nodal coordinates x on the facet if the facet should be added to the set, otherwise it suffices that f(x) returns true for one node.

addfacetset!(grid, "right", Set((FacetIndex(2,2), FacetIndex(4,2)))) #see grid manual example for reference
+addfacetset!(grid, "clamped", x -> norm(x[1]) ≈ 0.0) #see incompressible elasticity example for reference
source
Ferrite.addboundaryfacetset!Function

addboundaryfacetset!(grid::AbstractGrid, topology::ExclusiveTopology, name::String, f::Function; all::Bool=true)

Adds a boundary facetset to the grid with key name. A facetset maps a String key to a OrderedSet of tuples corresponding to (global_cell_id, local_facet_id). Facetsets are used to initialize Dirichlet structs, that are needed to specify the boundary for the ConstraintHandler. all=true implies that f(x) must return true for all nodal coordinates x on the facet if the facet should be added to the set, otherwise it suffices that f(x) returns true for one node.

source
Ferrite.addvertexset!Function
addvertexset!(grid::AbstractGrid, name::String, faceid::AbstractVecOrSet{FaceIndex})
+addvertexset!(grid::AbstractGrid, name::String, f::Function)

Adds a vertexset to the grid with key name. A vertexset maps a String key to a OrderedSet of tuples corresponding to (global_cell_id, local_vertex_id). Vertexsets can be used to initialize Dirichlet boundary conditions for the ConstraintHandler.

addvertexset!(grid, "right", Set((VertexIndex(2,2), VertexIndex(4,2))))
+addvertexset!(grid, "clamped", x -> norm(x[1]) ≈ 0.0)
source
Ferrite.addboundaryvertexset!Function

addboundaryvertexset!(grid::AbstractGrid, topology::ExclusiveTopology, name::String, f::Function; all::Bool=true)

Adds a boundary vertexset to the grid with key name. A vertexset maps a String key to an OrderedSet of tuples corresponding to (global_cell_id, local_vertex_id). all=true implies that f(x) must return true for all nodal coordinates x on the face if the face should be added to the set, otherwise it suffices that f(x) returns true for one node.

source
Ferrite.addnodeset!Function
addnodeset!(grid::AbstractGrid, name::String, nodeid::AbstractVecOrSet{Int})
+addnodeset!(grid::AbstractGrid, name::String, f::Function)

Adds a nodeset::OrderedSet{Int} to the grid's nodesets with key name. Has the same interface as addcellset. However, instead of mapping a cell id to the String key, a set of node ids is returned.

source

Multithreaded Assembly

Ferrite.create_coloringFunction
create_coloring(g::Grid, cellset=1:getncells(g); alg::ColoringAlgorithm)

Create a coloring of the cells in grid g such that no neighboring cells have the same color. If only a subset of cells should be colored, the cells to color can be specified by cellset.

Returns a vector of vectors with cell indexes, e.g.:

ret = [
+   [1, 3, 5, 10, ...], # cells for color 1
+   [2, 4, 6, 12, ...], # cells for color 2
+]

Two different algorithms are available, specified with the alg keyword argument:

  • alg = ColoringAlgorithm.WorkStream (default): Three step algorithm from Turcksin et al. [13], albeit with a greedy coloring in the second step. Generally results in more colors than ColoringAlgorithm.Greedy, however the cells are more equally distributed among the colors.
  • alg = ColoringAlgorithm.Greedy: greedy algorithm that works well for structured quadrilateral grids such as e.g. quadrilateral grids from generate_grid.

The resulting colors can be visualized using Ferrite.write_cell_colors.

Cell to color mapping

In a previous version of Ferrite this function returned a dictionary mapping cell ID to color numbers as the first argument. If you need this mapping you can create it using the following construct:

colors = create_coloring(...)
+cell_colormap = Dict{Int,Int}(
+    cellid => color for (color, cellids) in enumerate(final_colors) for cellid in cellids
+)

References

  • [13] Turcksin et al. ACM Trans. Math. Softw. 43 (2016).
source
diff --git a/previews/PR798/reference/index.html b/previews/PR798/reference/index.html new file mode 100644 index 0000000000..f10b9b9494 --- /dev/null +++ b/previews/PR798/reference/index.html @@ -0,0 +1,2 @@ + +Reference overview · Ferrite.jl
diff --git a/previews/PR798/reference/interpolations/index.html b/previews/PR798/reference/interpolations/index.html new file mode 100644 index 0000000000..50f08815c1 --- /dev/null +++ b/previews/PR798/reference/interpolations/index.html @@ -0,0 +1,6 @@ + +Interpolation · Ferrite.jl

Interpolation

Ferrite.InterpolationType
Interpolation{ref_shape, order}()

Abstract type for interpolations defined on ref_shape (see AbstractRefShape). order corresponds to the order of the interpolation. The interpolation is used to define shape functions to interpolate a function between nodes.

The following interpolations are implemented:

  • Lagrange{RefLine,1}
  • Lagrange{RefLine,2}
  • Lagrange{RefQuadrilateral,1}
  • Lagrange{RefQuadrilateral,2}
  • Lagrange{RefQuadrilateral,3}
  • Lagrange{RefTriangle,1}
  • Lagrange{RefTriangle,2}
  • Lagrange{RefTriangle,3}
  • Lagrange{RefTriangle,4}
  • Lagrange{RefTriangle,5}
  • BubbleEnrichedLagrange{RefTriangle,1}
  • CrouzeixRaviart{RefTriangle, 1}
  • CrouzeixRaviart{RefTetrahedron, 1}
  • RannacherTurek{RefQuadrilateral, 1}
  • RannacherTurek{RefHexahedron, 1}
  • Lagrange{RefHexahedron,1}
  • Lagrange{RefHexahedron,2}
  • Lagrange{RefTetrahedron,1}
  • Lagrange{RefTetrahedron,2}
  • Lagrange{RefPrism,1}
  • Lagrange{RefPrism,2}
  • Lagrange{RefPyramid,1}
  • Lagrange{RefPyramid,2}
  • Serendipity{RefQuadrilateral,2}
  • Serendipity{RefHexahedron,2}

Examples

julia> ip = Lagrange{RefTriangle, 2}()
+Lagrange{RefTriangle, 2}()
+
+julia> getnbasefunctions(ip)
+6
source
Ferrite.getrefdimMethod
Ferrite.getrefdim(::Interpolation)

Return the dimension of the reference element for a given interpolation.

source
Ferrite.getrefshapeFunction
Ferrite.getrefshape(::Interpolation)::AbstractRefShape

Return the reference element shape of the interpolation.

source

Implemented interpolations:

Ferrite.LagrangeType
Lagrange{refshape, order} <: ScalarInterpolation

Standard continuous Lagrange polynomials with equidistant node placement.

source
Ferrite.SerendipityType
Serendipity{refshape, order} <: ScalarInterpolation

Serendipity element on hypercubes. Currently only second order variants are implemented.

source
Ferrite.CrouzeixRaviartType
CrouzeixRaviart{refshape, order} <: ScalarInterpolation

Classical non-conforming Crouzeix–Raviart element.

For details we refer to the original paper [11].

source
Ferrite.RannacherTurekType
RannacherTurek{refshape, order} <: ScalarInterpolation

Classical non-conforming Rannacher-Turek element.

This element is basically the idea from Crouzeix and Raviart applied to hypercubes. For details see the original paper [12].

source
diff --git a/previews/PR798/reference/quadrature/index.html b/previews/PR798/reference/quadrature/index.html new file mode 100644 index 0000000000..349bc417b2 --- /dev/null +++ b/previews/PR798/reference/quadrature/index.html @@ -0,0 +1,24 @@ + +Quadrature · Ferrite.jl

Quadrature

Ferrite.QuadratureRuleType
QuadratureRule{shape}([::Type{T},] [quad_rule_type::Symbol,] order::Int)
+QuadratureRule{shape}(weights::AbstractVector{T}, points::AbstractVector{Vec{rdim, T}})

Create a QuadratureRule used for integration on the refshape shape (of type AbstractRefShape). order is the order of the quadrature rule. quad_rule_type is an optional argument determining the type of quadrature rule, currently the :legendre and :lobatto rules are implemented for hypercubes. For triangles up to order 8 the default rule is the one by :dunavant (see [8]) and for tetrahedra the default rule is keast_minimal (see [9]). Wedges and pyramids default to :polyquad (see [10]). Furthermore we have implemented

  • :gaussjacobi for triangles (order 9-15)
  • :keast_minimal (see [9]) for tetrahedra (order 1-5), containing negative weights
  • :keast_positive (see [9]) for tetrahedra (order 1-5), containing only positive weights

A QuadratureRule is used to approximate an integral on a domain by a weighted sum of function values at specific points:

$\int\limits_\Omega f(\mathbf{x}) \text{d} \Omega \approx \sum\limits_{q = 1}^{n_q} f(\mathbf{x}_q) w_q$

The quadrature rule consists of $n_q$ points in space $\mathbf{x}_q$ with corresponding weights $w_q$.

In Ferrite, the QuadratureRule type is mostly used as one of the components to create CellValues.

Common methods:

Example:

julia> qr = QuadratureRule{RefTriangle}(1)
+QuadratureRule{RefTriangle, Float64, 2}([0.5], Vec{2, Float64}[[0.33333333333333, 0.33333333333333]])
+
+julia> getpoints(qr)
+1-element Vector{Vec{2, Float64}}:
+ [0.33333333333333, 0.33333333333333]
source
Ferrite.FacetQuadratureRuleType
FacetQuadratureRule{shape}([::Type{T},] [quad_rule_type::Symbol,] order::Int)
+FacetQuadratureRule{shape}(face_rules::NTuple{<:Any, <:QuadratureRule{shape}})
+FacetQuadratureRule{shape}(face_rules::AbstractVector{<:QuadratureRule{shape}})

Create a FacetQuadratureRule used for integration of the faces of the refshape shape (of type AbstractRefShape). order is the order of the quadrature rule. If no symbol is provided, the default quad_rule_type for each facet's reference shape is used (see QuadratureRule). For non-default quad_rule_types on cells with mixed facet types (e.g. RefPrism and RefPyramid), the face_rules must be provided explicitly.

FacetQuadratureRule is used as one of the components to create FacetValues.

source
Ferrite.getnquadpointsMethod
getnquadpoints(qr::FacetQuadratureRule, face::Int)

Return the number of quadrature points in qr for local face index face.

source
Ferrite.getpointsFunction
getpoints(qr::QuadratureRule)
+getpoints(qr::FacetQuadratureRule, face::Int)

Return the points of the quadrature rule.

Examples

julia> qr = QuadratureRule{RefTriangle}(:legendre, 2);
+
+julia> getpoints(qr)
+3-element Vector{Vec{2, Float64}}:
+ [0.16666666666667, 0.16666666666667]
+ [0.16666666666667, 0.66666666666667]
+ [0.66666666666667, 0.16666666666667]
source
Ferrite.getweightsFunction
getweights(qr::QuadratureRule)
+getweights(qr::FacetQuadratureRule, face::Int)

Return the weights of the quadrature rule.

Examples

julia> qr = QuadratureRule{RefTriangle}(:legendre, 2);
+
+julia> getweights(qr)
+3-element Array{Float64,1}:
+ 0.166667
+ 0.166667
+ 0.166667
source
diff --git a/previews/PR798/reference/sparsity_pattern/index.html b/previews/PR798/reference/sparsity_pattern/index.html new file mode 100644 index 0000000000..4386b336f0 --- /dev/null +++ b/previews/PR798/reference/sparsity_pattern/index.html @@ -0,0 +1,43 @@ + +Sparsity pattern and sparse matrices · Ferrite.jl

Sparsity pattern and sparse matrices

This is the reference documentation for sparsity patterns and sparse matrix instantiation. See the topic section on Sparsity pattern and sparse matrices.

Sparsity patterns

AbstractSparsityPattern

The following applies to all subtypes of AbstractSparsityPattern:

Ferrite.init_sparsity_patternFunction
init_sparsity_pattern(dh::DofHandler; nnz_per_row::Int)

Initialize an empty SparsityPattern with ndofs(dh) rows and ndofs(dh) columns.

Keyword arguments

  • nnz_per_row: memory optimization hint for the number of non-zero entries per row that will be added to the pattern.
source
Ferrite.add_sparsity_entries!Function
add_sparsity_entries!(
+    sp::AbstractSparsityPattern,
+    dh::DofHandler,
+    ch::Union{ConstraintHandler, Nothing} = nothing;
+    topology = nothing,
+    keep_constrained::Bool = true,
+    coupling = nothing,
+    interface_coupling = nothing,
+)

Convenience method for doing the common task of calling add_cell_entries!, add_interface_entries!, and add_constraint_entries!, depending on what arguments are passed:

  • add_cell_entries! is always called
  • add_interface_entries! is called if topology is provided (i.e. not nothing)
  • add_constraint_entries! is called if the ConstraintHandler is provided

For more details about arguments and keyword arguments, see the respective functions.

source
Ferrite.add_cell_entries!Function
add_cell_entries!(
+    sp::AbstractSparsityPattern,
+    dh::DofHandler,
+    ch::Union{ConstraintHandler, Nothing} = nothing;
+    keep_constrained::Bool = true,
+    coupling::Union{AbstractMatrix{Bool}, Nothing}, = nothing
+)

Add entries to the sparsity pattern sp corresponding to DoF couplings within the cells as described by the DofHandler dh.

Keyword arguments

  • keep_constrained: whether or not entries for constrained DoFs should be kept (keep_constrained = true) or eliminated (keep_constrained = false) from the sparsity pattern. keep_constrained = false requires passing the ConstraintHandler ch.
  • coupling: the coupling between fields/components within each cell. By default (coupling = nothing) it is assumed that all DoFs in each cell couple with each other.
source
Ferrite.add_interface_entries!Function
add_interface_entries!(
+    sp::SparsityPattern, dh::DofHandler, ch::Union{ConstraintHandler, Nothing};
+    topology::ExclusiveTopology, keep_constrained::Bool = true,
+    interface_coupling::AbstractMatrix{Bool},
+)

Add entries to the sparsity pattern sp corresponding to DoF couplings on the interface between cells as described by the DofHandler dh.

Keyword arguments

  • topology: the topology corresponding to the grid.
  • keep_constrained: whether or not entries for constrained DoFs should be kept (keep_constrained = true) or eliminated (keep_constrained = false) from the sparsity pattern. keep_constrained = false requires passing the ConstraintHandler ch.
  • interface_coupling: the coupling between fields/components across the interface.
source
Ferrite.add_constraint_entries!Function
add_constraint_entries!(
+    sp::AbstractSparsityPattern, ch::ConstraintHandler;
+    keep_constrained::Bool = true,
+)

Add all entries resulting from constraints in the ConstraintHandler ch to the sparsity pattern. Note that, since this operation depends on existing entries in the pattern, this function must be called as the last step when creating the sparsity pattern.

Keyword arguments

  • keep_constrained: whether or not entries for constrained DoFs should be kept (keep_constrained = true) or eliminated (keep_constrained = false) from the sparsity pattern.
source
Ferrite.add_entry!Function
add_entry!(sp::AbstractSparsityPattern, row::Int, col::Int)

Add an entry to the sparsity pattern sp at row row and column col.

source

SparsityPattern

Ferrite.SparsityPatternMethod
SparsityPattern(nrows::Int, ncols::Int; nnz_per_row::Int = 8)

Create an empty SparsityPattern with nrows rows and ncols columns. nnz_per_row is used as a memory hint for the number of non zero entries per row.

SparsityPattern is the default sparsity pattern type for the standard DofHandler and is therefore commonly constructed using init_sparsity_pattern instead of with this constructor.

Examples

# Create a sparsity pattern for an 100 x 100 matrix, hinting at 10 entries per row
+sparsity_pattern = SparsityPattern(100, 100; nnz_per_row = 10)

Methods

The following methods apply to SparsityPattern (see their respective documentation for more details):

source
Ferrite.SparsityPatternType
struct SparsityPattern <: AbstractSparsityPattern

Data structure representing non-zero entries in the eventual sparse matrix.

See the constructor SparsityPattern(::Int, ::Int) for the user-facing documentation.

Struct fields

  • nrows::Int: number of rows
  • ncols::Int: number of column
  • rows::Vector{Vector{Int}}: vector of length nrows, where rows[i] is a sorted vector of column indices for non zero entries in row i.
Internal struct

The specific implementation of this struct, such as struct fields, type layout and type parameters, are internal and should not be relied upon.

source

BlockSparsityPattern

Package extension

This functionality is only enabled when the package BlockArrays.jl is installed (pkg> add BlockArrays) and loaded (using BlockArrays) in the session.

Ferrite.BlockSparsityPatternMethod
BlockSparsityPattern(block_sizes::Vector{Int})

Create an empty BlockSparsityPattern with row and column block sizes given by block_sizes.

Examples

# Create a block sparsity pattern with block size 10 x 5
+sparsity_pattern = BlockSparsityPattern([10, 5])

Methods

The following methods apply to BlockSparsityPattern (see their respective documentation for more details):

Package extension

This functionality is only enabled when the package BlockArrays.jl is installed (pkg> add BlockArrays) and loaded (using BlockArrays) in the session.

source
Ferrite.BlockSparsityPatternType
struct BlockSparsityPattern <: AbstractSparsityPattern

Data structure representing non-zero entries for an eventual blocked sparse matrix.

See the constructor BlockSparsityPattern(::Vector{Int}) for the user-facing documentation.

Struct fields

  • nrows::Int: number of rows
  • ncols::Int: number of column
  • block_sizes::Vector{Int}: row and column block sizes
  • blocks::Matrix{SparsityPattern}: matrix of size length(block_sizes) × length(block_sizes) where blocks[i, j] is a SparsityPattern corresponding to block (i, j).
Internal struct

The specific implementation of this struct, such as struct fields, type layout and type parameters, are internal and should not be relied upon.

source
Ferrite.allocate_matrixMethod
allocate_matrix(::Type{SparseMatrixCSC{Tv, Ti}}, sp::SparsityPattern)

Allocate a sparse matrix of type SparseMatrixCSC{Tv, Ti} from the sparsity pattern sp.

source
allocate_matrix(::Type{Symmetric{Tv, SparseMatrixCSC{Tv, Ti}}}, sp::SparsityPattern)

Instantiate a sparse matrix of type Symmetric{Tv, SparseMatrixCSC{Tv, Ti}}, i.e. a LinearAlgebra.Symmetric-wrapped SparseMatrixCSC, from the sparsity pattern sp. The resulting matrix will only store entries above, and including, the diagonal.

source
allocate_matrix(MatrixType, dh::DofHandler, args...; kwargs...)

Allocate a matrix of type MatrixType from the DofHandler dh.

This is a convenience method and is equivalent to:

julia sp = init_sparsity_pattern(dh) add_sparsity_entries!(sp, dh, args...; kwargs...) allocate_matrix(MatrixType, sp)`

Refer to allocate_matrix for supported matrix types, and to init_sparsity_pattern for details about supported arguments args and keyword arguments kwargs.

Note

If more than one sparse matrix is needed (e.g. a stiffness and a mass matrix) it is more efficient to explicitly create the sparsity pattern instead of using this method, i.e. use

sp = init_sparsity_pattern(dh)
+add_sparsity_entries!(sp, dh)
+K = allocate_matrix(sp)
+M = allocate_matrix(sp)

instead of

K = allocate_matrix(dh)
+M = allocate_matrix(dh)

Note that for some matrix types it is possible to copy the instantiated matrix (M = copy(K)) instead.

source
allocate_matrix(::Type{BlockMatrix}, sp::BlockSparsityPattern)
+allocate_matrix(::Type{BlockMatrix{T, Matrix{S}}}, sp::BlockSparsityPattern)

Instantiate a blocked sparse matrix from the blocked sparsity pattern sp.

The type of the returned matrix is a BlockMatrix with blocks of type S (defaults to SparseMatrixCSC{T, Int}).

Examples

# Create a sparse matrix with default block type
+allocate_matrix(BlockMatrix, sparsity_pattern)
+
+# Create a sparse matrix with blocks of type SparseMatrixCSC{Float32, Int}
+allocate_matrix(BlockMatrix{Float32, Matrix{SparseMatrixCSC{Float32, Int}}}, sparsity_pattern)
Package extension

This functionality is only enabled when the package BlockArrays.jl is installed (pkg> add BlockArrays) and loaded (using BlockArrays) in the session.

source
Ferrite.allocate_matrixMethod
allocate_matrix(::Type{BlockMatrix}, sp::BlockSparsityPattern)
+allocate_matrix(::Type{BlockMatrix{T, Matrix{S}}}, sp::BlockSparsityPattern)

Instantiate a blocked sparse matrix from the blocked sparsity pattern sp.

The type of the returned matrix is a BlockMatrix with blocks of type S (defaults to SparseMatrixCSC{T, Int}).

Examples

# Create a sparse matrix with default block type
+allocate_matrix(BlockMatrix, sparsity_pattern)
+
+# Create a sparse matrix with blocks of type SparseMatrixCSC{Float32, Int}
+allocate_matrix(BlockMatrix{Float32, Matrix{SparseMatrixCSC{Float32, Int}}}, sparsity_pattern)
Package extension

This functionality is only enabled when the package BlockArrays.jl is installed (pkg> add BlockArrays) and loaded (using BlockArrays) in the session.

source

Sparse matrices

Creating matrix from SparsityPattern

Ferrite.allocate_matrixMethod
allocate_matrix(::Type{SparseMatrixCSC{Tv, Ti}}, sp::SparsityPattern)

Allocate a sparse matrix of type SparseMatrixCSC{Tv, Ti} from the sparsity pattern sp.

source
Ferrite.allocate_matrixMethod
allocate_matrix(::Type{Symmetric{Tv, SparseMatrixCSC{Tv, Ti}}}, sp::SparsityPattern)

Instantiate a sparse matrix of type Symmetric{Tv, SparseMatrixCSC{Tv, Ti}}, i.e. a LinearAlgebra.Symmetric-wrapped SparseMatrixCSC, from the sparsity pattern sp. The resulting matrix will only store entries above, and including, the diagonal.

source

Creating matrix from DofHandler

Ferrite.allocate_matrixMethod
allocate_matrix(MatrixType, dh::DofHandler, args...; kwargs...)

Allocate a matrix of type MatrixType from the DofHandler dh.

This is a convenience method and is equivalent to:

julia sp = init_sparsity_pattern(dh) add_sparsity_entries!(sp, dh, args...; kwargs...) allocate_matrix(MatrixType, sp)`

Refer to allocate_matrix for supported matrix types, and to init_sparsity_pattern for details about supported arguments args and keyword arguments kwargs.

Note

If more than one sparse matrix is needed (e.g. a stiffness and a mass matrix) it is more efficient to explicitly create the sparsity pattern instead of using this method, i.e. use

sp = init_sparsity_pattern(dh)
+add_sparsity_entries!(sp, dh)
+K = allocate_matrix(sp)
+M = allocate_matrix(sp)

instead of

K = allocate_matrix(dh)
+M = allocate_matrix(dh)

Note that for some matrix types it is possible to copy the instantiated matrix (M = copy(K)) instead.

source
diff --git a/previews/PR798/reference/utils/index.html b/previews/PR798/reference/utils/index.html new file mode 100644 index 0000000000..5e06d078f4 --- /dev/null +++ b/previews/PR798/reference/utils/index.html @@ -0,0 +1,2 @@ + +Development utility functions · Ferrite.jl

Development utility functions

Ferrite.debug_modeFunction
Ferrite.debug_mode(; enable=true)

Helper to turn on (enable=true) or off (enable=false) debug expressions in Ferrite.

Debug mode influences Ferrite.@debug expr: when debug mode is enabled, expr is evaluated, and when debug mode is disabled expr is ignored.

source
diff --git a/previews/PR798/search_index.js b/previews/PR798/search_index.js new file mode 100644 index 0000000000..d5fdb61629 --- /dev/null +++ b/previews/PR798/search_index.js @@ -0,0 +1,3 @@ +var documenterSearchIndex = {"docs": +[{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"EditURL = \"../literate-tutorials/linear_shell.jl\"","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"runic: off","category":"page"},{"location":"tutorials/linear_shell/#tutorial-linear-shell","page":"Linear shell","title":"Linear shell","text":"","category":"section"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"(Image: )","category":"page"},{"location":"tutorials/linear_shell/#Introduction","page":"Linear shell","title":"Introduction","text":"","category":"section"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"In this example we show how shell elements can be analyzed with Ferrite. The shell implemented here comes from the book \"The finite element method - Linear static and dynamic finite element analysis\" by Hughes (1987), and a brief description of it is given at the end of this tutorial. The first part of the tutorial explains how to set up the problem.","category":"page"},{"location":"tutorials/linear_shell/#Setting-up-the-problem","page":"Linear shell","title":"Setting up the problem","text":"","category":"section"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"using Ferrite\nusing ForwardDiff\n\nfunction main() #wrap everything in a function...","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"First we generate a flat rectangular mesh. There is currently no built-in function for generating shell meshes in Ferrite, so we have to create our own simple mesh generator (see the function generate_shell_grid further down in this file).","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"nels = (10,10)\nsize = (10.0, 10.0)\ngrid = generate_shell_grid(nels, size)","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"Here we define the bi-linear interpolation used for the geometrical description of the shell. We also create two quadrature rules for the in-plane and out-of-plane directions. Note that we use under integration for the inplane integration, to avoid shear locking.","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"ip = Lagrange{RefQuadrilateral,1}()\nqr_inplane = QuadratureRule{RefQuadrilateral}(1)\nqr_ooplane = QuadratureRule{RefLine}(2)\ncv = CellValues(qr_inplane, ip, ip^3)","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"Next we distribute displacement dofs,:u = (x,y,z) and rotational dofs, :θ = (θ₁, θ₂).","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"dh = DofHandler(grid)\nadd!(dh, :u, ip^3)\nadd!(dh, :θ, ip^2)\nclose!(dh)","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"In order to apply our boundary conditions, we first need to create some facet- and vertex-sets. This is done with addfacetset! and addvertexset!","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"addfacetset!(grid, \"left\", (x) -> x[1] ≈ 0.0)\naddfacetset!(grid, \"right\", (x) -> x[1] ≈ size[1])\naddvertexset!(grid, \"corner\", (x) -> x[1] ≈ 0.0 && x[2] ≈ 0.0 && x[3] ≈ 0.0)","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"Here we define the boundary conditions. On the left edge, we lock the displacements in the x- and z- directions, and all the rotations.","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"ch = ConstraintHandler(dh)\nadd!(ch, Dirichlet(:u, getfacetset(grid, \"left\"), (x, t) -> (0.0, 0.0), [1,3]) )\nadd!(ch, Dirichlet(:θ, getfacetset(grid, \"left\"), (x, t) -> (0.0, 0.0), [1,2]) )","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"On the right edge, we also lock the displacements in the x- and z- directions, but apply a precribed rotation.","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"add!(ch, Dirichlet(:u, getfacetset(grid, \"right\"), (x, t) -> (0.0, 0.0), [1,3]) )\nadd!(ch, Dirichlet(:θ, getfacetset(grid, \"right\"), (x, t) -> (0.0, pi/10), [1,2]) )","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"In order to not get rigid body motion, we lock the y-displacement in one of the corners.","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"add!(ch, Dirichlet(:θ, getvertexset(grid, \"corner\"), (x, t) -> (0.0), [2]) )\n\nclose!(ch)\nupdate!(ch, 0.0)","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"Next we define relevant data for the shell, such as shear correction factor and stiffness matrix for the material. In this linear shell, plane stress is assumed, ie sigma_zz = 0. Therefor, the stiffness matrix is 5x5 (opposed to the normal 6x6).","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"κ = 5/6 # Shear correction factor\nE = 210.0\nν = 0.3\na = (1-ν)/2\nC = E/(1-ν^2) * [1 ν 0 0 0;\n ν 1 0 0 0;\n 0 0 a*κ 0 0;\n 0 0 0 a*κ 0;\n 0 0 0 0 a*κ]\n\n\ndata = (thickness = 1.0, C = C); #Named tuple\nnothing #hide","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"We now assemble the problem in standard finite element fashion","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"nnodes = getnbasefunctions(ip)\nndofs_shell = ndofs_per_cell(dh)\n\nK = allocate_matrix(dh)\nf = zeros(Float64, ndofs(dh))\n\nke = zeros(ndofs_shell, ndofs_shell)\nfe = zeros(ndofs_shell)\n\ncelldofs = zeros(Int, ndofs_shell)\ncellcoords = zeros(Vec{3,Float64}, nnodes)\n\nassembler = start_assemble(K, f)\nfor cell in CellIterator(grid)\n fill!(ke, 0.0)\n reinit!(cv, cell)\n celldofs!(celldofs, dh, cellid(cell))\n getcoordinates!(cellcoords, grid, cellid(cell))\n\n #Call the element routine\n integrate_shell!(ke, cv, qr_ooplane, cellcoords, data)\n\n assemble!(assembler, celldofs, ke, fe)\nend","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"Apply BC and solve.","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"apply!(K, f, ch)\na = K\\f","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"Output results.","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"VTKGridFile(\"linear_shell\", dh) do vtk\n write_solution(vtk, dh, a)\nend\n\nend; #end main functions\nnothing #hide","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"Below is the function that creates the shell mesh. It simply generates a 2d-quadrature mesh, and appends a third coordinate (z-direction) to the node-positions.","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"function generate_shell_grid(nels, size)\n _grid = generate_grid(Quadrilateral, nels, Vec((0.0,0.0)), Vec(size))\n nodes = [(n.x[1], n.x[2], 0.0) |> Vec{3} |> Node for n in _grid.nodes]\n\n grid = Grid(_grid.cells, nodes)\n\n return grid\nend;\nnothing #hide","category":"page"},{"location":"tutorials/linear_shell/#The-shell-element","page":"Linear shell","title":"The shell element","text":"","category":"section"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"The shell presented here comes from the book \"The finite element method - Linear static and dynamic finite element analysis\" by Hughes (1987). The shell is a so called degenerate shell element, meaning it is based on a continuum element. A brief description of the shell is given here.","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"note: Note\nThis element might experience various locking phenomenas, and should only be seen as a proof of concept.","category":"page"},{"location":"tutorials/linear_shell/#Fiber-coordinate-system","page":"Linear shell","title":"Fiber coordinate system","text":"","category":"section"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"The element uses two coordinate systems. The first coordianate system, called the fiber system, is created for each element node, and is used as a reference frame for the rotations. The function below implements an algorithm that return the fiber directions, boldsymbole^f_a1, boldsymbole^f_a2 and boldsymbole^f_a3, at each node a.","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"function fiber_coordsys(Ps::Vector{Vec{3,Float64}})\n\n ef1 = Vec{3,Float64}[]\n ef2 = Vec{3,Float64}[]\n ef3 = Vec{3,Float64}[]\n for P in Ps\n a = abs.(P)\n j = 1\n if a[1] > a[3]; a[3] = a[1]; j = 2; end\n if a[2] > a[3]; j = 3; end\n\n e3 = P\n e2 = Tensors.cross(P, basevec(Vec{3}, j))\n e2 /= norm(e2)\n e1 = Tensors.cross(e2, P)\n\n push!(ef1, e1)\n push!(ef2, e2)\n push!(ef3, e3)\n end\n return ef1, ef2, ef3\n\nend;\nnothing #hide","category":"page"},{"location":"tutorials/linear_shell/#Lamina-coordinate-system","page":"Linear shell","title":"Lamina coordinate system","text":"","category":"section"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"The second coordinate system is the so called Lamina Coordinate system. It is created for each integration point, and is defined to be tangent to the mid-surface. It is in this system that we enforce that plane stress assumption, i.e. sigma_zz = 0. The function below returns the rotation matrix, boldsymbolq, for this coordinate system.","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"function lamina_coordsys(dNdξ, ζ, x, p, h)\n\n e1 = zero(Vec{3})\n e2 = zero(Vec{3})\n\n for i in 1:length(dNdξ)\n e1 += dNdξ[i][1] * x[i] + 0.5*h*ζ * dNdξ[i][1] * p[i]\n e2 += dNdξ[i][2] * x[i] + 0.5*h*ζ * dNdξ[i][1] * p[i]\n end\n\n e1 /= norm(e1)\n e2 /= norm(e2)\n\n ez = Tensors.cross(e1,e2)\n ez /= norm(ez)\n\n a = 0.5*(e1 + e2)\n a /= norm(a)\n\n b = Tensors.cross(ez,a)\n b /= norm(b)\n\n ex = sqrt(2)/2 * (a - b)\n ey = sqrt(2)/2 * (a + b)\n\n return Tensor{2,3}(hcat(ex,ey,ez))\nend;\nnothing #hide","category":"page"},{"location":"tutorials/linear_shell/#Geometrical-description","page":"Linear shell","title":"Geometrical description","text":"","category":"section"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"A material point in the shell is defined as","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"boldsymbol x(xi eta zeta) = sum_a=1^N_textnodes N_a(xi eta) boldsymbolbarx_a + ζ frach2 boldsymbolbarp_a","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"where boldsymbolbarx_a are nodal positions on the mid-surface, and boldsymbolbarp_a is an vector that defines the fiber direction on the reference surface. N_a arethe shape functions.","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"Based on the definition of the position vector, we create an function for obtaining the Jacobian-matrix,","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"J_ij = fracpartial x_ipartial xi_j","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"function getjacobian(q, N, dNdξ, ζ, X, p, h)\n J = zeros(3,3)\n for a in 1:length(N)\n for i in 1:3, j in 1:3\n _dNdξ = (j==3) ? 0.0 : dNdξ[a][j]\n _dζdξ = (j==3) ? 1.0 : 0.0\n _N = N[a]\n\n J[i,j] += _dNdξ * X[a][i] + (_dNdξ*ζ + _N*_dζdξ) * h/2 * p[a][i]\n end\n end\n\n return (q' * J) |> Tensor{2,3,Float64}\nend;\nnothing #hide","category":"page"},{"location":"tutorials/linear_shell/#Strains","page":"Linear shell","title":"Strains","text":"","category":"section"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"Small deformation is assumed,","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"varepsilon_ij= frac12(fracpartial u_ipartial x_j + fracpartial u_jpartial x_i)","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"The displacement field is calculated as:","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"boldsymbol u = sum_a=1^N_textnodes N_a barboldsymbol u_a +\n N_a ζfrach2(theta_a2 boldsymbol e^f_a1 - theta_a1 boldsymbol e^f_a2)\n","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"The gradient of the displacement (in the lamina coordinate system), then becomes:","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"fracpartial u_ipartial x_j = sum_m=1^3 q_im sum_a=1^N_textnodes fracpartial N_apartial x_j baru_am +\n fracpartial(N_a ζ)partial x_j frach2 (theta_a2 e^f_am1 - theta_a1 e^f_am2)","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"function strain(dofvec::Vector{T}, N, dNdx, ζ, dζdx, q, ef1, ef2, h) where T\n\n u = reinterpret(Vec{3,T}, dofvec[1:12])\n θ = reinterpret(Vec{2,T}, dofvec[13:20])\n\n dudx = zeros(T, 3, 3)\n for m in 1:3, j in 1:3\n for a in 1:length(N)\n dudx[m,j] += dNdx[a][j] * u[a][m] + h/2 * (dNdx[a][j]*ζ + N[a]*dζdx[j]) * (θ[a][2]*ef1[a][m] - θ[a][1]*ef2[a][m])\n end\n end\n\n dudx = q*dudx\n ε = [dudx[1,1], dudx[2,2], dudx[1,2]+dudx[2,1], dudx[2,3]+dudx[3,2], dudx[1,3]+dudx[3,1]]\n return ε\nend;\nnothing #hide","category":"page"},{"location":"tutorials/linear_shell/#Main-element-routine","page":"Linear shell","title":"Main element routine","text":"","category":"section"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"Below is the main routine that calculates the stiffness matrix of the shell element. Since it is a so called degenerate shell element, the code is similar to that for an standard continuum element.","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"shape_reference_gradient(cv::CellValues, q_point, i) = cv.fun_values.dNdξ[i, q_point]\n\nfunction integrate_shell!(ke, cv, qr_ooplane, X, data)\n nnodes = getnbasefunctions(cv)\n ndofs = nnodes*5\n h = data.thickness\n\n #Create the directors in each node.\n #Note: For a more general case, the directors should\n #be input parameters for the element routine.\n p = zeros(Vec{3}, nnodes)\n for i in 1:nnodes\n a = Vec{3}((0.0, 0.0, 1.0))\n p[i] = a/norm(a)\n end\n\n ef1, ef2, ef3 = fiber_coordsys(p)\n\n for iqp in 1:getnquadpoints(cv)\n N = [shape_value(cv, iqp, i) for i in 1:nnodes]\n dNdξ = [shape_reference_gradient(cv, iqp, i) for i in 1:nnodes]\n dNdx = [shape_gradient(cv, iqp, i) for i in 1:nnodes]\n\n for oqp in 1:length(qr_ooplane.weights)\n ζ = qr_ooplane.points[oqp][1]\n q = lamina_coordsys(dNdξ, ζ, X, p, h)\n\n J = getjacobian(q, N, dNdξ, ζ, X, p, h)\n Jinv = inv(J)\n dζdx = Vec{3}((0.0, 0.0, 1.0)) ⋅ Jinv\n\n #For simplicity, use automatic differentiation to construct the B-matrix from the strain.\n B = ForwardDiff.jacobian(\n (a) -> strain(a, N, dNdx, ζ, dζdx, q, ef1, ef2, h), zeros(Float64, ndofs) )\n\n dV = qr_ooplane.weights[oqp] * getdetJdV(cv, iqp)\n ke .+= B'*data.C*B * dV\n end\n end\nend;\nnothing #hide","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"Run everything:","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"main()","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"This page was generated using Literate.jl.","category":"page"},{"location":"topics/export/","page":"Export","title":"Export","text":"using Ferrite\ngrid = generate_grid(Triangle, (2, 2))\ndh = DofHandler(grid); add!(dh, :u, Lagrange{RefTriangle,1}()); close!(dh)\nu = rand(ndofs(dh)); σ = rand(getncells(grid))","category":"page"},{"location":"topics/export/#Export","page":"Export","title":"Export","text":"","category":"section"},{"location":"topics/export/","page":"Export","title":"Export","text":"When the problem is solved, and the solution vector u is known we typically want to visualize it. The simplest way to do this is to write the solution to a VTK-file, which can be viewed in e.g. Paraview. To write VTK-files, Ferrite comes with an export interface with a WriteVTK.jl backend to simplify the exporting.","category":"page"},{"location":"topics/export/","page":"Export","title":"Export","text":"The following structure can be used to write various output to a vtk-file:","category":"page"},{"location":"topics/export/","page":"Export","title":"Export","text":"VTKGridFile(\"my_solution\", grid) do vtk\n write_solution(vtk, dh, u)\nend;","category":"page"},{"location":"topics/export/","page":"Export","title":"Export","text":"where write_solution is just one example of the following functions that can be used","category":"page"},{"location":"topics/export/","page":"Export","title":"Export","text":"write_solution\nwrite_cell_data\nwrite_node_data\nwrite_projection\nFerrite.write_cellset\nFerrite.write_nodeset\nFerrite.write_constraints\nFerrite.write_cell_colors","category":"page"},{"location":"topics/export/","page":"Export","title":"Export","text":"Instead of using the do-block, it is also possible to do","category":"page"},{"location":"topics/export/","page":"Export","title":"Export","text":"vtk = VTKGridFile(\"my_solution\", grid)\nwrite_solution(vtk, dh, u)\n# etc.\nclose(vtk);","category":"page"},{"location":"topics/export/","page":"Export","title":"Export","text":"The data written by write_solution, write_cell_data, write_node_data, and write_projection may be either scalar (Vector{<:Number}) or tensor (Vector{<:AbstractTensor}) data.","category":"page"},{"location":"topics/export/","page":"Export","title":"Export","text":"For simulations with multiple time steps, typically one VTK (.vtu) file is written for each time step. In order to connect the actual time with each of these files, the paraview_collection can function from WriteVTK.jl can be used. This will create one paraview datafile (.pvd) file and one VTKGridFile (.vtu) for each time step.","category":"page"},{"location":"topics/export/","page":"Export","title":"Export","text":"using WriteVTK\npvd = paraview_collection(\"my_results\")\nfor (step, t) in enumerate(range(0, 1, 5))\n # Do calculations to update u\n VTKGridFile(\"my_results_$step\", dh) do vtk\n write_solution(vtk, dh, u)\n pvd[t] = vtk\n end\nend\nvtk_save(pvd);","category":"page"},{"location":"topics/export/","page":"Export","title":"Export","text":"See Transient heat equation for an example","category":"page"},{"location":"reference/export/","page":"Postprocessing","title":"Postprocessing","text":"DocTestSetup = :(using Ferrite)","category":"page"},{"location":"reference/export/#Postprocessing","page":"Postprocessing","title":"Postprocessing","text":"","category":"section"},{"location":"reference/export/#Projection-of-quadrature-point-data","page":"Postprocessing","title":"Projection of quadrature point data","text":"","category":"section"},{"location":"reference/export/","page":"Postprocessing","title":"Postprocessing","text":"L2Projector(::Ferrite.AbstractGrid)\nadd!(::L2Projector, ::Ferrite.AbstractVecOrSet{Int}, ::Interpolation; kwargs...)\nclose!(::L2Projector)\nL2Projector(::Interpolation, ::Ferrite.AbstractGrid; kwargs...)\nproject","category":"page"},{"location":"reference/export/#Ferrite.L2Projector-Tuple{Ferrite.AbstractGrid}","page":"Postprocessing","title":"Ferrite.L2Projector","text":"L2Projector(grid::AbstractGrid)\n\nInitiate an L2Projector for projecting quadrature data onto a function space. To define the function space, add interpolations for different cell sets with add! before close!ing the projector, see the example below.\n\nThe L2Projector acts as the integrated left hand side of the projection equation: Find projection u in U_h(Omega) subset L_2(Omega) such that\n\nint v u mathrmdOmega = int v f mathrmdOmega quad forall v in U_h(Omega)\n\nwhere f in L_2(Omega) is the data to project. The function space U_h(Omega) is the finite element approximation given by the interpolations add!ed to the L2Projector.\n\nExample\n\nproj = L2Projector(grid)\nqr_quad = QuadratureRule{RefQuadrilateral}(2)\nadd!(proj, quad_set, Lagrange{RefQuadrilateral, 1}(); qr_rhs = qr_quad)\nqr_tria = QuadratureRule{RefTriangle}(1)\nadd!(proj, tria_set, Lagrange{RefTriangle, 1}(); qr_rhs = qr_tria)\nclose!(proj)\n\nvals = Dict{Int, Vector{Float64}}() # Can also be Vector{Vector},\n # indexed with cellnr\nfor (set, qr) in ((quad_set, qr_quad), (tria_set, qr_tria))\n nqp = getnquadpoints(qr)\n for cellnr in set\n vals[cellnr] = rand(nqp)\n end\nend\n\nprojected = project(proj, vals)\n\nwhere projected can be used in e.g. evaluate_at_points with the PointEvalHandler, or with evaluate_at_grid_nodes.\n\n\n\n\n\n","category":"method"},{"location":"reference/export/#Ferrite.add!-Tuple{L2Projector, Union{AbstractSet{Int64}, AbstractVector{Int64}}, Interpolation}","page":"Postprocessing","title":"Ferrite.add!","text":"add!(proj::L2Projector, set::AbstractVecOrSet{Int}, ip::Interpolation;\n qr_rhs, [qr_lhs])\n\nAdd an interpolation ip on the cells in set to the L2Projector proj.\n\nqr_rhs sets the quadrature rule used to later integrate the right-hand-side of the projection equation, when calling project. It should match the quadrature points used when creating the quadrature-point variables to project.\nThe optional qr_lhs sets the quadrature rule used to integrate the left-hand-side of the projection equation, and defaults to a quadrature rule that integrates the mass-matrix exactly for the given interpolation ip.\n\n\n\n\n\n","category":"method"},{"location":"reference/export/#Ferrite.close!-Tuple{L2Projector}","page":"Postprocessing","title":"Ferrite.close!","text":"close!(proj::L2Projector)\n\nClose proj which assembles and calculates the left-hand-side of the projection equation, before doing a Cholesky factorization of the mass-matrix.\n\n\n\n\n\n","category":"method"},{"location":"reference/export/#Ferrite.L2Projector-Tuple{Interpolation, Ferrite.AbstractGrid}","page":"Postprocessing","title":"Ferrite.L2Projector","text":"L2Projector(ip::Interpolation, grid::AbstractGrid; [qr_lhs], [set])\n\nA quick way to initiate an L2Projector, add an interpolation ip on the set to it, and then close! it so that it can be used to project. The optional keyword argument set defaults to all cells in the grid, while qr_lhs defaults to a quadrature rule that integrates the mass matrix exactly for the interpolation ip.\n\n\n\n\n\n","category":"method"},{"location":"reference/export/#Ferrite.project","page":"Postprocessing","title":"Ferrite.project","text":"project(proj::L2Projector, vals, [qr_rhs::QuadratureRule])\n\nMakes a L2 projection of data vals to the nodes of the grid using the projector proj (see L2Projector).\n\nproject integrates the right hand side, and solves the projection u from the following projection equation: Find projection u in U_h(Omega) subset L_2(Omega) such that\n\nint v u mathrmdOmega = int v f mathrmdOmega quad forall v in U_h(Omega)\n\nwhere f in L_2(Omega) is the data to project. The function space U_h(Omega) is the finite element approximation given by the interpolations in proj.\n\nThe data vals should be an AbstractVector or AbstractDict that is indexed by the cell number. Each index in vals should give an AbstractVector with one element for each cell quadrature point.\n\nIf proj was created by calling L2Projector(ip, grid, set), qr_rhs must be given. Otherwise, this is added for each domain when calling add!(proj, args...).\n\nAlternatively, vals can be a matrix, with the column index referring the cell number, and the row index corresponding to quadrature point number. Example (scalar) input data:\n\nvals = [\n [0.44, 0.98, 0.32], # data for quadrature point 1, 2, 3 of element 1\n [0.29, 0.48, 0.55], # data for quadrature point 1, 2, 3 of element 2\n # ...\n]\n\nor equivalent in matrix form:\n\nvals = [\n 0.44 0.29 # ...\n 0.98 0.48 # ...\n 0.32 0.55 # ...\n]\n\nSupported data types to project are Numbers and AbstractTensors.\n\nnote: Note\nThe order of the returned data correspond to the order of the L2Projector's internal DofHandler. The data can be further analyzed with evaluate_at_points and evaluate_at_grid_nodes. Use write_projection to export the result.\n\n\n\n\n\n","category":"function"},{"location":"reference/export/#Evaluation-at-points","page":"Postprocessing","title":"Evaluation at points","text":"","category":"section"},{"location":"reference/export/","page":"Postprocessing","title":"Postprocessing","text":"evaluate_at_grid_nodes\nPointEvalHandler\nevaluate_at_points\nPointValues\nPointIterator\nPointLocation","category":"page"},{"location":"reference/export/#Ferrite.evaluate_at_grid_nodes","page":"Postprocessing","title":"Ferrite.evaluate_at_grid_nodes","text":"evaluate_at_grid_nodes(dh::AbstractDofHandler, u::AbstractVector{T}, fieldname::Symbol) where T\n\nEvaluate the approximated solution for field fieldname at the node coordinates of the grid given the Dof handler dh and the solution vector u.\n\nReturn a vector of length getnnodes(grid) where entry i contains the evaluation of the approximation in the coordinate of node i. If the field does not live on parts of the grid, the corresponding values for those nodes will be returned as NaNs.\n\n\n\n\n\n","category":"function"},{"location":"reference/export/#Ferrite.PointEvalHandler","page":"Postprocessing","title":"Ferrite.PointEvalHandler","text":"PointEvalHandler(grid::Grid, points::AbstractVector{Vec{dim,T}}; kwargs...) where {dim, T}\n\nThe PointEvalHandler can be used for function evaluation in arbitrary points in the domain – not just in quadrature points or nodes.\n\nThe constructor takes a grid and a vector of coordinates for the points. The PointEvalHandler computes i) the corresponding cell, and ii) the (local) coordinate within the cell, for each point. The fields of the PointEvalHandler are:\n\ncells::Vector{Union{Int,Nothing}}: vector with cell IDs for the points, with nothing for points that could not be found.\nlocal_coords::Vector{Union{Vec,Nothing}}: vector with the local coordinates (i.e. coordinates in the reference configuration) for the points, with nothing for points that could not be found.\n\nThere are two ways to use the PointEvalHandler to evaluate functions:\n\nevaluate_at_points: can be used when the function is described by i) a dh::DofHandler + uh::Vector (for example the FE-solution), or ii) a p::L2Projector + ph::Vector (for projected data).\nIteration with PointIterator + PointValues: can be used for more flexible evaluation in the points, for example to compute gradients.\n\n\n\n\n\n","category":"type"},{"location":"reference/export/#Ferrite.evaluate_at_points","page":"Postprocessing","title":"Ferrite.evaluate_at_points","text":"evaluate_at_points(ph::PointEvalHandler, dh::AbstractDofHandler, dof_values::Vector{T}, [fieldname::Symbol]) where T\nevaluate_at_points(ph::PointEvalHandler, proj::L2Projector, dof_values::Vector{T}) where T\n\nReturn a Vector{T} (for a 1-dimensional field) or a Vector{Vec{fielddim, T}} (for a vector field) with the field values of field fieldname in the points of the PointEvalHandler. The fieldname can be omitted if only one field is stored in dh. The field values are computed based on the dof_values and interpolated to the local coordinates by the function interpolation of the corresponding field stored in the AbstractDofHandler or the L2Projector.\n\nPoints that could not be found in the domain when constructing the PointEvalHandler will have NaNs for the corresponding entries in the output vector.\n\n\n\n\n\n","category":"function"},{"location":"reference/export/#Ferrite.PointValues","page":"Postprocessing","title":"Ferrite.PointValues","text":"PointValues(cv::CellValues)\nPointValues([::Type{T}], func_interpol::Interpolation, [geom_interpol::Interpolation])\n\nSimilar to CellValues but with a single updateable \"quadrature point\". PointValues are used for evaluation of functions/gradients in arbitrary points of the domain together with a PointEvalHandler.\n\nPointValues can be created from CellValues, or from the interpolations directly.\n\nPointValues are reinitialized like other CellValues, but since the local reference coordinate of the \"quadrature point\" changes this needs to be passed to reinit!, in addition to the element coordinates: reinit!(pv, coords, local_coord). Alternatively, it can be reinitialized with a PointLocation when iterating a PointEvalHandler with a PointIterator.\n\nFor function/gradient evaluation, PointValues are used in the same way as CellValues, i.e. by using function_value, function_gradient, etc, with the exception that there is no need to specify the quadrature point index (since PointValues only have 1, this is the default).\n\n\n\n\n\n","category":"type"},{"location":"reference/export/#Ferrite.PointIterator","page":"Postprocessing","title":"Ferrite.PointIterator","text":"PointIterator(ph::PointEvalHandler)\n\nCreate an iterator over the points in the PointEvalHandler. The elements of the iterator are either a PointLocation, if the corresponding point could be found in the grid, or nothing, if the point was not found.\n\nA PointLocation can be used to query the cell ID with the cellid function, and can be used to reinitialize PointValues with reinit!.\n\nExamples\n\nph = PointEvalHandler(grid, points)\n\nfor point in PointIterator(ph)\n point === nothing && continue # Skip any points that weren't found\n reinit!(pointvalues, point) # Update pointvalues\n # ...\nend\n\n\n\n\n\n","category":"type"},{"location":"reference/export/#Ferrite.PointLocation","page":"Postprocessing","title":"Ferrite.PointLocation","text":"PointLocation\n\nElement of a PointIterator, typically used to reinitialize PointValues. Fields:\n\ncid::Int: ID of the cell containing the point\nlocal_coord::Vec: the local (reference) coordinate of the point\ncoords::Vector{Vec}: the coordinates of the cell\n\n\n\n\n\n","category":"type"},{"location":"reference/export/#VTK-export","page":"Postprocessing","title":"VTK export","text":"","category":"section"},{"location":"reference/export/","page":"Postprocessing","title":"Postprocessing","text":"VTKGridFile\nwrite_solution\nwrite_projection\nwrite_cell_data\nwrite_node_data\nFerrite.write_cellset\nFerrite.write_nodeset\nFerrite.write_constraints\nFerrite.write_cell_colors","category":"page"},{"location":"reference/export/#Ferrite.VTKGridFile","page":"Postprocessing","title":"Ferrite.VTKGridFile","text":"VTKGridFile(filename::AbstractString, grid::AbstractGrid; kwargs...)\nVTKGridFile(filename::AbstractString, dh::DofHandler; kwargs...)\n\nCreate a VTKGridFile that contains an unstructured VTK grid. The keyword arguments are forwarded to WriteVTK.vtk_grid, see Data Formatting Options\n\nThis file handler can be used to to write data with\n\nwrite_solution\nwrite_cell_data\nwrite_projection\nwrite_node_data.\nFerrite.write_cellset\nFerrite.write_nodeset\nFerrite.write_constraints\n\nIt is necessary to call close(::VTKGridFile) to save the data after writing to the file handler. Using the supported do-block does this automatically:\n\nVTKGridFile(filename, grid) do vtk\n write_solution(vtk, dh, u)\n write_cell_data(vtk, celldata)\nend\n\n\n\n\n\n","category":"type"},{"location":"reference/export/#Ferrite.write_solution","page":"Postprocessing","title":"Ferrite.write_solution","text":"write_solution(vtk::VTKGridFile, dh::AbstractDofHandler, u::Vector, suffix=\"\")\n\nSave the values at the nodes in the degree of freedom vector u to vtk. Each field in dh will be saved separately, and suffix can be used to append to the fieldname.\n\nu can also contain tensorial values, but each entry in u must correspond to a degree of freedom in dh, see write_node_data for details. Use write_node_data directly when exporting values that are already sorted by the nodes in the grid.\n\n\n\n\n\n","category":"function"},{"location":"reference/export/#Ferrite.write_projection","page":"Postprocessing","title":"Ferrite.write_projection","text":"write_projection(vtk::VTKGridFile, proj::L2Projector, vals::Vector, name::AbstractString)\n\nProject vals to the grid nodes with proj and save to vtk.\n\n\n\n\n\n","category":"function"},{"location":"reference/export/#Ferrite.write_cell_data","page":"Postprocessing","title":"Ferrite.write_cell_data","text":"write_cell_data(vtk::VTKGridFile, celldata::AbstractVector, name::String)\n\nWrite the celldata that is ordered by the cells in the grid to the vtk file.\n\n\n\n\n\n","category":"function"},{"location":"reference/export/#Ferrite.write_node_data","page":"Postprocessing","title":"Ferrite.write_node_data","text":"write_node_data(vtk::VTKGridFile, nodedata::Vector{Real}, name)\nwrite_node_data(vtk::VTKGridFile, nodedata::Vector{<:AbstractTensor}, name)\n\nWrite the nodedata that is ordered by the nodes in the grid to vtk.\n\nWhen nodedata contains Tensors.Vecs, each component is exported. Two-dimensional vectors are padded with zeros.\n\nWhen nodedata contains second order tensors, the index order, [11, 22, 33, 23, 13, 12, 32, 31, 21], follows the default Voigt order in Tensors.jl.\n\n\n\n\n\n","category":"function"},{"location":"reference/export/#Ferrite.write_cellset","page":"Postprocessing","title":"Ferrite.write_cellset","text":"write_cellset(vtk, grid::AbstractGrid)\nwrite_cellset(vtk, grid::AbstractGrid, cellset::String)\nwrite_cellset(vtk, grid::AbstractGrid, cellsets::Union{AbstractVector{String},AbstractSet{String})\n\nWrite all cell sets in the grid with name according to their keys and celldata 1 if the cell is in the set, and 0 otherwise. It is also possible to only export a single cellset, or multiple cellsets.\n\n\n\n\n\n","category":"function"},{"location":"reference/export/#Ferrite.write_nodeset","page":"Postprocessing","title":"Ferrite.write_nodeset","text":"write_nodeset(vtk::VTKGridFile, grid::AbstractGrid, nodeset::String)\n\nWrite nodal values of 1 for nodes in nodeset, and 0 otherwise\n\n\n\n\n\n","category":"function"},{"location":"reference/export/#Ferrite.write_constraints","page":"Postprocessing","title":"Ferrite.write_constraints","text":"write_constraints(vtk::VTKGridFile, ch::ConstraintHandler)\n\nSaves the dirichlet boundary conditions to a vtkfile. Values will have a 1 where bcs are active and 0 otherwise\n\n\n\n\n\n","category":"function"},{"location":"reference/export/#Ferrite.write_cell_colors","page":"Postprocessing","title":"Ferrite.write_cell_colors","text":"write_cell_colors(vtk::VTKGridFile, grid::AbstractGrid, cell_colors, name=\"coloring\")\n\nWrite cell colors (see create_coloring) to a VTK file for visualization.\n\nIn case of coloring a subset, the cells which are not part of the subset are represented as color 0.\n\n\n\n\n\n","category":"function"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"EditURL = \"../literate-tutorials/linear_elasticity.jl\"","category":"page"},{"location":"tutorials/linear_elasticity/#tutorial-linear-elasticity","page":"Linear elasticity","title":"Linear elasticity","text":"","category":"section"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"(Image: )","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"Figure 1: Linear elastically deformed 1mm times 1mm Ferrite logo.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"tip: Tip\nThis tutorial is also available as a Jupyter notebook: linear_elasticity.ipynb.","category":"page"},{"location":"tutorials/linear_elasticity/#Introduction","page":"Linear elasticity","title":"Introduction","text":"","category":"section"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"The classical first finite element problem to solve in solid mechanics is a linear balance of momentum problem. We will use this to introduce a vector valued field, the displacements boldsymbolu(boldsymbolx). In addition, some features of the Tensors.jl toolbox are demonstrated.","category":"page"},{"location":"tutorials/linear_elasticity/#Strong-form","page":"Linear elasticity","title":"Strong form","text":"","category":"section"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"The strong form of the balance of momentum for quasi-static loading is given by","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"beginalignat*2\n mathrmdiv(boldsymbolsigma) + boldsymbolb = 0 quad boldsymbolx in Omega \n boldsymbolu = boldsymbolu_mathrmD quad boldsymbolx in Gamma_mathrmD \n boldsymboln cdot boldsymbolsigma = boldsymbolt_mathrmN quad boldsymbolx in Gamma_mathrmN\nendalignat*","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"where boldsymbolsigma is the (Cauchy) stress tensor and boldsymbolb the body force. The domain, Omega, has the boundary Gamma, consisting of a Dirichlet part, Gamma_mathrmD, and a Neumann part, Gamma_mathrmN, with outward pointing normal vector boldsymboln. boldsymbolu_mathrmD denotes prescribed displacements on Gamma_mathrmD, while boldsymbolt_mathrmN the known tractions on Gamma_mathrmN.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"In this tutorial, we use linear elasticity, such that","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"boldsymbolsigma = mathsfC boldsymbolvarepsilon quad\nboldsymbolvarepsilon = leftmathrmgrad(boldsymbolu)right^mathrmsym","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"where mathsfC is the 4th order elastic stiffness tensor and boldsymbolvarepsilon the small strain tensor. The colon, , represents the double contraction, sigma_ij = mathsfC_ijkl varepsilon_kl, and the superscript mathrmsym denotes the symmetric part.","category":"page"},{"location":"tutorials/linear_elasticity/#Weak-form","page":"Linear elasticity","title":"Weak form","text":"","category":"section"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"The resulting weak form is given given as follows: Find boldsymbolu in mathbbU such that","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"int_Omega\n mathrmgrad(delta boldsymbolu) boldsymbolsigma\n mathrmdOmega\n=\nint_Gamma\n delta boldsymbolu cdot boldsymbolt\n mathrmdGamma\n+\nint_Omega\n delta boldsymbolu cdot boldsymbolb\n mathrmdOmega\nquad forall delta boldsymbolu in mathbbT","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"where mathbbU and mathbbT denote suitable trial and test function spaces. delta boldsymbolu is a vector valued test function and boldsymbolt = boldsymbolsigmacdotboldsymboln is the traction vector on the boundary. In this tutorial, we will neglect body forces (i.e. boldsymbolb = boldsymbol0) and the weak form reduces to","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"int_Omega\n mathrmgrad(delta boldsymbolu) boldsymbolsigma\n mathrmdOmega\n=\nint_Gamma\n delta boldsymbolu cdot boldsymbolt\n mathrmdGamma ","category":"page"},{"location":"tutorials/linear_elasticity/#Finite-element-form","page":"Linear elasticity","title":"Finite element form","text":"","category":"section"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"Finally, the finite element form is obtained by introducing the finite element shape functions. Since the displacement field, boldsymbolu, is vector valued, we use vector valued shape functions deltaboldsymbolN_i and boldsymbolN_i to approximate the test and trial functions:","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"boldsymbolu approx sum_i=1^N boldsymbolN_i (boldsymbolx) hatu_i\nqquad\ndelta boldsymbolu approx sum_i=1^N deltaboldsymbolN_i (boldsymbolx) delta hatu_i","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"Here N is the number of nodal variables, with hatu_i and deltahatu_i representing the i-th nodal value. Using the Einstein summation convention, we can write this in short form as boldsymbolu approx boldsymbolN_i hatu_i and deltaboldsymbolu approx deltaboldsymbolN_i deltahatu_i.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"Inserting the these into the weak form, and noting that that the equation should hold for all delta hatu_i, we get","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"underbraceint_Omega mathrmgrad(delta boldsymbolN_i) boldsymbolsigma mathrmdOmega_f_i^mathrmint = underbraceint_Gamma delta boldsymbolN_i cdot boldsymbolt mathrmdGamma_f_i^mathrmext","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"Inserting the linear constitutive relationship, boldsymbolsigma = mathsfCboldsymbolvarepsilon, in the internal force vector, f_i^mathrmint, yields the linear equation","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"underbraceleftint_Omega mathrmgrad(delta boldsymbolN_i) mathsfC leftmathrmgrad(boldsymbolN_j)right^mathrmsym mathrmdOmegaright_K_ij hatu_j = f_i^mathrmext","category":"page"},{"location":"tutorials/linear_elasticity/#Implementation","page":"Linear elasticity","title":"Implementation","text":"","category":"section"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"First we load Ferrite, and some other packages we need.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"using Ferrite, FerriteGmsh, SparseArrays","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"As in the heat equation tutorial, we will use a unit square - but here we'll load the grid of the Ferrite logo! This is done by downloading logo.geo and loading it using FerriteGmsh.jl,","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"using Downloads: download\nlogo_mesh = \"logo.geo\"\nasset_url = \"https://raw.githubusercontent.com/Ferrite-FEM/Ferrite.jl/gh-pages/assets/\"\nisfile(logo_mesh) || download(string(asset_url, logo_mesh), logo_mesh)\n\nFerriteGmsh.Gmsh.initialize() #hide\nFerriteGmsh.Gmsh.gmsh.option.set_number(\"General.Verbosity\", 2) #hide\ngrid = togrid(logo_mesh);\nFerriteGmsh.Gmsh.finalize(); #hide\nnothing #hide","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"The generated grid lacks the facetsets for the boundaries, so we add them by using Ferrite's addfacetset!. It allows us to add facetsets to the grid based on coordinates. Note that approximate comparison to 0.0 doesn't work well, so we use a tolerance instead.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"addfacetset!(grid, \"top\", x -> x[2] ≈ 1.0) # facets for which x[2] ≈ 1.0 for all nodes\naddfacetset!(grid, \"left\", x -> abs(x[1]) < 1.0e-6)\naddfacetset!(grid, \"bottom\", x -> abs(x[2]) < 1.0e-6);\nnothing #hide","category":"page"},{"location":"tutorials/linear_elasticity/#Trial-and-test-functions","page":"Linear elasticity","title":"Trial and test functions","text":"","category":"section"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"In this tutorial, we use the same linear Lagrange shape functions to approximate both the test and trial spaces, i.e. deltaboldsymbolN_i = boldsymbolN_i. As our grid is composed of triangular elements, we need the Lagrange functions defined on a RefTriangle. All currently available interpolations can be found under Interpolation.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"Here we use linear triangular elements (also called constant strain triangles). The vector valued shape functions are constructed by raising the interpolation to the power dim (the dimension) since the displacement field has one component in each spatial dimension.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"dim = 2\norder = 1 # linear interpolation\nip = Lagrange{RefTriangle, order}()^dim; # vector valued interpolation\nnothing #hide","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"In order to evaluate the integrals, we need to specify the quadrature rules to use. Due to the linear interpolation, a single quadrature point suffices, both inside the cell and on the facet. In 2d, a facet is the edge of the element.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"qr = QuadratureRule{RefTriangle}(1) # 1 quadrature point\nqr_face = FacetQuadratureRule{RefTriangle}(1);\nnothing #hide","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"Finally, we collect the interpolations and quadrature rules into the CellValues and FacetValues buffers, which we will later use to evaluate the integrals over the cells and facets.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"cellvalues = CellValues(qr, ip)\nfacetvalues = FacetValues(qr_face, ip);\nnothing #hide","category":"page"},{"location":"tutorials/linear_elasticity/#Degrees-of-freedom","page":"Linear elasticity","title":"Degrees of freedom","text":"","category":"section"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"For distributing degrees of freedom, we define a DofHandler. The DofHandler knows that u has two degrees of freedom per node because we vectorized the interpolation above.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"dh = DofHandler(grid)\nadd!(dh, :u, ip)\nclose!(dh);\nnothing #hide","category":"page"},{"location":"tutorials/linear_elasticity/#Boundary-conditions","page":"Linear elasticity","title":"Boundary conditions","text":"","category":"section"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"We set Dirichlet boundary conditions by fixing the motion normal to the bottom and left boundaries. The last argument to Dirichlet determines which components of the field should be constrained. If no argument is given, all components are constrained by default.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"ch = ConstraintHandler(dh)\nadd!(ch, Dirichlet(:u, getfacetset(grid, \"bottom\"), (x, t) -> 0.0, 2))\nadd!(ch, Dirichlet(:u, getfacetset(grid, \"left\"), (x, t) -> 0.0, 1))\nclose!(ch);\nnothing #hide","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"In addition, we will use Neumann boundary conditions on the top surface, where we add a traction vector of the form","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"boldsymbolt_mathrmN(boldsymbolx) = (20e3) x_1 boldsymbole_2 mathrmNmathrmmm^2","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"traction(x) = Vec(0.0, 20.0e3 * x[1]);\nnothing #hide","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"On the right boundary, we don't do anything, resulting in a zero traction Neumann boundary. In order to assemble the external forces, f_i^mathrmext, we need to iterate over all facets in the relevant facetset. We do this by using the FacetIterator.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"function assemble_external_forces!(f_ext, dh, facetset, facetvalues, prescribed_traction)\n # Create a temporary array for the facet's local contributions to the external force vector\n fe_ext = zeros(getnbasefunctions(facetvalues))\n for facet in FacetIterator(dh, facetset)\n # Update the facetvalues to the correct facet number\n reinit!(facetvalues, facet)\n # Reset the temporary array for the next facet\n fill!(fe_ext, 0.0)\n # Access the cell's coordinates\n cell_coordinates = getcoordinates(facet)\n for qp in 1:getnquadpoints(facetvalues)\n # Calculate the global coordinate of the quadrature point.\n x = spatial_coordinate(facetvalues, qp, cell_coordinates)\n tₚ = prescribed_traction(x)\n # Get the integration weight for the current quadrature point.\n dΓ = getdetJdV(facetvalues, qp)\n for i in 1:getnbasefunctions(facetvalues)\n Nᵢ = shape_value(facetvalues, qp, i)\n fe_ext[i] += tₚ ⋅ Nᵢ * dΓ\n end\n end\n # Add the local contributions to the correct indices in the global external force vector\n assemble!(f_ext, celldofs(facet), fe_ext)\n end\n return f_ext\nend\nnothing #hide","category":"page"},{"location":"tutorials/linear_elasticity/#Material-behavior","page":"Linear elasticity","title":"Material behavior","text":"","category":"section"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"Next, we need to define the material behavior, specifically the elastic stiffness tensor, mathsfC. In this tutorial, we use plane strain linear isotropic elasticity, with Hooke's law as","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"boldsymbolsigma = 2G boldsymbolvarepsilon^mathrmdev + 3K boldsymbolvarepsilon^mathrmvol","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"where G is the shear modulus and K the bulk modulus. This expression can be written as boldsymbolsigma = mathsfCboldsymbolvarepsilon, with","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":" mathsfC = fracpartial boldsymbolsigmapartial boldsymbolvarepsilon","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"The volumetric, boldsymbolvarepsilon^mathrmvol, and deviatoric, boldsymbolvarepsilon^mathrmdev strains, are defined as","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"beginalign*\nboldsymbolvarepsilon^mathrmvol = fracmathrmtr(boldsymbolvarepsilon)3boldsymbolI quad\nboldsymbolvarepsilon^mathrmdev = boldsymbolvarepsilon - boldsymbolvarepsilon^mathrmvol\nendalign*","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"Starting from Young's modulus, E, and Poisson's ratio, nu, the shear and bulk modulus are","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"G = fracE2(1 + nu) quad K = fracE3(1 - 2nu)","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"Emod = 200.0e3 # Young's modulus [MPa]\nν = 0.3 # Poisson's ratio [-]\n\nGmod = Emod / (2(1 + ν)) # Shear modulus\nKmod = Emod / (3(1 - 2ν)) # Bulk modulus","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"Finally, we demonstrate Tensors.jl's automatic differentiation capabilities when calculating the elastic stiffness tensor","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"C = gradient(ϵ -> 2 * Gmod * dev(ϵ) + 3 * Kmod * vol(ϵ), zero(SymmetricTensor{2, 2}));\nnothing #hide","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"details: Plane stress instead of plane strain?\nIn order to change this tutorial to consider plane stress instead of plane strain, the elastic stiffness tensor should be changed to reflect this. The plane stress elasticity stiffness matrix in Voigt notation for engineering shear strains, is given asunderlineunderlineboldsymbolE = fracE1 - nu^2beginbmatrix\n1 nu 0 \nnu 1 0 \n0 0 (1 - nu)2\nendbmatrixThis matrix can be converted into the 4th order elastic stiffness tensor asC_voigt = Emod * [1.0 ν 0.0; ν 1.0 0.0; 0.0 0.0 (1-ν)/2] / (1 - ν^2)\nC = fromvoigt(SymmetricTensor{4,2}, E_voigt)","category":"page"},{"location":"tutorials/linear_elasticity/#Element-routine","page":"Linear elasticity","title":"Element routine","text":"","category":"section"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"To calculate the global stiffness matrix, K_ij, the element routine computes the local stiffness matrix ke for a single element and assembles it into the global matrix. ke is pre-allocated and reused for all elements.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"Note that the elastic stiffness tensor mathsfC is constant. Thus is needs to be computed and once and can then be used for all integration points.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"function assemble_cell!(ke, cellvalues, C)\n for q_point in 1:getnquadpoints(cellvalues)\n # Get the integration weight for the quadrature point\n dΩ = getdetJdV(cellvalues, q_point)\n for i in 1:getnbasefunctions(cellvalues)\n # Gradient of the test function\n ∇Nᵢ = shape_gradient(cellvalues, q_point, i)\n for j in 1:getnbasefunctions(cellvalues)\n # Symmetric gradient of the trial function\n ∇ˢʸᵐNⱼ = shape_symmetric_gradient(cellvalues, q_point, j)\n ke[i, j] += (∇Nᵢ ⊡ C ⊡ ∇ˢʸᵐNⱼ) * dΩ\n end\n end\n end\n return ke\nend\nnothing #hide","category":"page"},{"location":"tutorials/linear_elasticity/#Global-assembly","page":"Linear elasticity","title":"Global assembly","text":"","category":"section"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"We define the function assemble_global to loop over the elements and do the global assembly. The function takes the preallocated sparse matrix K, our DofHandler dh, our cellvalues and the elastic stiffness tensor C as input arguments and computes the global stiffness matrix K.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"function assemble_global!(K, dh, cellvalues, C)\n # Allocate the element stiffness matrix\n n_basefuncs = getnbasefunctions(cellvalues)\n ke = zeros(n_basefuncs, n_basefuncs)\n # Create an assembler\n assembler = start_assemble(K)\n # Loop over all cells\n for cell in CellIterator(dh)\n # Update the shape function gradients based on the cell coordinates\n reinit!(cellvalues, cell)\n # Reset the element stiffness matrix\n fill!(ke, 0.0)\n # Compute element contribution\n assemble_cell!(ke, cellvalues, C)\n # Assemble ke into K\n assemble!(assembler, celldofs(cell), ke)\n end\n return K\nend\nnothing #hide","category":"page"},{"location":"tutorials/linear_elasticity/#Solution-of-the-system","page":"Linear elasticity","title":"Solution of the system","text":"","category":"section"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"The last step is to solve the system. First we allocate the global stiffness matrix K and assemble it.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"K = allocate_matrix(dh)\nassemble_global!(K, dh, cellvalues, C);\nnothing #hide","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"Then we allocate and assemble the external force vector.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"f_ext = zeros(ndofs(dh))\nassemble_external_forces!(f_ext, dh, getfacetset(grid, \"top\"), facetvalues, traction);\nnothing #hide","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"To account for the Dirichlet boundary conditions we use the apply! function. This modifies elements in K and f, such that we can get the correct solution vector u by using solving the linear equation system K_ij hatu_j = f^mathrmext_i,","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"apply!(K, f_ext, ch)\nu = K \\ f_ext;\nnothing #hide","category":"page"},{"location":"tutorials/linear_elasticity/#Postprocessing","page":"Linear elasticity","title":"Postprocessing","text":"","category":"section"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"In this case, we want to analyze the displacements, as well as the stress field. We calculate the stress in each quadrature point, and then export it in two different ways:","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"Constant in each cell (matching the approximation of constant strains in each element). Note that a current limitation is that cell data for second order tensors must be exported component-wise (see issue #768)\nInterpolated using the linear lagrange ansatz functions via the L2Projector.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"function calculate_stresses(grid, dh, cv, u, C)\n qp_stresses = [\n [zero(SymmetricTensor{2, 2}) for _ in 1:getnquadpoints(cv)]\n for _ in 1:getncells(grid)\n ]\n avg_cell_stresses = tuple((zeros(getncells(grid)) for _ in 1:3)...)\n for cell in CellIterator(dh)\n reinit!(cv, cell)\n cell_stresses = qp_stresses[cellid(cell)]\n for q_point in 1:getnquadpoints(cv)\n ε = function_symmetric_gradient(cv, q_point, u, celldofs(cell))\n cell_stresses[q_point] = C ⊡ ε\n end\n σ_avg = sum(cell_stresses) / getnquadpoints(cv)\n avg_cell_stresses[1][cellid(cell)] = σ_avg[1, 1]\n avg_cell_stresses[2][cellid(cell)] = σ_avg[2, 2]\n avg_cell_stresses[3][cellid(cell)] = σ_avg[1, 2]\n end\n return qp_stresses, avg_cell_stresses\nend\n\nqp_stresses, avg_cell_stresses = calculate_stresses(grid, dh, cellvalues, u, C);\nnothing #hide","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"We now use the the L2Projector to project the stress-field onto the piecewise linear finite element space that we used to solve the problem.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"proj = L2Projector(Lagrange{RefTriangle, 1}(), grid)\nstress_field = project(proj, qp_stresses, qr);\n\ncolor_data = zeros(Int, getncells(grid)) #hide\ncolors = [ #hide\n \"1\" => 1, \"5\" => 1, # purple #hide\n \"2\" => 2, \"3\" => 2, # red #hide\n \"4\" => 3, # blue #hide\n \"6\" => 4, # green #hide\n] #hide\nfor (key, color) in colors #hide\n for i in getcellset(grid, key) #hide\n color_data[i] = color #hide\n end #hide\nend #hide\nnothing #hide","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"To visualize the result we export to a VTK-file. Specifically, an unstructured grid file, .vtu, is created, which can be viewed in e.g. ParaView.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"VTKGridFile(\"linear_elasticity\", dh) do vtk\n write_solution(vtk, dh, u)\n for (i, key) in enumerate((\"11\", \"22\", \"12\"))\n write_cell_data(vtk, avg_cell_stresses[i], \"sigma_\" * key)\n end\n write_projection(vtk, proj, stress_field, \"stress field\")\n Ferrite.write_cellset(vtk, grid)\n write_cell_data(vtk, color_data, \"colors\") #hide\nend","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"We used the displacement field to visualize the deformed logo in Figure 1, and in Figure 2, we demonstrate the difference between the interpolated stress field and the constant stress in each cell.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"(Image: )","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"Figure 2: Vertical normal stresses (MPa) exported using the L2Projector (left) and constant stress in each cell (right).","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"using Test #hide\nlinux_result = 0.31742879147646924 #hide\n@test abs(norm(u) - linux_result) < 0.01 #hide\nSys.islinux() && @test norm(u) ≈ linux_result #hide\nnothing #hide","category":"page"},{"location":"tutorials/linear_elasticity/#linear_elasticity-plain-program","page":"Linear elasticity","title":"Plain program","text":"","category":"section"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"Here follows a version of the program without any comments. The file is also available here: linear_elasticity.jl.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"using Ferrite, FerriteGmsh, SparseArrays\n\nusing Downloads: download\nlogo_mesh = \"logo.geo\"\nasset_url = \"https://raw.githubusercontent.com/Ferrite-FEM/Ferrite.jl/gh-pages/assets/\"\nisfile(logo_mesh) || download(string(asset_url, logo_mesh), logo_mesh)\n\ngrid = togrid(logo_mesh);\n\naddfacetset!(grid, \"top\", x -> x[2] ≈ 1.0) # facets for which x[2] ≈ 1.0 for all nodes\naddfacetset!(grid, \"left\", x -> abs(x[1]) < 1.0e-6)\naddfacetset!(grid, \"bottom\", x -> abs(x[2]) < 1.0e-6);\n\ndim = 2\norder = 1 # linear interpolation\nip = Lagrange{RefTriangle, order}()^dim; # vector valued interpolation\n\nqr = QuadratureRule{RefTriangle}(1) # 1 quadrature point\nqr_face = FacetQuadratureRule{RefTriangle}(1);\n\ncellvalues = CellValues(qr, ip)\nfacetvalues = FacetValues(qr_face, ip);\n\ndh = DofHandler(grid)\nadd!(dh, :u, ip)\nclose!(dh);\n\nch = ConstraintHandler(dh)\nadd!(ch, Dirichlet(:u, getfacetset(grid, \"bottom\"), (x, t) -> 0.0, 2))\nadd!(ch, Dirichlet(:u, getfacetset(grid, \"left\"), (x, t) -> 0.0, 1))\nclose!(ch);\n\ntraction(x) = Vec(0.0, 20.0e3 * x[1]);\n\nfunction assemble_external_forces!(f_ext, dh, facetset, facetvalues, prescribed_traction)\n # Create a temporary array for the facet's local contributions to the external force vector\n fe_ext = zeros(getnbasefunctions(facetvalues))\n for facet in FacetIterator(dh, facetset)\n # Update the facetvalues to the correct facet number\n reinit!(facetvalues, facet)\n # Reset the temporary array for the next facet\n fill!(fe_ext, 0.0)\n # Access the cell's coordinates\n cell_coordinates = getcoordinates(facet)\n for qp in 1:getnquadpoints(facetvalues)\n # Calculate the global coordinate of the quadrature point.\n x = spatial_coordinate(facetvalues, qp, cell_coordinates)\n tₚ = prescribed_traction(x)\n # Get the integration weight for the current quadrature point.\n dΓ = getdetJdV(facetvalues, qp)\n for i in 1:getnbasefunctions(facetvalues)\n Nᵢ = shape_value(facetvalues, qp, i)\n fe_ext[i] += tₚ ⋅ Nᵢ * dΓ\n end\n end\n # Add the local contributions to the correct indices in the global external force vector\n assemble!(f_ext, celldofs(facet), fe_ext)\n end\n return f_ext\nend\n\nEmod = 200.0e3 # Young's modulus [MPa]\nν = 0.3 # Poisson's ratio [-]\n\nGmod = Emod / (2(1 + ν)) # Shear modulus\nKmod = Emod / (3(1 - 2ν)) # Bulk modulus\n\nC = gradient(ϵ -> 2 * Gmod * dev(ϵ) + 3 * Kmod * vol(ϵ), zero(SymmetricTensor{2, 2}));\n\nfunction assemble_cell!(ke, cellvalues, C)\n for q_point in 1:getnquadpoints(cellvalues)\n # Get the integration weight for the quadrature point\n dΩ = getdetJdV(cellvalues, q_point)\n for i in 1:getnbasefunctions(cellvalues)\n # Gradient of the test function\n ∇Nᵢ = shape_gradient(cellvalues, q_point, i)\n for j in 1:getnbasefunctions(cellvalues)\n # Symmetric gradient of the trial function\n ∇ˢʸᵐNⱼ = shape_symmetric_gradient(cellvalues, q_point, j)\n ke[i, j] += (∇Nᵢ ⊡ C ⊡ ∇ˢʸᵐNⱼ) * dΩ\n end\n end\n end\n return ke\nend\n\nfunction assemble_global!(K, dh, cellvalues, C)\n # Allocate the element stiffness matrix\n n_basefuncs = getnbasefunctions(cellvalues)\n ke = zeros(n_basefuncs, n_basefuncs)\n # Create an assembler\n assembler = start_assemble(K)\n # Loop over all cells\n for cell in CellIterator(dh)\n # Update the shape function gradients based on the cell coordinates\n reinit!(cellvalues, cell)\n # Reset the element stiffness matrix\n fill!(ke, 0.0)\n # Compute element contribution\n assemble_cell!(ke, cellvalues, C)\n # Assemble ke into K\n assemble!(assembler, celldofs(cell), ke)\n end\n return K\nend\n\nK = allocate_matrix(dh)\nassemble_global!(K, dh, cellvalues, C);\n\nf_ext = zeros(ndofs(dh))\nassemble_external_forces!(f_ext, dh, getfacetset(grid, \"top\"), facetvalues, traction);\n\napply!(K, f_ext, ch)\nu = K \\ f_ext;\n\nfunction calculate_stresses(grid, dh, cv, u, C)\n qp_stresses = [\n [zero(SymmetricTensor{2, 2}) for _ in 1:getnquadpoints(cv)]\n for _ in 1:getncells(grid)\n ]\n avg_cell_stresses = tuple((zeros(getncells(grid)) for _ in 1:3)...)\n for cell in CellIterator(dh)\n reinit!(cv, cell)\n cell_stresses = qp_stresses[cellid(cell)]\n for q_point in 1:getnquadpoints(cv)\n ε = function_symmetric_gradient(cv, q_point, u, celldofs(cell))\n cell_stresses[q_point] = C ⊡ ε\n end\n σ_avg = sum(cell_stresses) / getnquadpoints(cv)\n avg_cell_stresses[1][cellid(cell)] = σ_avg[1, 1]\n avg_cell_stresses[2][cellid(cell)] = σ_avg[2, 2]\n avg_cell_stresses[3][cellid(cell)] = σ_avg[1, 2]\n end\n return qp_stresses, avg_cell_stresses\nend\n\nqp_stresses, avg_cell_stresses = calculate_stresses(grid, dh, cellvalues, u, C);\n\nproj = L2Projector(Lagrange{RefTriangle, 1}(), grid)\nstress_field = project(proj, qp_stresses, qr);\n\n\nVTKGridFile(\"linear_elasticity\", dh) do vtk\n write_solution(vtk, dh, u)\n for (i, key) in enumerate((\"11\", \"22\", \"12\"))\n write_cell_data(vtk, avg_cell_stresses[i], \"sigma_\" * key)\n end\n write_projection(vtk, proj, stress_field, \"stress field\")\n Ferrite.write_cellset(vtk, grid)\nend","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"This page was generated using Literate.jl.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"EditURL = \"../literate-tutorials/heat_equation.jl\"","category":"page"},{"location":"tutorials/heat_equation/#tutorial-heat-equation","page":"Heat equation","title":"Heat equation","text":"","category":"section"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"(Image: )","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"Figure 1: Temperature field on the unit square with an internal uniform heat source solved with homogeneous Dirichlet boundary conditions on the boundary.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"tip: Tip\nThis example is also available as a Jupyter notebook: heat_equation.ipynb.","category":"page"},{"location":"tutorials/heat_equation/#Introduction","page":"Heat equation","title":"Introduction","text":"","category":"section"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"The heat equation is the \"Hello, world!\" equation of finite elements. Here we solve the equation on a unit square, with a uniform internal source. The strong form of the (linear) heat equation is given by","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":" -nabla cdot (k nabla u) = f quad textbfx in Omega","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"where u is the unknown temperature field, k the heat conductivity, f the heat source and Omega the domain. For simplicity we set f = 1 and k = 1. We will consider homogeneous Dirichlet boundary conditions such that","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"u(textbfx) = 0 quad textbfx in partial Omega","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"where partial Omega denotes the boundary of Omega. The resulting weak form is given given as follows: Find u in mathbbU such that","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"int_Omega nabla delta u cdot nabla u dOmega = int_Omega delta u dOmega quad forall delta u in mathbbT","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"where delta u is a test function, and where mathbbU and mathbbT are suitable trial and test function sets, respectively.","category":"page"},{"location":"tutorials/heat_equation/#Commented-Program","page":"Heat equation","title":"Commented Program","text":"","category":"section"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"Now we solve the problem in Ferrite. What follows is a program spliced with comments. The full program, without comments, can be found in the next section.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"First we load Ferrite, and some other packages we need","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"using Ferrite, SparseArrays","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"We start by generating a simple grid with 20x20 quadrilateral elements using generate_grid. The generator defaults to the unit square, so we don't need to specify the corners of the domain.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"grid = generate_grid(Quadrilateral, (20, 20));\nnothing #hide","category":"page"},{"location":"tutorials/heat_equation/#Trial-and-test-functions","page":"Heat equation","title":"Trial and test functions","text":"","category":"section"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"A CellValues facilitates the process of evaluating values and gradients of test and trial functions (among other things). To define this we need to specify an interpolation space for the shape functions. We use Lagrange functions based on the two-dimensional reference quadrilateral. We also define a quadrature rule based on the same reference element. We combine the interpolation and the quadrature rule to a CellValues object.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"ip = Lagrange{RefQuadrilateral, 1}()\nqr = QuadratureRule{RefQuadrilateral}(2)\ncellvalues = CellValues(qr, ip);\nnothing #hide","category":"page"},{"location":"tutorials/heat_equation/#Degrees-of-freedom","page":"Heat equation","title":"Degrees of freedom","text":"","category":"section"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"Next we need to define a DofHandler, which will take care of numbering and distribution of degrees of freedom for our approximated fields. We create the DofHandler and then add a single scalar field called :u based on our interpolation ip defined above. Lastly we close! the DofHandler, it is now that the dofs are distributed for all the elements.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"dh = DofHandler(grid)\nadd!(dh, :u, ip)\nclose!(dh);\nnothing #hide","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"Now that we have distributed all our dofs we can create our tangent matrix, using allocate_matrix. This function returns a sparse matrix with the correct entries stored.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"K = allocate_matrix(dh)","category":"page"},{"location":"tutorials/heat_equation/#Boundary-conditions","page":"Heat equation","title":"Boundary conditions","text":"","category":"section"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"In Ferrite constraints like Dirichlet boundary conditions are handled by a ConstraintHandler.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"ch = ConstraintHandler(dh);\nnothing #hide","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"Next we need to add constraints to ch. For this problem we define homogeneous Dirichlet boundary conditions on the whole boundary, i.e. the union of all the facet sets on the boundary.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"∂Ω = union(\n getfacetset(grid, \"left\"),\n getfacetset(grid, \"right\"),\n getfacetset(grid, \"top\"),\n getfacetset(grid, \"bottom\"),\n);\nnothing #hide","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"Now we are set up to define our constraint. We specify which field the condition is for, and our combined facet set ∂Ω. The last argument is a function of the form f(textbfx) or f(textbfx t), where textbfx is the spatial coordinate and t the current time, and returns the prescribed value. Since the boundary condition in this case do not depend on time we define our function as f(textbfx) = 0, i.e. no matter what textbfx we return 0. When we have specified our constraint we add! it to ch.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"dbc = Dirichlet(:u, ∂Ω, (x, t) -> 0)\nadd!(ch, dbc);\nnothing #hide","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"Finally we also need to close! our constraint handler. When we call close! the dofs corresponding to our constraints are calculated and stored in our ch object.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"close!(ch)","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"Note that if one or more of the constraints are time dependent we would use update! to recompute prescribed values in each new timestep.","category":"page"},{"location":"tutorials/heat_equation/#Assembling-the-linear-system","page":"Heat equation","title":"Assembling the linear system","text":"","category":"section"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"Now we have all the pieces needed to assemble the linear system, K u = f. Assembling of the global system is done by looping over all the elements in order to compute the element contributions K_e and f_e, which are then assembled to the appropriate place in the global K and f.","category":"page"},{"location":"tutorials/heat_equation/#Element-assembly","page":"Heat equation","title":"Element assembly","text":"","category":"section"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"We define the function assemble_element! (see below) which computes the contribution for an element. The function takes pre-allocated ke and fe (they are allocated once and then reused for all elements) so we first need to make sure that they are all zeroes at the start of the function by using fill!. Then we loop over all the quadrature points, and for each quadrature point we loop over all the (local) shape functions. We need the value and gradient of the test function, δu and also the gradient of the trial function u. We get all of these from cellvalues.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"note: Notation\nComparing with the brief finite element introduction in Introduction to FEM, the variables δu, ∇δu and ∇u are actually phi_i(textbfx_q), nabla phi_i(textbfx_q) and nabla phi_j(textbfx_q), i.e. the evaluation of the trial and test functions in the quadrature point textbfx_q. However, to underline the strong parallel between the weak form and the implementation, this example uses the symbols appearing in the weak form.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"function assemble_element!(Ke::Matrix, fe::Vector, cellvalues::CellValues)\n n_basefuncs = getnbasefunctions(cellvalues)\n # Reset to 0\n fill!(Ke, 0)\n fill!(fe, 0)\n # Loop over quadrature points\n for q_point in 1:getnquadpoints(cellvalues)\n # Get the quadrature weight\n dΩ = getdetJdV(cellvalues, q_point)\n # Loop over test shape functions\n for i in 1:n_basefuncs\n δu = shape_value(cellvalues, q_point, i)\n ∇δu = shape_gradient(cellvalues, q_point, i)\n # Add contribution to fe\n fe[i] += δu * dΩ\n # Loop over trial shape functions\n for j in 1:n_basefuncs\n ∇u = shape_gradient(cellvalues, q_point, j)\n # Add contribution to Ke\n Ke[i, j] += (∇δu ⋅ ∇u) * dΩ\n end\n end\n end\n return Ke, fe\nend\nnothing # hide","category":"page"},{"location":"tutorials/heat_equation/#Global-assembly","page":"Heat equation","title":"Global assembly","text":"","category":"section"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"We define the function assemble_global to loop over the elements and do the global assembly. The function takes our cellvalues, the sparse matrix K, and our DofHandler as input arguments and returns the assembled global stiffness matrix, and the assembled global force vector. We start by allocating Ke, fe, and the global force vector f. We also create an assembler by using start_assemble. The assembler lets us assemble into K and f efficiently. We then start the loop over all the elements. In each loop iteration we reinitialize cellvalues (to update derivatives of shape functions etc.), compute the element contribution with assemble_element!, and then assemble into the global K and f with assemble!.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"note: Notation\nComparing again with Introduction to FEM, f and u correspond to underlinehatf and underlinehatu, since they represent the discretized versions. However, through the code we use f and u instead to reflect the strong connection between the weak form and the Ferrite implementation.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"function assemble_global(cellvalues::CellValues, K::SparseMatrixCSC, dh::DofHandler)\n # Allocate the element stiffness matrix and element force vector\n n_basefuncs = getnbasefunctions(cellvalues)\n Ke = zeros(n_basefuncs, n_basefuncs)\n fe = zeros(n_basefuncs)\n # Allocate global force vector f\n f = zeros(ndofs(dh))\n # Create an assembler\n assembler = start_assemble(K, f)\n # Loop over all cels\n for cell in CellIterator(dh)\n # Reinitialize cellvalues for this cell\n reinit!(cellvalues, cell)\n # Compute element contribution\n assemble_element!(Ke, fe, cellvalues)\n # Assemble Ke and fe into K and f\n assemble!(assembler, celldofs(cell), Ke, fe)\n end\n return K, f\nend\nnothing # hide","category":"page"},{"location":"tutorials/heat_equation/#Solution-of-the-system","page":"Heat equation","title":"Solution of the system","text":"","category":"section"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"The last step is to solve the system. First we call assemble_global to obtain the global stiffness matrix K and force vector f.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"K, f = assemble_global(cellvalues, K, dh);\nnothing #hide","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"To account for the boundary conditions we use the apply! function. This modifies elements in K and f respectively, such that we can get the correct solution vector u by using \\.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"apply!(K, f, ch)\nu = K \\ f;\nnothing #hide","category":"page"},{"location":"tutorials/heat_equation/#Exporting-to-VTK","page":"Heat equation","title":"Exporting to VTK","text":"","category":"section"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"To visualize the result we export the grid and our field u to a VTK-file, which can be viewed in e.g. ParaView.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"VTKGridFile(\"heat_equation\", dh) do vtk\n write_solution(vtk, dh, u)\nend","category":"page"},{"location":"tutorials/heat_equation/#heat_equation-plain-program","page":"Heat equation","title":"Plain program","text":"","category":"section"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"Here follows a version of the program without any comments. The file is also available here: heat_equation.jl.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"using Ferrite, SparseArrays\n\ngrid = generate_grid(Quadrilateral, (20, 20));\n\nip = Lagrange{RefQuadrilateral, 1}()\nqr = QuadratureRule{RefQuadrilateral}(2)\ncellvalues = CellValues(qr, ip);\n\ndh = DofHandler(grid)\nadd!(dh, :u, ip)\nclose!(dh);\n\nK = allocate_matrix(dh)\n\nch = ConstraintHandler(dh);\n\n∂Ω = union(\n getfacetset(grid, \"left\"),\n getfacetset(grid, \"right\"),\n getfacetset(grid, \"top\"),\n getfacetset(grid, \"bottom\"),\n);\n\ndbc = Dirichlet(:u, ∂Ω, (x, t) -> 0)\nadd!(ch, dbc);\n\nclose!(ch)\n\nfunction assemble_element!(Ke::Matrix, fe::Vector, cellvalues::CellValues)\n n_basefuncs = getnbasefunctions(cellvalues)\n # Reset to 0\n fill!(Ke, 0)\n fill!(fe, 0)\n # Loop over quadrature points\n for q_point in 1:getnquadpoints(cellvalues)\n # Get the quadrature weight\n dΩ = getdetJdV(cellvalues, q_point)\n # Loop over test shape functions\n for i in 1:n_basefuncs\n δu = shape_value(cellvalues, q_point, i)\n ∇δu = shape_gradient(cellvalues, q_point, i)\n # Add contribution to fe\n fe[i] += δu * dΩ\n # Loop over trial shape functions\n for j in 1:n_basefuncs\n ∇u = shape_gradient(cellvalues, q_point, j)\n # Add contribution to Ke\n Ke[i, j] += (∇δu ⋅ ∇u) * dΩ\n end\n end\n end\n return Ke, fe\nend\n\nfunction assemble_global(cellvalues::CellValues, K::SparseMatrixCSC, dh::DofHandler)\n # Allocate the element stiffness matrix and element force vector\n n_basefuncs = getnbasefunctions(cellvalues)\n Ke = zeros(n_basefuncs, n_basefuncs)\n fe = zeros(n_basefuncs)\n # Allocate global force vector f\n f = zeros(ndofs(dh))\n # Create an assembler\n assembler = start_assemble(K, f)\n # Loop over all cels\n for cell in CellIterator(dh)\n # Reinitialize cellvalues for this cell\n reinit!(cellvalues, cell)\n # Compute element contribution\n assemble_element!(Ke, fe, cellvalues)\n # Assemble Ke and fe into K and f\n assemble!(assembler, celldofs(cell), Ke, fe)\n end\n return K, f\nend\n\nK, f = assemble_global(cellvalues, K, dh);\n\napply!(K, f, ch)\nu = K \\ f;\n\nVTKGridFile(\"heat_equation\", dh) do vtk\n write_solution(vtk, dh, u)\nend","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"This page was generated using Literate.jl.","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"DocTestSetup = :(using Ferrite)","category":"page"},{"location":"topics/assembly/#man-assembly","page":"Assembly","title":"Assembly","text":"","category":"section"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"When the local stiffness matrix and force vector have been calculated they should be assembled into the global stiffness matrix and the global force vector. This is just a matter of adding the local matrix and vector to the global one, at the correct place. Consider e.g. assembling the local stiffness matrix ke and the local force vector fe into the global K and f respectively. These should be assembled into the row/column which corresponds to the degrees of freedom for the cell:","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"K[celldofs, celldofs] += ke\nf[celldofs] += fe","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"where celldofs is the vector containing the degrees of freedom for the cell. The method above is very inefficient – it is especially costly to index into the sparse matrix K directly (see Comparison of assembly strategies for details). Therefore we will instead use an Assembler that will help with the assembling of both the global stiffness matrix and the global force vector. It is also often convenient to create the sparse matrix just once, and reuse the allocated matrix. This is useful for e.g. iterative solvers or time dependent problems where the sparse matrix structure, or Sparsity Pattern will stay the same in every iteration/time step.","category":"page"},{"location":"topics/assembly/#Assembler","page":"Assembly","title":"Assembler","text":"","category":"section"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"Assembling efficiently into the sparse matrix requires some extra workspace. This workspace is allocated in an Assembler. start_assemble is used to create an Assembler:","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"A = start_assemble(K)\nA = start_assemble(K, f)","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"where K is the global stiffness matrix, and f the global force vector. It is optional to pass the force vector to the assembler – sometimes there is no need to assemble a global force vector.","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"The assemble! function is used to assemble element contributions to the assembler. For example, to assemble the element tangent stiffness ke and the element force vector fe to the assembler A, the following code can be used:","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"assemble!(A, celldofs, ke)\nassemble!(A, celldofs, ke, fe)","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"which perform the following operations in an efficient manner:","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"K[celldofs, celldofs] += ke\nf[celldofs] += fe","category":"page"},{"location":"topics/assembly/#Pseudo-code-for-efficient-assembly","page":"Assembly","title":"Pseudo-code for efficient assembly","text":"","category":"section"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"Quite often the same sparsity pattern can be reused multiple times. For example:","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"For time-dependent problems the pattern can be reused for all timesteps\nFor non-linear problems the pattern can be reused for all iterations","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"In such cases it is enough to construct the global matrix K once. Below is some pseudo-code for how to do this for a time-dependent problem:","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"K = allocate_matrix(dh)\nf = zeros(ndofs(dh))\n\nfor t in 1:timesteps\n A = start_assemble(K, f) # start_assemble zeroes K and f\n for cell in CellIterator(dh)\n ke, fe = element_routine(...)\n assemble!(A, celldofs(cell), ke, fe)\n end\n # Apply boundary conditions and solve for u(t)\n # ...\nend","category":"page"},{"location":"topics/assembly/#Comparison-of-assembly-strategies","page":"Assembly","title":"Comparison of assembly strategies","text":"","category":"section"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"As discussed above there are various ways to assemble the local matrix into the global one. In particular, it was mentioned that naive indexing is very inefficient and that using an assembler is faster. To put some concrete numbers to these statements we will compare some strategies in this section. First we compare just a single assembly operation (e.g. assembling an already computed local matrix) and then to relate this to a more realistic scenario we compare the full matrix assembly including the integration of all the elements.","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"note: Pre-allocated global matrix\nAll strategies that we compare below uses a pre-allocated global matrix K with the correct sparsity pattern. Starting with something like K = spzeros(ndofs(dh), ndofs(dh)) and then inserting entries is excruciatingly slow due to the sparse data structure so this method is not even considered.","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"For the comparison we need a representative global matrix to assemble into. In the following setup code we create a grid with triangles and a DofHandler with a quadratic scalar field. From this we instantiate the global matrix.","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"using Ferrite\n\n# Quadratic scalar interpolation\nip = Lagrange{RefTriangle, 2}()\n\n# DofHandler\nconst N = 100\ngrid = generate_grid(Triangle, (N, N))\nconst dh = DofHandler(grid)\nadd!(dh, :u, ip)\nclose!(dh)\n\n# Global matrix and a corresponding assembler\nconst K = allocate_matrix(dh)\nnothing # hide","category":"page"},{"location":"topics/assembly/#Strategy-1:-matrix-indexing","page":"Assembly","title":"Strategy 1: matrix indexing","text":"","category":"section"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"The first strategy is to index directly, using the vector of global dofs, into the global matrix:","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"function assemble_v1(_, K, dofs, Ke)\n K[dofs, dofs] += Ke\n return\nend\nnothing # hide","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"This looks very simple, but it is very inefficient (as the numbers will show later). To understand why the operation K[dofs, dofs] += Ke (with K being a sparse matrix) is so slow we can dig into the details.","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"In Julia there is no \"+=\"-operation and so x += y is identical to x = x + y. Translating this to our example we have","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"K[dofs, dofs] = K[dofs, dofs] + Ke","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"We can break down this a bit further into these equivalent three steps:","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"tmp1 = K[dofs, dofs] # 1\ntmp2 = tmp1 + Ke # 2\nK[dofs, dofs] = tmp2 # 3","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"Now the problem with this strategy becomes a bit more obvious:","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"In line 1 there is first an allocation of a new matrix (tmp1) followed by indexing into K to copy elements from K to tmp1. Both of these operations are rather costly: allocations should always be minimized in tight loops, and indexing into a sparse matrix is non-trivial due to the data structure. In addition, since the dofs vector contains the global indices (which are neither sorted nor consecutive) we have a random access pattern.\nIn line 2 there is another allocation of a matrix (tmp2) for the result of the addition of tmp1 and Ke.\nIn line 3 we again need to index into the sparse matrix to copy over the elements from tmp2 to K. This essentially duplicates the indexing effort from line 1 since we need to lookup the same locations in K again.","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"note: Broadcasting\nUsing broadcasting, e.g. K[dofs, dofs] .+= Ke is an alternative to the above, and resembles a +=-operation. In theory this should be as efficient as the explicit loop presented in the next section.","category":"page"},{"location":"topics/assembly/#Strategy-2:-scalar-indexing","page":"Assembly","title":"Strategy 2: scalar indexing","text":"","category":"section"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"A variant of the first strategy is to explicitly loop over the indices and add the elements individually as scalars:","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"function assemble_v2(_, K, dofs, Ke)\n for (i, I) in pairs(dofs)\n for (j, J) in pairs(dofs)\n K[I, J] += Ke[i, j]\n end\n end\n return\nend\nnothing # hide","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"The core operation, K[I, J] += Ke[i, j], can still be broken down into three equivalent steps:","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"tmp1 = K[I, J]\ntmp2 = tmp1 + Ke[i, j]\nK[I, J] = tmp2","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"The key difference here is that we index using integers (I, J, i, and j) which means that tmp1 and tmp2 are scalars which don't need to be allocated on the heap. This stragety thus eliminates all allocations that were present in the first strategy. However, we still lookup the same location in K twice, and we still have a random access pattern.","category":"page"},{"location":"topics/assembly/#Strategy-3:-scalar-indexing-with-single-lookup","page":"Assembly","title":"Strategy 3: scalar indexing with single lookup","text":"","category":"section"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"To improve on the second strategy we will get rid of the double lookup into the sparse matrix K. While Julia doesn't have a \"+=\"-operation, Ferrite has an internal addindex!-function which does exactly what we want: it adds a value to a specific location in a sparse matrix using a single lookup.","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"function assemble_v3(_, K, dofs, Ke)\n for (i, I) in pairs(dofs)\n for (j, J) in pairs(dofs)\n Ferrite.addindex!(K, Ke[i, j], I, J)\n end\n end\n return\nend\nnothing # hide","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"With this method we remove the double lookup, but the issue of random access patterns still remains.","category":"page"},{"location":"topics/assembly/#Strategy-4:-using-an-assembler","page":"Assembly","title":"Strategy 4: using an assembler","text":"","category":"section"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"Finally, the last strategy we consider uses an assembler. The assembler is a specific datastructure that pre-allocates some workspace to make the assembly more efficient:","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"function assemble_v4(assembler, _, dofs, Ke)\n assemble!(assembler, dofs, Ke)\n return\nend\nnothing # hide","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"The extra workspace inside the assembler is used to sort the dofs when assemble! is called. After sorting it is possible to loop over the sparse matrix data structure and insert all elements of Ke in one go instead of having to lookup locations randomly.","category":"page"},{"location":"topics/assembly/#Single-element-assembly","page":"Assembly","title":"Single element assembly","text":"","category":"section"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"First we will compare the four functions above for a single assembly operation, i.e. inserting one local matrix into the global matrix. For this we simply create a random local matrix since we are not conserned with the actual values. We also pick the \"middle\" element and extract the dofs for that element. Finally, an assembler is created with start_assemble to use with the fourth strategy.","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"dofs_per_cell = ndofs_per_cell(dh)\nconst Ke = rand(dofs_per_cell, dofs_per_cell)\nconst dofs = celldofs(dh, N * N ÷ 2)\n\nconst assembler = start_assemble(K)\nnothing # hide","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"We use BenchmarkTools to measure the performance:","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"assemble_v1(assembler, K, dofs, Ke) # hide\nassemble_v2(assembler, K, dofs, Ke) # hide\nassemble_v3(assembler, K, dofs, Ke) # hide\nassemble_v4(assembler, K, dofs, Ke) # hide\nnothing # hide","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"using BenchmarkTools\n\n@btime assemble_v1(assembler, K, dofs, Ke) evals = 10 setup = fill!(K, 0)\n@btime assemble_v2(assembler, K, dofs, Ke) evals = 10 setup = fill!(K, 0)\n@btime assemble_v3(assembler, K, dofs, Ke) evals = 10 setup = fill!(K, 0)\n@btime assemble_v4(assembler, K, dofs, Ke) evals = 10 setup = fill!(K, 0)","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"The results below are obtained on an Macbook Pro with an Apple M3 CPU.","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"606.438 μs (36 allocations: 7.67 MiB)\n283.300 ns (0 allocations: 0 bytes)\n158.300 ns (0 allocations: 0 bytes)\n 83.400 ns (0 allocations: 0 bytes)","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"The results match what we expect based on the explanations above:","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"Between strategy 1 and 2 we got rid of the allocations completely and decreased the time with a factor of 2100(!).\nBetween strategy 2 and 3 we got rid of the double lookup and decreased the time with another factor of almost 2.\nBetween strategy 3 and 4 we got rid of the random lookup order and decreased the time with another factor of almost 2.","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"The most important thing for this benchmark is to get rid of the allocations. By using an assembler instead of doing the naive thing we reduce the runtime with a factor of more than 7000(!!) in total.","category":"page"},{"location":"topics/assembly/#Full-system-assembly","page":"Assembly","title":"Full system assembly","text":"","category":"section"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"We will now compare the four strategies in a more realistic scenario where we assemble all elements. This is to put the assembly performance in relation to other operations in the finite element program. After all, assembly performance might not matter in the end if other things dominate the runtime anyway.","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"For this comparison we simply consider the heat equation (see Tutorial 1: Heat equation) and assemble the global matrix.","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"function assemble_system!(assembler_function::F, K, dh, cv) where {F}\n assembler = start_assemble(K)\n ke = zeros(ndofs_per_cell(dh), ndofs_per_cell(dh))\n n = getnbasefunctions(cv)\n for cell in CellIterator(dh)\n reinit!(cv, cell)\n ke .= 0\n for qp in 1:getnquadpoints(cv)\n dΩ = getdetJdV(cv, qp)\n for i in 1:n\n ∇ϕi = shape_gradient(cv, qp, i)\n for j in 1:n\n ∇ϕj = shape_gradient(cv, qp, j)\n ke[i, j] += ( ∇ϕi ⋅ ∇ϕj ) * dΩ\n end\n end\n end\n assembler_function(assembler, K, celldofs(cell), ke)\n end\n return\nend\nnothing # hide","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"Finally, we need cellvalues for the field in order to perform the integration:","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"qr = QuadratureRule{RefTriangle}(2)\nconst cellvalues = CellValues(qr, ip)\nnothing # hide","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"We can now time the four assembly strategies:","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"res = 1138.8803468514259 # hide\n# assemble_system!(assemble_v1, K, dh, cellvalues) # hide\n# @assert norm(K.nzval) ≈ res # hide\nassemble_system!(assemble_v2, K, dh, cellvalues) # hide\n@assert norm(K.nzval) ≈ res # hide\nassemble_system!(assemble_v3, K, dh, cellvalues) # hide\n@assert norm(K.nzval) ≈ res # hide\nassemble_system!(assemble_v4, K, dh, cellvalues) # hide\n@assert norm(K.nzval) ≈ res # hide\nnothing # hide","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"@time assemble_system!(assemble_v1, K, dh, cellvalues)\n@time assemble_system!(assemble_v2, K, dh, cellvalues)\n@time assemble_system!(assemble_v3, K, dh, cellvalues)\n@time assemble_system!(assemble_v4, K, dh, cellvalues)","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"We then obtain the following results (running on the same machine as above):","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"12.175625 seconds (719.99 k allocations: 149.809 GiB, 11.59% gc time)\n 0.009313 seconds (8 allocations: 928 bytes)\n 0.006055 seconds (8 allocations: 928 bytes)\n 0.004530 seconds (10 allocations: 1.062 KiB)","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"This follows the same trend as for the benchmarks for individual cell assembly and shows that the efficiency of the assembly strategy is crucial for the overall performance of the program. In particular this benchmark shows that allocations in such a tight loop from the first strategy is very costly and puts a strain on the garbage collector: 11% of the time is spent in GC instead of crunching numbers.","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"It should of course be noted that the more expensive the element routine is, the less the performance of the assembly strategy matters for the total runtime. However, there are no reason not to use the fastest method given that it is readily available in Ferrite.","category":"page"},{"location":"devdocs/elements/#devdocs-elements","page":"Elements and cells","title":"Elements and cells","text":"","category":"section"},{"location":"devdocs/elements/#Type-definitions","page":"Elements and cells","title":"Type definitions","text":"","category":"section"},{"location":"devdocs/elements/","page":"Elements and cells","title":"Elements and cells","text":"Elements or cells are subtypes of AbstractCell{<:AbstractRefShape}. As shown, they are parametrized by the associated reference element.","category":"page"},{"location":"devdocs/elements/#Required-methods-to-implement-for-all-subtypes-of-AbstractCell-to-define-a-new-element","page":"Elements and cells","title":"Required methods to implement for all subtypes of AbstractCell to define a new element","text":"","category":"section"},{"location":"devdocs/elements/","page":"Elements and cells","title":"Elements and cells","text":"Ferrite.get_node_ids","category":"page"},{"location":"devdocs/elements/#Ferrite.get_node_ids","page":"Elements and cells","title":"Ferrite.get_node_ids","text":"Ferrite.get_node_ids(c::AbstractCell)\n\nReturn the node id's for cell c in the order determined by the cell's reference cell.\n\nDefault implementation: c.nodes.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/elements/#Common-utilities-and-definitions-when-working-with-grids-internally.","page":"Elements and cells","title":"Common utilities and definitions when working with grids internally.","text":"","category":"section"},{"location":"devdocs/elements/","page":"Elements and cells","title":"Elements and cells","text":"First we have some topological queries on the element","category":"page"},{"location":"devdocs/elements/","page":"Elements and cells","title":"Elements and cells","text":"Ferrite.vertices(::Ferrite.AbstractCell)\nFerrite.edges(::Ferrite.AbstractCell)\nFerrite.faces(::Ferrite.AbstractCell)\nFerrite.facets(::Ferrite.AbstractCell)\nFerrite.boundaryfunction(::Type{<:Ferrite.BoundaryIndex})\nFerrite.reference_vertices(::Ferrite.AbstractCell)\nFerrite.reference_edges(::Ferrite.AbstractCell)\nFerrite.reference_faces(::Ferrite.AbstractCell)","category":"page"},{"location":"devdocs/elements/#Ferrite.vertices-Tuple{Ferrite.AbstractCell}","page":"Elements and cells","title":"Ferrite.vertices","text":"Ferrite.vertices(::AbstractCell)\n\nReturns a tuple with the node indices (of the nodes in a grid) for each vertex in a given cell. This function induces the VertexIndex, where the second index corresponds to the local index into this tuple.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/elements/#Ferrite.edges-Tuple{Ferrite.AbstractCell}","page":"Elements and cells","title":"Ferrite.edges","text":"Ferrite.edges(::AbstractCell)\n\nReturns a tuple of 2-tuples containing the ordered node indices (of the nodes in a grid) corresponding to the vertices that define an oriented edge. This function induces the EdgeIndex, where the second index corresponds to the local index into this tuple.\n\nNote that the vertices are sufficient to define an edge uniquely.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/elements/#Ferrite.faces-Tuple{Ferrite.AbstractCell}","page":"Elements and cells","title":"Ferrite.faces","text":"Ferrite.faces(::AbstractCell)\n\nReturns a tuple of n-tuples containing the ordered node indices (of the nodes in a grid) corresponding to the vertices that define an oriented face. This function induces the FaceIndex, where the second index corresponds to the local index into this tuple.\n\nAn oriented face is a face with the first node having the local index and the other nodes spanning such that the normal to the face is pointing outwards.\n\nNote that the vertices are sufficient to define a face uniquely.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/elements/#Ferrite.facets-Tuple{Ferrite.AbstractCell}","page":"Elements and cells","title":"Ferrite.facets","text":"Ferrite.facets(::AbstractCell)\n\nReturns a tuple of n-tuples containing the ordered node indices (of the nodes in a grid) corresponding to the vertices that define an oriented facet. This function induces the FacetIndex, where the second index corresponds to the local index into this tuple.\n\nSee also vertices, edges, and faces\n\n\n\n\n\n","category":"method"},{"location":"devdocs/elements/#Ferrite.boundaryfunction-Tuple{Type{<:Ferrite.BoundaryIndex}}","page":"Elements and cells","title":"Ferrite.boundaryfunction","text":"boundaryfunction(::Type{<:BoundaryIndex})\n\nHelper function to dispatch on the correct entity from a given boundary index.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/elements/#Ferrite.reference_vertices-Tuple{Ferrite.AbstractCell}","page":"Elements and cells","title":"Ferrite.reference_vertices","text":"reference_vertices(::Type{<:AbstractRefShape})\nreference_vertices(::AbstractCell)\n\nReturns a tuple of integers containing the local node indices corresponding to the vertices (i.e. corners or endpoints) of the cell.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/elements/#Ferrite.reference_edges-Tuple{Ferrite.AbstractCell}","page":"Elements and cells","title":"Ferrite.reference_edges","text":"reference_edges(::Type{<:AbstractRefShape})\nreference_edges(::AbstractCell)\n\nReturns a tuple of 2-tuples containing the ordered local node indices (corresponding to the vertices) that define an edge.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/elements/#Ferrite.reference_faces-Tuple{Ferrite.AbstractCell}","page":"Elements and cells","title":"Ferrite.reference_faces","text":"reference_faces(::Type{<:AbstractRefShape})\nreference_faces(::AbstractCell)\n\nReturns a tuple of n-tuples containing the ordered local node indices (corresponding to the vertices) that define a face.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/elements/","page":"Elements and cells","title":"Elements and cells","text":"and some generic utils which are commonly found in finite element codes","category":"page"},{"location":"devdocs/elements/","page":"Elements and cells","title":"Elements and cells","text":"Ferrite.BoundaryIndex\nFerrite.get_coordinate_eltype(::Ferrite.AbstractGrid)\nFerrite.get_coordinate_eltype(::Node)\nFerrite.toglobal\nFerrite.sortface\nFerrite.sortface_fast\nFerrite.sortedge\nFerrite.sortedge_fast\nFerrite.element_to_facet_transformation\nFerrite.facet_to_element_transformation\nFerrite.InterfaceOrientationInfo\nFerrite.transform_interface_points!\nFerrite.get_transformation_matrix","category":"page"},{"location":"devdocs/elements/#Ferrite.BoundaryIndex","page":"Elements and cells","title":"Ferrite.BoundaryIndex","text":"Abstract type which is used as identifier for faces, edges and verices\n\n\n\n\n\n","category":"type"},{"location":"devdocs/elements/#Ferrite.get_coordinate_eltype-Tuple{Ferrite.AbstractGrid}","page":"Elements and cells","title":"Ferrite.get_coordinate_eltype","text":"Return the number type of the nodal coordinates.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/elements/#Ferrite.get_coordinate_eltype-Tuple{Node}","page":"Elements and cells","title":"Ferrite.get_coordinate_eltype","text":"get_coordinate_eltype(::Node)\n\nGet the data type of the components of the nodes coordinate.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/elements/#Ferrite.toglobal","page":"Elements and cells","title":"Ferrite.toglobal","text":"toglobal(grid::AbstractGrid, vertexidx::VertexIndex) -> Int\ntoglobal(grid::AbstractGrid, vertexidx::Vector{VertexIndex}) -> Vector{Int}\n\nThis function takes the local vertex representation (a VertexIndex) and looks up the unique global id (an Int).\n\n\n\n\n\n","category":"function"},{"location":"devdocs/elements/#Ferrite.sortface","page":"Elements and cells","title":"Ferrite.sortface","text":"sortface(face::Tuple{Int})\nsortface(face::Tuple{Int,Int})\nsortface(face::Tuple{Int,Int,Int})\nsortface(face::Tuple{Int,Int,Int,Int})\n\nReturns the unique representation of a face. Here the unique representation is the sorted node index tuple. Note that in 3D we only need indices to uniquely identify a face, so the unique representation is always a tuple length 3.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/elements/#Ferrite.sortface_fast","page":"Elements and cells","title":"Ferrite.sortface_fast","text":"sortface_fast(face::Tuple{Int})\nsortface_fast(face::Tuple{Int,Int})\nsortface_fast(face::Tuple{Int,Int,Int})\nsortface_fast(face::Tuple{Int,Int,Int,Int})\n\nReturns the unique representation of a face. Here the unique representation is the sorted node index tuple. Note that in 3D we only need indices to uniquely identify a face, so the unique representation is always a tuple length 3.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/elements/#Ferrite.sortedge","page":"Elements and cells","title":"Ferrite.sortedge","text":"sortedge(edge::Tuple{Int,Int})\n\nReturns the unique representation of an edge and its orientation. Here the unique representation is the sorted node index tuple. The orientation is true if the edge is not flipped, where it is false if the edge is flipped.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/elements/#Ferrite.sortedge_fast","page":"Elements and cells","title":"Ferrite.sortedge_fast","text":"sortedge_fast(edge::Tuple{Int,Int})\n\nReturns the unique representation of an edge. Here the unique representation is the sorted node index tuple.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/elements/#Ferrite.element_to_facet_transformation","page":"Elements and cells","title":"Ferrite.element_to_facet_transformation","text":"element_to_facet_transformation(point::AbstractVector, ::Type{<:AbstractRefShape}, facet::Int)\n\nTransform quadrature point from the cell's coordinates to the facet's reference coordinates, decreasing the number of dimensions by one. This is the inverse of facet_to_element_transformation.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/elements/#Ferrite.facet_to_element_transformation","page":"Elements and cells","title":"Ferrite.facet_to_element_transformation","text":"facet_to_element_transformation(point::Vec, ::Type{<:AbstractRefShape}, facet::Int)\n\nTransform quadrature point from the facet's reference coordinates to coordinates on the cell's facet, increasing the number of dimensions by one.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/elements/#Ferrite.InterfaceOrientationInfo","page":"Elements and cells","title":"Ferrite.InterfaceOrientationInfo","text":"InterfaceOrientationInfo\n\nRelative orientation information for 1D and 2D interfaces in 2D and 3D elements respectively. This information is used to construct the transformation matrix to transform the quadrature points from faceta to facetb achieving synced spatial coordinates. Face B's orientation relative to Face A's can possibly be flipped (i.e. the vertices indices order is reversed) and the vertices can be rotated against each other. The reference orientation of face B is such that the first node has the lowest vertex index. Thus, this structure also stores the shift of the lowest vertex index which is used to reorient the face in case of flipping transform_interface_points!.\n\n\n\n\n\n","category":"type"},{"location":"devdocs/elements/#Ferrite.transform_interface_points!","page":"Elements and cells","title":"Ferrite.transform_interface_points!","text":"transform_interface_points!(dst::AbstractVector{Vec{3, Float64}}, points::AbstractVector{Vec{3, Float64}}, interface_transformation::InterfaceOrientationInfo)\n\nTransform the points from face A to face B using the orientation information of the interface and store it in the vector dst. For 3D, the faces are transformed into regular polygons such that the rotation angle is the shift in reference node index × 2π ÷ number of edges in face. If the face is flipped then the flipping is about the axis that preserves the position of the first node (which is the reference node after being rotated to be in the first position, it's rotated back in the opposite direction after flipping). Take for example the interface\n\n 2 3\n | \\ | \\\n | \\ | \\\ny | A \\ | B \\\n↑ | \\ | \\\n→ x 1-----3 1-----2\n\nTransforming A to an equilateral triangle and translating it such that {0,0} is equidistant to all nodes\n\n 3\n +\n / \\\n / \\\n / x \\\n / ↑ \\\n / ← \\\n / y \\\n2+-------------+1\n\nRotating it -270° (or 120°) such that the reference node (the node with the smallest index) is at index 1\n\n 1\n +\n / \\\n / \\\n / x \\\n / ↑ \\\n / ← \\\n / y \\\n3+-------------+2\n\nFlipping about the x axis (such that the position of the reference node doesn't change) and rotating 270° (or -120°)\n\n 2\n +\n / \\\n / \\\n / x \\\n / ↑ \\\n / ← \\\n / y \\\n3+-------------+1\n\nTransforming back to triangle B\n\n 3\n | \\\n | \\\ny | \\\n↑ | \\\n→ x 1-----2\n\n\n\n\n\n","category":"function"},{"location":"devdocs/elements/#Ferrite.get_transformation_matrix","page":"Elements and cells","title":"Ferrite.get_transformation_matrix","text":"get_transformation_matrix(interface_transformation::InterfaceOrientationInfo)\n\nReturns the transformation matrix corresponding to the interface orientation information stored in InterfaceOrientationInfo. The transformation matrix is constructed using a combination of affine transformations defined for each interface reference shape. The transformation for a flipped face is a function of both relative orientation and the orientation of the second face. If the face is not flipped then the transformation is a function of relative orientation only.\n\n\n\n\n\n","category":"function"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"EditURL = \"https://github.com/Ferrite-FEM/Ferrite.jl/blob/master/CHANGELOG.md\"","category":"page"},{"location":"changelog/#Ferrite-changelog","page":"Changelog","title":"Ferrite changelog","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"All notable changes to this project will be documented in this file.","category":"page"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.","category":"page"},{"location":"changelog/#[Unreleased]","page":"Changelog","title":"[Unreleased]","text":"","category":"section"},{"location":"changelog/#Removed","page":"Changelog","title":"Removed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"The deprecated third type parameter for interpolations have been removed. Old code which tries to use three parameters will now throw the somewhat cryptic error:\njulia> Lagrange{2, RefCube, 1}()\nERROR: too many parameters for type\n(#1083)","category":"page"},{"location":"changelog/#Other","page":"Changelog","title":"Other","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Ferrite now uses Runic.jl for code formatting. (#1096)","category":"page"},{"location":"changelog/#[v1.0.0](https://github.com/Ferrite-FEM/Ferrite.jl/releases/tag/v1.0.0)-2024-09-30","page":"Changelog","title":"v1.0.0 - 2024-09-30","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Ferrite version 1.0 is a relatively large release, with a lot of new features, improvements, deprecations and some removals. These changes are made to make the code base more consistent and more suitable for future improvements. With this 1.0 release we are aiming for long time stability, and there is no breaking release 2.0 on the horizon.","category":"page"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Unfortunately this means that code written for Ferrite version 0.3 will have to be updated. All changes, with upgrade paths, are listed in the sections below. Since these sections include a lot of other information as well (new features, internal changes, ...) there is also a dedicated section about upgrading code from Ferrite 0.3 to 1.0 (see below) which include the most common changes that are required. In addition, in all cases where possible, you will be presented with a descriptive error message telling you what needs to change.","category":"page"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Deprecations for 1.0 will be removed during the 1.x release series. When upgrading old code it is therefore recommended to use Ferrite 1.0 as a first stepping stone since this release contain descriptive deprecation error messages that might not exist in e.g. Ferrite version 1.2.","category":"page"},{"location":"changelog/#Upgrading-code-from-Ferrite-0.3-to-1.0","page":"Changelog","title":"Upgrading code from Ferrite 0.3 to 1.0","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"This section give a short overview of the most common required changes. More details and motivation are included in the following sections (with links to issues/pull request for more discussion).","category":"page"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Interpolations: remove the first parameter (the reference dimension) and use new reference shapes.\nExamples:\n# Linear Lagrange interpolation for a line\n- Lagrange{1, RefCube, 1}()\n+ Lagrange{RefLine, 1}()\n\n# Linear Lagrange interpolation for a quadrilateral\n- Lagrange{2, RefCube, 1}()\n+ Lagrange{RefQuadrilateral, 1}()\n\n# Quadratic Lagrange interpolation for a triangle\n- Lagrange{2, RefTetrahedron, 2}()\n+ Lagrange{RefTriangle, 2}()\nFor vector valued problems it is now required to explicitly vectorize the interpolation using the new VectorizedInterpolation. This is required when passing the interpolation to CellValues and when adding fields to the DofHandler using add!. In both of these places the interpolation was implicitly vectorized in Ferrite 0.3.\nExamples:\n# Linear Lagrange interpolation for a vector problem on the triangle (vector dimension\n# same as the reference dimension)\nip_scalar = Lagrange{RefTriangle, 1}()\nip_vector = ip_scalar ^ 2 # or VectorizedInterpolation{2}(ip_scalar)\nQuadrature: remove the first parameter (the reference dimension) and use new reference shapes.\nExamples:\n# Quadrature for a line\n- QuadratureRule{1, RefCube}(quadrature_order)\n+ QuadratureRule{RefLine}(quadrature_order)\n\n# Quadrature for a quadrilateral\n- QuadratureRule{2, RefCube}(quadrature_order)\n+ QuadratureRule{RefQuadrilateral}(quadrature_order)\n\n# Quadrature for a tetrahedron\n- QuadratureRule{3, RefTetrahedron}(quadrature_order)\n+ QuadratureRule{RefTetrahedron}(quadrature_order)\nQuadrature for face integration (FacetValues): replace QuadratureRule{dim-1, reference_shape}(quadrature_order) with FacetQuadratureRule{reference_shape}(quadrature_order).\nExamples:\n# Quadrature for the facets of a quadrilateral\n- QuadratureRule{1, RefCube}(quadrature_order)\n+ FacetQuadratureRule{RefQuadrilateral}(quadrature_order)\n\n# Quadrature for the facets of a triangle\n- QuadratureRule{1, RefTetrahedron}(quadrature_order)\n+ FacetQuadratureRule{RefTriangle}(quadrature_order)\n\n# Quadrature for the facets of a hexhedron\n- QuadratureRule{2, RefCube}(quadrature_order)\n+ FacetQuadratureRule{RefHexahedron}(quadrature_order)\nCellValues: replace usage of CellScalarValues and CellVectorValues with CellValues. For vector valued problems the interpolation passed to CellValues should be vectorized to a VectorizedInterpolation (see above).\nExamples:\n# CellValues for a scalar problem with triangle elements\n- qr = QuadratureRule{2, RefTetrahedron}(quadrature_order)\n- ip = Lagrange{2, RefTetrahedron, 1}()\n- cv = CellScalarValues(qr, ip)\n+ qr = QuadratureRule{RefTriangle}(quadrature_order)\n+ ip = Lagrange{RefTriangle, 1}()\n+ cv = CellValues(qr, ip)\n\n# CellValues for a vector problem with hexahedronal elements\n- qr = QuadratureRule{3, RefCube}(quadrature_order)\n- ip = Lagrange{3, RefCube, 1}()\n- cv = CellVectorValues(qr, ip)\n+ qr = QuadratureRule{RefHexahedron}(quadrature_order)\n+ ip = Lagrange{RefHexahedron, 1}() ^ 3\n+ cv = CellValues(qr, ip)\nIf you use CellScalarValues or CellVectorValues in method signature you must replace them with CellValues. Note that the type parameters are different.\nExamples:\n- function do_something(cvs::CellScalarValues, cvv::CellVectorValues)\n+ function do_something(cvs::CellValues, cvv::CellValues)\nThe default geometric interpolation have changed from the function interpolation to always use linear Lagrange interpolation. If you use linear elements in the grid, and a higher order interpolation for the function you can now rely on the new default:\nqr = QuadratureRule(...)\n- ip_function = Lagrange{2, RefTetrahedron, 2}()\n- ip_geometry = Lagrange{2, RefTetrahedron, 1}()\n- cv = CellScalarValues(qr, ip_function, ip_geometry)\n+ ip_function = Lagrange{2, RefTetrahedron, 2}()\n+ cv = CellValues(qr, ip_function)\nand if you have quadratic (or higher order) elements in the grid you must now pass the corresponding interpolation to the constructor:\nqr = QuadratureRule(...)\n- ip_function = Lagrange{2, RefTetrahedron, 2}()\n- cv = CellScalarValues(qr, ip_function)\n+ ip_function = Lagrange{2, RefTetrahedron, 2}()\n+ ip_geometry = Lagrange{2, RefTetrahedron, 1}()\n+ cv = CellValues(qr, ip_function, ip_geometry)\nFacetValues: replace usage of FaceScalarValues and FaceVectorValues with FacetValues. For vector valued problems the interpolation passed to FacetValues should be vectorized to a VectorizedInterpolation (see above). The input quadrature rule should be a FacetQuadratureRule instead of a QuadratureRule.\nExamples:\n# FacetValues for a scalar problem with triangle elements\n- qr = QuadratureRule{1, RefTetrahedron}(quadrature_order)\n- ip = Lagrange{2, RefTetrahedron, 1}()\n- cv = FaceScalarValues(qr, ip)\n+ qr = FacetQuadratureRule{RefTriangle}(quadrature_order)\n+ ip = Lagrange{RefTriangle, 1}()\n+ cv = FacetValues(qr, ip)\n\n# FaceValues for a vector problem with hexahedronal elements\n- qr = QuadratureRule{2, RefCube}(quadrature_order)\n- ip = Lagrange{3, RefCube, 1}()\n- cv = FaceVectorValues(qr, ip)\n+ qr = FacetQuadratureRule{RefHexahedron}(quadrature_order)\n+ ip = Lagrange{RefHexahedron, 1}() ^ 3\n+ cv = FacetValues(qr, ip)\nDofHandler construction: it is now required to pass the interpolation explicitly when adding new fields using add! (previously it was optional, defaulting to the default interpolation of the elements in the grid). For vector-valued fields the interpolation should be vectorized, instead of passing the number of components to add! as an integer.\nExamples:\ndh = DofHandler(grid) # grid with triangles\n\n# Vector field :u\n- add!(dh, :u, 2)\n+ add!(dh, :u, Lagrange{RefTriangle, 1}()^2)\n\n# Scalar field :p\n- add!(dh, :u, 1)\n+ add!(dh, :u, Lagrange{RefTriangle, 1}())\nBoundary conditions: The entity enclosing a cell was previously called face, but is now denoted a facet. When applying boundary conditions, rename getfaceset to getfacetset and addfaceset! is now addfacetset!. These sets are now described by FacetIndex instead of FaceIndex. When looping over the facets of a cell, change nfaces to nfacets.\nExamples:\n# Dirichlet boundary conditions\n- addfaceset!(grid, \"dbc\", x -> x[1] ≈ 1.0)\n+ addfacetset!(grid, \"dbc\", x -> x[1] ≈ 1.0)\n\n- dbc = Dirichlet(:u, getfaceset(grid, \"dbc\"), Returns(0.0))\n+ dbc = Dirichlet(:u, getfacetset(grid, \"dbc\"), Returns(0.0))\n\n# Neumann boundary conditions\n- for facet in 1:nfaces(cell)\n- if (cellid(cell), facet) ∈ getfaceset(grid, \"Neumann Boundary\")\n+ for facet in 1:nfacets(cell)\n+ if (cellid(cell), facet) ∈ getfacetset(grid, \"Neumann Boundary\")\n # ...\nVTK Export: The VTK export has been changed #692.\n- vtk_grid(name, dh) do vtk\n- vtk_point_data(vtk, dh, a)\n- vtk_point_data(vtk, nodal_data, \"my node data\")\n- vtk_point_data(vtk, proj, projected_data, \"my projected data\")\n- vtk_cell_data(vtk, proj, projected_data, \"my projected data\")\n+ VTKGridFile(name, dh) do vtk\n+ write_solution(vtk, dh, a)\n+ write_node_data(vtk, nodal_data, \"my node data\")\n+ write_projection(vtk, proj, projected_data, \"my projected data\")\n+ write_cell_data(vtk, cell_data, \"my projected data\")\nend\nWhen using a paraview_collection collection for e.g. multiple timesteps the VTKGridFile object can be used instead of the previous type returned from vtk_grid.\nSparsity pattern and global matrix construction: since there is now explicit support for working with the sparsity pattern before instantiating a matrix the function create_sparsity_pattern has been removed. To recover the old functionality that return a sparse matrix from the DofHandler directly use allocate_matrix instead.\nExamples:\n# Create sparse matrix from DofHandler\n- K = create_sparsity_pattern(dh)\n+ K = allocate_matrix(dh)\n\n# Create condensed sparse matrix from DofHandler + ConstraintHandler\n- K = create_sparsity_pattern(dh, ch)\n+ K = allocate_matrix(dh, ch)","category":"page"},{"location":"changelog/#Added","page":"Changelog","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"InterfaceValues for computing jumps and averages over interfaces. (#743)\nInterfaceIterator and InterfaceCache for iterating over interfaces. (#747)\nFacetQuadratureRule implementation for RefPrism and RefPyramid. (#779)\nThe DofHandler now support selectively adding fields on sub-domains (rather than the full domain). This new functionality is included with the new SubDofHandler struct, which, as the name suggest, is a DofHandler for a subdomain. (#624, #667, #735)\nNew reference shape structs RefLine, RefTriangle, RefQuadrilateral, RefTetrahedron, RefHexahedron, and RefPrism have been added. These encode the reference dimension, and will thus replace the old reference shapes for which it was necessary to always pair with an explicit dimension (i.e. RefLine replaces (RefCube, 1), RefTriangle replaces (RefTetrahedron, 2), etc.). For writing \"dimension independent code\" it is possible to use Ferrite.RefHypercube{dim} and Ferrite.RefSimplex{dim}. (#679)\nNew methods for adding entitysets that are located on the boundary of the grid: addboundaryfacetset! and addboundaryvertexset!. These work similar to addfacetset! and addvertexset!, but filters out all instances not on the boundary (this can be used to avoid accidental inclusion of internal entities in sets used for boundary conditions, for example). (#606)\nNew interpolation VectorizedInterpolation which vectorizes scalar interpolations for vector-valued problems. A VectorizedInterpolation is created from a (scalar) interpolation ip using either ip ^ dim or VectorizedInterpolation{dim}(ip). For convenience, the method VectorizedInterpolation(ip) vectorizes the interpolation to the reference dimension of the interpolation. (#694, #736)\nNew (scalar) interpolation Lagrange{RefQuadrilateral, 3}(), i.e. third order Lagrange interpolation for 2D quadrilaterals. (#701, #731)\nCellValues now support embedded elements. Specifically you can now embed elements with reference dimension 1 into spatial dimension 2 or 3, and elements with reference dimension 2 in to spatial dimension 3. (#651)\nCellValues now support (vector) interpolations with dimension different from the spatial dimension. (#651)\nFacetQuadratureRule have been added and should be used for FacetValues. A FacetQuadratureRule for integration of the facets of e.g. a triangle can be constructed by FacetQuadratureRule{RefTriangle}(order) (similar to how QuadratureRule is constructed). (#716)\nNew functions Ferrite.reference_shape_value(::Interpolation, ξ::Vec, i::Int) and Ferrite.reference_shape_gradient(::Interpolation, ξ::Vec, i::Int) for evaluating the value/gradient of the ith shape function of an interpolation in local reference coordinate ξ. These methods are public but not exported. (Note that these methods return the value/gradient wrt. the reference coordinate ξ, whereas the corresponding methods for CellValues etc return the value/gradient wrt the spatial coordinate x.) (#721)\nFacetIterator and FacetCache have been added. These work similarly to CellIterator and CellCache but are used to iterate over (boundary) face sets instead. These simplify boundary integrals in general, and in particular Neumann boundary conditions are more convenient to implement now that you can loop directly over the face set instead of checking all faces of a cell inside the element routine. (#495)\nThe ConstraintHandler now support adding Dirichlet boundary conditions on discontinuous interpolations. (#729)\ncollect_periodic_faces now have a keyword argument tol that can be used to relax the default tolerance when necessary. (#749)\nVTK export now work with QuadraticHexahedron elements. (#714)\nThe function bounding_box(::AbstractGrid) has been added. It computes the bounding box for a given grid (based on its node coordinates), and returns the minimum and maximum vertices of the bounding box. (#880)\nSupport for working with sparsity patterns has been added. This means that Ferrite exposes the intermediate \"state\" between the DofHandler and the instantiated matrix as the new struct SparsityPattern. This make it possible to insert custom equations or couplings in the pattern before instantiating the matrix. The function create_sparsity_pattern have been removed. The new function allocate_matrix is instead used to instantiate the matrix. Refer to the documentation for more details. (#888)\nTo upgrade: if you want to recover the old functionality and don't need to work with the pattern, replace any usage of create_sparsity_pattern with allocate_matrix.\nA new function, geometric_interpolation, is exported, which gives the geometric interpolation for each cell type. This is equivalent to the deprecated Ferrite.default_interpolation function. (#953)\nCellValues and FacetValues can now store and map second order gradients (Hessians). The number of gradients computed in CellValues/FacetValues is specified using the keyword arguments update_gradients::Bool (default true) and update_hessians::Bool (default false) in the constructors, i.e. CellValues(...; update_hessians=true). (#953)\nL2Projector supports projecting on grids with mixed celltypes. (#949)","category":"page"},{"location":"changelog/#Changed","page":"Changelog","title":"Changed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"It is now possible to create sparsity patterns with interface couplings, see the new function add_interface_entries! and the rework of sparsity pattern construction. (#710)\nThe AbstractCell interface has been reworked. This change should not affect user code, but may in some cases be relevant for code parsing external mesh files. In particular, the generic Cell struct have been removed in favor of concrete cell implementations (Line, Triangle, ...). (#679, #712)\nTo upgrade replace any usage of Cell{...}(...) with calls to the concrete implementations.\nThe default geometric mapping in CellValues and FacetValues have changed. The new default is to always use Lagrange{refshape, 1}(), i.e. linear Lagrange polynomials, for the geometric interpolation. Previously, the function interpolation was (re) used also for the geometry interpolation. (#695)\nTo upgrade, if you relied on the previous default, simply pass the function interpolation also as the third argument (the geometric interpolation).\nAll interpolations are now categorized as either scalar or vector interpolations. All (previously) existing interpolations are scalar. (Scalar) interpolations must now be explicitly vectorized, using the new VectorizedInterpolation, when used for vector problems. (Previously implicit vectorization happened in the CellValues constructor, and when adding fields to the DofHandler). (#694)\nIt is now required to explicitly pass the interpolation to the DofHandler when adding a new field using add!. For vector fields the interpolation should be vectorized, instead of passing number of components as an integer. (#694)\nTo upgrade don't pass the dimension as an integer, and pass the interpolation explicitly. See more details in Upgrading code from Ferrite 0.3 to 1.0.\nInterpolations should now be constructed using the new reference shapes. Since the new reference shapes encode the reference dimension the first type parameter of interpolations have been removed. (#711) To upgrade replace e.g. Lagrange{1, RefCube, 1}() with Lagrange{RefLine, 1}(), and Lagrange{2, RefTetrahedron, 1}() with Lagrange{RefTriangle, 1}(), etc.\nQuadratureRules should now be constructed using the new reference shapes. Since the new reference shapes encode the reference dimension the first type parameter of QuadratureRule have been removed. (#711, #716) To upgrade replace e.g. QuadratureRule{1, RefCube}(order) with QuadratureRule{RefLine}(order), and QuadratureRule{2, RefTetrahedron}(1) with Lagrange{RefTriangle}(order), etc.\nCellScalarValues and CellVectorValues have been merged into CellValues, FaceScalarValues and FaceVectorValues have been merged into FacetValues, and PointScalarValues and PointVectorValues have been merged into PointValues. The differentiation between scalar and vector have thus been moved to the interpolation (see above). Note that previously CellValues, FaceValues, and PointValues where abstract types, but they are now concrete implementations with different type parameters, except FaceValues which is now FacetValues (#708) To upgrade, for scalar problems, it is enough to replace CellScalarValues with CellValues, FaceScalarValues with FacetValues and PointScalarValues with PointValues, respectively. For vector problems, make sure to vectorize the interpolation (see above) and then replace CellVectorValues with CellValues, FaceVectorValues with FacetValues, and PointVectorValues with PointValues.\nThe quadrature rule passed to FacetValues should now be of type FacetQuadratureRule rather than of type QuadratureRule. (#716) To upgrade replace the quadrature rule passed to FacetValues with a FacetQuadratureRule.\nChecking if a face (ele_id, local_face_id) ∈ faceset has been previously implemented by type piracy. In order to be invariant to the underlying Set datatype as well as omitting type piracy, (#835) implemented isequal and hash for BoundaryIndex datatypes.\nVTK export: Ferrite no longer extends WriteVTK.vtk_grid and associated functions, instead the new type VTKGridFile should be used instead. New methods exists for writing to a VTKGridFile, e.g. write_solution, write_cell_data, write_node_data, and write_projection. See #692.\nDefinitions: Previously, face and edge referred to codimension 1 relative reference shape. In Ferrite v1, volume, face, edge, and vertex refer to 3, 2, 1, and 0 dimensional entities, and facet replaces the old definition of face. No direct replacement for edges exits. See #914 and #914. The main implications of this change are\nFaceIndex -> FacetIndex (FaceIndex still exists, but has a different meaning)\nFaceValues -> FacetValues\nnfaces -> nfacets (nfaces is now an internal method with different meaning)\naddfaceset! -> addfacetset\ngetfaceset -> getfacetset\nFurthermore, subtypes of Interpolation should now define vertexdof_indices, edgedof_indices, facedof_indices, volumedof_indices (and similar) according to these definitions.\nFerrite.getdim has been changed into Ferrite.getrefdim for getting the dimension of the reference shape and Ferrite.getspatialdim to get the spatial dimension (of the grid). (#943)\nFerrite.getfielddim(::AbstractDofHandler, args...) has been renamed to Ferrite.n_components. (#943)\nThe constructor for ExclusiveTopology only accept an AbstractGrid as input, removing the alternative of providing a Vector{<:AbstractCell}, as knowing the spatial dimension is required for correct code paths. Furthermore, it uses a new internal data structure, ArrayOfVectorViews, to store the neighborhood information more efficiently The datatype for the neighborhood has thus changed to a view of a vector, instead of the now removed EntityNeighborhood container. This also applies to vertex_star_stencils. (#974).\nproject(::L2Projector, data, qr_rhs) now expects data to be indexed by the cellid, as opposed to the index in the vector of cellids passed to the L2Projector. The data may be passed as an AbstractDict{Int, <:AbstractVector}, as an alternative to AbstractArray{<:AbstractVector}. (#949)","category":"page"},{"location":"changelog/#Deprecated","page":"Changelog","title":"Deprecated","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"The rarely (if ever) used methods of function_value, function_gradient, function_divergence, and function_curl taking vectorized dof values as in put have been deprecated. (#698)\nThe function reshape_to_nodes have been deprecated in favor of evaluate_at_grid_nodes. (#703)\nstart_assemble([n::Int]) has been deprecated in favor of calling Ferrite.COOAssembler() directly (#916, #1058).\nstart_assemble(f, K) have been deprecated in favor of the \"canonical\" start_assemble(K, f). (#707)\nassemble!(assembler, dofs, fe, Ke) have been deprecated in favor of the \"canonical\" assemble!(assembler, dofs, Ke, fe). (#1059)\nend_assemble have been deprecated in favor of finish_assemble. (#754)\nget_point_values have been deprecated in favor of evaluate_at_points. (#754)\ntransform! have been deprecated in favor of transform_coordinates!. (#754)\nFerrite.default_interpolation has been deprecated in favor of geometric_interpolation. (#953)","category":"page"},{"location":"changelog/#Removed-2","page":"Changelog","title":"Removed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"MixedDofHandler + FieldHandler have been removed in favor of DofHandler + SubDofHandler. Note that the syntax has changed, and note that SubDofHandler is much more capable compared to FieldHandler. Previously it was often required to pass both the MixedDofHandler and the FieldHandler to e.g. the assembly routine, but now it is enough to pass the SubDofHandler since it can be used for e.g. DoF queries etc. (#624, #667, #735)\nSome old methods to construct the L2Projector have been removed after being deprecated for several releases. (#697)\nThe option project_to_nodes have been removed from project(::L2Projector, ...). The returned values are now always ordered according to the projectors internal DofHandler. (#699)\nThe function compute_vertex_values have been removed. (#700)\nThe names getweights, getpoints, getcellsets, getnodesets, getfacesets, getedgesets, and getvertexsets have been removed from the list of exported names. (For now you can still use them by prefixing Ferrite., e.g. Ferrite.getweights.) (#754)\nThe onboundary function (and the associated boundary_matrix property of the Grid datastructure) have been removed (#924). Instead of first checking onboundary and then check whether a facet belong to a specific facetset, check the facetset directly. For example:\n- if onboundary(cell, local_face_id) && (cell_id, local_face_id) in getfacesets(grid, \"traction_boundary\")\n+ if (cell_id, local_face_id) in getfacesets(grid, \"traction_boundary\")\n # integrate the \"traction_boundary\" boundary\n end","category":"page"},{"location":"changelog/#Fixed","page":"Changelog","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Benchmarks now work with master branch. (#751, #855)\nTopology construction have been generalized to, in particular, fix construction for 1D and for wedge elements. (#641, #670, #684)","category":"page"},{"location":"changelog/#Other-improvements","page":"Changelog","title":"Other improvements","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Documentation:\nThe documentation is now structured according to the Diataxis framework. There is now also clear separation between tutorials (for teaching) and code gallery (for showing off). (#737, #756)\nNew section in the developer documentation that describes the (new) reference shapes and their numbering scheme. (#688)\nPerformance:\nFerrite.transform!(grid, f) (for transforming the node coordinates in the grid according to a function f) is now faster and allocates less. (#675)\nSlight performance improvement in construction of PointEvalHandler (faster reverse coordinate lookup). (#719)\nVarious performance improvements to topology construction. (#753, #759)\nInternal improvements:\nThe dof distribution interface have been updated to support higher order elements (future work). (#627, #732, #733)\nThe AbstractGrid and AbstractDofHandler interfaces are now used more consistently internally. This will help with the implementation of distributed grids and DofHandlers. (#655)\nVTK export now uses the (geometric) interpolation directly when evaluating the finite element field instead of trying to work backwards how DoFs map to nodes. (#703)\nImproved bounds checking in assemble!. (#706)\nInternal methods Ferrite.value and Ferrite.derivative for computing the value/gradient of all shape functions have been removed. (#720)\nFerrite.create_incidence_matrix now work with any AbstractGrid (not just Grid). (#726)","category":"page"},{"location":"changelog/#[v0.3.14](https://github.com/Ferrite-FEM/Ferrite.jl/releases/tag/v0.3.14)-2023-04-03","page":"Changelog","title":"v0.3.14 - 2023-04-03","text":"","category":"section"},{"location":"changelog/#Added-2","page":"Changelog","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Support reordering dofs of a MixedDofHandler by the built-in orderings FieldWise and ComponentWise. This includes support for reordering dofs of fields on subdomains. (#645)\nSupport specifying the coupling between fields in a MixedDofHandler when creating the sparsity pattern. (#650)\nSupport Metis dof reordering with coupling information for MixedDofHandler. (#650)\nPretty printing for MixedDofHandler and L2Projector. (#465)","category":"page"},{"location":"changelog/#Other-improvements-2","page":"Changelog","title":"Other improvements","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"The MixedDofHandler have gone through a performance review (see #629) and now performs the same as DofHandler. This was part of the push to merge the two DoF handlers. Since MixedDofHandler is strictly more flexible, and now equally performant, it will replace DofHandler in the next breaking release. (#637, #639, #642, #643, #656, #660)","category":"page"},{"location":"changelog/#Internal-changes","page":"Changelog","title":"Internal changes","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Changes listed here should not affect regular usage, but listed here in case you have been poking into Ferrite internals:","category":"page"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Ferrite.ndim(dh, fieldname) has been removed, use Ferrite.getfielddim(dh, fieldname) instead. (#658)\nFerrite.nfields(dh) has been removed, use length(Ferrite.getfieldnames(dh)) instead. (#444, #653)\ngetfielddims(::FieldHandler) and getfieldinterpolations(::FieldHandler) have been removed (#647, #659)","category":"page"},{"location":"changelog/#[v0.3.13](https://github.com/Ferrite-FEM/Ferrite.jl/releases/tag/v0.3.13)-2023-03-23","page":"Changelog","title":"v0.3.13 - 2023-03-23","text":"","category":"section"},{"location":"changelog/#Added-3","page":"Changelog","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Support for classical trilinear and triquadratic wedge elements. (#581)\nSymmetric quadrature rules up to order 10 for prismatic elements. (#581)\nFiner granulation of dof distribution, allowing to distribute different amounts of dofs per entity. (#581)","category":"page"},{"location":"changelog/#Fixed-2","page":"Changelog","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Dof distribution for embedded elements. (#581)\nImprove numerical accuracy in shape function evaluation for the Lagrange{2,Tetrahedron,(3|4|5)} interpolations. (#582, #633)","category":"page"},{"location":"changelog/#Other-improvements-3","page":"Changelog","title":"Other improvements","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Documentation:\nNew \"Developer documentation\" section in the manual for documenting Ferrite.jl internals and developer tools. (#611)\nFix a bug in constraint computation in Stoke's flow example. (#614)\nPerformance:\nBenchmarking infrastructure to help tracking performance changes. (#388)\nPerformance improvements for various accessor functions for MixedDofHandler. (#621)","category":"page"},{"location":"changelog/#Internal-changes-2","page":"Changelog","title":"Internal changes","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"To clarify the dof management vertices(ip), edges(ip) and faces(ip) has been deprecated in favor of vertexdof_indices(ip), edgedof_indices(ip) and facedof_indices(ip). (#581)\nDuplicate grid representation has been removed from the MixedDofHandler. (#577)","category":"page"},{"location":"changelog/#[v0.3.12](https://github.com/Ferrite-FEM/Ferrite.jl/releases/tag/v0.3.12)-2023-02-28","page":"Changelog","title":"v0.3.12 - 2023-02-28","text":"","category":"section"},{"location":"changelog/#Added-4","page":"Changelog","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Added a basic show method for assemblers. (#598)","category":"page"},{"location":"changelog/#Fixed-3","page":"Changelog","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Fix an issue in constraint application of Symmetric-wrapped sparse matrices (i.e. obtained from create_symmatric_sparsity_pattern). In particular, apply!(K::Symmetric, f, ch) would incorrectly modify f if any of the constraints were inhomogeneous. (#592)\nProperly disable the Metis extension on Julia 1.9 instead of causing precompilation errors. (#588)\nFix adding Dirichlet boundary conditions on nodes when using MixedDofHandler. (#593, #594)\nFix accidentally slow implementation of show for Grids. (#599)\nFixes to topology functionality. (#453, #518, #455)\nFix grid coloring for cell sets with 0 or 1 cells. (#600)","category":"page"},{"location":"changelog/#Other-improvements-4","page":"Changelog","title":"Other improvements","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Documentation improvements:\nSimplications and clarifications to hyperelasticity example. (#591)\nRemove duplicate docstring entry for vtk_point_data. (#602)\nUpdate documentation about initial conditions. (#601, #604)","category":"page"},{"location":"changelog/#[v0.3.11](https://github.com/Ferrite-FEM/Ferrite.jl/releases/tag/v0.3.11)-2023-01-17","page":"Changelog","title":"v0.3.11 - 2023-01-17","text":"","category":"section"},{"location":"changelog/#Added-5","page":"Changelog","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Metis.jl extension for fill-reducing DoF permutation. This uses Julias new package extension mechanism (requires Julia 1.10) to support a new DoF renumbering order DofOrder.Ext{Metis}() that can be passed to renumber! to renumber DoFs using the Metis.jl library. (#393, #549)\nBlockArrays.jl extension for creating a globally blocked system matrix. create_sparsity_pattern(BlockMatrix, dh, ch; kwargs...) return a matrix that is blocked by field (requires DoFs to be (re)numbered by field, i.e. renumber!(dh, DofOrder.FieldWise())). For custom blocking it is possible to pass an uninitialized BlockMatrix with the correct block sizes (see BlockArrays.jl docs). This functionality is useful for e.g. special solvers where individual blocks need to be extracted. Requires Julia version 1.9 or above. (#567)\nNew function apply_analytical! for setting the values of the degrees of freedom for a specific field according to a spatial function f(x). (#532)\nNew cache struct CellCache to be used when iterating over the cells in a grid or DoF handler. CellCache caches nodes, coordinates, and DoFs, for the cell. The cache cc can be re-initialized for a new cell index ci by calling reinit!(cc, ci). This can be used as an alternative to CellIterator when more control over which element to loop over is needed. See documentation for CellCache for more information. (#546)\nIt is now possible to create the sparsity pattern without constrained entries (they will be zeroed out later anyway) by passing keep_constrained=false to create_sparsity_pattern. This naturally only works together with local condensation of constraints since there won't be space allocated in the global matrix for the full (i.e. \"non-condensed\") element matrix. Creating the matrix without constrained entries reduces the memory footprint, but unless a significant amount of DoFs are constrained (e.g. high mesh resolution at a boundary) the savings are negligible. (#539)","category":"page"},{"location":"changelog/#Changed-2","page":"Changelog","title":"Changed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"ConstraintHandler: update! is now called implicitly in close!. This was easy to miss, and somewhat of a strange requirement when solving problems without time stepping. (#459)\nThe function for computing the inhomogeneity in a Dirichlet constraint can now be specified as either f(x) or f(x, t), where x is the spatial coordinate and t the time. (#459)\nThe elements of a CellIterator are now CellCache instead of the iterator itself, which was confusing in some cases. This change does not affect typical user code. (#546)","category":"page"},{"location":"changelog/#Deprecated-2","page":"Changelog","title":"Deprecated","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Adding fields to a DoF handler with push!(dh, ...) has been deprecated in favor of add!(dh, ...). This is to make it consistent with how constraints are added to a constraint handler. (#578)","category":"page"},{"location":"changelog/#Fixed-4","page":"Changelog","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Fix shape_value for the linear, discontinuous Lagrange interpolation. (#553)\nFix reference_coordinate dispatch for discontinuous Lagrange interpolations. (#559)\nFix show(::Grid) for custom cell types. (#570)\nFix apply_zero!(Δa, ch) when using inhomogeneous affine constraints (#575)","category":"page"},{"location":"changelog/#Other-improvements-5","page":"Changelog","title":"Other improvements","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Internal changes defining a new global matrix/vector \"interface\". These changes make it easy to enable more array types (e.g. BlockMatrix support added in this release) and solvers in the future. (#562, #571)\nPerformance improvements:\nReduced time and memory allocations for global sparse matrix creation (Julia >= 1.10). (#563)\nDocumentation improvements:\nAdded an overview of the Examples section. (#531)\nAdded an example showing topology optimization. (#531)\nVarious typo fixes. (#574)\nFix broken links. (#583)","category":"page"},{"location":"changelog/#[v0.3.10](https://github.com/Ferrite-FEM/Ferrite.jl/releases/tag/v0.3.10)-2022-12-11","page":"Changelog","title":"v0.3.10 - 2022-12-11","text":"","category":"section"},{"location":"changelog/#Added-6","page":"Changelog","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"New functions apply_local! and apply_assemble! for applying constraints locally on the element level before assembling to the global system. (#528)\nNew functionality to renumber DoFs by fields or by components. This is useful when you need the global matrix to be blocked. (#378, #545)\nFunctionality to renumber DoFs in DofHandler and ConstraintHandler simultaneously: renumber!(dh::DofHandler, ch::ConstraintHandler, order). Previously renumbering had to be done before creating the ConstraintHandler since otherwise DoF numbers would be inconsistent. However, this was inconvenient in cases where the constraints impact the new DoF order permutation. (#542)\nThe coupling between fields can now be specified when creating the global matrix with create_sparsity_pattern by passing a Matrix{Bool}. For example, in a problem with unknowns (u, p) and corresponding test functions (v, q), if there is no coupling between p and q it is unnecessary to allocate entries in the global matrix corresponding to these DoFs. This can now be communicated to create_sparsity_pattern by passing the coupling matrix [true true; true false] in the keyword argument coupling. (#544)","category":"page"},{"location":"changelog/#Changed-3","page":"Changelog","title":"Changed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Runtime and allocations for application of boundary conditions in apply! and apply_zero! have been improved. As a result, the strategy keyword argument is obsolete and thus ignored. (#489)\nThe internal representation of Dirichlet boundary conditions and AffineConstraints in the ConstraintHandler have been unified. As a result, conflicting constraints on DoFs are handled more consistently: the constraint added last to the ConstraintHandler now always override any previous constraints. Conflicting constraints could previously cause problems when a DoF where prescribed by both Dirichlet and AffineConstraint. (#529)\nEntries in local matrix/vector are now ignored in the assembly procedure. This allows, for example, using a dense local matrix [a b; c d] even if no entries exist in the global matrix for the d block, i.e. in [A B; C D] the D block is zero, and these global entries might not exist in the sparse matrix. (Such sparsity patterns can now be created by create_sparsity_pattern, see #544.) (#543)","category":"page"},{"location":"changelog/#Fixed-5","page":"Changelog","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Fix affine constraints with prescribed DoFs in the right-hand-side. In particular, DoFs that are prescribed by just an inhomogeneity are now handled correctly, and nested affine constraints now give an error instead of silently giving the wrong result. (#530, #535)\nFixed internal inconsistency in edge ordering for 2nd order RefTetrahedron and RefCube. (#520, #523)","category":"page"},{"location":"changelog/#Other-improvements-6","page":"Changelog","title":"Other improvements","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Performance improvements:\nReduced time and memory allocations in DoF distribution for MixedDofHandler. (#533)\nReduced time and memory allocations reductions in getcoordinates!. (#536)\nReduced time and memory allocations in affine constraint condensation. (#537, #541, #550)\nDocumentation improvements:\nUse :static scheduling for threaded for-loop (#534)\nRemove use of @inbounds (#547)\nUnification of create_sparsity_pattern methods to remove code duplication between DofHandler and MixedDofHandler. (#538, #540)","category":"page"},{"location":"changelog/#[v0.3.9](https://github.com/Ferrite-FEM/Ferrite.jl/releases/tag/v0.3.9)-2022-10-19","page":"Changelog","title":"v0.3.9 - 2022-10-19","text":"","category":"section"},{"location":"changelog/#Added-7","page":"Changelog","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"New higher order function interpolations for triangles (Lagrange{2,RefTetrahedron,3}, Lagrange{2,RefTetrahedron,4}, and Lagrange{2,RefTetrahedron,5}). (#482, #512)\nNew Gaussian quadrature formula for triangles up to order 15. (#514)\nAdd debug mode for working with Ferrite internals. (#524)","category":"page"},{"location":"changelog/#Changed-4","page":"Changelog","title":"Changed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"The default components to constrain in Dirichlet and PeriodicDirichlet have changed from component 1 to all components of the field. For scalar problems this has no effect. (#506, #509)","category":"page"},{"location":"changelog/#[v0.3.8](https://github.com/Ferrite-FEM/Ferrite.jl/releases/tag/v0.3.8)-2022-10-05","page":"Changelog","title":"v0.3.8 - 2022-10-05","text":"","category":"section"},{"location":"changelog/#Added-8","page":"Changelog","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Ferrite.jl now has a logo! (#464)\nNew keyword argument search_nneighbors::Int in PointEvalHandler for specifying how many neighboring elements to consider in the kNN search. The default is still 3 (usually sufficient). (#466)\nThe IJV-assembler now support assembling non-square matrices. (#471)\nPeriodic boundary conditions have been reworked and generalized. It now supports arbitrary relations between the mirror and image boundaries (e.g. not only translations in x/y/z direction). (#478, #481, #496, #501)","category":"page"},{"location":"changelog/#Fixed-6","page":"Changelog","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Fix PointEvalHandler when the first point is missing. (#466)\nFix the ordering of nodes on the face for (Quadratic)Tetrahedron cells. (#475)","category":"page"},{"location":"changelog/#Other-improvements-7","page":"Changelog","title":"Other improvements","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Many improvements to the documentation. (#467, #473, #487, #494, #500)\nImproved error messages in reinit! when number of geometric base functions and number of element coordinates mismatch. (#469)\nRemove some unnecessary function parametrizations. (#503)\nRemove some unnecessary allocations in grid coloring. (#505)\nMore efficient way of creating the sparsity pattern when using AffineConstraints and/or PeriodicDirichlet. (#436)","category":"page"},{"location":"changelog/#[v0.3.7](https://github.com/Ferrite-FEM/Ferrite.jl/releases/tag/v0.3.7)-2022-07-05","page":"Changelog","title":"v0.3.7 - 2022-07-05","text":"","category":"section"},{"location":"changelog/#Fixed-7","page":"Changelog","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Fix tests for newer version of WriteVTK (no functional change). (#462)","category":"page"},{"location":"changelog/#Other-improvements-8","page":"Changelog","title":"Other improvements","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Various improvements to the heat equation example and the hyperelasticity example in the documentation. (#460, #461)","category":"page"},{"location":"changelog/#[v0.3.6](https://github.com/Ferrite-FEM/Ferrite.jl/releases/tag/v0.3.6)-2022-06-30","page":"Changelog","title":"v0.3.6 - 2022-06-30","text":"","category":"section"},{"location":"changelog/#Fixed-8","page":"Changelog","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Fix a bug with L2Projection of mixed grid. (#456)","category":"page"},{"location":"changelog/#Other-improvements-9","page":"Changelog","title":"Other improvements","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Expanded manual section of Dirichlet BCs. (#458)","category":"page"},{"location":"changelog/#[v0.3.5](https://github.com/Ferrite-FEM/Ferrite.jl/releases/tag/v0.3.5)-2022-05-30","page":"Changelog","title":"v0.3.5 - 2022-05-30","text":"","category":"section"},{"location":"changelog/#Added-9","page":"Changelog","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Functionality for querying information about the grid topology (e.g. neighboring cells, boundaries, ...). (#363)","category":"page"},{"location":"changelog/#Fixed-9","page":"Changelog","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Fix application of boundary conditions when combining RHSData and affine constraints. (#431)","category":"page"},{"location":"changelog/#[v0.3.4](https://github.com/Ferrite-FEM/Ferrite.jl/releases/tag/v0.3.4)-2022-02-25","page":"Changelog","title":"v0.3.4 - 2022-02-25","text":"","category":"section"},{"location":"changelog/#Added-10","page":"Changelog","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Affine (linear) constraints between degrees-of-freedom. (#401)\nPeriodic Dirichlet boundary conditions. (#418)\nEvaluation of arbitrary quantities in FE space. (#425)","category":"page"},{"location":"changelog/#Changed-5","page":"Changelog","title":"Changed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Interpolation(s) and the quadrature rule are now stored as part of the CellValues structs (cv.func_interp, cv.geo_interp, and cv.qr). (#428)","category":"page"},{"location":"changelog/#[v0.3.3](https://github.com/Ferrite-FEM/Ferrite.jl/releases/tag/v0.3.3)-2022-02-04","page":"Changelog","title":"v0.3.3 - 2022-02-04","text":"","category":"section"},{"location":"changelog/#Changed-6","page":"Changelog","title":"Changed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Verify user input in various functions to eliminate possible out-of-bounds accesses. (#407, #411)","category":"page"},{"location":"changelog/#[v0.3.2](https://github.com/Ferrite-FEM/Ferrite.jl/releases/tag/v0.3.2)-2022-01-18","page":"Changelog","title":"v0.3.2 - 2022-01-18","text":"","category":"section"},{"location":"changelog/#Added-11","page":"Changelog","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Support for new interpolation types: DiscontinuousLagrange, BubbleEnrichedLagrange, and CrouzeixRaviart. (#352, #392)","category":"page"},{"location":"changelog/#Changed-7","page":"Changelog","title":"Changed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Julia version 1.0 is no longer supported for Ferrite versions >= 0.3.2. Use Julia version >= 1.6. (#385)\nQuadrature data for L2 projection can now be given as a matrix of size \"number of elements\" x \"number of quadrature points per element\". (#386)\nProjected values from L2 projection can now be exported directly to VTK. (#390)\nGrid coloring can now act on a subset of cells. (#402)\nVarious functions related to cell values now use traits to make it easier to extend and reuse functionality in external code. (#404)","category":"page"},{"location":"changelog/#Fixed-10","page":"Changelog","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Exporting tensors to VTK now use correct names for the components. (#406)","category":"page"},{"location":"reference/quadrature/","page":"Quadrature","title":"Quadrature","text":"CurrentModule = Ferrite\nDocTestSetup = :(using Ferrite)","category":"page"},{"location":"reference/quadrature/#Quadrature","page":"Quadrature","title":"Quadrature","text":"","category":"section"},{"location":"reference/quadrature/","page":"Quadrature","title":"Quadrature","text":"QuadratureRule\nFacetQuadratureRule\ngetnquadpoints(::QuadratureRule)\ngetnquadpoints(::FacetQuadratureRule, ::Int)\ngetpoints\ngetweights","category":"page"},{"location":"reference/quadrature/#Ferrite.QuadratureRule","page":"Quadrature","title":"Ferrite.QuadratureRule","text":"QuadratureRule{shape}([::Type{T},] [quad_rule_type::Symbol,] order::Int)\nQuadratureRule{shape}(weights::AbstractVector{T}, points::AbstractVector{Vec{rdim, T}})\n\nCreate a QuadratureRule used for integration on the refshape shape (of type AbstractRefShape). order is the order of the quadrature rule. quad_rule_type is an optional argument determining the type of quadrature rule, currently the :legendre and :lobatto rules are implemented for hypercubes. For triangles up to order 8 the default rule is the one by :dunavant (see [8]) and for tetrahedra the default rule is keast_minimal (see [9]). Wedges and pyramids default to :polyquad (see [10]). Furthermore we have implemented\n\n:gaussjacobi for triangles (order 9-15)\n:keast_minimal (see [9]) for tetrahedra (order 1-5), containing negative weights\n:keast_positive (see [9]) for tetrahedra (order 1-5), containing only positive weights\n\nA QuadratureRule is used to approximate an integral on a domain by a weighted sum of function values at specific points:\n\nintlimits_Omega f(mathbfx) textd Omega approx sumlimits_q = 1^n_q f(mathbfx_q) w_q\n\nThe quadrature rule consists of n_q points in space mathbfx_q with corresponding weights w_q.\n\nIn Ferrite, the QuadratureRule type is mostly used as one of the components to create CellValues.\n\nCommon methods:\n\ngetpoints : the points of the quadrature rule\ngetweights : the weights of the quadrature rule\n\nExample:\n\njulia> qr = QuadratureRule{RefTriangle}(1)\nQuadratureRule{RefTriangle, Float64, 2}([0.5], Vec{2, Float64}[[0.33333333333333, 0.33333333333333]])\n\njulia> getpoints(qr)\n1-element Vector{Vec{2, Float64}}:\n [0.33333333333333, 0.33333333333333]\n\n\n\n\n\n","category":"type"},{"location":"reference/quadrature/#Ferrite.FacetQuadratureRule","page":"Quadrature","title":"Ferrite.FacetQuadratureRule","text":"FacetQuadratureRule{shape}([::Type{T},] [quad_rule_type::Symbol,] order::Int)\nFacetQuadratureRule{shape}(face_rules::NTuple{<:Any, <:QuadratureRule{shape}})\nFacetQuadratureRule{shape}(face_rules::AbstractVector{<:QuadratureRule{shape}})\n\nCreate a FacetQuadratureRule used for integration of the faces of the refshape shape (of type AbstractRefShape). order is the order of the quadrature rule. If no symbol is provided, the default quad_rule_type for each facet's reference shape is used (see QuadratureRule). For non-default quad_rule_types on cells with mixed facet types (e.g. RefPrism and RefPyramid), the face_rules must be provided explicitly.\n\nFacetQuadratureRule is used as one of the components to create FacetValues.\n\n\n\n\n\n","category":"type"},{"location":"reference/quadrature/#Ferrite.getnquadpoints-Tuple{QuadratureRule}","page":"Quadrature","title":"Ferrite.getnquadpoints","text":"getnquadpoints(qr::QuadratureRule)\n\nReturn the number of quadrature points in qr.\n\n\n\n\n\n","category":"method"},{"location":"reference/quadrature/#Ferrite.getnquadpoints-Tuple{FacetQuadratureRule, Int64}","page":"Quadrature","title":"Ferrite.getnquadpoints","text":"getnquadpoints(qr::FacetQuadratureRule, face::Int)\n\nReturn the number of quadrature points in qr for local face index face.\n\n\n\n\n\n","category":"method"},{"location":"reference/quadrature/#Ferrite.getpoints","page":"Quadrature","title":"Ferrite.getpoints","text":"getpoints(qr::QuadratureRule)\ngetpoints(qr::FacetQuadratureRule, face::Int)\n\nReturn the points of the quadrature rule.\n\nExamples\n\njulia> qr = QuadratureRule{RefTriangle}(:legendre, 2);\n\njulia> getpoints(qr)\n3-element Vector{Vec{2, Float64}}:\n [0.16666666666667, 0.16666666666667]\n [0.16666666666667, 0.66666666666667]\n [0.66666666666667, 0.16666666666667]\n\n\n\n\n\n","category":"function"},{"location":"reference/quadrature/#Ferrite.getweights","page":"Quadrature","title":"Ferrite.getweights","text":"getweights(qr::QuadratureRule)\ngetweights(qr::FacetQuadratureRule, face::Int)\n\nReturn the weights of the quadrature rule.\n\nExamples\n\njulia> qr = QuadratureRule{RefTriangle}(:legendre, 2);\n\njulia> getweights(qr)\n3-element Array{Float64,1}:\n 0.166667\n 0.166667\n 0.166667\n\n\n\n\n\n","category":"function"},{"location":"topics/#Topic-guides","page":"Topic guide overview","title":"Topic guides","text":"","category":"section"},{"location":"topics/","page":"Topic guide overview","title":"Topic guide overview","text":"This is an overview of the topic guides.","category":"page"},{"location":"topics/","page":"Topic guide overview","title":"Topic guide overview","text":"Pages = [\n \"fe_intro.md\",\n \"reference_shapes.md\",\n \"FEValues.md\",\n \"degrees_of_freedom.md\",\n \"assembly.md\",\n \"boundary_conditions.md\",\n \"constraints.md\",\n \"grid.md\",\n \"export.md\"\n]","category":"page"},{"location":"devdocs/dofhandler/#dofhandler-interpolations","page":"Dof Handler","title":"Dof Handler","text":"","category":"section"},{"location":"devdocs/dofhandler/#Type-definitions","page":"Dof Handler","title":"Type definitions","text":"","category":"section"},{"location":"devdocs/dofhandler/","page":"Dof Handler","title":"Dof Handler","text":"Dof handlers are subtypes of AbstractDofhandler{sdim}, i.e. they are parametrized by the spatial dimension. Internally a helper struct InterpolationInfo is utilized to enforce type stability during dof distribution, because the interpolations are not available as concrete types.","category":"page"},{"location":"devdocs/dofhandler/","page":"Dof Handler","title":"Dof Handler","text":"Ferrite.InterpolationInfo\nFerrite.PathOrientationInfo\nFerrite.SurfaceOrientationInfo","category":"page"},{"location":"devdocs/dofhandler/#Ferrite.InterpolationInfo","page":"Dof Handler","title":"Ferrite.InterpolationInfo","text":"InterpolationInfo\n\nGathers all the information needed to distribute dofs for a given interpolation. Note that this cache is of the same type no matter the interpolation: the purpose is to make dof-distribution type-stable.\n\n\n\n\n\n","category":"type"},{"location":"devdocs/dofhandler/#Ferrite.PathOrientationInfo","page":"Dof Handler","title":"Ferrite.PathOrientationInfo","text":"PathOrientationInfo\n\nOrientation information for 1D entities.\n\nThe orientation for 1D entities is defined by the indices of the grid nodes associated to the vertices. To give an example, the oriented path\n\n1 ---> 2\n\nis called regular, indicated by regular=true, while the oriented path\n\n2 ---> 1\n\nis called inverted, indicated by regular=false.\n\n\n\n\n\n","category":"type"},{"location":"devdocs/dofhandler/#Ferrite.SurfaceOrientationInfo","page":"Dof Handler","title":"Ferrite.SurfaceOrientationInfo","text":"SurfaceOrientationInfo\n\nOrientation information for 2D entities. Such an entity can be possibly flipped (i.e. the defining vertex order is reverse to the spanning vertex order) and the vertices can be rotated against each other. Take for example the faces\n\n1---2 2---3\n| A | | B |\n4---3 1---4\n\nwhich are rotated against each other by 90° (shift index is 1) or the faces\n\n1---2 2---1\n| A | | B |\n4---3 3---4\n\nwhich are flipped against each other. Any combination of these can happen. The combination to map this local face to the defining face is encoded with this data structure via rotate circ flip where the rotation is indiced by the shift index. !!!NOTE TODO implement me.\n\n\n\n\n\n","category":"type"},{"location":"devdocs/dofhandler/#Internal-API","page":"Dof Handler","title":"Internal API","text":"","category":"section"},{"location":"devdocs/dofhandler/","page":"Dof Handler","title":"Dof Handler","text":"The main entry point for dof distribution is __close!.","category":"page"},{"location":"devdocs/dofhandler/","page":"Dof Handler","title":"Dof Handler","text":"Ferrite.__close!\nFerrite.get_grid\nFerrite.find_field\nFerrite._find_field\nFerrite._close_subdofhandler!\nFerrite._distribute_dofs_for_cell!\nFerrite.permute_and_push!","category":"page"},{"location":"devdocs/dofhandler/#Ferrite.__close!","page":"Dof Handler","title":"Ferrite.__close!","text":"__close!(dh::DofHandler)\n\nInternal entry point for dof distribution.\n\nDofs are distributed as follows: For the DofHandler each SubDofHandler is visited in the order they were added. For each field in the SubDofHandler create dofs for the cell. This means that dofs on a particular cell will be numbered in groups for each field, so first the dofs for field 1 are distributed, then field 2, etc. For each cell dofs are first distributed on its vertices, then on the interior of edges (if applicable), then on the interior of faces (if applicable), and finally on the cell interior. The entity ordering follows the geometrical ordering found in vertices, faces and edges.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/dofhandler/#Ferrite.get_grid","page":"Dof Handler","title":"Ferrite.get_grid","text":"get_grid(dh::AbstractDofHandler)\n\nAccess some grid representation for the dof handler.\n\nnote: Note\nThis API function is currently not well-defined. It acts as the interface between distributed assembly and assembly on a single process, because most parts of the functionality can be handled by only acting on the locally owned cell set.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/dofhandler/#Ferrite.find_field","page":"Dof Handler","title":"Ferrite.find_field","text":"find_field(dh::DofHandler, field_name::Symbol)::NTuple{2,Int}\n\nReturn the index of the field with name field_name in a DofHandler. The index is a NTuple{2,Int}, where the 1st entry is the index of the SubDofHandler within which the field was found and the 2nd entry is the index of the field within the SubDofHandler.\n\nnote: Note\nAlways finds the 1st occurrence of a field within DofHandler.\n\nSee also: find_field(sdh::SubDofHandler, field_name::Symbol), Ferrite._find_field(sdh::SubDofHandler, field_name::Symbol).\n\n\n\n\n\nfind_field(sdh::SubDofHandler, field_name::Symbol)::Int\n\nReturn the index of the field with name field_name in a SubDofHandler. Throw an error if the field is not found.\n\nSee also: find_field(dh::DofHandler, field_name::Symbol), _find_field(sdh::SubDofHandler, field_name::Symbol).\n\n\n\n\n\n","category":"function"},{"location":"devdocs/dofhandler/#Ferrite._find_field","page":"Dof Handler","title":"Ferrite._find_field","text":"_find_field(sdh::SubDofHandler, field_name::Symbol)::Int\n\nReturn the index of the field with name field_name in the SubDofHandler sdh. Return nothing if the field is not found.\n\nSee also: find_field(dh::DofHandler, field_name::Symbol), find_field(sdh::SubDofHandler, field_name::Symbol).\n\n\n\n\n\n","category":"function"},{"location":"devdocs/dofhandler/#Ferrite._close_subdofhandler!","page":"Dof Handler","title":"Ferrite._close_subdofhandler!","text":"_close_subdofhandler!(dh::DofHandler{sdim}, sdh::SubDofHandler, sdh_index::Int, nextdof::Int, vertexdicts, edgedicts, facedicts) where {sdim}\n\nMain entry point to distribute dofs for a single SubDofHandler on its subdomain.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/dofhandler/#Ferrite._distribute_dofs_for_cell!","page":"Dof Handler","title":"Ferrite._distribute_dofs_for_cell!","text":"_distribute_dofs_for_cell!(dh::DofHandler{sdim}, cell::AbstractCell, ip_info::InterpolationInfo, nextdof::Int, vertexdict, edgedict, facedict) where {sdim}\n\nMain entry point to distribute dofs for a single cell.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/dofhandler/#Ferrite.permute_and_push!","page":"Dof Handler","title":"Ferrite.permute_and_push!","text":"permute_and_push!\n\nFor interpolations with more than one interior dof per edge it may be necessary to adjust the dofs. Since dofs are (initially) enumerated according to the local edge direction there can be a direction mismatch with the neighboring element. For example, in the following nodal interpolation example, with three interior dofs on each edge, the initial pass have distributed dofs 4, 5, 6 according to the local edge directions:\n\n+-----------+\n| A |\n+--4--5--6->+ local edge on element A\n\n ----------> global edge\n\n+<-6--5--4--+ local edge on element B\n| B |\n+-----------+\n\nFor most scalar-valued interpolations we can simply compensate for this by reversing the numbering on all edges that do not match the global edge direction, i.e. for the edge on element B in the example.\n\nIn addition, we also have to preserve the ordering at each dof location.\n\nFor more details we refer to Scroggs et al. [15] as we follow the methodology described therein.\n\nReferences\n\n[15] Scroggs et al. ACM Trans. Math. Softw. 48 (2022).\n\n\n\n\n\n!!!NOTE TODO implement me.\n\nFor more details we refer to [1] as we follow the methodology described therein.\n\n[1] Scroggs, M. W., Dokken, J. S., Richardson, C. N., & Wells, G. N. (2022). Construction of arbitrary order finite element degree-of-freedom maps on polygonal and polyhedral cell meshes. ACM Transactions on Mathematical Software (TOMS), 48(2), 1-23.\n\n!!!TODO citation via software.\n\n!!!TODO Investigate if we can somehow pass the interpolation into this function in a typestable way.\n\n\n\n\n\n","category":"function"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"EditURL = \"../literate-howto/threaded_assembly.jl\"","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"Draft = false","category":"page"},{"location":"howto/threaded_assembly/#tutorial-threaded-assembly","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"","category":"section"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"tip: Tip\nThis example is also available as a Jupyter notebook: threaded_assembly.ipynb.","category":"page"},{"location":"howto/threaded_assembly/#Introduction","page":"Multi-threaded assembly","title":"Introduction","text":"","category":"section"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"In this howto we will explore how to use task based multithreading (shared memory parallelism) to speed up the analysis. Some parts of a finite element simulation are trivially parallelizable such as the computation of the local element contributions since each element can be processed independently. However, two things need to be considered in order to parallelize safely:","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"Modification of shared data: Although the contributions from all the elements can be computed independently, eventually they need to be assembled into the global matrix and vector. Letting each task assemble their own contribution would lead to race conditions since elements share degrees of freedom with each other. There are various ways to remedy this, for example:\nLocking: By using a lock around the call to assemble! we can ensure that only one task assembles at a time. This is simple to implement but can lead to lock contention and thus poor performance. Another drawback is that the results will not be deterministic since floating point operations are neither associative nor commutative.\nAssembler task: By using a designated task for the assembling we (obviously) ensure that only a single task assembles. The worker tasks (the tasks computing the element contributions) would then hand off their results to the assemly task. This can be a useful approach if computing the element contributions is much slower than the assembly – otherwise the assembler task can't keep up with the worker tasks. There might also be some extra overhead because of task switching in the scheduler. The problem with non-deterministic results still remains.\nGrid coloring: By \"coloring\" the grid such that, within each color, no two elements share degrees of freedom, we can safely assemble each color in parallel. Even if concurrently running tasks will write to the global matrix and vector they will not write to the same memory locations. Note also that this procedure gives predictable results because for a memory location which, for example, a \"red\", a \"blue\", and a \"green\" element will contribute to we will always add the red first, then the blue, and finally the green.\nScratch data: In order to speed up the computation of the element contributions we typically pre-allocate some data structures that can be reused for every element. Such scratch data include, for example, the local matrix and vector, and the CellValues. Each task need their own copy of the scratch data since they will be modified for each element.","category":"page"},{"location":"howto/threaded_assembly/#Grid-coloring","page":"Multi-threaded assembly","title":"Grid coloring","text":"","category":"section"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"Ferrite include functionality to color the grid with the create_coloring function. Here we create a simple 2D grid, color it, and export the colors to a VTK file to visualize the result (see Figure 1.). Note that no cells with the same color has any shared nodes (dofs). This means that it is safe to assemble in parallel as long as we only assemble one color at a time.","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"There are two coloring algorithms implemented: the \"workstream\" algorithm (from Turcksin et al. [13]) and a \"greedy\" algorithm. For this structured grid the greedy algorithm uses fewer colors, but both algorithms result in colors that contain roughly the same number of elements. The workstream algorithm is the default one since it in general results in more balanced colors. For unstructured grids the greedy algorithm can result in colors with very few elements, for example.","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"using Ferrite, SparseArrays\n\nfunction create_example_2d_grid()\n grid = generate_grid(Quadrilateral, (10, 10), Vec{2}((0.0, 0.0)), Vec{2}((10.0, 10.0)))\n colors_workstream = create_coloring(grid; alg = ColoringAlgorithm.WorkStream)\n colors_greedy = create_coloring(grid; alg = ColoringAlgorithm.Greedy)\n VTKGridFile(\"colored\", grid) do vtk\n Ferrite.write_cell_colors(vtk, grid, colors_workstream, \"workstream-coloring\")\n Ferrite.write_cell_colors(vtk, grid, colors_greedy, \"greedy-coloring\")\n end\n return\nend\n\ncreate_example_2d_grid()","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"(Image: )","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"Figure 1: Element coloring using the \"workstream\"-algorithm (left) and the \"greedy\"- algorithm (right).","category":"page"},{"location":"howto/threaded_assembly/#Multithreaded-assembly-of-a-cantilever-beam-in-3D","page":"Multi-threaded assembly","title":"Multithreaded assembly of a cantilever beam in 3D","text":"","category":"section"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"We will now look at an example where we assemble the stiffness matrix and right hand side using multiple threads. The problem setup is a cantilever beam in 3D with a linear elastic material behavior. For this exercise we only focus on the multithreading and are not bothered with boundary conditions. For more details refer to the tutorial on linear elasticity.","category":"page"},{"location":"howto/threaded_assembly/#Setup","page":"Multi-threaded assembly","title":"Setup","text":"","category":"section"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"We define the element routine, material stiffness, grid and DofHandler just like in the tutorial on linear elasticity without discussing it further here.","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"# Element routine\nfunction assemble_cell!(Ke::Matrix, fe::Vector, cellvalues::CellValues, C::SymmetricTensor, b::Vec)\n fill!(Ke, 0)\n fill!(fe, 0)\n for q_point in 1:getnquadpoints(cellvalues)\n dΩ = getdetJdV(cellvalues, q_point)\n for i in 1:getnbasefunctions(cellvalues)\n δui = shape_value(cellvalues, q_point, i)\n fe[i] += (δui ⋅ b) * dΩ\n ∇δui = shape_symmetric_gradient(cellvalues, q_point, i)\n for j in 1:getnbasefunctions(cellvalues)\n ∇uj = shape_symmetric_gradient(cellvalues, q_point, j)\n Ke[i, j] += (∇δui ⊡ C ⊡ ∇uj) * dΩ\n end\n end\n end\n return Ke, fe\nend\n\n# Material stiffness\nfunction create_material_stiffness()\n E = 200.0e9\n ν = 0.3\n λ = E * ν / ((1 + ν) * (1 - 2ν))\n μ = E / (2(1 + ν))\n δ(i, j) = i == j ? 1.0 : 0.0\n C = SymmetricTensor{4, 3}() do i, j, k, l\n return λ * δ(i, j) * δ(k, l) + μ * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k))\n end\n return C\nend\n\n# Grid and grid coloring\nfunction create_cantilever_grid(n::Int)\n xmin = Vec{3}((0.0, 0.0, 0.0))\n xmax = Vec{3}((10.0, 1.0, 1.0))\n grid = generate_grid(Hexahedron, (10 * n, n, n), xmin, xmax)\n colors = create_coloring(grid)\n return grid, colors\nend\n\n# DofHandler with displacement field u\nfunction create_dofhandler(grid::Grid, interpolation::VectorInterpolation)\n dh = DofHandler(grid)\n add!(dh, :u, interpolation)\n close!(dh)\n return dh\nend\nnothing # hide","category":"page"},{"location":"howto/threaded_assembly/#Task-local-scratch-data","page":"Multi-threaded assembly","title":"Task local scratch data","text":"","category":"section"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"We group everything that needs to be duplicated for each task in the struct ScratchData:","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"cell_cache::CellCache: contain buffers for coordinates and (global) dofs which will be reinit!ed for each cell.\ncellvalues::CellValues: the cell values which will be reinit!ed for each cell using the cell_cache\nKe::Matrix: the local matrix\nfe::Vector: the local vector\nassembler: the assembler (which needs to be duplicated because it contains buffers that are modified during the call to assemble!)","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"struct ScratchData{CC, CV, T, A}\n cell_cache::CC\n cellvalues::CV\n Ke::Matrix{T}\n fe::Vector{T}\n assembler::A\nend","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"This constructor will be called within each task to create a independent ScratchData object. For cell_cache, Ke, and fe we simply call the constructors to allocate independent objects. For cellvalues we use copy which Ferrite defines for this purpose. Finally, for the assembler we call start_assemble to create a new assembler but note that we set fillzero = false because we don't want to risk that a task that starts a bit later will zero out data that another task have already assembled.","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"function ScratchData(dh::DofHandler, K::SparseMatrixCSC, f::Vector, cellvalues::CellValues)\n cell_cache = CellCache(dh)\n n = ndofs_per_cell(dh)\n Ke = zeros(n, n)\n fe = zeros(n)\n asm = start_assemble(K, f; fillzero = false)\n return ScratchData(cell_cache, copy(cellvalues), Ke, fe, asm)\nend\nnothing # hide","category":"page"},{"location":"howto/threaded_assembly/#Global-assembly-routine","page":"Multi-threaded assembly","title":"Global assembly routine","text":"","category":"section"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"Finally we define the global assemble routine, which is where the parallelization happens. The main difference from all previous assemble_global! functions is that we now have an outer loop over the colors, and then the inner loop over the cells in each color, which can be parallelized.","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"For the scheduling of parallel tasks we use the OhMyThreads.jl package. OhMyThreads provides a macro based and a functional API. Here we use the macro based API because it is slightly more convenient when using task local values since they can be defined with the @local macro.","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"note: Schedulers and load balancing\nOhMyThreads provides a number of different schedulers. In this example we use the DynamicScheduler (which is the default one). The DynamicScheduler will spawn ntasks tasks where each task will process a chunk of (roughly) equal number of cells (i.e. length(color) ÷ ntasks). This should be a good choice for this example because we expect all cells to take the same time to process and we don't need any load balancing.For a different problem setup where some cells might take longer to process (perhaps they experience plastic deformation and we need to solve a local problem) we might benefit from load balancing. The DynamicScheduler can be used also for load balancing by specifiying nchunks or chunksize. However, the DynamicScheduler will always spawn nchunks tasks which can become costly since we are allocating scratch data for every task. To limit the number of tasks, while allowing for more than ntasks chunks, we can use the GreedyScheduler with chunking. For example, scheduler = OhMyThreads.GreedyScheduler(; ntasks = ntasks, nchunks = 10 * ntasks) will split the work into 10 * ntasks chunks and spawn ntasks tasks to process them. Refer to the OhMyThreads documentation for details.","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"using OhMyThreads, TaskLocalValues\n\nfunction assemble_global!(\n K::SparseMatrixCSC, f::Vector, dh::DofHandler, colors,\n cellvalues_template::CellValues; ntasks = Threads.nthreads()\n )\n # Zero-out existing data in K and f\n _ = start_assemble(K, f)\n # Body force and material stiffness\n b = Vec{3}((0.0, 0.0, -1.0))\n C = create_material_stiffness()\n # Loop over the colors\n for color in colors\n # Dynamic scheduler spawning `ntasks` tasks where each task will process a chunk of\n # (roughly) equal number of cells (`length(color) ÷ ntasks`).\n scheduler = OhMyThreads.DynamicScheduler(; ntasks)\n # Parallelize the loop over the cells in this color\n OhMyThreads.@tasks for cellidx in color\n # Tell the @tasks loop to use the scheduler defined above\n @set scheduler = scheduler\n # Obtain a task local scratch and unpack it\n @local scratch = ScratchData(dh, K, f, cellvalues_template)\n (; cell_cache, cellvalues, Ke, fe, assembler) = scratch\n # Reinitialize the cell cache and then the cellvalues\n reinit!(cell_cache, cellidx)\n reinit!(cellvalues, cell_cache)\n # Compute the local contribution of the cell\n assemble_cell!(Ke, fe, cellvalues, C, b)\n # Assemble local contribution\n assemble!(assembler, celldofs(cell_cache), Ke, fe)\n end\n end\n return K, f\nend\nnothing # hide","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"details: OhMyThreads functional API: OhMyThreads.tforeach\nThe OhMyThreads.@tasks block above corresponds to a call to OhMyThreads.tforeach. Using the functional API directly would look like below. The main difference is that we need to manually create a TaskLocalValue for the scratch data.# using TaskLocalValues\nscratches = TaskLocalValue() do\n ScratchData(dh, K, f, cellvalues)\nend\nOhMyThreads.tforeach(color; scheduler) do cellidx\n # Obtain a task local scratch and unpack it\n scratch = scratches[]\n (; cell_cache, cellvalues, Ke, fe, assembler) = scratch\n # Reinitialize the cell cache and then the cellvalues\n reinit!(cell_cache, cellidx)\n reinit!(cellvalues, cell_cache)\n # Compute the local contribution of the cell\n assemble_cell!(Ke, fe, cellvalues, C, b)\n # Assemble local contribution\n assemble!(assembler, celldofs(cell_cache), Ke, fe)\nend","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"We define the main function to setup everything and then time the call to assemble_global!.","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"function main(; n = 20, ntasks = Threads.nthreads())\n # Interpolation, quadrature and cellvalues\n interpolation = Lagrange{RefHexahedron, 1}()^3\n quadrature = QuadratureRule{RefHexahedron}(2)\n cellvalues = CellValues(quadrature, interpolation)\n # Grid, colors and DofHandler\n grid, colors = create_cantilever_grid(n)\n dh = create_dofhandler(grid, interpolation)\n # Global matrix and vector\n K = allocate_matrix(dh)\n f = zeros(ndofs(dh))\n # Compile it\n assemble_global!(K, f, dh, colors, cellvalues; ntasks = ntasks)\n # Time it\n @time assemble_global!(K, f, dh, colors, cellvalues; ntasks = ntasks)\n return\nend\nnothing # hide","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"On a machine with 4 cores, starting julia with --threads=auto, we obtain the following timings:","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"main(; ntasks = 1) # 1.970784 seconds (902 allocations: 816.172 KiB)\nmain(; ntasks = 2) # 1.025065 seconds (1.64 k allocations: 1.564 MiB)\nmain(; ntasks = 3) # 0.700423 seconds (2.38 k allocations: 2.332 MiB)\nmain(; ntasks = 4) # 0.548356 seconds (3.12 k allocations: 3.099 MiB)","category":"page"},{"location":"howto/threaded_assembly/#threaded_assembly-plain-program","page":"Multi-threaded assembly","title":"Plain program","text":"","category":"section"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"Here follows a version of the program without any comments. The file is also available here: threaded_assembly.jl.","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"using Ferrite, SparseArrays\n\nfunction create_example_2d_grid()\n grid = generate_grid(Quadrilateral, (10, 10), Vec{2}((0.0, 0.0)), Vec{2}((10.0, 10.0)))\n colors_workstream = create_coloring(grid; alg = ColoringAlgorithm.WorkStream)\n colors_greedy = create_coloring(grid; alg = ColoringAlgorithm.Greedy)\n VTKGridFile(\"colored\", grid) do vtk\n Ferrite.write_cell_colors(vtk, grid, colors_workstream, \"workstream-coloring\")\n Ferrite.write_cell_colors(vtk, grid, colors_greedy, \"greedy-coloring\")\n end\n return\nend\n\ncreate_example_2d_grid()\n\n# Element routine\nfunction assemble_cell!(Ke::Matrix, fe::Vector, cellvalues::CellValues, C::SymmetricTensor, b::Vec)\n fill!(Ke, 0)\n fill!(fe, 0)\n for q_point in 1:getnquadpoints(cellvalues)\n dΩ = getdetJdV(cellvalues, q_point)\n for i in 1:getnbasefunctions(cellvalues)\n δui = shape_value(cellvalues, q_point, i)\n fe[i] += (δui ⋅ b) * dΩ\n ∇δui = shape_symmetric_gradient(cellvalues, q_point, i)\n for j in 1:getnbasefunctions(cellvalues)\n ∇uj = shape_symmetric_gradient(cellvalues, q_point, j)\n Ke[i, j] += (∇δui ⊡ C ⊡ ∇uj) * dΩ\n end\n end\n end\n return Ke, fe\nend\n\n# Material stiffness\nfunction create_material_stiffness()\n E = 200.0e9\n ν = 0.3\n λ = E * ν / ((1 + ν) * (1 - 2ν))\n μ = E / (2(1 + ν))\n δ(i, j) = i == j ? 1.0 : 0.0\n C = SymmetricTensor{4, 3}() do i, j, k, l\n return λ * δ(i, j) * δ(k, l) + μ * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k))\n end\n return C\nend\n\n# Grid and grid coloring\nfunction create_cantilever_grid(n::Int)\n xmin = Vec{3}((0.0, 0.0, 0.0))\n xmax = Vec{3}((10.0, 1.0, 1.0))\n grid = generate_grid(Hexahedron, (10 * n, n, n), xmin, xmax)\n colors = create_coloring(grid)\n return grid, colors\nend\n\n# DofHandler with displacement field u\nfunction create_dofhandler(grid::Grid, interpolation::VectorInterpolation)\n dh = DofHandler(grid)\n add!(dh, :u, interpolation)\n close!(dh)\n return dh\nend\nnothing # hide\n\nstruct ScratchData{CC, CV, T, A}\n cell_cache::CC\n cellvalues::CV\n Ke::Matrix{T}\n fe::Vector{T}\n assembler::A\nend\n\nfunction ScratchData(dh::DofHandler, K::SparseMatrixCSC, f::Vector, cellvalues::CellValues)\n cell_cache = CellCache(dh)\n n = ndofs_per_cell(dh)\n Ke = zeros(n, n)\n fe = zeros(n)\n asm = start_assemble(K, f; fillzero = false)\n return ScratchData(cell_cache, copy(cellvalues), Ke, fe, asm)\nend\nnothing # hide\n\nusing OhMyThreads, TaskLocalValues\n\nfunction assemble_global!(\n K::SparseMatrixCSC, f::Vector, dh::DofHandler, colors,\n cellvalues_template::CellValues; ntasks = Threads.nthreads()\n )\n # Zero-out existing data in K and f\n _ = start_assemble(K, f)\n # Body force and material stiffness\n b = Vec{3}((0.0, 0.0, -1.0))\n C = create_material_stiffness()\n # Loop over the colors\n for color in colors\n # Dynamic scheduler spawning `ntasks` tasks where each task will process a chunk of\n # (roughly) equal number of cells (`length(color) ÷ ntasks`).\n scheduler = OhMyThreads.DynamicScheduler(; ntasks)\n # Parallelize the loop over the cells in this color\n OhMyThreads.@tasks for cellidx in color\n # Tell the @tasks loop to use the scheduler defined above\n @set scheduler = scheduler\n # Obtain a task local scratch and unpack it\n @local scratch = ScratchData(dh, K, f, cellvalues_template)\n (; cell_cache, cellvalues, Ke, fe, assembler) = scratch\n # Reinitialize the cell cache and then the cellvalues\n reinit!(cell_cache, cellidx)\n reinit!(cellvalues, cell_cache)\n # Compute the local contribution of the cell\n assemble_cell!(Ke, fe, cellvalues, C, b)\n # Assemble local contribution\n assemble!(assembler, celldofs(cell_cache), Ke, fe)\n end\n end\n return K, f\nend\nnothing # hide\n\nfunction main(; n = 20, ntasks = Threads.nthreads())\n # Interpolation, quadrature and cellvalues\n interpolation = Lagrange{RefHexahedron, 1}()^3\n quadrature = QuadratureRule{RefHexahedron}(2)\n cellvalues = CellValues(quadrature, interpolation)\n # Grid, colors and DofHandler\n grid, colors = create_cantilever_grid(n)\n dh = create_dofhandler(grid, interpolation)\n # Global matrix and vector\n K = allocate_matrix(dh)\n f = zeros(ndofs(dh))\n # Compile it\n assemble_global!(K, f, dh, colors, cellvalues; ntasks = ntasks)\n # Time it\n @time assemble_global!(K, f, dh, colors, cellvalues; ntasks = ntasks)\n return\nend\nnothing # hide","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"This page was generated using Literate.jl.","category":"page"},{"location":"reference/interpolations/","page":"Interpolation","title":"Interpolation","text":"CurrentModule = Ferrite\nDocTestSetup = :(using Ferrite)","category":"page"},{"location":"reference/interpolations/#reference-interpolation","page":"Interpolation","title":"Interpolation","text":"","category":"section"},{"location":"reference/interpolations/","page":"Interpolation","title":"Interpolation","text":"Interpolation\ngetnbasefunctions\ngetrefdim(::Interpolation)\ngetrefshape\ngetorder","category":"page"},{"location":"reference/interpolations/#Ferrite.Interpolation","page":"Interpolation","title":"Ferrite.Interpolation","text":"Interpolation{ref_shape, order}()\n\nAbstract type for interpolations defined on ref_shape (see AbstractRefShape). order corresponds to the order of the interpolation. The interpolation is used to define shape functions to interpolate a function between nodes.\n\nThe following interpolations are implemented:\n\nLagrange{RefLine,1}\nLagrange{RefLine,2}\nLagrange{RefQuadrilateral,1}\nLagrange{RefQuadrilateral,2}\nLagrange{RefQuadrilateral,3}\nLagrange{RefTriangle,1}\nLagrange{RefTriangle,2}\nLagrange{RefTriangle,3}\nLagrange{RefTriangle,4}\nLagrange{RefTriangle,5}\nBubbleEnrichedLagrange{RefTriangle,1}\nCrouzeixRaviart{RefTriangle, 1}\nCrouzeixRaviart{RefTetrahedron, 1}\nRannacherTurek{RefQuadrilateral, 1}\nRannacherTurek{RefHexahedron, 1}\nLagrange{RefHexahedron,1}\nLagrange{RefHexahedron,2}\nLagrange{RefTetrahedron,1}\nLagrange{RefTetrahedron,2}\nLagrange{RefPrism,1}\nLagrange{RefPrism,2}\nLagrange{RefPyramid,1}\nLagrange{RefPyramid,2}\nSerendipity{RefQuadrilateral,2}\nSerendipity{RefHexahedron,2}\n\nExamples\n\njulia> ip = Lagrange{RefTriangle, 2}()\nLagrange{RefTriangle, 2}()\n\njulia> getnbasefunctions(ip)\n6\n\n\n\n\n\n","category":"type"},{"location":"reference/interpolations/#Ferrite.getnbasefunctions","page":"Interpolation","title":"Ferrite.getnbasefunctions","text":"Ferrite.getnbasefunctions(ip::Interpolation)\n\nReturn the number of base functions for the interpolation ip.\n\n\n\n\n\n","category":"function"},{"location":"reference/interpolations/#Ferrite.getrefdim-Tuple{Interpolation}","page":"Interpolation","title":"Ferrite.getrefdim","text":"Ferrite.getrefdim(::Interpolation)\n\nReturn the dimension of the reference element for a given interpolation.\n\n\n\n\n\n","category":"method"},{"location":"reference/interpolations/#Ferrite.getrefshape","page":"Interpolation","title":"Ferrite.getrefshape","text":"Ferrite.getrefshape(::Interpolation)::AbstractRefShape\n\nReturn the reference element shape of the interpolation.\n\n\n\n\n\n","category":"function"},{"location":"reference/interpolations/#Ferrite.getorder","page":"Interpolation","title":"Ferrite.getorder","text":"Ferrite.getorder(::Interpolation)\n\nReturn order of the interpolation.\n\n\n\n\n\n","category":"function"},{"location":"reference/interpolations/","page":"Interpolation","title":"Interpolation","text":"Implemented interpolations:","category":"page"},{"location":"reference/interpolations/","page":"Interpolation","title":"Interpolation","text":"Lagrange\nSerendipity\nDiscontinuousLagrange\nBubbleEnrichedLagrange\nCrouzeixRaviart\nRannacherTurek","category":"page"},{"location":"reference/interpolations/#Ferrite.Lagrange","page":"Interpolation","title":"Ferrite.Lagrange","text":"Lagrange{refshape, order} <: ScalarInterpolation\n\nStandard continuous Lagrange polynomials with equidistant node placement.\n\n\n\n\n\n","category":"type"},{"location":"reference/interpolations/#Ferrite.Serendipity","page":"Interpolation","title":"Ferrite.Serendipity","text":"Serendipity{refshape, order} <: ScalarInterpolation\n\nSerendipity element on hypercubes. Currently only second order variants are implemented.\n\n\n\n\n\n","category":"type"},{"location":"reference/interpolations/#Ferrite.DiscontinuousLagrange","page":"Interpolation","title":"Ferrite.DiscontinuousLagrange","text":"Piecewise discontinuous Lagrange basis via Gauss-Lobatto points.\n\n\n\n\n\n","category":"type"},{"location":"reference/interpolations/#Ferrite.BubbleEnrichedLagrange","page":"Interpolation","title":"Ferrite.BubbleEnrichedLagrange","text":"Lagrange element with bubble stabilization.\n\n\n\n\n\n","category":"type"},{"location":"reference/interpolations/#Ferrite.CrouzeixRaviart","page":"Interpolation","title":"Ferrite.CrouzeixRaviart","text":"CrouzeixRaviart{refshape, order} <: ScalarInterpolation\n\nClassical non-conforming Crouzeix–Raviart element.\n\nFor details we refer to the original paper [11].\n\n\n\n\n\n","category":"type"},{"location":"reference/interpolations/#Ferrite.RannacherTurek","page":"Interpolation","title":"Ferrite.RannacherTurek","text":"RannacherTurek{refshape, order} <: ScalarInterpolation\n\nClassical non-conforming Rannacher-Turek element.\n\nThis element is basically the idea from Crouzeix and Raviart applied to hypercubes. For details see the original paper [12].\n\n\n\n\n\n","category":"type"},{"location":"devdocs/#Developer-documentation","page":"Developer documentation","title":"Developer documentation","text":"","category":"section"},{"location":"devdocs/","page":"Developer documentation","title":"Developer documentation","text":"Here you can find some documentation of the internals of Ferrite which are useful when developing the library.","category":"page"},{"location":"devdocs/","page":"Developer documentation","title":"Developer documentation","text":"Depth = 1\nPages = [\"reference_cells.md\", \"interpolations.md\", \"elements.md\", \"FEValues.md\", \"dofhandler.md\", \"assembly.md\", \"performance.md\", \"special_datastructures.md\"]","category":"page"},{"location":"topics/fe_intro/#Introduction-to-FEM","page":"Introduction to FEM","title":"Introduction to FEM","text":"","category":"section"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"Here we will present a very brief introduction to partial differential equations (PDEs) and to the finite element method (FEM). Perhaps the simplest PDE of all is the (steady-state, linear) heat equation, also known as the Poisson equation. We will use this equation as a demonstrative example of the method, and demonstrate how we go from the strong form of the equation, to the weak form, and then finally to the discrete FE problem.","category":"page"},{"location":"topics/fe_intro/#Strong-form","page":"Introduction to FEM","title":"Strong form","text":"","category":"section"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"The strong form of the heat equation may be written as:","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"- nabla cdot mathbfq(u) = f quad forall mathbfx in Omega","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"where u is the unknown temperature field, mathbfq is the heat flux, f is an internal heat source, and Omega is the domain on which the equation is defined. To complete the problem we need to specify what happens at the domain boundary Gamma. This set of specifications is called boundary conditions. There are different types of boundary conditions, where the most common ones are Dirichlet – which means that the solution u is known at some part of the boundary, and Neumann – which means that the gradient of the solution, nabla u is known. Formally we write for our example","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"u = u^mathrmp quad forall mathbfx in Gamma_mathrmD\nmathbfq cdot mathbfn = q^mathrmp quad forall mathbfx in Gamma_mathrmN","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"i.e. the temperature is prescribed to a known function u^mathrmp at the Dirichlet part of the boundary, Gamma_mathrmD, and the heat flux is prescribed to q^mathrmp at the Neumann part of the boundary, Gamma_mathrmN, where mathbfn describes the outward pointing normal vector at the boundary.","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"We also need a constitutive equation which links the temperature field, u, to the heat flux, mathbfq. The simplest case is to use Fourier's law","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"mathbfq(u) = -k nabla u","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"where k is the conductivity of the material. In general the conductivity can vary throughout the domain as a function of the coordinate, i.e. k = k(mathbfx), but for simplicity we will consider only constant conductivity k.","category":"page"},{"location":"topics/fe_intro/#Weak-form","page":"Introduction to FEM","title":"Weak form","text":"","category":"section"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"The solution to the equation above is usually calculated from the corresponding weak form. By multiplying the equation with an arbitrary test function delta u, integrating over the domain and using partial integration we obtain the weak form. Now our problem can be stated as:","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"Find u in mathbbU s.t.","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"int_Omega nabla delta u cdot (k nabla u) mathrmdOmega =\nint_Gamma_mathrmN delta u q^mathrmp mathrmdGamma +\nint_Omega delta u f mathrmdOmega quad forall delta u in mathbbT","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"where mathbbU mathbbT are suitable function spaces with sufficiently regular functions. Under very general assumptions it can be shown that the solution to the weak form is identical to the solution to the strong form.","category":"page"},{"location":"topics/fe_intro/#Finite-Element-approximation","page":"Introduction to FEM","title":"Finite Element approximation","text":"","category":"section"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"Using the finite element method to solve partial differential equations is usually preceded with the construction of a discretization of the domain Omega into a finite set of elements or cells. We call this geometric discretization grid (or mesh) and denote it with Omega_h. In this example the corners of the triangles are called nodes.","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"Next we introduce the finite element approximation u_mathrmh approx u as a sum of N nodal shape functions, where we denote each of these function by phi_i and the corresponding nodal values hatu_i. Note that shape functions are sometimes referred to as basis functions or trial functions, and instead of phi_i they are sometimes denoted N_i. In this example we choose to approximate the test function in the same way. This approach is known as the Galerkin finite element method. Formally we write the evaluation of our approximations at a specific point mathbfx in our domain Omega as:","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"u_mathrmh(mathbfx) = sum_i=1^mathrmN phi_i(mathbfx) hatu_iqquad\ndelta u_mathrmh(mathbfx) = sum_i=1^mathrmN phi_i(mathbfx) delta hatu_i ","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"Since test and trial functions are usually chosen in such a way, that they build the basis of some function space (basis as in basis of a vector space), sometimes are they are also called basis functions. In the following the argument mathbfx is dropped to keep the notation compact. We may now insert these approximations in the weak form, which results in","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"sum_i^N delta hatu_i left(sum_j^N int_Omega_mathrmh nabla phi_i cdot (k nabla phi_j) mathrmdOmega hatu_j right) =\nsum_i^N delta hatu_i left( int_Gamma_mathrmN phi_i q^mathrmp mathrmdGamma +\nint_Omega_mathrmh phi_i f mathrmdOmega right) ","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"Since this equation must hold for arbitrary delta u_mathrmh, the equation must especially hold for the specific choice that only one of the nodal values delta hatu_i is fixed to 1 while an all other coefficients are fixed to 0. Repeating this argument for all i from 1 to N we obtain N linear equations. This way the discrete problem can be written as a system of linear equations","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"underlineunderlineK underlinehatu = underlinehatf ","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"where we call underlineunderlineK the (tangent) stiffness matrix, underlinehatu the solution vector with the nodal values and underlinehatf the force vector. The specific naming is for historical reasons, because the finite element method has its origins in mechanics. The elements of underlineunderlineK and underlinehatf are given by","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"(underlineunderlineK)_ij =\n int_Omega_mathrmh nabla phi_i cdot (k nabla phi_j) mathrmdOmega \n\n(underlinehatf)_i =\n int_Gamma_mathrmN phi_i q^mathrmp mathrmdGamma + int_Omega_mathrmh phi_i f mathrmdOmega ","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"Finally we also need to take care of the Dirichlet boundary conditions. These are enforced by setting the corresponding hatu_i to the prescribed values and eliminating the associated equations from the system. Now, solving this equation system yields the nodal values and thus an approximation to the true solution.","category":"page"},{"location":"topics/fe_intro/#Notes-on-the-implementation","page":"Introduction to FEM","title":"Notes on the implementation","text":"","category":"section"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"In practice, the shape functions phi_i are only non-zero on parts of the domain Omega_mathrmh. Thus, the integrals are evaluated on sub-domains, called elements or cells.","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"Each cell gives a contribution to the global stiffness matrix and force vector. The process of constructing the system of equations is also called assembly. For clarification, let us rewrite the formula for the stiffness matrix entries as follows:","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"(underlineunderlineK)_ij\n = int_Omega_mathrmh nabla phi_i cdot (k nabla phi_j) mathrmdOmega\n = sum_E in Omega_mathrmh int_E nabla phi_i cdot (k nabla phi_j) mathrmdOmega ","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"This formulation underlines the element-centric perspective of finite element methods and reflects how it is usually implemented in software.","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"Computing the element integrals by hand can become a tedious task. To avoid this issue we approximate the element integrals with a technique called numerical integration. Skipping any of the mathematical details, the basic idea is to evaluate the function under the integral at specific points and weighting the evaluations accordingly, such that their sum approximates the volume properly. A very nice feature of these techniques is, that under quite general circumstances the formula is not just an approximation, but the exact evaluation of the integral. To avoid the recomputation of the just mentioned evaluation positions of the integral for each individual element, we perform a coordinate transformation onto a so-called reference element. Formally we write","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":" int_E nabla phi_i cdot (k nabla phi_j) mathrmdOmega\n approx sum_q nabla phi_i(textbfx_q) cdot (k(textbfx_q) nabla phi_j(textbfx_q)) w_q textrmdet(J(textbfx_q)) ","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"where J is the Jacobian of the coordinate transformation function. The computation of the transformation, weights, positions and of the Jacobi determinant is handled by Ferrite. On an intuitive level, and to explain the notation used in the implementation, we think of","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":" mathrmdOmega approx w textrmdet(J)","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"being the chosen approximation when changing from the integral to the finite summation.","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"For an example of the implementation to solve a heat problem with Ferrite check out this thoroughly commented example.","category":"page"},{"location":"topics/fe_intro/#More-details","page":"Introduction to FEM","title":"More details","text":"","category":"section"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"We finally want to note that this quick introduction barely scratches the surface of the finite element method. Also, we presented some things in a simplified way for the sake of keeping this article short and concise. There is a large corpus of literature and online tutorials containing more details about the finite element method. To give a few recommendations there is:","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"Hans Petter Langtangen's Script\nWolfgang Bangerth's Lecture Series\nIntroduction to the Finite Element Method by Niels Ottosen and Hans Petersson\nThe Finite Element Method for Elliptic Problems by Philippe Ciarlet\nFinite Elements: Theory, Fast Solvers, and Applications in Elasticity Theory by Dietrich Braess\nAn Analysis of the Finite Element Method by Gilbert Strang and George Fix\nFinite Element Procedures by Klaus-Jürgen Bathe\nThe Finite Element Method: Its Basis and Fundamentals by Olgierd Cecil Zienkiewicz, Robert Taylor and J.Z. Zhu\nHigher-Order Finite Element Methods by Pavel Šolín, Karel Segeth and Ivo Doležel","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"This list is neither meant to be exhaustive, nor does the absence of a work mean that it is in any way bad or not recommendable. The ordering of the articles also has no particular meaning.","category":"page"},{"location":"devdocs/FEValues/#devdocs-fevalues","page":"FEValues","title":"FEValues","text":"","category":"section"},{"location":"devdocs/FEValues/#Type-definitions","page":"FEValues","title":"Type definitions","text":"","category":"section"},{"location":"devdocs/FEValues/","page":"FEValues","title":"FEValues","text":"AbstractValues\nAbstractCellValues\nCellValues\nAbstractFacetValues\nFacetValues\nBCValues\nPointValues\nInterfaceValues","category":"page"},{"location":"devdocs/FEValues/#Internal-types","page":"FEValues","title":"Internal types","text":"","category":"section"},{"location":"devdocs/FEValues/","page":"FEValues","title":"FEValues","text":"Ferrite.GeometryMapping\nFerrite.MappingValues\nFerrite.FunctionValues\nFerrite.BCValues","category":"page"},{"location":"devdocs/FEValues/#Ferrite.GeometryMapping","page":"FEValues","title":"Ferrite.GeometryMapping","text":"GeometryMapping{DiffOrder}(::Type{T}, ip_geo, qr::QuadratureRule)\n\nCreate a GeometryMapping object which contains the geometric\n\nshape values\ngradient values (if DiffOrder ≥ 1)\nhessians values (if DiffOrder ≥ 2)\n\nT<:AbstractFloat gives the numeric type of the values.\n\n\n\n\n\n","category":"type"},{"location":"devdocs/FEValues/#Ferrite.MappingValues","page":"FEValues","title":"Ferrite.MappingValues","text":"MappingValues(J, H)\n\nThe mapping values are calculated based on a geometric_mapping::GeometryMapping along with the cell coordinates, and the stored jacobian, J, and potentially hessian, H, are used when mapping the FunctionValues to the current cell during reinit!.\n\n\n\n\n\n","category":"type"},{"location":"devdocs/FEValues/#Ferrite.FunctionValues","page":"FEValues","title":"Ferrite.FunctionValues","text":"FunctionValues{DiffOrder}(::Type{T}, ip_fun, qr::QuadratureRule, ip_geo::VectorizedInterpolation)\n\nCreate a FunctionValues object containing the shape values and gradients (up to order DiffOrder) for both the reference cell (precalculated) and the real cell (updated in reinit!).\n\n\n\n\n\n","category":"type"},{"location":"devdocs/FEValues/#Ferrite.BCValues","page":"FEValues","title":"Ferrite.BCValues","text":"BCValues(func_interpol::Interpolation, geom_interpol::Interpolation, boundary_type::Union{Type{<:BoundaryIndex}})\n\nBCValues stores the shape values at all facet/faces/edges/vertices (depending on boundary_type) for the geometric interpolation (geom_interpol), for each dof-position determined by the func_interpol. Used mainly by the ConstraintHandler.\n\n\n\n\n\n","category":"type"},{"location":"devdocs/FEValues/#Internal-utilities","page":"FEValues","title":"Internal utilities","text":"","category":"section"},{"location":"devdocs/FEValues/","page":"FEValues","title":"FEValues","text":"Ferrite.embedding_det\nFerrite.shape_value_type(::Ferrite.AbstractValues)\nFerrite.shape_gradient_type\nFerrite.ValuesUpdateFlags","category":"page"},{"location":"devdocs/FEValues/#Ferrite.embedding_det","page":"FEValues","title":"Ferrite.embedding_det","text":"embedding_det(J::SMatrix{3, 2})\n\nEmbedding determinant for surfaces in 3D.\n\nTLDR: \"det(J) =\" ||∂x/∂ξ₁ × ∂x/∂ξ₂||₂\n\nThe transformation theorem for some function f on a 2D surface in 3D space leads to ∫ f ⋅ dS = ∫ f ⋅ (∂x/∂ξ₁ × ∂x/∂ξ₂) dξ₁dξ₂ = ∫ f ⋅ n ||∂x/∂ξ₁ × ∂x/∂ξ₂||₂ dξ₁dξ₂ where ||∂x/∂ξ₁ × ∂x/∂ξ₂||₂ is \"detJ\" and n is the unit normal. See e.g. https://scicomp.stackexchange.com/questions/41741/integration-of-d-1-dimensional-functions-on-finite-element-surfaces for simple explanation. For more details see e.g. the doctoral thesis by Mirza Cenanovic Tangential Calculus [14].\n\n\n\n\n\nembedding_det(J::Union{SMatrix{2, 1}, SMatrix{3, 1}})\n\nEmbedding determinant for curves in 2D and 3D.\n\nTLDR: \"det(J) =\" ||∂x/∂ξ||₂\n\nThe transformation theorem for some function f on a 1D curve in 2D and 3D space leads to ∫ f ⋅ dE = ∫ f ⋅ ∂x/∂ξ dξ = ∫ f ⋅ t ||∂x/∂ξ||₂ dξ where ||∂x/∂ξ||₂ is \"detJ\" and t is \"the unit tangent\". See e.g. https://scicomp.stackexchange.com/questions/41741/integration-of-d-1-dimensional-functions-on-finite-element-surfaces for simple explanation.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/FEValues/#Ferrite.shape_value_type-Tuple{Ferrite.AbstractValues}","page":"FEValues","title":"Ferrite.shape_value_type","text":"shape_value_type(fe_v::AbstractValues)\n\nReturn the type of shape_value(fe_v, q_point, base_function)\n\n\n\n\n\n","category":"method"},{"location":"devdocs/FEValues/#Ferrite.shape_gradient_type","page":"FEValues","title":"Ferrite.shape_gradient_type","text":"shape_gradient_type(fe_v::AbstractValues)\n\nReturn the type of shape_gradient(fe_v, q_point, base_function)\n\n\n\n\n\n","category":"function"},{"location":"devdocs/FEValues/#Ferrite.ValuesUpdateFlags","page":"FEValues","title":"Ferrite.ValuesUpdateFlags","text":"ValuesUpdateFlags(ip_fun::Interpolation; update_gradients = Val(true), update_hessians = Val(false), update_detJdV = Val(true))\n\nCreates a singleton type for specifying what parts of the AbstractValues should be updated. Note that this is internal API used to get type-stable construction. Keyword arguments in AbstractValues constructors are forwarded, and the public API is passing these as Bool, while the ValuesUpdateFlags method supports both boolean and Val(::Bool) keyword args.\n\n\n\n\n\n","category":"type"},{"location":"devdocs/FEValues/#Custom-FEValues","page":"FEValues","title":"Custom FEValues","text":"","category":"section"},{"location":"devdocs/FEValues/","page":"FEValues","title":"FEValues","text":"Custom FEValues, fe_v::AbstractValues, should normally implement the reinit! method. Subtypes of AbstractValues have default implementations for some functions, but require some lower-level access functions, specifically","category":"page"},{"location":"devdocs/FEValues/","page":"FEValues","title":"FEValues","text":"function_value, requires\nshape_value\ngetnquadpoints\ngetnbasefunctions\nfunction_gradient, function_divergence, function_symmetric_gradient, and function_curl requires\nshape_gradient\ngetnquadpoints\ngetnbasefunctions\nspatial_coordinate, requires\ngeometric_value\ngetngeobasefunctions\ngetnquadpoints","category":"page"},{"location":"devdocs/FEValues/#Array-bounds","page":"FEValues","title":"Array bounds","text":"","category":"section"},{"location":"devdocs/FEValues/","page":"FEValues","title":"FEValues","text":"Asking for the nth quadrature point must be inside array bounds if 1 <= n <= getnquadpoints(fe_v). (checkquadpoint can, alternatively, be dispatched to check that n is inbounds.)\nAsking for the ith shape value or gradient must be inside array bounds if 1 <= i <= getnbasefunctions(fe_v)\nAsking for the ith geometric value must be inside array bounds if 1 <= i <= getngeobasefunctions(fe_v)","category":"page"},{"location":"topics/FEValues/#fevalues_topicguide","page":"FEValues","title":"FEValues","text":"","category":"section"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"A key type of object in Ferrite is the so-called FEValues, where the most common ones are CellValues and FacetValues. These objects are used inside the element routines and are used to query the integration weights, shape function values and gradients, and much more; see CellValues and FacetValues. For these values to be correct, it is necessary to reinitialize these for the current cell by using the reinit! function. This function maps the values from the reference cell to the actual cell, a process described in detail below, see Mapping of finite elements. After that, we show an implementation of a SimpleCellValues type to illustrate how CellValues work for the most standard case, excluding the generalizations and optimization that complicates the actual code.","category":"page"},{"location":"topics/FEValues/#mapping_theory","page":"FEValues","title":"Mapping of finite elements","text":"","category":"section"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"The shape functions and gradients stored in an FEValues object, are reinitialized for each cell by calling the reinit! function. The main part of this calculation, considers how to map the values and derivatives of the shape functions, defined on the reference cell, to the actual cell.","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"The geometric mapping of a finite element from the reference coordinates to the real coordinates is shown in the following illustration.","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"(Image: mapping_figure)","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"This mapping is given by the geometric shape functions, hatN_i^g(boldsymbolxi), such that","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"beginalign*\n boldsymbolx(boldsymbolxi) = sum_alpha=1^N hatboldsymbolx_alpha hatN_alpha^g(boldsymbolxi) \n boldsymbolJ = fracmathrmdboldsymbolxmathrmdboldsymbolxi = sum_alpha=1^N hatboldsymbolx_alpha otimes fracmathrmd hatN_alpha^gmathrmdboldsymbolxi\n boldsymbolmathcalH =\n fracmathrmd boldsymbolJmathrmd boldsymbolxi = sum_alpha=1^N hatboldsymbolx_alpha otimes fracmathrmd^2 hatN^g_alphamathrmd boldsymbolxi^2\nendalign*","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"where the defined boldsymbolJ is the jacobian of the mapping, and in some cases we will also need the corresponding hessian, boldsymbolmathcalH (3rd order tensor).","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"We require that the mapping from reference coordinates to real coordinates is diffeomorphic, meaning that we can express boldsymbolx = boldsymbolx(boldsymbolxi(boldsymbolx)), such that","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"beginalign*\n fracmathrmdboldsymbolxmathrmdboldsymbolx = boldsymbolI = fracmathrmdboldsymbolxmathrmdboldsymbolxi cdot fracmathrmdboldsymbolximathrmdboldsymbolx\n quadRightarrowquad\n fracmathrmdboldsymbolximathrmdboldsymbolx = leftfracmathrmdboldsymbolxmathrmdboldsymbolxiright^-1 = boldsymbolJ^-1\nendalign*","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"Depending on the function interpolation, we may want different types of mappings to conserve certain properties of the fields. This results in the different mapping types described below.","category":"page"},{"location":"topics/FEValues/#Identity-mapping","page":"FEValues","title":"Identity mapping","text":"","category":"section"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"Ferrite.IdentityMapping","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"For scalar fields, we always use scalar base functions. For tensorial fields (non-scalar, e.g. vector-fields), the base functions can be constructed from scalar base functions, by using e.g. VectorizedInterpolation. From the perspective of the mapping, however, each component is mapped as an individual scalar base function. And for scalar base functions, we only require that the value of the base function is invariant to the element shape (real coordinate), and only depends on the reference coordinate, i.e.","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"beginalign*\n N(boldsymbolx) = hatN(boldsymbolxi(boldsymbolx))nonumber \n mathrmgrad(N(boldsymbolx)) = fracmathrmdhatNmathrmdboldsymbolxi cdot boldsymbolJ^-1\nendalign*","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"Second order gradients of the shape functions are computed as","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"beginalign*\n mathrmgrad(mathrmgrad(N(boldsymbolx))) = fracmathrmd^2 Nmathrmdboldsymbolx^2 = boldsymbolJ^-T cdot fracmathrmd^2hatNmathrmdboldsymbolxi^2 cdot boldsymbolJ^-1 - boldsymbolJ^-T cdotmathrmgrad(N) cdot boldsymbolmathcalH cdot boldsymbolJ^-1\nendalign*","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"details: Derivation\nThe gradient of the shape functions is obtained using the chain rule:beginalign*\n fracmathrmd Nmathrmdx_i = fracmathrmd hat Nmathrmd xi_rfracmathrmd xi_rmathrmd x_i = fracmathrmd hat Nmathrmd xi_r J^-1_ri\nendalign*For the second order gradients, we first use the product rule on the equation above:beginalign\n fracmathrmd^2 Nmathrmdx_i mathrmdx_j = fracmathrmdmathrmdx_jleftfracmathrmd hat Nmathrmd xi_rright J^-1_ri + fracmathrmd hat Nmathrmd xi_r fracmathrmdJ^-1_rimathrmdx_j\nendalignUsing the fact that fracmathrmdhatf(boldsymbolxi)mathrmdx_j = fracmathrmdhatf(boldsymbolxi)mathrmdxi_s J^-1_sj, the first term in the equation above can be expressed as:beginalign*\n fracmathrmdmathrmdx_jleftfracmathrmd hat Nmathrmd xi_rright J^-1_ri = J^-1_sjfracmathrmdmathrmdxi_sleftfracmathrmd hat Nmathrmd xi_rright J^-1_ri = J^-1_sjleftfracmathrmd^2 hat Nmathrmd xi_smathrmd xi_rright J^-1_ri\nendalign*The second term can be written as:beginalign*\n fracmathrmd hat Nmathrmd xi_rfracmathrmdJ^-1_rimathrmdx_j = fracmathrmd hat Nmathrmd xi_rleftfracmathrmdJ^-1_rimathrmdxi_srightJ^-1_sj = fracmathrmd hat Nmathrmd xi_rleft- J^-1_rkmathcalH_kps J^-1_piright J^-1_sj = - fracmathrmd hat Nmathrmd x_kmathcalH_kps J^-1_piJ^-1_sj\nendalign*where we have used that the inverse of the jacobian can be computed as:beginalign*\n0 = fracmathrmdmathrmdxi_s (J_kr J^-1_ri ) = fracmathrmdJ_kpmathrmdxi_s J^-1_pi + J_kr fracmathrmdJ^-1_rimathrmdxi_s = 0 quad Rightarrow \nendalign*beginalign*\nfracmathrmdJ^-1_rimathrmdxi_s = - J^-1_rkfracmathrmdJ_kpmathrmdxi_s J^-1_pi = - J^-1_rkmathcalH_kps J^-1_pi\nendalign*","category":"page"},{"location":"topics/FEValues/#Covariant-Piola-mapping,-H(curl)","page":"FEValues","title":"Covariant Piola mapping, H(curl)","text":"","category":"section"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"Ferrite.CovariantPiolaMapping","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"The covariant Piola mapping of a vectorial base function preserves the tangential components. For the value, the mapping is defined as","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"beginalign*\n boldsymbolN(boldsymbolx) = boldsymbolJ^-mathrmT cdot hatboldsymbolN(boldsymbolxi(boldsymbolx))\nendalign*","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"which yields the gradient,","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"beginalign*\n mathrmgrad(boldsymbolN(boldsymbolx)) = boldsymbolJ^-T cdot fracmathrmd hatboldsymbolNmathrmd boldsymbolxi cdot boldsymbolJ^-1 - boldsymbolJ^-T cdot lefthatboldsymbolN(boldsymbolxi(boldsymbolx))cdot boldsymbolJ^-1 cdot boldsymbolmathcalHcdot boldsymbolJ^-1right\nendalign*","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"details: Derivation\nExpressing the gradient, mathrmgrad(boldsymbolN), in index notation,beginalign*\n fracmathrmd N_imathrmd x_j = fracmathrmdmathrmd x_j leftJ^-mathrmT_ik hatN_kright = fracmathrmd J^-mathrmT_ikmathrmd x_j hatN_k + J^-mathrmT_ik fracmathrmd hatN_kmathrmd xi_l J_lj^-1\nendalign*Except for a few elements, boldsymbolJ varies as a function of boldsymbolx. The derivative can be calculated asbeginalign*\n fracmathrmd J^-mathrmT_ikmathrmd x_j = fracmathrmd J^-mathrmT_ikmathrmd J_mn fracmathrmd J_mnmathrmd x_j = - J_km^-1 J_in^-T fracmathrmd J_mnmathrmd x_j nonumber \n fracmathrmd J_mnmathrmd x_j = mathcalH_mno J_oj^-1\nendalign*","category":"page"},{"location":"topics/FEValues/#Contravariant-Piola-mapping,-H(div)","page":"FEValues","title":"Contravariant Piola mapping, H(div)","text":"","category":"section"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"Ferrite.ContravariantPiolaMapping","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"The covariant Piola mapping of a vectorial base function preserves the normal components. For the value, the mapping is defined as","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"beginalign*\n boldsymbolN(boldsymbolx) = fracboldsymbolJdet(boldsymbolJ) cdot hatboldsymbolN(boldsymbolxi(boldsymbolx))\nendalign*","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"This gives the gradient","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"beginalign*\n mathrmgrad(boldsymbolN(boldsymbolx)) = boldsymbolmathcalHcdotboldsymbolJ^-1 fracboldsymbolI underlineotimes boldsymbolI cdot hatboldsymbolNdet(boldsymbolJ)\n - leftfracboldsymbolJ cdot hatboldsymbolNdet(boldsymbolJ)right otimes leftboldsymbolJ^-T boldsymbolmathcalH cdot boldsymbolJ^-1right\n + boldsymbolJ cdot fracmathrmd hatboldsymbolNmathrmd boldsymbolxi cdot fracboldsymbolJ^-1det(boldsymbolJ)\nendalign*","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"details: Derivation\nExpressing the gradient, mathrmgrad(boldsymbolN), in index notation,beginalign*\n fracmathrmd N_imathrmd x_j = fracmathrmdmathrmd x_j leftfracJ_ikdet(boldsymbolJ) hatN_kright =nonumber\n = fracmathrmd J_ikmathrmd x_j frachatN_kdet(boldsymbolJ)\n - fracmathrmd det(boldsymbolJ)mathrmd x_j fracJ_ik hatN_kdet(boldsymbolJ)^2\n + fracJ_ikdet(boldsymbolJ) fracmathrmd hatN_kmathrmd xi_l J_lj^-1 \n = mathcalH_ikl J^-1_lj frachatN_kdet(boldsymbolJ)\n - J^-T_mn mathcalH_mnl J^-1_lj fracJ_ik hatN_kdet(boldsymbolJ)\n + fracJ_ikdet(boldsymbolJ) fracmathrmd hatN_kmathrmd xi_l J_lj^-1\nendalign*","category":"page"},{"location":"topics/FEValues/#SimpleCellValues","page":"FEValues","title":"Walkthrough: Creating SimpleCellValues","text":"","category":"section"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"In the following, we walk through how to create a SimpleCellValues type which works similar to Ferrite's CellValues, but is not performance optimized and not as general. The main purpose is to explain how the CellValues works for the standard case of IdentityMapping described above. Please note that several internal functions are used, and these may change without a major version increment. Please see the Developer documentation for their documentation.","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"# Include the example here, but modify the Literate output to suit being embedded\nusing Literate, Markdown\nbase_name = \"SimpleCellValues_literate\"\nLiterate.markdown(string(base_name, \".jl\"); name = base_name, execute = true, credit = false, documenter=false)\ncontent = read(string(base_name, \".md\"), String)\nrm(string(base_name, \".md\"))\nrm(string(base_name, \".jl\"))\nMarkdown.parse(content)","category":"page"},{"location":"topics/FEValues/#Further-reading","page":"FEValues","title":"Further reading","text":"","category":"section"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"defelement.com\nKirby (2017) [7]","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"DocTestSetup = :(using Ferrite)","category":"page"},{"location":"topics/grid/#Grid","page":"Grid","title":"Grid","text":"","category":"section"},{"location":"topics/grid/#Mesh-reading","page":"Grid","title":"Mesh reading","text":"","category":"section"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"A Ferrite Grid can be generated with the generate_grid function. More advanced meshes can be imported with the FerriteMeshParser.jl (from Abaqus input files), or even created and translated with the Gmsh.jl and FerriteGmsh.jl package, respectively.","category":"page"},{"location":"topics/grid/#FerriteGmsh.jl","page":"Grid","title":"FerriteGmsh.jl","text":"","category":"section"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"FerriteGmsh.jl supports all defined cells with an alias in Ferrite.jl as well as the 3D Serendipity Cell{3,20,6}. Either, a mesh is created on the fly with the gmsh API or a mesh in .msh or .geo format can be read and translated with the FerriteGmsh.togrid function.","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"FerriteGmsh.togrid","category":"page"},{"location":"topics/grid/#FerriteGmsh.togrid","page":"Grid","title":"FerriteGmsh.togrid","text":"togrid(filename::String; domain=\"\")\n\nOpen the Gmsh file filename (ie a .geo or .msh file) and return the corresponding Ferrite.Grid.\n\n\n\n\n\ntogrid(; domain=\"\")\n\nGenerate a Ferrite.Grid from the current active/open model in the Gmsh library.\n\n\n\n\n\n","category":"function"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"FerriteGmsh supports currently the translation of cellsets and facetsets. Such sets are defined in Gmsh as PhysicalGroups of dimension dim and dim-1, respectively. In case only a part of the mesh is the domain, the domain can be specified by providing the keyword argument domain the name of the PhysicalGroups in the FerriteGmsh.togrid function.","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"note: Why you should read a .msh file\nReading a .msh file is the advertised way, since otherwise you remesh whenever you run the code. Further, if you choose to read the grid directly from the current model of the gmsh API you get artificial nodes, which doesn't harm the FE computation, but maybe distort your sophisticated grid operations (if present). For more information, see this issue.","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"If you want to read another, not yet supported cell from gmsh, consider to open a PR at FerriteGmsh that extends the gmshtoferritecell dict and if needed, reorder the element nodes by dispatching FerriteGmsh.translate_elements. The reordering of nodes is necessary if the Gmsh ordering doesn't match the one from Ferrite. Gmsh ordering is documented here. For an exemplary usage of Gmsh.jl and FerriteGmsh.jl, consider the Stokes flow and Incompressible Navier-Stokes Equations via DifferentialEquations.jl example.","category":"page"},{"location":"topics/grid/#FerriteMeshParser.jl","page":"Grid","title":"FerriteMeshParser.jl","text":"","category":"section"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"FerriteMeshParser.jl converts the mesh in an Abaqus input file (.inp) to a Ferrite.Grid with its function get_ferrite_grid. The translations for most of Abaqus' standard 2d and 3d continuum elements to a Ferrite.AbstractCell are defined. Custom translations can be given as input, which can be used to import other (custom) elements or to override the default translation.","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"FerriteMeshParser.get_ferrite_grid","category":"page"},{"location":"topics/grid/#FerriteMeshParser.get_ferrite_grid","page":"Grid","title":"FerriteMeshParser.get_ferrite_grid","text":"function get_ferrite_grid(\n filename; \n meshformat=AutomaticMeshFormat(), \n user_elements=Dict{String,DataType}(), \n generate_facetsets=true\n )\n\nCreate a Ferrite.Grid by reading in the file specified by filename.\n\nOptional arguments:\n\nmeshformat: Which format the mesh is given in, normally automatically detected by the file extension\nuser_elements: Used to add extra elements not supported, might require a separate cell constructor.\ngenerate_facetsets: Should facesets be automatically generated from all nodesets?\n\n\n\n\n\n","category":"function"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"If you are missing the translation of an Abaqus element that is equivalent to a Ferrite.AbstractCell, consider to open an issue or a pull request.","category":"page"},{"location":"topics/grid/#Grid-datastructure","page":"Grid","title":"Grid datastructure","text":"","category":"section"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"In Ferrite a Grid is a collection of Nodes and Cells and is parameterized in its physical dimensionality and cell type. Nodes are points in the physical space and can be initialized by a N-Tuple, where N corresponds to the dimensions.","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"n1 = Node((0.0, 0.0))","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"Cells are defined based on the Node IDs. Hence, they collect IDs in a N-Tuple. Consider the following 2D mesh:","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"(Image: global mesh)","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"The cells of the grid can be described in the following way","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"cells = [Quadrilateral((1, 2, 5, 4)),\n Quadrilateral((2, 3, 6, 5)),\n Quadrilateral((4, 5, 8, 7)),\n Quadrilateral((5, 6, 9, 8))]","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"where each Quadrilateral <: AbstractCell is defined by the tuple of node IDs. Additionally, the data structure Grid contains node-, cell-, facet-, and vertexsets. Each of these sets is defined by a Dict{String, OrderedSet}.","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"Node- and cellsets are represented by an OrderedSet{Int}, giving a set of node or cell ID, respectively.","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"Facet- and vertexsets are represented by OrderedSet{<:BoundaryIndex}, where BoundaryIndex is a FacetIndex or VertexIndex respectively. FacetIndex and VertexIndex wraps a Tuple, (global_cell_id, local_facet_id) and (global_cell_id, local_vertex_id), where the local IDs are defined according to the reference shapes, see Reference shapes.","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"The highlighted facets, i.e. the two edges from node ID 3 to 6 and from 6 to 9, on the right hand side of our test mesh can now be described as","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"boundary_facets = [(3, 6), (6, 9)]","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"i.e. by using the node IDs of the reference shape vertices.","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"The first of these can be found as the 2nd facet of the 2nd cell.","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"using Ferrite #hide\nFerrite.facets(Quadrilateral((2, 3, 6, 5)))","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"The unique representation of an entity is given by the sorted version of this tuple. While we could use this information to construct a facet set, Ferrite can construct this set by filtering based on the coordinates, using addfacetset!.","category":"page"},{"location":"topics/grid/#AbstractGrid","page":"Grid","title":"AbstractGrid","text":"","category":"section"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"It can be very useful to use a grid type for a certain special case, e.g. mixed cell types, adaptivity, IGA, etc. In order to define your own <: AbstractGrid you need to fulfill the AbstractGrid interface. In case that certain structures are preserved from the Ferrite.Grid type, you don't need to dispatch on your own type, but rather rely on the fallback AbstractGrid dispatch.","category":"page"},{"location":"topics/grid/#Example","page":"Grid","title":"Example","text":"","category":"section"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"As a starting point, we choose a minimal working example from the test suite:","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"struct SmallGrid{dim,N,C<:Ferrite.AbstractCell} <: Ferrite.AbstractGrid{dim}\n nodes_test::Vector{NTuple{dim,Float64}}\n cells_test::NTuple{N,C}\nend","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"Here, the names of the fields as well as their underlying datastructure changed compared to the Grid type. This would lead to the fact, that any usage with the utility functions and DoF management will not work. So, we need to feed into the interface how to handle this subtyped datastructure. We start with the utility functions that are associated with the cells of the grid:","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"Ferrite.getcells(grid::SmallGrid) = grid.cells_test\nFerrite.getcells(grid::SmallGrid, v::Union{Int, Vector{Int}}) = grid.cells_test[v]\nFerrite.getncells(grid::SmallGrid{dim,N}) where {dim,N} = N\nFerrite.getcelltype(grid::SmallGrid) = eltype(grid.cells_test)\nFerrite.getcelltype(grid::SmallGrid, i::Int) = typeof(grid.cells_test[i])","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"Next, we define some helper functions that take care of the node handling.","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"Ferrite.getnodes(grid::SmallGrid) = grid.nodes_test\nFerrite.getnodes(grid::SmallGrid, v::Union{Int, Vector{Int}}) = grid.nodes_test[v]\nFerrite.getnnodes(grid::SmallGrid) = length(grid.nodes_test)\nFerrite.get_coordinate_eltype(::SmallGrid) = Float64\nFerrite.get_coordinate_type(::SmallGrid{dim}) where dim = Vec{dim,Float64}\nFerrite.nnodes_per_cell(grid::SmallGrid, i::Int=1) = Ferrite.nnodes(grid.cells_test[i])","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"These definitions make many of Ferrite functions work out of the box, e.g. you can now call getcoordinates(grid, cellid) on the SmallGrid.","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"Now, you would be able to assemble the heat equation example over the new custom SmallGrid type. Note that this particular subtype isn't able to handle boundary entity sets and so, you can't describe boundaries with it. In order to use boundaries, e.g. for Dirichlet constraints in the ConstraintHandler, you would need to dispatch the AbstractGrid sets utility functions on SmallGrid.","category":"page"},{"location":"topics/grid/#Topology","page":"Grid","title":"Topology","text":"","category":"section"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"Ferrite.jl's Grid type offers experimental features w.r.t. topology information. The functions getneighborhood and facetskeleton are the interface to obtain topological information. The getneighborhood can construct lists of directly connected entities based on a given entity (CellIndex, FacetIndex, FaceIndex, EdgeIndex, or VertexIndex). The facetskeleton function can be used to evaluate integrals over material interfaces or computing element interface values such as jumps.","category":"page"},{"location":"topics/sparse_matrix/#topic-sparse-matrix","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"","category":"section"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"An important property of the finite element method is that it results in sparse matrices for the linear systems to be solved. On this page the topic of sparsity and sparse matrices are discussed.","category":"page"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"Pages = [\"sparse_matrix.md\"]\nDepth = 2:2","category":"page"},{"location":"topics/sparse_matrix/#Sparsity-pattern","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern","text":"","category":"section"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"The sparse structure of the linear system depends on many factors such as e.g. the weak form, the discretization, and the choice of interpolation(s). In the end it boils down to how the degrees of freedom (DoFs) couple with each other. The most common reason that two DoFs couple is because they belong to the same element. Note, however, that this is not guaranteed to result in a coupling since it depends on the specific weak form that is being discretized, see e.g. Increasing the sparsity. Boundary conditions and constraints can also result in additional DoF couplings.","category":"page"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"If DoFs i and j couple, then the computed value in the eventual matrix will be structurally nonzero[1]. In this case the entry (i, j) should be included in the sparsity pattern. Conversely, if DoFs i and j don't couple, then the computed value will be zero. In this case the entry (i, j) should not be included in the sparsity pattern since there is no need to allocate memory for entries that will be zero.","category":"page"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"The sparsity, i.e. the ratio of zero-entries to the total number of entries, is often[2] very high and taking advantage of this results in huge savings in terms of memory. For example, in a problem with 10^6 DoFs there will be a matrix of size 10^6 times 10^6. If all 10^12 entries of this matrix had to be stored (0% sparsity) as double precision (Float64, 8 bytes) it would require 8 TB of memory. If instead the sparsity is 99.9973% (which is the case when solving the heat equation on a three dimensional hypercube with linear Lagrange interpolation) this would be reduced to 216 MB.","category":"page"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"[1]: Structurally nonzero means that there is a possibility of a nonzero value even though the computed value might become zero in the end for various reasons.","category":"page"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"[2]: At least for most practical problems using low order interpolations.","category":"page"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"details: Sparsity pattern example\nTo give an example, in this one-dimensional heat problem (see the Heat equation tutorial for the weak form) we have 4 nodes with 3 elements in between. For simplicity DoF numbers and node numbers are the same but this is not true in general since nodes and DoFs can be numbered independently (and in fact are numbered independently in Ferrite).1 ----- 2 ----- 3 ----- 4Assuming we use linear Lagrange interpolation (the \"hat functions\") this will give the following connections according to the weak form:Trial function 1 couples with test functions 1 and 2 (entries (1, 1) and (1, 2) included in the sparsity pattern)\nTrial function 2 couples with test functions 1, 2, and 3 (entries (2, 1), (2, 2), and (2, 3) included in the sparsity pattern)\nTrial function 3 couples with test functions 2, 3, and 4 (entries (3, 2), (3, 3), and (3, 4) included in the sparsity pattern)\nTrial function 4 couples with test functions 3 and 4 (entries (4, 3) and (4, 4) included in the sparsity pattern)The resulting sparsity pattern would look like this:4×4 SparseArrays.SparseMatrixCSC{Float64, Int64} with 10 stored entries:\n 0.0 0.0 ⋅ ⋅\n 0.0 0.0 0.0 ⋅\n ⋅ 0.0 0.0 0.0\n ⋅ ⋅ 0.0 0.0Moreover, if the problem is solved with periodic boundary conditions, for example by constraining the value on the right side to the value on the left side, there will be additional couplings. In the example above, this means that DoF 4 should be equal to DoFSince DoF 4 is constrained it has to be eliminated from the system. Existing entriesthat include DoF 4 are (3, 4), (4, 3), and (4, 4). Given the simple constraint in this case we can simply replace DoF 4 with DoF 1 in these entries and we end up with entries (3, 1), (1, 3), and (1, 1). This results in two new entries: (3, 1) and (1, 3) (entry (1, 1) is already included).","category":"page"},{"location":"topics/sparse_matrix/#Creating-sparsity-patterns","page":"Sparsity pattern and sparse matrices","title":"Creating sparsity patterns","text":"","category":"section"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"Creating a sparsity pattern can be quite expensive if not done properly and therefore Ferrite provides efficient methods and data structures for this. In general the sparsity pattern is not known in advance and has to be created incrementally. To make this incremental construction efficient it is necessary to use a dynamic data structure which allow for fast insertions.","category":"page"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"The sparsity pattern also serves as a \"matrix builder\". When all entries are inserted into the sparsity pattern the dynamic data structure is typically converted, or \"compressed\", into a sparse matrix format such as e.g. the compressed sparse row (CSR) format or the compressed sparse column (CSC) format, where the latter is the default sparse matrix type implemented in the SparseArrays standard library. These matrix formats allow for fast linear algebra operations, such as factorizations and matrix-vector multiplications, that are needed when the linear system is solved. See Instantiating the sparse matrix for more details.","category":"page"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"In summary, a dynamic structure is more efficient when incrementally building the pattern by inserting new entries, and a static or compressed structure is more efficient for linear algebra operations.","category":"page"},{"location":"topics/sparse_matrix/#Basic-sparsity-patterns-construction","page":"Sparsity pattern and sparse matrices","title":"Basic sparsity patterns construction","text":"","category":"section"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"Working with the sparsity pattern explicitly is in many cases not necessary. For basic usage (e.g. when only one matrix needed, when no customization of the pattern is required, etc) there exist convenience methods of allocate_matrix that return the matrix directly. Most examples in this documentation don't deal with the sparsity pattern explicitly because the basic method suffice. See also Instantiating the sparse matrix for more details.","category":"page"},{"location":"topics/sparse_matrix/#Custom-sparsity-pattern-construction","page":"Sparsity pattern and sparse matrices","title":"Custom sparsity pattern construction","text":"","category":"section"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"In more advanced cases there might be a need for more fine grained control of the sparsity pattern. The following steps are typically taken when constructing a sparsity pattern in Ferrite:","category":"page"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"Initialize an empty pattern: This can be done by either using the init_sparsity_pattern(dh) function or by using a constructor directly. init_sparsity_pattern will return a default pattern type that is compatible with the DofHandler. In some cases you might require another type of pattern (for example a blocked pattern, see Blocked sparsity pattern) and in that case you can use the constructor directly.\nAdd entries to the pattern: There are a number of functions that add entries to the pattern:\nadd_sparsity_entries! is a convenience method for performing the common task of calling add_cell_entries!, add_interface_entries!, and add_constraint_entries! after each other (see below).\nadd_cell_entries! adds entries for all couplings between the DoFs within each element. These entries correspond to assembling the standard element matrix and is thus almost always required.\nadd_interface_entries! adds entries for couplings between the DoFs in neighboring elements. These entries are required when integrating along internal interfaces between elements (e.g. for discontinuous Galerkin methods).\nadd_constraint_entries! adds entries required from constraints and boundary conditions in the ConstraintHandler. Note that this operation depends on existing entries in the pattern and must be called as the last operation on the pattern.\nFerrite.add_entry! adds a single entry to the pattern. This can be used if you need to add custom entries that are not covered by the other functions.\nInstantiate the matrix: A sparse matrix can be created from the sparsity pattern using allocate_matrix, see Instantiating the sparse matrix below for more details.","category":"page"},{"location":"topics/sparse_matrix/#Increasing-the-sparsity","page":"Sparsity pattern and sparse matrices","title":"Increasing the sparsity","text":"","category":"section"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"By default, when creating a sparsity pattern, it is assumed that each DoF within an element couple with with all other DoFs in the element.","category":"page"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"todo: Todo\nDiscuss the coupling keyword argument.\nDiscuss the keep_constrained keyword argument.","category":"page"},{"location":"topics/sparse_matrix/#Blocked-sparsity-pattern","page":"Sparsity pattern and sparse matrices","title":"Blocked sparsity pattern","text":"","category":"section"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"todo: Todo\nDiscuss BlockSparsityPattern and BlockArrays extension.","category":"page"},{"location":"topics/sparse_matrix/#Instantiating-the-sparse-matrix","page":"Sparsity pattern and sparse matrices","title":"Instantiating the sparse matrix","text":"","category":"section"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"As mentioned above, for many simple cases there is no need to work with the sparsity pattern directly and using methods of allocate_matrix that take the DofHandler as input is enough, for example:","category":"page"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"K = allocate_matrix(dh, ch)","category":"page"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"allocate_matrix is also used to instantiate a matrix from a sparsity pattern, for example:","category":"page"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"K = allocate_matrix(sp)","category":"page"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"note: Multiple matrices with the same pattern\nFor some problems there is a need for multiple matrices with the same sparsity pattern, for example a mass matrix and a stiffness matrix. In this case it is more efficient to create the sparsity pattern once and then instantiate both matrices from it.","category":"page"},{"location":"reference/dofhandler/","page":"Degrees of Freedom","title":"Degrees of Freedom","text":"DocTestSetup = :(using Ferrite)","category":"page"},{"location":"reference/dofhandler/#Degrees-of-Freedom","page":"Degrees of Freedom","title":"Degrees of Freedom","text":"","category":"section"},{"location":"reference/dofhandler/","page":"Degrees of Freedom","title":"Degrees of Freedom","text":"Degrees of freedom (dofs) are distributed by the DofHandler.","category":"page"},{"location":"reference/dofhandler/","page":"Degrees of Freedom","title":"Degrees of Freedom","text":"DofHandler\nSubDofHandler","category":"page"},{"location":"reference/dofhandler/#Ferrite.DofHandler","page":"Degrees of Freedom","title":"Ferrite.DofHandler","text":"DofHandler(grid::Grid)\n\nConstruct a DofHandler based on the grid grid.\n\nAfter construction any number of discrete fields can be added to the DofHandler using add!. Construction is finalized by calling close!.\n\nBy default fields are added to all elements of the grid. Refer to SubDofHandler for restricting fields to subdomains of the grid.\n\nExamples\n\ndh = DofHandler(grid)\nip_u = Lagrange{RefTriangle, 2}()^2 # vector interpolation for a field u\nip_p = Lagrange{RefTriangle, 1}() # scalar interpolation for a field p\nadd!(dh, :u, ip_u)\nadd!(dh, :p, ip_p)\nclose!(dh)\n\n\n\n\n\n","category":"type"},{"location":"reference/dofhandler/#Ferrite.SubDofHandler","page":"Degrees of Freedom","title":"Ferrite.SubDofHandler","text":"SubDofHandler(dh::AbstractDofHandler, cellset::AbstractVecOrSet{Int})\n\nCreate an sdh::SubDofHandler from the parent dh, pertaining to the cells in cellset. This allows you to add fields to parts of the domain, or using different interpolations or cell types (e.g. Triangles and Quadrilaterals). All fields and cell types must be the same in one SubDofHandler.\n\nAfter construction any number of discrete fields can be added to the SubDofHandler using add!. Construction is finalized by calling close! on the parent dh.\n\nExamples\n\nWe assume we have a grid containing \"Triangle\" and \"Quadrilateral\" cells, including the cellsets \"triangles\" and \"quadilaterals\" for to these cells.\n\ndh = DofHandler(grid)\n\nsdh_tri = SubDofHandler(dh, getcellset(grid, \"triangles\"))\nip_tri = Lagrange{RefTriangle, 2}()^2 # vector interpolation for a field u\nadd!(sdh_tri, :u, ip_tri)\n\nsdh_quad = SubDofHandler(dh, getcellset(grid, \"quadilaterals\"))\nip_quad = Lagrange{RefQuadrilateral, 2}()^2 # vector interpolation for a field u\nadd!(sdh_quad, :u, ip_quad)\n\nclose!(dh) # Finalize by closing the parent\n\n\n\n\n\n","category":"type"},{"location":"reference/dofhandler/#Adding-fields-to-the-DofHandlers","page":"Degrees of Freedom","title":"Adding fields to the DofHandlers","text":"","category":"section"},{"location":"reference/dofhandler/","page":"Degrees of Freedom","title":"Degrees of Freedom","text":"add!(::DofHandler, ::Symbol, ::Interpolation)\nadd!(::SubDofHandler, ::Symbol, ::Interpolation)\nclose!(::DofHandler)","category":"page"},{"location":"reference/dofhandler/#Ferrite.add!-Tuple{DofHandler, Symbol, Interpolation}","page":"Degrees of Freedom","title":"Ferrite.add!","text":"add!(dh::DofHandler, name::Symbol, ip::Interpolation)\n\nAdd a field called name approximated by ip to the DofHandler dh.\n\nThe field is added to all cells of the underlying grid, use SubDofHandlers if the grid contains multiple cell types, or to add the field to subset of all the cells.\n\n\n\n\n\n","category":"method"},{"location":"reference/dofhandler/#Ferrite.add!-Tuple{SubDofHandler, Symbol, Interpolation}","page":"Degrees of Freedom","title":"Ferrite.add!","text":"add!(sdh::SubDofHandler, name::Symbol, ip::Interpolation)\n\nAdd a field called name approximated by ip to the SubDofHandler sdh.\n\n\n\n\n\n","category":"method"},{"location":"reference/dofhandler/#Ferrite.close!-Tuple{DofHandler}","page":"Degrees of Freedom","title":"Ferrite.close!","text":"close!(dh::AbstractDofHandler)\n\nCloses dh and creates degrees of freedom for each cell.\n\n\n\n\n\n","category":"method"},{"location":"reference/dofhandler/#Dof-renumbering","page":"Degrees of Freedom","title":"Dof renumbering","text":"","category":"section"},{"location":"reference/dofhandler/","page":"Degrees of Freedom","title":"Degrees of Freedom","text":"renumber!\nDofOrder.FieldWise\nDofOrder.ComponentWise","category":"page"},{"location":"reference/dofhandler/#Ferrite.renumber!","page":"Degrees of Freedom","title":"Ferrite.renumber!","text":"renumber!(dh::AbstractDofHandler, order)\nrenumber!(dh::AbstractDofHandler, ch::ConstraintHandler, order)\n\nRenumber the degrees of freedom in the DofHandler and/or ConstraintHandler according to the ordering order.\n\norder can be given by one of the following options:\n\nA permutation vector perm::AbstractVector{Int} such that dof i is renumbered to perm[i].\nDofOrder.FieldWise() for renumbering dofs field wise.\nDofOrder.ComponentWise() for renumbering dofs component wise.\nDofOrder.Ext{T} for \"external\" renumber permutations, see documentation for DofOrder.Ext for details.\n\nwarning: Warning\nThe dof numbering in the DofHandler and ConstraintHandler must always be consistent. It is therefore necessary to either renumber before creating the ConstraintHandler in the first place, or to renumber the DofHandler and the ConstraintHandler together.\n\n\n\n\n\n","category":"function"},{"location":"reference/dofhandler/#Ferrite.DofOrder.FieldWise","page":"Degrees of Freedom","title":"Ferrite.DofOrder.FieldWise","text":"DofOrder.FieldWise()\nDofOrder.FieldWise(target_blocks::Vector{Int})\n\nDof order passed to renumber! to renumber global dofs field wise resulting in a globally blocked system.\n\nThe default behavior is to group dofs of each field into their own block, with the same order as in the DofHandler. This can be customized by passing a vector of the same length as the total number of fields in the DofHandler (see getfieldnames(dh)) that maps each field to a \"target block\": to renumber a DofHandler with three fields :u, :v, :w such that dofs for :u and :w end up in the first global block, and dofs for :v in the second global block use DofOrder.FieldWise([1, 2, 1]).\n\nThis renumbering is stable such that the original relative ordering of dofs within each target block is maintained.\n\n\n\n\n\n","category":"type"},{"location":"reference/dofhandler/#Ferrite.DofOrder.ComponentWise","page":"Degrees of Freedom","title":"Ferrite.DofOrder.ComponentWise","text":"DofOrder.ComponentWise()\nDofOrder.ComponentWise(target_blocks::Vector{Int})\n\nDof order passed to renumber! to renumber global dofs component wise resulting in a globally blocked system.\n\nThe default behavior is to group dofs of each component into their own block, with the same order as in the DofHandler. This can be customized by passing a vector of length ncomponents that maps each component to a \"target block\" (see DofOrder.FieldWise for details).\n\nThis renumbering is stable such that the original relative ordering of dofs within each target block is maintained.\n\n\n\n\n\n","category":"type"},{"location":"reference/dofhandler/#Common-methods","page":"Degrees of Freedom","title":"Common methods","text":"","category":"section"},{"location":"reference/dofhandler/","page":"Degrees of Freedom","title":"Degrees of Freedom","text":"ndofs\nndofs_per_cell\ndof_range\ncelldofs\ncelldofs!","category":"page"},{"location":"reference/dofhandler/#Ferrite.ndofs","page":"Degrees of Freedom","title":"Ferrite.ndofs","text":"ndofs(dh::AbstractDofHandler)\n\nReturn the number of degrees of freedom in dh\n\n\n\n\n\n","category":"function"},{"location":"reference/dofhandler/#Ferrite.ndofs_per_cell","page":"Degrees of Freedom","title":"Ferrite.ndofs_per_cell","text":"ndofs_per_cell(dh::AbstractDofHandler[, cell::Int=1])\n\nReturn the number of degrees of freedom for the cell with index cell.\n\nSee also ndofs.\n\n\n\n\n\n","category":"function"},{"location":"reference/dofhandler/#Ferrite.dof_range","page":"Degrees of Freedom","title":"Ferrite.dof_range","text":"dof_range(sdh::SubDofHandler, field_idx::Int)\ndof_range(sdh::SubDofHandler, field_name::Symbol)\ndof_range(dh:DofHandler, field_name::Symbol)\n\nReturn the local dof range for a given field. The field can be specified by its name or index, where field_idx represents the index of a field within a SubDofHandler and field_idxs is a tuple of the SubDofHandler-index within the DofHandler and the field_idx.\n\nnote: Note\nThe dof_range of a field can vary between different SubDofHandlers. Therefore, it is advised to use the field_idxs or refer to a given SubDofHandler directly in case several SubDofHandlers exist. Using the field_name will always refer to the first occurrence of field within the DofHandler.\n\nExample:\n\njulia> grid = generate_grid(Triangle, (3, 3))\nGrid{2, Triangle, Float64} with 18 Triangle cells and 16 nodes\n\njulia> dh = DofHandler(grid); add!(dh, :u, 3); add!(dh, :p, 1); close!(dh);\n\njulia> dof_range(dh, :u)\n1:9\n\njulia> dof_range(dh, :p)\n10:12\n\njulia> dof_range(dh, (1,1)) # field :u\n1:9\n\njulia> dof_range(dh.subdofhandlers[1], 2) # field :p\n10:12\n\n\n\n\n\n","category":"function"},{"location":"reference/dofhandler/#Ferrite.celldofs","page":"Degrees of Freedom","title":"Ferrite.celldofs","text":"celldofs(dh::AbstractDofHandler, i::Int)\n\nReturn a vector with the degrees of freedom that belong to cell i.\n\nSee also celldofs!.\n\n\n\n\n\n","category":"function"},{"location":"reference/dofhandler/#Ferrite.celldofs!","page":"Degrees of Freedom","title":"Ferrite.celldofs!","text":"celldofs!(global_dofs::Vector{Int}, dh::AbstractDofHandler, i::Int)\n\nStore the degrees of freedom that belong to cell i in global_dofs.\n\nSee also celldofs.\n\n\n\n\n\n","category":"function"},{"location":"reference/dofhandler/#Grid-iterators","page":"Degrees of Freedom","title":"Grid iterators","text":"","category":"section"},{"location":"reference/dofhandler/","page":"Degrees of Freedom","title":"Degrees of Freedom","text":"CellCache\nCellIterator\nFacetCache\nFacetIterator\nInterfaceCache\nInterfaceIterator","category":"page"},{"location":"reference/dofhandler/#Ferrite.CellCache","page":"Degrees of Freedom","title":"Ferrite.CellCache","text":"CellCache(grid::Grid)\nCellCache(dh::AbstractDofHandler)\n\nCreate a cache object with pre-allocated memory for the nodes, coordinates, and dofs of a cell. The cache is updated for a new cell by calling reinit!(cache, cellid) where cellid::Int is the cell id.\n\nMethods with CellCache\n\nreinit!(cc, i): reinitialize the cache for cell i\ncellid(cc): get the cell id of the currently cached cell\ngetnodes(cc): get the global node ids of the cell\ngetcoordinates(cc): get the coordinates of the cell\ncelldofs(cc): get the global dof ids of the cell\nreinit!(fev, cc): reinitialize CellValues or FacetValues\n\nSee also CellIterator.\n\n\n\n\n\n","category":"type"},{"location":"reference/dofhandler/#Ferrite.CellIterator","page":"Degrees of Freedom","title":"Ferrite.CellIterator","text":"CellIterator(grid::Grid, cellset=1:getncells(grid))\nCellIterator(dh::AbstractDofHandler, cellset=1:getncells(dh))\n\nCreate a CellIterator to conveniently iterate over all, or a subset, of the cells in a grid. The elements of the iterator are CellCaches which are properly reinit!ialized. See CellCache for more details.\n\nLooping over a CellIterator, i.e.:\n\nfor cc in CellIterator(grid, cellset)\n # ...\nend\n\nis thus simply convenience for the following equivalent snippet:\n\ncc = CellCache(grid)\nfor idx in cellset\n reinit!(cc, idx)\n # ...\nend\n\nwarning: Warning\nCellIterator is stateful and should not be used for things other than for-looping (e.g. broadcasting over, or collecting the iterator may yield unexpected results).\n\n\n\n\n\n","category":"type"},{"location":"reference/dofhandler/#Ferrite.FacetCache","page":"Degrees of Freedom","title":"Ferrite.FacetCache","text":"FacetCache(grid::Grid)\nFacetCache(dh::AbstractDofHandler)\n\nCreate a cache object with pre-allocated memory for the nodes, coordinates, and dofs of a cell suitable for looping over faces in a grid. The cache is updated for a new face by calling reinit!(cache, fi::FacetIndex).\n\nMethods with fc::FacetCache\n\nreinit!(fc, fi): reinitialize the cache for face fi::FacetIndex\ncellid(fc): get the current cellid\ngetnodes(fc): get the global node ids of the cell\ngetcoordinates(fc): get the coordinates of the cell\ncelldofs(fc): get the global dof ids of the cell\nreinit!(fv, fc): reinitialize FacetValues\n\nSee also FacetIterator.\n\n\n\n\n\n","category":"type"},{"location":"reference/dofhandler/#Ferrite.FacetIterator","page":"Degrees of Freedom","title":"Ferrite.FacetIterator","text":"FacetIterator(gridordh::Union{Grid,AbstractDofHandler}, facetset::AbstractVecOrSet{FacetIndex})\n\nCreate a FacetIterator to conveniently iterate over the faces in facestet. The elements of the iterator are FacetCaches which are properly reinit!ialized. See FacetCache for more details.\n\nLooping over a FacetIterator, i.e.:\n\nfor fc in FacetIterator(grid, facetset)\n # ...\nend\n\nis thus simply convenience for the following equivalent snippet: ```julia fc = FacetCache(grid) for faceindex in facetset reinit!(fc, faceindex) # ... end\n\n\n\n\n\n","category":"type"},{"location":"reference/dofhandler/#Ferrite.InterfaceCache","page":"Degrees of Freedom","title":"Ferrite.InterfaceCache","text":"InterfaceCache(grid::Grid)\nInterfaceCache(dh::AbstractDofHandler)\n\nCreate a cache object with pre-allocated memory for the nodes, coordinates, and dofs of an interface. The cache is updated for a new cell by calling reinit!(cache, facet_a, facet_b) where facet_a::FacetIndex and facet_b::FacetIndex are the two interface faces.\n\nStruct fields of InterfaceCache\n\nic.a :: FacetCache: face cache for the first face of the interface\nic.b :: FacetCache: face cache for the second face of the interface\nic.dofs :: Vector{Int}: global dof ids for the interface (union of ic.a.dofs and ic.b.dofs)\n\nMethods with InterfaceCache\n\nreinit!(cache::InterfaceCache, facet_a::FacetIndex, facet_b::FacetIndex): reinitialize the cache for a new interface\ninterfacedofs(ic): get the global dof ids of the interface\n\nSee also InterfaceIterator.\n\n\n\n\n\n","category":"type"},{"location":"reference/dofhandler/#Ferrite.InterfaceIterator","page":"Degrees of Freedom","title":"Ferrite.InterfaceIterator","text":"InterfaceIterator(grid::Grid, [topology::ExclusiveTopology])\nInterfaceIterator(dh::AbstractDofHandler, [topology::ExclusiveTopology])\n\nCreate an InterfaceIterator to conveniently iterate over all the interfaces in a grid. The elements of the iterator are InterfaceCaches which are properly reinit!ialized. See InterfaceCache for more details. Looping over an InterfaceIterator, i.e.:\n\nfor ic in InterfaceIterator(grid, topology)\n # ...\nend\n\nis thus simply convenience for the following equivalent snippet for grids of dimensions > 1:\n\nic = InterfaceCache(grid, topology)\nfor face in topology.face_skeleton\n neighborhood = topology.face_face_neighbor[face[1], face[2]]\n isempty(neighborhood) && continue\n neighbor_face = neighborhood[1]\n reinit!(ic, face, neighbor_face)\n # ...\nend\n\nwarning: Warning\nInterfaceIterator is stateful and should not be used for things other than for-looping (e.g. broadcasting over, or collecting the iterator may yield unexpected results).\n\n\n\n\n\n","category":"type"},{"location":"topics/degrees_of_freedom/","page":"Degrees of Freedom","title":"Degrees of Freedom","text":"using Ferrite","category":"page"},{"location":"topics/degrees_of_freedom/#Degrees-of-Freedom","page":"Degrees of Freedom","title":"Degrees of Freedom","text":"","category":"section"},{"location":"topics/degrees_of_freedom/","page":"Degrees of Freedom","title":"Degrees of Freedom","text":"The distribution and numbering of degrees of freedom (dofs) are handled by the DofHandler. The DofHandler will be used to query information about the dofs. For example we can obtain the dofs for a particular cell, which we need when assembling the system.","category":"page"},{"location":"topics/degrees_of_freedom/","page":"Degrees of Freedom","title":"Degrees of Freedom","text":"The DofHandler is based on the grid. Here we create a simple grid with Triangle cells, and then create a DofHandler based on the grid","category":"page"},{"location":"topics/degrees_of_freedom/","page":"Degrees of Freedom","title":"Degrees of Freedom","text":"grid = generate_grid(Triangle, (20, 20))\ndh = DofHandler(grid)\n# hide","category":"page"},{"location":"topics/degrees_of_freedom/#Fields","page":"Degrees of Freedom","title":"Fields","text":"","category":"section"},{"location":"topics/degrees_of_freedom/","page":"Degrees of Freedom","title":"Degrees of Freedom","text":"Before we can distribute the dofs we need to specify fields. A field is simply the unknown function(s) we are solving for. To add a field we need a name (a Symbol) and the the interpolation describing the shape functions for the field. Here we add a scalar field :p, interpolated using linear (degree 1) shape functions on a triangle, and a vector field :u, also interpolated with linear shape functions on a triangle, but raised to the power 2 to indicate that it is a vector field with 2 components (for a 2D problem).","category":"page"},{"location":"topics/degrees_of_freedom/","page":"Degrees of Freedom","title":"Degrees of Freedom","text":"add!(dh, :p, Lagrange{RefTriangle, 1}())\nadd!(dh, :u, Lagrange{RefTriangle, 1}()^2)\n# hide","category":"page"},{"location":"topics/degrees_of_freedom/","page":"Degrees of Freedom","title":"Degrees of Freedom","text":"Finally, when we have added all the fields, we have to close! the DofHandler. When the DofHandler is closed it will traverse the grid and distribute all the dofs for the fields we added.","category":"page"},{"location":"topics/degrees_of_freedom/","page":"Degrees of Freedom","title":"Degrees of Freedom","text":"close!(dh)","category":"page"},{"location":"topics/degrees_of_freedom/#Ordering-of-Dofs","page":"Degrees of Freedom","title":"Ordering of Dofs","text":"","category":"section"},{"location":"topics/degrees_of_freedom/","page":"Degrees of Freedom","title":"Degrees of Freedom","text":"todo: Todo\nDescribe dof ordering within elements (vertices -> edges -> faces -> volumes) and dof_range. Describe (global) dof renumbering","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"EditURL = \"../literate-tutorials/dg_heat_equation.jl\"","category":"page"},{"location":"tutorials/dg_heat_equation/#tutorial-dg-heat-equation","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"","category":"section"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"(Image: )","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"Figure 1: Temperature field on the unit square with an internal uniform heat source solved with inhomogeneous Dirichlet boundary conditions on the left and right boundaries and flux on the top and bottom boundaries.","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"tip: Tip\nThis example is also available as a Jupyter notebook: dg_heat_equation.ipynb.","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"This example was developed as part of the Google summer of code funded project \"Discontinuous Galerkin Infrastructure For the finite element toolbox Ferrite.jl\"","category":"page"},{"location":"tutorials/dg_heat_equation/#Introduction","page":"Discontinuous Galerkin heat equation","title":"Introduction","text":"","category":"section"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"This tutorial extends Tutorial 1: Heat equation by using the discontinuous Galerkin method. The reader is expected to have gone through Tutorial 1: Heat equation before proceeding with this tutorial. The main differences between the two tutorials are the interface integral terms in the weak form, the boundary conditions, and some implementation differences explained in the commented program below.","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"The strong form considered in this tutorial is given as follows","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":" -boldsymbolnabla cdot boldsymbolnabla(u) = 1 quad textbfx in Omega","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"with the inhomogeneous Dirichlet boundary conditions","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"u(textbfx) = 1 quad textbfx in partial Omega_D^+ = lbracetextbfx x_1 = 10rbrace \nu(textbfx) = -1 quad textbfx in partial Omega_D^- = lbracetextbfx x_1 = -10rbrace","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"and Neumann boundary conditions","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"boldsymbolnabla (u(textbfx)) cdot boldsymboln = 1 quad textbfx in partial Omega_N^+ = lbracetextbfx x_2 = 10rbrace \nboldsymbolnabla (u(textbfx)) cdot boldsymboln = -1 quad textbfx in partial Omega_N^- = lbracetextbfx x_2 = -10rbrace","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"The following definitions of average and jump on interfaces between elements are adopted in this tutorial:","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":" u = frac12(u^+ + u^-)quad llbracket urrbracket = u^+ boldsymboln^+ + u^- boldsymboln^-","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"where u^+ and u^- are the temperature on the two sides of the interface.","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"details: Derivation of the weak form for homogeneous Dirichlet boundary condition\nDefining boldsymbolsigma as the gradient of the temperature field the equation can be expressed as boldsymbolsigma = boldsymbolnabla (u)\n -boldsymbolnabla cdot boldsymbolsigma = 1Multiplying by test functions $ \\boldsymbol{\\tau} $ and $ \\delta u $ respectively and integrating over the domain, int_Omega boldsymbolsigma cdot boldsymboltau mathrmdOmega = int_Omega boldsymbolnabla (u) cdot boldsymboltau mathrmdOmega\n -int_Omega boldsymbolnabla cdot boldsymbolsigma delta u mathrmdOmega = int_Omega delta u mathrmdOmegaIntegrating by parts and applying divergence theorem, int_Omega boldsymbolsigma cdot boldsymboltau mathrmdOmega = -int_Omega u (boldsymbolnabla cdot boldsymboltau) mathrmdOmega + int_Gamma hatu boldsymboltau cdot boldsymboln mathrmdGamma\n int_Omega boldsymbolsigma cdot boldsymbolnabla (delta u) mathrmdOmega = int_Omega delta u mathrmdOmega + int_Gamma delta u boldsymbolhatsigma cdot boldsymboln mathrmdGammaWhere boldsymboln is the outwards pointing normal, Gamma is the union of the elements' boundaries, and hatu hatsigma are the numerical fluxes. Substituting the integrals of form int_Gamma q boldsymbolphi cdot boldsymboln mathrmdGamma = int_Gamma llbracket qrrbracket cdot boldsymbolphi mathrmdGamma + int_Gamma^0 q llbracket boldsymbolphirrbracket mathrmdGamma^0where Gamma^0 Gamma setminus partial Omega, and the jump of the vector-valued field boldsymbolphi is defined as llbracket boldsymbolphirrbracket = boldsymbolphi^+ cdot boldsymboln^+ + boldsymbolphi^- cdot boldsymboln^-with the jumps and averages results in int_Omega boldsymbolsigma cdot boldsymboltau mathrmdOmega = -int_Omega u (boldsymbolnabla cdot boldsymboltau) mathrmdOmega + int_Gamma llbracket haturrbracket cdot boldsymboltau mathrmdGamma + int_Gamma^0 hatu llbracket boldsymboltaurrbracket mathrmdGamma^0\n int_Omega boldsymbolsigma cdot boldsymbolnabla (delta u) mathrmdOmega = int_Omega delta u mathrmdOmega + int_Gamma llbracket delta urrbracket cdot hatboldsymbolsigma mathrmdGamma + int_Gamma^0 delta u llbracket hatboldsymbolsigmarrbracket mathrmdGamma^0Integrating $ \\int_\\Omega [\\boldsymbol{\\nabla} (u)] \\cdot \\boldsymbol{\\tau} \\,\\mathrm{d}\\Omega $ by parts and applying divergence theorem without using numerical flux, then substitute in the equation to obtain a weak form. int_Omega boldsymbolsigma cdot boldsymboltau mathrmdOmega = int_Omega boldsymbolnabla (u) cdot boldsymboltau mathrmdOmega + int_Gamma llbracket hatu - urrbracket cdot boldsymboltau mathrmdGamma + int_Gamma^0 hatu - u llbracket boldsymboltaurrbracket mathrmdGamma^0\n int_Omega boldsymbolsigma cdot boldsymbolnabla (delta u) mathrmdOmega = int_Omega delta u mathrmdOmega + int_Gamma llbracket delta urrbracket cdot hatboldsymbolsigma mathrmdGamma + int_Gamma^0 delta u llbracket hatboldsymbolsigmarrbracket mathrmdGamma^0Substituting boldsymboltau = boldsymbolnabla (delta u)results in int_Omega boldsymbolsigma cdot boldsymbolnabla (delta u) mathrmdOmega = int_Omega boldsymbolnabla (u) cdot boldsymbolnabla (delta u) mathrmdOmega + int_Gamma llbracket hatu - urrbracket cdot boldsymbolnabla (delta u) mathrmdGamma + int_Gamma^0 hatu - u llbracket boldsymbolnabla (delta u)rrbracket mathrmdGamma^0\n int_Omega boldsymbolsigma cdot boldsymbolnabla (delta u) mathrmdOmega = int_Omega delta u mathrmdOmega + int_Gamma llbracket delta urrbracket cdot hatboldsymbolsigma mathrmdGamma + int_Gamma^0 delta u llbracket hatboldsymbolsigmarrbracket mathrmdGamma^0Combining the two equations, int_Omega boldsymbolnabla (u) cdot boldsymbolnabla (delta u) mathrmdOmega + int_Gamma llbracket hatu - urrbracket cdot boldsymbolnabla (delta u) mathrmdGamma + int_Gamma^0 hatu - u llbracket boldsymbolnabla (delta u)rrbracket mathrmdGamma^0 - int_Gamma llbracket delta urrbracket cdot hatboldsymbolsigma mathrmdGamma - int_Gamma^0 delta u llbracket hatboldsymbolsigmarrbracket mathrmdGamma^0 = int_Omega delta u mathrmdOmegaThe numerical fluxes chosen for the interior penalty method are boldsymbolhatsigma = boldsymbolnabla (u) - alpha(llbracket urrbracket) on Gamma, hatu = u on the interfaces between elements Gamma^0 Gamma setminus partial Omega, and hatu = 0 on partial Omega. Such choice results in hatboldsymbolsigma = boldsymbolnabla (u) - alpha(llbracket urrbracket), llbracket haturrbracket = 0, hatu = u, llbracket hatboldsymbolsigmarrbracket = 0 and the equation becomes int_Omega boldsymbolnabla (u) cdot boldsymbolnabla (delta u) mathrmdOmega - int_Gamma llbracket urrbracket cdot boldsymbolnabla (delta u) mathrmdGamma - int_Gamma llbracket delta urrbracket cdot boldsymbolnabla (u) - llbracket delta urrbracket cdot alpha(llbracket urrbracket) mathrmdGamma = int_Omega delta u mathrmdOmegaWhere alpha(llbracket urrbracket) = mu llbracket urrbracketWhere mu = eta h_e^-1, the weak form becomes int_Omega boldsymbolnabla (u) cdot boldsymbolnabla (delta u) mathrmdOmega - int_Gamma llbracket u rrbracket cdot boldsymbolnabla (delta u) + llbracket delta u rrbracket cdot boldsymbolnabla (u) mathrmdGamma + int_Gamma fracetah_e llbracket urrbracket cdot llbracket delta urrbracket mathrmdGamma = int_Omega delta u mathrmdOmega","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"Since partial Omega is constrained with both Dirichlet and Neumann boundary conditions the term int_partial Omega boldsymbolnabla (u) cdot boldsymboln delta u mathrmd Omega can be expressed as an integral over partial Omega_N, where partial Omega_N is the boundaries with only prescribed Neumann boundary condition, The resulting weak form is given given as follows: Find u in mathbbU such that","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":" int_Omega boldsymbolnabla (u) cdot boldsymbolnabla (delta u) mathrmdOmega - int_Gamma^0 llbracket urrbracket cdot boldsymbolnabla (delta u) + llbracket delta urrbracket cdot boldsymbolnabla (u) mathrmdGamma^0 + int_Gamma^0 fracetah_e llbracket urrbracket cdot llbracket delta urrbracket mathrmdGamma^0 = int_Omega delta u mathrmdOmega + int_partial Omega_N (boldsymbolnabla (u) cdot boldsymboln) delta u mathrmd partial Omega_N","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"where h_e is the characteristic size (the diameter of the interface), and eta is a large enough positive number independent of h_e [5], delta u in mathbbT is a test function, and where mathbbU and mathbbT are suitable trial and test function sets, respectively. We use the value eta = (1 + O)^D, where O is the polynomial order and D the dimension, in this tutorial.","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"More details on DG formulations for elliptic problems can be found in [6].","category":"page"},{"location":"tutorials/dg_heat_equation/#Commented-Program","page":"Discontinuous Galerkin heat equation","title":"Commented Program","text":"","category":"section"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"Now we solve the problem in Ferrite. What follows is a program spliced with comments. The full program, without comments, can be found in the next section.","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"First we load Ferrite and other packages, and generate grid just like the heat equation tutorial","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"using Ferrite, SparseArrays\ndim = 2;\ngrid = generate_grid(Quadrilateral, ntuple(_ -> 20, dim));\nnothing #hide","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"We construct the topology information which is used later for generating the sparsity pattern for stiffness matrix.","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"topology = ExclusiveTopology(grid);\nnothing #hide","category":"page"},{"location":"tutorials/dg_heat_equation/#Trial-and-test-functions","page":"Discontinuous Galerkin heat equation","title":"Trial and test functions","text":"","category":"section"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"CellValues, FacetValues, and InterfaceValues facilitate the process of evaluating values and gradients of test and trial functions (among other things). To define these we need to specify an interpolation space for the shape functions. We use DiscontinuousLagrange functions based on the two-dimensional reference quadrilateral. We also define a quadrature rule based on the same reference element. We combine the interpolation and the quadrature rule to CellValues and InterfaceValues object. Note that InterfaceValues object contains two FacetValues objects which can be used individually.","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"order = 1;\nip = DiscontinuousLagrange{RefQuadrilateral, order}();\nqr = QuadratureRule{RefQuadrilateral}(2);\nnothing #hide","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"For FacetValues and InterfaceValues we use FacetQuadratureRule","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"facet_qr = FacetQuadratureRule{RefQuadrilateral}(2);\ncellvalues = CellValues(qr, ip);\nfacetvalues = FacetValues(facet_qr, ip);\ninterfacevalues = InterfaceValues(facet_qr, ip);\nnothing #hide","category":"page"},{"location":"tutorials/dg_heat_equation/#Penalty-term-parameters","page":"Discontinuous Galerkin heat equation","title":"Penalty term parameters","text":"","category":"section"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"We define functions to calculate the diameter of a set of points, used to calculate the characteristic size h_e in the assembly routine.","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"getdistance(p1::Vec{N, T}, p2::Vec{N, T}) where {N, T} = norm(p1 - p2);\ngetdiameter(cell_coords::Vector{Vec{N, T}}) where {N, T} = maximum(getdistance.(cell_coords, reshape(cell_coords, (1, :))));\nnothing #hide","category":"page"},{"location":"tutorials/dg_heat_equation/#Degrees-of-freedom","page":"Discontinuous Galerkin heat equation","title":"Degrees of freedom","text":"","category":"section"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"Degrees of freedom distribution is handled using DofHandler as usual","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"dh = DofHandler(grid)\nadd!(dh, :u, ip)\nclose!(dh);\nnothing #hide","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"However, when generating the sparsity pattern we need to pass the topology and the cross-element coupling matrix when we're using discontinuous interpolations. The cross-element coupling matrix is of size [1,1] in this case as we have only one field and one DofHandler.","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"K = allocate_matrix(dh, topology = topology, interface_coupling = trues(1, 1));\nnothing #hide","category":"page"},{"location":"tutorials/dg_heat_equation/#Boundary-conditions","page":"Discontinuous Galerkin heat equation","title":"Boundary conditions","text":"","category":"section"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"The Dirichlet boundary conditions are treated as usual by a ConstraintHandler.","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"ch = ConstraintHandler(dh)\nadd!(ch, Dirichlet(:u, getfacetset(grid, \"right\"), (x, t) -> 1.0))\nadd!(ch, Dirichlet(:u, getfacetset(grid, \"left\"), (x, t) -> -1.0))\nclose!(ch);\nnothing #hide","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"Furthermore, we define partial Omega_N as the union of the facet sets with Neumann boundary conditions for later use","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"∂Ωₙ = union(\n getfacetset(grid, \"top\"),\n getfacetset(grid, \"bottom\"),\n);\nnothing #hide","category":"page"},{"location":"tutorials/dg_heat_equation/#Assembling-the-linear-system","page":"Discontinuous Galerkin heat equation","title":"Assembling the linear system","text":"","category":"section"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"Now we have all the pieces needed to assemble the linear system, K u = f. Assembling of the global system is done by looping over i) all the elements in order to compute the element contributions K_e and f_e, ii) all the interfaces to compute their contributions K_i, and iii) all the Neumann boundary facets to compute their contributions f_e. All these local contributions are then assembled into the appropriate place in the global K and f.","category":"page"},{"location":"tutorials/dg_heat_equation/#Local-assembly","page":"Discontinuous Galerkin heat equation","title":"Local assembly","text":"","category":"section"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"We define the functions","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"assemble_element! to compute the contributions K_e and f_e of volume integrals over an element using cellvalues.\nassemble_interface! to compute the contribution K_i of surface integrals over an interface using interfacevalues.\nassemble_boundary! to compute the contribution f_e of surface integrals over a boundary facet using FacetValues.","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"function assemble_element!(Ke::Matrix, fe::Vector, cellvalues::CellValues)\n n_basefuncs = getnbasefunctions(cellvalues)\n # Reset to 0\n fill!(Ke, 0)\n fill!(fe, 0)\n # Loop over quadrature points\n for q_point in 1:getnquadpoints(cellvalues)\n # Quadrature weight\n dΩ = getdetJdV(cellvalues, q_point)\n # Loop over test shape functions\n for i in 1:n_basefuncs\n δu = shape_value(cellvalues, q_point, i)\n ∇δu = shape_gradient(cellvalues, q_point, i)\n # Add contribution to fe\n fe[i] += δu * dΩ\n # Loop over trial shape functions\n for j in 1:n_basefuncs\n ∇u = shape_gradient(cellvalues, q_point, j)\n # Add contribution to Ke\n Ke[i, j] += (∇δu ⋅ ∇u) * dΩ\n end\n end\n end\n return Ke, fe\nend\n\nfunction assemble_interface!(Ki::Matrix, iv::InterfaceValues, μ::Float64)\n # Reset to 0\n fill!(Ki, 0)\n # Loop over quadrature points\n for q_point in 1:getnquadpoints(iv)\n # Get the normal to facet A\n normal = getnormal(iv, q_point)\n # Get the quadrature weight\n dΓ = getdetJdV(iv, q_point)\n # Loop over test shape functions\n for i in 1:getnbasefunctions(iv)\n # Multiply the jump by the negative normal to get the definition from the theory section.\n δu_jump = shape_value_jump(iv, q_point, i) * (-normal)\n ∇δu_avg = shape_gradient_average(iv, q_point, i)\n # Loop over trial shape functions\n for j in 1:getnbasefunctions(iv)\n # Multiply the jump by the negative normal to get the definition from the theory section.\n u_jump = shape_value_jump(iv, q_point, j) * (-normal)\n ∇u_avg = shape_gradient_average(iv, q_point, j)\n # Add contribution to Ki\n Ki[i, j] += -(δu_jump ⋅ ∇u_avg + ∇δu_avg ⋅ u_jump) * dΓ + μ * (δu_jump ⋅ u_jump) * dΓ\n end\n end\n end\n return Ki\nend\n\nfunction assemble_boundary!(fe::Vector, fv::FacetValues)\n # Reset to 0\n fill!(fe, 0)\n # Loop over quadrature points\n for q_point in 1:getnquadpoints(fv)\n # Get the normal to facet A\n normal = getnormal(fv, q_point)\n # Get the quadrature weight\n ∂Ω = getdetJdV(fv, q_point)\n # Loop over test shape functions\n for i in 1:getnbasefunctions(fv)\n δu = shape_value(fv, q_point, i)\n boundary_flux = normal[2]\n fe[i] = boundary_flux * δu * ∂Ω\n end\n end\n return fe\nend\nnothing # hide","category":"page"},{"location":"tutorials/dg_heat_equation/#Global-assembly","page":"Discontinuous Galerkin heat equation","title":"Global assembly","text":"","category":"section"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"We define the function assemble_global to loop over all elements and internal facets (interfaces), as well as the external facets involved in Neumann boundary conditions.","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"function assemble_global(cellvalues::CellValues, facetvalues::FacetValues, interfacevalues::InterfaceValues, K::SparseMatrixCSC, dh::DofHandler, order::Int, dim::Int)\n # Allocate the element stiffness matrix and element force vector\n n_basefuncs = getnbasefunctions(cellvalues)\n Ke = zeros(n_basefuncs, n_basefuncs)\n fe = zeros(n_basefuncs)\n Ki = zeros(n_basefuncs * 2, n_basefuncs * 2)\n # Allocate global force vector f\n f = zeros(ndofs(dh))\n # Create an assembler\n assembler = start_assemble(K, f)\n # Loop over all cells\n for cell in CellIterator(dh)\n # Reinitialize cellvalues for this cell\n reinit!(cellvalues, cell)\n # Compute volume integral contribution\n assemble_element!(Ke, fe, cellvalues)\n # Assemble Ke and fe into K and f\n assemble!(assembler, celldofs(cell), Ke, fe)\n end\n # Loop over all interfaces\n for ic in InterfaceIterator(dh)\n # Reinitialize interfacevalues for this interface\n reinit!(interfacevalues, ic)\n # Calculate the characteristic size hₑ as the face diameter\n interfacecoords = ∩(getcoordinates(ic)...)\n hₑ = getdiameter(interfacecoords)\n # Calculate μ\n μ = (1 + order)^dim / hₑ\n # Compute interface surface integrals contribution\n assemble_interface!(Ki, interfacevalues, μ)\n # Assemble Ki into K\n assemble!(assembler, interfacedofs(ic), Ki)\n end\n # Loop over domain boundaries with Neumann boundary conditions\n for fc in FacetIterator(dh, ∂Ωₙ)\n # Reinitialize facetvalues for this boundary facet\n reinit!(facetvalues, fc)\n # Compute boundary facet surface integrals contribution\n assemble_boundary!(fe, facetvalues)\n # Assemble fe into f\n assemble!(f, celldofs(fc), fe)\n end\n return K, f\nend\nK, f = assemble_global(cellvalues, facetvalues, interfacevalues, K, dh, order, dim);\nnothing # hide","category":"page"},{"location":"tutorials/dg_heat_equation/#Solution-of-the-system","page":"Discontinuous Galerkin heat equation","title":"Solution of the system","text":"","category":"section"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"The solution of the system is independent of the discontinuous discretization and the application of constraints, linear solve, and exporting is done as usual.","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"apply!(K, f, ch)\nu = K \\ f;\nVTKGridFile(\"dg_heat_equation\", dh) do vtk\n write_solution(vtk, dh, u)\nend;\nnothing #hide","category":"page"},{"location":"tutorials/dg_heat_equation/#References","page":"Discontinuous Galerkin heat equation","title":"References","text":"","category":"section"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"L. Mu, J. Wang, Y. Wang and X. Ye. Interior penalty discontinuous Galerkin method on very general polygonal and polyhedral meshes. Journal of Computational and Applied Mathematics 255, 432–440 (2014).\n\n\n\nD. N. Arnold, F. Brezzi, B. Cockburn and L. D. Marini. Unified Analysis of Discontinuous Galerkin Methods for Elliptic Problems. SIAM Journal on Numerical Analysis 39, 1749–1779 (2002). Accessed on Dec 20, 2023.\n\n\n\n","category":"page"},{"location":"tutorials/dg_heat_equation/#heat_equation-DG-plain-program","page":"Discontinuous Galerkin heat equation","title":"Plain program","text":"","category":"section"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"Here follows a version of the program without any comments. The file is also available here: dg_heat_equation.jl.","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"using Ferrite, SparseArrays\ndim = 2;\ngrid = generate_grid(Quadrilateral, ntuple(_ -> 20, dim));\n\ntopology = ExclusiveTopology(grid);\n\norder = 1;\nip = DiscontinuousLagrange{RefQuadrilateral, order}();\nqr = QuadratureRule{RefQuadrilateral}(2);\n\nfacet_qr = FacetQuadratureRule{RefQuadrilateral}(2);\ncellvalues = CellValues(qr, ip);\nfacetvalues = FacetValues(facet_qr, ip);\ninterfacevalues = InterfaceValues(facet_qr, ip);\n\ngetdistance(p1::Vec{N, T}, p2::Vec{N, T}) where {N, T} = norm(p1 - p2);\ngetdiameter(cell_coords::Vector{Vec{N, T}}) where {N, T} = maximum(getdistance.(cell_coords, reshape(cell_coords, (1, :))));\n\ndh = DofHandler(grid)\nadd!(dh, :u, ip)\nclose!(dh);\n\nK = allocate_matrix(dh, topology = topology, interface_coupling = trues(1, 1));\n\nch = ConstraintHandler(dh)\nadd!(ch, Dirichlet(:u, getfacetset(grid, \"right\"), (x, t) -> 1.0))\nadd!(ch, Dirichlet(:u, getfacetset(grid, \"left\"), (x, t) -> -1.0))\nclose!(ch);\n\n∂Ωₙ = union(\n getfacetset(grid, \"top\"),\n getfacetset(grid, \"bottom\"),\n);\n\nfunction assemble_element!(Ke::Matrix, fe::Vector, cellvalues::CellValues)\n n_basefuncs = getnbasefunctions(cellvalues)\n # Reset to 0\n fill!(Ke, 0)\n fill!(fe, 0)\n # Loop over quadrature points\n for q_point in 1:getnquadpoints(cellvalues)\n # Quadrature weight\n dΩ = getdetJdV(cellvalues, q_point)\n # Loop over test shape functions\n for i in 1:n_basefuncs\n δu = shape_value(cellvalues, q_point, i)\n ∇δu = shape_gradient(cellvalues, q_point, i)\n # Add contribution to fe\n fe[i] += δu * dΩ\n # Loop over trial shape functions\n for j in 1:n_basefuncs\n ∇u = shape_gradient(cellvalues, q_point, j)\n # Add contribution to Ke\n Ke[i, j] += (∇δu ⋅ ∇u) * dΩ\n end\n end\n end\n return Ke, fe\nend\n\nfunction assemble_interface!(Ki::Matrix, iv::InterfaceValues, μ::Float64)\n # Reset to 0\n fill!(Ki, 0)\n # Loop over quadrature points\n for q_point in 1:getnquadpoints(iv)\n # Get the normal to facet A\n normal = getnormal(iv, q_point)\n # Get the quadrature weight\n dΓ = getdetJdV(iv, q_point)\n # Loop over test shape functions\n for i in 1:getnbasefunctions(iv)\n # Multiply the jump by the negative normal to get the definition from the theory section.\n δu_jump = shape_value_jump(iv, q_point, i) * (-normal)\n ∇δu_avg = shape_gradient_average(iv, q_point, i)\n # Loop over trial shape functions\n for j in 1:getnbasefunctions(iv)\n # Multiply the jump by the negative normal to get the definition from the theory section.\n u_jump = shape_value_jump(iv, q_point, j) * (-normal)\n ∇u_avg = shape_gradient_average(iv, q_point, j)\n # Add contribution to Ki\n Ki[i, j] += -(δu_jump ⋅ ∇u_avg + ∇δu_avg ⋅ u_jump) * dΓ + μ * (δu_jump ⋅ u_jump) * dΓ\n end\n end\n end\n return Ki\nend\n\nfunction assemble_boundary!(fe::Vector, fv::FacetValues)\n # Reset to 0\n fill!(fe, 0)\n # Loop over quadrature points\n for q_point in 1:getnquadpoints(fv)\n # Get the normal to facet A\n normal = getnormal(fv, q_point)\n # Get the quadrature weight\n ∂Ω = getdetJdV(fv, q_point)\n # Loop over test shape functions\n for i in 1:getnbasefunctions(fv)\n δu = shape_value(fv, q_point, i)\n boundary_flux = normal[2]\n fe[i] = boundary_flux * δu * ∂Ω\n end\n end\n return fe\nend\n\nfunction assemble_global(cellvalues::CellValues, facetvalues::FacetValues, interfacevalues::InterfaceValues, K::SparseMatrixCSC, dh::DofHandler, order::Int, dim::Int)\n # Allocate the element stiffness matrix and element force vector\n n_basefuncs = getnbasefunctions(cellvalues)\n Ke = zeros(n_basefuncs, n_basefuncs)\n fe = zeros(n_basefuncs)\n Ki = zeros(n_basefuncs * 2, n_basefuncs * 2)\n # Allocate global force vector f\n f = zeros(ndofs(dh))\n # Create an assembler\n assembler = start_assemble(K, f)\n # Loop over all cells\n for cell in CellIterator(dh)\n # Reinitialize cellvalues for this cell\n reinit!(cellvalues, cell)\n # Compute volume integral contribution\n assemble_element!(Ke, fe, cellvalues)\n # Assemble Ke and fe into K and f\n assemble!(assembler, celldofs(cell), Ke, fe)\n end\n # Loop over all interfaces\n for ic in InterfaceIterator(dh)\n # Reinitialize interfacevalues for this interface\n reinit!(interfacevalues, ic)\n # Calculate the characteristic size hₑ as the face diameter\n interfacecoords = ∩(getcoordinates(ic)...)\n hₑ = getdiameter(interfacecoords)\n # Calculate μ\n μ = (1 + order)^dim / hₑ\n # Compute interface surface integrals contribution\n assemble_interface!(Ki, interfacevalues, μ)\n # Assemble Ki into K\n assemble!(assembler, interfacedofs(ic), Ki)\n end\n # Loop over domain boundaries with Neumann boundary conditions\n for fc in FacetIterator(dh, ∂Ωₙ)\n # Reinitialize facetvalues for this boundary facet\n reinit!(facetvalues, fc)\n # Compute boundary facet surface integrals contribution\n assemble_boundary!(fe, facetvalues)\n # Assemble fe into f\n assemble!(f, celldofs(fc), fe)\n end\n return K, f\nend\nK, f = assemble_global(cellvalues, facetvalues, interfacevalues, K, dh, order, dim);\n\napply!(K, f, ch)\nu = K \\ f;\nVTKGridFile(\"dg_heat_equation\", dh) do vtk\n write_solution(vtk, dh, u)\nend;","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"This page was generated using Literate.jl.","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"EditURL = \"../literate-tutorials/hyperelasticity.jl\"","category":"page"},{"location":"tutorials/hyperelasticity/#tutorial-hyperelasticity","page":"Hyperelasticity","title":"Hyperelasticity","text":"","category":"section"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"Keywords: hyperelasticity, finite strain, large deformations, Newton's method, conjugate gradient, automatic differentiation","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"(Image: hyperelasticity.png)","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"Figure 1: Cube loaded in torsion modeled with a hyperelastic material model and finite strain.","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"tip: Tip\nThis example is also available as a Jupyter notebook: hyperelasticity.ipynb.","category":"page"},{"location":"tutorials/hyperelasticity/#Introduction","page":"Hyperelasticity","title":"Introduction","text":"","category":"section"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"In this example we will solve a problem in a finite strain setting using an hyperelastic material model. In order to compute the stress we will use automatic differentiation, to solve the non-linear system we use Newton's method, and for solving the Newton increment we use conjugate gradients.","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"The weak form is expressed in terms of the first Piola-Kirchoff stress mathbfP as follows: Find mathbfu in mathbbU such that","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"int_Omega nabla_mathbfX delta mathbfu mathbfP(mathbfu) mathrmdOmega =\nint_Omega delta mathbfu cdot mathbfb mathrmdOmega + int_Gamma_mathrmN\ndelta mathbfu cdot mathbft mathrmdGamma\nquad forall delta mathbfu in mathbbU^0","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"where mathbfu is the unknown displacement field, mathbfb is the body force acting on the reference domain, mathbft is the traction acting on the Neumann part of the reference domain's boundary, and where mathbbU and mathbbU^0 are suitable trial and test sets. Omega denotes the reference (sometimes also called initial or material) domain. Gradients are defined with respect to the reference domain, here denoted with an mathbfX. Formally this is expressed as (nabla_mathbfX bullet)_ij = fracpartial(bullet)_ipartial X_j. Note that for large deformation problems it is also possible that gradients and integrals are defined on the deformed (sometimes also called current or spatial) domain, depending on the specific formulation.","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"The specific problem we will solve in this example is the cube from Figure 1: On one side we apply a rotation using Dirichlet boundary conditions, on the opposite side we fix the displacement with a homogeneous Dirichlet boundary condition, and on the remaining four sides we apply a traction in the normal direction of the surface. In addition, a body force is applied in one direction.","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"In addition to Ferrite.jl and Tensors.jl, this examples uses TimerOutputs.jl for timing the program and print a summary at the end, ProgressMeter.jl for showing a simple progress bar, and IterativeSolvers.jl for solving the linear system using conjugate gradients.","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"using Ferrite, Tensors, TimerOutputs, ProgressMeter, IterativeSolvers","category":"page"},{"location":"tutorials/hyperelasticity/#Hyperelastic-material-model","page":"Hyperelasticity","title":"Hyperelastic material model","text":"","category":"section"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"The stress can be derived from an energy potential, defined in terms of the right Cauchy-Green tensor mathbfC = mathbfF^mathrmT cdot mathbfF, where mathbfF = mathbfI + nabla_mathbfX mathbfu is the deformation gradient. We shall use the compressible neo-Hookean model from Wikipedia with the potential","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"Psi(mathbfC) = underbracefracmu2 (I_1 - 3)_W(mathbfC) underbrace- mu ln(J) + fraclambda2 (J - 1)^2_U(J)","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"where I_1 = mathrmtr(mathbfC) is the first invariant, J = sqrtdet(mathbfC) and mu and lambda material parameters.","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"details: Extra details on compressible neo-Hookean formulations\nThe Neo-Hooke model is only a well defined terminology in the incompressible case. Thus, only W(mathbfC) specifies the neo-Hookean behavior, the volume penalty U(J) can vary in different formulations. In order to obtain a well-posed problem, it is crucial to choose a convex formulation of U(J). Other examples for U(J) can be found, e.g. in [3, Eq. (6.138)] beta^-2 (beta ln J + J^-beta -1)where [4, Eq. (2.37)] published a non-generalized version with beta=-2. This shows the possible variety of U(J) while all of them refer to compressible neo-Hookean models. Sometimes the modified first invariant overlineI_1=fracI_1I_3^13 is used in W(mathbfC) instead of I_1.","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"From the potential we obtain the second Piola-Kirchoff stress mathbfS as","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"mathbfS = 2 fracpartial Psipartial mathbfC","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"and the tangent of mathbfS as","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"fracpartial mathbfSpartial mathbfC = 2 fracpartial^2 Psipartial mathbfC^2","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"Finally, for the finite element problem we need mathbfP and fracpartial mathbfPpartial mathbfF, which can be obtained by using the following relations:","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"beginalign*\nmathbfP = mathbfF cdot mathbfS\nfracpartial mathbfPpartial mathbfF = mathbfI barotimes mathbfS + 2 mathbfF cdot\nfracpartial mathbfSpartial mathbfC mathbfF^mathrmT barotimes mathbfI\nendalign*","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"details: Derivation of $\\partial \\mathbf{P} / \\partial \\mathbf{F}$\nTip: See knutam.github.io/tensors for an explanation of the index notation used in this derivation.Using the product rule, the chain rule, and the relations mathbfP = mathbfF cdot mathbfS and mathbfC = mathbfF^mathrmT cdot mathbfF, we obtain the following:beginaligned\nfracpartial P_ijpartial F_kl =\nfracpartial (F_imS_mj)partial F_kl =\nfracpartial F_impartial F_klS_mj +\nF_imfracpartial S_mjpartial F_kl =\ndelta_ikdelta_ml S_mj +\nF_imfracpartial S_mjpartial C_nofracpartial C_nopartial F_kl =\ndelta_ikS_lj +\nF_imfracpartial S_mjpartial C_no\nfracpartial (F^mathrmT_npF_po)partial F_kl =\ndelta_ikS^mathrmT_jl +\nF_imfracpartial S_mjpartial C_no\nleft(\nfracpartial F^mathrmT_nppartial F_klF_po +\nF^mathrmT_npfracpartial F_popartial F_kl\nright) =\ndelta_ikS_jl +\nF_imfracpartial S_mjpartial C_no\n(delta_nl delta_pk F_po + F^mathrmT_npdelta_pk delta_ol) =\ndelta_ikS_lj +\nF_imfracpartial S_mjpartial C_no\n(F^mathrmT_ok delta_nl + F^mathrmT_nk delta_ol) =\ndelta_ikS_jl +\n2 F_im fracpartial S_mjpartial C_no\nF^mathrmT_nk delta_ol \nfracpartial mathbfPpartial mathbfF =\nmathbfIbarotimesmathbfS +\n2 mathbfF cdot fracpartial mathbfSpartial mathbfC\n mathbfF^mathrmT barotimes mathbfI\nendalignedwhere we used the fact that mathbfS is symmetric (S_lj = S_jl) and that fracpartial mathbfSpartial mathbfC is minor symmetric (fracpartial S_mjpartial C_no = fracpartial S_mjpartial C_on).","category":"page"},{"location":"tutorials/hyperelasticity/#Implementation-of-material-model-using-automatic-differentiation","page":"Hyperelasticity","title":"Implementation of material model using automatic differentiation","text":"","category":"section"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"We can implement the material model as follows, where we utilize automatic differentiation for the stress and the tangent, and thus only define the potential:","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"struct NeoHooke\n μ::Float64\n λ::Float64\nend\n\nfunction Ψ(C, mp::NeoHooke)\n μ = mp.μ\n λ = mp.λ\n Ic = tr(C)\n J = sqrt(det(C))\n return μ / 2 * (Ic - 3 - 2 * log(J)) + λ / 2 * (J - 1)^2\nend\n\nfunction constitutive_driver(C, mp::NeoHooke)\n # Compute all derivatives in one function call\n ∂²Ψ∂C², ∂Ψ∂C = Tensors.hessian(y -> Ψ(y, mp), C, :all)\n S = 2.0 * ∂Ψ∂C\n ∂S∂C = 2.0 * ∂²Ψ∂C²\n return S, ∂S∂C\nend;\nnothing #hide","category":"page"},{"location":"tutorials/hyperelasticity/#Newton's-method","page":"Hyperelasticity","title":"Newton's method","text":"","category":"section"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"As mentioned above, to deal with the non-linear weak form we first linearize the problem such that we can apply Newton's method, and then apply the FEM to discretize the problem. Skipping a detailed derivation, Newton's method can be expressed as: Given some initial guess for the degrees of freedom underlineu^0, find a sequence underlineu^k by iterating","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"underlineu^k+1 = underlineu^k - Delta underlineu^k","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"until some termination condition has been met. Therein we determine Delta underlineu^k from the linearized problem","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"underlineunderlineK(underlineu^k) Delta underlineu^k = underlineg(underlineu^k)","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"where the global residual, underlineg, and the Jacobi matrix, underlineunderlineK = fracpartial underlinegpartial underlineu, are evaluated at the current guess underlineu^k. The entries of underlineg are given by","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"(underlineg)_i = int_Omega nabla_mathbfX delta mathbfu_i \nmathbfP mathrmd Omega - int_Omega delta mathbfu_i cdot mathbfb \nmathrmd Omega - int_Gamma_mathrmN delta mathbfu_i cdot mathbft\nmathrmdGamma","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"and the entries of underlineunderlineK are given by","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"(underlineunderlineK)_ij = int_Omega nabla_mathbfX delta\nmathbfu_i fracpartial mathbfPpartial mathbfF nabla_mathbfX\ndelta mathbfu_j mathrmd Omega","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"A detailed derivation can be found in every continuum mechanics book, which has a chapter about finite elasticity theory. We used \"Nonlinear solid mechanics: a continuum approach for engineering science.\" by Holzapfel [3], Chapter 8 as a reference.","category":"page"},{"location":"tutorials/hyperelasticity/#Finite-element-assembly","page":"Hyperelasticity","title":"Finite element assembly","text":"","category":"section"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"The element routine for assembling the residual and tangent stiffness is implemented as usual, with loops over quadrature points and shape functions:","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"function assemble_element!(ke, ge, cell, cv, fv, mp, ue, ΓN)\n # Reinitialize cell values, and reset output arrays\n reinit!(cv, cell)\n fill!(ke, 0.0)\n fill!(ge, 0.0)\n\n b = Vec{3}((0.0, -0.5, 0.0)) # Body force\n tn = 0.1 # Traction (to be scaled with surface normal)\n ndofs = getnbasefunctions(cv)\n\n for qp in 1:getnquadpoints(cv)\n dΩ = getdetJdV(cv, qp)\n # Compute deformation gradient F and right Cauchy-Green tensor C\n ∇u = function_gradient(cv, qp, ue)\n F = one(∇u) + ∇u\n C = tdot(F) # F' ⋅ F\n # Compute stress and tangent\n S, ∂S∂C = constitutive_driver(C, mp)\n P = F ⋅ S\n I = one(S)\n ∂P∂F = otimesu(I, S) + 2 * F ⋅ ∂S∂C ⊡ otimesu(F', I)\n\n # Loop over test functions\n for i in 1:ndofs\n # Test function and gradient\n δui = shape_value(cv, qp, i)\n ∇δui = shape_gradient(cv, qp, i)\n # Add contribution to the residual from this test function\n ge[i] += (∇δui ⊡ P - δui ⋅ b) * dΩ\n\n ∇δui∂P∂F = ∇δui ⊡ ∂P∂F # Hoisted computation\n for j in 1:ndofs\n ∇δuj = shape_gradient(cv, qp, j)\n # Add contribution to the tangent\n ke[i, j] += (∇δui∂P∂F ⊡ ∇δuj) * dΩ\n end\n end\n end\n\n # Surface integral for the traction\n for facet in 1:nfacets(cell)\n if (cellid(cell), facet) in ΓN\n reinit!(fv, cell, facet)\n for q_point in 1:getnquadpoints(fv)\n t = tn * getnormal(fv, q_point)\n dΓ = getdetJdV(fv, q_point)\n for i in 1:ndofs\n δui = shape_value(fv, q_point, i)\n ge[i] -= (δui ⋅ t) * dΓ\n end\n end\n end\n end\n return\nend;\nnothing #hide","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"Assembling global residual and tangent is also done in the usual way, just looping over the elements, call the element routine and assemble in the the global matrix K and residual g.","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"function assemble_global!(K, g, dh, cv, fv, mp, u, ΓN)\n n = ndofs_per_cell(dh)\n ke = zeros(n, n)\n ge = zeros(n)\n\n # start_assemble resets K and g\n assembler = start_assemble(K, g)\n\n # Loop over all cells in the grid\n @timeit \"assemble\" for cell in CellIterator(dh)\n global_dofs = celldofs(cell)\n ue = u[global_dofs] # element dofs\n @timeit \"element assemble\" assemble_element!(ke, ge, cell, cv, fv, mp, ue, ΓN)\n assemble!(assembler, global_dofs, ke, ge)\n end\n return\nend;\nnothing #hide","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"Finally, we define a main function which sets up everything and then performs Newton iterations until convergence.","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"function solve()\n reset_timer!()\n\n # Generate a grid\n N = 10\n L = 1.0\n left = zero(Vec{3})\n right = L * ones(Vec{3})\n grid = generate_grid(Tetrahedron, (N, N, N), left, right)\n\n # Material parameters\n E = 10.0\n ν = 0.3\n μ = E / (2(1 + ν))\n λ = (E * ν) / ((1 + ν) * (1 - 2ν))\n mp = NeoHooke(μ, λ)\n\n # Finite element base\n ip = Lagrange{RefTetrahedron, 1}()^3\n qr = QuadratureRule{RefTetrahedron}(1)\n qr_facet = FacetQuadratureRule{RefTetrahedron}(1)\n cv = CellValues(qr, ip)\n fv = FacetValues(qr_facet, ip)\n\n # DofHandler\n dh = DofHandler(grid)\n add!(dh, :u, ip) # Add a displacement field\n close!(dh)\n\n function rotation(X, t)\n θ = pi / 3 # 60°\n x, y, z = X\n return t * Vec{3}(\n (\n 0.0,\n L / 2 - y + (y - L / 2) * cos(θ) - (z - L / 2) * sin(θ),\n L / 2 - z + (y - L / 2) * sin(θ) + (z - L / 2) * cos(θ),\n )\n )\n end\n\n dbcs = ConstraintHandler(dh)\n # Add a homogeneous boundary condition on the \"clamped\" edge\n dbc = Dirichlet(:u, getfacetset(grid, \"right\"), (x, t) -> [0.0, 0.0, 0.0], [1, 2, 3])\n add!(dbcs, dbc)\n dbc = Dirichlet(:u, getfacetset(grid, \"left\"), (x, t) -> rotation(x, t), [1, 2, 3])\n add!(dbcs, dbc)\n close!(dbcs)\n t = 0.5\n Ferrite.update!(dbcs, t)\n\n # Neumann part of the boundary\n ΓN = union(\n getfacetset(grid, \"top\"),\n getfacetset(grid, \"bottom\"),\n getfacetset(grid, \"front\"),\n getfacetset(grid, \"back\"),\n )\n\n # Pre-allocation of vectors for the solution and Newton increments\n _ndofs = ndofs(dh)\n un = zeros(_ndofs) # previous solution vector\n u = zeros(_ndofs)\n Δu = zeros(_ndofs)\n ΔΔu = zeros(_ndofs)\n apply!(un, dbcs)\n\n # Create sparse matrix and residual vector\n K = allocate_matrix(dh)\n g = zeros(_ndofs)\n\n # Perform Newton iterations\n newton_itr = -1\n NEWTON_TOL = 1.0e-8\n NEWTON_MAXITER = 30\n prog = ProgressMeter.ProgressThresh(NEWTON_TOL; desc = \"Solving:\")\n\n while true\n newton_itr += 1\n # Construct the current guess\n u .= un .+ Δu\n # Compute residual and tangent for current guess\n assemble_global!(K, g, dh, cv, fv, mp, u, ΓN)\n # Apply boundary conditions\n apply_zero!(K, g, dbcs)\n # Compute the residual norm and compare with tolerance\n normg = norm(g)\n ProgressMeter.update!(prog, normg; showvalues = [(:iter, newton_itr)])\n if normg < NEWTON_TOL\n break\n elseif newton_itr > NEWTON_MAXITER\n error(\"Reached maximum Newton iterations, aborting\")\n end\n\n # Compute increment using conjugate gradients\n @timeit \"linear solve\" IterativeSolvers.cg!(ΔΔu, K, g; maxiter = 1000)\n\n apply_zero!(ΔΔu, dbcs)\n Δu .-= ΔΔu\n end\n\n # Save the solution\n @timeit \"export\" begin\n VTKGridFile(\"hyperelasticity\", dh) do vtk\n write_solution(vtk, dh, u)\n end\n end\n\n print_timer(title = \"Analysis with $(getncells(grid)) elements\", linechars = :ascii)\n return u\nend","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"Run the simulation","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"u = solve();\nnothing #hide","category":"page"},{"location":"tutorials/hyperelasticity/#Plain-program","page":"Hyperelasticity","title":"Plain program","text":"","category":"section"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"Here follows a version of the program without any comments. The file is also available here: hyperelasticity.jl.","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"using Ferrite, Tensors, TimerOutputs, ProgressMeter, IterativeSolvers\n\nstruct NeoHooke\n μ::Float64\n λ::Float64\nend\n\nfunction Ψ(C, mp::NeoHooke)\n μ = mp.μ\n λ = mp.λ\n Ic = tr(C)\n J = sqrt(det(C))\n return μ / 2 * (Ic - 3 - 2 * log(J)) + λ / 2 * (J - 1)^2\nend\n\nfunction constitutive_driver(C, mp::NeoHooke)\n # Compute all derivatives in one function call\n ∂²Ψ∂C², ∂Ψ∂C = Tensors.hessian(y -> Ψ(y, mp), C, :all)\n S = 2.0 * ∂Ψ∂C\n ∂S∂C = 2.0 * ∂²Ψ∂C²\n return S, ∂S∂C\nend;\n\nfunction assemble_element!(ke, ge, cell, cv, fv, mp, ue, ΓN)\n # Reinitialize cell values, and reset output arrays\n reinit!(cv, cell)\n fill!(ke, 0.0)\n fill!(ge, 0.0)\n\n b = Vec{3}((0.0, -0.5, 0.0)) # Body force\n tn = 0.1 # Traction (to be scaled with surface normal)\n ndofs = getnbasefunctions(cv)\n\n for qp in 1:getnquadpoints(cv)\n dΩ = getdetJdV(cv, qp)\n # Compute deformation gradient F and right Cauchy-Green tensor C\n ∇u = function_gradient(cv, qp, ue)\n F = one(∇u) + ∇u\n C = tdot(F) # F' ⋅ F\n # Compute stress and tangent\n S, ∂S∂C = constitutive_driver(C, mp)\n P = F ⋅ S\n I = one(S)\n ∂P∂F = otimesu(I, S) + 2 * F ⋅ ∂S∂C ⊡ otimesu(F', I)\n\n # Loop over test functions\n for i in 1:ndofs\n # Test function and gradient\n δui = shape_value(cv, qp, i)\n ∇δui = shape_gradient(cv, qp, i)\n # Add contribution to the residual from this test function\n ge[i] += (∇δui ⊡ P - δui ⋅ b) * dΩ\n\n ∇δui∂P∂F = ∇δui ⊡ ∂P∂F # Hoisted computation\n for j in 1:ndofs\n ∇δuj = shape_gradient(cv, qp, j)\n # Add contribution to the tangent\n ke[i, j] += (∇δui∂P∂F ⊡ ∇δuj) * dΩ\n end\n end\n end\n\n # Surface integral for the traction\n for facet in 1:nfacets(cell)\n if (cellid(cell), facet) in ΓN\n reinit!(fv, cell, facet)\n for q_point in 1:getnquadpoints(fv)\n t = tn * getnormal(fv, q_point)\n dΓ = getdetJdV(fv, q_point)\n for i in 1:ndofs\n δui = shape_value(fv, q_point, i)\n ge[i] -= (δui ⋅ t) * dΓ\n end\n end\n end\n end\n return\nend;\n\nfunction assemble_global!(K, g, dh, cv, fv, mp, u, ΓN)\n n = ndofs_per_cell(dh)\n ke = zeros(n, n)\n ge = zeros(n)\n\n # start_assemble resets K and g\n assembler = start_assemble(K, g)\n\n # Loop over all cells in the grid\n @timeit \"assemble\" for cell in CellIterator(dh)\n global_dofs = celldofs(cell)\n ue = u[global_dofs] # element dofs\n @timeit \"element assemble\" assemble_element!(ke, ge, cell, cv, fv, mp, ue, ΓN)\n assemble!(assembler, global_dofs, ke, ge)\n end\n return\nend;\n\nfunction solve()\n reset_timer!()\n\n # Generate a grid\n N = 10\n L = 1.0\n left = zero(Vec{3})\n right = L * ones(Vec{3})\n grid = generate_grid(Tetrahedron, (N, N, N), left, right)\n\n # Material parameters\n E = 10.0\n ν = 0.3\n μ = E / (2(1 + ν))\n λ = (E * ν) / ((1 + ν) * (1 - 2ν))\n mp = NeoHooke(μ, λ)\n\n # Finite element base\n ip = Lagrange{RefTetrahedron, 1}()^3\n qr = QuadratureRule{RefTetrahedron}(1)\n qr_facet = FacetQuadratureRule{RefTetrahedron}(1)\n cv = CellValues(qr, ip)\n fv = FacetValues(qr_facet, ip)\n\n # DofHandler\n dh = DofHandler(grid)\n add!(dh, :u, ip) # Add a displacement field\n close!(dh)\n\n function rotation(X, t)\n θ = pi / 3 # 60°\n x, y, z = X\n return t * Vec{3}(\n (\n 0.0,\n L / 2 - y + (y - L / 2) * cos(θ) - (z - L / 2) * sin(θ),\n L / 2 - z + (y - L / 2) * sin(θ) + (z - L / 2) * cos(θ),\n )\n )\n end\n\n dbcs = ConstraintHandler(dh)\n # Add a homogeneous boundary condition on the \"clamped\" edge\n dbc = Dirichlet(:u, getfacetset(grid, \"right\"), (x, t) -> [0.0, 0.0, 0.0], [1, 2, 3])\n add!(dbcs, dbc)\n dbc = Dirichlet(:u, getfacetset(grid, \"left\"), (x, t) -> rotation(x, t), [1, 2, 3])\n add!(dbcs, dbc)\n close!(dbcs)\n t = 0.5\n Ferrite.update!(dbcs, t)\n\n # Neumann part of the boundary\n ΓN = union(\n getfacetset(grid, \"top\"),\n getfacetset(grid, \"bottom\"),\n getfacetset(grid, \"front\"),\n getfacetset(grid, \"back\"),\n )\n\n # Pre-allocation of vectors for the solution and Newton increments\n _ndofs = ndofs(dh)\n un = zeros(_ndofs) # previous solution vector\n u = zeros(_ndofs)\n Δu = zeros(_ndofs)\n ΔΔu = zeros(_ndofs)\n apply!(un, dbcs)\n\n # Create sparse matrix and residual vector\n K = allocate_matrix(dh)\n g = zeros(_ndofs)\n\n # Perform Newton iterations\n newton_itr = -1\n NEWTON_TOL = 1.0e-8\n NEWTON_MAXITER = 30\n prog = ProgressMeter.ProgressThresh(NEWTON_TOL; desc = \"Solving:\")\n\n while true\n newton_itr += 1\n # Construct the current guess\n u .= un .+ Δu\n # Compute residual and tangent for current guess\n assemble_global!(K, g, dh, cv, fv, mp, u, ΓN)\n # Apply boundary conditions\n apply_zero!(K, g, dbcs)\n # Compute the residual norm and compare with tolerance\n normg = norm(g)\n ProgressMeter.update!(prog, normg; showvalues = [(:iter, newton_itr)])\n if normg < NEWTON_TOL\n break\n elseif newton_itr > NEWTON_MAXITER\n error(\"Reached maximum Newton iterations, aborting\")\n end\n\n # Compute increment using conjugate gradients\n @timeit \"linear solve\" IterativeSolvers.cg!(ΔΔu, K, g; maxiter = 1000)\n\n apply_zero!(ΔΔu, dbcs)\n Δu .-= ΔΔu\n end\n\n # Save the solution\n @timeit \"export\" begin\n VTKGridFile(\"hyperelasticity\", dh) do vtk\n write_solution(vtk, dh, u)\n end\n end\n\n print_timer(title = \"Analysis with $(getncells(grid)) elements\", linechars = :ascii)\n return u\nend\n\nu = solve();","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"This page was generated using Literate.jl.","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"EditURL = \"../literate-gallery/landau.jl\"","category":"page"},{"location":"gallery/landau/#tutorial-ginzburg-landau-minimizer","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"","category":"section"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"(Image: landau_orig.png)","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"Original","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"(Image: landau_opt.png)","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"Optimized","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"In this example a basic Ginzburg-Landau model is solved. This example gives an idea of how the API together with ForwardDiff can be leveraged to performantly solve non standard problems on a FEM grid. A large portion of the code is there only for performance reasons, but since this usually really matters and is what takes the most time to optimize, it is included.","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"The key to using a method like this for minimizing a free energy function directly, rather than the weak form, as is usually done with FEM, is to split up the gradient and Hessian calculations. This means that they are performed for each cell separately instead of for the grid as a whole.","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"using ForwardDiff\nimport ForwardDiff: GradientConfig, HessianConfig, Chunk\nusing Ferrite\nusing Optim, LineSearches\nusing SparseArrays\nusing Tensors\nusing Base.Threads","category":"page"},{"location":"gallery/landau/#Energy-terms","page":"Ginzburg-Landau model energy minimization","title":"Energy terms","text":"","category":"section"},{"location":"gallery/landau/#4th-order-Landau-free-energy","page":"Ginzburg-Landau model energy minimization","title":"4th order Landau free energy","text":"","category":"section"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"function Fl(P::Vec{3, T}, α::Vec{3}) where {T}\n P2 = Vec{3, T}((P[1]^2, P[2]^2, P[3]^2))\n return α[1] * sum(P2) +\n α[2] * (P[1]^4 + P[2]^4 + P[3]^4) +\n α[3] * ((P2[1] * P2[2] + P2[2] * P2[3]) + P2[1] * P2[3])\nend","category":"page"},{"location":"gallery/landau/#Ginzburg-free-energy","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg free energy","text":"","category":"section"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"@inline Fg(∇P, G) = 0.5(∇P ⊡ G) ⊡ ∇P","category":"page"},{"location":"gallery/landau/#GL-free-energy","page":"Ginzburg-Landau model energy minimization","title":"GL free energy","text":"","category":"section"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"F(P, ∇P, params) = Fl(P, params.α) + Fg(∇P, params.G)","category":"page"},{"location":"gallery/landau/#Parameters-that-characterize-the-model","page":"Ginzburg-Landau model energy minimization","title":"Parameters that characterize the model","text":"","category":"section"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"struct ModelParams{V, T}\n α::V\n G::T\nend","category":"page"},{"location":"gallery/landau/#ThreadCache","page":"Ginzburg-Landau model energy minimization","title":"ThreadCache","text":"","category":"section"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"This holds the values that each thread will use during the assembly.","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"struct ThreadCache{CV, T, DIM, F <: Function, GC <: GradientConfig, HC <: HessianConfig}\n cvP::CV\n element_indices::Vector{Int}\n element_dofs::Vector{T}\n element_gradient::Vector{T}\n element_hessian::Matrix{T}\n element_coords::Vector{Vec{DIM, T}}\n element_potential::F\n gradconf::GC\n hessconf::HC\nend\nfunction ThreadCache(dpc::Int, nodespercell, cvP::CellValues, modelparams, elpotential)\n element_indices = zeros(Int, dpc)\n element_dofs = zeros(dpc)\n element_gradient = zeros(dpc)\n element_hessian = zeros(dpc, dpc)\n element_coords = zeros(Vec{3, Float64}, nodespercell)\n potfunc = x -> elpotential(x, cvP, modelparams)\n gradconf = GradientConfig(potfunc, zeros(dpc), Chunk{12}())\n hessconf = HessianConfig(potfunc, zeros(dpc), Chunk{4}())\n return ThreadCache(cvP, element_indices, element_dofs, element_gradient, element_hessian, element_coords, potfunc, gradconf, hessconf)\nend","category":"page"},{"location":"gallery/landau/#The-Model","page":"Ginzburg-Landau model energy minimization","title":"The Model","text":"","category":"section"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"everything is combined into a model.","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"mutable struct LandauModel{T, DH <: DofHandler, CH <: ConstraintHandler, TC <: ThreadCache}\n dofs::Vector{T}\n dofhandler::DH\n boundaryconds::CH\n threadindices::Vector{Vector{Int}}\n threadcaches::Vector{TC}\nend\n\nfunction LandauModel(α, G, gridsize, left::Vec{DIM, T}, right::Vec{DIM, T}, elpotential) where {DIM, T}\n grid = generate_grid(Tetrahedron, gridsize, left, right)\n threadindices = Ferrite.create_coloring(grid)\n\n qr = QuadratureRule{RefTetrahedron}(2)\n ipP = Lagrange{RefTetrahedron, 1}()^3\n cvP = CellValues(qr, ipP)\n\n dofhandler = DofHandler(grid)\n add!(dofhandler, :P, ipP)\n close!(dofhandler)\n\n dofvector = zeros(ndofs(dofhandler))\n startingconditions!(dofvector, dofhandler)\n boundaryconds = ConstraintHandler(dofhandler)\n #boundary conditions can be added but aren't necessary for optimization\n #add!(boundaryconds, Dirichlet(:P, getfacetset(grid, \"left\"), (x, t) -> [0.0,0.0,0.53], [1,2,3]))\n #add!(boundaryconds, Dirichlet(:P, getfacetset(grid, \"right\"), (x, t) -> [0.0,0.0,-0.53], [1,2,3]))\n close!(boundaryconds)\n update!(boundaryconds, 0.0)\n\n apply!(dofvector, boundaryconds)\n\n hessian = allocate_matrix(dofhandler)\n dpc = ndofs_per_cell(dofhandler)\n cpc = length(grid.cells[1].nodes)\n caches = [ThreadCache(dpc, cpc, copy(cvP), ModelParams(α, G), elpotential) for t in 1:nthreads()]\n return LandauModel(dofvector, dofhandler, boundaryconds, threadindices, caches)\nend","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"utility to quickly save a model","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"function save_landau(path, model, dofs = model.dofs)\n VTKGridFile(path, model.dofhandler) do vtk\n write_solution(vtk, model.dofhandler, dofs)\n end\n return\nend","category":"page"},{"location":"gallery/landau/#Assembly","page":"Ginzburg-Landau model energy minimization","title":"Assembly","text":"","category":"section"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"This macro defines most of the assembly step, since the structure is the same for the energy, gradient and Hessian calculations.","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"macro assemble!(innerbody)\n return esc(\n quote\n dofhandler = model.dofhandler\n for indices in model.threadindices\n @threads for i in indices\n cache = model.threadcaches[threadid()]\n eldofs = cache.element_dofs\n nodeids = dofhandler.grid.cells[i].nodes\n for j in 1:length(cache.element_coords)\n cache.element_coords[j] = dofhandler.grid.nodes[nodeids[j]].x\n end\n reinit!(cache.cvP, cache.element_coords)\n\n celldofs!(cache.element_indices, dofhandler, i)\n for j in 1:length(cache.element_dofs)\n eldofs[j] = dofvector[cache.element_indices[j]]\n end\n $innerbody\n end\n end\n end\n )\nend","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"This calculates the total energy calculation of the grid","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"function F(dofvector::Vector{T}, model) where {T}\n outs = fill(zero(T), nthreads())\n @assemble! begin\n outs[threadid()] += cache.element_potential(eldofs)\n end\n return sum(outs)\nend","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"The gradient calculation for each dof","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"function ∇F!(∇f::Vector{T}, dofvector::Vector{T}, model::LandauModel{T}) where {T}\n fill!(∇f, zero(T))\n @assemble! begin\n ForwardDiff.gradient!(cache.element_gradient, cache.element_potential, eldofs, cache.gradconf)\n @inbounds assemble!(∇f, cache.element_indices, cache.element_gradient)\n end\n return\nend","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"The Hessian calculation for the whole grid","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"function ∇²F!(∇²f::SparseMatrixCSC, dofvector::Vector{T}, model::LandauModel{T}) where {T}\n assemblers = [start_assemble(∇²f) for t in 1:nthreads()]\n @assemble! begin\n ForwardDiff.hessian!(cache.element_hessian, cache.element_potential, eldofs, cache.hessconf)\n @inbounds assemble!(assemblers[threadid()], cache.element_indices, cache.element_hessian)\n end\n return\nend","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"We can also calculate all things in one go!","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"function calcall(∇²f::SparseMatrixCSC, ∇f::Vector{T}, dofvector::Vector{T}, model::LandauModel{T}) where {T}\n outs = fill(zero(T), nthreads())\n fill!(∇f, zero(T))\n assemblers = [start_assemble(∇²f, ∇f) for t in 1:nthreads()]\n @assemble! begin\n outs[threadid()] += cache.element_potential(eldofs)\n ForwardDiff.hessian!(cache.element_hessian, cache.element_potential, eldofs, cache.hessconf)\n ForwardDiff.gradient!(cache.element_gradient, cache.element_potential, eldofs, cache.gradconf)\n @inbounds assemble!(assemblers[threadid()], cache.element_indices, cache.element_gradient, cache.element_hessian)\n end\n return sum(outs)\nend","category":"page"},{"location":"gallery/landau/#Minimization","page":"Ginzburg-Landau model energy minimization","title":"Minimization","text":"","category":"section"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"Now everything can be combined to minimize the energy, and find the equilibrium configuration.","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"function minimize!(model; kwargs...)\n dh = model.dofhandler\n dofs = model.dofs\n ∇f = fill(0.0, length(dofs))\n ∇²f = allocate_matrix(dh)\n function g!(storage, x)\n ∇F!(storage, x, model)\n return apply_zero!(storage, model.boundaryconds)\n end\n function h!(storage, x)\n return ∇²F!(storage, x, model)\n # apply!(storage, model.boundaryconds)\n end\n f(x) = F(x, model)\n\n od = TwiceDifferentiable(f, g!, h!, model.dofs, 0.0, ∇f, ∇²f)\n\n # this way of minimizing is only beneficial when the initial guess is completely off,\n # then a quick couple of ConjuageGradient steps brings us easily closer to the minimum.\n # res = optimize(od, model.dofs, ConjugateGradient(linesearch=BackTracking()), Optim.Options(show_trace=true, show_every=1, g_tol=1e-20, iterations=10))\n # model.dofs .= res.minimizer\n # to get the final convergence, Newton's method is more ideal since the energy landscape should be almost parabolic\n ##+\n res = optimize(od, model.dofs, Newton(linesearch = BackTracking()), Optim.Options(show_trace = true, show_every = 1, g_tol = 1.0e-20))\n model.dofs .= res.minimizer\n return res\nend","category":"page"},{"location":"gallery/landau/#Testing-it","page":"Ginzburg-Landau model energy minimization","title":"Testing it","text":"","category":"section"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"This calculates the contribution of each element to the total energy, it is also the function that will be put through ForwardDiff for the gradient and Hessian.","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"function element_potential(eldofs::AbstractVector{T}, cvP, params) where {T}\n energy = zero(T)\n for qp in 1:getnquadpoints(cvP)\n P = function_value(cvP, qp, eldofs)\n ∇P = function_gradient(cvP, qp, eldofs)\n energy += F(P, ∇P, params) * getdetJdV(cvP, qp)\n end\n return energy\nend","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"now we define some starting conditions","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"function startingconditions!(dofvector, dofhandler)\n for cell in CellIterator(dofhandler)\n globaldofs = celldofs(cell)\n it = 1\n for i in 1:3:length(globaldofs)\n dofvector[globaldofs[i]] = -2.0\n dofvector[globaldofs[i + 1]] = 2.0\n dofvector[globaldofs[i + 2]] = -2.0tanh(cell.coords[it][1] / 20)\n it += 1\n end\n end\n return\nend\n\nδ(i, j) = i == j ? one(i) : zero(i)\nV2T(p11, p12, p44) = Tensor{4, 3}((i, j, k, l) -> p11 * δ(i, j) * δ(k, l) * δ(i, k) + p12 * δ(i, j) * δ(k, l) * (1 - δ(i, k)) + p44 * δ(i, k) * δ(j, l) * (1 - δ(i, j)))\n\nG = V2T(1.0e2, 0.0, 1.0e2)\nα = Vec{3}((-1.0, 1.0, 1.0))\nleft = Vec{3}((-75.0, -25.0, -2.0))\nright = Vec{3}((75.0, 25.0, 2.0))\nmodel = LandauModel(α, G, (50, 50, 2), left, right, element_potential)\n\nsave_landau(\"landauorig\", model)\n@time minimize!(model)\nsave_landau(\"landaufinal\", model)","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"as we can see this runs very quickly even for relatively large gridsizes. The key to get high performance like this is to minimize the allocations inside the threaded loops, ideally to 0.","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"This page was generated using Literate.jl.","category":"page"},{"location":"","page":"Home","title":"Home","text":"DocTestSetup = :(using Ferrite)","category":"page"},{"location":"#Ferrite.jl","page":"Home","title":"Ferrite.jl","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Welcome to the documentation for Ferrite.jl! Ferrite is a finite element toolbox that provides functionalities to implement finite element analysis in Julia. The aim is to be i) general, ii) performant, and iii) to keep mathematical abstractions.","category":"page"},{"location":"","page":"Home","title":"Home","text":"note: Upgrading code from version 0.3.x to version 1.0\nFerrite version 1.0 contains a number of breaking changes compared to version 0.3.x. The Changelog documents all changes and there is also a section specifically for Upgrading code from Ferrite 0.3 to 1.0.","category":"page"},{"location":"","page":"Home","title":"Home","text":"note: Note\nPlease help improve this documentation – if something confuses you, chances are you're not alone. It's easy to do as you read along: just click on the \"Edit on GitHub\" link at the top of each page, and then edit the files directly in your browser. Your changes will be vetted by developers before becoming permanent, so don't worry about whether you might say something wrong. See also Contributing to Ferrite for more details.","category":"page"},{"location":"#How-the-documentation-is-organized","page":"Home","title":"How the documentation is organized","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"This high level view of the documentation structure will help you find what you are looking for. The document is organized as follows[1]:","category":"page"},{"location":"","page":"Home","title":"Home","text":"Tutorials are thoroughly documented examples which guides you through the process of solving partial differential equations using Ferrite.\nTopic guides contains more in-depth explanations and discussions about finite element programming concepts and ideas, and specifically how these are realized in Ferrite.\nReference contains the technical API reference of functions and methods (e.g. the documentation strings).\nHow-to guides will guide you through the steps involved in addressing common tasks and use-cases. These usually build on top of the tutorials and thus assume basic knowledge of how Ferrite works.","category":"page"},{"location":"","page":"Home","title":"Home","text":"[1]: The organization of the document follows the Diátaxis Framework.","category":"page"},{"location":"","page":"Home","title":"Home","text":"The four sections above form the main user-facing parts of the documentation. In addition, the document also contain the following sections:","category":"page"},{"location":"","page":"Home","title":"Home","text":"Code gallery contain user contributed example programs showcasing what can be done with Ferrite.\nChangelog contain release notes and information about how to upgrade between releases.\nDeveloper documentation contain documentation of Ferrite internal code and is mainly targeted at developers of Ferrite.","category":"page"},{"location":"#Getting-started","page":"Home","title":"Getting started","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"As a new user of Ferrite it is suggested to start working with the tutorials before using Ferrite to tackle the specific equation you ultimately want to solve. The tutorials start with explaining the basic concepts and then increase in complexity. Understanding the first tutorial program, solving the heat equation, is essential in order to understand how Ferrite works. Already this rather simple program discusses many of the important concepts. See the tutorials overview for suggestion on how to progress to more advanced usage.","category":"page"},{"location":"#Getting-help","page":"Home","title":"Getting help","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"If you have questions about Ferrite it is suggested to use the #ferrite-fem channel on the Julia Slack, or the #Ferrite.jl stream on Zulip. Alternatively you can use the discussion forum on the GitHub repository.","category":"page"},{"location":"#Installation","page":"Home","title":"Installation","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"To use Ferrite you first need to install Julia, see https://julialang.org/ for details. Installing Ferrite can then be done from the Pkg REPL; press ] at the julia> promp to enter pkg> mode:","category":"page"},{"location":"","page":"Home","title":"Home","text":"pkg> add Ferrite","category":"page"},{"location":"","page":"Home","title":"Home","text":"This will install Ferrite and all necessary dependencies. Press backspace to get back to the julia> prompt. (See the documentation for Pkg, Julia's package manager, for more help regarding package installation and project management.)","category":"page"},{"location":"","page":"Home","title":"Home","text":"Finally, to load Ferrite, use","category":"page"},{"location":"","page":"Home","title":"Home","text":"using Ferrite","category":"page"},{"location":"","page":"Home","title":"Home","text":"You are now all set to start using Ferrite!","category":"page"},{"location":"#Contributing-to-Ferrite","page":"Home","title":"Contributing to Ferrite","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Ferrite is still under active development. If you find a bug, or have ideas for improvements, you are encouraged to interact with the developers on the Ferrite GitHub repository. There is also a thorough contributor guide which can be found in CONTRIBUTING.md.","category":"page"},{"location":"devdocs/assembly/#devdocs-assembly","page":"Assembly","title":"Assembly","text":"","category":"section"},{"location":"devdocs/assembly/#Type-definitions","page":"Assembly","title":"Type definitions","text":"","category":"section"},{"location":"devdocs/assembly/","page":"Assembly","title":"Assembly","text":"Ferrite.COOAssembler\nFerrite.CSCAssembler\nFerrite.SymmetricCSCAssembler","category":"page"},{"location":"devdocs/assembly/#Ferrite.COOAssembler","page":"Assembly","title":"Ferrite.COOAssembler","text":"struct COOAssembler{Tv, Ti}\n\nThis assembler creates a COO (coordinate format) representation of a sparse matrix during assembly and converts it into a SparseMatrixCSC{Tv, Ti} on finalization.\n\n\n\n\n\n","category":"type"},{"location":"devdocs/assembly/#Ferrite.CSCAssembler","page":"Assembly","title":"Ferrite.CSCAssembler","text":"Assembler for sparse matrix with CSC storage type.\n\n\n\n\n\n","category":"type"},{"location":"devdocs/assembly/#Ferrite.SymmetricCSCAssembler","page":"Assembly","title":"Ferrite.SymmetricCSCAssembler","text":"Assembler for symmetric sparse matrix with CSC storage type.\n\n\n\n\n\n","category":"type"},{"location":"devdocs/assembly/#Utility-functions","page":"Assembly","title":"Utility functions","text":"","category":"section"},{"location":"devdocs/assembly/","page":"Assembly","title":"Assembly","text":"Ferrite.matrix_handle\nFerrite.vector_handle\nFerrite._sortdofs_for_assembly!\nFerrite.sortperm2!","category":"page"},{"location":"devdocs/assembly/#Ferrite.matrix_handle","page":"Assembly","title":"Ferrite.matrix_handle","text":"matrix_handle(a::AbstractAssembler)\nvector_handle(a::AbstractAssembler)\n\nReturn a reference to the underlying matrix/vector of the assembler used during assembly operations.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/assembly/#Ferrite.vector_handle","page":"Assembly","title":"Ferrite.vector_handle","text":"matrix_handle(a::AbstractAssembler)\nvector_handle(a::AbstractAssembler)\n\nReturn a reference to the underlying matrix/vector of the assembler used during assembly operations.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/assembly/#Ferrite._sortdofs_for_assembly!","page":"Assembly","title":"Ferrite._sortdofs_for_assembly!","text":"_sortdofs_for_assembly!(permutation::Vector{Int}, sorteddofs::Vector{Int}, dofs::AbstractVector)\n\nSorts the dofs into a separate buffer and returns it together with a permutation vector.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/assembly/#Ferrite.sortperm2!","page":"Assembly","title":"Ferrite.sortperm2!","text":"sortperm2!(data::AbstractVector, permutation::AbstractVector)\n\nSort the input vector inplace and compute the corresponding permutation.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/interpolations/#devdocs-interpolations","page":"Interpolations","title":"Interpolations","text":"","category":"section"},{"location":"devdocs/interpolations/#Type-definitions","page":"Interpolations","title":"Type definitions","text":"","category":"section"},{"location":"devdocs/interpolations/","page":"Interpolations","title":"Interpolations","text":"Interpolations are subtypes of Interpolation{shape, order}, i.e. they are parametrized by the reference element and its characteristic order.","category":"page"},{"location":"devdocs/interpolations/#Fallback-methods-applicable-for-all-subtypes-of-Interpolation","page":"Interpolations","title":"Fallback methods applicable for all subtypes of Interpolation","text":"","category":"section"},{"location":"devdocs/interpolations/","page":"Interpolations","title":"Interpolations","text":"Ferrite.getrefshape(::Interpolation)\nFerrite.getorder(::Interpolation)\nFerrite.reference_shape_gradient(::Interpolation, ::Vec, ::Int)\nFerrite.reference_shape_gradient_and_value(::Interpolation, ::Vec, ::Int)\nFerrite.reference_shape_hessian_gradient_and_value(::Interpolation, ::Vec, ::Int)\nFerrite.boundarydof_indices\nFerrite.dirichlet_boundarydof_indices\nFerrite.reference_shape_values!\nFerrite.reference_shape_gradients!\nFerrite.reference_shape_gradients_and_values!\nFerrite.reference_shape_hessians_gradients_and_values!\nFerrite.shape_value_type(ip::Interpolation, ::Type{T}) where T<:Number","category":"page"},{"location":"devdocs/interpolations/#Ferrite.getrefshape-Tuple{Interpolation}","page":"Interpolations","title":"Ferrite.getrefshape","text":"Ferrite.getrefshape(::Interpolation)::AbstractRefShape\n\nReturn the reference element shape of the interpolation.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.getorder-Tuple{Interpolation}","page":"Interpolations","title":"Ferrite.getorder","text":"Ferrite.getorder(::Interpolation)\n\nReturn order of the interpolation.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.reference_shape_gradient-Tuple{Interpolation, Vec, Int64}","page":"Interpolations","title":"Ferrite.reference_shape_gradient","text":"reference_shape_gradient(ip::Interpolation, ξ::Vec, i::Int)\n\nEvaluate the gradient of the ith shape function of the interpolation ip in reference coordinate ξ.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.reference_shape_gradient_and_value-Tuple{Interpolation, Vec, Int64}","page":"Interpolations","title":"Ferrite.reference_shape_gradient_and_value","text":"reference_shape_gradient_and_value(ip::Interpolation, ξ::Vec, i::Int)\n\nOptimized version combining the evaluation Ferrite.reference_shape_value(::Interpolation) and Ferrite.reference_shape_gradient(::Interpolation).\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.reference_shape_hessian_gradient_and_value-Tuple{Interpolation, Vec, Int64}","page":"Interpolations","title":"Ferrite.reference_shape_hessian_gradient_and_value","text":"reference_shape_hessian_gradient_and_value(ip::Interpolation, ξ::Vec, i::Int)\n\nOptimized version combining the evaluation Ferrite.reference_shape_value(::Interpolation), Ferrite.reference_shape_gradient(::Interpolation), and the gradient of the latter.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.boundarydof_indices","page":"Interpolations","title":"Ferrite.boundarydof_indices","text":"boundarydof_indices(::Type{<:BoundaryIndex})\n\nHelper function to generically dispatch on the correct dof sets of a boundary entity.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/interpolations/#Ferrite.dirichlet_boundarydof_indices","page":"Interpolations","title":"Ferrite.dirichlet_boundarydof_indices","text":"dirichlet_boundarydof_indices(::Type{<:BoundaryIndex})\n\nHelper function to generically dispatch on the correct dof sets of a boundary entity. Used internally in ConstraintHandler and defaults to boundarydof_indices(ip::Interpolation) for continuous interpolation.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/interpolations/#Ferrite.reference_shape_values!","page":"Interpolations","title":"Ferrite.reference_shape_values!","text":"reference_shape_values!(values::AbstractArray{T}, ip::Interpolation, ξ::Vec)\n\nEvaluate all shape functions of ip at once at the reference point ξ and store them in values.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/interpolations/#Ferrite.reference_shape_gradients!","page":"Interpolations","title":"Ferrite.reference_shape_gradients!","text":"reference_shape_gradients!(gradients::AbstractArray, ip::Interpolation, ξ::Vec)\n\nEvaluate all shape function gradients of ip at once at the reference point ξ and store them in gradients.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/interpolations/#Ferrite.reference_shape_gradients_and_values!","page":"Interpolations","title":"Ferrite.reference_shape_gradients_and_values!","text":"reference_shape_gradients_and_values!(gradients::AbstractArray, values::AbstractArray, ip::Interpolation, ξ::Vec)\n\nEvaluate all shape function gradients and values of ip at once at the reference point ξ and store them in values.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/interpolations/#Ferrite.reference_shape_hessians_gradients_and_values!","page":"Interpolations","title":"Ferrite.reference_shape_hessians_gradients_and_values!","text":"reference_shape_hessians_gradients_and_values!(hessians::AbstractVector, gradients::AbstractVector, values::AbstractVector, ip::Interpolation, ξ::Vec)\n\nEvaluate all shape function hessians, gradients and values of ip at once at the reference point ξ and store them in hessians, gradients, and values.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/interpolations/#Ferrite.shape_value_type-Union{Tuple{T}, Tuple{Interpolation, Type{T}}} where T<:Number","page":"Interpolations","title":"Ferrite.shape_value_type","text":"shape_value_type(ip::Interpolation, ::Type{T}) where T<:Number\n\nReturn the type of shape_value(ip::Interpolation, ξ::Vec, ib::Int).\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Required-methods-to-implement-for-all-subtypes-of-Interpolation-to-define-a-new-finite-element","page":"Interpolations","title":"Required methods to implement for all subtypes of Interpolation to define a new finite element","text":"","category":"section"},{"location":"devdocs/interpolations/","page":"Interpolations","title":"Interpolations","text":"Depending on the dimension of the reference element the following functions have to be implemented","category":"page"},{"location":"devdocs/interpolations/","page":"Interpolations","title":"Interpolations","text":"Ferrite.reference_shape_value(::Interpolation, ::Vec, ::Int)\nFerrite.vertexdof_indices(::Interpolation)\nFerrite.dirichlet_vertexdof_indices(::Interpolation)\nFerrite.facedof_indices(::Interpolation)\nFerrite.dirichlet_facedof_indices(::Interpolation)\nFerrite.facedof_interior_indices(::Interpolation)\nFerrite.edgedof_indices(::Interpolation)\nFerrite.dirichlet_edgedof_indices(::Interpolation)\nFerrite.edgedof_interior_indices(::Interpolation)\nFerrite.volumedof_interior_indices(::Interpolation)\nFerrite.getnbasefunctions(::Interpolation)\nFerrite.reference_coordinates(::Interpolation)\nFerrite.is_discontinuous(::Interpolation)\nFerrite.adjust_dofs_during_distribution(::Interpolation)\nFerrite.mapping_type","category":"page"},{"location":"devdocs/interpolations/#Ferrite.reference_shape_value-Tuple{Interpolation, Vec, Int64}","page":"Interpolations","title":"Ferrite.reference_shape_value","text":"reference_shape_value(ip::Interpolation, ξ::Vec, i::Int)\n\nEvaluate the value of the ith shape function of the interpolation ip at a point ξ on the reference element. The index i must match the index in vertices(::Interpolation), faces(::Interpolation) and edges(::Interpolation).\n\nFor nodal interpolations the indices also must match the indices of reference_coordinates(::Interpolation).\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.vertexdof_indices-Tuple{Interpolation}","page":"Interpolations","title":"Ferrite.vertexdof_indices","text":"vertexdof_indices(ip::Interpolation)\n\nA tuple containing tuples of local dof indices for the respective vertex in local enumeration on a cell defined by vertices(::Cell). The vertex enumeration must match the vertex enumeration of the corresponding geometrical cell.\n\nnote: Note\nThe dofs appearing in the tuple must be continuous and increasing! The first dof must be the 1, as vertex dofs are enumerated first.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.dirichlet_vertexdof_indices-Tuple{Interpolation}","page":"Interpolations","title":"Ferrite.dirichlet_vertexdof_indices","text":"dirichlet_vertexdof_indices(ip::Interpolation)\n\nA tuple containing tuples of local dof indices for the respective vertex in local enumeration on a cell defined by vertices(::Cell). The vertex enumeration must match the vertex enumeration of the corresponding geometrical cell. Used internally in ConstraintHandler and defaults to vertexdof_indices(ip::Interpolation) for continuous interpolation.\n\nnote: Note\nThe dofs appearing in the tuple must be continuous and increasing! The first dof must be the 1, as vertex dofs are enumerated first.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.facedof_indices-Tuple{Interpolation}","page":"Interpolations","title":"Ferrite.facedof_indices","text":"facedof_indices(ip::Interpolation)\n\nA tuple containing tuples of all local dof indices for the respective face in local enumeration on a cell defined by faces(::Cell). The face enumeration must match the face enumeration of the corresponding geometrical cell.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.dirichlet_facedof_indices-Tuple{Interpolation}","page":"Interpolations","title":"Ferrite.dirichlet_facedof_indices","text":"dirichlet_facedof_indices(ip::Interpolation)\n\nA tuple containing tuples of all local dof indices for the respective face in local enumeration on a cell defined by faces(::Cell). The face enumeration must match the face enumeration of the corresponding geometrical cell. Used internally in ConstraintHandler and defaults to facedof_indices(ip::Interpolation) for continuous interpolation.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.facedof_interior_indices-Tuple{Interpolation}","page":"Interpolations","title":"Ferrite.facedof_interior_indices","text":"facedof_interior_indices(ip::Interpolation)\n\nA tuple containing tuples of the local dof indices on the interior of the respective face in local enumeration on a cell defined by faces(::Cell). The face enumeration must match the face enumeration of the corresponding geometrical cell. Note that the vertex and edge dofs are included here.\n\nnote: Note\nThe dofs appearing in the tuple must be continuous and increasing! The first dof must be the computed via \"last edge interior dof index + 1\", if face dofs exist.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.edgedof_indices-Tuple{Interpolation}","page":"Interpolations","title":"Ferrite.edgedof_indices","text":"edgedof_indices(ip::Interpolation)\n\nA tuple containing tuples of local dof indices for the respective edge in local enumeration on a cell defined by edges(::Cell). The edge enumeration must match the edge enumeration of the corresponding geometrical cell.\n\nThe dofs are guaranteed to be aligned with the local ordering of the entities on the oriented edge. Here the first entries are the vertex dofs, followed by the edge interior dofs.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.dirichlet_edgedof_indices-Tuple{Interpolation}","page":"Interpolations","title":"Ferrite.dirichlet_edgedof_indices","text":"dirichlet_edgedof_indices(ip::Interpolation)\n\nA tuple containing tuples of local dof indices for the respective edge in local enumeration on a cell defined by edges(::Cell). The edge enumeration must match the edge enumeration of the corresponding geometrical cell. Used internally in ConstraintHandler and defaults to edgedof_indices(ip::Interpolation) for continuous interpolation.\n\nThe dofs are guaranteed to be aligned with the local ordering of the entities on the oriented edge. Here the first entries are the vertex dofs, followed by the edge interior dofs.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.edgedof_interior_indices-Tuple{Interpolation}","page":"Interpolations","title":"Ferrite.edgedof_interior_indices","text":"edgedof_interior_indices(ip::Interpolation)\n\nA tuple containing tuples of the local dof indices on the interior of the respective edge in local enumeration on a cell defined by edges(::Cell). The edge enumeration must match the edge enumeration of the corresponding geometrical cell. Note that the vertex dofs are included here.\n\nnote: Note\nThe dofs appearing in the tuple must be continuous and increasing! The first dof must be computed via \"last vertex dof index + 1\", if edge dofs exist.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.volumedof_interior_indices-Tuple{Interpolation}","page":"Interpolations","title":"Ferrite.volumedof_interior_indices","text":"volumedof_interior_indices(ip::Interpolation)\n\nTuple containing the dof indices associated with the interior of a volume.\n\nnote: Note\nThe dofs appearing in the tuple must be continuous and increasing, volumedofs are enumerated last.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.getnbasefunctions-Tuple{Interpolation}","page":"Interpolations","title":"Ferrite.getnbasefunctions","text":"Ferrite.getnbasefunctions(ip::Interpolation)\n\nReturn the number of base functions for the interpolation ip.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.reference_coordinates-Tuple{Interpolation}","page":"Interpolations","title":"Ferrite.reference_coordinates","text":"reference_coordinates(ip::Interpolation)\n\nReturns a vector of coordinates with length getnbasefunctions(::Interpolation) and indices corresponding to the indices of a dof in vertices, faces and edges.\n\nOnly required for nodal interpolations.\n\nTODO: Separate nodal and non-nodal interpolations.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.is_discontinuous-Tuple{Interpolation}","page":"Interpolations","title":"Ferrite.is_discontinuous","text":"is_discontinuous(::Interpolation)\nis_discontinuous(::Type{<:Interpolation})\n\nChecks whether the interpolation is discontinuous (i.e. DiscontinuousLagrange)\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.adjust_dofs_during_distribution-Tuple{Interpolation}","page":"Interpolations","title":"Ferrite.adjust_dofs_during_distribution","text":"adjust_dofs_during_distribution(::Interpolation)\n\nThis function must return true if the dofs should be adjusted (i.e. permuted) during dof distribution. This is in contrast to i) adjusting the dofs during reinit! in the assembly loop, or ii) not adjusting at all (which is not needed for low order interpolations, generally).\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.mapping_type","page":"Interpolations","title":"Ferrite.mapping_type","text":"mapping_type(ip::Interpolation)\n\nGet the type of mapping from the reference cell to the real cell for an interpolation ip. Subtypes of ScalarInterpolation and VectorizedInterpolation return IdentityMapping(), but other non-scalar interpolations may request different mapping types.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/interpolations/","page":"Interpolations","title":"Interpolations","text":"for all entities which exist on that reference element. The dof functions default to having no dofs defined on a specific entity. Hence, not overloading of the dof functions will result in an element with zero dofs. Also, it should always be double checked that everything is consistent as specified in the docstring of the corresponding function, as inconsistent implementations can lead to bugs which are really difficult to track down.","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"DocTestSetup = :(using Ferrite)","category":"page"},{"location":"topics/boundary_conditions/#Boundary-and-initial-conditions","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"","category":"section"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"Every PDE is accompanied with boundary conditions. There are different types of boundary conditions, and they need to be handled in different ways. Below we discuss how to handle the most common ones, Dirichlet and Neumann boundary conditions, and how to do it in Ferrite.","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"While boundary conditions can be applied directly to nodes, vertices, edges, or faces, they are most commonly applied to facets. Each facet is described by a FacetIndex. When adding boundary conditions to points instead, vertices are preferred over nodes.","category":"page"},{"location":"topics/boundary_conditions/#Dirichlet-boundary-conditions","page":"Boundary and initial conditions","title":"Dirichlet boundary conditions","text":"","category":"section"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"At a Dirichlet boundary the unknown field is prescribed to a given value. For the discrete FE-solution this means that there are some degrees of freedom that are fixed. To handle Dirichlet boundary conditions in Ferrite we use the ConstraintHandler. A constraint handler is created from a DoF handler:","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"ch = ConstraintHandler(dh)","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"We can now create Dirichlet constraints and add them to the constraint handler. To create a Dirichlet constraint we need to specify a field name, a part of the boundary, and a function for computing the prescribed value. Example:","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"dbc1 = Dirichlet(\n :u, # Name of the field\n getfacetset(grid, \"left\"), # Part of the boundary\n x -> 1.0, # Function mapping coordinate to a prescribed value\n)","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"The field name is given as a symbol, just like when the field was added to the dof handler, the part of the boundary where this constraint is active is given as a facet set, and the function computing the prescribed value should be of the form f(x) or f(x, t) (coordinate x and time t) and return the prescribed value(s).","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"note: Multiple sets\nTo apply a constraint on multiple facet sets in the grid you can use union to join them, for exampleleft_right = union(getfacetset(grid, \"left\"), getfacetset(grid, \"right\"))creates a new facetset containing all facets in the \"left\" and \"right\" facetsets, which can be passed to the Dirichlet constructor.","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"By default the constraint is added to all components of the given field. To add the constraint to selected components a fourth argument with the components should be passed to the constructor. Here is an example where a constraint is added to component 1 and 3 of a vector field :u:","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"dbc2 = Dirichlet(\n :u, # Name of the field\n getfacetset(grid, \"left\"), # Part of the boundary\n x -> [0.0, 0.0], # Function mapping coordinate to prescribed values\n [1, 3], # Components\n)","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"Note that the return value of the function must match with the components – in the example above we prescribe components 1 and 3 to 0 so we return a vector of length 2.","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"Adding the constraints to the constraint handler is done with add!:","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"add!(ch, dbc1)\nadd!(ch, dbc2)","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"Finally, just like for the dof handler, we need to use close! to finalize the constraint handler. Internally this will then compute the degrees-of-freedom that match the constraints we added.","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"If one or more of the constraints depend on time, i.e. they are specified as f(x, t), the prescribed values can be recomputed in each new time step by calling update! with the proper time, e.g.:","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"for t in 0.0:0.1:1.0\n update!(ch, t) # Compute prescribed values for this t\n # Solve for time t...\nend","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"note: Examples\nMost examples make use of Dirichlet boundary conditions, for example Heat Equation.","category":"page"},{"location":"topics/boundary_conditions/#Neumann-boundary-conditions","page":"Boundary and initial conditions","title":"Neumann boundary conditions","text":"","category":"section"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"At the Neumann part of the boundary we know something about the gradient of the solution. Two different methods for applying these are described below. For complete examples that use Neumann boundary conditions, please see","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"von-Mises-plasticity\nHyperelasticity","category":"page"},{"location":"topics/boundary_conditions/#Using-the-FacetIterator","page":"Boundary and initial conditions","title":"Using the FacetIterator","text":"","category":"section"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"A Neumann boundary contribution can be added by iterating over the relevant facetset by using the FacetIterator. For a scalar field, this can be done as","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"grid = generate_grid(Quadrilateral, (3,3))\ndh = DofHandler(grid); push!(dh, :u, 1); close!(dh)\nfv = FacetValues(QuadratureRule{RefQuadrilateral}(2), Lagrange{RefQuadrilateral, 1}())\nf = zeros(ndofs(dh))\nfe = zeros(ndofs_per_cell(dh))\nqn = 1.0 # Normal flux\nfor fc in FacetIterator(dh, getfacetset(grid, \"right\"))\n reinit!(fv, fc)\n fill!(fe, 0)\n for q_point in 1:getnquadpoints(fv)\n dΓ = getdetJdV(fv, q_point)\n for i in 1:getnbasefunctions(fv)\n δu = shape_value(fv, q_point, i)\n fe[i] += δu * qn * dΓ\n end\n end\n assemble!(f, celldofs(fc), fe)\nend","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"Alternatively, it is possible to add the values directly to the global f (without going through the local fe vector and then using assemble!):","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"# ...\ndofs = celldofs(fc)\nfor i in 1:getnbasefunctions(fv)\n f[dofs[i]] += δu * qn * dΓ\nend","category":"page"},{"location":"topics/boundary_conditions/#In-the-element-routine","page":"Boundary and initial conditions","title":"In the element routine","text":"","category":"section"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"Alternatively, the following code snippet can be included in the element routine, to evaluate the boundary integral:","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"for facet in 1:nfacets(cell)\n if (cellid(cell), facet) ∈ getfacetset(grid, \"Neumann Boundary\")\n reinit!(facetvalues, cell, facet)\n for q_point in 1:getnquadpoints(facetvalues)\n dΓ = getdetJdV(facetvalues, q_point)\n for i in 1:getnbasefunctions(facetvalues)\n δu = shape_value(facetvalues, q_point, i)\n fe[i] += δu * qn * dΓ\n end\n end\n end\nend","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"We start by looping over all the facets of the cell, next we check if this particular facet is located on our facetset of interest called \"Neumann Boundary\". If we have determined that the current facet is indeed on the boundary and in our facetset, then we reinitialize FacetValues for this facet, using reinit!. When reinit!ing FacetValues we also need to give the facet number in addition to the cell. Next we simply loop over the quadrature points of the facet, and then loop over all the test functions and assemble the contribution to the force vector.","category":"page"},{"location":"topics/boundary_conditions/#Periodic-boundary-conditions","page":"Boundary and initial conditions","title":"Periodic boundary conditions","text":"","category":"section"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"Periodic boundary conditions ensure that the solution is periodic across two boundaries. To define the periodicity we first define the image boundary Gamma^+ and the mirror boundary Gamma^-. We also define a (unique) coordinate mapping between the image and the mirror: varphi Gamma^+ rightarrow Gamma^-. With the mapping we can, for every coordinate on the image, compute the corresponding coordinate on the mirror:","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"boldsymbolx^- = varphi(boldsymbolx^+)quad boldsymbolx^- in Gamma^-\nboldsymbolx^+ in Gamma^+","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"We now want to ensure that the solution on the image Gamma^+ is mirrored on the mirror Gamma^-. This periodicity constraint can thus be described by","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"u(boldsymbolx^-) = u(boldsymbolx^+)","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"Sometimes this is written as","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"llbracket u rrbracket = 0","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"where llbracket bullet rrbracket = bullet(boldsymbolx^+) - bullet(boldsymbolx^-) is the \"jump operator\". Thus, this condition ensure that the jump, or difference, in the solution between the image and mirror boundary is the zero – the solution becomes periodic. For a vector valued problem the periodicity constraint can in general be written as","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"boldsymbolu(boldsymbolx^-) = boldsymbolR cdot boldsymbolu(boldsymbolx^+)\nquad Leftrightarrow quad llbracket boldsymbolu rrbracket =\nboldsymbolR cdot boldsymbolu(boldsymbolx^+) - boldsymbolu(boldsymbolx^-) =\nboldsymbol0","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"where boldsymbolR is a rotation matrix. If the mapping between mirror and image is simply a translation (e.g. sides of a cube) this matrix will be the identity matrix.","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"In Ferrite this type of periodic Dirichlet boundary conditions can be added to the ConstraintHandler by constructing an instance of PeriodicDirichlet. This is usually done it two steps. First we compute the mapping between mirror and image facets using collect_periodic_facets. Here we specify the mirror set and image sets (the sets are usually known or can be constructed easily ) and the mapping varphi. Second we construct the constraint using the PeriodicDirichlet constructor. Here we specify which components of the function that should be constrained, and the rotation matrix boldsymbolR (when needed). When adding the constraint to the ConstraintHandler the resulting dof-mapping is computed.","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"Here is a simple example where periodicity is enforced for components 1 and 2 of the field :u between the mirror boundary set \"left\" and the image boundary set \"right\". Note that no rotation matrix is needed here since the mirror and image are parallel, just shifted in the x-direction (as seen by the mapping φ):","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"# Create a constraint handler from the dof handler\nch = ConstraintHandler(dofhandler)\n\n# Compute the facet mapping\nφ(x) = x - Vec{2}((1.0, 0.0))\nface_mapping = collect_periodic_facets(grid, \"left\", \"right\", φ)\n\n# Construct the periodic constraint for field :u\npdbc = PeriodicDirichlet(:u, face_mapping, [1, 2])\n\n# Add the constraint to the constraint handler\nadd!(ch, pdbc)\n\n# If no more constraints should be added we can close\nclose!(ch)","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"note: Note\nPeriodicDirichlet constraints are imposed in a strong sense, so note that this requires a periodic mesh such that it is possible to compute the facet mapping between facets on the mirror and boundary.","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"note: Examples\nPeriodic boundary conditions are used in the following examples Computational homogenization, Stokes flow.","category":"page"},{"location":"topics/boundary_conditions/#Heterogeneous-\"periodic\"-constraint","page":"Boundary and initial conditions","title":"Heterogeneous \"periodic\" constraint","text":"","category":"section"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"It is also possible to define constraints of the form","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"llbracket u rrbracket = llbracket f rrbracket\nquad Leftrightarrow quad\nu(boldsymbolx^+) - u(boldsymbolx^-) =\nf(boldsymbolx^+) - f(boldsymbolx^-)","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"where f is a prescribed function. Although the constraint in this case is not technically periodic, PeriodicDirichlet can be used for this too. This is done by passing a function to PeriodicDirichlet, similar to Dirichlet, which, given the coordinate boldsymbolx and time t, computes the prescribed values of f on the boundary.","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"Here is an example of how to implement this type of boundary condition, for a known function f:","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"pdbc = PeriodicDirichlet(\n :u,\n face_mapping,\n (x, t) -> f(x),\n [1, 2],\n)","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"note: Note\nOne application for this type of boundary conditions is multiscale modeling and computational homogenization when solving the finite element problem for the subscale. In this case the unknown u is split into a macroscopic part u^mathrmM and a microscopic/fluctuation part u^mu, i.e. u = u^mathrmM + u^mu. Periodicity is then usually enforced for the fluctuation part, i.e. llbracket u^mu rrbracket = 0. The equivalent constraint for u then becomes llbracket u rrbracket = llbracket u^mathrmM rrbracket.As an example, consider first order homogenization where the macroscopic part is constructed as u^mathrmM = baru + boldsymbolnabla baru cdot boldsymbolx - barboldsymbolx for known baru and boldsymbolnabla baru. This could be implemented aspdbc = PeriodicDirichlet(\n :u,\n face_mapping,\n (x, t) -> ū + ∇ū ⋅ (x - x̄)\n)","category":"page"},{"location":"topics/boundary_conditions/#Initial-conditions","page":"Boundary and initial conditions","title":"Initial conditions","text":"","category":"section"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"When solving time-dependent problems, initial conditions, different from zero, may be required. For finite element formulations of ODE-type, i.e. boldsymbolu(t) = boldsymbolf(boldsymbolu(t)t), where boldsymbolu(t) are the degrees of freedom, initial conditions can be specified by the apply_analytical! function. For example, specify the initial pressure as a function of the y-coordinate","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"ρ = 1000; g = 9.81 # density [kg/m³] and gravity [N/kg]\ngrid = generate_grid(Quadrilateral, (10,10))\ndh = DofHandler(grid); add!(dh, :u, 2); add!(dh, :p, 1); close!(dh)\nu = zeros(ndofs(dh))\napply_analytical!(u, dh, :p, x -> ρ * g * x[2])","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"See also Transient heat equation for one example.","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"note: Consistency\napply_analytical! does not enforce consistency of the applied solution with the system of equations. Some problems, like for example differential-algebraic systems of equations (DAEs) need extra care during initialization. We refer to the paper \"Consistent Initial Condition Calculation for Differential-Algebraic Systems\" by Brown et al. for more details on this matter.","category":"page"},{"location":"tutorials/#Tutorials","page":"Tutorials overview","title":"Tutorials","text":"","category":"section"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"On this page you find an overview of Ferrite tutorials. The tutorials explain and show how Ferrite can be used to solve a wide range of problems. See also the Code gallery for more examples.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"The tutorials all follow roughly the same structure:","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"Introduction introduces the problem to be solved and discusses the learning outcomes of the tutorial.\nCommented program is the code for solving the problem with explanations and comments.\nPlain program is the raw source code of the program.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"When studying the tutorials it is a good idea to obtain a local copy of the code and run it on your own machine as you read along. Some of the tutorials also include suggestions for tweaks to the program that you can try out on your own.","category":"page"},{"location":"tutorials/#Tutorial-index","page":"Tutorials overview","title":"Tutorial index","text":"","category":"section"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"The tutorials are listed in roughly increasing order of complexity. However, since they focus on different aspects, and solve different problems, it is suggested to have a look at the brief descriptions below to get an idea about what you will learn from each tutorial.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"If you are new to Ferrite then Tutorial 1 - Tutorial 6 is the best place to start. These tutorials introduces and teaches most of the basic finite element techniques (e.g. linear and non-linear problems, scalar- and vector-valued problems, Dirichlet and Neumann boundary conditions, mixed finite elements, time integration, direct and iterative linear solvers, etc). In particular the very first tutorial is essential in order to be able to follow any of the other tutorials. The remaining tutorials discuss more advanced topics.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"","category":"page"},{"location":"tutorials/#[Tutorial-1:-Heat-equation](heat_equation.md)","page":"Tutorials overview","title":"Tutorial 1: Heat equation","text":"","category":"section"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"This tutorial guides you through the process of solving the linear stationary heat equation (i.e. Poisson's equation) on a unit square with homogeneous Dirichlet boundary conditions. This tutorial introduces and teaches many important parts of Ferrite: problem setup, degree of freedom management, assembly procedure, boundary conditions, solving the linear system, visualization of the result). Understanding this tutorial is essential to follow more complex tutorials.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"Keywords: scalar-valued solution, Dirichlet boundary conditions.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"","category":"page"},{"location":"tutorials/#[Tutorial-2:-Linear-elasticity](linear_elasticity.md)","page":"Tutorials overview","title":"Tutorial 2: Linear elasticity","text":"","category":"section"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"TBW.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"Keywords: vector-valued solution, Dirichlet and Neumann boundary conditions.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"","category":"page"},{"location":"tutorials/#[Tutorial-3:-Incompressible-elasticity](incompressible_elasticity.md)","page":"Tutorials overview","title":"Tutorial 3: Incompressible elasticity","text":"","category":"section"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"This tutorial focuses on a mixed formulation of linear elasticity, with (vector) displacement and (scalar) pressure as the two unknowns, suitable for incompressibility. Thus, this tutorial guides you through the process of solving a problem with two unknowns from two coupled weak forms. The problem that is studied is Cook's membrane in the incompressible limit.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"Keywords: mixed finite elements, Dirichlet and Neumann boundary conditions.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"","category":"page"},{"location":"tutorials/#[Tutorial-4:-Hyperelasticity](hyperelasticity.md)","page":"Tutorials overview","title":"Tutorial 4: Hyperelasticity","text":"","category":"section"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"In this tutorial you will learn how to solve a non-linear finite element problem. In particular, a hyperelastic material model, in a finite strain setting, is used to solve the rotation of a cube. Automatic differentiatio (AD) is used for the consitutive relations. Newton's method is used for the non-linear iteration, and a conjugate gradient (CG) solver is used for the linear solution of the increment.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"Keywords: non-linear finite element, finite strain, automatic differentiation (AD), Newton's method, conjugate gradient (CG).","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"","category":"page"},{"location":"tutorials/#[Tutorial-5:-von-Mises-Plasticity](plasticity.md)","page":"Tutorials overview","title":"Tutorial 5: von Mises Plasticity","text":"","category":"section"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"This tutorial revisits the cantilever beam problem from Tutorial 2: Linear elasticity, but instead of linear elasticity a plasticity model is used for the constitutive relation. You will learn how to solve a problem which require the solution of a local material problem, and the storage of material state, in each quadrature point. Newton's method is used both locally in the material routine, and globally on the finite element level.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"Keywords: non-linear finite element, plasticity, material modeling, state variables, Newton’s method.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"","category":"page"},{"location":"tutorials/#[Tutorial-6:-Transient-heat-equation](@ref-tutorial-transient-heat-equation)","page":"Tutorials overview","title":"Tutorial 6: Transient heat equation","text":"","category":"section"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"In this tutorial the transient heat equation is solved on the unit square. The problem to be solved is thus similar to the one solved in the first tutorial, Heat equation, but with time-varying boundary conditions. In particular you will learn how to solve a time dependent problem with an implicit Euler scheme for the time integration.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"Keywords: time dependent finite elements, implicit Euler time integration.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"","category":"page"},{"location":"tutorials/#[Tutorial-7:-Computational-homogenization](computational_homogenization.md)","page":"Tutorials overview","title":"Tutorial 7: Computational homogenization","text":"","category":"section"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"This tutorial guides you through computational homogenization of an representative volume element (RVE) consisting of a soft matrix material with stiff inclusions. The computational mesh is read from an external mesh file generated with Gmsh. Dirichlet and periodic boundary conditions are used.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"Keywords: Gmsh mesh reading, Dirichlet and periodic boundary conditions","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"","category":"page"},{"location":"tutorials/#[Tutorial-8:-Stokes-flow](stokes-flow.md)","page":"Tutorials overview","title":"Tutorial 8: Stokes flow","text":"","category":"section"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"In this tutorial Stokes flow with (vector) velocity and (scalar) pressure is solved on on a quarter circle. Rotationally periodic boundary conditions is used for the inlet/outlet coupling. To obtain a unique solution, a mean value constraint is applied on the pressure using an affine constraint. The computational mesh is generated directly using the Gmsh API.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"Keywords: periodic boundary conditions, mean value constraint, mesh generation with Gmsh.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"","category":"page"},{"location":"tutorials/#[Tutorial-9:-Porous-media-(SubDofHandler)](porous_media.md)","page":"Tutorials overview","title":"Tutorial 9: Porous media (SubDofHandler)","text":"","category":"section"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"This tutorial introduces how to solve a complex linear problem, where there are different fields on different subdomains, and different cell types in the grid. This requires using the SubDofHandler interface.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"Keywords: Mixed grids, multiple fields, porous media, SubDofHandler","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"","category":"page"},{"location":"tutorials/#[Tutorial-10:-Incompressible-Navier-Stokes-equations](ns_vs_diffeq.md)","page":"Tutorials overview","title":"Tutorial 10: Incompressible Navier-Stokes equations","text":"","category":"section"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"In this tutorial the incompressible Navier-Stokes equations are solved. The domain is discretized in space with Ferrite as usual, and then forumalated in a way to be compatible with the OrdinaryDiffEq.jl package, which is used for the time-integration.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"Keywords: non-linear time dependent problem","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"","category":"page"},{"location":"tutorials/#[Tutorial-10:-Reactive-surface](@ref-tutorial-reactive-surface)","page":"Tutorials overview","title":"Tutorial 10: Reactive surface","text":"","category":"section"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"In this tutorial a reaction diffusion system on a sphere surface embedded in 3D is solved. Ferrite is used to assemble the diffusion operators and the mass matrices. The problem is solved by using the usual first order reaction diffusion operator splitting.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"Keywords: embedded elements, operator splitting, gmsh","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"","category":"page"},{"location":"tutorials/#[Tutorial-11:-Linear-shell](@ref-tutorial-linear-shell)","page":"Tutorials overview","title":"Tutorial 11: Linear shell","text":"","category":"section"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"In this tutorial a linear shell element formulation is set up as a two-dimensional domain embedded in three-dimensional space. This will teach, and perhaps inspire, you on how Ferrite can be used for non-standard things and how to add \"hacks\" that build on top of Ferrite.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"Keywords: shell elements, automatic differentiation","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"","category":"page"},{"location":"tutorials/#[Tutorial-12:-Discontinuous-Galerkin-heat-equation](@ref-tutorial-dg-heat-equation)","page":"Tutorials overview","title":"Tutorial 12: Discontinuous Galerkin heat equation","text":"","category":"section"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"This tutorial guides you through the process of solving the linear stationary heat equation (i.e. Poisson's equation) on a unit square with inhomogeneous Dirichlet and Neumann boundary conditions using the interior penalty discontinuous Galerkin method. This tutorial follows the heat equation tutorial, introducing face and interface iterators, jump and average operators, and cross-element coupling in sparsity patterns. This example was developed as part of the Google Summer of Code funded project \"Discontinuous Galerkin Infrastructure For the finite element toolbox Ferrite.jl\".","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"Keywords: scalar-valued solution, Dirichlet boundary conditions, Discontinuous Galerkin, Interior penalty","category":"page"},{"location":"tutorials/heat_equation_hdiv/","page":"Heat equation - Mixed H(div) conforming formulation)","title":"Heat equation - Mixed H(div) conforming formulation)","text":"EditURL = \"../literate-tutorials/heat_equation_hdiv.jl\"","category":"page"},{"location":"tutorials/heat_equation_hdiv/#tutorial-heat-equation-hdiv","page":"Heat equation - Mixed H(div) conforming formulation)","title":"Heat equation - Mixed H(div) conforming formulation)","text":"","category":"section"},{"location":"tutorials/heat_equation_hdiv/","page":"Heat equation - Mixed H(div) conforming formulation)","title":"Heat equation - Mixed H(div) conforming formulation)","text":"As an alternative to the standard formulation for solving the heat equation used in the heat equation tutorial, we can used a mixed formulation where both the temperature, u(mathbfx), and the heat flux, boldsymbolq(boldsymbolx), are primary variables. From a theoretical standpoint, there are many details on e.g. which combinations of interpolations that are stable. See e.g. [1] and [2] for further reading. This tutorial is based on the theory in Fenics' mixed poisson example.","category":"page"},{"location":"tutorials/heat_equation_hdiv/","page":"Heat equation - Mixed H(div) conforming formulation)","title":"Heat equation - Mixed H(div) conforming formulation)","text":"(Image: Temperature solution) Figure: Temperature distribution considering a central part with lower heat conductivity.","category":"page"},{"location":"tutorials/heat_equation_hdiv/","page":"Heat equation - Mixed H(div) conforming formulation)","title":"Heat equation - Mixed H(div) conforming formulation)","text":"The advantage with the mixed formulation is that the heat flux is approximated better. However, the temperature becomes discontinuous where the conductivity is discontinuous.","category":"page"},{"location":"tutorials/heat_equation_hdiv/#Theory","page":"Heat equation - Mixed H(div) conforming formulation)","title":"Theory","text":"","category":"section"},{"location":"tutorials/heat_equation_hdiv/","page":"Heat equation - Mixed H(div) conforming formulation)","title":"Heat equation - Mixed H(div) conforming formulation)","text":"We start with the strong form of the heat equation: Find the temperature, u(boldsymbolx), and heat flux, boldsymbolq(x), such that","category":"page"},{"location":"tutorials/heat_equation_hdiv/","page":"Heat equation - Mixed H(div) conforming formulation)","title":"Heat equation - Mixed H(div) conforming formulation)","text":"beginalign*\nboldsymbolnablacdot boldsymbolq = h(boldsymbolx) quad forall boldsymbolx in Omega \nboldsymbolq(boldsymbolx) = - k boldsymbolnabla u(boldsymbolx) quad forall boldsymbolx in Omega \nboldsymbolq(boldsymbolx)cdot boldsymboln(boldsymbolx) = q_n quad forall boldsymbolx in Gamma_mathrmN\nu(boldsymbolx) = u_mathrmD quad forall boldsymbolx in Gamma_mathrmD\nendalign*","category":"page"},{"location":"tutorials/heat_equation_hdiv/","page":"Heat equation - Mixed H(div) conforming formulation)","title":"Heat equation - Mixed H(div) conforming formulation)","text":"From this strong form, we can formulate the weak form as a mixed formulation. Find u in mathbbU and boldsymbolqinmathbbQ such that","category":"page"},{"location":"tutorials/heat_equation_hdiv/","page":"Heat equation - Mixed H(div) conforming formulation)","title":"Heat equation - Mixed H(div) conforming formulation)","text":"beginalign*\nint_Omega delta u boldsymbolnabla cdot boldsymbolq mathrmdOmega = int_Omega delta u h mathrmdOmega quad forall delta u in deltamathbbU \nint_Omega boldsymboldelta q cdot boldsymbolq mathrmdOmega = -int_Omega boldsymboldelta q cdot k boldsymbolnabla u mathrmdOmega \nint_Omega boldsymboldelta q cdot boldsymbolq mathrmdOmega - int_Omega boldsymbolnabla cdot boldsymboldelta q k u mathrmdOmega =\n-int_Gamma boldsymboldelta q cdot boldsymboln k u mathrmdOmega quad forall boldsymboldelta q in deltamathbbQ\nendalign*","category":"page"},{"location":"tutorials/heat_equation_hdiv/","page":"Heat equation - Mixed H(div) conforming formulation)","title":"Heat equation - Mixed H(div) conforming formulation)","text":"where we have the function spaces,","category":"page"},{"location":"tutorials/heat_equation_hdiv/","page":"Heat equation - Mixed H(div) conforming formulation)","title":"Heat equation - Mixed H(div) conforming formulation)","text":"beginalign*\nmathbbU = deltamathbbU = L^2 \nmathbbQ = lbrace boldsymbolq in H(mathrmdiv) text such that boldsymbolqcdotboldsymboln = q_mathrmn text on Gamma_mathrmDrbrace \ndeltamathbbQ = lbrace boldsymbolq in H(mathrmdiv) text such that boldsymbolqcdotboldsymboln = 0 text on Gamma_mathrmDrbrace\nendalign*","category":"page"},{"location":"tutorials/heat_equation_hdiv/","page":"Heat equation - Mixed H(div) conforming formulation)","title":"Heat equation - Mixed H(div) conforming formulation)","text":"A stable choice of finite element spaces for this problem on grid with triangles is using","category":"page"},{"location":"tutorials/heat_equation_hdiv/","page":"Heat equation - Mixed H(div) conforming formulation)","title":"Heat equation - Mixed H(div) conforming formulation)","text":"DiscontinuousLagrange{RefTriangle, k-1} for approximating L^2\nBrezziDouglasMarini{RefTriangle, k} for approximating H(mathrmdiv)","category":"page"},{"location":"tutorials/heat_equation_hdiv/","page":"Heat equation - Mixed H(div) conforming formulation)","title":"Heat equation - Mixed H(div) conforming formulation)","text":"We will also investigate the consequences of using H^1 Lagrange instead of H(mathrmdiv) interpolations.","category":"page"},{"location":"tutorials/heat_equation_hdiv/#Commented-Program","page":"Heat equation - Mixed H(div) conforming formulation)","title":"Commented Program","text":"","category":"section"},{"location":"tutorials/heat_equation_hdiv/","page":"Heat equation - Mixed H(div) conforming formulation)","title":"Heat equation - Mixed H(div) conforming formulation)","text":"Now we solve the problem in Ferrite. What follows is a program spliced with comments.","category":"page"},{"location":"tutorials/heat_equation_hdiv/","page":"Heat equation - Mixed H(div) conforming formulation)","title":"Heat equation - Mixed H(div) conforming formulation)","text":"First we load Ferrite,","category":"page"},{"location":"tutorials/heat_equation_hdiv/","page":"Heat equation - Mixed H(div) conforming formulation)","title":"Heat equation - Mixed H(div) conforming formulation)","text":"using Ferrite","category":"page"},{"location":"tutorials/heat_equation_hdiv/","page":"Heat equation - Mixed H(div) conforming formulation)","title":"Heat equation - Mixed H(div) conforming formulation)","text":"And define our grid, representing a channel with a central part having a lower conductivity, k, than the surrounding.","category":"page"},{"location":"tutorials/heat_equation_hdiv/","page":"Heat equation - Mixed H(div) conforming formulation)","title":"Heat equation - Mixed H(div) conforming formulation)","text":"function create_grid(ny::Int)\n width = 10.0\n length = 40.0\n center_width = 5.0\n center_length = 20.0\n upper_right = Vec((length / 2, width / 2))\n grid = generate_grid(Triangle, (round(Int, ny * length / width), ny), -upper_right, upper_right)\n addcellset!(grid, \"center\", x -> abs(x[1]) < center_length / 2 && abs(x[2]) < center_width / 2)\n addcellset!(grid, \"around\", setdiff(1:getncells(grid), getcellset(grid, \"center\")))\n return grid\nend\n\ngrid = create_grid(100)","category":"page"},{"location":"tutorials/heat_equation_hdiv/#Setup","page":"Heat equation - Mixed H(div) conforming formulation)","title":"Setup","text":"","category":"section"},{"location":"tutorials/heat_equation_hdiv/","page":"Heat equation - Mixed H(div) conforming formulation)","title":"Heat equation - Mixed H(div) conforming formulation)","text":"We define one CellValues for each field which share the same quadrature rule.","category":"page"},{"location":"tutorials/heat_equation_hdiv/","page":"Heat equation - Mixed H(div) conforming formulation)","title":"Heat equation - Mixed H(div) conforming formulation)","text":"ip_geo = geometric_interpolation(getcelltype(grid))\nipu = DiscontinuousLagrange{RefTriangle, 0}()\nipq = Ferrite.BrezziDouglasMarini{2, RefTriangle, 1}()\nqr = QuadratureRule{RefTriangle}(2)\ncellvalues = (u = CellValues(qr, ipu, ip_geo), q = CellValues(qr, ipq, ip_geo))","category":"page"},{"location":"tutorials/heat_equation_hdiv/","page":"Heat equation - Mixed H(div) conforming formulation)","title":"Heat equation - Mixed H(div) conforming formulation)","text":"Distribute the degrees of freedom","category":"page"},{"location":"tutorials/heat_equation_hdiv/","page":"Heat equation - Mixed H(div) conforming formulation)","title":"Heat equation - Mixed H(div) conforming formulation)","text":"dh = DofHandler(grid)\nadd!(dh, :u, ipu)\nadd!(dh, :q, ipq)\nclose!(dh);\nnothing #hide","category":"page"},{"location":"tutorials/heat_equation_hdiv/","page":"Heat equation - Mixed H(div) conforming formulation)","title":"Heat equation - Mixed H(div) conforming formulation)","text":"In this problem, we have a zero temperature on the boundary, Γ, which is enforced via zero Neumann boundary conditions. Hence, we don't need a Constrainthandler.","category":"page"},{"location":"tutorials/heat_equation_hdiv/","page":"Heat equation - Mixed H(div) conforming formulation)","title":"Heat equation - Mixed H(div) conforming formulation)","text":"Γ = union((getfacetset(grid, name) for name in (\"left\", \"right\", \"bottom\", \"top\"))...)","category":"page"},{"location":"tutorials/heat_equation_hdiv/#Element-implementation","page":"Heat equation - Mixed H(div) conforming formulation)","title":"Element implementation","text":"","category":"section"},{"location":"tutorials/heat_equation_hdiv/","page":"Heat equation - Mixed H(div) conforming formulation)","title":"Heat equation - Mixed H(div) conforming formulation)","text":"function assemble_element!(Ke::Matrix, fe::Vector, cv::NamedTuple, dr::NamedTuple, k::Number)\n cvu = cv[:u]\n cvq = cv[:q]\n dru = dr[:u]\n drq = dr[:q]\n h = 1.0 # Heat source\n # Loop over quadrature points\n for q_point in 1:getnquadpoints(cvu)\n # Get the quadrature weight\n dΩ = getdetJdV(cvu, q_point)\n # Loop over test shape functions\n for (iu, Iu) in pairs(dru)\n δNu = shape_value(cvu, q_point, iu)\n # Add contribution to fe\n fe[Iu] += δNu * h * dΩ\n # Loop over trial shape functions\n for (jq, Jq) in pairs(drq)\n div_Nq = shape_divergence(cvq, q_point, jq)\n # Add contribution to Ke\n Ke[Iu, Jq] += (δNu * div_Nq) * dΩ\n end\n end\n for (iq, Iq) in pairs(drq)\n δNq = shape_value(cvq, q_point, iq)\n div_δNq = shape_divergence(cvq, q_point, iq)\n for (ju, Ju) in pairs(dru)\n Nu = shape_value(cvu, q_point, ju)\n Ke[Iq, Ju] -= div_δNq * k * Nu * dΩ\n end\n for (jq, Jq) in pairs(drq)\n Nq = shape_value(cvq, q_point, jq)\n Ke[Iq, Jq] += (δNq ⋅ Nq) * dΩ\n end\n end\n end\n return Ke, fe\nend\nnothing # hide","category":"page"},{"location":"tutorials/heat_equation_hdiv/#Global-assembly","page":"Heat equation - Mixed H(div) conforming formulation)","title":"Global assembly","text":"","category":"section"},{"location":"tutorials/heat_equation_hdiv/","page":"Heat equation - Mixed H(div) conforming formulation)","title":"Heat equation - Mixed H(div) conforming formulation)","text":"function assemble_global(cellvalues, dh::DofHandler)\n grid = dh.grid\n # Allocate the element stiffness matrix and element force vector\n dofranges = (u = dof_range(dh, :u), q = dof_range(dh, :q))\n ncelldofs = ndofs_per_cell(dh)\n Ke = zeros(ncelldofs, ncelldofs)\n fe = zeros(ncelldofs)\n # Allocate global system matrix and vector\n K = allocate_matrix(dh)\n f = zeros(ndofs(dh))\n # Create an assembler\n assembler = start_assemble(K, f)\n x = copy(getcoordinates(grid, 1))\n dofs = copy(celldofs(dh, 1))\n # Loop over all cells\n for (cells, k) in (\n (getcellset(grid, \"center\"), 0.1),\n (getcellset(grid, \"around\"), 1.0),\n )\n for cellnr in cells\n # Reinitialize cellvalues for this cell\n cell = getcells(grid, cellnr)\n getcoordinates!(x, grid, cell)\n celldofs!(dofs, dh, cellnr)\n reinit!(cellvalues[:u], cell, x)\n reinit!(cellvalues[:q], cell, x)\n # Reset to 0\n fill!(Ke, 0)\n fill!(fe, 0)\n # Compute element contribution\n assemble_element!(Ke, fe, cellvalues, dofranges, k)\n # Assemble Ke and fe into K and f\n assemble!(assembler, dofs, Ke, fe)\n end\n end\n return K, f\nend\nnothing # hide","category":"page"},{"location":"tutorials/heat_equation_hdiv/#Solution-of-the-system","page":"Heat equation - Mixed H(div) conforming formulation)","title":"Solution of the system","text":"","category":"section"},{"location":"tutorials/heat_equation_hdiv/","page":"Heat equation - Mixed H(div) conforming formulation)","title":"Heat equation - Mixed H(div) conforming formulation)","text":"K, f = assemble_global(cellvalues, dh);\nu = K \\ f;\nnothing #hide","category":"page"},{"location":"tutorials/heat_equation_hdiv/#Exporting-to-VTK","page":"Heat equation - Mixed H(div) conforming formulation)","title":"Exporting to VTK","text":"","category":"section"},{"location":"tutorials/heat_equation_hdiv/","page":"Heat equation - Mixed H(div) conforming formulation)","title":"Heat equation - Mixed H(div) conforming formulation)","text":"Currently, exporting discontinuous interpolations is not supported. Since in this case, we have a single temperature degree of freedom per cell, we work around this by calculating the per-cell temperature.","category":"page"},{"location":"tutorials/heat_equation_hdiv/","page":"Heat equation - Mixed H(div) conforming formulation)","title":"Heat equation - Mixed H(div) conforming formulation)","text":"temperature_dof = first(dof_range(dh, :u))\nu_cells = map(1:getncells(grid)) do i\n u[celldofs(dh, i)[temperature_dof]]\nend\nVTKGridFile(\"heat_equation_hdiv\", dh) do vtk\n write_cell_data(vtk, u_cells, \"temperature\")\nend","category":"page"},{"location":"tutorials/heat_equation_hdiv/#Postprocess-the-total-flux","page":"Heat equation - Mixed H(div) conforming formulation)","title":"Postprocess the total flux","text":"","category":"section"},{"location":"tutorials/heat_equation_hdiv/","page":"Heat equation - Mixed H(div) conforming formulation)","title":"Heat equation - Mixed H(div) conforming formulation)","text":"We applied a constant unit heat source to the body, and the total heat flux exiting across the boundary should therefore match the area for the considered stationary case.","category":"page"},{"location":"tutorials/heat_equation_hdiv/","page":"Heat equation - Mixed H(div) conforming formulation)","title":"Heat equation - Mixed H(div) conforming formulation)","text":"function calculate_flux(dh, boundary_facets, ip, a)\n grid = dh.grid\n qr = FacetQuadratureRule{RefTriangle}(4)\n ip_geo = geometric_interpolation(getcelltype(grid))\n fv = FacetValues(qr, ip, ip_geo)\n\n dofrange = dof_range(dh, :q)\n flux = 0.0\n dofs = celldofs(dh, 1)\n ae = zeros(length(dofs))\n x = getcoordinates(grid, 1)\n for (cellnr, facetnr) in boundary_facets\n getcoordinates!(x, grid, cellnr)\n cell = getcells(grid, cellnr)\n celldofs!(dofs, dh, cellnr)\n map!(i -> a[i], ae, dofs)\n reinit!(fv, cell, x, facetnr)\n for q_point in 1:getnquadpoints(fv)\n dΓ = getdetJdV(fv, q_point)\n n = getnormal(fv, q_point)\n q = function_value(fv, q_point, ae, dofrange)\n flux += (q ⋅ n) * dΓ\n end\n end\n return flux\nend\n\nprintln(\"Outward flux: \", calculate_flux(dh, Γ, ipq, u))","category":"page"},{"location":"tutorials/heat_equation_hdiv/","page":"Heat equation - Mixed H(div) conforming formulation)","title":"Heat equation - Mixed H(div) conforming formulation)","text":"Note that this is not the case for the standard Heat equation, as the flux terms are less accurately approximated. A fine mesh is required to converge in that case. However, the present example gives a worse approximation of the temperature field.","category":"page"},{"location":"tutorials/heat_equation_hdiv/#tutorial-heat-equation-hdiv-plain","page":"Heat equation - Mixed H(div) conforming formulation)","title":"Plain program","text":"","category":"section"},{"location":"tutorials/heat_equation_hdiv/","page":"Heat equation - Mixed H(div) conforming formulation)","title":"Heat equation - Mixed H(div) conforming formulation)","text":"Here follows a version of the program without any comments. The file is also available here: heat_equation_hdiv.jl.","category":"page"},{"location":"tutorials/heat_equation_hdiv/","page":"Heat equation - Mixed H(div) conforming formulation)","title":"Heat equation - Mixed H(div) conforming formulation)","text":"using Ferrite\n\nfunction create_grid(ny::Int)\n width = 10.0\n length = 40.0\n center_width = 5.0\n center_length = 20.0\n upper_right = Vec((length / 2, width / 2))\n grid = generate_grid(Triangle, (round(Int, ny * length / width), ny), -upper_right, upper_right)\n addcellset!(grid, \"center\", x -> abs(x[1]) < center_length / 2 && abs(x[2]) < center_width / 2)\n addcellset!(grid, \"around\", setdiff(1:getncells(grid), getcellset(grid, \"center\")))\n return grid\nend\n\ngrid = create_grid(100)\n\nip_geo = geometric_interpolation(getcelltype(grid))\nipu = DiscontinuousLagrange{RefTriangle, 0}()\nipq = Ferrite.BrezziDouglasMarini{2, RefTriangle, 1}()\nqr = QuadratureRule{RefTriangle}(2)\ncellvalues = (u = CellValues(qr, ipu, ip_geo), q = CellValues(qr, ipq, ip_geo))\n\ndh = DofHandler(grid)\nadd!(dh, :u, ipu)\nadd!(dh, :q, ipq)\nclose!(dh);\n\nΓ = union((getfacetset(grid, name) for name in (\"left\", \"right\", \"bottom\", \"top\"))...)\n\nfunction assemble_element!(Ke::Matrix, fe::Vector, cv::NamedTuple, dr::NamedTuple, k::Number)\n cvu = cv[:u]\n cvq = cv[:q]\n dru = dr[:u]\n drq = dr[:q]\n h = 1.0 # Heat source\n # Loop over quadrature points\n for q_point in 1:getnquadpoints(cvu)\n # Get the quadrature weight\n dΩ = getdetJdV(cvu, q_point)\n # Loop over test shape functions\n for (iu, Iu) in pairs(dru)\n δNu = shape_value(cvu, q_point, iu)\n # Add contribution to fe\n fe[Iu] += δNu * h * dΩ\n # Loop over trial shape functions\n for (jq, Jq) in pairs(drq)\n div_Nq = shape_divergence(cvq, q_point, jq)\n # Add contribution to Ke\n Ke[Iu, Jq] += (δNu * div_Nq) * dΩ\n end\n end\n for (iq, Iq) in pairs(drq)\n δNq = shape_value(cvq, q_point, iq)\n div_δNq = shape_divergence(cvq, q_point, iq)\n for (ju, Ju) in pairs(dru)\n Nu = shape_value(cvu, q_point, ju)\n Ke[Iq, Ju] -= div_δNq * k * Nu * dΩ\n end\n for (jq, Jq) in pairs(drq)\n Nq = shape_value(cvq, q_point, jq)\n Ke[Iq, Jq] += (δNq ⋅ Nq) * dΩ\n end\n end\n end\n return Ke, fe\nend\n\nfunction assemble_global(cellvalues, dh::DofHandler)\n grid = dh.grid\n # Allocate the element stiffness matrix and element force vector\n dofranges = (u = dof_range(dh, :u), q = dof_range(dh, :q))\n ncelldofs = ndofs_per_cell(dh)\n Ke = zeros(ncelldofs, ncelldofs)\n fe = zeros(ncelldofs)\n # Allocate global system matrix and vector\n K = allocate_matrix(dh)\n f = zeros(ndofs(dh))\n # Create an assembler\n assembler = start_assemble(K, f)\n x = copy(getcoordinates(grid, 1))\n dofs = copy(celldofs(dh, 1))\n # Loop over all cells\n for (cells, k) in (\n (getcellset(grid, \"center\"), 0.1),\n (getcellset(grid, \"around\"), 1.0),\n )\n for cellnr in cells\n # Reinitialize cellvalues for this cell\n cell = getcells(grid, cellnr)\n getcoordinates!(x, grid, cell)\n celldofs!(dofs, dh, cellnr)\n reinit!(cellvalues[:u], cell, x)\n reinit!(cellvalues[:q], cell, x)\n # Reset to 0\n fill!(Ke, 0)\n fill!(fe, 0)\n # Compute element contribution\n assemble_element!(Ke, fe, cellvalues, dofranges, k)\n # Assemble Ke and fe into K and f\n assemble!(assembler, dofs, Ke, fe)\n end\n end\n return K, f\nend\n\nK, f = assemble_global(cellvalues, dh);\nu = K \\ f;\n\ntemperature_dof = first(dof_range(dh, :u))\nu_cells = map(1:getncells(grid)) do i\n u[celldofs(dh, i)[temperature_dof]]\nend\nVTKGridFile(\"heat_equation_hdiv\", dh) do vtk\n write_cell_data(vtk, u_cells, \"temperature\")\nend\n\nfunction calculate_flux(dh, boundary_facets, ip, a)\n grid = dh.grid\n qr = FacetQuadratureRule{RefTriangle}(4)\n ip_geo = geometric_interpolation(getcelltype(grid))\n fv = FacetValues(qr, ip, ip_geo)\n\n dofrange = dof_range(dh, :q)\n flux = 0.0\n dofs = celldofs(dh, 1)\n ae = zeros(length(dofs))\n x = getcoordinates(grid, 1)\n for (cellnr, facetnr) in boundary_facets\n getcoordinates!(x, grid, cellnr)\n cell = getcells(grid, cellnr)\n celldofs!(dofs, dh, cellnr)\n map!(i -> a[i], ae, dofs)\n reinit!(fv, cell, x, facetnr)\n for q_point in 1:getnquadpoints(fv)\n dΓ = getdetJdV(fv, q_point)\n n = getnormal(fv, q_point)\n q = function_value(fv, q_point, ae, dofrange)\n flux += (q ⋅ n) * dΓ\n end\n end\n return flux\nend\n\nprintln(\"Outward flux: \", calculate_flux(dh, Γ, ipq, u))","category":"page"},{"location":"tutorials/heat_equation_hdiv/","page":"Heat equation - Mixed H(div) conforming formulation)","title":"Heat equation - Mixed H(div) conforming formulation)","text":"","category":"page"},{"location":"tutorials/heat_equation_hdiv/","page":"Heat equation - Mixed H(div) conforming formulation)","title":"Heat equation - Mixed H(div) conforming formulation)","text":"This page was generated using Literate.jl.","category":"page"},{"location":"howto/#How-to-guides","page":"How-to guide overview","title":"How-to guides","text":"","category":"section"},{"location":"howto/","page":"How-to guide overview","title":"How-to guide overview","text":"This page gives an overview of the how-to guides. How-to guides address various common tasks one might want to do in a finite element program. Many of the guides are extensions, or build on top of, the tutorials and, therefore, some familiarity with Ferrite is assumed.","category":"page"},{"location":"howto/","page":"How-to guide overview","title":"How-to guide overview","text":"","category":"page"},{"location":"howto/#[Post-processing-and-visualization](postprocessing.md)","page":"How-to guide overview","title":"Post processing and visualization","text":"","category":"section"},{"location":"howto/","page":"How-to guide overview","title":"How-to guide overview","text":"This guide builds on top of Tutorial 1: Heat equation and discusses various post processsing techniques with the goal of visualizing primary fields (the finite element solution) and secondary quantities (e.g. fluxes, stresses, etc.). Concretely, this guide answers:","category":"page"},{"location":"howto/","page":"How-to guide overview","title":"How-to guide overview","text":"How to visualize data from quadrature points?\nHow to evaluate the finite element solution, or secondary quantities, in arbitrary points of the domain?","category":"page"},{"location":"howto/","page":"How-to guide overview","title":"How-to guide overview","text":"","category":"page"},{"location":"howto/#[Multi-threaded-assembly](threaded_assembly.md)","page":"How-to guide overview","title":"Multi-threaded assembly","text":"","category":"section"},{"location":"howto/","page":"How-to guide overview","title":"How-to guide overview","text":"This guide modifies Tutorial 2: Linear elasticity such that the program is using multi-threading to parallelize the assembly procedure. Concretely this shows how to use grid coloring and \"scratch values\" in order to use multi-threading without running into race-conditions.","category":"page"},{"location":"howto/","page":"How-to guide overview","title":"How-to guide overview","text":"","category":"page"},{"location":"reference/sparsity_pattern/#Sparsity-pattern-and-sparse-matrices","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"","category":"section"},{"location":"reference/sparsity_pattern/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"This is the reference documentation for sparsity patterns and sparse matrix instantiation. See the topic section on Sparsity pattern and sparse matrices.","category":"page"},{"location":"reference/sparsity_pattern/#Sparsity-patterns","page":"Sparsity pattern and sparse matrices","title":"Sparsity patterns","text":"","category":"section"},{"location":"reference/sparsity_pattern/#AbstractSparsityPattern","page":"Sparsity pattern and sparse matrices","title":"AbstractSparsityPattern","text":"","category":"section"},{"location":"reference/sparsity_pattern/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"The following applies to all subtypes of AbstractSparsityPattern:","category":"page"},{"location":"reference/sparsity_pattern/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"Ferrite.AbstractSparsityPattern\ninit_sparsity_pattern\nadd_sparsity_entries!\nadd_cell_entries!\nadd_interface_entries!\nadd_constraint_entries!\nFerrite.add_entry!","category":"page"},{"location":"reference/sparsity_pattern/#Ferrite.AbstractSparsityPattern","page":"Sparsity pattern and sparse matrices","title":"Ferrite.AbstractSparsityPattern","text":"Ferrite.AbstractSparsityPattern\n\nSupertype for sparsity pattern implementations, e.g. SparsityPattern and BlockSparsityPattern.\n\n\n\n\n\n","category":"type"},{"location":"reference/sparsity_pattern/#Ferrite.init_sparsity_pattern","page":"Sparsity pattern and sparse matrices","title":"Ferrite.init_sparsity_pattern","text":"init_sparsity_pattern(dh::DofHandler; nnz_per_row::Int)\n\nInitialize an empty SparsityPattern with ndofs(dh) rows and ndofs(dh) columns.\n\nKeyword arguments\n\nnnz_per_row: memory optimization hint for the number of non-zero entries per row that will be added to the pattern.\n\n\n\n\n\n","category":"function"},{"location":"reference/sparsity_pattern/#Ferrite.add_sparsity_entries!","page":"Sparsity pattern and sparse matrices","title":"Ferrite.add_sparsity_entries!","text":"add_sparsity_entries!(\n sp::AbstractSparsityPattern,\n dh::DofHandler,\n ch::Union{ConstraintHandler, Nothing} = nothing;\n topology = nothing,\n keep_constrained::Bool = true,\n coupling = nothing,\n interface_coupling = nothing,\n)\n\nConvenience method for doing the common task of calling add_cell_entries!, add_interface_entries!, and add_constraint_entries!, depending on what arguments are passed:\n\nadd_cell_entries! is always called\nadd_interface_entries! is called if topology is provided (i.e. not nothing)\nadd_constraint_entries! is called if the ConstraintHandler is provided\n\nFor more details about arguments and keyword arguments, see the respective functions.\n\n\n\n\n\n","category":"function"},{"location":"reference/sparsity_pattern/#Ferrite.add_cell_entries!","page":"Sparsity pattern and sparse matrices","title":"Ferrite.add_cell_entries!","text":"add_cell_entries!(\n sp::AbstractSparsityPattern,\n dh::DofHandler,\n ch::Union{ConstraintHandler, Nothing} = nothing;\n keep_constrained::Bool = true,\n coupling::Union{AbstractMatrix{Bool}, Nothing}, = nothing\n)\n\nAdd entries to the sparsity pattern sp corresponding to DoF couplings within the cells as described by the DofHandler dh.\n\nKeyword arguments\n\nkeep_constrained: whether or not entries for constrained DoFs should be kept (keep_constrained = true) or eliminated (keep_constrained = false) from the sparsity pattern. keep_constrained = false requires passing the ConstraintHandler ch.\ncoupling: the coupling between fields/components within each cell. By default (coupling = nothing) it is assumed that all DoFs in each cell couple with each other.\n\n\n\n\n\n","category":"function"},{"location":"reference/sparsity_pattern/#Ferrite.add_interface_entries!","page":"Sparsity pattern and sparse matrices","title":"Ferrite.add_interface_entries!","text":"add_interface_entries!(\n sp::SparsityPattern, dh::DofHandler, ch::Union{ConstraintHandler, Nothing};\n topology::ExclusiveTopology, keep_constrained::Bool = true,\n interface_coupling::AbstractMatrix{Bool},\n)\n\nAdd entries to the sparsity pattern sp corresponding to DoF couplings on the interface between cells as described by the DofHandler dh.\n\nKeyword arguments\n\ntopology: the topology corresponding to the grid.\nkeep_constrained: whether or not entries for constrained DoFs should be kept (keep_constrained = true) or eliminated (keep_constrained = false) from the sparsity pattern. keep_constrained = false requires passing the ConstraintHandler ch.\ninterface_coupling: the coupling between fields/components across the interface.\n\n\n\n\n\n","category":"function"},{"location":"reference/sparsity_pattern/#Ferrite.add_constraint_entries!","page":"Sparsity pattern and sparse matrices","title":"Ferrite.add_constraint_entries!","text":"add_constraint_entries!(\n sp::AbstractSparsityPattern, ch::ConstraintHandler;\n keep_constrained::Bool = true,\n)\n\nAdd all entries resulting from constraints in the ConstraintHandler ch to the sparsity pattern. Note that, since this operation depends on existing entries in the pattern, this function must be called as the last step when creating the sparsity pattern.\n\nKeyword arguments\n\nkeep_constrained: whether or not entries for constrained DoFs should be kept (keep_constrained = true) or eliminated (keep_constrained = false) from the sparsity pattern.\n\n\n\n\n\n","category":"function"},{"location":"reference/sparsity_pattern/#Ferrite.add_entry!","page":"Sparsity pattern and sparse matrices","title":"Ferrite.add_entry!","text":"add_entry!(sp::AbstractSparsityPattern, row::Int, col::Int)\n\nAdd an entry to the sparsity pattern sp at row row and column col.\n\n\n\n\n\n","category":"function"},{"location":"reference/sparsity_pattern/#SparsityPattern","page":"Sparsity pattern and sparse matrices","title":"SparsityPattern","text":"","category":"section"},{"location":"reference/sparsity_pattern/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"SparsityPattern(::Int, ::Int)\nallocate_matrix(::SparsityPattern)\nSparsityPattern","category":"page"},{"location":"reference/sparsity_pattern/#Ferrite.SparsityPattern-Tuple{Int64, Int64}","page":"Sparsity pattern and sparse matrices","title":"Ferrite.SparsityPattern","text":"SparsityPattern(nrows::Int, ncols::Int; nnz_per_row::Int = 8)\n\nCreate an empty SparsityPattern with nrows rows and ncols columns. nnz_per_row is used as a memory hint for the number of non zero entries per row.\n\nSparsityPattern is the default sparsity pattern type for the standard DofHandler and is therefore commonly constructed using init_sparsity_pattern instead of with this constructor.\n\nExamples\n\n# Create a sparsity pattern for an 100 x 100 matrix, hinting at 10 entries per row\nsparsity_pattern = SparsityPattern(100, 100; nnz_per_row = 10)\n\nMethods\n\nThe following methods apply to SparsityPattern (see their respective documentation for more details):\n\nadd_sparsity_entries!: convenience method for calling add_cell_entries!, add_interface_entries!, and add_constraint_entries!.\nadd_cell_entries!: add entries corresponding to DoF couplings within the cells.\nadd_interface_entries!: add entries corresponding to DoF couplings on the interface between cells.\nadd_constraint_entries!: add entries resulting from constraints.\nallocate_matrix: instantiate a matrix from the pattern. The default matrix type is SparseMatrixCSC{Float64, Int}.\n\n\n\n\n\n","category":"method"},{"location":"reference/sparsity_pattern/#Ferrite.allocate_matrix-Tuple{SparsityPattern}","page":"Sparsity pattern and sparse matrices","title":"Ferrite.allocate_matrix","text":"allocate_matrix(sp::SparsityPattern)\n\nAllocate a sparse matrix of type SparseMatrixCSC{Float64, Int} from the sparsity pattern sp.\n\nThis method is a shorthand for the equivalent allocate_matrix(SparseMatrixCSC{Float64, Int}, sp).\n\n\n\n\n\n","category":"method"},{"location":"reference/sparsity_pattern/#Ferrite.SparsityPattern","page":"Sparsity pattern and sparse matrices","title":"Ferrite.SparsityPattern","text":"struct SparsityPattern <: AbstractSparsityPattern\n\nData structure representing non-zero entries in the eventual sparse matrix.\n\nSee the constructor SparsityPattern(::Int, ::Int) for the user-facing documentation.\n\nStruct fields\n\nnrows::Int: number of rows\nncols::Int: number of column\nrows::Vector{Vector{Int}}: vector of length nrows, where rows[i] is a sorted vector of column indices for non zero entries in row i.\n\nwarning: Internal struct\nThe specific implementation of this struct, such as struct fields, type layout and type parameters, are internal and should not be relied upon.\n\n\n\n\n\n","category":"type"},{"location":"reference/sparsity_pattern/#BlockSparsityPattern","page":"Sparsity pattern and sparse matrices","title":"BlockSparsityPattern","text":"","category":"section"},{"location":"reference/sparsity_pattern/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"note: Package extension\nThis functionality is only enabled when the package BlockArrays.jl is installed (pkg> add BlockArrays) and loaded (using BlockArrays) in the session.","category":"page"},{"location":"reference/sparsity_pattern/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"BlockSparsityPattern(::Vector{Int})\nMain.FerriteBlockArrays.BlockSparsityPattern\nallocate_matrix(::Main.FerriteBlockArrays.BlockSparsityPattern)\nallocate_matrix(::Type{<:BlockMatrix{T, Matrix{S}}}, sp::Main.FerriteBlockArrays.BlockSparsityPattern) where {T, S <: AbstractMatrix{T}}","category":"page"},{"location":"reference/sparsity_pattern/#Ferrite.BlockSparsityPattern-Tuple{Vector{Int64}}","page":"Sparsity pattern and sparse matrices","title":"Ferrite.BlockSparsityPattern","text":"BlockSparsityPattern(block_sizes::Vector{Int})\n\nCreate an empty BlockSparsityPattern with row and column block sizes given by block_sizes.\n\nExamples\n\n# Create a block sparsity pattern with block size 10 x 5\nsparsity_pattern = BlockSparsityPattern([10, 5])\n\nMethods\n\nThe following methods apply to BlockSparsityPattern (see their respective documentation for more details):\n\nadd_sparsity_entries!: convenience method for calling add_cell_entries!, add_interface_entries!, and add_constraint_entries!.\nadd_cell_entries!: add entries corresponding to DoF couplings within the cells.\nadd_interface_entries!: add entries corresponding to DoF couplings on the interface between cells.\nadd_constraint_entries!: add entries resulting from constraints.\nallocate_matrix: instantiate a (block) matrix from the pattern. The default matrix type is BlockMatrix{Float64, Matrix{SparseMatrixCSC{Float64, Int}}}, i.e. a BlockMatrix, where the individual blocks are of type SparseMatrixCSC{Float64, Int}.\n\nnote: Package extension\nThis functionality is only enabled when the package BlockArrays.jl is installed (pkg> add BlockArrays) and loaded (using BlockArrays) in the session.\n\n\n\n\n\n","category":"method"},{"location":"reference/sparsity_pattern/#Ferrite.BlockSparsityPattern","page":"Sparsity pattern and sparse matrices","title":"Ferrite.BlockSparsityPattern","text":"struct BlockSparsityPattern <: AbstractSparsityPattern\n\nData structure representing non-zero entries for an eventual blocked sparse matrix.\n\nSee the constructor BlockSparsityPattern(::Vector{Int}) for the user-facing documentation.\n\nStruct fields\n\nnrows::Int: number of rows\nncols::Int: number of column\nblock_sizes::Vector{Int}: row and column block sizes\nblocks::Matrix{SparsityPattern}: matrix of size length(block_sizes) × length(block_sizes) where blocks[i, j] is a SparsityPattern corresponding to block (i, j).\n\nwarning: Internal struct\nThe specific implementation of this struct, such as struct fields, type layout and type parameters, are internal and should not be relied upon.\n\n\n\n\n\n","category":"type"},{"location":"reference/sparsity_pattern/#Ferrite.allocate_matrix-Tuple{BlockSparsityPattern}","page":"Sparsity pattern and sparse matrices","title":"Ferrite.allocate_matrix","text":"allocate_matrix(::Type{SparseMatrixCSC{Tv, Ti}}, sp::SparsityPattern)\n\nAllocate a sparse matrix of type SparseMatrixCSC{Tv, Ti} from the sparsity pattern sp.\n\n\n\n\n\nallocate_matrix(::Type{Symmetric{Tv, SparseMatrixCSC{Tv, Ti}}}, sp::SparsityPattern)\n\nInstantiate a sparse matrix of type Symmetric{Tv, SparseMatrixCSC{Tv, Ti}}, i.e. a LinearAlgebra.Symmetric-wrapped SparseMatrixCSC, from the sparsity pattern sp. The resulting matrix will only store entries above, and including, the diagonal.\n\n\n\n\n\nallocate_matrix(MatrixType, dh::DofHandler, args...; kwargs...)\n\nAllocate a matrix of type MatrixType from the DofHandler dh.\n\nThis is a convenience method and is equivalent to:\n\njulia sp = init_sparsity_pattern(dh) add_sparsity_entries!(sp, dh, args...; kwargs...) allocate_matrix(MatrixType, sp)`\n\nRefer to allocate_matrix for supported matrix types, and to init_sparsity_pattern for details about supported arguments args and keyword arguments kwargs.\n\nnote: Note\nIf more than one sparse matrix is needed (e.g. a stiffness and a mass matrix) it is more efficient to explicitly create the sparsity pattern instead of using this method, i.e. usesp = init_sparsity_pattern(dh)\nadd_sparsity_entries!(sp, dh)\nK = allocate_matrix(sp)\nM = allocate_matrix(sp)instead ofK = allocate_matrix(dh)\nM = allocate_matrix(dh)Note that for some matrix types it is possible to copy the instantiated matrix (M = copy(K)) instead.\n\n\n\n\n\nallocate_matrix(::Type{BlockMatrix}, sp::BlockSparsityPattern)\nallocate_matrix(::Type{BlockMatrix{T, Matrix{S}}}, sp::BlockSparsityPattern)\n\nInstantiate a blocked sparse matrix from the blocked sparsity pattern sp.\n\nThe type of the returned matrix is a BlockMatrix with blocks of type S (defaults to SparseMatrixCSC{T, Int}).\n\nExamples\n\n# Create a sparse matrix with default block type\nallocate_matrix(BlockMatrix, sparsity_pattern)\n\n# Create a sparse matrix with blocks of type SparseMatrixCSC{Float32, Int}\nallocate_matrix(BlockMatrix{Float32, Matrix{SparseMatrixCSC{Float32, Int}}}, sparsity_pattern)\n\nnote: Package extension\nThis functionality is only enabled when the package BlockArrays.jl is installed (pkg> add BlockArrays) and loaded (using BlockArrays) in the session.\n\n\n\n\n\n","category":"method"},{"location":"reference/sparsity_pattern/#Ferrite.allocate_matrix-Union{Tuple{S}, Tuple{T}, Tuple{Type{<:BlockArray{T, 2, Matrix{S}, BS} where BS<:Tuple{AbstractUnitRange{<:Integer}, AbstractUnitRange{<:Integer}}}, BlockSparsityPattern}} where {T, S<:AbstractMatrix{T}}","page":"Sparsity pattern and sparse matrices","title":"Ferrite.allocate_matrix","text":"allocate_matrix(::Type{BlockMatrix}, sp::BlockSparsityPattern)\nallocate_matrix(::Type{BlockMatrix{T, Matrix{S}}}, sp::BlockSparsityPattern)\n\nInstantiate a blocked sparse matrix from the blocked sparsity pattern sp.\n\nThe type of the returned matrix is a BlockMatrix with blocks of type S (defaults to SparseMatrixCSC{T, Int}).\n\nExamples\n\n# Create a sparse matrix with default block type\nallocate_matrix(BlockMatrix, sparsity_pattern)\n\n# Create a sparse matrix with blocks of type SparseMatrixCSC{Float32, Int}\nallocate_matrix(BlockMatrix{Float32, Matrix{SparseMatrixCSC{Float32, Int}}}, sparsity_pattern)\n\nnote: Package extension\nThis functionality is only enabled when the package BlockArrays.jl is installed (pkg> add BlockArrays) and loaded (using BlockArrays) in the session.\n\n\n\n\n\n","category":"method"},{"location":"reference/sparsity_pattern/#Sparse-matrices","page":"Sparsity pattern and sparse matrices","title":"Sparse matrices","text":"","category":"section"},{"location":"reference/sparsity_pattern/#Creating-matrix-from-SparsityPattern","page":"Sparsity pattern and sparse matrices","title":"Creating matrix from SparsityPattern","text":"","category":"section"},{"location":"reference/sparsity_pattern/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"allocate_matrix(::Type{S}, ::Ferrite.AbstractSparsityPattern) where {Tv, Ti, S <: SparseMatrixCSC{Tv, Ti}}\nallocate_matrix(::Type{Symmetric{Tv, S}}, ::Ferrite.AbstractSparsityPattern) where {Tv, Ti, S <: SparseMatrixCSC{Tv, Ti}}","category":"page"},{"location":"reference/sparsity_pattern/#Ferrite.allocate_matrix-Union{Tuple{S}, Tuple{Ti}, Tuple{Tv}, Tuple{Type{S}, Ferrite.AbstractSparsityPattern}} where {Tv, Ti, S<:SparseMatrixCSC{Tv, Ti}}","page":"Sparsity pattern and sparse matrices","title":"Ferrite.allocate_matrix","text":"allocate_matrix(::Type{SparseMatrixCSC{Tv, Ti}}, sp::SparsityPattern)\n\nAllocate a sparse matrix of type SparseMatrixCSC{Tv, Ti} from the sparsity pattern sp.\n\n\n\n\n\n","category":"method"},{"location":"reference/sparsity_pattern/#Ferrite.allocate_matrix-Union{Tuple{S}, Tuple{Ti}, Tuple{Tv}, Tuple{Type{Symmetric{Tv, S}}, Ferrite.AbstractSparsityPattern}} where {Tv, Ti, S<:SparseMatrixCSC{Tv, Ti}}","page":"Sparsity pattern and sparse matrices","title":"Ferrite.allocate_matrix","text":"allocate_matrix(::Type{Symmetric{Tv, SparseMatrixCSC{Tv, Ti}}}, sp::SparsityPattern)\n\nInstantiate a sparse matrix of type Symmetric{Tv, SparseMatrixCSC{Tv, Ti}}, i.e. a LinearAlgebra.Symmetric-wrapped SparseMatrixCSC, from the sparsity pattern sp. The resulting matrix will only store entries above, and including, the diagonal.\n\n\n\n\n\n","category":"method"},{"location":"reference/sparsity_pattern/#Creating-matrix-from-DofHandler","page":"Sparsity pattern and sparse matrices","title":"Creating matrix from DofHandler","text":"","category":"section"},{"location":"reference/sparsity_pattern/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"allocate_matrix(::Type{MatrixType}, ::DofHandler, args...; kwargs...) where {MatrixType}\nallocate_matrix(::DofHandler, args...; kwargs...)","category":"page"},{"location":"reference/sparsity_pattern/#Ferrite.allocate_matrix-Union{Tuple{MatrixType}, Tuple{Type{MatrixType}, DofHandler, Vararg{Any}}} where MatrixType","page":"Sparsity pattern and sparse matrices","title":"Ferrite.allocate_matrix","text":"allocate_matrix(MatrixType, dh::DofHandler, args...; kwargs...)\n\nAllocate a matrix of type MatrixType from the DofHandler dh.\n\nThis is a convenience method and is equivalent to:\n\njulia sp = init_sparsity_pattern(dh) add_sparsity_entries!(sp, dh, args...; kwargs...) allocate_matrix(MatrixType, sp)`\n\nRefer to allocate_matrix for supported matrix types, and to init_sparsity_pattern for details about supported arguments args and keyword arguments kwargs.\n\nnote: Note\nIf more than one sparse matrix is needed (e.g. a stiffness and a mass matrix) it is more efficient to explicitly create the sparsity pattern instead of using this method, i.e. usesp = init_sparsity_pattern(dh)\nadd_sparsity_entries!(sp, dh)\nK = allocate_matrix(sp)\nM = allocate_matrix(sp)instead ofK = allocate_matrix(dh)\nM = allocate_matrix(dh)Note that for some matrix types it is possible to copy the instantiated matrix (M = copy(K)) instead.\n\n\n\n\n\n","category":"method"},{"location":"reference/sparsity_pattern/#Ferrite.allocate_matrix-Tuple{DofHandler, Vararg{Any}}","page":"Sparsity pattern and sparse matrices","title":"Ferrite.allocate_matrix","text":"allocate_matrix(dh::DofHandler, args...; kwargs...)\n\nAllocate a matrix of type SparseMatrixCSC{Float64, Int} from the DofHandler dh.\n\nThis method is a shorthand for the equivalent allocate_matrix(SparseMatrixCSC{Float64, Int}, dh, args...; kwargs...) – refer to that method for details.\n\n\n\n\n\n","category":"method"},{"location":"topics/constraints/","page":"Constraints","title":"Constraints","text":"DocTestSetup = :(using Ferrite)","category":"page"},{"location":"topics/constraints/#Constraints","page":"Constraints","title":"Constraints","text":"","category":"section"},{"location":"topics/constraints/","page":"Constraints","title":"Constraints","text":"PDEs can in general be subjected to a number of constraints,","category":"page"},{"location":"topics/constraints/","page":"Constraints","title":"Constraints","text":"g_I(underlinea) = 0 quad I = 1 text to n_c","category":"page"},{"location":"topics/constraints/","page":"Constraints","title":"Constraints","text":"where g are (non-linear) constraint equations, underlinea is a vector of the degrees of freedom, and n_c is the number of constraints. There are many ways to enforce these constraints, e.g. penalty methods and Lagrange multiplier methods.","category":"page"},{"location":"topics/constraints/#Affine-constraints","page":"Constraints","title":"Affine constraints","text":"","category":"section"},{"location":"topics/constraints/","page":"Constraints","title":"Constraints","text":"Affine or linear constraints can be handled directly in Ferrite. Such constraints can typically be expressed as:","category":"page"},{"location":"topics/constraints/","page":"Constraints","title":"Constraints","text":"a_1 = 5a_2 + 3a_3 + 1 \na_4 = 2a_3 + 6a_5 \ndots","category":"page"},{"location":"topics/constraints/","page":"Constraints","title":"Constraints","text":"where a_1, a_2 etc. are system degrees of freedom. In Ferrite, we can account for such constraint using the ConstraintHandler:","category":"page"},{"location":"topics/constraints/","page":"Constraints","title":"Constraints","text":"ch = ConstraintHandler(dh)\nlc1 = AffineConstraint(1, [2 => 5.0, 3 => 3.0], 1)\nlc2 = AffineConstraint(4, [3 => 2.0, 5 => 6.0], 0)\nadd!(ch, lc1)\nadd!(ch, lc2)","category":"page"},{"location":"topics/constraints/","page":"Constraints","title":"Constraints","text":"Affine constraints will affect the sparsity pattern of the stiffness matrix, and as such, it is important to also include the ConstraintHandler as an argument when creating the sparsity pattern:","category":"page"},{"location":"topics/constraints/","page":"Constraints","title":"Constraints","text":"K = allocate_matrix(dh, ch)","category":"page"},{"location":"topics/constraints/#Solving-linear-problems","page":"Constraints","title":"Solving linear problems","text":"","category":"section"},{"location":"topics/constraints/","page":"Constraints","title":"Constraints","text":"To solve the system underlineunderlineKunderlinea=underlinef, account for affine constraints the same way as for Dirichlet boundary conditions; first call apply!(K, f, ch). This will condense K and f inplace (i.e no new matrix will be created). Note however that we must also call apply! on the solution vector after solving the system to enforce the affine constraints:","category":"page"},{"location":"topics/constraints/","page":"Constraints","title":"Constraints","text":"# ...\n# Assemble K and f...\n\napply!(K, f, ch)\na = K\\f\napply!(a, ch) # enforces affine constraints\n","category":"page"},{"location":"topics/constraints/#Solving-nonlinear-problems","page":"Constraints","title":"Solving nonlinear problems","text":"","category":"section"},{"location":"topics/constraints/","page":"Constraints","title":"Constraints","text":"It is important to check the residual after applying boundary conditions when solving nonlinear problems with affine constraints. apply_zero!(K, r, ch) modifies the residual entries for dofs that are involved in constraints to account for constraint forces. The following pseudo-code shows a typical pattern for solving a non-linear problem with Newton's method:","category":"page"},{"location":"topics/constraints/","page":"Constraints","title":"Constraints","text":"a = initial_guess(...) # Make any initial guess for a here, e.g. `a=zeros(ndofs(dh))`\napply!(a, ch) # Make the guess fulfill all constraints in `ch`\nfor iter in 1:maxiter\n doassemble!(K, r, ...) # Assemble the residual, r, and stiffness, K=∂r/∂a.\n apply_zero!(K, r, ch) # Modify `K` and `r` to account for the constraints.\n check_convergence(r, ...) && break # Only check convergence after `apply_zero!(K, r, ch)`\n Δa = K \\ r # Calculate the (negative) update\n apply_zero!(Δa, ch) # Change the constrained values in `Δa` such that `a-Δa`\n # fulfills constraints if `a` did.\n a .-= Δa\nend","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"EditURL = \"../literate-howto/postprocessing.jl\"","category":"page"},{"location":"howto/postprocessing/#howto-postprocessing","page":"Post processing and visualization","title":"Post processing and visualization","text":"","category":"section"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"(Image: )","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"Figure 1: Heat flux computed from the solution to the heat equation on the unit square, see previous example: Heat equation.","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"tip: Tip\nThis example is also available as a Jupyter notebook: postprocessing.ipynb.","category":"page"},{"location":"howto/postprocessing/#Introduction","page":"Post processing and visualization","title":"Introduction","text":"","category":"section"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"After running a simulation, we usually want to visualize the results in different ways. The L2Projector and the PointEvalHandler build a pipeline for doing so. With the L2Projector, integration point quantities can be projected to the nodes. The PointEvalHandler enables evaluation of the finite element approximated function in any coordinate in the domain. Thus with the combination of both functionalities, both nodal quantities and integration point quantities can be evaluated in any coordinate, allowing for example cut-planes through 3D structures or cut-lines through 2D-structures.","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"This example continues from the Heat equation example, where the temperature field was determined on a square domain. In this example, we first compute the heat flux in each integration point (based on the solved temperature field) and then we do an L2-projection of the fluxes to the nodes of the mesh. By doing this, we can more easily visualize integration points quantities. Finally, we visualize the temperature field and the heat fluxes along a cut-line.","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"The L2-projection is defined as follows: Find projection q(boldsymbolx) in U_h(Omega) such that","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"int v q mathrmdOmega = int v d mathrmdOmega quad forall v in U_h(Omega)","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"where d is the quadrature data to project. Since the flux is a vector the projection function will be solved with multiple right hand sides, e.g. with d = q_x and d = q_y for this 2D problem. In this example, we use standard Lagrange interpolations, and the finite element space U_h is then a subset of the H^1 space (continuous functions).","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"Ferrite has functionality for doing much of this automatically, as displayed in the code below. In particular L2Projector for assembling the left hand side, and project for assembling the right hand sides and solving for the projection.","category":"page"},{"location":"howto/postprocessing/#Implementation","page":"Post processing and visualization","title":"Implementation","text":"","category":"section"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"Start by simply running the Heat equation example to solve the problem","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"include(\"../tutorials/heat_equation.jl\");\nnothing #hide","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"Next we define a function that computes the heat flux for each integration point in the domain. Fourier's law is adopted, where the conductivity tensor is assumed to be isotropic with unit conductivity lambda = 1 q = - nabla u, where u is the temperature.","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"function compute_heat_fluxes(cellvalues::CellValues, dh::DofHandler, a::AbstractVector{T}) where {T}\n\n n = getnbasefunctions(cellvalues)\n cell_dofs = zeros(Int, n)\n nqp = getnquadpoints(cellvalues)\n\n # Allocate storage for the fluxes to store\n q = [Vec{2, T}[] for _ in 1:getncells(dh.grid)]\n\n for (cell_num, cell) in enumerate(CellIterator(dh))\n q_cell = q[cell_num]\n celldofs!(cell_dofs, dh, cell_num)\n aᵉ = a[cell_dofs]\n reinit!(cellvalues, cell)\n\n for q_point in 1:nqp\n q_qp = - function_gradient(cellvalues, q_point, aᵉ)\n push!(q_cell, q_qp)\n end\n end\n return q\nend\nnothing # hide","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"Now call the function to get all the fluxes.","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"q_gp = compute_heat_fluxes(cellvalues, dh, u);\nnothing #hide","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"Next, create an L2Projector using the same interpolation as was used to approximate the temperature field. On instantiation, the projector assembles the coefficient matrix M and computes the Cholesky factorization of it. By doing so, the projector can be reused without having to invert M every time.","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"projector = L2Projector(ip, grid);\nnothing #hide","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"Project the integration point values to the nodal values","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"q_projected = project(projector, q_gp, qr);\nnothing #hide","category":"page"},{"location":"howto/postprocessing/#Exporting-to-VTK","page":"Post processing and visualization","title":"Exporting to VTK","text":"","category":"section"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"To visualize the heat flux, we export the projected field q_projected to a VTK-file, which can be viewed in e.g. ParaView. The result is also visualized in Figure 1.","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"VTKGridFile(\"heat_equation_flux\", grid) do vtk\n write_projection(vtk, projector, q_projected, \"q\")\nend;\nnothing #hide","category":"page"},{"location":"howto/postprocessing/#Point-evaluation","page":"Post processing and visualization","title":"Point evaluation","text":"","category":"section"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"(Image: )","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"Figure 2: Visualization of the cut line where we want to compute the temperature and heat flux.","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"Consider a cut-line through the domain like the black line in Figure 2 above. We will evaluate the temperature and the heat flux distribution along a horizontal line.","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"points = [Vec((x, 0.75)) for x in range(-1.0, 1.0, length = 101)];\nnothing #hide","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"First, we need to generate a PointEvalHandler. This will find and store the cells containing the input points.","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"ph = PointEvalHandler(grid, points);\nnothing #hide","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"After the L2-Projection, the heat fluxes q_projected are stored in the DoF-ordering determined by the projector's internal DoFHandler, so to evaluate the flux q at our points we give the PointEvalHandler, the L2Projector and the values q_projected.","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"q_points = evaluate_at_points(ph, projector, q_projected);\nnothing #hide","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"We can also extract the field values, here the temperature, right away from the result vector of the simulation, that is stored in u. These values are stored in the order of our initial DofHandler so the input is not the PointEvalHandler, the original DofHandler, the dof-vector u, and (optionally for single-field problems) the name of the field. From the L2Projection, the values are stored in the order of the degrees of freedom.","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"u_points = evaluate_at_points(ph, dh, u, :u);\nnothing #hide","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"Now, we can plot the temperature and flux values with the help of any plotting library, e.g. Plots.jl. To do this, we need to import the package:","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"import Plots","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"Firstly, we are going to plot the temperature values along the given line.","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"Plots.plot(getindex.(points, 1), u_points, xlabel = \"x (coordinate)\", ylabel = \"u (temperature)\", label = nothing)","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"Figure 3: Temperature along the cut line from Figure 2.","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"Secondly, the horizontal heat flux (i.e. the first component of the heat flux vector) is plotted.","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"Plots.plot(getindex.(points, 1), getindex.(q_points, 1), xlabel = \"x (coordinate)\", ylabel = \"q_x (flux in x-direction)\", label = nothing)","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"Figure 4: x-component of the flux along the cut line from Figure 2.","category":"page"},{"location":"howto/postprocessing/#postprocessing-plain-program","page":"Post processing and visualization","title":"Plain program","text":"","category":"section"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"Here follows a version of the program without any comments. The file is also available here: postprocessing.jl.","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"include(\"../tutorials/heat_equation.jl\");\n\nfunction compute_heat_fluxes(cellvalues::CellValues, dh::DofHandler, a::AbstractVector{T}) where {T}\n\n n = getnbasefunctions(cellvalues)\n cell_dofs = zeros(Int, n)\n nqp = getnquadpoints(cellvalues)\n\n # Allocate storage for the fluxes to store\n q = [Vec{2, T}[] for _ in 1:getncells(dh.grid)]\n\n for (cell_num, cell) in enumerate(CellIterator(dh))\n q_cell = q[cell_num]\n celldofs!(cell_dofs, dh, cell_num)\n aᵉ = a[cell_dofs]\n reinit!(cellvalues, cell)\n\n for q_point in 1:nqp\n q_qp = - function_gradient(cellvalues, q_point, aᵉ)\n push!(q_cell, q_qp)\n end\n end\n return q\nend\n\nq_gp = compute_heat_fluxes(cellvalues, dh, u);\n\nprojector = L2Projector(ip, grid);\n\nq_projected = project(projector, q_gp, qr);\n\nVTKGridFile(\"heat_equation_flux\", grid) do vtk\n write_projection(vtk, projector, q_projected, \"q\")\nend;\n\npoints = [Vec((x, 0.75)) for x in range(-1.0, 1.0, length = 101)];\n\nph = PointEvalHandler(grid, points);\n\nq_points = evaluate_at_points(ph, projector, q_projected);\n\nu_points = evaluate_at_points(ph, dh, u, :u);\n\nimport Plots\n\nPlots.plot(getindex.(points, 1), u_points, xlabel = \"x (coordinate)\", ylabel = \"u (temperature)\", label = nothing)\n\nPlots.plot(getindex.(points, 1), getindex.(q_points, 1), xlabel = \"x (coordinate)\", ylabel = \"q_x (flux in x-direction)\", label = nothing)","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"This page was generated using Literate.jl.","category":"page"},{"location":"reference/grid/","page":"Grid & AbstractGrid","title":"Grid & AbstractGrid","text":"DocTestSetup = :(using Ferrite)","category":"page"},{"location":"reference/grid/#Grid-and-AbstractGrid","page":"Grid & AbstractGrid","title":"Grid & AbstractGrid","text":"","category":"section"},{"location":"reference/grid/#Grid","page":"Grid & AbstractGrid","title":"Grid","text":"","category":"section"},{"location":"reference/grid/","page":"Grid & AbstractGrid","title":"Grid & AbstractGrid","text":"generate_grid\nNode\nCellIndex\nVertexIndex\nEdgeIndex\nFaceIndex\nFacetIndex\nGrid","category":"page"},{"location":"reference/grid/#Ferrite.generate_grid","page":"Grid & AbstractGrid","title":"Ferrite.generate_grid","text":"generate_grid(celltype::Cell, nel::NTuple, [left::Vec, right::Vec)\n\nReturn a Grid for a rectangle in 1, 2 or 3 dimensions. celltype defined the type of cells, e.g. Triangle or Hexahedron. nel is a tuple of the number of elements in each direction. left and right are optional endpoints of the domain. Defaults to -1 and 1 in all directions.\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.Node","page":"Grid & AbstractGrid","title":"Ferrite.Node","text":"Node{dim, T}\n\nA Node is a point in space.\n\nFields\n\nx::Vec{dim,T}: stores the coordinates\n\n\n\n\n\n","category":"type"},{"location":"reference/grid/#Ferrite.CellIndex","page":"Grid & AbstractGrid","title":"Ferrite.CellIndex","text":"A CellIndex wraps an Int and corresponds to a cell with that number in the mesh\n\n\n\n\n\n","category":"type"},{"location":"reference/grid/#Ferrite.VertexIndex","page":"Grid & AbstractGrid","title":"Ferrite.VertexIndex","text":"A VertexIndex wraps an (Int, Int) and defines a local vertex by pointing to a (cell, vert).\n\n\n\n\n\n","category":"type"},{"location":"reference/grid/#Ferrite.EdgeIndex","page":"Grid & AbstractGrid","title":"Ferrite.EdgeIndex","text":"A EdgeIndex wraps an (Int, Int) and defines a local edge by pointing to a (cell, edge).\n\n\n\n\n\n","category":"type"},{"location":"reference/grid/#Ferrite.FaceIndex","page":"Grid & AbstractGrid","title":"Ferrite.FaceIndex","text":"A FaceIndex wraps an (Int, Int) and defines a local face by pointing to a (cell, face).\n\n\n\n\n\n","category":"type"},{"location":"reference/grid/#Ferrite.FacetIndex","page":"Grid & AbstractGrid","title":"Ferrite.FacetIndex","text":"A FacetIndex wraps an (Int, Int) and defines a local facet by pointing to a (cell, facet).\n\n\n\n\n\n","category":"type"},{"location":"reference/grid/#Ferrite.Grid","page":"Grid & AbstractGrid","title":"Ferrite.Grid","text":"Grid{dim, C<:AbstractCell, T<:Real} <: AbstractGrid}\n\nA Grid is a collection of Ferrite.AbstractCells and Ferrite.Nodes which covers the computational domain. Helper structures for applying boundary conditions or define subdomains are gathered in cellsets, nodesets, facetsets, and vertexsets.\n\nFields\n\ncells::Vector{C}: stores all cells of the grid\nnodes::Vector{Node{dim,T}}: stores the dim dimensional nodes of the grid\ncellsets::Dict{String, OrderedSet{Int}}: maps a String key to an OrderedSet of cell ids\nnodesets::Dict{String, OrderedSet{Int}}: maps a String key to an OrderedSet of global node ids\nfacetsets::Dict{String, OrderedSet{FacetIndex}}: maps a String to an OrderedSet of FacetIndex\nvertexsets::Dict{String, OrderedSet{VertexIndex}}: maps a String key to an OrderedSet of VertexIndex\n\n\n\n\n\n","category":"type"},{"location":"reference/grid/#Utility-Functions","page":"Grid & AbstractGrid","title":"Utility Functions","text":"","category":"section"},{"location":"reference/grid/","page":"Grid & AbstractGrid","title":"Grid & AbstractGrid","text":"getcells\ngetncells\ngetnodes\ngetnnodes\nFerrite.nnodes_per_cell\ngetcellset\ngetnodeset\ngetfacetset\ngetvertexset\ntransform_coordinates!\ngetcoordinates\ngetcoordinates!\ngeometric_interpolation(::Ferrite.AbstractCell)\nget_node_coordinate\nFerrite.getspatialdim(::Ferrite.AbstractGrid)\nFerrite.getrefdim(::Ferrite.AbstractCell)","category":"page"},{"location":"reference/grid/#Ferrite.getcells","page":"Grid & AbstractGrid","title":"Ferrite.getcells","text":"getcells(grid::AbstractGrid)\ngetcells(grid::AbstractGrid, v::Union{Int,Vector{Int}}\ngetcells(grid::AbstractGrid, setname::String)\n\nReturns either all cells::Collection{C<:AbstractCell} of a <:AbstractGrid or a subset based on an Int, Vector{Int} or String. Whereas the last option tries to call a cellset of the grid. Collection can be any indexable type, for Grid it is Vector{C<:AbstractCell}.\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.getncells","page":"Grid & AbstractGrid","title":"Ferrite.getncells","text":"Returns the number of cells in the <:AbstractGrid.\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.getnodes","page":"Grid & AbstractGrid","title":"Ferrite.getnodes","text":"getnodes(grid::AbstractGrid)\ngetnodes(grid::AbstractGrid, v::Union{Int,Vector{Int}}\ngetnodes(grid::AbstractGrid, setname::String)\n\nReturns either all nodes::Collection{N} of a <:AbstractGrid or a subset based on an Int, Vector{Int} or String. The last option tries to call a nodeset of the <:AbstractGrid. Collection{N} refers to some indexable collection where each element corresponds to a Node.\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.getnnodes","page":"Grid & AbstractGrid","title":"Ferrite.getnnodes","text":"Returns the number of nodes in the grid.\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.nnodes_per_cell","page":"Grid & AbstractGrid","title":"Ferrite.nnodes_per_cell","text":"Returns the number of nodes of the i-th cell.\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.getcellset","page":"Grid & AbstractGrid","title":"Ferrite.getcellset","text":"getcellset(grid::AbstractGrid, setname::String)\n\nReturns all cells as cellid in the set with name setname.\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.getnodeset","page":"Grid & AbstractGrid","title":"Ferrite.getnodeset","text":"getnodeset(grid::AbstractGrid, setname::String)\n\nReturns all nodes as nodeid in the set with name setname.\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.getfacetset","page":"Grid & AbstractGrid","title":"Ferrite.getfacetset","text":"getfacetset(grid::AbstractGrid, setname::String)\n\nReturns all faces as FacetIndex in the set with name setname.\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.getvertexset","page":"Grid & AbstractGrid","title":"Ferrite.getvertexset","text":"getvertexset(grid::AbstractGrid, setname::String)\n\nReturns all vertices as VertexIndex in the set with name setname.\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.transform_coordinates!","page":"Grid & AbstractGrid","title":"Ferrite.transform_coordinates!","text":"transform_coordinates!(grid::Abstractgrid, f::Function)\n\nTransform the coordinates of all nodes of the grid based on some transformation function f(x).\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.getcoordinates","page":"Grid & AbstractGrid","title":"Ferrite.getcoordinates","text":"getcoordinates(grid::AbstractGrid, idx::Union{Int,CellIndex})\ngetcoordinates(cache::CellCache)\n\nGet a vector with the coordinates of the cell corresponding to idx or cache\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.getcoordinates!","page":"Grid & AbstractGrid","title":"Ferrite.getcoordinates!","text":"getcoordinates!(x::Vector{<:Vec}, grid::AbstractGrid, idx::Union{Int,CellIndex})\ngetcoordinates!(x::Vector{<:Vec}, grid::AbstractGrid, cell::AbstractCell)\n\nMutate x to the coordinates of the cell corresponding to idx or cell.\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.geometric_interpolation-Tuple{Ferrite.AbstractCell}","page":"Grid & AbstractGrid","title":"Ferrite.geometric_interpolation","text":"geometric_interpolation(::AbstractCell)::ScalarInterpolation\ngeometric_interpolation(::Type{<:AbstractCell})::ScalarInterpolation\n\nEach AbstractCell type has a unique geometric interpolation describing its geometry. This function returns that interpolation, which is always a scalar interpolation.\n\n\n\n\n\n","category":"method"},{"location":"reference/grid/#Ferrite.get_node_coordinate","page":"Grid & AbstractGrid","title":"Ferrite.get_node_coordinate","text":"get_node_coordinate(::Node)\n\nGet the value of the node coordinate.\n\n\n\n\n\nget_node_coordinate(grid::AbstractGrid, n::Int)\n\nReturn the coordinate of the nth node in grid\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.getspatialdim-Tuple{Ferrite.AbstractGrid}","page":"Grid & AbstractGrid","title":"Ferrite.getspatialdim","text":"Ferrite.getspatialdim(grid::AbstractGrid)\n\nGet the spatial dimension of the grid, corresponding to the vector dimension of the grid's coordinates.\n\n\n\n\n\n","category":"method"},{"location":"reference/grid/#Ferrite.getrefdim-Tuple{Ferrite.AbstractCell}","page":"Grid & AbstractGrid","title":"Ferrite.getrefdim","text":"Ferrite.getrefdim(cell::AbstractCell)\nFerrite.getrefdim(::Type{<:AbstractCell})\n\nGet the reference dimension of the cell, i.e. the dimension of the cell's reference shape.\n\n\n\n\n\n","category":"method"},{"location":"reference/grid/#Topology","page":"Grid & AbstractGrid","title":"Topology","text":"","category":"section"},{"location":"reference/grid/","page":"Grid & AbstractGrid","title":"Grid & AbstractGrid","text":"ExclusiveTopology\ngetneighborhood\nfacetskeleton\nvertex_star_stencils\ngetstencil","category":"page"},{"location":"reference/grid/#Ferrite.ExclusiveTopology","page":"Grid & AbstractGrid","title":"Ferrite.ExclusiveTopology","text":"ExclusiveTopology(grid::AbstractGrid)\n\nThe experimental feature ExclusiveTopology saves topological (connectivity/neighborhood) data of the grid. Only the highest dimensional neighborhood is saved. I.e., if something is connected by a face and an edge, only the face neighborhood is saved. The lower dimensional neighborhood is recomputed when calling getneighborhood if needed.\n\nFields\n\nvertex_to_cell::AbstractArray{AbstractVector{Int}, 1}: global vertex id to all cells containing the vertex\ncell_neighbor::AbstractArray{AbstractVector{Int}, 1}: cellid to all connected cells\nface_neighbor::AbstractArray{AbstractVector{FaceIndex}, 2}: face_neighbor[cellid, local_face_id] -> neighboring faces\nedge_neighbor::AbstractArray{AbstractVector{EdgeIndex}, 2}: edge_neighbor[cellid, local_edge_id] -> neighboring edges\nvertex_neighbor::AbstractArray{AbstractVector{VertexIndex}, 2}: vertex_neighbor[cellid, local_vertex_id] -> neighboring vertices\nface_skeleton::Union{Vector{FaceIndex}, Nothing}: List of unique faces in the grid given as FaceIndex\nedge_skeleton::Union{Vector{EdgeIndex}, Nothing}: List of unique edges in the grid given as EdgeIndex\nvertex_skeleton::Union{Vector{VertexIndex}, Nothing}: List of unique vertices in the grid given as VertexIndex\n\nwarning: Limitations\nThe implementation only works with conforming grids, i.e. grids without \"hanging nodes\". Non-conforming grids will give unexpected results. Grids with embedded cells (different reference dimension compared to the spatial dimension) are not supported, and will error on construction.\n\n\n\n\n\n","category":"type"},{"location":"reference/grid/#Ferrite.getneighborhood","page":"Grid & AbstractGrid","title":"Ferrite.getneighborhood","text":"getneighborhood(topology, grid::AbstractGrid, cellidx::CellIndex, include_self=false)\ngetneighborhood(topology, grid::AbstractGrid, faceidx::FaceIndex, include_self=false)\ngetneighborhood(topology, grid::AbstractGrid, vertexidx::VertexIndex, include_self=false)\ngetneighborhood(topology, grid::AbstractGrid, edgeidx::EdgeIndex, include_self=false)\n\nReturns all connected entities of the same type as defined by the respective topology. If include_self is true, the given entity is included in the returned list as well.\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.facetskeleton","page":"Grid & AbstractGrid","title":"Ferrite.facetskeleton","text":"facetskeleton(top::ExclusiveTopology, grid::AbstractGrid)\n\nMaterializes the skeleton from the neighborhood information by returning an iterable over the unique facets in the grid, described by FacetIndex.\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.vertex_star_stencils","page":"Grid & AbstractGrid","title":"Ferrite.vertex_star_stencils","text":"vertex_star_stencils(top::ExclusiveTopology, grid::Grid) -> AbstractVector{AbstractVector{VertexIndex}}\n\nComputes the stencils induced by the edge connectivity of the vertices.\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.getstencil","page":"Grid & AbstractGrid","title":"Ferrite.getstencil","text":"getstencil(top::ArrayOfVectorViews{VertexIndex, 1}, grid::AbstractGrid, vertex_idx::VertexIndex) -> AbstractVector{VertexIndex}\n\nGet an iterateable over the stencil members for a given local entity.\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Grid-Sets-Utility","page":"Grid & AbstractGrid","title":"Grid Sets Utility","text":"","category":"section"},{"location":"reference/grid/","page":"Grid & AbstractGrid","title":"Grid & AbstractGrid","text":"addcellset!\naddfacetset!\naddboundaryfacetset!\naddvertexset!\naddboundaryvertexset!\naddnodeset!","category":"page"},{"location":"reference/grid/#Ferrite.addcellset!","page":"Grid & AbstractGrid","title":"Ferrite.addcellset!","text":"addcellset!(grid::AbstractGrid, name::String, cellid::AbstractVecOrSet{Int})\naddcellset!(grid::AbstractGrid, name::String, f::function; all::Bool=true)\n\nAdds a cellset to the grid with key name. Cellsets are typically used to define subdomains of the problem, e.g. two materials in the computational domain. The DofHandler can construct different fields which live not on the whole domain, but rather on a cellset. all=true implies that f(x) must return true for all nodal coordinates x in the cell if the cell should be added to the set, otherwise it suffices that f(x) returns true for one node.\n\naddcellset!(grid, \"left\", Set((1,3))) #add cells with id 1 and 3 to cellset left\naddcellset!(grid, \"right\", x -> norm(x[1]) < 2.0 ) #add cell to cellset right, if x[1] of each cell's node is smaller than 2.0\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.addfacetset!","page":"Grid & AbstractGrid","title":"Ferrite.addfacetset!","text":"addfacetset!(grid::AbstractGrid, name::String, faceid::AbstractVecOrSet{FacetIndex})\naddfacetset!(grid::AbstractGrid, name::String, f::Function; all::Bool=true)\n\nAdds a facetset to the grid with key name. A facetset maps a String key to a OrderedSet of tuples corresponding to (global_cell_id, local_facet_id). Facetsets can be used to initialize Dirichlet boundary conditions for the ConstraintHandler. all=true implies that f(x) must return true for all nodal coordinates x on the facet if the facet should be added to the set, otherwise it suffices that f(x) returns true for one node.\n\naddfacetset!(grid, \"right\", Set((FacetIndex(2,2), FacetIndex(4,2)))) #see grid manual example for reference\naddfacetset!(grid, \"clamped\", x -> norm(x[1]) ≈ 0.0) #see incompressible elasticity example for reference\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.addboundaryfacetset!","page":"Grid & AbstractGrid","title":"Ferrite.addboundaryfacetset!","text":"addboundaryfacetset!(grid::AbstractGrid, topology::ExclusiveTopology, name::String, f::Function; all::Bool=true)\n\nAdds a boundary facetset to the grid with key name. A facetset maps a String key to a OrderedSet of tuples corresponding to (global_cell_id, local_facet_id). Facetsets are used to initialize Dirichlet structs, that are needed to specify the boundary for the ConstraintHandler. all=true implies that f(x) must return true for all nodal coordinates x on the facet if the facet should be added to the set, otherwise it suffices that f(x) returns true for one node.\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.addvertexset!","page":"Grid & AbstractGrid","title":"Ferrite.addvertexset!","text":"addvertexset!(grid::AbstractGrid, name::String, faceid::AbstractVecOrSet{FaceIndex})\naddvertexset!(grid::AbstractGrid, name::String, f::Function)\n\nAdds a vertexset to the grid with key name. A vertexset maps a String key to a OrderedSet of tuples corresponding to (global_cell_id, local_vertex_id). Vertexsets can be used to initialize Dirichlet boundary conditions for the ConstraintHandler.\n\naddvertexset!(grid, \"right\", Set((VertexIndex(2,2), VertexIndex(4,2))))\naddvertexset!(grid, \"clamped\", x -> norm(x[1]) ≈ 0.0)\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.addboundaryvertexset!","page":"Grid & AbstractGrid","title":"Ferrite.addboundaryvertexset!","text":"addboundaryvertexset!(grid::AbstractGrid, topology::ExclusiveTopology, name::String, f::Function; all::Bool=true)\n\nAdds a boundary vertexset to the grid with key name. A vertexset maps a String key to an OrderedSet of tuples corresponding to (global_cell_id, local_vertex_id). all=true implies that f(x) must return true for all nodal coordinates x on the face if the face should be added to the set, otherwise it suffices that f(x) returns true for one node.\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.addnodeset!","page":"Grid & AbstractGrid","title":"Ferrite.addnodeset!","text":"addnodeset!(grid::AbstractGrid, name::String, nodeid::AbstractVecOrSet{Int})\naddnodeset!(grid::AbstractGrid, name::String, f::Function)\n\nAdds a nodeset::OrderedSet{Int} to the grid's nodesets with key name. Has the same interface as addcellset. However, instead of mapping a cell id to the String key, a set of node ids is returned.\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Multithreaded-Assembly","page":"Grid & AbstractGrid","title":"Multithreaded Assembly","text":"","category":"section"},{"location":"reference/grid/","page":"Grid & AbstractGrid","title":"Grid & AbstractGrid","text":"create_coloring","category":"page"},{"location":"reference/grid/#Ferrite.create_coloring","page":"Grid & AbstractGrid","title":"Ferrite.create_coloring","text":"create_coloring(g::Grid, cellset=1:getncells(g); alg::ColoringAlgorithm)\n\nCreate a coloring of the cells in grid g such that no neighboring cells have the same color. If only a subset of cells should be colored, the cells to color can be specified by cellset.\n\nReturns a vector of vectors with cell indexes, e.g.:\n\nret = [\n [1, 3, 5, 10, ...], # cells for color 1\n [2, 4, 6, 12, ...], # cells for color 2\n]\n\nTwo different algorithms are available, specified with the alg keyword argument:\n\nalg = ColoringAlgorithm.WorkStream (default): Three step algorithm from Turcksin et al. [13], albeit with a greedy coloring in the second step. Generally results in more colors than ColoringAlgorithm.Greedy, however the cells are more equally distributed among the colors.\nalg = ColoringAlgorithm.Greedy: greedy algorithm that works well for structured quadrilateral grids such as e.g. quadrilateral grids from generate_grid.\n\nThe resulting colors can be visualized using Ferrite.write_cell_colors.\n\nnote: Cell to color mapping\nIn a previous version of Ferrite this function returned a dictionary mapping cell ID to color numbers as the first argument. If you need this mapping you can create it using the following construct:colors = create_coloring(...)\ncell_colormap = Dict{Int,Int}(\n cellid => color for (color, cellids) in enumerate(final_colors) for cellid in cellids\n)\n\nReferences\n\n[13] Turcksin et al. ACM Trans. Math. Softw. 43 (2016).\n\n\n\n\n\n","category":"function"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"EditURL = \"../literate-tutorials/porous_media.jl\"","category":"page"},{"location":"tutorials/porous_media/#Porous-media","page":"Porous media","title":"Porous media","text":"","category":"section"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"Porous media is a two-phase material, consisting of solid parts and a liquid occupying the pores inbetween. Using the porous media theory, we can model such a material without explicitly resolving the microstructure, but by considering the interactions between the solid and liquid. In this example, we will additionally consider larger linear elastic solid aggregates that are impermeable. Hence, there is no liquids in these particles and the only unknown variable is the displacement field :u. In the porous media, denoted the matrix, we have both the displacement field, :u, as well as the liquid pressure, :p, as unknown. The simulation result is shown below","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"(Image: Pressure evolution.)","category":"page"},{"location":"tutorials/porous_media/#Theory-of-porous-media","page":"Porous media","title":"Theory of porous media","text":"","category":"section"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"The strong forms are given as","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"beginaligned\nboldsymbolsigma(boldsymbolepsilon p) cdot boldsymbolnabla = boldsymbol0 \ndotPhi(boldsymbolepsilon p) + boldsymbolw(p) cdot boldsymbolnabla = 0\nendaligned","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"where boldsymbolepsilon = leftboldsymboluotimesboldsymbolnablaright^mathrmsym The constitutive relationships are","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"beginaligned\nboldsymbolsigma = boldsymbolmathsfCboldsymbolepsilon - alpha p boldsymbolI \nboldsymbolw = - k boldsymbolnabla p \nPhi = phi + alpha mathrmtr(boldsymbolepsilon) + beta p\nendaligned","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"with boldsymbolmathsfC=2G boldsymbolmathsfI^mathrmdev + 3K boldsymbolIotimesboldsymbolI. The material parameters are then the shear modulus, G, bulk modulus, K, permeability, k, Biot's coefficient, alpha, and liquid compressibility, beta. The porosity, phi, doesn't enter into the equations (A different porosity leads to different skeleton stiffness and permeability).","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"The variational (weak) form can then be derived for the variations boldsymboldelta u and delta p as","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"beginaligned\nint_Omega leftleftboldsymboldelta uotimesboldsymbolnablaright^mathrmsym\nboldsymbolmathsfCboldsymbolepsilon - boldsymboldelta u cdot boldsymbolnabla alpha pright mathrmdOmega\n= int_Gamma boldsymboldelta u cdot boldsymbolt mathrmd Gamma \nint_Omega leftdelta p leftalpha dotboldsymbolu cdot boldsymbolnabla + beta dotpright +\nboldsymbolnabla(delta p) cdot k boldsymbolnablaright mathrmdOmega\n= int_Gamma delta p w_mathrmn mathrmd Gamma\nendaligned","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"where boldsymbolt=boldsymbolncdotboldsymbolsigma is the traction and w_mathrmn = boldsymbolncdotboldsymbolw is the normal flux.","category":"page"},{"location":"tutorials/porous_media/#Finite-element-form","page":"Porous media","title":"Finite element form","text":"","category":"section"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"Discretizing in space using finite elements, we obtain the vector equation r_i = f_i^mathrmint - f_i^mathrmext where f^mathrmext are the external \"forces\", and f_i^mathrmint are the internal \"forces\". We split this into the displacement part r_i^mathrmu = f_i^mathrmintu - f_i^mathrmextu and pressure part r_i^mathrmp = f_i^mathrmintp - f_i^mathrmextp to obtain the discretized equation system","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"beginaligned\nf_i^mathrmintu = int_Omega boldsymboldelta N^mathrmu_iotimesboldsymbolnabla^mathrmsym boldsymbolmathsfC boldsymboluotimesboldsymbolnabla^mathrmsym \n- boldsymboldelta N^mathrmu_i cdot boldsymbolnabla alpha p mathrmdOmega\n= int_Gamma boldsymboldelta N^mathrmu_i cdot boldsymbolt mathrmd Gamma \nf_i^mathrmintp = int_Omega delta N_i^mathrmp alpha dotboldsymbolucdotboldsymbolnabla + betadotp + boldsymbolnabla(delta N_i^mathrmp) cdot k boldsymbolnabla(p) mathrmdOmega\n= int_Gamma delta N_i^mathrmp w_mathrmn mathrmd Gamma\nendaligned","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"Approximating the time-derivatives, dotboldsymboluapprox leftboldsymbolu-^nboldsymbolurightDelta t and dotpapprox leftp-^nprightDelta t, we can implement the finite element equations in the residual form r_i(boldsymbola(t) t) = 0 where the vector boldsymbola contains all unknown displacements u_i and pressures p_i.","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"The jacobian, K_ij = partial r_ipartial a_j, is then split into four parts,","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"beginaligned\nK_ij^mathrmuu = fracpartial r_i^mathrmupartial u_j = int_Omega boldsymboldelta N^mathrmu_iotimesboldsymbolnabla^mathrmsym boldsymbolmathsfC boldsymbolN_j^mathrmuotimesboldsymbolnabla^mathrmsym mathrmdOmega \nK_ij^mathrmup = fracpartial r_i^mathrmupartial p_j = - int_Omega boldsymboldelta N^mathrmu_i cdot boldsymbolnabla alpha N_j^mathrmp mathrmdOmega \nK_ij^mathrmpu = fracpartial r_i^mathrmppartial u_j = int_Omega delta N_i^mathrmp fracalphaDelta t boldsymbolN_j^mathrmu cdotboldsymbolnabla mathrmdOmega\nK_ij^mathrmpp = fracpartial r_i^mathrmppartial p_j = int_Omega delta N_i^mathrmp fracN_j^mathrmpDelta t + boldsymbolnabla(delta N_i^mathrmp) cdot k boldsymbolnabla(N_j^mathrmp) mathrmdOmega\nendaligned","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"We could assemble one stiffness matrix and one mass matrix, which would be constant, but for simplicity we only consider a single system matrix that depends on the time step, and assemble this for each step. The equations are still linear, so no iterations are required.","category":"page"},{"location":"tutorials/porous_media/#Implementation","page":"Porous media","title":"Implementation","text":"","category":"section"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"We now solve the problem step by step. The full program with fewer comments is found in the final section","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"Required packages","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"using Ferrite, FerriteMeshParser, Tensors, WriteVTK","category":"page"},{"location":"tutorials/porous_media/#Elasticity","page":"Porous media","title":"Elasticity","text":"","category":"section"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"We start by defining the elastic material type, containing the elastic stiffness, for the linear elastic impermeable solid aggregates.","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"struct Elastic{T}\n C::SymmetricTensor{4, 2, T, 9}\nend\nfunction Elastic(; E = 20.0e3, ν = 0.3)\n G = E / 2(1 + ν)\n K = E / 3(1 - 2ν)\n I2 = one(SymmetricTensor{2, 2})\n I4vol = I2 ⊗ I2\n I4dev = minorsymmetric(otimesu(I2, I2)) - I4vol / 3\n return Elastic(2G * I4dev + K * I4vol)\nend;\nnothing #hide","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"Next, we define the element routine for the solid aggregates, where we dispatch on the Elastic material struct. Note that the unused inputs here are used for the porous matrix below.","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"function element_routine!(Ke, re, material::Elastic, cv, cell, a, args...)\n reinit!(cv, cell)\n n_basefuncs = getnbasefunctions(cv)\n\n for q_point in 1:getnquadpoints(cv)\n dΩ = getdetJdV(cv, q_point)\n ϵ = function_symmetric_gradient(cv, q_point, a)\n σ = material.C ⊡ ϵ\n for i in 1:n_basefuncs\n δ∇N = shape_symmetric_gradient(cv, q_point, i)\n re[i] += (δ∇N ⊡ σ) * dΩ\n for j in 1:n_basefuncs\n ∇N = shape_symmetric_gradient(cv, q_point, j)\n Ke[i, j] += (δ∇N ⊡ material.C ⊡ ∇N) * dΩ\n end\n end\n end\n return\nend;\nnothing #hide","category":"page"},{"location":"tutorials/porous_media/#PoroElasticity","page":"Porous media","title":"PoroElasticity","text":"","category":"section"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"To define the poroelastic material, we re-use the elastic part from above for the skeleton, and add the additional required material parameters.","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"struct PoroElastic{T}\n elastic::Elastic{T} ## Skeleton stiffness\n k::T ## Permeability of liquid [mm^4/(Ns)]\n ϕ::T ## Porosity [-]\n α::T ## Biot's coefficient [-]\n β::T ## Liquid compressibility [1/MPa]\nend\nPoroElastic(; elastic, k, ϕ, α, β) = PoroElastic(elastic, k, ϕ, α, β);\nnothing #hide","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"The element routine requires a few more inputs since we have two fields, as well as the dependence on the rates of the displacements and pressure. Again, we dispatch on the material type.","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"function element_routine!(Ke, re, m::PoroElastic, cvs::Tuple, cell, a, a_old, Δt, sdh)\n # Setup cellvalues and give easier names\n reinit!.(cvs, (cell,))\n cv_u, cv_p = cvs\n dr_u = dof_range(sdh, :u)\n dr_p = dof_range(sdh, :p)\n\n C = m.elastic.C ## Elastic stiffness\n\n # Assemble stiffness and force vectors\n for q_point in 1:getnquadpoints(cv_u)\n dΩ = getdetJdV(cv_u, q_point)\n p = function_value(cv_p, q_point, a, dr_p)\n p_old = function_value(cv_p, q_point, a_old, dr_p)\n pdot = (p - p_old) / Δt\n ∇p = function_gradient(cv_p, q_point, a, dr_p)\n ϵ = function_symmetric_gradient(cv_u, q_point, a, dr_u)\n tr_ϵ_old = function_divergence(cv_u, q_point, a_old, dr_u)\n tr_ϵ_dot = (tr(ϵ) - tr_ϵ_old) / Δt\n σ_eff = C ⊡ ϵ\n # Variation of u_i\n for (iᵤ, Iᵤ) in pairs(dr_u)\n ∇δNu = shape_symmetric_gradient(cv_u, q_point, iᵤ)\n div_δNu = shape_divergence(cv_u, q_point, iᵤ)\n re[Iᵤ] += (∇δNu ⊡ σ_eff - div_δNu * p * m.α) * dΩ\n for (jᵤ, Jᵤ) in pairs(dr_u)\n ∇Nu = shape_symmetric_gradient(cv_u, q_point, jᵤ)\n Ke[Iᵤ, Jᵤ] += (∇δNu ⊡ C ⊡ ∇Nu) * dΩ\n end\n for (jₚ, Jₚ) in pairs(dr_p)\n Np = shape_value(cv_p, q_point, jₚ)\n Ke[Iᵤ, Jₚ] -= (div_δNu * m.α * Np) * dΩ\n end\n end\n # Variation of p_i\n for (iₚ, Iₚ) in pairs(dr_p)\n δNp = shape_value(cv_p, q_point, iₚ)\n ∇δNp = shape_gradient(cv_p, q_point, iₚ)\n re[Iₚ] += (δNp * (m.α * tr_ϵ_dot + m.β * pdot) + m.k * (∇δNp ⋅ ∇p)) * dΩ\n for (jᵤ, Jᵤ) in pairs(dr_u)\n div_Nu = shape_divergence(cv_u, q_point, jᵤ)\n Ke[Iₚ, Jᵤ] += δNp * (m.α / Δt) * div_Nu * dΩ\n end\n for (jₚ, Jₚ) in pairs(dr_p)\n ∇Np = shape_gradient(cv_p, q_point, jₚ)\n Np = shape_value(cv_p, q_point, jₚ)\n Ke[Iₚ, Jₚ] += (δNp * m.β * Np / Δt + m.k * (∇δNp ⋅ ∇Np)) * dΩ\n end\n end\n end\n return\nend;\nnothing #hide","category":"page"},{"location":"tutorials/porous_media/#Assembly","page":"Porous media","title":"Assembly","text":"","category":"section"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"To organize the different domains, we'll first define a container type","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"struct FEDomain{M, CV, SDH <: SubDofHandler}\n material::M\n cellvalues::CV\n sdh::SDH\nend;\nnothing #hide","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"And then we can loop over a vector of such domains, allowing us to loop over each domain, to assemble the contributions from each cell in that domain (given by the SubDofHandler's cellset)","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"function doassemble!(K, r, domains::Vector{<:FEDomain}, a, a_old, Δt)\n assembler = start_assemble(K, r)\n for domain in domains\n doassemble!(assembler, domain, a, a_old, Δt)\n end\n return\nend;\nnothing #hide","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"For one domain (corresponding to a specific SubDofHandler), we can then loop over all cells in its cellset. Doing this in a separate function (instead of a nested loop), ensures that the calls to the element_routine are type stable, which can be important for good performance.","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"function doassemble!(assembler, domain::FEDomain, a, a_old, Δt)\n material = domain.material\n cv = domain.cellvalues\n sdh = domain.sdh\n n = ndofs_per_cell(sdh)\n Ke = zeros(n, n)\n re = zeros(n)\n ae_old = zeros(n)\n ae = zeros(n)\n for cell in CellIterator(sdh)\n # copy values from a to ae\n map!(i -> a[i], ae, celldofs(cell))\n map!(i -> a_old[i], ae_old, celldofs(cell))\n fill!(Ke, 0)\n fill!(re, 0)\n element_routine!(Ke, re, material, cv, cell, ae, ae_old, Δt, sdh)\n assemble!(assembler, celldofs(cell), Ke, re)\n end\n return\nend;\nnothing #hide","category":"page"},{"location":"tutorials/porous_media/#Mesh-import","page":"Porous media","title":"Mesh import","text":"","category":"section"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"In this example, we import the mesh from the Abaqus input file, porous_media_0p25.inp using FerriteMeshParser's get_ferrite_grid function. We then create one cellset for each phase (solid and porous) for each element type. These 4 sets will later be used in their own SubDofHandler","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"function get_grid()\n # Import grid from abaqus mesh\n grid = get_ferrite_grid(joinpath(@__DIR__, \"porous_media_0p25.inp\"))\n\n # Create cellsets for each fieldhandler\n addcellset!(grid, \"solid3\", intersect(getcellset(grid, \"solid\"), getcellset(grid, \"CPS3\")))\n addcellset!(grid, \"solid4\", intersect(getcellset(grid, \"solid\"), getcellset(grid, \"CPS4R\")))\n addcellset!(grid, \"porous3\", intersect(getcellset(grid, \"porous\"), getcellset(grid, \"CPS3\")))\n addcellset!(grid, \"porous4\", intersect(getcellset(grid, \"porous\"), getcellset(grid, \"CPS4R\")))\n return grid\nend;\nnothing #hide","category":"page"},{"location":"tutorials/porous_media/#Problem-setup","page":"Porous media","title":"Problem setup","text":"","category":"section"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"Define the finite element interpolation, integration, and boundary conditions.","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"function setup_problem(; t_rise = 0.1, u_max = -0.1)\n\n grid = get_grid()\n\n # Define materials\n m_solid = Elastic(; E = 20.0e3, ν = 0.3)\n m_porous = PoroElastic(; elastic = Elastic(; E = 10.0e3, ν = 0.3), β = 1 / 15.0e3, α = 0.9, k = 5.0e-3, ϕ = 0.8)\n\n # Define interpolations\n ipu_quad = Lagrange{RefQuadrilateral, 2}()^2\n ipu_tri = Lagrange{RefTriangle, 2}()^2\n ipp_quad = Lagrange{RefQuadrilateral, 1}()\n ipp_tri = Lagrange{RefTriangle, 1}()\n\n # Quadrature rules\n qr_quad = QuadratureRule{RefQuadrilateral}(2)\n qr_tri = QuadratureRule{RefTriangle}(2)\n\n # CellValues\n cvu_quad = CellValues(qr_quad, ipu_quad)\n cvu_tri = CellValues(qr_tri, ipu_tri)\n cvp_quad = CellValues(qr_quad, ipp_quad)\n cvp_tri = CellValues(qr_tri, ipp_tri)\n\n # Setup the DofHandler\n dh = DofHandler(grid)\n # Solid quads\n sdh_solid_quad = SubDofHandler(dh, getcellset(grid, \"solid4\"))\n add!(sdh_solid_quad, :u, ipu_quad)\n # Solid triangles\n sdh_solid_tri = SubDofHandler(dh, getcellset(grid, \"solid3\"))\n add!(sdh_solid_tri, :u, ipu_tri)\n # Porous quads\n sdh_porous_quad = SubDofHandler(dh, getcellset(grid, \"porous4\"))\n add!(sdh_porous_quad, :u, ipu_quad)\n add!(sdh_porous_quad, :p, ipp_quad)\n # Porous triangles\n sdh_porous_tri = SubDofHandler(dh, getcellset(grid, \"porous3\"))\n add!(sdh_porous_tri, :u, ipu_tri)\n add!(sdh_porous_tri, :p, ipp_tri)\n\n close!(dh)\n\n # Setup the domains\n domains = [\n FEDomain(m_solid, cvu_quad, sdh_solid_quad),\n FEDomain(m_solid, cvu_tri, sdh_solid_tri),\n FEDomain(m_porous, (cvu_quad, cvp_quad), sdh_porous_quad),\n FEDomain(m_porous, (cvu_tri, cvp_tri), sdh_porous_tri),\n ]\n\n # Boundary conditions\n # Sliding for u, except top which is compressed\n # Sealed for p, except top with prescribed zero pressure\n addfacetset!(dh.grid, \"sides\", x -> x[1] < 1.0e-6 || x[1] ≈ 5.0)\n addfacetset!(dh.grid, \"top\", x -> x[2] ≈ 10.0)\n ch = ConstraintHandler(dh)\n add!(ch, Dirichlet(:u, getfacetset(grid, \"bottom\"), (x, t) -> zero(Vec{1}), [2]))\n add!(ch, Dirichlet(:u, getfacetset(grid, \"sides\"), (x, t) -> zero(Vec{1}), [1]))\n add!(ch, Dirichlet(:u, getfacetset(grid, \"top\"), (x, t) -> u_max * clamp(t / t_rise, 0, 1), [2]))\n add!(ch, Dirichlet(:p, getfacetset(grid, \"top_p\"), (x, t) -> 0.0))\n close!(ch)\n\n return dh, ch, domains\nend;\nnothing #hide","category":"page"},{"location":"tutorials/porous_media/#Solving","page":"Porous media","title":"Solving","text":"","category":"section"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"Given the DofHandler, ConstraintHandler, and CellValues, we can solve the problem by stepping through the time history","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"function solve(dh, ch, domains; Δt = 0.025, t_total = 1.0)\n K = allocate_matrix(dh)\n r = zeros(ndofs(dh))\n a = zeros(ndofs(dh))\n a_old = copy(a)\n pvd = paraview_collection(\"porous_media\")\n step = 0\n for t in 0:Δt:t_total\n if t > 0\n update!(ch, t)\n apply!(a, ch)\n doassemble!(K, r, domains, a, a_old, Δt)\n apply_zero!(K, r, ch)\n Δa = -K \\ r\n apply_zero!(Δa, ch)\n a .+= Δa\n copyto!(a_old, a)\n end\n step += 1\n VTKGridFile(\"porous_media_$step\", dh) do vtk\n write_solution(vtk, dh, a)\n pvd[t] = vtk\n end\n end\n vtk_save(pvd)\n return\nend;\nnothing #hide","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"Finally we call the functions to actually run the code","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"dh, ch, domains = setup_problem()\nsolve(dh, ch, domains);\nnothing #hide","category":"page"},{"location":"tutorials/porous_media/#porous-media-plain-program","page":"Porous media","title":"Plain program","text":"","category":"section"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"Here follows a version of the program without any comments. The file is also available here: porous_media.jl.","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"using Ferrite, FerriteMeshParser, Tensors, WriteVTK\n\nstruct Elastic{T}\n C::SymmetricTensor{4, 2, T, 9}\nend\nfunction Elastic(; E = 20.0e3, ν = 0.3)\n G = E / 2(1 + ν)\n K = E / 3(1 - 2ν)\n I2 = one(SymmetricTensor{2, 2})\n I4vol = I2 ⊗ I2\n I4dev = minorsymmetric(otimesu(I2, I2)) - I4vol / 3\n return Elastic(2G * I4dev + K * I4vol)\nend;\n\nfunction element_routine!(Ke, re, material::Elastic, cv, cell, a, args...)\n reinit!(cv, cell)\n n_basefuncs = getnbasefunctions(cv)\n\n for q_point in 1:getnquadpoints(cv)\n dΩ = getdetJdV(cv, q_point)\n ϵ = function_symmetric_gradient(cv, q_point, a)\n σ = material.C ⊡ ϵ\n for i in 1:n_basefuncs\n δ∇N = shape_symmetric_gradient(cv, q_point, i)\n re[i] += (δ∇N ⊡ σ) * dΩ\n for j in 1:n_basefuncs\n ∇N = shape_symmetric_gradient(cv, q_point, j)\n Ke[i, j] += (δ∇N ⊡ material.C ⊡ ∇N) * dΩ\n end\n end\n end\n return\nend;\n\nstruct PoroElastic{T}\n elastic::Elastic{T} ## Skeleton stiffness\n k::T ## Permeability of liquid [mm^4/(Ns)]\n ϕ::T ## Porosity [-]\n α::T ## Biot's coefficient [-]\n β::T ## Liquid compressibility [1/MPa]\nend\nPoroElastic(; elastic, k, ϕ, α, β) = PoroElastic(elastic, k, ϕ, α, β);\n\nfunction element_routine!(Ke, re, m::PoroElastic, cvs::Tuple, cell, a, a_old, Δt, sdh)\n # Setup cellvalues and give easier names\n reinit!.(cvs, (cell,))\n cv_u, cv_p = cvs\n dr_u = dof_range(sdh, :u)\n dr_p = dof_range(sdh, :p)\n\n C = m.elastic.C ## Elastic stiffness\n\n # Assemble stiffness and force vectors\n for q_point in 1:getnquadpoints(cv_u)\n dΩ = getdetJdV(cv_u, q_point)\n p = function_value(cv_p, q_point, a, dr_p)\n p_old = function_value(cv_p, q_point, a_old, dr_p)\n pdot = (p - p_old) / Δt\n ∇p = function_gradient(cv_p, q_point, a, dr_p)\n ϵ = function_symmetric_gradient(cv_u, q_point, a, dr_u)\n tr_ϵ_old = function_divergence(cv_u, q_point, a_old, dr_u)\n tr_ϵ_dot = (tr(ϵ) - tr_ϵ_old) / Δt\n σ_eff = C ⊡ ϵ\n # Variation of u_i\n for (iᵤ, Iᵤ) in pairs(dr_u)\n ∇δNu = shape_symmetric_gradient(cv_u, q_point, iᵤ)\n div_δNu = shape_divergence(cv_u, q_point, iᵤ)\n re[Iᵤ] += (∇δNu ⊡ σ_eff - div_δNu * p * m.α) * dΩ\n for (jᵤ, Jᵤ) in pairs(dr_u)\n ∇Nu = shape_symmetric_gradient(cv_u, q_point, jᵤ)\n Ke[Iᵤ, Jᵤ] += (∇δNu ⊡ C ⊡ ∇Nu) * dΩ\n end\n for (jₚ, Jₚ) in pairs(dr_p)\n Np = shape_value(cv_p, q_point, jₚ)\n Ke[Iᵤ, Jₚ] -= (div_δNu * m.α * Np) * dΩ\n end\n end\n # Variation of p_i\n for (iₚ, Iₚ) in pairs(dr_p)\n δNp = shape_value(cv_p, q_point, iₚ)\n ∇δNp = shape_gradient(cv_p, q_point, iₚ)\n re[Iₚ] += (δNp * (m.α * tr_ϵ_dot + m.β * pdot) + m.k * (∇δNp ⋅ ∇p)) * dΩ\n for (jᵤ, Jᵤ) in pairs(dr_u)\n div_Nu = shape_divergence(cv_u, q_point, jᵤ)\n Ke[Iₚ, Jᵤ] += δNp * (m.α / Δt) * div_Nu * dΩ\n end\n for (jₚ, Jₚ) in pairs(dr_p)\n ∇Np = shape_gradient(cv_p, q_point, jₚ)\n Np = shape_value(cv_p, q_point, jₚ)\n Ke[Iₚ, Jₚ] += (δNp * m.β * Np / Δt + m.k * (∇δNp ⋅ ∇Np)) * dΩ\n end\n end\n end\n return\nend;\n\nstruct FEDomain{M, CV, SDH <: SubDofHandler}\n material::M\n cellvalues::CV\n sdh::SDH\nend;\n\nfunction doassemble!(K, r, domains::Vector{<:FEDomain}, a, a_old, Δt)\n assembler = start_assemble(K, r)\n for domain in domains\n doassemble!(assembler, domain, a, a_old, Δt)\n end\n return\nend;\n\nfunction doassemble!(assembler, domain::FEDomain, a, a_old, Δt)\n material = domain.material\n cv = domain.cellvalues\n sdh = domain.sdh\n n = ndofs_per_cell(sdh)\n Ke = zeros(n, n)\n re = zeros(n)\n ae_old = zeros(n)\n ae = zeros(n)\n for cell in CellIterator(sdh)\n # copy values from a to ae\n map!(i -> a[i], ae, celldofs(cell))\n map!(i -> a_old[i], ae_old, celldofs(cell))\n fill!(Ke, 0)\n fill!(re, 0)\n element_routine!(Ke, re, material, cv, cell, ae, ae_old, Δt, sdh)\n assemble!(assembler, celldofs(cell), Ke, re)\n end\n return\nend;\n\nfunction get_grid()\n # Import grid from abaqus mesh\n grid = get_ferrite_grid(joinpath(@__DIR__, \"porous_media_0p25.inp\"))\n\n # Create cellsets for each fieldhandler\n addcellset!(grid, \"solid3\", intersect(getcellset(grid, \"solid\"), getcellset(grid, \"CPS3\")))\n addcellset!(grid, \"solid4\", intersect(getcellset(grid, \"solid\"), getcellset(grid, \"CPS4R\")))\n addcellset!(grid, \"porous3\", intersect(getcellset(grid, \"porous\"), getcellset(grid, \"CPS3\")))\n addcellset!(grid, \"porous4\", intersect(getcellset(grid, \"porous\"), getcellset(grid, \"CPS4R\")))\n return grid\nend;\n\nfunction setup_problem(; t_rise = 0.1, u_max = -0.1)\n\n grid = get_grid()\n\n # Define materials\n m_solid = Elastic(; E = 20.0e3, ν = 0.3)\n m_porous = PoroElastic(; elastic = Elastic(; E = 10.0e3, ν = 0.3), β = 1 / 15.0e3, α = 0.9, k = 5.0e-3, ϕ = 0.8)\n\n # Define interpolations\n ipu_quad = Lagrange{RefQuadrilateral, 2}()^2\n ipu_tri = Lagrange{RefTriangle, 2}()^2\n ipp_quad = Lagrange{RefQuadrilateral, 1}()\n ipp_tri = Lagrange{RefTriangle, 1}()\n\n # Quadrature rules\n qr_quad = QuadratureRule{RefQuadrilateral}(2)\n qr_tri = QuadratureRule{RefTriangle}(2)\n\n # CellValues\n cvu_quad = CellValues(qr_quad, ipu_quad)\n cvu_tri = CellValues(qr_tri, ipu_tri)\n cvp_quad = CellValues(qr_quad, ipp_quad)\n cvp_tri = CellValues(qr_tri, ipp_tri)\n\n # Setup the DofHandler\n dh = DofHandler(grid)\n # Solid quads\n sdh_solid_quad = SubDofHandler(dh, getcellset(grid, \"solid4\"))\n add!(sdh_solid_quad, :u, ipu_quad)\n # Solid triangles\n sdh_solid_tri = SubDofHandler(dh, getcellset(grid, \"solid3\"))\n add!(sdh_solid_tri, :u, ipu_tri)\n # Porous quads\n sdh_porous_quad = SubDofHandler(dh, getcellset(grid, \"porous4\"))\n add!(sdh_porous_quad, :u, ipu_quad)\n add!(sdh_porous_quad, :p, ipp_quad)\n # Porous triangles\n sdh_porous_tri = SubDofHandler(dh, getcellset(grid, \"porous3\"))\n add!(sdh_porous_tri, :u, ipu_tri)\n add!(sdh_porous_tri, :p, ipp_tri)\n\n close!(dh)\n\n # Setup the domains\n domains = [\n FEDomain(m_solid, cvu_quad, sdh_solid_quad),\n FEDomain(m_solid, cvu_tri, sdh_solid_tri),\n FEDomain(m_porous, (cvu_quad, cvp_quad), sdh_porous_quad),\n FEDomain(m_porous, (cvu_tri, cvp_tri), sdh_porous_tri),\n ]\n\n # Boundary conditions\n # Sliding for u, except top which is compressed\n # Sealed for p, except top with prescribed zero pressure\n addfacetset!(dh.grid, \"sides\", x -> x[1] < 1.0e-6 || x[1] ≈ 5.0)\n addfacetset!(dh.grid, \"top\", x -> x[2] ≈ 10.0)\n ch = ConstraintHandler(dh)\n add!(ch, Dirichlet(:u, getfacetset(grid, \"bottom\"), (x, t) -> zero(Vec{1}), [2]))\n add!(ch, Dirichlet(:u, getfacetset(grid, \"sides\"), (x, t) -> zero(Vec{1}), [1]))\n add!(ch, Dirichlet(:u, getfacetset(grid, \"top\"), (x, t) -> u_max * clamp(t / t_rise, 0, 1), [2]))\n add!(ch, Dirichlet(:p, getfacetset(grid, \"top_p\"), (x, t) -> 0.0))\n close!(ch)\n\n return dh, ch, domains\nend;\n\nfunction solve(dh, ch, domains; Δt = 0.025, t_total = 1.0)\n K = allocate_matrix(dh)\n r = zeros(ndofs(dh))\n a = zeros(ndofs(dh))\n a_old = copy(a)\n pvd = paraview_collection(\"porous_media\")\n step = 0\n for t in 0:Δt:t_total\n if t > 0\n update!(ch, t)\n apply!(a, ch)\n doassemble!(K, r, domains, a, a_old, Δt)\n apply_zero!(K, r, ch)\n Δa = -K \\ r\n apply_zero!(Δa, ch)\n a .+= Δa\n copyto!(a_old, a)\n end\n step += 1\n VTKGridFile(\"porous_media_$step\", dh) do vtk\n write_solution(vtk, dh, a)\n pvd[t] = vtk\n end\n end\n vtk_save(pvd)\n return\nend;\n\ndh, ch, domains = setup_problem()\nsolve(dh, ch, domains);","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"This page was generated using Literate.jl.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"EditURL = \"../literate-tutorials/computational_homogenization.jl\"","category":"page"},{"location":"tutorials/computational_homogenization/#tutorial-computational-homogenization","page":"Computational homogenization","title":"Computational homogenization","text":"","category":"section"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"(Image: )","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"Figure 1: von Mises stress in an RVE with 5 stiff inclusions embedded in a softer matrix material that is loaded in shear. The problem is solved by using homogeneous Dirichlet boundary conditions (left) and (strong) periodic boundary conditions (right).","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"tip: Tip\nThis example is also available as a Jupyter notebook: computational_homogenization.ipynb.","category":"page"},{"location":"tutorials/computational_homogenization/#Introduction","page":"Computational homogenization","title":"Introduction","text":"","category":"section"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"In this example we will solve the Representative Volume Element (RVE) problem for computational homogenization of linear elasticity and compute the effective/homogenized stiffness of an RVE with 5 stiff circular inclusions embedded in a softer matrix material (see Figure 1).","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"It is possible to obtain upper and lower bounds on the stiffness analytically, see for example Rule of mixtures. An upper bound is obtained from the Voigt model, where the strain is assumed to be the same in the two constituents,","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"mathsfE_mathrmVoigt = v_mathrmm mathsfE_mathrmm +\n(1 - v_mathrmm) mathsfE_mathrmi","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"where v_mathrmm is the volume fraction of the matrix material, and where mathsfE_mathrmm and mathsfE_mathrmi are the individual stiffness for the matrix material and the inclusions, respectively. The lower bound is obtained from the Reuss model, where the stress is assumed to be the same in the two constituents,","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"mathsfE_mathrmReuss = left(v_mathrmm mathsfE_mathrmm^-1 +\n(1 - v_mathrmm) mathsfE_mathrmi^-1 right)^-1","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"However, neither of these assumptions are, in general, very close to the \"truth\" which is why it is of interest to computationally find the homogenized properties for a given RVE.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"The canonical version of the RVE problem can be formulated as follows: For given homogenized field barboldsymbolu, barboldsymbolvarepsilon = boldsymbolvarepsilonbarboldsymbolu, find boldsymbolu in mathbbU_Box, boldsymbolt in mathbbT_Box such that","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"frac1Omega_Box int_Omega_Boxboldsymbolvarepsilondeltaboldsymbolu\n mathsfE boldsymbolvarepsilonboldsymbolu mathrmdOmega\n- frac1Omega_Box int_Gamma_Boxdelta boldsymbolu cdot\nboldsymbolt mathrmdGamma = 0 quad\nforall delta boldsymbolu in mathbbU_Boxquad (1mathrma)\n- frac1Omega_Box int_Gamma_Boxdelta boldsymbolt cdot\nboldsymbolu mathrmdGamma = - barboldsymbolvarepsilon \nleft frac1Omega_Box int_Gamma_Boxdelta boldsymbolt otimes\nboldsymbolx - barboldsymbolx mathrmdGamma right\nquad forall delta boldsymbolt in mathbbT_Box quad (1mathrmb)","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"where boldsymbolu = barboldsymbolvarepsilon cdot boldsymbolx - barboldsymbolx + boldsymbolu^mu, where Omega_Box and Omega_Box are the domain and volume of the RVE, where Gamma_Box is the boundary, and where mathbbU_Box, mathbbT_Box are set of \"sufficiently regular\" functions defined on the RVE.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"This system is not solvable without introducing extra restrictions on mathbbU_Box, mathbbT_Box. In this example we will consider the common cases of Dirichlet boundary conditions and (strong) periodic boundary conditions.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"Dirichlet boundary conditions","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"We can introduce the more restrictive sets of mathbbU_Box:","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"beginalign*\nmathbbU_Box^mathrmD = leftboldsymbolu in mathbbU_Box boldsymbolu\n= barboldsymbolvarepsilon cdot boldsymbolx - barboldsymbolx\n mathrmon Gamma_Boxright\nmathbbU_Box^mathrmD0 = leftboldsymbolu in mathbbU_Box boldsymbolu\n= boldsymbol0 mathrmon Gamma_Boxright\nendalign*","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"and use these as trial and test sets to obtain a solvable RVE problem pertaining to Dirichlet boundary conditions. Eq. (1mathrmb) is trivially fulfilled, the second term of Eq. (1mathrma) vanishes, and we are left with the following problem: Find boldsymbolu in mathbbU_Box^mathrmD that solve","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"frac1Omega_Box int_Omega_Boxboldsymbolvarepsilondeltaboldsymbolu\n mathsfE boldsymbolvarepsilonboldsymbolu mathrmdOmega = 0\nquad forall delta boldsymbolu in mathbbU_Box^mathrmD0","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"Note that, since boldsymbolu = barboldsymbolvarepsilon cdot boldsymbolx - barboldsymbolx + boldsymbolu^mu, this problem is equivalent to solving for boldsymbolu^mu in mathbbU_Box^mathrmD0, which is what we will do in the implementation.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"Periodic boundary conditions","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"The RVE problem pertaining to periodic boundary conditions is obtained by restricting boldsymbolu^mu to be periodic, and boldsymbolt anti-periodic across the RVE. Similarly as for Dirichlet boundary conditions, Eq. (1mathrmb) is directly fulfilled, and the second term in Eq. (1mathrma) vanishes, with these restrictions, and we are left with the following problem: Find boldsymbolu^mu in mathbbU_Box^mathrmP0 such that","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"frac1Omega_Box int_Omega_Boxboldsymbolvarepsilondeltaboldsymbolu\n mathsfE (barboldsymbolvarepsilon + boldsymbolvarepsilon\nboldsymbolu^mu) mathrmdOmega = 0\nquad forall delta boldsymbolu in mathbbU_Box^mathrmP0","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"where","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"mathbbU_Box^mathrmP0 = leftboldsymbolu in mathbbU_Box\n llbracket boldsymbolu rrbracket_Box = boldsymbol0\n mathrmon Gamma_Box^+right","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"where llbracket bullet rrbracket_Box = bullet(boldsymbolx^+) - bullet(boldsymbolx^-) defines the \"jump\" over the RVE, i.e. the difference between the value on the image part Gamma_Box^+ (coordinate boldsymbolx^+) and the mirror part Gamma_Box^- (coordinate boldsymbolx^-) of the boundary. To make sure this restriction holds in a strong sense we need a periodic mesh.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"Note that it would be possible to solve for the total boldsymbolu directly by instead enforcing the jump to be equal to the jump in the macroscopic part, boldsymbolu^mathrmM, i.e.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"llbracket boldsymbolu rrbracket_Box =\nllbracket boldsymbolu^mathrmM rrbracket_Box =\nllbracket barboldsymbolvarepsilon cdot boldsymbolx - barboldsymbolx\nrrbracket_Box =\nbarboldsymbolvarepsilon cdot boldsymbolx^+ - boldsymbolx^-","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"Homogenization of effective properties","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"In general it is necessary to compute the homogenized stress and the stiffness on the fly, but since we in this example consider linear elasticity it is possible to compute the effective properties once and for all for a given RVE configuration. We do this by computing sensitivity fields for every independent strain component (6 in 3D, 3 in 2D). Thus, for a 2D problem, as in the implementation below, we compute sensitivities hatboldsymbolu_11, hatboldsymbolu_22, and hatboldsymbolu_12 = hatboldsymbolu_21 by using","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"barboldsymbolvarepsilon = beginpmatrix1 0 0 0endpmatrix quad\nbarboldsymbolvarepsilon = beginpmatrix0 0 0 1endpmatrix quad\nbarboldsymbolvarepsilon = beginpmatrix0 05 05 0endpmatrix","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"as the input to the RVE problem. When the sensitivies are solved we can compute the entries of the homogenized stiffness as follows","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"mathsfE_ijkl = fracpartial barsigma_ijpartial barvarepsilon_kl\n= barsigma_ij(hatboldsymbolu_kl)","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"where the homogenized stress, barboldsymbolsigma(boldsymbolu), is computed as the volume average of the stress in the RVE, i.e.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"barboldsymbolsigma(boldsymbolu) =\nfrac1Omega_Box int_Omega_Box boldsymbolsigma mathrmdOmega =\nfrac1Omega_Box int_Omega_Box\nmathsfE boldsymbolvarepsilonboldsymbolu mathrmdOmega","category":"page"},{"location":"tutorials/computational_homogenization/#Commented-program","page":"Computational homogenization","title":"Commented program","text":"","category":"section"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"Now we will see how this can be implemented in Ferrite. What follows is a program with comments in between which describe the different steps. You can also find the same program without comments at the end of the page, see Plain program.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"using Ferrite, SparseArrays, LinearAlgebra","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"We first load the mesh file periodic-rve.msh (periodic-rve-coarse.msh for a coarser mesh). The mesh is generated with Gmsh, and we read it in as a Ferrite Grid using the FerriteGmsh.jl package:","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"using FerriteGmsh\n\ngrid = togrid(\"periodic-rve.msh\")","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"grid = redirect_stdout(devnull) do #hide\n togrid(\"periodic-rve-coarse.msh\") #hide\nend #hide","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"Next we construct the interpolation and quadrature rule, and combining them into cellvalues as usual:","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"dim = 2\nip = Lagrange{RefTriangle, 1}()^dim\nqr = QuadratureRule{RefTriangle}(2)\ncellvalues = CellValues(qr, ip);\nnothing #hide","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"We define a dof handler with a displacement field :u:","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"dh = DofHandler(grid)\nadd!(dh, :u, ip)\nclose!(dh);\nnothing #hide","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"Now we need to define boundary conditions. As discussed earlier we will solve the problem using (i) homogeneous Dirichlet boundary conditions, and (ii) periodic Dirichlet boundary conditions. We construct two different constraint handlers, one for each case. The Dirichlet boundary condition we have seen in many other examples. Here we simply define the condition that the field, :u, should have both components prescribed to 0 on the full boundary:","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"ch_dirichlet = ConstraintHandler(dh)\ndirichlet = Dirichlet(\n :u,\n union(getfacetset.(Ref(grid), [\"left\", \"right\", \"top\", \"bottom\"])...),\n (x, t) -> [0, 0],\n [1, 2]\n)\nadd!(ch_dirichlet, dirichlet)\nclose!(ch_dirichlet)\nupdate!(ch_dirichlet, 0.0)","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"For periodic boundary conditions we use the PeriodicDirichlet constraint type, which is very similar to the Dirichlet type, but instead of a passing a facetset we pass a vector with \"facet pairs\", i.e. the mapping between mirror and image parts of the boundary. In this example the \"left\" and \"bottom\" boundaries are mirrors, and the \"right\" and \"top\" boundaries are the mirrors.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"ch_periodic = ConstraintHandler(dh);\nperiodic = PeriodicDirichlet(\n :u,\n [\"left\" => \"right\", \"bottom\" => \"top\"],\n [1, 2]\n)\nadd!(ch_periodic, periodic)\nclose!(ch_periodic)\nupdate!(ch_periodic, 0.0)","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"This will now constrain any degrees of freedom located on the mirror boundaries to the matching degree of freedom on the image boundaries. Internally this will create a number of AffineConstraints of the form u_i = 1 * u_j + 0:","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"a = AffineConstraint(u_m, [u_i => 1], 0)","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"where u_m is the degree of freedom on the mirror and u_i the matching one on the image part. PeriodicDirichlet is thus simply just a more convenient way of constructing such affine constraints since it computes the degree of freedom mapping automatically.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"To simplify things we group the constraint handlers into a named tuple","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"ch = (dirichlet = ch_dirichlet, periodic = ch_periodic);\nnothing #hide","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"We can now construct the sparse matrix. Note that, since we are using affine constraints, which need to modify the matrix sparsity pattern in order to account for the constraint equations, we construct the matrix for the periodic case by passing both the dof handler and the constraint handler.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"K = (\n dirichlet = allocate_matrix(dh),\n periodic = allocate_matrix(dh, ch.periodic),\n);\nnothing #hide","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"We define the fourth order elasticity tensor for the matrix material, and define the inclusions to have 10 times higher stiffness","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"λ, μ = 1.0e10, 7.0e9 # Lamé parameters\nδ(i, j) = i == j ? 1.0 : 0.0\nEm = SymmetricTensor{4, 2}(\n (i, j, k, l) -> λ * δ(i, j) * δ(k, l) + μ * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k))\n)\nEi = 10 * Em;\nnothing #hide","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"As mentioned above, in order to compute the apparent/homogenized stiffness we will solve the problem repeatedly with different macroscale strain tensors to compute the sensitvity of the homogenized stress, barboldsymbolsigma, w.r.t. the macroscopic strain, barboldsymbolvarepsilon. The corresponding unit strains are defined below, and will result in three different right-hand-sides:","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"εᴹ = [\n SymmetricTensor{2, 2}([1.0 0.0; 0.0 0.0]), # ε_11 loading\n SymmetricTensor{2, 2}([0.0 0.0; 0.0 1.0]), # ε_22 loading\n SymmetricTensor{2, 2}([0.0 0.5; 0.5 0.0]), # ε_12/ε_21 loading\n];\nnothing #hide","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"The assembly function is nothing strange, and in particular there is no impact from the choice of boundary conditions, so the same function can be used for both cases. Since we want to solve the system 3 times, once for each macroscopic strain component, we assemble 3 right-hand-sides.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"function doassemble!(cellvalues::CellValues, K::SparseMatrixCSC, dh::DofHandler, εᴹ)\n\n n_basefuncs = getnbasefunctions(cellvalues)\n ndpc = ndofs_per_cell(dh)\n Ke = zeros(ndpc, ndpc)\n fe = zeros(ndpc, length(εᴹ))\n f = zeros(ndofs(dh), length(εᴹ))\n assembler = start_assemble(K)\n\n for cell in CellIterator(dh)\n\n E = cellid(cell) in getcellset(dh.grid, \"inclusions\") ? Ei : Em\n reinit!(cellvalues, cell)\n fill!(Ke, 0)\n fill!(fe, 0)\n\n for q_point in 1:getnquadpoints(cellvalues)\n dΩ = getdetJdV(cellvalues, q_point)\n for i in 1:n_basefuncs\n δεi = shape_symmetric_gradient(cellvalues, q_point, i)\n for j in 1:n_basefuncs\n δεj = shape_symmetric_gradient(cellvalues, q_point, j)\n Ke[i, j] += (δεi ⊡ E ⊡ δεj) * dΩ\n end\n for (rhs, ε) in enumerate(εᴹ)\n σᴹ = E ⊡ ε\n fe[i, rhs] += (- δεi ⊡ σᴹ) * dΩ\n end\n end\n end\n\n cdofs = celldofs(cell)\n assemble!(assembler, cdofs, Ke)\n f[cdofs, :] .+= fe\n end\n return f\nend;\nnothing #hide","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"We can now assemble the system. The assembly function modifies the matrix in-place, but return the right hand side(s) which we collect in another named tuple.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"rhs = (\n dirichlet = doassemble!(cellvalues, K.dirichlet, dh, εᴹ),\n periodic = doassemble!(cellvalues, K.periodic, dh, εᴹ),\n);\nnothing #hide","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"The next step is to solve the systems. Since application of boundary conditions, using the apply! function, modifies both the matrix and the right hand sides we can not use it directly in this case since we want to reuse the matrix again for the next right hand sides. We could of course re-assemble the matrix for every right hand side, but that would not be very efficient. Instead we will use the get_rhs_data function, together with apply_rhs! in a later step. This will extract the necessary data from the matrix such that we can apply it for all the different right hand sides. Note that we call apply! with just the matrix and no right hand side.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"rhsdata = (\n dirichlet = get_rhs_data(ch.dirichlet, K.dirichlet),\n periodic = get_rhs_data(ch.periodic, K.periodic),\n)\n\napply!(K.dirichlet, ch.dirichlet)\napply!(K.periodic, ch.periodic)","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"We can now solve the problem(s). Note that we only use apply_rhs! in the loops below. The boundary conditions are already applied to the matrix above, so we only need to modify the right hand side.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"u = (\n dirichlet = Vector{Float64}[],\n periodic = Vector{Float64}[],\n)\n\nfor i in 1:size(rhs.dirichlet, 2)\n rhs_i = @view rhs.dirichlet[:, i] # Extract this RHS\n apply_rhs!(rhsdata.dirichlet, rhs_i, ch.dirichlet) # Apply BC\n u_i = cholesky(Symmetric(K.dirichlet)) \\ rhs_i # Solve\n apply!(u_i, ch.dirichlet) # Apply BC on the solution\n push!(u.dirichlet, u_i) # Save the solution vector\nend\n\nfor i in 1:size(rhs.periodic, 2)\n rhs_i = @view rhs.periodic[:, i] # Extract this RHS\n apply_rhs!(rhsdata.periodic, rhs_i, ch.periodic) # Apply BC\n u_i = cholesky(Symmetric(K.periodic)) \\ rhs_i # Solve\n apply!(u_i, ch.periodic) # Apply BC on the solution\n push!(u.periodic, u_i) # Save the solution vector\nend","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"When the solution(s) are known we can compute the averaged stress, barboldsymbolsigma in the RVE. We define a function that does this, and also returns the von Mise stress in every quadrature point for visualization.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"function compute_stress(cellvalues::CellValues, dh::DofHandler, u, εᴹ)\n σvM_qpdata = zeros(getnquadpoints(cellvalues), getncells(dh.grid))\n σ̄Ω = zero(SymmetricTensor{2, 2})\n Ω = 0.0 # Total volume\n for cell in CellIterator(dh)\n E = cellid(cell) in getcellset(dh.grid, \"inclusions\") ? Ei : Em\n reinit!(cellvalues, cell)\n for q_point in 1:getnquadpoints(cellvalues)\n dΩ = getdetJdV(cellvalues, q_point)\n εμ = function_symmetric_gradient(cellvalues, q_point, u[celldofs(cell)])\n σ = E ⊡ (εᴹ + εμ)\n σvM_qpdata[q_point, cellid(cell)] = sqrt(3 / 2 * dev(σ) ⊡ dev(σ))\n Ω += dΩ # Update total volume\n σ̄Ω += σ * dΩ # Update integrated stress\n end\n end\n σ̄ = σ̄Ω / Ω\n return σvM_qpdata, σ̄\nend;\nnothing #hide","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"We now compute the homogenized stress and von Mise stress for all cases","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"σ̄ = (\n dirichlet = SymmetricTensor{2, 2}[],\n periodic = SymmetricTensor{2, 2}[],\n)\nσ = (\n dirichlet = Vector{Float64}[],\n periodic = Vector{Float64}[],\n)\n\nprojector = L2Projector(ip, grid)\n\nfor i in 1:3\n σ_qp, σ̄_i = compute_stress(cellvalues, dh, u.dirichlet[i], εᴹ[i])\n proj = project(projector, σ_qp, qr)\n push!(σ.dirichlet, proj)\n push!(σ̄.dirichlet, σ̄_i)\nend\n\nfor i in 1:3\n σ_qp, σ̄_i = compute_stress(cellvalues, dh, u.periodic[i], εᴹ[i])\n proj = project(projector, σ_qp, qr)\n push!(σ.periodic, proj)\n push!(σ̄.periodic, σ̄_i)\nend","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"The remaining thing is to compute the homogenized stiffness. As mentioned in the introduction we can find all the components from the average stress of the sensitivity fields that we have solved for","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"mathsfE_ijkl = barsigma_ij(hatboldsymbolu_kl)","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"So we have now already computed all the components, and just need to gather the data in a fourth order tensor:","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"E_dirichlet = SymmetricTensor{4, 2}() do i, j, k, l\n if k == l == 1\n σ̄.dirichlet[1][i, j] # ∂σ∂ε_**11\n elseif k == l == 2\n σ̄.dirichlet[2][i, j] # ∂σ∂ε_**22\n else\n σ̄.dirichlet[3][i, j] # ∂σ∂ε_**12 and ∂σ∂ε_**21\n end\nend\n\nE_periodic = SymmetricTensor{4, 2}() do i, j, k, l\n if k == l == 1\n σ̄.periodic[1][i, j]\n elseif k == l == 2\n σ̄.periodic[2][i, j]\n else\n σ̄.periodic[3][i, j]\n end\nend","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"We can check that the result are what we expect, namely that the stiffness with Dirichlet boundary conditions is higher than when using periodic boundary conditions, and that the Reuss assumption is an lower bound, and the Voigt assumption a upper bound. We first compute the volume fraction of the matrix, and then the Voigt and Reuss bounds:","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"function matrix_volume_fraction(grid, cellvalues)\n V = 0.0 # Total volume\n Vm = 0.0 # Volume of the matrix\n for c in CellIterator(grid)\n reinit!(cellvalues, c)\n is_matrix = !(cellid(c) in getcellset(grid, \"inclusions\"))\n for qp in 1:getnquadpoints(cellvalues)\n dΩ = getdetJdV(cellvalues, qp)\n V += dΩ\n if is_matrix\n Vm += dΩ\n end\n end\n end\n return Vm / V\nend\n\nvm = matrix_volume_fraction(grid, cellvalues)","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"E_voigt = vm * Em + (1 - vm) * Ei\nE_reuss = inv(vm * inv(Em) + (1 - vm) * inv(Ei));\nnothing #hide","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"We can now compare the different computed stiffness tensors. We expect E_mathrmReuss leq E_mathrmPeriodicBC leq E_mathrmDirichletBC leq E_mathrmVoigt. A simple thing to compare are the eigenvalues of the tensors. Here we look at the first eigenvalue:","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"ev = (first ∘ eigvals).((E_reuss, E_periodic, E_dirichlet, E_voigt))\nround.(ev; digits = -8)","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"Finally, we export the solution and the stress field to a VTK file. For the export we also compute the macroscopic part of the displacement.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"uM = zeros(ndofs(dh))\n\nVTKGridFile(\"homogenization\", dh) do vtk\n for i in 1:3\n # Compute macroscopic solution\n apply_analytical!(uM, dh, :u, x -> εᴹ[i] ⋅ x)\n # Dirichlet\n write_solution(vtk, dh, uM + u.dirichlet[i], \"_dirichlet_$i\")\n write_projection(vtk, projector, σ.dirichlet[i], \"σvM_dirichlet_$i\")\n # Periodic\n write_solution(vtk, dh, uM + u.periodic[i], \"_periodic_$i\")\n write_projection(vtk, projector, σ.periodic[i], \"σvM_periodic_$i\")\n end\nend;\nnothing #hide","category":"page"},{"location":"tutorials/computational_homogenization/#homogenization-plain-program","page":"Computational homogenization","title":"Plain program","text":"","category":"section"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"Here follows a version of the program without any comments. The file is also available here: computational_homogenization.jl.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"using Ferrite, SparseArrays, LinearAlgebra\n\nusing FerriteGmsh\n\n# grid = togrid(\"periodic-rve-coarse.msh\")\ngrid = togrid(\"periodic-rve.msh\")\n\ndim = 2\nip = Lagrange{RefTriangle, 1}()^dim\nqr = QuadratureRule{RefTriangle}(2)\ncellvalues = CellValues(qr, ip);\n\ndh = DofHandler(grid)\nadd!(dh, :u, ip)\nclose!(dh);\n\nch_dirichlet = ConstraintHandler(dh)\ndirichlet = Dirichlet(\n :u,\n union(getfacetset.(Ref(grid), [\"left\", \"right\", \"top\", \"bottom\"])...),\n (x, t) -> [0, 0],\n [1, 2]\n)\nadd!(ch_dirichlet, dirichlet)\nclose!(ch_dirichlet)\nupdate!(ch_dirichlet, 0.0)\n\nch_periodic = ConstraintHandler(dh);\nperiodic = PeriodicDirichlet(\n :u,\n [\"left\" => \"right\", \"bottom\" => \"top\"],\n [1, 2]\n)\nadd!(ch_periodic, periodic)\nclose!(ch_periodic)\nupdate!(ch_periodic, 0.0)\n\nch = (dirichlet = ch_dirichlet, periodic = ch_periodic);\n\nK = (\n dirichlet = allocate_matrix(dh),\n periodic = allocate_matrix(dh, ch.periodic),\n);\n\nλ, μ = 1.0e10, 7.0e9 # Lamé parameters\nδ(i, j) = i == j ? 1.0 : 0.0\nEm = SymmetricTensor{4, 2}(\n (i, j, k, l) -> λ * δ(i, j) * δ(k, l) + μ * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k))\n)\nEi = 10 * Em;\n\nεᴹ = [\n SymmetricTensor{2, 2}([1.0 0.0; 0.0 0.0]), # ε_11 loading\n SymmetricTensor{2, 2}([0.0 0.0; 0.0 1.0]), # ε_22 loading\n SymmetricTensor{2, 2}([0.0 0.5; 0.5 0.0]), # ε_12/ε_21 loading\n];\n\nfunction doassemble!(cellvalues::CellValues, K::SparseMatrixCSC, dh::DofHandler, εᴹ)\n\n n_basefuncs = getnbasefunctions(cellvalues)\n ndpc = ndofs_per_cell(dh)\n Ke = zeros(ndpc, ndpc)\n fe = zeros(ndpc, length(εᴹ))\n f = zeros(ndofs(dh), length(εᴹ))\n assembler = start_assemble(K)\n\n for cell in CellIterator(dh)\n\n E = cellid(cell) in getcellset(dh.grid, \"inclusions\") ? Ei : Em\n reinit!(cellvalues, cell)\n fill!(Ke, 0)\n fill!(fe, 0)\n\n for q_point in 1:getnquadpoints(cellvalues)\n dΩ = getdetJdV(cellvalues, q_point)\n for i in 1:n_basefuncs\n δεi = shape_symmetric_gradient(cellvalues, q_point, i)\n for j in 1:n_basefuncs\n δεj = shape_symmetric_gradient(cellvalues, q_point, j)\n Ke[i, j] += (δεi ⊡ E ⊡ δεj) * dΩ\n end\n for (rhs, ε) in enumerate(εᴹ)\n σᴹ = E ⊡ ε\n fe[i, rhs] += (- δεi ⊡ σᴹ) * dΩ\n end\n end\n end\n\n cdofs = celldofs(cell)\n assemble!(assembler, cdofs, Ke)\n f[cdofs, :] .+= fe\n end\n return f\nend;\n\nrhs = (\n dirichlet = doassemble!(cellvalues, K.dirichlet, dh, εᴹ),\n periodic = doassemble!(cellvalues, K.periodic, dh, εᴹ),\n);\n\nrhsdata = (\n dirichlet = get_rhs_data(ch.dirichlet, K.dirichlet),\n periodic = get_rhs_data(ch.periodic, K.periodic),\n)\n\napply!(K.dirichlet, ch.dirichlet)\napply!(K.periodic, ch.periodic)\n\nu = (\n dirichlet = Vector{Float64}[],\n periodic = Vector{Float64}[],\n)\n\nfor i in 1:size(rhs.dirichlet, 2)\n rhs_i = @view rhs.dirichlet[:, i] # Extract this RHS\n apply_rhs!(rhsdata.dirichlet, rhs_i, ch.dirichlet) # Apply BC\n u_i = cholesky(Symmetric(K.dirichlet)) \\ rhs_i # Solve\n apply!(u_i, ch.dirichlet) # Apply BC on the solution\n push!(u.dirichlet, u_i) # Save the solution vector\nend\n\nfor i in 1:size(rhs.periodic, 2)\n rhs_i = @view rhs.periodic[:, i] # Extract this RHS\n apply_rhs!(rhsdata.periodic, rhs_i, ch.periodic) # Apply BC\n u_i = cholesky(Symmetric(K.periodic)) \\ rhs_i # Solve\n apply!(u_i, ch.periodic) # Apply BC on the solution\n push!(u.periodic, u_i) # Save the solution vector\nend\n\nfunction compute_stress(cellvalues::CellValues, dh::DofHandler, u, εᴹ)\n σvM_qpdata = zeros(getnquadpoints(cellvalues), getncells(dh.grid))\n σ̄Ω = zero(SymmetricTensor{2, 2})\n Ω = 0.0 # Total volume\n for cell in CellIterator(dh)\n E = cellid(cell) in getcellset(dh.grid, \"inclusions\") ? Ei : Em\n reinit!(cellvalues, cell)\n for q_point in 1:getnquadpoints(cellvalues)\n dΩ = getdetJdV(cellvalues, q_point)\n εμ = function_symmetric_gradient(cellvalues, q_point, u[celldofs(cell)])\n σ = E ⊡ (εᴹ + εμ)\n σvM_qpdata[q_point, cellid(cell)] = sqrt(3 / 2 * dev(σ) ⊡ dev(σ))\n Ω += dΩ # Update total volume\n σ̄Ω += σ * dΩ # Update integrated stress\n end\n end\n σ̄ = σ̄Ω / Ω\n return σvM_qpdata, σ̄\nend;\n\nσ̄ = (\n dirichlet = SymmetricTensor{2, 2}[],\n periodic = SymmetricTensor{2, 2}[],\n)\nσ = (\n dirichlet = Vector{Float64}[],\n periodic = Vector{Float64}[],\n)\n\nprojector = L2Projector(ip, grid)\n\nfor i in 1:3\n σ_qp, σ̄_i = compute_stress(cellvalues, dh, u.dirichlet[i], εᴹ[i])\n proj = project(projector, σ_qp, qr)\n push!(σ.dirichlet, proj)\n push!(σ̄.dirichlet, σ̄_i)\nend\n\nfor i in 1:3\n σ_qp, σ̄_i = compute_stress(cellvalues, dh, u.periodic[i], εᴹ[i])\n proj = project(projector, σ_qp, qr)\n push!(σ.periodic, proj)\n push!(σ̄.periodic, σ̄_i)\nend\n\nE_dirichlet = SymmetricTensor{4, 2}() do i, j, k, l\n if k == l == 1\n σ̄.dirichlet[1][i, j] # ∂σ∂ε_**11\n elseif k == l == 2\n σ̄.dirichlet[2][i, j] # ∂σ∂ε_**22\n else\n σ̄.dirichlet[3][i, j] # ∂σ∂ε_**12 and ∂σ∂ε_**21\n end\nend\n\nE_periodic = SymmetricTensor{4, 2}() do i, j, k, l\n if k == l == 1\n σ̄.periodic[1][i, j]\n elseif k == l == 2\n σ̄.periodic[2][i, j]\n else\n σ̄.periodic[3][i, j]\n end\nend\n\nfunction matrix_volume_fraction(grid, cellvalues)\n V = 0.0 # Total volume\n Vm = 0.0 # Volume of the matrix\n for c in CellIterator(grid)\n reinit!(cellvalues, c)\n is_matrix = !(cellid(c) in getcellset(grid, \"inclusions\"))\n for qp in 1:getnquadpoints(cellvalues)\n dΩ = getdetJdV(cellvalues, qp)\n V += dΩ\n if is_matrix\n Vm += dΩ\n end\n end\n end\n return Vm / V\nend\n\nvm = matrix_volume_fraction(grid, cellvalues)\n\nE_voigt = vm * Em + (1 - vm) * Ei\nE_reuss = inv(vm * inv(Em) + (1 - vm) * inv(Ei));\n\nev = (first ∘ eigvals).((E_reuss, E_periodic, E_dirichlet, E_voigt))\nround.(ev; digits = -8)\n\nuM = zeros(ndofs(dh))\n\nVTKGridFile(\"homogenization\", dh) do vtk\n for i in 1:3\n # Compute macroscopic solution\n apply_analytical!(uM, dh, :u, x -> εᴹ[i] ⋅ x)\n # Dirichlet\n write_solution(vtk, dh, uM + u.dirichlet[i], \"_dirichlet_$i\")\n write_projection(vtk, projector, σ.dirichlet[i], \"σvM_dirichlet_$i\")\n # Periodic\n write_solution(vtk, dh, uM + u.periodic[i], \"_periodic_$i\")\n write_projection(vtk, projector, σ.periodic[i], \"σvM_periodic_$i\")\n end\nend;","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"This page was generated using Literate.jl.","category":"page"},{"location":"topics/reference_shapes/#Reference-shapes","page":"Reference shapes","title":"Reference shapes","text":"","category":"section"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"The reference shapes in Ferrite are used to define grid cells, function interpolations (i.e. shape functions), and quadrature rules. Currently, the following reference shapes are defined","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"RefLine\nRefTriangle\nRefQuadrilateral\nRefTetrahedron\nRefHexahedron\nRefPrism\nRefPyramid","category":"page"},{"location":"topics/reference_shapes/#Entity-naming","page":"Reference shapes","title":"Entity naming","text":"","category":"section"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"Ferrite denotes the entities of a reference shape as follows","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"Entity Description\nVertex 0-dimensional entity in the reference shape.\nEdge 1-dimensional entity connecting two vertices.\nFace 2-dimensional entity enclosed by edges.\nVolume 3-dimensional entity enclosed by faces.","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"Note that a node in Ferrite is not the same as a vertex. Vertices denote endpoints of edges, while nodes may also be located in the middle of edges (e.g. for a QuadraticLine cell).","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"To write dimensionally independent code, Ferrite also denotes entities by their codimension, defined relative the reference shape dimension. Specifically, Ferrite has the entities","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"Entity Description\nCell 0-codimensional entity, i.e. the same as the reference shape.\nFacet 1-codimensional entity defining the boundary of cells.","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"Standard use cases mostly deal with these codimensional entities, such as CellValues and FacetValues.","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"note: Definition of codimension\nIn Ferrite, codimension is defined relative to the reference dimension of the specific entity. Note that other finite element codes may define it differently (e.g. relative the highest reference dimension in the grid).","category":"page"},{"location":"topics/reference_shapes/#Entity-numbering","page":"Reference shapes","title":"Entity numbering","text":"","category":"section"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"Each reference shape defines the numbering of its vertices, edges, and faces entities, where the edge and face entities are defined from their vertex numbers.","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"note: Note\nThe numbering and identification of entities is (mostly) for internal use and typically not something users of Ferrite need to interact with.","category":"page"},{"location":"topics/reference_shapes/#Example","page":"Reference shapes","title":"Example","text":"","category":"section"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"The RefQuadrilateral is defined on the domain -1 1 times -1 1 in the local xi_1-xi_2 coordinate system.","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"(Image: local element)","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"The vertices of a RefQuadrilateral are then","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"using Ferrite #hide\nFerrite.reference_vertices(RefQuadrilateral)","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"and its edges are then defined as","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"using Ferrite #hide\nFerrite.reference_edges(RefQuadrilateral)","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"where the numbers refer to the vertex number. Finally, this reference shape is 2-dimensional, so it only has a single face, corresponding to the cell itself,","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"using Ferrite #hide\nFerrite.reference_faces(RefQuadrilateral)","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"also defined in terms of its vertices.","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"As this is a 2-dimensional reference shape, the facets are the edges, i.e.","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"using Ferrite #hide\nFerrite.reference_facets(RefQuadrilateral)","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"note: Not public API\nThe functions reference_vertices, reference_edges, reference_faces, and reference_facets are not public and only shown here to explain the numbering concept. The specific ordering may also change, and is therefore only documented in the Developer documentation.","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"EditURL = \"../literate-tutorials/plasticity.jl\"","category":"page"},{"location":"tutorials/plasticity/#tutorial-plasticity","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"","category":"section"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"(Image: Shows the von Mises stress distribution in a cantilever beam.)","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"Figure 1. A coarse mesh solution of a cantilever beam subjected to a load causing plastic deformations. The initial yield limit is 200 MPa but due to hardening it increases up to approximately 240 MPa.","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"tip: Tip\nThis example is also available as a Jupyter notebook: plasticity.ipynb.","category":"page"},{"location":"tutorials/plasticity/#Introduction","page":"Von Mises plasticity","title":"Introduction","text":"","category":"section"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"This example illustrates the use of a nonlinear material model in Ferrite. The particular model is von Mises plasticity (also know as J₂-plasticity) with isotropic hardening. The model is fully 3D, meaning that no assumptions like plane stress or plane strain are introduced.","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"Also note that the theory of the model is not described here, instead one is referred to standard textbooks on material modeling.","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"To illustrate the use of the plasticity model, we setup and solve a FE-problem consisting of a cantilever beam loaded at its free end. But first, we shortly describe the parts of the implementation dealing with the material modeling.","category":"page"},{"location":"tutorials/plasticity/#Material-modeling","page":"Von Mises plasticity","title":"Material modeling","text":"","category":"section"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"This section describes the structs and methods used to implement the material model","category":"page"},{"location":"tutorials/plasticity/#Material-parameters-and-state-variables","page":"Von Mises plasticity","title":"Material parameters and state variables","text":"","category":"section"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"Start by loading some necessary packages","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"using Ferrite, Tensors, SparseArrays, LinearAlgebra, Printf","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"We define a J₂-plasticity-material, containing material parameters and the elastic stiffness Dᵉ (since it is constant)","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"struct J2Plasticity{T, S <: SymmetricTensor{4, 3, T}}\n G::T # Shear modulus\n K::T # Bulk modulus\n σ₀::T # Initial yield limit\n H::T # Hardening modulus\n Dᵉ::S # Elastic stiffness tensor\nend;\nnothing #hide","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"Next, we define a constructor for the material instance.","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"function J2Plasticity(E, ν, σ₀, H)\n δ(i, j) = i == j ? 1.0 : 0.0 # helper function\n G = E / 2(1 + ν)\n K = E / 3(1 - 2ν)\n\n Isymdev(i, j, k, l) = 0.5 * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k)) - 1.0 / 3.0 * δ(i, j) * δ(k, l)\n temp(i, j, k, l) = 2.0G * (0.5 * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k)) + ν / (1.0 - 2.0ν) * δ(i, j) * δ(k, l))\n Dᵉ = SymmetricTensor{4, 3}(temp)\n return J2Plasticity(G, K, σ₀, H, Dᵉ)\nend;\nnothing #hide","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"note: Note\nAbove, we defined a constructor J2Plasticity(E, ν, σ₀, H) in terms of the more common material parameters E and ν - simply as a convenience for the user.","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"Define a struct to store the material state for a Gauss point.","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"struct MaterialState{T, S <: SecondOrderTensor{3, T}}\n # Store \"converged\" values\n ϵᵖ::S # plastic strain\n σ::S # stress\n k::T # hardening variable\nend","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"Constructor for initializing a material state. Every quantity is set to zero.","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"function MaterialState()\n return MaterialState(\n zero(SymmetricTensor{2, 3}),\n zero(SymmetricTensor{2, 3}),\n 0.0\n )\nend","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"For later use, during the post-processing step, we define a function to compute the von Mises effective stress.","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"function vonMises(σ)\n s = dev(σ)\n return sqrt(3.0 / 2.0 * s ⊡ s)\nend;\nnothing #hide","category":"page"},{"location":"tutorials/plasticity/#Constitutive-driver","page":"Von Mises plasticity","title":"Constitutive driver","text":"","category":"section"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"This is the actual method which computes the stress and material tangent stiffness in a given integration point. Input is the current strain and the material state from the previous timestep.","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"function compute_stress_tangent(ϵ::SymmetricTensor{2, 3}, material::J2Plasticity, state::MaterialState)\n # unpack some material parameters\n G = material.G\n H = material.H\n\n # We use (•)ᵗ to denote *trial*-values\n σᵗ = material.Dᵉ ⊡ (ϵ - state.ϵᵖ) # trial-stress\n sᵗ = dev(σᵗ) # deviatoric part of trial-stress\n J₂ = 0.5 * sᵗ ⊡ sᵗ # second invariant of sᵗ\n σᵗₑ = sqrt(3.0 * J₂) # effective trial-stress (von Mises stress)\n σʸ = material.σ₀ + H * state.k # Previous yield limit\n\n φᵗ = σᵗₑ - σʸ # Trial-value of the yield surface\n\n if φᵗ < 0.0 # elastic loading\n return σᵗ, material.Dᵉ, MaterialState(state.ϵᵖ, σᵗ, state.k)\n else # plastic loading\n h = H + 3G\n μ = φᵗ / h # plastic multiplier\n\n c1 = 1 - 3G * μ / σᵗₑ\n s = c1 * sᵗ # updated deviatoric stress\n σ = s + vol(σᵗ) # updated stress\n\n # Compute algorithmic tangent stiffness ``D = \\frac{\\Delta \\sigma }{\\Delta \\epsilon}``\n κ = H * (state.k + μ) # drag stress\n σₑ = material.σ₀ + κ # updated yield surface\n\n δ(i, j) = i == j ? 1.0 : 0.0\n Isymdev(i, j, k, l) = 0.5 * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k)) - 1.0 / 3.0 * δ(i, j) * δ(k, l)\n Q(i, j, k, l) = Isymdev(i, j, k, l) - 3.0 / (2.0 * σₑ^2) * s[i, j] * s[k, l]\n b = (3G * μ / σₑ) / (1.0 + 3G * μ / σₑ)\n\n Dtemp(i, j, k, l) = -2G * b * Q(i, j, k, l) - 9G^2 / (h * σₑ^2) * s[i, j] * s[k, l]\n D = material.Dᵉ + SymmetricTensor{4, 3}(Dtemp)\n\n # Return new state\n Δϵᵖ = 3 / 2 * μ / σₑ * s # plastic strain\n ϵᵖ = state.ϵᵖ + Δϵᵖ # plastic strain\n k = state.k + μ # hardening variable\n return σ, D, MaterialState(ϵᵖ, σ, k)\n end\nend","category":"page"},{"location":"tutorials/plasticity/#FE-problem","page":"Von Mises plasticity","title":"FE-problem","text":"","category":"section"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"What follows are methods for assembling and and solving the FE-problem.","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"function create_values(interpolation)\n # setup quadrature rules\n qr = QuadratureRule{RefTetrahedron}(2)\n facet_qr = FacetQuadratureRule{RefTetrahedron}(3)\n\n # cell and facetvalues for u\n cellvalues_u = CellValues(qr, interpolation)\n facetvalues_u = FacetValues(facet_qr, interpolation)\n\n return cellvalues_u, facetvalues_u\nend;\nnothing #hide","category":"page"},{"location":"tutorials/plasticity/#Add-degrees-of-freedom","page":"Von Mises plasticity","title":"Add degrees of freedom","text":"","category":"section"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"function create_dofhandler(grid, interpolation)\n dh = DofHandler(grid)\n add!(dh, :u, interpolation) # add a displacement field with 3 components\n close!(dh)\n return dh\nend","category":"page"},{"location":"tutorials/plasticity/#Boundary-conditions","page":"Von Mises plasticity","title":"Boundary conditions","text":"","category":"section"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"function create_bc(dh, grid)\n dbcs = ConstraintHandler(dh)\n # Clamped on the left side\n dofs = [1, 2, 3]\n dbc = Dirichlet(:u, getfacetset(grid, \"left\"), (x, t) -> [0.0, 0.0, 0.0], dofs)\n add!(dbcs, dbc)\n close!(dbcs)\n return dbcs\nend;\nnothing #hide","category":"page"},{"location":"tutorials/plasticity/#Assembling-of-element-contributions","page":"Von Mises plasticity","title":"Assembling of element contributions","text":"","category":"section"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"Residual vector r\nTangent stiffness K","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"function doassemble!(\n K::SparseMatrixCSC, r::Vector, cellvalues::CellValues, dh::DofHandler,\n material::J2Plasticity, u, states, states_old\n )\n assembler = start_assemble(K, r)\n nu = getnbasefunctions(cellvalues)\n re = zeros(nu) # element residual vector\n ke = zeros(nu, nu) # element tangent matrix\n\n for (i, cell) in enumerate(CellIterator(dh))\n fill!(ke, 0)\n fill!(re, 0)\n eldofs = celldofs(cell)\n ue = u[eldofs]\n state = @view states[:, i]\n state_old = @view states_old[:, i]\n assemble_cell!(ke, re, cell, cellvalues, material, ue, state, state_old)\n assemble!(assembler, eldofs, ke, re)\n end\n return K, r\nend","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"Compute element contribution to the residual and the tangent.","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"note: Note\nDue to symmetry, we only compute the lower half of the tangent and then symmetrize it.","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"function assemble_cell!(Ke, re, cell, cellvalues, material, ue, state, state_old)\n n_basefuncs = getnbasefunctions(cellvalues)\n reinit!(cellvalues, cell)\n\n for q_point in 1:getnquadpoints(cellvalues)\n # For each integration point, compute stress and material stiffness\n ϵ = function_symmetric_gradient(cellvalues, q_point, ue) # Total strain\n σ, D, state[q_point] = compute_stress_tangent(ϵ, material, state_old[q_point])\n\n dΩ = getdetJdV(cellvalues, q_point)\n for i in 1:n_basefuncs\n δϵ = shape_symmetric_gradient(cellvalues, q_point, i)\n re[i] += (δϵ ⊡ σ) * dΩ # add internal force to residual\n for j in 1:i # loop only over lower half\n Δϵ = shape_symmetric_gradient(cellvalues, q_point, j)\n Ke[i, j] += δϵ ⊡ D ⊡ Δϵ * dΩ\n end\n end\n end\n symmetrize_lower!(Ke)\n return\nend","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"Helper function to symmetrize the material tangent","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"function symmetrize_lower!(K)\n for i in 1:size(K, 1)\n for j in (i + 1):size(K, 1)\n K[i, j] = K[j, i]\n end\n end\n return\nend;\n\nfunction doassemble_neumann!(r, dh, facetset, facetvalues, t)\n n_basefuncs = getnbasefunctions(facetvalues)\n re = zeros(n_basefuncs) # element residual vector\n for fc in FacetIterator(dh, facetset)\n # Add traction as a negative contribution to the element residual `re`:\n reinit!(facetvalues, fc)\n fill!(re, 0)\n for q_point in 1:getnquadpoints(facetvalues)\n dΓ = getdetJdV(facetvalues, q_point)\n for i in 1:n_basefuncs\n δu = shape_value(facetvalues, q_point, i)\n re[i] -= (δu ⋅ t) * dΓ\n end\n end\n assemble!(r, celldofs(fc), re)\n end\n return r\nend","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"Define a function which solves the FE-problem.","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"function solve()\n # Define material parameters\n E = 200.0e9 # [Pa]\n H = E / 20 # [Pa]\n ν = 0.3 # [-]\n σ₀ = 200.0e6 # [Pa]\n material = J2Plasticity(E, ν, σ₀, H)\n\n L = 10.0 # beam length [m]\n w = 1.0 # beam width [m]\n h = 1.0 # beam height[m]\n n_timesteps = 10\n u_max = zeros(n_timesteps)\n traction_magnitude = 1.0e7 * range(0.5, 1.0, length = n_timesteps)\n\n # Create geometry, dofs and boundary conditions\n n = 2\n nels = (10n, n, 2n) # number of elements in each spatial direction\n P1 = Vec((0.0, 0.0, 0.0)) # start point for geometry\n P2 = Vec((L, w, h)) # end point for geometry\n grid = generate_grid(Tetrahedron, nels, P1, P2)\n interpolation = Lagrange{RefTetrahedron, 1}()^3\n\n dh = create_dofhandler(grid, interpolation) # JuaFEM helper function\n dbcs = create_bc(dh, grid) # create Dirichlet boundary-conditions\n\n cellvalues, facetvalues = create_values(interpolation)\n\n # Pre-allocate solution vectors, etc.\n n_dofs = ndofs(dh) # total number of dofs\n u = zeros(n_dofs) # solution vector\n Δu = zeros(n_dofs) # displacement correction\n r = zeros(n_dofs) # residual\n K = allocate_matrix(dh) # tangent stiffness matrix\n\n # Create material states. One array for each cell, where each element is an array of material-\n # states - one for each integration point\n nqp = getnquadpoints(cellvalues)\n states = [MaterialState() for _ in 1:nqp, _ in 1:getncells(grid)]\n states_old = [MaterialState() for _ in 1:nqp, _ in 1:getncells(grid)]\n\n # Newton-Raphson loop\n NEWTON_TOL = 1 # 1 N\n print(\"\\n Starting Netwon iterations:\\n\")\n\n for timestep in 1:n_timesteps\n t = timestep # actual time (used for evaluating d-bndc)\n traction = Vec((0.0, 0.0, traction_magnitude[timestep]))\n newton_itr = -1\n print(\"\\n Time step @time = $timestep:\\n\")\n update!(dbcs, t) # evaluates the D-bndc at time t\n apply!(u, dbcs) # set the prescribed values in the solution vector\n\n while true\n newton_itr += 1\n if newton_itr > 8\n error(\"Reached maximum Newton iterations, aborting\")\n break\n end\n # Tangent and residual contribution from the cells (volume integral)\n doassemble!(K, r, cellvalues, dh, material, u, states, states_old)\n # Residual contribution from the Neumann boundary (surface integral)\n doassemble_neumann!(r, dh, getfacetset(grid, \"right\"), facetvalues, traction)\n norm_r = norm(r[Ferrite.free_dofs(dbcs)])\n\n print(\"Iteration: $newton_itr \\tresidual: $(@sprintf(\"%.8f\", norm_r))\\n\")\n if norm_r < NEWTON_TOL\n break\n end\n\n apply_zero!(K, r, dbcs)\n Δu = Symmetric(K) \\ r\n u -= Δu\n end\n\n # Update the old states with the converged values for next timestep\n states_old .= states\n\n u_max[timestep] = maximum(abs, u) # maximum displacement in current timestep\n end\n\n # ## Postprocessing\n # Only a vtu-file corresponding to the last time-step is exported.\n #\n # The following is a quick (and dirty) way of extracting average cell data for export.\n mises_values = zeros(getncells(grid))\n κ_values = zeros(getncells(grid))\n for (el, cell_states) in enumerate(eachcol(states))\n for state in cell_states\n mises_values[el] += vonMises(state.σ)\n κ_values[el] += state.k * material.H\n end\n mises_values[el] /= length(cell_states) # average von Mises stress\n κ_values[el] /= length(cell_states) # average drag stress\n end\n VTKGridFile(\"plasticity\", dh) do vtk\n write_solution(vtk, dh, u) # displacement field\n write_cell_data(vtk, mises_values, \"von Mises [Pa]\")\n write_cell_data(vtk, κ_values, \"Drag stress [Pa]\")\n end\n\n return u_max, traction_magnitude\nend","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"Solve the FE-problem and for each time-step extract maximum displacement and the corresponding traction load. Also compute the limit-traction-load","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"u_max, traction_magnitude = solve();\nnothing #hide","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"Finally we plot the load-displacement curve.","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"using Plots\nplot(\n vcat(0.0, u_max), # add the origin as a point\n vcat(0.0, traction_magnitude),\n linewidth = 2,\n title = \"Traction-displacement\",\n label = nothing,\n markershape = :auto\n)\nylabel!(\"Traction [Pa]\")\nxlabel!(\"Maximum deflection [m]\")","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"Figure 2. Load-displacement-curve for the beam, showing a clear decrease in stiffness as more material starts to yield.","category":"page"},{"location":"tutorials/plasticity/#plasticity-plain-program","page":"Von Mises plasticity","title":"Plain program","text":"","category":"section"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"Here follows a version of the program without any comments. The file is also available here: plasticity.jl.","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"using Ferrite, Tensors, SparseArrays, LinearAlgebra, Printf\n\nstruct J2Plasticity{T, S <: SymmetricTensor{4, 3, T}}\n G::T # Shear modulus\n K::T # Bulk modulus\n σ₀::T # Initial yield limit\n H::T # Hardening modulus\n Dᵉ::S # Elastic stiffness tensor\nend;\n\nfunction J2Plasticity(E, ν, σ₀, H)\n δ(i, j) = i == j ? 1.0 : 0.0 # helper function\n G = E / 2(1 + ν)\n K = E / 3(1 - 2ν)\n\n Isymdev(i, j, k, l) = 0.5 * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k)) - 1.0 / 3.0 * δ(i, j) * δ(k, l)\n temp(i, j, k, l) = 2.0G * (0.5 * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k)) + ν / (1.0 - 2.0ν) * δ(i, j) * δ(k, l))\n Dᵉ = SymmetricTensor{4, 3}(temp)\n return J2Plasticity(G, K, σ₀, H, Dᵉ)\nend;\n\nstruct MaterialState{T, S <: SecondOrderTensor{3, T}}\n # Store \"converged\" values\n ϵᵖ::S # plastic strain\n σ::S # stress\n k::T # hardening variable\nend\n\nfunction MaterialState()\n return MaterialState(\n zero(SymmetricTensor{2, 3}),\n zero(SymmetricTensor{2, 3}),\n 0.0\n )\nend\n\nfunction vonMises(σ)\n s = dev(σ)\n return sqrt(3.0 / 2.0 * s ⊡ s)\nend;\n\nfunction compute_stress_tangent(ϵ::SymmetricTensor{2, 3}, material::J2Plasticity, state::MaterialState)\n # unpack some material parameters\n G = material.G\n H = material.H\n\n # We use (•)ᵗ to denote *trial*-values\n σᵗ = material.Dᵉ ⊡ (ϵ - state.ϵᵖ) # trial-stress\n sᵗ = dev(σᵗ) # deviatoric part of trial-stress\n J₂ = 0.5 * sᵗ ⊡ sᵗ # second invariant of sᵗ\n σᵗₑ = sqrt(3.0 * J₂) # effective trial-stress (von Mises stress)\n σʸ = material.σ₀ + H * state.k # Previous yield limit\n\n φᵗ = σᵗₑ - σʸ # Trial-value of the yield surface\n\n if φᵗ < 0.0 # elastic loading\n return σᵗ, material.Dᵉ, MaterialState(state.ϵᵖ, σᵗ, state.k)\n else # plastic loading\n h = H + 3G\n μ = φᵗ / h # plastic multiplier\n\n c1 = 1 - 3G * μ / σᵗₑ\n s = c1 * sᵗ # updated deviatoric stress\n σ = s + vol(σᵗ) # updated stress\n\n # Compute algorithmic tangent stiffness ``D = \\frac{\\Delta \\sigma }{\\Delta \\epsilon}``\n κ = H * (state.k + μ) # drag stress\n σₑ = material.σ₀ + κ # updated yield surface\n\n δ(i, j) = i == j ? 1.0 : 0.0\n Isymdev(i, j, k, l) = 0.5 * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k)) - 1.0 / 3.0 * δ(i, j) * δ(k, l)\n Q(i, j, k, l) = Isymdev(i, j, k, l) - 3.0 / (2.0 * σₑ^2) * s[i, j] * s[k, l]\n b = (3G * μ / σₑ) / (1.0 + 3G * μ / σₑ)\n\n Dtemp(i, j, k, l) = -2G * b * Q(i, j, k, l) - 9G^2 / (h * σₑ^2) * s[i, j] * s[k, l]\n D = material.Dᵉ + SymmetricTensor{4, 3}(Dtemp)\n\n # Return new state\n Δϵᵖ = 3 / 2 * μ / σₑ * s # plastic strain\n ϵᵖ = state.ϵᵖ + Δϵᵖ # plastic strain\n k = state.k + μ # hardening variable\n return σ, D, MaterialState(ϵᵖ, σ, k)\n end\nend\n\nfunction create_values(interpolation)\n # setup quadrature rules\n qr = QuadratureRule{RefTetrahedron}(2)\n facet_qr = FacetQuadratureRule{RefTetrahedron}(3)\n\n # cell and facetvalues for u\n cellvalues_u = CellValues(qr, interpolation)\n facetvalues_u = FacetValues(facet_qr, interpolation)\n\n return cellvalues_u, facetvalues_u\nend;\n\nfunction create_dofhandler(grid, interpolation)\n dh = DofHandler(grid)\n add!(dh, :u, interpolation) # add a displacement field with 3 components\n close!(dh)\n return dh\nend\n\nfunction create_bc(dh, grid)\n dbcs = ConstraintHandler(dh)\n # Clamped on the left side\n dofs = [1, 2, 3]\n dbc = Dirichlet(:u, getfacetset(grid, \"left\"), (x, t) -> [0.0, 0.0, 0.0], dofs)\n add!(dbcs, dbc)\n close!(dbcs)\n return dbcs\nend;\n\nfunction doassemble!(\n K::SparseMatrixCSC, r::Vector, cellvalues::CellValues, dh::DofHandler,\n material::J2Plasticity, u, states, states_old\n )\n assembler = start_assemble(K, r)\n nu = getnbasefunctions(cellvalues)\n re = zeros(nu) # element residual vector\n ke = zeros(nu, nu) # element tangent matrix\n\n for (i, cell) in enumerate(CellIterator(dh))\n fill!(ke, 0)\n fill!(re, 0)\n eldofs = celldofs(cell)\n ue = u[eldofs]\n state = @view states[:, i]\n state_old = @view states_old[:, i]\n assemble_cell!(ke, re, cell, cellvalues, material, ue, state, state_old)\n assemble!(assembler, eldofs, ke, re)\n end\n return K, r\nend\n\nfunction assemble_cell!(Ke, re, cell, cellvalues, material, ue, state, state_old)\n n_basefuncs = getnbasefunctions(cellvalues)\n reinit!(cellvalues, cell)\n\n for q_point in 1:getnquadpoints(cellvalues)\n # For each integration point, compute stress and material stiffness\n ϵ = function_symmetric_gradient(cellvalues, q_point, ue) # Total strain\n σ, D, state[q_point] = compute_stress_tangent(ϵ, material, state_old[q_point])\n\n dΩ = getdetJdV(cellvalues, q_point)\n for i in 1:n_basefuncs\n δϵ = shape_symmetric_gradient(cellvalues, q_point, i)\n re[i] += (δϵ ⊡ σ) * dΩ # add internal force to residual\n for j in 1:i # loop only over lower half\n Δϵ = shape_symmetric_gradient(cellvalues, q_point, j)\n Ke[i, j] += δϵ ⊡ D ⊡ Δϵ * dΩ\n end\n end\n end\n symmetrize_lower!(Ke)\n return\nend\n\nfunction symmetrize_lower!(K)\n for i in 1:size(K, 1)\n for j in (i + 1):size(K, 1)\n K[i, j] = K[j, i]\n end\n end\n return\nend;\n\nfunction doassemble_neumann!(r, dh, facetset, facetvalues, t)\n n_basefuncs = getnbasefunctions(facetvalues)\n re = zeros(n_basefuncs) # element residual vector\n for fc in FacetIterator(dh, facetset)\n # Add traction as a negative contribution to the element residual `re`:\n reinit!(facetvalues, fc)\n fill!(re, 0)\n for q_point in 1:getnquadpoints(facetvalues)\n dΓ = getdetJdV(facetvalues, q_point)\n for i in 1:n_basefuncs\n δu = shape_value(facetvalues, q_point, i)\n re[i] -= (δu ⋅ t) * dΓ\n end\n end\n assemble!(r, celldofs(fc), re)\n end\n return r\nend\n\nfunction solve()\n # Define material parameters\n E = 200.0e9 # [Pa]\n H = E / 20 # [Pa]\n ν = 0.3 # [-]\n σ₀ = 200.0e6 # [Pa]\n material = J2Plasticity(E, ν, σ₀, H)\n\n L = 10.0 # beam length [m]\n w = 1.0 # beam width [m]\n h = 1.0 # beam height[m]\n n_timesteps = 10\n u_max = zeros(n_timesteps)\n traction_magnitude = 1.0e7 * range(0.5, 1.0, length = n_timesteps)\n\n # Create geometry, dofs and boundary conditions\n n = 2\n nels = (10n, n, 2n) # number of elements in each spatial direction\n P1 = Vec((0.0, 0.0, 0.0)) # start point for geometry\n P2 = Vec((L, w, h)) # end point for geometry\n grid = generate_grid(Tetrahedron, nels, P1, P2)\n interpolation = Lagrange{RefTetrahedron, 1}()^3\n\n dh = create_dofhandler(grid, interpolation) # JuaFEM helper function\n dbcs = create_bc(dh, grid) # create Dirichlet boundary-conditions\n\n cellvalues, facetvalues = create_values(interpolation)\n\n # Pre-allocate solution vectors, etc.\n n_dofs = ndofs(dh) # total number of dofs\n u = zeros(n_dofs) # solution vector\n Δu = zeros(n_dofs) # displacement correction\n r = zeros(n_dofs) # residual\n K = allocate_matrix(dh) # tangent stiffness matrix\n\n # Create material states. One array for each cell, where each element is an array of material-\n # states - one for each integration point\n nqp = getnquadpoints(cellvalues)\n states = [MaterialState() for _ in 1:nqp, _ in 1:getncells(grid)]\n states_old = [MaterialState() for _ in 1:nqp, _ in 1:getncells(grid)]\n\n # Newton-Raphson loop\n NEWTON_TOL = 1 # 1 N\n print(\"\\n Starting Netwon iterations:\\n\")\n\n for timestep in 1:n_timesteps\n t = timestep # actual time (used for evaluating d-bndc)\n traction = Vec((0.0, 0.0, traction_magnitude[timestep]))\n newton_itr = -1\n print(\"\\n Time step @time = $timestep:\\n\")\n update!(dbcs, t) # evaluates the D-bndc at time t\n apply!(u, dbcs) # set the prescribed values in the solution vector\n\n while true\n newton_itr += 1\n if newton_itr > 8\n error(\"Reached maximum Newton iterations, aborting\")\n break\n end\n # Tangent and residual contribution from the cells (volume integral)\n doassemble!(K, r, cellvalues, dh, material, u, states, states_old)\n # Residual contribution from the Neumann boundary (surface integral)\n doassemble_neumann!(r, dh, getfacetset(grid, \"right\"), facetvalues, traction)\n norm_r = norm(r[Ferrite.free_dofs(dbcs)])\n\n print(\"Iteration: $newton_itr \\tresidual: $(@sprintf(\"%.8f\", norm_r))\\n\")\n if norm_r < NEWTON_TOL\n break\n end\n\n apply_zero!(K, r, dbcs)\n Δu = Symmetric(K) \\ r\n u -= Δu\n end\n\n # Update the old states with the converged values for next timestep\n states_old .= states\n\n u_max[timestep] = maximum(abs, u) # maximum displacement in current timestep\n end\n\n # ## Postprocessing\n # Only a vtu-file corresponding to the last time-step is exported.\n #\n # The following is a quick (and dirty) way of extracting average cell data for export.\n mises_values = zeros(getncells(grid))\n κ_values = zeros(getncells(grid))\n for (el, cell_states) in enumerate(eachcol(states))\n for state in cell_states\n mises_values[el] += vonMises(state.σ)\n κ_values[el] += state.k * material.H\n end\n mises_values[el] /= length(cell_states) # average von Mises stress\n κ_values[el] /= length(cell_states) # average drag stress\n end\n VTKGridFile(\"plasticity\", dh) do vtk\n write_solution(vtk, dh, u) # displacement field\n write_cell_data(vtk, mises_values, \"von Mises [Pa]\")\n write_cell_data(vtk, κ_values, \"Drag stress [Pa]\")\n end\n\n return u_max, traction_magnitude\nend\n\nu_max, traction_magnitude = solve();\n\nusing Plots\nplot(\n vcat(0.0, u_max), # add the origin as a point\n vcat(0.0, traction_magnitude),\n linewidth = 2,\n title = \"Traction-displacement\",\n label = nothing,\n markershape = :auto\n)\nylabel!(\"Traction [Pa]\")\nxlabel!(\"Maximum deflection [m]\")","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"This page was generated using Literate.jl.","category":"page"},{"location":"reference/boundary_conditions/","page":"Boundary conditions","title":"Boundary conditions","text":"DocTestSetup = :(using Ferrite)","category":"page"},{"location":"reference/boundary_conditions/#Boundary-conditions","page":"Boundary conditions","title":"Boundary conditions","text":"","category":"section"},{"location":"reference/boundary_conditions/","page":"Boundary conditions","title":"Boundary conditions","text":"Pages = [\"boundary_conditions.md\"]","category":"page"},{"location":"reference/boundary_conditions/","page":"Boundary conditions","title":"Boundary conditions","text":"ConstraintHandler\nDirichlet\nPeriodicDirichlet\ncollect_periodic_facets\ncollect_periodic_facets!\nadd!\nclose!\nupdate!\napply!\napply_zero!\napply_local!\napply_assemble!\nget_rhs_data\napply_rhs!\nFerrite.RHSData","category":"page"},{"location":"reference/boundary_conditions/#Ferrite.ConstraintHandler","page":"Boundary conditions","title":"Ferrite.ConstraintHandler","text":"ConstraintHandler([T=Float64], dh::AbstractDofHandler)\n\nA collection of constraints associated with the dof handler dh. T is the numeric type for stored values.\n\n\n\n\n\n","category":"type"},{"location":"reference/boundary_conditions/#Ferrite.Dirichlet","page":"Boundary conditions","title":"Ferrite.Dirichlet","text":"Dirichlet(u::Symbol, ∂Ω::AbstractVecOrSet, f::Function, components=nothing)\n\nCreate a Dirichlet boundary condition on u on the ∂Ω part of the boundary. f is a function of the form f(x) or f(x, t) where x is the spatial coordinate and t is the current time, and returns the prescribed value. components specify the components of u that are prescribed by this condition. By default all components of u are prescribed.\n\nThe set, ∂Ω, can be an AbstractSet or AbstractVector with elements of type FacetIndex, FaceIndex, EdgeIndex, VertexIndex, or Int. For most cases, the element type is FacetIndex, as shown below. To constrain a single point, using VertexIndex is recommended, but it is also possible to constrain a specific nodes by giving the node numbers via Int elements. To constrain e.g. an edge in 3d EdgeIndex elements can be given.\n\nFor example, here we create a Dirichlet condition for the :u field, on the facetset called ∂Ω and the value given by the sin function:\n\nExamples\n\n# Obtain the facetset from the grid\n∂Ω = getfacetset(grid, \"boundary-1\")\n\n# Prescribe scalar field :s on ∂Ω to sin(t)\ndbc = Dirichlet(:s, ∂Ω, (x, t) -> sin(t))\n\n# Prescribe all components of vector field :v on ∂Ω to 0\ndbc = Dirichlet(:v, ∂Ω, x -> 0 * x)\n\n# Prescribe component 2 and 3 of vector field :v on ∂Ω to [sin(t), cos(t)]\ndbc = Dirichlet(:v, ∂Ω, (x, t) -> [sin(t), cos(t)], [2, 3])\n\nDirichlet boundary conditions are added to a ConstraintHandler which applies the condition via apply! and/or apply_zero!.\n\n\n\n\n\n","category":"type"},{"location":"reference/boundary_conditions/#Ferrite.PeriodicDirichlet","page":"Boundary conditions","title":"Ferrite.PeriodicDirichlet","text":"PeriodicDirichlet(u::Symbol, facet_mapping, components=nothing)\nPeriodicDirichlet(u::Symbol, facet_mapping, R::AbstractMatrix, components=nothing)\nPeriodicDirichlet(u::Symbol, facet_mapping, f::Function, components=nothing)\n\nCreate a periodic Dirichlet boundary condition for the field u on the facet-pairs given in facet_mapping. The mapping can be computed with collect_periodic_facets. The constraint ensures that degrees-of-freedom on the mirror facet are constrained to the corresponding degrees-of-freedom on the image facet. components specify the components of u that are prescribed by this condition. By default all components of u are prescribed.\n\nIf the mapping is not aligned with the coordinate axis (e.g. rotated) a rotation matrix R should be passed to the constructor. This matrix rotates dofs on the mirror facet to the image facet. Note that this is only applicable for vector-valued problems.\n\nTo construct an inhomogeneous periodic constraint it is possible to pass a function f. Note that this is currently only supported when the periodicity is aligned with the coordinate axes.\n\nSee the manual section on Periodic boundary conditions for more information.\n\n\n\n\n\n","category":"type"},{"location":"reference/boundary_conditions/#Ferrite.collect_periodic_facets","page":"Boundary conditions","title":"Ferrite.collect_periodic_facets","text":"collect_periodic_facets(grid::Grid, mset, iset, transform::Union{Function,Nothing}=nothing; tol=1e-12)\n\nMatch all mirror facets in mset with a corresponding image facet in iset. Return a dictionary which maps each mirror facet to a image facet. The result can then be passed to PeriodicDirichlet.\n\nmset and iset can be given as a String (an existing facet set in the grid) or as a AbstractSet{FacetIndex} directly.\n\nBy default this function looks for a matching facet in the directions of the coordinate system. For other types of periodicities the transform function can be used. The transform function is applied on the coordinates of the image facet, and is expected to transform the coordinates to the matching locations in the mirror set.\n\nThe keyword tol specifies the tolerance (i.e. distance and deviation in facet-normals) between a image-facet and mirror-facet, for them to be considered matched.\n\nSee also: collect_periodic_facets!, PeriodicDirichlet.\n\n\n\n\n\ncollect_periodic_facets(grid::Grid, all_facets::Union{AbstractSet{FacetIndex},String,Nothing}=nothing; tol=1e-12)\n\nSplit all facets in all_facets into image and mirror sets. For each matching pair, the facet located further along the vector (1, 1, 1) becomes the image facet.\n\nIf no set is given, all facets on the outer boundary of the grid (i.e. all facets that do not have a neighbor) is used.\n\nSee also: collect_periodic_facets!, PeriodicDirichlet.\n\n\n\n\n\n","category":"function"},{"location":"reference/boundary_conditions/#Ferrite.collect_periodic_facets!","page":"Boundary conditions","title":"Ferrite.collect_periodic_facets!","text":"collect_periodic_facets!(facet_map::Vector{PeriodicFacetPair}, grid::Grid, mset, iset, transform::Union{Function,Nothing}; tol=1e-12)\n\nSame as collect_periodic_facets but adds all matches to the existing facet_map.\n\n\n\n\n\n","category":"function"},{"location":"reference/boundary_conditions/#Ferrite.add!","page":"Boundary conditions","title":"Ferrite.add!","text":"add!(sdh::SubDofHandler, name::Symbol, ip::Interpolation)\n\nAdd a field called name approximated by ip to the SubDofHandler sdh.\n\n\n\n\n\nadd!(dh::DofHandler, name::Symbol, ip::Interpolation)\n\nAdd a field called name approximated by ip to the DofHandler dh.\n\nThe field is added to all cells of the underlying grid, use SubDofHandlers if the grid contains multiple cell types, or to add the field to subset of all the cells.\n\n\n\n\n\nadd!(ch::ConstraintHandler, ac::AffineConstraint)\n\nAdd the AffineConstraint to the ConstraintHandler.\n\n\n\n\n\nadd!(ch::ConstraintHandler, dbc::Dirichlet)\n\nAdd a Dirichlet boundary condition to the ConstraintHandler.\n\n\n\n\n\nadd!(proj::L2Projector, set::AbstractVecOrSet{Int}, ip::Interpolation;\n qr_rhs, [qr_lhs])\n\nAdd an interpolation ip on the cells in set to the L2Projector proj.\n\nqr_rhs sets the quadrature rule used to later integrate the right-hand-side of the projection equation, when calling project. It should match the quadrature points used when creating the quadrature-point variables to project.\nThe optional qr_lhs sets the quadrature rule used to integrate the left-hand-side of the projection equation, and defaults to a quadrature rule that integrates the mass-matrix exactly for the given interpolation ip.\n\n\n\n\n\n","category":"function"},{"location":"reference/boundary_conditions/#Ferrite.close!","page":"Boundary conditions","title":"Ferrite.close!","text":"close!(dh::AbstractDofHandler)\n\nCloses dh and creates degrees of freedom for each cell.\n\n\n\n\n\nclose!(ch::ConstraintHandler)\n\nClose and finalize the ConstraintHandler.\n\n\n\n\n\nclose!(proj::L2Projector)\n\nClose proj which assembles and calculates the left-hand-side of the projection equation, before doing a Cholesky factorization of the mass-matrix.\n\n\n\n\n\n","category":"function"},{"location":"reference/boundary_conditions/#Ferrite.update!","page":"Boundary conditions","title":"Ferrite.update!","text":"update!(ch::ConstraintHandler, time::Real=0.0)\n\nUpdate time-dependent inhomogeneities for the new time. This calls f(x) or f(x, t) when applicable, where f is the function(s) corresponding to the constraints in the handler, to compute the inhomogeneities.\n\nNote that this is called implicitly in close!(::ConstraintHandler).\n\n\n\n\n\n","category":"function"},{"location":"reference/boundary_conditions/#Ferrite.apply!","page":"Boundary conditions","title":"Ferrite.apply!","text":"apply!(K::SparseMatrixCSC, rhs::AbstractVector, ch::ConstraintHandler)\n\nAdjust the matrix K and right hand side rhs to account for the Dirichlet boundary conditions specified in ch such that K \\ rhs gives the expected solution.\n\nnote: Note\napply!(K, rhs, ch) essentially calculatesrhs[free] = rhs[free] - K[constrained, constrained] * a[constrained]where a[constrained] are the inhomogeneities. Consequently, the sign of rhs matters (in contrast with apply_zero!).\n\napply!(v::AbstractVector, ch::ConstraintHandler)\n\nApply Dirichlet boundary conditions and affine constraints, specified in ch, to the solution vector v.\n\nExamples\n\nK, f = assemble_system(...) # Assemble system\napply!(K, f, ch) # Adjust K and f to account for boundary conditions\nu = K \\ f # Solve the system, u should be \"approximately correct\"\napply!(u, ch) # Explicitly make sure bcs are correct\n\nnote: Note\nThe last operation is not strictly necessary since the boundary conditions should already be fulfilled after apply!(K, f, ch). However, solvers of linear systems are not exact, and thus apply!(u, ch) can be used to make sure the boundary conditions are fulfilled exactly.\n\n\n\n\n\n","category":"function"},{"location":"reference/boundary_conditions/#Ferrite.apply_zero!","page":"Boundary conditions","title":"Ferrite.apply_zero!","text":"apply_zero!(K::SparseMatrixCSC, rhs::AbstractVector, ch::ConstraintHandler)\n\nAdjust the matrix K and the right hand side rhs to account for prescribed Dirichlet boundary conditions and affine constraints such that du = K \\ rhs gives the expected result (e.g. du zero for all prescribed degrees of freedom).\n\napply_zero!(v::AbstractVector, ch::ConstraintHandler)\n\nZero-out values in v corresponding to prescribed degrees of freedom and update values prescribed by affine constraints, such that if a fulfills the constraints, a ± v also will.\n\nThese methods are typically used in e.g. a Newton solver where the increment, du, should be prescribed to zero even for non-homogeneouos boundary conditions.\n\nSee also: apply!.\n\nExamples\n\nu = un + Δu # Current guess\nK, g = assemble_system(...) # Assemble residual and tangent for current guess\napply_zero!(K, g, ch) # Adjust tangent and residual to take prescribed values into account\nΔΔu = K \\ g # Compute the (negative) increment, prescribed values are \"approximately\" zero\napply_zero!(ΔΔu, ch) # Make sure values are exactly zero\nΔu .-= ΔΔu # Update current guess\n\nnote: Note\nThe last call to apply_zero! is only strictly necessary for affine constraints. However, even if the Dirichlet boundary conditions should be fulfilled after apply!(K, g, ch), solvers of linear systems are not exact. apply!(ΔΔu, ch) can be used to make sure the values for the prescribed degrees of freedom are fulfilled exactly.\n\n\n\n\n\n","category":"function"},{"location":"reference/boundary_conditions/#Ferrite.apply_local!","page":"Boundary conditions","title":"Ferrite.apply_local!","text":"apply_local!(\n local_matrix::AbstractMatrix, local_vector::AbstractVector,\n global_dofs::AbstractVector, ch::ConstraintHandler;\n apply_zero::Bool = false\n)\n\nSimilar to apply! but perform condensation of constrained degrees-of-freedom locally in local_matrix and local_vector before they are to be assembled into the global system.\n\nWhen the keyword argument apply_zero is true all inhomogeneities are set to 0 (cf. apply! vs apply_zero!).\n\nThis method can only be used if all constraints are \"local\", i.e. no constraint couples with dofs outside of the element dofs (global_dofs) since condensation of such constraints requires writing to entries in the global matrix/vector. For such a case, apply_assemble! can be used instead.\n\nNote that this method is destructive since it, by definition, modifies local_matrix and local_vector.\n\n\n\n\n\n","category":"function"},{"location":"reference/boundary_conditions/#Ferrite.apply_assemble!","page":"Boundary conditions","title":"Ferrite.apply_assemble!","text":"apply_assemble!(\n assembler::AbstractAssembler, ch::ConstraintHandler,\n global_dofs::AbstractVector{Int},\n local_matrix::AbstractMatrix, local_vector::AbstractVector;\n apply_zero::Bool = false\n)\n\nAssemble local_matrix and local_vector into the global system in assembler by first doing constraint condensation using apply_local!.\n\nThis is similar to using apply_local! followed by assemble! with the advantage that non-local constraints can be handled, since this method can write to entries of the global matrix and vector outside of the indices in global_dofs.\n\nWhen the keyword argument apply_zero is true all inhomogeneities are set to 0 (cf. apply! vs apply_zero!).\n\nNote that this method is destructive since it modifies local_matrix and local_vector.\n\n\n\n\n\n","category":"function"},{"location":"reference/boundary_conditions/#Ferrite.get_rhs_data","page":"Boundary conditions","title":"Ferrite.get_rhs_data","text":"get_rhs_data(ch::ConstraintHandler, A::SparseMatrixCSC) -> RHSData\n\nReturns the needed RHSData for apply_rhs!.\n\nThis must be used when the same stiffness matrix is reused for multiple steps, for example when timestepping, with different non-homogeneouos Dirichlet boundary conditions.\n\n\n\n\n\n","category":"function"},{"location":"reference/boundary_conditions/#Ferrite.apply_rhs!","page":"Boundary conditions","title":"Ferrite.apply_rhs!","text":"apply_rhs!(data::RHSData, f::AbstractVector, ch::ConstraintHandler, applyzero::Bool=false)\n\nApplies the boundary condition to the right-hand-side vector without modifying the stiffness matrix.\n\nSee also: get_rhs_data.\n\n\n\n\n\n","category":"function"},{"location":"reference/boundary_conditions/#Ferrite.RHSData","page":"Boundary conditions","title":"Ferrite.RHSData","text":"RHSData\n\nStores the constrained columns and mean of the diagonal of stiffness matrix A.\n\n\n\n\n\n","category":"type"},{"location":"reference/boundary_conditions/#Initial-conditions","page":"Boundary conditions","title":"Initial conditions","text":"","category":"section"},{"location":"reference/boundary_conditions/","page":"Boundary conditions","title":"Boundary conditions","text":"apply_analytical!","category":"page"},{"location":"reference/boundary_conditions/#Ferrite.apply_analytical!","page":"Boundary conditions","title":"Ferrite.apply_analytical!","text":"apply_analytical!(\n a::AbstractVector, dh::AbstractDofHandler, fieldname::Symbol,\n f::Function, cellset=1:getncells(get_grid(dh)))\n\nApply a solution f(x) by modifying the values in the degree of freedom vector a pertaining to the field fieldname for all cells in cellset. The function f(x) are given the spatial coordinate of the degree of freedom. For scalar fields, f(x)::Number, and for vector fields with dimension dim, f(x)::Vec{dim}.\n\nThis function can be used to apply initial conditions for time dependent problems.\n\nnote: Note\nThis function only works for standard nodal finite element interpolations when the function value at the (algebraic) node is equal to the corresponding degree of freedom value. This holds for e.g. Lagrange and Serendipity interpolations, including sub- and superparametric elements.\n\n\n\n\n\n","category":"function"},{"location":"cited-literature/#Cited-literature","page":"Cited literature","title":"Cited literature","text":"","category":"section"},{"location":"cited-literature/","page":"Cited literature","title":"Cited literature","text":"G. N. Gatica. A Simple Introduction to the Mixed Finite Element Method: Theory and Applications (Springer International Publishing, 2014).\n\n\n\nD. Boffi, F. Brezzi and M. Fortin. Mixed Finite Element Methods and Applications (Springer Berlin Heidelberg, 2013).\n\n\n\nG. A. Holzapfel. Nonlinear Solid Mechanics: A Continuum Approach for Engineering (Wiley, Chichester ; New York, 2000).\n\n\n\nJ. Simo and C. Miehe. Associative coupled thermoplasticity at finite strains: Formulation, numerical analysis and implementation. Computer Methods in Applied Mechanics and Engineering 98, 41–104 (1992).\n\n\n\nL. Mu, J. Wang, Y. Wang and X. Ye. Interior penalty discontinuous Galerkin method on very general polygonal and polyhedral meshes. Journal of Computational and Applied Mathematics 255, 432–440 (2014).\n\n\n\nD. N. Arnold, F. Brezzi, B. Cockburn and L. D. Marini. Unified Analysis of Discontinuous Galerkin Methods for Elliptic Problems. SIAM Journal on Numerical Analysis 39, 1749–1779 (2002). Accessed on Dec 20, 2023.\n\n\n\nR. C. Kirby. A general approach to transforming finite elements (2017), arXiv:1706.09017 [math.NA].\n\n\n\nD. Dunavant. High degree efficient symmetrical Gaussian quadrature rules for the triangle. International journal for numerical methods in engineering 21, 1129–1148 (1985).\n\n\n\nP. Keast. Moderate-degree tetrahedral quadrature formulas. Computer methods in applied mechanics and engineering 55, 339–348 (1986).\n\n\n\nF. D. Witherden and P. E. Vincent. On the identification of symmetric quadrature rules for finite element methods. Computers & Mathematics with Applications 69, 1232–1241 (2015).\n\n\n\nM. Crouzeix and P.-A. Raviart. Conforming and nonconforming finite element methods for solving the stationary Stokes equations I. Revue française d'automatique informatique recherche opérationnelle. Mathématique 7, 33–75 (1973).\n\n\n\nR. Rannacher and S. Turek. Simple nonconforming quadrilateral Stokes element. Numerical Methods for Partial Differential Equations 8, 97–111 (1992).\n\n\n\nB. Turcksin, M. Kronbichler and W. Bangerth. WorkStream – A Design Pattern for Multicore-Enabled Finite Element Computations. ACM Trans. Math. Softw. 43 (2016).\n\n\n\nM. Cenanovic. Finite element methods for surface problems. Ph.D. Thesis, Jönköping University, School of Engineering (2017).\n\n\n\nM. W. Scroggs, J. S. Dokken, C. N. Richardson and G. N. Wells. Construction of Arbitrary Order Finite Element Degree-of-Freedom Maps on Polygonal and Polyhedral Cell Meshes. ACM Trans. Math. Softw. 48 (2022).\n\n\n\nD. R. Jantos, K. Hackl and P. Junker. An accurate and fast regularization approach to thermodynamic topology optimization. International Journal for Numerical Methods in Engineering 117, 991–1017 (2019).\n\n\n\nM. Blaszczyk, D. R. Jantos and P. Junker. Application of Taylor series combined with the weighted least square method to thermodynamic topology optimization. Computer Methods in Applied Mechanics and Engineering 393, 114698 (2022).\n\n\n\n","category":"page"},{"location":"cited-literature/","page":"Cited literature","title":"Cited literature","text":"","category":"page"},{"location":"devdocs/reference_cells/#Reference-cells","page":"Reference cells","title":"Reference cells","text":"","category":"section"},{"location":"devdocs/reference_cells/","page":"Reference cells","title":"Reference cells","text":"The reference cells are used to i) define grid cells, ii) define shape functions, and iii) define quadrature rules. The numbering of vertices, edges, faces are visualized below. See also FerriteViz.elementinfo.","category":"page"},{"location":"devdocs/reference_cells/#AbstractRefShape-subtypes","page":"Reference cells","title":"AbstractRefShape subtypes","text":"","category":"section"},{"location":"devdocs/reference_cells/","page":"Reference cells","title":"Reference cells","text":"Ferrite.AbstractRefShape\nFerrite.RefLine\nFerrite.RefTriangle\nFerrite.RefQuadrilateral\nFerrite.RefTetrahedron\nFerrite.RefHexahedron\nFerrite.RefPrism","category":"page"},{"location":"devdocs/reference_cells/#Ferrite.AbstractRefShape","page":"Reference cells","title":"Ferrite.AbstractRefShape","text":"AbstractRefShape{refdim}\n\nSupertype for all reference shapes, with reference dimension refdim. Reference shapes are used to define grid cells, shape functions, and quadrature rules. Currently existing reference shapes are: RefLine, RefTriangle, RefQuadrilateral, RefTetrahedron, RefHexahedron, RefPrism.\n\n\n\n\n\n","category":"type"},{"location":"devdocs/reference_cells/#Ferrite.RefLine","page":"Reference cells","title":"Ferrite.RefLine","text":"RefLine <: AbstractRefShape{1}\n\nReference line/interval, reference dimension 1.\n\n----------------+--------------------\nVertex numbers: | Vertex coordinates:\n 1-------2 | v1: 𝛏 = (-1.0,)\n --> ξ₁ | v2: 𝛏 = ( 1.0,)\n----------------+--------------------\nFace numbers: | Face identifiers:\n 1-------2 | f1: (v1,)\n | f2: (v2,)\n----------------+--------------------\n\n\n\n\n\n","category":"type"},{"location":"devdocs/reference_cells/#Ferrite.RefTriangle","page":"Reference cells","title":"Ferrite.RefTriangle","text":"RefTriangle <: AbstractRefShape{2}\n\nReference triangle, reference dimension 2.\n\n----------------+--------------------\nVertex numbers: | Vertex coordinates:\n 2 |\n | \\ | v1: 𝛏 = (1.0, 0.0)\n | \\ | v2: 𝛏 = (0.0, 1.0)\nξ₂^ | \\ | v3: 𝛏 = (0.0, 0.0)\n | 3-------1 |\n +--> ξ₁ |\n----------------+--------------------\nFace numbers: | Face identifiers:\n + |\n | \\ | f1: (v1, v2)\n 2 1 | f2: (v2, v3)\n | \\ | f3: (v3, v1)\n +---3---+ |\n----------------+--------------------\n\n\n\n\n\n","category":"type"},{"location":"devdocs/reference_cells/#Ferrite.RefQuadrilateral","page":"Reference cells","title":"Ferrite.RefQuadrilateral","text":"RefQuadrilateral <: AbstractRefShape{2}\n\nReference quadrilateral, reference dimension 2.\n\n----------------+---------------------\nVertex numbers: | Vertex coordinates:\n 4-------3 |\n | | | v1: 𝛏 = (-1.0, -1.0)\n | | | v2: 𝛏 = ( 1.0, -1.0)\nξ₂^ | | | v3: 𝛏 = ( 1.0, 1.0)\n | 1-------2 | v4: 𝛏 = (-1.0, 1.0)\n +--> ξ₁ |\n----------------+---------------------\nFace numbers: | Face identifiers:\n +---3---+ | f1: (v1, v2)\n | | | f2: (v2, v3)\n 4 2 | f3: (v3, v4)\n | | | f4: (v4, v1)\n +---1---+ |\n----------------+---------------------\n\n\n\n\n\n","category":"type"},{"location":"devdocs/reference_cells/#Ferrite.RefTetrahedron","page":"Reference cells","title":"Ferrite.RefTetrahedron","text":"RefTetrahedron <: AbstractRefShape{3}\n\nReference tetrahedron, reference dimension 3.\n\n---------------------------------------+-------------------------\nVertex numbers: | Vertex coordinates:\n 4 4 |\n ^ ξ₃ / \\ /| \\ | v1: 𝛏 = (0.0, 0.0, 0.0)\n | / \\ / | \\ | v2: 𝛏 = (1.0, 0.0, 0.0)\n +-> ξ₂ / \\ / 1___ \\ | v3: 𝛏 = (0.0, 1.0, 0.0)\n / / __--3 / / __‾-3 | v4: 𝛏 = (0.0, 0.0, 1.0)\nξ₁ 2 __--‾‾ 2/__--‾‾ |\n---------------------------------------+-------------------------\nEdge numbers: | Edge identifiers:\n + + | e1: (v1, v2)\n / \\ /| \\ | e2: (v2, v3)\n 5 / \\ 6 5 / |4 \\ 6 | e3: (v3, v1)\n / \\ / +__3 \\ | e4: (v1, v4)\n / __--+ / /1 __‾-+ | e5: (v2, v4)\n + __--‾‾2 +/__--‾‾2 | e6: (v3, v4)\n---------------------------------------+-------------------------\nFace numbers: | Face identifiers:\n + + |\n / \\ /| \\ | f1: (v1, v3, v2)\n / \\ / | 4 \\ | f2: (v1, v2, v4)\n / 3 \\ /2 +___ \\ | f3: (v2, v3, v4)\n / __--+ / / 1 __‾-+ | f4: (v1, v4, v3)\n + __--‾‾ +/__--‾‾ |\n---------------------------------------+-------------------------\n\n\n\n\n\n","category":"type"},{"location":"devdocs/reference_cells/#Ferrite.RefHexahedron","page":"Reference cells","title":"Ferrite.RefHexahedron","text":"RefHexahedron <: AbstractRefShape{3}\n\nReference hexahedron, reference dimension 3.\n\n-----------------------------------------+----------------------------\nVertex numbers: | Vertex coordinates:\n 5--------8 5--------8 | v1: 𝛏 = (-1.0, -1.0, -1.0)\n / /| /| | | v2: 𝛏 = ( 1.0, -1.0, -1.0)\n / / | / | | | v3: 𝛏 = ( 1.0, 1.0, -1.0)\n ^ ξ₃ 6--------7 | 6 | | | v4: 𝛏 = (-1.0, 1.0, -1.0)\n | | | 4 | 1--------4 | v5: 𝛏 = (-1.0, -1.0, 1.0)\n +-> ξ₂ | | / | / / | v6: 𝛏 = ( 1.0, -1.0, 1.0)\n / | |/ |/ / | v7: 𝛏 = ( 1.0, 1.0, 1.0)\nξ₁ 2--------3 2--------3 | v8: 𝛏 = (-1.0, 1.0, 1.0)\n-----------------------------------------+-----------------------------\nEdge numbers: | Edge identifiers:\n +----8---+ +----8---+ |\n 5/ /| 5/| | | e1: (v1, v2), e2: (v2, v3)\n / 7/ |12 / |9 12| | e3: (v3, v4), e4: (v4, v1)\n +----6---+ | + | | | e5: (v5, v6), e6: (v6, v7)\n | | + | +---4----+ | e7: (v7, v8), e8: (v8, v5)\n 10| 11| / 10| /1 / | e9: (v1, v5), e10: (v2, v6)\n | |/3 |/ /3 | e11: (v3, v7), e12: (v4, v8)\n +---2----+ +---2----+ |\n-----------------------------------------+-----------------------------\nFace numbers: | Face identifiers:\n +--------+ +--------+ |\n / 6 /| /| | | f1: (v1, v4, v3, v2)\n / / | / | 5 | | f2: (v1, v2, v6, v5)\n +--------+ 4| + | | | f3: (v2, v3, v7, v6)\n | | + |2 +--------+ | f4: (v3, v4, v8, v7)\n | 3 | / | / / | f5: (v1, v5, v8, v4)\n | |/ |/ 1 / | f6: (v5, v6, v7, v8)\n +--------+ +--------+ |\n-----------------------------------------+-----------------------------\n\n\n\n\n\n","category":"type"},{"location":"devdocs/reference_cells/#Ferrite.RefPrism","page":"Reference cells","title":"Ferrite.RefPrism","text":"RefPrism <: AbstractRefShape{3}\n\nReference prism, reference dimension 3.\n\n-----------------------------------------+----------------------------\nVertex numbers: | Vertex coordinates:\n 4-------/6 4--------6 |\n / / | /| | | v1: 𝛏 = (0.0, 0.0, 0.0)\n / / | / | | | v2: 𝛏 = (1.0, 0.0, 0.0)\n ^ ξ₃ 5 / | 5 | | | v3: 𝛏 = (0.0, 1.0, 0.0)\n | | /3 | 1-------/3 | v4: 𝛏 = (0.0, 0.0, 1.0)\n +-> ξ₂ | / | / / | v5: 𝛏 = (1.0, 0.0, 1.0)\n / | / |/ / | v6: 𝛏 = (0.0, 1.0, 1.0)\nξ₁ 2 / 2 / |\n-----------------------------------------+----------------------------\nEdge numbers: | Edge identifiers:\n +---8---/+ +---8----+ |\n 7/ / | 7/| | | e1: (v2, v1), e2: (v1, v3)\n / / 9 |6 / |3 |6 | e3: (v1, v4), e4: (v3, v2)\n + / | + | | | e5: (v2, v5), e6: (v3, v6)\n | /+ | +--2----/+ | e7: (v4, v5), e8: (v4, v6)\n 5| / 5| /1 / | e9: (v6, v5)\n | / 4 |/ / 4 |\n + / + / |\n-----------------------------------------+----------------------------\nFace numbers: | Face identifiers:\n +-------/+ +--------+ |\n / 5 / | /| | | f1: (v1, v3, v2)\n / / | / | 3 | | f2: (v1, v2, v5, v4)\n + / | + | | | f3: (v3, v1, v4, v6)\n | 4 /+ |2 +-------/+ | f4: (v2, v3, v6, v5)\n | / | / 1 / | f5: (v4, v5, v6)\n | / |/ / |\n + / + / |\n-----------------------------------------+----------------------------\n\n\n\n\n\n","category":"type"},{"location":"devdocs/reference_cells/#Required-methods-to-implement-for-all-subtypes-of-AbstractRefShape-to-define-a-new-reference-shape","page":"Reference cells","title":"Required methods to implement for all subtypes of AbstractRefShape to define a new reference shape","text":"","category":"section"},{"location":"devdocs/reference_cells/","page":"Reference cells","title":"Reference cells","text":"Ferrite.reference_vertices(::Type{<:Ferrite.AbstractRefShape})\nFerrite.reference_edges(::Type{<:Ferrite.AbstractRefShape})\nFerrite.reference_faces(::Type{<:Ferrite.AbstractRefShape})","category":"page"},{"location":"devdocs/reference_cells/#Ferrite.reference_vertices-Tuple{Type{<:Ferrite.AbstractRefShape}}","page":"Reference cells","title":"Ferrite.reference_vertices","text":"reference_vertices(::Type{<:AbstractRefShape})\nreference_vertices(::AbstractCell)\n\nReturns a tuple of integers containing the local node indices corresponding to the vertices (i.e. corners or endpoints) of the cell.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/reference_cells/#Ferrite.reference_edges-Tuple{Type{<:Ferrite.AbstractRefShape}}","page":"Reference cells","title":"Ferrite.reference_edges","text":"reference_edges(::Type{<:AbstractRefShape})\nreference_edges(::AbstractCell)\n\nReturns a tuple of 2-tuples containing the ordered local node indices (corresponding to the vertices) that define an edge.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/reference_cells/#Ferrite.reference_faces-Tuple{Type{<:Ferrite.AbstractRefShape}}","page":"Reference cells","title":"Ferrite.reference_faces","text":"reference_faces(::Type{<:AbstractRefShape})\nreference_faces(::AbstractCell)\n\nReturns a tuple of n-tuples containing the ordered local node indices (corresponding to the vertices) that define a face.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/reference_cells/","page":"Reference cells","title":"Reference cells","text":"which automatically defines","category":"page"},{"location":"devdocs/reference_cells/","page":"Reference cells","title":"Reference cells","text":"Ferrite.reference_facets(::Type{<:Ferrite.AbstractRefShape})","category":"page"},{"location":"devdocs/reference_cells/#Ferrite.reference_facets-Tuple{Type{<:Ferrite.AbstractRefShape}}","page":"Reference cells","title":"Ferrite.reference_facets","text":"Ferrite.reference_facets(::Type{<:AbstractRefShape})\nFerrite.reference_facets(::AbstractCell)\n\nReturns a tuple of n-tuples containing the ordered local node indices (corresponding to the vertices) that define a facet.\n\nSee also reference_vertices, reference_edges, and reference_faces.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/reference_cells/#Applicable-methods-to-AbstractRefShapes","page":"Reference cells","title":"Applicable methods to AbstractRefShapes","text":"","category":"section"},{"location":"devdocs/reference_cells/","page":"Reference cells","title":"Reference cells","text":"Ferrite.getrefdim(::Type{<:Ferrite.AbstractRefShape})","category":"page"},{"location":"devdocs/reference_cells/#Ferrite.getrefdim-Tuple{Type{<:Ferrite.AbstractRefShape}}","page":"Reference cells","title":"Ferrite.getrefdim","text":"Ferrite.getrefdim(RefShape::Type{<:AbstractRefShape})\n\nGet the dimension of the reference shape\n\n\n\n\n\n","category":"method"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"EditURL = \"../literate-tutorials/incompressible_elasticity.jl\"","category":"page"},{"location":"tutorials/incompressible_elasticity/#tutorial-incompressible-elasticity","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"","category":"section"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"tip: Tip\nThis example is also available as a Jupyter notebook: incompressible_elasticity.ipynb.","category":"page"},{"location":"tutorials/incompressible_elasticity/#Introduction","page":"Incompressible elasticity","title":"Introduction","text":"","category":"section"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"Mixed elements can be used to overcome locking when the material becomes incompressible. However, for an element to be stable, it needs to fulfill the LBB condition. In this example we will consider two different element formulations","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"linear displacement with linear pressure approximation (does not fulfill LBB)\nquadratic displacement with linear pressure approximation (does fulfill LBB)","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"The quadratic/linear element is also known as the Taylor-Hood element. We will consider Cook's Membrane with an applied traction on the right hand side.","category":"page"},{"location":"tutorials/incompressible_elasticity/#Commented-program","page":"Incompressible elasticity","title":"Commented program","text":"","category":"section"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"What follows is a program spliced with comments. The full program, without comments, can be found in the next section.","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"using Ferrite, Tensors\nusing BlockArrays, SparseArrays, LinearAlgebra","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"First we generate a simple grid, specifying the 4 corners of Cooks membrane.","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"function create_cook_grid(nx, ny)\n corners = [\n Vec{2}((0.0, 0.0)),\n Vec{2}((48.0, 44.0)),\n Vec{2}((48.0, 60.0)),\n Vec{2}((0.0, 44.0)),\n ]\n grid = generate_grid(Triangle, (nx, ny), corners)\n # facesets for boundary conditions\n addfacetset!(grid, \"clamped\", x -> norm(x[1]) ≈ 0.0)\n addfacetset!(grid, \"traction\", x -> norm(x[1]) ≈ 48.0)\n return grid\nend;\nnothing #hide","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"Next we define a function to set up our cell- and FacetValues.","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"function create_values(interpolation_u, interpolation_p)\n # quadrature rules\n qr = QuadratureRule{RefTriangle}(3)\n facet_qr = FacetQuadratureRule{RefTriangle}(3)\n\n # cell and FacetValues for u\n cellvalues_u = CellValues(qr, interpolation_u)\n facetvalues_u = FacetValues(facet_qr, interpolation_u)\n\n # cellvalues for p\n cellvalues_p = CellValues(qr, interpolation_p)\n\n return cellvalues_u, cellvalues_p, facetvalues_u\nend;\nnothing #hide","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"We create a DofHandler, with two fields, :u and :p, with possibly different interpolations","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"function create_dofhandler(grid, ipu, ipp)\n dh = DofHandler(grid)\n add!(dh, :u, ipu) # displacement\n add!(dh, :p, ipp) # pressure\n close!(dh)\n return dh\nend;\nnothing #hide","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"We also need to add Dirichlet boundary conditions on the \"clamped\" facetset. We specify a homogeneous Dirichlet bc on the displacement field, :u.","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"function create_bc(dh)\n dbc = ConstraintHandler(dh)\n add!(dbc, Dirichlet(:u, getfacetset(dh.grid, \"clamped\"), x -> zero(x), [1, 2]))\n close!(dbc)\n return dbc\nend;\nnothing #hide","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"The material is linear elastic, which is here specified by the shear and bulk moduli","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"struct LinearElasticity{T}\n G::T\n K::T\nend","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"Now to the assembling of the stiffness matrix. This mixed formulation leads to a blocked element matrix. Since Ferrite does not force us to use any particular matrix type we will use a BlockedArray from BlockArrays.jl.","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"function doassemble(\n cellvalues_u::CellValues,\n cellvalues_p::CellValues,\n facetvalues_u::FacetValues,\n K::SparseMatrixCSC, grid::Grid, dh::DofHandler, mp::LinearElasticity\n )\n f = zeros(ndofs(dh))\n assembler = start_assemble(K, f)\n nu = getnbasefunctions(cellvalues_u)\n np = getnbasefunctions(cellvalues_p)\n\n fe = BlockedArray(zeros(nu + np), [nu, np]) # local force vector\n ke = BlockedArray(zeros(nu + np, nu + np), [nu, np], [nu, np]) # local stiffness matrix\n\n # traction vector\n t = Vec{2}((0.0, 1 / 16))\n # cache ɛdev outside the element routine to avoid some unnecessary allocations\n ɛdev = [zero(SymmetricTensor{2, 2}) for i in 1:getnbasefunctions(cellvalues_u)]\n\n for cell in CellIterator(dh)\n fill!(ke, 0)\n fill!(fe, 0)\n assemble_up!(ke, fe, cell, cellvalues_u, cellvalues_p, facetvalues_u, grid, mp, ɛdev, t)\n assemble!(assembler, celldofs(cell), ke, fe)\n end\n\n return K, f\nend;\nnothing #hide","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"The element routine integrates the local stiffness and force vector for all elements. Since the problem results in a symmetric matrix we choose to only assemble the lower part, and then symmetrize it after the loop over the quadrature points.","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"function assemble_up!(Ke, fe, cell, cellvalues_u, cellvalues_p, facetvalues_u, grid, mp, ɛdev, t)\n\n n_basefuncs_u = getnbasefunctions(cellvalues_u)\n n_basefuncs_p = getnbasefunctions(cellvalues_p)\n u▄, p▄ = 1, 2\n reinit!(cellvalues_u, cell)\n reinit!(cellvalues_p, cell)\n\n # We only assemble lower half triangle of the stiffness matrix and then symmetrize it.\n for q_point in 1:getnquadpoints(cellvalues_u)\n for i in 1:n_basefuncs_u\n ɛdev[i] = dev(symmetric(shape_gradient(cellvalues_u, q_point, i)))\n end\n dΩ = getdetJdV(cellvalues_u, q_point)\n for i in 1:n_basefuncs_u\n divδu = shape_divergence(cellvalues_u, q_point, i)\n δu = shape_value(cellvalues_u, q_point, i)\n for j in 1:i\n Ke[BlockIndex((u▄, u▄), (i, j))] += 2 * mp.G * ɛdev[i] ⊡ ɛdev[j] * dΩ\n end\n end\n\n for i in 1:n_basefuncs_p\n δp = shape_value(cellvalues_p, q_point, i)\n for j in 1:n_basefuncs_u\n divδu = shape_divergence(cellvalues_u, q_point, j)\n Ke[BlockIndex((p▄, u▄), (i, j))] += -δp * divδu * dΩ\n end\n for j in 1:i\n p = shape_value(cellvalues_p, q_point, j)\n Ke[BlockIndex((p▄, p▄), (i, j))] += - 1 / mp.K * δp * p * dΩ\n end\n\n end\n end\n\n symmetrize_lower!(Ke)\n\n # We integrate the Neumann boundary using the FacetValues.\n # We loop over all the facets in the cell, then check if the facet\n # is in our `\"traction\"` facetset.\n for facet in 1:nfacets(cell)\n if (cellid(cell), facet) ∈ getfacetset(grid, \"traction\")\n reinit!(facetvalues_u, cell, facet)\n for q_point in 1:getnquadpoints(facetvalues_u)\n dΓ = getdetJdV(facetvalues_u, q_point)\n for i in 1:n_basefuncs_u\n δu = shape_value(facetvalues_u, q_point, i)\n fe[i] += (δu ⋅ t) * dΓ\n end\n end\n end\n end\n return\nend\n\nfunction symmetrize_lower!(Ke)\n for i in 1:size(Ke, 1)\n for j in (i + 1):size(Ke, 1)\n Ke[i, j] = Ke[j, i]\n end\n end\n return\nend;\nnothing #hide","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"To evaluate the stresses after solving the problem we once again loop over the cells in the grid. Stresses are evaluated in the quadrature points, however, for export/visualization you typically want values in the nodes of the mesh, or as single data points per cell. For the former you can project the quadrature point data to a finite element space (see the example with the L2Projector in Post processing and visualization). In this example we choose to compute the mean value of the stress within each cell, and thus end up with one data point per cell. The mean value is computed as","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"barboldsymbolsigma_i = frac1 Omega_i\nint_Omega_i boldsymbolsigma mathrmdOmega quad\nOmega_i = int_Omega_i 1 mathrmdOmega","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"where Omega_i is the domain occupied by cell number i, and Omega_i the volume (area) of the cell. The integrals are evaluated using numerical quadrature with the help of cellvalues for u and p, just like in the assembly procedure.","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"Note that even though all strain components in the out-of-plane direction are zero (plane strain) the stress components are not. Specifically, sigma_33 will be non-zero in this formulation. Therefore we expand the strain to a 3D tensor, and then compute the (3D) stress tensor.","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"function compute_stresses(\n cellvalues_u::CellValues, cellvalues_p::CellValues,\n dh::DofHandler, mp::LinearElasticity, a::Vector\n )\n ae = zeros(ndofs_per_cell(dh)) # local solution vector\n u_range = dof_range(dh, :u) # local range of dofs corresponding to u\n p_range = dof_range(dh, :p) # local range of dofs corresponding to p\n # Allocate storage for the stresses\n σ = zeros(SymmetricTensor{2, 3}, getncells(dh.grid))\n # Loop over the cells and compute the cell-average stress\n for cc in CellIterator(dh)\n # Update cellvalues\n reinit!(cellvalues_u, cc)\n reinit!(cellvalues_p, cc)\n # Extract the cell local part of the solution\n for (i, I) in pairs(celldofs(cc))\n ae[i] = a[I]\n end\n # Loop over the quadrature points\n σΩi = zero(SymmetricTensor{2, 3}) # stress integrated over the cell\n Ωi = 0.0 # cell volume (area)\n for qp in 1:getnquadpoints(cellvalues_u)\n dΩ = getdetJdV(cellvalues_u, qp)\n # Evaluate the strain and the pressure\n ε = function_symmetric_gradient(cellvalues_u, qp, ae, u_range)\n p = function_value(cellvalues_p, qp, ae, p_range)\n # Expand strain to 3D\n ε3D = SymmetricTensor{2, 3}((i, j) -> i < 3 && j < 3 ? ε[i, j] : 0.0)\n # Compute the stress in this quadrature point\n σqp = 2 * mp.G * dev(ε3D) - one(ε3D) * p\n σΩi += σqp * dΩ\n Ωi += dΩ\n end\n # Store the value\n σ[cellid(cc)] = σΩi / Ωi\n end\n return σ\nend;\nnothing #hide","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"Now we have constructed all the necessary components, we just need a function to put it all together.","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"function solve(ν, interpolation_u, interpolation_p)\n # material\n Emod = 1.0\n Gmod = Emod / 2(1 + ν)\n Kmod = Emod * ν / ((1 + ν) * (1 - 2ν))\n mp = LinearElasticity(Gmod, Kmod)\n\n # Grid, dofhandler, boundary condition\n n = 50\n grid = create_cook_grid(n, n)\n dh = create_dofhandler(grid, interpolation_u, interpolation_p)\n dbc = create_bc(dh)\n\n # CellValues\n cellvalues_u, cellvalues_p, facetvalues_u = create_values(interpolation_u, interpolation_p)\n\n # Assembly and solve\n K = allocate_matrix(dh)\n K, f = doassemble(cellvalues_u, cellvalues_p, facetvalues_u, K, grid, dh, mp)\n apply!(K, f, dbc)\n u = K \\ f\n\n # Compute the stress\n σ = compute_stresses(cellvalues_u, cellvalues_p, dh, mp, u)\n σvM = map(x -> √(3 / 2 * dev(x) ⊡ dev(x)), σ) # von Mise effective stress\n\n # Export the solution and the stress\n filename = \"cook_\" *\n (interpolation_u == Lagrange{RefTriangle, 1}()^2 ? \"linear\" : \"quadratic\") *\n \"_linear\"\n\n VTKGridFile(filename, grid) do vtk\n write_solution(vtk, dh, u)\n for i in 1:3, j in 1:3\n σij = [x[i, j] for x in σ]\n write_cell_data(vtk, σij, \"sigma_$(i)$(j)\")\n end\n write_cell_data(vtk, σvM, \"sigma von Mises\")\n end\n return u\nend\nnothing # hide","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"We now define the interpolation for displacement and pressure. We use (scalar) Lagrange interpolation as a basis for both, and for the displacement, which is a vector, we vectorize it to 2 dimensions such that we obtain vector shape functions (and 2nd order tensors for the gradients).","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"linear_p = Lagrange{RefTriangle, 1}()\nlinear_u = Lagrange{RefTriangle, 1}()^2\nquadratic_u = Lagrange{RefTriangle, 2}()^2\nnothing # hide","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"All that is left is to solve the problem. We choose a value of Poissons ratio that results in incompressibility (ν = 05) and thus expect the linear/linear approximation to return garbage, and the quadratic/linear approximation to be stable.","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"u1 = solve(0.5, linear_u, linear_p);\nu2 = solve(0.5, quadratic_u, linear_p);\nnothing #hide","category":"page"},{"location":"tutorials/incompressible_elasticity/#incompressible_elasticity-plain-program","page":"Incompressible elasticity","title":"Plain program","text":"","category":"section"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"Here follows a version of the program without any comments. The file is also available here: incompressible_elasticity.jl.","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"using Ferrite, Tensors\nusing BlockArrays, SparseArrays, LinearAlgebra\n\nfunction create_cook_grid(nx, ny)\n corners = [\n Vec{2}((0.0, 0.0)),\n Vec{2}((48.0, 44.0)),\n Vec{2}((48.0, 60.0)),\n Vec{2}((0.0, 44.0)),\n ]\n grid = generate_grid(Triangle, (nx, ny), corners)\n # facesets for boundary conditions\n addfacetset!(grid, \"clamped\", x -> norm(x[1]) ≈ 0.0)\n addfacetset!(grid, \"traction\", x -> norm(x[1]) ≈ 48.0)\n return grid\nend;\n\nfunction create_values(interpolation_u, interpolation_p)\n # quadrature rules\n qr = QuadratureRule{RefTriangle}(3)\n facet_qr = FacetQuadratureRule{RefTriangle}(3)\n\n # cell and FacetValues for u\n cellvalues_u = CellValues(qr, interpolation_u)\n facetvalues_u = FacetValues(facet_qr, interpolation_u)\n\n # cellvalues for p\n cellvalues_p = CellValues(qr, interpolation_p)\n\n return cellvalues_u, cellvalues_p, facetvalues_u\nend;\n\nfunction create_dofhandler(grid, ipu, ipp)\n dh = DofHandler(grid)\n add!(dh, :u, ipu) # displacement\n add!(dh, :p, ipp) # pressure\n close!(dh)\n return dh\nend;\n\nfunction create_bc(dh)\n dbc = ConstraintHandler(dh)\n add!(dbc, Dirichlet(:u, getfacetset(dh.grid, \"clamped\"), x -> zero(x), [1, 2]))\n close!(dbc)\n return dbc\nend;\n\nstruct LinearElasticity{T}\n G::T\n K::T\nend\n\nfunction doassemble(\n cellvalues_u::CellValues,\n cellvalues_p::CellValues,\n facetvalues_u::FacetValues,\n K::SparseMatrixCSC, grid::Grid, dh::DofHandler, mp::LinearElasticity\n )\n f = zeros(ndofs(dh))\n assembler = start_assemble(K, f)\n nu = getnbasefunctions(cellvalues_u)\n np = getnbasefunctions(cellvalues_p)\n\n fe = BlockedArray(zeros(nu + np), [nu, np]) # local force vector\n ke = BlockedArray(zeros(nu + np, nu + np), [nu, np], [nu, np]) # local stiffness matrix\n\n # traction vector\n t = Vec{2}((0.0, 1 / 16))\n # cache ɛdev outside the element routine to avoid some unnecessary allocations\n ɛdev = [zero(SymmetricTensor{2, 2}) for i in 1:getnbasefunctions(cellvalues_u)]\n\n for cell in CellIterator(dh)\n fill!(ke, 0)\n fill!(fe, 0)\n assemble_up!(ke, fe, cell, cellvalues_u, cellvalues_p, facetvalues_u, grid, mp, ɛdev, t)\n assemble!(assembler, celldofs(cell), ke, fe)\n end\n\n return K, f\nend;\n\nfunction assemble_up!(Ke, fe, cell, cellvalues_u, cellvalues_p, facetvalues_u, grid, mp, ɛdev, t)\n\n n_basefuncs_u = getnbasefunctions(cellvalues_u)\n n_basefuncs_p = getnbasefunctions(cellvalues_p)\n u▄, p▄ = 1, 2\n reinit!(cellvalues_u, cell)\n reinit!(cellvalues_p, cell)\n\n # We only assemble lower half triangle of the stiffness matrix and then symmetrize it.\n for q_point in 1:getnquadpoints(cellvalues_u)\n for i in 1:n_basefuncs_u\n ɛdev[i] = dev(symmetric(shape_gradient(cellvalues_u, q_point, i)))\n end\n dΩ = getdetJdV(cellvalues_u, q_point)\n for i in 1:n_basefuncs_u\n divδu = shape_divergence(cellvalues_u, q_point, i)\n δu = shape_value(cellvalues_u, q_point, i)\n for j in 1:i\n Ke[BlockIndex((u▄, u▄), (i, j))] += 2 * mp.G * ɛdev[i] ⊡ ɛdev[j] * dΩ\n end\n end\n\n for i in 1:n_basefuncs_p\n δp = shape_value(cellvalues_p, q_point, i)\n for j in 1:n_basefuncs_u\n divδu = shape_divergence(cellvalues_u, q_point, j)\n Ke[BlockIndex((p▄, u▄), (i, j))] += -δp * divδu * dΩ\n end\n for j in 1:i\n p = shape_value(cellvalues_p, q_point, j)\n Ke[BlockIndex((p▄, p▄), (i, j))] += - 1 / mp.K * δp * p * dΩ\n end\n\n end\n end\n\n symmetrize_lower!(Ke)\n\n # We integrate the Neumann boundary using the FacetValues.\n # We loop over all the facets in the cell, then check if the facet\n # is in our `\"traction\"` facetset.\n for facet in 1:nfacets(cell)\n if (cellid(cell), facet) ∈ getfacetset(grid, \"traction\")\n reinit!(facetvalues_u, cell, facet)\n for q_point in 1:getnquadpoints(facetvalues_u)\n dΓ = getdetJdV(facetvalues_u, q_point)\n for i in 1:n_basefuncs_u\n δu = shape_value(facetvalues_u, q_point, i)\n fe[i] += (δu ⋅ t) * dΓ\n end\n end\n end\n end\n return\nend\n\nfunction symmetrize_lower!(Ke)\n for i in 1:size(Ke, 1)\n for j in (i + 1):size(Ke, 1)\n Ke[i, j] = Ke[j, i]\n end\n end\n return\nend;\n\nfunction compute_stresses(\n cellvalues_u::CellValues, cellvalues_p::CellValues,\n dh::DofHandler, mp::LinearElasticity, a::Vector\n )\n ae = zeros(ndofs_per_cell(dh)) # local solution vector\n u_range = dof_range(dh, :u) # local range of dofs corresponding to u\n p_range = dof_range(dh, :p) # local range of dofs corresponding to p\n # Allocate storage for the stresses\n σ = zeros(SymmetricTensor{2, 3}, getncells(dh.grid))\n # Loop over the cells and compute the cell-average stress\n for cc in CellIterator(dh)\n # Update cellvalues\n reinit!(cellvalues_u, cc)\n reinit!(cellvalues_p, cc)\n # Extract the cell local part of the solution\n for (i, I) in pairs(celldofs(cc))\n ae[i] = a[I]\n end\n # Loop over the quadrature points\n σΩi = zero(SymmetricTensor{2, 3}) # stress integrated over the cell\n Ωi = 0.0 # cell volume (area)\n for qp in 1:getnquadpoints(cellvalues_u)\n dΩ = getdetJdV(cellvalues_u, qp)\n # Evaluate the strain and the pressure\n ε = function_symmetric_gradient(cellvalues_u, qp, ae, u_range)\n p = function_value(cellvalues_p, qp, ae, p_range)\n # Expand strain to 3D\n ε3D = SymmetricTensor{2, 3}((i, j) -> i < 3 && j < 3 ? ε[i, j] : 0.0)\n # Compute the stress in this quadrature point\n σqp = 2 * mp.G * dev(ε3D) - one(ε3D) * p\n σΩi += σqp * dΩ\n Ωi += dΩ\n end\n # Store the value\n σ[cellid(cc)] = σΩi / Ωi\n end\n return σ\nend;\n\nfunction solve(ν, interpolation_u, interpolation_p)\n # material\n Emod = 1.0\n Gmod = Emod / 2(1 + ν)\n Kmod = Emod * ν / ((1 + ν) * (1 - 2ν))\n mp = LinearElasticity(Gmod, Kmod)\n\n # Grid, dofhandler, boundary condition\n n = 50\n grid = create_cook_grid(n, n)\n dh = create_dofhandler(grid, interpolation_u, interpolation_p)\n dbc = create_bc(dh)\n\n # CellValues\n cellvalues_u, cellvalues_p, facetvalues_u = create_values(interpolation_u, interpolation_p)\n\n # Assembly and solve\n K = allocate_matrix(dh)\n K, f = doassemble(cellvalues_u, cellvalues_p, facetvalues_u, K, grid, dh, mp)\n apply!(K, f, dbc)\n u = K \\ f\n\n # Compute the stress\n σ = compute_stresses(cellvalues_u, cellvalues_p, dh, mp, u)\n σvM = map(x -> √(3 / 2 * dev(x) ⊡ dev(x)), σ) # von Mise effective stress\n\n # Export the solution and the stress\n filename = \"cook_\" *\n (interpolation_u == Lagrange{RefTriangle, 1}()^2 ? \"linear\" : \"quadratic\") *\n \"_linear\"\n\n VTKGridFile(filename, grid) do vtk\n write_solution(vtk, dh, u)\n for i in 1:3, j in 1:3\n σij = [x[i, j] for x in σ]\n write_cell_data(vtk, σij, \"sigma_$(i)$(j)\")\n end\n write_cell_data(vtk, σvM, \"sigma von Mises\")\n end\n return u\nend\n\nlinear_p = Lagrange{RefTriangle, 1}()\nlinear_u = Lagrange{RefTriangle, 1}()^2\nquadratic_u = Lagrange{RefTriangle, 2}()^2\n\nu1 = solve(0.5, linear_u, linear_p);\nu2 = solve(0.5, quadratic_u, linear_p);","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"This page was generated using Literate.jl.","category":"page"},{"location":"reference/fevalues/","page":"FEValues","title":"FEValues","text":"CurrentModule = Ferrite\nDocTestSetup = :(using Ferrite)","category":"page"},{"location":"reference/fevalues/#FEValues","page":"FEValues","title":"FEValues","text":"","category":"section"},{"location":"reference/fevalues/#Main-types","page":"FEValues","title":"Main types","text":"","category":"section"},{"location":"reference/fevalues/","page":"FEValues","title":"FEValues","text":"CellValues and FacetValues are the most common subtypes of Ferrite.AbstractValues. For more details about how these work, please see the related topic guide.","category":"page"},{"location":"reference/fevalues/","page":"FEValues","title":"FEValues","text":"CellValues\nFacetValues","category":"page"},{"location":"reference/fevalues/#Ferrite.CellValues","page":"FEValues","title":"Ferrite.CellValues","text":"CellValues([::Type{T},] quad_rule::QuadratureRule, func_interpol::Interpolation, [geom_interpol::Interpolation])\n\nA CellValues object facilitates the process of evaluating values of shape functions, gradients of shape functions, values of nodal functions, gradients and divergences of nodal functions etc. in the finite element cell.\n\nArguments:\n\nT: an optional argument (default to Float64) to determine the type the internal data is stored as.\nquad_rule: an instance of a QuadratureRule\nfunc_interpol: an instance of an Interpolation used to interpolate the approximated function\ngeom_interpol: an optional instance of a Interpolation which is used to interpolate the geometry. By default linear Lagrange interpolation is used. For embedded elements the geometric interpolations should be vectorized to the spatial dimension.\n\nKeyword arguments: The following keyword arguments are experimental and may change in future minor releases\n\nupdate_gradients: Specifies if the gradients of the shape functions should be updated (default true)\nupdate_hessians: Specifies if the hessians of the shape functions should be updated (default false)\nupdate_detJdV: Specifies if the volume associated with each quadrature point should be updated (default true)\n\nCommon methods:\n\nreinit!\ngetnquadpoints\ngetdetJdV\nshape_value\nshape_gradient\nshape_symmetric_gradient\nshape_divergence\nfunction_value\nfunction_gradient\nfunction_symmetric_gradient\nfunction_divergence\nspatial_coordinate\n\n\n\n\n\n","category":"type"},{"location":"reference/fevalues/#Ferrite.FacetValues","page":"FEValues","title":"Ferrite.FacetValues","text":"FacetValues([::Type{T}], quad_rule::FacetQuadratureRule, func_interpol::Interpolation, [geom_interpol::Interpolation])\n\nA FacetValues object facilitates the process of evaluating values of shape functions, gradients of shape functions, values of nodal functions, gradients and divergences of nodal functions etc. on the facets of finite elements.\n\nArguments:\n\nT: an optional argument (default to Float64) to determine the type the internal data is stored as.\nquad_rule: an instance of a FacetQuadratureRule\nfunc_interpol: an instance of an Interpolation used to interpolate the approximated function\ngeom_interpol: an optional instance of an Interpolation which is used to interpolate the geometry. By default linear Lagrange interpolation is used.\n\nKeyword arguments: The following keyword arguments are experimental and may change in future minor releases\n\nupdate_gradients: Specifies if the gradients of the shape functions should be updated (default true)\nupdate_hessians: Specifies if the hessians of the shape functions should be updated (default false)\n\nCommon methods:\n\nreinit!\ngetnquadpoints\ngetdetJdV\nshape_value\nshape_gradient\nshape_symmetric_gradient\nshape_divergence\nfunction_value\nfunction_gradient\nfunction_symmetric_gradient\nfunction_divergence\nspatial_coordinate\n\n\n\n\n\n","category":"type"},{"location":"reference/fevalues/","page":"FEValues","title":"FEValues","text":"warning: Embedded API\nCurrently, embedded FEValues returns SArrays, which behave differently from the Tensors for normal value. In the future, we expect to return an AbstractTensor, this change may happen in a minor release, and the API for embedded FEValues should therefore be considered experimental.","category":"page"},{"location":"reference/fevalues/#Applicable-functions","page":"FEValues","title":"Applicable functions","text":"","category":"section"},{"location":"reference/fevalues/","page":"FEValues","title":"FEValues","text":"The following functions are applicable to both CellValues and FacetValues.","category":"page"},{"location":"reference/fevalues/","page":"FEValues","title":"FEValues","text":"reinit!\ngetnquadpoints\ngetdetJdV\n\nshape_value(::Ferrite.AbstractValues, ::Int, ::Int)\nshape_gradient(::Ferrite.AbstractValues, ::Int, ::Int)\nshape_symmetric_gradient\nshape_divergence\nshape_curl\ngeometric_value\n\nfunction_value\nfunction_gradient\nfunction_symmetric_gradient\nfunction_divergence\nfunction_curl\nspatial_coordinate","category":"page"},{"location":"reference/fevalues/#Ferrite.reinit!","page":"FEValues","title":"Ferrite.reinit!","text":"reinit!(cv::CellValues, cell::AbstractCell, x::AbstractVector)\nreinit!(cv::CellValues, x::AbstractVector)\nreinit!(fv::FacetValues, cell::AbstractCell, x::AbstractVector, facet::Int)\nreinit!(fv::FacetValues, x::AbstractVector, function_gradient::Int)\n\nUpdate the CellValues/FacetValues object for a cell or facet with cell coordinates x. The derivatives of the shape functions, and the new integration weights are computed. For interpolations with non-identity mappings, the current cell is also required.\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.getnquadpoints","page":"FEValues","title":"Ferrite.getnquadpoints","text":"getnquadpoints(fe_v::AbstractValues)\n\nReturn the number of quadrature points. For FacetValues, this is the number for the current facet.\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.getdetJdV","page":"FEValues","title":"Ferrite.getdetJdV","text":"getdetJdV(fe_v::AbstractValues, q_point::Int)\n\nReturn the product between the determinant of the Jacobian and the quadrature point weight for the given quadrature point: det(J(mathbfx)) w_q.\n\nThis value is typically used when one integrates a function on a finite element cell or facet as\n\nintlimits_Omega f(mathbfx) d Omega approx sumlimits_q = 1^n_q f(mathbfx_q) det(J(mathbfx)) w_q intlimits_Gamma f(mathbfx) d Gamma approx sumlimits_q = 1^n_q f(mathbfx_q) det(J(mathbfx)) w_q\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.shape_value-Tuple{Ferrite.AbstractValues, Int64, Int64}","page":"FEValues","title":"Ferrite.shape_value","text":"shape_value(fe_v::AbstractValues, q_point::Int, base_function::Int)\n\nReturn the value of shape function base_function evaluated in quadrature point q_point.\n\n\n\n\n\n","category":"method"},{"location":"reference/fevalues/#Ferrite.shape_gradient-Tuple{Ferrite.AbstractValues, Int64, Int64}","page":"FEValues","title":"Ferrite.shape_gradient","text":"shape_gradient(fe_v::AbstractValues, q_point::Int, base_function::Int)\n\nReturn the gradient of shape function base_function evaluated in quadrature point q_point.\n\n\n\n\n\n","category":"method"},{"location":"reference/fevalues/#Ferrite.shape_symmetric_gradient","page":"FEValues","title":"Ferrite.shape_symmetric_gradient","text":"shape_symmetric_gradient(fe_v::AbstractValues, q_point::Int, base_function::Int)\n\nReturn the symmetric gradient of shape function base_function evaluated in quadrature point q_point.\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.shape_divergence","page":"FEValues","title":"Ferrite.shape_divergence","text":"shape_divergence(fe_v::AbstractValues, q_point::Int, base_function::Int)\n\nReturn the divergence of shape function base_function evaluated in quadrature point q_point.\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.shape_curl","page":"FEValues","title":"Ferrite.shape_curl","text":"shape_curl(fe_v::AbstractValues, q_point::Int, base_function::Int)\n\nReturn the curl of shape function base_function evaluated in quadrature point q_point.\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.geometric_value","page":"FEValues","title":"Ferrite.geometric_value","text":"geometric_value(fe_v::AbstractValues, q_point, base_function::Int)\n\nReturn the value of the geometric shape function base_function evaluated in quadrature point q_point.\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.function_value","page":"FEValues","title":"Ferrite.function_value","text":"function_value(iv::InterfaceValues, q_point::Int, u; here::Bool)\nfunction_value(iv::InterfaceValues, q_point::Int, u, dof_range_here, dof_range_there; here::Bool)\n\nCompute the value of the function in quadrature point q_point on the \"here\" (here=true) or \"there\" (here=false) side of the interface. u_here and u_there are the values of the degrees of freedom for the respective element.\n\nu is a vector of scalar values for the degrees of freedom. This function can be used with a single u vector containing the dofs of both elements of the interface or two vectors (u_here and u_there) which contain the dofs of each cell of the interface respectively.\n\nhere determines which element to use for calculating function value. true uses the value on the first element's side of the interface, while false uses the value on the second element's side.\n\nThe value of a scalar valued function is computed as u(mathbfx) = sumlimits_i = 1^n N_i (mathbfx) u_i where u_i are the value of u in the nodes. For a vector valued function the value is calculated as mathbfu(mathbfx) = sumlimits_i = 1^n N_i (mathbfx) mathbfu_i where mathbfu_i are the nodal values of mathbfu.\n\n\n\n\n\nfunction_value(fe_v::AbstractValues, q_point::Int, u::AbstractVector, [dof_range])\n\nCompute the value of the function in a quadrature point. u is a vector with values for the degrees of freedom. For a scalar valued function, u contains scalars. For a vector valued function, u can be a vector of scalars (for use of VectorValues) or u can be a vector of Vecs (for use with ScalarValues).\n\nThe value of a scalar valued function is computed as u(mathbfx) = sumlimits_i = 1^n N_i (mathbfx) u_i where u_i are the value of u in the nodes. For a vector valued function the value is calculated as mathbfu(mathbfx) = sumlimits_i = 1^n N_i (mathbfx) mathbfu_i where mathbfu_i are the nodal values of mathbfu.\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.function_gradient","page":"FEValues","title":"Ferrite.function_gradient","text":"function_gradient(iv::InterfaceValues, q_point::Int, u; here::Bool)\nfunction_gradient(iv::InterfaceValues, q_point::Int, u, dof_range_here, dof_range_there; here::Bool)\n\nCompute the gradient of the function in a quadrature point. u is a vector of scalar values for the degrees of freedom. This function can be used with a single u vector containing the dofs of both elements of the interface or two vectors (u_here and u_there) which contain the dofs of each cell of the interface respectively.\n\nhere determines which element to use for calculating function value. true uses the value on the first element's side of the interface, while false uses the value on the second element's side.\n\nThe gradient of a scalar function or a vector valued function with use of VectorValues is computed as mathbfnabla u(mathbfx) = sumlimits_i = 1^n mathbfnabla N_i (mathbfx) u_i or mathbfnabla u(mathbfx) = sumlimits_i = 1^n mathbfnabla mathbfN_i (mathbfx) u_i respectively, where u_i are the nodal values of the function. For a vector valued function with use of ScalarValues the gradient is computed as mathbfnabla mathbfu(mathbfx) = sumlimits_i = 1^n mathbfu_i otimes mathbfnabla N_i (mathbfx) where mathbfu_i are the nodal values of mathbfu.\n\n\n\n\n\nfunction_gradient(fe_v::AbstractValues{dim}, q_point::Int, u::AbstractVector, [dof_range])\n\nCompute the gradient of the function in a quadrature point. u is a vector with values for the degrees of freedom. For a scalar valued function, u contains scalars. For a vector valued function, u can be a vector of scalars (for use of VectorValues) or u can be a vector of Vecs (for use with ScalarValues).\n\nThe gradient of a scalar function or a vector valued function with use of VectorValues is computed as mathbfnabla u(mathbfx) = sumlimits_i = 1^n mathbfnabla N_i (mathbfx) u_i or mathbfnabla u(mathbfx) = sumlimits_i = 1^n mathbfnabla mathbfN_i (mathbfx) u_i respectively, where u_i are the nodal values of the function. For a vector valued function with use of ScalarValues the gradient is computed as mathbfnabla mathbfu(mathbfx) = sumlimits_i = 1^n mathbfu_i otimes mathbfnabla N_i (mathbfx) where mathbfu_i are the nodal values of mathbfu.\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.function_symmetric_gradient","page":"FEValues","title":"Ferrite.function_symmetric_gradient","text":"function_symmetric_gradient(fe_v::AbstractValues, q_point::Int, u::AbstractVector, [dof_range])\n\nCompute the symmetric gradient of the function, see function_gradient. Return a SymmetricTensor.\n\nThe symmetric gradient of a scalar function is computed as left mathbfnabla mathbfu(mathbfx_q) right^textsym = sumlimits_i = 1^n frac12 left mathbfnabla N_i (mathbfx_q) otimes mathbfu_i + mathbfu_i otimes mathbfnabla N_i (mathbfx_q) right where mathbfu_i are the nodal values of the function.\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.function_divergence","page":"FEValues","title":"Ferrite.function_divergence","text":"function_divergence(fe_v::AbstractValues, q_point::Int, u::AbstractVector, [dof_range])\n\nCompute the divergence of the vector valued function in a quadrature point.\n\nThe divergence of a vector valued functions in the quadrature point mathbfx_q) is computed as mathbfnabla cdot mathbfu(mathbfx_q) = sumlimits_i = 1^n mathbfnabla N_i (mathbfx_q) cdot mathbfu_i where mathbfu_i are the nodal values of the function.\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.function_curl","page":"FEValues","title":"Ferrite.function_curl","text":"function_curl(fe_v::AbstractValues, q_point::Int, u::AbstractVector, [dof_range])\n\nCompute the curl of the vector valued function in a quadrature point.\n\nThe curl of a vector valued functions in the quadrature point mathbfx_q) is computed as mathbfnabla times mathbfu(mathbfx_q) = sumlimits_i = 1^n mathbfnabla N_i times (mathbfx_q) cdot mathbfu_i where mathbfu_i are the nodal values of the function.\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.spatial_coordinate","page":"FEValues","title":"Ferrite.spatial_coordinate","text":"spatial_coordinate(fe_v::AbstractValues, q_point::Int, x::AbstractVector)\n\nCompute the spatial coordinate in a quadrature point. x contains the nodal coordinates of the cell.\n\nThe coordinate is computed, using the geometric interpolation, as mathbfx = sumlimits_i = 1^n M_i (mathbfxi) mathbfhatx_i.\n\nwhere xiis the coordinate of the given quadrature point q_point of the associated quadrature rule.\n\n\n\n\n\nspatial_coordinate(ip::ScalarInterpolation, ξ::Vec, x::AbstractVector{<:Vec{sdim, T}})\n\nCompute the spatial coordinate in a given quadrature point. x contains the nodal coordinates of the cell.\n\nThe coordinate is computed, using the geometric interpolation, as mathbfx = sumlimits_i = 1^n M_i (mathbfxi) mathbfhatx_i\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/","page":"FEValues","title":"FEValues","text":"In addition, there are some methods that are unique for FacetValues.","category":"page"},{"location":"reference/fevalues/","page":"FEValues","title":"FEValues","text":"Ferrite.getcurrentfacet\ngetnormal","category":"page"},{"location":"reference/fevalues/#Ferrite.getcurrentfacet","page":"FEValues","title":"Ferrite.getcurrentfacet","text":"getcurrentfacet(fv::FacetValues)\n\nReturn the current active facet of the FacetValues object (from last reinit!).\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.getnormal","page":"FEValues","title":"Ferrite.getnormal","text":"getnormal(fv::FacetValues, qp::Int)\n\nReturn the normal at the quadrature point qp for the active facet of the FacetValues object(from last reinit!).\n\n\n\n\n\ngetnormal(iv::InterfaceValues, qp::Int; here::Bool=true)\n\nReturn the normal vector in the quadrature point qp on the interface. If here = true (default) the outward normal to the \"here\" element is returned, otherwise the outward normal to the \"there\" element.\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#reference-interfacevalues","page":"FEValues","title":"InterfaceValues","text":"","category":"section"},{"location":"reference/fevalues/","page":"FEValues","title":"FEValues","text":"All of the methods for FacetValues apply for InterfaceValues as well. In addition, there are some methods that are unique for InterfaceValues:","category":"page"},{"location":"reference/fevalues/","page":"FEValues","title":"FEValues","text":"InterfaceValues\nshape_value_average\nshape_value_jump\nshape_gradient_average\nshape_gradient_jump\nfunction_value_average\nfunction_value_jump\nfunction_gradient_average\nfunction_gradient_jump","category":"page"},{"location":"reference/fevalues/#Ferrite.InterfaceValues","page":"FEValues","title":"Ferrite.InterfaceValues","text":"InterfaceValues\n\nAn InterfaceValues object facilitates the process of evaluating values, averages, jumps and gradients of shape functions and function on the interfaces between elements.\n\nThe first element of the interface is denoted \"here\" and the second element \"there\".\n\nConstructors\n\nInterfaceValues(qr::FacetQuadratureRule, ip::Interpolation): same quadrature rule and interpolation on both sides, default linear Lagrange geometric interpolation.\nInterfaceValues(qr::FacetQuadratureRule, ip::Interpolation, ip_geo::Interpolation): same as above but with given geometric interpolation.\nInterfaceValues(qr_here::FacetQuadratureRule, ip_here::Interpolation, qr_there::FacetQuadratureRule, ip_there::Interpolation): different quadrature rule and interpolation on the two sides, default linear Lagrange geometric interpolation.\nInterfaceValues(qr_here::FacetQuadratureRule, ip_here::Interpolation, ip_geo_here::Interpolation, qr_there::FacetQuadratureRule, ip_there::Interpolation, ip_geo_there::Interpolation): same as above but with given geometric interpolation.\nInterfaceValues(fv::FacetValues): quadrature rule and interpolations from face values (same on both sides).\nInterfaceValues(fv_here::FacetValues, fv_there::FacetValues): quadrature rule and interpolations from the face values.\n\nAssociated methods:\n\nshape_value_average\nshape_value_jump\nshape_gradient_average\nshape_gradient_jump\n\nCommon methods:\n\nreinit!\ngetnquadpoints\ngetdetJdV\nshape_value\nshape_gradient\nshape_divergence\nshape_curl\nfunction_value\nfunction_gradient\nfunction_symmetric_gradient\nfunction_divergence\nfunction_curl\nspatial_coordinate\n\n\n\n\n\n","category":"type"},{"location":"reference/fevalues/#Ferrite.shape_value_average","page":"FEValues","title":"Ferrite.shape_value_average","text":"shape_value_average(iv::InterfaceValues, qp::Int, i::Int)\n\nCompute the average of the value of shape function i at quadrature point qp across the interface.\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.shape_value_jump","page":"FEValues","title":"Ferrite.shape_value_jump","text":"shape_value_jump(iv::InterfaceValues, qp::Int, i::Int)\n\nCompute the jump of the value of shape function i at quadrature point qp across the interface in the default normal direction.\n\nThis function uses the definition llbracket vecv rrbracket=vecv^textthere -vecv^texthere. To obtain the form, llbracket vecv rrbracket=vecv^textthere cdot vecn^textthere + vecv^texthere cdot vecn^texthere, multiply by minus the outward facing normal to the first element's side of the interface (which is the default normal for getnormal with InterfaceValues).\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.shape_gradient_average","page":"FEValues","title":"Ferrite.shape_gradient_average","text":"shape_gradient_average(iv::InterfaceValues, qp::Int, i::Int)\n\nCompute the average of the gradient of shape function i at quadrature point qp across the interface.\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.shape_gradient_jump","page":"FEValues","title":"Ferrite.shape_gradient_jump","text":"shape_gradient_jump(iv::InterfaceValues, qp::Int, i::Int)\n\nCompute the jump of the gradient of shape function i at quadrature point qp across the interface in the default normal direction.\n\nThis function uses the definition llbracket vecv rrbracket=vecv^textthere -vecv^texthere. To obtain the form, llbracket vecv rrbracket=vecv^textthere vecn^textthere + vecv^texthere vecn^texthere, multiply by minus the outward facing normal to the first element's side of the interface (which is the default normal for getnormal with InterfaceValues).\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.function_value_average","page":"FEValues","title":"Ferrite.function_value_average","text":"function_value_average(iv::InterfaceValues, q_point::Int, u)\nfunction_value_average(iv::InterfaceValues, q_point::Int, u, dof_range_here, dof_range_there)\n\nCompute the average of the function value at the quadrature point on the interface.\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.function_value_jump","page":"FEValues","title":"Ferrite.function_value_jump","text":"function_value_jump(iv::InterfaceValues, q_point::Int, u)\nfunction_value_jump(iv::InterfaceValues, q_point::Int, u, dof_range_here, dof_range_there)\n\nCompute the jump of the function value at the quadrature point over the interface along the default normal direction.\n\nThis function uses the definition llbracket vecv rrbracket=vecv^textthere -vecv^texthere. To obtain the form, llbracket vecv rrbracket=vecv^textthere vecn^textthere + vecv^texthere vecn^texthere, multiply by minus the outward facing normal to the first element's side of the interface (which is the default normal for getnormal with InterfaceValues).\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.function_gradient_average","page":"FEValues","title":"Ferrite.function_gradient_average","text":"function_gradient_average(iv::InterfaceValues, q_point::Int, u)\nfunction_gradient_average(iv::InterfaceValues, q_point::Int, u, dof_range_here, dof_range_there)\n\nCompute the average of the function gradient at the quadrature point on the interface.\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.function_gradient_jump","page":"FEValues","title":"Ferrite.function_gradient_jump","text":"function_gradient_jump(iv::InterfaceValues, q_point::Int, u)\nfunction_gradient_jump(iv::InterfaceValues, q_point::Int, u, dof_range_here, dof_range_there)\n\nCompute the jump of the function gradient at the quadrature point over the interface along the default normal direction.\n\nThis function uses the definition llbracket vecv rrbracket=vecv^textthere -vecv^texthere. To obtain the form, llbracket vecv rrbracket=vecv^textthere vecn^textthere + vecv^texthere vecn^texthere, multiply by minus the outward facing normal to the first element's side of the interface (which is the default normal for getnormal with InterfaceValues).\n\n\n\n\n\n","category":"function"},{"location":"reference/assembly/","page":"Assembly","title":"Assembly","text":"DocTestSetup = :(using Ferrite)","category":"page"},{"location":"reference/assembly/#Assembly","page":"Assembly","title":"Assembly","text":"","category":"section"},{"location":"reference/assembly/","page":"Assembly","title":"Assembly","text":"start_assemble\nassemble!\nfinish_assemble","category":"page"},{"location":"reference/assembly/#Ferrite.start_assemble","page":"Assembly","title":"Ferrite.start_assemble","text":"start_assemble(K::AbstractSparseMatrixCSC; fillzero::Bool=true) -> CSCAssembler\nstart_assemble(K::AbstractSparseMatrixCSC, f::Vector; fillzero::Bool=true) -> CSCAssembler\n\nCreate a CSCAssembler from the matrix K and optional vector f.\n\nstart_assemble(K::Symmetric{AbstractSparseMatrixCSC}; fillzero::Bool=true) -> SymmetricCSCAssembler\nstart_assemble(K::Symmetric{AbstractSparseMatrixCSC}, f::Vector=Td[]; fillzero::Bool=true) -> SymmetricCSCAssembler\n\nCreate a SymmetricCSCAssembler from the matrix K and optional vector f.\n\nCSCAssembler and SymmetricCSCAssembler allocate workspace necessary for efficient matrix assembly. To assemble the contribution from an element, use assemble!.\n\nThe keyword argument fillzero can be set to false if K and f should not be zeroed out, but instead keep their current values.\n\n\n\n\n\n","category":"function"},{"location":"reference/assembly/#Ferrite.assemble!","page":"Assembly","title":"Ferrite.assemble!","text":"assemble!(a::COOAssembler, dofs, Ke)\nassemble!(a::COOAssembler, dofs, Ke, fe)\n\nAssembles the element matrix Ke and element vector fe into a.\n\n\n\n\n\nassemble!(a::COOAssembler, rowdofs, coldofs, Ke)\n\nAssembles the matrix Ke into a according to the dofs specified by rowdofs and coldofs.\n\n\n\n\n\nassemble!(g, dofs, ge)\n\nAssembles the element residual ge into the global residual vector g.\n\n\n\n\n\nassemble!(A::AbstractAssembler, dofs::AbstractVector{Int}, Ke::AbstractMatrix)\nassemble!(A::AbstractAssembler, dofs::AbstractVector{Int}, Ke::AbstractMatrix, fe::AbstractVector)\n\nAssemble the element stiffness matrix Ke (and optional force vector fe) into the global stiffness (and force) in A, given the element degrees of freedom dofs.\n\nThis is equivalent to K[dofs, dofs] += Ke and f[dofs] += fe, where K is the global stiffness matrix and f the global force/residual vector, but more efficient.\n\n\n\n\n\n","category":"function"},{"location":"reference/assembly/#Ferrite.finish_assemble","page":"Assembly","title":"Ferrite.finish_assemble","text":"finish_assemble(a::COOAssembler) -> K, f\n\nFinalize the assembly and return the sparse matrix K::SparseMatrixCSC and vector f::Vector. If the assembler have not been used for vector assembly, f is an empty vector.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/performance/#devdocs-performance","page":"Performance analysis","title":"Performance analysis","text":"","category":"section"},{"location":"devdocs/performance/","page":"Performance analysis","title":"Performance analysis","text":"In the benchmark folder we provide basic infrastructure to analyze the performance of Ferrite to help tracking down performance regression issues. Two basic tools can be directly executed via make: A basic benchmark for the current branch and a comparison between two commits. To execute the benchmark on the current branch only open a shell in the benchmark folder and call","category":"page"},{"location":"devdocs/performance/","page":"Performance analysis","title":"Performance analysis","text":"make benchmark","category":"page"},{"location":"devdocs/performance/","page":"Performance analysis","title":"Performance analysis","text":"whereas for the comparison of two commits simply call","category":"page"},{"location":"devdocs/performance/","page":"Performance analysis","title":"Performance analysis","text":"make compare target= baseline=","category":"page"},{"location":"devdocs/performance/","page":"Performance analysis","title":"Performance analysis","text":"If you have a custom julia executable that is not accessible via the julia command, then you can pass the executable via","category":"page"},{"location":"devdocs/performance/","page":"Performance analysis","title":"Performance analysis","text":"JULIA_CMD= make compare target= baseline=","category":"page"},{"location":"devdocs/performance/","page":"Performance analysis","title":"Performance analysis","text":"note: Note\nFor the performance comparison between two commits you must not have any uncommitted or untracked files in your Ferrite.jl folder! Otherwise the PkgBenchmark.jl will fail to setup the comparison.","category":"page"},{"location":"devdocs/performance/","page":"Performance analysis","title":"Performance analysis","text":"For more fine grained control you can run subsets of the benchmarks via by appending - to compare or benchmark, e.g.","category":"page"},{"location":"devdocs/performance/","page":"Performance analysis","title":"Performance analysis","text":"make benchmark-mesh","category":"page"},{"location":"devdocs/performance/","page":"Performance analysis","title":"Performance analysis","text":"to benchmark only the mesh functionality. The following subsets are currently available:","category":"page"},{"location":"devdocs/performance/","page":"Performance analysis","title":"Performance analysis","text":"assembly\nboundary-conditions\ndofs\nmesh","category":"page"},{"location":"devdocs/performance/","page":"Performance analysis","title":"Performance analysis","text":"note: Note\nIt is recommended to run all benchmarks before running subsets to get the correct tuning parameters for each benchmark.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"EditURL = \"../literate-gallery/topology_optimization.jl\"","category":"page"},{"location":"gallery/topology_optimization/#tutorial-topology-optimization","page":"Topology optimization","title":"Topology optimization","text":"","category":"section"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"Keywords: Topology optimization, weak and strong form, non-linear problem, Laplacian, grid topology","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"(Image: )","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"Figure 1: Optimization of the bending beam. Evolution of the density for fixed total mass.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"tip: Tip\nThis example is also available as a Jupyter notebook: topology_optimization.ipynb.","category":"page"},{"location":"gallery/topology_optimization/#Introduction","page":"Topology optimization","title":"Introduction","text":"","category":"section"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"Topology optimization is the task of finding structures that are mechanically ideal. In this example we cover the bending beam, where we specify a load, boundary conditions and the total mass. Then, our objective is to find the most suitable geometry within the design space minimizing the compliance (i.e. the inverse stiffness) of the structure. We shortly introduce our simplified model for regular meshes. A detailed derivation of the method and advanced techniques can be found in [16] and [17].","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"We start by introducing the local, elementwise density chi in chi_textmin 1 of the material, where we choose chi_textmin slightly above zero to prevent numerical instabilities. Here, chi = chi_textmin means void and chi=1 means bulk material. Then, we use a SIMP ansatz (solid isotropic material with penalization) for the stiffness tensor C(chi) = chi^p C_0, where C_0 is the stiffness of the bulk material. The SIMP exponent p1 ensures that the model prefers the density values void and bulk before the intermediate values. The variational formulation then yields the modified Gibbs energy","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"G = int_Omega frac12 chi^p varepsilon C varepsilon textdV - int_Omega boldsymbolf cdot boldsymbolu textdV - int_partialOmega boldsymbolt cdot boldsymbolu textdA","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"Furthermore, we receive the evolution equation of the density and the additional Neumann boundary condition in the strong form","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"p_chi + eta dotchi + lambda + gamma - beta nabla^2 chi ni 0 quad forall textbfx in Omega","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"beta nabla chi cdot textbfn = 0 quad forall textbfx in partial Omega","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"with the thermodynamic driving force","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"p_chi = frac12 p chi^p-1 varepsilon C varepsilon","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"We obtain the mechanical displacement field by applying the Finite Element Method to the weak form of the Gibbs energy using Ferrite. In contrast, we use the evolution equation (i.e. the strong form) to calculate the value of the density field chi. The advantage of this \"split\" approach is the very high computation speed. The evolution equation consists of the driving force, the damping parameter eta, the regularization parameter beta times the Laplacian, which is necessary to avoid numerical issues like mesh dependence or checkerboarding, and the constraint parameters lambda, to keep the mass constant, and gamma, to avoid leaving the set chi_textmin 1. By including gradient regularization, it becomes necessary to calculate the Laplacian. The Finite Difference Method for square meshes with the edge length Delta h approximates the Laplacian as follows:","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"nabla^2 chi_p = frac1(Delta h)^2 (chi_n + chi_s + chi_w + chi_e - 4 chi_p)","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"Here, the indices refer to the different cardinal directions. Boundary element do not have neighbors in each direction. However, we can calculate the central difference to fulfill Neumann boundary condition. For example, if the element is on the left boundary, we have to fulfill","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"nabla chi_p cdot textbfn = frac1Delta h (chi_w - chi_e) = 0","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"from which follows chi_w = chi_e. Thus for boundary elements we can replace the value for the missing neighbor by the value of the opposite neighbor. In order to find the corresponding neighbor elements, we will make use of Ferrites grid topology funcionalities.","category":"page"},{"location":"gallery/topology_optimization/#Commented-Program","page":"Topology optimization","title":"Commented Program","text":"","category":"section"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"We now solve the problem in Ferrite. What follows is a program spliced with comments. The full program, without comments, can be found in the next section.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"First we load all necessary packages.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"using Ferrite, SparseArrays, LinearAlgebra, Tensors, Printf","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"Next, we create a simple square grid of the size 2x1. We apply a fixed Dirichlet boundary condition to the left facet set, called clamped. On the right facet, we create a small set traction, where we will later apply a force in negative y-direction.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"function create_grid(n)\n corners = [\n Vec{2}((0.0, 0.0)),\n Vec{2}((2.0, 0.0)),\n Vec{2}((2.0, 1.0)),\n Vec{2}((0.0, 1.0)),\n ]\n grid = generate_grid(Quadrilateral, (2 * n, n), corners)\n\n # node-/facesets for boundary conditions\n addnodeset!(grid, \"clamped\", x -> x[1] ≈ 0.0)\n addfacetset!(grid, \"traction\", x -> x[1] ≈ 2.0 && norm(x[2] - 0.5) <= 0.05)\n return grid\nend\nnothing # hide","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"Next, we create the FE values, the DofHandler and the Dirichlet boundary condition.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"function create_values()\n # quadrature rules\n qr = QuadratureRule{RefQuadrilateral}(2)\n facet_qr = FacetQuadratureRule{RefQuadrilateral}(2)\n\n # cell and facetvalues for u\n ip = Lagrange{RefQuadrilateral, 1}()^2\n cellvalues = CellValues(qr, ip)\n facetvalues = FacetValues(facet_qr, ip)\n\n return cellvalues, facetvalues\nend\n\nfunction create_dofhandler(grid)\n dh = DofHandler(grid)\n add!(dh, :u, Lagrange{RefQuadrilateral, 1}()^2) # displacement\n close!(dh)\n return dh\nend\n\nfunction create_bc(dh)\n dbc = ConstraintHandler(dh)\n add!(dbc, Dirichlet(:u, getnodeset(dh.grid, \"clamped\"), (x, t) -> zero(Vec{2}), [1, 2]))\n close!(dbc)\n t = 0.0\n update!(dbc, t)\n return dbc\nend\nnothing # hide","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"Now, we define a struct to store all necessary material parameters (stiffness tensor of the bulk material and the parameters for topology optimization) and add a constructor to the struct to initialize it by using the common material parameters Young's modulus and Poisson number.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"struct MaterialParameters{T, S <: SymmetricTensor{4, 2, T}}\n C::S\n χ_min::T\n p::T\n β::T\n η::T\nend\nnothing # hide\n\nfunction MaterialParameters(E, ν, χ_min, p, β, η)\n δ(i, j) = i == j ? 1.0 : 0.0 # helper function\n\n G = E / 2(1 + ν) # =μ\n λ = E * ν / (1 - ν^2) # correction for plane stress included\n\n C = SymmetricTensor{4, 2}((i, j, k, l) -> λ * δ(i, j) * δ(k, l) + G * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k)))\n return MaterialParameters(C, χ_min, p, β, η)\nend\nnothing # hide","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"To store the density and the strain required to calculate the driving forces, we create the struct MaterialState. We add a constructor to initialize the struct. The function update_material_states! updates the density values once we calculated the new values.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"mutable struct MaterialState{T, S <: AbstractArray{SymmetricTensor{2, 2, T, 3}, 1}}\n χ::T # density\n ε::S # strain in each quadrature point\nend\n\nfunction MaterialState(ρ, n_qp)\n return MaterialState(ρ, Array{SymmetricTensor{2, 2, Float64, 3}, 1}(undef, n_qp))\nend\n\nfunction update_material_states!(χn1, states, dh)\n for (element, state) in zip(CellIterator(dh), states)\n state.χ = χn1[cellid(element)]\n end\n return\nend\nnothing # hide","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"Next, we define a function to calculate the driving forces for all elements. For this purpose, we iterate through all elements and calculate the average strain in each element. Then, we compute the driving force from the formula introduced at the beginning. We create a second function to collect the density in each element.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"function compute_driving_forces(states, mp, dh, χn)\n pΨ = zeros(length(states))\n for (element, state) in zip(CellIterator(dh), states)\n i = cellid(element)\n ε = sum(state.ε) / length(state.ε) # average element strain\n pΨ[i] = 1 / 2 * mp.p * χn[i]^(mp.p - 1) * (ε ⊡ mp.C ⊡ ε)\n end\n return pΨ\nend\n\nfunction compute_densities(states, dh)\n χn = zeros(length(states))\n for (element, state) in zip(CellIterator(dh), states)\n i = cellid(element)\n χn[i] = state.χ\n end\n return χn\nend\nnothing # hide","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"For the Laplacian we need some neighboorhood information which is constant throughout the analysis so we compute it once and cache it. We iterate through each facet of each element, obtaining the neighboring element by using the getneighborhood function. For boundary facets, the function call will return an empty object. In that case we use the dictionary to instead find the opposite facet, as discussed in the introduction.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"function cache_neighborhood(dh, topology)\n nbgs = Vector{Vector{Int}}(undef, getncells(dh.grid))\n _nfacets = nfacets(dh.grid.cells[1])\n opp = Dict(1 => 3, 2 => 4, 3 => 1, 4 => 2)\n\n for element in CellIterator(dh)\n nbg = zeros(Int, _nfacets)\n i = cellid(element)\n for j in 1:_nfacets\n nbg_cellid = getneighborhood(topology, dh.grid, FacetIndex(i, j))\n if !isempty(nbg_cellid)\n nbg[j] = first(nbg_cellid)[1] # assuming only one facet neighbor per cell\n else # boundary facet\n nbg[j] = first(getneighborhood(topology, dh.grid, FacetIndex(i, opp[j])))[1]\n end\n end\n\n nbgs[i] = nbg\n end\n\n return nbgs\nend\nnothing # hide","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"Now we calculate the Laplacian using the previously cached neighboorhood information.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"function approximate_laplacian(nbgs, χn, Δh)\n ∇²χ = zeros(length(nbgs))\n for i in 1:length(nbgs)\n nbg = nbgs[i]\n ∇²χ[i] = (χn[nbg[1]] + χn[nbg[2]] + χn[nbg[3]] + χn[nbg[4]] - 4 * χn[i]) / (Δh^2)\n end\n\n return ∇²χ\nend\nnothing # hide","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"For the iterative computation of the solution, a function is needed to update the densities in each element. To ensure that the mass is kept constant, we have to calculate the constraint parameter lambda, which we do via the bisection method. We repeat the calculation until the difference between the average density (calculated from the element-wise trial densities) and the target density nearly vanishes. By using the extremal values of Delta chi as the starting interval, we guarantee that the method converges eventually.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"function compute_χn1(χn, Δχ, ρ, ηs, χ_min)\n n_el = length(χn)\n\n χ_trial = zeros(n_el)\n ρ_trial = 0.0\n\n λ_lower = minimum(Δχ) - ηs\n λ_upper = maximum(Δχ) + ηs\n λ_trial = 0.0\n\n while abs(ρ - ρ_trial) > 1.0e-7\n for i in 1:n_el\n Δχt = 1 / ηs * (Δχ[i] - λ_trial)\n χ_trial[i] = max(χ_min, min(1.0, χn[i] + Δχt))\n end\n\n ρ_trial = 0.0\n for i in 1:n_el\n ρ_trial += χ_trial[i] / n_el\n end\n\n if ρ_trial > ρ\n λ_lower = λ_trial\n elseif ρ_trial < ρ\n λ_upper = λ_trial\n end\n λ_trial = 1 / 2 * (λ_upper + λ_lower)\n end\n\n return χ_trial\nend\nnothing # hide","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"Lastly, we use the following helper function to compute the average driving force, which is later used to normalize the driving forces. This makes the used material parameters and numerical parameters independent of the problem.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"function compute_average_driving_force(mp, pΨ, χn)\n n = length(pΨ)\n w = zeros(n)\n\n for i in 1:n\n w[i] = (χn[i] - mp.χ_min) * (1 - χn[i])\n end\n\n p_Ω = sum(w .* pΨ) / sum(w) # average driving force\n\n return p_Ω\nend\nnothing # hide","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"Finally, we put everything together to update the density. The loop ensures the stability of the updated solution.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"function update_density(dh, states, mp, ρ, neighboorhoods, Δh)\n n_j = Int(ceil(6 * mp.β / (mp.η * Δh^2))) # iterations needed for stability\n χn = compute_densities(states, dh) # old density field\n χn1 = zeros(length(χn))\n\n for j in 1:n_j\n ∇²χ = approximate_laplacian(neighboorhoods, χn, Δh) # Laplacian\n pΨ = compute_driving_forces(states, mp, dh, χn) # driving forces\n p_Ω = compute_average_driving_force(mp, pΨ, χn) # average driving force\n\n Δχ = pΨ / p_Ω + mp.β * ∇²χ\n\n χn1 = compute_χn1(χn, Δχ, ρ, mp.η, mp.χ_min)\n\n if j < n_j\n χn[:] = χn1[:]\n end\n end\n\n return χn1\nend\nnothing # hide","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"Now, we move on to the Finite Element part of the program. We use the following function to assemble our linear system.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"function doassemble!(cellvalues::CellValues, facetvalues::FacetValues, K::SparseMatrixCSC, grid::Grid, dh::DofHandler, mp::MaterialParameters, u, states)\n r = zeros(ndofs(dh))\n assembler = start_assemble(K, r)\n nu = getnbasefunctions(cellvalues)\n\n re = zeros(nu) # local residual vector\n Ke = zeros(nu, nu) # local stiffness matrix\n\n for (element, state) in zip(CellIterator(dh), states)\n fill!(Ke, 0)\n fill!(re, 0)\n\n eldofs = celldofs(element)\n ue = u[eldofs]\n\n elmt!(Ke, re, element, cellvalues, facetvalues, grid, mp, ue, state)\n assemble!(assembler, celldofs(element), Ke, re)\n end\n\n return K, r\nend\nnothing # hide","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"The element routine is used to calculate the elementwise stiffness matrix and the residual. In contrast to a purely elastomechanic problem, for topology optimization we additionally use our material state to receive the density value of the element and to store the strain at each quadrature point.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"function elmt!(Ke, re, element, cellvalues, facetvalues, grid, mp, ue, state)\n n_basefuncs = getnbasefunctions(cellvalues)\n reinit!(cellvalues, element)\n χ = state.χ\n\n # We only assemble lower half triangle of the stiffness matrix and then symmetrize it.\n @inbounds for q_point in 1:getnquadpoints(cellvalues)\n dΩ = getdetJdV(cellvalues, q_point)\n state.ε[q_point] = function_symmetric_gradient(cellvalues, q_point, ue)\n\n for i in 1:n_basefuncs\n δεi = shape_symmetric_gradient(cellvalues, q_point, i)\n for j in 1:i\n δεj = shape_symmetric_gradient(cellvalues, q_point, j)\n Ke[i, j] += (χ)^(mp.p) * (δεi ⊡ mp.C ⊡ δεj) * dΩ\n end\n re[i] += (-δεi ⊡ ((χ)^(mp.p) * mp.C ⊡ state.ε[q_point])) * dΩ\n end\n end\n\n symmetrize_lower!(Ke)\n\n @inbounds for facet in 1:nfacets(getcells(grid, cellid(element)))\n if (cellid(element), facet) ∈ getfacetset(grid, \"traction\")\n reinit!(facetvalues, element, facet)\n t = Vec((0.0, -1.0)) # force pointing downwards\n for q_point in 1:getnquadpoints(facetvalues)\n dΓ = getdetJdV(facetvalues, q_point)\n for i in 1:n_basefuncs\n δu = shape_value(facetvalues, q_point, i)\n re[i] += (δu ⋅ t) * dΓ\n end\n end\n end\n end\n return\nend\n\nfunction symmetrize_lower!(K)\n for i in 1:size(K, 1)\n for j in (i + 1):size(K, 1)\n K[i, j] = K[j, i]\n end\n end\n return\nend\nnothing # hide","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"We put everything together in the main function. Here the user may choose the radius parameter, which is related to the regularization parameter as beta = ra^2, the starting density, the number of elements in vertical direction and finally the name of the output. Additionally, the user may choose whether only the final design (default) or every iteration step is saved.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"First, we compute the material parameters and create the grid, DofHandler, boundary condition and FE values. Then we prepare the iterative Newton-Raphson method by pre-allocating all important vectors. Furthermore, we create material states for each element and construct the topology of the grid.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"During each iteration step, first we solve our FE problem in the Newton-Raphson loop. With the solution of the elastomechanic problem, we check for convergence of our topology design. The criteria has to be fulfilled twice in a row to avoid oscillations. If no convergence is reached yet, we update our design and prepare the next iteration step. Finally, we output the results in paraview and calculate the relative stiffness of the final design, i.e. how much how the stiffness increased compared to the starting point.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"function topopt(ra, ρ, n, filename; output = :false)\n # material\n mp = MaterialParameters(210.0e3, 0.3, 1.0e-3, 3.0, ra^2, 15.0)\n\n # grid, dofhandler, boundary condition\n grid = create_grid(n)\n dh = create_dofhandler(grid)\n Δh = 1 / n # element edge length\n dbc = create_bc(dh)\n\n # cellvalues\n cellvalues, facetvalues = create_values()\n\n # Pre-allocate solution vectors, etc.\n n_dofs = ndofs(dh) # total number of dofs\n u = zeros(n_dofs) # solution vector\n un = zeros(n_dofs) # previous solution vector\n\n Δu = zeros(n_dofs) # previous displacement correction\n ΔΔu = zeros(n_dofs) # new displacement correction\n\n # create material states\n states = [MaterialState(ρ, getnquadpoints(cellvalues)) for _ in 1:getncells(dh.grid)]\n\n χ = zeros(getncells(dh.grid))\n\n r = zeros(n_dofs) # residual\n K = allocate_matrix(dh) # stiffness matrix\n\n i_max = 300 ## maximum number of iteration steps\n tol = 1.0e-4\n compliance = 0.0\n compliance_0 = 0.0\n compliance_n = 0.0\n conv = :false\n\n topology = ExclusiveTopology(grid)\n neighboorhoods = cache_neighborhood(dh, topology)\n\n # Newton-Raphson loop\n NEWTON_TOL = 1.0e-8\n print(\"\\n Starting Newton iterations\\n\")\n\n for it in 1:i_max\n apply_zero!(u, dbc)\n newton_itr = -1\n\n while true\n newton_itr += 1\n\n if newton_itr > 10\n error(\"Reached maximum Newton iterations, aborting\")\n break\n end\n\n # current guess\n u .= un .+ Δu\n K, r = doassemble!(cellvalues, facetvalues, K, grid, dh, mp, u, states)\n norm_r = norm(r[Ferrite.free_dofs(dbc)])\n\n if (norm_r) < NEWTON_TOL\n break\n end\n\n apply_zero!(K, r, dbc)\n ΔΔu = Symmetric(K) \\ r\n\n apply_zero!(ΔΔu, dbc)\n Δu .+= ΔΔu\n end # of loop while NR-Iteration\n\n # calculate compliance\n compliance = 1 / 2 * u' * K * u\n\n if it == 1\n compliance_0 = compliance\n end\n\n # check convergence criterium (twice!)\n if abs(compliance - compliance_n) / compliance < tol\n if conv\n println(\"Converged at iteration number: \", it)\n break\n else\n conv = :true\n end\n else\n conv = :false\n end\n\n # update density\n χ = update_density(dh, states, mp, ρ, neighboorhoods, Δh)\n\n # update old displacement, density and compliance\n un .= u\n Δu .= 0.0\n update_material_states!(χ, states, dh)\n compliance_n = compliance\n\n # output during calculation\n if output\n i = @sprintf(\"%3.3i\", it)\n filename_it = string(filename, \"_\", i)\n\n VTKGridFile(filename_it, grid) do vtk\n write_cell_data(vtk, χ, \"density\")\n end\n end\n end\n\n # export converged results\n if !output\n VTKGridFile(filename, grid) do vtk\n write_cell_data(vtk, χ, \"density\")\n end\n end\n @printf \"Rel. stiffness: %.4f \\n\" compliance^(-1) / compliance_0^(-1)\n\n return\nend\nnothing # hide","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"Lastly, we call our main function and compare the results. To create the complete output with all iteration steps, it is possible to set the output parameter to true.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"grid, χ =topopt(0.02, 0.5, 60, \"small_radius\"; output=:false);","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"@time topopt(0.03, 0.5, 60, \"large_radius\"; output = :false);\n#topopt(0.02, 0.5, 60, \"topopt_animation\"; output=:true); # can be used to create animations","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"We observe, that the stiffness for the lower value of ra is higher, but also requires more iterations until convergence and finer structures to be manufactured, as can be seen in Figure 2:","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"(Image: )","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"Figure 2: Optimization results of the bending beam for smaller (left) and larger (right) value of the regularization parameter beta.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"To prove mesh independence, the user could vary the mesh resolution and compare the results.","category":"page"},{"location":"gallery/topology_optimization/#References","page":"Topology optimization","title":"References","text":"","category":"section"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"D. R. Jantos, K. Hackl and P. Junker. An accurate and fast regularization approach to thermodynamic topology optimization. International Journal for Numerical Methods in Engineering 117, 991–1017 (2019).\n\n\n\nM. Blaszczyk, D. R. Jantos and P. Junker. Application of Taylor series combined with the weighted least square method to thermodynamic topology optimization. Computer Methods in Applied Mechanics and Engineering 393, 114698 (2022).\n\n\n\n","category":"page"},{"location":"gallery/topology_optimization/#topology_optimization-plain-program","page":"Topology optimization","title":"Plain program","text":"","category":"section"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"Here follows a version of the program without any comments. The file is also available here: topology_optimization.jl.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"using Ferrite, SparseArrays, LinearAlgebra, Tensors, Printf\n\nfunction create_grid(n)\n corners = [\n Vec{2}((0.0, 0.0)),\n Vec{2}((2.0, 0.0)),\n Vec{2}((2.0, 1.0)),\n Vec{2}((0.0, 1.0)),\n ]\n grid = generate_grid(Quadrilateral, (2 * n, n), corners)\n\n # node-/facesets for boundary conditions\n addnodeset!(grid, \"clamped\", x -> x[1] ≈ 0.0)\n addfacetset!(grid, \"traction\", x -> x[1] ≈ 2.0 && norm(x[2] - 0.5) <= 0.05)\n return grid\nend\n\nfunction create_values()\n # quadrature rules\n qr = QuadratureRule{RefQuadrilateral}(2)\n facet_qr = FacetQuadratureRule{RefQuadrilateral}(2)\n\n # cell and facetvalues for u\n ip = Lagrange{RefQuadrilateral, 1}()^2\n cellvalues = CellValues(qr, ip)\n facetvalues = FacetValues(facet_qr, ip)\n\n return cellvalues, facetvalues\nend\n\nfunction create_dofhandler(grid)\n dh = DofHandler(grid)\n add!(dh, :u, Lagrange{RefQuadrilateral, 1}()^2) # displacement\n close!(dh)\n return dh\nend\n\nfunction create_bc(dh)\n dbc = ConstraintHandler(dh)\n add!(dbc, Dirichlet(:u, getnodeset(dh.grid, \"clamped\"), (x, t) -> zero(Vec{2}), [1, 2]))\n close!(dbc)\n t = 0.0\n update!(dbc, t)\n return dbc\nend\n\nstruct MaterialParameters{T, S <: SymmetricTensor{4, 2, T}}\n C::S\n χ_min::T\n p::T\n β::T\n η::T\nend\n\nfunction MaterialParameters(E, ν, χ_min, p, β, η)\n δ(i, j) = i == j ? 1.0 : 0.0 # helper function\n\n G = E / 2(1 + ν) # =μ\n λ = E * ν / (1 - ν^2) # correction for plane stress included\n\n C = SymmetricTensor{4, 2}((i, j, k, l) -> λ * δ(i, j) * δ(k, l) + G * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k)))\n return MaterialParameters(C, χ_min, p, β, η)\nend\n\nmutable struct MaterialState{T, S <: AbstractArray{SymmetricTensor{2, 2, T, 3}, 1}}\n χ::T # density\n ε::S # strain in each quadrature point\nend\n\nfunction MaterialState(ρ, n_qp)\n return MaterialState(ρ, Array{SymmetricTensor{2, 2, Float64, 3}, 1}(undef, n_qp))\nend\n\nfunction update_material_states!(χn1, states, dh)\n for (element, state) in zip(CellIterator(dh), states)\n state.χ = χn1[cellid(element)]\n end\n return\nend\n\nfunction compute_driving_forces(states, mp, dh, χn)\n pΨ = zeros(length(states))\n for (element, state) in zip(CellIterator(dh), states)\n i = cellid(element)\n ε = sum(state.ε) / length(state.ε) # average element strain\n pΨ[i] = 1 / 2 * mp.p * χn[i]^(mp.p - 1) * (ε ⊡ mp.C ⊡ ε)\n end\n return pΨ\nend\n\nfunction compute_densities(states, dh)\n χn = zeros(length(states))\n for (element, state) in zip(CellIterator(dh), states)\n i = cellid(element)\n χn[i] = state.χ\n end\n return χn\nend\n\nfunction cache_neighborhood(dh, topology)\n nbgs = Vector{Vector{Int}}(undef, getncells(dh.grid))\n _nfacets = nfacets(dh.grid.cells[1])\n opp = Dict(1 => 3, 2 => 4, 3 => 1, 4 => 2)\n\n for element in CellIterator(dh)\n nbg = zeros(Int, _nfacets)\n i = cellid(element)\n for j in 1:_nfacets\n nbg_cellid = getneighborhood(topology, dh.grid, FacetIndex(i, j))\n if !isempty(nbg_cellid)\n nbg[j] = first(nbg_cellid)[1] # assuming only one facet neighbor per cell\n else # boundary facet\n nbg[j] = first(getneighborhood(topology, dh.grid, FacetIndex(i, opp[j])))[1]\n end\n end\n\n nbgs[i] = nbg\n end\n\n return nbgs\nend\n\nfunction approximate_laplacian(nbgs, χn, Δh)\n ∇²χ = zeros(length(nbgs))\n for i in 1:length(nbgs)\n nbg = nbgs[i]\n ∇²χ[i] = (χn[nbg[1]] + χn[nbg[2]] + χn[nbg[3]] + χn[nbg[4]] - 4 * χn[i]) / (Δh^2)\n end\n\n return ∇²χ\nend\n\nfunction compute_χn1(χn, Δχ, ρ, ηs, χ_min)\n n_el = length(χn)\n\n χ_trial = zeros(n_el)\n ρ_trial = 0.0\n\n λ_lower = minimum(Δχ) - ηs\n λ_upper = maximum(Δχ) + ηs\n λ_trial = 0.0\n\n while abs(ρ - ρ_trial) > 1.0e-7\n for i in 1:n_el\n Δχt = 1 / ηs * (Δχ[i] - λ_trial)\n χ_trial[i] = max(χ_min, min(1.0, χn[i] + Δχt))\n end\n\n ρ_trial = 0.0\n for i in 1:n_el\n ρ_trial += χ_trial[i] / n_el\n end\n\n if ρ_trial > ρ\n λ_lower = λ_trial\n elseif ρ_trial < ρ\n λ_upper = λ_trial\n end\n λ_trial = 1 / 2 * (λ_upper + λ_lower)\n end\n\n return χ_trial\nend\n\nfunction compute_average_driving_force(mp, pΨ, χn)\n n = length(pΨ)\n w = zeros(n)\n\n for i in 1:n\n w[i] = (χn[i] - mp.χ_min) * (1 - χn[i])\n end\n\n p_Ω = sum(w .* pΨ) / sum(w) # average driving force\n\n return p_Ω\nend\n\nfunction update_density(dh, states, mp, ρ, neighboorhoods, Δh)\n n_j = Int(ceil(6 * mp.β / (mp.η * Δh^2))) # iterations needed for stability\n χn = compute_densities(states, dh) # old density field\n χn1 = zeros(length(χn))\n\n for j in 1:n_j\n ∇²χ = approximate_laplacian(neighboorhoods, χn, Δh) # Laplacian\n pΨ = compute_driving_forces(states, mp, dh, χn) # driving forces\n p_Ω = compute_average_driving_force(mp, pΨ, χn) # average driving force\n\n Δχ = pΨ / p_Ω + mp.β * ∇²χ\n\n χn1 = compute_χn1(χn, Δχ, ρ, mp.η, mp.χ_min)\n\n if j < n_j\n χn[:] = χn1[:]\n end\n end\n\n return χn1\nend\n\nfunction doassemble!(cellvalues::CellValues, facetvalues::FacetValues, K::SparseMatrixCSC, grid::Grid, dh::DofHandler, mp::MaterialParameters, u, states)\n r = zeros(ndofs(dh))\n assembler = start_assemble(K, r)\n nu = getnbasefunctions(cellvalues)\n\n re = zeros(nu) # local residual vector\n Ke = zeros(nu, nu) # local stiffness matrix\n\n for (element, state) in zip(CellIterator(dh), states)\n fill!(Ke, 0)\n fill!(re, 0)\n\n eldofs = celldofs(element)\n ue = u[eldofs]\n\n elmt!(Ke, re, element, cellvalues, facetvalues, grid, mp, ue, state)\n assemble!(assembler, celldofs(element), Ke, re)\n end\n\n return K, r\nend\n\nfunction elmt!(Ke, re, element, cellvalues, facetvalues, grid, mp, ue, state)\n n_basefuncs = getnbasefunctions(cellvalues)\n reinit!(cellvalues, element)\n χ = state.χ\n\n # We only assemble lower half triangle of the stiffness matrix and then symmetrize it.\n @inbounds for q_point in 1:getnquadpoints(cellvalues)\n dΩ = getdetJdV(cellvalues, q_point)\n state.ε[q_point] = function_symmetric_gradient(cellvalues, q_point, ue)\n\n for i in 1:n_basefuncs\n δεi = shape_symmetric_gradient(cellvalues, q_point, i)\n for j in 1:i\n δεj = shape_symmetric_gradient(cellvalues, q_point, j)\n Ke[i, j] += (χ)^(mp.p) * (δεi ⊡ mp.C ⊡ δεj) * dΩ\n end\n re[i] += (-δεi ⊡ ((χ)^(mp.p) * mp.C ⊡ state.ε[q_point])) * dΩ\n end\n end\n\n symmetrize_lower!(Ke)\n\n @inbounds for facet in 1:nfacets(getcells(grid, cellid(element)))\n if (cellid(element), facet) ∈ getfacetset(grid, \"traction\")\n reinit!(facetvalues, element, facet)\n t = Vec((0.0, -1.0)) # force pointing downwards\n for q_point in 1:getnquadpoints(facetvalues)\n dΓ = getdetJdV(facetvalues, q_point)\n for i in 1:n_basefuncs\n δu = shape_value(facetvalues, q_point, i)\n re[i] += (δu ⋅ t) * dΓ\n end\n end\n end\n end\n return\nend\n\nfunction symmetrize_lower!(K)\n for i in 1:size(K, 1)\n for j in (i + 1):size(K, 1)\n K[i, j] = K[j, i]\n end\n end\n return\nend\n\nfunction topopt(ra, ρ, n, filename; output = :false)\n # material\n mp = MaterialParameters(210.0e3, 0.3, 1.0e-3, 3.0, ra^2, 15.0)\n\n # grid, dofhandler, boundary condition\n grid = create_grid(n)\n dh = create_dofhandler(grid)\n Δh = 1 / n # element edge length\n dbc = create_bc(dh)\n\n # cellvalues\n cellvalues, facetvalues = create_values()\n\n # Pre-allocate solution vectors, etc.\n n_dofs = ndofs(dh) # total number of dofs\n u = zeros(n_dofs) # solution vector\n un = zeros(n_dofs) # previous solution vector\n\n Δu = zeros(n_dofs) # previous displacement correction\n ΔΔu = zeros(n_dofs) # new displacement correction\n\n # create material states\n states = [MaterialState(ρ, getnquadpoints(cellvalues)) for _ in 1:getncells(dh.grid)]\n\n χ = zeros(getncells(dh.grid))\n\n r = zeros(n_dofs) # residual\n K = allocate_matrix(dh) # stiffness matrix\n\n i_max = 300 ## maximum number of iteration steps\n tol = 1.0e-4\n compliance = 0.0\n compliance_0 = 0.0\n compliance_n = 0.0\n conv = :false\n\n topology = ExclusiveTopology(grid)\n neighboorhoods = cache_neighborhood(dh, topology)\n\n # Newton-Raphson loop\n NEWTON_TOL = 1.0e-8\n print(\"\\n Starting Newton iterations\\n\")\n\n for it in 1:i_max\n apply_zero!(u, dbc)\n newton_itr = -1\n\n while true\n newton_itr += 1\n\n if newton_itr > 10\n error(\"Reached maximum Newton iterations, aborting\")\n break\n end\n\n # current guess\n u .= un .+ Δu\n K, r = doassemble!(cellvalues, facetvalues, K, grid, dh, mp, u, states)\n norm_r = norm(r[Ferrite.free_dofs(dbc)])\n\n if (norm_r) < NEWTON_TOL\n break\n end\n\n apply_zero!(K, r, dbc)\n ΔΔu = Symmetric(K) \\ r\n\n apply_zero!(ΔΔu, dbc)\n Δu .+= ΔΔu\n end # of loop while NR-Iteration\n\n # calculate compliance\n compliance = 1 / 2 * u' * K * u\n\n if it == 1\n compliance_0 = compliance\n end\n\n # check convergence criterium (twice!)\n if abs(compliance - compliance_n) / compliance < tol\n if conv\n println(\"Converged at iteration number: \", it)\n break\n else\n conv = :true\n end\n else\n conv = :false\n end\n\n # update density\n χ = update_density(dh, states, mp, ρ, neighboorhoods, Δh)\n\n # update old displacement, density and compliance\n un .= u\n Δu .= 0.0\n update_material_states!(χ, states, dh)\n compliance_n = compliance\n\n # output during calculation\n if output\n i = @sprintf(\"%3.3i\", it)\n filename_it = string(filename, \"_\", i)\n\n VTKGridFile(filename_it, grid) do vtk\n write_cell_data(vtk, χ, \"density\")\n end\n end\n end\n\n # export converged results\n if !output\n VTKGridFile(filename, grid) do vtk\n write_cell_data(vtk, χ, \"density\")\n end\n end\n @printf \"Rel. stiffness: %.4f \\n\" compliance^(-1) / compliance_0^(-1)\n\n return\nend\n\n@time topopt(0.03, 0.5, 60, \"large_radius\"; output = :false);\n#topopt(0.02, 0.5, 60, \"topopt_animation\"; output=:true); # can be used to create animations","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"This page was generated using Literate.jl.","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"EditURL = \"../literate-gallery/helmholtz.jl\"","category":"page"},{"location":"gallery/helmholtz/#tutorial-helmholtz","page":"Helmholtz equation","title":"Helmholtz equation","text":"","category":"section"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"In this example, we want to solve a (variant of) of the Helmholtz equation. The example is inspired by an dealii step_7 on the standard square.","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":" - Delta u + u = f","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"With boundary conditions given by","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"u = g_1 quad x in Gamma_1","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"and","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"n cdot nabla u = g_2 quad x in Gamma_2","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"Here Γ₁ is the union of the top and the right boundary of the square, while Γ₂ is the union of the bottom and the left boundary.","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"(Image: )","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"We will use the following weak formulation:","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"int_Omega nabla δu cdot nabla u dOmega\n+ int_Omega δu cdot u dOmega\n- int_Omega δu cdot f dOmega\n- int_Gamma_2 δu g_2 dGamma = 0 quad forall δu","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"where δu is a suitable test function that satisfies:","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"δu = 0 quad x in Gamma_1","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"and u is a suitable function that satisfies:","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"u = g_1 quad x in Gamma_1","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"The example highlights the following interesting features:","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"There are two kinds of boundary conditions, \"Dirichlet\" and \"Von Neumann\"\nThe example contains boundary integrals\nThe Dirichlet condition is imposed strongly and the Von Neumann condition is imposed weakly.","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"using Ferrite\nusing Tensors\nusing SparseArrays\nusing LinearAlgebra\n\nconst ∇ = Tensors.gradient\nconst Δ = Tensors.hessian;\n\ngrid = generate_grid(Quadrilateral, (150, 150))\n\nip = Lagrange{RefQuadrilateral, 1}()\nqr = QuadratureRule{RefQuadrilateral}(2)\nqr_facet = FacetQuadratureRule{RefQuadrilateral}(2)\ncellvalues = CellValues(qr, ip);\nfacetvalues = FacetValues(qr_facet, ip);\n\ndh = DofHandler(grid)\nadd!(dh, :u, ip)\nclose!(dh)","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"We will set things up, so that a known analytic solution is approximately reproduced. This is a good testing strategy for PDE codes and known as the method of manufactured solutions.","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"function u_ana(x::Vec{2, T}) where {T}\n xs = (\n Vec{2}((-0.5, 0.5)),\n Vec{2}((-0.5, -0.5)),\n Vec{2}((0.5, -0.5)),\n )\n σ = 1 / 8\n s = zero(eltype(x))\n for i in 1:3\n s += exp(- norm(x - xs[i])^2 / σ^2)\n end\n return max(1.0e-15 * one(T), s) # Denormals, be gone\nend;\n\ndbcs = ConstraintHandler(dh)","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"The (strong) Dirichlet boundary condition can be handled automatically by the Ferrite library.","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"dbc = Dirichlet(:u, union(getfacetset(grid, \"top\"), getfacetset(grid, \"right\")), (x, t) -> u_ana(x))\nadd!(dbcs, dbc)\nclose!(dbcs)\nupdate!(dbcs, 0.0)\n\nK = allocate_matrix(dh);\n\nfunction doassemble(\n cellvalues::CellValues, facetvalues::FacetValues, K::SparseMatrixCSC, dh::DofHandler\n )\n b = 1.0\n f = zeros(ndofs(dh))\n assembler = start_assemble(K, f)\n\n n_basefuncs = getnbasefunctions(cellvalues)\n\n fe = zeros(n_basefuncs) # Local force vector\n Ke = zeros(n_basefuncs, n_basefuncs) # Local stiffness mastrix\n\n for (cellcount, cell) in enumerate(CellIterator(dh))\n fill!(Ke, 0)\n fill!(fe, 0)\n coords = getcoordinates(cell)\n\n reinit!(cellvalues, cell)","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"First we derive the non boundary part of the variation problem from the destined solution u_ana","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"int_Omega nabla δu cdot nabla u dOmega\n+ int_Omega δu cdot u dOmega\n- int_Omega δu cdot f dOmega","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":" for q_point in 1:getnquadpoints(cellvalues)\n dΩ = getdetJdV(cellvalues, q_point)\n coords_qp = spatial_coordinate(cellvalues, q_point, coords)\n f_true = -LinearAlgebra.tr(hessian(u_ana, coords_qp)) + u_ana(coords_qp)\n for i in 1:n_basefuncs\n δu = shape_value(cellvalues, q_point, i)\n ∇δu = shape_gradient(cellvalues, q_point, i)\n fe[i] += (δu * f_true) * dΩ\n for j in 1:n_basefuncs\n u = shape_value(cellvalues, q_point, j)\n ∇u = shape_gradient(cellvalues, q_point, j)\n Ke[i, j] += (∇δu ⋅ ∇u + δu * u) * dΩ\n end\n end\n end","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"Now we manually add the von Neumann boundary terms","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"int_Gamma_2 δu g_2 dGamma","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":" for facet in 1:nfacets(cell)\n if (cellcount, facet) ∈ getfacetset(grid, \"left\") ||\n (cellcount, facet) ∈ getfacetset(grid, \"bottom\")\n reinit!(facetvalues, cell, facet)\n for q_point in 1:getnquadpoints(facetvalues)\n coords_qp = spatial_coordinate(facetvalues, q_point, coords)\n n = getnormal(facetvalues, q_point)\n g_2 = gradient(u_ana, coords_qp) ⋅ n\n dΓ = getdetJdV(facetvalues, q_point)\n for i in 1:n_basefuncs\n δu = shape_value(facetvalues, q_point, i)\n fe[i] += (δu * g_2) * dΓ\n end\n end\n end\n end\n\n assemble!(assembler, celldofs(cell), Ke, fe)\n end\n return K, f\nend;\n\nK, f = doassemble(cellvalues, facetvalues, K, dh);\napply!(K, f, dbcs)\nu = Symmetric(K) \\ f;\n\nvtk = VTKGridFile(\"helmholtz\", dh)\nwrite_solution(vtk, dh, u)\nclose(vtk)\nprintln(\"Helmholtz successful\")","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"This page was generated using Literate.jl.","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"EditURL = \"../literate-tutorials/stokes-flow.jl\"","category":"page"},{"location":"tutorials/stokes-flow/#tutorial-stokes-flow","page":"Stokes flow","title":"Stokes flow","text":"","category":"section"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"Keywords: periodic boundary conditions, multiple fields, mean value constraint","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"tip: Tip\nThis example is also available as a Jupyter notebook: stokes-flow.ipynb.","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"(Image: ) Figure 1: Left: Computational domain Omega with boundaries Gamma_1, Gamma_3 (periodic boundary conditions) and Gamma_2, Gamma_4 (homogeneous Dirichlet boundary conditions). Right: Magnitude of the resulting velocity field.","category":"page"},{"location":"tutorials/stokes-flow/#Introduction-and-problem-formulation","page":"Stokes flow","title":"Introduction and problem formulation","text":"","category":"section"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"This example is a translation of the step-45 example from deal.ii which solves Stokes flow on a quarter circle. In particular it shows how to use periodic boundary conditions, how to solve a problem with multiple unknown fields, and how to enforce a specific mean value of the solution. For the mesh generation we use Gmsh.jl and then use FerriteGmsh.jl to import the mesh into Ferrite's format.","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"The strong form of Stokes flow with velocity boldsymbolu and pressure p can be written as follows:","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"beginalign*\n-Delta boldsymbolu + boldsymbolnabla p = bigl(exp(-100boldsymbolx - (075 01)^2) 0bigr) =\nboldsymbolb quad forall boldsymbolx in Omega\n-boldsymbolnabla cdot boldsymbolu = 0 quad forall boldsymbolx in Omega\nendalign*","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"where the domain is defined as Omega = boldsymbolx in (0 1)^2 boldsymbolx in (05 1), see Figure 1. For the velocity we use periodic boundary conditions on the inlet Gamma_1 and outlet Gamma_3:","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"beginalign*\nu_x(0nu) = -u_y(nu 0) quad nu in 05 1\nu_y(0nu) = u_x(nu 0) quad nu in 05 1\nendalign*","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"and homogeneous Dirichlet boundary conditions for Gamma_2 and Gamma_4:","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"boldsymbolu = boldsymbol0 quad forall boldsymbolx in\nGamma_2 cup Gamma_4 = boldsymbolx boldsymbolx in 05 1","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"The corresponding weak form reads as follows: Find (boldsymbolu p) in mathbbU times mathrmL_2 s.t.","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"beginalign*\nint_Omega Bigldeltaboldsymboluotimesboldsymbolnablaboldsymboluotimesboldsymbolnabla -\n(boldsymbolnablacdotdeltaboldsymbolu) p Bigr mathrmdOmega =\nint_Omega deltaboldsymbolu cdot boldsymbolb mathrmdOmega quad forall\ndelta boldsymbolu in mathbbU\nint_Omega - (boldsymbolnablacdotboldsymbolu) delta p mathrmdOmega = 0\nquad forall delta p in mathrmL_2\nendalign*","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"where mathbbU is a suitable function space, that, in particular, enforces the Dirichlet boundary conditions, and the periodicity constraints. This formulation is a saddle point problem, and, just like the example with Incompressible Elasticity, we need our formulation to fulfill the LBB condition. We ensure this by using a quadratic approximation for the velocity field, and a linear approximation for the pressure.","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"With this formulation and boundary conditions for boldsymbolu the pressure will only be determined up to a constant. We will therefore add an additional constraint which fixes this constant (see deal.ii step-11 for some more discussion around this). In particular, we will enforce the mean value of the pressure on the boundary to be 0, i.e. int_Gamma p mathrmdGamma = 0. One option is to enforce this using a Lagrange multiplier. This would give a contribution lambda int_Gamma delta p mathrmdGamma to the second equation in the weak form above, and a third equation deltalambda int_Gamma p mathrmdGamma = 0 so that we can solve for lambda. However, since we in this case are not interested in computing lambda, and since the constraint is linear, we can directly embed this constraint using an AffineConstraint in Ferrite.","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"After FE discretization we obtain a linear system of the form underlineunderlineK underlinea = underlinef, where","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"underlineunderlineK =\nbeginbmatrix\nunderlineunderlineK_uu underlineunderlineK_pu^textrmT \nunderlineunderlineK_pu underlineunderline0\nendbmatrix quad\nunderlinea = beginbmatrix\nunderlinea_u \nunderlinea_p\nendbmatrix quad\nunderlinef = beginbmatrix\nunderlinef_u \nunderline0\nendbmatrix","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"and where","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"beginalign*\n(underlineunderlineK_uu)_ij = int_Omega boldsymbolphi^u_iotimesboldsymbolnablaboldsymbolphi^u_jotimesboldsymbolnabla mathrmdOmega \n(underlineunderlineK_pu)_ij = int_Omega - (boldsymbolnablacdotboldsymbolphi^u_j) phi^p_i mathrmdOmega \n(underlinef_u)_i = int_Omega boldsymbolphi^u_i cdot boldsymbolb mathrmdOmega\nendalign*","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"The affine constraint to enforce zero mean pressure on the boundary is obtained from underlineunderlineC_p underlinea_p = underline0, where","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"(underlineunderlineC_p)_1j = int_Gamma phi^p_j mathrmdGamma","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"note: Note\nThe constraint matrix underlineunderlineC_p is the same matrix we would have obtained when assembling the system with the Lagrange multiplier. In that case the full system would beunderlineunderlineK =\nbeginbmatrix\nunderlineunderlineK_uu underlineunderlineK_pu^textrmT \nunderlineunderline0\nunderlineunderlineK_pu underlineunderline0 underlineunderlineC_p^mathrmT \nunderlineunderline0 underlineunderlineC_p 0 \nendbmatrix quad\nunderlinea = beginbmatrix\nunderlinea_u \nunderlinea_p \nunderlinea_lambda\nendbmatrix quad\nunderlinef = beginbmatrix\nunderlinef_u \nunderline0 \nunderline0\nendbmatrix","category":"page"},{"location":"tutorials/stokes-flow/#Commented-program","page":"Stokes flow","title":"Commented program","text":"","category":"section"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"What follows is a program spliced with comments. The full program, without comments, can be found in the next section.","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"using Ferrite, FerriteGmsh, Gmsh, Tensors, LinearAlgebra, SparseArrays","category":"page"},{"location":"tutorials/stokes-flow/#Geometry-and-mesh-generation-with-Gmsh.jl","page":"Stokes flow","title":"Geometry and mesh generation with Gmsh.jl","text":"","category":"section"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"In the setup_grid function below we use the Gmsh.jl package for setting up the geometry and performing the meshing. We will not discuss this part in much detail but refer to the Gmsh API documentation instead. The most important thing to note is the mesh periodicity constraint that is applied between the \"inlet\" and \"outlet\" parts using gmsh.model.set_periodic. This is necessary to later on apply a periodicity constraint for the approximated velocity field.","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"function setup_grid(h = 0.05)\n # Initialize gmsh\n Gmsh.initialize()\n gmsh.option.set_number(\"General.Verbosity\", 2)\n\n # Add the points\n o = gmsh.model.geo.add_point(0.0, 0.0, 0.0, h)\n p1 = gmsh.model.geo.add_point(0.5, 0.0, 0.0, h)\n p2 = gmsh.model.geo.add_point(1.0, 0.0, 0.0, h)\n p3 = gmsh.model.geo.add_point(0.0, 1.0, 0.0, h)\n p4 = gmsh.model.geo.add_point(0.0, 0.5, 0.0, h)\n\n # Add the lines\n l1 = gmsh.model.geo.add_line(p1, p2)\n l2 = gmsh.model.geo.add_circle_arc(p2, o, p3)\n l3 = gmsh.model.geo.add_line(p3, p4)\n l4 = gmsh.model.geo.add_circle_arc(p4, o, p1)\n\n # Create the closed curve loop and the surface\n loop = gmsh.model.geo.add_curve_loop([l1, l2, l3, l4])\n surf = gmsh.model.geo.add_plane_surface([loop])\n\n # Synchronize the model\n gmsh.model.geo.synchronize()\n\n # Create the physical domains\n gmsh.model.add_physical_group(1, [l1], -1, \"Γ1\")\n gmsh.model.add_physical_group(1, [l2], -1, \"Γ2\")\n gmsh.model.add_physical_group(1, [l3], -1, \"Γ3\")\n gmsh.model.add_physical_group(1, [l4], -1, \"Γ4\")\n gmsh.model.add_physical_group(2, [surf])\n\n # Add the periodicity constraint using 4x4 affine transformation matrix,\n # see https://en.wikipedia.org/wiki/Transformation_matrix#Affine_transformations\n transformation_matrix = zeros(4, 4)\n transformation_matrix[1, 2] = 1 # -sin(-pi/2)\n transformation_matrix[2, 1] = -1 # cos(-pi/2)\n transformation_matrix[3, 3] = 1\n transformation_matrix[4, 4] = 1\n transformation_matrix = vec(transformation_matrix')\n gmsh.model.mesh.set_periodic(1, [l1], [l3], transformation_matrix)\n\n # Generate a 2D mesh\n gmsh.model.mesh.generate(2)\n\n # Save the mesh, and read back in as a Ferrite Grid\n grid = mktempdir() do dir\n path = joinpath(dir, \"mesh.msh\")\n gmsh.write(path)\n togrid(path)\n end\n\n # Finalize the Gmsh library\n Gmsh.finalize()\n\n return grid\nend\nnothing #hide","category":"page"},{"location":"tutorials/stokes-flow/#Degrees-of-freedom","page":"Stokes flow","title":"Degrees of freedom","text":"","category":"section"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"As mentioned in the introduction we will use a quadratic approximation for the velocity field and a linear approximation for the pressure to ensure that we fulfill the LBB condition. We create the corresponding FE values with interpolations ipu for the velocity and ipp for the pressure. Note that we specify linear geometric mapping (ipg) for both the velocity and pressure because our grid contains linear triangles. However, since linear mapping is default this could have been skipped. We also construct facet-values for the pressure since we need to integrate along the boundary when assembling the constraint matrix underlineunderlineC.","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"function setup_fevalues(ipu, ipp, ipg)\n qr = QuadratureRule{RefTriangle}(2)\n cvu = CellValues(qr, ipu, ipg)\n cvp = CellValues(qr, ipp, ipg)\n qr_facet = FacetQuadratureRule{RefTriangle}(2)\n fvp = FacetValues(qr_facet, ipp, ipg)\n return cvu, cvp, fvp\nend\nnothing #hide","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"The setup_dofs function creates the DofHandler, and adds the two fields: a vector field :u with interpolation ipu, and a scalar field :p with interpolation ipp.","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"function setup_dofs(grid, ipu, ipp)\n dh = DofHandler(grid)\n add!(dh, :u, ipu)\n add!(dh, :p, ipp)\n close!(dh)\n return dh\nend\nnothing #hide","category":"page"},{"location":"tutorials/stokes-flow/#Boundary-conditions-and-constraints","page":"Stokes flow","title":"Boundary conditions and constraints","text":"","category":"section"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"Now it is time to setup the ConstraintHandler and add our boundary conditions and the mean value constraint. This is perhaps the most interesting section in this example, and deserves some attention.","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"Let's first discuss the assembly of the constraint matrix underlineunderlineC and how to create an AffineConstraint from it. This is done in the setup_mean_constraint function below. Assembling this is not so different from standard assembly in Ferrite: we loop over all the facets, loop over the quadrature points, and loop over the shape functions. Note that since there is only one constraint the matrix will only have one row. After assembling C we construct an AffineConstraint from it. We select the constrained dof to be the one with the highest weight (just to avoid selecting one with 0 or a very small weight), then move the remaining to the right hand side. As an example, consider the case where the constraint equation underlineunderlineC_p underlinea_p is","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"w_10 p_10 + w_23 p_23 + w_154 p_154 = 0","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"i.e. dofs 10, 23, and 154, are the ones located on the boundary (all other dofs naturally gives 0 contribution). If w_23 is the largest weight, then we select p_23 to be the constrained one, and thus reorder the constraint to the form","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"p_23 = -fracw_10w_23 p_10 -fracw_154w_23 p_154 + 0","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"which is the form the AffineConstraint constructor expects.","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"note: Note\nIf all nodes along the boundary are equidistant all the weights would be the same. In this case we can construct the constraint without having to do any integration by simply finding all degrees of freedom that are located along the boundary (and using 1 as the weight). This is what is done in the deal.ii step-11 example.","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"function setup_mean_constraint(dh, fvp)\n assembler = Ferrite.COOAssembler()\n # All external boundaries\n set = union(\n getfacetset(dh.grid, \"Γ1\"),\n getfacetset(dh.grid, \"Γ2\"),\n getfacetset(dh.grid, \"Γ3\"),\n getfacetset(dh.grid, \"Γ4\"),\n )\n # Allocate buffers\n range_p = dof_range(dh, :p)\n element_dofs = zeros(Int, ndofs_per_cell(dh))\n element_dofs_p = view(element_dofs, range_p)\n element_coords = zeros(Vec{2}, 3)\n Ce = zeros(1, length(range_p)) # Local constraint matrix (only 1 row)\n # Loop over all the boundaries\n for (ci, fi) in set\n Ce .= 0\n getcoordinates!(element_coords, dh.grid, ci)\n reinit!(fvp, element_coords, fi)\n celldofs!(element_dofs, dh, ci)\n for qp in 1:getnquadpoints(fvp)\n dΓ = getdetJdV(fvp, qp)\n for i in 1:getnbasefunctions(fvp)\n Ce[1, i] += shape_value(fvp, qp, i) * dΓ\n end\n end\n # Assemble to row 1\n assemble!(assembler, [1], element_dofs_p, Ce)\n end\n C, _ = finish_assemble(assembler)\n # Create an AffineConstraint from the C-matrix\n _, J, V = findnz(C)\n _, constrained_dof_idx = findmax(abs2, V)\n constrained_dof = J[constrained_dof_idx]\n V ./= V[constrained_dof_idx]\n mean_value_constraint = AffineConstraint(\n constrained_dof,\n Pair{Int, Float64}[J[i] => -V[i] for i in 1:length(J) if J[i] != constrained_dof],\n 0.0,\n )\n return mean_value_constraint\nend\nnothing #hide","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"We now setup all the boundary conditions in the setup_constraints function below. Since the periodicity constraint for this example is between two boundaries which are not parallel to each other we need to i) compute the mapping between each mirror facet and the corresponding image facet (on the element level) and ii) describe the dof relation between dofs on these two facets. In Ferrite this is done by defining a transformation of entities on the image boundary such that they line up with the matching entities on the mirror boundary. In this example we consider the inlet Gamma_1 to be the image, and the outlet Gamma_3 to be the mirror. The necessary transformation to apply then becomes a rotation of pi2 radians around the out-of-plane axis. We set up the rotation matrix R, and then compute the mapping between mirror and image facets using collect_periodic_facets where the rotation is applied to the coordinates. In the next step we construct the constraint using the PeriodicDirichlet constructor. We pass the constructor the computed mapping, and also the rotation matrix. This matrix is used to rotate the dofs on the mirror surface such that we properly constrain boldsymbolu_x-dofs on the mirror to -boldsymbolu_y-dofs on the image, and boldsymbolu_y-dofs on the mirror to boldsymbolu_x-dofs on the image.","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"For the remaining part of the boundary we add a homogeneous Dirichlet boundary condition on both components of the velocity field. This is done using the Dirichlet constructor, which we have discussed in other tutorials.","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"function setup_constraints(dh, fvp)\n ch = ConstraintHandler(dh)\n # Periodic BC\n R = rotation_tensor(π / 2)\n periodic_faces = collect_periodic_facets(dh.grid, \"Γ3\", \"Γ1\", x -> R ⋅ x)\n periodic = PeriodicDirichlet(:u, periodic_faces, R, [1, 2])\n add!(ch, periodic)\n # Dirichlet BC\n Γ24 = union(getfacetset(dh.grid, \"Γ2\"), getfacetset(dh.grid, \"Γ4\"))\n dbc = Dirichlet(:u, Γ24, (x, t) -> [0, 0], [1, 2])\n add!(ch, dbc)\n # Compute mean value constraint and add it\n mean_value_constraint = setup_mean_constraint(dh, fvp)\n add!(ch, mean_value_constraint)\n # Finalize\n close!(ch)\n update!(ch, 0)\n return ch\nend\nnothing #hide","category":"page"},{"location":"tutorials/stokes-flow/#Global-and-local-assembly","page":"Stokes flow","title":"Global and local assembly","text":"","category":"section"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"Assembly of the global system is also something that we have seen in many previous tutorials. One interesting thing to note here is that, since we have two unknown fields, we use the dof_range function to make sure we assemble the element contributions to the correct block of the local stiffness matrix ke.","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"function assemble_system!(K, f, dh, cvu, cvp)\n assembler = start_assemble(K, f)\n ke = zeros(ndofs_per_cell(dh), ndofs_per_cell(dh))\n fe = zeros(ndofs_per_cell(dh))\n range_u = dof_range(dh, :u)\n ndofs_u = length(range_u)\n range_p = dof_range(dh, :p)\n ndofs_p = length(range_p)\n ϕᵤ = Vector{Vec{2, Float64}}(undef, ndofs_u)\n ∇ϕᵤ = Vector{Tensor{2, 2, Float64, 4}}(undef, ndofs_u)\n divϕᵤ = Vector{Float64}(undef, ndofs_u)\n ϕₚ = Vector{Float64}(undef, ndofs_p)\n for cell in CellIterator(dh)\n reinit!(cvu, cell)\n reinit!(cvp, cell)\n ke .= 0\n fe .= 0\n for qp in 1:getnquadpoints(cvu)\n dΩ = getdetJdV(cvu, qp)\n for i in 1:ndofs_u\n ϕᵤ[i] = shape_value(cvu, qp, i)\n ∇ϕᵤ[i] = shape_gradient(cvu, qp, i)\n divϕᵤ[i] = shape_divergence(cvu, qp, i)\n end\n for i in 1:ndofs_p\n ϕₚ[i] = shape_value(cvp, qp, i)\n end\n # u-u\n for (i, I) in pairs(range_u), (j, J) in pairs(range_u)\n ke[I, J] += (∇ϕᵤ[i] ⊡ ∇ϕᵤ[j]) * dΩ\n end\n # u-p\n for (i, I) in pairs(range_u), (j, J) in pairs(range_p)\n ke[I, J] += (-divϕᵤ[i] * ϕₚ[j]) * dΩ\n end\n # p-u\n for (i, I) in pairs(range_p), (j, J) in pairs(range_u)\n ke[I, J] += (-divϕᵤ[j] * ϕₚ[i]) * dΩ\n end\n # rhs\n for (i, I) in pairs(range_u)\n x = spatial_coordinate(cvu, qp, getcoordinates(cell))\n b = exp(-100 * norm(x - Vec{2}((0.75, 0.1)))^2)\n bv = Vec{2}((b, 0.0))\n fe[I] += (ϕᵤ[i] ⋅ bv) * dΩ\n end\n end\n assemble!(assembler, celldofs(cell), ke, fe)\n end\n return K, f\nend\nnothing #hide","category":"page"},{"location":"tutorials/stokes-flow/#Running-the-simulation","page":"Stokes flow","title":"Running the simulation","text":"","category":"section"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"We now have all the puzzle pieces, and just need to define the main function, which puts them all together.","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"function main()\n # Grid\n h = 0.05 # approximate element size\n grid = setup_grid(h)\n # Interpolations\n ipu = Lagrange{RefTriangle, 2}()^2 # quadratic\n ipp = Lagrange{RefTriangle, 1}() # linear\n # Dofs\n dh = setup_dofs(grid, ipu, ipp)\n # FE values\n ipg = Lagrange{RefTriangle, 1}() # linear geometric interpolation\n cvu, cvp, fvp = setup_fevalues(ipu, ipp, ipg)\n # Boundary conditions\n ch = setup_constraints(dh, fvp)\n # Global tangent matrix and rhs\n coupling = [true true; true false] # no coupling between pressure test/trial functions\n K = allocate_matrix(dh, ch; coupling = coupling)\n f = zeros(ndofs(dh))\n # Assemble system\n assemble_system!(K, f, dh, cvu, cvp)\n # Apply boundary conditions and solve\n apply!(K, f, ch)\n u = K \\ f\n apply!(u, ch)\n # Export the solution\n VTKGridFile(\"stokes-flow\", grid) do vtk\n write_solution(vtk, dh, u)\n end\n\n\n return\nend\nnothing #hide","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"Run it!","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"main()","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"The resulting magnitude of the velocity field is visualized in Figure 1.","category":"page"},{"location":"tutorials/stokes-flow/#stokes-flow-plain-program","page":"Stokes flow","title":"Plain program","text":"","category":"section"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"Here follows a version of the program without any comments. The file is also available here: stokes-flow.jl.","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"using Ferrite, FerriteGmsh, Gmsh, Tensors, LinearAlgebra, SparseArrays\n\nfunction setup_grid(h = 0.05)\n # Initialize gmsh\n Gmsh.initialize()\n gmsh.option.set_number(\"General.Verbosity\", 2)\n\n # Add the points\n o = gmsh.model.geo.add_point(0.0, 0.0, 0.0, h)\n p1 = gmsh.model.geo.add_point(0.5, 0.0, 0.0, h)\n p2 = gmsh.model.geo.add_point(1.0, 0.0, 0.0, h)\n p3 = gmsh.model.geo.add_point(0.0, 1.0, 0.0, h)\n p4 = gmsh.model.geo.add_point(0.0, 0.5, 0.0, h)\n\n # Add the lines\n l1 = gmsh.model.geo.add_line(p1, p2)\n l2 = gmsh.model.geo.add_circle_arc(p2, o, p3)\n l3 = gmsh.model.geo.add_line(p3, p4)\n l4 = gmsh.model.geo.add_circle_arc(p4, o, p1)\n\n # Create the closed curve loop and the surface\n loop = gmsh.model.geo.add_curve_loop([l1, l2, l3, l4])\n surf = gmsh.model.geo.add_plane_surface([loop])\n\n # Synchronize the model\n gmsh.model.geo.synchronize()\n\n # Create the physical domains\n gmsh.model.add_physical_group(1, [l1], -1, \"Γ1\")\n gmsh.model.add_physical_group(1, [l2], -1, \"Γ2\")\n gmsh.model.add_physical_group(1, [l3], -1, \"Γ3\")\n gmsh.model.add_physical_group(1, [l4], -1, \"Γ4\")\n gmsh.model.add_physical_group(2, [surf])\n\n # Add the periodicity constraint using 4x4 affine transformation matrix,\n # see https://en.wikipedia.org/wiki/Transformation_matrix#Affine_transformations\n transformation_matrix = zeros(4, 4)\n transformation_matrix[1, 2] = 1 # -sin(-pi/2)\n transformation_matrix[2, 1] = -1 # cos(-pi/2)\n transformation_matrix[3, 3] = 1\n transformation_matrix[4, 4] = 1\n transformation_matrix = vec(transformation_matrix')\n gmsh.model.mesh.set_periodic(1, [l1], [l3], transformation_matrix)\n\n # Generate a 2D mesh\n gmsh.model.mesh.generate(2)\n\n # Save the mesh, and read back in as a Ferrite Grid\n grid = mktempdir() do dir\n path = joinpath(dir, \"mesh.msh\")\n gmsh.write(path)\n togrid(path)\n end\n\n # Finalize the Gmsh library\n Gmsh.finalize()\n\n return grid\nend\n\nfunction setup_fevalues(ipu, ipp, ipg)\n qr = QuadratureRule{RefTriangle}(2)\n cvu = CellValues(qr, ipu, ipg)\n cvp = CellValues(qr, ipp, ipg)\n qr_facet = FacetQuadratureRule{RefTriangle}(2)\n fvp = FacetValues(qr_facet, ipp, ipg)\n return cvu, cvp, fvp\nend\n\nfunction setup_dofs(grid, ipu, ipp)\n dh = DofHandler(grid)\n add!(dh, :u, ipu)\n add!(dh, :p, ipp)\n close!(dh)\n return dh\nend\n\nfunction setup_mean_constraint(dh, fvp)\n assembler = Ferrite.COOAssembler()\n # All external boundaries\n set = union(\n getfacetset(dh.grid, \"Γ1\"),\n getfacetset(dh.grid, \"Γ2\"),\n getfacetset(dh.grid, \"Γ3\"),\n getfacetset(dh.grid, \"Γ4\"),\n )\n # Allocate buffers\n range_p = dof_range(dh, :p)\n element_dofs = zeros(Int, ndofs_per_cell(dh))\n element_dofs_p = view(element_dofs, range_p)\n element_coords = zeros(Vec{2}, 3)\n Ce = zeros(1, length(range_p)) # Local constraint matrix (only 1 row)\n # Loop over all the boundaries\n for (ci, fi) in set\n Ce .= 0\n getcoordinates!(element_coords, dh.grid, ci)\n reinit!(fvp, element_coords, fi)\n celldofs!(element_dofs, dh, ci)\n for qp in 1:getnquadpoints(fvp)\n dΓ = getdetJdV(fvp, qp)\n for i in 1:getnbasefunctions(fvp)\n Ce[1, i] += shape_value(fvp, qp, i) * dΓ\n end\n end\n # Assemble to row 1\n assemble!(assembler, [1], element_dofs_p, Ce)\n end\n C, _ = finish_assemble(assembler)\n # Create an AffineConstraint from the C-matrix\n _, J, V = findnz(C)\n _, constrained_dof_idx = findmax(abs2, V)\n constrained_dof = J[constrained_dof_idx]\n V ./= V[constrained_dof_idx]\n mean_value_constraint = AffineConstraint(\n constrained_dof,\n Pair{Int, Float64}[J[i] => -V[i] for i in 1:length(J) if J[i] != constrained_dof],\n 0.0,\n )\n return mean_value_constraint\nend\n\nfunction setup_constraints(dh, fvp)\n ch = ConstraintHandler(dh)\n # Periodic BC\n R = rotation_tensor(π / 2)\n periodic_faces = collect_periodic_facets(dh.grid, \"Γ3\", \"Γ1\", x -> R ⋅ x)\n periodic = PeriodicDirichlet(:u, periodic_faces, R, [1, 2])\n add!(ch, periodic)\n # Dirichlet BC\n Γ24 = union(getfacetset(dh.grid, \"Γ2\"), getfacetset(dh.grid, \"Γ4\"))\n dbc = Dirichlet(:u, Γ24, (x, t) -> [0, 0], [1, 2])\n add!(ch, dbc)\n # Compute mean value constraint and add it\n mean_value_constraint = setup_mean_constraint(dh, fvp)\n add!(ch, mean_value_constraint)\n # Finalize\n close!(ch)\n update!(ch, 0)\n return ch\nend\n\nfunction assemble_system!(K, f, dh, cvu, cvp)\n assembler = start_assemble(K, f)\n ke = zeros(ndofs_per_cell(dh), ndofs_per_cell(dh))\n fe = zeros(ndofs_per_cell(dh))\n range_u = dof_range(dh, :u)\n ndofs_u = length(range_u)\n range_p = dof_range(dh, :p)\n ndofs_p = length(range_p)\n ϕᵤ = Vector{Vec{2, Float64}}(undef, ndofs_u)\n ∇ϕᵤ = Vector{Tensor{2, 2, Float64, 4}}(undef, ndofs_u)\n divϕᵤ = Vector{Float64}(undef, ndofs_u)\n ϕₚ = Vector{Float64}(undef, ndofs_p)\n for cell in CellIterator(dh)\n reinit!(cvu, cell)\n reinit!(cvp, cell)\n ke .= 0\n fe .= 0\n for qp in 1:getnquadpoints(cvu)\n dΩ = getdetJdV(cvu, qp)\n for i in 1:ndofs_u\n ϕᵤ[i] = shape_value(cvu, qp, i)\n ∇ϕᵤ[i] = shape_gradient(cvu, qp, i)\n divϕᵤ[i] = shape_divergence(cvu, qp, i)\n end\n for i in 1:ndofs_p\n ϕₚ[i] = shape_value(cvp, qp, i)\n end\n # u-u\n for (i, I) in pairs(range_u), (j, J) in pairs(range_u)\n ke[I, J] += (∇ϕᵤ[i] ⊡ ∇ϕᵤ[j]) * dΩ\n end\n # u-p\n for (i, I) in pairs(range_u), (j, J) in pairs(range_p)\n ke[I, J] += (-divϕᵤ[i] * ϕₚ[j]) * dΩ\n end\n # p-u\n for (i, I) in pairs(range_p), (j, J) in pairs(range_u)\n ke[I, J] += (-divϕᵤ[j] * ϕₚ[i]) * dΩ\n end\n # rhs\n for (i, I) in pairs(range_u)\n x = spatial_coordinate(cvu, qp, getcoordinates(cell))\n b = exp(-100 * norm(x - Vec{2}((0.75, 0.1)))^2)\n bv = Vec{2}((b, 0.0))\n fe[I] += (ϕᵤ[i] ⋅ bv) * dΩ\n end\n end\n assemble!(assembler, celldofs(cell), ke, fe)\n end\n return K, f\nend\n\nfunction main()\n # Grid\n h = 0.05 # approximate element size\n grid = setup_grid(h)\n # Interpolations\n ipu = Lagrange{RefTriangle, 2}()^2 # quadratic\n ipp = Lagrange{RefTriangle, 1}() # linear\n # Dofs\n dh = setup_dofs(grid, ipu, ipp)\n # FE values\n ipg = Lagrange{RefTriangle, 1}() # linear geometric interpolation\n cvu, cvp, fvp = setup_fevalues(ipu, ipp, ipg)\n # Boundary conditions\n ch = setup_constraints(dh, fvp)\n # Global tangent matrix and rhs\n coupling = [true true; true false] # no coupling between pressure test/trial functions\n K = allocate_matrix(dh, ch; coupling = coupling)\n f = zeros(ndofs(dh))\n # Assemble system\n assemble_system!(K, f, dh, cvu, cvp)\n # Apply boundary conditions and solve\n apply!(K, f, ch)\n u = K \\ f\n apply!(u, ch)\n # Export the solution\n VTKGridFile(\"stokes-flow\", grid) do vtk\n write_solution(vtk, dh, u)\n end\n\n\n return\nend\n\nmain()","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"This page was generated using Literate.jl.","category":"page"},{"location":"reference/#Reference","page":"Reference overview","title":"Reference","text":"","category":"section"},{"location":"reference/","page":"Reference overview","title":"Reference overview","text":"Pages = [\n \"quadrature.md\",\n \"interpolations.md\",\n \"fevalues.md\",\n \"dofhandler.md\",\n \"assembly.md\",\n \"boundary_conditions.md\",\n \"grid.md\",\n \"export.md\",\n \"utils.md\",\n]","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"EditURL = \"../literate-tutorials/reactive_surface.jl\"","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"if isdefined(Main, :is_ci) #hide\n IS_CI = Main.is_ci #hide\nelse #hide\n IS_CI = false #hide\nend #hide\nnothing #hide","category":"page"},{"location":"tutorials/reactive_surface/#tutorial-reactive-surface","page":"Reactive surface","title":"Reactive surface","text":"","category":"section"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"(Image: )","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"Figure 1: Reactant concentration field of the Gray-Scott model on the unit sphere.","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"tip: Tip\nThis example is also available as a Jupyter notebook: reactive_surface.ipynb.","category":"page"},{"location":"tutorials/reactive_surface/#Introduction","page":"Reactive surface","title":"Introduction","text":"","category":"section"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"This tutorial gives a quick tutorial on how to assemble and solve time-dependent problems on embedded surfaces.","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"For this showcase we use the well known Gray-Scott model, which is a well-known reaction-diffusion system to study pattern formation. The strong form is given by","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":" beginaligned\n partial_t r_1 = nabla cdot (D_1 nabla r_1) - r_1*r_2^2 + F *(1 - r_1) quad textbfx in Omega \n partial_t r_2 = nabla cdot (D_2 nabla r_2) + r_1*r_2^2 - r_2*(F + k ) quad textbfx in Omega\n endaligned","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"where r_1 and r_2 are the reaction fields, D_1 and D_2 the diffusion tensors, k is the conversion rate, F is the feed rate and Omega the domain. Depending on the choice of parameters a different pattern can be observed. Please also note that the domain does not have a boundary. The corresponding weak form can be derived as usual.","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"For simplicity we will solve the problem with the Lie-Troter-Godunov operator splitting technique with the classical reaction-diffusion split. In this method we split our problem in two problems, i.e. a heat problem and a pointwise reaction problem, and solve them alternatingly to advance in time.","category":"page"},{"location":"tutorials/reactive_surface/#Solver-details","page":"Reactive surface","title":"Solver details","text":"","category":"section"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"The main idea for the Lie-Trotter-Godunov scheme is simple. We can write down the reaction diffusion problem in an abstract way as","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":" partial_t mathbfr = mathcalDmathbfr + R(mathbfr) quad textbfx in Omega","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"where mathcalD is the diffusion operator and R is the reaction operator. Notice that the right hand side is just the sum of two operators. Now with our operator splitting scheme we can advance a solution mathbfr(t_1) to mathbfr(t_2) by first solving a heat problem","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":" partial_t mathbfr^mathrmmathrmA = mathcalDmathbfr^mathrmA quad textbfx in Omega","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"with mathbfr^mathrmA(t_1) = mathbfr(t_1) on the time interval t_1 to t_2 and use the solution as the initial condition to solve the reaction problem","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":" partial_t mathbfr^mathrmB = R(mathbfr^mathrmB) quad textbfx in Omega","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"with mathbfr^mathrmB(t_1) = mathbfr^mathrmA(t_2). This way we obtain a solution approximation mathbfr(t_2) approx mathbfr^mathrmB(t_2).","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"note: Note\nThe operator splitting itself is an approximation, so even if we solve the subproblems analytically we end up with having only a solution approximation. We also do not have a beginner friendly reference for the theory behind operator splitting and can only refer to the original papers for each method.","category":"page"},{"location":"tutorials/reactive_surface/#Commented-Program","page":"Reactive surface","title":"Commented Program","text":"","category":"section"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"Now we solve the problem in Ferrite. What follows is a program spliced with comments. The full program, without comments, can be found in the next section.","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"First we load Ferrite, and some other packages we need","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"using Ferrite, FerriteGmsh\nusing BlockArrays, SparseArrays, LinearAlgebra, WriteVTK","category":"page"},{"location":"tutorials/reactive_surface/#Assembly-routines","page":"Reactive surface","title":"Assembly routines","text":"","category":"section"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"Before we head into the assembly, we define a helper struct to control the dispatches.","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"struct GrayScottMaterial{T}\n D₁::T\n D₂::T\n F::T\n k::T\nend;\nnothing #hide","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"The following assembly routines are written analogue to these found in previous tutorials.","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"function assemble_element_mass!(Me::Matrix, cellvalues::CellValues)\n n_basefuncs = getnbasefunctions(cellvalues)\n # The mass matrices between the reactions are not coupled, so we get a blocked-strided matrix.\n num_reactants = 2\n r₁range = 1:num_reactants:(num_reactants * n_basefuncs)\n r₂range = 2:num_reactants:(num_reactants * n_basefuncs)\n Me₁ = @view Me[r₁range, r₁range]\n Me₂ = @view Me[r₂range, r₂range]\n # Reset to 0\n fill!(Me, 0)\n # Loop over quadrature points\n for q_point in 1:getnquadpoints(cellvalues)\n # Get the quadrature weight\n dΩ = getdetJdV(cellvalues, q_point)\n # Loop over test shape functions\n for i in 1:n_basefuncs\n δuᵢ = shape_value(cellvalues, q_point, i)\n # Loop over trial shape functions\n for j in 1:n_basefuncs\n δuⱼ = shape_value(cellvalues, q_point, j)\n # Add contribution to Ke\n Me₁[i, j] += (δuᵢ * δuⱼ) * dΩ\n Me₂[i, j] += (δuᵢ * δuⱼ) * dΩ\n end\n end\n end\n return nothing\nend\n\nfunction assemble_element_diffusion!(De::Matrix, cellvalues::CellValues, material::GrayScottMaterial)\n n_basefuncs = getnbasefunctions(cellvalues)\n D₁ = material.D₁\n D₂ = material.D₂\n # The diffusion between the reactions is not coupled, so we get a blocked-strided matrix.\n num_reactants = 2\n r₁range = 1:num_reactants:(num_reactants * n_basefuncs)\n r₂range = 2:num_reactants:(num_reactants * n_basefuncs)\n De₁ = @view De[r₁range, r₁range]\n De₂ = @view De[r₂range, r₂range]\n # Reset to 0\n fill!(De, 0)\n # Loop over quadrature points\n for q_point in 1:getnquadpoints(cellvalues)\n # Get the quadrature weight\n dΩ = getdetJdV(cellvalues, q_point)\n # Loop over test shape functions\n for i in 1:n_basefuncs\n ∇δuᵢ = shape_gradient(cellvalues, q_point, i)\n # Loop over trial shape functions\n for j in 1:n_basefuncs\n ∇δuⱼ = shape_gradient(cellvalues, q_point, j)\n # Add contribution to Ke\n De₁[i, j] += D₁ * (∇δuᵢ ⋅ ∇δuⱼ) * dΩ\n De₂[i, j] += D₂ * (∇δuᵢ ⋅ ∇δuⱼ) * dΩ\n end\n end\n end\n return nothing\nend\n\nfunction assemble_matrices!(M::SparseMatrixCSC, D::SparseMatrixCSC, cellvalues::CellValues, dh::DofHandler, material::GrayScottMaterial)\n n_basefuncs = getnbasefunctions(cellvalues)\n\n # Allocate the element stiffness matrix and element force vector\n Me = zeros(2 * n_basefuncs, 2 * n_basefuncs)\n De = zeros(2 * n_basefuncs, 2 * n_basefuncs)\n\n # Create an assembler\n M_assembler = start_assemble(M)\n D_assembler = start_assemble(D)\n # Loop over all cels\n for cell in CellIterator(dh)\n # Reinitialize cellvalues for this cell\n reinit!(cellvalues, cell)\n # Compute element contribution\n assemble_element_mass!(Me, cellvalues)\n assemble!(M_assembler, celldofs(cell), Me)\n\n assemble_element_diffusion!(De, cellvalues, material)\n assemble!(D_assembler, celldofs(cell), De)\n end\n return nothing\nend;\nnothing #hide","category":"page"},{"location":"tutorials/reactive_surface/#Initial-condition-setup","page":"Reactive surface","title":"Initial condition setup","text":"","category":"section"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"Time-dependent problems always need an initial condition from which the time evolution starts. In this tutorial we set the concentration of reactant 1 to 1 and the concentration of reactant 2 to 0 for all nodal dof with associated coordinate z leq 09 on the sphere. Since the simulation would be pretty boring with a steady-state initial condition, we introduce some heterogeneity by setting the dofs associated to top part of the sphere (i.e. dofs with z 09 to store the reactant concentrations of 05 and 025 for the reactants 1 and 2 respectively.","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"function setup_initial_conditions!(u₀::Vector, cellvalues::CellValues, dh::DofHandler)\n u₀ .= ones(ndofs(dh))\n u₀[2:2:end] .= 0.0\n\n n_basefuncs = getnbasefunctions(cellvalues)\n\n for cell in CellIterator(dh)\n reinit!(cellvalues, cell)\n\n coords = getcoordinates(cell)\n dofs = celldofs(cell)\n uₑ = @view u₀[dofs]\n rv₀ₑ = reshape(uₑ, (2, n_basefuncs))\n\n for i in 1:n_basefuncs\n if coords[i][3] > 0.9\n rv₀ₑ[1, i] = 0.5\n rv₀ₑ[2, i] = 0.25\n end\n end\n end\n\n u₀ .+= 0.01 * rand(ndofs(dh))\n return\nend;\nnothing #hide","category":"page"},{"location":"tutorials/reactive_surface/#Mesh-generation","page":"Reactive surface","title":"Mesh generation","text":"","category":"section"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"In this section we define a routine to create a surface mesh with the help of FerriteGmsh.jl.","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"function create_embedded_sphere(refinements)\n gmsh.initialize()\n\n # Add a unit sphere in 3D space\n gmsh.model.occ.addSphere(0.0, 0.0, 0.0, 1.0)\n gmsh.model.occ.synchronize()\n\n # Generate nodes and surface elements only, hence we need to pass 2 into generate\n gmsh.model.mesh.generate(2)\n\n # To get good solution quality refine the elements several times\n for _ in 1:refinements\n gmsh.model.mesh.refine()\n end\n\n # Now we create a Ferrite grid out of it. Note that we also call toelements\n # with our surface element dimension to obtain these.\n nodes = tonodes()\n elements, _ = toelements(2)\n gmsh.finalize()\n return Grid(elements, nodes)\nend","category":"page"},{"location":"tutorials/reactive_surface/#Simulation-routines","page":"Reactive surface","title":"Simulation routines","text":"","category":"section"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"Now we define a function to setup and solve the problem with given feed and conversion rates F and k, as well as the time step length and for how long we want to solve the model.","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"function gray_scott_on_sphere(material::GrayScottMaterial, Δt::Real, T::Real, refinements::Integer)\n # We start by setting up grid, dof handler and the matrices for the heat problem.\n grid = create_embedded_sphere(refinements)\n\n # Next we are creating our element assembly helper for surface elements.\n # The only change which we need to introduce here is to pass in a geometrical\n # interpolation with the same dimension as the physical space into which our\n # elements are embedded into, which is in this example 3.\n ip = Lagrange{RefTriangle, 1}()\n qr = QuadratureRule{RefTriangle}(2)\n cellvalues = CellValues(qr, ip, ip^3)\n\n # We have two options to add the reactants to the dof handler, which will give us slightly\n # different resulting dof distributions:\n # A) We can add a scalar-valued interpolation for each reactant.\n # B) We can add one vectorized interpolation whose dimension is the number of reactants\n # number of reactants.\n # In this tutorial we opt for B, because the dofs are distributed per cell entity -- or\n # to be specific for this tutorial, we use an isoparametric concept such that the nodes\n # of our grid and the nodes of our solution approximation coincide. This way a reaction\n # we can create simply reshape the solution vector u to a matrix where the inner index\n # corresponds to the index of the reactant. Note that we will still use the scalar\n # interpolation for the assembly procedure.\n dh = DofHandler(grid)\n add!(dh, :reactants, ip^2)\n close!(dh)\n\n # We can save some memory by telling the sparsity pattern that the matrices are not coupled.\n M = allocate_matrix(dh; coupling = [true false; false true])\n D = allocate_matrix(dh; coupling = [true false; false true])\n\n # Since the heat problem is linear and has no time dependent parameters, we precompute the\n # decomposition of the system matrix to speed up the linear system solver.\n assemble_matrices!(M, D, cellvalues, dh, material)\n A = M + Δt .* D\n cholA = cholesky(A)\n\n # Now we setup buffers for the time dependent solution and fill the initial condition.\n uₜ = zeros(ndofs(dh))\n uₜ₋₁ = ones(ndofs(dh))\n setup_initial_conditions!(uₜ₋₁, cellvalues, dh)\n\n # And prepare output for visualization.\n pvd = paraview_collection(\"reactive-surface\")\n VTKGridFile(\"reactive-surface-0\", dh) do vtk\n write_solution(vtk, dh, uₜ₋₁)\n pvd[0.0] = vtk\n end\n\n # This is now the main solve loop.\n F = material.F\n k = material.k\n for (iₜ, t) in enumerate(Δt:Δt:T)\n # First we solve the heat problem\n uₜ .= cholA \\ (M * uₜ₋₁)\n\n # Then we solve the point-wise reaction problem with the solution of\n # the heat problem as initial guess. 2 is the number of reactants.\n num_individual_reaction_dofs = ndofs(dh) ÷ 2\n rvₜ = reshape(uₜ, (2, num_individual_reaction_dofs))\n for i in 1:num_individual_reaction_dofs\n r₁ = rvₜ[1, i]\n r₂ = rvₜ[2, i]\n rvₜ[1, i] += Δt * (-r₁ * r₂^2 + F * (1 - r₁))\n rvₜ[2, i] += Δt * (r₁ * r₂^2 - r₂ * (F + k))\n end\n\n # The solution is then stored every 10th step to vtk files for\n # later visualization purposes.\n if (iₜ % 10) == 0\n VTKGridFile(\"reactive-surface-$(iₜ)\", dh) do vtk\n write_solution(vtk, dh, uₜ₋₁)\n pvd[t] = vtk\n end\n end\n\n # Finally we totate the solution to initialize the next timestep.\n uₜ₋₁ .= uₜ\n end\n vtk_save(pvd)\n return\nend\n\n# This parametrization gives the spot pattern shown in the gif above.\nmaterial = GrayScottMaterial(0.00016, 0.00008, 0.06, 0.062)\n gray_scott_on_sphere(material, 10.0, 32000.0, 3)","category":"page"},{"location":"tutorials/reactive_surface/#reactive_surface-plain-program","page":"Reactive surface","title":"Plain program","text":"","category":"section"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"Here follows a version of the program without any comments. The file is also available here: reactive_surface.jl.","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"using Ferrite, FerriteGmsh\nusing BlockArrays, SparseArrays, LinearAlgebra, WriteVTK\n\nstruct GrayScottMaterial{T}\n D₁::T\n D₂::T\n F::T\n k::T\nend;\n\nfunction assemble_element_mass!(Me::Matrix, cellvalues::CellValues)\n n_basefuncs = getnbasefunctions(cellvalues)\n # The mass matrices between the reactions are not coupled, so we get a blocked-strided matrix.\n num_reactants = 2\n r₁range = 1:num_reactants:(num_reactants * n_basefuncs)\n r₂range = 2:num_reactants:(num_reactants * n_basefuncs)\n Me₁ = @view Me[r₁range, r₁range]\n Me₂ = @view Me[r₂range, r₂range]\n # Reset to 0\n fill!(Me, 0)\n # Loop over quadrature points\n for q_point in 1:getnquadpoints(cellvalues)\n # Get the quadrature weight\n dΩ = getdetJdV(cellvalues, q_point)\n # Loop over test shape functions\n for i in 1:n_basefuncs\n δuᵢ = shape_value(cellvalues, q_point, i)\n # Loop over trial shape functions\n for j in 1:n_basefuncs\n δuⱼ = shape_value(cellvalues, q_point, j)\n # Add contribution to Ke\n Me₁[i, j] += (δuᵢ * δuⱼ) * dΩ\n Me₂[i, j] += (δuᵢ * δuⱼ) * dΩ\n end\n end\n end\n return nothing\nend\n\nfunction assemble_element_diffusion!(De::Matrix, cellvalues::CellValues, material::GrayScottMaterial)\n n_basefuncs = getnbasefunctions(cellvalues)\n D₁ = material.D₁\n D₂ = material.D₂\n # The diffusion between the reactions is not coupled, so we get a blocked-strided matrix.\n num_reactants = 2\n r₁range = 1:num_reactants:(num_reactants * n_basefuncs)\n r₂range = 2:num_reactants:(num_reactants * n_basefuncs)\n De₁ = @view De[r₁range, r₁range]\n De₂ = @view De[r₂range, r₂range]\n # Reset to 0\n fill!(De, 0)\n # Loop over quadrature points\n for q_point in 1:getnquadpoints(cellvalues)\n # Get the quadrature weight\n dΩ = getdetJdV(cellvalues, q_point)\n # Loop over test shape functions\n for i in 1:n_basefuncs\n ∇δuᵢ = shape_gradient(cellvalues, q_point, i)\n # Loop over trial shape functions\n for j in 1:n_basefuncs\n ∇δuⱼ = shape_gradient(cellvalues, q_point, j)\n # Add contribution to Ke\n De₁[i, j] += D₁ * (∇δuᵢ ⋅ ∇δuⱼ) * dΩ\n De₂[i, j] += D₂ * (∇δuᵢ ⋅ ∇δuⱼ) * dΩ\n end\n end\n end\n return nothing\nend\n\nfunction assemble_matrices!(M::SparseMatrixCSC, D::SparseMatrixCSC, cellvalues::CellValues, dh::DofHandler, material::GrayScottMaterial)\n n_basefuncs = getnbasefunctions(cellvalues)\n\n # Allocate the element stiffness matrix and element force vector\n Me = zeros(2 * n_basefuncs, 2 * n_basefuncs)\n De = zeros(2 * n_basefuncs, 2 * n_basefuncs)\n\n # Create an assembler\n M_assembler = start_assemble(M)\n D_assembler = start_assemble(D)\n # Loop over all cels\n for cell in CellIterator(dh)\n # Reinitialize cellvalues for this cell\n reinit!(cellvalues, cell)\n # Compute element contribution\n assemble_element_mass!(Me, cellvalues)\n assemble!(M_assembler, celldofs(cell), Me)\n\n assemble_element_diffusion!(De, cellvalues, material)\n assemble!(D_assembler, celldofs(cell), De)\n end\n return nothing\nend;\n\nfunction setup_initial_conditions!(u₀::Vector, cellvalues::CellValues, dh::DofHandler)\n u₀ .= ones(ndofs(dh))\n u₀[2:2:end] .= 0.0\n\n n_basefuncs = getnbasefunctions(cellvalues)\n\n for cell in CellIterator(dh)\n reinit!(cellvalues, cell)\n\n coords = getcoordinates(cell)\n dofs = celldofs(cell)\n uₑ = @view u₀[dofs]\n rv₀ₑ = reshape(uₑ, (2, n_basefuncs))\n\n for i in 1:n_basefuncs\n if coords[i][3] > 0.9\n rv₀ₑ[1, i] = 0.5\n rv₀ₑ[2, i] = 0.25\n end\n end\n end\n\n u₀ .+= 0.01 * rand(ndofs(dh))\n return\nend;\n\nfunction create_embedded_sphere(refinements)\n gmsh.initialize()\n\n # Add a unit sphere in 3D space\n gmsh.model.occ.addSphere(0.0, 0.0, 0.0, 1.0)\n gmsh.model.occ.synchronize()\n\n # Generate nodes and surface elements only, hence we need to pass 2 into generate\n gmsh.model.mesh.generate(2)\n\n # To get good solution quality refine the elements several times\n for _ in 1:refinements\n gmsh.model.mesh.refine()\n end\n\n # Now we create a Ferrite grid out of it. Note that we also call toelements\n # with our surface element dimension to obtain these.\n nodes = tonodes()\n elements, _ = toelements(2)\n gmsh.finalize()\n return Grid(elements, nodes)\nend\n\nfunction gray_scott_on_sphere(material::GrayScottMaterial, Δt::Real, T::Real, refinements::Integer)\n # We start by setting up grid, dof handler and the matrices for the heat problem.\n grid = create_embedded_sphere(refinements)\n\n # Next we are creating our element assembly helper for surface elements.\n # The only change which we need to introduce here is to pass in a geometrical\n # interpolation with the same dimension as the physical space into which our\n # elements are embedded into, which is in this example 3.\n ip = Lagrange{RefTriangle, 1}()\n qr = QuadratureRule{RefTriangle}(2)\n cellvalues = CellValues(qr, ip, ip^3)\n\n # We have two options to add the reactants to the dof handler, which will give us slightly\n # different resulting dof distributions:\n # A) We can add a scalar-valued interpolation for each reactant.\n # B) We can add one vectorized interpolation whose dimension is the number of reactants\n # number of reactants.\n # In this tutorial we opt for B, because the dofs are distributed per cell entity -- or\n # to be specific for this tutorial, we use an isoparametric concept such that the nodes\n # of our grid and the nodes of our solution approximation coincide. This way a reaction\n # we can create simply reshape the solution vector u to a matrix where the inner index\n # corresponds to the index of the reactant. Note that we will still use the scalar\n # interpolation for the assembly procedure.\n dh = DofHandler(grid)\n add!(dh, :reactants, ip^2)\n close!(dh)\n\n # We can save some memory by telling the sparsity pattern that the matrices are not coupled.\n M = allocate_matrix(dh; coupling = [true false; false true])\n D = allocate_matrix(dh; coupling = [true false; false true])\n\n # Since the heat problem is linear and has no time dependent parameters, we precompute the\n # decomposition of the system matrix to speed up the linear system solver.\n assemble_matrices!(M, D, cellvalues, dh, material)\n A = M + Δt .* D\n cholA = cholesky(A)\n\n # Now we setup buffers for the time dependent solution and fill the initial condition.\n uₜ = zeros(ndofs(dh))\n uₜ₋₁ = ones(ndofs(dh))\n setup_initial_conditions!(uₜ₋₁, cellvalues, dh)\n\n # And prepare output for visualization.\n pvd = paraview_collection(\"reactive-surface\")\n VTKGridFile(\"reactive-surface-0\", dh) do vtk\n write_solution(vtk, dh, uₜ₋₁)\n pvd[0.0] = vtk\n end\n\n # This is now the main solve loop.\n F = material.F\n k = material.k\n for (iₜ, t) in enumerate(Δt:Δt:T)\n # First we solve the heat problem\n uₜ .= cholA \\ (M * uₜ₋₁)\n\n # Then we solve the point-wise reaction problem with the solution of\n # the heat problem as initial guess. 2 is the number of reactants.\n num_individual_reaction_dofs = ndofs(dh) ÷ 2\n rvₜ = reshape(uₜ, (2, num_individual_reaction_dofs))\n for i in 1:num_individual_reaction_dofs\n r₁ = rvₜ[1, i]\n r₂ = rvₜ[2, i]\n rvₜ[1, i] += Δt * (-r₁ * r₂^2 + F * (1 - r₁))\n rvₜ[2, i] += Δt * (r₁ * r₂^2 - r₂ * (F + k))\n end\n\n # The solution is then stored every 10th step to vtk files for\n # later visualization purposes.\n if (iₜ % 10) == 0\n VTKGridFile(\"reactive-surface-$(iₜ)\", dh) do vtk\n write_solution(vtk, dh, uₜ₋₁)\n pvd[t] = vtk\n end\n end\n\n # Finally we totate the solution to initialize the next timestep.\n uₜ₋₁ .= uₜ\n end\n vtk_save(pvd)\n return\nend\n\n# This parametrization gives the spot pattern shown in the gif above.\nmaterial = GrayScottMaterial(0.00016, 0.00008, 0.06, 0.062)\n gray_scott_on_sphere(material, 10.0, 32000.0, 3)","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"This page was generated using Literate.jl.","category":"page"},{"location":"devdocs/special_datastructures/#Special-data-structures","page":"Special data structures","title":"Special data structures","text":"","category":"section"},{"location":"devdocs/special_datastructures/#ArrayOfVectorViews","page":"Special data structures","title":"ArrayOfVectorViews","text":"","category":"section"},{"location":"devdocs/special_datastructures/","page":"Special data structures","title":"Special data structures","text":"ArrayOfVectorViews is a data structure representing an Array of vector views (specifically SubArray{T, 1} where T). By arranging all data (of type T) continuously in memory, this will significantly reduce the garbage collection time compared to using an Array{AbstractVector{T}}. While the data in each view can be mutated, the length of each view is fixed after construction. This data structure provides two features not provided by ArraysOfArrays.jl: Support of matrices and higher order arrays for storing vectors of different dimensions and efficient construction when the number of elements in each view is not known in advance.","category":"page"},{"location":"devdocs/special_datastructures/","page":"Special data structures","title":"Special data structures","text":"Ferrite.ArrayOfVectorViews\nFerrite.ConstructionBuffer\nFerrite.push_at_index!","category":"page"},{"location":"devdocs/special_datastructures/#Ferrite.CollectionsOfViews.ArrayOfVectorViews","page":"Special data structures","title":"Ferrite.CollectionsOfViews.ArrayOfVectorViews","text":"ArrayOfVectorViews(f!::Function, data::Vector{T}, dims::NTuple{N, Int}; sizehint)\n\nCreate an ArrayOfVectorViews to store many vector views of potentially different sizes, emulating an Array{Vector{T}, N} with size dims. However, it avoids allocating each vector individually by storing all data in data, and instead of Vector{T}, the each element is a typeof(view(data, 1:2)).\n\nWhen the length of each vector is unknown, the ArrayOfVectorViews can be created reasonably efficient with the following do-block, which creates an intermediate buffer::ConstructionBuffer supporting the push_at_index! function.\n\nvector_views = ArrayOfVectorViews(data, dims; sizehint) do buffer\n for (ind, val) in some_data\n push_at_index!(buffer, val, ind)\n end\nend\n\nsizehint tells how much space to allocate for the index ind if no val has been added to that index before, or how much more space to allocate in case all previously allocated space for ind has been used up.\n\n\n\n\n\nArrayOfVectorViews(b::CollectionsOfViews.ConstructionBuffer)\n\nCreates the ArrayOfVectorViews directly from the ConstructionBuffer that was manually created and filled.\n\n\n\n\n\nArrayOfVectorViews(indices::Vector{Int}, data::Vector{T}, lin_idx::LinearIndices{N}; checkargs = true)\n\nCreates the ArrayOfVectorViews directly where the user is responsible for having the correct input data. Checking of the argument dimensions can be elided by setting checkargs = false, but incorrect dimensions may lead to illegal out of bounds access later.\n\ndata is indexed by indices[i]:indices[i+1], where i = lin_idx[idx...] and idx... are the user-provided indices to the ArrayOfVectorViews.\n\n\n\n\n\n","category":"type"},{"location":"devdocs/special_datastructures/#Ferrite.CollectionsOfViews.ConstructionBuffer","page":"Special data structures","title":"Ferrite.CollectionsOfViews.ConstructionBuffer","text":"ConstructionBuffer(data::Vector, dims::NTuple{N, Int}, sizehint)\n\nCreate a buffer for creating an ArrayOfVectorViews, representing an array with N axes. sizehint sets the number of elements in data allocated when a new index is added via push_at_index!, or when the current storage for the index is full, how much many additional elements are reserved for that index. Any content in data is overwritten, but performance is improved by pre-allocating it to a reasonable size or by sizehint!ing it.\n\n\n\n\n\n","category":"type"},{"location":"devdocs/special_datastructures/#Ferrite.CollectionsOfViews.push_at_index!","page":"Special data structures","title":"Ferrite.CollectionsOfViews.push_at_index!","text":"push_at_index!(b::ConstructionBuffer, val, indices::Int...)\n\npush! the value val to the Vector view at the index given by indices, typically called inside the ArrayOfVectorViews constructor do-block. But can also be used when manually creating a ConstructionBuffer.\n\n\n\n\n\n","category":"function"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"EditURL = \"../literate-tutorials/ns_vs_diffeq.jl\"","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"if isdefined(Main, :is_ci) #hide\n IS_CI = Main.is_ci #hide\nelse #hide\n IS_CI = false #hide\nend #hide\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/#tutorial-ins-ordinarydiffeq","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"","category":"section"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"(Image: nsdiffeq)","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"In this example we focus on a simple but visually appealing problem from fluid dynamics, namely vortex shedding. This problem is also known as von-Karman vortex streets. Within this example, we show how to utilize DifferentialEquations.jl in tandem with Ferrite to solve this space-time problem. To keep things simple we use a naive approach to discretize the system.","category":"page"},{"location":"tutorials/ns_vs_diffeq/#Remarks-on-DifferentialEquations.jl","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Remarks on DifferentialEquations.jl","text":"","category":"section"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"note: Required Version\nThis example will only work with OrdinaryDiffEq@v6.80.1. or above","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Many \"time step solvers\" of DifferentialEquations.jl assume that that the problem is provided in mass matrix form. The incompressible Navier-Stokes equations as stated above yield a DAE in this form after applying a spatial discretization technique - in our case FEM. The mass matrix form of ODEs and DAEs is given as:","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":" M(t) mathrmd_t u = f(ut)","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"where M is a possibly time-dependent and not necessarily invertible mass matrix, u the vector of unknowns and f the right-hand-side (RHS). For us f can be interpreted as the spatial discretization of all linear and nonlinear operators depending on u and t, but not on the time derivative of u.","category":"page"},{"location":"tutorials/ns_vs_diffeq/#Some-theory-on-the-incompressible-Navier-Stokes-equations","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Some theory on the incompressible Navier-Stokes equations","text":"","category":"section"},{"location":"tutorials/ns_vs_diffeq/#Problem-description-in-strong-form","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Problem description in strong form","text":"","category":"section"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"The incompressible Navier-Stokes equations can be stated as the system","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":" beginaligned\n partial_t v = underbracenu Delta v_textviscosity - underbrace(v cdot nabla) v_textadvection - underbracenabla p_textpressure \n 0 = underbracenabla cdot v_textincompressibility\n endaligned","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"where v is the unknown velocity field, p the unknown pressure field, nu the dynamic viscosity and Delta the Laplacian. In the derivation we assumed a constant density of 1 for the fluid and negligible coupling between the velocity components.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Our setup is derived from Turek's DFG benchmark. We model a channel with size 041 times 11 and a hole of radius 005 centered at (02 02). The left side has a parabolic inflow profile, which is ramped up over time, modeled as the time dependent Dirichlet condition","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":" v(xyt)\n =\n beginbmatrix\n 4 v_in(t) y (041-y)041^2 \n 0\n endbmatrix","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"where v_in(t) = textclamp(t 00 15). With a dynamic viscosity of nu = 0001 this is enough to induce turbulence behind the cylinder which leads to vortex shedding. The top and bottom of our channel have no-slip conditions, i.e. v = 00^textrmT, while the right boundary has the do-nothing boundary condition nu partial_textrmn v - p n = 0 to model outflow. With these boundary conditions we can choose the zero solution as a feasible initial condition.","category":"page"},{"location":"tutorials/ns_vs_diffeq/#Derivation-of-Semi-Discrete-Weak-Form","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Derivation of Semi-Discrete Weak Form","text":"","category":"section"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"By multiplying test functions varphi and psi from a suitable test function space on the strong form, followed by integrating over the domain and applying partial integration to the pressure and viscosity terms we can obtain the following weak form","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":" beginaligned\n int_Omega partial_t v cdot varphi = - int_Omega nu nabla v nabla varphi - int_Omega (v cdot nabla) v cdot varphi + int_Omega p (nabla cdot varphi) + int_partial Omega_N underbrace(nu partial_n v - p n )_=0 cdot varphi \n 0 = int_Omega (nabla cdot v) psi\n endaligned","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"for all possible test functions from the suitable space.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Now we can discretize the problem as usual with the finite element method utilizing Taylor-Hood elements (Q2Q1) to yield a stable discretization in mass matrix form:","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":" underbracebeginbmatrix\n M_v 0 \n 0 0\n endbmatrix_=M\n beginbmatrix\n mathrmd_thatv \n mathrmd_thatp\n endbmatrix\n =\n underbracebeginbmatrix\n A B^textrmT \n B 0\n endbmatrix_=K\n beginbmatrix\n hatv \n hatp\n endbmatrix\n +\n beginbmatrix\n N(hatv hatv hatvarphi) \n 0\n endbmatrix","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Here M is the singular block mass matrix, K is the discretized Stokes operator and N the nonlinear advection term, which is also called trilinear form. hatv and hatp represent the time-dependent vectors of nodal values of the discretizations of v and p respectively, while hatvarphi is the choice for the test function in the discretization. The hats are dropped in the implementation and only stated for clarity in this section.","category":"page"},{"location":"tutorials/ns_vs_diffeq/#Commented-implementation","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Commented implementation","text":"","category":"section"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Now we solve the problem with Ferrite and DifferentialEquations.jl. What follows is a program spliced with comments. The full program, without comments, can be found in the next section.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"First we load Ferrite and some other packages we need","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"using Ferrite, SparseArrays, BlockArrays, LinearAlgebra, UnPack, LinearSolve, WriteVTK","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Since we do not need the complete DifferentialEquations suite, we just load the required ODE infrastructure, which can also handle DAEs in mass matrix form.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"using OrdinaryDiffEq","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"We start off by defining our only material parameter.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"ν = 1.0 / 1000.0; #dynamic viscosity\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Next a rectangular grid with a cylinder in it has to be generated. We use Gmsh.jl for the creation of the mesh and FerriteGmsh.jl to translate it to a Ferrite.Grid. Note that the mesh is pretty fine, leading to a high memory consumption when feeding the equation system to direct solvers.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"using FerriteGmsh\nusing FerriteGmsh: Gmsh\nGmsh.initialize()\ngmsh.option.set_number(\"General.Verbosity\", 2)\ndim = 2;\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"We specify first the rectangle, the cylinder, the surface spanned by the cylinder and the boolean difference of rectangle and cylinder.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"if !IS_CI #hide\nrect_tag = gmsh.model.occ.add_rectangle(0, 0, 0, 1.1, 0.41)\ncircle_tag = gmsh.model.occ.add_circle(0.2, 0.2, 0, 0.05)\ncircle_curve_tag = gmsh.model.occ.add_curve_loop([circle_tag])\ncircle_surf_tag = gmsh.model.occ.add_plane_surface([circle_curve_tag])\ngmsh.model.occ.cut([(dim, rect_tag)], [(dim, circle_surf_tag)])\nelse #hide\n rect_tag = gmsh.model.occ.add_rectangle(0, 0, 0, 0.55, 0.41) #hide\nend #hide\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Now, the geometrical entities need to be synchronized in order to be available outside of gmsh.model.occ","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"gmsh.model.occ.synchronize()","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"In the next lines, we add the physical groups needed to define boundary conditions.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"if !IS_CI #hide\nbottomtag = gmsh.model.model.add_physical_group(dim - 1, [6], -1, \"bottom\")\nlefttag = gmsh.model.model.add_physical_group(dim - 1, [7], -1, \"left\")\nrighttag = gmsh.model.model.add_physical_group(dim - 1, [8], -1, \"right\")\ntoptag = gmsh.model.model.add_physical_group(dim - 1, [9], -1, \"top\")\nholetag = gmsh.model.model.add_physical_group(dim - 1, [5], -1, \"hole\")\nelse #hide\n gmsh.model.model.add_physical_group(dim - 1, [4], 7, \"left\") #hide\n gmsh.model.model.add_physical_group(dim - 1, [3], 8, \"top\") #hide\n gmsh.model.model.add_physical_group(dim - 1, [2], 9, \"right\") #hide\n gmsh.model.model.add_physical_group(dim - 1, [1], 10, \"bottom\") #hide\nend #hide\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Since we want a quad mesh, we specify the meshing algorithm to the quasi structured quad one. For a complete list, see the Gmsh docs.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"gmsh.option.setNumber(\"Mesh.Algorithm\", 11)\ngmsh.option.setNumber(\"Mesh.MeshSizeFromCurvature\", 20)\ngmsh.option.setNumber(\"Mesh.MeshSizeMax\", 0.05)\nif IS_CI #hide\n gmsh.option.setNumber(\"Mesh.MeshSizeFromCurvature\", 20) #hide\n gmsh.option.setNumber(\"Mesh.MeshSizeMax\", 0.15) #hide\nend #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"In the next step, the mesh is generated and finally translated.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"gmsh.model.mesh.generate(dim)\ngrid = togrid()\nGmsh.finalize();\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/#Function-Space","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Function Space","text":"","category":"section"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"To ensure stability we utilize the Taylor-Hood element pair Q2-Q1. We have to utilize the same quadrature rule for the pressure as for the velocity, because in the weak form the linear pressure term is tested against a quadratic function.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"ip_v = Lagrange{RefQuadrilateral, 2}()^dim\nqr = QuadratureRule{RefQuadrilateral}(4)\ncellvalues_v = CellValues(qr, ip_v);\n\nip_p = Lagrange{RefQuadrilateral, 1}()\ncellvalues_p = CellValues(qr, ip_p);\n\ndh = DofHandler(grid)\nadd!(dh, :v, ip_v)\nadd!(dh, :p, ip_p)\nclose!(dh);\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/#Boundary-conditions","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Boundary conditions","text":"","category":"section"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"As in the DFG benchmark we apply no-slip conditions to the top, bottom and cylinder boundary. The no-slip condition states that the velocity of the fluid on this portion of the boundary is fixed to be zero.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"ch = ConstraintHandler(dh);\n\nnosplip_facet_names = [\"top\", \"bottom\", \"hole\"];\nif IS_CI #hide\n nosplip_facet_names = [\"top\", \"bottom\"] #hide\nend #hide\n∂Ω_noslip = union(getfacetset.((grid,), nosplip_facet_names)...);\nnoslip_bc = Dirichlet(:v, ∂Ω_noslip, (x, t) -> Vec((0.0, 0.0)), [1, 2])\nadd!(ch, noslip_bc);\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"The left boundary has a parabolic inflow with peak velocity of 1.5. This ensures that for the given geometry the Reynolds number is 100, which is already enough to obtain some simple vortex streets. By increasing the velocity further we can obtain stronger vortices - which may need additional refinement of the grid.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"∂Ω_inflow = getfacetset(grid, \"left\");\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"note: Note\nThe kink in the velocity profile will lead to a discontinuity in the pressure at t=1. This needs to be considered in the DiffEq init by providing the keyword argument d_discontinuities=[1.0].","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"vᵢₙ(t) = min(t * 1.5, 1.5) #inflow velocity\n\nparabolic_inflow_profile(x, t) = Vec((4 * vᵢₙ(t) * x[2] * (0.41 - x[2]) / 0.41^2, 0.0))\ninflow_bc = Dirichlet(:v, ∂Ω_inflow, parabolic_inflow_profile, [1, 2])\nadd!(ch, inflow_bc);\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"The outflow boundary condition has been applied on the right side of the cylinder when the weak form has been derived by setting the boundary integral to zero. It is also called the do-nothing condition. Other outflow conditions are also possible.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"∂Ω_free = getfacetset(grid, \"right\");\n\nclose!(ch)\nupdate!(ch, 0.0);\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/#Linear-System-Assembly","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Linear System Assembly","text":"","category":"section"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Next we describe how the block mass matrix and the Stokes matrix are assembled.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"For the block mass matrix M we remember that only the first equation had a time derivative and that the block mass matrix corresponds to the term arising from discretizing the time derivatives. Hence, only the upper left block has non-zero components.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"function assemble_mass_matrix(cellvalues_v::CellValues, cellvalues_p::CellValues, M::SparseMatrixCSC, dh::DofHandler)\n # Allocate a buffer for the local matrix and some helpers, together with the assembler.\n n_basefuncs_v = getnbasefunctions(cellvalues_v)\n n_basefuncs_p = getnbasefunctions(cellvalues_p)\n n_basefuncs = n_basefuncs_v + n_basefuncs_p\n v▄, p▄ = 1, 2\n Mₑ = BlockedArray(zeros(n_basefuncs, n_basefuncs), [n_basefuncs_v, n_basefuncs_p], [n_basefuncs_v, n_basefuncs_p])\n\n # It follows the assembly loop as explained in the basic tutorials.\n mass_assembler = start_assemble(M)\n for cell in CellIterator(dh)\n fill!(Mₑ, 0)\n Ferrite.reinit!(cellvalues_v, cell)\n\n for q_point in 1:getnquadpoints(cellvalues_v)\n dΩ = getdetJdV(cellvalues_v, q_point)\n # Remember that we assemble a vector mass term, hence the dot product.\n # There is only one time derivative on the left hand side, so only one mass block is non-zero.\n for i in 1:n_basefuncs_v\n φᵢ = shape_value(cellvalues_v, q_point, i)\n for j in 1:n_basefuncs_v\n φⱼ = shape_value(cellvalues_v, q_point, j)\n Mₑ[BlockIndex((v▄, v▄), (i, j))] += φᵢ ⋅ φⱼ * dΩ\n end\n end\n end\n assemble!(mass_assembler, celldofs(cell), Mₑ)\n end\n\n return M\nend;\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Next we discuss the assembly of the Stokes matrix appearing on the right hand side. Remember that we use the same function spaces for trial and test, hence the matrix has the following block form","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":" K = beginbmatrix\n A B^textrmT \n B 0\n endbmatrix","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"which is also called saddle point matrix. These problems are known to have a non-trivial kernel, which is a reflection of the strong form as discussed in the theory portion if this example.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"function assemble_stokes_matrix(cellvalues_v::CellValues, cellvalues_p::CellValues, ν, K::SparseMatrixCSC, dh::DofHandler)\n # Again, some buffers and helpers\n n_basefuncs_v = getnbasefunctions(cellvalues_v)\n n_basefuncs_p = getnbasefunctions(cellvalues_p)\n n_basefuncs = n_basefuncs_v + n_basefuncs_p\n v▄, p▄ = 1, 2\n Kₑ = BlockedArray(zeros(n_basefuncs, n_basefuncs), [n_basefuncs_v, n_basefuncs_p], [n_basefuncs_v, n_basefuncs_p])\n\n # Assembly loop\n stiffness_assembler = start_assemble(K)\n for cell in CellIterator(dh)\n # Don't forget to initialize everything\n fill!(Kₑ, 0)\n\n Ferrite.reinit!(cellvalues_v, cell)\n Ferrite.reinit!(cellvalues_p, cell)\n\n for q_point in 1:getnquadpoints(cellvalues_v)\n dΩ = getdetJdV(cellvalues_v, q_point)","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Assemble local viscosity block of A","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":" for i in 1:n_basefuncs_v\n ∇φᵢ = shape_gradient(cellvalues_v, q_point, i)\n for j in 1:n_basefuncs_v\n ∇φⱼ = shape_gradient(cellvalues_v, q_point, j)\n Kₑ[BlockIndex((v▄, v▄), (i, j))] -= ν * ∇φᵢ ⊡ ∇φⱼ * dΩ\n end\n end","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Assemble local pressure and incompressibility blocks of B^textrmT and B.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":" for j in 1:n_basefuncs_p\n ψ = shape_value(cellvalues_p, q_point, j)\n for i in 1:n_basefuncs_v\n divφ = shape_divergence(cellvalues_v, q_point, i)\n Kₑ[BlockIndex((v▄, p▄), (i, j))] += (divφ * ψ) * dΩ\n Kₑ[BlockIndex((p▄, v▄), (j, i))] += (ψ * divφ) * dΩ\n end\n end\n end\n\n # Assemble `Kₑ` into the Stokes matrix `K`.\n assemble!(stiffness_assembler, celldofs(cell), Kₑ)\n end\n return K\nend;\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/#Solution-of-the-semi-discretized-system-via-DifferentialEquations.jl","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Solution of the semi-discretized system via DifferentialEquations.jl","text":"","category":"section"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"First we assemble the linear portions for efficiency. These matrices are assumed to be constant over time.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"note: Note\nTo obtain the vortex street a small time step is important to resolve the small oscillation forming. The mesh size becomes important to \"only\" resolve the smaller vertices forming, but less important for the initial formation.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"T = 6.0\nΔt₀ = 0.001\nif IS_CI #hide\n Δt₀ = 0.1 #hide\nend #hide\nΔt_save = 0.1\n\nM = allocate_matrix(dh);\nM = assemble_mass_matrix(cellvalues_v, cellvalues_p, M, dh);\n\nK = allocate_matrix(dh);\nK = assemble_stokes_matrix(cellvalues_v, cellvalues_p, ν, K, dh);\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"These are our initial conditions. We start from the zero solution, because it is trivially admissible if the Dirichlet conditions are zero everywhere on the Dirichlet boundary for t=0. Note that the time stepper is also doing fine if the Dirichlet condition is non-zero and not too pathological.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"u₀ = zeros(ndofs(dh))\napply!(u₀, ch);\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"DifferentialEquations assumes dense matrices by default, which is not feasible for semi-discretization of finite element models. We communicate that a sparse matrix with specified pattern should be utilized through the jac_prototyp argument. It is simple to see that the Jacobian and the stiffness matrix share the same sparsity pattern, since they share the same relation between trial and test functions.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"jac_sparsity = sparse(K);\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"To apply the nonlinear portion of the Navier-Stokes problem we simply hand over the dof handler and cell values to the right-hand-side (RHS) as a parameter. Furthermore the pre-assembled linear part, our Stokes operator (which is time independent) is passed to save some additional runtime. To apply the time-dependent Dirichlet BCs, we also need to hand over the constraint handler. The basic idea to apply the Dirichlet BCs consistently is that we copy the current solution u, apply the Dirichlet BCs on the copy, evaluate the discretized RHS of the Navier-Stokes equations with this vector. Furthermore we pass down the Jacobian assembly manually. For the Jacobian we eliminate all rows and columns associated with constrained dofs. Also note that we eliminate the mass matrix beforehand in a similar fashion. This decouples the time evolution of the constrained dofs from the true unknowns. The correct solution is enforced by utilizing step and stage limiters. The correct norms are computed by passing down a custom norm which simply ignores all constrained dofs.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"note: Note\nAn alternative strategy is to hook into the nonlinear and linear solvers and enforce the solution therein. However, this is not possible at the time of writing this tutorial.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"apply!(M, ch)\n\nstruct RHSparams\n K::SparseMatrixCSC\n ch::ConstraintHandler\n dh::DofHandler\n cellvalues_v::CellValues\n u::Vector\nend\np = RHSparams(K, ch, dh, cellvalues_v, copy(u₀))\n\nfunction ferrite_limiter!(u, _, p, t)\n update!(p.ch, t)\n return apply!(u, p.ch)\nend\n\nfunction navierstokes_rhs_element!(dvₑ, vₑ, cellvalues_v)\n n_basefuncs = getnbasefunctions(cellvalues_v)\n for q_point in 1:getnquadpoints(cellvalues_v)\n dΩ = getdetJdV(cellvalues_v, q_point)\n ∇v = function_gradient(cellvalues_v, q_point, vₑ)\n v = function_value(cellvalues_v, q_point, vₑ)\n for j in 1:n_basefuncs\n φⱼ = shape_value(cellvalues_v, q_point, j)","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Note that in Tensors.jl the definition textrmgrad v = nabla v holds. With this information it can be quickly shown in index notation that","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"(v cdot nabla) v_textrmi = v_textrmj (partial_textrmj v_textrmi) = v (nabla v)^textrmT_textrmi","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"where we should pay attentation to the transpose of the gradient.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":" dvₑ[j] -= v ⋅ ∇v' ⋅ φⱼ * dΩ\n end\n end\n return\nend\n\nfunction navierstokes!(du, u_uc, p::RHSparams, t)","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Unpack the struct to save some allocations.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":" @unpack K, ch, dh, cellvalues_v, u = p","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"We start by applying the time-dependent Dirichlet BCs. Note that we are not allowed to mutate u_uc! Furthermore not that we also can not pre- allocate a buffer for this variable variable if we want to use AD to derive the Jacobian matrix, which appears in stiff solvers. Therefore, for efficiency reasons, we simply pass down the jacobian analytically.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":" u .= u_uc\n update!(ch, t)\n apply!(u, ch)","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Now we apply the rhs of the Navier-Stokes equations","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":" # Linear contribution (Stokes operator)\n mul!(du, K, u) # du .= K * u\n\n # nonlinear contribution\n v_range = dof_range(dh, :v)\n n_basefuncs = getnbasefunctions(cellvalues_v)\n vₑ = zeros(n_basefuncs)\n duₑ = zeros(n_basefuncs)\n for cell in CellIterator(dh)\n Ferrite.reinit!(cellvalues_v, cell)\n v_celldofs = @view celldofs(cell)[v_range]\n vₑ .= @views u[v_celldofs]\n fill!(duₑ, 0.0)\n navierstokes_rhs_element!(duₑ, vₑ, cellvalues_v)\n assemble!(du, v_celldofs, duₑ)\n end\n return\nend;\n\nfunction navierstokes_jac_element!(Jₑ, vₑ, cellvalues_v)\n n_basefuncs = getnbasefunctions(cellvalues_v)\n for q_point in 1:getnquadpoints(cellvalues_v)\n dΩ = getdetJdV(cellvalues_v, q_point)\n ∇v = function_gradient(cellvalues_v, q_point, vₑ)\n v = function_value(cellvalues_v, q_point, vₑ)\n for j in 1:n_basefuncs\n φⱼ = shape_value(cellvalues_v, q_point, j)","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Note that in Tensors.jl the definition textrmgrad v = nabla v holds. With this information it can be quickly shown in index notation that","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"(v cdot nabla) v_textrmi = v_textrmj (partial_textrmj v_textrmi) = v (nabla v)^textrmT_textrmi","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"where we should pay attentation to the transpose of the gradient.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":" for i in 1:n_basefuncs\n φᵢ = shape_value(cellvalues_v, q_point, i)\n ∇φᵢ = shape_gradient(cellvalues_v, q_point, i)\n Jₑ[j, i] -= (φᵢ ⋅ ∇v' + v ⋅ ∇φᵢ') ⋅ φⱼ * dΩ\n end\n end\n end\n return\nend\n\nfunction navierstokes_jac!(J, u_uc, p, t)","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Unpack the struct to save some allocations.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":" @unpack K, ch, dh, cellvalues_v, u = p","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"We start by applying the time-dependent Dirichlet BCs. Note that we are not allowed to mutate u_uc, so we use our buffer again.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":" u .= u_uc\n update!(ch, t)\n apply!(u, ch)","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Now we apply the Jacobian of the Navier-Stokes equations.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":" # Linear contribution (Stokes operator)\n # Here we assume that J has exactly the same structure as K by construction\n nonzeros(J) .= nonzeros(K)\n\n assembler = start_assemble(J; fillzero = false)\n\n # Assemble variation of the nonlinear term\n n_basefuncs = getnbasefunctions(cellvalues_v)\n Jₑ = zeros(n_basefuncs, n_basefuncs)\n vₑ = zeros(n_basefuncs)\n v_range = dof_range(dh, :v)\n for cell in CellIterator(dh)\n Ferrite.reinit!(cellvalues_v, cell)\n v_celldofs = @view celldofs(cell)[v_range]\n\n vₑ .= @views u[v_celldofs]\n fill!(Jₑ, 0.0)\n navierstokes_jac_element!(Jₑ, vₑ, cellvalues_v)\n assemble!(assembler, v_celldofs, Jₑ)\n end","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Finally we eliminate the constrained dofs from the Jacobian to decouple them in the nonlinear solver from the remaining system.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":" return apply!(J, ch)\nend;\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Finally, together with our pre-assembled mass matrix, we are now able to define our problem in mass matrix form.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"rhs = ODEFunction(navierstokes!, mass_matrix = M; jac = navierstokes_jac!, jac_prototype = jac_sparsity)\nproblem = ODEProblem(rhs, u₀, (0.0, T), p);\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"All norms must not depend on constrained dofs. A problem with the presented implementation is that we are currently unable to strictly enforce constraint everywhere in the internal time integration process of DifferentialEquations.jl, hence the values might differ, resulting in worse error estimates. We try to resolve this issue in the future. Volunteers are also welcome to take a look into this!","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"struct FreeDofErrorNorm\n ch::ConstraintHandler\nend\n(fe_norm::FreeDofErrorNorm)(u::Union{AbstractFloat, Complex}, t) = DiffEqBase.ODE_DEFAULT_NORM(u, t)\n(fe_norm::FreeDofErrorNorm)(u::AbstractArray, t) = DiffEqBase.ODE_DEFAULT_NORM(u[fe_norm.ch.free_dofs], t)","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Now we can put everything together by specifying how to solve the problem. We want to use an adaptive variant of the implicit Euler method. Further we enable the progress bar with the progress and progress_steps arguments. Finally we have to communicate the time step length and initialization algorithm. Since we start with a valid initial state we do not use one of DifferentialEquations.jl initialization algorithms.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"note: DAE initialization\nAt the time of writing this no Hessenberg index 2 initialization is implemented.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"To visualize the result we export the grid and our fields to VTK-files, which can be viewed in ParaView by utilizing the corresponding pvd file.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"timestepper = Rodas5P(autodiff = false, step_limiter! = ferrite_limiter!);\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"info: Debugging convergence issues\nWe can obtain some debug information from OrdinaryDiffEq by wrapping the following section into a debug logger.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"integrator = init(\n problem, timestepper; initializealg = NoInit(), dt = Δt₀,\n adaptive = true, abstol = 1.0e-4, reltol = 1.0e-5,\n progress = true, progress_steps = 1,\n verbose = true, internalnorm = FreeDofErrorNorm(ch), d_discontinuities = [1.0]\n);\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"note: Export of solution\nExporting interpolated solutions of problems containing mass matrices is currently broken. Thus, the intervals iterator is used. Note that solve holds all solutions in the memory.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"pvd = paraview_collection(\"vortex-street\")\nfor (step, (u, t)) in enumerate(intervals(integrator))\n VTKGridFile(\"vortex-street-$step\", dh) do vtk\n write_solution(vtk, dh, u)\n pvd[t] = vtk\n end\nend\nvtk_save(pvd);\n\n\nusing Test #hide\nif IS_CI #hide\n function compute_divergence(dh, u, cellvalues_v) #hide\n divv = 0.0 #hide\n for cell in CellIterator(dh) #hide\n Ferrite.reinit!(cellvalues_v, cell) #hide\n for q_point in 1:getnquadpoints(cellvalues_v) #hide\n dΩ = getdetJdV(cellvalues_v, q_point) #hide\n #hide\n all_celldofs = celldofs(cell) #hide\n v_celldofs = all_celldofs[dof_range(dh, :v)] #hide\n v_cell = u[v_celldofs] #hide\n #hide\n divv += function_divergence(cellvalues_v, q_point, v_cell) * dΩ #hide\n end #hide\n end #hide\n return divv #hide\n end #hide\n let #hide\n u = copy(integrator.u) #hide\n Δdivv = abs(compute_divergence(dh, u, cellvalues_v)) #hide\n @test isapprox(Δdivv, 0.0, atol = 1.0e-12) #hide\n #hide\n Δv = 0.0 #hide\n for cell in CellIterator(dh) #hide\n Ferrite.reinit!(cellvalues_v, cell) #hide\n all_celldofs = celldofs(cell) #hide\n v_celldofs = all_celldofs[dof_range(dh, :v)] #hide\n v_cell = u[v_celldofs] #hide\n coords = getcoordinates(cell) #hide\n for q_point in 1:getnquadpoints(cellvalues_v) #hide\n dΩ = getdetJdV(cellvalues_v, q_point) #hide\n coords_qp = spatial_coordinate(cellvalues_v, q_point, coords) #hide\n v = function_value(cellvalues_v, q_point, v_cell) #hide\n Δv += norm(v - parabolic_inflow_profile(coords_qp, T))^2 * dΩ #hide\n end #hide\n end #hide\n @test isapprox(sqrt(Δv), 0.0, atol = 1.0e-3) #hide\n end #hide\n nothing #hide\nend #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/#ns_vs_diffeq-plain-program","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Plain program","text":"","category":"section"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Here follows a version of the program without any comments. The file is also available here: ns_vs_diffeq.jl.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"using Ferrite, SparseArrays, BlockArrays, LinearAlgebra, UnPack, LinearSolve, WriteVTK\n\nusing OrdinaryDiffEq\n\nν = 1.0 / 1000.0; #dynamic viscosity\n\nusing FerriteGmsh\nusing FerriteGmsh: Gmsh\nGmsh.initialize()\ngmsh.option.set_number(\"General.Verbosity\", 2)\ndim = 2;\n\nrect_tag = gmsh.model.occ.add_rectangle(0, 0, 0, 1.1, 0.41)\ncircle_tag = gmsh.model.occ.add_circle(0.2, 0.2, 0, 0.05)\ncircle_curve_tag = gmsh.model.occ.add_curve_loop([circle_tag])\ncircle_surf_tag = gmsh.model.occ.add_plane_surface([circle_curve_tag])\ngmsh.model.occ.cut([(dim, rect_tag)], [(dim, circle_surf_tag)])\n\ngmsh.model.occ.synchronize()\n\nbottomtag = gmsh.model.model.add_physical_group(dim - 1, [6], -1, \"bottom\")\nlefttag = gmsh.model.model.add_physical_group(dim - 1, [7], -1, \"left\")\nrighttag = gmsh.model.model.add_physical_group(dim - 1, [8], -1, \"right\")\ntoptag = gmsh.model.model.add_physical_group(dim - 1, [9], -1, \"top\")\nholetag = gmsh.model.model.add_physical_group(dim - 1, [5], -1, \"hole\")\n\ngmsh.option.setNumber(\"Mesh.Algorithm\", 11)\ngmsh.option.setNumber(\"Mesh.MeshSizeFromCurvature\", 20)\ngmsh.option.setNumber(\"Mesh.MeshSizeMax\", 0.05)\n\ngmsh.model.mesh.generate(dim)\ngrid = togrid()\nGmsh.finalize();\n\nip_v = Lagrange{RefQuadrilateral, 2}()^dim\nqr = QuadratureRule{RefQuadrilateral}(4)\ncellvalues_v = CellValues(qr, ip_v);\n\nip_p = Lagrange{RefQuadrilateral, 1}()\ncellvalues_p = CellValues(qr, ip_p);\n\ndh = DofHandler(grid)\nadd!(dh, :v, ip_v)\nadd!(dh, :p, ip_p)\nclose!(dh);\n\nch = ConstraintHandler(dh);\n\nnosplip_facet_names = [\"top\", \"bottom\", \"hole\"];\n∂Ω_noslip = union(getfacetset.((grid,), nosplip_facet_names)...);\nnoslip_bc = Dirichlet(:v, ∂Ω_noslip, (x, t) -> Vec((0.0, 0.0)), [1, 2])\nadd!(ch, noslip_bc);\n\n∂Ω_inflow = getfacetset(grid, \"left\");\n\nvᵢₙ(t) = min(t * 1.5, 1.5) #inflow velocity\n\nparabolic_inflow_profile(x, t) = Vec((4 * vᵢₙ(t) * x[2] * (0.41 - x[2]) / 0.41^2, 0.0))\ninflow_bc = Dirichlet(:v, ∂Ω_inflow, parabolic_inflow_profile, [1, 2])\nadd!(ch, inflow_bc);\n\n∂Ω_free = getfacetset(grid, \"right\");\n\nclose!(ch)\nupdate!(ch, 0.0);\n\nfunction assemble_mass_matrix(cellvalues_v::CellValues, cellvalues_p::CellValues, M::SparseMatrixCSC, dh::DofHandler)\n # Allocate a buffer for the local matrix and some helpers, together with the assembler.\n n_basefuncs_v = getnbasefunctions(cellvalues_v)\n n_basefuncs_p = getnbasefunctions(cellvalues_p)\n n_basefuncs = n_basefuncs_v + n_basefuncs_p\n v▄, p▄ = 1, 2\n Mₑ = BlockedArray(zeros(n_basefuncs, n_basefuncs), [n_basefuncs_v, n_basefuncs_p], [n_basefuncs_v, n_basefuncs_p])\n\n # It follows the assembly loop as explained in the basic tutorials.\n mass_assembler = start_assemble(M)\n for cell in CellIterator(dh)\n fill!(Mₑ, 0)\n Ferrite.reinit!(cellvalues_v, cell)\n\n for q_point in 1:getnquadpoints(cellvalues_v)\n dΩ = getdetJdV(cellvalues_v, q_point)\n # Remember that we assemble a vector mass term, hence the dot product.\n # There is only one time derivative on the left hand side, so only one mass block is non-zero.\n for i in 1:n_basefuncs_v\n φᵢ = shape_value(cellvalues_v, q_point, i)\n for j in 1:n_basefuncs_v\n φⱼ = shape_value(cellvalues_v, q_point, j)\n Mₑ[BlockIndex((v▄, v▄), (i, j))] += φᵢ ⋅ φⱼ * dΩ\n end\n end\n end\n assemble!(mass_assembler, celldofs(cell), Mₑ)\n end\n\n return M\nend;\n\nfunction assemble_stokes_matrix(cellvalues_v::CellValues, cellvalues_p::CellValues, ν, K::SparseMatrixCSC, dh::DofHandler)\n # Again, some buffers and helpers\n n_basefuncs_v = getnbasefunctions(cellvalues_v)\n n_basefuncs_p = getnbasefunctions(cellvalues_p)\n n_basefuncs = n_basefuncs_v + n_basefuncs_p\n v▄, p▄ = 1, 2\n Kₑ = BlockedArray(zeros(n_basefuncs, n_basefuncs), [n_basefuncs_v, n_basefuncs_p], [n_basefuncs_v, n_basefuncs_p])\n\n # Assembly loop\n stiffness_assembler = start_assemble(K)\n for cell in CellIterator(dh)\n # Don't forget to initialize everything\n fill!(Kₑ, 0)\n\n Ferrite.reinit!(cellvalues_v, cell)\n Ferrite.reinit!(cellvalues_p, cell)\n\n for q_point in 1:getnquadpoints(cellvalues_v)\n dΩ = getdetJdV(cellvalues_v, q_point)\n\n for i in 1:n_basefuncs_v\n ∇φᵢ = shape_gradient(cellvalues_v, q_point, i)\n for j in 1:n_basefuncs_v\n ∇φⱼ = shape_gradient(cellvalues_v, q_point, j)\n Kₑ[BlockIndex((v▄, v▄), (i, j))] -= ν * ∇φᵢ ⊡ ∇φⱼ * dΩ\n end\n end\n\n for j in 1:n_basefuncs_p\n ψ = shape_value(cellvalues_p, q_point, j)\n for i in 1:n_basefuncs_v\n divφ = shape_divergence(cellvalues_v, q_point, i)\n Kₑ[BlockIndex((v▄, p▄), (i, j))] += (divφ * ψ) * dΩ\n Kₑ[BlockIndex((p▄, v▄), (j, i))] += (ψ * divφ) * dΩ\n end\n end\n end\n\n # Assemble `Kₑ` into the Stokes matrix `K`.\n assemble!(stiffness_assembler, celldofs(cell), Kₑ)\n end\n return K\nend;\n\nT = 6.0\nΔt₀ = 0.001\nΔt_save = 0.1\n\nM = allocate_matrix(dh);\nM = assemble_mass_matrix(cellvalues_v, cellvalues_p, M, dh);\n\nK = allocate_matrix(dh);\nK = assemble_stokes_matrix(cellvalues_v, cellvalues_p, ν, K, dh);\n\nu₀ = zeros(ndofs(dh))\napply!(u₀, ch);\n\njac_sparsity = sparse(K);\n\napply!(M, ch)\n\nstruct RHSparams\n K::SparseMatrixCSC\n ch::ConstraintHandler\n dh::DofHandler\n cellvalues_v::CellValues\n u::Vector\nend\np = RHSparams(K, ch, dh, cellvalues_v, copy(u₀))\n\nfunction ferrite_limiter!(u, _, p, t)\n update!(p.ch, t)\n return apply!(u, p.ch)\nend\n\nfunction navierstokes_rhs_element!(dvₑ, vₑ, cellvalues_v)\n n_basefuncs = getnbasefunctions(cellvalues_v)\n for q_point in 1:getnquadpoints(cellvalues_v)\n dΩ = getdetJdV(cellvalues_v, q_point)\n ∇v = function_gradient(cellvalues_v, q_point, vₑ)\n v = function_value(cellvalues_v, q_point, vₑ)\n for j in 1:n_basefuncs\n φⱼ = shape_value(cellvalues_v, q_point, j)\n\n dvₑ[j] -= v ⋅ ∇v' ⋅ φⱼ * dΩ\n end\n end\n return\nend\n\nfunction navierstokes!(du, u_uc, p::RHSparams, t)\n\n @unpack K, ch, dh, cellvalues_v, u = p\n\n u .= u_uc\n update!(ch, t)\n apply!(u, ch)\n\n # Linear contribution (Stokes operator)\n mul!(du, K, u) # du .= K * u\n\n # nonlinear contribution\n v_range = dof_range(dh, :v)\n n_basefuncs = getnbasefunctions(cellvalues_v)\n vₑ = zeros(n_basefuncs)\n duₑ = zeros(n_basefuncs)\n for cell in CellIterator(dh)\n Ferrite.reinit!(cellvalues_v, cell)\n v_celldofs = @view celldofs(cell)[v_range]\n vₑ .= @views u[v_celldofs]\n fill!(duₑ, 0.0)\n navierstokes_rhs_element!(duₑ, vₑ, cellvalues_v)\n assemble!(du, v_celldofs, duₑ)\n end\n return\nend;\n\nfunction navierstokes_jac_element!(Jₑ, vₑ, cellvalues_v)\n n_basefuncs = getnbasefunctions(cellvalues_v)\n for q_point in 1:getnquadpoints(cellvalues_v)\n dΩ = getdetJdV(cellvalues_v, q_point)\n ∇v = function_gradient(cellvalues_v, q_point, vₑ)\n v = function_value(cellvalues_v, q_point, vₑ)\n for j in 1:n_basefuncs\n φⱼ = shape_value(cellvalues_v, q_point, j)\n\n for i in 1:n_basefuncs\n φᵢ = shape_value(cellvalues_v, q_point, i)\n ∇φᵢ = shape_gradient(cellvalues_v, q_point, i)\n Jₑ[j, i] -= (φᵢ ⋅ ∇v' + v ⋅ ∇φᵢ') ⋅ φⱼ * dΩ\n end\n end\n end\n return\nend\n\nfunction navierstokes_jac!(J, u_uc, p, t)\n\n @unpack K, ch, dh, cellvalues_v, u = p\n\n u .= u_uc\n update!(ch, t)\n apply!(u, ch)\n\n # Linear contribution (Stokes operator)\n # Here we assume that J has exactly the same structure as K by construction\n nonzeros(J) .= nonzeros(K)\n\n assembler = start_assemble(J; fillzero = false)\n\n # Assemble variation of the nonlinear term\n n_basefuncs = getnbasefunctions(cellvalues_v)\n Jₑ = zeros(n_basefuncs, n_basefuncs)\n vₑ = zeros(n_basefuncs)\n v_range = dof_range(dh, :v)\n for cell in CellIterator(dh)\n Ferrite.reinit!(cellvalues_v, cell)\n v_celldofs = @view celldofs(cell)[v_range]\n\n vₑ .= @views u[v_celldofs]\n fill!(Jₑ, 0.0)\n navierstokes_jac_element!(Jₑ, vₑ, cellvalues_v)\n assemble!(assembler, v_celldofs, Jₑ)\n end\n\n return apply!(J, ch)\nend;\n\nrhs = ODEFunction(navierstokes!, mass_matrix = M; jac = navierstokes_jac!, jac_prototype = jac_sparsity)\nproblem = ODEProblem(rhs, u₀, (0.0, T), p);\n\nstruct FreeDofErrorNorm\n ch::ConstraintHandler\nend\n(fe_norm::FreeDofErrorNorm)(u::Union{AbstractFloat, Complex}, t) = DiffEqBase.ODE_DEFAULT_NORM(u, t)\n(fe_norm::FreeDofErrorNorm)(u::AbstractArray, t) = DiffEqBase.ODE_DEFAULT_NORM(u[fe_norm.ch.free_dofs], t)\n\ntimestepper = Rodas5P(autodiff = false, step_limiter! = ferrite_limiter!);\n\nintegrator = init(\n problem, timestepper; initializealg = NoInit(), dt = Δt₀,\n adaptive = true, abstol = 1.0e-4, reltol = 1.0e-5,\n progress = true, progress_steps = 1,\n verbose = true, internalnorm = FreeDofErrorNorm(ch), d_discontinuities = [1.0]\n);\n\npvd = paraview_collection(\"vortex-street\")\nfor (step, (u, t)) in enumerate(intervals(integrator))\n VTKGridFile(\"vortex-street-$step\", dh) do vtk\n write_solution(vtk, dh, u)\n pvd[t] = vtk\n end\nend\nvtk_save(pvd);","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"This page was generated using Literate.jl.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"EditURL = \"../literate-tutorials/transient_heat_equation.jl\"","category":"page"},{"location":"tutorials/transient_heat_equation/#tutorial-transient-heat-equation","page":"Transient heat equation","title":"Transient heat equation","text":"","category":"section"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"(Image: ) (Image: )","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"Figure 1: Visualization of the temperature time evolution on a unit square where the prescribed temperature on the upper and lower parts of the boundary increase with time.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"tip: Tip\nThis example is also available as a Jupyter notebook: transient_heat_equation.ipynb.","category":"page"},{"location":"tutorials/transient_heat_equation/#Introduction","page":"Transient heat equation","title":"Introduction","text":"","category":"section"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"In this example we extend the heat equation by a time dependent term, i.e.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":" fracpartial upartial t-nabla cdot (k nabla u) = f quad x in Omega","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"where u is the unknown temperature field, k the heat conductivity, f the heat source and Omega the domain. For simplicity, we hard code f = 01 and k = 10^-3. We define homogeneous Dirichlet boundary conditions along the left and right edge of the domain.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"u(xt) = 0 quad x in partial Omega_1","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"where partial Omega_1 denotes the left and right boundary of Omega.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"Further, we define heterogeneous Dirichlet boundary conditions at the top and bottom edge partial Omega_2. We choose a linearly increasing function a(t) that describes the temperature at this boundary","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"u(xt) = a(t) quad x in partial Omega_2","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"The semidiscrete weak form is given by","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"int_Omegav fracpartial upartial t mathrmdOmega + int_Omega k nabla v cdot nabla u mathrmdOmega = int_Omega f v mathrmdOmega","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"where v is a suitable test function. Now, we still need to discretize the time derivative. An implicit Euler scheme is applied, which yields:","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"int_Omega v u_n+1 mathrmdOmega + Delta tint_Omega k nabla v cdot nabla u_n+1 mathrmdOmega = Delta tint_Omega f v mathrmdOmega + int_Omega v u_n mathrmdOmega","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"If we assemble the discrete operators, we get the following algebraic system:","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"mathbfM mathbfu_n+1 + Δt mathbfK mathbfu_n+1 = Δt mathbff + mathbfM mathbfu_n","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"In this example we apply the boundary conditions to the assembled discrete operators (mass matrix mathbfM and stiffnes matrix mathbfK) only once. We utilize the fact that in finite element computations Dirichlet conditions can be applied by zero out rows and columns that correspond to a prescribed dof in the system matrix (mathbfA = Δt mathbfK + mathbfM) and setting the value of the right-hand side vector to the value of the Dirichlet condition. Thus, we only need to apply in every time step the Dirichlet condition to the right-hand side of the problem.","category":"page"},{"location":"tutorials/transient_heat_equation/#Commented-Program","page":"Transient heat equation","title":"Commented Program","text":"","category":"section"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"Now we solve the problem in Ferrite. What follows is a program spliced with comments. The full program, without comments, can be found in the next section.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"First we load Ferrite, and some other packages we need.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"using Ferrite, SparseArrays, WriteVTK","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"We create the same grid as in the heat equation example.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"grid = generate_grid(Quadrilateral, (100, 100));\nnothing #hide","category":"page"},{"location":"tutorials/transient_heat_equation/#Trial-and-test-functions","page":"Transient heat equation","title":"Trial and test functions","text":"","category":"section"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"Again, we define the structs that are responsible for the shape_value and shape_gradient evaluation.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"ip = Lagrange{RefQuadrilateral, 1}()\nqr = QuadratureRule{RefQuadrilateral}(2)\ncellvalues = CellValues(qr, ip);\nnothing #hide","category":"page"},{"location":"tutorials/transient_heat_equation/#Degrees-of-freedom","page":"Transient heat equation","title":"Degrees of freedom","text":"","category":"section"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"After this, we can define the DofHandler and distribute the DOFs of the problem.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"dh = DofHandler(grid)\nadd!(dh, :u, ip)\nclose!(dh);\nnothing #hide","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"By means of the DofHandler we can allocate the needed SparseMatrixCSC. M refers here to the so called mass matrix, which always occurs in time related terms, i.e.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"M_ij = int_Omega v_i u_j mathrmdOmega","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"where u_i and v_j are trial and test functions, respectively.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"K = allocate_matrix(dh);\nM = allocate_matrix(dh);\nnothing #hide","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"We also preallocate the right hand side","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"f = zeros(ndofs(dh));\nnothing #hide","category":"page"},{"location":"tutorials/transient_heat_equation/#Boundary-conditions","page":"Transient heat equation","title":"Boundary conditions","text":"","category":"section"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"In order to define the time dependent problem, we need some end time T and something that describes the linearly increasing Dirichlet boundary condition on partial Omega_2.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"max_temp = 100\nΔt = 1\nT = 200\nt_rise = 100\nch = ConstraintHandler(dh);\nnothing #hide","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"Here, we define the boundary condition related to partial Omega_1.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"∂Ω₁ = union(getfacetset.((grid,), [\"left\", \"right\"])...)\ndbc = Dirichlet(:u, ∂Ω₁, (x, t) -> 0)\nadd!(ch, dbc);\nnothing #hide","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"While the next code block corresponds to the linearly increasing temperature description on partial Omega_2 until t=t_rise, and then keep constant","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"∂Ω₂ = union(getfacetset.((grid,), [\"top\", \"bottom\"])...)\ndbc = Dirichlet(:u, ∂Ω₂, (x, t) -> max_temp * clamp(t / t_rise, 0, 1))\nadd!(ch, dbc)\nclose!(ch)\nupdate!(ch, 0.0);\nnothing #hide","category":"page"},{"location":"tutorials/transient_heat_equation/#Assembling-the-linear-system","page":"Transient heat equation","title":"Assembling the linear system","text":"","category":"section"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"As in the heat equation example we define a doassemble! function that assembles the diffusion parts of the equation:","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"function doassemble_K!(K::SparseMatrixCSC, f::Vector, cellvalues::CellValues, dh::DofHandler)\n\n n_basefuncs = getnbasefunctions(cellvalues)\n Ke = zeros(n_basefuncs, n_basefuncs)\n fe = zeros(n_basefuncs)\n\n assembler = start_assemble(K, f)\n\n for cell in CellIterator(dh)\n\n fill!(Ke, 0)\n fill!(fe, 0)\n\n reinit!(cellvalues, cell)\n\n for q_point in 1:getnquadpoints(cellvalues)\n dΩ = getdetJdV(cellvalues, q_point)\n\n for i in 1:n_basefuncs\n v = shape_value(cellvalues, q_point, i)\n ∇v = shape_gradient(cellvalues, q_point, i)\n fe[i] += 0.1 * v * dΩ\n for j in 1:n_basefuncs\n ∇u = shape_gradient(cellvalues, q_point, j)\n Ke[i, j] += 1.0e-3 * (∇v ⋅ ∇u) * dΩ\n end\n end\n end\n\n assemble!(assembler, celldofs(cell), Ke, fe)\n end\n return K, f\nend\nnothing # hide","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"In addition to the diffusive part, we also need a function that assembles the mass matrix M.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"function doassemble_M!(M::SparseMatrixCSC, cellvalues::CellValues, dh::DofHandler)\n\n n_basefuncs = getnbasefunctions(cellvalues)\n Me = zeros(n_basefuncs, n_basefuncs)\n\n assembler = start_assemble(M)\n\n for cell in CellIterator(dh)\n\n fill!(Me, 0)\n\n reinit!(cellvalues, cell)\n\n for q_point in 1:getnquadpoints(cellvalues)\n dΩ = getdetJdV(cellvalues, q_point)\n\n for i in 1:n_basefuncs\n v = shape_value(cellvalues, q_point, i)\n for j in 1:n_basefuncs\n u = shape_value(cellvalues, q_point, j)\n Me[i, j] += (v * u) * dΩ\n end\n end\n end\n\n assemble!(assembler, celldofs(cell), Me)\n end\n return M\nend\nnothing # hide","category":"page"},{"location":"tutorials/transient_heat_equation/#Solution-of-the-system","page":"Transient heat equation","title":"Solution of the system","text":"","category":"section"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"We first assemble all parts in the prior allocated SparseMatrixCSC.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"K, f = doassemble_K!(K, f, cellvalues, dh)\nM = doassemble_M!(M, cellvalues, dh)\nA = (Δt .* K) + M;\nnothing #hide","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"Now, we need to save all boundary condition related values of the unaltered system matrix A, which is done by get_rhs_data. The function returns a RHSData struct, which contains all needed information to apply the boundary conditions solely on the right-hand-side vector of the problem.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"rhsdata = get_rhs_data(ch, A);\nnothing #hide","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"We set the values at initial time step, denoted by uₙ, to a bubble-shape described by (x_1^2-1)(x_2^2-1), such that it is zero at the boundaries and the maximum temperature in the center.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"uₙ = zeros(length(f));\napply_analytical!(uₙ, dh, :u, x -> (x[1]^2 - 1) * (x[2]^2 - 1) * max_temp);\nnothing #hide","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"Here, we apply once the boundary conditions to the system matrix A.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"apply!(A, ch);\nnothing #hide","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"To store the solution, we initialize a paraview collection (.pvd) file,","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"pvd = paraview_collection(\"transient-heat\")\nVTKGridFile(\"transient-heat-0\", dh) do vtk\n write_solution(vtk, dh, uₙ)\n pvd[0.0] = vtk\nend","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"At this point everything is set up and we can finally approach the time loop.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"for (step, t) in enumerate(Δt:Δt:T)\n #First of all, we need to update the Dirichlet boundary condition values.\n update!(ch, t)\n\n #Secondly, we compute the right-hand-side of the problem.\n b = Δt .* f .+ M * uₙ\n #Then, we can apply the boundary conditions of the current time step.\n apply_rhs!(rhsdata, b, ch)\n\n #Finally, we can solve the time step and save the solution afterwards.\n u = A \\ b\n\n VTKGridFile(\"transient-heat-$step\", dh) do vtk\n write_solution(vtk, dh, u)\n pvd[t] = vtk\n end\n #At the end of the time loop, we set the previous solution to the current one and go to the next time step.\n uₙ .= u\nend","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"In order to use the .pvd file we need to store it to the disk, which is done by:","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"vtk_save(pvd);\nnothing #hide","category":"page"},{"location":"tutorials/transient_heat_equation/#transient_heat_equation-plain-program","page":"Transient heat equation","title":"Plain program","text":"","category":"section"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"Here follows a version of the program without any comments. The file is also available here: transient_heat_equation.jl.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"using Ferrite, SparseArrays, WriteVTK\n\ngrid = generate_grid(Quadrilateral, (100, 100));\n\nip = Lagrange{RefQuadrilateral, 1}()\nqr = QuadratureRule{RefQuadrilateral}(2)\ncellvalues = CellValues(qr, ip);\n\ndh = DofHandler(grid)\nadd!(dh, :u, ip)\nclose!(dh);\n\nK = allocate_matrix(dh);\nM = allocate_matrix(dh);\n\nf = zeros(ndofs(dh));\n\nmax_temp = 100\nΔt = 1\nT = 200\nt_rise = 100\nch = ConstraintHandler(dh);\n\n∂Ω₁ = union(getfacetset.((grid,), [\"left\", \"right\"])...)\ndbc = Dirichlet(:u, ∂Ω₁, (x, t) -> 0)\nadd!(ch, dbc);\n\n∂Ω₂ = union(getfacetset.((grid,), [\"top\", \"bottom\"])...)\ndbc = Dirichlet(:u, ∂Ω₂, (x, t) -> max_temp * clamp(t / t_rise, 0, 1))\nadd!(ch, dbc)\nclose!(ch)\nupdate!(ch, 0.0);\n\nfunction doassemble_K!(K::SparseMatrixCSC, f::Vector, cellvalues::CellValues, dh::DofHandler)\n\n n_basefuncs = getnbasefunctions(cellvalues)\n Ke = zeros(n_basefuncs, n_basefuncs)\n fe = zeros(n_basefuncs)\n\n assembler = start_assemble(K, f)\n\n for cell in CellIterator(dh)\n\n fill!(Ke, 0)\n fill!(fe, 0)\n\n reinit!(cellvalues, cell)\n\n for q_point in 1:getnquadpoints(cellvalues)\n dΩ = getdetJdV(cellvalues, q_point)\n\n for i in 1:n_basefuncs\n v = shape_value(cellvalues, q_point, i)\n ∇v = shape_gradient(cellvalues, q_point, i)\n fe[i] += 0.1 * v * dΩ\n for j in 1:n_basefuncs\n ∇u = shape_gradient(cellvalues, q_point, j)\n Ke[i, j] += 1.0e-3 * (∇v ⋅ ∇u) * dΩ\n end\n end\n end\n\n assemble!(assembler, celldofs(cell), Ke, fe)\n end\n return K, f\nend\n\nfunction doassemble_M!(M::SparseMatrixCSC, cellvalues::CellValues, dh::DofHandler)\n\n n_basefuncs = getnbasefunctions(cellvalues)\n Me = zeros(n_basefuncs, n_basefuncs)\n\n assembler = start_assemble(M)\n\n for cell in CellIterator(dh)\n\n fill!(Me, 0)\n\n reinit!(cellvalues, cell)\n\n for q_point in 1:getnquadpoints(cellvalues)\n dΩ = getdetJdV(cellvalues, q_point)\n\n for i in 1:n_basefuncs\n v = shape_value(cellvalues, q_point, i)\n for j in 1:n_basefuncs\n u = shape_value(cellvalues, q_point, j)\n Me[i, j] += (v * u) * dΩ\n end\n end\n end\n\n assemble!(assembler, celldofs(cell), Me)\n end\n return M\nend\n\nK, f = doassemble_K!(K, f, cellvalues, dh)\nM = doassemble_M!(M, cellvalues, dh)\nA = (Δt .* K) + M;\n\nrhsdata = get_rhs_data(ch, A);\n\nuₙ = zeros(length(f));\napply_analytical!(uₙ, dh, :u, x -> (x[1]^2 - 1) * (x[2]^2 - 1) * max_temp);\n\napply!(A, ch);\n\npvd = paraview_collection(\"transient-heat\")\nVTKGridFile(\"transient-heat-0\", dh) do vtk\n write_solution(vtk, dh, uₙ)\n pvd[0.0] = vtk\nend\n\nfor (step, t) in enumerate(Δt:Δt:T)\n #First of all, we need to update the Dirichlet boundary condition values.\n update!(ch, t)\n\n #Secondly, we compute the right-hand-side of the problem.\n b = Δt .* f .+ M * uₙ\n #Then, we can apply the boundary conditions of the current time step.\n apply_rhs!(rhsdata, b, ch)\n\n #Finally, we can solve the time step and save the solution afterwards.\n u = A \\ b\n\n VTKGridFile(\"transient-heat-$step\", dh) do vtk\n write_solution(vtk, dh, u)\n pvd[t] = vtk\n end\n #At the end of the time loop, we set the previous solution to the current one and go to the next time step.\n uₙ .= u\nend\n\nvtk_save(pvd);","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"This page was generated using Literate.jl.","category":"page"},{"location":"gallery/#Code-gallery","page":"Code gallery","title":"Code gallery","text":"","category":"section"},{"location":"gallery/","page":"Code gallery","title":"Code gallery","text":"This page gives an overview of the code gallery. Compared to the tutorials, these programs do not focus on teaching Ferrite, but rather focus on showing how Ferrite can be used \"in the wild\".","category":"page"},{"location":"gallery/","page":"Code gallery","title":"Code gallery","text":"note: Contribute to the gallery!\nMost of the gallery is user contributed. If you use Ferrite, and have something you want to share, please contribute to the gallery! This could, for example, be your research code for a published paper, some interesting application, or just some nice trick.","category":"page"},{"location":"gallery/","page":"Code gallery","title":"Code gallery","text":"","category":"page"},{"location":"gallery/#[Helmholtz-equation](helmholtz.md)","page":"Code gallery","title":"Helmholtz equation","text":"","category":"section"},{"location":"gallery/","page":"Code gallery","title":"Code gallery","text":"Solves the Helmholtz equation on the unit square using a combination of Dirichlet and Neumann boundary conditions and the method of manufactured solutions.","category":"page"},{"location":"gallery/","page":"Code gallery","title":"Code gallery","text":"Contributed by: Kristoffer Carlsson (@KristofferC).","category":"page"},{"location":"gallery/","page":"Code gallery","title":"Code gallery","text":"","category":"page"},{"location":"gallery/#[Nearly-incompressible-hyperelasticity](quasi_incompressible_hyperelasticity.md)","page":"Code gallery","title":"Nearly incompressible hyperelasticity","text":"","category":"section"},{"location":"gallery/","page":"Code gallery","title":"Code gallery","text":"This program combines the ideas from Tutorial 3: Incompressible elasticity and Tutorial 4: Hyperelasticity to construct a mixed element solving three-dimensional displacement-pressure equations.","category":"page"},{"location":"gallery/","page":"Code gallery","title":"Code gallery","text":"Contributed by: Bhavesh Shrimali (@bhaveshshrimali).","category":"page"},{"location":"gallery/","page":"Code gallery","title":"Code gallery","text":"","category":"page"},{"location":"gallery/#[Ginzburg-Landau-model-energy-minimization](landau.md)","page":"Code gallery","title":"Ginzburg-Landau model energy minimization","text":"","category":"section"},{"location":"gallery/","page":"Code gallery","title":"Code gallery","text":"A basic Ginzburg-Landau model is solved. ForwardDiff.jl is used to compute the gradient and hessian of the energy function. Multi-threading is used to parallelize the assembly procedure.","category":"page"},{"location":"gallery/","page":"Code gallery","title":"Code gallery","text":"Contributed by: Louis Ponet (@louisponet).","category":"page"},{"location":"gallery/","page":"Code gallery","title":"Code gallery","text":"","category":"page"},{"location":"gallery/#[Topology-optimization](topology_optimization.md)","page":"Code gallery","title":"Topology optimization","text":"","category":"section"},{"location":"gallery/","page":"Code gallery","title":"Code gallery","text":"Topology optimization is shown for the bending problem by using a SIMP material model. To avoid numerical instabilities, a regularization scheme requiring the calculation of the Laplacian is imposed, which is done by using the grid topology functionalities.","category":"page"},{"location":"gallery/","page":"Code gallery","title":"Code gallery","text":"Contributed by: Mischa Blaszczyk (@blaszm).","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"EditURL = \"../literate-gallery/quasi_incompressible_hyperelasticity.jl\"","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/#tutorial-nearly-incompressible-hyperelasticity","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"","category":"section"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"(Image: )","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"tip: Tip\nThis example is also available as a Jupyter notebook: quasi_incompressible_hyperelasticity.ipynb","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/#Introduction","page":"Nearly Incompressible Hyperelasticity","title":"Introduction","text":"","category":"section"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"In this example we study quasi- or nearly-incompressible hyperelasticity using the stable Taylor-Hood approximation. In spirit, this example is the nonlinear analogue of incompressible_elasticity and the incompressible analogue of hyperelasticity. Much of the code therefore follows from the above two examples. The problem is formulated in the undeformed or reference configuration with the displacement mathbfu and pressure p being the unknown fields. We now briefly outline the formulation. Consider the standard hyperelasticity problem","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":" mathbfu = argmin_mathbfvinmathcalK(Omega)Pi(mathbfv)quad textwherequad Pi(mathbfv) = int_Omega Psi(mathbfv) mathrmdOmega ","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"where mathcalK(Omega) is a suitable function space.","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"For clarity of presentation we ignore any non-zero surface tractions and body forces and instead consider only applied displacements (i.e. non-homogeneous dirichlet boundary conditions). Moreover we stick our attention to the standard Neo-Hookean stored energy density","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":" Psi(mathbfu) = fracmu2left(I_1 - 3 right) - mu log(J) + fraclambda2left( J - 1right)^2","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"where I_1 = mathrmtr(mathbfC) = mathrmtr(mathbfF^mathrmT mathbfF) = F_ijF_ij and J = det(mathbfF) denote the standard invariants of the deformation gradient tensor mathbfF = mathbfI+nabla_mathbfX mathbfu. The above problem is ill-posed in the limit of incompressibility (or near-incompressibility), namely when","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":" lambdamu rightarrow +infty","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"In order to alleviate the problem, we consider the partial legendre transform of the strain energy density Psi with respect to J = det(mathbfF), namely","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":" widehatPsi(mathbfu p) = sup_J left p(J - 1) - fracmu2left(I_1 - 3 right) + mu log(J) - fraclambda2left( J - 1right)^2 right","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"The supremum, say J^star, can be calculated in closed form by the first order optimailty condition partialwidehatPsipartial J = 0. This gives","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":" J^star(p) = fraclambda + p + sqrt(lambda + p)^2 + 4 lambda mu (2 lambda)","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"Furthermore, taking the partial legendre transform of widehatPsi once again, gives us back the original problem, i.e.","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":" Psi(mathbfu) = Psi^star(mathbfu p) = sup_p left p(J - 1) - p(J^star - 1) + fracmu2left(I_1 - 3 right) - mu log(J^star) + fraclambda2left( J^star - 1right)^2 right","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"Therefore our original hyperelasticity problem can now be reformulated as","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":" inf_mathbfuinmathcalK(Omega)sup_p int_OmegaPsi^star (mathbfu p) mathrmdOmega","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"The total (modified) energy Pi^star can then be written as","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":" Pi^star(mathbfu p) = int_Omega p (J - J^star) mathrmdOmega + int_Omega fracmu2 left( I_1 - 3right) mathrmdOmega - int_Omega mulog(J^star) mathrmdOmega + int_Omega fraclambda2left( J^star - 1 right)^2 mathrmdOmega","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"The Euler-Lagrange equations corresponding to the above energy give us our governing PDEs in the weak form, namely","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":" int_Omega fracpartialPsi^starpartial mathbfFdelta mathbfF mathrmdOmega = 0","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"and","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":" int_Omega fracpartial Psi^starpartial pdelta p mathrmdOmega = 0","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"where delta mathrmF = delta mathrmgrad_0(mathbfu) = mathrmgrad_0(delta mathbfu) and delta mathbfu and delta p denote arbitrary variations with respect to displacement and pressure (or the test functions). See the references below for a more detailed explanation of the above mathematical trick. Now, in order to apply Newton's method to the above problem, we further need to linearize the above equations and calculate the respective hessians (or tangents), namely, partial^2Psi^starpartial mathbfF^2, partial^2Psi^starpartial p^2 and partial^2Psi^starpartial mathbfFpartial p which, using Tensors.jl, can be determined conveniently using automatic differentiation (see the code below). Hence we only need to define the above potential. The remaineder of the example follows similarly.","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/#References","page":"Nearly Incompressible Hyperelasticity","title":"References","text":"","category":"section"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"A paradigm for higher-order polygonal elements in finite elasticity using a gradient correction scheme, CMAME 2016, 306, 216–251\nApproximation of incompressible large deformation elastic problems: some unresolved issues, Computational Mechanics, 2013","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/#Implementation","page":"Nearly Incompressible Hyperelasticity","title":"Implementation","text":"","category":"section"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"We now get to the actual code. First, we import the respective packages","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"using Ferrite, Tensors, ProgressMeter, WriteVTK\nusing BlockArrays, SparseArrays, LinearAlgebra","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"and the corresponding struct to store our material properties.","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"struct NeoHooke\n μ::Float64\n λ::Float64\nend","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"We then create a function to generate a simple test mesh on which to compute FE solution. We also mark the boundaries to later assign Dirichlet boundary conditions","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"function importTestGrid()\n grid = generate_grid(Tetrahedron, (5, 5, 5), zero(Vec{3}), ones(Vec{3}))\n addfacetset!(grid, \"myBottom\", x -> norm(x[2]) ≈ 0.0)\n addfacetset!(grid, \"myBack\", x -> norm(x[3]) ≈ 0.0)\n addfacetset!(grid, \"myRight\", x -> norm(x[1]) ≈ 1.0)\n addfacetset!(grid, \"myLeft\", x -> norm(x[1]) ≈ 0.0)\n return grid\nend;\nnothing #hide","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"The function to create corresponding cellvalues for the displacement field u and pressure p follows in a similar fashion from the incompressible_elasticity example","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"function create_values(interpolation_u, interpolation_p)\n # quadrature rules\n qr = QuadratureRule{RefTetrahedron}(4)\n facet_qr = FacetQuadratureRule{RefTetrahedron}(4)\n\n # cell and facetvalues for u\n cellvalues_u = CellValues(qr, interpolation_u)\n facetvalues_u = FacetValues(facet_qr, interpolation_u)\n\n # cellvalues for p\n cellvalues_p = CellValues(qr, interpolation_p)\n\n return cellvalues_u, cellvalues_p, facetvalues_u\nend;\nnothing #hide","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"We now create the function for Ψ*","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"function Ψ(F, p, mp::NeoHooke)\n μ = mp.μ\n λ = mp.λ\n Ic = tr(tdot(F))\n J = det(F)\n Js = (λ + p + sqrt((λ + p)^2.0 + 4.0 * λ * μ)) / (2.0 * λ)\n return p * (Js - J) + μ / 2 * (Ic - 3) - μ * log(Js) + λ / 2 * (Js - 1)^2\nend;\nnothing #hide","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"and it's derivatives (required in computing the jacobian and hessian respectively)","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"function constitutive_driver(F, p, mp::NeoHooke)\n # Compute all derivatives in one function call\n ∂²Ψ∂F², ∂Ψ∂F = Tensors.hessian(y -> Ψ(y, p, mp), F, :all)\n ∂²Ψ∂p², ∂Ψ∂p = Tensors.hessian(y -> Ψ(F, y, mp), p, :all)\n ∂²Ψ∂F∂p = Tensors.gradient(q -> Tensors.gradient(y -> Ψ(y, q, mp), F), p)\n return ∂Ψ∂F, ∂²Ψ∂F², ∂Ψ∂p, ∂²Ψ∂p², ∂²Ψ∂F∂p\nend;\nnothing #hide","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"The functions to create the DofHandler and ConstraintHandler (to assign corresponding boundary conditions) follow likewise from the incompressible elasticity example, namely","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"function create_dofhandler(grid, ipu, ipp)\n dh = DofHandler(grid)\n add!(dh, :u, ipu) # displacement dim = 3\n add!(dh, :p, ipp) # pressure dim = 1\n close!(dh)\n return dh\nend;\nnothing #hide","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"We are simulating a uniaxial tensile loading of a unit cube. Hence we apply a displacement field (:u) in x direction on the right face. The left, bottom and back facets are fixed in the x, y and z components of the displacement so as to emulate the uniaxial nature of the loading.","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"function create_bc(dh)\n dbc = ConstraintHandler(dh)\n add!(dbc, Dirichlet(:u, getfacetset(dh.grid, \"myLeft\"), (x, t) -> zero(Vec{1}), [1]))\n add!(dbc, Dirichlet(:u, getfacetset(dh.grid, \"myBottom\"), (x, t) -> zero(Vec{1}), [2]))\n add!(dbc, Dirichlet(:u, getfacetset(dh.grid, \"myBack\"), (x, t) -> zero(Vec{1}), [3]))\n add!(dbc, Dirichlet(:u, getfacetset(dh.grid, \"myRight\"), (x, t) -> t * ones(Vec{1}), [1]))\n close!(dbc)\n Ferrite.update!(dbc, 0.0)\n return dbc\nend;\nnothing #hide","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"Also, since we are considering incompressible hyperelasticity, an interesting quantity that we can compute is the deformed volume of the solid. It is easy to show that this is equal to ∫J*dΩ where J=det(F). This can be done at the level of each element (cell)","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"function calculate_element_volume(cell, cellvalues_u, ue)\n reinit!(cellvalues_u, cell)\n evol::Float64 = 0.0\n for qp in 1:getnquadpoints(cellvalues_u)\n dΩ = getdetJdV(cellvalues_u, qp)\n ∇u = function_gradient(cellvalues_u, qp, ue)\n F = one(∇u) + ∇u\n J = det(F)\n evol += J * dΩ\n end\n return evol\nend;\nnothing #hide","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"and then assembled over all the cells (elements)","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"function calculate_volume_deformed_mesh(w, dh::DofHandler, cellvalues_u)\n evol::Float64 = 0.0\n for cell in CellIterator(dh)\n global_dofs = celldofs(cell)\n nu = getnbasefunctions(cellvalues_u)\n global_dofs_u = global_dofs[1:nu]\n ue = w[global_dofs_u]\n δevol = calculate_element_volume(cell, cellvalues_u, ue)\n evol += δevol\n end\n return evol\nend;\nnothing #hide","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"The function to assemble the element stiffness matrix for each element in the mesh now has a block structure like in incompressible_elasticity.","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"function assemble_element!(Ke, fe, cell, cellvalues_u, cellvalues_p, mp, ue, pe)\n # Reinitialize cell values, and reset output arrays\n ublock, pblock = 1, 2\n reinit!(cellvalues_u, cell)\n reinit!(cellvalues_p, cell)\n fill!(Ke, 0.0)\n fill!(fe, 0.0)\n\n n_basefuncs_u = getnbasefunctions(cellvalues_u)\n n_basefuncs_p = getnbasefunctions(cellvalues_p)\n\n for qp in 1:getnquadpoints(cellvalues_u)\n dΩ = getdetJdV(cellvalues_u, qp)\n # Compute deformation gradient F\n ∇u = function_gradient(cellvalues_u, qp, ue)\n p = function_value(cellvalues_p, qp, pe)\n F = one(∇u) + ∇u\n\n # Compute first Piola-Kirchhoff stress and tangent modulus\n ∂Ψ∂F, ∂²Ψ∂F², ∂Ψ∂p, ∂²Ψ∂p², ∂²Ψ∂F∂p = constitutive_driver(F, p, mp)\n\n # Loop over the `u`-test functions to calculate the `u`-`u` and `u`-`p` blocks\n for i in 1:n_basefuncs_u\n # gradient of the test function\n ∇δui = shape_gradient(cellvalues_u, qp, i)\n # Add contribution to the residual from this test function\n fe[BlockIndex((ublock), (i))] += (∇δui ⊡ ∂Ψ∂F) * dΩ\n\n ∇δui∂S∂F = ∇δui ⊡ ∂²Ψ∂F²\n for j in 1:n_basefuncs_u\n ∇δuj = shape_gradient(cellvalues_u, qp, j)\n\n # Add contribution to the tangent\n Ke[BlockIndex((ublock, ublock), (i, j))] += (∇δui∂S∂F ⊡ ∇δuj) * dΩ\n end\n # Loop over the `p`-test functions\n for j in 1:n_basefuncs_p\n δp = shape_value(cellvalues_p, qp, j)\n # Add contribution to the tangent\n Ke[BlockIndex((ublock, pblock), (i, j))] += (∂²Ψ∂F∂p ⊡ ∇δui) * δp * dΩ\n end\n end\n # Loop over the `p`-test functions to calculate the `p-`u` and `p`-`p` blocks\n for i in 1:n_basefuncs_p\n δp = shape_value(cellvalues_p, qp, i)\n fe[BlockIndex((pblock), (i))] += (δp * ∂Ψ∂p) * dΩ\n\n for j in 1:n_basefuncs_u\n ∇δuj = shape_gradient(cellvalues_u, qp, j)\n Ke[BlockIndex((pblock, ublock), (i, j))] += ∇δuj ⊡ ∂²Ψ∂F∂p * δp * dΩ\n end\n for j in 1:n_basefuncs_p\n δp = shape_value(cellvalues_p, qp, j)\n Ke[BlockIndex((pblock, pblock), (i, j))] += δp * ∂²Ψ∂p² * δp * dΩ\n end\n end\n end\n return\nend;\nnothing #hide","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"The only thing that changes in the assembly of the global stiffness matrix is slicing the corresponding element dofs for the displacement (see global_dofsu) and pressure (global_dofsp).","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"function assemble_global!(\n K::SparseMatrixCSC, f, cellvalues_u::CellValues,\n cellvalues_p::CellValues, dh::DofHandler, mp::NeoHooke, w\n )\n nu = getnbasefunctions(cellvalues_u)\n np = getnbasefunctions(cellvalues_p)\n\n # start_assemble resets K and f\n fe = BlockedArray(zeros(nu + np), [nu, np]) # local force vector\n ke = BlockedArray(zeros(nu + np, nu + np), [nu, np], [nu, np]) # local stiffness matrix\n\n assembler = start_assemble(K, f)\n # Loop over all cells in the grid\n for cell in CellIterator(dh)\n global_dofs = celldofs(cell)\n global_dofsu = global_dofs[1:nu] # first nu dofs are displacement\n global_dofsp = global_dofs[(nu + 1):end] # last np dofs are pressure\n @assert size(global_dofs, 1) == nu + np # sanity check\n ue = w[global_dofsu] # displacement dofs for the current cell\n pe = w[global_dofsp] # pressure dofs for the current cell\n assemble_element!(ke, fe, cell, cellvalues_u, cellvalues_p, mp, ue, pe)\n assemble!(assembler, global_dofs, ke, fe)\n end\n return\nend;\nnothing #hide","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"We now define a main function solve. For nonlinear quasistatic problems we often like to parameterize the solution in terms of a pseudo time like parameter, which in this case is used to gradually apply the boundary displacement on the right face. Also for definitenessm we consider λ/μ = 10⁴","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"function solve(interpolation_u, interpolation_p)\n\n # import the mesh\n grid = importTestGrid()\n\n # Material parameters\n μ = 1.0\n λ = 1.0e4 * μ\n mp = NeoHooke(μ, λ)\n\n # Create the DofHandler and CellValues\n dh = create_dofhandler(grid, interpolation_u, interpolation_p)\n cellvalues_u, cellvalues_p, facetvalues_u = create_values(interpolation_u, interpolation_p)\n\n # Create the DirichletBCs\n dbc = create_bc(dh)\n\n # Pre-allocation of vectors for the solution and Newton increments\n _ndofs = ndofs(dh)\n w = zeros(_ndofs)\n ΔΔw = zeros(_ndofs)\n apply!(w, dbc)\n\n # Create the sparse matrix and residual vector\n K = allocate_matrix(dh)\n f = zeros(_ndofs)\n\n # We run the simulation parameterized by a time like parameter. `Tf` denotes the final value\n # of this parameter, and Δt denotes its increment in each step\n Tf = 2.0\n Δt = 0.1\n NEWTON_TOL = 1.0e-8\n\n pvd = paraview_collection(\"hyperelasticity_incomp_mixed\")\n for (step, t) in enumerate(0.0:Δt:Tf)\n # Perform Newton iterations\n Ferrite.update!(dbc, t)\n apply!(w, dbc)\n newton_itr = -1\n prog = ProgressMeter.ProgressThresh(NEWTON_TOL; desc = \"Solving @ time $t of $Tf;\")\n fill!(ΔΔw, 0.0)\n while true\n newton_itr += 1\n assemble_global!(K, f, cellvalues_u, cellvalues_p, dh, mp, w)\n norm_res = norm(f[Ferrite.free_dofs(dbc)])\n apply_zero!(K, f, dbc)\n # Only display output at specific load steps\n if t % (5 * Δt) == 0\n ProgressMeter.update!(prog, norm_res; showvalues = [(:iter, newton_itr)])\n end\n if norm_res < NEWTON_TOL\n break\n elseif newton_itr > 30\n error(\"Reached maximum Newton iterations, aborting\")\n end\n # Compute the incremental `dof`-vector (both displacement and pressure)\n ΔΔw .= K \\ f\n\n apply_zero!(ΔΔw, dbc)\n w .-= ΔΔw\n end\n\n # Save the solution fields\n VTKGridFile(\"hyperelasticity_incomp_mixed_$step\", grid) do vtk\n write_solution(vtk, dh, w)\n pvd[t] = vtk\n end\n end\n vtk_save(pvd)\n vol_def = calculate_volume_deformed_mesh(w, dh, cellvalues_u)\n print(\"Deformed volume is $vol_def\")\n return vol_def\nend;\nnothing #hide","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"We can now test the solution using the Taylor-Hood approximation","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"quadratic_u = Lagrange{RefTetrahedron, 2}()^3\nlinear_p = Lagrange{RefTetrahedron, 1}()\nvol_def = solve(quadratic_u, linear_p)","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"The deformed volume is indeed close to 1 (as should be for a nearly incompressible material).","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/#Plain-program","page":"Nearly Incompressible Hyperelasticity","title":"Plain program","text":"","category":"section"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"Here follows a version of the program without any comments. The file is also available here: quasi_incompressible_hyperelasticity.jl.","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"using Ferrite, Tensors, ProgressMeter, WriteVTK\nusing BlockArrays, SparseArrays, LinearAlgebra\n\nstruct NeoHooke\n μ::Float64\n λ::Float64\nend\n\nfunction importTestGrid()\n grid = generate_grid(Tetrahedron, (5, 5, 5), zero(Vec{3}), ones(Vec{3}))\n addfacetset!(grid, \"myBottom\", x -> norm(x[2]) ≈ 0.0)\n addfacetset!(grid, \"myBack\", x -> norm(x[3]) ≈ 0.0)\n addfacetset!(grid, \"myRight\", x -> norm(x[1]) ≈ 1.0)\n addfacetset!(grid, \"myLeft\", x -> norm(x[1]) ≈ 0.0)\n return grid\nend;\n\nfunction create_values(interpolation_u, interpolation_p)\n # quadrature rules\n qr = QuadratureRule{RefTetrahedron}(4)\n facet_qr = FacetQuadratureRule{RefTetrahedron}(4)\n\n # cell and facetvalues for u\n cellvalues_u = CellValues(qr, interpolation_u)\n facetvalues_u = FacetValues(facet_qr, interpolation_u)\n\n # cellvalues for p\n cellvalues_p = CellValues(qr, interpolation_p)\n\n return cellvalues_u, cellvalues_p, facetvalues_u\nend;\n\nfunction Ψ(F, p, mp::NeoHooke)\n μ = mp.μ\n λ = mp.λ\n Ic = tr(tdot(F))\n J = det(F)\n Js = (λ + p + sqrt((λ + p)^2.0 + 4.0 * λ * μ)) / (2.0 * λ)\n return p * (Js - J) + μ / 2 * (Ic - 3) - μ * log(Js) + λ / 2 * (Js - 1)^2\nend;\n\nfunction constitutive_driver(F, p, mp::NeoHooke)\n # Compute all derivatives in one function call\n ∂²Ψ∂F², ∂Ψ∂F = Tensors.hessian(y -> Ψ(y, p, mp), F, :all)\n ∂²Ψ∂p², ∂Ψ∂p = Tensors.hessian(y -> Ψ(F, y, mp), p, :all)\n ∂²Ψ∂F∂p = Tensors.gradient(q -> Tensors.gradient(y -> Ψ(y, q, mp), F), p)\n return ∂Ψ∂F, ∂²Ψ∂F², ∂Ψ∂p, ∂²Ψ∂p², ∂²Ψ∂F∂p\nend;\n\nfunction create_dofhandler(grid, ipu, ipp)\n dh = DofHandler(grid)\n add!(dh, :u, ipu) # displacement dim = 3\n add!(dh, :p, ipp) # pressure dim = 1\n close!(dh)\n return dh\nend;\n\nfunction create_bc(dh)\n dbc = ConstraintHandler(dh)\n add!(dbc, Dirichlet(:u, getfacetset(dh.grid, \"myLeft\"), (x, t) -> zero(Vec{1}), [1]))\n add!(dbc, Dirichlet(:u, getfacetset(dh.grid, \"myBottom\"), (x, t) -> zero(Vec{1}), [2]))\n add!(dbc, Dirichlet(:u, getfacetset(dh.grid, \"myBack\"), (x, t) -> zero(Vec{1}), [3]))\n add!(dbc, Dirichlet(:u, getfacetset(dh.grid, \"myRight\"), (x, t) -> t * ones(Vec{1}), [1]))\n close!(dbc)\n Ferrite.update!(dbc, 0.0)\n return dbc\nend;\n\nfunction calculate_element_volume(cell, cellvalues_u, ue)\n reinit!(cellvalues_u, cell)\n evol::Float64 = 0.0\n for qp in 1:getnquadpoints(cellvalues_u)\n dΩ = getdetJdV(cellvalues_u, qp)\n ∇u = function_gradient(cellvalues_u, qp, ue)\n F = one(∇u) + ∇u\n J = det(F)\n evol += J * dΩ\n end\n return evol\nend;\n\nfunction calculate_volume_deformed_mesh(w, dh::DofHandler, cellvalues_u)\n evol::Float64 = 0.0\n for cell in CellIterator(dh)\n global_dofs = celldofs(cell)\n nu = getnbasefunctions(cellvalues_u)\n global_dofs_u = global_dofs[1:nu]\n ue = w[global_dofs_u]\n δevol = calculate_element_volume(cell, cellvalues_u, ue)\n evol += δevol\n end\n return evol\nend;\n\nfunction assemble_element!(Ke, fe, cell, cellvalues_u, cellvalues_p, mp, ue, pe)\n # Reinitialize cell values, and reset output arrays\n ublock, pblock = 1, 2\n reinit!(cellvalues_u, cell)\n reinit!(cellvalues_p, cell)\n fill!(Ke, 0.0)\n fill!(fe, 0.0)\n\n n_basefuncs_u = getnbasefunctions(cellvalues_u)\n n_basefuncs_p = getnbasefunctions(cellvalues_p)\n\n for qp in 1:getnquadpoints(cellvalues_u)\n dΩ = getdetJdV(cellvalues_u, qp)\n # Compute deformation gradient F\n ∇u = function_gradient(cellvalues_u, qp, ue)\n p = function_value(cellvalues_p, qp, pe)\n F = one(∇u) + ∇u\n\n # Compute first Piola-Kirchhoff stress and tangent modulus\n ∂Ψ∂F, ∂²Ψ∂F², ∂Ψ∂p, ∂²Ψ∂p², ∂²Ψ∂F∂p = constitutive_driver(F, p, mp)\n\n # Loop over the `u`-test functions to calculate the `u`-`u` and `u`-`p` blocks\n for i in 1:n_basefuncs_u\n # gradient of the test function\n ∇δui = shape_gradient(cellvalues_u, qp, i)\n # Add contribution to the residual from this test function\n fe[BlockIndex((ublock), (i))] += (∇δui ⊡ ∂Ψ∂F) * dΩ\n\n ∇δui∂S∂F = ∇δui ⊡ ∂²Ψ∂F²\n for j in 1:n_basefuncs_u\n ∇δuj = shape_gradient(cellvalues_u, qp, j)\n\n # Add contribution to the tangent\n Ke[BlockIndex((ublock, ublock), (i, j))] += (∇δui∂S∂F ⊡ ∇δuj) * dΩ\n end\n # Loop over the `p`-test functions\n for j in 1:n_basefuncs_p\n δp = shape_value(cellvalues_p, qp, j)\n # Add contribution to the tangent\n Ke[BlockIndex((ublock, pblock), (i, j))] += (∂²Ψ∂F∂p ⊡ ∇δui) * δp * dΩ\n end\n end\n # Loop over the `p`-test functions to calculate the `p-`u` and `p`-`p` blocks\n for i in 1:n_basefuncs_p\n δp = shape_value(cellvalues_p, qp, i)\n fe[BlockIndex((pblock), (i))] += (δp * ∂Ψ∂p) * dΩ\n\n for j in 1:n_basefuncs_u\n ∇δuj = shape_gradient(cellvalues_u, qp, j)\n Ke[BlockIndex((pblock, ublock), (i, j))] += ∇δuj ⊡ ∂²Ψ∂F∂p * δp * dΩ\n end\n for j in 1:n_basefuncs_p\n δp = shape_value(cellvalues_p, qp, j)\n Ke[BlockIndex((pblock, pblock), (i, j))] += δp * ∂²Ψ∂p² * δp * dΩ\n end\n end\n end\n return\nend;\n\nfunction assemble_global!(\n K::SparseMatrixCSC, f, cellvalues_u::CellValues,\n cellvalues_p::CellValues, dh::DofHandler, mp::NeoHooke, w\n )\n nu = getnbasefunctions(cellvalues_u)\n np = getnbasefunctions(cellvalues_p)\n\n # start_assemble resets K and f\n fe = BlockedArray(zeros(nu + np), [nu, np]) # local force vector\n ke = BlockedArray(zeros(nu + np, nu + np), [nu, np], [nu, np]) # local stiffness matrix\n\n assembler = start_assemble(K, f)\n # Loop over all cells in the grid\n for cell in CellIterator(dh)\n global_dofs = celldofs(cell)\n global_dofsu = global_dofs[1:nu] # first nu dofs are displacement\n global_dofsp = global_dofs[(nu + 1):end] # last np dofs are pressure\n @assert size(global_dofs, 1) == nu + np # sanity check\n ue = w[global_dofsu] # displacement dofs for the current cell\n pe = w[global_dofsp] # pressure dofs for the current cell\n assemble_element!(ke, fe, cell, cellvalues_u, cellvalues_p, mp, ue, pe)\n assemble!(assembler, global_dofs, ke, fe)\n end\n return\nend;\n\nfunction solve(interpolation_u, interpolation_p)\n\n # import the mesh\n grid = importTestGrid()\n\n # Material parameters\n μ = 1.0\n λ = 1.0e4 * μ\n mp = NeoHooke(μ, λ)\n\n # Create the DofHandler and CellValues\n dh = create_dofhandler(grid, interpolation_u, interpolation_p)\n cellvalues_u, cellvalues_p, facetvalues_u = create_values(interpolation_u, interpolation_p)\n\n # Create the DirichletBCs\n dbc = create_bc(dh)\n\n # Pre-allocation of vectors for the solution and Newton increments\n _ndofs = ndofs(dh)\n w = zeros(_ndofs)\n ΔΔw = zeros(_ndofs)\n apply!(w, dbc)\n\n # Create the sparse matrix and residual vector\n K = allocate_matrix(dh)\n f = zeros(_ndofs)\n\n # We run the simulation parameterized by a time like parameter. `Tf` denotes the final value\n # of this parameter, and Δt denotes its increment in each step\n Tf = 2.0\n Δt = 0.1\n NEWTON_TOL = 1.0e-8\n\n pvd = paraview_collection(\"hyperelasticity_incomp_mixed\")\n for (step, t) in enumerate(0.0:Δt:Tf)\n # Perform Newton iterations\n Ferrite.update!(dbc, t)\n apply!(w, dbc)\n newton_itr = -1\n prog = ProgressMeter.ProgressThresh(NEWTON_TOL; desc = \"Solving @ time $t of $Tf;\")\n fill!(ΔΔw, 0.0)\n while true\n newton_itr += 1\n assemble_global!(K, f, cellvalues_u, cellvalues_p, dh, mp, w)\n norm_res = norm(f[Ferrite.free_dofs(dbc)])\n apply_zero!(K, f, dbc)\n # Only display output at specific load steps\n if t % (5 * Δt) == 0\n ProgressMeter.update!(prog, norm_res; showvalues = [(:iter, newton_itr)])\n end\n if norm_res < NEWTON_TOL\n break\n elseif newton_itr > 30\n error(\"Reached maximum Newton iterations, aborting\")\n end\n # Compute the incremental `dof`-vector (both displacement and pressure)\n ΔΔw .= K \\ f\n\n apply_zero!(ΔΔw, dbc)\n w .-= ΔΔw\n end\n\n # Save the solution fields\n VTKGridFile(\"hyperelasticity_incomp_mixed_$step\", grid) do vtk\n write_solution(vtk, dh, w)\n pvd[t] = vtk\n end\n end\n vtk_save(pvd)\n vol_def = calculate_volume_deformed_mesh(w, dh, cellvalues_u)\n print(\"Deformed volume is $vol_def\")\n return vol_def\nend;\n\nquadratic_u = Lagrange{RefTetrahedron, 2}()^3\nlinear_p = Lagrange{RefTetrahedron, 1}()\nvol_def = solve(quadratic_u, linear_p)","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"This page was generated using Literate.jl.","category":"page"},{"location":"reference/utils/","page":"Development utility functions","title":"Development utility functions","text":"DocTestSetup = :(using Ferrite)","category":"page"},{"location":"reference/utils/#Development-utility-functions","page":"Development utility functions","title":"Development utility functions","text":"","category":"section"},{"location":"reference/utils/","page":"Development utility functions","title":"Development utility functions","text":"Ferrite.debug_mode","category":"page"},{"location":"reference/utils/#Ferrite.debug_mode","page":"Development utility functions","title":"Ferrite.debug_mode","text":"Ferrite.debug_mode(; enable=true)\n\nHelper to turn on (enable=true) or off (enable=false) debug expressions in Ferrite.\n\nDebug mode influences Ferrite.@debug expr: when debug mode is enabled, expr is evaluated, and when debug mode is disabled expr is ignored.\n\n\n\n\n\n","category":"function"}] +} diff --git a/previews/PR798/siteinfo.js b/previews/PR798/siteinfo.js new file mode 100644 index 0000000000..5e0afadcae --- /dev/null +++ b/previews/PR798/siteinfo.js @@ -0,0 +1 @@ +var DOCUMENTER_CURRENT_VERSION = "previews/PR798"; diff --git a/previews/PR798/topics/FEValues/index.html b/previews/PR798/topics/FEValues/index.html new file mode 100644 index 0000000000..43517bc16f --- /dev/null +++ b/previews/PR798/topics/FEValues/index.html @@ -0,0 +1,128 @@ + +FEValues · Ferrite.jl

FEValues

A key type of object in Ferrite is the so-called FEValues, where the most common ones are CellValues and FacetValues. These objects are used inside the element routines and are used to query the integration weights, shape function values and gradients, and much more; see CellValues and FacetValues. For these values to be correct, it is necessary to reinitialize these for the current cell by using the reinit! function. This function maps the values from the reference cell to the actual cell, a process described in detail below, see Mapping of finite elements. After that, we show an implementation of a SimpleCellValues type to illustrate how CellValues work for the most standard case, excluding the generalizations and optimization that complicates the actual code.

Mapping of finite elements

The shape functions and gradients stored in an FEValues object, are reinitialized for each cell by calling the reinit! function. The main part of this calculation, considers how to map the values and derivatives of the shape functions, defined on the reference cell, to the actual cell.

The geometric mapping of a finite element from the reference coordinates to the real coordinates is shown in the following illustration.

mapping_figure

This mapping is given by the geometric shape functions, $\hat{N}_i^g(\boldsymbol{\xi})$, such that

\[\begin{align*} + \boldsymbol{x}(\boldsymbol{\xi}) =& \sum_{\alpha=1}^N \hat{\boldsymbol{x}}_\alpha \hat{N}_\alpha^g(\boldsymbol{\xi}) \\ + \boldsymbol{J} :=& \frac{\mathrm{d}\boldsymbol{x}}{\mathrm{d}\boldsymbol{\xi}} = \sum_{\alpha=1}^N \hat{\boldsymbol{x}}_\alpha \otimes \frac{\mathrm{d} \hat{N}_\alpha^g}{\mathrm{d}\boldsymbol{\xi}}\\ + \boldsymbol{\mathcal{H}} :=& + \frac{\mathrm{d} \boldsymbol{J}}{\mathrm{d} \boldsymbol{\xi}} = \sum_{\alpha=1}^N \hat{\boldsymbol{x}}_\alpha \otimes \frac{\mathrm{d}^2 \hat{N}^g_\alpha}{\mathrm{d} \boldsymbol{\xi}^2} +\end{align*}\]

where the defined $\boldsymbol{J}$ is the jacobian of the mapping, and in some cases we will also need the corresponding hessian, $\boldsymbol{\mathcal{H}}$ (3rd order tensor).

We require that the mapping from reference coordinates to real coordinates is diffeomorphic, meaning that we can express $\boldsymbol{x} = \boldsymbol{x}(\boldsymbol{\xi}(\boldsymbol{x}))$, such that

\[\begin{align*} + \frac{\mathrm{d}\boldsymbol{x}}{\mathrm{d}\boldsymbol{x}} = \boldsymbol{I} &= \frac{\mathrm{d}\boldsymbol{x}}{\mathrm{d}\boldsymbol{\xi}} \cdot \frac{\mathrm{d}\boldsymbol{\xi}}{\mathrm{d}\boldsymbol{x}} + \quad\Rightarrow\quad + \frac{\mathrm{d}\boldsymbol{\xi}}{\mathrm{d}\boldsymbol{x}} = \left[\frac{\mathrm{d}\boldsymbol{x}}{\mathrm{d}\boldsymbol{\xi}}\right]^{-1} = \boldsymbol{J}^{-1} +\end{align*}\]

Depending on the function interpolation, we may want different types of mappings to conserve certain properties of the fields. This results in the different mapping types described below.

Identity mapping

Ferrite.IdentityMapping

For scalar fields, we always use scalar base functions. For tensorial fields (non-scalar, e.g. vector-fields), the base functions can be constructed from scalar base functions, by using e.g. VectorizedInterpolation. From the perspective of the mapping, however, each component is mapped as an individual scalar base function. And for scalar base functions, we only require that the value of the base function is invariant to the element shape (real coordinate), and only depends on the reference coordinate, i.e.

\[\begin{align*} + N(\boldsymbol{x}) &= \hat{N}(\boldsymbol{\xi}(\boldsymbol{x}))\nonumber \\ + \mathrm{grad}(N(\boldsymbol{x})) &= \frac{\mathrm{d}\hat{N}}{\mathrm{d}\boldsymbol{\xi}} \cdot \boldsymbol{J}^{-1} +\end{align*}\]

Second order gradients of the shape functions are computed as

\[\begin{align*} + \mathrm{grad}(\mathrm{grad}(N(\boldsymbol{x}))) = \frac{\mathrm{d}^2 N}{\mathrm{d}\boldsymbol{x}^2} = \boldsymbol{J}^{-T} \cdot \frac{\mathrm{d}^2\hat{N}}{\mathrm{d}\boldsymbol{\xi}^2} \cdot \boldsymbol{J}^{-1} - \boldsymbol{J}^{-T} \cdot\mathrm{grad}(N) \cdot \boldsymbol{\mathcal{H}} \cdot \boldsymbol{J}^{-1} +\end{align*}\]

Derivation

The gradient of the shape functions is obtained using the chain rule:

\[\begin{align*} + \frac{\mathrm{d} N}{\mathrm{d}x_i} = \frac{\mathrm{d} \hat N}{\mathrm{d} \xi_r}\frac{\mathrm{d} \xi_r}{\mathrm{d} x_i} = \frac{\mathrm{d} \hat N}{\mathrm{d} \xi_r} J^{-1}_{ri} +\end{align*}\]

For the second order gradients, we first use the product rule on the equation above:

\[\begin{align} + \frac{\mathrm{d}^2 N}{\mathrm{d}x_i \mathrm{d}x_j} = \frac{\mathrm{d}}{\mathrm{d}x_j}\left[\frac{\mathrm{d} \hat N}{\mathrm{d} \xi_r}\right] J^{-1}_{ri} + \frac{\mathrm{d} \hat N}{\mathrm{d} \xi_r} \frac{\mathrm{d}J^{-1}_{ri}}{\mathrm{d}x_j} +\end{align}\]

Using the fact that $\frac{\mathrm{d}\hat{f}(\boldsymbol{\xi})}{\mathrm{d}x_j} = \frac{\mathrm{d}\hat{f}(\boldsymbol{\xi})}{\mathrm{d}\xi_s} J^{-1}_{sj}$, the first term in the equation above can be expressed as:

\[\begin{align*} + \frac{\mathrm{d}}{\mathrm{d}x_j}\left[\frac{\mathrm{d} \hat N}{\mathrm{d} \xi_r}\right] J^{-1}_{ri} = J^{-1}_{sj}\frac{\mathrm{d}}{\mathrm{d}\xi_s}\left[\frac{\mathrm{d} \hat N}{\mathrm{d} \xi_r}\right] J^{-1}_{ri} = J^{-1}_{sj}\left[\frac{\mathrm{d}^2 \hat N}{\mathrm{d} \xi_s\mathrm{d} \xi_r}\right] J^{-1}_{ri} +\end{align*}\]

The second term can be written as:

\[\begin{align*} + \frac{\mathrm{d} \hat N}{\mathrm{d} \xi_r}\frac{\mathrm{d}J^{-1}_{ri}}{\mathrm{d}x_j} = \frac{\mathrm{d} \hat N}{\mathrm{d} \xi_r}\left[\frac{\mathrm{d}J^{-1}_{ri}}{\mathrm{d}\xi_s}\right]J^{-1}_{sj} = \frac{\mathrm{d} \hat N}{\mathrm{d} \xi_r}\left[- J^{-1}_{rk}\mathcal{H}_{kps} J^{-1}_{pi}\right] J^{-1}_{sj} = - \frac{\mathrm{d} \hat N}{\mathrm{d} x_k}\mathcal{H}_{kps} J^{-1}_{pi}J^{-1}_{sj} +\end{align*}\]

where we have used that the inverse of the jacobian can be computed as:

\[\begin{align*} +0 = \frac{\mathrm{d}}{\mathrm{d}\xi_s} (J_{kr} J^{-1}_{ri} ) = \frac{\mathrm{d}J_{kp}}{\mathrm{d}\xi_s} J^{-1}_{pi} + J_{kr} \frac{\mathrm{d}J^{-1}_{ri}}{\mathrm{d}\xi_s} = 0 \quad \Rightarrow \\ +\end{align*}\]

\[\begin{align*} +\frac{\mathrm{d}J^{-1}_{ri}}{\mathrm{d}\xi_s} = - J^{-1}_{rk}\frac{\mathrm{d}J_{kp}}{\mathrm{d}\xi_s} J^{-1}_{pi} = - J^{-1}_{rk}\mathcal{H}_{kps} J^{-1}_{pi}\\ +\end{align*}\]

Covariant Piola mapping, H(curl)

Ferrite.CovariantPiolaMapping

The covariant Piola mapping of a vectorial base function preserves the tangential components. For the value, the mapping is defined as

\[\begin{align*} + \boldsymbol{N}(\boldsymbol{x}) = \boldsymbol{J}^{-\mathrm{T}} \cdot \hat{\boldsymbol{N}}(\boldsymbol{\xi}(\boldsymbol{x})) +\end{align*}\]

which yields the gradient,

\[\begin{align*} + \mathrm{grad}(\boldsymbol{N}(\boldsymbol{x})) &= \boldsymbol{J}^{-T} \cdot \frac{\mathrm{d} \hat{\boldsymbol{N}}}{\mathrm{d} \boldsymbol{\xi}} \cdot \boldsymbol{J}^{-1} - \boldsymbol{J}^{-T} \cdot \left[\hat{\boldsymbol{N}}(\boldsymbol{\xi}(\boldsymbol{x}))\cdot \boldsymbol{J}^{-1} \cdot \boldsymbol{\mathcal{H}}\cdot \boldsymbol{J}^{-1}\right] +\end{align*}\]

Derivation

Expressing the gradient, $\mathrm{grad}(\boldsymbol{N})$, in index notation,

\[\begin{align*} + \frac{\mathrm{d} N_i}{\mathrm{d} x_j} &= \frac{\mathrm{d}}{\mathrm{d} x_j} \left[J^{-\mathrm{T}}_{ik} \hat{N}_k\right] = \frac{\mathrm{d} J^{-\mathrm{T}}_{ik}}{\mathrm{d} x_j} \hat{N}_k + J^{-\mathrm{T}}_{ik} \frac{\mathrm{d} \hat{N}_k}{\mathrm{d} \xi_l} J_{lj}^{-1} +\end{align*}\]

Except for a few elements, $\boldsymbol{J}$ varies as a function of $\boldsymbol{x}$. The derivative can be calculated as

\[\begin{align*} + \frac{\mathrm{d} J^{-\mathrm{T}}_{ik}}{\mathrm{d} x_j} &= \frac{\mathrm{d} J^{-\mathrm{T}}_{ik}}{\mathrm{d} J_{mn}} \frac{\mathrm{d} J_{mn}}{\mathrm{d} x_j} = - J_{km}^{-1} J_{in}^{-T} \frac{\mathrm{d} J_{mn}}{\mathrm{d} x_j} \nonumber \\ + \frac{\mathrm{d} J_{mn}}{\mathrm{d} x_j} &= \mathcal{H}_{mno} J_{oj}^{-1} +\end{align*}\]

Contravariant Piola mapping, H(div)

Ferrite.ContravariantPiolaMapping

The covariant Piola mapping of a vectorial base function preserves the normal components. For the value, the mapping is defined as

\[\begin{align*} + \boldsymbol{N}(\boldsymbol{x}) = \frac{\boldsymbol{J}}{\det(\boldsymbol{J})} \cdot \hat{\boldsymbol{N}}(\boldsymbol{\xi}(\boldsymbol{x})) +\end{align*}\]

This gives the gradient

\[\begin{align*} + \mathrm{grad}(\boldsymbol{N}(\boldsymbol{x})) = [\boldsymbol{\mathcal{H}}\cdot\boldsymbol{J}^{-1}] : \frac{[\boldsymbol{I} \underline{\otimes} \boldsymbol{I}] \cdot \hat{\boldsymbol{N}}}{\det(\boldsymbol{J})} + - \left[\frac{\boldsymbol{J} \cdot \hat{\boldsymbol{N}}}{\det(\boldsymbol{J})}\right] \otimes \left[\boldsymbol{J}^{-T} : \boldsymbol{\mathcal{H}} \cdot \boldsymbol{J}^{-1}\right] + + \boldsymbol{J} \cdot \frac{\mathrm{d} \hat{\boldsymbol{N}}}{\mathrm{d} \boldsymbol{\xi}} \cdot \frac{\boldsymbol{J}^{-1}}{\det(\boldsymbol{J})} +\end{align*}\]

Derivation

Expressing the gradient, $\mathrm{grad}(\boldsymbol{N})$, in index notation,

\[\begin{align*} + \frac{\mathrm{d} N_i}{\mathrm{d} x_j} &= \frac{\mathrm{d}}{\mathrm{d} x_j} \left[\frac{J_{ik}}{\det(\boldsymbol{J})} \hat{N}_k\right] =\nonumber\\ + &= \frac{\mathrm{d} J_{ik}}{\mathrm{d} x_j} \frac{\hat{N}_k}{\det(\boldsymbol{J})} + - \frac{\mathrm{d} \det(\boldsymbol{J})}{\mathrm{d} x_j} \frac{J_{ik} \hat{N}_k}{\det(\boldsymbol{J})^2} + + \frac{J_{ik}}{\det(\boldsymbol{J})} \frac{\mathrm{d} \hat{N}_k}{\mathrm{d} \xi_l} J_{lj}^{-1} \\ + &= \mathcal{H}_{ikl} J^{-1}_{lj} \frac{\hat{N}_k}{\det(\boldsymbol{J})} + - J^{-T}_{mn} \mathcal{H}_{mnl} J^{-1}_{lj} \frac{J_{ik} \hat{N}_k}{\det(\boldsymbol{J})} + + \frac{J_{ik}}{\det(\boldsymbol{J})} \frac{\mathrm{d} \hat{N}_k}{\mathrm{d} \xi_l} J_{lj}^{-1} +\end{align*}\]

Walkthrough: Creating SimpleCellValues

In the following, we walk through how to create a SimpleCellValues type which works similar to Ferrite's CellValues, but is not performance optimized and not as general. The main purpose is to explain how the CellValues works for the standard case of IdentityMapping described above. Please note that several internal functions are used, and these may change without a major version increment. Please see the Developer documentation for their documentation.

We start by including Ferrite and Test (to check our implementation).

using Ferrite, Test

Then, we define a simple version of the cell values object, which only supports

  • Scalar interpolations
  • Identity mapping from reference to physical cell.
  • The cell shape has the same dimension as the physical space (excludes so-called embedded cells).
struct SimpleCellValues{T, dim} <: Ferrite.AbstractCellValues
+    # Precalculated shape values, N[i, q_point] where i is the
+    # shape function number and q_point the integration point
+    N::Matrix{T}
+    # Precalculated shape gradients in the reference domain, dNdξ[i, q_point]
+    dNdξ::Matrix{Vec{dim, T}}
+    # Cache for shape gradients in the physical domain, dNdx[i, q_point]
+    dNdx::Matrix{Vec{dim, T}}
+    # Precalculated geometric shape values, M[j, q_point] where j is the
+    # geometric shape function number
+    M::Matrix{T}
+    # Precalculated geometric shape gradients, dMdξ[j, q_point]
+    dMdξ::Matrix{Vec{dim, T}}
+    # Given quadrature weights in the reference domain, weights[q_point]
+    weights::Vector{T}
+    # Cache for quadrature weights in the physical domain, detJdV[q_point], i.e.
+    # det(J)*weight[q_point], where J is the jacobian of the geometric mapping
+    # at the quadrature point, q_point.
+    detJdV::Vector{T}
+end;

Next, we create a constructor with the same input as CellValues

function SimpleCellValues(qr::QuadratureRule, ip_fun::Interpolation, ip_geo::Interpolation)
+    dim = Ferrite.getrefdim(ip_fun)
+    # Quadrature weights and coordinates (in reference cell)
+    weights = Ferrite.getweights(qr)
+    n_qpoints = length(weights)
+    T = eltype(weights)
+
+    # Function interpolation
+    n_func_basefuncs = getnbasefunctions(ip_fun)
+    N = zeros(T, n_func_basefuncs, n_qpoints)
+    dNdx = zeros(Vec{dim, T}, n_func_basefuncs, n_qpoints)
+    dNdξ = zeros(Vec{dim, T}, n_func_basefuncs, n_qpoints)
+
+    # Geometry interpolation
+    n_geom_basefuncs = getnbasefunctions(ip_geo)
+    M = zeros(T, n_geom_basefuncs, n_qpoints)
+    dMdξ = zeros(Vec{dim, T}, n_geom_basefuncs, n_qpoints)
+
+    # Precalculate function and geometric shape values and gradients
+    for (qp, ξ) in pairs(Ferrite.getpoints(qr))
+        for i in 1:n_func_basefuncs
+            dNdξ[i, qp], N[i, qp] = Ferrite.reference_shape_gradient_and_value(ip_fun, ξ, i)
+        end
+        for i in 1:n_geom_basefuncs
+            dMdξ[i, qp], M[i, qp] = Ferrite.reference_shape_gradient_and_value(ip_geo, ξ, i)
+        end
+    end
+
+    detJdV = zeros(T, n_qpoints)
+    return SimpleCellValues(N, dNdξ, dNdx, M, dMdξ, weights, detJdV)
+end;

To make our SimpleCellValues work in standard Ferrite code, we need to dispatch some access functions:

Ferrite.getnbasefunctions(cv::SimpleCellValues) = size(cv.N, 1)
+Ferrite.getnquadpoints(cv::SimpleCellValues) = size(cv.N, 2)
+Ferrite.shape_value(cv::SimpleCellValues, q_point::Int, i::Int) = cv.N[i, q_point]
+Ferrite.shape_gradient(cv::SimpleCellValues, q_point::Int, i::Int) = cv.dNdx[i, q_point];

The last step is then to dispatch reinit! for our SimpleCellValues to calculate the cached values dNdx and detJdV for the current cell according to the theory for IdentityMapping above.

function Ferrite.reinit!(cv::SimpleCellValues, x::Vector{Vec{dim, T}}) where {dim, T}
+    for (q_point, w) in pairs(cv.weights) # Loop over each quadrature point
+        # Calculate the jacobian, J
+        J = zero(Tensor{2, dim, T})
+        for i in eachindex(x)
+            J += x[i] ⊗ cv.dMdξ[i, q_point]
+        end
+        # Calculate the correct integration weight for the current q_point
+        cv.detJdV[q_point] = det(J) * w
+        # map the shape gradients to the current geometry
+        Jinv = inv(J)
+        for i in 1:getnbasefunctions(cv)
+            cv.dNdx[i, q_point] = cv.dNdξ[i, q_point] ⋅ Jinv
+        end
+    end
+    return
+end;

To test our implementation, we create instances of our SimpleCellValues and the standard CellValues:

qr = QuadratureRule{RefQuadrilateral}(2)
+ip = Lagrange{RefQuadrilateral, 1}()
+simple_cv = SimpleCellValues(qr, ip, ip)
+cv = CellValues(qr, ip, ip);

The first thing to try is to reinitialize the cell values to a given cell, in this case cell nr. 2

grid = generate_grid(Quadrilateral, (2, 2))
+x = getcoordinates(grid, 2)
+reinit!(simple_cv, x)
+reinit!(cv, x);

If we now pretend we are inside an element routine and have a vector of element degree of freedom values, ue. Then, we can check that our function values and gradients match Ferrite's builtin CellValues:

ue = rand(getnbasefunctions(simple_cv))
+q_point = 2
+@test function_value(cv, q_point, ue) ≈ function_value(simple_cv, q_point, ue)
+@test function_gradient(cv, q_point, ue) ≈ function_gradient(simple_cv, q_point, ue)
Test Passed

Further reading

diff --git a/previews/PR798/topics/assembly/index.html b/previews/PR798/topics/assembly/index.html new file mode 100644 index 0000000000..7766507532 --- /dev/null +++ b/previews/PR798/topics/assembly/index.html @@ -0,0 +1,93 @@ + +Assembly · Ferrite.jl

Assembly

When the local stiffness matrix and force vector have been calculated they should be assembled into the global stiffness matrix and the global force vector. This is just a matter of adding the local matrix and vector to the global one, at the correct place. Consider e.g. assembling the local stiffness matrix ke and the local force vector fe into the global K and f respectively. These should be assembled into the row/column which corresponds to the degrees of freedom for the cell:

K[celldofs, celldofs] += ke
+f[celldofs]           += fe

where celldofs is the vector containing the degrees of freedom for the cell. The method above is very inefficient – it is especially costly to index into the sparse matrix K directly (see Comparison of assembly strategies for details). Therefore we will instead use an Assembler that will help with the assembling of both the global stiffness matrix and the global force vector. It is also often convenient to create the sparse matrix just once, and reuse the allocated matrix. This is useful for e.g. iterative solvers or time dependent problems where the sparse matrix structure, or Sparsity Pattern will stay the same in every iteration/time step.

Assembler

Assembling efficiently into the sparse matrix requires some extra workspace. This workspace is allocated in an Assembler. start_assemble is used to create an Assembler:

A = start_assemble(K)
+A = start_assemble(K, f)

where K is the global stiffness matrix, and f the global force vector. It is optional to pass the force vector to the assembler – sometimes there is no need to assemble a global force vector.

The assemble! function is used to assemble element contributions to the assembler. For example, to assemble the element tangent stiffness ke and the element force vector fe to the assembler A, the following code can be used:

assemble!(A, celldofs, ke)
+assemble!(A, celldofs, ke, fe)

which perform the following operations in an efficient manner:

K[celldofs, celldofs] += ke
+f[celldofs]           += fe

Pseudo-code for efficient assembly

Quite often the same sparsity pattern can be reused multiple times. For example:

  • For time-dependent problems the pattern can be reused for all timesteps
  • For non-linear problems the pattern can be reused for all iterations

In such cases it is enough to construct the global matrix K once. Below is some pseudo-code for how to do this for a time-dependent problem:

K = allocate_matrix(dh)
+f = zeros(ndofs(dh))
+
+for t in 1:timesteps
+    A = start_assemble(K, f) # start_assemble zeroes K and f
+    for cell in CellIterator(dh)
+        ke, fe = element_routine(...)
+        assemble!(A, celldofs(cell), ke, fe)
+    end
+    # Apply boundary conditions and solve for u(t)
+    # ...
+end

Comparison of assembly strategies

As discussed above there are various ways to assemble the local matrix into the global one. In particular, it was mentioned that naive indexing is very inefficient and that using an assembler is faster. To put some concrete numbers to these statements we will compare some strategies in this section. First we compare just a single assembly operation (e.g. assembling an already computed local matrix) and then to relate this to a more realistic scenario we compare the full matrix assembly including the integration of all the elements.

Pre-allocated global matrix

All strategies that we compare below uses a pre-allocated global matrix K with the correct sparsity pattern. Starting with something like K = spzeros(ndofs(dh), ndofs(dh)) and then inserting entries is excruciatingly slow due to the sparse data structure so this method is not even considered.

For the comparison we need a representative global matrix to assemble into. In the following setup code we create a grid with triangles and a DofHandler with a quadratic scalar field. From this we instantiate the global matrix.

using Ferrite
+
+# Quadratic scalar interpolation
+ip = Lagrange{RefTriangle, 2}()
+
+# DofHandler
+const N = 100
+grid = generate_grid(Triangle, (N, N))
+const dh = DofHandler(grid)
+add!(dh, :u, ip)
+close!(dh)
+
+# Global matrix and a corresponding assembler
+const K = allocate_matrix(dh)

Strategy 1: matrix indexing

The first strategy is to index directly, using the vector of global dofs, into the global matrix:

function assemble_v1(_, K, dofs, Ke)
+    K[dofs, dofs] += Ke
+    return
+end

This looks very simple, but it is very inefficient (as the numbers will show later). To understand why the operation K[dofs, dofs] += Ke (with K being a sparse matrix) is so slow we can dig into the details.

In Julia there is no "+="-operation and so x += y is identical to x = x + y. Translating this to our example we have

K[dofs, dofs] = K[dofs, dofs] + Ke

We can break down this a bit further into these equivalent three steps:

tmp1 = K[dofs, dofs]   # 1
+tmp2 = tmp1 + Ke       # 2
+K[dofs, dofs] = tmp2   # 3

Now the problem with this strategy becomes a bit more obvious:

  • In line 1 there is first an allocation of a new matrix (tmp1) followed by indexing into K to copy elements from K to tmp1. Both of these operations are rather costly: allocations should always be minimized in tight loops, and indexing into a sparse matrix is non-trivial due to the data structure. In addition, since the dofs vector contains the global indices (which are neither sorted nor consecutive) we have a random access pattern.
  • In line 2 there is another allocation of a matrix (tmp2) for the result of the addition of tmp1 and Ke.
  • In line 3 we again need to index into the sparse matrix to copy over the elements from tmp2 to K. This essentially duplicates the indexing effort from line 1 since we need to lookup the same locations in K again.
Broadcasting

Using broadcasting, e.g. K[dofs, dofs] .+= Ke is an alternative to the above, and resembles a +=-operation. In theory this should be as efficient as the explicit loop presented in the next section.

Strategy 2: scalar indexing

A variant of the first strategy is to explicitly loop over the indices and add the elements individually as scalars:

function assemble_v2(_, K, dofs, Ke)
+    for (i, I) in pairs(dofs)
+        for (j, J) in pairs(dofs)
+            K[I, J] += Ke[i, j]
+        end
+    end
+    return
+end

The core operation, K[I, J] += Ke[i, j], can still be broken down into three equivalent steps:

tmp1 = K[I, J]
+tmp2 = tmp1 + Ke[i, j]
+K[I, J] = tmp2

The key difference here is that we index using integers (I, J, i, and j) which means that tmp1 and tmp2 are scalars which don't need to be allocated on the heap. This stragety thus eliminates all allocations that were present in the first strategy. However, we still lookup the same location in K twice, and we still have a random access pattern.

Strategy 3: scalar indexing with single lookup

To improve on the second strategy we will get rid of the double lookup into the sparse matrix K. While Julia doesn't have a "+="-operation, Ferrite has an internal addindex!-function which does exactly what we want: it adds a value to a specific location in a sparse matrix using a single lookup.

function assemble_v3(_, K, dofs, Ke)
+    for (i, I) in pairs(dofs)
+        for (j, J) in pairs(dofs)
+            Ferrite.addindex!(K, Ke[i, j], I, J)
+        end
+    end
+    return
+end

With this method we remove the double lookup, but the issue of random access patterns still remains.

Strategy 4: using an assembler

Finally, the last strategy we consider uses an assembler. The assembler is a specific datastructure that pre-allocates some workspace to make the assembly more efficient:

function assemble_v4(assembler, _, dofs, Ke)
+    assemble!(assembler, dofs, Ke)
+    return
+end

The extra workspace inside the assembler is used to sort the dofs when assemble! is called. After sorting it is possible to loop over the sparse matrix data structure and insert all elements of Ke in one go instead of having to lookup locations randomly.

Single element assembly

First we will compare the four functions above for a single assembly operation, i.e. inserting one local matrix into the global matrix. For this we simply create a random local matrix since we are not conserned with the actual values. We also pick the "middle" element and extract the dofs for that element. Finally, an assembler is created with start_assemble to use with the fourth strategy.

dofs_per_cell = ndofs_per_cell(dh)
+const Ke = rand(dofs_per_cell, dofs_per_cell)
+const dofs = celldofs(dh, N * N ÷ 2)
+
+const assembler = start_assemble(K)

We use BenchmarkTools to measure the performance:

using BenchmarkTools
+
+@btime assemble_v1(assembler, K, dofs, Ke) evals = 10 setup = fill!(K, 0)
+@btime assemble_v2(assembler, K, dofs, Ke) evals = 10 setup = fill!(K, 0)
+@btime assemble_v3(assembler, K, dofs, Ke) evals = 10 setup = fill!(K, 0)
+@btime assemble_v4(assembler, K, dofs, Ke) evals = 10 setup = fill!(K, 0)

The results below are obtained on an Macbook Pro with an Apple M3 CPU.

606.438 μs (36 allocations: 7.67 MiB)
+283.300 ns (0 allocations: 0 bytes)
+158.300 ns (0 allocations: 0 bytes)
+ 83.400 ns (0 allocations: 0 bytes)

The results match what we expect based on the explanations above:

  • Between strategy 1 and 2 we got rid of the allocations completely and decreased the time with a factor of 2100(!).
  • Between strategy 2 and 3 we got rid of the double lookup and decreased the time with another factor of almost 2.
  • Between strategy 3 and 4 we got rid of the random lookup order and decreased the time with another factor of almost 2.

The most important thing for this benchmark is to get rid of the allocations. By using an assembler instead of doing the naive thing we reduce the runtime with a factor of more than 7000(!!) in total.

Full system assembly

We will now compare the four strategies in a more realistic scenario where we assemble all elements. This is to put the assembly performance in relation to other operations in the finite element program. After all, assembly performance might not matter in the end if other things dominate the runtime anyway.

For this comparison we simply consider the heat equation (see Tutorial 1: Heat equation) and assemble the global matrix.

function assemble_system!(assembler_function::F, K, dh, cv) where {F}
+    assembler = start_assemble(K)
+    ke = zeros(ndofs_per_cell(dh), ndofs_per_cell(dh))
+    n = getnbasefunctions(cv)
+    for cell in CellIterator(dh)
+        reinit!(cv, cell)
+        ke .= 0
+        for qp in 1:getnquadpoints(cv)
+            dΩ = getdetJdV(cv, qp)
+            for i in 1:n
+                ∇ϕi = shape_gradient(cv, qp, i)
+                for j in 1:n
+                    ∇ϕj = shape_gradient(cv, qp, j)
+                    ke[i, j] += ( ∇ϕi ⋅ ∇ϕj ) * dΩ
+                end
+            end
+        end
+        assembler_function(assembler, K, celldofs(cell), ke)
+    end
+    return
+end

Finally, we need cellvalues for the field in order to perform the integration:

qr = QuadratureRule{RefTriangle}(2)
+const cellvalues = CellValues(qr, ip)

We can now time the four assembly strategies:

@time assemble_system!(assemble_v1, K, dh, cellvalues)
+@time assemble_system!(assemble_v2, K, dh, cellvalues)
+@time assemble_system!(assemble_v3, K, dh, cellvalues)
+@time assemble_system!(assemble_v4, K, dh, cellvalues)

We then obtain the following results (running on the same machine as above):

12.175625 seconds (719.99 k allocations: 149.809 GiB, 11.59% gc time)
+ 0.009313 seconds (8 allocations: 928 bytes)
+ 0.006055 seconds (8 allocations: 928 bytes)
+ 0.004530 seconds (10 allocations: 1.062 KiB)

This follows the same trend as for the benchmarks for individual cell assembly and shows that the efficiency of the assembly strategy is crucial for the overall performance of the program. In particular this benchmark shows that allocations in such a tight loop from the first strategy is very costly and puts a strain on the garbage collector: 11% of the time is spent in GC instead of crunching numbers.

It should of course be noted that the more expensive the element routine is, the less the performance of the assembly strategy matters for the total runtime. However, there are no reason not to use the fastest method given that it is readily available in Ferrite.

diff --git a/previews/PR798/topics/assets/global_mesh.svg b/previews/PR798/topics/assets/global_mesh.svg new file mode 100644 index 0000000000..3d0e806f49 --- /dev/null +++ b/previews/PR798/topics/assets/global_mesh.svg @@ -0,0 +1,598 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + 2 + + 3 + 4 + 5 + 6 + 7 + 8 + 9 + + 2 + + 1 + + 4 + + 3 + + + + + + + + 3 + 6 + 9 + + + x + y + + diff --git a/previews/PR798/topics/assets/local_element.svg b/previews/PR798/topics/assets/local_element.svg new file mode 100644 index 0000000000..aaa7b41650 --- /dev/null +++ b/previews/PR798/topics/assets/local_element.svg @@ -0,0 +1,266 @@ + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + 1 + + + + 2 + 3 + 4 + (1) + (2) + + (3) + (4) + + + ξ₁ + ξ₂ + + diff --git a/previews/PR798/topics/boundary_conditions/index.html b/previews/PR798/topics/boundary_conditions/index.html new file mode 100644 index 0000000000..286ff66d25 --- /dev/null +++ b/previews/PR798/topics/boundary_conditions/index.html @@ -0,0 +1,81 @@ + +Boundary and initial conditions · Ferrite.jl

Boundary and initial conditions

Every PDE is accompanied with boundary conditions. There are different types of boundary conditions, and they need to be handled in different ways. Below we discuss how to handle the most common ones, Dirichlet and Neumann boundary conditions, and how to do it in Ferrite.

While boundary conditions can be applied directly to nodes, vertices, edges, or faces, they are most commonly applied to facets. Each facet is described by a FacetIndex. When adding boundary conditions to points instead, vertices are preferred over nodes.

Dirichlet boundary conditions

At a Dirichlet boundary the unknown field is prescribed to a given value. For the discrete FE-solution this means that there are some degrees of freedom that are fixed. To handle Dirichlet boundary conditions in Ferrite we use the ConstraintHandler. A constraint handler is created from a DoF handler:

ch = ConstraintHandler(dh)

We can now create Dirichlet constraints and add them to the constraint handler. To create a Dirichlet constraint we need to specify a field name, a part of the boundary, and a function for computing the prescribed value. Example:

dbc1 = Dirichlet(
+    :u,                        # Name of the field
+    getfacetset(grid, "left"), # Part of the boundary
+    x -> 1.0,                  # Function mapping coordinate to a prescribed value
+)

The field name is given as a symbol, just like when the field was added to the dof handler, the part of the boundary where this constraint is active is given as a facet set, and the function computing the prescribed value should be of the form f(x) or f(x, t) (coordinate x and time t) and return the prescribed value(s).

Multiple sets

To apply a constraint on multiple facet sets in the grid you can use union to join them, for example

left_right = union(getfacetset(grid, "left"), getfacetset(grid, "right"))

creates a new facetset containing all facets in the "left" and "right" facetsets, which can be passed to the Dirichlet constructor.

By default the constraint is added to all components of the given field. To add the constraint to selected components a fourth argument with the components should be passed to the constructor. Here is an example where a constraint is added to component 1 and 3 of a vector field :u:

dbc2 = Dirichlet(
+    :u,                        # Name of the field
+    getfacetset(grid, "left"), # Part of the boundary
+    x -> [0.0, 0.0],           # Function mapping coordinate to prescribed values
+    [1, 3],                    # Components
+)

Note that the return value of the function must match with the components – in the example above we prescribe components 1 and 3 to 0 so we return a vector of length 2.

Adding the constraints to the constraint handler is done with add!:

add!(ch, dbc1)
+add!(ch, dbc2)

Finally, just like for the dof handler, we need to use close! to finalize the constraint handler. Internally this will then compute the degrees-of-freedom that match the constraints we added.

If one or more of the constraints depend on time, i.e. they are specified as f(x, t), the prescribed values can be recomputed in each new time step by calling update! with the proper time, e.g.:

for t in 0.0:0.1:1.0
+    update!(ch, t) # Compute prescribed values for this t
+    # Solve for time t...
+end
Examples

Most examples make use of Dirichlet boundary conditions, for example Heat Equation.

Neumann boundary conditions

At the Neumann part of the boundary we know something about the gradient of the solution. Two different methods for applying these are described below. For complete examples that use Neumann boundary conditions, please see

Using the FacetIterator

A Neumann boundary contribution can be added by iterating over the relevant facetset by using the FacetIterator. For a scalar field, this can be done as

grid = generate_grid(Quadrilateral, (3,3))
+dh = DofHandler(grid); push!(dh, :u, 1); close!(dh)
+fv = FacetValues(QuadratureRule{RefQuadrilateral}(2), Lagrange{RefQuadrilateral, 1}())
+f = zeros(ndofs(dh))
+fe = zeros(ndofs_per_cell(dh))
+qn = 1.0    # Normal flux
+for fc in FacetIterator(dh, getfacetset(grid, "right"))
+    reinit!(fv, fc)
+    fill!(fe, 0)
+    for q_point in 1:getnquadpoints(fv)
+        dΓ = getdetJdV(fv, q_point)
+        for i in 1:getnbasefunctions(fv)
+            δu = shape_value(fv, q_point, i)
+            fe[i] += δu * qn * dΓ
+        end
+    end
+    assemble!(f, celldofs(fc), fe)
+end

Alternatively, it is possible to add the values directly to the global f (without going through the local fe vector and then using assemble!):

# ...
+dofs = celldofs(fc)
+for i in 1:getnbasefunctions(fv)
+    f[dofs[i]] += δu * qn * dΓ
+end

In the element routine

Alternatively, the following code snippet can be included in the element routine, to evaluate the boundary integral:

for facet in 1:nfacets(cell)
+    if (cellid(cell), facet) ∈ getfacetset(grid, "Neumann Boundary")
+        reinit!(facetvalues, cell, facet)
+        for q_point in 1:getnquadpoints(facetvalues)
+            dΓ = getdetJdV(facetvalues, q_point)
+            for i in 1:getnbasefunctions(facetvalues)
+                δu = shape_value(facetvalues, q_point, i)
+                fe[i] += δu * qn * dΓ
+            end
+        end
+    end
+end

We start by looping over all the facets of the cell, next we check if this particular facet is located on our facetset of interest called "Neumann Boundary". If we have determined that the current facet is indeed on the boundary and in our facetset, then we reinitialize FacetValues for this facet, using reinit!. When reinit!ing FacetValues we also need to give the facet number in addition to the cell. Next we simply loop over the quadrature points of the facet, and then loop over all the test functions and assemble the contribution to the force vector.

Periodic boundary conditions

Periodic boundary conditions ensure that the solution is periodic across two boundaries. To define the periodicity we first define the image boundary $\Gamma^+$ and the mirror boundary $\Gamma^-$. We also define a (unique) coordinate mapping between the image and the mirror: $\varphi:\ \Gamma^+\, \rightarrow\, \Gamma^-$. With the mapping we can, for every coordinate on the image, compute the corresponding coordinate on the mirror:

\[\boldsymbol{x}^- = \varphi(\boldsymbol{x}^+),\quad \boldsymbol{x}^- \in \Gamma^-,\, +\boldsymbol{x}^+ \in \Gamma^+.\]

We now want to ensure that the solution on the image $\Gamma^+$ is mirrored on the mirror $\Gamma^-$. This periodicity constraint can thus be described by

\[u(\boldsymbol{x}^-) = u(\boldsymbol{x}^+).\]

Sometimes this is written as

\[\llbracket u \rrbracket = 0,\]

where $\llbracket \bullet \rrbracket := \bullet(\boldsymbol{x}^+) - \bullet(\boldsymbol{x}^-)$ is the "jump operator". Thus, this condition ensure that the jump, or difference, in the solution between the image and mirror boundary is the zero – the solution becomes periodic. For a vector valued problem the periodicity constraint can in general be written as

\[\boldsymbol{u}(\boldsymbol{x}^-) = \boldsymbol{R} \cdot \boldsymbol{u}(\boldsymbol{x}^+) +\quad \Leftrightarrow \quad \llbracket \boldsymbol{u} \rrbracket = +\boldsymbol{R} \cdot \boldsymbol{u}(\boldsymbol{x}^+) - \boldsymbol{u}(\boldsymbol{x}^-) = +\boldsymbol{0}\]

where $\boldsymbol{R}$ is a rotation matrix. If the mapping between mirror and image is simply a translation (e.g. sides of a cube) this matrix will be the identity matrix.

In Ferrite this type of periodic Dirichlet boundary conditions can be added to the ConstraintHandler by constructing an instance of PeriodicDirichlet. This is usually done it two steps. First we compute the mapping between mirror and image facets using collect_periodic_facets. Here we specify the mirror set and image sets (the sets are usually known or can be constructed easily ) and the mapping $\varphi$. Second we construct the constraint using the PeriodicDirichlet constructor. Here we specify which components of the function that should be constrained, and the rotation matrix $\boldsymbol{R}$ (when needed). When adding the constraint to the ConstraintHandler the resulting dof-mapping is computed.

Here is a simple example where periodicity is enforced for components 1 and 2 of the field :u between the mirror boundary set "left" and the image boundary set "right". Note that no rotation matrix is needed here since the mirror and image are parallel, just shifted in the $x$-direction (as seen by the mapping φ):

# Create a constraint handler from the dof handler
+ch = ConstraintHandler(dofhandler)
+
+# Compute the facet mapping
+φ(x) = x - Vec{2}((1.0, 0.0))
+face_mapping = collect_periodic_facets(grid, "left", "right", φ)
+
+# Construct the periodic constraint for field :u
+pdbc = PeriodicDirichlet(:u, face_mapping, [1, 2])
+
+# Add the constraint to the constraint handler
+add!(ch, pdbc)
+
+# If no more constraints should be added we can close
+close!(ch)
Note

PeriodicDirichlet constraints are imposed in a strong sense, so note that this requires a periodic mesh such that it is possible to compute the facet mapping between facets on the mirror and boundary.

Examples

Periodic boundary conditions are used in the following examples Computational homogenization, Stokes flow.

Heterogeneous "periodic" constraint

It is also possible to define constraints of the form

\[\llbracket u \rrbracket = \llbracket f \rrbracket +\quad \Leftrightarrow \quad +u(\boldsymbol{x}^+) - u(\boldsymbol{x}^-) = +f(\boldsymbol{x}^+) - f(\boldsymbol{x}^-),\]

where $f$ is a prescribed function. Although the constraint in this case is not technically periodic, PeriodicDirichlet can be used for this too. This is done by passing a function to PeriodicDirichlet, similar to Dirichlet, which, given the coordinate $\boldsymbol{x}$ and time t, computes the prescribed values of $f$ on the boundary.

Here is an example of how to implement this type of boundary condition, for a known function f:

pdbc = PeriodicDirichlet(
+    :u,
+    face_mapping,
+    (x, t) -> f(x),
+    [1, 2],
+)
Note

One application for this type of boundary conditions is multiscale modeling and computational homogenization when solving the finite element problem for the subscale. In this case the unknown $u$ is split into a macroscopic part $u^{\mathrm{M}}$ and a microscopic/fluctuation part $u^\mu$, i.e. $u = u^{\mathrm{M}} + u^{\mu}$. Periodicity is then usually enforced for the fluctuation part, i.e. $\llbracket u^\mu \rrbracket = 0$. The equivalent constraint for $u$ then becomes $\llbracket u \rrbracket = \llbracket u^{\mathrm{M}} \rrbracket$.

As an example, consider first order homogenization where the macroscopic part is constructed as $u^{\mathrm{M}} = \bar{u} + \boldsymbol{\nabla} \bar{u} \cdot [\boldsymbol{x} - \bar{\boldsymbol{x}}]$ for known $\bar{u}$ and $\boldsymbol{\nabla} \bar{u}$. This could be implemented as

pdbc = PeriodicDirichlet(
+    :u,
+    face_mapping,
+    (x, t) -> ū + ∇ū  ⋅ (x - x̄)
+)

Initial conditions

When solving time-dependent problems, initial conditions, different from zero, may be required. For finite element formulations of ODE-type, i.e. $\boldsymbol{u}'(t) = \boldsymbol{f}(\boldsymbol{u}(t),t)$, where $\boldsymbol{u}(t)$ are the degrees of freedom, initial conditions can be specified by the apply_analytical! function. For example, specify the initial pressure as a function of the y-coordinate

ρ = 1000; g = 9.81    # density [kg/m³] and gravity [N/kg]
+grid = generate_grid(Quadrilateral, (10,10))
+dh = DofHandler(grid); add!(dh, :u, 2); add!(dh, :p, 1); close!(dh)
+u = zeros(ndofs(dh))
+apply_analytical!(u, dh, :p, x -> ρ * g * x[2])

See also Transient heat equation for one example.

Consistency

apply_analytical! does not enforce consistency of the applied solution with the system of equations. Some problems, like for example differential-algebraic systems of equations (DAEs) need extra care during initialization. We refer to the paper "Consistent Initial Condition Calculation for Differential-Algebraic Systems" by Brown et al. for more details on this matter.

diff --git a/previews/PR798/topics/constraints/index.html b/previews/PR798/topics/constraints/index.html new file mode 100644 index 0000000000..c06020b594 --- /dev/null +++ b/previews/PR798/topics/constraints/index.html @@ -0,0 +1,24 @@ + +Constraints · Ferrite.jl

Constraints

PDEs can in general be subjected to a number of constraints,

\[g_I(\underline{a}) = 0, \quad I = 1 \text{ to } n_c\]

where $g$ are (non-linear) constraint equations, $\underline{a}$ is a vector of the degrees of freedom, and $n_c$ is the number of constraints. There are many ways to enforce these constraints, e.g. penalty methods and Lagrange multiplier methods.

Affine constraints

Affine or linear constraints can be handled directly in Ferrite. Such constraints can typically be expressed as:

\[a_1 = 5a_2 + 3a_3 + 1 \\ +a_4 = 2a_3 + 6a_5 \\ +\dots\]

where $a_1$, $a_2$ etc. are system degrees of freedom. In Ferrite, we can account for such constraint using the ConstraintHandler:

ch = ConstraintHandler(dh)
+lc1 = AffineConstraint(1, [2 => 5.0, 3 => 3.0], 1)
+lc2 = AffineConstraint(4, [3 => 2.0, 5 => 6.0], 0)
+add!(ch, lc1)
+add!(ch, lc2)

Affine constraints will affect the sparsity pattern of the stiffness matrix, and as such, it is important to also include the ConstraintHandler as an argument when creating the sparsity pattern:

K = allocate_matrix(dh, ch)

Solving linear problems

To solve the system $\underline{\underline{K}}\underline{a}=\underline{f}$, account for affine constraints the same way as for Dirichlet boundary conditions; first call apply!(K, f, ch). This will condense K and f inplace (i.e no new matrix will be created). Note however that we must also call apply! on the solution vector after solving the system to enforce the affine constraints:

# ...
+# Assemble K and f...
+
+apply!(K, f, ch)
+a = K\f
+apply!(a, ch) # enforces affine constraints
+

Solving nonlinear problems

It is important to check the residual after applying boundary conditions when solving nonlinear problems with affine constraints. apply_zero!(K, r, ch) modifies the residual entries for dofs that are involved in constraints to account for constraint forces. The following pseudo-code shows a typical pattern for solving a non-linear problem with Newton's method:

a = initial_guess(...)  # Make any initial guess for a here, e.g. `a=zeros(ndofs(dh))`
+apply!(a, ch)           # Make the guess fulfill all constraints in `ch`
+for iter in 1:maxiter
+    doassemble!(K, r, ...)  # Assemble the residual, r, and stiffness, K=∂r/∂a.
+    apply_zero!(K, r, ch)   # Modify `K` and `r` to account for the constraints.
+    check_convergence(r, ...) && break # Only check convergence after `apply_zero!(K, r, ch)`
+    Δa = K \ r              # Calculate the (negative) update
+    apply_zero!(Δa, ch)     # Change the constrained values in `Δa` such that `a-Δa`
+                            # fulfills constraints if `a` did.
+    a .-= Δa
+end
diff --git a/previews/PR798/topics/degrees_of_freedom/index.html b/previews/PR798/topics/degrees_of_freedom/index.html new file mode 100644 index 0000000000..64c8c2e02c --- /dev/null +++ b/previews/PR798/topics/degrees_of_freedom/index.html @@ -0,0 +1,15 @@ + +Degrees of Freedom · Ferrite.jl

Degrees of Freedom

The distribution and numbering of degrees of freedom (dofs) are handled by the DofHandler. The DofHandler will be used to query information about the dofs. For example we can obtain the dofs for a particular cell, which we need when assembling the system.

The DofHandler is based on the grid. Here we create a simple grid with Triangle cells, and then create a DofHandler based on the grid

grid = generate_grid(Triangle, (20, 20))
+dh = DofHandler(grid)
DofHandler{2, Grid{2, Triangle, Float64}}
+  Fields:
+  Not closed!

Fields

Before we can distribute the dofs we need to specify fields. A field is simply the unknown function(s) we are solving for. To add a field we need a name (a Symbol) and the the interpolation describing the shape functions for the field. Here we add a scalar field :p, interpolated using linear (degree 1) shape functions on a triangle, and a vector field :u, also interpolated with linear shape functions on a triangle, but raised to the power 2 to indicate that it is a vector field with 2 components (for a 2D problem).

add!(dh, :p, Lagrange{RefTriangle, 1}())
+add!(dh, :u, Lagrange{RefTriangle, 1}()^2)
DofHandler{2, Grid{2, Triangle, Float64}}
+  Fields:
+    :p, Lagrange{RefTriangle, 1}()
+    :u, Lagrange{RefTriangle, 1}()^2
+  Not closed!  Not closed!

Finally, when we have added all the fields, we have to close! the DofHandler. When the DofHandler is closed it will traverse the grid and distribute all the dofs for the fields we added.

close!(dh)
DofHandler{2, Grid{2, Triangle, Float64}}
+  Fields:
+    :p, Lagrange{RefTriangle, 1}()
+    :u, Lagrange{RefTriangle, 1}()^2
+  Dofs per cell: 9
+  Total dofs: 1323

Ordering of Dofs

Todo

Describe dof ordering within elements (vertices -> edges -> faces -> volumes) and dof_range. Describe (global) dof renumbering

diff --git a/previews/PR798/topics/export/index.html b/previews/PR798/topics/export/index.html new file mode 100644 index 0000000000..c6bc87f7e1 --- /dev/null +++ b/previews/PR798/topics/export/index.html @@ -0,0 +1,22 @@ + +Export · Ferrite.jl

Export

When the problem is solved, and the solution vector u is known we typically want to visualize it. The simplest way to do this is to write the solution to a VTK-file, which can be viewed in e.g. Paraview. To write VTK-files, Ferrite comes with an export interface with a WriteVTK.jl backend to simplify the exporting.

The following structure can be used to write various output to a vtk-file:

VTKGridFile("my_solution", grid) do vtk
+    write_solution(vtk, dh, u)
+end;
VTKGridFile for the closed file "my_solution.vtu".

where write_solution is just one example of the following functions that can be used

Instead of using the do-block, it is also possible to do

vtk = VTKGridFile("my_solution", grid)
+write_solution(vtk, dh, u)
+# etc.
+close(vtk);
VTKGridFile for the closed file "my_solution.vtu".

The data written by write_solution, write_cell_data, write_node_data, and write_projection may be either scalar (Vector{<:Number}) or tensor (Vector{<:AbstractTensor}) data.

For simulations with multiple time steps, typically one VTK (.vtu) file is written for each time step. In order to connect the actual time with each of these files, the paraview_collection can function from WriteVTK.jl can be used. This will create one paraview datafile (.pvd) file and one VTKGridFile (.vtu) for each time step.

using WriteVTK
+pvd = paraview_collection("my_results")
+for (step, t) in enumerate(range(0, 1, 5))
+    # Do calculations to update u
+    VTKGridFile("my_results_$step", dh) do vtk
+        write_solution(vtk, dh, u)
+        pvd[t] = vtk
+    end
+end
+vtk_save(pvd);
6-element Vector{String}:
+ "my_results.pvd"
+ "my_results_1.vtu"
+ "my_results_2.vtu"
+ "my_results_3.vtu"
+ "my_results_4.vtu"
+ "my_results_5.vtu"

See Transient heat equation for an example

diff --git a/previews/PR798/topics/fe_intro/index.html b/previews/PR798/topics/fe_intro/index.html new file mode 100644 index 0000000000..afd97e08b0 --- /dev/null +++ b/previews/PR798/topics/fe_intro/index.html @@ -0,0 +1,15 @@ + +Introduction to FEM · Ferrite.jl

Introduction to FEM

Here we will present a very brief introduction to partial differential equations (PDEs) and to the finite element method (FEM). Perhaps the simplest PDE of all is the (steady-state, linear) heat equation, also known as the Poisson equation. We will use this equation as a demonstrative example of the method, and demonstrate how we go from the strong form of the equation, to the weak form, and then finally to the discrete FE problem.

Strong form

The strong form of the heat equation may be written as:

\[- \nabla \cdot \mathbf{q}(u) = f \quad \forall \, \mathbf{x} \in \Omega,\]

where $u$ is the unknown temperature field, $\mathbf{q}$ is the heat flux, $f$ is an internal heat source, and $\Omega$ is the domain on which the equation is defined. To complete the problem we need to specify what happens at the domain boundary $\Gamma$. This set of specifications is called boundary conditions. There are different types of boundary conditions, where the most common ones are Dirichlet – which means that the solution $u$ is known at some part of the boundary, and Neumann – which means that the gradient of the solution, $\nabla u$ is known. Formally we write for our example

\[u = u^\mathrm{p} \quad \forall \, \mathbf{x} \in \Gamma_\mathrm{D},\\ +\mathbf{q} \cdot \mathbf{n} = q^\mathrm{p} \quad \forall \, \mathbf{x} \in \Gamma_\mathrm{N},\]

i.e. the temperature is prescribed to a known function $u^\mathrm{p}$ at the Dirichlet part of the boundary, $\Gamma_\mathrm{D}$, and the heat flux is prescribed to $q^\mathrm{p}$ at the Neumann part of the boundary, $\Gamma_\mathrm{N}$, where $\mathbf{n}$ describes the outward pointing normal vector at the boundary.

We also need a constitutive equation which links the temperature field, $u$, to the heat flux, $\mathbf{q}$. The simplest case is to use Fourier's law

\[\mathbf{q}(u) = -k \nabla u\]

where $k$ is the conductivity of the material. In general the conductivity can vary throughout the domain as a function of the coordinate, i.e. $k = k(\mathbf{x})$, but for simplicity we will consider only constant conductivity $k$.

Weak form

The solution to the equation above is usually calculated from the corresponding weak form. By multiplying the equation with an arbitrary test function $\delta u$, integrating over the domain and using partial integration we obtain the weak form. Now our problem can be stated as:

Find $u \in \mathbb{U}$ s.t.

\[\int_\Omega \nabla \delta u \cdot (k \nabla u) \, \mathrm{d}\Omega = +\int_{\Gamma_\mathrm{N}} \delta u \, q^\mathrm{p} \, \mathrm{d}\Gamma + +\int_\Omega \delta u \, f \, \mathrm{d}\Omega \quad \forall \, \delta u \in \mathbb{T}\]

where $\mathbb{U}, \mathbb{T}$ are suitable function spaces with sufficiently regular functions. Under very general assumptions it can be shown that the solution to the weak form is identical to the solution to the strong form.

Finite Element approximation

Using the finite element method to solve partial differential equations is usually preceded with the construction of a discretization of the domain $\Omega$ into a finite set of elements or cells. We call this geometric discretization grid (or mesh) and denote it with $\Omega_h$. In this example the corners of the triangles are called nodes.

Next we introduce the finite element approximation $u_\mathrm{h} \approx u$ as a sum of N nodal shape functions, where we denote each of these function by $\phi_i$ and the corresponding nodal values $\hat{u}_i$. Note that shape functions are sometimes referred to as basis functions or trial functions, and instead of $\phi_i$ they are sometimes denoted $N_i$. In this example we choose to approximate the test function in the same way. This approach is known as the Galerkin finite element method. Formally we write the evaluation of our approximations at a specific point $\mathbf{x}$ in our domain $\Omega$ as:

\[u_\mathrm{h}(\mathbf{x}) = \sum_{i=1}^{\mathrm{N}} \phi_i(\mathbf{x}) \, \hat{u}_i,\qquad +\delta u_\mathrm{h}(\mathbf{x}) = \sum_{i=1}^{\mathrm{N}} \phi_i(\mathbf{x}) \, \delta \hat{u}_i \, .\]

Since test and trial functions are usually chosen in such a way, that they build the basis of some function space (basis as in basis of a vector space), sometimes are they are also called basis functions. In the following the argument $\mathbf{x}$ is dropped to keep the notation compact. We may now insert these approximations in the weak form, which results in

\[\sum_i^N \delta \hat{u}_i \left(\sum_j^N \int_{\Omega_\mathrm{h}} \nabla \phi_i \cdot (k \nabla \phi_j) \, \mathrm{d}\Omega \ \hat{u}_j \right) = +\sum_i^N \delta \hat{u}_i \left( \int_{\Gamma_\mathrm{N}} \phi_i \, q^\mathrm{p} \, \mathrm{d}\Gamma + +\int_{\Omega_\mathrm{h}} \phi_i \, f \, \mathrm{d}\Omega \right) \, .\]

Since this equation must hold for arbitrary $\delta u_\mathrm{h}$, the equation must especially hold for the specific choice that only one of the nodal values $\delta \hat{u}_i$ is fixed to 1 while an all other coefficients are fixed to 0. Repeating this argument for all $i$ from 1 to N we obtain N linear equations. This way the discrete problem can be written as a system of linear equations

\[\underline{\underline{K}}\ \underline{\hat{u}} = \underline{\hat{f}} \, ,\]

where we call $\underline{\underline{K}}$ the (tangent) stiffness matrix, $\underline{\hat{u}}$ the solution vector with the nodal values and $\underline{\hat{f}}$ the force vector. The specific naming is for historical reasons, because the finite element method has its origins in mechanics. The elements of $\underline{\underline{K}}$ and $\underline{\hat{f}}$ are given by

\[(\underline{\underline{K}})_{ij} = + \int_{\Omega_\mathrm{h}} \nabla \phi_i \cdot (k \nabla \phi_j) \mathrm{d}\Omega \, , \\ + +(\underline{\hat{f}})_{i} = + \int_{\Gamma_\mathrm{N}} \phi_i \, q^\mathrm{p} \, \mathrm{d}\Gamma + \int_{\Omega_\mathrm{h}} \phi_i \, f \, \mathrm{d}\Omega \, .\]

Finally we also need to take care of the Dirichlet boundary conditions. These are enforced by setting the corresponding $\hat{u}_i$ to the prescribed values and eliminating the associated equations from the system. Now, solving this equation system yields the nodal values and thus an approximation to the true solution.

Notes on the implementation

In practice, the shape functions $\phi_i$ are only non-zero on parts of the domain $\Omega_\mathrm{h}$. Thus, the integrals are evaluated on sub-domains, called elements or cells.

Each cell gives a contribution to the global stiffness matrix and force vector. The process of constructing the system of equations is also called assembly. For clarification, let us rewrite the formula for the stiffness matrix entries as follows:

\[(\underline{\underline{K}})_{ij} + = \int_{\Omega_\mathrm{h}} \nabla \phi_i \cdot (k \nabla \phi_j) \mathrm{d}\Omega + = \sum_{E \in \Omega_\mathrm{h}} \int_E \nabla \phi_i \cdot (k \nabla \phi_j) \mathrm{d}\Omega \, .\]

This formulation underlines the element-centric perspective of finite element methods and reflects how it is usually implemented in software.

Computing the element integrals by hand can become a tedious task. To avoid this issue we approximate the element integrals with a technique called numerical integration. Skipping any of the mathematical details, the basic idea is to evaluate the function under the integral at specific points and weighting the evaluations accordingly, such that their sum approximates the volume properly. A very nice feature of these techniques is, that under quite general circumstances the formula is not just an approximation, but the exact evaluation of the integral. To avoid the recomputation of the just mentioned evaluation positions of the integral for each individual element, we perform a coordinate transformation onto a so-called reference element. Formally we write

\[ \int_E \nabla \phi_i \cdot (k \nabla \phi_j) \mathrm{d}\Omega + \approx \sum_q \nabla \phi_i(\textbf{x}_q) \cdot (k(\textbf{x}_q) \nabla \phi_j(\textbf{x}_q)) \, w_q \, \textrm{det}(J(\textbf{x}_q)) \, ,\]

where $J$ is the Jacobian of the coordinate transformation function. The computation of the transformation, weights, positions and of the Jacobi determinant is handled by Ferrite. On an intuitive level, and to explain the notation used in the implementation, we think of

\[ \mathrm{d}\Omega \approx \, w \, \textrm{det}(J)\]

being the chosen approximation when changing from the integral to the finite summation.

For an example of the implementation to solve a heat problem with Ferrite check out this thoroughly commented example.

More details

We finally want to note that this quick introduction barely scratches the surface of the finite element method. Also, we presented some things in a simplified way for the sake of keeping this article short and concise. There is a large corpus of literature and online tutorials containing more details about the finite element method. To give a few recommendations there is:

  • Hans Petter Langtangen's Script
  • Wolfgang Bangerth's Lecture Series
  • Introduction to the Finite Element Method by Niels Ottosen and Hans Petersson
  • The Finite Element Method for Elliptic Problems by Philippe Ciarlet
  • Finite Elements: Theory, Fast Solvers, and Applications in Elasticity Theory by Dietrich Braess
  • An Analysis of the Finite Element Method by Gilbert Strang and George Fix
  • Finite Element Procedures by Klaus-Jürgen Bathe
  • The Finite Element Method: Its Basis and Fundamentals by Olgierd Cecil Zienkiewicz, Robert Taylor and J.Z. Zhu
  • Higher-Order Finite Element Methods by Pavel Šolín, Karel Segeth and Ivo Doležel

This list is neither meant to be exhaustive, nor does the absence of a work mean that it is in any way bad or not recommendable. The ordering of the articles also has no particular meaning.

diff --git a/previews/PR798/topics/grid/index.html b/previews/PR798/topics/grid/index.html new file mode 100644 index 0000000000..97b5fbfe74 --- /dev/null +++ b/previews/PR798/topics/grid/index.html @@ -0,0 +1,22 @@ + +Grid · Ferrite.jl

Grid

Mesh reading

A Ferrite Grid can be generated with the generate_grid function. More advanced meshes can be imported with the FerriteMeshParser.jl (from Abaqus input files), or even created and translated with the Gmsh.jl and FerriteGmsh.jl package, respectively.

FerriteGmsh.jl

FerriteGmsh.jl supports all defined cells with an alias in Ferrite.jl as well as the 3D Serendipity Cell{3,20,6}. Either, a mesh is created on the fly with the gmsh API or a mesh in .msh or .geo format can be read and translated with the FerriteGmsh.togrid function.

FerriteGmsh.togridFunction
togrid(filename::String; domain="")

Open the Gmsh file filename (ie a .geo or .msh file) and return the corresponding Ferrite.Grid.

source
togrid(; domain="")

Generate a Ferrite.Grid from the current active/open model in the Gmsh library.

source

FerriteGmsh supports currently the translation of cellsets and facetsets. Such sets are defined in Gmsh as PhysicalGroups of dimension dim and dim-1, respectively. In case only a part of the mesh is the domain, the domain can be specified by providing the keyword argument domain the name of the PhysicalGroups in the FerriteGmsh.togrid function.

Why you should read a .msh file

Reading a .msh file is the advertised way, since otherwise you remesh whenever you run the code. Further, if you choose to read the grid directly from the current model of the gmsh API you get artificial nodes, which doesn't harm the FE computation, but maybe distort your sophisticated grid operations (if present). For more information, see this issue.

If you want to read another, not yet supported cell from gmsh, consider to open a PR at FerriteGmsh that extends the gmshtoferritecell dict and if needed, reorder the element nodes by dispatching FerriteGmsh.translate_elements. The reordering of nodes is necessary if the Gmsh ordering doesn't match the one from Ferrite. Gmsh ordering is documented here. For an exemplary usage of Gmsh.jl and FerriteGmsh.jl, consider the Stokes flow and Incompressible Navier-Stokes Equations via DifferentialEquations.jl example.

FerriteMeshParser.jl

FerriteMeshParser.jl converts the mesh in an Abaqus input file (.inp) to a Ferrite.Grid with its function get_ferrite_grid. The translations for most of Abaqus' standard 2d and 3d continuum elements to a Ferrite.AbstractCell are defined. Custom translations can be given as input, which can be used to import other (custom) elements or to override the default translation.

FerriteMeshParser.get_ferrite_gridFunction
function get_ferrite_grid(
+    filename; 
+    meshformat=AutomaticMeshFormat(), 
+    user_elements=Dict{String,DataType}(), 
+    generate_facetsets=true
+    )

Create a Ferrite.Grid by reading in the file specified by filename.

Optional arguments:

  • meshformat: Which format the mesh is given in, normally automatically detected by the file extension
  • user_elements: Used to add extra elements not supported, might require a separate cell constructor.
  • generate_facetsets: Should facesets be automatically generated from all nodesets?
source

If you are missing the translation of an Abaqus element that is equivalent to a Ferrite.AbstractCell, consider to open an issue or a pull request.

Grid datastructure

In Ferrite a Grid is a collection of Nodes and Cells and is parameterized in its physical dimensionality and cell type. Nodes are points in the physical space and can be initialized by a N-Tuple, where N corresponds to the dimensions.

n1 = Node((0.0, 0.0))

Cells are defined based on the Node IDs. Hence, they collect IDs in a N-Tuple. Consider the following 2D mesh:

global mesh

The cells of the grid can be described in the following way

cells = [Quadrilateral((1, 2, 5, 4)),
+         Quadrilateral((2, 3, 6, 5)),
+         Quadrilateral((4, 5, 8, 7)),
+         Quadrilateral((5, 6, 9, 8))]

where each Quadrilateral <: AbstractCell is defined by the tuple of node IDs. Additionally, the data structure Grid contains node-, cell-, facet-, and vertexsets. Each of these sets is defined by a Dict{String, OrderedSet}.

Node- and cellsets are represented by an OrderedSet{Int}, giving a set of node or cell ID, respectively.

Facet- and vertexsets are represented by OrderedSet{<:BoundaryIndex}, where BoundaryIndex is a FacetIndex or VertexIndex respectively. FacetIndex and VertexIndex wraps a Tuple, (global_cell_id, local_facet_id) and (global_cell_id, local_vertex_id), where the local IDs are defined according to the reference shapes, see Reference shapes.

The highlighted facets, i.e. the two edges from node ID 3 to 6 and from 6 to 9, on the right hand side of our test mesh can now be described as

boundary_facets = [(3, 6), (6, 9)]

i.e. by using the node IDs of the reference shape vertices.

The first of these can be found as the 2nd facet of the 2nd cell.


julia> Ferrite.facets(Quadrilateral((2, 3, 6, 5)))((2, 3), (3, 6), (6, 5), (5, 2))

The unique representation of an entity is given by the sorted version of this tuple. While we could use this information to construct a facet set, Ferrite can construct this set by filtering based on the coordinates, using addfacetset!.

AbstractGrid

It can be very useful to use a grid type for a certain special case, e.g. mixed cell types, adaptivity, IGA, etc. In order to define your own <: AbstractGrid you need to fulfill the AbstractGrid interface. In case that certain structures are preserved from the Ferrite.Grid type, you don't need to dispatch on your own type, but rather rely on the fallback AbstractGrid dispatch.

Example

As a starting point, we choose a minimal working example from the test suite:

struct SmallGrid{dim,N,C<:Ferrite.AbstractCell} <: Ferrite.AbstractGrid{dim}
+    nodes_test::Vector{NTuple{dim,Float64}}
+    cells_test::NTuple{N,C}
+end

Here, the names of the fields as well as their underlying datastructure changed compared to the Grid type. This would lead to the fact, that any usage with the utility functions and DoF management will not work. So, we need to feed into the interface how to handle this subtyped datastructure. We start with the utility functions that are associated with the cells of the grid:

Ferrite.getcells(grid::SmallGrid) = grid.cells_test
+Ferrite.getcells(grid::SmallGrid, v::Union{Int, Vector{Int}}) = grid.cells_test[v]
+Ferrite.getncells(grid::SmallGrid{dim,N}) where {dim,N} = N
+Ferrite.getcelltype(grid::SmallGrid) = eltype(grid.cells_test)
+Ferrite.getcelltype(grid::SmallGrid, i::Int) = typeof(grid.cells_test[i])

Next, we define some helper functions that take care of the node handling.

Ferrite.getnodes(grid::SmallGrid) = grid.nodes_test
+Ferrite.getnodes(grid::SmallGrid, v::Union{Int, Vector{Int}}) = grid.nodes_test[v]
+Ferrite.getnnodes(grid::SmallGrid) = length(grid.nodes_test)
+Ferrite.get_coordinate_eltype(::SmallGrid) = Float64
+Ferrite.get_coordinate_type(::SmallGrid{dim}) where dim = Vec{dim,Float64}
+Ferrite.nnodes_per_cell(grid::SmallGrid, i::Int=1) = Ferrite.nnodes(grid.cells_test[i])

These definitions make many of Ferrite functions work out of the box, e.g. you can now call getcoordinates(grid, cellid) on the SmallGrid.

Now, you would be able to assemble the heat equation example over the new custom SmallGrid type. Note that this particular subtype isn't able to handle boundary entity sets and so, you can't describe boundaries with it. In order to use boundaries, e.g. for Dirichlet constraints in the ConstraintHandler, you would need to dispatch the AbstractGrid sets utility functions on SmallGrid.

Topology

Ferrite.jl's Grid type offers experimental features w.r.t. topology information. The functions getneighborhood and facetskeleton are the interface to obtain topological information. The getneighborhood can construct lists of directly connected entities based on a given entity (CellIndex, FacetIndex, FaceIndex, EdgeIndex, or VertexIndex). The facetskeleton function can be used to evaluate integrals over material interfaces or computing element interface values such as jumps.

diff --git a/previews/PR798/topics/index.html b/previews/PR798/topics/index.html new file mode 100644 index 0000000000..06303168aa --- /dev/null +++ b/previews/PR798/topics/index.html @@ -0,0 +1,2 @@ + +Topic guide overview · Ferrite.jl
diff --git a/previews/PR798/topics/my_results.pvd b/previews/PR798/topics/my_results.pvd new file mode 100755 index 0000000000..093f7b6741 --- /dev/null +++ b/previews/PR798/topics/my_results.pvd @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/previews/PR798/topics/my_results_1.vtu b/previews/PR798/topics/my_results_1.vtu new file mode 100644 index 0000000000..1fac2652c4 Binary files /dev/null and b/previews/PR798/topics/my_results_1.vtu differ diff --git a/previews/PR798/topics/my_results_2.vtu b/previews/PR798/topics/my_results_2.vtu new file mode 100644 index 0000000000..1fac2652c4 Binary files /dev/null and b/previews/PR798/topics/my_results_2.vtu differ diff --git a/previews/PR798/topics/my_results_3.vtu b/previews/PR798/topics/my_results_3.vtu new file mode 100644 index 0000000000..1fac2652c4 Binary files /dev/null and b/previews/PR798/topics/my_results_3.vtu differ diff --git a/previews/PR798/topics/my_results_4.vtu b/previews/PR798/topics/my_results_4.vtu new file mode 100644 index 0000000000..1fac2652c4 Binary files /dev/null and b/previews/PR798/topics/my_results_4.vtu differ diff --git a/previews/PR798/topics/my_results_5.vtu b/previews/PR798/topics/my_results_5.vtu new file mode 100644 index 0000000000..1fac2652c4 Binary files /dev/null and b/previews/PR798/topics/my_results_5.vtu differ diff --git a/previews/PR798/topics/my_solution.vtu b/previews/PR798/topics/my_solution.vtu new file mode 100644 index 0000000000..1fac2652c4 Binary files /dev/null and b/previews/PR798/topics/my_solution.vtu differ diff --git a/previews/PR798/topics/reference_shapes/index.html b/previews/PR798/topics/reference_shapes/index.html new file mode 100644 index 0000000000..611514fe75 --- /dev/null +++ b/previews/PR798/topics/reference_shapes/index.html @@ -0,0 +1,2 @@ + +Reference shapes · Ferrite.jl

Reference shapes

The reference shapes in Ferrite are used to define grid cells, function interpolations (i.e. shape functions), and quadrature rules. Currently, the following reference shapes are defined

  • RefLine
  • RefTriangle
  • RefQuadrilateral
  • RefTetrahedron
  • RefHexahedron
  • RefPrism
  • RefPyramid

Entity naming

Ferrite denotes the entities of a reference shape as follows

EntityDescription
Vertex0-dimensional entity in the reference shape.
Edge1-dimensional entity connecting two vertices.
Face2-dimensional entity enclosed by edges.
Volume3-dimensional entity enclosed by faces.

Note that a node in Ferrite is not the same as a vertex. Vertices denote endpoints of edges, while nodes may also be located in the middle of edges (e.g. for a QuadraticLine cell).

To write dimensionally independent code, Ferrite also denotes entities by their codimension, defined relative the reference shape dimension. Specifically, Ferrite has the entities

EntityDescription
Cell0-codimensional entity, i.e. the same as the reference shape.
Facet1-codimensional entity defining the boundary of cells.

Standard use cases mostly deal with these codimensional entities, such as CellValues and FacetValues.

Definition of codimension

In Ferrite, codimension is defined relative to the reference dimension of the specific entity. Note that other finite element codes may define it differently (e.g. relative the highest reference dimension in the grid).

Entity numbering

Each reference shape defines the numbering of its vertices, edges, and faces entities, where the edge and face entities are defined from their vertex numbers.

Note

The numbering and identification of entities is (mostly) for internal use and typically not something users of Ferrite need to interact with.

Example

The RefQuadrilateral is defined on the domain $[-1, 1] \times [-1, 1]$ in the local $\xi_1-\xi_2$ coordinate system.

local element

The vertices of a RefQuadrilateral are then

Ferrite.reference_vertices(RefQuadrilateral)
(1, 2, 3, 4)

and its edges are then defined as

Ferrite.reference_edges(RefQuadrilateral)
((1, 2), (2, 3), (3, 4), (4, 1))

where the numbers refer to the vertex number. Finally, this reference shape is 2-dimensional, so it only has a single face, corresponding to the cell itself,

Ferrite.reference_faces(RefQuadrilateral)
((1, 2, 3, 4),)

also defined in terms of its vertices.

As this is a 2-dimensional reference shape, the facets are the edges, i.e.

Ferrite.reference_facets(RefQuadrilateral)
((1, 2), (2, 3), (3, 4), (4, 1))
Not public API

The functions reference_vertices, reference_edges, reference_faces, and reference_facets are not public and only shown here to explain the numbering concept. The specific ordering may also change, and is therefore only documented in the Developer documentation.

diff --git a/previews/PR798/topics/sparse_matrix/index.html b/previews/PR798/topics/sparse_matrix/index.html new file mode 100644 index 0000000000..1b4ce9cef7 --- /dev/null +++ b/previews/PR798/topics/sparse_matrix/index.html @@ -0,0 +1,6 @@ + +Sparsity pattern and sparse matrices · Ferrite.jl

Sparsity pattern and sparse matrices

An important property of the finite element method is that it results in sparse matrices for the linear systems to be solved. On this page the topic of sparsity and sparse matrices are discussed.

Sparsity pattern

The sparse structure of the linear system depends on many factors such as e.g. the weak form, the discretization, and the choice of interpolation(s). In the end it boils down to how the degrees of freedom (DoFs) couple with each other. The most common reason that two DoFs couple is because they belong to the same element. Note, however, that this is not guaranteed to result in a coupling since it depends on the specific weak form that is being discretized, see e.g. Increasing the sparsity. Boundary conditions and constraints can also result in additional DoF couplings.

If DoFs i and j couple, then the computed value in the eventual matrix will be structurally nonzero[1]. In this case the entry (i, j) should be included in the sparsity pattern. Conversely, if DoFs i and j don't couple, then the computed value will be zero. In this case the entry (i, j) should not be included in the sparsity pattern since there is no need to allocate memory for entries that will be zero.

The sparsity, i.e. the ratio of zero-entries to the total number of entries, is often[2] very high and taking advantage of this results in huge savings in terms of memory. For example, in a problem with $10^6$ DoFs there will be a matrix of size $10^6 \times 10^6$. If all $10^{12}$ entries of this matrix had to be stored (0% sparsity) as double precision (Float64, 8 bytes) it would require 8 TB of memory. If instead the sparsity is 99.9973% (which is the case when solving the heat equation on a three dimensional hypercube with linear Lagrange interpolation) this would be reduced to 216 MB.

Sparsity pattern example

To give an example, in this one-dimensional heat problem (see the Heat equation tutorial for the weak form) we have 4 nodes with 3 elements in between. For simplicity DoF numbers and node numbers are the same but this is not true in general since nodes and DoFs can be numbered independently (and in fact are numbered independently in Ferrite).

1 ----- 2 ----- 3 ----- 4

Assuming we use linear Lagrange interpolation (the "hat functions") this will give the following connections according to the weak form:

  • Trial function 1 couples with test functions 1 and 2 (entries (1, 1) and (1, 2) included in the sparsity pattern)
  • Trial function 2 couples with test functions 1, 2, and 3 (entries (2, 1), (2, 2), and (2, 3) included in the sparsity pattern)
  • Trial function 3 couples with test functions 2, 3, and 4 (entries (3, 2), (3, 3), and (3, 4) included in the sparsity pattern)
  • Trial function 4 couples with test functions 3 and 4 (entries (4, 3) and (4, 4) included in the sparsity pattern)

The resulting sparsity pattern would look like this:

4×4 SparseArrays.SparseMatrixCSC{Float64, Int64} with 10 stored entries:
+ 0.0  0.0   ⋅    ⋅
+ 0.0  0.0  0.0   ⋅
+  ⋅   0.0  0.0  0.0
+  ⋅    ⋅   0.0  0.0

Moreover, if the problem is solved with periodic boundary conditions, for example by constraining the value on the right side to the value on the left side, there will be additional couplings. In the example above, this means that DoF 4 should be equal to DoF

  1. Since DoF 4 is constrained it has to be eliminated from the system. Existing entries

that include DoF 4 are (3, 4), (4, 3), and (4, 4). Given the simple constraint in this case we can simply replace DoF 4 with DoF 1 in these entries and we end up with entries (3, 1), (1, 3), and (1, 1). This results in two new entries: (3, 1) and (1, 3) (entry (1, 1) is already included).

Creating sparsity patterns

Creating a sparsity pattern can be quite expensive if not done properly and therefore Ferrite provides efficient methods and data structures for this. In general the sparsity pattern is not known in advance and has to be created incrementally. To make this incremental construction efficient it is necessary to use a dynamic data structure which allow for fast insertions.

The sparsity pattern also serves as a "matrix builder". When all entries are inserted into the sparsity pattern the dynamic data structure is typically converted, or "compressed", into a sparse matrix format such as e.g. the compressed sparse row (CSR) format or the compressed sparse column (CSC) format, where the latter is the default sparse matrix type implemented in the SparseArrays standard library. These matrix formats allow for fast linear algebra operations, such as factorizations and matrix-vector multiplications, that are needed when the linear system is solved. See Instantiating the sparse matrix for more details.

In summary, a dynamic structure is more efficient when incrementally building the pattern by inserting new entries, and a static or compressed structure is more efficient for linear algebra operations.

Basic sparsity patterns construction

Working with the sparsity pattern explicitly is in many cases not necessary. For basic usage (e.g. when only one matrix needed, when no customization of the pattern is required, etc) there exist convenience methods of allocate_matrix that return the matrix directly. Most examples in this documentation don't deal with the sparsity pattern explicitly because the basic method suffice. See also Instantiating the sparse matrix for more details.

Custom sparsity pattern construction

In more advanced cases there might be a need for more fine grained control of the sparsity pattern. The following steps are typically taken when constructing a sparsity pattern in Ferrite:

  1. Initialize an empty pattern: This can be done by either using the init_sparsity_pattern(dh) function or by using a constructor directly. init_sparsity_pattern will return a default pattern type that is compatible with the DofHandler. In some cases you might require another type of pattern (for example a blocked pattern, see Blocked sparsity pattern) and in that case you can use the constructor directly.

  2. Add entries to the pattern: There are a number of functions that add entries to the pattern:

    • add_sparsity_entries! is a convenience method for performing the common task of calling add_cell_entries!, add_interface_entries!, and add_constraint_entries! after each other (see below).
    • add_cell_entries! adds entries for all couplings between the DoFs within each element. These entries correspond to assembling the standard element matrix and is thus almost always required.
    • add_interface_entries! adds entries for couplings between the DoFs in neighboring elements. These entries are required when integrating along internal interfaces between elements (e.g. for discontinuous Galerkin methods).
    • add_constraint_entries! adds entries required from constraints and boundary conditions in the ConstraintHandler. Note that this operation depends on existing entries in the pattern and must be called as the last operation on the pattern.
    • Ferrite.add_entry! adds a single entry to the pattern. This can be used if you need to add custom entries that are not covered by the other functions.
  3. Instantiate the matrix: A sparse matrix can be created from the sparsity pattern using allocate_matrix, see Instantiating the sparse matrix below for more details.

Increasing the sparsity

By default, when creating a sparsity pattern, it is assumed that each DoF within an element couple with with all other DoFs in the element.

Todo
  • Discuss the coupling keyword argument.
  • Discuss the keep_constrained keyword argument.

Blocked sparsity pattern

Todo

Discuss BlockSparsityPattern and BlockArrays extension.

Instantiating the sparse matrix

As mentioned above, for many simple cases there is no need to work with the sparsity pattern directly and using methods of allocate_matrix that take the DofHandler as input is enough, for example:

K = allocate_matrix(dh, ch)

allocate_matrix is also used to instantiate a matrix from a sparsity pattern, for example:

K = allocate_matrix(sp)
Multiple matrices with the same pattern

For some problems there is a need for multiple matrices with the same sparsity pattern, for example a mass matrix and a stiffness matrix. In this case it is more efficient to create the sparsity pattern once and then instantiate both matrices from it.

  • 1Structurally nonzero means that there is a possibility of a nonzero value even though the computed value might become zero in the end for various reasons.
  • 2At least for most practical problems using low order interpolations.
diff --git a/previews/PR798/tutorials/computational_homogenization.ipynb b/previews/PR798/tutorials/computational_homogenization.ipynb new file mode 100644 index 0000000000..4985d74707 --- /dev/null +++ b/previews/PR798/tutorials/computational_homogenization.ipynb @@ -0,0 +1,844 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Computational homogenization\n", + "\n", + "![](rve_homogenization.png)\n", + "\n", + "*Figure 1*: von Mises stress in an RVE with 5 stiff inclusions embedded in a softer matrix\n", + "material that is loaded in shear. The problem is solved by using homogeneous Dirichlet\n", + "boundary conditions (left) and (strong) periodic boundary conditions (right)." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Introduction\n", + "\n", + "In this example we will solve the Representative Volume Element (RVE) problem for\n", + "computational homogenization of linear elasticity and compute the effective/homogenized\n", + "stiffness of an RVE with 5 stiff circular inclusions embedded in a softer matrix material\n", + "(see Figure 1).\n", + "\n", + "It is possible to obtain upper and lower bounds on the stiffness analytically, see for\n", + "example [Rule of mixtures](https://en.wikipedia.org/wiki/Rule_of_mixtures). An upper\n", + "bound is obtained from the Voigt model, where the *strain* is assumed to be the same in\n", + "the two constituents,\n", + "\n", + "$$\n", + "\\mathsf{E}_\\mathrm{Voigt} = v_\\mathrm{m} \\mathsf{E}_\\mathrm{m} +\n", + "(1 - v_\\mathrm{m}) \\mathsf{E}_\\mathrm{i}\n", + "$$\n", + "\n", + "where $v_\\mathrm{m}$ is the volume fraction of the matrix material, and where\n", + "$\\mathsf{E}_\\mathrm{m}$ and $\\mathsf{E}_\\mathrm{i}$ are the individual stiffness for\n", + "the matrix material and the inclusions, respectively. The lower bound is obtained from\n", + "the Reuss model, where the *stress* is assumed to be the same in the two constituents,\n", + "\n", + "$$\n", + "\\mathsf{E}_\\mathrm{Reuss} = \\left(v_\\mathrm{m} \\mathsf{E}_\\mathrm{m}^{-1} +\n", + "(1 - v_\\mathrm{m}) \\mathsf{E}_\\mathrm{i}^{-1} \\right)^{-1}.\n", + "$$\n", + "\n", + "However, neither of these assumptions are, in general, very close to the \"truth\" which is\n", + "why it is of interest to computationally find the homogenized properties for a given RVE.\n", + "\n", + "The canonical version of the RVE problem can be formulated as follows:\n", + "For given homogenized field $\\bar{\\boldsymbol{u}}$, $\\bar{\\boldsymbol{\\varepsilon}} =\n", + "\\boldsymbol{\\varepsilon}[\\bar{\\boldsymbol{u}}]$, find $\\boldsymbol{u} \\in\n", + "\\mathbb{U}_\\Box$, $\\boldsymbol{t} \\in \\mathbb{T}_\\Box$ such that\n", + "\n", + "$$\n", + "\\frac{1}{|\\Omega_\\Box|} \\int_{\\Omega_\\Box}\\boldsymbol{\\varepsilon}[\\delta\\boldsymbol{u}]\n", + ": \\mathsf{E} : \\boldsymbol{\\varepsilon}[\\boldsymbol{u}]\\ \\mathrm{d}\\Omega\n", + "- \\frac{1}{|\\Omega_\\Box|} \\int_{\\Gamma_\\Box}\\delta \\boldsymbol{u} \\cdot\n", + "\\boldsymbol{t}\\ \\mathrm{d}\\Gamma = 0 \\quad\n", + "\\forall \\delta \\boldsymbol{u} \\in \\mathbb{U}_\\Box,\\quad (1\\mathrm{a})\\\\\n", + "- \\frac{1}{|\\Omega_\\Box|} \\int_{\\Gamma_\\Box}\\delta \\boldsymbol{t} \\cdot\n", + "\\boldsymbol{u}\\ \\mathrm{d}\\Gamma = - \\bar{\\boldsymbol{\\varepsilon}} :\n", + "\\left[ \\frac{1}{|\\Omega_\\Box|} \\int_{\\Gamma_\\Box}\\delta \\boldsymbol{t} \\otimes\n", + "[\\boldsymbol{x} - \\bar{\\boldsymbol{x}}]\\ \\mathrm{d}\\Gamma \\right]\n", + "\\quad \\forall \\delta \\boldsymbol{t} \\in \\mathbb{T}_\\Box, \\quad (1\\mathrm{b})\n", + "$$\n", + "\n", + "where $\\boldsymbol{u} = \\bar{\\boldsymbol{\\varepsilon}} \\cdot [\\boldsymbol{x} -\n", + "\\bar{\\boldsymbol{x}}] + \\boldsymbol{u}^\\mu$, where $\\Omega_\\Box$ and $|\\Omega_\\Box|$\n", + "are the domain and volume of the RVE, where $\\Gamma_\\Box$ is the boundary, and where\n", + "$\\mathbb{U}_\\Box$, $\\mathbb{T}_\\Box$ are set of \"sufficiently regular\" functions\n", + "defined on the RVE.\n", + "\n", + "This system is not solvable without introducing extra restrictions on $\\mathbb{U}_\\Box$,\n", + "$\\mathbb{T}_\\Box$. In this example we will consider the common cases of Dirichlet\n", + "boundary conditions and (strong) periodic boundary conditions.\n", + "\n", + "**Dirichlet boundary conditions**\n", + "\n", + "We can introduce the more restrictive sets of $\\mathbb{U}_\\Box$:\n", + "\n", + "$$\n", + "\\begin{align*}\n", + "\\mathbb{U}_\\Box^\\mathrm{D} &:= \\left\\{\\boldsymbol{u} \\in \\mathbb{U}_\\Box|\\ \\boldsymbol{u}\n", + "= \\bar{\\boldsymbol{\\varepsilon}} \\cdot [\\boldsymbol{x} - \\bar{\\boldsymbol{x}}]\n", + "\\ \\mathrm{on}\\ \\Gamma_\\Box\\right\\},\\\\\n", + "\\mathbb{U}_\\Box^{\\mathrm{D},0} &:= \\left\\{\\boldsymbol{u} \\in \\mathbb{U}_\\Box|\\ \\boldsymbol{u}\n", + "= \\boldsymbol{0}\\ \\mathrm{on}\\ \\Gamma_\\Box\\right\\},\n", + "\\end{align*}\n", + "$$\n", + "\n", + "and use these as trial and test sets to obtain a solvable RVE problem pertaining to\n", + "Dirichlet boundary conditions. Eq. $(1\\mathrm{b})$ is trivially fulfilled, the second\n", + "term of Eq. $(1\\mathrm{a})$ vanishes, and we are left with the following problem:\n", + "Find $\\boldsymbol{u} \\in \\mathbb{U}_\\Box^\\mathrm{D}$ that solve\n", + "\n", + "$$\n", + "\\frac{1}{|\\Omega_\\Box|} \\int_{\\Omega_\\Box}\\boldsymbol{\\varepsilon}[\\delta\\boldsymbol{u}]\n", + ": \\mathsf{E} : \\boldsymbol{\\varepsilon}[\\boldsymbol{u}]\\ \\mathrm{d}\\Omega = 0\n", + "\\quad \\forall \\delta \\boldsymbol{u} \\in \\mathbb{U}_\\Box^{\\mathrm{D},0}.\n", + "$$\n", + "\n", + "Note that, since $\\boldsymbol{u} = \\bar{\\boldsymbol{\\varepsilon}} \\cdot [\\boldsymbol{x} -\n", + "\\bar{\\boldsymbol{x}}] + \\boldsymbol{u}^\\mu$, this problem is equivalent to solving for\n", + "$\\boldsymbol{u}^\\mu \\in \\mathbb{U}_\\Box^{\\mathrm{D},0}$, which is what we will do in\n", + "the implementation.\n", + "\n", + "**Periodic boundary conditions**\n", + "\n", + "The RVE problem pertaining to periodic boundary conditions is obtained by restricting\n", + "$\\boldsymbol{u}^\\mu$ to be periodic, and $\\boldsymbol{t}$ anti-periodic across the\n", + "RVE. Similarly as for Dirichlet boundary conditions, Eq. $(1\\mathrm{b})$ is directly\n", + "fulfilled, and the second term in Eq. $(1\\mathrm{a})$ vanishes, with these restrictions,\n", + "and we are left with the following problem:\n", + "Find $\\boldsymbol{u}^\\mu \\in \\mathbb{U}_\\Box^{\\mathrm{P},0}$ such that\n", + "\n", + "$$\n", + "\\frac{1}{|\\Omega_\\Box|} \\int_{\\Omega_\\Box}\\boldsymbol{\\varepsilon}[\\delta\\boldsymbol{u}]\n", + ": \\mathsf{E} : (\\bar{\\boldsymbol{\\varepsilon}} + \\boldsymbol{\\varepsilon}\n", + "[\\boldsymbol{u}^\\mu])\\ \\mathrm{d}\\Omega = 0\n", + "\\quad \\forall \\delta \\boldsymbol{u} \\in \\mathbb{U}_\\Box^{\\mathrm{P},0},\n", + "$$\n", + "\n", + "where\n", + "\n", + "$$\n", + "\\mathbb{U}_\\Box^{\\mathrm{P},0} := \\left\\{\\boldsymbol{u} \\in \\mathbb{U}_\\Box|\n", + "\\ [\\![ \\boldsymbol{u} ]\\!]_\\Box = \\boldsymbol{0}\n", + "\\ \\mathrm{on}\\ \\Gamma_\\Box^+\\right\\}\n", + "$$\n", + "\n", + "where $[\\![ \\bullet ]\\!]_\\Box = \\bullet(\\boldsymbol{x}^+) -\n", + "\\bullet(\\boldsymbol{x}^-)$ defines the \"jump\" over the RVE, i.e. the difference between\n", + "the value on the image part $\\Gamma_\\Box^+$ (coordinate $\\boldsymbol{x}^+$) and the\n", + "mirror part $\\Gamma_\\Box^-$ (coordinate $\\boldsymbol{x}^-$) of the boundary.\n", + "To make sure this restriction holds in a strong sense we need a periodic mesh.\n", + "\n", + "Note that it would be possible to solve for the total $\\boldsymbol{u}$ directly by\n", + "instead enforcing the jump to be equal to the jump in the macroscopic part,\n", + "$\\boldsymbol{u}^\\mathrm{M}$, i.e.\n", + "\n", + "$$\n", + "[\\![ \\boldsymbol{u} ]\\!]_\\Box =\n", + "[\\![ \\boldsymbol{u}^\\mathrm{M} ]\\!]_\\Box =\n", + "[\\![ \\bar{\\boldsymbol{\\varepsilon}} \\cdot [\\boldsymbol{x} - \\bar{\\boldsymbol{x}}]\n", + "]\\!]_\\Box =\n", + "\\bar{\\boldsymbol{\\varepsilon}} \\cdot [\\boldsymbol{x}^+ - \\boldsymbol{x}^-].\n", + "$$\n", + "\n", + "**Homogenization of effective properties**\n", + "\n", + "In general it is necessary to compute the homogenized stress and the stiffness on the fly,\n", + "but since we in this example consider linear elasticity it is possible to compute the\n", + "effective properties once and for all for a given RVE configuration. We do this by\n", + "computing sensitivity fields for every independent strain component (6 in 3D, 3 in 2D).\n", + "Thus, for a 2D problem, as in the implementation below, we compute sensitivities\n", + "$\\hat{\\boldsymbol{u}}_{11}$, $\\hat{\\boldsymbol{u}}_{22}$, and\n", + "$\\hat{\\boldsymbol{u}}_{12} = \\hat{\\boldsymbol{u}}_{21}$ by using\n", + "\n", + "$$\n", + "\\bar{\\boldsymbol{\\varepsilon}} = \\begin{pmatrix}1 & 0\\\\ 0 & 0\\end{pmatrix}, \\quad\n", + "\\bar{\\boldsymbol{\\varepsilon}} = \\begin{pmatrix}0 & 0\\\\ 0 & 1\\end{pmatrix}, \\quad\n", + "\\bar{\\boldsymbol{\\varepsilon}} = \\begin{pmatrix}0 & 0.5\\\\ 0.5 & 0\\end{pmatrix}\n", + "$$\n", + "\n", + "as the input to the RVE problem. When the sensitivies are solved we can compute the\n", + "entries of the homogenized stiffness as follows\n", + "\n", + "$$\n", + "\\mathsf{E}_{ijkl} = \\frac{\\partial\\ \\bar{\\sigma}_{ij}}{\\partial\\ \\bar{\\varepsilon}_{kl}}\n", + "= \\bar{\\sigma}_{ij}(\\hat{\\boldsymbol{u}}_{kl}),\n", + "$$\n", + "\n", + "where the homogenized stress, $\\bar{\\boldsymbol{\\sigma}}(\\boldsymbol{u})$, is computed\n", + "as the volume average of the stress in the RVE, i.e.\n", + "\n", + "$$\n", + "\\bar{\\boldsymbol{\\sigma}}(\\boldsymbol{u}) :=\n", + "\\frac{1}{|\\Omega_\\Box|} \\int_{\\Omega_\\Box} \\boldsymbol{\\sigma}\\ \\mathrm{d}\\Omega =\n", + "\\frac{1}{|\\Omega_\\Box|} \\int_{\\Omega_\\Box}\n", + "\\mathsf{E} : \\boldsymbol{\\varepsilon}[\\boldsymbol{u}]\\ \\mathrm{d}\\Omega.\n", + "$$" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Commented program\n", + "\n", + "Now we will see how this can be implemented in Ferrite. What follows is a program\n", + "with comments in between which describe the different steps." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using Ferrite, SparseArrays, LinearAlgebra" + ], + "metadata": {}, + "execution_count": 1 + }, + { + "cell_type": "markdown", + "source": [ + "We first load the mesh file [`periodic-rve.msh`](periodic-rve.msh)\n", + "([`periodic-rve-coarse.msh`](periodic-rve-coarse.msh) for a coarser mesh). The mesh is\n", + "generated with [Gmsh](https://gmsh.info/), and we read it in as a Ferrite `Grid` using\n", + "the [FerriteGmsh.jl](https://github.com/Ferrite-FEM/FerriteGmsh.jl) package:" + ], + "metadata": {} + }, + { + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Info : Reading 'periodic-rve-coarse.msh'...\n", + "Info : 38 entities\n", + "Info : 112 nodes\n", + "Info : 222 elements\n", + "Info : Done reading 'periodic-rve-coarse.msh'\n" + ] + }, + { + "output_type": "execute_result", + "data": { + "text/plain": "Grid{2, Triangle, Float64} with 186 Triangle cells and 112 nodes" + }, + "metadata": {}, + "execution_count": 2 + } + ], + "cell_type": "code", + "source": [ + "using FerriteGmsh\n", + "\n", + "# grid = togrid(\"periodic-rve.msh\")\n", + "grid = togrid(\"periodic-rve-coarse.msh\")" + ], + "metadata": {}, + "execution_count": 2 + }, + { + "cell_type": "markdown", + "source": [ + "Next we construct the interpolation and quadrature rule, and combining them into\n", + "cellvalues as usual:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "dim = 2\n", + "ip = Lagrange{RefTriangle, 1}()^dim\n", + "qr = QuadratureRule{RefTriangle}(2)\n", + "cellvalues = CellValues(qr, ip);" + ], + "metadata": {}, + "execution_count": 3 + }, + { + "cell_type": "markdown", + "source": [ + "We define a dof handler with a displacement field `:u`:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "dh = DofHandler(grid)\n", + "add!(dh, :u, ip)\n", + "close!(dh);" + ], + "metadata": {}, + "execution_count": 4 + }, + { + "cell_type": "markdown", + "source": [ + "Now we need to define boundary conditions. As discussed earlier we will solve the problem\n", + "using (i) homogeneous Dirichlet boundary conditions, and (ii) periodic Dirichlet boundary\n", + "conditions. We construct two different constraint handlers, one for each case. The\n", + "`Dirichlet` boundary condition we have seen in many other examples. Here we simply\n", + "define the condition that the field, `:u`, should have both components prescribed to `0`\n", + "on the full boundary:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "ch_dirichlet = ConstraintHandler(dh)\n", + "dirichlet = Dirichlet(\n", + " :u,\n", + " union(getfacetset.(Ref(grid), [\"left\", \"right\", \"top\", \"bottom\"])...),\n", + " (x, t) -> [0, 0],\n", + " [1, 2]\n", + ")\n", + "add!(ch_dirichlet, dirichlet)\n", + "close!(ch_dirichlet)\n", + "update!(ch_dirichlet, 0.0)" + ], + "metadata": {}, + "execution_count": 5 + }, + { + "cell_type": "markdown", + "source": [ + "For periodic boundary conditions we use the `PeriodicDirichlet` constraint type,\n", + "which is very similar to the `Dirichlet` type, but instead of a passing a facetset we pass\n", + "a vector with \"facet pairs\", i.e. the mapping between mirror and image parts of the\n", + "boundary. In this example the `\"left\"` and `\"bottom\"` boundaries are mirrors, and the\n", + "`\"right\"` and `\"top\"` boundaries are the mirrors." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "ch_periodic = ConstraintHandler(dh);\n", + "periodic = PeriodicDirichlet(\n", + " :u,\n", + " [\"left\" => \"right\", \"bottom\" => \"top\"],\n", + " [1, 2]\n", + ")\n", + "add!(ch_periodic, periodic)\n", + "close!(ch_periodic)\n", + "update!(ch_periodic, 0.0)" + ], + "metadata": {}, + "execution_count": 6 + }, + { + "cell_type": "markdown", + "source": [ + "This will now constrain any degrees of freedom located on the mirror boundaries to\n", + "the matching degree of freedom on the image boundaries. Internally this will create\n", + "a number of `AffineConstraint`s of the form `u_i = 1 * u_j + 0`:\n", + "```julia\n", + "a = AffineConstraint(u_m, [u_i => 1], 0)\n", + "```\n", + "where `u_m` is the degree of freedom on the mirror and `u_i` the matching one on the\n", + "image part. `PeriodicDirichlet` is thus simply just a more convenient way of\n", + "constructing such affine constraints since it computes the degree of freedom mapping\n", + "automatically.\n", + "\n", + "To simplify things we group the constraint handlers into a named tuple" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "ch = (dirichlet = ch_dirichlet, periodic = ch_periodic);" + ], + "metadata": {}, + "execution_count": 7 + }, + { + "cell_type": "markdown", + "source": [ + "We can now construct the sparse matrix. Note that, since we are using affine constraints,\n", + "which need to modify the matrix sparsity pattern in order to account for the constraint\n", + "equations, we construct the matrix for the periodic case by passing both the dof handler\n", + "and the constraint handler." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "K = (\n", + " dirichlet = allocate_matrix(dh),\n", + " periodic = allocate_matrix(dh, ch.periodic),\n", + ");" + ], + "metadata": {}, + "execution_count": 8 + }, + { + "cell_type": "markdown", + "source": [ + "We define the fourth order elasticity tensor for the matrix material, and define the\n", + "inclusions to have 10 times higher stiffness" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "λ, μ = 1.0e10, 7.0e9 # Lamé parameters\n", + "δ(i, j) = i == j ? 1.0 : 0.0\n", + "Em = SymmetricTensor{4, 2}(\n", + " (i, j, k, l) -> λ * δ(i, j) * δ(k, l) + μ * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k))\n", + ")\n", + "Ei = 10 * Em;" + ], + "metadata": {}, + "execution_count": 9 + }, + { + "cell_type": "markdown", + "source": [ + "As mentioned above, in order to compute the apparent/homogenized stiffness we will solve\n", + "the problem repeatedly with different macroscale strain tensors to compute the sensitvity\n", + "of the homogenized stress, $\\bar{\\boldsymbol{\\sigma}}$, w.r.t. the macroscopic strain,\n", + "$\\bar{\\boldsymbol{\\varepsilon}}$. The corresponding unit strains are defined below,\n", + "and will result in three different right-hand-sides:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "εᴹ = [\n", + " SymmetricTensor{2, 2}([1.0 0.0; 0.0 0.0]), # ε_11 loading\n", + " SymmetricTensor{2, 2}([0.0 0.0; 0.0 1.0]), # ε_22 loading\n", + " SymmetricTensor{2, 2}([0.0 0.5; 0.5 0.0]), # ε_12/ε_21 loading\n", + "];" + ], + "metadata": {}, + "execution_count": 10 + }, + { + "cell_type": "markdown", + "source": [ + "The assembly function is nothing strange, and in particular there is no impact from the\n", + "choice of boundary conditions, so the same function can be used for both cases. Since\n", + "we want to solve the system 3 times, once for each macroscopic strain component, we\n", + "assemble 3 right-hand-sides." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function doassemble!(cellvalues::CellValues, K::SparseMatrixCSC, dh::DofHandler, εᴹ)\n", + "\n", + " n_basefuncs = getnbasefunctions(cellvalues)\n", + " ndpc = ndofs_per_cell(dh)\n", + " Ke = zeros(ndpc, ndpc)\n", + " fe = zeros(ndpc, length(εᴹ))\n", + " f = zeros(ndofs(dh), length(εᴹ))\n", + " assembler = start_assemble(K)\n", + "\n", + " for cell in CellIterator(dh)\n", + "\n", + " E = cellid(cell) in getcellset(dh.grid, \"inclusions\") ? Ei : Em\n", + " reinit!(cellvalues, cell)\n", + " fill!(Ke, 0)\n", + " fill!(fe, 0)\n", + "\n", + " for q_point in 1:getnquadpoints(cellvalues)\n", + " dΩ = getdetJdV(cellvalues, q_point)\n", + " for i in 1:n_basefuncs\n", + " δεi = shape_symmetric_gradient(cellvalues, q_point, i)\n", + " for j in 1:n_basefuncs\n", + " δεj = shape_symmetric_gradient(cellvalues, q_point, j)\n", + " Ke[i, j] += (δεi ⊡ E ⊡ δεj) * dΩ\n", + " end\n", + " for (rhs, ε) in enumerate(εᴹ)\n", + " σᴹ = E ⊡ ε\n", + " fe[i, rhs] += (- δεi ⊡ σᴹ) * dΩ\n", + " end\n", + " end\n", + " end\n", + "\n", + " cdofs = celldofs(cell)\n", + " assemble!(assembler, cdofs, Ke)\n", + " f[cdofs, :] .+= fe\n", + " end\n", + " return f\n", + "end;" + ], + "metadata": {}, + "execution_count": 11 + }, + { + "cell_type": "markdown", + "source": [ + "We can now assemble the system. The assembly function modifies the matrix in-place, but\n", + "return the right hand side(s) which we collect in another named tuple." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "rhs = (\n", + " dirichlet = doassemble!(cellvalues, K.dirichlet, dh, εᴹ),\n", + " periodic = doassemble!(cellvalues, K.periodic, dh, εᴹ),\n", + ");" + ], + "metadata": {}, + "execution_count": 12 + }, + { + "cell_type": "markdown", + "source": [ + "The next step is to solve the systems. Since application of boundary conditions, using\n", + "the `apply!` function, modifies both the matrix and the right hand sides we can\n", + "not use it directly in this case since we want to reuse the matrix again for the next\n", + "right hand sides. We could of course re-assemble the matrix for every right hand side,\n", + "but that would not be very efficient. Instead we will use the `get_rhs_data`\n", + "function, together with `apply_rhs!` in a later step. This will extract the\n", + "necessary data from the matrix such that we can apply it for all the different right\n", + "hand sides. Note that we call `apply!` with just the matrix and no right hand side." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "rhsdata = (\n", + " dirichlet = get_rhs_data(ch.dirichlet, K.dirichlet),\n", + " periodic = get_rhs_data(ch.periodic, K.periodic),\n", + ")\n", + "\n", + "apply!(K.dirichlet, ch.dirichlet)\n", + "apply!(K.periodic, ch.periodic)" + ], + "metadata": {}, + "execution_count": 13 + }, + { + "cell_type": "markdown", + "source": [ + "We can now solve the problem(s). Note that we only use `apply_rhs!` in the loops below.\n", + "The boundary conditions are already applied to the matrix above, so we only need to\n", + "modify the right hand side." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "u = (\n", + " dirichlet = Vector{Float64}[],\n", + " periodic = Vector{Float64}[],\n", + ")\n", + "\n", + "for i in 1:size(rhs.dirichlet, 2)\n", + " rhs_i = @view rhs.dirichlet[:, i] # Extract this RHS\n", + " apply_rhs!(rhsdata.dirichlet, rhs_i, ch.dirichlet) # Apply BC\n", + " u_i = cholesky(Symmetric(K.dirichlet)) \\ rhs_i # Solve\n", + " apply!(u_i, ch.dirichlet) # Apply BC on the solution\n", + " push!(u.dirichlet, u_i) # Save the solution vector\n", + "end\n", + "\n", + "for i in 1:size(rhs.periodic, 2)\n", + " rhs_i = @view rhs.periodic[:, i] # Extract this RHS\n", + " apply_rhs!(rhsdata.periodic, rhs_i, ch.periodic) # Apply BC\n", + " u_i = cholesky(Symmetric(K.periodic)) \\ rhs_i # Solve\n", + " apply!(u_i, ch.periodic) # Apply BC on the solution\n", + " push!(u.periodic, u_i) # Save the solution vector\n", + "end" + ], + "metadata": {}, + "execution_count": 14 + }, + { + "cell_type": "markdown", + "source": [ + "When the solution(s) are known we can compute the averaged stress,\n", + "$\\bar{\\boldsymbol{\\sigma}}$ in the RVE. We define a function that does this, and also\n", + "returns the von Mise stress in every quadrature point for visualization." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function compute_stress(cellvalues::CellValues, dh::DofHandler, u, εᴹ)\n", + " σvM_qpdata = zeros(getnquadpoints(cellvalues), getncells(dh.grid))\n", + " σ̄Ω = zero(SymmetricTensor{2, 2})\n", + " Ω = 0.0 # Total volume\n", + " for cell in CellIterator(dh)\n", + " E = cellid(cell) in getcellset(dh.grid, \"inclusions\") ? Ei : Em\n", + " reinit!(cellvalues, cell)\n", + " for q_point in 1:getnquadpoints(cellvalues)\n", + " dΩ = getdetJdV(cellvalues, q_point)\n", + " εμ = function_symmetric_gradient(cellvalues, q_point, u[celldofs(cell)])\n", + " σ = E ⊡ (εᴹ + εμ)\n", + " σvM_qpdata[q_point, cellid(cell)] = sqrt(3 / 2 * dev(σ) ⊡ dev(σ))\n", + " Ω += dΩ # Update total volume\n", + " σ̄Ω += σ * dΩ # Update integrated stress\n", + " end\n", + " end\n", + " σ̄ = σ̄Ω / Ω\n", + " return σvM_qpdata, σ̄\n", + "end;" + ], + "metadata": {}, + "execution_count": 15 + }, + { + "cell_type": "markdown", + "source": [ + "We now compute the homogenized stress and von Mise stress for all cases" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "σ̄ = (\n", + " dirichlet = SymmetricTensor{2, 2}[],\n", + " periodic = SymmetricTensor{2, 2}[],\n", + ")\n", + "σ = (\n", + " dirichlet = Vector{Float64}[],\n", + " periodic = Vector{Float64}[],\n", + ")\n", + "\n", + "projector = L2Projector(ip, grid)\n", + "\n", + "for i in 1:3\n", + " σ_qp, σ̄_i = compute_stress(cellvalues, dh, u.dirichlet[i], εᴹ[i])\n", + " proj = project(projector, σ_qp, qr)\n", + " push!(σ.dirichlet, proj)\n", + " push!(σ̄.dirichlet, σ̄_i)\n", + "end\n", + "\n", + "for i in 1:3\n", + " σ_qp, σ̄_i = compute_stress(cellvalues, dh, u.periodic[i], εᴹ[i])\n", + " proj = project(projector, σ_qp, qr)\n", + " push!(σ.periodic, proj)\n", + " push!(σ̄.periodic, σ̄_i)\n", + "end" + ], + "metadata": {}, + "execution_count": 16 + }, + { + "cell_type": "markdown", + "source": [ + "The remaining thing is to compute the homogenized stiffness. As mentioned in the\n", + "introduction we can find all the components from the average stress of the sensitivity\n", + "fields that we have solved for\n", + "\n", + "$$\n", + "\\mathsf{E}_{ijkl} = \\bar{\\sigma}_{ij}(\\hat{\\boldsymbol{u}}_{kl}).\n", + "$$\n", + "\n", + "So we have now already computed all the components, and just need to gather the data in\n", + "a fourth order tensor:" + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "2×2×2×2 SymmetricTensor{4, 2, Float64, 9}:\n[:, :, 1, 1] =\n 4.30443e10 -809961.0\n -809961.0 1.43401e10\n\n[:, :, 2, 1] =\n -809961.0 1.16827e10\n 1.16827e10 -2.18543e6\n\n[:, :, 1, 2] =\n -809961.0 1.16827e10\n 1.16827e10 -2.18543e6\n\n[:, :, 2, 2] =\n 1.43401e10 -2.18543e6\n -2.18543e6 4.30725e10" + }, + "metadata": {}, + "execution_count": 17 + } + ], + "cell_type": "code", + "source": [ + "E_dirichlet = SymmetricTensor{4, 2}() do i, j, k, l\n", + " if k == l == 1\n", + " σ̄.dirichlet[1][i, j] # ∂σ∂ε_**11\n", + " elseif k == l == 2\n", + " σ̄.dirichlet[2][i, j] # ∂σ∂ε_**22\n", + " else\n", + " σ̄.dirichlet[3][i, j] # ∂σ∂ε_**12 and ∂σ∂ε_**21\n", + " end\n", + "end\n", + "\n", + "E_periodic = SymmetricTensor{4, 2}() do i, j, k, l\n", + " if k == l == 1\n", + " σ̄.periodic[1][i, j]\n", + " elseif k == l == 2\n", + " σ̄.periodic[2][i, j]\n", + " else\n", + " σ̄.periodic[3][i, j]\n", + " end\n", + "end" + ], + "metadata": {}, + "execution_count": 17 + }, + { + "cell_type": "markdown", + "source": [ + "We can check that the result are what we expect, namely that the stiffness with Dirichlet\n", + "boundary conditions is higher than when using periodic boundary conditions, and that\n", + "the Reuss assumption is an lower bound, and the Voigt assumption a upper bound. We first\n", + "compute the volume fraction of the matrix, and then the Voigt and Reuss bounds:" + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "0.64796265456868" + }, + "metadata": {}, + "execution_count": 18 + } + ], + "cell_type": "code", + "source": [ + "function matrix_volume_fraction(grid, cellvalues)\n", + " V = 0.0 # Total volume\n", + " Vm = 0.0 # Volume of the matrix\n", + " for c in CellIterator(grid)\n", + " reinit!(cellvalues, c)\n", + " is_matrix = !(cellid(c) in getcellset(grid, \"inclusions\"))\n", + " for qp in 1:getnquadpoints(cellvalues)\n", + " dΩ = getdetJdV(cellvalues, qp)\n", + " V += dΩ\n", + " if is_matrix\n", + " Vm += dΩ\n", + " end\n", + " end\n", + " end\n", + " return Vm / V\n", + "end\n", + "\n", + "vm = matrix_volume_fraction(grid, cellvalues)" + ], + "metadata": {}, + "execution_count": 18 + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "E_voigt = vm * Em + (1 - vm) * Ei\n", + "E_reuss = inv(vm * inv(Em) + (1 - vm) * inv(Ei));" + ], + "metadata": {}, + "execution_count": 19 + }, + { + "cell_type": "markdown", + "source": [ + "We can now compare the different computed stiffness tensors. We expect\n", + "$E_\\mathrm{Reuss} \\leq E_\\mathrm{PeriodicBC} \\leq E_\\mathrm{DirichletBC} \\leq\n", + "E_\\mathrm{Voigt}$. A simple thing to compare are the eigenvalues of the tensors. Here\n", + "we look at the first eigenvalue:" + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "(2.05e10, 2.34e10, 2.82e10, 5.84e10)" + }, + "metadata": {}, + "execution_count": 20 + } + ], + "cell_type": "code", + "source": [ + "ev = (first ∘ eigvals).((E_reuss, E_periodic, E_dirichlet, E_voigt))\n", + "round.(ev; digits = -8)" + ], + "metadata": {}, + "execution_count": 20 + }, + { + "cell_type": "markdown", + "source": [ + "Finally, we export the solution and the stress field to a VTK file. For the export we\n", + "also compute the macroscopic part of the displacement." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "uM = zeros(ndofs(dh))\n", + "\n", + "VTKGridFile(\"homogenization\", dh) do vtk\n", + " for i in 1:3\n", + " # Compute macroscopic solution\n", + " apply_analytical!(uM, dh, :u, x -> εᴹ[i] ⋅ x)\n", + " # Dirichlet\n", + " write_solution(vtk, dh, uM + u.dirichlet[i], \"_dirichlet_$i\")\n", + " write_projection(vtk, projector, σ.dirichlet[i], \"σvM_dirichlet_$i\")\n", + " # Periodic\n", + " write_solution(vtk, dh, uM + u.periodic[i], \"_periodic_$i\")\n", + " write_projection(vtk, projector, σ.periodic[i], \"σvM_periodic_$i\")\n", + " end\n", + "end;" + ], + "metadata": {}, + "execution_count": 21 + }, + { + "cell_type": "markdown", + "source": [ + "---\n", + "\n", + "*This notebook was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).*" + ], + "metadata": {} + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.1" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.1", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR798/tutorials/computational_homogenization.jl b/previews/PR798/tutorials/computational_homogenization.jl new file mode 100644 index 0000000000..732dc1ad15 --- /dev/null +++ b/previews/PR798/tutorials/computational_homogenization.jl @@ -0,0 +1,235 @@ +using Ferrite, SparseArrays, LinearAlgebra + +using FerriteGmsh + +# grid = togrid("periodic-rve-coarse.msh") +grid = togrid("periodic-rve.msh") + +dim = 2 +ip = Lagrange{RefTriangle, 1}()^dim +qr = QuadratureRule{RefTriangle}(2) +cellvalues = CellValues(qr, ip); + +dh = DofHandler(grid) +add!(dh, :u, ip) +close!(dh); + +ch_dirichlet = ConstraintHandler(dh) +dirichlet = Dirichlet( + :u, + union(getfacetset.(Ref(grid), ["left", "right", "top", "bottom"])...), + (x, t) -> [0, 0], + [1, 2] +) +add!(ch_dirichlet, dirichlet) +close!(ch_dirichlet) +update!(ch_dirichlet, 0.0) + +ch_periodic = ConstraintHandler(dh); +periodic = PeriodicDirichlet( + :u, + ["left" => "right", "bottom" => "top"], + [1, 2] +) +add!(ch_periodic, periodic) +close!(ch_periodic) +update!(ch_periodic, 0.0) + +ch = (dirichlet = ch_dirichlet, periodic = ch_periodic); + +K = ( + dirichlet = allocate_matrix(dh), + periodic = allocate_matrix(dh, ch.periodic), +); + +λ, μ = 1.0e10, 7.0e9 # Lamé parameters +δ(i, j) = i == j ? 1.0 : 0.0 +Em = SymmetricTensor{4, 2}( + (i, j, k, l) -> λ * δ(i, j) * δ(k, l) + μ * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k)) +) +Ei = 10 * Em; + +εᴹ = [ + SymmetricTensor{2, 2}([1.0 0.0; 0.0 0.0]), # ε_11 loading + SymmetricTensor{2, 2}([0.0 0.0; 0.0 1.0]), # ε_22 loading + SymmetricTensor{2, 2}([0.0 0.5; 0.5 0.0]), # ε_12/ε_21 loading +]; + +function doassemble!(cellvalues::CellValues, K::SparseMatrixCSC, dh::DofHandler, εᴹ) + + n_basefuncs = getnbasefunctions(cellvalues) + ndpc = ndofs_per_cell(dh) + Ke = zeros(ndpc, ndpc) + fe = zeros(ndpc, length(εᴹ)) + f = zeros(ndofs(dh), length(εᴹ)) + assembler = start_assemble(K) + + for cell in CellIterator(dh) + + E = cellid(cell) in getcellset(dh.grid, "inclusions") ? Ei : Em + reinit!(cellvalues, cell) + fill!(Ke, 0) + fill!(fe, 0) + + for q_point in 1:getnquadpoints(cellvalues) + dΩ = getdetJdV(cellvalues, q_point) + for i in 1:n_basefuncs + δεi = shape_symmetric_gradient(cellvalues, q_point, i) + for j in 1:n_basefuncs + δεj = shape_symmetric_gradient(cellvalues, q_point, j) + Ke[i, j] += (δεi ⊡ E ⊡ δεj) * dΩ + end + for (rhs, ε) in enumerate(εᴹ) + σᴹ = E ⊡ ε + fe[i, rhs] += (- δεi ⊡ σᴹ) * dΩ + end + end + end + + cdofs = celldofs(cell) + assemble!(assembler, cdofs, Ke) + f[cdofs, :] .+= fe + end + return f +end; + +rhs = ( + dirichlet = doassemble!(cellvalues, K.dirichlet, dh, εᴹ), + periodic = doassemble!(cellvalues, K.periodic, dh, εᴹ), +); + +rhsdata = ( + dirichlet = get_rhs_data(ch.dirichlet, K.dirichlet), + periodic = get_rhs_data(ch.periodic, K.periodic), +) + +apply!(K.dirichlet, ch.dirichlet) +apply!(K.periodic, ch.periodic) + +u = ( + dirichlet = Vector{Float64}[], + periodic = Vector{Float64}[], +) + +for i in 1:size(rhs.dirichlet, 2) + rhs_i = @view rhs.dirichlet[:, i] # Extract this RHS + apply_rhs!(rhsdata.dirichlet, rhs_i, ch.dirichlet) # Apply BC + u_i = cholesky(Symmetric(K.dirichlet)) \ rhs_i # Solve + apply!(u_i, ch.dirichlet) # Apply BC on the solution + push!(u.dirichlet, u_i) # Save the solution vector +end + +for i in 1:size(rhs.periodic, 2) + rhs_i = @view rhs.periodic[:, i] # Extract this RHS + apply_rhs!(rhsdata.periodic, rhs_i, ch.periodic) # Apply BC + u_i = cholesky(Symmetric(K.periodic)) \ rhs_i # Solve + apply!(u_i, ch.periodic) # Apply BC on the solution + push!(u.periodic, u_i) # Save the solution vector +end + +function compute_stress(cellvalues::CellValues, dh::DofHandler, u, εᴹ) + σvM_qpdata = zeros(getnquadpoints(cellvalues), getncells(dh.grid)) + σ̄Ω = zero(SymmetricTensor{2, 2}) + Ω = 0.0 # Total volume + for cell in CellIterator(dh) + E = cellid(cell) in getcellset(dh.grid, "inclusions") ? Ei : Em + reinit!(cellvalues, cell) + for q_point in 1:getnquadpoints(cellvalues) + dΩ = getdetJdV(cellvalues, q_point) + εμ = function_symmetric_gradient(cellvalues, q_point, u[celldofs(cell)]) + σ = E ⊡ (εᴹ + εμ) + σvM_qpdata[q_point, cellid(cell)] = sqrt(3 / 2 * dev(σ) ⊡ dev(σ)) + Ω += dΩ # Update total volume + σ̄Ω += σ * dΩ # Update integrated stress + end + end + σ̄ = σ̄Ω / Ω + return σvM_qpdata, σ̄ +end; + +σ̄ = ( + dirichlet = SymmetricTensor{2, 2}[], + periodic = SymmetricTensor{2, 2}[], +) +σ = ( + dirichlet = Vector{Float64}[], + periodic = Vector{Float64}[], +) + +projector = L2Projector(ip, grid) + +for i in 1:3 + σ_qp, σ̄_i = compute_stress(cellvalues, dh, u.dirichlet[i], εᴹ[i]) + proj = project(projector, σ_qp, qr) + push!(σ.dirichlet, proj) + push!(σ̄.dirichlet, σ̄_i) +end + +for i in 1:3 + σ_qp, σ̄_i = compute_stress(cellvalues, dh, u.periodic[i], εᴹ[i]) + proj = project(projector, σ_qp, qr) + push!(σ.periodic, proj) + push!(σ̄.periodic, σ̄_i) +end + +E_dirichlet = SymmetricTensor{4, 2}() do i, j, k, l + if k == l == 1 + σ̄.dirichlet[1][i, j] # ∂σ∂ε_**11 + elseif k == l == 2 + σ̄.dirichlet[2][i, j] # ∂σ∂ε_**22 + else + σ̄.dirichlet[3][i, j] # ∂σ∂ε_**12 and ∂σ∂ε_**21 + end +end + +E_periodic = SymmetricTensor{4, 2}() do i, j, k, l + if k == l == 1 + σ̄.periodic[1][i, j] + elseif k == l == 2 + σ̄.periodic[2][i, j] + else + σ̄.periodic[3][i, j] + end +end + +function matrix_volume_fraction(grid, cellvalues) + V = 0.0 # Total volume + Vm = 0.0 # Volume of the matrix + for c in CellIterator(grid) + reinit!(cellvalues, c) + is_matrix = !(cellid(c) in getcellset(grid, "inclusions")) + for qp in 1:getnquadpoints(cellvalues) + dΩ = getdetJdV(cellvalues, qp) + V += dΩ + if is_matrix + Vm += dΩ + end + end + end + return Vm / V +end + +vm = matrix_volume_fraction(grid, cellvalues) + +E_voigt = vm * Em + (1 - vm) * Ei +E_reuss = inv(vm * inv(Em) + (1 - vm) * inv(Ei)); + +ev = (first ∘ eigvals).((E_reuss, E_periodic, E_dirichlet, E_voigt)) +round.(ev; digits = -8) + +uM = zeros(ndofs(dh)) + +VTKGridFile("homogenization", dh) do vtk + for i in 1:3 + # Compute macroscopic solution + apply_analytical!(uM, dh, :u, x -> εᴹ[i] ⋅ x) + # Dirichlet + write_solution(vtk, dh, uM + u.dirichlet[i], "_dirichlet_$i") + write_projection(vtk, projector, σ.dirichlet[i], "σvM_dirichlet_$i") + # Periodic + write_solution(vtk, dh, uM + u.periodic[i], "_periodic_$i") + write_projection(vtk, projector, σ.periodic[i], "σvM_periodic_$i") + end +end; + +# This file was generated using Literate.jl, https://github.com/fredrikekre/Literate.jl diff --git a/previews/PR798/tutorials/computational_homogenization/index.html b/previews/PR798/tutorials/computational_homogenization/index.html new file mode 100644 index 0000000000..5f482188b7 --- /dev/null +++ b/previews/PR798/tutorials/computational_homogenization/index.html @@ -0,0 +1,474 @@ + +Computational homogenization · Ferrite.jl

Computational homogenization

Figure 1: von Mises stress in an RVE with 5 stiff inclusions embedded in a softer matrix material that is loaded in shear. The problem is solved by using homogeneous Dirichlet boundary conditions (left) and (strong) periodic boundary conditions (right).

Tip

This example is also available as a Jupyter notebook: computational_homogenization.ipynb.

Introduction

In this example we will solve the Representative Volume Element (RVE) problem for computational homogenization of linear elasticity and compute the effective/homogenized stiffness of an RVE with 5 stiff circular inclusions embedded in a softer matrix material (see Figure 1).

It is possible to obtain upper and lower bounds on the stiffness analytically, see for example Rule of mixtures. An upper bound is obtained from the Voigt model, where the strain is assumed to be the same in the two constituents,

\[\mathsf{E}_\mathrm{Voigt} = v_\mathrm{m} \mathsf{E}_\mathrm{m} + +(1 - v_\mathrm{m}) \mathsf{E}_\mathrm{i}\]

where $v_\mathrm{m}$ is the volume fraction of the matrix material, and where $\mathsf{E}_\mathrm{m}$ and $\mathsf{E}_\mathrm{i}$ are the individual stiffness for the matrix material and the inclusions, respectively. The lower bound is obtained from the Reuss model, where the stress is assumed to be the same in the two constituents,

\[\mathsf{E}_\mathrm{Reuss} = \left(v_\mathrm{m} \mathsf{E}_\mathrm{m}^{-1} + +(1 - v_\mathrm{m}) \mathsf{E}_\mathrm{i}^{-1} \right)^{-1}.\]

However, neither of these assumptions are, in general, very close to the "truth" which is why it is of interest to computationally find the homogenized properties for a given RVE.

The canonical version of the RVE problem can be formulated as follows: For given homogenized field $\bar{\boldsymbol{u}}$, $\bar{\boldsymbol{\varepsilon}} = \boldsymbol{\varepsilon}[\bar{\boldsymbol{u}}]$, find $\boldsymbol{u} \in \mathbb{U}_\Box$, $\boldsymbol{t} \in \mathbb{T}_\Box$ such that

\[\frac{1}{|\Omega_\Box|} \int_{\Omega_\Box}\boldsymbol{\varepsilon}[\delta\boldsymbol{u}] +: \mathsf{E} : \boldsymbol{\varepsilon}[\boldsymbol{u}]\ \mathrm{d}\Omega +- \frac{1}{|\Omega_\Box|} \int_{\Gamma_\Box}\delta \boldsymbol{u} \cdot +\boldsymbol{t}\ \mathrm{d}\Gamma = 0 \quad +\forall \delta \boldsymbol{u} \in \mathbb{U}_\Box,\quad (1\mathrm{a})\\ +- \frac{1}{|\Omega_\Box|} \int_{\Gamma_\Box}\delta \boldsymbol{t} \cdot +\boldsymbol{u}\ \mathrm{d}\Gamma = - \bar{\boldsymbol{\varepsilon}} : +\left[ \frac{1}{|\Omega_\Box|} \int_{\Gamma_\Box}\delta \boldsymbol{t} \otimes +[\boldsymbol{x} - \bar{\boldsymbol{x}}]\ \mathrm{d}\Gamma \right] +\quad \forall \delta \boldsymbol{t} \in \mathbb{T}_\Box, \quad (1\mathrm{b})\]

where $\boldsymbol{u} = \bar{\boldsymbol{\varepsilon}} \cdot [\boldsymbol{x} - \bar{\boldsymbol{x}}] + \boldsymbol{u}^\mu$, where $\Omega_\Box$ and $|\Omega_\Box|$ are the domain and volume of the RVE, where $\Gamma_\Box$ is the boundary, and where $\mathbb{U}_\Box$, $\mathbb{T}_\Box$ are set of "sufficiently regular" functions defined on the RVE.

This system is not solvable without introducing extra restrictions on $\mathbb{U}_\Box$, $\mathbb{T}_\Box$. In this example we will consider the common cases of Dirichlet boundary conditions and (strong) periodic boundary conditions.

Dirichlet boundary conditions

We can introduce the more restrictive sets of $\mathbb{U}_\Box$:

\[\begin{align*} +\mathbb{U}_\Box^\mathrm{D} &:= \left\{\boldsymbol{u} \in \mathbb{U}_\Box|\ \boldsymbol{u} += \bar{\boldsymbol{\varepsilon}} \cdot [\boldsymbol{x} - \bar{\boldsymbol{x}}] +\ \mathrm{on}\ \Gamma_\Box\right\},\\ +\mathbb{U}_\Box^{\mathrm{D},0} &:= \left\{\boldsymbol{u} \in \mathbb{U}_\Box|\ \boldsymbol{u} += \boldsymbol{0}\ \mathrm{on}\ \Gamma_\Box\right\}, +\end{align*}\]

and use these as trial and test sets to obtain a solvable RVE problem pertaining to Dirichlet boundary conditions. Eq. $(1\mathrm{b})$ is trivially fulfilled, the second term of Eq. $(1\mathrm{a})$ vanishes, and we are left with the following problem: Find $\boldsymbol{u} \in \mathbb{U}_\Box^\mathrm{D}$ that solve

\[\frac{1}{|\Omega_\Box|} \int_{\Omega_\Box}\boldsymbol{\varepsilon}[\delta\boldsymbol{u}] +: \mathsf{E} : \boldsymbol{\varepsilon}[\boldsymbol{u}]\ \mathrm{d}\Omega = 0 +\quad \forall \delta \boldsymbol{u} \in \mathbb{U}_\Box^{\mathrm{D},0}.\]

Note that, since $\boldsymbol{u} = \bar{\boldsymbol{\varepsilon}} \cdot [\boldsymbol{x} - \bar{\boldsymbol{x}}] + \boldsymbol{u}^\mu$, this problem is equivalent to solving for $\boldsymbol{u}^\mu \in \mathbb{U}_\Box^{\mathrm{D},0}$, which is what we will do in the implementation.

Periodic boundary conditions

The RVE problem pertaining to periodic boundary conditions is obtained by restricting $\boldsymbol{u}^\mu$ to be periodic, and $\boldsymbol{t}$ anti-periodic across the RVE. Similarly as for Dirichlet boundary conditions, Eq. $(1\mathrm{b})$ is directly fulfilled, and the second term in Eq. $(1\mathrm{a})$ vanishes, with these restrictions, and we are left with the following problem: Find $\boldsymbol{u}^\mu \in \mathbb{U}_\Box^{\mathrm{P},0}$ such that

\[\frac{1}{|\Omega_\Box|} \int_{\Omega_\Box}\boldsymbol{\varepsilon}[\delta\boldsymbol{u}] +: \mathsf{E} : (\bar{\boldsymbol{\varepsilon}} + \boldsymbol{\varepsilon} +[\boldsymbol{u}^\mu])\ \mathrm{d}\Omega = 0 +\quad \forall \delta \boldsymbol{u} \in \mathbb{U}_\Box^{\mathrm{P},0},\]

where

\[\mathbb{U}_\Box^{\mathrm{P},0} := \left\{\boldsymbol{u} \in \mathbb{U}_\Box| +\ \llbracket \boldsymbol{u} \rrbracket_\Box = \boldsymbol{0} +\ \mathrm{on}\ \Gamma_\Box^+\right\}\]

where $\llbracket \bullet \rrbracket_\Box = \bullet(\boldsymbol{x}^+) - \bullet(\boldsymbol{x}^-)$ defines the "jump" over the RVE, i.e. the difference between the value on the image part $\Gamma_\Box^+$ (coordinate $\boldsymbol{x}^+$) and the mirror part $\Gamma_\Box^-$ (coordinate $\boldsymbol{x}^-$) of the boundary. To make sure this restriction holds in a strong sense we need a periodic mesh.

Note that it would be possible to solve for the total $\boldsymbol{u}$ directly by instead enforcing the jump to be equal to the jump in the macroscopic part, $\boldsymbol{u}^\mathrm{M}$, i.e.

\[\llbracket \boldsymbol{u} \rrbracket_\Box = +\llbracket \boldsymbol{u}^\mathrm{M} \rrbracket_\Box = +\llbracket \bar{\boldsymbol{\varepsilon}} \cdot [\boldsymbol{x} - \bar{\boldsymbol{x}}] +\rrbracket_\Box = +\bar{\boldsymbol{\varepsilon}} \cdot [\boldsymbol{x}^+ - \boldsymbol{x}^-].\]

Homogenization of effective properties

In general it is necessary to compute the homogenized stress and the stiffness on the fly, but since we in this example consider linear elasticity it is possible to compute the effective properties once and for all for a given RVE configuration. We do this by computing sensitivity fields for every independent strain component (6 in 3D, 3 in 2D). Thus, for a 2D problem, as in the implementation below, we compute sensitivities $\hat{\boldsymbol{u}}_{11}$, $\hat{\boldsymbol{u}}_{22}$, and $\hat{\boldsymbol{u}}_{12} = \hat{\boldsymbol{u}}_{21}$ by using

\[\bar{\boldsymbol{\varepsilon}} = \begin{pmatrix}1 & 0\\ 0 & 0\end{pmatrix}, \quad +\bar{\boldsymbol{\varepsilon}} = \begin{pmatrix}0 & 0\\ 0 & 1\end{pmatrix}, \quad +\bar{\boldsymbol{\varepsilon}} = \begin{pmatrix}0 & 0.5\\ 0.5 & 0\end{pmatrix}\]

as the input to the RVE problem. When the sensitivies are solved we can compute the entries of the homogenized stiffness as follows

\[\mathsf{E}_{ijkl} = \frac{\partial\ \bar{\sigma}_{ij}}{\partial\ \bar{\varepsilon}_{kl}} += \bar{\sigma}_{ij}(\hat{\boldsymbol{u}}_{kl}),\]

where the homogenized stress, $\bar{\boldsymbol{\sigma}}(\boldsymbol{u})$, is computed as the volume average of the stress in the RVE, i.e.

\[\bar{\boldsymbol{\sigma}}(\boldsymbol{u}) := +\frac{1}{|\Omega_\Box|} \int_{\Omega_\Box} \boldsymbol{\sigma}\ \mathrm{d}\Omega = +\frac{1}{|\Omega_\Box|} \int_{\Omega_\Box} +\mathsf{E} : \boldsymbol{\varepsilon}[\boldsymbol{u}]\ \mathrm{d}\Omega.\]

Commented program

Now we will see how this can be implemented in Ferrite. What follows is a program with comments in between which describe the different steps. You can also find the same program without comments at the end of the page, see Plain program.

using Ferrite, SparseArrays, LinearAlgebra

We first load the mesh file periodic-rve.msh (periodic-rve-coarse.msh for a coarser mesh). The mesh is generated with Gmsh, and we read it in as a Ferrite Grid using the FerriteGmsh.jl package:

using FerriteGmsh
+
+grid = togrid("periodic-rve.msh")
Grid{2, Triangle, Float64} with 11904 Triangle cells and 6097 nodes
Grid{2, Triangle, Float64} with 186 Triangle cells and 112 nodes

Next we construct the interpolation and quadrature rule, and combining them into cellvalues as usual:

dim = 2
+ip = Lagrange{RefTriangle, 1}()^dim
+qr = QuadratureRule{RefTriangle}(2)
+cellvalues = CellValues(qr, ip);

We define a dof handler with a displacement field :u:

dh = DofHandler(grid)
+add!(dh, :u, ip)
+close!(dh);

Now we need to define boundary conditions. As discussed earlier we will solve the problem using (i) homogeneous Dirichlet boundary conditions, and (ii) periodic Dirichlet boundary conditions. We construct two different constraint handlers, one for each case. The Dirichlet boundary condition we have seen in many other examples. Here we simply define the condition that the field, :u, should have both components prescribed to 0 on the full boundary:

ch_dirichlet = ConstraintHandler(dh)
+dirichlet = Dirichlet(
+    :u,
+    union(getfacetset.(Ref(grid), ["left", "right", "top", "bottom"])...),
+    (x, t) -> [0, 0],
+    [1, 2]
+)
+add!(ch_dirichlet, dirichlet)
+close!(ch_dirichlet)
+update!(ch_dirichlet, 0.0)

For periodic boundary conditions we use the PeriodicDirichlet constraint type, which is very similar to the Dirichlet type, but instead of a passing a facetset we pass a vector with "facet pairs", i.e. the mapping between mirror and image parts of the boundary. In this example the "left" and "bottom" boundaries are mirrors, and the "right" and "top" boundaries are the mirrors.

ch_periodic = ConstraintHandler(dh);
+periodic = PeriodicDirichlet(
+    :u,
+    ["left" => "right", "bottom" => "top"],
+    [1, 2]
+)
+add!(ch_periodic, periodic)
+close!(ch_periodic)
+update!(ch_periodic, 0.0)

This will now constrain any degrees of freedom located on the mirror boundaries to the matching degree of freedom on the image boundaries. Internally this will create a number of AffineConstraints of the form u_i = 1 * u_j + 0:

a = AffineConstraint(u_m, [u_i => 1], 0)

where u_m is the degree of freedom on the mirror and u_i the matching one on the image part. PeriodicDirichlet is thus simply just a more convenient way of constructing such affine constraints since it computes the degree of freedom mapping automatically.

To simplify things we group the constraint handlers into a named tuple

ch = (dirichlet = ch_dirichlet, periodic = ch_periodic);

We can now construct the sparse matrix. Note that, since we are using affine constraints, which need to modify the matrix sparsity pattern in order to account for the constraint equations, we construct the matrix for the periodic case by passing both the dof handler and the constraint handler.

K = (
+    dirichlet = allocate_matrix(dh),
+    periodic = allocate_matrix(dh, ch.periodic),
+);

We define the fourth order elasticity tensor for the matrix material, and define the inclusions to have 10 times higher stiffness

λ, μ = 1.0e10, 7.0e9 # Lamé parameters
+δ(i, j) = i == j ? 1.0 : 0.0
+Em = SymmetricTensor{4, 2}(
+    (i, j, k, l) -> λ * δ(i, j) * δ(k, l) + μ * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k))
+)
+Ei = 10 * Em;

As mentioned above, in order to compute the apparent/homogenized stiffness we will solve the problem repeatedly with different macroscale strain tensors to compute the sensitvity of the homogenized stress, $\bar{\boldsymbol{\sigma}}$, w.r.t. the macroscopic strain, $\bar{\boldsymbol{\varepsilon}}$. The corresponding unit strains are defined below, and will result in three different right-hand-sides:

εᴹ = [
+    SymmetricTensor{2, 2}([1.0 0.0; 0.0 0.0]), # ε_11 loading
+    SymmetricTensor{2, 2}([0.0 0.0; 0.0 1.0]), # ε_22 loading
+    SymmetricTensor{2, 2}([0.0 0.5; 0.5 0.0]), # ε_12/ε_21 loading
+];

The assembly function is nothing strange, and in particular there is no impact from the choice of boundary conditions, so the same function can be used for both cases. Since we want to solve the system 3 times, once for each macroscopic strain component, we assemble 3 right-hand-sides.

function doassemble!(cellvalues::CellValues, K::SparseMatrixCSC, dh::DofHandler, εᴹ)
+
+    n_basefuncs = getnbasefunctions(cellvalues)
+    ndpc = ndofs_per_cell(dh)
+    Ke = zeros(ndpc, ndpc)
+    fe = zeros(ndpc, length(εᴹ))
+    f = zeros(ndofs(dh), length(εᴹ))
+    assembler = start_assemble(K)
+
+    for cell in CellIterator(dh)
+
+        E = cellid(cell) in getcellset(dh.grid, "inclusions") ? Ei : Em
+        reinit!(cellvalues, cell)
+        fill!(Ke, 0)
+        fill!(fe, 0)
+
+        for q_point in 1:getnquadpoints(cellvalues)
+            dΩ = getdetJdV(cellvalues, q_point)
+            for i in 1:n_basefuncs
+                δεi = shape_symmetric_gradient(cellvalues, q_point, i)
+                for j in 1:n_basefuncs
+                    δεj = shape_symmetric_gradient(cellvalues, q_point, j)
+                    Ke[i, j] += (δεi ⊡ E ⊡ δεj) * dΩ
+                end
+                for (rhs, ε) in enumerate(εᴹ)
+                    σᴹ = E ⊡ ε
+                    fe[i, rhs] += (- δεi ⊡ σᴹ) * dΩ
+                end
+            end
+        end
+
+        cdofs = celldofs(cell)
+        assemble!(assembler, cdofs, Ke)
+        f[cdofs, :] .+= fe
+    end
+    return f
+end;

We can now assemble the system. The assembly function modifies the matrix in-place, but return the right hand side(s) which we collect in another named tuple.

rhs = (
+    dirichlet = doassemble!(cellvalues, K.dirichlet, dh, εᴹ),
+    periodic = doassemble!(cellvalues, K.periodic, dh, εᴹ),
+);

The next step is to solve the systems. Since application of boundary conditions, using the apply! function, modifies both the matrix and the right hand sides we can not use it directly in this case since we want to reuse the matrix again for the next right hand sides. We could of course re-assemble the matrix for every right hand side, but that would not be very efficient. Instead we will use the get_rhs_data function, together with apply_rhs! in a later step. This will extract the necessary data from the matrix such that we can apply it for all the different right hand sides. Note that we call apply! with just the matrix and no right hand side.

rhsdata = (
+    dirichlet = get_rhs_data(ch.dirichlet, K.dirichlet),
+    periodic = get_rhs_data(ch.periodic, K.periodic),
+)
+
+apply!(K.dirichlet, ch.dirichlet)
+apply!(K.periodic, ch.periodic)

We can now solve the problem(s). Note that we only use apply_rhs! in the loops below. The boundary conditions are already applied to the matrix above, so we only need to modify the right hand side.

u = (
+    dirichlet = Vector{Float64}[],
+    periodic = Vector{Float64}[],
+)
+
+for i in 1:size(rhs.dirichlet, 2)
+    rhs_i = @view rhs.dirichlet[:, i]                  # Extract this RHS
+    apply_rhs!(rhsdata.dirichlet, rhs_i, ch.dirichlet) # Apply BC
+    u_i = cholesky(Symmetric(K.dirichlet)) \ rhs_i     # Solve
+    apply!(u_i, ch.dirichlet)                          # Apply BC on the solution
+    push!(u.dirichlet, u_i)                            # Save the solution vector
+end
+
+for i in 1:size(rhs.periodic, 2)
+    rhs_i = @view rhs.periodic[:, i]                   # Extract this RHS
+    apply_rhs!(rhsdata.periodic, rhs_i, ch.periodic)   # Apply BC
+    u_i = cholesky(Symmetric(K.periodic)) \ rhs_i      # Solve
+    apply!(u_i, ch.periodic)                           # Apply BC on the solution
+    push!(u.periodic, u_i)                             # Save the solution vector
+end

When the solution(s) are known we can compute the averaged stress, $\bar{\boldsymbol{\sigma}}$ in the RVE. We define a function that does this, and also returns the von Mise stress in every quadrature point for visualization.

function compute_stress(cellvalues::CellValues, dh::DofHandler, u, εᴹ)
+    σvM_qpdata = zeros(getnquadpoints(cellvalues), getncells(dh.grid))
+    σ̄Ω = zero(SymmetricTensor{2, 2})
+    Ω = 0.0 # Total volume
+    for cell in CellIterator(dh)
+        E = cellid(cell) in getcellset(dh.grid, "inclusions") ? Ei : Em
+        reinit!(cellvalues, cell)
+        for q_point in 1:getnquadpoints(cellvalues)
+            dΩ = getdetJdV(cellvalues, q_point)
+            εμ = function_symmetric_gradient(cellvalues, q_point, u[celldofs(cell)])
+            σ = E ⊡ (εᴹ + εμ)
+            σvM_qpdata[q_point, cellid(cell)] = sqrt(3 / 2 * dev(σ) ⊡ dev(σ))
+            Ω += dΩ # Update total volume
+            σ̄Ω += σ * dΩ # Update integrated stress
+        end
+    end
+    σ̄ = σ̄Ω / Ω
+    return σvM_qpdata, σ̄
+end;

We now compute the homogenized stress and von Mise stress for all cases

σ̄ = (
+    dirichlet = SymmetricTensor{2, 2}[],
+    periodic = SymmetricTensor{2, 2}[],
+)
+σ = (
+    dirichlet = Vector{Float64}[],
+    periodic = Vector{Float64}[],
+)
+
+projector = L2Projector(ip, grid)
+
+for i in 1:3
+    σ_qp, σ̄_i = compute_stress(cellvalues, dh, u.dirichlet[i], εᴹ[i])
+    proj = project(projector, σ_qp, qr)
+    push!(σ.dirichlet, proj)
+    push!(σ̄.dirichlet, σ̄_i)
+end
+
+for i in 1:3
+    σ_qp, σ̄_i = compute_stress(cellvalues, dh, u.periodic[i], εᴹ[i])
+    proj = project(projector, σ_qp, qr)
+    push!(σ.periodic, proj)
+    push!(σ̄.periodic, σ̄_i)
+end

The remaining thing is to compute the homogenized stiffness. As mentioned in the introduction we can find all the components from the average stress of the sensitivity fields that we have solved for

\[\mathsf{E}_{ijkl} = \bar{\sigma}_{ij}(\hat{\boldsymbol{u}}_{kl}).\]

So we have now already computed all the components, and just need to gather the data in a fourth order tensor:

E_dirichlet = SymmetricTensor{4, 2}() do i, j, k, l
+    if k == l == 1
+        σ̄.dirichlet[1][i, j] # ∂σ∂ε_**11
+    elseif k == l == 2
+        σ̄.dirichlet[2][i, j] # ∂σ∂ε_**22
+    else
+        σ̄.dirichlet[3][i, j] # ∂σ∂ε_**12 and ∂σ∂ε_**21
+    end
+end
+
+E_periodic = SymmetricTensor{4, 2}() do i, j, k, l
+    if k == l == 1
+        σ̄.periodic[1][i, j]
+    elseif k == l == 2
+        σ̄.periodic[2][i, j]
+    else
+        σ̄.periodic[3][i, j]
+    end
+end
2×2×2×2 SymmetricTensor{4, 2, Float64, 9}:
+[:, :, 1, 1] =
+       4.30443e10  -809961.0
+ -809961.0               1.43401e10
+
+[:, :, 2, 1] =
+ -809961.0          1.16827e10
+       1.16827e10  -2.18543e6
+
+[:, :, 1, 2] =
+ -809961.0          1.16827e10
+       1.16827e10  -2.18543e6
+
+[:, :, 2, 2] =
+  1.43401e10  -2.18543e6
+ -2.18543e6    4.30725e10

We can check that the result are what we expect, namely that the stiffness with Dirichlet boundary conditions is higher than when using periodic boundary conditions, and that the Reuss assumption is an lower bound, and the Voigt assumption a upper bound. We first compute the volume fraction of the matrix, and then the Voigt and Reuss bounds:

function matrix_volume_fraction(grid, cellvalues)
+    V = 0.0 # Total volume
+    Vm = 0.0 # Volume of the matrix
+    for c in CellIterator(grid)
+        reinit!(cellvalues, c)
+        is_matrix = !(cellid(c) in getcellset(grid, "inclusions"))
+        for qp in 1:getnquadpoints(cellvalues)
+            dΩ = getdetJdV(cellvalues, qp)
+            V += dΩ
+            if is_matrix
+                Vm += dΩ
+            end
+        end
+    end
+    return Vm / V
+end
+
+vm = matrix_volume_fraction(grid, cellvalues)
0.64796265456868
E_voigt = vm * Em + (1 - vm) * Ei
+E_reuss = inv(vm * inv(Em) + (1 - vm) * inv(Ei));

We can now compare the different computed stiffness tensors. We expect $E_\mathrm{Reuss} \leq E_\mathrm{PeriodicBC} \leq E_\mathrm{DirichletBC} \leq E_\mathrm{Voigt}$. A simple thing to compare are the eigenvalues of the tensors. Here we look at the first eigenvalue:

ev = (first ∘ eigvals).((E_reuss, E_periodic, E_dirichlet, E_voigt))
+round.(ev; digits = -8)
(2.05e10, 2.34e10, 2.82e10, 5.84e10)

Finally, we export the solution and the stress field to a VTK file. For the export we also compute the macroscopic part of the displacement.

uM = zeros(ndofs(dh))
+
+VTKGridFile("homogenization", dh) do vtk
+    for i in 1:3
+        # Compute macroscopic solution
+        apply_analytical!(uM, dh, :u, x -> εᴹ[i] ⋅ x)
+        # Dirichlet
+        write_solution(vtk, dh, uM + u.dirichlet[i], "_dirichlet_$i")
+        write_projection(vtk, projector, σ.dirichlet[i], "σvM_dirichlet_$i")
+        # Periodic
+        write_solution(vtk, dh, uM + u.periodic[i], "_periodic_$i")
+        write_projection(vtk, projector, σ.periodic[i], "σvM_periodic_$i")
+    end
+end;

Plain program

Here follows a version of the program without any comments. The file is also available here: computational_homogenization.jl.

using Ferrite, SparseArrays, LinearAlgebra
+
+using FerriteGmsh
+
+# grid = togrid("periodic-rve-coarse.msh")
+grid = togrid("periodic-rve.msh")
+
+dim = 2
+ip = Lagrange{RefTriangle, 1}()^dim
+qr = QuadratureRule{RefTriangle}(2)
+cellvalues = CellValues(qr, ip);
+
+dh = DofHandler(grid)
+add!(dh, :u, ip)
+close!(dh);
+
+ch_dirichlet = ConstraintHandler(dh)
+dirichlet = Dirichlet(
+    :u,
+    union(getfacetset.(Ref(grid), ["left", "right", "top", "bottom"])...),
+    (x, t) -> [0, 0],
+    [1, 2]
+)
+add!(ch_dirichlet, dirichlet)
+close!(ch_dirichlet)
+update!(ch_dirichlet, 0.0)
+
+ch_periodic = ConstraintHandler(dh);
+periodic = PeriodicDirichlet(
+    :u,
+    ["left" => "right", "bottom" => "top"],
+    [1, 2]
+)
+add!(ch_periodic, periodic)
+close!(ch_periodic)
+update!(ch_periodic, 0.0)
+
+ch = (dirichlet = ch_dirichlet, periodic = ch_periodic);
+
+K = (
+    dirichlet = allocate_matrix(dh),
+    periodic = allocate_matrix(dh, ch.periodic),
+);
+
+λ, μ = 1.0e10, 7.0e9 # Lamé parameters
+δ(i, j) = i == j ? 1.0 : 0.0
+Em = SymmetricTensor{4, 2}(
+    (i, j, k, l) -> λ * δ(i, j) * δ(k, l) + μ * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k))
+)
+Ei = 10 * Em;
+
+εᴹ = [
+    SymmetricTensor{2, 2}([1.0 0.0; 0.0 0.0]), # ε_11 loading
+    SymmetricTensor{2, 2}([0.0 0.0; 0.0 1.0]), # ε_22 loading
+    SymmetricTensor{2, 2}([0.0 0.5; 0.5 0.0]), # ε_12/ε_21 loading
+];
+
+function doassemble!(cellvalues::CellValues, K::SparseMatrixCSC, dh::DofHandler, εᴹ)
+
+    n_basefuncs = getnbasefunctions(cellvalues)
+    ndpc = ndofs_per_cell(dh)
+    Ke = zeros(ndpc, ndpc)
+    fe = zeros(ndpc, length(εᴹ))
+    f = zeros(ndofs(dh), length(εᴹ))
+    assembler = start_assemble(K)
+
+    for cell in CellIterator(dh)
+
+        E = cellid(cell) in getcellset(dh.grid, "inclusions") ? Ei : Em
+        reinit!(cellvalues, cell)
+        fill!(Ke, 0)
+        fill!(fe, 0)
+
+        for q_point in 1:getnquadpoints(cellvalues)
+            dΩ = getdetJdV(cellvalues, q_point)
+            for i in 1:n_basefuncs
+                δεi = shape_symmetric_gradient(cellvalues, q_point, i)
+                for j in 1:n_basefuncs
+                    δεj = shape_symmetric_gradient(cellvalues, q_point, j)
+                    Ke[i, j] += (δεi ⊡ E ⊡ δεj) * dΩ
+                end
+                for (rhs, ε) in enumerate(εᴹ)
+                    σᴹ = E ⊡ ε
+                    fe[i, rhs] += (- δεi ⊡ σᴹ) * dΩ
+                end
+            end
+        end
+
+        cdofs = celldofs(cell)
+        assemble!(assembler, cdofs, Ke)
+        f[cdofs, :] .+= fe
+    end
+    return f
+end;
+
+rhs = (
+    dirichlet = doassemble!(cellvalues, K.dirichlet, dh, εᴹ),
+    periodic = doassemble!(cellvalues, K.periodic, dh, εᴹ),
+);
+
+rhsdata = (
+    dirichlet = get_rhs_data(ch.dirichlet, K.dirichlet),
+    periodic = get_rhs_data(ch.periodic, K.periodic),
+)
+
+apply!(K.dirichlet, ch.dirichlet)
+apply!(K.periodic, ch.periodic)
+
+u = (
+    dirichlet = Vector{Float64}[],
+    periodic = Vector{Float64}[],
+)
+
+for i in 1:size(rhs.dirichlet, 2)
+    rhs_i = @view rhs.dirichlet[:, i]                  # Extract this RHS
+    apply_rhs!(rhsdata.dirichlet, rhs_i, ch.dirichlet) # Apply BC
+    u_i = cholesky(Symmetric(K.dirichlet)) \ rhs_i     # Solve
+    apply!(u_i, ch.dirichlet)                          # Apply BC on the solution
+    push!(u.dirichlet, u_i)                            # Save the solution vector
+end
+
+for i in 1:size(rhs.periodic, 2)
+    rhs_i = @view rhs.periodic[:, i]                   # Extract this RHS
+    apply_rhs!(rhsdata.periodic, rhs_i, ch.periodic)   # Apply BC
+    u_i = cholesky(Symmetric(K.periodic)) \ rhs_i      # Solve
+    apply!(u_i, ch.periodic)                           # Apply BC on the solution
+    push!(u.periodic, u_i)                             # Save the solution vector
+end
+
+function compute_stress(cellvalues::CellValues, dh::DofHandler, u, εᴹ)
+    σvM_qpdata = zeros(getnquadpoints(cellvalues), getncells(dh.grid))
+    σ̄Ω = zero(SymmetricTensor{2, 2})
+    Ω = 0.0 # Total volume
+    for cell in CellIterator(dh)
+        E = cellid(cell) in getcellset(dh.grid, "inclusions") ? Ei : Em
+        reinit!(cellvalues, cell)
+        for q_point in 1:getnquadpoints(cellvalues)
+            dΩ = getdetJdV(cellvalues, q_point)
+            εμ = function_symmetric_gradient(cellvalues, q_point, u[celldofs(cell)])
+            σ = E ⊡ (εᴹ + εμ)
+            σvM_qpdata[q_point, cellid(cell)] = sqrt(3 / 2 * dev(σ) ⊡ dev(σ))
+            Ω += dΩ # Update total volume
+            σ̄Ω += σ * dΩ # Update integrated stress
+        end
+    end
+    σ̄ = σ̄Ω / Ω
+    return σvM_qpdata, σ̄
+end;
+
+σ̄ = (
+    dirichlet = SymmetricTensor{2, 2}[],
+    periodic = SymmetricTensor{2, 2}[],
+)
+σ = (
+    dirichlet = Vector{Float64}[],
+    periodic = Vector{Float64}[],
+)
+
+projector = L2Projector(ip, grid)
+
+for i in 1:3
+    σ_qp, σ̄_i = compute_stress(cellvalues, dh, u.dirichlet[i], εᴹ[i])
+    proj = project(projector, σ_qp, qr)
+    push!(σ.dirichlet, proj)
+    push!(σ̄.dirichlet, σ̄_i)
+end
+
+for i in 1:3
+    σ_qp, σ̄_i = compute_stress(cellvalues, dh, u.periodic[i], εᴹ[i])
+    proj = project(projector, σ_qp, qr)
+    push!(σ.periodic, proj)
+    push!(σ̄.periodic, σ̄_i)
+end
+
+E_dirichlet = SymmetricTensor{4, 2}() do i, j, k, l
+    if k == l == 1
+        σ̄.dirichlet[1][i, j] # ∂σ∂ε_**11
+    elseif k == l == 2
+        σ̄.dirichlet[2][i, j] # ∂σ∂ε_**22
+    else
+        σ̄.dirichlet[3][i, j] # ∂σ∂ε_**12 and ∂σ∂ε_**21
+    end
+end
+
+E_periodic = SymmetricTensor{4, 2}() do i, j, k, l
+    if k == l == 1
+        σ̄.periodic[1][i, j]
+    elseif k == l == 2
+        σ̄.periodic[2][i, j]
+    else
+        σ̄.periodic[3][i, j]
+    end
+end
+
+function matrix_volume_fraction(grid, cellvalues)
+    V = 0.0 # Total volume
+    Vm = 0.0 # Volume of the matrix
+    for c in CellIterator(grid)
+        reinit!(cellvalues, c)
+        is_matrix = !(cellid(c) in getcellset(grid, "inclusions"))
+        for qp in 1:getnquadpoints(cellvalues)
+            dΩ = getdetJdV(cellvalues, qp)
+            V += dΩ
+            if is_matrix
+                Vm += dΩ
+            end
+        end
+    end
+    return Vm / V
+end
+
+vm = matrix_volume_fraction(grid, cellvalues)
+
+E_voigt = vm * Em + (1 - vm) * Ei
+E_reuss = inv(vm * inv(Em) + (1 - vm) * inv(Ei));
+
+ev = (first ∘ eigvals).((E_reuss, E_periodic, E_dirichlet, E_voigt))
+round.(ev; digits = -8)
+
+uM = zeros(ndofs(dh))
+
+VTKGridFile("homogenization", dh) do vtk
+    for i in 1:3
+        # Compute macroscopic solution
+        apply_analytical!(uM, dh, :u, x -> εᴹ[i] ⋅ x)
+        # Dirichlet
+        write_solution(vtk, dh, uM + u.dirichlet[i], "_dirichlet_$i")
+        write_projection(vtk, projector, σ.dirichlet[i], "σvM_dirichlet_$i")
+        # Periodic
+        write_solution(vtk, dh, uM + u.periodic[i], "_periodic_$i")
+        write_projection(vtk, projector, σ.periodic[i], "σvM_periodic_$i")
+    end
+end;

This page was generated using Literate.jl.

diff --git a/previews/PR798/tutorials/dg_heat_equation.ipynb b/previews/PR798/tutorials/dg_heat_equation.ipynb new file mode 100644 index 0000000000..201085239a --- /dev/null +++ b/previews/PR798/tutorials/dg_heat_equation.ipynb @@ -0,0 +1,534 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Discontinuous Galerkin heat equation\n", + "\n", + "![](dg_heat_equation.png)\n", + "\n", + "*Figure 1*: Temperature field on the unit square with an internal uniform heat source\n", + "solved with inhomogeneous Dirichlet boundary conditions on the left and right boundaries\n", + "and flux on the top and bottom boundaries." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "This example was developed\n", + "as part of the Google summer of code funded project\n", + "[\"Discontinuous Galerkin Infrastructure For the finite element toolbox Ferrite.jl\"](https://summerofcode.withgoogle.com/programs/2023/projects/SLGbRNI5)\n", + "\n", + "## Introduction\n", + "\n", + "This tutorial extends [Tutorial 1: Heat equation](heat_equation.md) by using the\n", + "discontinuous Galerkin method. The reader is expected to have gone through [Tutorial 1:\n", + "Heat equation](heat_equation.md) before proceeding with this tutorial. The main\n", + "differences between the two tutorials are the interface integral terms in the weak form,\n", + "the boundary conditions, and some implementation differences explained in the commented\n", + "program below.\n", + "\n", + "The strong form considered in this tutorial is given as follows\n", + "$$\n", + " -\\boldsymbol{\\nabla} \\cdot [\\boldsymbol{\\nabla}(u)] = 1 \\quad \\textbf{x} \\in \\Omega,\n", + "$$\n", + "\n", + "with the inhomogeneous Dirichlet boundary conditions\n", + "$$\n", + "u(\\textbf{x}) = 1 \\quad \\textbf{x} \\in \\partial \\Omega_D^+ = \\lbrace\\textbf{x} : x_1 = 1.0\\rbrace, \\\\\n", + "u(\\textbf{x}) = -1 \\quad \\textbf{x} \\in \\partial \\Omega_D^- = \\lbrace\\textbf{x} : x_1 = -1.0\\rbrace,\n", + "$$\n", + "and Neumann boundary conditions\n", + "$$\n", + "[\\boldsymbol{\\nabla} (u(\\textbf{x}))] \\cdot \\boldsymbol{n} = 1 \\quad \\textbf{x} \\in \\partial \\Omega_N^+ = \\lbrace\\textbf{x} : x_2 = 1.0\\rbrace, \\\\\n", + "[\\boldsymbol{\\nabla} (u(\\textbf{x}))] \\cdot \\boldsymbol{n} = -1 \\quad \\textbf{x} \\in \\partial \\Omega_N^- = \\lbrace\\textbf{x} : x_2 = -1.0\\rbrace,\n", + "$$\n", + "\n", + "The following definitions of average and jump on interfaces between elements are adopted\n", + "in this tutorial:\n", + "$$\n", + " \\{u\\} = \\frac{1}{2}(u^+ + u^-),\\quad [\\![ u]\\!] = u^+ \\boldsymbol{n}^+ + u^- \\boldsymbol{n}^-\\\\\n", + "$$\n", + "where $u^+$ and $u^-$ are the temperature on the two sides of the interface.\n", + "\n", + "\n", + "> **Derivation of the weak form for homogeneous Dirichlet boundary condition**\n", + ">\n", + "> Defining $\\boldsymbol{\\sigma}$ as the gradient of the temperature field the equation can be expressed as\n", + "> $$\n", + "> \\boldsymbol{\\sigma} = \\boldsymbol{\\nabla} (u),\\\\\n", + "> -\\boldsymbol{\\nabla} \\cdot \\boldsymbol{\\sigma} = 1,\n", + "> $$\n", + "> Multiplying by test functions $ \\boldsymbol{\\tau} $ and $ \\delta u $ respectively and integrating\n", + "> over the domain,\n", + "> $$\n", + "> \\int_\\Omega \\boldsymbol{\\sigma} \\cdot \\boldsymbol{\\tau} \\,\\mathrm{d}\\Omega = \\int_\\Omega [\\boldsymbol{\\nabla} (u)] \\cdot \\boldsymbol{\\tau} \\,\\mathrm{d}\\Omega,\\\\\n", + "> -\\int_\\Omega \\boldsymbol{\\nabla} \\cdot \\boldsymbol{\\sigma} \\delta u \\,\\mathrm{d}\\Omega = \\int_\\Omega \\delta u \\,\\mathrm{d}\\Omega,\n", + "> $$\n", + "> Integrating by parts and applying divergence theorem,\n", + "> $$\n", + "> \\int_\\Omega \\boldsymbol{\\sigma} \\cdot \\boldsymbol{\\tau} \\,\\mathrm{d}\\Omega = -\\int_\\Omega u (\\boldsymbol{\\nabla} \\cdot \\boldsymbol{\\tau}) \\,\\mathrm{d}\\Omega + \\int_\\Gamma \\hat{u} \\boldsymbol{\\tau} \\cdot \\boldsymbol{n} \\,\\mathrm{d}\\Gamma,\\\\\n", + "> \\int_\\Omega \\boldsymbol{\\sigma} \\cdot [\\boldsymbol{\\nabla} (\\delta u)] \\,\\mathrm{d}\\Omega = \\int_\\Omega \\delta u \\,\\mathrm{d}\\Omega + \\int_\\Gamma \\delta u \\boldsymbol{\\hat{\\sigma}} \\cdot \\boldsymbol{n} \\,\\mathrm{d}\\Gamma,\n", + "> $$\n", + "> Where $\\boldsymbol{n}$ is the outwards pointing normal, $\\Gamma$ is the union of the elements' boundaries, and $\\hat{u}, \\, \\hat{\\sigma}$ are the numerical fluxes.\n", + "> Substituting the integrals of form\n", + "> $$\n", + "> \\int_\\Gamma q \\boldsymbol{\\phi} \\cdot \\boldsymbol{n} \\,\\mathrm{d}\\Gamma = \\int_\\Gamma [\\![ q]\\!] \\cdot \\{\\boldsymbol{\\phi}\\} \\,\\mathrm{d}\\Gamma + \\int_{\\Gamma^0} \\{q\\} [\\![ \\boldsymbol{\\phi}]\\!] \\,\\mathrm{d}\\Gamma^0,\n", + "> $$\n", + "> where $\\Gamma^0 : \\Gamma \\setminus \\partial \\Omega$, and the jump of the vector-valued field $\\boldsymbol{\\phi}$ is defined as\n", + "> $$\n", + "> [\\![ \\boldsymbol{\\phi}]\\!] = \\boldsymbol{\\phi}^+ \\cdot \\boldsymbol{n}^+ + \\boldsymbol{\\phi}^- \\cdot \\boldsymbol{n}^-\\\\\n", + "> $$\n", + "> with the jumps and averages results in\n", + "> $$\n", + "> \\int_\\Omega \\boldsymbol{\\sigma} \\cdot \\boldsymbol{\\tau} \\,\\mathrm{d}\\Omega = -\\int_\\Omega u (\\boldsymbol{\\nabla} \\cdot \\boldsymbol{\\tau}) \\,\\mathrm{d}\\Omega + \\int_\\Gamma [\\![ \\hat{u}]\\!] \\cdot \\{\\boldsymbol{\\tau}\\} \\,\\mathrm{d}\\Gamma + \\int_{\\Gamma^0} \\{\\hat{u}\\} [\\![ \\boldsymbol{\\tau}]\\!] \\,\\mathrm{d}\\Gamma^0,\\\\\n", + "> \\int_\\Omega \\boldsymbol{\\sigma} \\cdot [\\boldsymbol{\\nabla} (\\delta u)] \\,\\mathrm{d}\\Omega = \\int_\\Omega \\delta u \\,\\mathrm{d}\\Omega + \\int_\\Gamma [\\![ \\delta u]\\!] \\cdot \\{\\hat{\\boldsymbol{\\sigma}}\\} \\,\\mathrm{d}\\Gamma + \\int_{\\Gamma^0} \\{\\delta u\\} [\\![ \\hat{\\boldsymbol{\\sigma}}]\\!] \\,\\mathrm{d}\\Gamma^0,\n", + "> $$\n", + "> Integrating $ \\int_\\Omega [\\boldsymbol{\\nabla} (u)] \\cdot \\boldsymbol{\\tau} \\,\\mathrm{d}\\Omega $ by parts and applying divergence theorem\n", + "> without using numerical flux, then substitute in the equation to obtain a weak form.\n", + "> $$\n", + "> \\int_\\Omega \\boldsymbol{\\sigma} \\cdot \\boldsymbol{\\tau} \\,\\mathrm{d}\\Omega = \\int_\\Omega [\\boldsymbol{\\nabla} (u)] \\cdot \\boldsymbol{\\tau} \\,\\mathrm{d}\\Omega + \\int_\\Gamma [\\![ \\hat{u} - u]\\!] \\cdot \\{\\boldsymbol{\\tau}\\} \\,\\mathrm{d}\\Gamma + \\int_{\\Gamma^0} \\{\\hat{u} - u\\} [\\![ \\boldsymbol{\\tau}]\\!] \\,\\mathrm{d}\\Gamma^0,\\\\\n", + "> \\int_\\Omega \\boldsymbol{\\sigma} \\cdot [\\boldsymbol{\\nabla} (\\delta u)] \\,\\mathrm{d}\\Omega = \\int_\\Omega \\delta u \\,\\mathrm{d}\\Omega + \\int_\\Gamma [\\![ \\delta u]\\!] \\cdot \\{\\hat{\\boldsymbol{\\sigma}}\\} \\,\\mathrm{d}\\Gamma + \\int_{\\Gamma^0} \\{\\delta u\\} [\\![ \\hat{\\boldsymbol{\\sigma}}]\\!] \\,\\mathrm{d}\\Gamma^0,\n", + "> $$\n", + "> Substituting\n", + "> $$\n", + "> \\boldsymbol{\\tau} = \\boldsymbol{\\nabla} (\\delta u),\\\\\n", + "> $$\n", + "> results in\n", + "> $$\n", + "> \\int_\\Omega \\boldsymbol{\\sigma} \\cdot [\\boldsymbol{\\nabla} (\\delta u)] \\,\\mathrm{d}\\Omega = \\int_\\Omega [\\boldsymbol{\\nabla} (u)] \\cdot [\\boldsymbol{\\nabla} (\\delta u)] \\,\\mathrm{d}\\Omega + \\int_\\Gamma [\\![ \\hat{u} - u]\\!] \\cdot \\{\\boldsymbol{\\nabla} (\\delta u)\\} \\,\\mathrm{d}\\Gamma + \\int_{\\Gamma^0} \\{\\hat{u} - u\\} [\\![ \\boldsymbol{\\nabla} (\\delta u)]\\!] \\,\\mathrm{d}\\Gamma^0,\\\\\n", + "> \\int_\\Omega \\boldsymbol{\\sigma} \\cdot [\\boldsymbol{\\nabla} (\\delta u)] \\,\\mathrm{d}\\Omega = \\int_\\Omega \\delta u \\,\\mathrm{d}\\Omega + \\int_\\Gamma [\\![ \\delta u]\\!] \\cdot \\{\\hat{\\boldsymbol{\\sigma}}\\} \\,\\mathrm{d}\\Gamma + \\int_{\\Gamma^0} \\{\\delta u\\} [\\![ \\hat{\\boldsymbol{\\sigma}}]\\!] \\,\\mathrm{d}\\Gamma^0,\n", + "> $$\n", + "> Combining the two equations,\n", + "> $$\n", + "> \\int_\\Omega [\\boldsymbol{\\nabla} (u)] \\cdot [\\boldsymbol{\\nabla} (\\delta u)] \\,\\mathrm{d}\\Omega + \\int_\\Gamma [\\![ \\hat{u} - u]\\!] \\cdot \\{\\boldsymbol{\\nabla} (\\delta u)\\} \\,\\mathrm{d}\\Gamma + \\int_{\\Gamma^0} \\{\\hat{u} - u\\} [\\![ \\boldsymbol{\\nabla} (\\delta u)]\\!] \\,\\mathrm{d}\\Gamma^0 - \\int_\\Gamma [\\![ \\delta u]\\!] \\cdot \\{\\hat{\\boldsymbol{\\sigma}}\\} \\,\\mathrm{d}\\Gamma - \\int_{\\Gamma^0} \\{\\delta u\\} [\\![ \\hat{\\boldsymbol{\\sigma}}]\\!] \\,\\mathrm{d}\\Gamma^0 = \\int_\\Omega \\delta u \\,\\mathrm{d}\\Omega,\\\\\n", + "> $$\n", + "> The numerical fluxes chosen for the interior penalty method are $\\boldsymbol{\\hat{\\sigma}} = \\{\\boldsymbol{\\nabla} (u)\\} - \\alpha([\\![ u]\\!])$ on $\\Gamma$, $\\hat{u} = \\{u\\}$ on the interfaces between elements $\\Gamma^0 : \\Gamma \\setminus \\partial \\Omega$,\n", + "> and $\\hat{u} = 0$ on $\\partial \\Omega$. Such choice results in $\\{\\hat{\\boldsymbol{\\sigma}}\\} = \\{\\boldsymbol{\\nabla} (u)\\} - \\alpha([\\![ u]\\!])$, $[\\![ \\hat{u}]\\!] = 0$, $\\{\\hat{u}\\} = \\{u\\}$, $[\\![ \\hat{\\boldsymbol{\\sigma}}]\\!] = 0$ and the equation becomes\n", + "> $$\n", + "> \\int_\\Omega [\\boldsymbol{\\nabla} (u)] \\cdot [\\boldsymbol{\\nabla} (\\delta u)] \\,\\mathrm{d}\\Omega - \\int_\\Gamma [\\![ u]\\!] \\cdot \\{\\boldsymbol{\\nabla} (\\delta u)\\} \\,\\mathrm{d}\\Gamma - \\int_\\Gamma [\\![ \\delta u]\\!] \\cdot \\{\\boldsymbol{\\nabla} (u)\\} - [\\![ \\delta u]\\!] \\cdot \\alpha([\\![ u]\\!]) \\,\\mathrm{d}\\Gamma = \\int_\\Omega \\delta u \\,\\mathrm{d}\\Omega,\\\\\n", + "> $$\n", + "> Where\n", + "> $$\n", + "> \\alpha([\\![ u]\\!]) = \\mu [\\![ u]\\!]\n", + "> $$\n", + "> Where $\\mu = \\eta h_e^{-1}$, the weak form becomes\n", + "> $$\n", + "> \\int_\\Omega [\\boldsymbol{\\nabla} (u)] \\cdot [\\boldsymbol{\\nabla}] (\\delta u) \\,\\mathrm{d}\\Omega - \\int_\\Gamma [\\![ u ]\\!] \\cdot \\{\\boldsymbol{\\nabla} (\\delta u)\\} + [\\![ \\delta u ]\\!] \\cdot \\{\\boldsymbol{\\nabla} (u)\\} \\,\\mathrm{d}\\Gamma + \\int_\\Gamma \\frac{\\eta}{h_e} [\\![ u]\\!] \\cdot [\\![ \\delta u]\\!] \\,\\mathrm{d}\\Gamma = \\int_\\Omega \\delta u \\,\\mathrm{d}\\Omega,\\\\\n", + "> $$\n", + "Since $\\partial \\Omega$ is constrained with both Dirichlet and Neumann boundary conditions the term $\\int_{\\partial \\Omega} [\\boldsymbol{\\nabla} (u)] \\cdot \\boldsymbol{n} \\delta u \\,\\mathrm{d} \\Omega$ can be expressed as an integral over $\\partial \\Omega_N$, where $\\partial \\Omega_N$ is the boundaries with only prescribed Neumann boundary condition,\n", + "The resulting weak form is given given as follows: Find $u \\in \\mathbb{U}$ such that\n", + "$$\n", + " \\int_\\Omega [\\boldsymbol{\\nabla} (u)] \\cdot [\\boldsymbol{\\nabla} (\\delta u)] \\,\\mathrm{d}\\Omega - \\int_{\\Gamma^0} [\\![ u]\\!] \\cdot \\{\\boldsymbol{\\nabla} (\\delta u)\\} + [\\![ \\delta u]\\!] \\cdot \\{\\boldsymbol{\\nabla} (u)\\} \\,\\mathrm{d}\\Gamma^0 + \\int_{\\Gamma^0} \\frac{\\eta}{h_e} [\\![ u]\\!] \\cdot [\\![ \\delta u]\\!] \\,\\mathrm{d}\\Gamma^0 = \\int_\\Omega \\delta u \\,\\mathrm{d}\\Omega + \\int_{\\partial \\Omega_N} ([\\boldsymbol{\\nabla} (u)] \\cdot \\boldsymbol{n}) \\delta u \\,\\mathrm{d} \\partial \\Omega_N,\\\\\n", + "$$\n", + "where $h_e$ is the characteristic size (the diameter of the interface), and $\\eta$ is a large enough positive number independent of $h_e$ [Mu:2014:IP](@cite),\n", + "$\\delta u \\in \\mathbb{T}$ is a test function, and where $\\mathbb{U}$ and $\\mathbb{T}$ are suitable\n", + "trial and test function sets, respectively.\n", + "We use the value $\\eta = (1 + O)^{D}$, where $O$ is the polynomial order and $D$ the\n", + "dimension, in this tutorial.\n", + "\n", + "More details on DG formulations for elliptic problems can be found in [Cockburn:2002:unifiedanalysis](@cite)." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Commented Program\n", + "\n", + "Now we solve the problem in Ferrite. What follows is a program spliced with comments.\n", + "\n", + "First we load Ferrite and other packages, and generate grid just like the [heat equation tutorial](heat_equation.md)" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using Ferrite, SparseArrays\n", + "dim = 2;\n", + "grid = generate_grid(Quadrilateral, ntuple(_ -> 20, dim));" + ], + "metadata": {}, + "execution_count": 1 + }, + { + "cell_type": "markdown", + "source": [ + "We construct the topology information which is used later for generating the sparsity pattern for stiffness matrix." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "topology = ExclusiveTopology(grid);" + ], + "metadata": {}, + "execution_count": 2 + }, + { + "cell_type": "markdown", + "source": [ + "### Trial and test functions\n", + "`CellValues`, `FacetValues`, and `InterfaceValues` facilitate the process of evaluating values and gradients of\n", + "test and trial functions (among other things). To define\n", + "these we need to specify an interpolation space for the shape functions.\n", + "We use `DiscontinuousLagrange` functions\n", + "based on the two-dimensional reference quadrilateral. We also define a quadrature rule based on\n", + "the same reference element. We combine the interpolation and the quadrature rule\n", + "to `CellValues` and `InterfaceValues` object. Note that `InterfaceValues` object contains two `FacetValues` objects which can be used individually." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "order = 1;\n", + "ip = DiscontinuousLagrange{RefQuadrilateral, order}();\n", + "qr = QuadratureRule{RefQuadrilateral}(2);" + ], + "metadata": {}, + "execution_count": 3 + }, + { + "cell_type": "markdown", + "source": [ + "For `FacetValues` and `InterfaceValues` we use `FacetQuadratureRule`" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "facet_qr = FacetQuadratureRule{RefQuadrilateral}(2);\n", + "cellvalues = CellValues(qr, ip);\n", + "facetvalues = FacetValues(facet_qr, ip);\n", + "interfacevalues = InterfaceValues(facet_qr, ip);" + ], + "metadata": {}, + "execution_count": 4 + }, + { + "cell_type": "markdown", + "source": [ + "### Penalty term parameters\n", + "We define functions to calculate the diameter of a set of points, used to calculate the characteristic size $h_e$ in the assembly routine." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "getdistance(p1::Vec{N, T}, p2::Vec{N, T}) where {N, T} = norm(p1 - p2);\n", + "getdiameter(cell_coords::Vector{Vec{N, T}}) where {N, T} = maximum(getdistance.(cell_coords, reshape(cell_coords, (1, :))));" + ], + "metadata": {}, + "execution_count": 5 + }, + { + "cell_type": "markdown", + "source": [ + "### Degrees of freedom\n", + "Degrees of freedom distribution is handled using `DofHandler` as usual" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "dh = DofHandler(grid)\n", + "add!(dh, :u, ip)\n", + "close!(dh);" + ], + "metadata": {}, + "execution_count": 6 + }, + { + "cell_type": "markdown", + "source": [ + "However, when generating the sparsity pattern we need to pass the topology and the cross-element coupling matrix when we're using\n", + "discontinuous interpolations. The cross-element coupling matrix is of size [1,1] in this case as\n", + "we have only one field and one DofHandler." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "K = allocate_matrix(dh, topology = topology, interface_coupling = trues(1, 1));" + ], + "metadata": {}, + "execution_count": 7 + }, + { + "cell_type": "markdown", + "source": [ + "### Boundary conditions\n", + "The Dirichlet boundary conditions are treated\n", + "as usual by a `ConstraintHandler`." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "ch = ConstraintHandler(dh)\n", + "add!(ch, Dirichlet(:u, getfacetset(grid, \"right\"), (x, t) -> 1.0))\n", + "add!(ch, Dirichlet(:u, getfacetset(grid, \"left\"), (x, t) -> -1.0))\n", + "close!(ch);" + ], + "metadata": {}, + "execution_count": 8 + }, + { + "cell_type": "markdown", + "source": [ + "Furthermore, we define $\\partial \\Omega_N$ as the `union` of the facet sets with Neumann boundary conditions for later use" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "∂Ωₙ = union(\n", + " getfacetset(grid, \"top\"),\n", + " getfacetset(grid, \"bottom\"),\n", + ");" + ], + "metadata": {}, + "execution_count": 9 + }, + { + "cell_type": "markdown", + "source": [ + "### Assembling the linear system\n", + "\n", + "Now we have all the pieces needed to assemble the linear system, $K u = f$. Assembling of\n", + "the global system is done by looping over i) all the elements in order to compute the\n", + "element contributions $K_e$ and $f_e$, ii) all the interfaces to compute their\n", + "contributions $K_i$, and iii) all the Neumann boundary facets to compute their\n", + "contributions $f_e$. All these local contributions are then assembled into the\n", + "appropriate place in the global $K$ and $f$.\n", + "\n", + "#### Local assembly\n", + "We define the functions\n", + "* `assemble_element!` to compute the contributions $K_e$ and $f_e$ of volume integrals\n", + " over an element using `cellvalues`.\n", + "* `assemble_interface!` to compute the contribution $K_i$ of surface integrals over an\n", + " interface using `interfacevalues`.\n", + "* `assemble_boundary!` to compute the contribution $f_e$ of surface integrals over a\n", + " boundary facet using `FacetValues`." + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "assemble_boundary! (generic function with 1 method)" + }, + "metadata": {}, + "execution_count": 10 + } + ], + "cell_type": "code", + "source": [ + "function assemble_element!(Ke::Matrix, fe::Vector, cellvalues::CellValues)\n", + " n_basefuncs = getnbasefunctions(cellvalues)\n", + " # Reset to 0\n", + " fill!(Ke, 0)\n", + " fill!(fe, 0)\n", + " # Loop over quadrature points\n", + " for q_point in 1:getnquadpoints(cellvalues)\n", + " # Quadrature weight\n", + " dΩ = getdetJdV(cellvalues, q_point)\n", + " # Loop over test shape functions\n", + " for i in 1:n_basefuncs\n", + " δu = shape_value(cellvalues, q_point, i)\n", + " ∇δu = shape_gradient(cellvalues, q_point, i)\n", + " # Add contribution to fe\n", + " fe[i] += δu * dΩ\n", + " # Loop over trial shape functions\n", + " for j in 1:n_basefuncs\n", + " ∇u = shape_gradient(cellvalues, q_point, j)\n", + " # Add contribution to Ke\n", + " Ke[i, j] += (∇δu ⋅ ∇u) * dΩ\n", + " end\n", + " end\n", + " end\n", + " return Ke, fe\n", + "end\n", + "\n", + "function assemble_interface!(Ki::Matrix, iv::InterfaceValues, μ::Float64)\n", + " # Reset to 0\n", + " fill!(Ki, 0)\n", + " # Loop over quadrature points\n", + " for q_point in 1:getnquadpoints(iv)\n", + " # Get the normal to facet A\n", + " normal = getnormal(iv, q_point)\n", + " # Get the quadrature weight\n", + " dΓ = getdetJdV(iv, q_point)\n", + " # Loop over test shape functions\n", + " for i in 1:getnbasefunctions(iv)\n", + " # Multiply the jump by the negative normal to get the definition from the theory section.\n", + " δu_jump = shape_value_jump(iv, q_point, i) * (-normal)\n", + " ∇δu_avg = shape_gradient_average(iv, q_point, i)\n", + " # Loop over trial shape functions\n", + " for j in 1:getnbasefunctions(iv)\n", + " # Multiply the jump by the negative normal to get the definition from the theory section.\n", + " u_jump = shape_value_jump(iv, q_point, j) * (-normal)\n", + " ∇u_avg = shape_gradient_average(iv, q_point, j)\n", + " # Add contribution to Ki\n", + " Ki[i, j] += -(δu_jump ⋅ ∇u_avg + ∇δu_avg ⋅ u_jump) * dΓ + μ * (δu_jump ⋅ u_jump) * dΓ\n", + " end\n", + " end\n", + " end\n", + " return Ki\n", + "end\n", + "\n", + "function assemble_boundary!(fe::Vector, fv::FacetValues)\n", + " # Reset to 0\n", + " fill!(fe, 0)\n", + " # Loop over quadrature points\n", + " for q_point in 1:getnquadpoints(fv)\n", + " # Get the normal to facet A\n", + " normal = getnormal(fv, q_point)\n", + " # Get the quadrature weight\n", + " ∂Ω = getdetJdV(fv, q_point)\n", + " # Loop over test shape functions\n", + " for i in 1:getnbasefunctions(fv)\n", + " δu = shape_value(fv, q_point, i)\n", + " boundary_flux = normal[2]\n", + " fe[i] = boundary_flux * δu * ∂Ω\n", + " end\n", + " end\n", + " return fe\n", + "end" + ], + "metadata": {}, + "execution_count": 10 + }, + { + "cell_type": "markdown", + "source": [ + "#### Global assembly\n", + "\n", + "We define the function `assemble_global` to loop over all elements and internal facets\n", + "(interfaces), as well as the external facets involved in Neumann boundary conditions." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function assemble_global(cellvalues::CellValues, facetvalues::FacetValues, interfacevalues::InterfaceValues, K::SparseMatrixCSC, dh::DofHandler, order::Int, dim::Int)\n", + " # Allocate the element stiffness matrix and element force vector\n", + " n_basefuncs = getnbasefunctions(cellvalues)\n", + " Ke = zeros(n_basefuncs, n_basefuncs)\n", + " fe = zeros(n_basefuncs)\n", + " Ki = zeros(n_basefuncs * 2, n_basefuncs * 2)\n", + " # Allocate global force vector f\n", + " f = zeros(ndofs(dh))\n", + " # Create an assembler\n", + " assembler = start_assemble(K, f)\n", + " # Loop over all cells\n", + " for cell in CellIterator(dh)\n", + " # Reinitialize cellvalues for this cell\n", + " reinit!(cellvalues, cell)\n", + " # Compute volume integral contribution\n", + " assemble_element!(Ke, fe, cellvalues)\n", + " # Assemble Ke and fe into K and f\n", + " assemble!(assembler, celldofs(cell), Ke, fe)\n", + " end\n", + " # Loop over all interfaces\n", + " for ic in InterfaceIterator(dh)\n", + " # Reinitialize interfacevalues for this interface\n", + " reinit!(interfacevalues, ic)\n", + " # Calculate the characteristic size hₑ as the face diameter\n", + " interfacecoords = ∩(getcoordinates(ic)...)\n", + " hₑ = getdiameter(interfacecoords)\n", + " # Calculate μ\n", + " μ = (1 + order)^dim / hₑ\n", + " # Compute interface surface integrals contribution\n", + " assemble_interface!(Ki, interfacevalues, μ)\n", + " # Assemble Ki into K\n", + " assemble!(assembler, interfacedofs(ic), Ki)\n", + " end\n", + " # Loop over domain boundaries with Neumann boundary conditions\n", + " for fc in FacetIterator(dh, ∂Ωₙ)\n", + " # Reinitialize facetvalues for this boundary facet\n", + " reinit!(facetvalues, fc)\n", + " # Compute boundary facet surface integrals contribution\n", + " assemble_boundary!(fe, facetvalues)\n", + " # Assemble fe into f\n", + " assemble!(f, celldofs(fc), fe)\n", + " end\n", + " return K, f\n", + "end\n", + "K, f = assemble_global(cellvalues, facetvalues, interfacevalues, K, dh, order, dim);" + ], + "metadata": {}, + "execution_count": 11 + }, + { + "cell_type": "markdown", + "source": [ + "### Solution of the system\n", + "\n", + "The solution of the system is independent of the discontinuous discretization and the\n", + "application of constraints, linear solve, and exporting is done as usual." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "apply!(K, f, ch)\n", + "u = K \\ f;\n", + "VTKGridFile(\"dg_heat_equation\", dh) do vtk\n", + " write_solution(vtk, dh, u)\n", + "end;" + ], + "metadata": {}, + "execution_count": 12 + }, + { + "cell_type": "markdown", + "source": [ + "---\n", + "\n", + "*This notebook was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).*" + ], + "metadata": {} + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.1" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.1", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR798/tutorials/dg_heat_equation.jl b/previews/PR798/tutorials/dg_heat_equation.jl new file mode 100644 index 0000000000..5e001a70e7 --- /dev/null +++ b/previews/PR798/tutorials/dg_heat_equation.jl @@ -0,0 +1,159 @@ +using Ferrite, SparseArrays +dim = 2; +grid = generate_grid(Quadrilateral, ntuple(_ -> 20, dim)); + +topology = ExclusiveTopology(grid); + +order = 1; +ip = DiscontinuousLagrange{RefQuadrilateral, order}(); +qr = QuadratureRule{RefQuadrilateral}(2); + +facet_qr = FacetQuadratureRule{RefQuadrilateral}(2); +cellvalues = CellValues(qr, ip); +facetvalues = FacetValues(facet_qr, ip); +interfacevalues = InterfaceValues(facet_qr, ip); + +getdistance(p1::Vec{N, T}, p2::Vec{N, T}) where {N, T} = norm(p1 - p2); +getdiameter(cell_coords::Vector{Vec{N, T}}) where {N, T} = maximum(getdistance.(cell_coords, reshape(cell_coords, (1, :)))); + +dh = DofHandler(grid) +add!(dh, :u, ip) +close!(dh); + +K = allocate_matrix(dh, topology = topology, interface_coupling = trues(1, 1)); + +ch = ConstraintHandler(dh) +add!(ch, Dirichlet(:u, getfacetset(grid, "right"), (x, t) -> 1.0)) +add!(ch, Dirichlet(:u, getfacetset(grid, "left"), (x, t) -> -1.0)) +close!(ch); + +∂Ωₙ = union( + getfacetset(grid, "top"), + getfacetset(grid, "bottom"), +); + +function assemble_element!(Ke::Matrix, fe::Vector, cellvalues::CellValues) + n_basefuncs = getnbasefunctions(cellvalues) + # Reset to 0 + fill!(Ke, 0) + fill!(fe, 0) + # Loop over quadrature points + for q_point in 1:getnquadpoints(cellvalues) + # Quadrature weight + dΩ = getdetJdV(cellvalues, q_point) + # Loop over test shape functions + for i in 1:n_basefuncs + δu = shape_value(cellvalues, q_point, i) + ∇δu = shape_gradient(cellvalues, q_point, i) + # Add contribution to fe + fe[i] += δu * dΩ + # Loop over trial shape functions + for j in 1:n_basefuncs + ∇u = shape_gradient(cellvalues, q_point, j) + # Add contribution to Ke + Ke[i, j] += (∇δu ⋅ ∇u) * dΩ + end + end + end + return Ke, fe +end + +function assemble_interface!(Ki::Matrix, iv::InterfaceValues, μ::Float64) + # Reset to 0 + fill!(Ki, 0) + # Loop over quadrature points + for q_point in 1:getnquadpoints(iv) + # Get the normal to facet A + normal = getnormal(iv, q_point) + # Get the quadrature weight + dΓ = getdetJdV(iv, q_point) + # Loop over test shape functions + for i in 1:getnbasefunctions(iv) + # Multiply the jump by the negative normal to get the definition from the theory section. + δu_jump = shape_value_jump(iv, q_point, i) * (-normal) + ∇δu_avg = shape_gradient_average(iv, q_point, i) + # Loop over trial shape functions + for j in 1:getnbasefunctions(iv) + # Multiply the jump by the negative normal to get the definition from the theory section. + u_jump = shape_value_jump(iv, q_point, j) * (-normal) + ∇u_avg = shape_gradient_average(iv, q_point, j) + # Add contribution to Ki + Ki[i, j] += -(δu_jump ⋅ ∇u_avg + ∇δu_avg ⋅ u_jump) * dΓ + μ * (δu_jump ⋅ u_jump) * dΓ + end + end + end + return Ki +end + +function assemble_boundary!(fe::Vector, fv::FacetValues) + # Reset to 0 + fill!(fe, 0) + # Loop over quadrature points + for q_point in 1:getnquadpoints(fv) + # Get the normal to facet A + normal = getnormal(fv, q_point) + # Get the quadrature weight + ∂Ω = getdetJdV(fv, q_point) + # Loop over test shape functions + for i in 1:getnbasefunctions(fv) + δu = shape_value(fv, q_point, i) + boundary_flux = normal[2] + fe[i] = boundary_flux * δu * ∂Ω + end + end + return fe +end + +function assemble_global(cellvalues::CellValues, facetvalues::FacetValues, interfacevalues::InterfaceValues, K::SparseMatrixCSC, dh::DofHandler, order::Int, dim::Int) + # Allocate the element stiffness matrix and element force vector + n_basefuncs = getnbasefunctions(cellvalues) + Ke = zeros(n_basefuncs, n_basefuncs) + fe = zeros(n_basefuncs) + Ki = zeros(n_basefuncs * 2, n_basefuncs * 2) + # Allocate global force vector f + f = zeros(ndofs(dh)) + # Create an assembler + assembler = start_assemble(K, f) + # Loop over all cells + for cell in CellIterator(dh) + # Reinitialize cellvalues for this cell + reinit!(cellvalues, cell) + # Compute volume integral contribution + assemble_element!(Ke, fe, cellvalues) + # Assemble Ke and fe into K and f + assemble!(assembler, celldofs(cell), Ke, fe) + end + # Loop over all interfaces + for ic in InterfaceIterator(dh) + # Reinitialize interfacevalues for this interface + reinit!(interfacevalues, ic) + # Calculate the characteristic size hₑ as the face diameter + interfacecoords = ∩(getcoordinates(ic)...) + hₑ = getdiameter(interfacecoords) + # Calculate μ + μ = (1 + order)^dim / hₑ + # Compute interface surface integrals contribution + assemble_interface!(Ki, interfacevalues, μ) + # Assemble Ki into K + assemble!(assembler, interfacedofs(ic), Ki) + end + # Loop over domain boundaries with Neumann boundary conditions + for fc in FacetIterator(dh, ∂Ωₙ) + # Reinitialize facetvalues for this boundary facet + reinit!(facetvalues, fc) + # Compute boundary facet surface integrals contribution + assemble_boundary!(fe, facetvalues) + # Assemble fe into f + assemble!(f, celldofs(fc), fe) + end + return K, f +end +K, f = assemble_global(cellvalues, facetvalues, interfacevalues, K, dh, order, dim); + +apply!(K, f, ch) +u = K \ f; +VTKGridFile("dg_heat_equation", dh) do vtk + write_solution(vtk, dh, u) +end; + +# This file was generated using Literate.jl, https://github.com/fredrikekre/Literate.jl diff --git a/previews/PR798/tutorials/dg_heat_equation.png b/previews/PR798/tutorials/dg_heat_equation.png new file mode 100644 index 0000000000..a3da16ad4a Binary files /dev/null and b/previews/PR798/tutorials/dg_heat_equation.png differ diff --git a/previews/PR798/tutorials/dg_heat_equation/index.html b/previews/PR798/tutorials/dg_heat_equation/index.html new file mode 100644 index 0000000000..3c24ae7b7c --- /dev/null +++ b/previews/PR798/tutorials/dg_heat_equation/index.html @@ -0,0 +1,300 @@ + +Discontinuous Galerkin heat equation · Ferrite.jl

Discontinuous Galerkin heat equation

Figure 1: Temperature field on the unit square with an internal uniform heat source solved with inhomogeneous Dirichlet boundary conditions on the left and right boundaries and flux on the top and bottom boundaries.

Tip

This example is also available as a Jupyter notebook: dg_heat_equation.ipynb.

This example was developed as part of the Google summer of code funded project "Discontinuous Galerkin Infrastructure For the finite element toolbox Ferrite.jl"

Introduction

This tutorial extends Tutorial 1: Heat equation by using the discontinuous Galerkin method. The reader is expected to have gone through Tutorial 1: Heat equation before proceeding with this tutorial. The main differences between the two tutorials are the interface integral terms in the weak form, the boundary conditions, and some implementation differences explained in the commented program below.

The strong form considered in this tutorial is given as follows

\[ -\boldsymbol{\nabla} \cdot [\boldsymbol{\nabla}(u)] = 1 \quad \textbf{x} \in \Omega,\]

with the inhomogeneous Dirichlet boundary conditions

\[u(\textbf{x}) = 1 \quad \textbf{x} \in \partial \Omega_D^+ = \lbrace\textbf{x} : x_1 = 1.0\rbrace, \\ +u(\textbf{x}) = -1 \quad \textbf{x} \in \partial \Omega_D^- = \lbrace\textbf{x} : x_1 = -1.0\rbrace,\]

and Neumann boundary conditions

\[[\boldsymbol{\nabla} (u(\textbf{x}))] \cdot \boldsymbol{n} = 1 \quad \textbf{x} \in \partial \Omega_N^+ = \lbrace\textbf{x} : x_2 = 1.0\rbrace, \\ +[\boldsymbol{\nabla} (u(\textbf{x}))] \cdot \boldsymbol{n} = -1 \quad \textbf{x} \in \partial \Omega_N^- = \lbrace\textbf{x} : x_2 = -1.0\rbrace,\]

The following definitions of average and jump on interfaces between elements are adopted in this tutorial:

\[ \{u\} = \frac{1}{2}(u^+ + u^-),\quad \llbracket u\rrbracket = u^+ \boldsymbol{n}^+ + u^- \boldsymbol{n}^-\\\]

where $u^+$ and $u^-$ are the temperature on the two sides of the interface.

Derivation of the weak form for homogeneous Dirichlet boundary condition

Defining $\boldsymbol{\sigma}$ as the gradient of the temperature field the equation can be expressed as

\[ \boldsymbol{\sigma} = \boldsymbol{\nabla} (u),\\ + -\boldsymbol{\nabla} \cdot \boldsymbol{\sigma} = 1,\]

Multiplying by test functions $ \boldsymbol{\tau} $ and $ \delta u $ respectively and integrating over the domain,

\[ \int_\Omega \boldsymbol{\sigma} \cdot \boldsymbol{\tau} \,\mathrm{d}\Omega = \int_\Omega [\boldsymbol{\nabla} (u)] \cdot \boldsymbol{\tau} \,\mathrm{d}\Omega,\\ + -\int_\Omega \boldsymbol{\nabla} \cdot \boldsymbol{\sigma} \delta u \,\mathrm{d}\Omega = \int_\Omega \delta u \,\mathrm{d}\Omega,\]

Integrating by parts and applying divergence theorem,

\[ \int_\Omega \boldsymbol{\sigma} \cdot \boldsymbol{\tau} \,\mathrm{d}\Omega = -\int_\Omega u (\boldsymbol{\nabla} \cdot \boldsymbol{\tau}) \,\mathrm{d}\Omega + \int_\Gamma \hat{u} \boldsymbol{\tau} \cdot \boldsymbol{n} \,\mathrm{d}\Gamma,\\ + \int_\Omega \boldsymbol{\sigma} \cdot [\boldsymbol{\nabla} (\delta u)] \,\mathrm{d}\Omega = \int_\Omega \delta u \,\mathrm{d}\Omega + \int_\Gamma \delta u \boldsymbol{\hat{\sigma}} \cdot \boldsymbol{n} \,\mathrm{d}\Gamma,\]

Where $\boldsymbol{n}$ is the outwards pointing normal, $\Gamma$ is the union of the elements' boundaries, and $\hat{u}, \, \hat{\sigma}$ are the numerical fluxes. Substituting the integrals of form

\[ \int_\Gamma q \boldsymbol{\phi} \cdot \boldsymbol{n} \,\mathrm{d}\Gamma = \int_\Gamma \llbracket q\rrbracket \cdot \{\boldsymbol{\phi}\} \,\mathrm{d}\Gamma + \int_{\Gamma^0} \{q\} \llbracket \boldsymbol{\phi}\rrbracket \,\mathrm{d}\Gamma^0,\]

where $\Gamma^0 : \Gamma \setminus \partial \Omega$, and the jump of the vector-valued field $\boldsymbol{\phi}$ is defined as

\[ \llbracket \boldsymbol{\phi}\rrbracket = \boldsymbol{\phi}^+ \cdot \boldsymbol{n}^+ + \boldsymbol{\phi}^- \cdot \boldsymbol{n}^-\\\]

with the jumps and averages results in

\[ \int_\Omega \boldsymbol{\sigma} \cdot \boldsymbol{\tau} \,\mathrm{d}\Omega = -\int_\Omega u (\boldsymbol{\nabla} \cdot \boldsymbol{\tau}) \,\mathrm{d}\Omega + \int_\Gamma \llbracket \hat{u}\rrbracket \cdot \{\boldsymbol{\tau}\} \,\mathrm{d}\Gamma + \int_{\Gamma^0} \{\hat{u}\} \llbracket \boldsymbol{\tau}\rrbracket \,\mathrm{d}\Gamma^0,\\ + \int_\Omega \boldsymbol{\sigma} \cdot [\boldsymbol{\nabla} (\delta u)] \,\mathrm{d}\Omega = \int_\Omega \delta u \,\mathrm{d}\Omega + \int_\Gamma \llbracket \delta u\rrbracket \cdot \{\hat{\boldsymbol{\sigma}}\} \,\mathrm{d}\Gamma + \int_{\Gamma^0} \{\delta u\} \llbracket \hat{\boldsymbol{\sigma}}\rrbracket \,\mathrm{d}\Gamma^0,\]

Integrating $ \int_\Omega [\boldsymbol{\nabla} (u)] \cdot \boldsymbol{\tau} \,\mathrm{d}\Omega $ by parts and applying divergence theorem without using numerical flux, then substitute in the equation to obtain a weak form.

\[ \int_\Omega \boldsymbol{\sigma} \cdot \boldsymbol{\tau} \,\mathrm{d}\Omega = \int_\Omega [\boldsymbol{\nabla} (u)] \cdot \boldsymbol{\tau} \,\mathrm{d}\Omega + \int_\Gamma \llbracket \hat{u} - u\rrbracket \cdot \{\boldsymbol{\tau}\} \,\mathrm{d}\Gamma + \int_{\Gamma^0} \{\hat{u} - u\} \llbracket \boldsymbol{\tau}\rrbracket \,\mathrm{d}\Gamma^0,\\ + \int_\Omega \boldsymbol{\sigma} \cdot [\boldsymbol{\nabla} (\delta u)] \,\mathrm{d}\Omega = \int_\Omega \delta u \,\mathrm{d}\Omega + \int_\Gamma \llbracket \delta u\rrbracket \cdot \{\hat{\boldsymbol{\sigma}}\} \,\mathrm{d}\Gamma + \int_{\Gamma^0} \{\delta u\} \llbracket \hat{\boldsymbol{\sigma}}\rrbracket \,\mathrm{d}\Gamma^0,\]

Substituting

\[ \boldsymbol{\tau} = \boldsymbol{\nabla} (\delta u),\\\]

results in

\[ \int_\Omega \boldsymbol{\sigma} \cdot [\boldsymbol{\nabla} (\delta u)] \,\mathrm{d}\Omega = \int_\Omega [\boldsymbol{\nabla} (u)] \cdot [\boldsymbol{\nabla} (\delta u)] \,\mathrm{d}\Omega + \int_\Gamma \llbracket \hat{u} - u\rrbracket \cdot \{\boldsymbol{\nabla} (\delta u)\} \,\mathrm{d}\Gamma + \int_{\Gamma^0} \{\hat{u} - u\} \llbracket \boldsymbol{\nabla} (\delta u)\rrbracket \,\mathrm{d}\Gamma^0,\\ + \int_\Omega \boldsymbol{\sigma} \cdot [\boldsymbol{\nabla} (\delta u)] \,\mathrm{d}\Omega = \int_\Omega \delta u \,\mathrm{d}\Omega + \int_\Gamma \llbracket \delta u\rrbracket \cdot \{\hat{\boldsymbol{\sigma}}\} \,\mathrm{d}\Gamma + \int_{\Gamma^0} \{\delta u\} \llbracket \hat{\boldsymbol{\sigma}}\rrbracket \,\mathrm{d}\Gamma^0,\]

Combining the two equations,

\[ \int_\Omega [\boldsymbol{\nabla} (u)] \cdot [\boldsymbol{\nabla} (\delta u)] \,\mathrm{d}\Omega + \int_\Gamma \llbracket \hat{u} - u\rrbracket \cdot \{\boldsymbol{\nabla} (\delta u)\} \,\mathrm{d}\Gamma + \int_{\Gamma^0} \{\hat{u} - u\} \llbracket \boldsymbol{\nabla} (\delta u)\rrbracket \,\mathrm{d}\Gamma^0 - \int_\Gamma \llbracket \delta u\rrbracket \cdot \{\hat{\boldsymbol{\sigma}}\} \,\mathrm{d}\Gamma - \int_{\Gamma^0} \{\delta u\} \llbracket \hat{\boldsymbol{\sigma}}\rrbracket \,\mathrm{d}\Gamma^0 = \int_\Omega \delta u \,\mathrm{d}\Omega,\\\]

The numerical fluxes chosen for the interior penalty method are $\boldsymbol{\hat{\sigma}} = \{\boldsymbol{\nabla} (u)\} - \alpha(\llbracket u\rrbracket)$ on $\Gamma$, $\hat{u} = \{u\}$ on the interfaces between elements $\Gamma^0 : \Gamma \setminus \partial \Omega$, and $\hat{u} = 0$ on $\partial \Omega$. Such choice results in $\{\hat{\boldsymbol{\sigma}}\} = \{\boldsymbol{\nabla} (u)\} - \alpha(\llbracket u\rrbracket)$, $\llbracket \hat{u}\rrbracket = 0$, $\{\hat{u}\} = \{u\}$, $\llbracket \hat{\boldsymbol{\sigma}}\rrbracket = 0$ and the equation becomes

\[ \int_\Omega [\boldsymbol{\nabla} (u)] \cdot [\boldsymbol{\nabla} (\delta u)] \,\mathrm{d}\Omega - \int_\Gamma \llbracket u\rrbracket \cdot \{\boldsymbol{\nabla} (\delta u)\} \,\mathrm{d}\Gamma - \int_\Gamma \llbracket \delta u\rrbracket \cdot \{\boldsymbol{\nabla} (u)\} - \llbracket \delta u\rrbracket \cdot \alpha(\llbracket u\rrbracket) \,\mathrm{d}\Gamma = \int_\Omega \delta u \,\mathrm{d}\Omega,\\\]

Where

\[ \alpha(\llbracket u\rrbracket) = \mu \llbracket u\rrbracket\]

Where $\mu = \eta h_e^{-1}$, the weak form becomes

\[ \int_\Omega [\boldsymbol{\nabla} (u)] \cdot [\boldsymbol{\nabla}] (\delta u) \,\mathrm{d}\Omega - \int_\Gamma \llbracket u \rrbracket \cdot \{\boldsymbol{\nabla} (\delta u)\} + \llbracket \delta u \rrbracket \cdot \{\boldsymbol{\nabla} (u)\} \,\mathrm{d}\Gamma + \int_\Gamma \frac{\eta}{h_e} \llbracket u\rrbracket \cdot \llbracket \delta u\rrbracket \,\mathrm{d}\Gamma = \int_\Omega \delta u \,\mathrm{d}\Omega,\\\]

Since $\partial \Omega$ is constrained with both Dirichlet and Neumann boundary conditions the term $\int_{\partial \Omega} [\boldsymbol{\nabla} (u)] \cdot \boldsymbol{n} \delta u \,\mathrm{d} \Omega$ can be expressed as an integral over $\partial \Omega_N$, where $\partial \Omega_N$ is the boundaries with only prescribed Neumann boundary condition, The resulting weak form is given given as follows: Find $u \in \mathbb{U}$ such that

\[ \int_\Omega [\boldsymbol{\nabla} (u)] \cdot [\boldsymbol{\nabla} (\delta u)] \,\mathrm{d}\Omega - \int_{\Gamma^0} \llbracket u\rrbracket \cdot \{\boldsymbol{\nabla} (\delta u)\} + \llbracket \delta u\rrbracket \cdot \{\boldsymbol{\nabla} (u)\} \,\mathrm{d}\Gamma^0 + \int_{\Gamma^0} \frac{\eta}{h_e} \llbracket u\rrbracket \cdot \llbracket \delta u\rrbracket \,\mathrm{d}\Gamma^0 = \int_\Omega \delta u \,\mathrm{d}\Omega + \int_{\partial \Omega_N} ([\boldsymbol{\nabla} (u)] \cdot \boldsymbol{n}) \delta u \,\mathrm{d} \partial \Omega_N,\\\]

where $h_e$ is the characteristic size (the diameter of the interface), and $\eta$ is a large enough positive number independent of $h_e$ [5], $\delta u \in \mathbb{T}$ is a test function, and where $\mathbb{U}$ and $\mathbb{T}$ are suitable trial and test function sets, respectively. We use the value $\eta = (1 + O)^{D}$, where $O$ is the polynomial order and $D$ the dimension, in this tutorial.

More details on DG formulations for elliptic problems can be found in [6].

Commented Program

Now we solve the problem in Ferrite. What follows is a program spliced with comments. The full program, without comments, can be found in the next section.

First we load Ferrite and other packages, and generate grid just like the heat equation tutorial

using Ferrite, SparseArrays
+dim = 2;
+grid = generate_grid(Quadrilateral, ntuple(_ -> 20, dim));

We construct the topology information which is used later for generating the sparsity pattern for stiffness matrix.

topology = ExclusiveTopology(grid);

Trial and test functions

CellValues, FacetValues, and InterfaceValues facilitate the process of evaluating values and gradients of test and trial functions (among other things). To define these we need to specify an interpolation space for the shape functions. We use DiscontinuousLagrange functions based on the two-dimensional reference quadrilateral. We also define a quadrature rule based on the same reference element. We combine the interpolation and the quadrature rule to CellValues and InterfaceValues object. Note that InterfaceValues object contains two FacetValues objects which can be used individually.

order = 1;
+ip = DiscontinuousLagrange{RefQuadrilateral, order}();
+qr = QuadratureRule{RefQuadrilateral}(2);

For FacetValues and InterfaceValues we use FacetQuadratureRule

facet_qr = FacetQuadratureRule{RefQuadrilateral}(2);
+cellvalues = CellValues(qr, ip);
+facetvalues = FacetValues(facet_qr, ip);
+interfacevalues = InterfaceValues(facet_qr, ip);

Penalty term parameters

We define functions to calculate the diameter of a set of points, used to calculate the characteristic size $h_e$ in the assembly routine.

getdistance(p1::Vec{N, T}, p2::Vec{N, T}) where {N, T} = norm(p1 - p2);
+getdiameter(cell_coords::Vector{Vec{N, T}}) where {N, T} = maximum(getdistance.(cell_coords, reshape(cell_coords, (1, :))));

Degrees of freedom

Degrees of freedom distribution is handled using DofHandler as usual

dh = DofHandler(grid)
+add!(dh, :u, ip)
+close!(dh);

However, when generating the sparsity pattern we need to pass the topology and the cross-element coupling matrix when we're using discontinuous interpolations. The cross-element coupling matrix is of size [1,1] in this case as we have only one field and one DofHandler.

K = allocate_matrix(dh, topology = topology, interface_coupling = trues(1, 1));

Boundary conditions

The Dirichlet boundary conditions are treated as usual by a ConstraintHandler.

ch = ConstraintHandler(dh)
+add!(ch, Dirichlet(:u, getfacetset(grid, "right"), (x, t) -> 1.0))
+add!(ch, Dirichlet(:u, getfacetset(grid, "left"), (x, t) -> -1.0))
+close!(ch);

Furthermore, we define $\partial \Omega_N$ as the union of the facet sets with Neumann boundary conditions for later use

∂Ωₙ = union(
+    getfacetset(grid, "top"),
+    getfacetset(grid, "bottom"),
+);

Assembling the linear system

Now we have all the pieces needed to assemble the linear system, $K u = f$. Assembling of the global system is done by looping over i) all the elements in order to compute the element contributions $K_e$ and $f_e$, ii) all the interfaces to compute their contributions $K_i$, and iii) all the Neumann boundary facets to compute their contributions $f_e$. All these local contributions are then assembled into the appropriate place in the global $K$ and $f$.

Local assembly

We define the functions

  • assemble_element! to compute the contributions $K_e$ and $f_e$ of volume integrals over an element using cellvalues.
  • assemble_interface! to compute the contribution $K_i$ of surface integrals over an interface using interfacevalues.
  • assemble_boundary! to compute the contribution $f_e$ of surface integrals over a boundary facet using FacetValues.
function assemble_element!(Ke::Matrix, fe::Vector, cellvalues::CellValues)
+    n_basefuncs = getnbasefunctions(cellvalues)
+    # Reset to 0
+    fill!(Ke, 0)
+    fill!(fe, 0)
+    # Loop over quadrature points
+    for q_point in 1:getnquadpoints(cellvalues)
+        # Quadrature weight
+        dΩ = getdetJdV(cellvalues, q_point)
+        # Loop over test shape functions
+        for i in 1:n_basefuncs
+            δu = shape_value(cellvalues, q_point, i)
+            ∇δu = shape_gradient(cellvalues, q_point, i)
+            # Add contribution to fe
+            fe[i] += δu * dΩ
+            # Loop over trial shape functions
+            for j in 1:n_basefuncs
+                ∇u = shape_gradient(cellvalues, q_point, j)
+                # Add contribution to Ke
+                Ke[i, j] += (∇δu ⋅ ∇u) * dΩ
+            end
+        end
+    end
+    return Ke, fe
+end
+
+function assemble_interface!(Ki::Matrix, iv::InterfaceValues, μ::Float64)
+    # Reset to 0
+    fill!(Ki, 0)
+    # Loop over quadrature points
+    for q_point in 1:getnquadpoints(iv)
+        # Get the normal to facet A
+        normal = getnormal(iv, q_point)
+        # Get the quadrature weight
+        dΓ = getdetJdV(iv, q_point)
+        # Loop over test shape functions
+        for i in 1:getnbasefunctions(iv)
+            # Multiply the jump by the negative normal to get the definition from the theory section.
+            δu_jump = shape_value_jump(iv, q_point, i) * (-normal)
+            ∇δu_avg = shape_gradient_average(iv, q_point, i)
+            # Loop over trial shape functions
+            for j in 1:getnbasefunctions(iv)
+                # Multiply the jump by the negative normal to get the definition from the theory section.
+                u_jump = shape_value_jump(iv, q_point, j) * (-normal)
+                ∇u_avg = shape_gradient_average(iv, q_point, j)
+                # Add contribution to Ki
+                Ki[i, j] += -(δu_jump ⋅ ∇u_avg + ∇δu_avg ⋅ u_jump) * dΓ + μ * (δu_jump ⋅ u_jump) * dΓ
+            end
+        end
+    end
+    return Ki
+end
+
+function assemble_boundary!(fe::Vector, fv::FacetValues)
+    # Reset to 0
+    fill!(fe, 0)
+    # Loop over quadrature points
+    for q_point in 1:getnquadpoints(fv)
+        # Get the normal to facet A
+        normal = getnormal(fv, q_point)
+        # Get the quadrature weight
+        ∂Ω = getdetJdV(fv, q_point)
+        # Loop over test shape functions
+        for i in 1:getnbasefunctions(fv)
+            δu = shape_value(fv, q_point, i)
+            boundary_flux = normal[2]
+            fe[i] = boundary_flux * δu * ∂Ω
+        end
+    end
+    return fe
+end

Global assembly

We define the function assemble_global to loop over all elements and internal facets (interfaces), as well as the external facets involved in Neumann boundary conditions.

function assemble_global(cellvalues::CellValues, facetvalues::FacetValues, interfacevalues::InterfaceValues, K::SparseMatrixCSC, dh::DofHandler, order::Int, dim::Int)
+    # Allocate the element stiffness matrix and element force vector
+    n_basefuncs = getnbasefunctions(cellvalues)
+    Ke = zeros(n_basefuncs, n_basefuncs)
+    fe = zeros(n_basefuncs)
+    Ki = zeros(n_basefuncs * 2, n_basefuncs * 2)
+    # Allocate global force vector f
+    f = zeros(ndofs(dh))
+    # Create an assembler
+    assembler = start_assemble(K, f)
+    # Loop over all cells
+    for cell in CellIterator(dh)
+        # Reinitialize cellvalues for this cell
+        reinit!(cellvalues, cell)
+        # Compute volume integral contribution
+        assemble_element!(Ke, fe, cellvalues)
+        # Assemble Ke and fe into K and f
+        assemble!(assembler, celldofs(cell), Ke, fe)
+    end
+    # Loop over all interfaces
+    for ic in InterfaceIterator(dh)
+        # Reinitialize interfacevalues for this interface
+        reinit!(interfacevalues, ic)
+        # Calculate the characteristic size hₑ as the face diameter
+        interfacecoords = ∩(getcoordinates(ic)...)
+        hₑ = getdiameter(interfacecoords)
+        # Calculate μ
+        μ = (1 + order)^dim / hₑ
+        # Compute interface surface integrals contribution
+        assemble_interface!(Ki, interfacevalues, μ)
+        # Assemble Ki into K
+        assemble!(assembler, interfacedofs(ic), Ki)
+    end
+    # Loop over domain boundaries with Neumann boundary conditions
+    for fc in FacetIterator(dh, ∂Ωₙ)
+        # Reinitialize facetvalues for this boundary facet
+        reinit!(facetvalues, fc)
+        # Compute boundary facet surface integrals contribution
+        assemble_boundary!(fe, facetvalues)
+        # Assemble fe into f
+        assemble!(f, celldofs(fc), fe)
+    end
+    return K, f
+end
+K, f = assemble_global(cellvalues, facetvalues, interfacevalues, K, dh, order, dim);

Solution of the system

The solution of the system is independent of the discontinuous discretization and the application of constraints, linear solve, and exporting is done as usual.

apply!(K, f, ch)
+u = K \ f;
+VTKGridFile("dg_heat_equation", dh) do vtk
+    write_solution(vtk, dh, u)
+end;

References

[5]
[6]
D. N. Arnold, F. Brezzi, B. Cockburn and L. D. Marini. Unified Analysis of Discontinuous Galerkin Methods for Elliptic Problems. SIAM Journal on Numerical Analysis 39, 1749–1779 (2002). Accessed on Dec 20, 2023.

Plain program

Here follows a version of the program without any comments. The file is also available here: dg_heat_equation.jl.

using Ferrite, SparseArrays
+dim = 2;
+grid = generate_grid(Quadrilateral, ntuple(_ -> 20, dim));
+
+topology = ExclusiveTopology(grid);
+
+order = 1;
+ip = DiscontinuousLagrange{RefQuadrilateral, order}();
+qr = QuadratureRule{RefQuadrilateral}(2);
+
+facet_qr = FacetQuadratureRule{RefQuadrilateral}(2);
+cellvalues = CellValues(qr, ip);
+facetvalues = FacetValues(facet_qr, ip);
+interfacevalues = InterfaceValues(facet_qr, ip);
+
+getdistance(p1::Vec{N, T}, p2::Vec{N, T}) where {N, T} = norm(p1 - p2);
+getdiameter(cell_coords::Vector{Vec{N, T}}) where {N, T} = maximum(getdistance.(cell_coords, reshape(cell_coords, (1, :))));
+
+dh = DofHandler(grid)
+add!(dh, :u, ip)
+close!(dh);
+
+K = allocate_matrix(dh, topology = topology, interface_coupling = trues(1, 1));
+
+ch = ConstraintHandler(dh)
+add!(ch, Dirichlet(:u, getfacetset(grid, "right"), (x, t) -> 1.0))
+add!(ch, Dirichlet(:u, getfacetset(grid, "left"), (x, t) -> -1.0))
+close!(ch);
+
+∂Ωₙ = union(
+    getfacetset(grid, "top"),
+    getfacetset(grid, "bottom"),
+);
+
+function assemble_element!(Ke::Matrix, fe::Vector, cellvalues::CellValues)
+    n_basefuncs = getnbasefunctions(cellvalues)
+    # Reset to 0
+    fill!(Ke, 0)
+    fill!(fe, 0)
+    # Loop over quadrature points
+    for q_point in 1:getnquadpoints(cellvalues)
+        # Quadrature weight
+        dΩ = getdetJdV(cellvalues, q_point)
+        # Loop over test shape functions
+        for i in 1:n_basefuncs
+            δu = shape_value(cellvalues, q_point, i)
+            ∇δu = shape_gradient(cellvalues, q_point, i)
+            # Add contribution to fe
+            fe[i] += δu * dΩ
+            # Loop over trial shape functions
+            for j in 1:n_basefuncs
+                ∇u = shape_gradient(cellvalues, q_point, j)
+                # Add contribution to Ke
+                Ke[i, j] += (∇δu ⋅ ∇u) * dΩ
+            end
+        end
+    end
+    return Ke, fe
+end
+
+function assemble_interface!(Ki::Matrix, iv::InterfaceValues, μ::Float64)
+    # Reset to 0
+    fill!(Ki, 0)
+    # Loop over quadrature points
+    for q_point in 1:getnquadpoints(iv)
+        # Get the normal to facet A
+        normal = getnormal(iv, q_point)
+        # Get the quadrature weight
+        dΓ = getdetJdV(iv, q_point)
+        # Loop over test shape functions
+        for i in 1:getnbasefunctions(iv)
+            # Multiply the jump by the negative normal to get the definition from the theory section.
+            δu_jump = shape_value_jump(iv, q_point, i) * (-normal)
+            ∇δu_avg = shape_gradient_average(iv, q_point, i)
+            # Loop over trial shape functions
+            for j in 1:getnbasefunctions(iv)
+                # Multiply the jump by the negative normal to get the definition from the theory section.
+                u_jump = shape_value_jump(iv, q_point, j) * (-normal)
+                ∇u_avg = shape_gradient_average(iv, q_point, j)
+                # Add contribution to Ki
+                Ki[i, j] += -(δu_jump ⋅ ∇u_avg + ∇δu_avg ⋅ u_jump) * dΓ + μ * (δu_jump ⋅ u_jump) * dΓ
+            end
+        end
+    end
+    return Ki
+end
+
+function assemble_boundary!(fe::Vector, fv::FacetValues)
+    # Reset to 0
+    fill!(fe, 0)
+    # Loop over quadrature points
+    for q_point in 1:getnquadpoints(fv)
+        # Get the normal to facet A
+        normal = getnormal(fv, q_point)
+        # Get the quadrature weight
+        ∂Ω = getdetJdV(fv, q_point)
+        # Loop over test shape functions
+        for i in 1:getnbasefunctions(fv)
+            δu = shape_value(fv, q_point, i)
+            boundary_flux = normal[2]
+            fe[i] = boundary_flux * δu * ∂Ω
+        end
+    end
+    return fe
+end
+
+function assemble_global(cellvalues::CellValues, facetvalues::FacetValues, interfacevalues::InterfaceValues, K::SparseMatrixCSC, dh::DofHandler, order::Int, dim::Int)
+    # Allocate the element stiffness matrix and element force vector
+    n_basefuncs = getnbasefunctions(cellvalues)
+    Ke = zeros(n_basefuncs, n_basefuncs)
+    fe = zeros(n_basefuncs)
+    Ki = zeros(n_basefuncs * 2, n_basefuncs * 2)
+    # Allocate global force vector f
+    f = zeros(ndofs(dh))
+    # Create an assembler
+    assembler = start_assemble(K, f)
+    # Loop over all cells
+    for cell in CellIterator(dh)
+        # Reinitialize cellvalues for this cell
+        reinit!(cellvalues, cell)
+        # Compute volume integral contribution
+        assemble_element!(Ke, fe, cellvalues)
+        # Assemble Ke and fe into K and f
+        assemble!(assembler, celldofs(cell), Ke, fe)
+    end
+    # Loop over all interfaces
+    for ic in InterfaceIterator(dh)
+        # Reinitialize interfacevalues for this interface
+        reinit!(interfacevalues, ic)
+        # Calculate the characteristic size hₑ as the face diameter
+        interfacecoords = ∩(getcoordinates(ic)...)
+        hₑ = getdiameter(interfacecoords)
+        # Calculate μ
+        μ = (1 + order)^dim / hₑ
+        # Compute interface surface integrals contribution
+        assemble_interface!(Ki, interfacevalues, μ)
+        # Assemble Ki into K
+        assemble!(assembler, interfacedofs(ic), Ki)
+    end
+    # Loop over domain boundaries with Neumann boundary conditions
+    for fc in FacetIterator(dh, ∂Ωₙ)
+        # Reinitialize facetvalues for this boundary facet
+        reinit!(facetvalues, fc)
+        # Compute boundary facet surface integrals contribution
+        assemble_boundary!(fe, facetvalues)
+        # Assemble fe into f
+        assemble!(f, celldofs(fc), fe)
+    end
+    return K, f
+end
+K, f = assemble_global(cellvalues, facetvalues, interfacevalues, K, dh, order, dim);
+
+apply!(K, f, ch)
+u = K \ f;
+VTKGridFile("dg_heat_equation", dh) do vtk
+    write_solution(vtk, dh, u)
+end;

This page was generated using Literate.jl.

diff --git a/previews/PR798/tutorials/heat_equation.ipynb b/previews/PR798/tutorials/heat_equation.ipynb new file mode 100644 index 0000000000..730a221a1a --- /dev/null +++ b/previews/PR798/tutorials/heat_equation.ipynb @@ -0,0 +1,481 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Heat equation\n", + "\n", + "![](heat_square.png)\n", + "\n", + "*Figure 1*: Temperature field on the unit square with an internal uniform heat source\n", + "solved with homogeneous Dirichlet boundary conditions on the boundary." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Introduction\n", + "\n", + "The heat equation is the \"Hello, world!\" equation of finite elements.\n", + "Here we solve the equation on a unit square, with a uniform internal source.\n", + "The strong form of the (linear) heat equation is given by\n", + "\n", + "$$\n", + " -\\nabla \\cdot (k \\nabla u) = f \\quad \\textbf{x} \\in \\Omega,\n", + "$$\n", + "\n", + "where $u$ is the unknown temperature field, $k$ the heat conductivity,\n", + "$f$ the heat source and $\\Omega$ the domain. For simplicity we set $f = 1$\n", + "and $k = 1$. We will consider homogeneous Dirichlet boundary conditions such that\n", + "$$\n", + "u(\\textbf{x}) = 0 \\quad \\textbf{x} \\in \\partial \\Omega,\n", + "$$\n", + "where $\\partial \\Omega$ denotes the boundary of $\\Omega$.\n", + "The resulting weak form is given given as follows: Find $u \\in \\mathbb{U}$ such that\n", + "$$\n", + "\\int_{\\Omega} \\nabla \\delta u \\cdot \\nabla u \\ d\\Omega = \\int_{\\Omega} \\delta u \\ d\\Omega \\quad \\forall \\delta u \\in \\mathbb{T},\n", + "$$\n", + "where $\\delta u$ is a test function, and where $\\mathbb{U}$ and $\\mathbb{T}$ are suitable\n", + "trial and test function sets, respectively." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Commented Program\n", + "\n", + "Now we solve the problem in Ferrite. What follows is a program spliced with comments.\n", + "\n", + "First we load Ferrite, and some other packages we need" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using Ferrite, SparseArrays" + ], + "metadata": {}, + "execution_count": 1 + }, + { + "cell_type": "markdown", + "source": [ + "We start by generating a simple grid with 20x20 quadrilateral elements\n", + "using `generate_grid`. The generator defaults to the unit square,\n", + "so we don't need to specify the corners of the domain." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "grid = generate_grid(Quadrilateral, (20, 20));" + ], + "metadata": {}, + "execution_count": 2 + }, + { + "cell_type": "markdown", + "source": [ + "### Trial and test functions\n", + "A `CellValues` facilitates the process of evaluating values and gradients of\n", + "test and trial functions (among other things). To define\n", + "this we need to specify an interpolation space for the shape functions.\n", + "We use Lagrange functions\n", + "based on the two-dimensional reference quadrilateral. We also define a quadrature rule based on\n", + "the same reference element. We combine the interpolation and the quadrature rule\n", + "to a `CellValues` object." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "ip = Lagrange{RefQuadrilateral, 1}()\n", + "qr = QuadratureRule{RefQuadrilateral}(2)\n", + "cellvalues = CellValues(qr, ip);" + ], + "metadata": {}, + "execution_count": 3 + }, + { + "cell_type": "markdown", + "source": [ + "### Degrees of freedom\n", + "Next we need to define a `DofHandler`, which will take care of numbering\n", + "and distribution of degrees of freedom for our approximated fields.\n", + "We create the `DofHandler` and then add a single scalar field called `:u` based on\n", + "our interpolation `ip` defined above.\n", + "Lastly we `close!` the `DofHandler`, it is now that the dofs are distributed\n", + "for all the elements." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "dh = DofHandler(grid)\n", + "add!(dh, :u, ip)\n", + "close!(dh);" + ], + "metadata": {}, + "execution_count": 4 + }, + { + "cell_type": "markdown", + "source": [ + "Now that we have distributed all our dofs we can create our tangent matrix,\n", + "using `allocate_matrix`. This function returns a sparse matrix\n", + "with the correct entries stored." + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "441×441 SparseMatrixCSC{Float64, Int64} with 3721 stored entries:\n⎡⠻⣦⡀⠀⢧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎤\n⎢⠀⠈⠻⣦⠈⢧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥\n⎢⠉⠓⠦⣄⡻⣮⡳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥\n⎢⠀⠀⠀⠀⠙⢮⡻⣮⡳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥\n⎢⠀⠀⠀⠀⠀⠀⠙⢮⡻⣮⡳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥\n⎢⠀⠀⠀⠀⠀⠀⠀⠀⠙⢮⡻⢎⡳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥\n⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢮⡻⣮⡳⣤⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥\n⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⣮⡻⣮⡳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥\n⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢮⡻⣮⡳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥\n⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢮⡻⣮⡳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥\n⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢮⡻⣮⡳⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥\n⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠹⢮⡻⣮⡳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥\n⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢮⡻⣮⡳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥\n⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢮⡻⣮⡳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥\n⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢮⡱⣮⡳⣄⠀⠀⠀⠀⠀⠀⠀⠀⎥\n⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢮⡻⣮⡳⣄⠀⠀⠀⠀⠀⠀⎥\n⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢮⡻⣮⡳⣄⠀⠀⠀⠀⎥\n⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢮⡻⣮⠳⣄⠀⠀⎥\n⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⡻⣮⡳⣄⎥\n⎣⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢮⠻⣦⎦" + }, + "metadata": {}, + "execution_count": 5 + } + ], + "cell_type": "code", + "source": [ + "K = allocate_matrix(dh)" + ], + "metadata": {}, + "execution_count": 5 + }, + { + "cell_type": "markdown", + "source": [ + "### Boundary conditions\n", + "In Ferrite constraints like Dirichlet boundary conditions\n", + "are handled by a `ConstraintHandler`." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "ch = ConstraintHandler(dh);" + ], + "metadata": {}, + "execution_count": 6 + }, + { + "cell_type": "markdown", + "source": [ + "Next we need to add constraints to `ch`. For this problem we define\n", + "homogeneous Dirichlet boundary conditions on the whole boundary, i.e.\n", + "the `union` of all the facet sets on the boundary." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "∂Ω = union(\n", + " getfacetset(grid, \"left\"),\n", + " getfacetset(grid, \"right\"),\n", + " getfacetset(grid, \"top\"),\n", + " getfacetset(grid, \"bottom\"),\n", + ");" + ], + "metadata": {}, + "execution_count": 7 + }, + { + "cell_type": "markdown", + "source": [ + "Now we are set up to define our constraint. We specify which field\n", + "the condition is for, and our combined facet set `∂Ω`. The last\n", + "argument is a function of the form $f(\\textbf{x})$ or $f(\\textbf{x}, t)$,\n", + "where $\\textbf{x}$ is the spatial coordinate and\n", + "$t$ the current time, and returns the prescribed value. Since the boundary condition in\n", + "this case do not depend on time we define our function as $f(\\textbf{x}) = 0$, i.e.\n", + "no matter what $\\textbf{x}$ we return $0$. When we have\n", + "specified our constraint we `add!` it to `ch`." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "dbc = Dirichlet(:u, ∂Ω, (x, t) -> 0)\n", + "add!(ch, dbc);" + ], + "metadata": {}, + "execution_count": 8 + }, + { + "cell_type": "markdown", + "source": [ + "Finally we also need to `close!` our constraint handler. When we call `close!`\n", + "the dofs corresponding to our constraints are calculated and stored\n", + "in our `ch` object." + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "ConstraintHandler:\n BCs:\n Field: u, Components: [1]" + }, + "metadata": {}, + "execution_count": 9 + } + ], + "cell_type": "code", + "source": [ + "close!(ch)" + ], + "metadata": {}, + "execution_count": 9 + }, + { + "cell_type": "markdown", + "source": [ + "Note that if one or more of the constraints are time dependent we would use\n", + "`update!` to recompute prescribed values in each new timestep." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "### Assembling the linear system\n", + "\n", + "Now we have all the pieces needed to assemble the linear system, $K u = f$.\n", + "Assembling of the global system is done by looping over all the elements in order to\n", + "compute the element contributions $K_e$ and $f_e$, which are then assembled to the\n", + "appropriate place in the global $K$ and $f$.\n", + "\n", + "#### Element assembly\n", + "We define the function `assemble_element!` (see below) which computes the contribution for\n", + "an element. The function takes pre-allocated `ke` and `fe` (they are allocated once and\n", + "then reused for all elements) so we first need to make sure that they are all zeroes at\n", + "the start of the function by using `fill!`. Then we loop over all the quadrature points,\n", + "and for each quadrature point we loop over all the (local) shape functions. We need the\n", + "value and gradient of the test function, `δu` and also the gradient of the trial function\n", + "`u`. We get all of these from `cellvalues`.\n", + "\n", + "> **Notation**\n", + ">\n", + "> Comparing with the brief finite element introduction in Introduction to FEM,\n", + "> the variables `δu`, `∇δu` and `∇u` are actually $\\phi_i(\\textbf{x}_q)$, $\\nabla\n", + "> \\phi_i(\\textbf{x}_q)$ and $\\nabla \\phi_j(\\textbf{x}_q)$, i.e. the evaluation of the\n", + "> trial and test functions in the quadrature point $\\textbf{x}_q$. However, to\n", + "> underline the strong parallel between the weak form and the implementation, this\n", + "> example uses the symbols appearing in the weak form." + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "assemble_element! (generic function with 1 method)" + }, + "metadata": {}, + "execution_count": 10 + } + ], + "cell_type": "code", + "source": [ + "function assemble_element!(Ke::Matrix, fe::Vector, cellvalues::CellValues)\n", + " n_basefuncs = getnbasefunctions(cellvalues)\n", + " # Reset to 0\n", + " fill!(Ke, 0)\n", + " fill!(fe, 0)\n", + " # Loop over quadrature points\n", + " for q_point in 1:getnquadpoints(cellvalues)\n", + " # Get the quadrature weight\n", + " dΩ = getdetJdV(cellvalues, q_point)\n", + " # Loop over test shape functions\n", + " for i in 1:n_basefuncs\n", + " δu = shape_value(cellvalues, q_point, i)\n", + " ∇δu = shape_gradient(cellvalues, q_point, i)\n", + " # Add contribution to fe\n", + " fe[i] += δu * dΩ\n", + " # Loop over trial shape functions\n", + " for j in 1:n_basefuncs\n", + " ∇u = shape_gradient(cellvalues, q_point, j)\n", + " # Add contribution to Ke\n", + " Ke[i, j] += (∇δu ⋅ ∇u) * dΩ\n", + " end\n", + " end\n", + " end\n", + " return Ke, fe\n", + "end" + ], + "metadata": {}, + "execution_count": 10 + }, + { + "cell_type": "markdown", + "source": [ + "#### Global assembly\n", + "We define the function `assemble_global` to loop over the elements and do the global\n", + "assembly. The function takes our `cellvalues`, the sparse matrix `K`, and our DofHandler\n", + "as input arguments and returns the assembled global stiffness matrix, and the assembled\n", + "global force vector. We start by allocating `Ke`, `fe`, and the global force vector `f`.\n", + "We also create an assembler by using `start_assemble`. The assembler lets us assemble into\n", + "`K` and `f` efficiently. We then start the loop over all the elements. In each loop\n", + "iteration we reinitialize `cellvalues` (to update derivatives of shape functions etc.),\n", + "compute the element contribution with `assemble_element!`, and then assemble into the\n", + "global `K` and `f` with `assemble!`.\n", + "\n", + "> **Notation**\n", + ">\n", + "> Comparing again with Introduction to FEM, `f` and `u` correspond to\n", + "> $\\underline{\\hat{f}}$ and $\\underline{\\hat{u}}$, since they represent the discretized\n", + "> versions. However, through the code we use `f` and `u` instead to reflect the strong\n", + "> connection between the weak form and the Ferrite implementation." + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "assemble_global (generic function with 1 method)" + }, + "metadata": {}, + "execution_count": 11 + } + ], + "cell_type": "code", + "source": [ + "function assemble_global(cellvalues::CellValues, K::SparseMatrixCSC, dh::DofHandler)\n", + " # Allocate the element stiffness matrix and element force vector\n", + " n_basefuncs = getnbasefunctions(cellvalues)\n", + " Ke = zeros(n_basefuncs, n_basefuncs)\n", + " fe = zeros(n_basefuncs)\n", + " # Allocate global force vector f\n", + " f = zeros(ndofs(dh))\n", + " # Create an assembler\n", + " assembler = start_assemble(K, f)\n", + " # Loop over all cels\n", + " for cell in CellIterator(dh)\n", + " # Reinitialize cellvalues for this cell\n", + " reinit!(cellvalues, cell)\n", + " # Compute element contribution\n", + " assemble_element!(Ke, fe, cellvalues)\n", + " # Assemble Ke and fe into K and f\n", + " assemble!(assembler, celldofs(cell), Ke, fe)\n", + " end\n", + " return K, f\n", + "end" + ], + "metadata": {}, + "execution_count": 11 + }, + { + "cell_type": "markdown", + "source": [ + "### Solution of the system\n", + "The last step is to solve the system. First we call `assemble_global`\n", + "to obtain the global stiffness matrix `K` and force vector `f`." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "K, f = assemble_global(cellvalues, K, dh);" + ], + "metadata": {}, + "execution_count": 12 + }, + { + "cell_type": "markdown", + "source": [ + "To account for the boundary conditions we use the `apply!` function.\n", + "This modifies elements in `K` and `f` respectively, such that\n", + "we can get the correct solution vector `u` by using `\\`." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "apply!(K, f, ch)\n", + "u = K \\ f;" + ], + "metadata": {}, + "execution_count": 13 + }, + { + "cell_type": "markdown", + "source": [ + "### Exporting to VTK\n", + "To visualize the result we export the grid and our field `u`\n", + "to a VTK-file, which can be viewed in e.g. [ParaView](https://www.paraview.org/)." + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "VTKGridFile for the closed file \"heat_equation.vtu\"." + }, + "metadata": {}, + "execution_count": 14 + } + ], + "cell_type": "code", + "source": [ + "VTKGridFile(\"heat_equation\", dh) do vtk\n", + " write_solution(vtk, dh, u)\n", + "end" + ], + "metadata": {}, + "execution_count": 14 + }, + { + "cell_type": "markdown", + "source": [ + "---\n", + "\n", + "*This notebook was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).*" + ], + "metadata": {} + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.1" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.1", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR798/tutorials/heat_equation.jl b/previews/PR798/tutorials/heat_equation.jl new file mode 100644 index 0000000000..6fb54b7e35 --- /dev/null +++ b/previews/PR798/tutorials/heat_equation.jl @@ -0,0 +1,85 @@ +using Ferrite, SparseArrays + +grid = generate_grid(Quadrilateral, (20, 20)); + +ip = Lagrange{RefQuadrilateral, 1}() +qr = QuadratureRule{RefQuadrilateral}(2) +cellvalues = CellValues(qr, ip); + +dh = DofHandler(grid) +add!(dh, :u, ip) +close!(dh); + +K = allocate_matrix(dh) + +ch = ConstraintHandler(dh); + +∂Ω = union( + getfacetset(grid, "left"), + getfacetset(grid, "right"), + getfacetset(grid, "top"), + getfacetset(grid, "bottom"), +); + +dbc = Dirichlet(:u, ∂Ω, (x, t) -> 0) +add!(ch, dbc); + +close!(ch) + +function assemble_element!(Ke::Matrix, fe::Vector, cellvalues::CellValues) + n_basefuncs = getnbasefunctions(cellvalues) + # Reset to 0 + fill!(Ke, 0) + fill!(fe, 0) + # Loop over quadrature points + for q_point in 1:getnquadpoints(cellvalues) + # Get the quadrature weight + dΩ = getdetJdV(cellvalues, q_point) + # Loop over test shape functions + for i in 1:n_basefuncs + δu = shape_value(cellvalues, q_point, i) + ∇δu = shape_gradient(cellvalues, q_point, i) + # Add contribution to fe + fe[i] += δu * dΩ + # Loop over trial shape functions + for j in 1:n_basefuncs + ∇u = shape_gradient(cellvalues, q_point, j) + # Add contribution to Ke + Ke[i, j] += (∇δu ⋅ ∇u) * dΩ + end + end + end + return Ke, fe +end + +function assemble_global(cellvalues::CellValues, K::SparseMatrixCSC, dh::DofHandler) + # Allocate the element stiffness matrix and element force vector + n_basefuncs = getnbasefunctions(cellvalues) + Ke = zeros(n_basefuncs, n_basefuncs) + fe = zeros(n_basefuncs) + # Allocate global force vector f + f = zeros(ndofs(dh)) + # Create an assembler + assembler = start_assemble(K, f) + # Loop over all cels + for cell in CellIterator(dh) + # Reinitialize cellvalues for this cell + reinit!(cellvalues, cell) + # Compute element contribution + assemble_element!(Ke, fe, cellvalues) + # Assemble Ke and fe into K and f + assemble!(assembler, celldofs(cell), Ke, fe) + end + return K, f +end + +K, f = assemble_global(cellvalues, K, dh); + +apply!(K, f, ch) +u = K \ f; + +VTKGridFile("heat_equation", dh) do vtk + write_solution(vtk, dh, u) +end + +# This file was generated using Literate.jl, https://github.com/fredrikekre/Literate.jl diff --git a/previews/PR798/tutorials/heat_equation/index.html b/previews/PR798/tutorials/heat_equation/index.html new file mode 100644 index 0000000000..3152d86f78 --- /dev/null +++ b/previews/PR798/tutorials/heat_equation/index.html @@ -0,0 +1,162 @@ + +Heat equation · Ferrite.jl

Heat equation

Figure 1: Temperature field on the unit square with an internal uniform heat source solved with homogeneous Dirichlet boundary conditions on the boundary.

Tip

This example is also available as a Jupyter notebook: heat_equation.ipynb.

Introduction

The heat equation is the "Hello, world!" equation of finite elements. Here we solve the equation on a unit square, with a uniform internal source. The strong form of the (linear) heat equation is given by

\[ -\nabla \cdot (k \nabla u) = f \quad \textbf{x} \in \Omega,\]

where $u$ is the unknown temperature field, $k$ the heat conductivity, $f$ the heat source and $\Omega$ the domain. For simplicity we set $f = 1$ and $k = 1$. We will consider homogeneous Dirichlet boundary conditions such that

\[u(\textbf{x}) = 0 \quad \textbf{x} \in \partial \Omega,\]

where $\partial \Omega$ denotes the boundary of $\Omega$. The resulting weak form is given given as follows: Find $u \in \mathbb{U}$ such that

\[\int_{\Omega} \nabla \delta u \cdot \nabla u \ d\Omega = \int_{\Omega} \delta u \ d\Omega \quad \forall \delta u \in \mathbb{T},\]

where $\delta u$ is a test function, and where $\mathbb{U}$ and $\mathbb{T}$ are suitable trial and test function sets, respectively.

Commented Program

Now we solve the problem in Ferrite. What follows is a program spliced with comments. The full program, without comments, can be found in the next section.

First we load Ferrite, and some other packages we need

using Ferrite, SparseArrays

We start by generating a simple grid with 20x20 quadrilateral elements using generate_grid. The generator defaults to the unit square, so we don't need to specify the corners of the domain.

grid = generate_grid(Quadrilateral, (20, 20));

Trial and test functions

A CellValues facilitates the process of evaluating values and gradients of test and trial functions (among other things). To define this we need to specify an interpolation space for the shape functions. We use Lagrange functions based on the two-dimensional reference quadrilateral. We also define a quadrature rule based on the same reference element. We combine the interpolation and the quadrature rule to a CellValues object.

ip = Lagrange{RefQuadrilateral, 1}()
+qr = QuadratureRule{RefQuadrilateral}(2)
+cellvalues = CellValues(qr, ip);

Degrees of freedom

Next we need to define a DofHandler, which will take care of numbering and distribution of degrees of freedom for our approximated fields. We create the DofHandler and then add a single scalar field called :u based on our interpolation ip defined above. Lastly we close! the DofHandler, it is now that the dofs are distributed for all the elements.

dh = DofHandler(grid)
+add!(dh, :u, ip)
+close!(dh);

Now that we have distributed all our dofs we can create our tangent matrix, using allocate_matrix. This function returns a sparse matrix with the correct entries stored.

K = allocate_matrix(dh)
441×441 SparseMatrixCSC{Float64, Int64} with 3721 stored entries:
+⎡⠻⣦⡀⠀⢧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎤
+⎢⠀⠈⠻⣦⠈⢧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
+⎢⠉⠓⠦⣄⡻⣮⡳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
+⎢⠀⠀⠀⠀⠙⢮⡻⣮⡳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
+⎢⠀⠀⠀⠀⠀⠀⠙⢮⡻⣮⡳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
+⎢⠀⠀⠀⠀⠀⠀⠀⠀⠙⢮⡻⢎⡳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
+⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢮⡻⣮⡳⣤⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
+⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⣮⡻⣮⡳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
+⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢮⡻⣮⡳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
+⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢮⡻⣮⡳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
+⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢮⡻⣮⡳⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
+⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠹⢮⡻⣮⡳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
+⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢮⡻⣮⡳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
+⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢮⡻⣮⡳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
+⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢮⡱⣮⡳⣄⠀⠀⠀⠀⠀⠀⠀⠀⎥
+⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢮⡻⣮⡳⣄⠀⠀⠀⠀⠀⠀⎥
+⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢮⡻⣮⡳⣄⠀⠀⠀⠀⎥
+⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢮⡻⣮⠳⣄⠀⠀⎥
+⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⡻⣮⡳⣄⎥
+⎣⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢮⠻⣦⎦

Boundary conditions

In Ferrite constraints like Dirichlet boundary conditions are handled by a ConstraintHandler.

ch = ConstraintHandler(dh);

Next we need to add constraints to ch. For this problem we define homogeneous Dirichlet boundary conditions on the whole boundary, i.e. the union of all the facet sets on the boundary.

∂Ω = union(
+    getfacetset(grid, "left"),
+    getfacetset(grid, "right"),
+    getfacetset(grid, "top"),
+    getfacetset(grid, "bottom"),
+);

Now we are set up to define our constraint. We specify which field the condition is for, and our combined facet set ∂Ω. The last argument is a function of the form $f(\textbf{x})$ or $f(\textbf{x}, t)$, where $\textbf{x}$ is the spatial coordinate and $t$ the current time, and returns the prescribed value. Since the boundary condition in this case do not depend on time we define our function as $f(\textbf{x}) = 0$, i.e. no matter what $\textbf{x}$ we return $0$. When we have specified our constraint we add! it to ch.

dbc = Dirichlet(:u, ∂Ω, (x, t) -> 0)
+add!(ch, dbc);

Finally we also need to close! our constraint handler. When we call close! the dofs corresponding to our constraints are calculated and stored in our ch object.

close!(ch)
ConstraintHandler:
+  BCs:
+    Field: u, Components: [1]

Note that if one or more of the constraints are time dependent we would use update! to recompute prescribed values in each new timestep.

Assembling the linear system

Now we have all the pieces needed to assemble the linear system, $K u = f$. Assembling of the global system is done by looping over all the elements in order to compute the element contributions $K_e$ and $f_e$, which are then assembled to the appropriate place in the global $K$ and $f$.

Element assembly

We define the function assemble_element! (see below) which computes the contribution for an element. The function takes pre-allocated ke and fe (they are allocated once and then reused for all elements) so we first need to make sure that they are all zeroes at the start of the function by using fill!. Then we loop over all the quadrature points, and for each quadrature point we loop over all the (local) shape functions. We need the value and gradient of the test function, δu and also the gradient of the trial function u. We get all of these from cellvalues.

Notation

Comparing with the brief finite element introduction in Introduction to FEM, the variables δu, ∇δu and ∇u are actually $\phi_i(\textbf{x}_q)$, $\nabla \phi_i(\textbf{x}_q)$ and $\nabla \phi_j(\textbf{x}_q)$, i.e. the evaluation of the trial and test functions in the quadrature point $\textbf{x}_q$. However, to underline the strong parallel between the weak form and the implementation, this example uses the symbols appearing in the weak form.

function assemble_element!(Ke::Matrix, fe::Vector, cellvalues::CellValues)
+    n_basefuncs = getnbasefunctions(cellvalues)
+    # Reset to 0
+    fill!(Ke, 0)
+    fill!(fe, 0)
+    # Loop over quadrature points
+    for q_point in 1:getnquadpoints(cellvalues)
+        # Get the quadrature weight
+        dΩ = getdetJdV(cellvalues, q_point)
+        # Loop over test shape functions
+        for i in 1:n_basefuncs
+            δu = shape_value(cellvalues, q_point, i)
+            ∇δu = shape_gradient(cellvalues, q_point, i)
+            # Add contribution to fe
+            fe[i] += δu * dΩ
+            # Loop over trial shape functions
+            for j in 1:n_basefuncs
+                ∇u = shape_gradient(cellvalues, q_point, j)
+                # Add contribution to Ke
+                Ke[i, j] += (∇δu ⋅ ∇u) * dΩ
+            end
+        end
+    end
+    return Ke, fe
+end

Global assembly

We define the function assemble_global to loop over the elements and do the global assembly. The function takes our cellvalues, the sparse matrix K, and our DofHandler as input arguments and returns the assembled global stiffness matrix, and the assembled global force vector. We start by allocating Ke, fe, and the global force vector f. We also create an assembler by using start_assemble. The assembler lets us assemble into K and f efficiently. We then start the loop over all the elements. In each loop iteration we reinitialize cellvalues (to update derivatives of shape functions etc.), compute the element contribution with assemble_element!, and then assemble into the global K and f with assemble!.

Notation

Comparing again with Introduction to FEM, f and u correspond to $\underline{\hat{f}}$ and $\underline{\hat{u}}$, since they represent the discretized versions. However, through the code we use f and u instead to reflect the strong connection between the weak form and the Ferrite implementation.

function assemble_global(cellvalues::CellValues, K::SparseMatrixCSC, dh::DofHandler)
+    # Allocate the element stiffness matrix and element force vector
+    n_basefuncs = getnbasefunctions(cellvalues)
+    Ke = zeros(n_basefuncs, n_basefuncs)
+    fe = zeros(n_basefuncs)
+    # Allocate global force vector f
+    f = zeros(ndofs(dh))
+    # Create an assembler
+    assembler = start_assemble(K, f)
+    # Loop over all cels
+    for cell in CellIterator(dh)
+        # Reinitialize cellvalues for this cell
+        reinit!(cellvalues, cell)
+        # Compute element contribution
+        assemble_element!(Ke, fe, cellvalues)
+        # Assemble Ke and fe into K and f
+        assemble!(assembler, celldofs(cell), Ke, fe)
+    end
+    return K, f
+end

Solution of the system

The last step is to solve the system. First we call assemble_global to obtain the global stiffness matrix K and force vector f.

K, f = assemble_global(cellvalues, K, dh);

To account for the boundary conditions we use the apply! function. This modifies elements in K and f respectively, such that we can get the correct solution vector u by using \.

apply!(K, f, ch)
+u = K \ f;

Exporting to VTK

To visualize the result we export the grid and our field u to a VTK-file, which can be viewed in e.g. ParaView.

VTKGridFile("heat_equation", dh) do vtk
+    write_solution(vtk, dh, u)
+end
VTKGridFile for the closed file "heat_equation.vtu".

Plain program

Here follows a version of the program without any comments. The file is also available here: heat_equation.jl.

using Ferrite, SparseArrays
+
+grid = generate_grid(Quadrilateral, (20, 20));
+
+ip = Lagrange{RefQuadrilateral, 1}()
+qr = QuadratureRule{RefQuadrilateral}(2)
+cellvalues = CellValues(qr, ip);
+
+dh = DofHandler(grid)
+add!(dh, :u, ip)
+close!(dh);
+
+K = allocate_matrix(dh)
+
+ch = ConstraintHandler(dh);
+
+∂Ω = union(
+    getfacetset(grid, "left"),
+    getfacetset(grid, "right"),
+    getfacetset(grid, "top"),
+    getfacetset(grid, "bottom"),
+);
+
+dbc = Dirichlet(:u, ∂Ω, (x, t) -> 0)
+add!(ch, dbc);
+
+close!(ch)
+
+function assemble_element!(Ke::Matrix, fe::Vector, cellvalues::CellValues)
+    n_basefuncs = getnbasefunctions(cellvalues)
+    # Reset to 0
+    fill!(Ke, 0)
+    fill!(fe, 0)
+    # Loop over quadrature points
+    for q_point in 1:getnquadpoints(cellvalues)
+        # Get the quadrature weight
+        dΩ = getdetJdV(cellvalues, q_point)
+        # Loop over test shape functions
+        for i in 1:n_basefuncs
+            δu = shape_value(cellvalues, q_point, i)
+            ∇δu = shape_gradient(cellvalues, q_point, i)
+            # Add contribution to fe
+            fe[i] += δu * dΩ
+            # Loop over trial shape functions
+            for j in 1:n_basefuncs
+                ∇u = shape_gradient(cellvalues, q_point, j)
+                # Add contribution to Ke
+                Ke[i, j] += (∇δu ⋅ ∇u) * dΩ
+            end
+        end
+    end
+    return Ke, fe
+end
+
+function assemble_global(cellvalues::CellValues, K::SparseMatrixCSC, dh::DofHandler)
+    # Allocate the element stiffness matrix and element force vector
+    n_basefuncs = getnbasefunctions(cellvalues)
+    Ke = zeros(n_basefuncs, n_basefuncs)
+    fe = zeros(n_basefuncs)
+    # Allocate global force vector f
+    f = zeros(ndofs(dh))
+    # Create an assembler
+    assembler = start_assemble(K, f)
+    # Loop over all cels
+    for cell in CellIterator(dh)
+        # Reinitialize cellvalues for this cell
+        reinit!(cellvalues, cell)
+        # Compute element contribution
+        assemble_element!(Ke, fe, cellvalues)
+        # Assemble Ke and fe into K and f
+        assemble!(assembler, celldofs(cell), Ke, fe)
+    end
+    return K, f
+end
+
+K, f = assemble_global(cellvalues, K, dh);
+
+apply!(K, f, ch)
+u = K \ f;
+
+VTKGridFile("heat_equation", dh) do vtk
+    write_solution(vtk, dh, u)
+end

This page was generated using Literate.jl.

diff --git a/previews/PR798/tutorials/heat_equation_hdiv.ipynb b/previews/PR798/tutorials/heat_equation_hdiv.ipynb new file mode 100644 index 0000000000..a6bda58e16 --- /dev/null +++ b/previews/PR798/tutorials/heat_equation_hdiv.ipynb @@ -0,0 +1,448 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Heat equation - Mixed H(div) conforming formulation)\n", + "As an alternative to the standard formulation for solving the heat equation used in\n", + "the heat equation tutorial, we can used a mixed formulation\n", + "where both the temperature, $u(\\mathbf{x})$, and the heat flux, $\\boldsymbol{q}(\\boldsymbol{x})$,\n", + "are primary variables. From a theoretical standpoint, there are many details on e.g. which combinations\n", + "of interpolations that are stable. See e.g. [Gatica2014](@cite) and [Boffi2013](@cite) for further reading.\n", + "This tutorial is based on the theory in\n", + "[Fenics' mixed poisson example](https://fenicsproject.org/olddocs/dolfin/1.4.0/python/demo/documented/mixed-poisson/python/documentation.html).\n", + "\n", + "![Temperature solution](https://raw.githubusercontent.com/Ferrite-FEM/Ferrite.jl/refs/heads/gh-pages/assets/heat_equation_hdiv.png)\n", + "**Figure:** Temperature distribution considering a central part with lower heat conductivity.\n", + "\n", + "The advantage with the mixed formulation is that the heat flux is approximated better. However, the\n", + "temperature becomes discontinuous where the conductivity is discontinuous.\n", + "\n", + "## Theory\n", + "We start with the strong form of the heat equation: Find the temperature, $u(\\boldsymbol{x})$, and heat flux, $\\boldsymbol{q}(x)$,\n", + "such that\n", + "$$\n", + "\\begin{align*}\n", + "\\boldsymbol{\\nabla}\\cdot \\boldsymbol{q} &= h(\\boldsymbol{x}), \\quad \\forall \\boldsymbol{x} \\in \\Omega \\\\\n", + "\\boldsymbol{q}(\\boldsymbol{x}) &= - k\\ \\boldsymbol{\\nabla} u(\\boldsymbol{x}), \\quad \\forall \\boldsymbol{x} \\in \\Omega \\\\\n", + "\\boldsymbol{q}(\\boldsymbol{x})\\cdot \\boldsymbol{n}(\\boldsymbol{x}) &= q_n, \\quad \\forall \\boldsymbol{x} \\in \\Gamma_\\mathrm{N}\\\\\n", + "u(\\boldsymbol{x}) &= u_\\mathrm{D}, \\quad \\forall \\boldsymbol{x} \\in \\Gamma_\\mathrm{D}\n", + "\\end{align*}\n", + "$$\n", + "\n", + "From this strong form, we can formulate the weak form as a mixed formulation.\n", + "Find $u \\in \\mathbb{U}$ and $\\boldsymbol{q}\\in\\mathbb{Q}$ such that\n", + "$$\n", + "\\begin{align*}\n", + "\\int_{\\Omega} \\delta u [\\boldsymbol{\\nabla} \\cdot \\boldsymbol{q}]\\ \\mathrm{d}\\Omega &= \\int_\\Omega \\delta u h\\ \\mathrm{d}\\Omega, \\quad \\forall\\ \\delta u \\in \\delta\\mathbb{U} \\\\\n", + "\\int_{\\Omega} \\boldsymbol{\\delta q} \\cdot \\boldsymbol{q}\\ \\mathrm{d}\\Omega &= -\\int_\\Omega \\boldsymbol{\\delta q} \\cdot [k\\ \\boldsymbol{\\nabla} u]\\ \\mathrm{d}\\Omega \\\\\n", + "\\int_{\\Omega} \\boldsymbol{\\delta q} \\cdot \\boldsymbol{q}\\ \\mathrm{d}\\Omega - \\int_{\\Omega} [\\boldsymbol{\\nabla} \\cdot \\boldsymbol{\\delta q}] k u \\ \\mathrm{d}\\Omega &=\n", + "-\\int_\\Gamma \\boldsymbol{\\delta q} \\cdot \\boldsymbol{n} k\\ u\\ \\mathrm{d}\\Omega, \\quad \\forall\\ \\boldsymbol{\\delta q} \\in \\delta\\mathbb{Q}\n", + "\\end{align*}\n", + "$$\n", + "where we have the function spaces,\n", + "$$\n", + "\\begin{align*}\n", + "\\mathbb{U} &= \\delta\\mathbb{U} = L^2 \\\\\n", + "\\mathbb{Q} &= \\lbrace \\boldsymbol{q} \\in H(\\mathrm{div}) \\text{ such that } \\boldsymbol{q}\\cdot\\boldsymbol{n} = q_\\mathrm{n} \\text{ on } \\Gamma_\\mathrm{D}\\rbrace \\\\\n", + "\\delta\\mathbb{Q} &= \\lbrace \\boldsymbol{q} \\in H(\\mathrm{div}) \\text{ such that } \\boldsymbol{q}\\cdot\\boldsymbol{n} = 0 \\text{ on } \\Gamma_\\mathrm{D}\\rbrace\n", + "\\end{align*}\n", + "$$\n", + "A stable choice of finite element spaces for this problem on grid with triangles is using\n", + "* `DiscontinuousLagrange{RefTriangle, k-1}` for approximating $L^2$\n", + "* `BrezziDouglasMarini{RefTriangle, k}` for approximating $H(\\mathrm{div})$\n", + "\n", + "We will also investigate the consequences of using $H^1$ `Lagrange` instead of $H(\\mathrm{div})$ interpolations.\n", + "\n", + "## Commented Program\n", + "\n", + "Now we solve the problem in Ferrite. What follows is a program spliced with comments.\n", + "\n", + "First we load Ferrite," + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using Ferrite" + ], + "metadata": {}, + "execution_count": 1 + }, + { + "cell_type": "markdown", + "source": [ + "And define our grid, representing a channel with a central part having a lower\n", + "conductivity, $k$, than the surrounding." + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "Grid{2, Triangle, Float64} with 80000 Triangle cells and 40501 nodes" + }, + "metadata": {}, + "execution_count": 2 + } + ], + "cell_type": "code", + "source": [ + "function create_grid(ny::Int)\n", + " width = 10.0\n", + " length = 40.0\n", + " center_width = 5.0\n", + " center_length = 20.0\n", + " upper_right = Vec((length / 2, width / 2))\n", + " grid = generate_grid(Triangle, (round(Int, ny * length / width), ny), -upper_right, upper_right)\n", + " addcellset!(grid, \"center\", x -> abs(x[1]) < center_length / 2 && abs(x[2]) < center_width / 2)\n", + " addcellset!(grid, \"around\", setdiff(1:getncells(grid), getcellset(grid, \"center\")))\n", + " return grid\n", + "end\n", + "\n", + "grid = create_grid(100)" + ], + "metadata": {}, + "execution_count": 2 + }, + { + "cell_type": "markdown", + "source": [ + "### Setup\n", + "We define one `CellValues` for each field which share the same quadrature rule." + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "(u = CellValues{Ferrite.FunctionValues{1, DiscontinuousLagrange{RefTriangle, 0}, Matrix{Float64}, Matrix{Vec{2, Float64}}, Matrix{Vec{2, Float64}}, Nothing, Nothing}, Ferrite.GeometryMapping{1, Lagrange{RefTriangle, 1}, Matrix{Float64}, Matrix{Vec{2, Float64}}, Nothing}, QuadratureRule{RefTriangle, Vector{Float64}, Vector{Vec{2, Float64}}}, Vector{Float64}}(Ferrite.FunctionValues{1, DiscontinuousLagrange{RefTriangle, 0}, Matrix{Float64}, Matrix{Vec{2, Float64}}, Matrix{Vec{2, Float64}}, Nothing, Nothing}(DiscontinuousLagrange{RefTriangle, 0}(), [1.0 1.0 1.0], [1.0 1.0 1.0], Vec{2, Float64}[[NaN, NaN] [NaN, NaN] [NaN, NaN]], Vec{2, Float64}[[0.0, 0.0] [0.0, 0.0] [0.0, 0.0]], nothing, nothing), Ferrite.GeometryMapping{1, Lagrange{RefTriangle, 1}, Matrix{Float64}, Matrix{Vec{2, Float64}}, Nothing}(Lagrange{RefTriangle, 1}(), [0.16666666666667 0.16666666666667 0.66666666666667; 0.16666666666667 0.66666666666667 0.16666666666667; 0.6666666666666601 0.16666666666666008 0.16666666666666005], Vec{2, Float64}[[1.0, 0.0] [1.0, 0.0] [1.0, 0.0]; [0.0, 1.0] [0.0, 1.0] [0.0, 1.0]; [-1.0, -1.0] [-1.0, -1.0] [-1.0, -1.0]], nothing), QuadratureRule{RefTriangle, Vector{Float64}, Vector{Vec{2, Float64}}}([0.166666666666665, 0.166666666666665, 0.166666666666665], Vec{2, Float64}[[0.16666666666667, 0.16666666666667], [0.16666666666667, 0.66666666666667], [0.66666666666667, 0.16666666666667]]), [NaN, NaN, NaN]), q = CellValues{Ferrite.FunctionValues{1, Ferrite.BrezziDouglasMarini{2, RefTriangle, 1}, Matrix{Vec{2, Float64}}, Matrix{Tensor{2, 2, Float64, 4}}, Matrix{Tensor{2, 2, Float64, 4}}, Nothing, Nothing}, Ferrite.GeometryMapping{2, Lagrange{RefTriangle, 1}, Matrix{Float64}, Matrix{Vec{2, Float64}}, Matrix{Tensor{2, 2, Float64, 4}}}, QuadratureRule{RefTriangle, Vector{Float64}, Vector{Vec{2, Float64}}}, Vector{Float64}}(Ferrite.FunctionValues{1, Ferrite.BrezziDouglasMarini{2, RefTriangle, 1}, Matrix{Vec{2, Float64}}, Matrix{Tensor{2, 2, Float64, 4}}, Matrix{Tensor{2, 2, Float64, 4}}, Nothing, Nothing}(Ferrite.BrezziDouglasMarini{2, RefTriangle, 1}(), Vec{2, Float64}[[0.0, 5.0e-324] [6.0e-323, 6.4e-323] [1.2e-322, 8.0e-323]; [1.0e-323, 1.5e-323] [7.0e-323, 7.4e-323] [8.0e-323, 8.0e-323]; … ; [4.0e-323, 4.4e-323] [8.0e-323, 8.0e-323] [8.4e-323, 6.4e-323]; [5.0e-323, 5.4e-323] [8.0e-323, 8.0e-323] [4.4e-323, 3.0e-323]], Vec{2, Float64}[[0.66666666666668, -0.33333333333334] [0.66666666666668, -1.33333333333334] [2.66666666666668, -0.33333333333334]; [-0.33333333333334, 0.66666666666668] [-0.33333333333334, 2.66666666666668] [-1.33333333333334, 0.66666666666668]; … ; [-0.33333333333334, -2.3333333333333] [-0.33333333333334, -0.3333333333333002] [-1.33333333333334, 0.6666666666666998]; [0.66666666666668, 0.6666666666666401] [0.66666666666668, -0.3333333333333597] [2.66666666666668, -2.3333333333333597]], Tensor{2, 2, Float64, 4}[[NaN NaN; NaN NaN] [NaN NaN; NaN NaN] [NaN NaN; NaN NaN]; [NaN NaN; NaN NaN] [NaN NaN; NaN NaN] [NaN NaN; NaN NaN]; … ; [NaN NaN; NaN NaN] [NaN NaN; NaN NaN] [NaN NaN; NaN NaN]; [NaN NaN; NaN NaN] [NaN NaN; NaN NaN] [NaN NaN; NaN NaN]], Tensor{2, 2, Float64, 4}[[4.0 0.0; -0.0 -2.0] [4.0 0.0; -0.0 -2.0] [4.0 0.0; -0.0 -2.0]; [-2.0 -0.0; 0.0 4.0] [-2.0 -0.0; 0.0 4.0] [-2.0 -0.0; 0.0 4.0]; … ; [-2.0 -0.0; 6.0 4.0] [-2.0 -0.0; 6.0 4.0] [-2.0 -0.0; 6.0 4.0]; [4.0 0.0; -6.0 -2.0] [4.0 0.0; -6.0 -2.0] [4.0 0.0; -6.0 -2.0]], nothing, nothing), Ferrite.GeometryMapping{2, Lagrange{RefTriangle, 1}, Matrix{Float64}, Matrix{Vec{2, Float64}}, Matrix{Tensor{2, 2, Float64, 4}}}(Lagrange{RefTriangle, 1}(), [0.16666666666667 0.16666666666667 0.66666666666667; 0.16666666666667 0.66666666666667 0.16666666666667; 0.6666666666666601 0.16666666666666008 0.16666666666666005], Vec{2, Float64}[[1.0, 0.0] [1.0, 0.0] [1.0, 0.0]; [0.0, 1.0] [0.0, 1.0] [0.0, 1.0]; [-1.0, -1.0] [-1.0, -1.0] [-1.0, -1.0]], Tensor{2, 2, Float64, 4}[[0.0 0.0; 0.0 0.0] [0.0 0.0; 0.0 0.0] [0.0 0.0; 0.0 0.0]; [0.0 0.0; 0.0 0.0] [0.0 0.0; 0.0 0.0] [0.0 0.0; 0.0 0.0]; [-0.0 -0.0; -0.0 -0.0] [-0.0 -0.0; -0.0 -0.0] [-0.0 -0.0; -0.0 -0.0]]), QuadratureRule{RefTriangle, Vector{Float64}, Vector{Vec{2, Float64}}}([0.166666666666665, 0.166666666666665, 0.166666666666665], Vec{2, Float64}[[0.16666666666667, 0.16666666666667], [0.16666666666667, 0.66666666666667], [0.66666666666667, 0.16666666666667]]), [NaN, NaN, NaN]))" + }, + "metadata": {}, + "execution_count": 3 + } + ], + "cell_type": "code", + "source": [ + "ip_geo = geometric_interpolation(getcelltype(grid))\n", + "ipu = DiscontinuousLagrange{RefTriangle, 0}()\n", + "ipq = Ferrite.BrezziDouglasMarini{2, RefTriangle, 1}()\n", + "qr = QuadratureRule{RefTriangle}(2)\n", + "cellvalues = (u = CellValues(qr, ipu, ip_geo), q = CellValues(qr, ipq, ip_geo))" + ], + "metadata": {}, + "execution_count": 3 + }, + { + "cell_type": "markdown", + "source": [ + "Distribute the degrees of freedom" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "dh = DofHandler(grid)\n", + "add!(dh, :u, ipu)\n", + "add!(dh, :q, ipq)\n", + "close!(dh);" + ], + "metadata": {}, + "execution_count": 4 + }, + { + "cell_type": "markdown", + "source": [ + "In this problem, we have a zero temperature on the boundary, Γ, which is enforced\n", + "via zero Neumann boundary conditions. Hence, we don't need a `Constrainthandler`." + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "OrderedCollections.OrderedSet{FacetIndex} with 1000 elements:\n FacetIndex((1, 3))\n FacetIndex((801, 3))\n FacetIndex((1601, 3))\n FacetIndex((2401, 3))\n FacetIndex((3201, 3))\n FacetIndex((4001, 3))\n FacetIndex((4801, 3))\n FacetIndex((5601, 3))\n FacetIndex((6401, 3))\n FacetIndex((7201, 3))\n FacetIndex((8001, 3))\n FacetIndex((8801, 3))\n FacetIndex((9601, 3))\n FacetIndex((10401, 3))\n FacetIndex((11201, 3))\n FacetIndex((12001, 3))\n FacetIndex((12801, 3))\n FacetIndex((13601, 3))\n FacetIndex((14401, 3))\n ⋮ " + }, + "metadata": {}, + "execution_count": 5 + } + ], + "cell_type": "code", + "source": [ + "Γ = union((getfacetset(grid, name) for name in (\"left\", \"right\", \"bottom\", \"top\"))...)" + ], + "metadata": {}, + "execution_count": 5 + }, + { + "cell_type": "markdown", + "source": [ + "### Element implementation" + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "assemble_element! (generic function with 1 method)" + }, + "metadata": {}, + "execution_count": 6 + } + ], + "cell_type": "code", + "source": [ + "function assemble_element!(Ke::Matrix, fe::Vector, cv::NamedTuple, dr::NamedTuple, k::Number)\n", + " cvu = cv[:u]\n", + " cvq = cv[:q]\n", + " dru = dr[:u]\n", + " drq = dr[:q]\n", + " h = 1.0 # Heat source\n", + " # Loop over quadrature points\n", + " for q_point in 1:getnquadpoints(cvu)\n", + " # Get the quadrature weight\n", + " dΩ = getdetJdV(cvu, q_point)\n", + " # Loop over test shape functions\n", + " for (iu, Iu) in pairs(dru)\n", + " δNu = shape_value(cvu, q_point, iu)\n", + " # Add contribution to fe\n", + " fe[Iu] += δNu * h * dΩ\n", + " # Loop over trial shape functions\n", + " for (jq, Jq) in pairs(drq)\n", + " div_Nq = shape_divergence(cvq, q_point, jq)\n", + " # Add contribution to Ke\n", + " Ke[Iu, Jq] += (δNu * div_Nq) * dΩ\n", + " end\n", + " end\n", + " for (iq, Iq) in pairs(drq)\n", + " δNq = shape_value(cvq, q_point, iq)\n", + " div_δNq = shape_divergence(cvq, q_point, iq)\n", + " for (ju, Ju) in pairs(dru)\n", + " Nu = shape_value(cvu, q_point, ju)\n", + " Ke[Iq, Ju] -= div_δNq * k * Nu * dΩ\n", + " end\n", + " for (jq, Jq) in pairs(drq)\n", + " Nq = shape_value(cvq, q_point, jq)\n", + " Ke[Iq, Jq] += (δNq ⋅ Nq) * dΩ\n", + " end\n", + " end\n", + " end\n", + " return Ke, fe\n", + "end" + ], + "metadata": {}, + "execution_count": 6 + }, + { + "cell_type": "markdown", + "source": [ + "### Global assembly" + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "assemble_global (generic function with 1 method)" + }, + "metadata": {}, + "execution_count": 7 + } + ], + "cell_type": "code", + "source": [ + "function assemble_global(cellvalues, dh::DofHandler)\n", + " grid = dh.grid\n", + " # Allocate the element stiffness matrix and element force vector\n", + " dofranges = (u = dof_range(dh, :u), q = dof_range(dh, :q))\n", + " ncelldofs = ndofs_per_cell(dh)\n", + " Ke = zeros(ncelldofs, ncelldofs)\n", + " fe = zeros(ncelldofs)\n", + " # Allocate global system matrix and vector\n", + " K = allocate_matrix(dh)\n", + " f = zeros(ndofs(dh))\n", + " # Create an assembler\n", + " assembler = start_assemble(K, f)\n", + " x = copy(getcoordinates(grid, 1))\n", + " dofs = copy(celldofs(dh, 1))\n", + " # Loop over all cells\n", + " for (cells, k) in (\n", + " (getcellset(grid, \"center\"), 0.1),\n", + " (getcellset(grid, \"around\"), 1.0),\n", + " )\n", + " for cellnr in cells\n", + " # Reinitialize cellvalues for this cell\n", + " cell = getcells(grid, cellnr)\n", + " getcoordinates!(x, grid, cell)\n", + " celldofs!(dofs, dh, cellnr)\n", + " reinit!(cellvalues[:u], cell, x)\n", + " reinit!(cellvalues[:q], cell, x)\n", + " # Reset to 0\n", + " fill!(Ke, 0)\n", + " fill!(fe, 0)\n", + " # Compute element contribution\n", + " assemble_element!(Ke, fe, cellvalues, dofranges, k)\n", + " # Assemble Ke and fe into K and f\n", + " assemble!(assembler, dofs, Ke, fe)\n", + " end\n", + " end\n", + " return K, f\n", + "end" + ], + "metadata": {}, + "execution_count": 7 + }, + { + "cell_type": "markdown", + "source": [ + "### Solution of the system" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "K, f = assemble_global(cellvalues, dh);\n", + "u = K \\ f;" + ], + "metadata": {}, + "execution_count": 8 + }, + { + "cell_type": "markdown", + "source": [ + "### Exporting to VTK\n", + "Currently, exporting discontinuous interpolations is not supported.\n", + "Since in this case, we have a single temperature degree of freedom\n", + "per cell, we work around this by calculating the per-cell temperature." + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "VTKGridFile for the closed file \"heat_equation_hdiv.vtu\"." + }, + "metadata": {}, + "execution_count": 9 + } + ], + "cell_type": "code", + "source": [ + "temperature_dof = first(dof_range(dh, :u))\n", + "u_cells = map(1:getncells(grid)) do i\n", + " u[celldofs(dh, i)[temperature_dof]]\n", + "end\n", + "VTKGridFile(\"heat_equation_hdiv\", dh) do vtk\n", + " write_cell_data(vtk, u_cells, \"temperature\")\n", + "end" + ], + "metadata": {}, + "execution_count": 9 + }, + { + "cell_type": "markdown", + "source": [ + "## Postprocess the total flux\n", + "We applied a constant unit heat source to the body, and the\n", + "total heat flux exiting across the boundary should therefore\n", + "match the area for the considered stationary case." + ], + "metadata": {} + }, + { + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Outward flux: 399.9999999999993\n" + ] + } + ], + "cell_type": "code", + "source": [ + "function calculate_flux(dh, boundary_facets, ip, a)\n", + " grid = dh.grid\n", + " qr = FacetQuadratureRule{RefTriangle}(4)\n", + " ip_geo = geometric_interpolation(getcelltype(grid))\n", + " fv = FacetValues(qr, ip, ip_geo)\n", + "\n", + " dofrange = dof_range(dh, :q)\n", + " flux = 0.0\n", + " dofs = celldofs(dh, 1)\n", + " ae = zeros(length(dofs))\n", + " x = getcoordinates(grid, 1)\n", + " for (cellnr, facetnr) in boundary_facets\n", + " getcoordinates!(x, grid, cellnr)\n", + " cell = getcells(grid, cellnr)\n", + " celldofs!(dofs, dh, cellnr)\n", + " map!(i -> a[i], ae, dofs)\n", + " reinit!(fv, cell, x, facetnr)\n", + " for q_point in 1:getnquadpoints(fv)\n", + " dΓ = getdetJdV(fv, q_point)\n", + " n = getnormal(fv, q_point)\n", + " q = function_value(fv, q_point, ae, dofrange)\n", + " flux += (q ⋅ n) * dΓ\n", + " end\n", + " end\n", + " return flux\n", + "end\n", + "\n", + "println(\"Outward flux: \", calculate_flux(dh, Γ, ipq, u))" + ], + "metadata": {}, + "execution_count": 10 + }, + { + "cell_type": "markdown", + "source": [ + "Note that this is not the case for the standard Heat equation,\n", + "as the flux terms are less accurately approximated. A fine mesh is required to converge in that case.\n", + "However, the present example gives a worse approximation of the temperature field." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "---\n", + "\n", + "*This notebook was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).*" + ], + "metadata": {} + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.1" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.1", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR798/tutorials/heat_equation_hdiv.jl b/previews/PR798/tutorials/heat_equation_hdiv.jl new file mode 100644 index 0000000000..81a09f809a --- /dev/null +++ b/previews/PR798/tutorials/heat_equation_hdiv.jl @@ -0,0 +1,146 @@ +using Ferrite + +function create_grid(ny::Int) + width = 10.0 + length = 40.0 + center_width = 5.0 + center_length = 20.0 + upper_right = Vec((length / 2, width / 2)) + grid = generate_grid(Triangle, (round(Int, ny * length / width), ny), -upper_right, upper_right) + addcellset!(grid, "center", x -> abs(x[1]) < center_length / 2 && abs(x[2]) < center_width / 2) + addcellset!(grid, "around", setdiff(1:getncells(grid), getcellset(grid, "center"))) + return grid +end + +grid = create_grid(100) + +ip_geo = geometric_interpolation(getcelltype(grid)) +ipu = DiscontinuousLagrange{RefTriangle, 0}() +ipq = Ferrite.BrezziDouglasMarini{2, RefTriangle, 1}() +qr = QuadratureRule{RefTriangle}(2) +cellvalues = (u = CellValues(qr, ipu, ip_geo), q = CellValues(qr, ipq, ip_geo)) + +dh = DofHandler(grid) +add!(dh, :u, ipu) +add!(dh, :q, ipq) +close!(dh); + +Γ = union((getfacetset(grid, name) for name in ("left", "right", "bottom", "top"))...) + +function assemble_element!(Ke::Matrix, fe::Vector, cv::NamedTuple, dr::NamedTuple, k::Number) + cvu = cv[:u] + cvq = cv[:q] + dru = dr[:u] + drq = dr[:q] + h = 1.0 # Heat source + # Loop over quadrature points + for q_point in 1:getnquadpoints(cvu) + # Get the quadrature weight + dΩ = getdetJdV(cvu, q_point) + # Loop over test shape functions + for (iu, Iu) in pairs(dru) + δNu = shape_value(cvu, q_point, iu) + # Add contribution to fe + fe[Iu] += δNu * h * dΩ + # Loop over trial shape functions + for (jq, Jq) in pairs(drq) + div_Nq = shape_divergence(cvq, q_point, jq) + # Add contribution to Ke + Ke[Iu, Jq] += (δNu * div_Nq) * dΩ + end + end + for (iq, Iq) in pairs(drq) + δNq = shape_value(cvq, q_point, iq) + div_δNq = shape_divergence(cvq, q_point, iq) + for (ju, Ju) in pairs(dru) + Nu = shape_value(cvu, q_point, ju) + Ke[Iq, Ju] -= div_δNq * k * Nu * dΩ + end + for (jq, Jq) in pairs(drq) + Nq = shape_value(cvq, q_point, jq) + Ke[Iq, Jq] += (δNq ⋅ Nq) * dΩ + end + end + end + return Ke, fe +end + +function assemble_global(cellvalues, dh::DofHandler) + grid = dh.grid + # Allocate the element stiffness matrix and element force vector + dofranges = (u = dof_range(dh, :u), q = dof_range(dh, :q)) + ncelldofs = ndofs_per_cell(dh) + Ke = zeros(ncelldofs, ncelldofs) + fe = zeros(ncelldofs) + # Allocate global system matrix and vector + K = allocate_matrix(dh) + f = zeros(ndofs(dh)) + # Create an assembler + assembler = start_assemble(K, f) + x = copy(getcoordinates(grid, 1)) + dofs = copy(celldofs(dh, 1)) + # Loop over all cells + for (cells, k) in ( + (getcellset(grid, "center"), 0.1), + (getcellset(grid, "around"), 1.0), + ) + for cellnr in cells + # Reinitialize cellvalues for this cell + cell = getcells(grid, cellnr) + getcoordinates!(x, grid, cell) + celldofs!(dofs, dh, cellnr) + reinit!(cellvalues[:u], cell, x) + reinit!(cellvalues[:q], cell, x) + # Reset to 0 + fill!(Ke, 0) + fill!(fe, 0) + # Compute element contribution + assemble_element!(Ke, fe, cellvalues, dofranges, k) + # Assemble Ke and fe into K and f + assemble!(assembler, dofs, Ke, fe) + end + end + return K, f +end + +K, f = assemble_global(cellvalues, dh); +u = K \ f; + +temperature_dof = first(dof_range(dh, :u)) +u_cells = map(1:getncells(grid)) do i + u[celldofs(dh, i)[temperature_dof]] +end +VTKGridFile("heat_equation_hdiv", dh) do vtk + write_cell_data(vtk, u_cells, "temperature") +end + +function calculate_flux(dh, boundary_facets, ip, a) + grid = dh.grid + qr = FacetQuadratureRule{RefTriangle}(4) + ip_geo = geometric_interpolation(getcelltype(grid)) + fv = FacetValues(qr, ip, ip_geo) + + dofrange = dof_range(dh, :q) + flux = 0.0 + dofs = celldofs(dh, 1) + ae = zeros(length(dofs)) + x = getcoordinates(grid, 1) + for (cellnr, facetnr) in boundary_facets + getcoordinates!(x, grid, cellnr) + cell = getcells(grid, cellnr) + celldofs!(dofs, dh, cellnr) + map!(i -> a[i], ae, dofs) + reinit!(fv, cell, x, facetnr) + for q_point in 1:getnquadpoints(fv) + dΓ = getdetJdV(fv, q_point) + n = getnormal(fv, q_point) + q = function_value(fv, q_point, ae, dofrange) + flux += (q ⋅ n) * dΓ + end + end + return flux +end + +println("Outward flux: ", calculate_flux(dh, Γ, ipq, u)) + +# This file was generated using Literate.jl, https://github.com/fredrikekre/Literate.jl diff --git a/previews/PR798/tutorials/heat_equation_hdiv/index.html b/previews/PR798/tutorials/heat_equation_hdiv/index.html new file mode 100644 index 0000000000..a04ea49083 --- /dev/null +++ b/previews/PR798/tutorials/heat_equation_hdiv/index.html @@ -0,0 +1,304 @@ + +Heat equation - Mixed H(div) conforming formulation) · Ferrite.jl

Heat equation - Mixed H(div) conforming formulation)

As an alternative to the standard formulation for solving the heat equation used in the heat equation tutorial, we can used a mixed formulation where both the temperature, $u(\mathbf{x})$, and the heat flux, $\boldsymbol{q}(\boldsymbol{x})$, are primary variables. From a theoretical standpoint, there are many details on e.g. which combinations of interpolations that are stable. See e.g. [1] and [2] for further reading. This tutorial is based on the theory in Fenics' mixed poisson example.

Temperature solution Figure: Temperature distribution considering a central part with lower heat conductivity.

The advantage with the mixed formulation is that the heat flux is approximated better. However, the temperature becomes discontinuous where the conductivity is discontinuous.

Theory

We start with the strong form of the heat equation: Find the temperature, $u(\boldsymbol{x})$, and heat flux, $\boldsymbol{q}(x)$, such that

\[\begin{align*} +\boldsymbol{\nabla}\cdot \boldsymbol{q} &= h(\boldsymbol{x}), \quad \forall \boldsymbol{x} \in \Omega \\ +\boldsymbol{q}(\boldsymbol{x}) &= - k\ \boldsymbol{\nabla} u(\boldsymbol{x}), \quad \forall \boldsymbol{x} \in \Omega \\ +\boldsymbol{q}(\boldsymbol{x})\cdot \boldsymbol{n}(\boldsymbol{x}) &= q_n, \quad \forall \boldsymbol{x} \in \Gamma_\mathrm{N}\\ +u(\boldsymbol{x}) &= u_\mathrm{D}, \quad \forall \boldsymbol{x} \in \Gamma_\mathrm{D} +\end{align*}\]

From this strong form, we can formulate the weak form as a mixed formulation. Find $u \in \mathbb{U}$ and $\boldsymbol{q}\in\mathbb{Q}$ such that

\[\begin{align*} +\int_{\Omega} \delta u [\boldsymbol{\nabla} \cdot \boldsymbol{q}]\ \mathrm{d}\Omega &= \int_\Omega \delta u h\ \mathrm{d}\Omega, \quad \forall\ \delta u \in \delta\mathbb{U} \\ +\int_{\Omega} \boldsymbol{\delta q} \cdot \boldsymbol{q}\ \mathrm{d}\Omega &= -\int_\Omega \boldsymbol{\delta q} \cdot [k\ \boldsymbol{\nabla} u]\ \mathrm{d}\Omega \\ +\int_{\Omega} \boldsymbol{\delta q} \cdot \boldsymbol{q}\ \mathrm{d}\Omega - \int_{\Omega} [\boldsymbol{\nabla} \cdot \boldsymbol{\delta q}] k u \ \mathrm{d}\Omega &= +-\int_\Gamma \boldsymbol{\delta q} \cdot \boldsymbol{n} k\ u\ \mathrm{d}\Omega, \quad \forall\ \boldsymbol{\delta q} \in \delta\mathbb{Q} +\end{align*}\]

where we have the function spaces,

\[\begin{align*} +\mathbb{U} &= \delta\mathbb{U} = L^2 \\ +\mathbb{Q} &= \lbrace \boldsymbol{q} \in H(\mathrm{div}) \text{ such that } \boldsymbol{q}\cdot\boldsymbol{n} = q_\mathrm{n} \text{ on } \Gamma_\mathrm{D}\rbrace \\ +\delta\mathbb{Q} &= \lbrace \boldsymbol{q} \in H(\mathrm{div}) \text{ such that } \boldsymbol{q}\cdot\boldsymbol{n} = 0 \text{ on } \Gamma_\mathrm{D}\rbrace +\end{align*}\]

A stable choice of finite element spaces for this problem on grid with triangles is using

  • DiscontinuousLagrange{RefTriangle, k-1} for approximating $L^2$
  • BrezziDouglasMarini{RefTriangle, k} for approximating $H(\mathrm{div})$

We will also investigate the consequences of using $H^1$ Lagrange instead of $H(\mathrm{div})$ interpolations.

Commented Program

Now we solve the problem in Ferrite. What follows is a program spliced with comments.

First we load Ferrite,

using Ferrite

And define our grid, representing a channel with a central part having a lower conductivity, $k$, than the surrounding.

function create_grid(ny::Int)
+    width = 10.0
+    length = 40.0
+    center_width = 5.0
+    center_length = 20.0
+    upper_right = Vec((length / 2, width / 2))
+    grid = generate_grid(Triangle, (round(Int, ny * length / width), ny), -upper_right, upper_right)
+    addcellset!(grid, "center", x -> abs(x[1]) < center_length / 2 && abs(x[2]) < center_width / 2)
+    addcellset!(grid, "around", setdiff(1:getncells(grid), getcellset(grid, "center")))
+    return grid
+end
+
+grid = create_grid(100)
Grid{2, Triangle, Float64} with 80000 Triangle cells and 40501 nodes

Setup

We define one CellValues for each field which share the same quadrature rule.

ip_geo = geometric_interpolation(getcelltype(grid))
+ipu = DiscontinuousLagrange{RefTriangle, 0}()
+ipq = Ferrite.BrezziDouglasMarini{2, RefTriangle, 1}()
+qr = QuadratureRule{RefTriangle}(2)
+cellvalues = (u = CellValues(qr, ipu, ip_geo), q = CellValues(qr, ipq, ip_geo))
(u = CellValues{Ferrite.FunctionValues{1, DiscontinuousLagrange{RefTriangle, 0}, Matrix{Float64}, Matrix{Vec{2, Float64}}, Matrix{Vec{2, Float64}}, Nothing, Nothing}, Ferrite.GeometryMapping{1, Lagrange{RefTriangle, 1}, Matrix{Float64}, Matrix{Vec{2, Float64}}, Nothing}, QuadratureRule{RefTriangle, Vector{Float64}, Vector{Vec{2, Float64}}}, Vector{Float64}}(Ferrite.FunctionValues{1, DiscontinuousLagrange{RefTriangle, 0}, Matrix{Float64}, Matrix{Vec{2, Float64}}, Matrix{Vec{2, Float64}}, Nothing, Nothing}(DiscontinuousLagrange{RefTriangle, 0}(), [1.0 1.0 1.0], [1.0 1.0 1.0], Vec{2, Float64}[[NaN, NaN] [NaN, NaN] [NaN, NaN]], Vec{2, Float64}[[0.0, 0.0] [0.0, 0.0] [0.0, 0.0]], nothing, nothing), Ferrite.GeometryMapping{1, Lagrange{RefTriangle, 1}, Matrix{Float64}, Matrix{Vec{2, Float64}}, Nothing}(Lagrange{RefTriangle, 1}(), [0.16666666666667 0.16666666666667 0.66666666666667; 0.16666666666667 0.66666666666667 0.16666666666667; 0.6666666666666601 0.16666666666666008 0.16666666666666005], Vec{2, Float64}[[1.0, 0.0] [1.0, 0.0] [1.0, 0.0]; [0.0, 1.0] [0.0, 1.0] [0.0, 1.0]; [-1.0, -1.0] [-1.0, -1.0] [-1.0, -1.0]], nothing), QuadratureRule{RefTriangle, Vector{Float64}, Vector{Vec{2, Float64}}}([0.166666666666665, 0.166666666666665, 0.166666666666665], Vec{2, Float64}[[0.16666666666667, 0.16666666666667], [0.16666666666667, 0.66666666666667], [0.66666666666667, 0.16666666666667]]), [NaN, NaN, NaN]), q = CellValues{Ferrite.FunctionValues{1, Ferrite.BrezziDouglasMarini{2, RefTriangle, 1}, Matrix{Vec{2, Float64}}, Matrix{Tensor{2, 2, Float64, 4}}, Matrix{Tensor{2, 2, Float64, 4}}, Nothing, Nothing}, Ferrite.GeometryMapping{2, Lagrange{RefTriangle, 1}, Matrix{Float64}, Matrix{Vec{2, Float64}}, Matrix{Tensor{2, 2, Float64, 4}}}, QuadratureRule{RefTriangle, Vector{Float64}, Vector{Vec{2, Float64}}}, Vector{Float64}}(Ferrite.FunctionValues{1, Ferrite.BrezziDouglasMarini{2, RefTriangle, 1}, Matrix{Vec{2, Float64}}, Matrix{Tensor{2, 2, Float64, 4}}, Matrix{Tensor{2, 2, Float64, 4}}, Nothing, Nothing}(Ferrite.BrezziDouglasMarini{2, RefTriangle, 1}(), Vec{2, Float64}[[6.02e-321, 1.265e-321] [6.304e-321, 1.265e-321] [6.39e-321, 1.265e-321]; [6.047e-321, 1.265e-321] [6.334e-321, 1.265e-321] [6.393e-321, 1.265e-321]; … ; [6.265e-321, 1.265e-321] [6.354e-321, 1.265e-321] [6.423e-321, 1.265e-321]; [6.27e-321, 1.265e-321] [6.383e-321, 1.265e-321] [6.43e-321, 1.265e-321]], Vec{2, Float64}[[0.66666666666668, -0.33333333333334] [0.66666666666668, -1.33333333333334] [2.66666666666668, -0.33333333333334]; [-0.33333333333334, 0.66666666666668] [-0.33333333333334, 2.66666666666668] [-1.33333333333334, 0.66666666666668]; … ; [-0.33333333333334, -2.3333333333333] [-0.33333333333334, -0.3333333333333002] [-1.33333333333334, 0.6666666666666998]; [0.66666666666668, 0.6666666666666401] [0.66666666666668, -0.3333333333333597] [2.66666666666668, -2.3333333333333597]], Tensor{2, 2, Float64, 4}[[NaN NaN; NaN NaN] [NaN NaN; NaN NaN] [NaN NaN; NaN NaN]; [NaN NaN; NaN NaN] [NaN NaN; NaN NaN] [NaN NaN; NaN NaN]; … ; [NaN NaN; NaN NaN] [NaN NaN; NaN NaN] [NaN NaN; NaN NaN]; [NaN NaN; NaN NaN] [NaN NaN; NaN NaN] [NaN NaN; NaN NaN]], Tensor{2, 2, Float64, 4}[[4.0 0.0; -0.0 -2.0] [4.0 0.0; -0.0 -2.0] [4.0 0.0; -0.0 -2.0]; [-2.0 -0.0; 0.0 4.0] [-2.0 -0.0; 0.0 4.0] [-2.0 -0.0; 0.0 4.0]; … ; [-2.0 -0.0; 6.0 4.0] [-2.0 -0.0; 6.0 4.0] [-2.0 -0.0; 6.0 4.0]; [4.0 0.0; -6.0 -2.0] [4.0 0.0; -6.0 -2.0] [4.0 0.0; -6.0 -2.0]], nothing, nothing), Ferrite.GeometryMapping{2, Lagrange{RefTriangle, 1}, Matrix{Float64}, Matrix{Vec{2, Float64}}, Matrix{Tensor{2, 2, Float64, 4}}}(Lagrange{RefTriangle, 1}(), [0.16666666666667 0.16666666666667 0.66666666666667; 0.16666666666667 0.66666666666667 0.16666666666667; 0.6666666666666601 0.16666666666666008 0.16666666666666005], Vec{2, Float64}[[1.0, 0.0] [1.0, 0.0] [1.0, 0.0]; [0.0, 1.0] [0.0, 1.0] [0.0, 1.0]; [-1.0, -1.0] [-1.0, -1.0] [-1.0, -1.0]], Tensor{2, 2, Float64, 4}[[0.0 0.0; 0.0 0.0] [0.0 0.0; 0.0 0.0] [0.0 0.0; 0.0 0.0]; [0.0 0.0; 0.0 0.0] [0.0 0.0; 0.0 0.0] [0.0 0.0; 0.0 0.0]; [-0.0 -0.0; -0.0 -0.0] [-0.0 -0.0; -0.0 -0.0] [-0.0 -0.0; -0.0 -0.0]]), QuadratureRule{RefTriangle, Vector{Float64}, Vector{Vec{2, Float64}}}([0.166666666666665, 0.166666666666665, 0.166666666666665], Vec{2, Float64}[[0.16666666666667, 0.16666666666667], [0.16666666666667, 0.66666666666667], [0.66666666666667, 0.16666666666667]]), [NaN, NaN, NaN]))

Distribute the degrees of freedom

dh = DofHandler(grid)
+add!(dh, :u, ipu)
+add!(dh, :q, ipq)
+close!(dh);

In this problem, we have a zero temperature on the boundary, Γ, which is enforced via zero Neumann boundary conditions. Hence, we don't need a Constrainthandler.

Γ = union((getfacetset(grid, name) for name in ("left", "right", "bottom", "top"))...)
OrderedCollections.OrderedSet{FacetIndex} with 1000 elements:
+  FacetIndex((1, 3))
+  FacetIndex((801, 3))
+  FacetIndex((1601, 3))
+  FacetIndex((2401, 3))
+  FacetIndex((3201, 3))
+  FacetIndex((4001, 3))
+  FacetIndex((4801, 3))
+  FacetIndex((5601, 3))
+  FacetIndex((6401, 3))
+  FacetIndex((7201, 3))
+  FacetIndex((8001, 3))
+  FacetIndex((8801, 3))
+  FacetIndex((9601, 3))
+  FacetIndex((10401, 3))
+  FacetIndex((11201, 3))
+  FacetIndex((12001, 3))
+  FacetIndex((12801, 3))
+  FacetIndex((13601, 3))
+  FacetIndex((14401, 3))
+  ⋮ 

Element implementation

function assemble_element!(Ke::Matrix, fe::Vector, cv::NamedTuple, dr::NamedTuple, k::Number)
+    cvu = cv[:u]
+    cvq = cv[:q]
+    dru = dr[:u]
+    drq = dr[:q]
+    h = 1.0 # Heat source
+    # Loop over quadrature points
+    for q_point in 1:getnquadpoints(cvu)
+        # Get the quadrature weight
+        dΩ = getdetJdV(cvu, q_point)
+        # Loop over test shape functions
+        for (iu, Iu) in pairs(dru)
+            δNu = shape_value(cvu, q_point, iu)
+            # Add contribution to fe
+            fe[Iu] += δNu * h * dΩ
+            # Loop over trial shape functions
+            for (jq, Jq) in pairs(drq)
+                div_Nq = shape_divergence(cvq, q_point, jq)
+                # Add contribution to Ke
+                Ke[Iu, Jq] += (δNu * div_Nq) * dΩ
+            end
+        end
+        for (iq, Iq) in pairs(drq)
+            δNq = shape_value(cvq, q_point, iq)
+            div_δNq = shape_divergence(cvq, q_point, iq)
+            for (ju, Ju) in pairs(dru)
+                Nu = shape_value(cvu, q_point, ju)
+                Ke[Iq, Ju] -= div_δNq * k * Nu * dΩ
+            end
+            for (jq, Jq) in pairs(drq)
+                Nq = shape_value(cvq, q_point, jq)
+                Ke[Iq, Jq] += (δNq ⋅ Nq) * dΩ
+            end
+        end
+    end
+    return Ke, fe
+end

Global assembly

function assemble_global(cellvalues, dh::DofHandler)
+    grid = dh.grid
+    # Allocate the element stiffness matrix and element force vector
+    dofranges = (u = dof_range(dh, :u), q = dof_range(dh, :q))
+    ncelldofs = ndofs_per_cell(dh)
+    Ke = zeros(ncelldofs, ncelldofs)
+    fe = zeros(ncelldofs)
+    # Allocate global system matrix and vector
+    K = allocate_matrix(dh)
+    f = zeros(ndofs(dh))
+    # Create an assembler
+    assembler = start_assemble(K, f)
+    x = copy(getcoordinates(grid, 1))
+    dofs = copy(celldofs(dh, 1))
+    # Loop over all cells
+    for (cells, k) in (
+            (getcellset(grid, "center"), 0.1),
+            (getcellset(grid, "around"), 1.0),
+        )
+        for cellnr in cells
+            # Reinitialize cellvalues for this cell
+            cell = getcells(grid, cellnr)
+            getcoordinates!(x, grid, cell)
+            celldofs!(dofs, dh, cellnr)
+            reinit!(cellvalues[:u], cell, x)
+            reinit!(cellvalues[:q], cell, x)
+            # Reset to 0
+            fill!(Ke, 0)
+            fill!(fe, 0)
+            # Compute element contribution
+            assemble_element!(Ke, fe, cellvalues, dofranges, k)
+            # Assemble Ke and fe into K and f
+            assemble!(assembler, dofs, Ke, fe)
+        end
+    end
+    return K, f
+end

Solution of the system

K, f = assemble_global(cellvalues, dh);
+u = K \ f;

Exporting to VTK

Currently, exporting discontinuous interpolations is not supported. Since in this case, we have a single temperature degree of freedom per cell, we work around this by calculating the per-cell temperature.

temperature_dof = first(dof_range(dh, :u))
+u_cells = map(1:getncells(grid)) do i
+    u[celldofs(dh, i)[temperature_dof]]
+end
+VTKGridFile("heat_equation_hdiv", dh) do vtk
+    write_cell_data(vtk, u_cells, "temperature")
+end
VTKGridFile for the closed file "heat_equation_hdiv.vtu".

Postprocess the total flux

We applied a constant unit heat source to the body, and the total heat flux exiting across the boundary should therefore match the area for the considered stationary case.

function calculate_flux(dh, boundary_facets, ip, a)
+    grid = dh.grid
+    qr = FacetQuadratureRule{RefTriangle}(4)
+    ip_geo = geometric_interpolation(getcelltype(grid))
+    fv = FacetValues(qr, ip, ip_geo)
+
+    dofrange = dof_range(dh, :q)
+    flux = 0.0
+    dofs = celldofs(dh, 1)
+    ae = zeros(length(dofs))
+    x = getcoordinates(grid, 1)
+    for (cellnr, facetnr) in boundary_facets
+        getcoordinates!(x, grid, cellnr)
+        cell = getcells(grid, cellnr)
+        celldofs!(dofs, dh, cellnr)
+        map!(i -> a[i], ae, dofs)
+        reinit!(fv, cell, x, facetnr)
+        for q_point in 1:getnquadpoints(fv)
+            dΓ = getdetJdV(fv, q_point)
+            n = getnormal(fv, q_point)
+            q = function_value(fv, q_point, ae, dofrange)
+            flux += (q ⋅ n) * dΓ
+        end
+    end
+    return flux
+end
+
+println("Outward flux: ", calculate_flux(dh, Γ, ipq, u))
Outward flux: 399.9999999999993

Note that this is not the case for the standard Heat equation, as the flux terms are less accurately approximated. A fine mesh is required to converge in that case. However, the present example gives a worse approximation of the temperature field.

Plain program

Here follows a version of the program without any comments. The file is also available here: heat_equation_hdiv.jl.

using Ferrite
+
+function create_grid(ny::Int)
+    width = 10.0
+    length = 40.0
+    center_width = 5.0
+    center_length = 20.0
+    upper_right = Vec((length / 2, width / 2))
+    grid = generate_grid(Triangle, (round(Int, ny * length / width), ny), -upper_right, upper_right)
+    addcellset!(grid, "center", x -> abs(x[1]) < center_length / 2 && abs(x[2]) < center_width / 2)
+    addcellset!(grid, "around", setdiff(1:getncells(grid), getcellset(grid, "center")))
+    return grid
+end
+
+grid = create_grid(100)
+
+ip_geo = geometric_interpolation(getcelltype(grid))
+ipu = DiscontinuousLagrange{RefTriangle, 0}()
+ipq = Ferrite.BrezziDouglasMarini{2, RefTriangle, 1}()
+qr = QuadratureRule{RefTriangle}(2)
+cellvalues = (u = CellValues(qr, ipu, ip_geo), q = CellValues(qr, ipq, ip_geo))
+
+dh = DofHandler(grid)
+add!(dh, :u, ipu)
+add!(dh, :q, ipq)
+close!(dh);
+
+Γ = union((getfacetset(grid, name) for name in ("left", "right", "bottom", "top"))...)
+
+function assemble_element!(Ke::Matrix, fe::Vector, cv::NamedTuple, dr::NamedTuple, k::Number)
+    cvu = cv[:u]
+    cvq = cv[:q]
+    dru = dr[:u]
+    drq = dr[:q]
+    h = 1.0 # Heat source
+    # Loop over quadrature points
+    for q_point in 1:getnquadpoints(cvu)
+        # Get the quadrature weight
+        dΩ = getdetJdV(cvu, q_point)
+        # Loop over test shape functions
+        for (iu, Iu) in pairs(dru)
+            δNu = shape_value(cvu, q_point, iu)
+            # Add contribution to fe
+            fe[Iu] += δNu * h * dΩ
+            # Loop over trial shape functions
+            for (jq, Jq) in pairs(drq)
+                div_Nq = shape_divergence(cvq, q_point, jq)
+                # Add contribution to Ke
+                Ke[Iu, Jq] += (δNu * div_Nq) * dΩ
+            end
+        end
+        for (iq, Iq) in pairs(drq)
+            δNq = shape_value(cvq, q_point, iq)
+            div_δNq = shape_divergence(cvq, q_point, iq)
+            for (ju, Ju) in pairs(dru)
+                Nu = shape_value(cvu, q_point, ju)
+                Ke[Iq, Ju] -= div_δNq * k * Nu * dΩ
+            end
+            for (jq, Jq) in pairs(drq)
+                Nq = shape_value(cvq, q_point, jq)
+                Ke[Iq, Jq] += (δNq ⋅ Nq) * dΩ
+            end
+        end
+    end
+    return Ke, fe
+end
+
+function assemble_global(cellvalues, dh::DofHandler)
+    grid = dh.grid
+    # Allocate the element stiffness matrix and element force vector
+    dofranges = (u = dof_range(dh, :u), q = dof_range(dh, :q))
+    ncelldofs = ndofs_per_cell(dh)
+    Ke = zeros(ncelldofs, ncelldofs)
+    fe = zeros(ncelldofs)
+    # Allocate global system matrix and vector
+    K = allocate_matrix(dh)
+    f = zeros(ndofs(dh))
+    # Create an assembler
+    assembler = start_assemble(K, f)
+    x = copy(getcoordinates(grid, 1))
+    dofs = copy(celldofs(dh, 1))
+    # Loop over all cells
+    for (cells, k) in (
+            (getcellset(grid, "center"), 0.1),
+            (getcellset(grid, "around"), 1.0),
+        )
+        for cellnr in cells
+            # Reinitialize cellvalues for this cell
+            cell = getcells(grid, cellnr)
+            getcoordinates!(x, grid, cell)
+            celldofs!(dofs, dh, cellnr)
+            reinit!(cellvalues[:u], cell, x)
+            reinit!(cellvalues[:q], cell, x)
+            # Reset to 0
+            fill!(Ke, 0)
+            fill!(fe, 0)
+            # Compute element contribution
+            assemble_element!(Ke, fe, cellvalues, dofranges, k)
+            # Assemble Ke and fe into K and f
+            assemble!(assembler, dofs, Ke, fe)
+        end
+    end
+    return K, f
+end
+
+K, f = assemble_global(cellvalues, dh);
+u = K \ f;
+
+temperature_dof = first(dof_range(dh, :u))
+u_cells = map(1:getncells(grid)) do i
+    u[celldofs(dh, i)[temperature_dof]]
+end
+VTKGridFile("heat_equation_hdiv", dh) do vtk
+    write_cell_data(vtk, u_cells, "temperature")
+end
+
+function calculate_flux(dh, boundary_facets, ip, a)
+    grid = dh.grid
+    qr = FacetQuadratureRule{RefTriangle}(4)
+    ip_geo = geometric_interpolation(getcelltype(grid))
+    fv = FacetValues(qr, ip, ip_geo)
+
+    dofrange = dof_range(dh, :q)
+    flux = 0.0
+    dofs = celldofs(dh, 1)
+    ae = zeros(length(dofs))
+    x = getcoordinates(grid, 1)
+    for (cellnr, facetnr) in boundary_facets
+        getcoordinates!(x, grid, cellnr)
+        cell = getcells(grid, cellnr)
+        celldofs!(dofs, dh, cellnr)
+        map!(i -> a[i], ae, dofs)
+        reinit!(fv, cell, x, facetnr)
+        for q_point in 1:getnquadpoints(fv)
+            dΓ = getdetJdV(fv, q_point)
+            n = getnormal(fv, q_point)
+            q = function_value(fv, q_point, ae, dofrange)
+            flux += (q ⋅ n) * dΓ
+        end
+    end
+    return flux
+end
+
+println("Outward flux: ", calculate_flux(dh, Γ, ipq, u))

This page was generated using Literate.jl.

diff --git a/previews/PR798/tutorials/heat_square.png b/previews/PR798/tutorials/heat_square.png new file mode 100644 index 0000000000..1fc753e9ec Binary files /dev/null and b/previews/PR798/tutorials/heat_square.png differ diff --git a/previews/PR798/tutorials/hyperelasticity.ipynb b/previews/PR798/tutorials/hyperelasticity.ipynb new file mode 100644 index 0000000000..ae9feac03d --- /dev/null +++ b/previews/PR798/tutorials/hyperelasticity.ipynb @@ -0,0 +1,576 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Hyperelasticity\n", + "\n", + "**Keywords**: *hyperelasticity*, *finite strain*, *large deformations*, *Newton's method*,\n", + "*conjugate gradient*, *automatic differentiation*\n", + "\n", + "![hyperelasticity.png](hyperelasticity.png)\n", + "\n", + "*Figure 1*: Cube loaded in torsion modeled with a hyperelastic material model and\n", + "finite strain." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Introduction\n", + "\n", + "In this example we will solve a problem in a finite strain setting using an\n", + "hyperelastic material model. In order to compute the stress we will use automatic\n", + "differentiation, to solve the non-linear system we use Newton's\n", + "method, and for solving the Newton increment we use conjugate gradients.\n", + "\n", + "The weak form is expressed in terms of the first Piola-Kirchoff stress $\\mathbf{P}$\n", + "as follows: Find $\\mathbf{u} \\in \\mathbb{U}$ such that\n", + "\n", + "$$\n", + "\\int_{\\Omega} [\\nabla_{\\mathbf{X}} \\delta \\mathbf{u}] : \\mathbf{P}(\\mathbf{u})\\ \\mathrm{d}\\Omega =\n", + "\\int_{\\Omega} \\delta \\mathbf{u} \\cdot \\mathbf{b}\\ \\mathrm{d}\\Omega + \\int_{\\Gamma_\\mathrm{N}}\n", + "\\delta \\mathbf{u} \\cdot \\mathbf{t}\\ \\mathrm{d}\\Gamma\n", + "\\quad \\forall \\delta \\mathbf{u} \\in \\mathbb{U}^0,\n", + "$$\n", + "\n", + "where $\\mathbf{u}$ is the unknown displacement field, $\\mathbf{b}$ is the body force acting\n", + "on the reference domain, $\\mathbf{t}$ is the traction acting on the Neumann part of the reference\n", + "domain's boundary, and where $\\mathbb{U}$ and $\\mathbb{U}^0$ are suitable trial and test sets.\n", + "$\\Omega$ denotes the reference (sometimes also called *initial* or *material*) domain.\n", + "Gradients are defined with respect to the reference domain, here denoted with an $\\mathbf{X}$.\n", + "Formally this is expressed as $(\\nabla_{\\mathbf{X}} \\bullet)_{ij} := \\frac{\\partial(\\bullet)_i}{\\partial X_j}$.\n", + "Note that for large deformation problems it is also possible that gradients and integrals\n", + "are defined on the deformed (sometimes also called *current* or *spatial*) domain, depending\n", + "on the specific formulation.\n", + "\n", + "The specific problem we will solve in this example is the cube from Figure 1: On one side\n", + "we apply a rotation using Dirichlet boundary conditions, on the opposite side we fix the\n", + "displacement with a homogeneous Dirichlet boundary condition, and on the remaining four\n", + "sides we apply a traction in the normal direction of the surface. In addition, a body\n", + "force is applied in one direction.\n", + "\n", + "In addition to Ferrite.jl and Tensors.jl, this examples uses\n", + "[TimerOutputs.jl](https://github.com/KristofferC/TimerOutputs.jl) for timing the program\n", + "and print a summary at the end,\n", + "[ProgressMeter.jl](https://github.com/timholy/ProgressMeter.jl) for showing a simple\n", + "progress bar, and\n", + "[IterativeSolvers.jl](https://github.com/JuliaLinearAlgebra/IterativeSolvers.jl) for solving\n", + "the linear system using conjugate gradients." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using Ferrite, Tensors, TimerOutputs, ProgressMeter, IterativeSolvers" + ], + "metadata": {}, + "execution_count": 1 + }, + { + "cell_type": "markdown", + "source": [ + "## Hyperelastic material model\n", + "\n", + "The stress can be derived from an energy potential, defined in\n", + "terms of the right Cauchy-Green tensor $\\mathbf{C} = \\mathbf{F}^{\\mathrm{T}} \\cdot \\mathbf{F}$,\n", + "where $\\mathbf{F} = \\mathbf{I} + \\nabla_{\\mathbf{X}} \\mathbf{u}$ is the deformation gradient.\n", + "We shall use the compressible neo-Hookean model from [Wikipedia](https://en.wikipedia.org/wiki/Neo-Hookean_solid) with the potential\n", + "\n", + "$$\n", + "\\Psi(\\mathbf{C}) = \\underbrace{\\frac{\\mu}{2} (I_1 - 3)}_{W(\\mathbf{C})} \\underbrace{- {\\mu} \\ln(J) + \\frac{\\lambda}{2} (J - 1)^2}_{U(J)},\n", + "$$\n", + "\n", + "where $I_1 = \\mathrm{tr}(\\mathbf{C})$ is the first invariant, $J = \\sqrt{\\det(\\mathbf{C})}$\n", + "and $\\mu$ and $\\lambda$ material parameters." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "> **Extra details on compressible neo-Hookean formulations**\n", + ">\n", + "> The Neo-Hooke model is only a well defined terminology in the incompressible case.\n", + "> Thus, only $W(\\mathbf{C})$ specifies the neo-Hookean behavior, the volume penalty $U(J)$ can vary in different formulations.\n", + "> In order to obtain a well-posed problem, it is crucial to choose a convex formulation of $U(J)$.\n", + "> Other examples for $U(J)$ can be found, e.g. in [Hol:2000:nsm; Eq. (6.138)](@cite)\n", + "> $$\n", + "> \\beta^{-2} (\\beta \\ln J + J^{-\\beta} -1)\n", + "> $$\n", + "> where [SimMie:1992:act; Eq. (2.37)](@cite) published a non-generalized version with $\\beta=-2$.\n", + "> This shows the possible variety of $U(J)$ while all of them refer to compressible neo-Hookean models.\n", + "> Sometimes the modified first invariant $\\overline{I}_1=\\frac{I_1}{I_3^{1/3}}$ is used in $W(\\mathbf{C})$ instead of $I_1$." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "From the potential we obtain the second Piola-Kirchoff stress $\\mathbf{S}$ as\n", + "\n", + "$$\n", + "\\mathbf{S} = 2 \\frac{\\partial \\Psi}{\\partial \\mathbf{C}},\n", + "$$\n", + "\n", + "and the tangent of $\\mathbf{S}$ as\n", + "\n", + "$$\n", + "\\frac{\\partial \\mathbf{S}}{\\partial \\mathbf{C}} = 2 \\frac{\\partial^2 \\Psi}{\\partial \\mathbf{C}^2}.\n", + "$$\n", + "\n", + "Finally, for the finite element problem we need $\\mathbf{P}$ and\n", + "$\\frac{\\partial \\mathbf{P}}{\\partial \\mathbf{F}}$, which can be\n", + "obtained by using the following relations:\n", + "\n", + "$$\n", + "\\begin{align*}\n", + "\\mathbf{P} &= \\mathbf{F} \\cdot \\mathbf{S},\\\\\n", + "\\frac{\\partial \\mathbf{P}}{\\partial \\mathbf{F}} &= \\mathbf{I} \\bar{\\otimes} \\mathbf{S} + 2\\, \\mathbf{F} \\cdot\n", + "\\frac{\\partial \\mathbf{S}}{\\partial \\mathbf{C}} : \\mathbf{F}^\\mathrm{T} \\bar{\\otimes} \\mathbf{I}.\n", + "\\end{align*}\n", + "$$" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "> **Derivation of $\\partial \\mathbf{P} / \\partial \\mathbf{F}$**\n", + ">\n", + "> *Tip:* See [knutam.github.io/tensors](https://knutam.github.io/tensors/Theory/IndexNotation/) for\n", + "> an explanation of the index notation used in this derivation.\n", + "> Using the product rule, the chain rule, and the relations $\\mathbf{P} = \\mathbf{F} \\cdot\n", + "> \\mathbf{S}$ and $\\mathbf{C} = \\mathbf{F}^\\mathrm{T} \\cdot \\mathbf{F}$, we obtain the\n", + "> following:\n", + "> $$\n", + "> \\begin{aligned}\n", + "> \\frac{\\partial P_{ij}}{\\partial F_{kl}} &=\n", + "> \\frac{\\partial (F_{im}S_{mj})}{\\partial F_{kl}} \\\\ &=\n", + "> \\frac{\\partial F_{im}}{\\partial F_{kl}}S_{mj} +\n", + "> F_{im}\\frac{\\partial S_{mj}}{\\partial F_{kl}} \\\\ &=\n", + "> \\delta_{ik}\\delta_{ml} S_{mj} +\n", + "> F_{im}\\frac{\\partial S_{mj}}{\\partial C_{no}}\\frac{\\partial C_{no}}{\\partial F_{kl}} \\\\ &=\n", + "> \\delta_{ik}S_{lj} +\n", + "> F_{im}\\frac{\\partial S_{mj}}{\\partial C_{no}}\n", + "> \\frac{\\partial (F^\\mathrm{T}_{np}F_{po})}{\\partial F_{kl}} \\\\ &=\n", + "> \\delta_{ik}S^\\mathrm{T}_{jl} +\n", + "> F_{im}\\frac{\\partial S_{mj}}{\\partial C_{no}}\n", + "> \\left(\n", + "> \\frac{\\partial F^\\mathrm{T}_{np}}{\\partial F_{kl}}F_{po} +\n", + "> F^\\mathrm{T}_{np}\\frac{\\partial F_{po}}{\\partial F_{kl}}\n", + "> \\right) \\\\ &=\n", + "> \\delta_{ik}S_{jl} +\n", + "> F_{im}\\frac{\\partial S_{mj}}{\\partial C_{no}}\n", + "> (\\delta_{nl} \\delta_{pk} F_{po} + F^\\mathrm{T}_{np}\\delta_{pk} \\delta_{ol}) \\\\ &=\n", + "> \\delta_{ik}S_{lj} +\n", + "> F_{im}\\frac{\\partial S_{mj}}{\\partial C_{no}}\n", + "> (F^\\mathrm{T}_{ok} \\delta_{nl} + F^\\mathrm{T}_{nk} \\delta_{ol}) \\\\ &=\n", + "> \\delta_{ik}S_{jl} +\n", + "> 2\\, F_{im} \\frac{\\partial S_{mj}}{\\partial C_{no}}\n", + "> F^\\mathrm{T}_{nk} \\delta_{ol} \\\\\n", + "> \\frac{\\partial \\mathbf{P}}{\\partial \\mathbf{F}} &=\n", + "> \\mathbf{I}\\bar{\\otimes}\\mathbf{S} +\n", + "> 2\\, \\mathbf{F} \\cdot \\frac{\\partial \\mathbf{S}}{\\partial \\mathbf{C}}\n", + "> : \\mathbf{F}^\\mathrm{T} \\bar{\\otimes} \\mathbf{I},\n", + "> \\end{aligned}\n", + "> $$\n", + "> where we used the fact that $\\mathbf{S}$ is symmetric ($S_{lj} = S_{jl}$) and that\n", + "> $\\frac{\\partial \\mathbf{S}}{\\partial \\mathbf{C}}$ is *minor* symmetric ($\\frac{\\partial\n", + "> S_{mj}}{\\partial C_{no}} = \\frac{\\partial S_{mj}}{\\partial C_{on}}$)." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "### Implementation of material model using automatic differentiation\n", + "We can implement the material model as follows, where we utilize automatic differentiation\n", + "for the stress and the tangent, and thus only define the potential:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "struct NeoHooke\n", + " μ::Float64\n", + " λ::Float64\n", + "end\n", + "\n", + "function Ψ(C, mp::NeoHooke)\n", + " μ = mp.μ\n", + " λ = mp.λ\n", + " Ic = tr(C)\n", + " J = sqrt(det(C))\n", + " return μ / 2 * (Ic - 3 - 2 * log(J)) + λ / 2 * (J - 1)^2\n", + "end\n", + "\n", + "function constitutive_driver(C, mp::NeoHooke)\n", + " # Compute all derivatives in one function call\n", + " ∂²Ψ∂C², ∂Ψ∂C = Tensors.hessian(y -> Ψ(y, mp), C, :all)\n", + " S = 2.0 * ∂Ψ∂C\n", + " ∂S∂C = 2.0 * ∂²Ψ∂C²\n", + " return S, ∂S∂C\n", + "end;" + ], + "metadata": {}, + "execution_count": 2 + }, + { + "cell_type": "markdown", + "source": [ + "## Newton's method\n", + "\n", + "As mentioned above, to deal with the non-linear weak form we first linearize\n", + "the problem such that we can apply Newton's method, and then apply the FEM to\n", + "discretize the problem. Skipping a detailed derivation, Newton's method can\n", + "be expressed as:\n", + "Given some initial guess for the degrees of freedom $\\underline{u}^0$, find a sequence\n", + "$\\underline{u}^{k}$ by iterating\n", + "\n", + "$$\n", + "\\underline{u}^{k+1} = \\underline{u}^{k} - \\Delta \\underline{u}^{k}\n", + "$$\n", + "\n", + "until some termination condition has been met. Therein we determine $\\Delta \\underline{u}^{k}$\n", + "from the linearized problem\n", + "\n", + "$$\n", + "\\underline{\\underline{K}}(\\underline{u}^{k}) \\Delta \\underline{u}^{k} = \\underline{g}(\\underline{u}^{k})\n", + "$$\n", + "\n", + "where the global residual, $\\underline{g}$, and the Jacobi matrix,\n", + "$\\underline{\\underline{K}} = \\frac{\\partial \\underline{g}}{\\partial \\underline{u}}$, are\n", + "evaluated at the current guess $\\underline{u}^k$. The entries of $\\underline{g}$ are given\n", + "by\n", + "\n", + "$$\n", + "(\\underline{g})_{i} = \\int_{\\Omega} [\\nabla_{\\mathbf{X}} \\delta \\mathbf{u}_{i}] :\n", + "\\mathbf{P} \\, \\mathrm{d} \\Omega - \\int_{\\Omega} \\delta \\mathbf{u}_{i} \\cdot \\mathbf{b} \\,\n", + "\\mathrm{d} \\Omega - \\int_{\\Gamma_\\mathrm{N}} \\delta \\mathbf{u}_i \\cdot \\mathbf{t}\\\n", + "\\mathrm{d}\\Gamma,\n", + "$$\n", + "\n", + "and the entries of $\\underline{\\underline{K}}$ are given by\n", + "\n", + "$$\n", + "(\\underline{\\underline{K}})_{ij} = \\int_{\\Omega} [\\nabla_{\\mathbf{X}} \\delta\n", + "\\mathbf{u}_{i}] : \\frac{\\partial \\mathbf{P}}{\\partial \\mathbf{F}} : [\\nabla_{\\mathbf{X}}\n", + "\\delta \\mathbf{u}_{j}] \\, \\mathrm{d} \\Omega.\n", + "$$\n", + "\n", + "\n", + "A detailed derivation can be found in every continuum mechanics book, which has a\n", + "chapter about finite elasticity theory. We used \"Nonlinear solid mechanics: a continuum\n", + "approach for engineering science.\" by [Hol:2000:nsm; Chapter 8](@citet) as a reference.\n", + "\n", + "## Finite element assembly\n", + "\n", + "The element routine for assembling the residual and tangent stiffness is implemented\n", + "as usual, with loops over quadrature points and shape functions:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function assemble_element!(ke, ge, cell, cv, fv, mp, ue, ΓN)\n", + " # Reinitialize cell values, and reset output arrays\n", + " reinit!(cv, cell)\n", + " fill!(ke, 0.0)\n", + " fill!(ge, 0.0)\n", + "\n", + " b = Vec{3}((0.0, -0.5, 0.0)) # Body force\n", + " tn = 0.1 # Traction (to be scaled with surface normal)\n", + " ndofs = getnbasefunctions(cv)\n", + "\n", + " for qp in 1:getnquadpoints(cv)\n", + " dΩ = getdetJdV(cv, qp)\n", + " # Compute deformation gradient F and right Cauchy-Green tensor C\n", + " ∇u = function_gradient(cv, qp, ue)\n", + " F = one(∇u) + ∇u\n", + " C = tdot(F) # F' ⋅ F\n", + " # Compute stress and tangent\n", + " S, ∂S∂C = constitutive_driver(C, mp)\n", + " P = F ⋅ S\n", + " I = one(S)\n", + " ∂P∂F = otimesu(I, S) + 2 * F ⋅ ∂S∂C ⊡ otimesu(F', I)\n", + "\n", + " # Loop over test functions\n", + " for i in 1:ndofs\n", + " # Test function and gradient\n", + " δui = shape_value(cv, qp, i)\n", + " ∇δui = shape_gradient(cv, qp, i)\n", + " # Add contribution to the residual from this test function\n", + " ge[i] += (∇δui ⊡ P - δui ⋅ b) * dΩ\n", + "\n", + " ∇δui∂P∂F = ∇δui ⊡ ∂P∂F # Hoisted computation\n", + " for j in 1:ndofs\n", + " ∇δuj = shape_gradient(cv, qp, j)\n", + " # Add contribution to the tangent\n", + " ke[i, j] += (∇δui∂P∂F ⊡ ∇δuj) * dΩ\n", + " end\n", + " end\n", + " end\n", + "\n", + " # Surface integral for the traction\n", + " for facet in 1:nfacets(cell)\n", + " if (cellid(cell), facet) in ΓN\n", + " reinit!(fv, cell, facet)\n", + " for q_point in 1:getnquadpoints(fv)\n", + " t = tn * getnormal(fv, q_point)\n", + " dΓ = getdetJdV(fv, q_point)\n", + " for i in 1:ndofs\n", + " δui = shape_value(fv, q_point, i)\n", + " ge[i] -= (δui ⋅ t) * dΓ\n", + " end\n", + " end\n", + " end\n", + " end\n", + " return\n", + "end;" + ], + "metadata": {}, + "execution_count": 3 + }, + { + "cell_type": "markdown", + "source": [ + "Assembling global residual and tangent is also done in the usual way, just looping over\n", + "the elements, call the element routine and assemble in the the global matrix K and\n", + "residual g." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function assemble_global!(K, g, dh, cv, fv, mp, u, ΓN)\n", + " n = ndofs_per_cell(dh)\n", + " ke = zeros(n, n)\n", + " ge = zeros(n)\n", + "\n", + " # start_assemble resets K and g\n", + " assembler = start_assemble(K, g)\n", + "\n", + " # Loop over all cells in the grid\n", + " @timeit \"assemble\" for cell in CellIterator(dh)\n", + " global_dofs = celldofs(cell)\n", + " ue = u[global_dofs] # element dofs\n", + " @timeit \"element assemble\" assemble_element!(ke, ge, cell, cv, fv, mp, ue, ΓN)\n", + " assemble!(assembler, global_dofs, ke, ge)\n", + " end\n", + " return\n", + "end;" + ], + "metadata": {}, + "execution_count": 4 + }, + { + "cell_type": "markdown", + "source": [ + "Finally, we define a main function which sets up everything and then performs Newton\n", + "iterations until convergence." + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "solve (generic function with 1 method)" + }, + "metadata": {}, + "execution_count": 5 + } + ], + "cell_type": "code", + "source": [ + "function solve()\n", + " reset_timer!()\n", + "\n", + " # Generate a grid\n", + " N = 10\n", + " L = 1.0\n", + " left = zero(Vec{3})\n", + " right = L * ones(Vec{3})\n", + " grid = generate_grid(Tetrahedron, (N, N, N), left, right)\n", + "\n", + " # Material parameters\n", + " E = 10.0\n", + " ν = 0.3\n", + " μ = E / (2(1 + ν))\n", + " λ = (E * ν) / ((1 + ν) * (1 - 2ν))\n", + " mp = NeoHooke(μ, λ)\n", + "\n", + " # Finite element base\n", + " ip = Lagrange{RefTetrahedron, 1}()^3\n", + " qr = QuadratureRule{RefTetrahedron}(1)\n", + " qr_facet = FacetQuadratureRule{RefTetrahedron}(1)\n", + " cv = CellValues(qr, ip)\n", + " fv = FacetValues(qr_facet, ip)\n", + "\n", + " # DofHandler\n", + " dh = DofHandler(grid)\n", + " add!(dh, :u, ip) # Add a displacement field\n", + " close!(dh)\n", + "\n", + " function rotation(X, t)\n", + " θ = pi / 3 # 60°\n", + " x, y, z = X\n", + " return t * Vec{3}(\n", + " (\n", + " 0.0,\n", + " L / 2 - y + (y - L / 2) * cos(θ) - (z - L / 2) * sin(θ),\n", + " L / 2 - z + (y - L / 2) * sin(θ) + (z - L / 2) * cos(θ),\n", + " )\n", + " )\n", + " end\n", + "\n", + " dbcs = ConstraintHandler(dh)\n", + " # Add a homogeneous boundary condition on the \"clamped\" edge\n", + " dbc = Dirichlet(:u, getfacetset(grid, \"right\"), (x, t) -> [0.0, 0.0, 0.0], [1, 2, 3])\n", + " add!(dbcs, dbc)\n", + " dbc = Dirichlet(:u, getfacetset(grid, \"left\"), (x, t) -> rotation(x, t), [1, 2, 3])\n", + " add!(dbcs, dbc)\n", + " close!(dbcs)\n", + " t = 0.5\n", + " Ferrite.update!(dbcs, t)\n", + "\n", + " # Neumann part of the boundary\n", + " ΓN = union(\n", + " getfacetset(grid, \"top\"),\n", + " getfacetset(grid, \"bottom\"),\n", + " getfacetset(grid, \"front\"),\n", + " getfacetset(grid, \"back\"),\n", + " )\n", + "\n", + " # Pre-allocation of vectors for the solution and Newton increments\n", + " _ndofs = ndofs(dh)\n", + " un = zeros(_ndofs) # previous solution vector\n", + " u = zeros(_ndofs)\n", + " Δu = zeros(_ndofs)\n", + " ΔΔu = zeros(_ndofs)\n", + " apply!(un, dbcs)\n", + "\n", + " # Create sparse matrix and residual vector\n", + " K = allocate_matrix(dh)\n", + " g = zeros(_ndofs)\n", + "\n", + " # Perform Newton iterations\n", + " newton_itr = -1\n", + " NEWTON_TOL = 1.0e-8\n", + " NEWTON_MAXITER = 30\n", + " prog = ProgressMeter.ProgressThresh(NEWTON_TOL; desc = \"Solving:\")\n", + "\n", + " while true\n", + " newton_itr += 1\n", + " # Construct the current guess\n", + " u .= un .+ Δu\n", + " # Compute residual and tangent for current guess\n", + " assemble_global!(K, g, dh, cv, fv, mp, u, ΓN)\n", + " # Apply boundary conditions\n", + " apply_zero!(K, g, dbcs)\n", + " # Compute the residual norm and compare with tolerance\n", + " normg = norm(g)\n", + " ProgressMeter.update!(prog, normg; showvalues = [(:iter, newton_itr)])\n", + " if normg < NEWTON_TOL\n", + " break\n", + " elseif newton_itr > NEWTON_MAXITER\n", + " error(\"Reached maximum Newton iterations, aborting\")\n", + " end\n", + "\n", + " # Compute increment using conjugate gradients\n", + " @timeit \"linear solve\" IterativeSolvers.cg!(ΔΔu, K, g; maxiter = 1000)\n", + "\n", + " apply_zero!(ΔΔu, dbcs)\n", + " Δu .-= ΔΔu\n", + " end\n", + "\n", + " # Save the solution\n", + " @timeit \"export\" begin\n", + " VTKGridFile(\"hyperelasticity\", dh) do vtk\n", + " write_solution(vtk, dh, u)\n", + " end\n", + " end\n", + "\n", + " print_timer(title = \"Analysis with $(getncells(grid)) elements\", linechars = :ascii)\n", + " return u\n", + "end" + ], + "metadata": {}, + "execution_count": 5 + }, + { + "cell_type": "markdown", + "source": [ + "Run the simulation" + ], + "metadata": {} + }, + { + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\rSolving: (thresh = 1e-08, value = 0.000259699)\u001b[K\r\n", + " iter: 3\u001b[K\r\u001b[A\n", + "\r\u001b[K\u001b[A\rSolving: Time: 0:00:00 (6 iterations)\u001b[K\r\n", + " iter: 5\u001b[K\n", + "-------------------------------------------------------------------------------\n", + " Analysis with 6000 elements Time Allocations \n", + " ----------------------- ------------------------\n", + " Tot / % measured: 1.91s / 50.2% 145MiB / 44.2% \n", + "\n", + "Section ncalls time %tot avg alloc %tot avg\n", + "-------------------------------------------------------------------------------\n", + "export 1 775ms 80.8% 775ms 58.0MiB 90.7% 58.0MiB\n", + "assemble 6 110ms 11.4% 18.3ms 5.50MiB 8.6% 938KiB\n", + " element assemble 36.0k 64.3ms 6.7% 1.79μs 0.00B 0.0% 0.00B\n", + "linear solve 5 74.7ms 7.8% 14.9ms 473KiB 0.7% 94.6KiB\n", + "-------------------------------------------------------------------------------\n" + ] + } + ], + "cell_type": "code", + "source": [ + "u = solve();" + ], + "metadata": {}, + "execution_count": 6 + }, + { + "cell_type": "markdown", + "source": [ + "---\n", + "\n", + "*This notebook was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).*" + ], + "metadata": {} + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.1" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.1", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR798/tutorials/hyperelasticity.jl b/previews/PR798/tutorials/hyperelasticity.jl new file mode 100644 index 0000000000..aaf68928d5 --- /dev/null +++ b/previews/PR798/tutorials/hyperelasticity.jl @@ -0,0 +1,212 @@ +using Ferrite, Tensors, TimerOutputs, ProgressMeter, IterativeSolvers + +struct NeoHooke + μ::Float64 + λ::Float64 +end + +function Ψ(C, mp::NeoHooke) + μ = mp.μ + λ = mp.λ + Ic = tr(C) + J = sqrt(det(C)) + return μ / 2 * (Ic - 3 - 2 * log(J)) + λ / 2 * (J - 1)^2 +end + +function constitutive_driver(C, mp::NeoHooke) + # Compute all derivatives in one function call + ∂²Ψ∂C², ∂Ψ∂C = Tensors.hessian(y -> Ψ(y, mp), C, :all) + S = 2.0 * ∂Ψ∂C + ∂S∂C = 2.0 * ∂²Ψ∂C² + return S, ∂S∂C +end; + +function assemble_element!(ke, ge, cell, cv, fv, mp, ue, ΓN) + # Reinitialize cell values, and reset output arrays + reinit!(cv, cell) + fill!(ke, 0.0) + fill!(ge, 0.0) + + b = Vec{3}((0.0, -0.5, 0.0)) # Body force + tn = 0.1 # Traction (to be scaled with surface normal) + ndofs = getnbasefunctions(cv) + + for qp in 1:getnquadpoints(cv) + dΩ = getdetJdV(cv, qp) + # Compute deformation gradient F and right Cauchy-Green tensor C + ∇u = function_gradient(cv, qp, ue) + F = one(∇u) + ∇u + C = tdot(F) # F' ⋅ F + # Compute stress and tangent + S, ∂S∂C = constitutive_driver(C, mp) + P = F ⋅ S + I = one(S) + ∂P∂F = otimesu(I, S) + 2 * F ⋅ ∂S∂C ⊡ otimesu(F', I) + + # Loop over test functions + for i in 1:ndofs + # Test function and gradient + δui = shape_value(cv, qp, i) + ∇δui = shape_gradient(cv, qp, i) + # Add contribution to the residual from this test function + ge[i] += (∇δui ⊡ P - δui ⋅ b) * dΩ + + ∇δui∂P∂F = ∇δui ⊡ ∂P∂F # Hoisted computation + for j in 1:ndofs + ∇δuj = shape_gradient(cv, qp, j) + # Add contribution to the tangent + ke[i, j] += (∇δui∂P∂F ⊡ ∇δuj) * dΩ + end + end + end + + # Surface integral for the traction + for facet in 1:nfacets(cell) + if (cellid(cell), facet) in ΓN + reinit!(fv, cell, facet) + for q_point in 1:getnquadpoints(fv) + t = tn * getnormal(fv, q_point) + dΓ = getdetJdV(fv, q_point) + for i in 1:ndofs + δui = shape_value(fv, q_point, i) + ge[i] -= (δui ⋅ t) * dΓ + end + end + end + end + return +end; + +function assemble_global!(K, g, dh, cv, fv, mp, u, ΓN) + n = ndofs_per_cell(dh) + ke = zeros(n, n) + ge = zeros(n) + + # start_assemble resets K and g + assembler = start_assemble(K, g) + + # Loop over all cells in the grid + @timeit "assemble" for cell in CellIterator(dh) + global_dofs = celldofs(cell) + ue = u[global_dofs] # element dofs + @timeit "element assemble" assemble_element!(ke, ge, cell, cv, fv, mp, ue, ΓN) + assemble!(assembler, global_dofs, ke, ge) + end + return +end; + +function solve() + reset_timer!() + + # Generate a grid + N = 10 + L = 1.0 + left = zero(Vec{3}) + right = L * ones(Vec{3}) + grid = generate_grid(Tetrahedron, (N, N, N), left, right) + + # Material parameters + E = 10.0 + ν = 0.3 + μ = E / (2(1 + ν)) + λ = (E * ν) / ((1 + ν) * (1 - 2ν)) + mp = NeoHooke(μ, λ) + + # Finite element base + ip = Lagrange{RefTetrahedron, 1}()^3 + qr = QuadratureRule{RefTetrahedron}(1) + qr_facet = FacetQuadratureRule{RefTetrahedron}(1) + cv = CellValues(qr, ip) + fv = FacetValues(qr_facet, ip) + + # DofHandler + dh = DofHandler(grid) + add!(dh, :u, ip) # Add a displacement field + close!(dh) + + function rotation(X, t) + θ = pi / 3 # 60° + x, y, z = X + return t * Vec{3}( + ( + 0.0, + L / 2 - y + (y - L / 2) * cos(θ) - (z - L / 2) * sin(θ), + L / 2 - z + (y - L / 2) * sin(θ) + (z - L / 2) * cos(θ), + ) + ) + end + + dbcs = ConstraintHandler(dh) + # Add a homogeneous boundary condition on the "clamped" edge + dbc = Dirichlet(:u, getfacetset(grid, "right"), (x, t) -> [0.0, 0.0, 0.0], [1, 2, 3]) + add!(dbcs, dbc) + dbc = Dirichlet(:u, getfacetset(grid, "left"), (x, t) -> rotation(x, t), [1, 2, 3]) + add!(dbcs, dbc) + close!(dbcs) + t = 0.5 + Ferrite.update!(dbcs, t) + + # Neumann part of the boundary + ΓN = union( + getfacetset(grid, "top"), + getfacetset(grid, "bottom"), + getfacetset(grid, "front"), + getfacetset(grid, "back"), + ) + + # Pre-allocation of vectors for the solution and Newton increments + _ndofs = ndofs(dh) + un = zeros(_ndofs) # previous solution vector + u = zeros(_ndofs) + Δu = zeros(_ndofs) + ΔΔu = zeros(_ndofs) + apply!(un, dbcs) + + # Create sparse matrix and residual vector + K = allocate_matrix(dh) + g = zeros(_ndofs) + + # Perform Newton iterations + newton_itr = -1 + NEWTON_TOL = 1.0e-8 + NEWTON_MAXITER = 30 + prog = ProgressMeter.ProgressThresh(NEWTON_TOL; desc = "Solving:") + + while true + newton_itr += 1 + # Construct the current guess + u .= un .+ Δu + # Compute residual and tangent for current guess + assemble_global!(K, g, dh, cv, fv, mp, u, ΓN) + # Apply boundary conditions + apply_zero!(K, g, dbcs) + # Compute the residual norm and compare with tolerance + normg = norm(g) + ProgressMeter.update!(prog, normg; showvalues = [(:iter, newton_itr)]) + if normg < NEWTON_TOL + break + elseif newton_itr > NEWTON_MAXITER + error("Reached maximum Newton iterations, aborting") + end + + # Compute increment using conjugate gradients + @timeit "linear solve" IterativeSolvers.cg!(ΔΔu, K, g; maxiter = 1000) + + apply_zero!(ΔΔu, dbcs) + Δu .-= ΔΔu + end + + # Save the solution + @timeit "export" begin + VTKGridFile("hyperelasticity", dh) do vtk + write_solution(vtk, dh, u) + end + end + + print_timer(title = "Analysis with $(getncells(grid)) elements", linechars = :ascii) + return u +end + +u = solve(); + +# This file was generated using Literate.jl, https://github.com/fredrikekre/Literate.jl diff --git a/previews/PR798/tutorials/hyperelasticity.png b/previews/PR798/tutorials/hyperelasticity.png new file mode 100644 index 0000000000..38713fc540 Binary files /dev/null and b/previews/PR798/tutorials/hyperelasticity.png differ diff --git a/previews/PR798/tutorials/hyperelasticity/index.html b/previews/PR798/tutorials/hyperelasticity/index.html new file mode 100644 index 0000000000..e75457687d --- /dev/null +++ b/previews/PR798/tutorials/hyperelasticity/index.html @@ -0,0 +1,466 @@ + +Hyperelasticity · Ferrite.jl

Hyperelasticity

Keywords: hyperelasticity, finite strain, large deformations, Newton's method, conjugate gradient, automatic differentiation

hyperelasticity.png

Figure 1: Cube loaded in torsion modeled with a hyperelastic material model and finite strain.

Tip

This example is also available as a Jupyter notebook: hyperelasticity.ipynb.

Introduction

In this example we will solve a problem in a finite strain setting using an hyperelastic material model. In order to compute the stress we will use automatic differentiation, to solve the non-linear system we use Newton's method, and for solving the Newton increment we use conjugate gradients.

The weak form is expressed in terms of the first Piola-Kirchoff stress $\mathbf{P}$ as follows: Find $\mathbf{u} \in \mathbb{U}$ such that

\[\int_{\Omega} [\nabla_{\mathbf{X}} \delta \mathbf{u}] : \mathbf{P}(\mathbf{u})\ \mathrm{d}\Omega = +\int_{\Omega} \delta \mathbf{u} \cdot \mathbf{b}\ \mathrm{d}\Omega + \int_{\Gamma_\mathrm{N}} +\delta \mathbf{u} \cdot \mathbf{t}\ \mathrm{d}\Gamma +\quad \forall \delta \mathbf{u} \in \mathbb{U}^0,\]

where $\mathbf{u}$ is the unknown displacement field, $\mathbf{b}$ is the body force acting on the reference domain, $\mathbf{t}$ is the traction acting on the Neumann part of the reference domain's boundary, and where $\mathbb{U}$ and $\mathbb{U}^0$ are suitable trial and test sets. $\Omega$ denotes the reference (sometimes also called initial or material) domain. Gradients are defined with respect to the reference domain, here denoted with an $\mathbf{X}$. Formally this is expressed as $(\nabla_{\mathbf{X}} \bullet)_{ij} := \frac{\partial(\bullet)_i}{\partial X_j}$. Note that for large deformation problems it is also possible that gradients and integrals are defined on the deformed (sometimes also called current or spatial) domain, depending on the specific formulation.

The specific problem we will solve in this example is the cube from Figure 1: On one side we apply a rotation using Dirichlet boundary conditions, on the opposite side we fix the displacement with a homogeneous Dirichlet boundary condition, and on the remaining four sides we apply a traction in the normal direction of the surface. In addition, a body force is applied in one direction.

In addition to Ferrite.jl and Tensors.jl, this examples uses TimerOutputs.jl for timing the program and print a summary at the end, ProgressMeter.jl for showing a simple progress bar, and IterativeSolvers.jl for solving the linear system using conjugate gradients.

using Ferrite, Tensors, TimerOutputs, ProgressMeter, IterativeSolvers

Hyperelastic material model

The stress can be derived from an energy potential, defined in terms of the right Cauchy-Green tensor $\mathbf{C} = \mathbf{F}^{\mathrm{T}} \cdot \mathbf{F}$, where $\mathbf{F} = \mathbf{I} + \nabla_{\mathbf{X}} \mathbf{u}$ is the deformation gradient. We shall use the compressible neo-Hookean model from Wikipedia with the potential

\[\Psi(\mathbf{C}) = \underbrace{\frac{\mu}{2} (I_1 - 3)}_{W(\mathbf{C})} \underbrace{- {\mu} \ln(J) + \frac{\lambda}{2} (J - 1)^2}_{U(J)},\]

where $I_1 = \mathrm{tr}(\mathbf{C})$ is the first invariant, $J = \sqrt{\det(\mathbf{C})}$ and $\mu$ and $\lambda$ material parameters.

Extra details on compressible neo-Hookean formulations

The Neo-Hooke model is only a well defined terminology in the incompressible case. Thus, only $W(\mathbf{C})$ specifies the neo-Hookean behavior, the volume penalty $U(J)$ can vary in different formulations. In order to obtain a well-posed problem, it is crucial to choose a convex formulation of $U(J)$. Other examples for $U(J)$ can be found, e.g. in [3, Eq. (6.138)]

\[ \beta^{-2} (\beta \ln J + J^{-\beta} -1)\]

where [4, Eq. (2.37)] published a non-generalized version with $\beta=-2$. This shows the possible variety of $U(J)$ while all of them refer to compressible neo-Hookean models. Sometimes the modified first invariant $\overline{I}_1=\frac{I_1}{I_3^{1/3}}$ is used in $W(\mathbf{C})$ instead of $I_1$.

From the potential we obtain the second Piola-Kirchoff stress $\mathbf{S}$ as

\[\mathbf{S} = 2 \frac{\partial \Psi}{\partial \mathbf{C}},\]

and the tangent of $\mathbf{S}$ as

\[\frac{\partial \mathbf{S}}{\partial \mathbf{C}} = 2 \frac{\partial^2 \Psi}{\partial \mathbf{C}^2}.\]

Finally, for the finite element problem we need $\mathbf{P}$ and $\frac{\partial \mathbf{P}}{\partial \mathbf{F}}$, which can be obtained by using the following relations:

\[\begin{align*} +\mathbf{P} &= \mathbf{F} \cdot \mathbf{S},\\ +\frac{\partial \mathbf{P}}{\partial \mathbf{F}} &= \mathbf{I} \bar{\otimes} \mathbf{S} + 2\, \mathbf{F} \cdot +\frac{\partial \mathbf{S}}{\partial \mathbf{C}} : \mathbf{F}^\mathrm{T} \bar{\otimes} \mathbf{I}. +\end{align*}\]

Derivation of $\partial \mathbf{P} / \partial \mathbf{F}$

Tip: See knutam.github.io/tensors for an explanation of the index notation used in this derivation.

Using the product rule, the chain rule, and the relations $\mathbf{P} = \mathbf{F} \cdot \mathbf{S}$ and $\mathbf{C} = \mathbf{F}^\mathrm{T} \cdot \mathbf{F}$, we obtain the following:

\[\begin{aligned} +\frac{\partial P_{ij}}{\partial F_{kl}} &= +\frac{\partial (F_{im}S_{mj})}{\partial F_{kl}} \\ &= +\frac{\partial F_{im}}{\partial F_{kl}}S_{mj} + +F_{im}\frac{\partial S_{mj}}{\partial F_{kl}} \\ &= +\delta_{ik}\delta_{ml} S_{mj} + +F_{im}\frac{\partial S_{mj}}{\partial C_{no}}\frac{\partial C_{no}}{\partial F_{kl}} \\ &= +\delta_{ik}S_{lj} + +F_{im}\frac{\partial S_{mj}}{\partial C_{no}} +\frac{\partial (F^\mathrm{T}_{np}F_{po})}{\partial F_{kl}} \\ &= +\delta_{ik}S^\mathrm{T}_{jl} + +F_{im}\frac{\partial S_{mj}}{\partial C_{no}} +\left( +\frac{\partial F^\mathrm{T}_{np}}{\partial F_{kl}}F_{po} + +F^\mathrm{T}_{np}\frac{\partial F_{po}}{\partial F_{kl}} +\right) \\ &= +\delta_{ik}S_{jl} + +F_{im}\frac{\partial S_{mj}}{\partial C_{no}} +(\delta_{nl} \delta_{pk} F_{po} + F^\mathrm{T}_{np}\delta_{pk} \delta_{ol}) \\ &= +\delta_{ik}S_{lj} + +F_{im}\frac{\partial S_{mj}}{\partial C_{no}} +(F^\mathrm{T}_{ok} \delta_{nl} + F^\mathrm{T}_{nk} \delta_{ol}) \\ &= +\delta_{ik}S_{jl} + +2\, F_{im} \frac{\partial S_{mj}}{\partial C_{no}} +F^\mathrm{T}_{nk} \delta_{ol} \\ +\frac{\partial \mathbf{P}}{\partial \mathbf{F}} &= +\mathbf{I}\bar{\otimes}\mathbf{S} + +2\, \mathbf{F} \cdot \frac{\partial \mathbf{S}}{\partial \mathbf{C}} +: \mathbf{F}^\mathrm{T} \bar{\otimes} \mathbf{I}, +\end{aligned}\]

where we used the fact that $\mathbf{S}$ is symmetric ($S_{lj} = S_{jl}$) and that $\frac{\partial \mathbf{S}}{\partial \mathbf{C}}$ is minor symmetric ($\frac{\partial S_{mj}}{\partial C_{no}} = \frac{\partial S_{mj}}{\partial C_{on}}$).

Implementation of material model using automatic differentiation

We can implement the material model as follows, where we utilize automatic differentiation for the stress and the tangent, and thus only define the potential:

struct NeoHooke
+    μ::Float64
+    λ::Float64
+end
+
+function Ψ(C, mp::NeoHooke)
+    μ = mp.μ
+    λ = mp.λ
+    Ic = tr(C)
+    J = sqrt(det(C))
+    return μ / 2 * (Ic - 3 - 2 * log(J)) + λ / 2 * (J - 1)^2
+end
+
+function constitutive_driver(C, mp::NeoHooke)
+    # Compute all derivatives in one function call
+    ∂²Ψ∂C², ∂Ψ∂C = Tensors.hessian(y -> Ψ(y, mp), C, :all)
+    S = 2.0 * ∂Ψ∂C
+    ∂S∂C = 2.0 * ∂²Ψ∂C²
+    return S, ∂S∂C
+end;

Newton's method

As mentioned above, to deal with the non-linear weak form we first linearize the problem such that we can apply Newton's method, and then apply the FEM to discretize the problem. Skipping a detailed derivation, Newton's method can be expressed as: Given some initial guess for the degrees of freedom $\underline{u}^0$, find a sequence $\underline{u}^{k}$ by iterating

\[\underline{u}^{k+1} = \underline{u}^{k} - \Delta \underline{u}^{k}\]

until some termination condition has been met. Therein we determine $\Delta \underline{u}^{k}$ from the linearized problem

\[\underline{\underline{K}}(\underline{u}^{k}) \Delta \underline{u}^{k} = \underline{g}(\underline{u}^{k})\]

where the global residual, $\underline{g}$, and the Jacobi matrix, $\underline{\underline{K}} = \frac{\partial \underline{g}}{\partial \underline{u}}$, are evaluated at the current guess $\underline{u}^k$. The entries of $\underline{g}$ are given by

\[(\underline{g})_{i} = \int_{\Omega} [\nabla_{\mathbf{X}} \delta \mathbf{u}_{i}] : +\mathbf{P} \, \mathrm{d} \Omega - \int_{\Omega} \delta \mathbf{u}_{i} \cdot \mathbf{b} \, +\mathrm{d} \Omega - \int_{\Gamma_\mathrm{N}} \delta \mathbf{u}_i \cdot \mathbf{t}\ +\mathrm{d}\Gamma,\]

and the entries of $\underline{\underline{K}}$ are given by

\[(\underline{\underline{K}})_{ij} = \int_{\Omega} [\nabla_{\mathbf{X}} \delta +\mathbf{u}_{i}] : \frac{\partial \mathbf{P}}{\partial \mathbf{F}} : [\nabla_{\mathbf{X}} +\delta \mathbf{u}_{j}] \, \mathrm{d} \Omega.\]

A detailed derivation can be found in every continuum mechanics book, which has a chapter about finite elasticity theory. We used "Nonlinear solid mechanics: a continuum approach for engineering science." by Holzapfel [3], Chapter 8 as a reference.

Finite element assembly

The element routine for assembling the residual and tangent stiffness is implemented as usual, with loops over quadrature points and shape functions:

function assemble_element!(ke, ge, cell, cv, fv, mp, ue, ΓN)
+    # Reinitialize cell values, and reset output arrays
+    reinit!(cv, cell)
+    fill!(ke, 0.0)
+    fill!(ge, 0.0)
+
+    b = Vec{3}((0.0, -0.5, 0.0)) # Body force
+    tn = 0.1 # Traction (to be scaled with surface normal)
+    ndofs = getnbasefunctions(cv)
+
+    for qp in 1:getnquadpoints(cv)
+        dΩ = getdetJdV(cv, qp)
+        # Compute deformation gradient F and right Cauchy-Green tensor C
+        ∇u = function_gradient(cv, qp, ue)
+        F = one(∇u) + ∇u
+        C = tdot(F) # F' ⋅ F
+        # Compute stress and tangent
+        S, ∂S∂C = constitutive_driver(C, mp)
+        P = F ⋅ S
+        I = one(S)
+        ∂P∂F = otimesu(I, S) + 2 * F ⋅ ∂S∂C ⊡ otimesu(F', I)
+
+        # Loop over test functions
+        for i in 1:ndofs
+            # Test function and gradient
+            δui = shape_value(cv, qp, i)
+            ∇δui = shape_gradient(cv, qp, i)
+            # Add contribution to the residual from this test function
+            ge[i] += (∇δui ⊡ P - δui ⋅ b) * dΩ
+
+            ∇δui∂P∂F = ∇δui ⊡ ∂P∂F # Hoisted computation
+            for j in 1:ndofs
+                ∇δuj = shape_gradient(cv, qp, j)
+                # Add contribution to the tangent
+                ke[i, j] += (∇δui∂P∂F ⊡ ∇δuj) * dΩ
+            end
+        end
+    end
+
+    # Surface integral for the traction
+    for facet in 1:nfacets(cell)
+        if (cellid(cell), facet) in ΓN
+            reinit!(fv, cell, facet)
+            for q_point in 1:getnquadpoints(fv)
+                t = tn * getnormal(fv, q_point)
+                dΓ = getdetJdV(fv, q_point)
+                for i in 1:ndofs
+                    δui = shape_value(fv, q_point, i)
+                    ge[i] -= (δui ⋅ t) * dΓ
+                end
+            end
+        end
+    end
+    return
+end;

Assembling global residual and tangent is also done in the usual way, just looping over the elements, call the element routine and assemble in the the global matrix K and residual g.

function assemble_global!(K, g, dh, cv, fv, mp, u, ΓN)
+    n = ndofs_per_cell(dh)
+    ke = zeros(n, n)
+    ge = zeros(n)
+
+    # start_assemble resets K and g
+    assembler = start_assemble(K, g)
+
+    # Loop over all cells in the grid
+    @timeit "assemble" for cell in CellIterator(dh)
+        global_dofs = celldofs(cell)
+        ue = u[global_dofs] # element dofs
+        @timeit "element assemble" assemble_element!(ke, ge, cell, cv, fv, mp, ue, ΓN)
+        assemble!(assembler, global_dofs, ke, ge)
+    end
+    return
+end;

Finally, we define a main function which sets up everything and then performs Newton iterations until convergence.

function solve()
+    reset_timer!()
+
+    # Generate a grid
+    N = 10
+    L = 1.0
+    left = zero(Vec{3})
+    right = L * ones(Vec{3})
+    grid = generate_grid(Tetrahedron, (N, N, N), left, right)
+
+    # Material parameters
+    E = 10.0
+    ν = 0.3
+    μ = E / (2(1 + ν))
+    λ = (E * ν) / ((1 + ν) * (1 - 2ν))
+    mp = NeoHooke(μ, λ)
+
+    # Finite element base
+    ip = Lagrange{RefTetrahedron, 1}()^3
+    qr = QuadratureRule{RefTetrahedron}(1)
+    qr_facet = FacetQuadratureRule{RefTetrahedron}(1)
+    cv = CellValues(qr, ip)
+    fv = FacetValues(qr_facet, ip)
+
+    # DofHandler
+    dh = DofHandler(grid)
+    add!(dh, :u, ip) # Add a displacement field
+    close!(dh)
+
+    function rotation(X, t)
+        θ = pi / 3 # 60°
+        x, y, z = X
+        return t * Vec{3}(
+            (
+                0.0,
+                L / 2 - y + (y - L / 2) * cos(θ) - (z - L / 2) * sin(θ),
+                L / 2 - z + (y - L / 2) * sin(θ) + (z - L / 2) * cos(θ),
+            )
+        )
+    end
+
+    dbcs = ConstraintHandler(dh)
+    # Add a homogeneous boundary condition on the "clamped" edge
+    dbc = Dirichlet(:u, getfacetset(grid, "right"), (x, t) -> [0.0, 0.0, 0.0], [1, 2, 3])
+    add!(dbcs, dbc)
+    dbc = Dirichlet(:u, getfacetset(grid, "left"), (x, t) -> rotation(x, t), [1, 2, 3])
+    add!(dbcs, dbc)
+    close!(dbcs)
+    t = 0.5
+    Ferrite.update!(dbcs, t)
+
+    # Neumann part of the boundary
+    ΓN = union(
+        getfacetset(grid, "top"),
+        getfacetset(grid, "bottom"),
+        getfacetset(grid, "front"),
+        getfacetset(grid, "back"),
+    )
+
+    # Pre-allocation of vectors for the solution and Newton increments
+    _ndofs = ndofs(dh)
+    un = zeros(_ndofs) # previous solution vector
+    u = zeros(_ndofs)
+    Δu = zeros(_ndofs)
+    ΔΔu = zeros(_ndofs)
+    apply!(un, dbcs)
+
+    # Create sparse matrix and residual vector
+    K = allocate_matrix(dh)
+    g = zeros(_ndofs)
+
+    # Perform Newton iterations
+    newton_itr = -1
+    NEWTON_TOL = 1.0e-8
+    NEWTON_MAXITER = 30
+    prog = ProgressMeter.ProgressThresh(NEWTON_TOL; desc = "Solving:")
+
+    while true
+        newton_itr += 1
+        # Construct the current guess
+        u .= un .+ Δu
+        # Compute residual and tangent for current guess
+        assemble_global!(K, g, dh, cv, fv, mp, u, ΓN)
+        # Apply boundary conditions
+        apply_zero!(K, g, dbcs)
+        # Compute the residual norm and compare with tolerance
+        normg = norm(g)
+        ProgressMeter.update!(prog, normg; showvalues = [(:iter, newton_itr)])
+        if normg < NEWTON_TOL
+            break
+        elseif newton_itr > NEWTON_MAXITER
+            error("Reached maximum Newton iterations, aborting")
+        end
+
+        # Compute increment using conjugate gradients
+        @timeit "linear solve" IterativeSolvers.cg!(ΔΔu, K, g; maxiter = 1000)
+
+        apply_zero!(ΔΔu, dbcs)
+        Δu .-= ΔΔu
+    end
+
+    # Save the solution
+    @timeit "export" begin
+        VTKGridFile("hyperelasticity", dh) do vtk
+            write_solution(vtk, dh, u)
+        end
+    end
+
+    print_timer(title = "Analysis with $(getncells(grid)) elements", linechars = :ascii)
+    return u
+end
solve (generic function with 1 method)

Run the simulation

u = solve();

Solving: (thresh = 1e-08, value = 0.000259699)
+  iter:  3
+

Solving: Time: 0:00:00 (6 iterations)
+  iter:  5
+-------------------------------------------------------------------------------
+ Analysis with 6000 elements          Time                    Allocations
+                             -----------------------   ------------------------
+      Tot / % measured:           295ms /  67.4%           29.7MiB /  33.4%
+
+Section              ncalls     time    %tot     avg     alloc    %tot      avg
+-------------------------------------------------------------------------------
+assemble                  6    110ms   55.5%  18.4ms   5.50MiB   55.5%   938KiB
+  element assemble    36.0k   62.8ms   31.6%  1.75μs     0.00B    0.0%    0.00B
+linear solve              5   73.5ms   37.0%  14.7ms    473KiB    4.7%  94.6KiB
+export                    1   14.8ms    7.5%  14.8ms   3.94MiB   39.8%  3.94MiB
+-------------------------------------------------------------------------------

Plain program

Here follows a version of the program without any comments. The file is also available here: hyperelasticity.jl.

using Ferrite, Tensors, TimerOutputs, ProgressMeter, IterativeSolvers
+
+struct NeoHooke
+    μ::Float64
+    λ::Float64
+end
+
+function Ψ(C, mp::NeoHooke)
+    μ = mp.μ
+    λ = mp.λ
+    Ic = tr(C)
+    J = sqrt(det(C))
+    return μ / 2 * (Ic - 3 - 2 * log(J)) + λ / 2 * (J - 1)^2
+end
+
+function constitutive_driver(C, mp::NeoHooke)
+    # Compute all derivatives in one function call
+    ∂²Ψ∂C², ∂Ψ∂C = Tensors.hessian(y -> Ψ(y, mp), C, :all)
+    S = 2.0 * ∂Ψ∂C
+    ∂S∂C = 2.0 * ∂²Ψ∂C²
+    return S, ∂S∂C
+end;
+
+function assemble_element!(ke, ge, cell, cv, fv, mp, ue, ΓN)
+    # Reinitialize cell values, and reset output arrays
+    reinit!(cv, cell)
+    fill!(ke, 0.0)
+    fill!(ge, 0.0)
+
+    b = Vec{3}((0.0, -0.5, 0.0)) # Body force
+    tn = 0.1 # Traction (to be scaled with surface normal)
+    ndofs = getnbasefunctions(cv)
+
+    for qp in 1:getnquadpoints(cv)
+        dΩ = getdetJdV(cv, qp)
+        # Compute deformation gradient F and right Cauchy-Green tensor C
+        ∇u = function_gradient(cv, qp, ue)
+        F = one(∇u) + ∇u
+        C = tdot(F) # F' ⋅ F
+        # Compute stress and tangent
+        S, ∂S∂C = constitutive_driver(C, mp)
+        P = F ⋅ S
+        I = one(S)
+        ∂P∂F = otimesu(I, S) + 2 * F ⋅ ∂S∂C ⊡ otimesu(F', I)
+
+        # Loop over test functions
+        for i in 1:ndofs
+            # Test function and gradient
+            δui = shape_value(cv, qp, i)
+            ∇δui = shape_gradient(cv, qp, i)
+            # Add contribution to the residual from this test function
+            ge[i] += (∇δui ⊡ P - δui ⋅ b) * dΩ
+
+            ∇δui∂P∂F = ∇δui ⊡ ∂P∂F # Hoisted computation
+            for j in 1:ndofs
+                ∇δuj = shape_gradient(cv, qp, j)
+                # Add contribution to the tangent
+                ke[i, j] += (∇δui∂P∂F ⊡ ∇δuj) * dΩ
+            end
+        end
+    end
+
+    # Surface integral for the traction
+    for facet in 1:nfacets(cell)
+        if (cellid(cell), facet) in ΓN
+            reinit!(fv, cell, facet)
+            for q_point in 1:getnquadpoints(fv)
+                t = tn * getnormal(fv, q_point)
+                dΓ = getdetJdV(fv, q_point)
+                for i in 1:ndofs
+                    δui = shape_value(fv, q_point, i)
+                    ge[i] -= (δui ⋅ t) * dΓ
+                end
+            end
+        end
+    end
+    return
+end;
+
+function assemble_global!(K, g, dh, cv, fv, mp, u, ΓN)
+    n = ndofs_per_cell(dh)
+    ke = zeros(n, n)
+    ge = zeros(n)
+
+    # start_assemble resets K and g
+    assembler = start_assemble(K, g)
+
+    # Loop over all cells in the grid
+    @timeit "assemble" for cell in CellIterator(dh)
+        global_dofs = celldofs(cell)
+        ue = u[global_dofs] # element dofs
+        @timeit "element assemble" assemble_element!(ke, ge, cell, cv, fv, mp, ue, ΓN)
+        assemble!(assembler, global_dofs, ke, ge)
+    end
+    return
+end;
+
+function solve()
+    reset_timer!()
+
+    # Generate a grid
+    N = 10
+    L = 1.0
+    left = zero(Vec{3})
+    right = L * ones(Vec{3})
+    grid = generate_grid(Tetrahedron, (N, N, N), left, right)
+
+    # Material parameters
+    E = 10.0
+    ν = 0.3
+    μ = E / (2(1 + ν))
+    λ = (E * ν) / ((1 + ν) * (1 - 2ν))
+    mp = NeoHooke(μ, λ)
+
+    # Finite element base
+    ip = Lagrange{RefTetrahedron, 1}()^3
+    qr = QuadratureRule{RefTetrahedron}(1)
+    qr_facet = FacetQuadratureRule{RefTetrahedron}(1)
+    cv = CellValues(qr, ip)
+    fv = FacetValues(qr_facet, ip)
+
+    # DofHandler
+    dh = DofHandler(grid)
+    add!(dh, :u, ip) # Add a displacement field
+    close!(dh)
+
+    function rotation(X, t)
+        θ = pi / 3 # 60°
+        x, y, z = X
+        return t * Vec{3}(
+            (
+                0.0,
+                L / 2 - y + (y - L / 2) * cos(θ) - (z - L / 2) * sin(θ),
+                L / 2 - z + (y - L / 2) * sin(θ) + (z - L / 2) * cos(θ),
+            )
+        )
+    end
+
+    dbcs = ConstraintHandler(dh)
+    # Add a homogeneous boundary condition on the "clamped" edge
+    dbc = Dirichlet(:u, getfacetset(grid, "right"), (x, t) -> [0.0, 0.0, 0.0], [1, 2, 3])
+    add!(dbcs, dbc)
+    dbc = Dirichlet(:u, getfacetset(grid, "left"), (x, t) -> rotation(x, t), [1, 2, 3])
+    add!(dbcs, dbc)
+    close!(dbcs)
+    t = 0.5
+    Ferrite.update!(dbcs, t)
+
+    # Neumann part of the boundary
+    ΓN = union(
+        getfacetset(grid, "top"),
+        getfacetset(grid, "bottom"),
+        getfacetset(grid, "front"),
+        getfacetset(grid, "back"),
+    )
+
+    # Pre-allocation of vectors for the solution and Newton increments
+    _ndofs = ndofs(dh)
+    un = zeros(_ndofs) # previous solution vector
+    u = zeros(_ndofs)
+    Δu = zeros(_ndofs)
+    ΔΔu = zeros(_ndofs)
+    apply!(un, dbcs)
+
+    # Create sparse matrix and residual vector
+    K = allocate_matrix(dh)
+    g = zeros(_ndofs)
+
+    # Perform Newton iterations
+    newton_itr = -1
+    NEWTON_TOL = 1.0e-8
+    NEWTON_MAXITER = 30
+    prog = ProgressMeter.ProgressThresh(NEWTON_TOL; desc = "Solving:")
+
+    while true
+        newton_itr += 1
+        # Construct the current guess
+        u .= un .+ Δu
+        # Compute residual and tangent for current guess
+        assemble_global!(K, g, dh, cv, fv, mp, u, ΓN)
+        # Apply boundary conditions
+        apply_zero!(K, g, dbcs)
+        # Compute the residual norm and compare with tolerance
+        normg = norm(g)
+        ProgressMeter.update!(prog, normg; showvalues = [(:iter, newton_itr)])
+        if normg < NEWTON_TOL
+            break
+        elseif newton_itr > NEWTON_MAXITER
+            error("Reached maximum Newton iterations, aborting")
+        end
+
+        # Compute increment using conjugate gradients
+        @timeit "linear solve" IterativeSolvers.cg!(ΔΔu, K, g; maxiter = 1000)
+
+        apply_zero!(ΔΔu, dbcs)
+        Δu .-= ΔΔu
+    end
+
+    # Save the solution
+    @timeit "export" begin
+        VTKGridFile("hyperelasticity", dh) do vtk
+            write_solution(vtk, dh, u)
+        end
+    end
+
+    print_timer(title = "Analysis with $(getncells(grid)) elements", linechars = :ascii)
+    return u
+end
+
+u = solve();

This page was generated using Literate.jl.

diff --git a/previews/PR798/tutorials/incompressible_elasticity.ipynb b/previews/PR798/tutorials/incompressible_elasticity.ipynb new file mode 100644 index 0000000000..7a7e10927c --- /dev/null +++ b/previews/PR798/tutorials/incompressible_elasticity.ipynb @@ -0,0 +1,506 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Incompressible elasticity" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Introduction\n", + "\n", + "Mixed elements can be used to overcome locking when the material becomes\n", + "incompressible. However, for an element to be stable, it needs to fulfill\n", + "the LBB condition.\n", + "In this example we will consider two different element formulations\n", + "- linear displacement with linear pressure approximation (does *not* fulfill LBB)\n", + "- quadratic displacement with linear pressure approximation (does fulfill LBB)\n", + "The quadratic/linear element is also known as the Taylor-Hood element.\n", + "We will consider Cook's Membrane with an applied traction on the right hand side." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Commented program\n", + "\n", + "What follows is a program spliced with comments." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using Ferrite, Tensors\n", + "using BlockArrays, SparseArrays, LinearAlgebra" + ], + "metadata": {}, + "execution_count": 1 + }, + { + "cell_type": "markdown", + "source": [ + "First we generate a simple grid, specifying the 4 corners of Cooks membrane." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function create_cook_grid(nx, ny)\n", + " corners = [\n", + " Vec{2}((0.0, 0.0)),\n", + " Vec{2}((48.0, 44.0)),\n", + " Vec{2}((48.0, 60.0)),\n", + " Vec{2}((0.0, 44.0)),\n", + " ]\n", + " grid = generate_grid(Triangle, (nx, ny), corners)\n", + " # facesets for boundary conditions\n", + " addfacetset!(grid, \"clamped\", x -> norm(x[1]) ≈ 0.0)\n", + " addfacetset!(grid, \"traction\", x -> norm(x[1]) ≈ 48.0)\n", + " return grid\n", + "end;" + ], + "metadata": {}, + "execution_count": 2 + }, + { + "cell_type": "markdown", + "source": [ + "Next we define a function to set up our cell- and FacetValues." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function create_values(interpolation_u, interpolation_p)\n", + " # quadrature rules\n", + " qr = QuadratureRule{RefTriangle}(3)\n", + " facet_qr = FacetQuadratureRule{RefTriangle}(3)\n", + "\n", + " # cell and FacetValues for u\n", + " cellvalues_u = CellValues(qr, interpolation_u)\n", + " facetvalues_u = FacetValues(facet_qr, interpolation_u)\n", + "\n", + " # cellvalues for p\n", + " cellvalues_p = CellValues(qr, interpolation_p)\n", + "\n", + " return cellvalues_u, cellvalues_p, facetvalues_u\n", + "end;" + ], + "metadata": {}, + "execution_count": 3 + }, + { + "cell_type": "markdown", + "source": [ + "We create a DofHandler, with two fields, `:u` and `:p`,\n", + "with possibly different interpolations" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function create_dofhandler(grid, ipu, ipp)\n", + " dh = DofHandler(grid)\n", + " add!(dh, :u, ipu) # displacement\n", + " add!(dh, :p, ipp) # pressure\n", + " close!(dh)\n", + " return dh\n", + "end;" + ], + "metadata": {}, + "execution_count": 4 + }, + { + "cell_type": "markdown", + "source": [ + "We also need to add Dirichlet boundary conditions on the `\"clamped\"` facetset.\n", + "We specify a homogeneous Dirichlet bc on the displacement field, `:u`." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function create_bc(dh)\n", + " dbc = ConstraintHandler(dh)\n", + " add!(dbc, Dirichlet(:u, getfacetset(dh.grid, \"clamped\"), x -> zero(x), [1, 2]))\n", + " close!(dbc)\n", + " return dbc\n", + "end;" + ], + "metadata": {}, + "execution_count": 5 + }, + { + "cell_type": "markdown", + "source": [ + "The material is linear elastic, which is here specified by the shear and bulk moduli" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "struct LinearElasticity{T}\n", + " G::T\n", + " K::T\n", + "end" + ], + "metadata": {}, + "execution_count": 6 + }, + { + "cell_type": "markdown", + "source": [ + "Now to the assembling of the stiffness matrix. This mixed formulation leads to a blocked\n", + "element matrix. Since Ferrite does not force us to use any particular matrix type we will\n", + "use a `BlockedArray` from `BlockArrays.jl`." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function doassemble(\n", + " cellvalues_u::CellValues,\n", + " cellvalues_p::CellValues,\n", + " facetvalues_u::FacetValues,\n", + " K::SparseMatrixCSC, grid::Grid, dh::DofHandler, mp::LinearElasticity\n", + " )\n", + " f = zeros(ndofs(dh))\n", + " assembler = start_assemble(K, f)\n", + " nu = getnbasefunctions(cellvalues_u)\n", + " np = getnbasefunctions(cellvalues_p)\n", + "\n", + " fe = BlockedArray(zeros(nu + np), [nu, np]) # local force vector\n", + " ke = BlockedArray(zeros(nu + np, nu + np), [nu, np], [nu, np]) # local stiffness matrix\n", + "\n", + " # traction vector\n", + " t = Vec{2}((0.0, 1 / 16))\n", + " # cache ɛdev outside the element routine to avoid some unnecessary allocations\n", + " ɛdev = [zero(SymmetricTensor{2, 2}) for i in 1:getnbasefunctions(cellvalues_u)]\n", + "\n", + " for cell in CellIterator(dh)\n", + " fill!(ke, 0)\n", + " fill!(fe, 0)\n", + " assemble_up!(ke, fe, cell, cellvalues_u, cellvalues_p, facetvalues_u, grid, mp, ɛdev, t)\n", + " assemble!(assembler, celldofs(cell), ke, fe)\n", + " end\n", + "\n", + " return K, f\n", + "end;" + ], + "metadata": {}, + "execution_count": 7 + }, + { + "cell_type": "markdown", + "source": [ + "The element routine integrates the local stiffness and force vector for all elements.\n", + "Since the problem results in a symmetric matrix we choose to only assemble the lower part,\n", + "and then symmetrize it after the loop over the quadrature points." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function assemble_up!(Ke, fe, cell, cellvalues_u, cellvalues_p, facetvalues_u, grid, mp, ɛdev, t)\n", + "\n", + " n_basefuncs_u = getnbasefunctions(cellvalues_u)\n", + " n_basefuncs_p = getnbasefunctions(cellvalues_p)\n", + " u▄, p▄ = 1, 2\n", + " reinit!(cellvalues_u, cell)\n", + " reinit!(cellvalues_p, cell)\n", + "\n", + " # We only assemble lower half triangle of the stiffness matrix and then symmetrize it.\n", + " for q_point in 1:getnquadpoints(cellvalues_u)\n", + " for i in 1:n_basefuncs_u\n", + " ɛdev[i] = dev(symmetric(shape_gradient(cellvalues_u, q_point, i)))\n", + " end\n", + " dΩ = getdetJdV(cellvalues_u, q_point)\n", + " for i in 1:n_basefuncs_u\n", + " divδu = shape_divergence(cellvalues_u, q_point, i)\n", + " δu = shape_value(cellvalues_u, q_point, i)\n", + " for j in 1:i\n", + " Ke[BlockIndex((u▄, u▄), (i, j))] += 2 * mp.G * ɛdev[i] ⊡ ɛdev[j] * dΩ\n", + " end\n", + " end\n", + "\n", + " for i in 1:n_basefuncs_p\n", + " δp = shape_value(cellvalues_p, q_point, i)\n", + " for j in 1:n_basefuncs_u\n", + " divδu = shape_divergence(cellvalues_u, q_point, j)\n", + " Ke[BlockIndex((p▄, u▄), (i, j))] += -δp * divδu * dΩ\n", + " end\n", + " for j in 1:i\n", + " p = shape_value(cellvalues_p, q_point, j)\n", + " Ke[BlockIndex((p▄, p▄), (i, j))] += - 1 / mp.K * δp * p * dΩ\n", + " end\n", + "\n", + " end\n", + " end\n", + "\n", + " symmetrize_lower!(Ke)\n", + "\n", + " # We integrate the Neumann boundary using the FacetValues.\n", + " # We loop over all the facets in the cell, then check if the facet\n", + " # is in our `\"traction\"` facetset.\n", + " for facet in 1:nfacets(cell)\n", + " if (cellid(cell), facet) ∈ getfacetset(grid, \"traction\")\n", + " reinit!(facetvalues_u, cell, facet)\n", + " for q_point in 1:getnquadpoints(facetvalues_u)\n", + " dΓ = getdetJdV(facetvalues_u, q_point)\n", + " for i in 1:n_basefuncs_u\n", + " δu = shape_value(facetvalues_u, q_point, i)\n", + " fe[i] += (δu ⋅ t) * dΓ\n", + " end\n", + " end\n", + " end\n", + " end\n", + " return\n", + "end\n", + "\n", + "function symmetrize_lower!(Ke)\n", + " for i in 1:size(Ke, 1)\n", + " for j in (i + 1):size(Ke, 1)\n", + " Ke[i, j] = Ke[j, i]\n", + " end\n", + " end\n", + " return\n", + "end;" + ], + "metadata": {}, + "execution_count": 8 + }, + { + "cell_type": "markdown", + "source": [ + "To evaluate the stresses after solving the problem we once again loop over the cells in\n", + "the grid. Stresses are evaluated in the quadrature points, however, for\n", + "export/visualization you typically want values in the nodes of the mesh, or as single data\n", + "points per cell. For the former you can project the quadrature point data to a finite\n", + "element space (see the example with the `L2Projector` in Post processing and\n", + "visualization). In this example we choose to compute the mean\n", + "value of the stress within each cell, and thus end up with one data point per cell. The\n", + "mean value is computed as\n", + "$$\n", + "\\bar{\\boldsymbol{\\sigma}}_i = \\frac{1}{ |\\Omega_i|}\n", + "\\int_{\\Omega_i} \\boldsymbol{\\sigma}\\, \\mathrm{d}\\Omega, \\quad\n", + "|\\Omega_i| = \\int_{\\Omega_i} 1\\, \\mathrm{d}\\Omega\n", + "$$\n", + "where $\\Omega_i$ is the domain occupied by cell number $i$, and $|\\Omega_i|$ the volume\n", + "(area) of the cell. The integrals are evaluated using numerical quadrature with the help\n", + "of cellvalues for u and p, just like in the assembly procedure.\n", + "\n", + "Note that even though all strain components in the out-of-plane direction are zero (plane\n", + "strain) the stress components are not. Specifically, $\\sigma_{33}$ will be non-zero in\n", + "this formulation. Therefore we expand the strain to a 3D tensor, and then compute the (3D)\n", + "stress tensor." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function compute_stresses(\n", + " cellvalues_u::CellValues, cellvalues_p::CellValues,\n", + " dh::DofHandler, mp::LinearElasticity, a::Vector\n", + " )\n", + " ae = zeros(ndofs_per_cell(dh)) # local solution vector\n", + " u_range = dof_range(dh, :u) # local range of dofs corresponding to u\n", + " p_range = dof_range(dh, :p) # local range of dofs corresponding to p\n", + " # Allocate storage for the stresses\n", + " σ = zeros(SymmetricTensor{2, 3}, getncells(dh.grid))\n", + " # Loop over the cells and compute the cell-average stress\n", + " for cc in CellIterator(dh)\n", + " # Update cellvalues\n", + " reinit!(cellvalues_u, cc)\n", + " reinit!(cellvalues_p, cc)\n", + " # Extract the cell local part of the solution\n", + " for (i, I) in pairs(celldofs(cc))\n", + " ae[i] = a[I]\n", + " end\n", + " # Loop over the quadrature points\n", + " σΩi = zero(SymmetricTensor{2, 3}) # stress integrated over the cell\n", + " Ωi = 0.0 # cell volume (area)\n", + " for qp in 1:getnquadpoints(cellvalues_u)\n", + " dΩ = getdetJdV(cellvalues_u, qp)\n", + " # Evaluate the strain and the pressure\n", + " ε = function_symmetric_gradient(cellvalues_u, qp, ae, u_range)\n", + " p = function_value(cellvalues_p, qp, ae, p_range)\n", + " # Expand strain to 3D\n", + " ε3D = SymmetricTensor{2, 3}((i, j) -> i < 3 && j < 3 ? ε[i, j] : 0.0)\n", + " # Compute the stress in this quadrature point\n", + " σqp = 2 * mp.G * dev(ε3D) - one(ε3D) * p\n", + " σΩi += σqp * dΩ\n", + " Ωi += dΩ\n", + " end\n", + " # Store the value\n", + " σ[cellid(cc)] = σΩi / Ωi\n", + " end\n", + " return σ\n", + "end;" + ], + "metadata": {}, + "execution_count": 9 + }, + { + "cell_type": "markdown", + "source": [ + "Now we have constructed all the necessary components, we just need a function\n", + "to put it all together." + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "solve (generic function with 1 method)" + }, + "metadata": {}, + "execution_count": 10 + } + ], + "cell_type": "code", + "source": [ + "function solve(ν, interpolation_u, interpolation_p)\n", + " # material\n", + " Emod = 1.0\n", + " Gmod = Emod / 2(1 + ν)\n", + " Kmod = Emod * ν / ((1 + ν) * (1 - 2ν))\n", + " mp = LinearElasticity(Gmod, Kmod)\n", + "\n", + " # Grid, dofhandler, boundary condition\n", + " n = 50\n", + " grid = create_cook_grid(n, n)\n", + " dh = create_dofhandler(grid, interpolation_u, interpolation_p)\n", + " dbc = create_bc(dh)\n", + "\n", + " # CellValues\n", + " cellvalues_u, cellvalues_p, facetvalues_u = create_values(interpolation_u, interpolation_p)\n", + "\n", + " # Assembly and solve\n", + " K = allocate_matrix(dh)\n", + " K, f = doassemble(cellvalues_u, cellvalues_p, facetvalues_u, K, grid, dh, mp)\n", + " apply!(K, f, dbc)\n", + " u = K \\ f\n", + "\n", + " # Compute the stress\n", + " σ = compute_stresses(cellvalues_u, cellvalues_p, dh, mp, u)\n", + " σvM = map(x -> √(3 / 2 * dev(x) ⊡ dev(x)), σ) # von Mise effective stress\n", + "\n", + " # Export the solution and the stress\n", + " filename = \"cook_\" *\n", + " (interpolation_u == Lagrange{RefTriangle, 1}()^2 ? \"linear\" : \"quadratic\") *\n", + " \"_linear\"\n", + "\n", + " VTKGridFile(filename, grid) do vtk\n", + " write_solution(vtk, dh, u)\n", + " for i in 1:3, j in 1:3\n", + " σij = [x[i, j] for x in σ]\n", + " write_cell_data(vtk, σij, \"sigma_$(i)$(j)\")\n", + " end\n", + " write_cell_data(vtk, σvM, \"sigma von Mises\")\n", + " end\n", + " return u\n", + "end" + ], + "metadata": {}, + "execution_count": 10 + }, + { + "cell_type": "markdown", + "source": [ + "We now define the interpolation for displacement and pressure. We use (scalar) Lagrange\n", + "interpolation as a basis for both, and for the displacement, which is a vector, we\n", + "vectorize it to 2 dimensions such that we obtain vector shape functions (and 2nd order\n", + "tensors for the gradients)." + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "Lagrange{RefTriangle, 2}()^2" + }, + "metadata": {}, + "execution_count": 11 + } + ], + "cell_type": "code", + "source": [ + "linear_p = Lagrange{RefTriangle, 1}()\n", + "linear_u = Lagrange{RefTriangle, 1}()^2\n", + "quadratic_u = Lagrange{RefTriangle, 2}()^2" + ], + "metadata": {}, + "execution_count": 11 + }, + { + "cell_type": "markdown", + "source": [ + "All that is left is to solve the problem. We choose a value of Poissons\n", + "ratio that results in incompressibility ($ν = 0.5$) and thus expect the\n", + "linear/linear approximation to return garbage, and the quadratic/linear\n", + "approximation to be stable." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "u1 = solve(0.5, linear_u, linear_p);\n", + "u2 = solve(0.5, quadratic_u, linear_p);" + ], + "metadata": {}, + "execution_count": 12 + }, + { + "cell_type": "markdown", + "source": [ + "---\n", + "\n", + "*This notebook was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).*" + ], + "metadata": {} + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.1" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.1", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR798/tutorials/incompressible_elasticity.jl b/previews/PR798/tutorials/incompressible_elasticity.jl new file mode 100644 index 0000000000..a424890d52 --- /dev/null +++ b/previews/PR798/tutorials/incompressible_elasticity.jl @@ -0,0 +1,235 @@ +using Ferrite, Tensors +using BlockArrays, SparseArrays, LinearAlgebra + +function create_cook_grid(nx, ny) + corners = [ + Vec{2}((0.0, 0.0)), + Vec{2}((48.0, 44.0)), + Vec{2}((48.0, 60.0)), + Vec{2}((0.0, 44.0)), + ] + grid = generate_grid(Triangle, (nx, ny), corners) + # facesets for boundary conditions + addfacetset!(grid, "clamped", x -> norm(x[1]) ≈ 0.0) + addfacetset!(grid, "traction", x -> norm(x[1]) ≈ 48.0) + return grid +end; + +function create_values(interpolation_u, interpolation_p) + # quadrature rules + qr = QuadratureRule{RefTriangle}(3) + facet_qr = FacetQuadratureRule{RefTriangle}(3) + + # cell and FacetValues for u + cellvalues_u = CellValues(qr, interpolation_u) + facetvalues_u = FacetValues(facet_qr, interpolation_u) + + # cellvalues for p + cellvalues_p = CellValues(qr, interpolation_p) + + return cellvalues_u, cellvalues_p, facetvalues_u +end; + +function create_dofhandler(grid, ipu, ipp) + dh = DofHandler(grid) + add!(dh, :u, ipu) # displacement + add!(dh, :p, ipp) # pressure + close!(dh) + return dh +end; + +function create_bc(dh) + dbc = ConstraintHandler(dh) + add!(dbc, Dirichlet(:u, getfacetset(dh.grid, "clamped"), x -> zero(x), [1, 2])) + close!(dbc) + return dbc +end; + +struct LinearElasticity{T} + G::T + K::T +end + +function doassemble( + cellvalues_u::CellValues, + cellvalues_p::CellValues, + facetvalues_u::FacetValues, + K::SparseMatrixCSC, grid::Grid, dh::DofHandler, mp::LinearElasticity + ) + f = zeros(ndofs(dh)) + assembler = start_assemble(K, f) + nu = getnbasefunctions(cellvalues_u) + np = getnbasefunctions(cellvalues_p) + + fe = BlockedArray(zeros(nu + np), [nu, np]) # local force vector + ke = BlockedArray(zeros(nu + np, nu + np), [nu, np], [nu, np]) # local stiffness matrix + + # traction vector + t = Vec{2}((0.0, 1 / 16)) + # cache ɛdev outside the element routine to avoid some unnecessary allocations + ɛdev = [zero(SymmetricTensor{2, 2}) for i in 1:getnbasefunctions(cellvalues_u)] + + for cell in CellIterator(dh) + fill!(ke, 0) + fill!(fe, 0) + assemble_up!(ke, fe, cell, cellvalues_u, cellvalues_p, facetvalues_u, grid, mp, ɛdev, t) + assemble!(assembler, celldofs(cell), ke, fe) + end + + return K, f +end; + +function assemble_up!(Ke, fe, cell, cellvalues_u, cellvalues_p, facetvalues_u, grid, mp, ɛdev, t) + + n_basefuncs_u = getnbasefunctions(cellvalues_u) + n_basefuncs_p = getnbasefunctions(cellvalues_p) + u▄, p▄ = 1, 2 + reinit!(cellvalues_u, cell) + reinit!(cellvalues_p, cell) + + # We only assemble lower half triangle of the stiffness matrix and then symmetrize it. + for q_point in 1:getnquadpoints(cellvalues_u) + for i in 1:n_basefuncs_u + ɛdev[i] = dev(symmetric(shape_gradient(cellvalues_u, q_point, i))) + end + dΩ = getdetJdV(cellvalues_u, q_point) + for i in 1:n_basefuncs_u + divδu = shape_divergence(cellvalues_u, q_point, i) + δu = shape_value(cellvalues_u, q_point, i) + for j in 1:i + Ke[BlockIndex((u▄, u▄), (i, j))] += 2 * mp.G * ɛdev[i] ⊡ ɛdev[j] * dΩ + end + end + + for i in 1:n_basefuncs_p + δp = shape_value(cellvalues_p, q_point, i) + for j in 1:n_basefuncs_u + divδu = shape_divergence(cellvalues_u, q_point, j) + Ke[BlockIndex((p▄, u▄), (i, j))] += -δp * divδu * dΩ + end + for j in 1:i + p = shape_value(cellvalues_p, q_point, j) + Ke[BlockIndex((p▄, p▄), (i, j))] += - 1 / mp.K * δp * p * dΩ + end + + end + end + + symmetrize_lower!(Ke) + + # We integrate the Neumann boundary using the FacetValues. + # We loop over all the facets in the cell, then check if the facet + # is in our `"traction"` facetset. + for facet in 1:nfacets(cell) + if (cellid(cell), facet) ∈ getfacetset(grid, "traction") + reinit!(facetvalues_u, cell, facet) + for q_point in 1:getnquadpoints(facetvalues_u) + dΓ = getdetJdV(facetvalues_u, q_point) + for i in 1:n_basefuncs_u + δu = shape_value(facetvalues_u, q_point, i) + fe[i] += (δu ⋅ t) * dΓ + end + end + end + end + return +end + +function symmetrize_lower!(Ke) + for i in 1:size(Ke, 1) + for j in (i + 1):size(Ke, 1) + Ke[i, j] = Ke[j, i] + end + end + return +end; + +function compute_stresses( + cellvalues_u::CellValues, cellvalues_p::CellValues, + dh::DofHandler, mp::LinearElasticity, a::Vector + ) + ae = zeros(ndofs_per_cell(dh)) # local solution vector + u_range = dof_range(dh, :u) # local range of dofs corresponding to u + p_range = dof_range(dh, :p) # local range of dofs corresponding to p + # Allocate storage for the stresses + σ = zeros(SymmetricTensor{2, 3}, getncells(dh.grid)) + # Loop over the cells and compute the cell-average stress + for cc in CellIterator(dh) + # Update cellvalues + reinit!(cellvalues_u, cc) + reinit!(cellvalues_p, cc) + # Extract the cell local part of the solution + for (i, I) in pairs(celldofs(cc)) + ae[i] = a[I] + end + # Loop over the quadrature points + σΩi = zero(SymmetricTensor{2, 3}) # stress integrated over the cell + Ωi = 0.0 # cell volume (area) + for qp in 1:getnquadpoints(cellvalues_u) + dΩ = getdetJdV(cellvalues_u, qp) + # Evaluate the strain and the pressure + ε = function_symmetric_gradient(cellvalues_u, qp, ae, u_range) + p = function_value(cellvalues_p, qp, ae, p_range) + # Expand strain to 3D + ε3D = SymmetricTensor{2, 3}((i, j) -> i < 3 && j < 3 ? ε[i, j] : 0.0) + # Compute the stress in this quadrature point + σqp = 2 * mp.G * dev(ε3D) - one(ε3D) * p + σΩi += σqp * dΩ + Ωi += dΩ + end + # Store the value + σ[cellid(cc)] = σΩi / Ωi + end + return σ +end; + +function solve(ν, interpolation_u, interpolation_p) + # material + Emod = 1.0 + Gmod = Emod / 2(1 + ν) + Kmod = Emod * ν / ((1 + ν) * (1 - 2ν)) + mp = LinearElasticity(Gmod, Kmod) + + # Grid, dofhandler, boundary condition + n = 50 + grid = create_cook_grid(n, n) + dh = create_dofhandler(grid, interpolation_u, interpolation_p) + dbc = create_bc(dh) + + # CellValues + cellvalues_u, cellvalues_p, facetvalues_u = create_values(interpolation_u, interpolation_p) + + # Assembly and solve + K = allocate_matrix(dh) + K, f = doassemble(cellvalues_u, cellvalues_p, facetvalues_u, K, grid, dh, mp) + apply!(K, f, dbc) + u = K \ f + + # Compute the stress + σ = compute_stresses(cellvalues_u, cellvalues_p, dh, mp, u) + σvM = map(x -> √(3 / 2 * dev(x) ⊡ dev(x)), σ) # von Mise effective stress + + # Export the solution and the stress + filename = "cook_" * + (interpolation_u == Lagrange{RefTriangle, 1}()^2 ? "linear" : "quadratic") * + "_linear" + + VTKGridFile(filename, grid) do vtk + write_solution(vtk, dh, u) + for i in 1:3, j in 1:3 + σij = [x[i, j] for x in σ] + write_cell_data(vtk, σij, "sigma_$(i)$(j)") + end + write_cell_data(vtk, σvM, "sigma von Mises") + end + return u +end + +linear_p = Lagrange{RefTriangle, 1}() +linear_u = Lagrange{RefTriangle, 1}()^2 +quadratic_u = Lagrange{RefTriangle, 2}()^2 + +u1 = solve(0.5, linear_u, linear_p); +u2 = solve(0.5, quadratic_u, linear_p); + +# This file was generated using Literate.jl, https://github.com/fredrikekre/Literate.jl diff --git a/previews/PR798/tutorials/incompressible_elasticity/index.html b/previews/PR798/tutorials/incompressible_elasticity/index.html new file mode 100644 index 0000000000..94c297e878 --- /dev/null +++ b/previews/PR798/tutorials/incompressible_elasticity/index.html @@ -0,0 +1,446 @@ + +Incompressible elasticity · Ferrite.jl

Incompressible elasticity

Tip

This example is also available as a Jupyter notebook: incompressible_elasticity.ipynb.

Introduction

Mixed elements can be used to overcome locking when the material becomes incompressible. However, for an element to be stable, it needs to fulfill the LBB condition. In this example we will consider two different element formulations

  • linear displacement with linear pressure approximation (does not fulfill LBB)
  • quadratic displacement with linear pressure approximation (does fulfill LBB)

The quadratic/linear element is also known as the Taylor-Hood element. We will consider Cook's Membrane with an applied traction on the right hand side.

Commented program

What follows is a program spliced with comments. The full program, without comments, can be found in the next section.

using Ferrite, Tensors
+using BlockArrays, SparseArrays, LinearAlgebra

First we generate a simple grid, specifying the 4 corners of Cooks membrane.

function create_cook_grid(nx, ny)
+    corners = [
+        Vec{2}((0.0, 0.0)),
+        Vec{2}((48.0, 44.0)),
+        Vec{2}((48.0, 60.0)),
+        Vec{2}((0.0, 44.0)),
+    ]
+    grid = generate_grid(Triangle, (nx, ny), corners)
+    # facesets for boundary conditions
+    addfacetset!(grid, "clamped", x -> norm(x[1]) ≈ 0.0)
+    addfacetset!(grid, "traction", x -> norm(x[1]) ≈ 48.0)
+    return grid
+end;

Next we define a function to set up our cell- and FacetValues.

function create_values(interpolation_u, interpolation_p)
+    # quadrature rules
+    qr = QuadratureRule{RefTriangle}(3)
+    facet_qr = FacetQuadratureRule{RefTriangle}(3)
+
+    # cell and FacetValues for u
+    cellvalues_u = CellValues(qr, interpolation_u)
+    facetvalues_u = FacetValues(facet_qr, interpolation_u)
+
+    # cellvalues for p
+    cellvalues_p = CellValues(qr, interpolation_p)
+
+    return cellvalues_u, cellvalues_p, facetvalues_u
+end;

We create a DofHandler, with two fields, :u and :p, with possibly different interpolations

function create_dofhandler(grid, ipu, ipp)
+    dh = DofHandler(grid)
+    add!(dh, :u, ipu) # displacement
+    add!(dh, :p, ipp) # pressure
+    close!(dh)
+    return dh
+end;

We also need to add Dirichlet boundary conditions on the "clamped" facetset. We specify a homogeneous Dirichlet bc on the displacement field, :u.

function create_bc(dh)
+    dbc = ConstraintHandler(dh)
+    add!(dbc, Dirichlet(:u, getfacetset(dh.grid, "clamped"), x -> zero(x), [1, 2]))
+    close!(dbc)
+    return dbc
+end;

The material is linear elastic, which is here specified by the shear and bulk moduli

struct LinearElasticity{T}
+    G::T
+    K::T
+end

Now to the assembling of the stiffness matrix. This mixed formulation leads to a blocked element matrix. Since Ferrite does not force us to use any particular matrix type we will use a BlockedArray from BlockArrays.jl.

function doassemble(
+        cellvalues_u::CellValues,
+        cellvalues_p::CellValues,
+        facetvalues_u::FacetValues,
+        K::SparseMatrixCSC, grid::Grid, dh::DofHandler, mp::LinearElasticity
+    )
+    f = zeros(ndofs(dh))
+    assembler = start_assemble(K, f)
+    nu = getnbasefunctions(cellvalues_u)
+    np = getnbasefunctions(cellvalues_p)
+
+    fe = BlockedArray(zeros(nu + np), [nu, np]) # local force vector
+    ke = BlockedArray(zeros(nu + np, nu + np), [nu, np], [nu, np]) # local stiffness matrix
+
+    # traction vector
+    t = Vec{2}((0.0, 1 / 16))
+    # cache ɛdev outside the element routine to avoid some unnecessary allocations
+    ɛdev = [zero(SymmetricTensor{2, 2}) for i in 1:getnbasefunctions(cellvalues_u)]
+
+    for cell in CellIterator(dh)
+        fill!(ke, 0)
+        fill!(fe, 0)
+        assemble_up!(ke, fe, cell, cellvalues_u, cellvalues_p, facetvalues_u, grid, mp, ɛdev, t)
+        assemble!(assembler, celldofs(cell), ke, fe)
+    end
+
+    return K, f
+end;

The element routine integrates the local stiffness and force vector for all elements. Since the problem results in a symmetric matrix we choose to only assemble the lower part, and then symmetrize it after the loop over the quadrature points.

function assemble_up!(Ke, fe, cell, cellvalues_u, cellvalues_p, facetvalues_u, grid, mp, ɛdev, t)
+
+    n_basefuncs_u = getnbasefunctions(cellvalues_u)
+    n_basefuncs_p = getnbasefunctions(cellvalues_p)
+    u▄, p▄ = 1, 2
+    reinit!(cellvalues_u, cell)
+    reinit!(cellvalues_p, cell)
+
+    # We only assemble lower half triangle of the stiffness matrix and then symmetrize it.
+    for q_point in 1:getnquadpoints(cellvalues_u)
+        for i in 1:n_basefuncs_u
+            ɛdev[i] = dev(symmetric(shape_gradient(cellvalues_u, q_point, i)))
+        end
+        dΩ = getdetJdV(cellvalues_u, q_point)
+        for i in 1:n_basefuncs_u
+            divδu = shape_divergence(cellvalues_u, q_point, i)
+            δu = shape_value(cellvalues_u, q_point, i)
+            for j in 1:i
+                Ke[BlockIndex((u▄, u▄), (i, j))] += 2 * mp.G * ɛdev[i] ⊡ ɛdev[j] * dΩ
+            end
+        end
+
+        for i in 1:n_basefuncs_p
+            δp = shape_value(cellvalues_p, q_point, i)
+            for j in 1:n_basefuncs_u
+                divδu = shape_divergence(cellvalues_u, q_point, j)
+                Ke[BlockIndex((p▄, u▄), (i, j))] += -δp * divδu * dΩ
+            end
+            for j in 1:i
+                p = shape_value(cellvalues_p, q_point, j)
+                Ke[BlockIndex((p▄, p▄), (i, j))] += - 1 / mp.K * δp * p * dΩ
+            end
+
+        end
+    end
+
+    symmetrize_lower!(Ke)
+
+    # We integrate the Neumann boundary using the FacetValues.
+    # We loop over all the facets in the cell, then check if the facet
+    # is in our `"traction"` facetset.
+    for facet in 1:nfacets(cell)
+        if (cellid(cell), facet) ∈ getfacetset(grid, "traction")
+            reinit!(facetvalues_u, cell, facet)
+            for q_point in 1:getnquadpoints(facetvalues_u)
+                dΓ = getdetJdV(facetvalues_u, q_point)
+                for i in 1:n_basefuncs_u
+                    δu = shape_value(facetvalues_u, q_point, i)
+                    fe[i] += (δu ⋅ t) * dΓ
+                end
+            end
+        end
+    end
+    return
+end
+
+function symmetrize_lower!(Ke)
+    for i in 1:size(Ke, 1)
+        for j in (i + 1):size(Ke, 1)
+            Ke[i, j] = Ke[j, i]
+        end
+    end
+    return
+end;

To evaluate the stresses after solving the problem we once again loop over the cells in the grid. Stresses are evaluated in the quadrature points, however, for export/visualization you typically want values in the nodes of the mesh, or as single data points per cell. For the former you can project the quadrature point data to a finite element space (see the example with the L2Projector in Post processing and visualization). In this example we choose to compute the mean value of the stress within each cell, and thus end up with one data point per cell. The mean value is computed as

\[\bar{\boldsymbol{\sigma}}_i = \frac{1}{ |\Omega_i|} +\int_{\Omega_i} \boldsymbol{\sigma}\, \mathrm{d}\Omega, \quad +|\Omega_i| = \int_{\Omega_i} 1\, \mathrm{d}\Omega\]

where $\Omega_i$ is the domain occupied by cell number $i$, and $|\Omega_i|$ the volume (area) of the cell. The integrals are evaluated using numerical quadrature with the help of cellvalues for u and p, just like in the assembly procedure.

Note that even though all strain components in the out-of-plane direction are zero (plane strain) the stress components are not. Specifically, $\sigma_{33}$ will be non-zero in this formulation. Therefore we expand the strain to a 3D tensor, and then compute the (3D) stress tensor.

function compute_stresses(
+        cellvalues_u::CellValues, cellvalues_p::CellValues,
+        dh::DofHandler, mp::LinearElasticity, a::Vector
+    )
+    ae = zeros(ndofs_per_cell(dh)) # local solution vector
+    u_range = dof_range(dh, :u)    # local range of dofs corresponding to u
+    p_range = dof_range(dh, :p)    # local range of dofs corresponding to p
+    # Allocate storage for the stresses
+    σ = zeros(SymmetricTensor{2, 3}, getncells(dh.grid))
+    # Loop over the cells and compute the cell-average stress
+    for cc in CellIterator(dh)
+        # Update cellvalues
+        reinit!(cellvalues_u, cc)
+        reinit!(cellvalues_p, cc)
+        # Extract the cell local part of the solution
+        for (i, I) in pairs(celldofs(cc))
+            ae[i] = a[I]
+        end
+        # Loop over the quadrature points
+        σΩi = zero(SymmetricTensor{2, 3}) # stress integrated over the cell
+        Ωi = 0.0                          # cell volume (area)
+        for qp in 1:getnquadpoints(cellvalues_u)
+            dΩ = getdetJdV(cellvalues_u, qp)
+            # Evaluate the strain and the pressure
+            ε = function_symmetric_gradient(cellvalues_u, qp, ae, u_range)
+            p = function_value(cellvalues_p, qp, ae, p_range)
+            # Expand strain to 3D
+            ε3D = SymmetricTensor{2, 3}((i, j) -> i < 3 && j < 3 ? ε[i, j] : 0.0)
+            # Compute the stress in this quadrature point
+            σqp = 2 * mp.G * dev(ε3D) - one(ε3D) * p
+            σΩi += σqp * dΩ
+            Ωi += dΩ
+        end
+        # Store the value
+        σ[cellid(cc)] = σΩi / Ωi
+    end
+    return σ
+end;

Now we have constructed all the necessary components, we just need a function to put it all together.

function solve(ν, interpolation_u, interpolation_p)
+    # material
+    Emod = 1.0
+    Gmod = Emod / 2(1 + ν)
+    Kmod = Emod * ν / ((1 + ν) * (1 - 2ν))
+    mp = LinearElasticity(Gmod, Kmod)
+
+    # Grid, dofhandler, boundary condition
+    n = 50
+    grid = create_cook_grid(n, n)
+    dh = create_dofhandler(grid, interpolation_u, interpolation_p)
+    dbc = create_bc(dh)
+
+    # CellValues
+    cellvalues_u, cellvalues_p, facetvalues_u = create_values(interpolation_u, interpolation_p)
+
+    # Assembly and solve
+    K = allocate_matrix(dh)
+    K, f = doassemble(cellvalues_u, cellvalues_p, facetvalues_u, K, grid, dh, mp)
+    apply!(K, f, dbc)
+    u = K \ f
+
+    # Compute the stress
+    σ = compute_stresses(cellvalues_u, cellvalues_p, dh, mp, u)
+    σvM = map(x -> √(3 / 2 * dev(x) ⊡ dev(x)), σ) # von Mise effective stress
+
+    # Export the solution and the stress
+    filename = "cook_" *
+        (interpolation_u == Lagrange{RefTriangle, 1}()^2 ? "linear" : "quadratic") *
+        "_linear"
+
+    VTKGridFile(filename, grid) do vtk
+        write_solution(vtk, dh, u)
+        for i in 1:3, j in 1:3
+            σij = [x[i, j] for x in σ]
+            write_cell_data(vtk, σij, "sigma_$(i)$(j)")
+        end
+        write_cell_data(vtk, σvM, "sigma von Mises")
+    end
+    return u
+end

We now define the interpolation for displacement and pressure. We use (scalar) Lagrange interpolation as a basis for both, and for the displacement, which is a vector, we vectorize it to 2 dimensions such that we obtain vector shape functions (and 2nd order tensors for the gradients).

linear_p = Lagrange{RefTriangle, 1}()
+linear_u = Lagrange{RefTriangle, 1}()^2
+quadratic_u = Lagrange{RefTriangle, 2}()^2

All that is left is to solve the problem. We choose a value of Poissons ratio that results in incompressibility ($ν = 0.5$) and thus expect the linear/linear approximation to return garbage, and the quadratic/linear approximation to be stable.

u1 = solve(0.5, linear_u, linear_p);
+u2 = solve(0.5, quadratic_u, linear_p);

Plain program

Here follows a version of the program without any comments. The file is also available here: incompressible_elasticity.jl.

using Ferrite, Tensors
+using BlockArrays, SparseArrays, LinearAlgebra
+
+function create_cook_grid(nx, ny)
+    corners = [
+        Vec{2}((0.0, 0.0)),
+        Vec{2}((48.0, 44.0)),
+        Vec{2}((48.0, 60.0)),
+        Vec{2}((0.0, 44.0)),
+    ]
+    grid = generate_grid(Triangle, (nx, ny), corners)
+    # facesets for boundary conditions
+    addfacetset!(grid, "clamped", x -> norm(x[1]) ≈ 0.0)
+    addfacetset!(grid, "traction", x -> norm(x[1]) ≈ 48.0)
+    return grid
+end;
+
+function create_values(interpolation_u, interpolation_p)
+    # quadrature rules
+    qr = QuadratureRule{RefTriangle}(3)
+    facet_qr = FacetQuadratureRule{RefTriangle}(3)
+
+    # cell and FacetValues for u
+    cellvalues_u = CellValues(qr, interpolation_u)
+    facetvalues_u = FacetValues(facet_qr, interpolation_u)
+
+    # cellvalues for p
+    cellvalues_p = CellValues(qr, interpolation_p)
+
+    return cellvalues_u, cellvalues_p, facetvalues_u
+end;
+
+function create_dofhandler(grid, ipu, ipp)
+    dh = DofHandler(grid)
+    add!(dh, :u, ipu) # displacement
+    add!(dh, :p, ipp) # pressure
+    close!(dh)
+    return dh
+end;
+
+function create_bc(dh)
+    dbc = ConstraintHandler(dh)
+    add!(dbc, Dirichlet(:u, getfacetset(dh.grid, "clamped"), x -> zero(x), [1, 2]))
+    close!(dbc)
+    return dbc
+end;
+
+struct LinearElasticity{T}
+    G::T
+    K::T
+end
+
+function doassemble(
+        cellvalues_u::CellValues,
+        cellvalues_p::CellValues,
+        facetvalues_u::FacetValues,
+        K::SparseMatrixCSC, grid::Grid, dh::DofHandler, mp::LinearElasticity
+    )
+    f = zeros(ndofs(dh))
+    assembler = start_assemble(K, f)
+    nu = getnbasefunctions(cellvalues_u)
+    np = getnbasefunctions(cellvalues_p)
+
+    fe = BlockedArray(zeros(nu + np), [nu, np]) # local force vector
+    ke = BlockedArray(zeros(nu + np, nu + np), [nu, np], [nu, np]) # local stiffness matrix
+
+    # traction vector
+    t = Vec{2}((0.0, 1 / 16))
+    # cache ɛdev outside the element routine to avoid some unnecessary allocations
+    ɛdev = [zero(SymmetricTensor{2, 2}) for i in 1:getnbasefunctions(cellvalues_u)]
+
+    for cell in CellIterator(dh)
+        fill!(ke, 0)
+        fill!(fe, 0)
+        assemble_up!(ke, fe, cell, cellvalues_u, cellvalues_p, facetvalues_u, grid, mp, ɛdev, t)
+        assemble!(assembler, celldofs(cell), ke, fe)
+    end
+
+    return K, f
+end;
+
+function assemble_up!(Ke, fe, cell, cellvalues_u, cellvalues_p, facetvalues_u, grid, mp, ɛdev, t)
+
+    n_basefuncs_u = getnbasefunctions(cellvalues_u)
+    n_basefuncs_p = getnbasefunctions(cellvalues_p)
+    u▄, p▄ = 1, 2
+    reinit!(cellvalues_u, cell)
+    reinit!(cellvalues_p, cell)
+
+    # We only assemble lower half triangle of the stiffness matrix and then symmetrize it.
+    for q_point in 1:getnquadpoints(cellvalues_u)
+        for i in 1:n_basefuncs_u
+            ɛdev[i] = dev(symmetric(shape_gradient(cellvalues_u, q_point, i)))
+        end
+        dΩ = getdetJdV(cellvalues_u, q_point)
+        for i in 1:n_basefuncs_u
+            divδu = shape_divergence(cellvalues_u, q_point, i)
+            δu = shape_value(cellvalues_u, q_point, i)
+            for j in 1:i
+                Ke[BlockIndex((u▄, u▄), (i, j))] += 2 * mp.G * ɛdev[i] ⊡ ɛdev[j] * dΩ
+            end
+        end
+
+        for i in 1:n_basefuncs_p
+            δp = shape_value(cellvalues_p, q_point, i)
+            for j in 1:n_basefuncs_u
+                divδu = shape_divergence(cellvalues_u, q_point, j)
+                Ke[BlockIndex((p▄, u▄), (i, j))] += -δp * divδu * dΩ
+            end
+            for j in 1:i
+                p = shape_value(cellvalues_p, q_point, j)
+                Ke[BlockIndex((p▄, p▄), (i, j))] += - 1 / mp.K * δp * p * dΩ
+            end
+
+        end
+    end
+
+    symmetrize_lower!(Ke)
+
+    # We integrate the Neumann boundary using the FacetValues.
+    # We loop over all the facets in the cell, then check if the facet
+    # is in our `"traction"` facetset.
+    for facet in 1:nfacets(cell)
+        if (cellid(cell), facet) ∈ getfacetset(grid, "traction")
+            reinit!(facetvalues_u, cell, facet)
+            for q_point in 1:getnquadpoints(facetvalues_u)
+                dΓ = getdetJdV(facetvalues_u, q_point)
+                for i in 1:n_basefuncs_u
+                    δu = shape_value(facetvalues_u, q_point, i)
+                    fe[i] += (δu ⋅ t) * dΓ
+                end
+            end
+        end
+    end
+    return
+end
+
+function symmetrize_lower!(Ke)
+    for i in 1:size(Ke, 1)
+        for j in (i + 1):size(Ke, 1)
+            Ke[i, j] = Ke[j, i]
+        end
+    end
+    return
+end;
+
+function compute_stresses(
+        cellvalues_u::CellValues, cellvalues_p::CellValues,
+        dh::DofHandler, mp::LinearElasticity, a::Vector
+    )
+    ae = zeros(ndofs_per_cell(dh)) # local solution vector
+    u_range = dof_range(dh, :u)    # local range of dofs corresponding to u
+    p_range = dof_range(dh, :p)    # local range of dofs corresponding to p
+    # Allocate storage for the stresses
+    σ = zeros(SymmetricTensor{2, 3}, getncells(dh.grid))
+    # Loop over the cells and compute the cell-average stress
+    for cc in CellIterator(dh)
+        # Update cellvalues
+        reinit!(cellvalues_u, cc)
+        reinit!(cellvalues_p, cc)
+        # Extract the cell local part of the solution
+        for (i, I) in pairs(celldofs(cc))
+            ae[i] = a[I]
+        end
+        # Loop over the quadrature points
+        σΩi = zero(SymmetricTensor{2, 3}) # stress integrated over the cell
+        Ωi = 0.0                          # cell volume (area)
+        for qp in 1:getnquadpoints(cellvalues_u)
+            dΩ = getdetJdV(cellvalues_u, qp)
+            # Evaluate the strain and the pressure
+            ε = function_symmetric_gradient(cellvalues_u, qp, ae, u_range)
+            p = function_value(cellvalues_p, qp, ae, p_range)
+            # Expand strain to 3D
+            ε3D = SymmetricTensor{2, 3}((i, j) -> i < 3 && j < 3 ? ε[i, j] : 0.0)
+            # Compute the stress in this quadrature point
+            σqp = 2 * mp.G * dev(ε3D) - one(ε3D) * p
+            σΩi += σqp * dΩ
+            Ωi += dΩ
+        end
+        # Store the value
+        σ[cellid(cc)] = σΩi / Ωi
+    end
+    return σ
+end;
+
+function solve(ν, interpolation_u, interpolation_p)
+    # material
+    Emod = 1.0
+    Gmod = Emod / 2(1 + ν)
+    Kmod = Emod * ν / ((1 + ν) * (1 - 2ν))
+    mp = LinearElasticity(Gmod, Kmod)
+
+    # Grid, dofhandler, boundary condition
+    n = 50
+    grid = create_cook_grid(n, n)
+    dh = create_dofhandler(grid, interpolation_u, interpolation_p)
+    dbc = create_bc(dh)
+
+    # CellValues
+    cellvalues_u, cellvalues_p, facetvalues_u = create_values(interpolation_u, interpolation_p)
+
+    # Assembly and solve
+    K = allocate_matrix(dh)
+    K, f = doassemble(cellvalues_u, cellvalues_p, facetvalues_u, K, grid, dh, mp)
+    apply!(K, f, dbc)
+    u = K \ f
+
+    # Compute the stress
+    σ = compute_stresses(cellvalues_u, cellvalues_p, dh, mp, u)
+    σvM = map(x -> √(3 / 2 * dev(x) ⊡ dev(x)), σ) # von Mise effective stress
+
+    # Export the solution and the stress
+    filename = "cook_" *
+        (interpolation_u == Lagrange{RefTriangle, 1}()^2 ? "linear" : "quadratic") *
+        "_linear"
+
+    VTKGridFile(filename, grid) do vtk
+        write_solution(vtk, dh, u)
+        for i in 1:3, j in 1:3
+            σij = [x[i, j] for x in σ]
+            write_cell_data(vtk, σij, "sigma_$(i)$(j)")
+        end
+        write_cell_data(vtk, σvM, "sigma von Mises")
+    end
+    return u
+end
+
+linear_p = Lagrange{RefTriangle, 1}()
+linear_u = Lagrange{RefTriangle, 1}()^2
+quadratic_u = Lagrange{RefTriangle, 2}()^2
+
+u1 = solve(0.5, linear_u, linear_p);
+u2 = solve(0.5, quadratic_u, linear_p);

This page was generated using Literate.jl.

diff --git a/previews/PR798/tutorials/index.html b/previews/PR798/tutorials/index.html new file mode 100644 index 0000000000..8035d4fba5 --- /dev/null +++ b/previews/PR798/tutorials/index.html @@ -0,0 +1,2 @@ + +Tutorials overview · Ferrite.jl

Tutorials

On this page you find an overview of Ferrite tutorials. The tutorials explain and show how Ferrite can be used to solve a wide range of problems. See also the Code gallery for more examples.

The tutorials all follow roughly the same structure:

  • Introduction introduces the problem to be solved and discusses the learning outcomes of the tutorial.
  • Commented program is the code for solving the problem with explanations and comments.
  • Plain program is the raw source code of the program.

When studying the tutorials it is a good idea to obtain a local copy of the code and run it on your own machine as you read along. Some of the tutorials also include suggestions for tweaks to the program that you can try out on your own.

Tutorial index

The tutorials are listed in roughly increasing order of complexity. However, since they focus on different aspects, and solve different problems, it is suggested to have a look at the brief descriptions below to get an idea about what you will learn from each tutorial.

If you are new to Ferrite then Tutorial 1 - Tutorial 6 is the best place to start. These tutorials introduces and teaches most of the basic finite element techniques (e.g. linear and non-linear problems, scalar- and vector-valued problems, Dirichlet and Neumann boundary conditions, mixed finite elements, time integration, direct and iterative linear solvers, etc). In particular the very first tutorial is essential in order to be able to follow any of the other tutorials. The remaining tutorials discuss more advanced topics.


Tutorial 1: Heat equation

This tutorial guides you through the process of solving the linear stationary heat equation (i.e. Poisson's equation) on a unit square with homogeneous Dirichlet boundary conditions. This tutorial introduces and teaches many important parts of Ferrite: problem setup, degree of freedom management, assembly procedure, boundary conditions, solving the linear system, visualization of the result). Understanding this tutorial is essential to follow more complex tutorials.

Keywords: scalar-valued solution, Dirichlet boundary conditions.


Tutorial 2: Linear elasticity

TBW.

Keywords: vector-valued solution, Dirichlet and Neumann boundary conditions.


Tutorial 3: Incompressible elasticity

This tutorial focuses on a mixed formulation of linear elasticity, with (vector) displacement and (scalar) pressure as the two unknowns, suitable for incompressibility. Thus, this tutorial guides you through the process of solving a problem with two unknowns from two coupled weak forms. The problem that is studied is Cook's membrane in the incompressible limit.

Keywords: mixed finite elements, Dirichlet and Neumann boundary conditions.


Tutorial 4: Hyperelasticity

In this tutorial you will learn how to solve a non-linear finite element problem. In particular, a hyperelastic material model, in a finite strain setting, is used to solve the rotation of a cube. Automatic differentiatio (AD) is used for the consitutive relations. Newton's method is used for the non-linear iteration, and a conjugate gradient (CG) solver is used for the linear solution of the increment.

Keywords: non-linear finite element, finite strain, automatic differentiation (AD), Newton's method, conjugate gradient (CG).


Tutorial 5: von Mises Plasticity

This tutorial revisits the cantilever beam problem from Tutorial 2: Linear elasticity, but instead of linear elasticity a plasticity model is used for the constitutive relation. You will learn how to solve a problem which require the solution of a local material problem, and the storage of material state, in each quadrature point. Newton's method is used both locally in the material routine, and globally on the finite element level.

Keywords: non-linear finite element, plasticity, material modeling, state variables, Newton’s method.


Tutorial 6: Transient heat equation

In this tutorial the transient heat equation is solved on the unit square. The problem to be solved is thus similar to the one solved in the first tutorial, Heat equation, but with time-varying boundary conditions. In particular you will learn how to solve a time dependent problem with an implicit Euler scheme for the time integration.

Keywords: time dependent finite elements, implicit Euler time integration.


Tutorial 7: Computational homogenization

This tutorial guides you through computational homogenization of an representative volume element (RVE) consisting of a soft matrix material with stiff inclusions. The computational mesh is read from an external mesh file generated with Gmsh. Dirichlet and periodic boundary conditions are used.

Keywords: Gmsh mesh reading, Dirichlet and periodic boundary conditions


Tutorial 8: Stokes flow

In this tutorial Stokes flow with (vector) velocity and (scalar) pressure is solved on on a quarter circle. Rotationally periodic boundary conditions is used for the inlet/outlet coupling. To obtain a unique solution, a mean value constraint is applied on the pressure using an affine constraint. The computational mesh is generated directly using the Gmsh API.

Keywords: periodic boundary conditions, mean value constraint, mesh generation with Gmsh.


Tutorial 9: Porous media (SubDofHandler)

This tutorial introduces how to solve a complex linear problem, where there are different fields on different subdomains, and different cell types in the grid. This requires using the SubDofHandler interface.

Keywords: Mixed grids, multiple fields, porous media, SubDofHandler


Tutorial 10: Incompressible Navier-Stokes equations

In this tutorial the incompressible Navier-Stokes equations are solved. The domain is discretized in space with Ferrite as usual, and then forumalated in a way to be compatible with the OrdinaryDiffEq.jl package, which is used for the time-integration.

Keywords: non-linear time dependent problem


Tutorial 10: Reactive surface

In this tutorial a reaction diffusion system on a sphere surface embedded in 3D is solved. Ferrite is used to assemble the diffusion operators and the mass matrices. The problem is solved by using the usual first order reaction diffusion operator splitting.

Keywords: embedded elements, operator splitting, gmsh


Tutorial 11: Linear shell

In this tutorial a linear shell element formulation is set up as a two-dimensional domain embedded in three-dimensional space. This will teach, and perhaps inspire, you on how Ferrite can be used for non-standard things and how to add "hacks" that build on top of Ferrite.

Keywords: shell elements, automatic differentiation


Tutorial 12: Discontinuous Galerkin heat equation

This tutorial guides you through the process of solving the linear stationary heat equation (i.e. Poisson's equation) on a unit square with inhomogeneous Dirichlet and Neumann boundary conditions using the interior penalty discontinuous Galerkin method. This tutorial follows the heat equation tutorial, introducing face and interface iterators, jump and average operators, and cross-element coupling in sparsity patterns. This example was developed as part of the Google Summer of Code funded project "Discontinuous Galerkin Infrastructure For the finite element toolbox Ferrite.jl".

Keywords: scalar-valued solution, Dirichlet boundary conditions, Discontinuous Galerkin, Interior penalty

diff --git a/previews/PR798/tutorials/linear_elasticity.ipynb b/previews/PR798/tutorials/linear_elasticity.ipynb new file mode 100644 index 0000000000..d9cab061ac --- /dev/null +++ b/previews/PR798/tutorials/linear_elasticity.ipynb @@ -0,0 +1,725 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Linear elasticity\n", + "\n", + "![](linear_elasticity.svg)\n", + "\n", + "*Figure 1*: Linear elastically deformed 1mm $\\times$ 1mm Ferrite logo." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Introduction\n", + "\n", + "The classical first finite element problem to solve in solid mechanics is a linear balance\n", + "of momentum problem. We will use this to introduce a vector valued field, the displacements\n", + "$\\boldsymbol{u}(\\boldsymbol{x})$. In addition, some features of the\n", + "[`Tensors.jl`](https://github.com/Ferrite-FEM/Tensors.jl) toolbox are demonstrated.\n", + "\n", + "### Strong form\n", + "The strong form of the balance of momentum for quasi-static loading is given by\n", + "$$\n", + "\\begin{alignat*}{2}\n", + " \\mathrm{div}(\\boldsymbol{\\sigma}) + \\boldsymbol{b} &= 0 \\quad &&\\boldsymbol{x} \\in \\Omega \\\\\n", + " \\boldsymbol{u} &= \\boldsymbol{u}_\\mathrm{D} \\quad &&\\boldsymbol{x} \\in \\Gamma_\\mathrm{D} \\\\\n", + " \\boldsymbol{n} \\cdot \\boldsymbol{\\sigma} &= \\boldsymbol{t}_\\mathrm{N} \\quad &&\\boldsymbol{x} \\in \\Gamma_\\mathrm{N}\n", + "\\end{alignat*}\n", + "$$\n", + "where $\\boldsymbol{\\sigma}$ is the (Cauchy) stress tensor and $\\boldsymbol{b}$ the body force.\n", + "The domain, $\\Omega$, has the boundary $\\Gamma$, consisting of a Dirichlet part, $\\Gamma_\\mathrm{D}$, and\n", + "a Neumann part, $\\Gamma_\\mathrm{N}$, with outward pointing normal vector $\\boldsymbol{n}$.\n", + "$\\boldsymbol{u}_\\mathrm{D}$ denotes prescribed displacements on $\\Gamma_\\mathrm{D}$,\n", + "while $\\boldsymbol{t}_\\mathrm{N}$ the known tractions on $\\Gamma_\\mathrm{N}$.\n", + "\n", + "In this tutorial, we use linear elasticity, such that\n", + "$$\n", + "\\boldsymbol{\\sigma} = \\mathsf{C} : \\boldsymbol{\\varepsilon}, \\quad\n", + "\\boldsymbol{\\varepsilon} = \\left[\\mathrm{grad}(\\boldsymbol{u})\\right]^\\mathrm{sym}\n", + "$$\n", + "where $\\mathsf{C}$ is the 4th order elastic stiffness tensor and\n", + "$\\boldsymbol{\\varepsilon}$ the small strain tensor.\n", + "The colon, $:$, represents the double contraction,\n", + "$\\sigma_{ij} = \\mathsf{C}_{ijkl} \\varepsilon_{kl}$, and the superscript $\\mathrm{sym}$\n", + "denotes the symmetric part.\n", + "\n", + "### Weak form\n", + "The resulting weak form is given given as follows: Find $\\boldsymbol{u} \\in \\mathbb{U}$ such that\n", + "$$\n", + "\\int_\\Omega\n", + " \\mathrm{grad}(\\delta \\boldsymbol{u}) : \\boldsymbol{\\sigma}\n", + "\\, \\mathrm{d}\\Omega\n", + "=\n", + "\\int_{\\Gamma}\n", + " \\delta \\boldsymbol{u} \\cdot \\boldsymbol{t}\n", + "\\, \\mathrm{d}\\Gamma\n", + "+\n", + "\\int_\\Omega\n", + " \\delta \\boldsymbol{u} \\cdot \\boldsymbol{b}\n", + "\\, \\mathrm{d}\\Omega\n", + "\\quad \\forall \\, \\delta \\boldsymbol{u} \\in \\mathbb{T},\n", + "$$\n", + "where $\\mathbb{U}$ and $\\mathbb{T}$ denote suitable trial and test function spaces.\n", + "$\\delta \\boldsymbol{u}$ is a vector valued test function and\n", + "$\\boldsymbol{t} = \\boldsymbol{\\sigma}\\cdot\\boldsymbol{n}$ is the traction vector on\n", + "the boundary. In this tutorial, we will neglect body forces (i.e. $\\boldsymbol{b} = \\boldsymbol{0}$) and the weak form reduces to\n", + "$$\n", + "\\int_\\Omega\n", + " \\mathrm{grad}(\\delta \\boldsymbol{u}) : \\boldsymbol{\\sigma}\n", + "\\, \\mathrm{d}\\Omega\n", + "=\n", + "\\int_{\\Gamma}\n", + " \\delta \\boldsymbol{u} \\cdot \\boldsymbol{t}\n", + "\\, \\mathrm{d}\\Gamma \\,.\n", + "$$\n", + "\n", + "### Finite element form\n", + "Finally, the finite element form is obtained by introducing the finite element shape functions.\n", + "Since the displacement field, $\\boldsymbol{u}$, is vector valued, we use vector valued shape functions\n", + "$\\delta\\boldsymbol{N}_i$ and $\\boldsymbol{N}_i$ to approximate the test and trial functions:\n", + "$$\n", + "\\boldsymbol{u} \\approx \\sum_{i=1}^N \\boldsymbol{N}_i (\\boldsymbol{x}) \\, \\hat{u}_i\n", + "\\qquad\n", + "\\delta \\boldsymbol{u} \\approx \\sum_{i=1}^N \\delta\\boldsymbol{N}_i (\\boldsymbol{x}) \\, \\delta \\hat{u}_i\n", + "$$\n", + "Here $N$ is the number of nodal variables, with $\\hat{u}_i$ and $\\delta\\hat{u}_i$ representing the $i$-th nodal value.\n", + "Using the Einstein summation convention, we can write this in short form as\n", + "$\\boldsymbol{u} \\approx \\boldsymbol{N}_i \\, \\hat{u}_i$ and $\\delta\\boldsymbol{u} \\approx \\delta\\boldsymbol{N}_i \\, \\delta\\hat{u}_i$.\n", + "\n", + "Inserting the these into the weak form, and noting that that the equation should hold for all $\\delta \\hat{u}_i$, we get\n", + "$$\n", + "\\underbrace{\\int_\\Omega \\mathrm{grad}(\\delta \\boldsymbol{N}_i) : \\boldsymbol{\\sigma}\\ \\mathrm{d}\\Omega}_{f_i^\\mathrm{int}} = \\underbrace{\\int_\\Gamma \\delta \\boldsymbol{N}_i \\cdot \\boldsymbol{t}\\ \\mathrm{d}\\Gamma}_{f_i^\\mathrm{ext}}\n", + "$$\n", + "Inserting the linear constitutive relationship, $\\boldsymbol{\\sigma} = \\mathsf{C}:\\boldsymbol{\\varepsilon}$,\n", + "in the internal force vector, $f_i^\\mathrm{int}$, yields the linear equation\n", + "$$\n", + "\\underbrace{\\left[\\int_\\Omega \\mathrm{grad}(\\delta \\boldsymbol{N}_i) : \\mathsf{C} : \\left[\\mathrm{grad}(\\boldsymbol{N}_j)\\right]^\\mathrm{sym}\\ \\mathrm{d}\\Omega\\right]}_{K_{ij}}\\ \\hat{u}_j = f_i^\\mathrm{ext}\n", + "$$\n", + "\n", + "## Implementation\n", + "First we load Ferrite, and some other packages we need." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using Ferrite, FerriteGmsh, SparseArrays" + ], + "metadata": {}, + "execution_count": 1 + }, + { + "cell_type": "markdown", + "source": [ + "As in the heat equation tutorial, we will use a unit square - but here we'll load the grid of the Ferrite logo!\n", + "This is done by downloading [`logo.geo`](logo.geo) and loading it using [FerriteGmsh.jl](https://github.com/Ferrite-FEM/FerriteGmsh.jl)," + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using Downloads: download\n", + "logo_mesh = \"logo.geo\"\n", + "asset_url = \"https://raw.githubusercontent.com/Ferrite-FEM/Ferrite.jl/gh-pages/assets/\"\n", + "isfile(logo_mesh) || download(string(asset_url, logo_mesh), logo_mesh)\n", + "\n", + "FerriteGmsh.Gmsh.initialize() #hide\n", + "FerriteGmsh.Gmsh.gmsh.option.set_number(\"General.Verbosity\", 2) #hide\n", + "grid = togrid(logo_mesh);\n", + "FerriteGmsh.Gmsh.finalize(); #hide" + ], + "metadata": {}, + "execution_count": 2 + }, + { + "cell_type": "markdown", + "source": [ + "The generated grid lacks the facetsets for the boundaries, so we add them by using Ferrite's\n", + "`addfacetset!`. It allows us to add facetsets to the grid based on coordinates.\n", + "Note that approximate comparison to 0.0 doesn't work well, so we use a tolerance instead." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "addfacetset!(grid, \"top\", x -> x[2] ≈ 1.0) # facets for which x[2] ≈ 1.0 for all nodes\n", + "addfacetset!(grid, \"left\", x -> abs(x[1]) < 1.0e-6)\n", + "addfacetset!(grid, \"bottom\", x -> abs(x[2]) < 1.0e-6);" + ], + "metadata": {}, + "execution_count": 3 + }, + { + "cell_type": "markdown", + "source": [ + "### Trial and test functions\n", + "In this tutorial, we use the same linear Lagrange shape functions to approximate both the\n", + "test and trial spaces, i.e. $\\delta\\boldsymbol{N}_i = \\boldsymbol{N}_i$.\n", + "As our grid is composed of triangular elements, we need the Lagrange functions defined\n", + "on a `RefTriangle`. All currently available interpolations can be found under\n", + "`Interpolation`.\n", + "\n", + "Here we use linear triangular elements (also called constant strain triangles).\n", + "The vector valued shape functions are constructed by raising the interpolation\n", + "to the power `dim` (the dimension) since the displacement field has one component in each\n", + "spatial dimension." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "dim = 2\n", + "order = 1 # linear interpolation\n", + "ip = Lagrange{RefTriangle, order}()^dim; # vector valued interpolation" + ], + "metadata": {}, + "execution_count": 4 + }, + { + "cell_type": "markdown", + "source": [ + "In order to evaluate the integrals, we need to specify the quadrature rules to use. Due to the\n", + "linear interpolation, a single quadrature point suffices, both inside the cell and on the facet.\n", + "In 2d, a facet is the edge of the element." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "qr = QuadratureRule{RefTriangle}(1) # 1 quadrature point\n", + "qr_face = FacetQuadratureRule{RefTriangle}(1);" + ], + "metadata": {}, + "execution_count": 5 + }, + { + "cell_type": "markdown", + "source": [ + "Finally, we collect the interpolations and quadrature rules into the `CellValues` and `FacetValues`\n", + "buffers, which we will later use to evaluate the integrals over the cells and facets." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "cellvalues = CellValues(qr, ip)\n", + "facetvalues = FacetValues(qr_face, ip);" + ], + "metadata": {}, + "execution_count": 6 + }, + { + "cell_type": "markdown", + "source": [ + "### Degrees of freedom\n", + "For distributing degrees of freedom, we define a `DofHandler`. The `DofHandler` knows that\n", + "`u` has two degrees of freedom per node because we vectorized the interpolation above." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "dh = DofHandler(grid)\n", + "add!(dh, :u, ip)\n", + "close!(dh);" + ], + "metadata": {}, + "execution_count": 7 + }, + { + "cell_type": "markdown", + "source": [ + "### Boundary conditions\n", + "We set Dirichlet boundary conditions by fixing the motion normal to the bottom and left\n", + "boundaries. The last argument to `Dirichlet` determines which components of the field should be\n", + "constrained. If no argument is given, all components are constrained by default." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "ch = ConstraintHandler(dh)\n", + "add!(ch, Dirichlet(:u, getfacetset(grid, \"bottom\"), (x, t) -> 0.0, 2))\n", + "add!(ch, Dirichlet(:u, getfacetset(grid, \"left\"), (x, t) -> 0.0, 1))\n", + "close!(ch);" + ], + "metadata": {}, + "execution_count": 8 + }, + { + "cell_type": "markdown", + "source": [ + "In addition, we will use Neumann boundary conditions on the top surface, where\n", + "we add a traction vector of the form\n", + "$$\n", + "\\boldsymbol{t}_\\mathrm{N}(\\boldsymbol{x}) = (20e3) x_1 \\boldsymbol{e}_2\\ \\mathrm{N}/\\mathrm{mm}^2\n", + "$$" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "traction(x) = Vec(0.0, 20.0e3 * x[1]);" + ], + "metadata": {}, + "execution_count": 9 + }, + { + "cell_type": "markdown", + "source": [ + "On the right boundary, we don't do anything, resulting in a zero traction Neumann boundary.\n", + "In order to assemble the external forces, $f_i^\\mathrm{ext}$, we need to iterate over all\n", + "facets in the relevant facetset. We do this by using the `FacetIterator`." + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "assemble_external_forces! (generic function with 1 method)" + }, + "metadata": {}, + "execution_count": 10 + } + ], + "cell_type": "code", + "source": [ + "function assemble_external_forces!(f_ext, dh, facetset, facetvalues, prescribed_traction)\n", + " # Create a temporary array for the facet's local contributions to the external force vector\n", + " fe_ext = zeros(getnbasefunctions(facetvalues))\n", + " for facet in FacetIterator(dh, facetset)\n", + " # Update the facetvalues to the correct facet number\n", + " reinit!(facetvalues, facet)\n", + " # Reset the temporary array for the next facet\n", + " fill!(fe_ext, 0.0)\n", + " # Access the cell's coordinates\n", + " cell_coordinates = getcoordinates(facet)\n", + " for qp in 1:getnquadpoints(facetvalues)\n", + " # Calculate the global coordinate of the quadrature point.\n", + " x = spatial_coordinate(facetvalues, qp, cell_coordinates)\n", + " tₚ = prescribed_traction(x)\n", + " # Get the integration weight for the current quadrature point.\n", + " dΓ = getdetJdV(facetvalues, qp)\n", + " for i in 1:getnbasefunctions(facetvalues)\n", + " Nᵢ = shape_value(facetvalues, qp, i)\n", + " fe_ext[i] += tₚ ⋅ Nᵢ * dΓ\n", + " end\n", + " end\n", + " # Add the local contributions to the correct indices in the global external force vector\n", + " assemble!(f_ext, celldofs(facet), fe_ext)\n", + " end\n", + " return f_ext\n", + "end" + ], + "metadata": {}, + "execution_count": 10 + }, + { + "cell_type": "markdown", + "source": [ + "### Material behavior\n", + "Next, we need to define the material behavior, specifically the elastic stiffness tensor, $\\mathsf{C}$.\n", + "In this tutorial, we use plane strain linear isotropic elasticity, with Hooke's law as\n", + "$$\n", + "\\boldsymbol{\\sigma} = 2G \\boldsymbol{\\varepsilon}^\\mathrm{dev} + 3K \\boldsymbol{\\varepsilon}^\\mathrm{vol}\n", + "$$\n", + "where $G$ is the shear modulus and $K$ the bulk modulus. This expression can be written as\n", + "$\\boldsymbol{\\sigma} = \\mathsf{C}:\\boldsymbol{\\varepsilon}$, with\n", + "$$\n", + " \\mathsf{C} := \\frac{\\partial \\boldsymbol{\\sigma}}{\\partial \\boldsymbol{\\varepsilon}}\n", + "$$\n", + "The volumetric, $\\boldsymbol{\\varepsilon}^\\mathrm{vol}$,\n", + "and deviatoric, $\\boldsymbol{\\varepsilon}^\\mathrm{dev}$ strains, are defined as\n", + "$$\n", + "\\begin{align*}\n", + "\\boldsymbol{\\varepsilon}^\\mathrm{vol} &= \\frac{\\mathrm{tr}(\\boldsymbol{\\varepsilon})}{3}\\boldsymbol{I}, \\quad\n", + "\\boldsymbol{\\varepsilon}^\\mathrm{dev} &= \\boldsymbol{\\varepsilon} - \\boldsymbol{\\varepsilon}^\\mathrm{vol}\n", + "\\end{align*}\n", + "$$\n", + "\n", + "Starting from Young's modulus, $E$, and Poisson's ratio, $\\nu$, the shear and bulk modulus are\n", + "$$\n", + "G = \\frac{E}{2(1 + \\nu)}, \\quad K = \\frac{E}{3(1 - 2\\nu)}\n", + "$$" + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "166666.66666666663" + }, + "metadata": {}, + "execution_count": 11 + } + ], + "cell_type": "code", + "source": [ + "Emod = 200.0e3 # Young's modulus [MPa]\n", + "ν = 0.3 # Poisson's ratio [-]\n", + "\n", + "Gmod = Emod / (2(1 + ν)) # Shear modulus\n", + "Kmod = Emod / (3(1 - 2ν)) # Bulk modulus" + ], + "metadata": {}, + "execution_count": 11 + }, + { + "cell_type": "markdown", + "source": [ + "Finally, we demonstrate `Tensors.jl`'s automatic differentiation capabilities when\n", + "calculating the elastic stiffness tensor" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "C = gradient(ϵ -> 2 * Gmod * dev(ϵ) + 3 * Kmod * vol(ϵ), zero(SymmetricTensor{2, 2}));" + ], + "metadata": {}, + "execution_count": 12 + }, + { + "cell_type": "markdown", + "source": [ + "### Element routine\n", + "To calculate the global stiffness matrix, $K_{ij}$, the element routine computes the\n", + "local stiffness matrix `ke` for a single element and assembles it into the global matrix.\n", + "`ke` is pre-allocated and reused for all elements.\n", + "\n", + "Note that the elastic stiffness tensor $\\mathsf{C}$ is constant.\n", + "Thus is needs to be computed and once and can then be used for all integration points." + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "assemble_cell! (generic function with 1 method)" + }, + "metadata": {}, + "execution_count": 13 + } + ], + "cell_type": "code", + "source": [ + "function assemble_cell!(ke, cellvalues, C)\n", + " for q_point in 1:getnquadpoints(cellvalues)\n", + " # Get the integration weight for the quadrature point\n", + " dΩ = getdetJdV(cellvalues, q_point)\n", + " for i in 1:getnbasefunctions(cellvalues)\n", + " # Gradient of the test function\n", + " ∇Nᵢ = shape_gradient(cellvalues, q_point, i)\n", + " for j in 1:getnbasefunctions(cellvalues)\n", + " # Symmetric gradient of the trial function\n", + " ∇ˢʸᵐNⱼ = shape_symmetric_gradient(cellvalues, q_point, j)\n", + " ke[i, j] += (∇Nᵢ ⊡ C ⊡ ∇ˢʸᵐNⱼ) * dΩ\n", + " end\n", + " end\n", + " end\n", + " return ke\n", + "end" + ], + "metadata": {}, + "execution_count": 13 + }, + { + "cell_type": "markdown", + "source": [ + "### Global assembly\n", + "We define the function `assemble_global` to loop over the elements and do the global\n", + "assembly. The function takes the preallocated sparse matrix `K`, our DofHandler `dh`, our\n", + "`cellvalues` and the elastic stiffness tensor `C` as input arguments and computes the\n", + "global stiffness matrix `K`." + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "assemble_global! (generic function with 1 method)" + }, + "metadata": {}, + "execution_count": 14 + } + ], + "cell_type": "code", + "source": [ + "function assemble_global!(K, dh, cellvalues, C)\n", + " # Allocate the element stiffness matrix\n", + " n_basefuncs = getnbasefunctions(cellvalues)\n", + " ke = zeros(n_basefuncs, n_basefuncs)\n", + " # Create an assembler\n", + " assembler = start_assemble(K)\n", + " # Loop over all cells\n", + " for cell in CellIterator(dh)\n", + " # Update the shape function gradients based on the cell coordinates\n", + " reinit!(cellvalues, cell)\n", + " # Reset the element stiffness matrix\n", + " fill!(ke, 0.0)\n", + " # Compute element contribution\n", + " assemble_cell!(ke, cellvalues, C)\n", + " # Assemble ke into K\n", + " assemble!(assembler, celldofs(cell), ke)\n", + " end\n", + " return K\n", + "end" + ], + "metadata": {}, + "execution_count": 14 + }, + { + "cell_type": "markdown", + "source": [ + "### Solution of the system\n", + "The last step is to solve the system. First we allocate the global stiffness matrix `K`\n", + "and assemble it." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "K = allocate_matrix(dh)\n", + "assemble_global!(K, dh, cellvalues, C);" + ], + "metadata": {}, + "execution_count": 15 + }, + { + "cell_type": "markdown", + "source": [ + "Then we allocate and assemble the external force vector." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "f_ext = zeros(ndofs(dh))\n", + "assemble_external_forces!(f_ext, dh, getfacetset(grid, \"top\"), facetvalues, traction);" + ], + "metadata": {}, + "execution_count": 16 + }, + { + "cell_type": "markdown", + "source": [ + "To account for the Dirichlet boundary conditions we use the `apply!` function.\n", + "This modifies elements in `K` and `f`, such that we can get the\n", + "correct solution vector `u` by using solving the linear equation system $K_{ij} \\hat{u}_j = f^\\mathrm{ext}_i$," + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "apply!(K, f_ext, ch)\n", + "u = K \\ f_ext;" + ], + "metadata": {}, + "execution_count": 17 + }, + { + "cell_type": "markdown", + "source": [ + "### Postprocessing\n", + "In this case, we want to analyze the displacements, as well as the stress field.\n", + "We calculate the stress in each quadrature point, and then export it in two different\n", + "ways:\n", + "1) Constant in each cell (matching the approximation of constant strains in each element).\n", + " Note that a current limitation is that cell data for second order tensors must be exported\n", + " component-wise (see issue #768)\n", + "2) Interpolated using the linear lagrange ansatz functions via the `L2Projector`." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function calculate_stresses(grid, dh, cv, u, C)\n", + " qp_stresses = [\n", + " [zero(SymmetricTensor{2, 2}) for _ in 1:getnquadpoints(cv)]\n", + " for _ in 1:getncells(grid)\n", + " ]\n", + " avg_cell_stresses = tuple((zeros(getncells(grid)) for _ in 1:3)...)\n", + " for cell in CellIterator(dh)\n", + " reinit!(cv, cell)\n", + " cell_stresses = qp_stresses[cellid(cell)]\n", + " for q_point in 1:getnquadpoints(cv)\n", + " ε = function_symmetric_gradient(cv, q_point, u, celldofs(cell))\n", + " cell_stresses[q_point] = C ⊡ ε\n", + " end\n", + " σ_avg = sum(cell_stresses) / getnquadpoints(cv)\n", + " avg_cell_stresses[1][cellid(cell)] = σ_avg[1, 1]\n", + " avg_cell_stresses[2][cellid(cell)] = σ_avg[2, 2]\n", + " avg_cell_stresses[3][cellid(cell)] = σ_avg[1, 2]\n", + " end\n", + " return qp_stresses, avg_cell_stresses\n", + "end\n", + "\n", + "qp_stresses, avg_cell_stresses = calculate_stresses(grid, dh, cellvalues, u, C);" + ], + "metadata": {}, + "execution_count": 18 + }, + { + "cell_type": "markdown", + "source": [ + "We now use the the L2Projector to project the stress-field onto the piecewise linear\n", + "finite element space that we used to solve the problem." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "proj = L2Projector(Lagrange{RefTriangle, 1}(), grid)\n", + "stress_field = project(proj, qp_stresses, qr);\n", + "\n", + "color_data = zeros(Int, getncells(grid)) #hide\n", + "colors = [ #hide\n", + " \"1\" => 1, \"5\" => 1, # purple #hide\n", + " \"2\" => 2, \"3\" => 2, # red #hide\n", + " \"4\" => 3, # blue #hide\n", + " \"6\" => 4, # green #hide\n", + "] #hide\n", + "for (key, color) in colors #hide\n", + " for i in getcellset(grid, key) #hide\n", + " color_data[i] = color #hide\n", + " end #hide\n", + "end #hide" + ], + "metadata": {}, + "execution_count": 19 + }, + { + "cell_type": "markdown", + "source": [ + "To visualize the result we export to a VTK-file. Specifically, an unstructured\n", + "grid file, `.vtu`, is created, which can be viewed in e.g.\n", + "[ParaView](https://www.paraview.org/)." + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "VTKGridFile for the closed file \"linear_elasticity.vtu\"." + }, + "metadata": {}, + "execution_count": 20 + } + ], + "cell_type": "code", + "source": [ + "VTKGridFile(\"linear_elasticity\", dh) do vtk\n", + " write_solution(vtk, dh, u)\n", + " for (i, key) in enumerate((\"11\", \"22\", \"12\"))\n", + " write_cell_data(vtk, avg_cell_stresses[i], \"sigma_\" * key)\n", + " end\n", + " write_projection(vtk, proj, stress_field, \"stress field\")\n", + " Ferrite.write_cellset(vtk, grid)\n", + " write_cell_data(vtk, color_data, \"colors\") #hide\n", + "end" + ], + "metadata": {}, + "execution_count": 20 + }, + { + "cell_type": "markdown", + "source": [ + "We used the displacement field to visualize the deformed logo in *Figure 1*,\n", + "and in *Figure 2*, we demonstrate the difference between the interpolated stress\n", + "field and the constant stress in each cell." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "![](linear_elasticity_stress.png)\n", + "\n", + "*Figure 2*: Vertical normal stresses (MPa) exported using the `L2Projector` (left)\n", + "and constant stress in each cell (right)." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using Test #hide\n", + "linux_result = 0.31742879147646924 #hide\n", + "@test abs(norm(u) - linux_result) < 0.01 #hide\n", + "Sys.islinux() && @test norm(u) ≈ linux_result #hide\n", + "nothing #hide" + ], + "metadata": {}, + "execution_count": 21 + }, + { + "cell_type": "markdown", + "source": [ + "---\n", + "\n", + "*This notebook was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).*" + ], + "metadata": {} + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.1" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.1", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR798/tutorials/linear_elasticity.jl b/previews/PR798/tutorials/linear_elasticity.jl new file mode 100644 index 0000000000..0eeb1839ce --- /dev/null +++ b/previews/PR798/tutorials/linear_elasticity.jl @@ -0,0 +1,174 @@ +using Ferrite, FerriteGmsh, SparseArrays + +using Downloads: download +logo_mesh = "logo.geo" +asset_url = "https://raw.githubusercontent.com/Ferrite-FEM/Ferrite.jl/gh-pages/assets/" +isfile(logo_mesh) || download(string(asset_url, logo_mesh), logo_mesh) + +FerriteGmsh.Gmsh.initialize() #hide +FerriteGmsh.Gmsh.gmsh.option.set_number("General.Verbosity", 2) #hide +grid = togrid(logo_mesh); +FerriteGmsh.Gmsh.finalize(); #hide + +addfacetset!(grid, "top", x -> x[2] ≈ 1.0) # facets for which x[2] ≈ 1.0 for all nodes +addfacetset!(grid, "left", x -> abs(x[1]) < 1.0e-6) +addfacetset!(grid, "bottom", x -> abs(x[2]) < 1.0e-6); + +dim = 2 +order = 1 # linear interpolation +ip = Lagrange{RefTriangle, order}()^dim; # vector valued interpolation + +qr = QuadratureRule{RefTriangle}(1) # 1 quadrature point +qr_face = FacetQuadratureRule{RefTriangle}(1); + +cellvalues = CellValues(qr, ip) +facetvalues = FacetValues(qr_face, ip); + +dh = DofHandler(grid) +add!(dh, :u, ip) +close!(dh); + +ch = ConstraintHandler(dh) +add!(ch, Dirichlet(:u, getfacetset(grid, "bottom"), (x, t) -> 0.0, 2)) +add!(ch, Dirichlet(:u, getfacetset(grid, "left"), (x, t) -> 0.0, 1)) +close!(ch); + +traction(x) = Vec(0.0, 20.0e3 * x[1]); + +function assemble_external_forces!(f_ext, dh, facetset, facetvalues, prescribed_traction) + # Create a temporary array for the facet's local contributions to the external force vector + fe_ext = zeros(getnbasefunctions(facetvalues)) + for facet in FacetIterator(dh, facetset) + # Update the facetvalues to the correct facet number + reinit!(facetvalues, facet) + # Reset the temporary array for the next facet + fill!(fe_ext, 0.0) + # Access the cell's coordinates + cell_coordinates = getcoordinates(facet) + for qp in 1:getnquadpoints(facetvalues) + # Calculate the global coordinate of the quadrature point. + x = spatial_coordinate(facetvalues, qp, cell_coordinates) + tₚ = prescribed_traction(x) + # Get the integration weight for the current quadrature point. + dΓ = getdetJdV(facetvalues, qp) + for i in 1:getnbasefunctions(facetvalues) + Nᵢ = shape_value(facetvalues, qp, i) + fe_ext[i] += tₚ ⋅ Nᵢ * dΓ + end + end + # Add the local contributions to the correct indices in the global external force vector + assemble!(f_ext, celldofs(facet), fe_ext) + end + return f_ext +end + +Emod = 200.0e3 # Young's modulus [MPa] +ν = 0.3 # Poisson's ratio [-] + +Gmod = Emod / (2(1 + ν)) # Shear modulus +Kmod = Emod / (3(1 - 2ν)) # Bulk modulus + +C = gradient(ϵ -> 2 * Gmod * dev(ϵ) + 3 * Kmod * vol(ϵ), zero(SymmetricTensor{2, 2})); + +function assemble_cell!(ke, cellvalues, C) + for q_point in 1:getnquadpoints(cellvalues) + # Get the integration weight for the quadrature point + dΩ = getdetJdV(cellvalues, q_point) + for i in 1:getnbasefunctions(cellvalues) + # Gradient of the test function + ∇Nᵢ = shape_gradient(cellvalues, q_point, i) + for j in 1:getnbasefunctions(cellvalues) + # Symmetric gradient of the trial function + ∇ˢʸᵐNⱼ = shape_symmetric_gradient(cellvalues, q_point, j) + ke[i, j] += (∇Nᵢ ⊡ C ⊡ ∇ˢʸᵐNⱼ) * dΩ + end + end + end + return ke +end + +function assemble_global!(K, dh, cellvalues, C) + # Allocate the element stiffness matrix + n_basefuncs = getnbasefunctions(cellvalues) + ke = zeros(n_basefuncs, n_basefuncs) + # Create an assembler + assembler = start_assemble(K) + # Loop over all cells + for cell in CellIterator(dh) + # Update the shape function gradients based on the cell coordinates + reinit!(cellvalues, cell) + # Reset the element stiffness matrix + fill!(ke, 0.0) + # Compute element contribution + assemble_cell!(ke, cellvalues, C) + # Assemble ke into K + assemble!(assembler, celldofs(cell), ke) + end + return K +end + +K = allocate_matrix(dh) +assemble_global!(K, dh, cellvalues, C); + +f_ext = zeros(ndofs(dh)) +assemble_external_forces!(f_ext, dh, getfacetset(grid, "top"), facetvalues, traction); + +apply!(K, f_ext, ch) +u = K \ f_ext; + +function calculate_stresses(grid, dh, cv, u, C) + qp_stresses = [ + [zero(SymmetricTensor{2, 2}) for _ in 1:getnquadpoints(cv)] + for _ in 1:getncells(grid) + ] + avg_cell_stresses = tuple((zeros(getncells(grid)) for _ in 1:3)...) + for cell in CellIterator(dh) + reinit!(cv, cell) + cell_stresses = qp_stresses[cellid(cell)] + for q_point in 1:getnquadpoints(cv) + ε = function_symmetric_gradient(cv, q_point, u, celldofs(cell)) + cell_stresses[q_point] = C ⊡ ε + end + σ_avg = sum(cell_stresses) / getnquadpoints(cv) + avg_cell_stresses[1][cellid(cell)] = σ_avg[1, 1] + avg_cell_stresses[2][cellid(cell)] = σ_avg[2, 2] + avg_cell_stresses[3][cellid(cell)] = σ_avg[1, 2] + end + return qp_stresses, avg_cell_stresses +end + +qp_stresses, avg_cell_stresses = calculate_stresses(grid, dh, cellvalues, u, C); + +proj = L2Projector(Lagrange{RefTriangle, 1}(), grid) +stress_field = project(proj, qp_stresses, qr); + +color_data = zeros(Int, getncells(grid)) #hide +colors = [ #hide + "1" => 1, "5" => 1, # purple #hide + "2" => 2, "3" => 2, # red #hide + "4" => 3, # blue #hide + "6" => 4, # green #hide +] #hide +for (key, color) in colors #hide + for i in getcellset(grid, key) #hide + color_data[i] = color #hide + end #hide +end #hide + +VTKGridFile("linear_elasticity", dh) do vtk + write_solution(vtk, dh, u) + for (i, key) in enumerate(("11", "22", "12")) + write_cell_data(vtk, avg_cell_stresses[i], "sigma_" * key) + end + write_projection(vtk, proj, stress_field, "stress field") + Ferrite.write_cellset(vtk, grid) + write_cell_data(vtk, color_data, "colors") #hide +end + +using Test #hide +linux_result = 0.31742879147646924 #hide +@test abs(norm(u) - linux_result) < 0.01 #hide +Sys.islinux() && @test norm(u) ≈ linux_result #hide +nothing #hide + +# This file was generated using Literate.jl, https://github.com/fredrikekre/Literate.jl diff --git a/previews/PR798/tutorials/linear_elasticity.svg b/previews/PR798/tutorials/linear_elasticity.svg new file mode 100644 index 0000000000..5ef7f089f3 --- /dev/null +++ b/previews/PR798/tutorials/linear_elasticity.svg @@ -0,0 +1,1027 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/previews/PR798/tutorials/linear_elasticity/index.html b/previews/PR798/tutorials/linear_elasticity/index.html new file mode 100644 index 0000000000..6c92d02e04 --- /dev/null +++ b/previews/PR798/tutorials/linear_elasticity/index.html @@ -0,0 +1,293 @@ + +Linear elasticity · Ferrite.jl

Linear elasticity

Figure 1: Linear elastically deformed 1mm $\times$ 1mm Ferrite logo.

Tip

This tutorial is also available as a Jupyter notebook: linear_elasticity.ipynb.

Introduction

The classical first finite element problem to solve in solid mechanics is a linear balance of momentum problem. We will use this to introduce a vector valued field, the displacements $\boldsymbol{u}(\boldsymbol{x})$. In addition, some features of the Tensors.jl toolbox are demonstrated.

Strong form

The strong form of the balance of momentum for quasi-static loading is given by

\[\begin{alignat*}{2} + \mathrm{div}(\boldsymbol{\sigma}) + \boldsymbol{b} &= 0 \quad &&\boldsymbol{x} \in \Omega \\ + \boldsymbol{u} &= \boldsymbol{u}_\mathrm{D} \quad &&\boldsymbol{x} \in \Gamma_\mathrm{D} \\ + \boldsymbol{n} \cdot \boldsymbol{\sigma} &= \boldsymbol{t}_\mathrm{N} \quad &&\boldsymbol{x} \in \Gamma_\mathrm{N} +\end{alignat*}\]

where $\boldsymbol{\sigma}$ is the (Cauchy) stress tensor and $\boldsymbol{b}$ the body force. The domain, $\Omega$, has the boundary $\Gamma$, consisting of a Dirichlet part, $\Gamma_\mathrm{D}$, and a Neumann part, $\Gamma_\mathrm{N}$, with outward pointing normal vector $\boldsymbol{n}$. $\boldsymbol{u}_\mathrm{D}$ denotes prescribed displacements on $\Gamma_\mathrm{D}$, while $\boldsymbol{t}_\mathrm{N}$ the known tractions on $\Gamma_\mathrm{N}$.

In this tutorial, we use linear elasticity, such that

\[\boldsymbol{\sigma} = \mathsf{C} : \boldsymbol{\varepsilon}, \quad +\boldsymbol{\varepsilon} = \left[\mathrm{grad}(\boldsymbol{u})\right]^\mathrm{sym}\]

where $\mathsf{C}$ is the 4th order elastic stiffness tensor and $\boldsymbol{\varepsilon}$ the small strain tensor. The colon, $:$, represents the double contraction, $\sigma_{ij} = \mathsf{C}_{ijkl} \varepsilon_{kl}$, and the superscript $\mathrm{sym}$ denotes the symmetric part.

Weak form

The resulting weak form is given given as follows: Find $\boldsymbol{u} \in \mathbb{U}$ such that

\[\int_\Omega + \mathrm{grad}(\delta \boldsymbol{u}) : \boldsymbol{\sigma} +\, \mathrm{d}\Omega += +\int_{\Gamma} + \delta \boldsymbol{u} \cdot \boldsymbol{t} +\, \mathrm{d}\Gamma ++ +\int_\Omega + \delta \boldsymbol{u} \cdot \boldsymbol{b} +\, \mathrm{d}\Omega +\quad \forall \, \delta \boldsymbol{u} \in \mathbb{T},\]

where $\mathbb{U}$ and $\mathbb{T}$ denote suitable trial and test function spaces. $\delta \boldsymbol{u}$ is a vector valued test function and $\boldsymbol{t} = \boldsymbol{\sigma}\cdot\boldsymbol{n}$ is the traction vector on the boundary. In this tutorial, we will neglect body forces (i.e. $\boldsymbol{b} = \boldsymbol{0}$) and the weak form reduces to

\[\int_\Omega + \mathrm{grad}(\delta \boldsymbol{u}) : \boldsymbol{\sigma} +\, \mathrm{d}\Omega += +\int_{\Gamma} + \delta \boldsymbol{u} \cdot \boldsymbol{t} +\, \mathrm{d}\Gamma \,.\]

Finite element form

Finally, the finite element form is obtained by introducing the finite element shape functions. Since the displacement field, $\boldsymbol{u}$, is vector valued, we use vector valued shape functions $\delta\boldsymbol{N}_i$ and $\boldsymbol{N}_i$ to approximate the test and trial functions:

\[\boldsymbol{u} \approx \sum_{i=1}^N \boldsymbol{N}_i (\boldsymbol{x}) \, \hat{u}_i +\qquad +\delta \boldsymbol{u} \approx \sum_{i=1}^N \delta\boldsymbol{N}_i (\boldsymbol{x}) \, \delta \hat{u}_i\]

Here $N$ is the number of nodal variables, with $\hat{u}_i$ and $\delta\hat{u}_i$ representing the $i$-th nodal value. Using the Einstein summation convention, we can write this in short form as $\boldsymbol{u} \approx \boldsymbol{N}_i \, \hat{u}_i$ and $\delta\boldsymbol{u} \approx \delta\boldsymbol{N}_i \, \delta\hat{u}_i$.

Inserting the these into the weak form, and noting that that the equation should hold for all $\delta \hat{u}_i$, we get

\[\underbrace{\int_\Omega \mathrm{grad}(\delta \boldsymbol{N}_i) : \boldsymbol{\sigma}\ \mathrm{d}\Omega}_{f_i^\mathrm{int}} = \underbrace{\int_\Gamma \delta \boldsymbol{N}_i \cdot \boldsymbol{t}\ \mathrm{d}\Gamma}_{f_i^\mathrm{ext}}\]

Inserting the linear constitutive relationship, $\boldsymbol{\sigma} = \mathsf{C}:\boldsymbol{\varepsilon}$, in the internal force vector, $f_i^\mathrm{int}$, yields the linear equation

\[\underbrace{\left[\int_\Omega \mathrm{grad}(\delta \boldsymbol{N}_i) : \mathsf{C} : \left[\mathrm{grad}(\boldsymbol{N}_j)\right]^\mathrm{sym}\ \mathrm{d}\Omega\right]}_{K_{ij}}\ \hat{u}_j = f_i^\mathrm{ext}\]

Implementation

First we load Ferrite, and some other packages we need.

using Ferrite, FerriteGmsh, SparseArrays

As in the heat equation tutorial, we will use a unit square - but here we'll load the grid of the Ferrite logo! This is done by downloading logo.geo and loading it using FerriteGmsh.jl,

using Downloads: download
+logo_mesh = "logo.geo"
+asset_url = "https://raw.githubusercontent.com/Ferrite-FEM/Ferrite.jl/gh-pages/assets/"
+isfile(logo_mesh) || download(string(asset_url, logo_mesh), logo_mesh)
+
+grid = togrid(logo_mesh);

The generated grid lacks the facetsets for the boundaries, so we add them by using Ferrite's addfacetset!. It allows us to add facetsets to the grid based on coordinates. Note that approximate comparison to 0.0 doesn't work well, so we use a tolerance instead.

addfacetset!(grid, "top", x -> x[2] ≈ 1.0) # facets for which x[2] ≈ 1.0 for all nodes
+addfacetset!(grid, "left", x -> abs(x[1]) < 1.0e-6)
+addfacetset!(grid, "bottom", x -> abs(x[2]) < 1.0e-6);

Trial and test functions

In this tutorial, we use the same linear Lagrange shape functions to approximate both the test and trial spaces, i.e. $\delta\boldsymbol{N}_i = \boldsymbol{N}_i$. As our grid is composed of triangular elements, we need the Lagrange functions defined on a RefTriangle. All currently available interpolations can be found under Interpolation.

Here we use linear triangular elements (also called constant strain triangles). The vector valued shape functions are constructed by raising the interpolation to the power dim (the dimension) since the displacement field has one component in each spatial dimension.

dim = 2
+order = 1 # linear interpolation
+ip = Lagrange{RefTriangle, order}()^dim; # vector valued interpolation

In order to evaluate the integrals, we need to specify the quadrature rules to use. Due to the linear interpolation, a single quadrature point suffices, both inside the cell and on the facet. In 2d, a facet is the edge of the element.

qr = QuadratureRule{RefTriangle}(1) # 1 quadrature point
+qr_face = FacetQuadratureRule{RefTriangle}(1);

Finally, we collect the interpolations and quadrature rules into the CellValues and FacetValues buffers, which we will later use to evaluate the integrals over the cells and facets.

cellvalues = CellValues(qr, ip)
+facetvalues = FacetValues(qr_face, ip);

Degrees of freedom

For distributing degrees of freedom, we define a DofHandler. The DofHandler knows that u has two degrees of freedom per node because we vectorized the interpolation above.

dh = DofHandler(grid)
+add!(dh, :u, ip)
+close!(dh);

Boundary conditions

We set Dirichlet boundary conditions by fixing the motion normal to the bottom and left boundaries. The last argument to Dirichlet determines which components of the field should be constrained. If no argument is given, all components are constrained by default.

ch = ConstraintHandler(dh)
+add!(ch, Dirichlet(:u, getfacetset(grid, "bottom"), (x, t) -> 0.0, 2))
+add!(ch, Dirichlet(:u, getfacetset(grid, "left"), (x, t) -> 0.0, 1))
+close!(ch);

In addition, we will use Neumann boundary conditions on the top surface, where we add a traction vector of the form

\[\boldsymbol{t}_\mathrm{N}(\boldsymbol{x}) = (20e3) x_1 \boldsymbol{e}_2\ \mathrm{N}/\mathrm{mm}^2\]

traction(x) = Vec(0.0, 20.0e3 * x[1]);

On the right boundary, we don't do anything, resulting in a zero traction Neumann boundary. In order to assemble the external forces, $f_i^\mathrm{ext}$, we need to iterate over all facets in the relevant facetset. We do this by using the FacetIterator.

function assemble_external_forces!(f_ext, dh, facetset, facetvalues, prescribed_traction)
+    # Create a temporary array for the facet's local contributions to the external force vector
+    fe_ext = zeros(getnbasefunctions(facetvalues))
+    for facet in FacetIterator(dh, facetset)
+        # Update the facetvalues to the correct facet number
+        reinit!(facetvalues, facet)
+        # Reset the temporary array for the next facet
+        fill!(fe_ext, 0.0)
+        # Access the cell's coordinates
+        cell_coordinates = getcoordinates(facet)
+        for qp in 1:getnquadpoints(facetvalues)
+            # Calculate the global coordinate of the quadrature point.
+            x = spatial_coordinate(facetvalues, qp, cell_coordinates)
+            tₚ = prescribed_traction(x)
+            # Get the integration weight for the current quadrature point.
+            dΓ = getdetJdV(facetvalues, qp)
+            for i in 1:getnbasefunctions(facetvalues)
+                Nᵢ = shape_value(facetvalues, qp, i)
+                fe_ext[i] += tₚ ⋅ Nᵢ * dΓ
+            end
+        end
+        # Add the local contributions to the correct indices in the global external force vector
+        assemble!(f_ext, celldofs(facet), fe_ext)
+    end
+    return f_ext
+end

Material behavior

Next, we need to define the material behavior, specifically the elastic stiffness tensor, $\mathsf{C}$. In this tutorial, we use plane strain linear isotropic elasticity, with Hooke's law as

\[\boldsymbol{\sigma} = 2G \boldsymbol{\varepsilon}^\mathrm{dev} + 3K \boldsymbol{\varepsilon}^\mathrm{vol}\]

where $G$ is the shear modulus and $K$ the bulk modulus. This expression can be written as $\boldsymbol{\sigma} = \mathsf{C}:\boldsymbol{\varepsilon}$, with

\[ \mathsf{C} := \frac{\partial \boldsymbol{\sigma}}{\partial \boldsymbol{\varepsilon}}\]

The volumetric, $\boldsymbol{\varepsilon}^\mathrm{vol}$, and deviatoric, $\boldsymbol{\varepsilon}^\mathrm{dev}$ strains, are defined as

\[\begin{align*} +\boldsymbol{\varepsilon}^\mathrm{vol} &= \frac{\mathrm{tr}(\boldsymbol{\varepsilon})}{3}\boldsymbol{I}, \quad +\boldsymbol{\varepsilon}^\mathrm{dev} &= \boldsymbol{\varepsilon} - \boldsymbol{\varepsilon}^\mathrm{vol} +\end{align*}\]

Starting from Young's modulus, $E$, and Poisson's ratio, $\nu$, the shear and bulk modulus are

\[G = \frac{E}{2(1 + \nu)}, \quad K = \frac{E}{3(1 - 2\nu)}\]

Emod = 200.0e3 # Young's modulus [MPa]
+ν = 0.3        # Poisson's ratio [-]
+
+Gmod = Emod / (2(1 + ν))  # Shear modulus
+Kmod = Emod / (3(1 - 2ν)) # Bulk modulus
166666.66666666663

Finally, we demonstrate Tensors.jl's automatic differentiation capabilities when calculating the elastic stiffness tensor

C = gradient(ϵ -> 2 * Gmod * dev(ϵ) + 3 * Kmod * vol(ϵ), zero(SymmetricTensor{2, 2}));
Plane stress instead of plane strain?

In order to change this tutorial to consider plane stress instead of plane strain, the elastic stiffness tensor should be changed to reflect this. The plane stress elasticity stiffness matrix in Voigt notation for engineering shear strains, is given as

\[\underline{\underline{\boldsymbol{E}}} = \frac{E}{1 - \nu^2}\begin{bmatrix} +1 & \nu & 0 \\ +\nu & 1 & 0 \\ +0 & 0 & (1 - \nu)/2 +\end{bmatrix}\]

This matrix can be converted into the 4th order elastic stiffness tensor as

C_voigt = Emod * [1.0 ν 0.0; ν 1.0 0.0; 0.0 0.0 (1-ν)/2] / (1 - ν^2)
+C = fromvoigt(SymmetricTensor{4,2}, E_voigt)

Element routine

To calculate the global stiffness matrix, $K_{ij}$, the element routine computes the local stiffness matrix ke for a single element and assembles it into the global matrix. ke is pre-allocated and reused for all elements.

Note that the elastic stiffness tensor $\mathsf{C}$ is constant. Thus is needs to be computed and once and can then be used for all integration points.

function assemble_cell!(ke, cellvalues, C)
+    for q_point in 1:getnquadpoints(cellvalues)
+        # Get the integration weight for the quadrature point
+        dΩ = getdetJdV(cellvalues, q_point)
+        for i in 1:getnbasefunctions(cellvalues)
+            # Gradient of the test function
+            ∇Nᵢ = shape_gradient(cellvalues, q_point, i)
+            for j in 1:getnbasefunctions(cellvalues)
+                # Symmetric gradient of the trial function
+                ∇ˢʸᵐNⱼ = shape_symmetric_gradient(cellvalues, q_point, j)
+                ke[i, j] += (∇Nᵢ ⊡ C ⊡ ∇ˢʸᵐNⱼ) * dΩ
+            end
+        end
+    end
+    return ke
+end

Global assembly

We define the function assemble_global to loop over the elements and do the global assembly. The function takes the preallocated sparse matrix K, our DofHandler dh, our cellvalues and the elastic stiffness tensor C as input arguments and computes the global stiffness matrix K.

function assemble_global!(K, dh, cellvalues, C)
+    # Allocate the element stiffness matrix
+    n_basefuncs = getnbasefunctions(cellvalues)
+    ke = zeros(n_basefuncs, n_basefuncs)
+    # Create an assembler
+    assembler = start_assemble(K)
+    # Loop over all cells
+    for cell in CellIterator(dh)
+        # Update the shape function gradients based on the cell coordinates
+        reinit!(cellvalues, cell)
+        # Reset the element stiffness matrix
+        fill!(ke, 0.0)
+        # Compute element contribution
+        assemble_cell!(ke, cellvalues, C)
+        # Assemble ke into K
+        assemble!(assembler, celldofs(cell), ke)
+    end
+    return K
+end

Solution of the system

The last step is to solve the system. First we allocate the global stiffness matrix K and assemble it.

K = allocate_matrix(dh)
+assemble_global!(K, dh, cellvalues, C);

Then we allocate and assemble the external force vector.

f_ext = zeros(ndofs(dh))
+assemble_external_forces!(f_ext, dh, getfacetset(grid, "top"), facetvalues, traction);

To account for the Dirichlet boundary conditions we use the apply! function. This modifies elements in K and f, such that we can get the correct solution vector u by using solving the linear equation system $K_{ij} \hat{u}_j = f^\mathrm{ext}_i$,

apply!(K, f_ext, ch)
+u = K \ f_ext;

Postprocessing

In this case, we want to analyze the displacements, as well as the stress field. We calculate the stress in each quadrature point, and then export it in two different ways:

  1. Constant in each cell (matching the approximation of constant strains in each element). Note that a current limitation is that cell data for second order tensors must be exported component-wise (see issue #768)
  2. Interpolated using the linear lagrange ansatz functions via the L2Projector.
function calculate_stresses(grid, dh, cv, u, C)
+    qp_stresses = [
+        [zero(SymmetricTensor{2, 2}) for _ in 1:getnquadpoints(cv)]
+            for _ in 1:getncells(grid)
+    ]
+    avg_cell_stresses = tuple((zeros(getncells(grid)) for _ in 1:3)...)
+    for cell in CellIterator(dh)
+        reinit!(cv, cell)
+        cell_stresses = qp_stresses[cellid(cell)]
+        for q_point in 1:getnquadpoints(cv)
+            ε = function_symmetric_gradient(cv, q_point, u, celldofs(cell))
+            cell_stresses[q_point] = C ⊡ ε
+        end
+        σ_avg = sum(cell_stresses) / getnquadpoints(cv)
+        avg_cell_stresses[1][cellid(cell)] = σ_avg[1, 1]
+        avg_cell_stresses[2][cellid(cell)] = σ_avg[2, 2]
+        avg_cell_stresses[3][cellid(cell)] = σ_avg[1, 2]
+    end
+    return qp_stresses, avg_cell_stresses
+end
+
+qp_stresses, avg_cell_stresses = calculate_stresses(grid, dh, cellvalues, u, C);

We now use the the L2Projector to project the stress-field onto the piecewise linear finite element space that we used to solve the problem.

proj = L2Projector(Lagrange{RefTriangle, 1}(), grid)
+stress_field = project(proj, qp_stresses, qr);

To visualize the result we export to a VTK-file. Specifically, an unstructured grid file, .vtu, is created, which can be viewed in e.g. ParaView.

VTKGridFile("linear_elasticity", dh) do vtk
+    write_solution(vtk, dh, u)
+    for (i, key) in enumerate(("11", "22", "12"))
+        write_cell_data(vtk, avg_cell_stresses[i], "sigma_" * key)
+    end
+    write_projection(vtk, proj, stress_field, "stress field")
+    Ferrite.write_cellset(vtk, grid)
+end
VTKGridFile for the closed file "linear_elasticity.vtu".

We used the displacement field to visualize the deformed logo in Figure 1, and in Figure 2, we demonstrate the difference between the interpolated stress field and the constant stress in each cell.

Figure 2: Vertical normal stresses (MPa) exported using the L2Projector (left) and constant stress in each cell (right).

Plain program

Here follows a version of the program without any comments. The file is also available here: linear_elasticity.jl.

using Ferrite, FerriteGmsh, SparseArrays
+
+using Downloads: download
+logo_mesh = "logo.geo"
+asset_url = "https://raw.githubusercontent.com/Ferrite-FEM/Ferrite.jl/gh-pages/assets/"
+isfile(logo_mesh) || download(string(asset_url, logo_mesh), logo_mesh)
+
+grid = togrid(logo_mesh);
+
+addfacetset!(grid, "top", x -> x[2] ≈ 1.0) # facets for which x[2] ≈ 1.0 for all nodes
+addfacetset!(grid, "left", x -> abs(x[1]) < 1.0e-6)
+addfacetset!(grid, "bottom", x -> abs(x[2]) < 1.0e-6);
+
+dim = 2
+order = 1 # linear interpolation
+ip = Lagrange{RefTriangle, order}()^dim; # vector valued interpolation
+
+qr = QuadratureRule{RefTriangle}(1) # 1 quadrature point
+qr_face = FacetQuadratureRule{RefTriangle}(1);
+
+cellvalues = CellValues(qr, ip)
+facetvalues = FacetValues(qr_face, ip);
+
+dh = DofHandler(grid)
+add!(dh, :u, ip)
+close!(dh);
+
+ch = ConstraintHandler(dh)
+add!(ch, Dirichlet(:u, getfacetset(grid, "bottom"), (x, t) -> 0.0, 2))
+add!(ch, Dirichlet(:u, getfacetset(grid, "left"), (x, t) -> 0.0, 1))
+close!(ch);
+
+traction(x) = Vec(0.0, 20.0e3 * x[1]);
+
+function assemble_external_forces!(f_ext, dh, facetset, facetvalues, prescribed_traction)
+    # Create a temporary array for the facet's local contributions to the external force vector
+    fe_ext = zeros(getnbasefunctions(facetvalues))
+    for facet in FacetIterator(dh, facetset)
+        # Update the facetvalues to the correct facet number
+        reinit!(facetvalues, facet)
+        # Reset the temporary array for the next facet
+        fill!(fe_ext, 0.0)
+        # Access the cell's coordinates
+        cell_coordinates = getcoordinates(facet)
+        for qp in 1:getnquadpoints(facetvalues)
+            # Calculate the global coordinate of the quadrature point.
+            x = spatial_coordinate(facetvalues, qp, cell_coordinates)
+            tₚ = prescribed_traction(x)
+            # Get the integration weight for the current quadrature point.
+            dΓ = getdetJdV(facetvalues, qp)
+            for i in 1:getnbasefunctions(facetvalues)
+                Nᵢ = shape_value(facetvalues, qp, i)
+                fe_ext[i] += tₚ ⋅ Nᵢ * dΓ
+            end
+        end
+        # Add the local contributions to the correct indices in the global external force vector
+        assemble!(f_ext, celldofs(facet), fe_ext)
+    end
+    return f_ext
+end
+
+Emod = 200.0e3 # Young's modulus [MPa]
+ν = 0.3        # Poisson's ratio [-]
+
+Gmod = Emod / (2(1 + ν))  # Shear modulus
+Kmod = Emod / (3(1 - 2ν)) # Bulk modulus
+
+C = gradient(ϵ -> 2 * Gmod * dev(ϵ) + 3 * Kmod * vol(ϵ), zero(SymmetricTensor{2, 2}));
+
+function assemble_cell!(ke, cellvalues, C)
+    for q_point in 1:getnquadpoints(cellvalues)
+        # Get the integration weight for the quadrature point
+        dΩ = getdetJdV(cellvalues, q_point)
+        for i in 1:getnbasefunctions(cellvalues)
+            # Gradient of the test function
+            ∇Nᵢ = shape_gradient(cellvalues, q_point, i)
+            for j in 1:getnbasefunctions(cellvalues)
+                # Symmetric gradient of the trial function
+                ∇ˢʸᵐNⱼ = shape_symmetric_gradient(cellvalues, q_point, j)
+                ke[i, j] += (∇Nᵢ ⊡ C ⊡ ∇ˢʸᵐNⱼ) * dΩ
+            end
+        end
+    end
+    return ke
+end
+
+function assemble_global!(K, dh, cellvalues, C)
+    # Allocate the element stiffness matrix
+    n_basefuncs = getnbasefunctions(cellvalues)
+    ke = zeros(n_basefuncs, n_basefuncs)
+    # Create an assembler
+    assembler = start_assemble(K)
+    # Loop over all cells
+    for cell in CellIterator(dh)
+        # Update the shape function gradients based on the cell coordinates
+        reinit!(cellvalues, cell)
+        # Reset the element stiffness matrix
+        fill!(ke, 0.0)
+        # Compute element contribution
+        assemble_cell!(ke, cellvalues, C)
+        # Assemble ke into K
+        assemble!(assembler, celldofs(cell), ke)
+    end
+    return K
+end
+
+K = allocate_matrix(dh)
+assemble_global!(K, dh, cellvalues, C);
+
+f_ext = zeros(ndofs(dh))
+assemble_external_forces!(f_ext, dh, getfacetset(grid, "top"), facetvalues, traction);
+
+apply!(K, f_ext, ch)
+u = K \ f_ext;
+
+function calculate_stresses(grid, dh, cv, u, C)
+    qp_stresses = [
+        [zero(SymmetricTensor{2, 2}) for _ in 1:getnquadpoints(cv)]
+            for _ in 1:getncells(grid)
+    ]
+    avg_cell_stresses = tuple((zeros(getncells(grid)) for _ in 1:3)...)
+    for cell in CellIterator(dh)
+        reinit!(cv, cell)
+        cell_stresses = qp_stresses[cellid(cell)]
+        for q_point in 1:getnquadpoints(cv)
+            ε = function_symmetric_gradient(cv, q_point, u, celldofs(cell))
+            cell_stresses[q_point] = C ⊡ ε
+        end
+        σ_avg = sum(cell_stresses) / getnquadpoints(cv)
+        avg_cell_stresses[1][cellid(cell)] = σ_avg[1, 1]
+        avg_cell_stresses[2][cellid(cell)] = σ_avg[2, 2]
+        avg_cell_stresses[3][cellid(cell)] = σ_avg[1, 2]
+    end
+    return qp_stresses, avg_cell_stresses
+end
+
+qp_stresses, avg_cell_stresses = calculate_stresses(grid, dh, cellvalues, u, C);
+
+proj = L2Projector(Lagrange{RefTriangle, 1}(), grid)
+stress_field = project(proj, qp_stresses, qr);
+
+
+VTKGridFile("linear_elasticity", dh) do vtk
+    write_solution(vtk, dh, u)
+    for (i, key) in enumerate(("11", "22", "12"))
+        write_cell_data(vtk, avg_cell_stresses[i], "sigma_" * key)
+    end
+    write_projection(vtk, proj, stress_field, "stress field")
+    Ferrite.write_cellset(vtk, grid)
+end

This page was generated using Literate.jl.

diff --git a/previews/PR798/tutorials/linear_elasticity_stress.png b/previews/PR798/tutorials/linear_elasticity_stress.png new file mode 100644 index 0000000000..74736aa321 Binary files /dev/null and b/previews/PR798/tutorials/linear_elasticity_stress.png differ diff --git a/previews/PR798/tutorials/linear_shell.ipynb b/previews/PR798/tutorials/linear_shell.ipynb new file mode 100644 index 0000000000..f915788cbf --- /dev/null +++ b/previews/PR798/tutorials/linear_shell.ipynb @@ -0,0 +1,441 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "runic: off\n", + "# Linear shell\n", + "\n", + "![](linear_shell.png)" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Introduction\n", + "\n", + "In this example we show how shell elements can be analyzed with Ferrite. The shell implemented here comes from the book\n", + "\"The finite element method - Linear static and dynamic finite element analysis\" by Hughes (1987), and a brief description of it is\n", + "given at the end of this tutorial. The first part of the tutorial explains how to set up the problem." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Setting up the problem" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using Ferrite\n", + "using ForwardDiff\n", + "\n", + "function main() #wrap everything in a function...\n", + "# First we generate a flat rectangular mesh. There is currently no built-in function for generating\n", + "# shell meshes in Ferrite, so we have to create our own simple mesh generator (see the\n", + "# function `generate_shell_grid` further down in this file).\n", + "nels = (10,10)\n", + "size = (10.0, 10.0)\n", + "grid = generate_shell_grid(nels, size)\n", + "# Here we define the bi-linear interpolation used for the geometrical description of the shell.\n", + "# We also create two quadrature rules for the in-plane and out-of-plane directions. Note that we use\n", + "# under integration for the inplane integration, to avoid shear locking.\n", + "ip = Lagrange{RefQuadrilateral,1}()\n", + "qr_inplane = QuadratureRule{RefQuadrilateral}(1)\n", + "qr_ooplane = QuadratureRule{RefLine}(2)\n", + "cv = CellValues(qr_inplane, ip, ip^3)\n", + "# Next we distribute displacement dofs,`:u = (x,y,z)` and rotational dofs, `:θ = (θ₁, θ₂)`.\n", + "dh = DofHandler(grid)\n", + "add!(dh, :u, ip^3)\n", + "add!(dh, :θ, ip^2)\n", + "close!(dh)\n", + "# In order to apply our boundary conditions, we first need to create some facet- and vertex-sets. This\n", + "# is done with `addfacetset!` and `addvertexset!`\n", + "addfacetset!(grid, \"left\", (x) -> x[1] ≈ 0.0)\n", + "addfacetset!(grid, \"right\", (x) -> x[1] ≈ size[1])\n", + "addvertexset!(grid, \"corner\", (x) -> x[1] ≈ 0.0 && x[2] ≈ 0.0 && x[3] ≈ 0.0)\n", + "# Here we define the boundary conditions. On the left edge, we lock the displacements in the x- and z- directions, and all the rotations.\n", + "ch = ConstraintHandler(dh)\n", + "add!(ch, Dirichlet(:u, getfacetset(grid, \"left\"), (x, t) -> (0.0, 0.0), [1,3]) )\n", + "add!(ch, Dirichlet(:θ, getfacetset(grid, \"left\"), (x, t) -> (0.0, 0.0), [1,2]) )\n", + "# On the right edge, we also lock the displacements in the x- and z- directions, but apply a precribed rotation.\n", + "add!(ch, Dirichlet(:u, getfacetset(grid, \"right\"), (x, t) -> (0.0, 0.0), [1,3]) )\n", + "add!(ch, Dirichlet(:θ, getfacetset(grid, \"right\"), (x, t) -> (0.0, pi/10), [1,2]) )\n", + "# In order to not get rigid body motion, we lock the y-displacement in one of the corners.\n", + "add!(ch, Dirichlet(:θ, getvertexset(grid, \"corner\"), (x, t) -> (0.0), [2]) )\n", + "\n", + "close!(ch)\n", + "update!(ch, 0.0)\n", + "# Next we define relevant data for the shell, such as shear correction factor and stiffness matrix for the material.\n", + "# In this linear shell, plane stress is assumed, ie $\\\\sigma_{zz} = 0$. Therefor, the stiffness matrix is 5x5 (opposed to the normal 6x6).\n", + "κ = 5/6 # Shear correction factor\n", + "E = 210.0\n", + "ν = 0.3\n", + "a = (1-ν)/2\n", + "C = E/(1-ν^2) * [1 ν 0 0 0;\n", + " ν 1 0 0 0;\n", + " 0 0 a*κ 0 0;\n", + " 0 0 0 a*κ 0;\n", + " 0 0 0 0 a*κ]\n", + "\n", + "\n", + "data = (thickness = 1.0, C = C); #Named tuple\n", + "# We now assemble the problem in standard finite element fashion\n", + "nnodes = getnbasefunctions(ip)\n", + "ndofs_shell = ndofs_per_cell(dh)\n", + "\n", + "K = allocate_matrix(dh)\n", + "f = zeros(Float64, ndofs(dh))\n", + "\n", + "ke = zeros(ndofs_shell, ndofs_shell)\n", + "fe = zeros(ndofs_shell)\n", + "\n", + "celldofs = zeros(Int, ndofs_shell)\n", + "cellcoords = zeros(Vec{3,Float64}, nnodes)\n", + "\n", + "assembler = start_assemble(K, f)\n", + "for cell in CellIterator(grid)\n", + " fill!(ke, 0.0)\n", + " reinit!(cv, cell)\n", + " celldofs!(celldofs, dh, cellid(cell))\n", + " getcoordinates!(cellcoords, grid, cellid(cell))\n", + "\n", + " #Call the element routine\n", + " integrate_shell!(ke, cv, qr_ooplane, cellcoords, data)\n", + "\n", + " assemble!(assembler, celldofs, ke, fe)\n", + "end\n", + "# Apply BC and solve.\n", + "apply!(K, f, ch)\n", + "a = K\\f\n", + "# Output results.\n", + "VTKGridFile(\"linear_shell\", dh) do vtk\n", + " write_solution(vtk, dh, a)\n", + "end\n", + "\n", + "end; #end main functions" + ], + "metadata": {}, + "execution_count": 1 + }, + { + "cell_type": "markdown", + "source": [ + "Below is the function that creates the shell mesh. It simply generates a 2d-quadrature mesh, and appends\n", + "a third coordinate (z-direction) to the node-positions." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function generate_shell_grid(nels, size)\n", + " _grid = generate_grid(Quadrilateral, nels, Vec((0.0,0.0)), Vec(size))\n", + " nodes = [(n.x[1], n.x[2], 0.0) |> Vec{3} |> Node for n in _grid.nodes]\n", + "\n", + " grid = Grid(_grid.cells, nodes)\n", + "\n", + " return grid\n", + "end;" + ], + "metadata": {}, + "execution_count": 2 + }, + { + "cell_type": "markdown", + "source": [ + "## The shell element\n", + "\n", + "The shell presented here comes from the book \"The finite element method - Linear static and dynamic finite element analysis\" by Hughes (1987).\n", + "The shell is a so called degenerate shell element, meaning it is based on a continuum element.\n", + "A brief description of the shell is given here." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "##### Fiber coordinate system\n", + "The element uses two coordinate systems. The first coordianate system, called the fiber system, is created for each\n", + "element node, and is used as a reference frame for the rotations. The function below implements an algorithm that return the\n", + "fiber directions, $\\boldsymbol{e}^{f}_{a1}$, $\\boldsymbol{e}^{f}_{a2}$ and $\\boldsymbol{e}^{f}_{a3}$, at each node $a$." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function fiber_coordsys(Ps::Vector{Vec{3,Float64}})\n", + "\n", + " ef1 = Vec{3,Float64}[]\n", + " ef2 = Vec{3,Float64}[]\n", + " ef3 = Vec{3,Float64}[]\n", + " for P in Ps\n", + " a = abs.(P)\n", + " j = 1\n", + " if a[1] > a[3]; a[3] = a[1]; j = 2; end\n", + " if a[2] > a[3]; j = 3; end\n", + "\n", + " e3 = P\n", + " e2 = Tensors.cross(P, basevec(Vec{3}, j))\n", + " e2 /= norm(e2)\n", + " e1 = Tensors.cross(e2, P)\n", + "\n", + " push!(ef1, e1)\n", + " push!(ef2, e2)\n", + " push!(ef3, e3)\n", + " end\n", + " return ef1, ef2, ef3\n", + "\n", + "end;" + ], + "metadata": {}, + "execution_count": 3 + }, + { + "cell_type": "markdown", + "source": [ + "##### Lamina coordinate system\n", + "The second coordinate system is the so called Lamina Coordinate system. It is\n", + "created for each integration point, and is defined to be tangent to the\n", + "mid-surface. It is in this system that we enforce that plane stress assumption,\n", + "i.e. $\\sigma_{zz} = 0$. The function below returns the rotation matrix, $\\boldsymbol{q}$, for this coordinate system." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function lamina_coordsys(dNdξ, ζ, x, p, h)\n", + "\n", + " e1 = zero(Vec{3})\n", + " e2 = zero(Vec{3})\n", + "\n", + " for i in 1:length(dNdξ)\n", + " e1 += dNdξ[i][1] * x[i] + 0.5*h*ζ * dNdξ[i][1] * p[i]\n", + " e2 += dNdξ[i][2] * x[i] + 0.5*h*ζ * dNdξ[i][1] * p[i]\n", + " end\n", + "\n", + " e1 /= norm(e1)\n", + " e2 /= norm(e2)\n", + "\n", + " ez = Tensors.cross(e1,e2)\n", + " ez /= norm(ez)\n", + "\n", + " a = 0.5*(e1 + e2)\n", + " a /= norm(a)\n", + "\n", + " b = Tensors.cross(ez,a)\n", + " b /= norm(b)\n", + "\n", + " ex = sqrt(2)/2 * (a - b)\n", + " ey = sqrt(2)/2 * (a + b)\n", + "\n", + " return Tensor{2,3}(hcat(ex,ey,ez))\n", + "end;" + ], + "metadata": {}, + "execution_count": 4 + }, + { + "cell_type": "markdown", + "source": [ + "##### Geometrical description\n", + "A material point in the shell is defined as\n", + "$$\n", + "\\boldsymbol x(\\xi, \\eta, \\zeta) = \\sum_{a=1}^{N_{\\text{nodes}}} N_a(\\xi, \\eta) \\boldsymbol{\\bar{x}}_{a} + ζ \\frac{h}{2} \\boldsymbol{\\bar{p}_a}\n", + "$$\n", + "where $\\boldsymbol{\\bar{x}}_{a}$ are nodal positions on the mid-surface, and $\\boldsymbol{\\bar{p}_a}$ is an vector that defines the fiber direction\n", + "on the reference surface. $N_a$ arethe shape functions.\n", + "\n", + "Based on the definition of the position vector, we create an function for obtaining the Jacobian-matrix,\n", + "$$\n", + "J_{ij} = \\frac{\\partial x_i}{\\partial \\xi_j},\n", + "$$" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function getjacobian(q, N, dNdξ, ζ, X, p, h)\n", + " J = zeros(3,3)\n", + " for a in 1:length(N)\n", + " for i in 1:3, j in 1:3\n", + " _dNdξ = (j==3) ? 0.0 : dNdξ[a][j]\n", + " _dζdξ = (j==3) ? 1.0 : 0.0\n", + " _N = N[a]\n", + "\n", + " J[i,j] += _dNdξ * X[a][i] + (_dNdξ*ζ + _N*_dζdξ) * h/2 * p[a][i]\n", + " end\n", + " end\n", + "\n", + " return (q' * J) |> Tensor{2,3,Float64}\n", + "end;" + ], + "metadata": {}, + "execution_count": 5 + }, + { + "cell_type": "markdown", + "source": [ + "##### Strains\n", + "Small deformation is assumed,\n", + "$$\n", + "\\varepsilon_{ij}= \\frac{1}{2}(\\frac{\\partial u_{i}}{\\partial x_j} + \\frac{\\partial u_{j}}{\\partial x_i})\n", + "$$\n", + "The displacement field is calculated as:\n", + "$$\n", + "\\boldsymbol u = \\sum_{a=1}^{N_{\\text{nodes}}} N_a \\bar{\\boldsymbol u}_{a} +\n", + " N_a ζ\\frac{h}{2}(\\theta_{a2} \\boldsymbol e^{f}_{a1} - \\theta_{a1} \\boldsymbol e^{f}_{a2})\n", + "\n", + "$$\n", + "The gradient of the displacement (in the lamina coordinate system), then becomes:\n", + "$$\n", + "\\frac{\\partial u_{i}}{\\partial x_j} = \\sum_{m=1}^3 q_{im} \\sum_{a=1}^{N_{\\text{nodes}}} \\frac{\\partial N_a}{\\partial x_j} \\bar{u}_{am} +\n", + " \\frac{\\partial(N_a ζ)}{\\partial x_j} \\frac{h}{2} (\\theta_{a2} e^{f}_{am1} - \\theta_{a1} e^{f}_{am2})\n", + "$$" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function strain(dofvec::Vector{T}, N, dNdx, ζ, dζdx, q, ef1, ef2, h) where T\n", + "\n", + " u = reinterpret(Vec{3,T}, dofvec[1:12])\n", + " θ = reinterpret(Vec{2,T}, dofvec[13:20])\n", + "\n", + " dudx = zeros(T, 3, 3)\n", + " for m in 1:3, j in 1:3\n", + " for a in 1:length(N)\n", + " dudx[m,j] += dNdx[a][j] * u[a][m] + h/2 * (dNdx[a][j]*ζ + N[a]*dζdx[j]) * (θ[a][2]*ef1[a][m] - θ[a][1]*ef2[a][m])\n", + " end\n", + " end\n", + "\n", + " dudx = q*dudx\n", + " ε = [dudx[1,1], dudx[2,2], dudx[1,2]+dudx[2,1], dudx[2,3]+dudx[3,2], dudx[1,3]+dudx[3,1]]\n", + " return ε\n", + "end;" + ], + "metadata": {}, + "execution_count": 6 + }, + { + "cell_type": "markdown", + "source": [ + "##### Main element routine\n", + "Below is the main routine that calculates the stiffness matrix of the shell element.\n", + "Since it is a so called degenerate shell element, the code is similar to that for an standard continuum element." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "shape_reference_gradient(cv::CellValues, q_point, i) = cv.fun_values.dNdξ[i, q_point]\n", + "\n", + "function integrate_shell!(ke, cv, qr_ooplane, X, data)\n", + " nnodes = getnbasefunctions(cv)\n", + " ndofs = nnodes*5\n", + " h = data.thickness\n", + "\n", + " #Create the directors in each node.\n", + " #Note: For a more general case, the directors should\n", + " #be input parameters for the element routine.\n", + " p = zeros(Vec{3}, nnodes)\n", + " for i in 1:nnodes\n", + " a = Vec{3}((0.0, 0.0, 1.0))\n", + " p[i] = a/norm(a)\n", + " end\n", + "\n", + " ef1, ef2, ef3 = fiber_coordsys(p)\n", + "\n", + " for iqp in 1:getnquadpoints(cv)\n", + " N = [shape_value(cv, iqp, i) for i in 1:nnodes]\n", + " dNdξ = [shape_reference_gradient(cv, iqp, i) for i in 1:nnodes]\n", + " dNdx = [shape_gradient(cv, iqp, i) for i in 1:nnodes]\n", + "\n", + " for oqp in 1:length(qr_ooplane.weights)\n", + " ζ = qr_ooplane.points[oqp][1]\n", + " q = lamina_coordsys(dNdξ, ζ, X, p, h)\n", + "\n", + " J = getjacobian(q, N, dNdξ, ζ, X, p, h)\n", + " Jinv = inv(J)\n", + " dζdx = Vec{3}((0.0, 0.0, 1.0)) ⋅ Jinv\n", + "\n", + " #For simplicity, use automatic differentiation to construct the B-matrix from the strain.\n", + " B = ForwardDiff.jacobian(\n", + " (a) -> strain(a, N, dNdx, ζ, dζdx, q, ef1, ef2, h), zeros(Float64, ndofs) )\n", + "\n", + " dV = qr_ooplane.weights[oqp] * getdetJdV(cv, iqp)\n", + " ke .+= B'*data.C*B * dV\n", + " end\n", + " end\n", + "end;" + ], + "metadata": {}, + "execution_count": 7 + }, + { + "cell_type": "markdown", + "source": [ + "Run everything:" + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "VTKGridFile for the closed file \"linear_shell.vtu\"." + }, + "metadata": {}, + "execution_count": 8 + } + ], + "cell_type": "code", + "source": [ + "main()" + ], + "metadata": {}, + "execution_count": 8 + }, + { + "cell_type": "markdown", + "source": [ + "---\n", + "\n", + "*This notebook was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).*" + ], + "metadata": {} + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.1" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.1", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR798/tutorials/linear_shell.jl b/previews/PR798/tutorials/linear_shell.jl new file mode 100644 index 0000000000..14070a205c --- /dev/null +++ b/previews/PR798/tutorials/linear_shell.jl @@ -0,0 +1,219 @@ +using Ferrite +using ForwardDiff + +function main() #wrap everything in a function... + +nels = (10,10) +size = (10.0, 10.0) +grid = generate_shell_grid(nels, size) + +ip = Lagrange{RefQuadrilateral,1}() +qr_inplane = QuadratureRule{RefQuadrilateral}(1) +qr_ooplane = QuadratureRule{RefLine}(2) +cv = CellValues(qr_inplane, ip, ip^3) + +dh = DofHandler(grid) +add!(dh, :u, ip^3) +add!(dh, :θ, ip^2) +close!(dh) + +addfacetset!(grid, "left", (x) -> x[1] ≈ 0.0) +addfacetset!(grid, "right", (x) -> x[1] ≈ size[1]) +addvertexset!(grid, "corner", (x) -> x[1] ≈ 0.0 && x[2] ≈ 0.0 && x[3] ≈ 0.0) + +ch = ConstraintHandler(dh) +add!(ch, Dirichlet(:u, getfacetset(grid, "left"), (x, t) -> (0.0, 0.0), [1,3]) ) +add!(ch, Dirichlet(:θ, getfacetset(grid, "left"), (x, t) -> (0.0, 0.0), [1,2]) ) + +add!(ch, Dirichlet(:u, getfacetset(grid, "right"), (x, t) -> (0.0, 0.0), [1,3]) ) +add!(ch, Dirichlet(:θ, getfacetset(grid, "right"), (x, t) -> (0.0, pi/10), [1,2]) ) + +add!(ch, Dirichlet(:θ, getvertexset(grid, "corner"), (x, t) -> (0.0), [2]) ) + +close!(ch) +update!(ch, 0.0) + +κ = 5/6 # Shear correction factor +E = 210.0 +ν = 0.3 +a = (1-ν)/2 +C = E/(1-ν^2) * [1 ν 0 0 0; + ν 1 0 0 0; + 0 0 a*κ 0 0; + 0 0 0 a*κ 0; + 0 0 0 0 a*κ] + + +data = (thickness = 1.0, C = C); #Named tuple + +nnodes = getnbasefunctions(ip) +ndofs_shell = ndofs_per_cell(dh) + +K = allocate_matrix(dh) +f = zeros(Float64, ndofs(dh)) + +ke = zeros(ndofs_shell, ndofs_shell) +fe = zeros(ndofs_shell) + +celldofs = zeros(Int, ndofs_shell) +cellcoords = zeros(Vec{3,Float64}, nnodes) + +assembler = start_assemble(K, f) +for cell in CellIterator(grid) + fill!(ke, 0.0) + reinit!(cv, cell) + celldofs!(celldofs, dh, cellid(cell)) + getcoordinates!(cellcoords, grid, cellid(cell)) + + #Call the element routine + integrate_shell!(ke, cv, qr_ooplane, cellcoords, data) + + assemble!(assembler, celldofs, ke, fe) +end + +apply!(K, f, ch) +a = K\f + +VTKGridFile("linear_shell", dh) do vtk + write_solution(vtk, dh, a) +end + +end; #end main functions + +function generate_shell_grid(nels, size) + _grid = generate_grid(Quadrilateral, nels, Vec((0.0,0.0)), Vec(size)) + nodes = [(n.x[1], n.x[2], 0.0) |> Vec{3} |> Node for n in _grid.nodes] + + grid = Grid(_grid.cells, nodes) + + return grid +end; + +function fiber_coordsys(Ps::Vector{Vec{3,Float64}}) + + ef1 = Vec{3,Float64}[] + ef2 = Vec{3,Float64}[] + ef3 = Vec{3,Float64}[] + for P in Ps + a = abs.(P) + j = 1 + if a[1] > a[3]; a[3] = a[1]; j = 2; end + if a[2] > a[3]; j = 3; end + + e3 = P + e2 = Tensors.cross(P, basevec(Vec{3}, j)) + e2 /= norm(e2) + e1 = Tensors.cross(e2, P) + + push!(ef1, e1) + push!(ef2, e2) + push!(ef3, e3) + end + return ef1, ef2, ef3 + +end; + +function lamina_coordsys(dNdξ, ζ, x, p, h) + + e1 = zero(Vec{3}) + e2 = zero(Vec{3}) + + for i in 1:length(dNdξ) + e1 += dNdξ[i][1] * x[i] + 0.5*h*ζ * dNdξ[i][1] * p[i] + e2 += dNdξ[i][2] * x[i] + 0.5*h*ζ * dNdξ[i][1] * p[i] + end + + e1 /= norm(e1) + e2 /= norm(e2) + + ez = Tensors.cross(e1,e2) + ez /= norm(ez) + + a = 0.5*(e1 + e2) + a /= norm(a) + + b = Tensors.cross(ez,a) + b /= norm(b) + + ex = sqrt(2)/2 * (a - b) + ey = sqrt(2)/2 * (a + b) + + return Tensor{2,3}(hcat(ex,ey,ez)) +end; + +function getjacobian(q, N, dNdξ, ζ, X, p, h) + J = zeros(3,3) + for a in 1:length(N) + for i in 1:3, j in 1:3 + _dNdξ = (j==3) ? 0.0 : dNdξ[a][j] + _dζdξ = (j==3) ? 1.0 : 0.0 + _N = N[a] + + J[i,j] += _dNdξ * X[a][i] + (_dNdξ*ζ + _N*_dζdξ) * h/2 * p[a][i] + end + end + + return (q' * J) |> Tensor{2,3,Float64} +end; + +function strain(dofvec::Vector{T}, N, dNdx, ζ, dζdx, q, ef1, ef2, h) where T + + u = reinterpret(Vec{3,T}, dofvec[1:12]) + θ = reinterpret(Vec{2,T}, dofvec[13:20]) + + dudx = zeros(T, 3, 3) + for m in 1:3, j in 1:3 + for a in 1:length(N) + dudx[m,j] += dNdx[a][j] * u[a][m] + h/2 * (dNdx[a][j]*ζ + N[a]*dζdx[j]) * (θ[a][2]*ef1[a][m] - θ[a][1]*ef2[a][m]) + end + end + + dudx = q*dudx + ε = [dudx[1,1], dudx[2,2], dudx[1,2]+dudx[2,1], dudx[2,3]+dudx[3,2], dudx[1,3]+dudx[3,1]] + return ε +end; + +shape_reference_gradient(cv::CellValues, q_point, i) = cv.fun_values.dNdξ[i, q_point] + +function integrate_shell!(ke, cv, qr_ooplane, X, data) + nnodes = getnbasefunctions(cv) + ndofs = nnodes*5 + h = data.thickness + + #Create the directors in each node. + #Note: For a more general case, the directors should + #be input parameters for the element routine. + p = zeros(Vec{3}, nnodes) + for i in 1:nnodes + a = Vec{3}((0.0, 0.0, 1.0)) + p[i] = a/norm(a) + end + + ef1, ef2, ef3 = fiber_coordsys(p) + + for iqp in 1:getnquadpoints(cv) + N = [shape_value(cv, iqp, i) for i in 1:nnodes] + dNdξ = [shape_reference_gradient(cv, iqp, i) for i in 1:nnodes] + dNdx = [shape_gradient(cv, iqp, i) for i in 1:nnodes] + + for oqp in 1:length(qr_ooplane.weights) + ζ = qr_ooplane.points[oqp][1] + q = lamina_coordsys(dNdξ, ζ, X, p, h) + + J = getjacobian(q, N, dNdξ, ζ, X, p, h) + Jinv = inv(J) + dζdx = Vec{3}((0.0, 0.0, 1.0)) ⋅ Jinv + + #For simplicity, use automatic differentiation to construct the B-matrix from the strain. + B = ForwardDiff.jacobian( + (a) -> strain(a, N, dNdx, ζ, dζdx, q, ef1, ef2, h), zeros(Float64, ndofs) ) + + dV = qr_ooplane.weights[oqp] * getdetJdV(cv, iqp) + ke .+= B'*data.C*B * dV + end + end +end; + +main() + +# This file was generated using Literate.jl, https://github.com/fredrikekre/Literate.jl diff --git a/previews/PR798/tutorials/linear_shell.png b/previews/PR798/tutorials/linear_shell.png new file mode 100644 index 0000000000..7b1d812ebb Binary files /dev/null and b/previews/PR798/tutorials/linear_shell.png differ diff --git a/previews/PR798/tutorials/linear_shell/index.html b/previews/PR798/tutorials/linear_shell/index.html new file mode 100644 index 0000000000..7784ad0ccf --- /dev/null +++ b/previews/PR798/tutorials/linear_shell/index.html @@ -0,0 +1,185 @@ + +Linear shell · Ferrite.jl

runic: off

Linear shell

Introduction

In this example we show how shell elements can be analyzed with Ferrite. The shell implemented here comes from the book "The finite element method - Linear static and dynamic finite element analysis" by Hughes (1987), and a brief description of it is given at the end of this tutorial. The first part of the tutorial explains how to set up the problem.

Setting up the problem

using Ferrite
+using ForwardDiff
+
+function main() #wrap everything in a function...

First we generate a flat rectangular mesh. There is currently no built-in function for generating shell meshes in Ferrite, so we have to create our own simple mesh generator (see the function generate_shell_grid further down in this file).

nels = (10,10)
+size = (10.0, 10.0)
+grid = generate_shell_grid(nels, size)

Here we define the bi-linear interpolation used for the geometrical description of the shell. We also create two quadrature rules for the in-plane and out-of-plane directions. Note that we use under integration for the inplane integration, to avoid shear locking.

ip = Lagrange{RefQuadrilateral,1}()
+qr_inplane = QuadratureRule{RefQuadrilateral}(1)
+qr_ooplane = QuadratureRule{RefLine}(2)
+cv = CellValues(qr_inplane, ip, ip^3)

Next we distribute displacement dofs,:u = (x,y,z) and rotational dofs, :θ = (θ₁, θ₂).

dh = DofHandler(grid)
+add!(dh, :u, ip^3)
+add!(dh, :θ, ip^2)
+close!(dh)

In order to apply our boundary conditions, we first need to create some facet- and vertex-sets. This is done with addfacetset! and addvertexset!

addfacetset!(grid, "left",  (x) -> x[1] ≈ 0.0)
+addfacetset!(grid, "right", (x) -> x[1] ≈ size[1])
+addvertexset!(grid, "corner", (x) -> x[1] ≈ 0.0 && x[2] ≈ 0.0 && x[3] ≈ 0.0)

Here we define the boundary conditions. On the left edge, we lock the displacements in the x- and z- directions, and all the rotations.

ch = ConstraintHandler(dh)
+add!(ch,  Dirichlet(:u, getfacetset(grid, "left"), (x, t) -> (0.0, 0.0), [1,3])  )
+add!(ch,  Dirichlet(:θ, getfacetset(grid, "left"), (x, t) -> (0.0, 0.0), [1,2])  )

On the right edge, we also lock the displacements in the x- and z- directions, but apply a precribed rotation.

add!(ch,  Dirichlet(:u, getfacetset(grid, "right"), (x, t) -> (0.0, 0.0), [1,3])  )
+add!(ch,  Dirichlet(:θ, getfacetset(grid, "right"), (x, t) -> (0.0, pi/10), [1,2])  )

In order to not get rigid body motion, we lock the y-displacement in one of the corners.

add!(ch,  Dirichlet(:θ, getvertexset(grid, "corner"), (x, t) -> (0.0), [2])  )
+
+close!(ch)
+update!(ch, 0.0)

Next we define relevant data for the shell, such as shear correction factor and stiffness matrix for the material. In this linear shell, plane stress is assumed, ie $\\sigma_{zz} = 0$. Therefor, the stiffness matrix is 5x5 (opposed to the normal 6x6).

κ = 5/6 # Shear correction factor
+E = 210.0
+ν = 0.3
+a = (1-ν)/2
+C = E/(1-ν^2) * [1 ν 0   0   0;
+                ν 1 0   0   0;
+                0 0 a*κ 0   0;
+                0 0 0   a*κ 0;
+                0 0 0   0   a*κ]
+
+
+data = (thickness = 1.0, C = C); #Named tuple

We now assemble the problem in standard finite element fashion

nnodes = getnbasefunctions(ip)
+ndofs_shell = ndofs_per_cell(dh)
+
+K = allocate_matrix(dh)
+f = zeros(Float64, ndofs(dh))
+
+ke = zeros(ndofs_shell, ndofs_shell)
+fe = zeros(ndofs_shell)
+
+celldofs = zeros(Int, ndofs_shell)
+cellcoords = zeros(Vec{3,Float64}, nnodes)
+
+assembler = start_assemble(K, f)
+for cell in CellIterator(grid)
+    fill!(ke, 0.0)
+    reinit!(cv, cell)
+    celldofs!(celldofs, dh, cellid(cell))
+    getcoordinates!(cellcoords, grid, cellid(cell))
+
+    #Call the element routine
+    integrate_shell!(ke, cv, qr_ooplane, cellcoords, data)
+
+    assemble!(assembler, celldofs, ke, fe)
+end

Apply BC and solve.

apply!(K, f, ch)
+a = K\f

Output results.

VTKGridFile("linear_shell", dh) do vtk
+    write_solution(vtk, dh, a)
+end
+
+end; #end main functions

Below is the function that creates the shell mesh. It simply generates a 2d-quadrature mesh, and appends a third coordinate (z-direction) to the node-positions.

function generate_shell_grid(nels, size)
+    _grid = generate_grid(Quadrilateral, nels, Vec((0.0,0.0)), Vec(size))
+    nodes = [(n.x[1], n.x[2], 0.0) |> Vec{3} |> Node  for n in _grid.nodes]
+
+    grid = Grid(_grid.cells, nodes)
+
+    return grid
+end;

The shell element

The shell presented here comes from the book "The finite element method - Linear static and dynamic finite element analysis" by Hughes (1987). The shell is a so called degenerate shell element, meaning it is based on a continuum element. A brief description of the shell is given here.

Note

This element might experience various locking phenomenas, and should only be seen as a proof of concept.

Fiber coordinate system

The element uses two coordinate systems. The first coordianate system, called the fiber system, is created for each element node, and is used as a reference frame for the rotations. The function below implements an algorithm that return the fiber directions, $\boldsymbol{e}^{f}_{a1}$, $\boldsymbol{e}^{f}_{a2}$ and $\boldsymbol{e}^{f}_{a3}$, at each node $a$.

function fiber_coordsys(Ps::Vector{Vec{3,Float64}})
+
+    ef1 = Vec{3,Float64}[]
+    ef2 = Vec{3,Float64}[]
+    ef3 = Vec{3,Float64}[]
+    for P in Ps
+        a = abs.(P)
+        j = 1
+        if a[1] > a[3]; a[3] = a[1]; j = 2; end
+        if a[2] > a[3]; j = 3; end
+
+        e3 = P
+        e2 = Tensors.cross(P, basevec(Vec{3}, j))
+        e2 /= norm(e2)
+        e1 = Tensors.cross(e2, P)
+
+        push!(ef1, e1)
+        push!(ef2, e2)
+        push!(ef3, e3)
+    end
+    return ef1, ef2, ef3
+
+end;
Lamina coordinate system

The second coordinate system is the so called Lamina Coordinate system. It is created for each integration point, and is defined to be tangent to the mid-surface. It is in this system that we enforce that plane stress assumption, i.e. $\sigma_{zz} = 0$. The function below returns the rotation matrix, $\boldsymbol{q}$, for this coordinate system.

function lamina_coordsys(dNdξ, ζ, x, p, h)
+
+    e1 = zero(Vec{3})
+    e2 = zero(Vec{3})
+
+    for i in 1:length(dNdξ)
+        e1 += dNdξ[i][1] * x[i] + 0.5*h*ζ * dNdξ[i][1] * p[i]
+        e2 += dNdξ[i][2] * x[i] + 0.5*h*ζ * dNdξ[i][1] * p[i]
+    end
+
+    e1 /= norm(e1)
+    e2 /= norm(e2)
+
+    ez = Tensors.cross(e1,e2)
+    ez /= norm(ez)
+
+    a = 0.5*(e1 + e2)
+    a /= norm(a)
+
+    b = Tensors.cross(ez,a)
+    b /= norm(b)
+
+    ex = sqrt(2)/2 * (a - b)
+    ey = sqrt(2)/2 * (a + b)
+
+    return Tensor{2,3}(hcat(ex,ey,ez))
+end;
Geometrical description

A material point in the shell is defined as

\[\boldsymbol x(\xi, \eta, \zeta) = \sum_{a=1}^{N_{\text{nodes}}} N_a(\xi, \eta) \boldsymbol{\bar{x}}_{a} + ζ \frac{h}{2} \boldsymbol{\bar{p}_a}\]

where $\boldsymbol{\bar{x}}_{a}$ are nodal positions on the mid-surface, and $\boldsymbol{\bar{p}_a}$ is an vector that defines the fiber direction on the reference surface. $N_a$ arethe shape functions.

Based on the definition of the position vector, we create an function for obtaining the Jacobian-matrix,

\[J_{ij} = \frac{\partial x_i}{\partial \xi_j},\]

function getjacobian(q, N, dNdξ, ζ, X, p, h)
+    J = zeros(3,3)
+    for a in 1:length(N)
+        for i in 1:3, j in 1:3
+            _dNdξ = (j==3) ? 0.0 : dNdξ[a][j]
+            _dζdξ = (j==3) ? 1.0 : 0.0
+            _N = N[a]
+
+            J[i,j] += _dNdξ * X[a][i]  +  (_dNdξ*ζ + _N*_dζdξ) * h/2 * p[a][i]
+        end
+    end
+
+    return (q' * J) |> Tensor{2,3,Float64}
+end;
Strains

Small deformation is assumed,

\[\varepsilon_{ij}= \frac{1}{2}(\frac{\partial u_{i}}{\partial x_j} + \frac{\partial u_{j}}{\partial x_i})\]

The displacement field is calculated as:

\[\boldsymbol u = \sum_{a=1}^{N_{\text{nodes}}} N_a \bar{\boldsymbol u}_{a} + + N_a ζ\frac{h}{2}(\theta_{a2} \boldsymbol e^{f}_{a1} - \theta_{a1} \boldsymbol e^{f}_{a2}) +\]

The gradient of the displacement (in the lamina coordinate system), then becomes:

\[\frac{\partial u_{i}}{\partial x_j} = \sum_{m=1}^3 q_{im} \sum_{a=1}^{N_{\text{nodes}}} \frac{\partial N_a}{\partial x_j} \bar{u}_{am} + + \frac{\partial(N_a ζ)}{\partial x_j} \frac{h}{2} (\theta_{a2} e^{f}_{am1} - \theta_{a1} e^{f}_{am2})\]

function strain(dofvec::Vector{T}, N, dNdx, ζ, dζdx, q, ef1, ef2, h) where T
+
+    u = reinterpret(Vec{3,T}, dofvec[1:12])
+    θ = reinterpret(Vec{2,T}, dofvec[13:20])
+
+    dudx = zeros(T, 3, 3)
+    for m in 1:3, j in 1:3
+        for a in 1:length(N)
+            dudx[m,j] += dNdx[a][j] * u[a][m] + h/2 * (dNdx[a][j]*ζ + N[a]*dζdx[j]) * (θ[a][2]*ef1[a][m] - θ[a][1]*ef2[a][m])
+        end
+    end
+
+    dudx = q*dudx
+    ε = [dudx[1,1], dudx[2,2], dudx[1,2]+dudx[2,1], dudx[2,3]+dudx[3,2], dudx[1,3]+dudx[3,1]]
+    return ε
+end;
Main element routine

Below is the main routine that calculates the stiffness matrix of the shell element. Since it is a so called degenerate shell element, the code is similar to that for an standard continuum element.

shape_reference_gradient(cv::CellValues, q_point, i) = cv.fun_values.dNdξ[i, q_point]
+
+function integrate_shell!(ke, cv, qr_ooplane, X, data)
+    nnodes = getnbasefunctions(cv)
+    ndofs = nnodes*5
+    h = data.thickness
+
+    #Create the directors in each node.
+    #Note: For a more general case, the directors should
+    #be input parameters for the element routine.
+    p = zeros(Vec{3}, nnodes)
+    for i in 1:nnodes
+        a = Vec{3}((0.0, 0.0, 1.0))
+        p[i] = a/norm(a)
+    end
+
+    ef1, ef2, ef3 = fiber_coordsys(p)
+
+    for iqp in 1:getnquadpoints(cv)
+        N = [shape_value(cv, iqp, i) for i in 1:nnodes]
+        dNdξ = [shape_reference_gradient(cv, iqp, i) for i in 1:nnodes]
+        dNdx = [shape_gradient(cv, iqp, i) for i in 1:nnodes]
+
+        for oqp in 1:length(qr_ooplane.weights)
+            ζ = qr_ooplane.points[oqp][1]
+            q = lamina_coordsys(dNdξ, ζ, X, p, h)
+
+            J = getjacobian(q, N, dNdξ, ζ, X, p, h)
+            Jinv = inv(J)
+            dζdx = Vec{3}((0.0, 0.0, 1.0)) ⋅ Jinv
+
+            #For simplicity, use automatic differentiation to construct the B-matrix from the strain.
+            B = ForwardDiff.jacobian(
+                (a) -> strain(a, N, dNdx, ζ, dζdx, q, ef1, ef2, h), zeros(Float64, ndofs) )
+
+            dV = qr_ooplane.weights[oqp] * getdetJdV(cv, iqp)
+            ke .+= B'*data.C*B * dV
+        end
+    end
+end;

Run everything:

main()
VTKGridFile for the closed file "linear_shell.vtu".

This page was generated using Literate.jl.

diff --git a/previews/PR798/tutorials/logo.geo b/previews/PR798/tutorials/logo.geo new file mode 100644 index 0000000000..22434c9631 --- /dev/null +++ b/previews/PR798/tutorials/logo.geo @@ -0,0 +1,45 @@ +Point (1) = {1.000000000000,1.000000000000,0.000000000000}; +Point (2) = {1.000000000000,0.379757979263,-0.000000000000}; +Point (3) = {0.801534880751,0.454963545532,-0.000000000000}; +Point (4) = {0.656107331955,1.000000000000,-0.000000000000}; +Point (5) = {0.600672553037,0.775245709538,-0.000000000000}; +Point (6) = {0.000000000000,1.000000000000,0.000000000000}; +Point (7) = {0.392825178821,0.672136259831,-0.000000000000}; +Point (8) = {1.000000000000,0.000000000000,0.000000000000}; +Point (9) = {0.547800422194,-0.000000000000,-0.000000000000}; +Point (10) = {0.488710023938,0.224380304618,-0.000000000000}; +Point (11) = {0.000000000000,0.000000000000,0.000000000000}; +Point (12) = {-0.000000000000,0.324566579562,-0.000000000000}; +Point (13) = {0.172066723668,0.367888021869,-0.000000000000}; +Line (1) = {2,1}; +Line (2) = {1,4}; +Line (3) = {2,3}; +Line (4) = {3,5}; +Line (5) = {5,4}; +Line (6) = {4,6}; +Line (7) = {7,6}; +Line (8) = {5,7}; +Line (9) = {8,2}; +Line (10) = {9,8}; +Line (11) = {9,10}; +Line (12) = {10,3}; +Line (13) = {12,11}; +Line (14) = {11,9}; +Line (15) = {12,13}; +Line (16) = {13,10}; +Line (17) = {6,12}; +Line (18) = {7,13}; +Line Loop (1) = {-1,3,4,5,-2}; +Plane Surface (1) = {1}; Physical Surface (1) = {1}; +Line Loop (2) = {7,-6,-5,8}; +Plane Surface (2) = {2}; Physical Surface (2) = {2}; +Line Loop (3) = {-10,11,12,-3,-9}; +Plane Surface (3) = {3}; Physical Surface (3) = {3}; +Line Loop (4) = {-11,-14,-13,15,16}; +Plane Surface (4) = {4}; Physical Surface (4) = {4}; +Line Loop (5) = {-7,18,-15,-17}; +Plane Surface (5) = {5}; Physical Surface (5) = {5}; +Line Loop (6) = {-16,-18,-8,-4,-12}; +Plane Surface (6) = {6}; Physical Surface (6) = {6}; + +ReverseMesh Surface{1,2,3,4,5,6}; diff --git a/previews/PR798/tutorials/ns_vs_diffeq.ipynb b/previews/PR798/tutorials/ns_vs_diffeq.ipynb new file mode 100644 index 0000000000..3928939087 --- /dev/null +++ b/previews/PR798/tutorials/ns_vs_diffeq.ipynb @@ -0,0 +1,976 @@ +{ + "cells": [ + { + "outputs": [], + "cell_type": "code", + "source": [ + "if isdefined(Main, :is_ci) #hide\n", + " IS_CI = Main.is_ci #hide\n", + "else #hide\n", + " IS_CI = false #hide\n", + "end #hide\n", + "nothing #hide" + ], + "metadata": {}, + "execution_count": 1 + }, + { + "cell_type": "markdown", + "source": [ + "# Incompressible Navier-Stokes equations via DifferentialEquations.jl\n", + "\n", + "![nsdiffeq](nsdiffeq.gif)\n", + "\n", + "\n", + "In this example we focus on a simple but visually appealing problem from\n", + "fluid dynamics, namely vortex shedding. This problem is also known as\n", + "[von-Karman vortex streets](https://en.wikipedia.org/wiki/K%C3%A1rm%C3%A1n_vortex_street). Within this example, we show how to utilize [DifferentialEquations.jl](https://github.com/SciML/DifferentialEquations.jl)\n", + "in tandem with Ferrite to solve this space-time problem. To keep things simple we use a naive approach\n", + "to discretize the system.\n", + "\n", + "## Remarks on DifferentialEquations.jl\n", + "\n", + "> **Required Version**\n", + ">\n", + "> This example will only work with OrdinaryDiffEq@v6.80.1. or above\n", + "\n", + "Many \"time step solvers\" of [DifferentialEquations.jl](https://github.com/SciML/DifferentialEquations.jl) assume that that the\n", + "problem is provided in mass matrix form. The incompressible Navier-Stokes\n", + "equations as stated above yield a DAE in this form after applying a spatial\n", + "discretization technique - in our case FEM. The mass matrix form of ODEs and DAEs\n", + "is given as:\n", + "$$\n", + " M(t) \\mathrm{d}_t u = f(u,t)\n", + "$$\n", + "where $M$ is a possibly time-dependent and not necessarily invertible mass matrix,\n", + "$u$ the vector of unknowns and $f$ the right-hand-side (RHS). For us $f$ can be interpreted as\n", + "the spatial discretization of all linear and nonlinear operators depending on $u$ and $t$,\n", + "but not on the time derivative of $u$.\n", + "\n", + "## Some theory on the incompressible Navier-Stokes equations\n", + "\n", + "### Problem description in strong form\n", + "\n", + "The incompressible Navier-Stokes equations can be stated as the system\n", + "$$\n", + " \\begin{aligned}\n", + " \\partial_t v &= \\underbrace{\\nu \\Delta v}_{\\text{viscosity}} - \\underbrace{(v \\cdot \\nabla) v}_{\\text{advection}} - \\underbrace{\\nabla p}_{\\text{pressure}} \\\\\n", + " 0 &= \\underbrace{\\nabla \\cdot v}_{\\text{incompressibility}}\n", + " \\end{aligned}\n", + "$$\n", + "where $v$ is the unknown velocity field, $p$ the unknown pressure field,\n", + "$\\nu$ the dynamic viscosity and $\\Delta$ the Laplacian. In the derivation we assumed\n", + "a constant density of 1 for the fluid and negligible coupling between the velocity components.\n", + "\n", + "Our setup is derived from [Turek's DFG benchmark](http://www.mathematik.tu-dortmund.de/~featflow/en/benchmarks/cfdbenchmarking/flow/dfg_benchmark2_re100.html).\n", + "We model a channel with size $0.41 \\times 1.1$ and a hole of radius $0.05$ centered at $(0.2, 0.2)$.\n", + "The left side has a parabolic inflow profile, which is ramped up over time, modeled as the time dependent\n", + "Dirichlet condition\n", + "$$\n", + " v(x,y,t)\n", + " =\n", + " \\begin{bmatrix}\n", + " 4 v_{in}(t) y (0.41-y)/0.41^2 \\\\\n", + " 0\n", + " \\end{bmatrix}\n", + "$$\n", + "where $v_{in}(t) = \\text{clamp}(t, 0.0, 1.5)$. With a dynamic viscosity of $\\nu = 0.001$\n", + "this is enough to induce turbulence behind the cylinder which leads to vortex shedding. The top and bottom of our\n", + "channel have no-slip conditions, i.e. $v = [0,0]^{\\textrm{T}}$, while the right boundary has the do-nothing boundary condition\n", + "$\\nu \\partial_{\\textrm{n}} v - p n = 0$ to model outflow. With these boundary conditions we can choose the zero solution as a\n", + "feasible initial condition.\n", + "\n", + "### Derivation of Semi-Discrete Weak Form\n", + "\n", + "By multiplying test functions $\\varphi$ and $\\psi$ from a suitable test function space on the strong form,\n", + "followed by integrating over the domain and applying partial integration to the pressure and viscosity terms\n", + "we can obtain the following weak form\n", + "$$\n", + " \\begin{aligned}\n", + " \\int_\\Omega \\partial_t v \\cdot \\varphi &= - \\int_\\Omega \\nu \\nabla v : \\nabla \\varphi - \\int_\\Omega (v \\cdot \\nabla) v \\cdot \\varphi + \\int_\\Omega p (\\nabla \\cdot \\varphi) + \\int_{\\partial \\Omega_{N}} \\underbrace{(\\nu \\partial_n v - p n )}_{=0} \\cdot \\varphi \\\\\n", + " 0 &= \\int_\\Omega (\\nabla \\cdot v) \\psi\n", + " \\end{aligned}\n", + "$$\n", + "for all possible test functions from the suitable space.\n", + "\n", + "Now we can discretize the problem as usual with the finite element method\n", + "utilizing Taylor-Hood elements (Q2Q1) to yield a stable discretization in\n", + "mass matrix form:\n", + "$$\n", + " \\underbrace{\\begin{bmatrix}\n", + " M_v & 0 \\\\\n", + " 0 & 0\n", + " \\end{bmatrix}}_{:=M}\n", + " \\begin{bmatrix}\n", + " \\mathrm{d}_t\\hat{v} \\\\\n", + " \\mathrm{d}_t\\hat{p}\n", + " \\end{bmatrix}\n", + " =\n", + " \\underbrace{\\begin{bmatrix}\n", + " A & B^{\\textrm{T}} \\\\\n", + " B & 0\n", + " \\end{bmatrix}}_{:=K}\n", + " \\begin{bmatrix}\n", + " \\hat{v} \\\\\n", + " \\hat{p}\n", + " \\end{bmatrix}\n", + " +\n", + " \\begin{bmatrix}\n", + " N(\\hat{v}, \\hat{v}, \\hat{\\varphi}) \\\\\n", + " 0\n", + " \\end{bmatrix}\n", + "$$\n", + "Here $M$ is the singular block mass matrix, $K$ is the discretized Stokes operator and $N$ the nonlinear advection term, which\n", + "is also called trilinear form. $\\hat{v}$ and $\\hat{p}$ represent the time-dependent vectors of nodal values of the discretizations\n", + "of $v$ and $p$ respectively, while $\\hat{\\varphi}$ is the choice for the test function in the discretization. The hats are dropped\n", + "in the implementation and only stated for clarity in this section.\n", + "\n", + "\n", + "## Commented implementation\n", + "\n", + "Now we solve the problem with Ferrite and [DifferentialEquations.jl](https://github.com/SciML/DifferentialEquations.jl). What follows is a program spliced with comments.\n", + "The full program, without comments, can be found in the next section.\n", + "\n", + "First we load Ferrite and some other packages we need" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using Ferrite, SparseArrays, BlockArrays, LinearAlgebra, UnPack, LinearSolve, WriteVTK" + ], + "metadata": {}, + "execution_count": 2 + }, + { + "cell_type": "markdown", + "source": [ + "Since we do not need the complete DifferentialEquations suite, we just load the required ODE infrastructure, which can also handle\n", + "DAEs in mass matrix form." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using OrdinaryDiffEq" + ], + "metadata": {}, + "execution_count": 3 + }, + { + "cell_type": "markdown", + "source": [ + "We start off by defining our only material parameter." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "ν = 1.0 / 1000.0; #dynamic viscosity" + ], + "metadata": {}, + "execution_count": 4 + }, + { + "cell_type": "markdown", + "source": [ + "Next a rectangular grid with a cylinder in it has to be generated.\n", + "We use Gmsh.jl for the creation of the mesh and FerriteGmsh.jl to translate it to a `Ferrite.Grid`.\n", + "Note that the mesh is pretty fine, leading to a high memory consumption when\n", + "feeding the equation system to direct solvers." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using FerriteGmsh\n", + "using FerriteGmsh: Gmsh\n", + "Gmsh.initialize()\n", + "gmsh.option.set_number(\"General.Verbosity\", 2)\n", + "dim = 2;" + ], + "metadata": {}, + "execution_count": 5 + }, + { + "cell_type": "markdown", + "source": [ + "We specify first the rectangle, the cylinder, the surface spanned by the cylinder\n", + "and the boolean difference of rectangle and cylinder." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "if !IS_CI #hide\n", + "rect_tag = gmsh.model.occ.add_rectangle(0, 0, 0, 1.1, 0.41)\n", + "circle_tag = gmsh.model.occ.add_circle(0.2, 0.2, 0, 0.05)\n", + "circle_curve_tag = gmsh.model.occ.add_curve_loop([circle_tag])\n", + "circle_surf_tag = gmsh.model.occ.add_plane_surface([circle_curve_tag])\n", + "gmsh.model.occ.cut([(dim, rect_tag)], [(dim, circle_surf_tag)])\n", + "else #hide\n", + " rect_tag = gmsh.model.occ.add_rectangle(0, 0, 0, 0.55, 0.41) #hide\n", + "end #hide\n", + "nothing #hide" + ], + "metadata": {}, + "execution_count": 6 + }, + { + "cell_type": "markdown", + "source": [ + "Now, the geometrical entities need to be synchronized in order to be available outside\n", + "of `gmsh.model.occ`" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "gmsh.model.occ.synchronize()" + ], + "metadata": {}, + "execution_count": 7 + }, + { + "cell_type": "markdown", + "source": [ + "In the next lines, we add the physical groups needed to define boundary conditions." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "if !IS_CI #hide\n", + "bottomtag = gmsh.model.model.add_physical_group(dim - 1, [6], -1, \"bottom\")\n", + "lefttag = gmsh.model.model.add_physical_group(dim - 1, [7], -1, \"left\")\n", + "righttag = gmsh.model.model.add_physical_group(dim - 1, [8], -1, \"right\")\n", + "toptag = gmsh.model.model.add_physical_group(dim - 1, [9], -1, \"top\")\n", + "holetag = gmsh.model.model.add_physical_group(dim - 1, [5], -1, \"hole\")\n", + "else #hide\n", + " gmsh.model.model.add_physical_group(dim - 1, [4], 7, \"left\") #hide\n", + " gmsh.model.model.add_physical_group(dim - 1, [3], 8, \"top\") #hide\n", + " gmsh.model.model.add_physical_group(dim - 1, [2], 9, \"right\") #hide\n", + " gmsh.model.model.add_physical_group(dim - 1, [1], 10, \"bottom\") #hide\n", + "end #hide\n", + "nothing #hide" + ], + "metadata": {}, + "execution_count": 8 + }, + { + "cell_type": "markdown", + "source": [ + "Since we want a quad mesh, we specify the meshing algorithm to the quasi structured quad one.\n", + "For a complete list, [see the Gmsh docs](https://gmsh.info/doc/texinfo/gmsh.html#Mesh-options-list)." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "gmsh.option.setNumber(\"Mesh.Algorithm\", 11)\n", + "gmsh.option.setNumber(\"Mesh.MeshSizeFromCurvature\", 20)\n", + "gmsh.option.setNumber(\"Mesh.MeshSizeMax\", 0.05)\n", + "if IS_CI #hide\n", + " gmsh.option.setNumber(\"Mesh.MeshSizeFromCurvature\", 20) #hide\n", + " gmsh.option.setNumber(\"Mesh.MeshSizeMax\", 0.15) #hide\n", + "end #hide" + ], + "metadata": {}, + "execution_count": 9 + }, + { + "cell_type": "markdown", + "source": [ + "In the next step, the mesh is generated and finally translated." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "gmsh.model.mesh.generate(dim)\n", + "grid = togrid()\n", + "Gmsh.finalize();" + ], + "metadata": {}, + "execution_count": 10 + }, + { + "cell_type": "markdown", + "source": [ + " ### Function Space\n", + " To ensure stability we utilize the Taylor-Hood element pair Q2-Q1.\n", + " We have to utilize the same quadrature rule for the pressure as for the velocity, because in the weak form the\n", + " linear pressure term is tested against a quadratic function." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "ip_v = Lagrange{RefQuadrilateral, 2}()^dim\n", + "qr = QuadratureRule{RefQuadrilateral}(4)\n", + "cellvalues_v = CellValues(qr, ip_v);\n", + "\n", + "ip_p = Lagrange{RefQuadrilateral, 1}()\n", + "cellvalues_p = CellValues(qr, ip_p);\n", + "\n", + "dh = DofHandler(grid)\n", + "add!(dh, :v, ip_v)\n", + "add!(dh, :p, ip_p)\n", + "close!(dh);" + ], + "metadata": {}, + "execution_count": 11 + }, + { + "cell_type": "markdown", + "source": [ + "### Boundary conditions\n", + "As in the DFG benchmark we apply no-slip conditions to the top, bottom and\n", + "cylinder boundary. The no-slip condition states that the velocity of the\n", + "fluid on this portion of the boundary is fixed to be zero." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "ch = ConstraintHandler(dh);\n", + "\n", + "nosplip_facet_names = [\"top\", \"bottom\", \"hole\"];\n", + "if IS_CI #hide\n", + " nosplip_facet_names = [\"top\", \"bottom\"] #hide\n", + "end #hide\n", + "∂Ω_noslip = union(getfacetset.((grid,), nosplip_facet_names)...);\n", + "noslip_bc = Dirichlet(:v, ∂Ω_noslip, (x, t) -> Vec((0.0, 0.0)), [1, 2])\n", + "add!(ch, noslip_bc);" + ], + "metadata": {}, + "execution_count": 12 + }, + { + "cell_type": "markdown", + "source": [ + "The left boundary has a parabolic inflow with peak velocity of 1.5. This\n", + "ensures that for the given geometry the Reynolds number is 100, which\n", + "is already enough to obtain some simple vortex streets. By increasing the\n", + "velocity further we can obtain stronger vortices - which may need additional\n", + "refinement of the grid." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "∂Ω_inflow = getfacetset(grid, \"left\");" + ], + "metadata": {}, + "execution_count": 13 + }, + { + "cell_type": "markdown", + "source": [ + "> **Note**\n", + ">\n", + "> The kink in the velocity profile will lead to a discontinuity in the pressure at $t=1$.\n", + "> This needs to be considered in the DiffEq `init` by providing the keyword argument `d_discontinuities=[1.0]`." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "vᵢₙ(t) = min(t * 1.5, 1.5) #inflow velocity\n", + "\n", + "parabolic_inflow_profile(x, t) = Vec((4 * vᵢₙ(t) * x[2] * (0.41 - x[2]) / 0.41^2, 0.0))\n", + "inflow_bc = Dirichlet(:v, ∂Ω_inflow, parabolic_inflow_profile, [1, 2])\n", + "add!(ch, inflow_bc);" + ], + "metadata": {}, + "execution_count": 14 + }, + { + "cell_type": "markdown", + "source": [ + "The outflow boundary condition has been applied on the right side of the\n", + "cylinder when the weak form has been derived by setting the boundary integral\n", + "to zero. It is also called the do-nothing condition. Other outflow conditions\n", + "are also possible." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "∂Ω_free = getfacetset(grid, \"right\");\n", + "\n", + "close!(ch)\n", + "update!(ch, 0.0);" + ], + "metadata": {}, + "execution_count": 15 + }, + { + "cell_type": "markdown", + "source": [ + "### Linear System Assembly\n", + "Next we describe how the block mass matrix and the Stokes matrix are assembled.\n", + "\n", + "For the block mass matrix $M$ we remember that only the first equation had a time derivative\n", + "and that the block mass matrix corresponds to the term arising from discretizing the time\n", + "derivatives. Hence, only the upper left block has non-zero components." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function assemble_mass_matrix(cellvalues_v::CellValues, cellvalues_p::CellValues, M::SparseMatrixCSC, dh::DofHandler)\n", + " # Allocate a buffer for the local matrix and some helpers, together with the assembler.\n", + " n_basefuncs_v = getnbasefunctions(cellvalues_v)\n", + " n_basefuncs_p = getnbasefunctions(cellvalues_p)\n", + " n_basefuncs = n_basefuncs_v + n_basefuncs_p\n", + " v▄, p▄ = 1, 2\n", + " Mₑ = BlockedArray(zeros(n_basefuncs, n_basefuncs), [n_basefuncs_v, n_basefuncs_p], [n_basefuncs_v, n_basefuncs_p])\n", + "\n", + " # It follows the assembly loop as explained in the basic tutorials.\n", + " mass_assembler = start_assemble(M)\n", + " for cell in CellIterator(dh)\n", + " fill!(Mₑ, 0)\n", + " Ferrite.reinit!(cellvalues_v, cell)\n", + "\n", + " for q_point in 1:getnquadpoints(cellvalues_v)\n", + " dΩ = getdetJdV(cellvalues_v, q_point)\n", + " # Remember that we assemble a vector mass term, hence the dot product.\n", + " # There is only one time derivative on the left hand side, so only one mass block is non-zero.\n", + " for i in 1:n_basefuncs_v\n", + " φᵢ = shape_value(cellvalues_v, q_point, i)\n", + " for j in 1:n_basefuncs_v\n", + " φⱼ = shape_value(cellvalues_v, q_point, j)\n", + " Mₑ[BlockIndex((v▄, v▄), (i, j))] += φᵢ ⋅ φⱼ * dΩ\n", + " end\n", + " end\n", + " end\n", + " assemble!(mass_assembler, celldofs(cell), Mₑ)\n", + " end\n", + "\n", + " return M\n", + "end;" + ], + "metadata": {}, + "execution_count": 16 + }, + { + "cell_type": "markdown", + "source": [ + "Next we discuss the assembly of the Stokes matrix appearing on the right hand side.\n", + "Remember that we use the same function spaces for trial and test, hence the\n", + "matrix has the following block form\n", + "$$\n", + " K = \\begin{bmatrix}\n", + " A & B^{\\textrm{T}} \\\\\n", + " B & 0\n", + " \\end{bmatrix}\n", + "$$\n", + "which is also called saddle point matrix. These problems are known to have\n", + "a non-trivial kernel, which is a reflection of the strong form as discussed\n", + "in the theory portion if this example." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function assemble_stokes_matrix(cellvalues_v::CellValues, cellvalues_p::CellValues, ν, K::SparseMatrixCSC, dh::DofHandler)\n", + " # Again, some buffers and helpers\n", + " n_basefuncs_v = getnbasefunctions(cellvalues_v)\n", + " n_basefuncs_p = getnbasefunctions(cellvalues_p)\n", + " n_basefuncs = n_basefuncs_v + n_basefuncs_p\n", + " v▄, p▄ = 1, 2\n", + " Kₑ = BlockedArray(zeros(n_basefuncs, n_basefuncs), [n_basefuncs_v, n_basefuncs_p], [n_basefuncs_v, n_basefuncs_p])\n", + "\n", + " # Assembly loop\n", + " stiffness_assembler = start_assemble(K)\n", + " for cell in CellIterator(dh)\n", + " # Don't forget to initialize everything\n", + " fill!(Kₑ, 0)\n", + "\n", + " Ferrite.reinit!(cellvalues_v, cell)\n", + " Ferrite.reinit!(cellvalues_p, cell)\n", + "\n", + " for q_point in 1:getnquadpoints(cellvalues_v)\n", + " dΩ = getdetJdV(cellvalues_v, q_point)\n", + " # Assemble local viscosity block of $A$\n", + " for i in 1:n_basefuncs_v\n", + " ∇φᵢ = shape_gradient(cellvalues_v, q_point, i)\n", + " for j in 1:n_basefuncs_v\n", + " ∇φⱼ = shape_gradient(cellvalues_v, q_point, j)\n", + " Kₑ[BlockIndex((v▄, v▄), (i, j))] -= ν * ∇φᵢ ⊡ ∇φⱼ * dΩ\n", + " end\n", + " end\n", + " # Assemble local pressure and incompressibility blocks of $B^{\\textrm{T}}$ and $B$.\n", + " for j in 1:n_basefuncs_p\n", + " ψ = shape_value(cellvalues_p, q_point, j)\n", + " for i in 1:n_basefuncs_v\n", + " divφ = shape_divergence(cellvalues_v, q_point, i)\n", + " Kₑ[BlockIndex((v▄, p▄), (i, j))] += (divφ * ψ) * dΩ\n", + " Kₑ[BlockIndex((p▄, v▄), (j, i))] += (ψ * divφ) * dΩ\n", + " end\n", + " end\n", + " end\n", + "\n", + " # Assemble `Kₑ` into the Stokes matrix `K`.\n", + " assemble!(stiffness_assembler, celldofs(cell), Kₑ)\n", + " end\n", + " return K\n", + "end;" + ], + "metadata": {}, + "execution_count": 17 + }, + { + "cell_type": "markdown", + "source": [ + "### Solution of the semi-discretized system via DifferentialEquations.jl\n", + "First we assemble the linear portions for efficiency. These matrices are\n", + "assumed to be constant over time.\n", + "> **Note**\n", + ">\n", + "> To obtain the vortex street a small time step is important to resolve\n", + "> the small oscillation forming. The mesh size becomes important to\n", + "> \"only\" resolve the smaller vertices forming, but less important for\n", + "> the initial formation." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "T = 6.0\n", + "Δt₀ = 0.001\n", + "if IS_CI #hide\n", + " Δt₀ = 0.1 #hide\n", + "end #hide\n", + "Δt_save = 0.1\n", + "\n", + "M = allocate_matrix(dh);\n", + "M = assemble_mass_matrix(cellvalues_v, cellvalues_p, M, dh);\n", + "\n", + "K = allocate_matrix(dh);\n", + "K = assemble_stokes_matrix(cellvalues_v, cellvalues_p, ν, K, dh);" + ], + "metadata": {}, + "execution_count": 18 + }, + { + "cell_type": "markdown", + "source": [ + "These are our initial conditions. We start from the zero solution, because it\n", + "is trivially admissible if the Dirichlet conditions are zero everywhere on the\n", + "Dirichlet boundary for $t=0$. Note that the time stepper is also doing fine if the\n", + "Dirichlet condition is non-zero and not too pathological." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "u₀ = zeros(ndofs(dh))\n", + "apply!(u₀, ch);" + ], + "metadata": {}, + "execution_count": 19 + }, + { + "cell_type": "markdown", + "source": [ + "DifferentialEquations assumes dense matrices by default, which is not\n", + "feasible for semi-discretization of finite element models. We communicate\n", + "that a sparse matrix with specified pattern should be utilized through the\n", + "`jac_prototyp` argument. It is simple to see that the Jacobian and the\n", + "stiffness matrix share the same sparsity pattern, since they share the\n", + "same relation between trial and test functions." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "jac_sparsity = sparse(K);" + ], + "metadata": {}, + "execution_count": 20 + }, + { + "cell_type": "markdown", + "source": [ + "To apply the nonlinear portion of the Navier-Stokes problem we simply hand\n", + "over the dof handler and cell values to the right-hand-side (RHS) as a parameter.\n", + "Furthermore the pre-assembled linear part, our Stokes operator (which is time independent)\n", + "is passed to save some additional runtime. To apply the time-dependent Dirichlet BCs, we\n", + "also need to hand over the constraint handler.\n", + "The basic idea to apply the Dirichlet BCs consistently is that we copy the\n", + "current solution `u`, apply the Dirichlet BCs on the copy, evaluate the\n", + "discretized RHS of the Navier-Stokes equations with this vector.\n", + "Furthermore we pass down the Jacobian assembly manually. For the Jacobian we eliminate all\n", + "rows and columns associated with constrained dofs. Also note that we eliminate the mass\n", + "matrix beforehand in a similar fashion. This decouples the time evolution of the constrained\n", + "dofs from the true unknowns. The correct solution is enforced by utilizing step and\n", + "stage limiters. The correct norms are computed by passing down a custom norm which simply\n", + "ignores all constrained dofs.\n", + "\n", + "> **Note**\n", + ">\n", + "> An alternative strategy is to hook into the nonlinear and linear solvers and enforce\n", + "> the solution therein. However, this is not possible at the time of writing this tutorial." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "apply!(M, ch)\n", + "\n", + "struct RHSparams\n", + " K::SparseMatrixCSC\n", + " ch::ConstraintHandler\n", + " dh::DofHandler\n", + " cellvalues_v::CellValues\n", + " u::Vector\n", + "end\n", + "p = RHSparams(K, ch, dh, cellvalues_v, copy(u₀))\n", + "\n", + "function ferrite_limiter!(u, _, p, t)\n", + " update!(p.ch, t)\n", + " return apply!(u, p.ch)\n", + "end\n", + "\n", + "function navierstokes_rhs_element!(dvₑ, vₑ, cellvalues_v)\n", + " n_basefuncs = getnbasefunctions(cellvalues_v)\n", + " for q_point in 1:getnquadpoints(cellvalues_v)\n", + " dΩ = getdetJdV(cellvalues_v, q_point)\n", + " ∇v = function_gradient(cellvalues_v, q_point, vₑ)\n", + " v = function_value(cellvalues_v, q_point, vₑ)\n", + " for j in 1:n_basefuncs\n", + " φⱼ = shape_value(cellvalues_v, q_point, j)\n", + " # Note that in Tensors.jl the definition $\\textrm{grad} v = \\nabla v$ holds.\n", + " # With this information it can be quickly shown in index notation that\n", + " # $$\n", + " # [(v \\cdot \\nabla) v]_{\\textrm{i}} = v_{\\textrm{j}} (\\partial_{\\textrm{j}} v_{\\textrm{i}}) = [v (\\nabla v)^{\\textrm{T}}]_{\\textrm{i}}\n", + " # $$\n", + " # where we should pay attentation to the transpose of the gradient.\n", + " dvₑ[j] -= v ⋅ ∇v' ⋅ φⱼ * dΩ\n", + " end\n", + " end\n", + " return\n", + "end\n", + "\n", + "function navierstokes!(du, u_uc, p::RHSparams, t)\n", + " # Unpack the struct to save some allocations.\n", + " @unpack K, ch, dh, cellvalues_v, u = p\n", + " # We start by applying the time-dependent Dirichlet BCs. Note that we are\n", + " # not allowed to mutate `u_uc`! Furthermore not that we also can not pre-\n", + " # allocate a buffer for this variable variable if we want to use AD to derive\n", + " # the Jacobian matrix, which appears in stiff solvers.\n", + " # Therefore, for efficiency reasons, we simply pass down the jacobian analytically.\n", + " u .= u_uc\n", + " update!(ch, t)\n", + " apply!(u, ch)\n", + " # Now we apply the rhs of the Navier-Stokes equations\n", + " # Linear contribution (Stokes operator)\n", + " mul!(du, K, u) # du .= K * u\n", + "\n", + " # nonlinear contribution\n", + " v_range = dof_range(dh, :v)\n", + " n_basefuncs = getnbasefunctions(cellvalues_v)\n", + " vₑ = zeros(n_basefuncs)\n", + " duₑ = zeros(n_basefuncs)\n", + " for cell in CellIterator(dh)\n", + " Ferrite.reinit!(cellvalues_v, cell)\n", + " v_celldofs = @view celldofs(cell)[v_range]\n", + " vₑ .= @views u[v_celldofs]\n", + " fill!(duₑ, 0.0)\n", + " navierstokes_rhs_element!(duₑ, vₑ, cellvalues_v)\n", + " assemble!(du, v_celldofs, duₑ)\n", + " end\n", + " return\n", + "end;\n", + "\n", + "function navierstokes_jac_element!(Jₑ, vₑ, cellvalues_v)\n", + " n_basefuncs = getnbasefunctions(cellvalues_v)\n", + " for q_point in 1:getnquadpoints(cellvalues_v)\n", + " dΩ = getdetJdV(cellvalues_v, q_point)\n", + " ∇v = function_gradient(cellvalues_v, q_point, vₑ)\n", + " v = function_value(cellvalues_v, q_point, vₑ)\n", + " for j in 1:n_basefuncs\n", + " φⱼ = shape_value(cellvalues_v, q_point, j)\n", + " # Note that in Tensors.jl the definition $\\textrm{grad} v = \\nabla v$ holds.\n", + " # With this information it can be quickly shown in index notation that\n", + " # $$\n", + " # [(v \\cdot \\nabla) v]_{\\textrm{i}} = v_{\\textrm{j}} (\\partial_{\\textrm{j}} v_{\\textrm{i}}) = [v (\\nabla v)^{\\textrm{T}}]_{\\textrm{i}}\n", + " # $$\n", + " # where we should pay attentation to the transpose of the gradient.\n", + " for i in 1:n_basefuncs\n", + " φᵢ = shape_value(cellvalues_v, q_point, i)\n", + " ∇φᵢ = shape_gradient(cellvalues_v, q_point, i)\n", + " Jₑ[j, i] -= (φᵢ ⋅ ∇v' + v ⋅ ∇φᵢ') ⋅ φⱼ * dΩ\n", + " end\n", + " end\n", + " end\n", + " return\n", + "end\n", + "\n", + "function navierstokes_jac!(J, u_uc, p, t)\n", + " # Unpack the struct to save some allocations.\n", + " @unpack K, ch, dh, cellvalues_v, u = p\n", + " # We start by applying the time-dependent Dirichlet BCs. Note that we are\n", + " # not allowed to mutate `u_uc`, so we use our buffer again.\n", + " u .= u_uc\n", + " update!(ch, t)\n", + " apply!(u, ch)\n", + " # Now we apply the Jacobian of the Navier-Stokes equations.\n", + " # Linear contribution (Stokes operator)\n", + " # Here we assume that J has exactly the same structure as K by construction\n", + " nonzeros(J) .= nonzeros(K)\n", + "\n", + " assembler = start_assemble(J; fillzero = false)\n", + "\n", + " # Assemble variation of the nonlinear term\n", + " n_basefuncs = getnbasefunctions(cellvalues_v)\n", + " Jₑ = zeros(n_basefuncs, n_basefuncs)\n", + " vₑ = zeros(n_basefuncs)\n", + " v_range = dof_range(dh, :v)\n", + " for cell in CellIterator(dh)\n", + " Ferrite.reinit!(cellvalues_v, cell)\n", + " v_celldofs = @view celldofs(cell)[v_range]\n", + "\n", + " vₑ .= @views u[v_celldofs]\n", + " fill!(Jₑ, 0.0)\n", + " navierstokes_jac_element!(Jₑ, vₑ, cellvalues_v)\n", + " assemble!(assembler, v_celldofs, Jₑ)\n", + " end\n", + " # Finally we eliminate the constrained dofs from the Jacobian to\n", + " # decouple them in the nonlinear solver from the remaining system.\n", + " return apply!(J, ch)\n", + "end;" + ], + "metadata": {}, + "execution_count": 21 + }, + { + "cell_type": "markdown", + "source": [ + "Finally, together with our pre-assembled mass matrix, we are now able to\n", + "define our problem in mass matrix form." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "rhs = ODEFunction(navierstokes!, mass_matrix = M; jac = navierstokes_jac!, jac_prototype = jac_sparsity)\n", + "problem = ODEProblem(rhs, u₀, (0.0, T), p);" + ], + "metadata": {}, + "execution_count": 22 + }, + { + "cell_type": "markdown", + "source": [ + "All norms must not depend on constrained dofs. A problem with the presented implementation\n", + "is that we are currently unable to strictly enforce constraint everywhere in the internal\n", + "time integration process of [DifferentialEquations.jl](https://github.com/SciML/DifferentialEquations.jl),\n", + "hence the values might differ, resulting in worse error estimates.\n", + "We try to resolve this issue in the future. Volunteers are also welcome to take a look into this!" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "struct FreeDofErrorNorm\n", + " ch::ConstraintHandler\n", + "end\n", + "(fe_norm::FreeDofErrorNorm)(u::Union{AbstractFloat, Complex}, t) = DiffEqBase.ODE_DEFAULT_NORM(u, t)\n", + "(fe_norm::FreeDofErrorNorm)(u::AbstractArray, t) = DiffEqBase.ODE_DEFAULT_NORM(u[fe_norm.ch.free_dofs], t)" + ], + "metadata": {}, + "execution_count": 23 + }, + { + "cell_type": "markdown", + "source": [ + "Now we can put everything together by specifying how to solve the problem.\n", + "We want to use an adaptive variant of the implicit Euler method. Further we\n", + "enable the progress bar with the `progress` and `progress_steps` arguments.\n", + "Finally we have to communicate the time step length and initialization\n", + "algorithm. Since we start with a valid initial state we do not use one of\n", + "DifferentialEquations.jl initialization algorithms.\n", + "> **DAE initialization**\n", + ">\n", + "> At the time of writing this [no Hessenberg index 2 initialization is implemented](https://github.com/SciML/OrdinaryDiffEq.jl/issues/1019).\n", + "\n", + "To visualize the result we export the grid and our fields\n", + "to VTK-files, which can be viewed in [ParaView](https://www.paraview.org/)\n", + "by utilizing the corresponding pvd file." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "timestepper = Rodas5P(autodiff = false, step_limiter! = ferrite_limiter!);" + ], + "metadata": {}, + "execution_count": 24 + }, + { + "cell_type": "markdown", + "source": [ + "> **Debugging convergence issues**\n", + ">\n", + "> We can obtain some debug information from OrdinaryDiffEq by wrapping the following section into a [debug logger](https://docs.julialang.org/en/v1/stdlib/Logging/#Example:-Enable-debug-level-messages)." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "integrator = init(\n", + " problem, timestepper; initializealg = NoInit(), dt = Δt₀,\n", + " adaptive = true, abstol = 1.0e-4, reltol = 1.0e-5,\n", + " progress = true, progress_steps = 1,\n", + " verbose = true, internalnorm = FreeDofErrorNorm(ch), d_discontinuities = [1.0]\n", + ");" + ], + "metadata": {}, + "execution_count": 25 + }, + { + "cell_type": "markdown", + "source": [ + "> **Export of solution**\n", + ">\n", + "> Exporting interpolated solutions of problems containing mass matrices is currently broken.\n", + "> Thus, the `intervals` iterator is used. Note that `solve` holds all solutions in the memory." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "pvd = paraview_collection(\"vortex-street\")\n", + "for (step, (u, t)) in enumerate(intervals(integrator))\n", + " VTKGridFile(\"vortex-street-$step\", dh) do vtk\n", + " write_solution(vtk, dh, u)\n", + " pvd[t] = vtk\n", + " end\n", + "end\n", + "vtk_save(pvd);\n", + "\n", + "\n", + "using Test #hide\n", + "if IS_CI #hide\n", + " function compute_divergence(dh, u, cellvalues_v) #hide\n", + " divv = 0.0 #hide\n", + " for cell in CellIterator(dh) #hide\n", + " Ferrite.reinit!(cellvalues_v, cell) #hide\n", + " for q_point in 1:getnquadpoints(cellvalues_v) #hide\n", + " dΩ = getdetJdV(cellvalues_v, q_point) #hide\n", + " #hide\n", + " all_celldofs = celldofs(cell) #hide\n", + " v_celldofs = all_celldofs[dof_range(dh, :v)] #hide\n", + " v_cell = u[v_celldofs] #hide\n", + " #hide\n", + " divv += function_divergence(cellvalues_v, q_point, v_cell) * dΩ #hide\n", + " end #hide\n", + " end #hide\n", + " return divv #hide\n", + " end #hide\n", + " let #hide\n", + " u = copy(integrator.u) #hide\n", + " Δdivv = abs(compute_divergence(dh, u, cellvalues_v)) #hide\n", + " @test isapprox(Δdivv, 0.0, atol = 1.0e-12) #hide\n", + " #hide\n", + " Δv = 0.0 #hide\n", + " for cell in CellIterator(dh) #hide\n", + " Ferrite.reinit!(cellvalues_v, cell) #hide\n", + " all_celldofs = celldofs(cell) #hide\n", + " v_celldofs = all_celldofs[dof_range(dh, :v)] #hide\n", + " v_cell = u[v_celldofs] #hide\n", + " coords = getcoordinates(cell) #hide\n", + " for q_point in 1:getnquadpoints(cellvalues_v) #hide\n", + " dΩ = getdetJdV(cellvalues_v, q_point) #hide\n", + " coords_qp = spatial_coordinate(cellvalues_v, q_point, coords) #hide\n", + " v = function_value(cellvalues_v, q_point, v_cell) #hide\n", + " Δv += norm(v - parabolic_inflow_profile(coords_qp, T))^2 * dΩ #hide\n", + " end #hide\n", + " end #hide\n", + " @test isapprox(sqrt(Δv), 0.0, atol = 1.0e-3) #hide\n", + " end #hide\n", + " nothing #hide\n", + "end #hide" + ], + "metadata": {}, + "execution_count": 26 + }, + { + "cell_type": "markdown", + "source": [ + "---\n", + "\n", + "*This notebook was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).*" + ], + "metadata": {} + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.1" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.1", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR798/tutorials/ns_vs_diffeq.jl b/previews/PR798/tutorials/ns_vs_diffeq.jl new file mode 100644 index 0000000000..95f9f540c6 --- /dev/null +++ b/previews/PR798/tutorials/ns_vs_diffeq.jl @@ -0,0 +1,367 @@ +if isdefined(Main, :is_ci) #hide + IS_CI = Main.is_ci #hide +else #hide + IS_CI = false #hide +end #hide +nothing #hide + +using Ferrite, SparseArrays, BlockArrays, LinearAlgebra, UnPack, LinearSolve, WriteVTK + +using OrdinaryDiffEq + +ν = 1.0 / 1000.0; #dynamic viscosity + +using FerriteGmsh +using FerriteGmsh: Gmsh +Gmsh.initialize() +gmsh.option.set_number("General.Verbosity", 2) +dim = 2; + +if !IS_CI #hide +rect_tag = gmsh.model.occ.add_rectangle(0, 0, 0, 1.1, 0.41) +circle_tag = gmsh.model.occ.add_circle(0.2, 0.2, 0, 0.05) +circle_curve_tag = gmsh.model.occ.add_curve_loop([circle_tag]) +circle_surf_tag = gmsh.model.occ.add_plane_surface([circle_curve_tag]) +gmsh.model.occ.cut([(dim, rect_tag)], [(dim, circle_surf_tag)]) +else #hide + rect_tag = gmsh.model.occ.add_rectangle(0, 0, 0, 0.55, 0.41) #hide +end #hide +nothing #hide + +gmsh.model.occ.synchronize() + +if !IS_CI #hide +bottomtag = gmsh.model.model.add_physical_group(dim - 1, [6], -1, "bottom") +lefttag = gmsh.model.model.add_physical_group(dim - 1, [7], -1, "left") +righttag = gmsh.model.model.add_physical_group(dim - 1, [8], -1, "right") +toptag = gmsh.model.model.add_physical_group(dim - 1, [9], -1, "top") +holetag = gmsh.model.model.add_physical_group(dim - 1, [5], -1, "hole") +else #hide + gmsh.model.model.add_physical_group(dim - 1, [4], 7, "left") #hide + gmsh.model.model.add_physical_group(dim - 1, [3], 8, "top") #hide + gmsh.model.model.add_physical_group(dim - 1, [2], 9, "right") #hide + gmsh.model.model.add_physical_group(dim - 1, [1], 10, "bottom") #hide +end #hide +nothing #hide + +gmsh.option.setNumber("Mesh.Algorithm", 11) +gmsh.option.setNumber("Mesh.MeshSizeFromCurvature", 20) +gmsh.option.setNumber("Mesh.MeshSizeMax", 0.05) +if IS_CI #hide + gmsh.option.setNumber("Mesh.MeshSizeFromCurvature", 20) #hide + gmsh.option.setNumber("Mesh.MeshSizeMax", 0.15) #hide +end #hide + +gmsh.model.mesh.generate(dim) +grid = togrid() +Gmsh.finalize(); + +ip_v = Lagrange{RefQuadrilateral, 2}()^dim +qr = QuadratureRule{RefQuadrilateral}(4) +cellvalues_v = CellValues(qr, ip_v); + +ip_p = Lagrange{RefQuadrilateral, 1}() +cellvalues_p = CellValues(qr, ip_p); + +dh = DofHandler(grid) +add!(dh, :v, ip_v) +add!(dh, :p, ip_p) +close!(dh); + +ch = ConstraintHandler(dh); + +nosplip_facet_names = ["top", "bottom", "hole"]; +if IS_CI #hide + nosplip_facet_names = ["top", "bottom"] #hide +end #hide +∂Ω_noslip = union(getfacetset.((grid,), nosplip_facet_names)...); +noslip_bc = Dirichlet(:v, ∂Ω_noslip, (x, t) -> Vec((0.0, 0.0)), [1, 2]) +add!(ch, noslip_bc); + +∂Ω_inflow = getfacetset(grid, "left"); + +vᵢₙ(t) = min(t * 1.5, 1.5) #inflow velocity + +parabolic_inflow_profile(x, t) = Vec((4 * vᵢₙ(t) * x[2] * (0.41 - x[2]) / 0.41^2, 0.0)) +inflow_bc = Dirichlet(:v, ∂Ω_inflow, parabolic_inflow_profile, [1, 2]) +add!(ch, inflow_bc); + +∂Ω_free = getfacetset(grid, "right"); + +close!(ch) +update!(ch, 0.0); + +function assemble_mass_matrix(cellvalues_v::CellValues, cellvalues_p::CellValues, M::SparseMatrixCSC, dh::DofHandler) + # Allocate a buffer for the local matrix and some helpers, together with the assembler. + n_basefuncs_v = getnbasefunctions(cellvalues_v) + n_basefuncs_p = getnbasefunctions(cellvalues_p) + n_basefuncs = n_basefuncs_v + n_basefuncs_p + v▄, p▄ = 1, 2 + Mₑ = BlockedArray(zeros(n_basefuncs, n_basefuncs), [n_basefuncs_v, n_basefuncs_p], [n_basefuncs_v, n_basefuncs_p]) + + # It follows the assembly loop as explained in the basic tutorials. + mass_assembler = start_assemble(M) + for cell in CellIterator(dh) + fill!(Mₑ, 0) + Ferrite.reinit!(cellvalues_v, cell) + + for q_point in 1:getnquadpoints(cellvalues_v) + dΩ = getdetJdV(cellvalues_v, q_point) + # Remember that we assemble a vector mass term, hence the dot product. + # There is only one time derivative on the left hand side, so only one mass block is non-zero. + for i in 1:n_basefuncs_v + φᵢ = shape_value(cellvalues_v, q_point, i) + for j in 1:n_basefuncs_v + φⱼ = shape_value(cellvalues_v, q_point, j) + Mₑ[BlockIndex((v▄, v▄), (i, j))] += φᵢ ⋅ φⱼ * dΩ + end + end + end + assemble!(mass_assembler, celldofs(cell), Mₑ) + end + + return M +end; + +function assemble_stokes_matrix(cellvalues_v::CellValues, cellvalues_p::CellValues, ν, K::SparseMatrixCSC, dh::DofHandler) + # Again, some buffers and helpers + n_basefuncs_v = getnbasefunctions(cellvalues_v) + n_basefuncs_p = getnbasefunctions(cellvalues_p) + n_basefuncs = n_basefuncs_v + n_basefuncs_p + v▄, p▄ = 1, 2 + Kₑ = BlockedArray(zeros(n_basefuncs, n_basefuncs), [n_basefuncs_v, n_basefuncs_p], [n_basefuncs_v, n_basefuncs_p]) + + # Assembly loop + stiffness_assembler = start_assemble(K) + for cell in CellIterator(dh) + # Don't forget to initialize everything + fill!(Kₑ, 0) + + Ferrite.reinit!(cellvalues_v, cell) + Ferrite.reinit!(cellvalues_p, cell) + + for q_point in 1:getnquadpoints(cellvalues_v) + dΩ = getdetJdV(cellvalues_v, q_point) + + for i in 1:n_basefuncs_v + ∇φᵢ = shape_gradient(cellvalues_v, q_point, i) + for j in 1:n_basefuncs_v + ∇φⱼ = shape_gradient(cellvalues_v, q_point, j) + Kₑ[BlockIndex((v▄, v▄), (i, j))] -= ν * ∇φᵢ ⊡ ∇φⱼ * dΩ + end + end + + for j in 1:n_basefuncs_p + ψ = shape_value(cellvalues_p, q_point, j) + for i in 1:n_basefuncs_v + divφ = shape_divergence(cellvalues_v, q_point, i) + Kₑ[BlockIndex((v▄, p▄), (i, j))] += (divφ * ψ) * dΩ + Kₑ[BlockIndex((p▄, v▄), (j, i))] += (ψ * divφ) * dΩ + end + end + end + + # Assemble `Kₑ` into the Stokes matrix `K`. + assemble!(stiffness_assembler, celldofs(cell), Kₑ) + end + return K +end; + +T = 6.0 +Δt₀ = 0.001 +if IS_CI #hide + Δt₀ = 0.1 #hide +end #hide +Δt_save = 0.1 + +M = allocate_matrix(dh); +M = assemble_mass_matrix(cellvalues_v, cellvalues_p, M, dh); + +K = allocate_matrix(dh); +K = assemble_stokes_matrix(cellvalues_v, cellvalues_p, ν, K, dh); + +u₀ = zeros(ndofs(dh)) +apply!(u₀, ch); + +jac_sparsity = sparse(K); + +apply!(M, ch) + +struct RHSparams + K::SparseMatrixCSC + ch::ConstraintHandler + dh::DofHandler + cellvalues_v::CellValues + u::Vector +end +p = RHSparams(K, ch, dh, cellvalues_v, copy(u₀)) + +function ferrite_limiter!(u, _, p, t) + update!(p.ch, t) + return apply!(u, p.ch) +end + +function navierstokes_rhs_element!(dvₑ, vₑ, cellvalues_v) + n_basefuncs = getnbasefunctions(cellvalues_v) + for q_point in 1:getnquadpoints(cellvalues_v) + dΩ = getdetJdV(cellvalues_v, q_point) + ∇v = function_gradient(cellvalues_v, q_point, vₑ) + v = function_value(cellvalues_v, q_point, vₑ) + for j in 1:n_basefuncs + φⱼ = shape_value(cellvalues_v, q_point, j) + + dvₑ[j] -= v ⋅ ∇v' ⋅ φⱼ * dΩ + end + end + return +end + +function navierstokes!(du, u_uc, p::RHSparams, t) + + @unpack K, ch, dh, cellvalues_v, u = p + + u .= u_uc + update!(ch, t) + apply!(u, ch) + + # Linear contribution (Stokes operator) + mul!(du, K, u) # du .= K * u + + # nonlinear contribution + v_range = dof_range(dh, :v) + n_basefuncs = getnbasefunctions(cellvalues_v) + vₑ = zeros(n_basefuncs) + duₑ = zeros(n_basefuncs) + for cell in CellIterator(dh) + Ferrite.reinit!(cellvalues_v, cell) + v_celldofs = @view celldofs(cell)[v_range] + vₑ .= @views u[v_celldofs] + fill!(duₑ, 0.0) + navierstokes_rhs_element!(duₑ, vₑ, cellvalues_v) + assemble!(du, v_celldofs, duₑ) + end + return +end; + +function navierstokes_jac_element!(Jₑ, vₑ, cellvalues_v) + n_basefuncs = getnbasefunctions(cellvalues_v) + for q_point in 1:getnquadpoints(cellvalues_v) + dΩ = getdetJdV(cellvalues_v, q_point) + ∇v = function_gradient(cellvalues_v, q_point, vₑ) + v = function_value(cellvalues_v, q_point, vₑ) + for j in 1:n_basefuncs + φⱼ = shape_value(cellvalues_v, q_point, j) + + for i in 1:n_basefuncs + φᵢ = shape_value(cellvalues_v, q_point, i) + ∇φᵢ = shape_gradient(cellvalues_v, q_point, i) + Jₑ[j, i] -= (φᵢ ⋅ ∇v' + v ⋅ ∇φᵢ') ⋅ φⱼ * dΩ + end + end + end + return +end + +function navierstokes_jac!(J, u_uc, p, t) + + @unpack K, ch, dh, cellvalues_v, u = p + + u .= u_uc + update!(ch, t) + apply!(u, ch) + + # Linear contribution (Stokes operator) + # Here we assume that J has exactly the same structure as K by construction + nonzeros(J) .= nonzeros(K) + + assembler = start_assemble(J; fillzero = false) + + # Assemble variation of the nonlinear term + n_basefuncs = getnbasefunctions(cellvalues_v) + Jₑ = zeros(n_basefuncs, n_basefuncs) + vₑ = zeros(n_basefuncs) + v_range = dof_range(dh, :v) + for cell in CellIterator(dh) + Ferrite.reinit!(cellvalues_v, cell) + v_celldofs = @view celldofs(cell)[v_range] + + vₑ .= @views u[v_celldofs] + fill!(Jₑ, 0.0) + navierstokes_jac_element!(Jₑ, vₑ, cellvalues_v) + assemble!(assembler, v_celldofs, Jₑ) + end + + return apply!(J, ch) +end; + +rhs = ODEFunction(navierstokes!, mass_matrix = M; jac = navierstokes_jac!, jac_prototype = jac_sparsity) +problem = ODEProblem(rhs, u₀, (0.0, T), p); + +struct FreeDofErrorNorm + ch::ConstraintHandler +end +(fe_norm::FreeDofErrorNorm)(u::Union{AbstractFloat, Complex}, t) = DiffEqBase.ODE_DEFAULT_NORM(u, t) +(fe_norm::FreeDofErrorNorm)(u::AbstractArray, t) = DiffEqBase.ODE_DEFAULT_NORM(u[fe_norm.ch.free_dofs], t) + +timestepper = Rodas5P(autodiff = false, step_limiter! = ferrite_limiter!); + +integrator = init( + problem, timestepper; initializealg = NoInit(), dt = Δt₀, + adaptive = true, abstol = 1.0e-4, reltol = 1.0e-5, + progress = true, progress_steps = 1, + verbose = true, internalnorm = FreeDofErrorNorm(ch), d_discontinuities = [1.0] +); + +pvd = paraview_collection("vortex-street") +for (step, (u, t)) in enumerate(intervals(integrator)) + VTKGridFile("vortex-street-$step", dh) do vtk + write_solution(vtk, dh, u) + pvd[t] = vtk + end +end +vtk_save(pvd); + + +using Test #hide +if IS_CI #hide + function compute_divergence(dh, u, cellvalues_v) #hide + divv = 0.0 #hide + for cell in CellIterator(dh) #hide + Ferrite.reinit!(cellvalues_v, cell) #hide + for q_point in 1:getnquadpoints(cellvalues_v) #hide + dΩ = getdetJdV(cellvalues_v, q_point) #hide + #hide + all_celldofs = celldofs(cell) #hide + v_celldofs = all_celldofs[dof_range(dh, :v)] #hide + v_cell = u[v_celldofs] #hide + #hide + divv += function_divergence(cellvalues_v, q_point, v_cell) * dΩ #hide + end #hide + end #hide + return divv #hide + end #hide + let #hide + u = copy(integrator.u) #hide + Δdivv = abs(compute_divergence(dh, u, cellvalues_v)) #hide + @test isapprox(Δdivv, 0.0, atol = 1.0e-12) #hide + #hide + Δv = 0.0 #hide + for cell in CellIterator(dh) #hide + Ferrite.reinit!(cellvalues_v, cell) #hide + all_celldofs = celldofs(cell) #hide + v_celldofs = all_celldofs[dof_range(dh, :v)] #hide + v_cell = u[v_celldofs] #hide + coords = getcoordinates(cell) #hide + for q_point in 1:getnquadpoints(cellvalues_v) #hide + dΩ = getdetJdV(cellvalues_v, q_point) #hide + coords_qp = spatial_coordinate(cellvalues_v, q_point, coords) #hide + v = function_value(cellvalues_v, q_point, v_cell) #hide + Δv += norm(v - parabolic_inflow_profile(coords_qp, T))^2 * dΩ #hide + end #hide + end #hide + @test isapprox(sqrt(Δv), 0.0, atol = 1.0e-3) #hide + end #hide + nothing #hide +end #hide + +# This file was generated using Literate.jl, https://github.com/fredrikekre/Literate.jl diff --git a/previews/PR798/tutorials/ns_vs_diffeq/index.html b/previews/PR798/tutorials/ns_vs_diffeq/index.html new file mode 100644 index 0000000000..a39bda82d5 --- /dev/null +++ b/previews/PR798/tutorials/ns_vs_diffeq/index.html @@ -0,0 +1,549 @@ + +Incompressible Navier-Stokes equations via DifferentialEquations.jl · Ferrite.jl

Incompressible Navier-Stokes equations via DifferentialEquations.jl

nsdiffeq

In this example we focus on a simple but visually appealing problem from fluid dynamics, namely vortex shedding. This problem is also known as von-Karman vortex streets. Within this example, we show how to utilize DifferentialEquations.jl in tandem with Ferrite to solve this space-time problem. To keep things simple we use a naive approach to discretize the system.

Remarks on DifferentialEquations.jl

Required Version

This example will only work with OrdinaryDiffEq@v6.80.1. or above

Many "time step solvers" of DifferentialEquations.jl assume that that the problem is provided in mass matrix form. The incompressible Navier-Stokes equations as stated above yield a DAE in this form after applying a spatial discretization technique - in our case FEM. The mass matrix form of ODEs and DAEs is given as:

\[ M(t) \mathrm{d}_t u = f(u,t)\]

where $M$ is a possibly time-dependent and not necessarily invertible mass matrix, $u$ the vector of unknowns and $f$ the right-hand-side (RHS). For us $f$ can be interpreted as the spatial discretization of all linear and nonlinear operators depending on $u$ and $t$, but not on the time derivative of $u$.

Some theory on the incompressible Navier-Stokes equations

Problem description in strong form

The incompressible Navier-Stokes equations can be stated as the system

\[ \begin{aligned} + \partial_t v &= \underbrace{\nu \Delta v}_{\text{viscosity}} - \underbrace{(v \cdot \nabla) v}_{\text{advection}} - \underbrace{\nabla p}_{\text{pressure}} \\ + 0 &= \underbrace{\nabla \cdot v}_{\text{incompressibility}} + \end{aligned}\]

where $v$ is the unknown velocity field, $p$ the unknown pressure field, $\nu$ the dynamic viscosity and $\Delta$ the Laplacian. In the derivation we assumed a constant density of 1 for the fluid and negligible coupling between the velocity components.

Our setup is derived from Turek's DFG benchmark. We model a channel with size $0.41 \times 1.1$ and a hole of radius $0.05$ centered at $(0.2, 0.2)$. The left side has a parabolic inflow profile, which is ramped up over time, modeled as the time dependent Dirichlet condition

\[ v(x,y,t) + = + \begin{bmatrix} + 4 v_{in}(t) y (0.41-y)/0.41^2 \\ + 0 + \end{bmatrix}\]

where $v_{in}(t) = \text{clamp}(t, 0.0, 1.5)$. With a dynamic viscosity of $\nu = 0.001$ this is enough to induce turbulence behind the cylinder which leads to vortex shedding. The top and bottom of our channel have no-slip conditions, i.e. $v = [0,0]^{\textrm{T}}$, while the right boundary has the do-nothing boundary condition $\nu \partial_{\textrm{n}} v - p n = 0$ to model outflow. With these boundary conditions we can choose the zero solution as a feasible initial condition.

Derivation of Semi-Discrete Weak Form

By multiplying test functions $\varphi$ and $\psi$ from a suitable test function space on the strong form, followed by integrating over the domain and applying partial integration to the pressure and viscosity terms we can obtain the following weak form

\[ \begin{aligned} + \int_\Omega \partial_t v \cdot \varphi &= - \int_\Omega \nu \nabla v : \nabla \varphi - \int_\Omega (v \cdot \nabla) v \cdot \varphi + \int_\Omega p (\nabla \cdot \varphi) + \int_{\partial \Omega_{N}} \underbrace{(\nu \partial_n v - p n )}_{=0} \cdot \varphi \\ + 0 &= \int_\Omega (\nabla \cdot v) \psi + \end{aligned}\]

for all possible test functions from the suitable space.

Now we can discretize the problem as usual with the finite element method utilizing Taylor-Hood elements (Q2Q1) to yield a stable discretization in mass matrix form:

\[ \underbrace{\begin{bmatrix} + M_v & 0 \\ + 0 & 0 + \end{bmatrix}}_{:=M} + \begin{bmatrix} + \mathrm{d}_t\hat{v} \\ + \mathrm{d}_t\hat{p} + \end{bmatrix} + = + \underbrace{\begin{bmatrix} + A & B^{\textrm{T}} \\ + B & 0 + \end{bmatrix}}_{:=K} + \begin{bmatrix} + \hat{v} \\ + \hat{p} + \end{bmatrix} + + + \begin{bmatrix} + N(\hat{v}, \hat{v}, \hat{\varphi}) \\ + 0 + \end{bmatrix}\]

Here $M$ is the singular block mass matrix, $K$ is the discretized Stokes operator and $N$ the nonlinear advection term, which is also called trilinear form. $\hat{v}$ and $\hat{p}$ represent the time-dependent vectors of nodal values of the discretizations of $v$ and $p$ respectively, while $\hat{\varphi}$ is the choice for the test function in the discretization. The hats are dropped in the implementation and only stated for clarity in this section.

Commented implementation

Now we solve the problem with Ferrite and DifferentialEquations.jl. What follows is a program spliced with comments. The full program, without comments, can be found in the next section.

First we load Ferrite and some other packages we need

using Ferrite, SparseArrays, BlockArrays, LinearAlgebra, UnPack, LinearSolve, WriteVTK

Since we do not need the complete DifferentialEquations suite, we just load the required ODE infrastructure, which can also handle DAEs in mass matrix form.

using OrdinaryDiffEq

We start off by defining our only material parameter.

ν = 1.0 / 1000.0; #dynamic viscosity

Next a rectangular grid with a cylinder in it has to be generated. We use Gmsh.jl for the creation of the mesh and FerriteGmsh.jl to translate it to a Ferrite.Grid. Note that the mesh is pretty fine, leading to a high memory consumption when feeding the equation system to direct solvers.

using FerriteGmsh
+using FerriteGmsh: Gmsh
+Gmsh.initialize()
+gmsh.option.set_number("General.Verbosity", 2)
+dim = 2;

We specify first the rectangle, the cylinder, the surface spanned by the cylinder and the boolean difference of rectangle and cylinder.

rect_tag = gmsh.model.occ.add_rectangle(0, 0, 0, 1.1, 0.41)
+circle_tag = gmsh.model.occ.add_circle(0.2, 0.2, 0, 0.05)
+circle_curve_tag = gmsh.model.occ.add_curve_loop([circle_tag])
+circle_surf_tag = gmsh.model.occ.add_plane_surface([circle_curve_tag])
+gmsh.model.occ.cut([(dim, rect_tag)], [(dim, circle_surf_tag)])

Now, the geometrical entities need to be synchronized in order to be available outside of gmsh.model.occ

gmsh.model.occ.synchronize()

In the next lines, we add the physical groups needed to define boundary conditions.

bottomtag = gmsh.model.model.add_physical_group(dim - 1, [6], -1, "bottom")
+lefttag = gmsh.model.model.add_physical_group(dim - 1, [7], -1, "left")
+righttag = gmsh.model.model.add_physical_group(dim - 1, [8], -1, "right")
+toptag = gmsh.model.model.add_physical_group(dim - 1, [9], -1, "top")
+holetag = gmsh.model.model.add_physical_group(dim - 1, [5], -1, "hole")

Since we want a quad mesh, we specify the meshing algorithm to the quasi structured quad one. For a complete list, see the Gmsh docs.

gmsh.option.setNumber("Mesh.Algorithm", 11)
+gmsh.option.setNumber("Mesh.MeshSizeFromCurvature", 20)
+gmsh.option.setNumber("Mesh.MeshSizeMax", 0.05)

In the next step, the mesh is generated and finally translated.

gmsh.model.mesh.generate(dim)
+grid = togrid()
+Gmsh.finalize();

Function Space

To ensure stability we utilize the Taylor-Hood element pair Q2-Q1. We have to utilize the same quadrature rule for the pressure as for the velocity, because in the weak form the linear pressure term is tested against a quadratic function.

ip_v = Lagrange{RefQuadrilateral, 2}()^dim
+qr = QuadratureRule{RefQuadrilateral}(4)
+cellvalues_v = CellValues(qr, ip_v);
+
+ip_p = Lagrange{RefQuadrilateral, 1}()
+cellvalues_p = CellValues(qr, ip_p);
+
+dh = DofHandler(grid)
+add!(dh, :v, ip_v)
+add!(dh, :p, ip_p)
+close!(dh);

Boundary conditions

As in the DFG benchmark we apply no-slip conditions to the top, bottom and cylinder boundary. The no-slip condition states that the velocity of the fluid on this portion of the boundary is fixed to be zero.

ch = ConstraintHandler(dh);
+
+nosplip_facet_names = ["top", "bottom", "hole"];
+∂Ω_noslip = union(getfacetset.((grid,), nosplip_facet_names)...);
+noslip_bc = Dirichlet(:v, ∂Ω_noslip, (x, t) -> Vec((0.0, 0.0)), [1, 2])
+add!(ch, noslip_bc);

The left boundary has a parabolic inflow with peak velocity of 1.5. This ensures that for the given geometry the Reynolds number is 100, which is already enough to obtain some simple vortex streets. By increasing the velocity further we can obtain stronger vortices - which may need additional refinement of the grid.

∂Ω_inflow = getfacetset(grid, "left");
Note

The kink in the velocity profile will lead to a discontinuity in the pressure at $t=1$. This needs to be considered in the DiffEq init by providing the keyword argument d_discontinuities=[1.0].

vᵢₙ(t) = min(t * 1.5, 1.5) #inflow velocity
+
+parabolic_inflow_profile(x, t) = Vec((4 * vᵢₙ(t) * x[2] * (0.41 - x[2]) / 0.41^2, 0.0))
+inflow_bc = Dirichlet(:v, ∂Ω_inflow, parabolic_inflow_profile, [1, 2])
+add!(ch, inflow_bc);

The outflow boundary condition has been applied on the right side of the cylinder when the weak form has been derived by setting the boundary integral to zero. It is also called the do-nothing condition. Other outflow conditions are also possible.

∂Ω_free = getfacetset(grid, "right");
+
+close!(ch)
+update!(ch, 0.0);

Linear System Assembly

Next we describe how the block mass matrix and the Stokes matrix are assembled.

For the block mass matrix $M$ we remember that only the first equation had a time derivative and that the block mass matrix corresponds to the term arising from discretizing the time derivatives. Hence, only the upper left block has non-zero components.

function assemble_mass_matrix(cellvalues_v::CellValues, cellvalues_p::CellValues, M::SparseMatrixCSC, dh::DofHandler)
+    # Allocate a buffer for the local matrix and some helpers, together with the assembler.
+    n_basefuncs_v = getnbasefunctions(cellvalues_v)
+    n_basefuncs_p = getnbasefunctions(cellvalues_p)
+    n_basefuncs = n_basefuncs_v + n_basefuncs_p
+    v▄, p▄ = 1, 2
+    Mₑ = BlockedArray(zeros(n_basefuncs, n_basefuncs), [n_basefuncs_v, n_basefuncs_p], [n_basefuncs_v, n_basefuncs_p])
+
+    # It follows the assembly loop as explained in the basic tutorials.
+    mass_assembler = start_assemble(M)
+    for cell in CellIterator(dh)
+        fill!(Mₑ, 0)
+        Ferrite.reinit!(cellvalues_v, cell)
+
+        for q_point in 1:getnquadpoints(cellvalues_v)
+            dΩ = getdetJdV(cellvalues_v, q_point)
+            # Remember that we assemble a vector mass term, hence the dot product.
+            # There is only one time derivative on the left hand side, so only one mass block is non-zero.
+            for i in 1:n_basefuncs_v
+                φᵢ = shape_value(cellvalues_v, q_point, i)
+                for j in 1:n_basefuncs_v
+                    φⱼ = shape_value(cellvalues_v, q_point, j)
+                    Mₑ[BlockIndex((v▄, v▄), (i, j))] += φᵢ ⋅ φⱼ * dΩ
+                end
+            end
+        end
+        assemble!(mass_assembler, celldofs(cell), Mₑ)
+    end
+
+    return M
+end;

Next we discuss the assembly of the Stokes matrix appearing on the right hand side. Remember that we use the same function spaces for trial and test, hence the matrix has the following block form

\[ K = \begin{bmatrix} + A & B^{\textrm{T}} \\ + B & 0 + \end{bmatrix}\]

which is also called saddle point matrix. These problems are known to have a non-trivial kernel, which is a reflection of the strong form as discussed in the theory portion if this example.

function assemble_stokes_matrix(cellvalues_v::CellValues, cellvalues_p::CellValues, ν, K::SparseMatrixCSC, dh::DofHandler)
+    # Again, some buffers and helpers
+    n_basefuncs_v = getnbasefunctions(cellvalues_v)
+    n_basefuncs_p = getnbasefunctions(cellvalues_p)
+    n_basefuncs = n_basefuncs_v + n_basefuncs_p
+    v▄, p▄ = 1, 2
+    Kₑ = BlockedArray(zeros(n_basefuncs, n_basefuncs), [n_basefuncs_v, n_basefuncs_p], [n_basefuncs_v, n_basefuncs_p])
+
+    # Assembly loop
+    stiffness_assembler = start_assemble(K)
+    for cell in CellIterator(dh)
+        # Don't forget to initialize everything
+        fill!(Kₑ, 0)
+
+        Ferrite.reinit!(cellvalues_v, cell)
+        Ferrite.reinit!(cellvalues_p, cell)
+
+        for q_point in 1:getnquadpoints(cellvalues_v)
+            dΩ = getdetJdV(cellvalues_v, q_point)

Assemble local viscosity block of $A$

            for i in 1:n_basefuncs_v
+                ∇φᵢ = shape_gradient(cellvalues_v, q_point, i)
+                for j in 1:n_basefuncs_v
+                    ∇φⱼ = shape_gradient(cellvalues_v, q_point, j)
+                    Kₑ[BlockIndex((v▄, v▄), (i, j))] -= ν * ∇φᵢ ⊡ ∇φⱼ * dΩ
+                end
+            end

Assemble local pressure and incompressibility blocks of $B^{\textrm{T}}$ and $B$.

            for j in 1:n_basefuncs_p
+                ψ = shape_value(cellvalues_p, q_point, j)
+                for i in 1:n_basefuncs_v
+                    divφ = shape_divergence(cellvalues_v, q_point, i)
+                    Kₑ[BlockIndex((v▄, p▄), (i, j))] += (divφ * ψ) * dΩ
+                    Kₑ[BlockIndex((p▄, v▄), (j, i))] += (ψ * divφ) * dΩ
+                end
+            end
+        end
+
+        # Assemble `Kₑ` into the Stokes matrix `K`.
+        assemble!(stiffness_assembler, celldofs(cell), Kₑ)
+    end
+    return K
+end;

Solution of the semi-discretized system via DifferentialEquations.jl

First we assemble the linear portions for efficiency. These matrices are assumed to be constant over time.

Note

To obtain the vortex street a small time step is important to resolve the small oscillation forming. The mesh size becomes important to "only" resolve the smaller vertices forming, but less important for the initial formation.

T = 6.0
+Δt₀ = 0.001
+Δt_save = 0.1
+
+M = allocate_matrix(dh);
+M = assemble_mass_matrix(cellvalues_v, cellvalues_p, M, dh);
+
+K = allocate_matrix(dh);
+K = assemble_stokes_matrix(cellvalues_v, cellvalues_p, ν, K, dh);

These are our initial conditions. We start from the zero solution, because it is trivially admissible if the Dirichlet conditions are zero everywhere on the Dirichlet boundary for $t=0$. Note that the time stepper is also doing fine if the Dirichlet condition is non-zero and not too pathological.

u₀ = zeros(ndofs(dh))
+apply!(u₀, ch);

DifferentialEquations assumes dense matrices by default, which is not feasible for semi-discretization of finite element models. We communicate that a sparse matrix with specified pattern should be utilized through the jac_prototyp argument. It is simple to see that the Jacobian and the stiffness matrix share the same sparsity pattern, since they share the same relation between trial and test functions.

jac_sparsity = sparse(K);

To apply the nonlinear portion of the Navier-Stokes problem we simply hand over the dof handler and cell values to the right-hand-side (RHS) as a parameter. Furthermore the pre-assembled linear part, our Stokes operator (which is time independent) is passed to save some additional runtime. To apply the time-dependent Dirichlet BCs, we also need to hand over the constraint handler. The basic idea to apply the Dirichlet BCs consistently is that we copy the current solution u, apply the Dirichlet BCs on the copy, evaluate the discretized RHS of the Navier-Stokes equations with this vector. Furthermore we pass down the Jacobian assembly manually. For the Jacobian we eliminate all rows and columns associated with constrained dofs. Also note that we eliminate the mass matrix beforehand in a similar fashion. This decouples the time evolution of the constrained dofs from the true unknowns. The correct solution is enforced by utilizing step and stage limiters. The correct norms are computed by passing down a custom norm which simply ignores all constrained dofs.

Note

An alternative strategy is to hook into the nonlinear and linear solvers and enforce the solution therein. However, this is not possible at the time of writing this tutorial.

apply!(M, ch)
+
+struct RHSparams
+    K::SparseMatrixCSC
+    ch::ConstraintHandler
+    dh::DofHandler
+    cellvalues_v::CellValues
+    u::Vector
+end
+p = RHSparams(K, ch, dh, cellvalues_v, copy(u₀))
+
+function ferrite_limiter!(u, _, p, t)
+    update!(p.ch, t)
+    return apply!(u, p.ch)
+end
+
+function navierstokes_rhs_element!(dvₑ, vₑ, cellvalues_v)
+    n_basefuncs = getnbasefunctions(cellvalues_v)
+    for q_point in 1:getnquadpoints(cellvalues_v)
+        dΩ = getdetJdV(cellvalues_v, q_point)
+        ∇v = function_gradient(cellvalues_v, q_point, vₑ)
+        v = function_value(cellvalues_v, q_point, vₑ)
+        for j in 1:n_basefuncs
+            φⱼ = shape_value(cellvalues_v, q_point, j)

Note that in Tensors.jl the definition $\textrm{grad} v = \nabla v$ holds. With this information it can be quickly shown in index notation that

\[[(v \cdot \nabla) v]_{\textrm{i}} = v_{\textrm{j}} (\partial_{\textrm{j}} v_{\textrm{i}}) = [v (\nabla v)^{\textrm{T}}]_{\textrm{i}}\]

where we should pay attentation to the transpose of the gradient.

            dvₑ[j] -= v ⋅ ∇v' ⋅ φⱼ * dΩ
+        end
+    end
+    return
+end
+
+function navierstokes!(du, u_uc, p::RHSparams, t)

Unpack the struct to save some allocations.

    @unpack K, ch, dh, cellvalues_v, u = p

We start by applying the time-dependent Dirichlet BCs. Note that we are not allowed to mutate u_uc! Furthermore not that we also can not pre- allocate a buffer for this variable variable if we want to use AD to derive the Jacobian matrix, which appears in stiff solvers. Therefore, for efficiency reasons, we simply pass down the jacobian analytically.

    u .= u_uc
+    update!(ch, t)
+    apply!(u, ch)

Now we apply the rhs of the Navier-Stokes equations

    # Linear contribution (Stokes operator)
+    mul!(du, K, u) # du .= K * u
+
+    # nonlinear contribution
+    v_range = dof_range(dh, :v)
+    n_basefuncs = getnbasefunctions(cellvalues_v)
+    vₑ = zeros(n_basefuncs)
+    duₑ = zeros(n_basefuncs)
+    for cell in CellIterator(dh)
+        Ferrite.reinit!(cellvalues_v, cell)
+        v_celldofs = @view celldofs(cell)[v_range]
+        vₑ .= @views u[v_celldofs]
+        fill!(duₑ, 0.0)
+        navierstokes_rhs_element!(duₑ, vₑ, cellvalues_v)
+        assemble!(du, v_celldofs, duₑ)
+    end
+    return
+end;
+
+function navierstokes_jac_element!(Jₑ, vₑ, cellvalues_v)
+    n_basefuncs = getnbasefunctions(cellvalues_v)
+    for q_point in 1:getnquadpoints(cellvalues_v)
+        dΩ = getdetJdV(cellvalues_v, q_point)
+        ∇v = function_gradient(cellvalues_v, q_point, vₑ)
+        v = function_value(cellvalues_v, q_point, vₑ)
+        for j in 1:n_basefuncs
+            φⱼ = shape_value(cellvalues_v, q_point, j)

Note that in Tensors.jl the definition $\textrm{grad} v = \nabla v$ holds. With this information it can be quickly shown in index notation that

\[[(v \cdot \nabla) v]_{\textrm{i}} = v_{\textrm{j}} (\partial_{\textrm{j}} v_{\textrm{i}}) = [v (\nabla v)^{\textrm{T}}]_{\textrm{i}}\]

where we should pay attentation to the transpose of the gradient.

            for i in 1:n_basefuncs
+                φᵢ = shape_value(cellvalues_v, q_point, i)
+                ∇φᵢ = shape_gradient(cellvalues_v, q_point, i)
+                Jₑ[j, i] -= (φᵢ ⋅ ∇v' + v ⋅ ∇φᵢ') ⋅ φⱼ * dΩ
+            end
+        end
+    end
+    return
+end
+
+function navierstokes_jac!(J, u_uc, p, t)

Unpack the struct to save some allocations.

    @unpack K, ch, dh, cellvalues_v, u = p

We start by applying the time-dependent Dirichlet BCs. Note that we are not allowed to mutate u_uc, so we use our buffer again.

    u .= u_uc
+    update!(ch, t)
+    apply!(u, ch)

Now we apply the Jacobian of the Navier-Stokes equations.

    # Linear contribution (Stokes operator)
+    # Here we assume that J has exactly the same structure as K by construction
+    nonzeros(J) .= nonzeros(K)
+
+    assembler = start_assemble(J; fillzero = false)
+
+    # Assemble variation of the nonlinear term
+    n_basefuncs = getnbasefunctions(cellvalues_v)
+    Jₑ = zeros(n_basefuncs, n_basefuncs)
+    vₑ = zeros(n_basefuncs)
+    v_range = dof_range(dh, :v)
+    for cell in CellIterator(dh)
+        Ferrite.reinit!(cellvalues_v, cell)
+        v_celldofs = @view celldofs(cell)[v_range]
+
+        vₑ .= @views u[v_celldofs]
+        fill!(Jₑ, 0.0)
+        navierstokes_jac_element!(Jₑ, vₑ, cellvalues_v)
+        assemble!(assembler, v_celldofs, Jₑ)
+    end

Finally we eliminate the constrained dofs from the Jacobian to decouple them in the nonlinear solver from the remaining system.

    return apply!(J, ch)
+end;

Finally, together with our pre-assembled mass matrix, we are now able to define our problem in mass matrix form.

rhs = ODEFunction(navierstokes!, mass_matrix = M; jac = navierstokes_jac!, jac_prototype = jac_sparsity)
+problem = ODEProblem(rhs, u₀, (0.0, T), p);

All norms must not depend on constrained dofs. A problem with the presented implementation is that we are currently unable to strictly enforce constraint everywhere in the internal time integration process of DifferentialEquations.jl, hence the values might differ, resulting in worse error estimates. We try to resolve this issue in the future. Volunteers are also welcome to take a look into this!

struct FreeDofErrorNorm
+    ch::ConstraintHandler
+end
+(fe_norm::FreeDofErrorNorm)(u::Union{AbstractFloat, Complex}, t) = DiffEqBase.ODE_DEFAULT_NORM(u, t)
+(fe_norm::FreeDofErrorNorm)(u::AbstractArray, t) = DiffEqBase.ODE_DEFAULT_NORM(u[fe_norm.ch.free_dofs], t)

Now we can put everything together by specifying how to solve the problem. We want to use an adaptive variant of the implicit Euler method. Further we enable the progress bar with the progress and progress_steps arguments. Finally we have to communicate the time step length and initialization algorithm. Since we start with a valid initial state we do not use one of DifferentialEquations.jl initialization algorithms.

DAE initialization

To visualize the result we export the grid and our fields to VTK-files, which can be viewed in ParaView by utilizing the corresponding pvd file.

timestepper = Rodas5P(autodiff = false, step_limiter! = ferrite_limiter!);
Debugging convergence issues

We can obtain some debug information from OrdinaryDiffEq by wrapping the following section into a debug logger.

integrator = init(
+    problem, timestepper; initializealg = NoInit(), dt = Δt₀,
+    adaptive = true, abstol = 1.0e-4, reltol = 1.0e-5,
+    progress = true, progress_steps = 1,
+    verbose = true, internalnorm = FreeDofErrorNorm(ch), d_discontinuities = [1.0]
+);
Export of solution

Exporting interpolated solutions of problems containing mass matrices is currently broken. Thus, the intervals iterator is used. Note that solve holds all solutions in the memory.

pvd = paraview_collection("vortex-street")
+for (step, (u, t)) in enumerate(intervals(integrator))
+    VTKGridFile("vortex-street-$step", dh) do vtk
+        write_solution(vtk, dh, u)
+        pvd[t] = vtk
+    end
+end
+vtk_save(pvd);

Plain program

Here follows a version of the program without any comments. The file is also available here: ns_vs_diffeq.jl.

using Ferrite, SparseArrays, BlockArrays, LinearAlgebra, UnPack, LinearSolve, WriteVTK
+
+using OrdinaryDiffEq
+
+ν = 1.0 / 1000.0; #dynamic viscosity
+
+using FerriteGmsh
+using FerriteGmsh: Gmsh
+Gmsh.initialize()
+gmsh.option.set_number("General.Verbosity", 2)
+dim = 2;
+
+rect_tag = gmsh.model.occ.add_rectangle(0, 0, 0, 1.1, 0.41)
+circle_tag = gmsh.model.occ.add_circle(0.2, 0.2, 0, 0.05)
+circle_curve_tag = gmsh.model.occ.add_curve_loop([circle_tag])
+circle_surf_tag = gmsh.model.occ.add_plane_surface([circle_curve_tag])
+gmsh.model.occ.cut([(dim, rect_tag)], [(dim, circle_surf_tag)])
+
+gmsh.model.occ.synchronize()
+
+bottomtag = gmsh.model.model.add_physical_group(dim - 1, [6], -1, "bottom")
+lefttag = gmsh.model.model.add_physical_group(dim - 1, [7], -1, "left")
+righttag = gmsh.model.model.add_physical_group(dim - 1, [8], -1, "right")
+toptag = gmsh.model.model.add_physical_group(dim - 1, [9], -1, "top")
+holetag = gmsh.model.model.add_physical_group(dim - 1, [5], -1, "hole")
+
+gmsh.option.setNumber("Mesh.Algorithm", 11)
+gmsh.option.setNumber("Mesh.MeshSizeFromCurvature", 20)
+gmsh.option.setNumber("Mesh.MeshSizeMax", 0.05)
+
+gmsh.model.mesh.generate(dim)
+grid = togrid()
+Gmsh.finalize();
+
+ip_v = Lagrange{RefQuadrilateral, 2}()^dim
+qr = QuadratureRule{RefQuadrilateral}(4)
+cellvalues_v = CellValues(qr, ip_v);
+
+ip_p = Lagrange{RefQuadrilateral, 1}()
+cellvalues_p = CellValues(qr, ip_p);
+
+dh = DofHandler(grid)
+add!(dh, :v, ip_v)
+add!(dh, :p, ip_p)
+close!(dh);
+
+ch = ConstraintHandler(dh);
+
+nosplip_facet_names = ["top", "bottom", "hole"];
+∂Ω_noslip = union(getfacetset.((grid,), nosplip_facet_names)...);
+noslip_bc = Dirichlet(:v, ∂Ω_noslip, (x, t) -> Vec((0.0, 0.0)), [1, 2])
+add!(ch, noslip_bc);
+
+∂Ω_inflow = getfacetset(grid, "left");
+
+vᵢₙ(t) = min(t * 1.5, 1.5) #inflow velocity
+
+parabolic_inflow_profile(x, t) = Vec((4 * vᵢₙ(t) * x[2] * (0.41 - x[2]) / 0.41^2, 0.0))
+inflow_bc = Dirichlet(:v, ∂Ω_inflow, parabolic_inflow_profile, [1, 2])
+add!(ch, inflow_bc);
+
+∂Ω_free = getfacetset(grid, "right");
+
+close!(ch)
+update!(ch, 0.0);
+
+function assemble_mass_matrix(cellvalues_v::CellValues, cellvalues_p::CellValues, M::SparseMatrixCSC, dh::DofHandler)
+    # Allocate a buffer for the local matrix and some helpers, together with the assembler.
+    n_basefuncs_v = getnbasefunctions(cellvalues_v)
+    n_basefuncs_p = getnbasefunctions(cellvalues_p)
+    n_basefuncs = n_basefuncs_v + n_basefuncs_p
+    v▄, p▄ = 1, 2
+    Mₑ = BlockedArray(zeros(n_basefuncs, n_basefuncs), [n_basefuncs_v, n_basefuncs_p], [n_basefuncs_v, n_basefuncs_p])
+
+    # It follows the assembly loop as explained in the basic tutorials.
+    mass_assembler = start_assemble(M)
+    for cell in CellIterator(dh)
+        fill!(Mₑ, 0)
+        Ferrite.reinit!(cellvalues_v, cell)
+
+        for q_point in 1:getnquadpoints(cellvalues_v)
+            dΩ = getdetJdV(cellvalues_v, q_point)
+            # Remember that we assemble a vector mass term, hence the dot product.
+            # There is only one time derivative on the left hand side, so only one mass block is non-zero.
+            for i in 1:n_basefuncs_v
+                φᵢ = shape_value(cellvalues_v, q_point, i)
+                for j in 1:n_basefuncs_v
+                    φⱼ = shape_value(cellvalues_v, q_point, j)
+                    Mₑ[BlockIndex((v▄, v▄), (i, j))] += φᵢ ⋅ φⱼ * dΩ
+                end
+            end
+        end
+        assemble!(mass_assembler, celldofs(cell), Mₑ)
+    end
+
+    return M
+end;
+
+function assemble_stokes_matrix(cellvalues_v::CellValues, cellvalues_p::CellValues, ν, K::SparseMatrixCSC, dh::DofHandler)
+    # Again, some buffers and helpers
+    n_basefuncs_v = getnbasefunctions(cellvalues_v)
+    n_basefuncs_p = getnbasefunctions(cellvalues_p)
+    n_basefuncs = n_basefuncs_v + n_basefuncs_p
+    v▄, p▄ = 1, 2
+    Kₑ = BlockedArray(zeros(n_basefuncs, n_basefuncs), [n_basefuncs_v, n_basefuncs_p], [n_basefuncs_v, n_basefuncs_p])
+
+    # Assembly loop
+    stiffness_assembler = start_assemble(K)
+    for cell in CellIterator(dh)
+        # Don't forget to initialize everything
+        fill!(Kₑ, 0)
+
+        Ferrite.reinit!(cellvalues_v, cell)
+        Ferrite.reinit!(cellvalues_p, cell)
+
+        for q_point in 1:getnquadpoints(cellvalues_v)
+            dΩ = getdetJdV(cellvalues_v, q_point)
+
+            for i in 1:n_basefuncs_v
+                ∇φᵢ = shape_gradient(cellvalues_v, q_point, i)
+                for j in 1:n_basefuncs_v
+                    ∇φⱼ = shape_gradient(cellvalues_v, q_point, j)
+                    Kₑ[BlockIndex((v▄, v▄), (i, j))] -= ν * ∇φᵢ ⊡ ∇φⱼ * dΩ
+                end
+            end
+
+            for j in 1:n_basefuncs_p
+                ψ = shape_value(cellvalues_p, q_point, j)
+                for i in 1:n_basefuncs_v
+                    divφ = shape_divergence(cellvalues_v, q_point, i)
+                    Kₑ[BlockIndex((v▄, p▄), (i, j))] += (divφ * ψ) * dΩ
+                    Kₑ[BlockIndex((p▄, v▄), (j, i))] += (ψ * divφ) * dΩ
+                end
+            end
+        end
+
+        # Assemble `Kₑ` into the Stokes matrix `K`.
+        assemble!(stiffness_assembler, celldofs(cell), Kₑ)
+    end
+    return K
+end;
+
+T = 6.0
+Δt₀ = 0.001
+Δt_save = 0.1
+
+M = allocate_matrix(dh);
+M = assemble_mass_matrix(cellvalues_v, cellvalues_p, M, dh);
+
+K = allocate_matrix(dh);
+K = assemble_stokes_matrix(cellvalues_v, cellvalues_p, ν, K, dh);
+
+u₀ = zeros(ndofs(dh))
+apply!(u₀, ch);
+
+jac_sparsity = sparse(K);
+
+apply!(M, ch)
+
+struct RHSparams
+    K::SparseMatrixCSC
+    ch::ConstraintHandler
+    dh::DofHandler
+    cellvalues_v::CellValues
+    u::Vector
+end
+p = RHSparams(K, ch, dh, cellvalues_v, copy(u₀))
+
+function ferrite_limiter!(u, _, p, t)
+    update!(p.ch, t)
+    return apply!(u, p.ch)
+end
+
+function navierstokes_rhs_element!(dvₑ, vₑ, cellvalues_v)
+    n_basefuncs = getnbasefunctions(cellvalues_v)
+    for q_point in 1:getnquadpoints(cellvalues_v)
+        dΩ = getdetJdV(cellvalues_v, q_point)
+        ∇v = function_gradient(cellvalues_v, q_point, vₑ)
+        v = function_value(cellvalues_v, q_point, vₑ)
+        for j in 1:n_basefuncs
+            φⱼ = shape_value(cellvalues_v, q_point, j)
+
+            dvₑ[j] -= v ⋅ ∇v' ⋅ φⱼ * dΩ
+        end
+    end
+    return
+end
+
+function navierstokes!(du, u_uc, p::RHSparams, t)
+
+    @unpack K, ch, dh, cellvalues_v, u = p
+
+    u .= u_uc
+    update!(ch, t)
+    apply!(u, ch)
+
+    # Linear contribution (Stokes operator)
+    mul!(du, K, u) # du .= K * u
+
+    # nonlinear contribution
+    v_range = dof_range(dh, :v)
+    n_basefuncs = getnbasefunctions(cellvalues_v)
+    vₑ = zeros(n_basefuncs)
+    duₑ = zeros(n_basefuncs)
+    for cell in CellIterator(dh)
+        Ferrite.reinit!(cellvalues_v, cell)
+        v_celldofs = @view celldofs(cell)[v_range]
+        vₑ .= @views u[v_celldofs]
+        fill!(duₑ, 0.0)
+        navierstokes_rhs_element!(duₑ, vₑ, cellvalues_v)
+        assemble!(du, v_celldofs, duₑ)
+    end
+    return
+end;
+
+function navierstokes_jac_element!(Jₑ, vₑ, cellvalues_v)
+    n_basefuncs = getnbasefunctions(cellvalues_v)
+    for q_point in 1:getnquadpoints(cellvalues_v)
+        dΩ = getdetJdV(cellvalues_v, q_point)
+        ∇v = function_gradient(cellvalues_v, q_point, vₑ)
+        v = function_value(cellvalues_v, q_point, vₑ)
+        for j in 1:n_basefuncs
+            φⱼ = shape_value(cellvalues_v, q_point, j)
+
+            for i in 1:n_basefuncs
+                φᵢ = shape_value(cellvalues_v, q_point, i)
+                ∇φᵢ = shape_gradient(cellvalues_v, q_point, i)
+                Jₑ[j, i] -= (φᵢ ⋅ ∇v' + v ⋅ ∇φᵢ') ⋅ φⱼ * dΩ
+            end
+        end
+    end
+    return
+end
+
+function navierstokes_jac!(J, u_uc, p, t)
+
+    @unpack K, ch, dh, cellvalues_v, u = p
+
+    u .= u_uc
+    update!(ch, t)
+    apply!(u, ch)
+
+    # Linear contribution (Stokes operator)
+    # Here we assume that J has exactly the same structure as K by construction
+    nonzeros(J) .= nonzeros(K)
+
+    assembler = start_assemble(J; fillzero = false)
+
+    # Assemble variation of the nonlinear term
+    n_basefuncs = getnbasefunctions(cellvalues_v)
+    Jₑ = zeros(n_basefuncs, n_basefuncs)
+    vₑ = zeros(n_basefuncs)
+    v_range = dof_range(dh, :v)
+    for cell in CellIterator(dh)
+        Ferrite.reinit!(cellvalues_v, cell)
+        v_celldofs = @view celldofs(cell)[v_range]
+
+        vₑ .= @views u[v_celldofs]
+        fill!(Jₑ, 0.0)
+        navierstokes_jac_element!(Jₑ, vₑ, cellvalues_v)
+        assemble!(assembler, v_celldofs, Jₑ)
+    end
+
+    return apply!(J, ch)
+end;
+
+rhs = ODEFunction(navierstokes!, mass_matrix = M; jac = navierstokes_jac!, jac_prototype = jac_sparsity)
+problem = ODEProblem(rhs, u₀, (0.0, T), p);
+
+struct FreeDofErrorNorm
+    ch::ConstraintHandler
+end
+(fe_norm::FreeDofErrorNorm)(u::Union{AbstractFloat, Complex}, t) = DiffEqBase.ODE_DEFAULT_NORM(u, t)
+(fe_norm::FreeDofErrorNorm)(u::AbstractArray, t) = DiffEqBase.ODE_DEFAULT_NORM(u[fe_norm.ch.free_dofs], t)
+
+timestepper = Rodas5P(autodiff = false, step_limiter! = ferrite_limiter!);
+
+integrator = init(
+    problem, timestepper; initializealg = NoInit(), dt = Δt₀,
+    adaptive = true, abstol = 1.0e-4, reltol = 1.0e-5,
+    progress = true, progress_steps = 1,
+    verbose = true, internalnorm = FreeDofErrorNorm(ch), d_discontinuities = [1.0]
+);
+
+pvd = paraview_collection("vortex-street")
+for (step, (u, t)) in enumerate(intervals(integrator))
+    VTKGridFile("vortex-street-$step", dh) do vtk
+        write_solution(vtk, dh, u)
+        pvd[t] = vtk
+    end
+end
+vtk_save(pvd);

This page was generated using Literate.jl.

diff --git a/previews/PR798/tutorials/nsdiffeq.gif b/previews/PR798/tutorials/nsdiffeq.gif new file mode 100644 index 0000000000..c7ff2b77c0 Binary files /dev/null and b/previews/PR798/tutorials/nsdiffeq.gif differ diff --git a/previews/PR798/tutorials/periodic-rve-coarse.msh b/previews/PR798/tutorials/periodic-rve-coarse.msh new file mode 100644 index 0000000000..5cae326522 --- /dev/null +++ b/previews/PR798/tutorials/periodic-rve-coarse.msh @@ -0,0 +1,561 @@ +$MeshFormat +4.1 0 8 +$EndMeshFormat +$PhysicalNames +6 +1 21 "left" +1 22 "right" +1 23 "bottom" +1 24 "top" +2 19 "inclusions" +2 20 "matrix" +$EndPhysicalNames +$Entities +14 18 6 0 +1 0.2 -0.5 0 0 +2 -0.2 -0.4999999999999955 0 0 +3 -0.5 -0.5 0 0 +4 -0.5000000000000002 -0.2 0 0 +5 -0.3 0 0 0 +6 -0.5000000000000002 0.2 0 0 +7 -0.5 0.5 0 0 +8 -0.2 0.4999999999999956 0 0 +9 0.2 0.5 0 0 +10 0.5 0.5 0 0 +11 0.4999999999999999 0.2 0 0 +12 0.4999999999999998 -0.2 0 0 +13 0.5 -0.5 0 0 +14 0.2 0 0 0 +1 -0.2000001 -0.5000000999999999 -1e-07 0.2000001 -0.2999999 1e-07 0 2 1 -2 +2 -0.5000000999999999 -0.5000000999999999 -1e-07 -0.1999999 -0.4999999 1e-07 1 23 2 3 -2 +3 -0.5000000999999999 -0.5000000999999999 -1e-07 -0.4999999 -0.1999999 1e-07 1 21 2 4 -3 +4 -0.5000000999999999 -0.2000001 -1e-07 -0.2999999 9.999999994736442e-08 1e-07 0 2 4 -5 +5 -0.5000000999999999 -1.000000000028756e-07 -1e-07 -0.2999999 0.2000001 1e-07 0 2 5 -6 +6 -0.5000000999999999 0.1999999 -1e-07 -0.4999999 0.5000000999999999 1e-07 1 21 2 7 -6 +7 -0.5000000999999999 0.4999999 -1e-07 -0.1999999 0.5000000999999999 1e-07 1 24 2 8 -7 +8 -0.2000001 0.2999999 -1e-07 0.2000001 0.5000000999999999 1e-07 0 2 8 -9 +9 0.1999999 0.4999999 -1e-07 0.5000000999999999 0.5000000999999999 1e-07 1 24 2 10 -9 +10 0.4999999 0.1999999 -1e-07 0.5000000999999999 0.5000000999999999 1e-07 1 22 2 11 -10 +11 0.2999999 -0.2000001 -1e-07 0.5000000999999999 0.2000001 1e-07 0 2 11 -12 +12 0.4999999 -0.5000000999999999 -1e-07 0.5000000999999999 -0.1999999 1e-07 1 22 2 13 -12 +13 0.1999999 -0.5000000999999999 -1e-07 0.5000000999999999 -0.4999999 1e-07 1 23 2 1 -13 +14 -0.2000001 -0.2000001 -1e-07 0.2000001 0.2000001 1e-07 0 2 14 -14 +15 -0.2000001 -0.5000000999999999 -1e-07 0.2000001 -0.4999999 1e-07 1 23 2 2 -1 +16 0.4999999 -0.2000001 -1e-07 0.5000000999999999 0.2000001 1e-07 1 22 2 12 -11 +17 -0.5000000999999999 -0.2000001 -1e-07 -0.4999999 0.2000001 1e-07 1 21 2 6 -4 +18 -0.2000001 0.4999999 -1e-07 0.2000001 0.5000000999999999 1e-07 1 24 2 9 -8 +3 -0.2000001 -0.2000001 -1e-07 0.2000001 0.2000001 1e-07 1 19 1 14 +4 -0.5000000999999999 -0.5000000999999999 -1e-07 0.5000000999999999 0.5000000999999999 1e-07 1 20 14 -1 13 12 -11 10 9 -8 7 6 -5 -4 3 2 -14 +5 -0.2000001 -0.5000000999999999 -1e-07 0.2000001 -0.2999999 1e-07 1 19 2 15 1 +6 0.2999999 -0.2000001 -1e-07 0.5000000999999999 0.2000001 1e-07 1 19 2 11 16 +7 -0.5000000999999999 -0.2000001 -1e-07 -0.2999999 0.2000001 1e-07 1 19 3 17 4 5 +8 -0.2000001 0.2999999 -1e-07 0.2000001 0.5000000999999999 1e-07 1 19 2 8 18 +$EndEntities +$Nodes +38 112 1 112 +0 1 0 1 +1 +0.2 -0.5 0 +0 2 0 1 +2 +-0.2 -0.4999999999999955 0 +0 3 0 1 +3 +-0.5 -0.5 0 +0 4 0 1 +4 +-0.5000000000000002 -0.2 0 +0 5 0 1 +5 +-0.3 0 0 +0 6 0 1 +6 +-0.5000000000000002 0.2 0 +0 7 0 1 +7 +-0.5 0.5 0 +0 8 0 1 +8 +-0.2 0.4999999999999956 0 +0 9 0 1 +9 +0.2 0.5 0 +0 10 0 1 +10 +0.5 0.5 0 +0 11 0 1 +11 +0.4999999999999999 0.2 0 +0 12 0 1 +12 +0.4999999999999998 -0.2 0 +0 13 0 1 +13 +0.5 -0.5 0 +0 14 0 1 +14 +0.2 0 0 +1 1 0 4 +15 +16 +17 +18 +0.1618033988749894 -0.3824429495415053 0 +0.0618033988749892 -0.3097886967409692 0 +-0.06180339887498981 -0.3097886967409694 0 +-0.1618033988749897 -0.3824429495415057 0 +1 2 0 2 +19 +20 +-0.4 -0.5 0 +-0.2999999999999999 -0.5 0 +1 3 0 2 +21 +22 +-0.5 -0.3 0 +-0.5 -0.4 0 +1 4 0 2 +23 +24 +-0.4 -0.1732050807568877 0 +-0.3267949192431123 -0.1000000000000001 0 +1 5 0 2 +25 +26 +-0.3267949192431123 0.1000000000000001 0 +-0.4000000000000001 0.1732050807568878 0 +1 6 0 2 +27 +28 +-0.5 0.4 0 +-0.5 0.2999999999999999 0 +1 7 0 2 +29 +30 +-0.3 0.5 0 +-0.4 0.5 0 +1 8 0 4 +31 +32 +33 +34 +-0.1618033988749895 0.3824429495415053 0 +-0.06180339887498935 0.3097886967409692 0 +0.06180339887498979 0.3097886967409694 0 +0.1618033988749898 0.3824429495415058 0 +1 9 0 2 +35 +36 +0.4 0.5 0 +0.2999999999999999 0.5 0 +1 10 0 2 +37 +38 +0.5 0.3 0 +0.5 0.4 0 +1 11 0 4 +39 +40 +41 +42 +0.3824429495415053 0.1618033988749895 0 +0.3097886967409692 0.06180339887498934 0 +0.3097886967409694 -0.06180339887498981 0 +0.3824429495415058 -0.1618033988749898 0 +1 12 0 2 +43 +44 +0.5 -0.4 0 +0.5 -0.2999999999999999 0 +1 13 0 2 +45 +46 +0.3 -0.5 0 +0.4 -0.5 0 +1 14 0 8 +47 +48 +49 +50 +51 +52 +53 +54 +0.1532088886237956 0.128557521937308 0 +0.03472963553338573 0.1969615506024417 0 +-0.1000000000000004 0.1732050807568875 0 +-0.1879385241571819 0.06840402866513311 0 +-0.1879385241571815 -0.06840402866513423 0 +-0.09999999999999963 -0.173205080756888 0 +0.0347296355333867 -0.1969615506024415 0 +0.1532088886237963 -0.1285575219373071 0 +1 15 0 2 +55 +56 +-0.06666666666666649 -0.5 0 +0.06666666666666721 -0.5 0 +1 16 0 2 +57 +58 +0.5 -0.06666666666666649 0 +0.5 0.06666666666666721 0 +1 17 0 2 +59 +60 +-0.5 0.06666666666666649 0 +-0.5 -0.06666666666666721 0 +1 18 0 2 +61 +62 +0.06666666666666649 0.5 0 +-0.06666666666666721 0.5 0 +2 3 0 4 +63 +64 +65 +66 +0.06527036446661494 -0.02375646984555406 0 +-0.06299525026907359 0.02292839599809257 0 +-0.01736481776669272 -0.09848077530122061 0 +0.04999999999999997 0.08660254037844367 0 +2 4 0 41 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +-0.3497231749973342 -0.3968434059794638 0 +-0.416829225768484 0.3460550539379946 0 +0.3499377195224841 0.4049101557198651 0 +0.4232985522399446 -0.3514144819390383 0 +-0.3141526090785057 -0.2063881984494136 0 +-0.3107772909559421 0.2032395211026326 0 +-0.3667951547104187 -0.2793356112594476 0 +-0.2617107649701464 -0.3042168336934321 0 +0.1733485045939141 -0.2421999228511857 0 +0.264628905221344 -0.3353597820766629 0 +0.1719773033044808 0.2420239350750671 0 +0.2643634229141842 0.3282189693125515 0 +0.3046574312186154 -0.2362029824952383 0 +0.2885975724946423 0.2286833208792526 0 +0.3584958189862575 0.2887024332248267 0 +-0.3559272585202332 0.4217043541698738 0 +0.3472285749558256 -0.4119303556871165 0 +-0.3599824953701782 0.2701440844462686 0 +-0.2602849966190637 0.2959691352690023 0 +-0.1617365412580795 0.2695333065994889 0 +-0.1650933320584841 -0.2727685225643594 0 +-0.4321369585972424 -0.3429336095583846 0 +-0.253680039282744 -0.4157060408302236 0 +-0.2685055781569272 0.407970297896927 0 +0.4138641447877809 -0.2543703371381743 0 +0.25 -0.4215268940232576 0 +0.2553687243022512 0.4225510214347913 0 +0.4302106092351 0.3470739431243619 0 +-0.2098663036975409 0.179519579826169 0 +-0.2102294603450262 -0.1815063993134219 0 +-0.4292746426276027 0.4288064939538295 0 +0.4273040735444944 -0.4273040735444945 0 +-0.4332560012564908 -0.428996100992158 0 +-0.4389856439503024 0.2546903672099139 0 +-0.4430474206192116 -0.2524265391192383 0 +-0.3330178149272774 0.3477053484011808 0 +0.4350037166891992 0.4328301863593541 0 +0.3507355216847021 -0.3178555878672461 0 +0.4378264002126588 0.2560008834421256 0 +0.2489309771843098 -0.1326748883100445 0 +0.2510025684508988 0.1371452626069344 0 +2 5 0 1 +108 +-1.2094325381086e-16 -0.4041610814281969 0 +2 6 0 1 +109 +0.4041610814281969 -5.818858729986652e-17 0 +2 7 0 2 +110 +111 +-0.4064253521005499 -0.03124451516445637 0 +-0.4117571933601877 0.08573798977015037 0 +2 8 0 1 +112 +1.103563378790027e-16 0.4041610814281968 0 +$EndNodes +$Elements +18 222 1 222 +1 2 1 3 +1 3 19 +2 19 20 +3 20 2 +1 3 1 3 +4 4 21 +5 21 22 +6 22 3 +1 6 1 3 +7 7 27 +8 27 28 +9 28 6 +1 7 1 3 +10 8 29 +11 29 30 +12 30 7 +1 9 1 3 +13 10 35 +14 35 36 +15 36 9 +1 10 1 3 +16 11 37 +17 37 38 +18 38 10 +1 12 1 3 +19 13 43 +20 43 44 +21 44 12 +1 13 1 3 +22 1 45 +23 45 46 +24 46 13 +1 15 1 3 +25 2 55 +26 55 56 +27 56 1 +1 16 1 3 +28 12 57 +29 57 58 +30 58 11 +1 17 1 3 +31 6 59 +32 59 60 +33 60 4 +1 18 1 3 +34 9 61 +35 61 62 +36 62 8 +2 3 2 15 +37 51 65 64 +38 51 64 50 +39 63 65 54 +40 54 65 53 +41 14 66 63 +42 64 66 49 +43 47 66 14 +44 52 65 51 +45 49 66 48 +46 50 64 49 +47 14 63 54 +48 64 65 63 +49 63 66 64 +50 53 65 52 +51 48 66 47 +2 4 2 136 +52 67 74 73 +53 73 88 67 +54 71 96 24 +55 24 96 51 +56 5 24 51 +57 50 25 5 +58 52 17 53 +59 48 32 49 +60 25 95 72 +61 50 95 25 +62 47 77 48 +63 53 75 54 +64 78 81 69 +65 53 17 16 +66 48 33 32 +67 48 77 33 +68 16 75 53 +69 67 89 74 +70 50 5 51 +71 72 95 85 +72 81 94 69 +73 74 96 71 +74 31 90 85 +75 85 95 86 +76 79 104 91 +77 87 96 74 +78 85 86 31 +79 77 107 80 +80 79 106 75 +81 74 89 18 +82 79 91 42 +83 47 107 77 +84 75 106 54 +85 18 87 74 +86 80 81 78 +87 40 14 41 +88 15 76 75 +89 20 67 19 +90 28 68 27 +91 39 81 80 +92 23 73 71 +93 32 86 49 +94 33 77 34 +95 15 75 16 +96 52 87 17 +97 36 69 35 +98 44 70 43 +99 39 107 40 +100 41 106 42 +101 11 105 39 +102 23 71 24 +103 25 72 26 +104 18 89 2 +105 8 90 31 +106 1 92 15 +107 34 93 9 +108 42 91 12 +109 83 104 76 +110 70 104 83 +111 73 74 71 +112 14 106 41 +113 40 107 14 +114 80 107 39 +115 42 106 79 +116 94 105 37 +117 54 106 14 +118 14 107 47 +119 76 79 75 +120 69 93 78 +121 77 78 34 +122 30 82 29 +123 22 88 21 +124 46 83 45 +125 2 89 20 +126 76 92 83 +127 29 90 8 +128 12 91 44 +129 7 97 30 +130 27 97 7 +131 38 94 37 +132 39 105 81 +133 13 98 46 +134 43 98 13 +135 28 100 68 +136 90 102 85 +137 88 101 21 +138 36 93 69 +139 20 89 67 +140 15 92 76 +141 77 80 78 +142 31 86 32 +143 17 87 18 +144 72 85 84 +145 72 84 26 +146 45 92 1 +147 44 91 70 +148 19 99 3 +149 3 99 22 +150 9 93 36 +151 83 92 45 +152 82 90 29 +153 51 96 52 +154 68 100 84 +155 73 101 88 +156 81 105 94 +157 49 95 50 +158 82 102 90 +159 10 103 38 +160 35 103 10 +161 26 100 6 +162 4 101 23 +163 23 101 73 +164 78 93 34 +165 76 104 79 +166 67 99 19 +167 69 103 35 +168 68 97 27 +169 70 98 43 +170 6 100 28 +171 21 101 4 +172 37 105 11 +173 85 102 84 +174 52 96 87 +175 84 100 26 +176 86 95 49 +177 84 102 68 +178 82 97 68 +179 30 97 82 +180 46 98 83 +181 88 99 67 +182 83 98 70 +183 68 102 82 +184 22 99 88 +185 94 103 69 +186 91 104 70 +187 38 103 94 +2 5 2 8 +188 55 108 18 +189 15 108 56 +190 18 108 17 +191 16 108 15 +192 2 55 18 +193 1 15 56 +194 17 108 16 +195 56 108 55 +2 6 2 8 +196 39 109 58 +197 57 109 42 +198 42 109 41 +199 40 109 39 +200 42 12 57 +201 11 39 58 +202 41 109 40 +203 58 109 57 +2 7 2 11 +204 24 110 23 +205 23 60 4 +206 23 110 60 +207 25 111 5 +208 6 111 26 +209 5 111 110 +210 59 111 6 +211 60 110 59 +212 110 111 59 +213 5 110 24 +214 26 111 25 +2 8 2 8 +215 31 112 62 +216 61 112 34 +217 32 112 31 +218 34 112 33 +219 8 31 62 +220 34 9 61 +221 33 112 32 +222 62 112 61 +$EndElements diff --git a/previews/PR798/tutorials/periodic-rve.msh b/previews/PR798/tutorials/periodic-rve.msh new file mode 100644 index 0000000000..59a5637405 --- /dev/null +++ b/previews/PR798/tutorials/periodic-rve.msh @@ -0,0 +1,24501 @@ +$MeshFormat +4.1 0 8 +$EndMeshFormat +$PhysicalNames +6 +1 21 "left" +1 22 "right" +1 23 "bottom" +1 24 "top" +2 19 "inclusions" +2 20 "matrix" +$EndPhysicalNames +$Entities +14 18 6 0 +1 0.2 -0.5 0 0 +2 -0.2 -0.4999999999999955 0 0 +3 -0.5 -0.5 0 0 +4 -0.5000000000000002 -0.2 0 0 +5 -0.3 0 0 0 +6 -0.5000000000000002 0.2 0 0 +7 -0.5 0.5 0 0 +8 -0.2 0.4999999999999956 0 0 +9 0.2 0.5 0 0 +10 0.5 0.5 0 0 +11 0.4999999999999999 0.2 0 0 +12 0.4999999999999998 -0.2 0 0 +13 0.5 -0.5 0 0 +14 0.2 0 0 0 +1 -0.2000001 -0.5000000999999999 -1e-07 0.2000001 -0.2999999 1e-07 0 2 1 -2 +2 -0.5000000999999999 -0.5000000999999999 -1e-07 -0.1999999 -0.4999999 1e-07 1 23 2 3 -2 +3 -0.5000000999999999 -0.5000000999999999 -1e-07 -0.4999999 -0.1999999 1e-07 1 21 2 4 -3 +4 -0.5000000999999999 -0.2000001 -1e-07 -0.2999999 9.999999994736442e-08 1e-07 0 2 4 -5 +5 -0.5000000999999999 -1.000000000028756e-07 -1e-07 -0.2999999 0.2000001 1e-07 0 2 5 -6 +6 -0.5000000999999999 0.1999999 -1e-07 -0.4999999 0.5000000999999999 1e-07 1 21 2 7 -6 +7 -0.5000000999999999 0.4999999 -1e-07 -0.1999999 0.5000000999999999 1e-07 1 24 2 8 -7 +8 -0.2000001 0.2999999 -1e-07 0.2000001 0.5000000999999999 1e-07 0 2 8 -9 +9 0.1999999 0.4999999 -1e-07 0.5000000999999999 0.5000000999999999 1e-07 1 24 2 10 -9 +10 0.4999999 0.1999999 -1e-07 0.5000000999999999 0.5000000999999999 1e-07 1 22 2 11 -10 +11 0.2999999 -0.2000001 -1e-07 0.5000000999999999 0.2000001 1e-07 0 2 11 -12 +12 0.4999999 -0.5000000999999999 -1e-07 0.5000000999999999 -0.1999999 1e-07 1 22 2 13 -12 +13 0.1999999 -0.5000000999999999 -1e-07 0.5000000999999999 -0.4999999 1e-07 1 23 2 1 -13 +14 -0.2000001 -0.2000001 -1e-07 0.2000001 0.2000001 1e-07 0 2 14 -14 +15 -0.2000001 -0.5000000999999999 -1e-07 0.2000001 -0.4999999 1e-07 1 23 2 2 -1 +16 0.4999999 -0.2000001 -1e-07 0.5000000999999999 0.2000001 1e-07 1 22 2 12 -11 +17 -0.5000000999999999 -0.2000001 -1e-07 -0.4999999 0.2000001 1e-07 1 21 2 6 -4 +18 -0.2000001 0.4999999 -1e-07 0.2000001 0.5000000999999999 1e-07 1 24 2 9 -8 +3 -0.2000001 -0.2000001 -1e-07 0.2000001 0.2000001 1e-07 1 19 1 14 +4 -0.5000000999999999 -0.5000000999999999 -1e-07 0.5000000999999999 0.5000000999999999 1e-07 1 20 14 -1 13 12 -11 10 9 -8 7 6 -5 -4 3 2 -14 +5 -0.2000001 -0.5000000999999999 -1e-07 0.2000001 -0.2999999 1e-07 1 19 2 15 1 +6 0.2999999 -0.2000001 -1e-07 0.5000000999999999 0.2000001 1e-07 1 19 2 11 16 +7 -0.5000000999999999 -0.2000001 -1e-07 -0.2999999 0.2000001 1e-07 1 19 3 17 4 5 +8 -0.2000001 0.2999999 -1e-07 0.2000001 0.5000000999999999 1e-07 1 19 2 8 18 +$EndEntities +$Nodes +38 6097 1 6097 +0 1 0 1 +1 +0.2 -0.5 0 +0 2 0 1 +2 +-0.2 -0.4999999999999955 0 +0 3 0 1 +3 +-0.5 -0.5 0 +0 4 0 1 +4 +-0.5000000000000002 -0.2 0 +0 5 0 1 +5 +-0.3 0 0 +0 6 0 1 +6 +-0.5000000000000002 0.2 0 +0 7 0 1 +7 +-0.5 0.5 0 +0 8 0 1 +8 +-0.2 0.4999999999999956 0 +0 9 0 1 +9 +0.2 0.5 0 +0 10 0 1 +10 +0.5 0.5 0 +0 11 0 1 +11 +0.4999999999999999 0.2 0 +0 12 0 1 +12 +0.4999999999999998 -0.2 0 +0 13 0 1 +13 +0.5 -0.5 0 +0 14 0 1 +14 +0.2 0 0 +1 1 0 39 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +0.1993834667466256 -0.484308180854431 0 +0.1975376681190275 -0.4687131069919538 0 +0.1944739840795353 -0.4533109272288189 0 +0.1902113032590307 -0.4381966011250105 0 +0.1847759065022573 -0.423463313526982 0 +0.1782013048376735 -0.4092019000520906 0 +0.1705280328708184 -0.3955002870568101 0 +0.1618033988749894 -0.3824429495415053 0 +0.1520811931200061 -0.3701103903339631 0 +0.1414213562373094 -0.3585786437626903 0 +0.1298896096660366 -0.3479188068799937 0 +0.1175570504584945 -0.3381966011250104 0 +0.1044997129431896 -0.3294719671291815 0 +0.09079809994790913 -0.3217986951623263 0 +0.07653668647301771 -0.3152240934977425 0 +0.0618033988749892 -0.3097886967409692 0 +0.04668907277118079 -0.3055260159204646 0 +0.03128689300804588 -0.3024623318809724 0 +0.01569181914556869 -0.3006165332533743 0 +-2.986159789035704e-16 -0.3 0 +-0.01569181914556929 -0.3006165332533745 0 +-0.03128689300804651 -0.3024623318809725 0 +-0.04668907277118142 -0.3055260159204647 0 +-0.06180339887498981 -0.3097886967409694 0 +-0.07653668647301827 -0.3152240934977428 0 +-0.09079809994790966 -0.3217986951623266 0 +-0.1044997129431901 -0.3294719671291817 0 +-0.1175570504584949 -0.3381966011250107 0 +-0.129889609666037 -0.347918806879994 0 +-0.1414213562373098 -0.3585786437626907 0 +-0.1520811931200065 -0.3701103903339636 0 +-0.1618033988749897 -0.3824429495415057 0 +-0.1705280328708186 -0.3955002870568105 0 +-0.1782013048376737 -0.4092019000520908 0 +-0.1847759065022574 -0.4234633135269822 0 +-0.1902113032590308 -0.4381966011250107 0 +-0.1944739840795353 -0.453310927228819 0 +-0.1975376681190276 -0.4687131069919539 0 +-0.1993834667466256 -0.484308180854431 0 +1 2 0 23 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +-0.4875 -0.5 0 +-0.475 -0.5 0 +-0.4625 -0.5 0 +-0.45 -0.5 0 +-0.4375 -0.5 0 +-0.425 -0.5 0 +-0.4125 -0.5 0 +-0.4 -0.5 0 +-0.3875 -0.5 0 +-0.375 -0.5 0 +-0.3625 -0.5 0 +-0.35 -0.5 0 +-0.3375 -0.5 0 +-0.325 -0.5 0 +-0.3124999999999999 -0.5 0 +-0.2999999999999999 -0.5 0 +-0.2875 -0.5 0 +-0.275 -0.5 0 +-0.2625 -0.5 0 +-0.25 -0.5 0 +-0.2375 -0.5 0 +-0.225 -0.5 0 +-0.2125 -0.5 0 +1 3 0 23 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +-0.5 -0.2124999999999999 0 +-0.5 -0.225 0 +-0.5 -0.2375 0 +-0.5 -0.25 0 +-0.5 -0.2625 0 +-0.5 -0.275 0 +-0.5 -0.2875000000000001 0 +-0.5 -0.3 0 +-0.5 -0.3125 0 +-0.5 -0.3250000000000001 0 +-0.5 -0.3375000000000001 0 +-0.5 -0.3500000000000001 0 +-0.5 -0.3625 0 +-0.5 -0.375 0 +-0.5 -0.3875 0 +-0.5 -0.4 0 +-0.5 -0.4125 0 +-0.5 -0.4249999999999999 0 +-0.5 -0.4375 0 +-0.5 -0.45 0 +-0.5 -0.4624999999999999 0 +-0.5 -0.475 0 +-0.5 -0.4875 0 +1 4 0 23 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +-0.4869193741539714 -0.1995717846477207 0 +-0.4738947615559898 -0.1982889722747621 0 +-0.4609819355967745 -0.1961570560806462 0 +-0.448236190979496 -0.1931851652578137 0 +-0.4357121069393677 -0.1893860258990211 0 +-0.423463313526982 -0.1847759065022573 0 +-0.4115422619561996 -0.1793745483065376 0 +-0.4 -0.1732050807568877 0 +-0.3888859533960796 -0.1662939224605091 0 +-0.3782477141982559 -0.158670668058247 0 +-0.3681308369799862 -0.1503679614957955 0 +-0.3585786437626906 -0.1414213562373095 0 +-0.3496320385042045 -0.1318691630200138 0 +-0.341329331941753 -0.1217522858017442 0 +-0.3337060775394909 -0.1111140466039204 0 +-0.3267949192431123 -0.1000000000000001 0 +-0.3206254516934625 -0.08845773804380044 0 +-0.3152240934977427 -0.07653668647301809 0 +-0.3106139741009789 -0.06428789306063237 0 +-0.3068148347421863 -0.05176380902050414 0 +-0.3038429439193538 -0.03901806440322558 0 +-0.3017110277252379 -0.02610523844401034 0 +-0.3004282153522793 -0.01308062584602874 0 +1 5 0 23 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +-0.3004282153522793 0.01308062584602863 0 +-0.3017110277252379 0.02610523844401035 0 +-0.3038429439193539 0.03901806440322569 0 +-0.3068148347421863 0.0517638090205042 0 +-0.3106139741009789 0.06428789306063237 0 +-0.3152240934977427 0.07653668647301803 0 +-0.3206254516934623 0.08845773804380033 0 +-0.3267949192431123 0.1000000000000001 0 +-0.333706077539491 0.1111140466039205 0 +-0.341329331941753 0.1217522858017442 0 +-0.3496320385042045 0.1318691630200139 0 +-0.3585786437626906 0.1414213562373096 0 +-0.3681308369799863 0.1503679614957956 0 +-0.378247714198256 0.1586706680582471 0 +-0.3888859533960796 0.1662939224605091 0 +-0.4000000000000001 0.1732050807568878 0 +-0.4115422619561998 0.1793745483065377 0 +-0.4234633135269821 0.1847759065022574 0 +-0.4357121069393677 0.1893860258990212 0 +-0.4482361909794959 0.1931851652578137 0 +-0.4609819355967744 0.1961570560806461 0 +-0.4738947615559897 0.1982889722747621 0 +-0.4869193741539714 0.1995717846477207 0 +1 6 0 23 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +-0.5 0.4875 0 +-0.5 0.475 0 +-0.5 0.4625 0 +-0.5 0.45 0 +-0.5 0.4375 0 +-0.5 0.425 0 +-0.5 0.4125 0 +-0.5 0.4 0 +-0.5 0.3875 0 +-0.5 0.375 0 +-0.5 0.3625 0 +-0.5 0.35 0 +-0.5 0.3375 0 +-0.5 0.325 0 +-0.5 0.3124999999999999 0 +-0.5 0.2999999999999999 0 +-0.5 0.2875 0 +-0.5 0.275 0 +-0.5 0.2625 0 +-0.5 0.25 0 +-0.5 0.2375 0 +-0.5 0.225 0 +-0.5 0.2125 0 +1 7 0 23 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +-0.2124999999999999 0.5 0 +-0.225 0.5 0 +-0.2375 0.5 0 +-0.25 0.5 0 +-0.2625 0.5 0 +-0.275 0.5 0 +-0.2875000000000001 0.5 0 +-0.3 0.5 0 +-0.3125 0.5 0 +-0.3250000000000001 0.5 0 +-0.3375000000000001 0.5 0 +-0.3500000000000001 0.5 0 +-0.3625 0.5 0 +-0.375 0.5 0 +-0.3875 0.5 0 +-0.4 0.5 0 +-0.4125 0.5 0 +-0.4249999999999999 0.5 0 +-0.4375 0.5 0 +-0.45 0.5 0 +-0.4624999999999999 0.5 0 +-0.475 0.5 0 +-0.4875 0.5 0 +1 8 0 39 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +-0.1993834667466256 0.4843081808544311 0 +-0.1975376681190276 0.4687131069919538 0 +-0.1944739840795353 0.4533109272288189 0 +-0.1902113032590307 0.4381966011250105 0 +-0.1847759065022574 0.4234633135269821 0 +-0.1782013048376736 0.4092019000520907 0 +-0.1705280328708184 0.3955002870568102 0 +-0.1618033988749895 0.3824429495415053 0 +-0.1520811931200061 0.3701103903339631 0 +-0.1414213562373094 0.3585786437626904 0 +-0.1298896096660365 0.3479188068799937 0 +-0.1175570504584945 0.3381966011250104 0 +-0.1044997129431896 0.3294719671291814 0 +-0.09079809994790923 0.3217986951623263 0 +-0.07653668647301774 0.3152240934977426 0 +-0.06180339887498935 0.3097886967409692 0 +-0.04668907277118086 0.3055260159204646 0 +-0.03128689300804604 0.3024623318809724 0 +-0.01569181914556876 0.3006165332533743 0 +1.408962799656044e-16 0.3 0 +0.01569181914556922 0.3006165332533745 0 +0.03128689300804648 0.3024623318809725 0 +0.04668907277118148 0.3055260159204648 0 +0.06180339887498979 0.3097886967409694 0 +0.07653668647301833 0.3152240934977428 0 +0.09079809994790965 0.3217986951623266 0 +0.1044997129431901 0.3294719671291818 0 +0.1175570504584949 0.3381966011250107 0 +0.129889609666037 0.3479188068799941 0 +0.1414213562373099 0.3585786437626908 0 +0.1520811931200066 0.3701103903339637 0 +0.1618033988749898 0.3824429495415058 0 +0.1705280328708187 0.3955002870568106 0 +0.1782013048376737 0.4092019000520909 0 +0.1847759065022574 0.4234633135269822 0 +0.1902113032590308 0.4381966011250106 0 +0.1944739840795353 0.4533109272288189 0 +0.1975376681190275 0.4687131069919538 0 +0.1993834667466256 0.4843081808544311 0 +1 9 0 23 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +0.4875 0.5 0 +0.475 0.5 0 +0.4625 0.5 0 +0.45 0.5 0 +0.4375 0.5 0 +0.425 0.5 0 +0.4125 0.5 0 +0.4 0.5 0 +0.3875 0.5 0 +0.375 0.5 0 +0.3625 0.5 0 +0.35 0.5 0 +0.3375 0.5 0 +0.325 0.5 0 +0.3124999999999999 0.5 0 +0.2999999999999999 0.5 0 +0.2875 0.5 0 +0.275 0.5 0 +0.2625 0.5 0 +0.25 0.5 0 +0.2375 0.5 0 +0.225 0.5 0 +0.2125 0.5 0 +1 10 0 23 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +0.5 0.2124999999999999 0 +0.5 0.225 0 +0.5 0.2375 0 +0.5 0.25 0 +0.5 0.2625 0 +0.5 0.275 0 +0.5 0.2875000000000001 0 +0.5 0.3 0 +0.5 0.3125 0 +0.5 0.3250000000000001 0 +0.5 0.3375000000000001 0 +0.5 0.3500000000000001 0 +0.5 0.3625 0 +0.5 0.375 0 +0.5 0.3875 0 +0.5 0.4 0 +0.5 0.4125 0 +0.5 0.4249999999999999 0 +0.5 0.4375 0 +0.5 0.45 0 +0.5 0.4624999999999999 0 +0.5 0.475 0 +0.5 0.4875 0 +1 11 0 39 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +0.484308180854431 0.1993834667466256 0 +0.4687131069919538 0.1975376681190276 0 +0.4533109272288189 0.1944739840795353 0 +0.4381966011250105 0.1902113032590307 0 +0.423463313526982 0.1847759065022574 0 +0.4092019000520906 0.1782013048376735 0 +0.3955002870568102 0.1705280328708184 0 +0.3824429495415053 0.1618033988749895 0 +0.3701103903339632 0.1520811931200061 0 +0.3585786437626904 0.1414213562373094 0 +0.3479188068799937 0.1298896096660366 0 +0.3381966011250104 0.1175570504584945 0 +0.3294719671291815 0.1044997129431897 0 +0.3217986951623263 0.09079809994790922 0 +0.3152240934977426 0.07653668647301781 0 +0.3097886967409692 0.06180339887498934 0 +0.3055260159204646 0.04668907277118094 0 +0.3024623318809724 0.03128689300804603 0 +0.3006165332533743 0.01569181914556875 0 +0.3 -2.419605899270905e-16 0 +0.3006165332533745 -0.01569181914556923 0 +0.3024623318809725 -0.03128689300804641 0 +0.3055260159204647 -0.0466890727711814 0 +0.3097886967409694 -0.06180339887498981 0 +0.3152240934977427 -0.07653668647301826 0 +0.3217986951623266 -0.09079809994790966 0 +0.3294719671291818 -0.1044997129431901 0 +0.3381966011250107 -0.117557050458495 0 +0.3479188068799941 -0.129889609666037 0 +0.3585786437626908 -0.1414213562373098 0 +0.3701103903339635 -0.1520811931200065 0 +0.3824429495415058 -0.1618033988749898 0 +0.3955002870568106 -0.1705280328708187 0 +0.4092019000520909 -0.1782013048376737 0 +0.4234633135269822 -0.1847759065022574 0 +0.4381966011250106 -0.1902113032590308 0 +0.4533109272288189 -0.1944739840795353 0 +0.4687131069919538 -0.1975376681190275 0 +0.4843081808544311 -0.1993834667466256 0 +1 12 0 23 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +0.5 -0.4875 0 +0.5 -0.475 0 +0.5 -0.4625 0 +0.5 -0.45 0 +0.5 -0.4375 0 +0.5 -0.425 0 +0.5 -0.4125 0 +0.5 -0.4 0 +0.5 -0.3875 0 +0.5 -0.375 0 +0.5 -0.3625 0 +0.5 -0.35 0 +0.5 -0.3375 0 +0.5 -0.325 0 +0.5 -0.3124999999999999 0 +0.5 -0.2999999999999999 0 +0.5 -0.2875 0 +0.5 -0.275 0 +0.5 -0.2625 0 +0.5 -0.25 0 +0.5 -0.2375 0 +0.5 -0.225 0 +0.5 -0.2125 0 +1 13 0 23 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +0.2124999999999999 -0.5 0 +0.225 -0.5 0 +0.2375 -0.5 0 +0.25 -0.5 0 +0.2625 -0.5 0 +0.275 -0.5 0 +0.2875000000000001 -0.5 0 +0.3 -0.5 0 +0.3125 -0.5 0 +0.3250000000000001 -0.5 0 +0.3375000000000001 -0.5 0 +0.3500000000000001 -0.5 0 +0.3625 -0.5 0 +0.375 -0.5 0 +0.3875 -0.5 0 +0.4 -0.5 0 +0.4125 -0.5 0 +0.4249999999999999 -0.5 0 +0.4375 -0.5 0 +0.45 -0.5 0 +0.4624999999999999 -0.5 0 +0.475 -0.5 0 +0.4875 -0.5 0 +1 14 0 71 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417 +418 +419 +420 +421 +422 +423 +424 +425 +426 +427 +428 +429 +430 +431 +432 +0.1992389396183491 0.01743114854953165 0 +0.1969615506024416 0.0347296355333861 0 +0.1931851652578137 0.0517638090205042 0 +0.1879385241571817 0.06840402866513381 0 +0.18126155740733 0.08452365234813997 0 +0.1732050807568877 0.1000000000000001 0 +0.1638304088577983 0.1147152872702093 0 +0.1532088886237956 0.128557521937308 0 +0.1414213562373094 0.1414213562373096 0 +0.1285575219373077 0.1532088886237958 0 +0.114715287270209 0.1638304088577985 0 +0.0999999999999998 0.1732050807568879 0 +0.08452365234813965 0.1812615574073301 0 +0.06840402866513348 0.1879385241571818 0 +0.05176380902050385 0.1931851652578137 0 +0.03472963553338573 0.1969615506024417 0 +0.01743114854953132 0.1992389396183492 0 +-3.430248998885766e-16 0.2 0 +-0.017431148549532 0.1992389396183491 0 +-0.03472963553338645 0.1969615506024416 0 +-0.05176380902050452 0.1931851652578136 0 +-0.06840402866513412 0.1879385241571816 0 +-0.08452365234814027 0.1812615574073298 0 +-0.1000000000000004 0.1732050807568875 0 +-0.1147152872702095 0.1638304088577981 0 +-0.1285575219373082 0.1532088886237953 0 +-0.1414213562373098 0.1414213562373092 0 +-0.1532088886237959 0.1285575219373075 0 +-0.1638304088577987 0.1147152872702088 0 +-0.1732050807568881 0.09999999999999946 0 +-0.1812615574073303 0.08452365234813934 0 +-0.1879385241571819 0.06840402866513311 0 +-0.1931851652578138 0.05176380902050352 0 +-0.1969615506024417 0.03472963553338544 0 +-0.1992389396183492 0.01743114854953102 0 +-0.2 -5.972319578071407e-16 0 +-0.1992389396183491 -0.01743114854953221 0 +-0.1969615506024415 -0.03472963553338661 0 +-0.1931851652578135 -0.05176380902050468 0 +-0.1879385241571815 -0.06840402866513423 0 +-0.1812615574073298 -0.08452365234814034 0 +-0.1732050807568875 -0.1000000000000004 0 +-0.1638304088577981 -0.1147152872702096 0 +-0.1532088886237953 -0.1285575219373082 0 +-0.1414213562373092 -0.1414213562373098 0 +-0.1285575219373075 -0.1532088886237959 0 +-0.1147152872702088 -0.1638304088577986 0 +-0.09999999999999963 -0.173205080756888 0 +-0.08452365234813952 -0.1812615574073302 0 +-0.06840402866513337 -0.1879385241571818 0 +-0.05176380902050379 -0.1931851652578138 0 +-0.03472963553338554 -0.1969615506024417 0 +-0.01743114854953112 -0.1992389396183492 0 +4.961676478456546e-16 -0.2 0 +0.01743114854953211 -0.1992389396183491 0 +0.0347296355333867 -0.1969615506024415 0 +0.05176380902050475 -0.1931851652578135 0 +0.06840402866513448 -0.1879385241571814 0 +0.08452365234814058 -0.1812615574073297 0 +0.1000000000000008 -0.1732050807568873 0 +0.11471528727021 -0.1638304088577979 0 +0.1285575219373085 -0.1532088886237951 0 +0.1414213562373101 -0.1414213562373089 0 +0.1532088886237963 -0.1285575219373071 0 +0.1638304088577988 -0.1147152872702086 0 +0.1732050807568881 -0.09999999999999933 0 +0.1812615574073303 -0.0845236523481392 0 +0.1879385241571819 -0.06840402866513322 0 +0.1931851652578138 -0.05176380902050379 0 +0.1969615506024417 -0.03472963553338573 0 +0.1992389396183491 -0.01743114854953149 0 +1 15 0 23 +433 +434 +435 +436 +437 +438 +439 +440 +441 +442 +443 +444 +445 +446 +447 +448 +449 +450 +451 +452 +453 +454 +455 +-0.1833333333333333 -0.5 0 +-0.1666666666666666 -0.5 0 +-0.1499999999999999 -0.5 0 +-0.1333333333333332 -0.5 0 +-0.1166666666666665 -0.5 0 +-0.09999999999999987 -0.5 0 +-0.08333333333333315 -0.5 0 +-0.06666666666666649 -0.5 0 +-0.04999999999999977 -0.5 0 +-0.03333333333333305 -0.5 0 +-0.01666666666666633 -0.5 0 +3.33066907387547e-16 -0.5 0 +0.01666666666666705 -0.5 0 +0.03333333333333377 -0.5 0 +0.05000000000000049 -0.5 0 +0.06666666666666721 -0.5 0 +0.08333333333333381 -0.5 0 +0.1000000000000004 -0.5 0 +0.116666666666667 -0.5 0 +0.1333333333333336 -0.5 0 +0.1500000000000002 -0.5 0 +0.1666666666666669 -0.5 0 +0.1833333333333333 -0.5 0 +1 16 0 23 +456 +457 +458 +459 +460 +461 +462 +463 +464 +465 +466 +467 +468 +469 +470 +471 +472 +473 +474 +475 +476 +477 +478 +0.5 -0.1833333333333333 0 +0.5 -0.1666666666666666 0 +0.5 -0.1499999999999999 0 +0.5 -0.1333333333333332 0 +0.5 -0.1166666666666665 0 +0.5 -0.09999999999999987 0 +0.5 -0.08333333333333315 0 +0.5 -0.06666666666666649 0 +0.5 -0.04999999999999977 0 +0.5 -0.03333333333333305 0 +0.5 -0.01666666666666633 0 +0.5 3.33066907387547e-16 0 +0.5 0.01666666666666705 0 +0.5 0.03333333333333377 0 +0.5 0.05000000000000049 0 +0.5 0.06666666666666721 0 +0.5 0.08333333333333381 0 +0.5 0.1000000000000004 0 +0.5 0.116666666666667 0 +0.5 0.1333333333333336 0 +0.5 0.1500000000000002 0 +0.5 0.1666666666666669 0 +0.5 0.1833333333333333 0 +1 17 0 23 +479 +480 +481 +482 +483 +484 +485 +486 +487 +488 +489 +490 +491 +492 +493 +494 +495 +496 +497 +498 +499 +500 +501 +-0.5 0.1833333333333333 0 +-0.5 0.1666666666666666 0 +-0.5 0.1499999999999999 0 +-0.5 0.1333333333333332 0 +-0.5 0.1166666666666665 0 +-0.5 0.09999999999999987 0 +-0.5 0.08333333333333315 0 +-0.5 0.06666666666666649 0 +-0.5 0.04999999999999977 0 +-0.5 0.03333333333333305 0 +-0.5 0.01666666666666633 0 +-0.5 -3.33066907387547e-16 0 +-0.5 -0.01666666666666705 0 +-0.5 -0.03333333333333377 0 +-0.5 -0.05000000000000049 0 +-0.5 -0.06666666666666721 0 +-0.5 -0.08333333333333381 0 +-0.5 -0.1000000000000004 0 +-0.5 -0.116666666666667 0 +-0.5 -0.1333333333333336 0 +-0.5 -0.1500000000000002 0 +-0.5 -0.1666666666666669 0 +-0.5 -0.1833333333333333 0 +1 18 0 23 +502 +503 +504 +505 +506 +507 +508 +509 +510 +511 +512 +513 +514 +515 +516 +517 +518 +519 +520 +521 +522 +523 +524 +0.1833333333333333 0.5 0 +0.1666666666666666 0.5 0 +0.1499999999999999 0.5 0 +0.1333333333333332 0.5 0 +0.1166666666666665 0.5 0 +0.09999999999999987 0.5 0 +0.08333333333333315 0.5 0 +0.06666666666666649 0.5 0 +0.04999999999999977 0.5 0 +0.03333333333333305 0.5 0 +0.01666666666666633 0.5 0 +-3.33066907387547e-16 0.5 0 +-0.01666666666666705 0.5 0 +-0.03333333333333377 0.5 0 +-0.05000000000000049 0.5 0 +-0.06666666666666721 0.5 0 +-0.08333333333333381 0.5 0 +-0.1000000000000004 0.5 0 +-0.116666666666667 0.5 0 +-0.1333333333333336 0.5 0 +-0.1500000000000002 0.5 0 +-0.1666666666666669 0.5 0 +-0.1833333333333333 0.5 0 +2 3 0 445 +525 +526 +527 +528 +529 +530 +531 +532 +533 +534 +535 +536 +537 +538 +539 +540 +541 +542 +543 +544 +545 +546 +547 +548 +549 +550 +551 +552 +553 +554 +555 +556 +557 +558 +559 +560 +561 +562 +563 +564 +565 +566 +567 +568 +569 +570 +571 +572 +573 +574 +575 +576 +577 +578 +579 +580 +581 +582 +583 +584 +585 +586 +587 +588 +589 +590 +591 +592 +593 +594 +595 +596 +597 +598 +599 +600 +601 +602 +603 +604 +605 +606 +607 +608 +609 +610 +611 +612 +613 +614 +615 +616 +617 +618 +619 +620 +621 +622 +623 +624 +625 +626 +627 +628 +629 +630 +631 +632 +633 +634 +635 +636 +637 +638 +639 +640 +641 +642 +643 +644 +645 +646 +647 +648 +649 +650 +651 +652 +653 +654 +655 +656 +657 +658 +659 +660 +661 +662 +663 +664 +665 +666 +667 +668 +669 +670 +671 +672 +673 +674 +675 +676 +677 +678 +679 +680 +681 +682 +683 +684 +685 +686 +687 +688 +689 +690 +691 +692 +693 +694 +695 +696 +697 +698 +699 +700 +701 +702 +703 +704 +705 +706 +707 +708 +709 +710 +711 +712 +713 +714 +715 +716 +717 +718 +719 +720 +721 +722 +723 +724 +725 +726 +727 +728 +729 +730 +731 +732 +733 +734 +735 +736 +737 +738 +739 +740 +741 +742 +743 +744 +745 +746 +747 +748 +749 +750 +751 +752 +753 +754 +755 +756 +757 +758 +759 +760 +761 +762 +763 +764 +765 +766 +767 +768 +769 +770 +771 +772 +773 +774 +775 +776 +777 +778 +779 +780 +781 +782 +783 +784 +785 +786 +787 +788 +789 +790 +791 +792 +793 +794 +795 +796 +797 +798 +799 +800 +801 +802 +803 +804 +805 +806 +807 +808 +809 +810 +811 +812 +813 +814 +815 +816 +817 +818 +819 +820 +821 +822 +823 +824 +825 +826 +827 +828 +829 +830 +831 +832 +833 +834 +835 +836 +837 +838 +839 +840 +841 +842 +843 +844 +845 +846 +847 +848 +849 +850 +851 +852 +853 +854 +855 +856 +857 +858 +859 +860 +861 +862 +863 +864 +865 +866 +867 +868 +869 +870 +871 +872 +873 +874 +875 +876 +877 +878 +879 +880 +881 +882 +883 +884 +885 +886 +887 +888 +889 +890 +891 +892 +893 +894 +895 +896 +897 +898 +899 +900 +901 +902 +903 +904 +905 +906 +907 +908 +909 +910 +911 +912 +913 +914 +915 +916 +917 +918 +919 +920 +921 +922 +923 +924 +925 +926 +927 +928 +929 +930 +931 +932 +933 +934 +935 +936 +937 +938 +939 +940 +941 +942 +943 +944 +945 +946 +947 +948 +949 +950 +951 +952 +953 +954 +955 +956 +957 +958 +959 +960 +961 +962 +963 +964 +965 +966 +967 +968 +969 +0.06527036446661494 -0.02375646984555406 0 +-0.06353806886443658 0.02315448660544328 0 +-0.01736481776669272 -0.09848077530122061 0 +0.04999999999999997 0.08660254037844367 0 +-0.1008530206986341 -0.08293853316127649 0 +-0.04372861959399345 -0.0391699848755048 0 +-0.1243002883352477 -0.02208659921667022 0 +-0.1254668872131278 0.04566621233161284 0 +0.02580133803048715 -0.06027068161668559 0 +0.06460394798868038 -0.1137655673071555 0 +0.1060833125547977 -0.07012040174948569 0 +0.008682408883346988 -0.147721162951831 0 +0.1224389135442226 0.04571463218397696 0 +0.05832500464660378 0.03004490202280833 0 +0.126267579836159 -0.01440762363181047 0 +-0.008365393480820063 0.05821000444535348 0 +-0.02389149379333282 0.1283038345778152 0 +-0.08097937924832826 0.09670661901554549 0 +0.1016044443118978 0.1075800311578758 0 +-0.05868240888334618 -0.1358429280290543 0 +0.04236481776669285 0.1417820454904427 0 +0.00438843015584927 -0.001591507018240784 0 +-0.1443138600358246 -0.07600000509074632 0 +-0.1138564063767952 -0.052995666428628 0 +-0.1557166585180098 -0.04466214596986659 0 +-0.07212839094072163 -0.06088616646130958 0 +-0.08374446858507376 -0.03059590964821814 0 +-0.0572062648493984 -0.08975813887479787 0 +-0.03052467035130586 -0.06881632926214672 0 +-0.05496815861348792 -0.008836514995956833 0 +-0.09423106874110057 9.528983228586829e-05 0 +-0.1625467170213362 -0.01135954420785004 0 +-0.1272610538172396 0.0115388979476316 0 +-0.1627334436065639 0.02283310616580612 0 +-0.09423106874110068 0.03429730416485271 0 +-0.1567027056851548 0.05703512049837298 0 +0.04692209897189983 -0.0410771651427845 0 +0.0659256378367374 -0.06665630917287578 0 +0.08360411825925769 -0.04465378080344916 0 +0.04541364459676309 -0.08719844590239016 0 +0.08711655943844925 -0.09341896426576081 0 +0.003739174404779644 -0.0795942550062847 0 +0.02229052208354709 -0.1056086858699364 0 +0.1079800517124734 -0.1217504973985597 0 +0.1295359900027927 -0.09794706806756459 0 +0.08274006034590464 -0.1422884840119934 0 +0.03818493287133806 -0.1312007763115746 0 +0.0533163348965661 -0.1590460828407257 0 +-0.002268264086775909 -0.1234593913026882 0 +0.02170602220836684 -0.1723413567771363 0 +0.160997392068982 0.02392732706304133 0 +0.1266501301975769 0.0157373928143124 0 +0.1621438118163032 -0.008194263900818713 0 +0.09038991973829297 0.03783437164465724 0 +0.09329735102835396 0.009037616906012768 0 +0.08497011731033488 0.06654033338547012 0 +0.05401050209998903 0.05858312391290401 0 +0.06218434855949369 0.001888421545682044 0 +0.09293211356581974 -0.01928059292664763 0 +-0.03640780200273908 0.04212676033786501 0 +-0.04450257671867723 0.07734367327351792 0 +-0.07200106955023604 0.06008015168825086 0 +-0.01618712710559054 0.09313687214310194 0 +-0.0532355774380623 0.1139103178630588 0 +0.02027368602048808 0.07338560607537248 0 +0.01380259579741985 0.1056128670278102 0 +-0.06167748421985941 0.1509924649873916 0 +-0.09056931465014036 0.1342327700603284 0 +0.1274066664678467 0.1180687765475919 0 +0.1430820864914728 0.08755007745117398 0 +0.1133022221559489 0.07544065067354881 0 +0.1547635770305916 0.0558204250623352 0 +0.07753595563653252 0.09607357507379381 0 +-0.07934120444167291 -0.1545240043929711 0 +-0.1045340914893699 -0.1311423386866437 0 +-0.08066703992264164 -0.1096426650061158 0 +-0.1267754543351734 -0.1050560619512661 0 +-0.04011605893459935 -0.116605490103587 0 +-0.02958539246551829 0.1619943678080355 0 +0.008682408883346322 0.1358429280290541 0 +0.004217689478752948 0.167661201916119 0 +0.04411361178691573 0.1151296155218029 0 +0.0385472266500393 0.1693717980464422 0 +-0.1393378879184619 0.08711186713446016 0 +-0.1048971807446836 0.0729540726258026 0 +-0.1172189043097818 0.1131945393453942 0 +0.1581439041445198 -0.04021290056497789 0 +0.1209128132766506 -0.04406998768255657 0 +0.1469010890744045 -0.07088418873676777 0 +-0.01899703272410926 -0.01926651867980932 0 +-0.02894329536754907 0.01053881808704702 0 +-0.007961540942835748 -0.04945086056174676 0 +0.01385188719899871 -0.03089105495336334 0 +0.03686999871177275 -0.01329297779795752 0 +0.0305883090083901 0.01474968717381858 0 +0.02579379855574887 0.04286532380956126 0 +-0.002214357534627795 0.02699075743058774 0 +-0.01261596336827519 -0.1707883247969533 0 +-0.0251270222705723 -0.1428469611904433 0 +-0.04652045092393451 -0.1648082677746721 0 +0.07025313236809264 0.1561397922880818 0 +0.07237891723642929 0.1254567952702784 0 +0.1000641090133254 0.138925146521401 0 +-0.1659841834026148 -0.07235251582264658 0 +-0.1504954245026408 -0.06054361891789243 0 +-0.1718431332725429 -0.05640381220317607 0 +-0.12922003056458 -0.0644329758785752 0 +-0.1349140171836805 -0.04901924531943198 0 +-0.1228312675455848 -0.07954901982589965 0 +-0.1081350311289256 -0.0682253272241071 0 +-0.1192506302183978 -0.03756088970029393 0 +-0.1398082772617197 -0.03319173909482884 0 +-0.08703376172592361 -0.07202584890027407 0 +-0.09297249219351485 -0.0569376646773067 0 +-0.07812520203270364 -0.04581438668872911 0 +-0.09865834357359049 -0.04171236616257944 0 +-0.05775624549765256 -0.04995773293465789 0 +-0.06365870001112722 -0.03486061705614083 0 +-0.1040300807173788 -0.02637928507945801 0 +-0.07853340488273999 -0.08614435263365482 0 +-0.06571204842711252 -0.07578544222978487 0 +-0.04439033512830143 -0.07954503555929567 0 +-0.05135799724084668 -0.06486378508028449 0 +-0.03662988772117321 -0.09391384284356454 0 +-0.02366261775781015 -0.0835378030831623 0 +-0.03765956549547419 -0.05420126238817462 0 +-0.08910025467594629 -0.01529316537410853 0 +-0.1091698579384259 -0.01104619754126049 0 +-0.06920200349710155 -0.01963766305919976 0 +-0.07430823512528244 -0.004234804994452214 0 +-0.04949375970932452 -0.02403332275226658 0 +-0.05973476571897128 0.00675138071507674 0 +-0.07861315950508707 0.01151184291518922 0 +-0.1765632699150058 -0.04001197065103023 0 +-0.1598408694968862 -0.02825558836025618 0 +-0.1797495330755181 -0.02301711453279667 0 +-0.1435939932673683 -0.01690802051794496 0 +-0.1813667218032819 -0.005684454083380656 0 +-0.1265399419701071 -0.005418539540263253 0 +-0.1446240129474668 0.0001233217802187761 0 +-0.1447028690830298 0.01716702236755263 0 +-0.1629215579499994 0.005744242220902843 0 +-0.1269416778545994 0.02858340526794214 0 +-0.143927244011205 0.03425096753459846 0 +-0.1813667218032819 0.01141655308290276 0 +-0.1102904455148943 0.005867360306445774 0 +-0.09423106874110063 0.01719629699856929 0 +-0.1104673821076748 0.02291083715561238 0 +-0.07903270008751069 0.02876530997955309 0 +-0.1098489779771142 0.03998175824823277 0 +-0.1798474971045028 0.02878137084959578 0 +-0.1598478941882311 0.0399570040200329 0 +-0.1764775712932188 0.04583108575060727 0 +-0.1410847964491413 0.05135066641499291 0 +-0.1723206149211684 0.06271957458175303 0 +0.05653894860222643 -0.03193868234712371 0 +0.06526601279534866 -0.04353164889894485 0 +0.07341082553464798 -0.03312420389520342 0 +0.05590609144374648 -0.0537366570329169 0 +0.07526135359123928 -0.05579666983233701 0 +0.03670152797074632 -0.0505159699734959 0 +0.04589398449761262 -0.06373197280856745 0 +0.08594543977723459 -0.06866573194549558 0 +0.09457553622715234 -0.05696943037589152 0 +0.03539005902198141 -0.07362680189179356 0 +0.05592909520599426 -0.07697106703106966 0 +0.06631126242217662 -0.09052693928528131 0 +0.0763915393525272 -0.07974236711451102 0 +0.05506154136788498 -0.1002911403335178 0 +0.07622072701814514 -0.1039087779063726 0 +0.09693373390469284 -0.08195900892645103 0 +0.01482641182033832 -0.06994541147144633 0 +0.02444993646405797 -0.0834303110700992 0 +0.01326610131103476 -0.09275266085544746 0 +0.03404509395536472 -0.09650417823455402 0 +-0.007035419987529264 -0.08914023711926229 0 +0.002038178949112857 -0.1019182536630961 0 +0.04317160054061298 -0.1095669723823736 0 +0.1081040078986673 -0.09584289711987616 0 +0.11763659011666 -0.0835861121407827 0 +0.09747618658516072 -0.1071792807394947 0 +0.119162544816453 -0.1101808102962893 0 +0.08603810785117998 -0.1178177027089239 0 +0.1302896735957535 -0.1253289544447239 0 +0.1412761358021126 -0.1127525394002113 0 +0.1182935171680177 -0.1370162751295692 0 +0.09569764102552383 -0.1323865300675154 0 +0.1052984763433324 -0.147738711225397 0 +0.07372693742402683 -0.1276680601332547 0 +0.0915128086694543 -0.1575438084052357 0 +0.05174753155324868 -0.1228510704351953 0 +0.06008119325077859 -0.1366616170140786 0 +0.04568876346766364 -0.1449990773579692 0 +0.06829862825013878 -0.1510774741106662 0 +0.02349231551964818 -0.1391706593686892 0 +0.03069074019669561 -0.1528622149964753 0 +0.07642240786838064 -0.1659115988114101 0 +0.0304847655465458 -0.118441659689456 0 +0.01053349737338975 -0.1145618906816195 0 +0.01698050885713825 -0.1268605624560366 0 +-0.00946080931907247 -0.1109652721510905 0 +0.004054447894997348 -0.1357420566693896 0 +0.06087127984860665 -0.1733287222693193 0 +0.03776157419040307 -0.1662031932587092 0 +0.04478742380064876 -0.1796080692349914 0 +0.01519421554585692 -0.1600312598644837 0 +0.02821782887087677 -0.1846514536897889 0 +0.1804582621237301 0.01229757692446511 0 +0.1622236929313328 0.007881432379368614 0 +0.1807306776864765 -0.004408087838135751 0 +0.1435510877758439 0.01956072130778571 0 +0.1444591766288705 0.00405586634433298 0 +0.1415785217840553 0.0350329325137478 0 +0.1248869518759362 0.03076338534639437 0 +0.1269145420907029 0.0006472758158043389 0 +0.1438139698657486 -0.01152784469363017 0 +0.1062945578711859 0.04160022135207927 0 +0.1084081117413474 0.02696538754188421 0 +0.0921296990963817 0.02343882087158806 0 +0.1095943048447942 0.01224743353457718 0 +0.07423587276061419 0.03415791160617438 0 +0.0759403122207956 0.01984334949272302 0 +0.1098475276699923 -0.002443889324730767 0 +0.1035340641002743 0.05612798808256122 0 +0.08783224289962739 0.05219998290253854 0 +0.06968703100906139 0.0625989054493966 0 +0.07211776736861493 0.04849807750106681 0 +0.06702802635610275 0.07678720753484274 0 +0.05190879555832684 0.07280766410044395 0 +0.05629099237119355 0.04408585524950842 0 +0.09353031394018754 -0.005300949015987423 0 +0.1090619795894766 -0.01702162083400216 0 +0.07721947606767687 0.005568184328133057 0 +0.07805743352726108 -0.00822178845897107 0 +0.06035668937137732 0.01570246483391094 0 +0.06377729771028857 -0.01127739301995127 0 +0.07852017528936536 -0.02124276015296982 0 +-0.05009498345576106 0.03340996424283155 0 +-0.05413498654260484 0.05095097035196145 0 +-0.06762084398543944 0.04171298159294193 0 +-0.04036614304560748 0.05953128015988262 0 +-0.05835370264359232 0.0688948999368373 0 +-0.02246676001570362 0.05025137109513039 0 +-0.02639043155908879 0.06771480403785908 0 +-0.06271248620662007 0.08704981847791417 0 +-0.07639746283176571 0.07836566902663174 0 +-0.01222275301936305 0.07557381666390722 0 +-0.03046372796456599 0.08545709926737308 0 +-0.03468398202296057 0.10349381863833 0 +-0.04880593969284499 0.09545689858880209 0 +-0.02037440628363436 0.1111192249728162 0 +-0.03903854535473713 0.1218380119812959 0 +-0.06723728780618388 0.1054866640126187 0 +0.005845639439956783 0.06599532941188296 0 +0.002179669976730124 0.08300607725689198 0 +0.01698875901574769 0.08965601323711471 0 +-0.001624406283634316 0.1002939074255108 0 +0.03515173026155946 0.07997273002869985 0 +0.03214927832192292 0.09566489160840304 0 +-0.004795884339178251 0.1164860674429488 0 +-0.07188845753249504 0.124314286634339 0 +-0.08574085181589125 0.1152357111355767 0 +-0.0576305759515099 0.1324754086510158 0 +-0.0763952278370497 0.1431359402607084 0 +-0.04292669185050444 0.1398883470818718 0 +-0.08065004471463103 0.1621235492375357 0 +-0.09539601916793668 0.1536854346441454 0 +0.1403077775458211 0.1233131492424499 0 +0.1497672056371243 0.1087951574886773 0 +0.1357701481144775 0.1030362263955504 0 +0.1580501728087336 0.09364162892735485 0 +0.1145055553898722 0.1128244038527338 0 +0.1217683219469116 0.09734625098588075 0 +0.1651919442809442 0.07794979840108908 0 +0.1074533332339233 0.09151034091571231 0 +0.1281551217281648 0.081588648136039 0 +0.1338254366019609 0.06594404324245606 0 +0.1493997771895528 0.0717777950289691 0 +0.1186201192509417 0.06069281339185068 0 +0.1384317209990767 0.05046282307529946 0 +0.1712876952226891 0.06182713528489119 0 +0.09041024352454369 0.1013487069179468 0 +0.09512284832575091 0.08593269795011574 0 +0.0816342243255827 0.08102663614100201 0 +0.1000311875845463 0.07052712042294279 0 +0.06396510477351178 0.09114846716563638 0 +0.1756141731102865 0.04548805804296911 0 +0.1583535840181467 0.0399460034885929 0 +0.1786687030314159 0.02895746695622225 0 +-0.08967060222083627 -0.1638645425749295 0 +-0.1035247077208769 -0.1534512639178658 0 +-0.09234484747099939 -0.1432385836832112 0 +-0.1164988399334475 -0.1420279643385481 0 +-0.06901180666250954 -0.1451834662110127 0 +-0.0811395261847401 -0.1330902563044671 0 +-0.1286026710214067 -0.1297002760258632 0 +-0.06967472440299391 -0.1227427965175851 0 +-0.09257772319781904 -0.1203841801367159 0 +-0.1034516621672038 -0.1073011294729535 0 +-0.1159837960123145 -0.1183759126497917 0 +-0.09107389041166267 -0.09659026850175811 0 +-0.1136185924166851 -0.09374854889763784 0 +-0.1399611738629139 -0.1166714896253081 0 +-0.05015110403674162 -0.1261656236415158 0 +-0.05976291111480565 -0.1130523346749547 0 +-0.04864899061992983 -0.103390549846967 0 +-0.06931541696686302 -0.09976305158970805 0 +-0.02886108668553069 -0.1072483931055068 0 +-0.1503071795277914 -0.1028467887380663 0 +-0.136726259341852 -0.09105628047267328 0 +-0.1589925020052152 -0.08788979378668214 0 +-0.06529526829077026 0.1695370489340915 0 +-0.04618537355521921 0.1577099094827593 0 +-0.04906484727581793 0.1755069800213165 0 +-0.02675853621825295 0.1448658559430244 0 +-0.03223134589605963 0.179363040154529 0 +-0.00767525657942406 0.132542219771242 0 +-0.01027430274148216 0.1487109819701924 0 +0.006462248492603469 0.1517356075828992 0 +-0.01276035339787691 0.1652506164908305 0 +0.02552361332501959 0.1388124867597484 0 +0.02339238127020354 0.1541100675506168 0 +-0.01514794060753028 0.1820284917545047 0 +0.01099234374785769 0.1210116299121008 0 +0.02909997961056087 0.1105407198439941 0 +0.02683717911240414 0.125038045953561 0 +0.04670273894336418 0.1007837469378981 0 +0.04261111732072462 0.1288908956546655 0 +0.002050134247856932 0.1836858279436159 0 +0.02133262711181395 0.1690833349686517 0 +0.01933326127515894 0.1840910958169168 0 +0.04045602220836608 0.1555769217684424 0 +0.03663843109171252 0.1831666743244419 0 +-0.1646302899850041 0.07831796246865184 0 +-0.1481204187618589 0.07213074543545746 0 +-0.156271484337675 0.09355593356722981 0 +-0.1322573373276132 0.06626616712754978 0 +-0.1462733882711289 0.1078346945358838 0 +-0.1156121811258924 0.05970041578177056 0 +-0.1218849512672457 0.07987891639588253 0 +-0.1108806164252621 0.09292343493994923 0 +-0.1285018622521244 0.1003326448684879 0 +-0.09344408823707451 0.08549666118375103 0 +-0.09914478383651742 0.1052726711005329 0 +-0.1352810727514812 0.1209348260473531 0 +-0.09934135264331323 0.05343656944538679 0 +-0.08323875322145298 0.04739743567632201 0 +-0.08821998453808769 0.06628842660769084 0 +-0.12290520488235 0.133184616953087 0 +-0.1043092281439763 0.1245141658546013 0 +-0.1095249089941576 0.1440863216602928 0 +0.1796206507162249 -0.02109833050840469 0 +0.1605547471811679 -0.02428654223466351 0 +0.1770484301418609 -0.03769238931029251 0 +0.142238794809289 -0.02702338905936197 0 +0.1733528298773691 -0.05409873186329953 0 +0.1242853880166052 -0.02937608790435111 0 +0.1391841362946392 -0.04237999340311695 0 +0.133905170633259 -0.05724038889074021 0 +0.1533762850413356 -0.05589793827981388 0 +0.1140330453040848 -0.05737637548762888 0 +0.1264246497099505 -0.07088355132039113 0 +0.1672879745472309 -0.06993727836642195 0 +0.1071104210965106 -0.03130243286648338 0 +0.09056900301881374 -0.03283497512167631 0 +0.1021676852580398 -0.04485995837671363 0 +0.1597909552008621 -0.0849359914089578 0 +0.1386901290100464 -0.08471281763920684 0 +0.1512438010415318 -0.09938689406411021 0 +-0.0416376678964324 0.001338209005628649 0 +-0.04613984841522483 0.0167935314366963 0 +-0.03555444030151731 -0.01325950505719155 0 +-0.02466779644738823 -0.004117393171033033 0 +-0.0298506362387197 -0.0284356514696057 0 +-0.007548255524476207 -0.01031802768096221 0 +-0.01203951693135581 0.004384567229078278 0 +-0.02414683217592209 -0.04361179788201985 0 +-0.01348779544194135 -0.03434633436025572 0 +0.002835546942897826 -0.04012650670466904 0 +-0.002434421990315842 -0.02518803917531157 0 +0.008300454476428951 -0.05515219057036658 0 +0.01935261493751533 -0.04561902140471037 0 +0.009004286112471062 -0.01622696975258486 0 +-0.01844302811312448 -0.05878794429443399 0 +-0.01273922405032687 -0.07396409070684815 0 +-0.002315195335171453 -0.06458682466244482 0 +0.02531358162836005 -0.02189745606552697 0 +0.02102969225675781 -0.007587110846157668 0 +0.03054749468818817 -0.0361997539462519 0 +0.04136575032040909 -0.02718908745528556 0 +0.05212946925799213 -0.01877647602671228 0 +0.04912725519775619 -0.005349565401661593 0 +0.04662654950673121 0.008397046842289403 0 +0.03358197310931547 0.0006095685606180789 0 +0.04431026817524601 0.02241739118987788 0 +0.0174580381776098 0.00663668512310108 0 +0.04209360253977756 0.03651649100194589 0 +0.02814552218167183 0.02872958801426806 0 +0.0118138893667115 0.03490987492511883 0 +0.01443898867016992 0.0207960233587115 0 +0.009535576707424252 0.04892985995781229 0 +-0.004588829576209943 0.04097059191026841 0 +0.000953811218635105 0.01270874863258696 0 +0.03986005349170278 0.05076646350031568 0 +0.03778438927469264 0.06484839605290006 0 +0.02365998299105845 0.05688912800535618 0 +-0.01630871522583476 0.01903596567228396 0 +-0.01871323585984414 0.03301132386272452 0 +-0.03274417027052067 0.02575475066179261 0 +0.01091484866743826 -0.1855980571813055 0 +0.004503575958448663 -0.1721378652737372 0 +-0.006365771358358752 -0.1852118212123638 0 +-0.001809243000341641 -0.1588510897929063 0 +-0.02356571285064639 -0.1835052850575323 0 +-0.007821714282004429 -0.1457449457816203 0 +-0.0187979807394228 -0.1566829800888125 0 +-0.03583986369464608 -0.1536824320736626 0 +-0.02965551208093313 -0.1683294812930753 0 +-0.04241786311448565 -0.1396702007982397 0 +-0.05259370432677047 -0.1499007386907779 0 +-0.0405973557140551 -0.1805014902995873 0 +-0.01394368727537105 -0.1326851268384597 0 +-0.02113254993878885 -0.1201054764055908 0 +-0.03214539333138363 -0.1296691849322128 0 +-0.05735086656289933 -0.1762202351690587 0 +-0.06309129254830917 -0.1602179230842606 0 +-0.07373027446449842 -0.1706711311309553 0 +0.05321978230829735 0.1781339347970768 0 +0.05463649323952438 0.1632813702257054 0 +0.06932273899616975 0.1718531627799308 0 +0.05601624977582109 0.1486079398506368 0 +0.08489848478957987 0.164362033033278 0 +0.05716805463995469 0.1341413063569924 0 +0.07121897843229588 0.1407262029741559 0 +0.0862137107571726 0.1320898554919464 0 +0.08541771756690698 0.1479979297474793 0 +0.08758412551087191 0.1166395286458505 0 +0.1006742178154949 0.1228367282219477 0 +0.09987494627070576 0.1557143795246338 0 +0.05853793235293473 0.1199002564088666 0 +0.06088845916727385 0.1056162471091084 0 +0.07449153167525098 0.1107373109812583 0 +0.1141564478191831 0.145966128213362 0 +0.1140767428365739 0.1289629542412834 0 +0.1276544188073403 0.1351568755176321 0 +2 4 0 4136 +970 +971 +972 +973 +974 +975 +976 +977 +978 +979 +980 +981 +982 +983 +984 +985 +986 +987 +988 +989 +990 +991 +992 +993 +994 +995 +996 +997 +998 +999 +1000 +1001 +1002 +1003 +1004 +1005 +1006 +1007 +1008 +1009 +1010 +1011 +1012 +1013 +1014 +1015 +1016 +1017 +1018 +1019 +1020 +1021 +1022 +1023 +1024 +1025 +1026 +1027 +1028 +1029 +1030 +1031 +1032 +1033 +1034 +1035 +1036 +1037 +1038 +1039 +1040 +1041 +1042 +1043 +1044 +1045 +1046 +1047 +1048 +1049 +1050 +1051 +1052 +1053 +1054 +1055 +1056 +1057 +1058 +1059 +1060 +1061 +1062 +1063 +1064 +1065 +1066 +1067 +1068 +1069 +1070 +1071 +1072 +1073 +1074 +1075 +1076 +1077 +1078 +1079 +1080 +1081 +1082 +1083 +1084 +1085 +1086 +1087 +1088 +1089 +1090 +1091 +1092 +1093 +1094 +1095 +1096 +1097 +1098 +1099 +1100 +1101 +1102 +1103 +1104 +1105 +1106 +1107 +1108 +1109 +1110 +1111 +1112 +1113 +1114 +1115 +1116 +1117 +1118 +1119 +1120 +1121 +1122 +1123 +1124 +1125 +1126 +1127 +1128 +1129 +1130 +1131 +1132 +1133 +1134 +1135 +1136 +1137 +1138 +1139 +1140 +1141 +1142 +1143 +1144 +1145 +1146 +1147 +1148 +1149 +1150 +1151 +1152 +1153 +1154 +1155 +1156 +1157 +1158 +1159 +1160 +1161 +1162 +1163 +1164 +1165 +1166 +1167 +1168 +1169 +1170 +1171 +1172 +1173 +1174 +1175 +1176 +1177 +1178 +1179 +1180 +1181 +1182 +1183 +1184 +1185 +1186 +1187 +1188 +1189 +1190 +1191 +1192 +1193 +1194 +1195 +1196 +1197 +1198 +1199 +1200 +1201 +1202 +1203 +1204 +1205 +1206 +1207 +1208 +1209 +1210 +1211 +1212 +1213 +1214 +1215 +1216 +1217 +1218 +1219 +1220 +1221 +1222 +1223 +1224 +1225 +1226 +1227 +1228 +1229 +1230 +1231 +1232 +1233 +1234 +1235 +1236 +1237 +1238 +1239 +1240 +1241 +1242 +1243 +1244 +1245 +1246 +1247 +1248 +1249 +1250 +1251 +1252 +1253 +1254 +1255 +1256 +1257 +1258 +1259 +1260 +1261 +1262 +1263 +1264 +1265 +1266 +1267 +1268 +1269 +1270 +1271 +1272 +1273 +1274 +1275 +1276 +1277 +1278 +1279 +1280 +1281 +1282 +1283 +1284 +1285 +1286 +1287 +1288 +1289 +1290 +1291 +1292 +1293 +1294 +1295 +1296 +1297 +1298 +1299 +1300 +1301 +1302 +1303 +1304 +1305 +1306 +1307 +1308 +1309 +1310 +1311 +1312 +1313 +1314 +1315 +1316 +1317 +1318 +1319 +1320 +1321 +1322 +1323 +1324 +1325 +1326 +1327 +1328 +1329 +1330 +1331 +1332 +1333 +1334 +1335 +1336 +1337 +1338 +1339 +1340 +1341 +1342 +1343 +1344 +1345 +1346 +1347 +1348 +1349 +1350 +1351 +1352 +1353 +1354 +1355 +1356 +1357 +1358 +1359 +1360 +1361 +1362 +1363 +1364 +1365 +1366 +1367 +1368 +1369 +1370 +1371 +1372 +1373 +1374 +1375 +1376 +1377 +1378 +1379 +1380 +1381 +1382 +1383 +1384 +1385 +1386 +1387 +1388 +1389 +1390 +1391 +1392 +1393 +1394 +1395 +1396 +1397 +1398 +1399 +1400 +1401 +1402 +1403 +1404 +1405 +1406 +1407 +1408 +1409 +1410 +1411 +1412 +1413 +1414 +1415 +1416 +1417 +1418 +1419 +1420 +1421 +1422 +1423 +1424 +1425 +1426 +1427 +1428 +1429 +1430 +1431 +1432 +1433 +1434 +1435 +1436 +1437 +1438 +1439 +1440 +1441 +1442 +1443 +1444 +1445 +1446 +1447 +1448 +1449 +1450 +1451 +1452 +1453 +1454 +1455 +1456 +1457 +1458 +1459 +1460 +1461 +1462 +1463 +1464 +1465 +1466 +1467 +1468 +1469 +1470 +1471 +1472 +1473 +1474 +1475 +1476 +1477 +1478 +1479 +1480 +1481 +1482 +1483 +1484 +1485 +1486 +1487 +1488 +1489 +1490 +1491 +1492 +1493 +1494 +1495 +1496 +1497 +1498 +1499 +1500 +1501 +1502 +1503 +1504 +1505 +1506 +1507 +1508 +1509 +1510 +1511 +1512 +1513 +1514 +1515 +1516 +1517 +1518 +1519 +1520 +1521 +1522 +1523 +1524 +1525 +1526 +1527 +1528 +1529 +1530 +1531 +1532 +1533 +1534 +1535 +1536 +1537 +1538 +1539 +1540 +1541 +1542 +1543 +1544 +1545 +1546 +1547 +1548 +1549 +1550 +1551 +1552 +1553 +1554 +1555 +1556 +1557 +1558 +1559 +1560 +1561 +1562 +1563 +1564 +1565 +1566 +1567 +1568 +1569 +1570 +1571 +1572 +1573 +1574 +1575 +1576 +1577 +1578 +1579 +1580 +1581 +1582 +1583 +1584 +1585 +1586 +1587 +1588 +1589 +1590 +1591 +1592 +1593 +1594 +1595 +1596 +1597 +1598 +1599 +1600 +1601 +1602 +1603 +1604 +1605 +1606 +1607 +1608 +1609 +1610 +1611 +1612 +1613 +1614 +1615 +1616 +1617 +1618 +1619 +1620 +1621 +1622 +1623 +1624 +1625 +1626 +1627 +1628 +1629 +1630 +1631 +1632 +1633 +1634 +1635 +1636 +1637 +1638 +1639 +1640 +1641 +1642 +1643 +1644 +1645 +1646 +1647 +1648 +1649 +1650 +1651 +1652 +1653 +1654 +1655 +1656 +1657 +1658 +1659 +1660 +1661 +1662 +1663 +1664 +1665 +1666 +1667 +1668 +1669 +1670 +1671 +1672 +1673 +1674 +1675 +1676 +1677 +1678 +1679 +1680 +1681 +1682 +1683 +1684 +1685 +1686 +1687 +1688 +1689 +1690 +1691 +1692 +1693 +1694 +1695 +1696 +1697 +1698 +1699 +1700 +1701 +1702 +1703 +1704 +1705 +1706 +1707 +1708 +1709 +1710 +1711 +1712 +1713 +1714 +1715 +1716 +1717 +1718 +1719 +1720 +1721 +1722 +1723 +1724 +1725 +1726 +1727 +1728 +1729 +1730 +1731 +1732 +1733 +1734 +1735 +1736 +1737 +1738 +1739 +1740 +1741 +1742 +1743 +1744 +1745 +1746 +1747 +1748 +1749 +1750 +1751 +1752 +1753 +1754 +1755 +1756 +1757 +1758 +1759 +1760 +1761 +1762 +1763 +1764 +1765 +1766 +1767 +1768 +1769 +1770 +1771 +1772 +1773 +1774 +1775 +1776 +1777 +1778 +1779 +1780 +1781 +1782 +1783 +1784 +1785 +1786 +1787 +1788 +1789 +1790 +1791 +1792 +1793 +1794 +1795 +1796 +1797 +1798 +1799 +1800 +1801 +1802 +1803 +1804 +1805 +1806 +1807 +1808 +1809 +1810 +1811 +1812 +1813 +1814 +1815 +1816 +1817 +1818 +1819 +1820 +1821 +1822 +1823 +1824 +1825 +1826 +1827 +1828 +1829 +1830 +1831 +1832 +1833 +1834 +1835 +1836 +1837 +1838 +1839 +1840 +1841 +1842 +1843 +1844 +1845 +1846 +1847 +1848 +1849 +1850 +1851 +1852 +1853 +1854 +1855 +1856 +1857 +1858 +1859 +1860 +1861 +1862 +1863 +1864 +1865 +1866 +1867 +1868 +1869 +1870 +1871 +1872 +1873 +1874 +1875 +1876 +1877 +1878 +1879 +1880 +1881 +1882 +1883 +1884 +1885 +1886 +1887 +1888 +1889 +1890 +1891 +1892 +1893 +1894 +1895 +1896 +1897 +1898 +1899 +1900 +1901 +1902 +1903 +1904 +1905 +1906 +1907 +1908 +1909 +1910 +1911 +1912 +1913 +1914 +1915 +1916 +1917 +1918 +1919 +1920 +1921 +1922 +1923 +1924 +1925 +1926 +1927 +1928 +1929 +1930 +1931 +1932 +1933 +1934 +1935 +1936 +1937 +1938 +1939 +1940 +1941 +1942 +1943 +1944 +1945 +1946 +1947 +1948 +1949 +1950 +1951 +1952 +1953 +1954 +1955 +1956 +1957 +1958 +1959 +1960 +1961 +1962 +1963 +1964 +1965 +1966 +1967 +1968 +1969 +1970 +1971 +1972 +1973 +1974 +1975 +1976 +1977 +1978 +1979 +1980 +1981 +1982 +1983 +1984 +1985 +1986 +1987 +1988 +1989 +1990 +1991 +1992 +1993 +1994 +1995 +1996 +1997 +1998 +1999 +2000 +2001 +2002 +2003 +2004 +2005 +2006 +2007 +2008 +2009 +2010 +2011 +2012 +2013 +2014 +2015 +2016 +2017 +2018 +2019 +2020 +2021 +2022 +2023 +2024 +2025 +2026 +2027 +2028 +2029 +2030 +2031 +2032 +2033 +2034 +2035 +2036 +2037 +2038 +2039 +2040 +2041 +2042 +2043 +2044 +2045 +2046 +2047 +2048 +2049 +2050 +2051 +2052 +2053 +2054 +2055 +2056 +2057 +2058 +2059 +2060 +2061 +2062 +2063 +2064 +2065 +2066 +2067 +2068 +2069 +2070 +2071 +2072 +2073 +2074 +2075 +2076 +2077 +2078 +2079 +2080 +2081 +2082 +2083 +2084 +2085 +2086 +2087 +2088 +2089 +2090 +2091 +2092 +2093 +2094 +2095 +2096 +2097 +2098 +2099 +2100 +2101 +2102 +2103 +2104 +2105 +2106 +2107 +2108 +2109 +2110 +2111 +2112 +2113 +2114 +2115 +2116 +2117 +2118 +2119 +2120 +2121 +2122 +2123 +2124 +2125 +2126 +2127 +2128 +2129 +2130 +2131 +2132 +2133 +2134 +2135 +2136 +2137 +2138 +2139 +2140 +2141 +2142 +2143 +2144 +2145 +2146 +2147 +2148 +2149 +2150 +2151 +2152 +2153 +2154 +2155 +2156 +2157 +2158 +2159 +2160 +2161 +2162 +2163 +2164 +2165 +2166 +2167 +2168 +2169 +2170 +2171 +2172 +2173 +2174 +2175 +2176 +2177 +2178 +2179 +2180 +2181 +2182 +2183 +2184 +2185 +2186 +2187 +2188 +2189 +2190 +2191 +2192 +2193 +2194 +2195 +2196 +2197 +2198 +2199 +2200 +2201 +2202 +2203 +2204 +2205 +2206 +2207 +2208 +2209 +2210 +2211 +2212 +2213 +2214 +2215 +2216 +2217 +2218 +2219 +2220 +2221 +2222 +2223 +2224 +2225 +2226 +2227 +2228 +2229 +2230 +2231 +2232 +2233 +2234 +2235 +2236 +2237 +2238 +2239 +2240 +2241 +2242 +2243 +2244 +2245 +2246 +2247 +2248 +2249 +2250 +2251 +2252 +2253 +2254 +2255 +2256 +2257 +2258 +2259 +2260 +2261 +2262 +2263 +2264 +2265 +2266 +2267 +2268 +2269 +2270 +2271 +2272 +2273 +2274 +2275 +2276 +2277 +2278 +2279 +2280 +2281 +2282 +2283 +2284 +2285 +2286 +2287 +2288 +2289 +2290 +2291 +2292 +2293 +2294 +2295 +2296 +2297 +2298 +2299 +2300 +2301 +2302 +2303 +2304 +2305 +2306 +2307 +2308 +2309 +2310 +2311 +2312 +2313 +2314 +2315 +2316 +2317 +2318 +2319 +2320 +2321 +2322 +2323 +2324 +2325 +2326 +2327 +2328 +2329 +2330 +2331 +2332 +2333 +2334 +2335 +2336 +2337 +2338 +2339 +2340 +2341 +2342 +2343 +2344 +2345 +2346 +2347 +2348 +2349 +2350 +2351 +2352 +2353 +2354 +2355 +2356 +2357 +2358 +2359 +2360 +2361 +2362 +2363 +2364 +2365 +2366 +2367 +2368 +2369 +2370 +2371 +2372 +2373 +2374 +2375 +2376 +2377 +2378 +2379 +2380 +2381 +2382 +2383 +2384 +2385 +2386 +2387 +2388 +2389 +2390 +2391 +2392 +2393 +2394 +2395 +2396 +2397 +2398 +2399 +2400 +2401 +2402 +2403 +2404 +2405 +2406 +2407 +2408 +2409 +2410 +2411 +2412 +2413 +2414 +2415 +2416 +2417 +2418 +2419 +2420 +2421 +2422 +2423 +2424 +2425 +2426 +2427 +2428 +2429 +2430 +2431 +2432 +2433 +2434 +2435 +2436 +2437 +2438 +2439 +2440 +2441 +2442 +2443 +2444 +2445 +2446 +2447 +2448 +2449 +2450 +2451 +2452 +2453 +2454 +2455 +2456 +2457 +2458 +2459 +2460 +2461 +2462 +2463 +2464 +2465 +2466 +2467 +2468 +2469 +2470 +2471 +2472 +2473 +2474 +2475 +2476 +2477 +2478 +2479 +2480 +2481 +2482 +2483 +2484 +2485 +2486 +2487 +2488 +2489 +2490 +2491 +2492 +2493 +2494 +2495 +2496 +2497 +2498 +2499 +2500 +2501 +2502 +2503 +2504 +2505 +2506 +2507 +2508 +2509 +2510 +2511 +2512 +2513 +2514 +2515 +2516 +2517 +2518 +2519 +2520 +2521 +2522 +2523 +2524 +2525 +2526 +2527 +2528 +2529 +2530 +2531 +2532 +2533 +2534 +2535 +2536 +2537 +2538 +2539 +2540 +2541 +2542 +2543 +2544 +2545 +2546 +2547 +2548 +2549 +2550 +2551 +2552 +2553 +2554 +2555 +2556 +2557 +2558 +2559 +2560 +2561 +2562 +2563 +2564 +2565 +2566 +2567 +2568 +2569 +2570 +2571 +2572 +2573 +2574 +2575 +2576 +2577 +2578 +2579 +2580 +2581 +2582 +2583 +2584 +2585 +2586 +2587 +2588 +2589 +2590 +2591 +2592 +2593 +2594 +2595 +2596 +2597 +2598 +2599 +2600 +2601 +2602 +2603 +2604 +2605 +2606 +2607 +2608 +2609 +2610 +2611 +2612 +2613 +2614 +2615 +2616 +2617 +2618 +2619 +2620 +2621 +2622 +2623 +2624 +2625 +2626 +2627 +2628 +2629 +2630 +2631 +2632 +2633 +2634 +2635 +2636 +2637 +2638 +2639 +2640 +2641 +2642 +2643 +2644 +2645 +2646 +2647 +2648 +2649 +2650 +2651 +2652 +2653 +2654 +2655 +2656 +2657 +2658 +2659 +2660 +2661 +2662 +2663 +2664 +2665 +2666 +2667 +2668 +2669 +2670 +2671 +2672 +2673 +2674 +2675 +2676 +2677 +2678 +2679 +2680 +2681 +2682 +2683 +2684 +2685 +2686 +2687 +2688 +2689 +2690 +2691 +2692 +2693 +2694 +2695 +2696 +2697 +2698 +2699 +2700 +2701 +2702 +2703 +2704 +2705 +2706 +2707 +2708 +2709 +2710 +2711 +2712 +2713 +2714 +2715 +2716 +2717 +2718 +2719 +2720 +2721 +2722 +2723 +2724 +2725 +2726 +2727 +2728 +2729 +2730 +2731 +2732 +2733 +2734 +2735 +2736 +2737 +2738 +2739 +2740 +2741 +2742 +2743 +2744 +2745 +2746 +2747 +2748 +2749 +2750 +2751 +2752 +2753 +2754 +2755 +2756 +2757 +2758 +2759 +2760 +2761 +2762 +2763 +2764 +2765 +2766 +2767 +2768 +2769 +2770 +2771 +2772 +2773 +2774 +2775 +2776 +2777 +2778 +2779 +2780 +2781 +2782 +2783 +2784 +2785 +2786 +2787 +2788 +2789 +2790 +2791 +2792 +2793 +2794 +2795 +2796 +2797 +2798 +2799 +2800 +2801 +2802 +2803 +2804 +2805 +2806 +2807 +2808 +2809 +2810 +2811 +2812 +2813 +2814 +2815 +2816 +2817 +2818 +2819 +2820 +2821 +2822 +2823 +2824 +2825 +2826 +2827 +2828 +2829 +2830 +2831 +2832 +2833 +2834 +2835 +2836 +2837 +2838 +2839 +2840 +2841 +2842 +2843 +2844 +2845 +2846 +2847 +2848 +2849 +2850 +2851 +2852 +2853 +2854 +2855 +2856 +2857 +2858 +2859 +2860 +2861 +2862 +2863 +2864 +2865 +2866 +2867 +2868 +2869 +2870 +2871 +2872 +2873 +2874 +2875 +2876 +2877 +2878 +2879 +2880 +2881 +2882 +2883 +2884 +2885 +2886 +2887 +2888 +2889 +2890 +2891 +2892 +2893 +2894 +2895 +2896 +2897 +2898 +2899 +2900 +2901 +2902 +2903 +2904 +2905 +2906 +2907 +2908 +2909 +2910 +2911 +2912 +2913 +2914 +2915 +2916 +2917 +2918 +2919 +2920 +2921 +2922 +2923 +2924 +2925 +2926 +2927 +2928 +2929 +2930 +2931 +2932 +2933 +2934 +2935 +2936 +2937 +2938 +2939 +2940 +2941 +2942 +2943 +2944 +2945 +2946 +2947 +2948 +2949 +2950 +2951 +2952 +2953 +2954 +2955 +2956 +2957 +2958 +2959 +2960 +2961 +2962 +2963 +2964 +2965 +2966 +2967 +2968 +2969 +2970 +2971 +2972 +2973 +2974 +2975 +2976 +2977 +2978 +2979 +2980 +2981 +2982 +2983 +2984 +2985 +2986 +2987 +2988 +2989 +2990 +2991 +2992 +2993 +2994 +2995 +2996 +2997 +2998 +2999 +3000 +3001 +3002 +3003 +3004 +3005 +3006 +3007 +3008 +3009 +3010 +3011 +3012 +3013 +3014 +3015 +3016 +3017 +3018 +3019 +3020 +3021 +3022 +3023 +3024 +3025 +3026 +3027 +3028 +3029 +3030 +3031 +3032 +3033 +3034 +3035 +3036 +3037 +3038 +3039 +3040 +3041 +3042 +3043 +3044 +3045 +3046 +3047 +3048 +3049 +3050 +3051 +3052 +3053 +3054 +3055 +3056 +3057 +3058 +3059 +3060 +3061 +3062 +3063 +3064 +3065 +3066 +3067 +3068 +3069 +3070 +3071 +3072 +3073 +3074 +3075 +3076 +3077 +3078 +3079 +3080 +3081 +3082 +3083 +3084 +3085 +3086 +3087 +3088 +3089 +3090 +3091 +3092 +3093 +3094 +3095 +3096 +3097 +3098 +3099 +3100 +3101 +3102 +3103 +3104 +3105 +3106 +3107 +3108 +3109 +3110 +3111 +3112 +3113 +3114 +3115 +3116 +3117 +3118 +3119 +3120 +3121 +3122 +3123 +3124 +3125 +3126 +3127 +3128 +3129 +3130 +3131 +3132 +3133 +3134 +3135 +3136 +3137 +3138 +3139 +3140 +3141 +3142 +3143 +3144 +3145 +3146 +3147 +3148 +3149 +3150 +3151 +3152 +3153 +3154 +3155 +3156 +3157 +3158 +3159 +3160 +3161 +3162 +3163 +3164 +3165 +3166 +3167 +3168 +3169 +3170 +3171 +3172 +3173 +3174 +3175 +3176 +3177 +3178 +3179 +3180 +3181 +3182 +3183 +3184 +3185 +3186 +3187 +3188 +3189 +3190 +3191 +3192 +3193 +3194 +3195 +3196 +3197 +3198 +3199 +3200 +3201 +3202 +3203 +3204 +3205 +3206 +3207 +3208 +3209 +3210 +3211 +3212 +3213 +3214 +3215 +3216 +3217 +3218 +3219 +3220 +3221 +3222 +3223 +3224 +3225 +3226 +3227 +3228 +3229 +3230 +3231 +3232 +3233 +3234 +3235 +3236 +3237 +3238 +3239 +3240 +3241 +3242 +3243 +3244 +3245 +3246 +3247 +3248 +3249 +3250 +3251 +3252 +3253 +3254 +3255 +3256 +3257 +3258 +3259 +3260 +3261 +3262 +3263 +3264 +3265 +3266 +3267 +3268 +3269 +3270 +3271 +3272 +3273 +3274 +3275 +3276 +3277 +3278 +3279 +3280 +3281 +3282 +3283 +3284 +3285 +3286 +3287 +3288 +3289 +3290 +3291 +3292 +3293 +3294 +3295 +3296 +3297 +3298 +3299 +3300 +3301 +3302 +3303 +3304 +3305 +3306 +3307 +3308 +3309 +3310 +3311 +3312 +3313 +3314 +3315 +3316 +3317 +3318 +3319 +3320 +3321 +3322 +3323 +3324 +3325 +3326 +3327 +3328 +3329 +3330 +3331 +3332 +3333 +3334 +3335 +3336 +3337 +3338 +3339 +3340 +3341 +3342 +3343 +3344 +3345 +3346 +3347 +3348 +3349 +3350 +3351 +3352 +3353 +3354 +3355 +3356 +3357 +3358 +3359 +3360 +3361 +3362 +3363 +3364 +3365 +3366 +3367 +3368 +3369 +3370 +3371 +3372 +3373 +3374 +3375 +3376 +3377 +3378 +3379 +3380 +3381 +3382 +3383 +3384 +3385 +3386 +3387 +3388 +3389 +3390 +3391 +3392 +3393 +3394 +3395 +3396 +3397 +3398 +3399 +3400 +3401 +3402 +3403 +3404 +3405 +3406 +3407 +3408 +3409 +3410 +3411 +3412 +3413 +3414 +3415 +3416 +3417 +3418 +3419 +3420 +3421 +3422 +3423 +3424 +3425 +3426 +3427 +3428 +3429 +3430 +3431 +3432 +3433 +3434 +3435 +3436 +3437 +3438 +3439 +3440 +3441 +3442 +3443 +3444 +3445 +3446 +3447 +3448 +3449 +3450 +3451 +3452 +3453 +3454 +3455 +3456 +3457 +3458 +3459 +3460 +3461 +3462 +3463 +3464 +3465 +3466 +3467 +3468 +3469 +3470 +3471 +3472 +3473 +3474 +3475 +3476 +3477 +3478 +3479 +3480 +3481 +3482 +3483 +3484 +3485 +3486 +3487 +3488 +3489 +3490 +3491 +3492 +3493 +3494 +3495 +3496 +3497 +3498 +3499 +3500 +3501 +3502 +3503 +3504 +3505 +3506 +3507 +3508 +3509 +3510 +3511 +3512 +3513 +3514 +3515 +3516 +3517 +3518 +3519 +3520 +3521 +3522 +3523 +3524 +3525 +3526 +3527 +3528 +3529 +3530 +3531 +3532 +3533 +3534 +3535 +3536 +3537 +3538 +3539 +3540 +3541 +3542 +3543 +3544 +3545 +3546 +3547 +3548 +3549 +3550 +3551 +3552 +3553 +3554 +3555 +3556 +3557 +3558 +3559 +3560 +3561 +3562 +3563 +3564 +3565 +3566 +3567 +3568 +3569 +3570 +3571 +3572 +3573 +3574 +3575 +3576 +3577 +3578 +3579 +3580 +3581 +3582 +3583 +3584 +3585 +3586 +3587 +3588 +3589 +3590 +3591 +3592 +3593 +3594 +3595 +3596 +3597 +3598 +3599 +3600 +3601 +3602 +3603 +3604 +3605 +3606 +3607 +3608 +3609 +3610 +3611 +3612 +3613 +3614 +3615 +3616 +3617 +3618 +3619 +3620 +3621 +3622 +3623 +3624 +3625 +3626 +3627 +3628 +3629 +3630 +3631 +3632 +3633 +3634 +3635 +3636 +3637 +3638 +3639 +3640 +3641 +3642 +3643 +3644 +3645 +3646 +3647 +3648 +3649 +3650 +3651 +3652 +3653 +3654 +3655 +3656 +3657 +3658 +3659 +3660 +3661 +3662 +3663 +3664 +3665 +3666 +3667 +3668 +3669 +3670 +3671 +3672 +3673 +3674 +3675 +3676 +3677 +3678 +3679 +3680 +3681 +3682 +3683 +3684 +3685 +3686 +3687 +3688 +3689 +3690 +3691 +3692 +3693 +3694 +3695 +3696 +3697 +3698 +3699 +3700 +3701 +3702 +3703 +3704 +3705 +3706 +3707 +3708 +3709 +3710 +3711 +3712 +3713 +3714 +3715 +3716 +3717 +3718 +3719 +3720 +3721 +3722 +3723 +3724 +3725 +3726 +3727 +3728 +3729 +3730 +3731 +3732 +3733 +3734 +3735 +3736 +3737 +3738 +3739 +3740 +3741 +3742 +3743 +3744 +3745 +3746 +3747 +3748 +3749 +3750 +3751 +3752 +3753 +3754 +3755 +3756 +3757 +3758 +3759 +3760 +3761 +3762 +3763 +3764 +3765 +3766 +3767 +3768 +3769 +3770 +3771 +3772 +3773 +3774 +3775 +3776 +3777 +3778 +3779 +3780 +3781 +3782 +3783 +3784 +3785 +3786 +3787 +3788 +3789 +3790 +3791 +3792 +3793 +3794 +3795 +3796 +3797 +3798 +3799 +3800 +3801 +3802 +3803 +3804 +3805 +3806 +3807 +3808 +3809 +3810 +3811 +3812 +3813 +3814 +3815 +3816 +3817 +3818 +3819 +3820 +3821 +3822 +3823 +3824 +3825 +3826 +3827 +3828 +3829 +3830 +3831 +3832 +3833 +3834 +3835 +3836 +3837 +3838 +3839 +3840 +3841 +3842 +3843 +3844 +3845 +3846 +3847 +3848 +3849 +3850 +3851 +3852 +3853 +3854 +3855 +3856 +3857 +3858 +3859 +3860 +3861 +3862 +3863 +3864 +3865 +3866 +3867 +3868 +3869 +3870 +3871 +3872 +3873 +3874 +3875 +3876 +3877 +3878 +3879 +3880 +3881 +3882 +3883 +3884 +3885 +3886 +3887 +3888 +3889 +3890 +3891 +3892 +3893 +3894 +3895 +3896 +3897 +3898 +3899 +3900 +3901 +3902 +3903 +3904 +3905 +3906 +3907 +3908 +3909 +3910 +3911 +3912 +3913 +3914 +3915 +3916 +3917 +3918 +3919 +3920 +3921 +3922 +3923 +3924 +3925 +3926 +3927 +3928 +3929 +3930 +3931 +3932 +3933 +3934 +3935 +3936 +3937 +3938 +3939 +3940 +3941 +3942 +3943 +3944 +3945 +3946 +3947 +3948 +3949 +3950 +3951 +3952 +3953 +3954 +3955 +3956 +3957 +3958 +3959 +3960 +3961 +3962 +3963 +3964 +3965 +3966 +3967 +3968 +3969 +3970 +3971 +3972 +3973 +3974 +3975 +3976 +3977 +3978 +3979 +3980 +3981 +3982 +3983 +3984 +3985 +3986 +3987 +3988 +3989 +3990 +3991 +3992 +3993 +3994 +3995 +3996 +3997 +3998 +3999 +4000 +4001 +4002 +4003 +4004 +4005 +4006 +4007 +4008 +4009 +4010 +4011 +4012 +4013 +4014 +4015 +4016 +4017 +4018 +4019 +4020 +4021 +4022 +4023 +4024 +4025 +4026 +4027 +4028 +4029 +4030 +4031 +4032 +4033 +4034 +4035 +4036 +4037 +4038 +4039 +4040 +4041 +4042 +4043 +4044 +4045 +4046 +4047 +4048 +4049 +4050 +4051 +4052 +4053 +4054 +4055 +4056 +4057 +4058 +4059 +4060 +4061 +4062 +4063 +4064 +4065 +4066 +4067 +4068 +4069 +4070 +4071 +4072 +4073 +4074 +4075 +4076 +4077 +4078 +4079 +4080 +4081 +4082 +4083 +4084 +4085 +4086 +4087 +4088 +4089 +4090 +4091 +4092 +4093 +4094 +4095 +4096 +4097 +4098 +4099 +4100 +4101 +4102 +4103 +4104 +4105 +4106 +4107 +4108 +4109 +4110 +4111 +4112 +4113 +4114 +4115 +4116 +4117 +4118 +4119 +4120 +4121 +4122 +4123 +4124 +4125 +4126 +4127 +4128 +4129 +4130 +4131 +4132 +4133 +4134 +4135 +4136 +4137 +4138 +4139 +4140 +4141 +4142 +4143 +4144 +4145 +4146 +4147 +4148 +4149 +4150 +4151 +4152 +4153 +4154 +4155 +4156 +4157 +4158 +4159 +4160 +4161 +4162 +4163 +4164 +4165 +4166 +4167 +4168 +4169 +4170 +4171 +4172 +4173 +4174 +4175 +4176 +4177 +4178 +4179 +4180 +4181 +4182 +4183 +4184 +4185 +4186 +4187 +4188 +4189 +4190 +4191 +4192 +4193 +4194 +4195 +4196 +4197 +4198 +4199 +4200 +4201 +4202 +4203 +4204 +4205 +4206 +4207 +4208 +4209 +4210 +4211 +4212 +4213 +4214 +4215 +4216 +4217 +4218 +4219 +4220 +4221 +4222 +4223 +4224 +4225 +4226 +4227 +4228 +4229 +4230 +4231 +4232 +4233 +4234 +4235 +4236 +4237 +4238 +4239 +4240 +4241 +4242 +4243 +4244 +4245 +4246 +4247 +4248 +4249 +4250 +4251 +4252 +4253 +4254 +4255 +4256 +4257 +4258 +4259 +4260 +4261 +4262 +4263 +4264 +4265 +4266 +4267 +4268 +4269 +4270 +4271 +4272 +4273 +4274 +4275 +4276 +4277 +4278 +4279 +4280 +4281 +4282 +4283 +4284 +4285 +4286 +4287 +4288 +4289 +4290 +4291 +4292 +4293 +4294 +4295 +4296 +4297 +4298 +4299 +4300 +4301 +4302 +4303 +4304 +4305 +4306 +4307 +4308 +4309 +4310 +4311 +4312 +4313 +4314 +4315 +4316 +4317 +4318 +4319 +4320 +4321 +4322 +4323 +4324 +4325 +4326 +4327 +4328 +4329 +4330 +4331 +4332 +4333 +4334 +4335 +4336 +4337 +4338 +4339 +4340 +4341 +4342 +4343 +4344 +4345 +4346 +4347 +4348 +4349 +4350 +4351 +4352 +4353 +4354 +4355 +4356 +4357 +4358 +4359 +4360 +4361 +4362 +4363 +4364 +4365 +4366 +4367 +4368 +4369 +4370 +4371 +4372 +4373 +4374 +4375 +4376 +4377 +4378 +4379 +4380 +4381 +4382 +4383 +4384 +4385 +4386 +4387 +4388 +4389 +4390 +4391 +4392 +4393 +4394 +4395 +4396 +4397 +4398 +4399 +4400 +4401 +4402 +4403 +4404 +4405 +4406 +4407 +4408 +4409 +4410 +4411 +4412 +4413 +4414 +4415 +4416 +4417 +4418 +4419 +4420 +4421 +4422 +4423 +4424 +4425 +4426 +4427 +4428 +4429 +4430 +4431 +4432 +4433 +4434 +4435 +4436 +4437 +4438 +4439 +4440 +4441 +4442 +4443 +4444 +4445 +4446 +4447 +4448 +4449 +4450 +4451 +4452 +4453 +4454 +4455 +4456 +4457 +4458 +4459 +4460 +4461 +4462 +4463 +4464 +4465 +4466 +4467 +4468 +4469 +4470 +4471 +4472 +4473 +4474 +4475 +4476 +4477 +4478 +4479 +4480 +4481 +4482 +4483 +4484 +4485 +4486 +4487 +4488 +4489 +4490 +4491 +4492 +4493 +4494 +4495 +4496 +4497 +4498 +4499 +4500 +4501 +4502 +4503 +4504 +4505 +4506 +4507 +4508 +4509 +4510 +4511 +4512 +4513 +4514 +4515 +4516 +4517 +4518 +4519 +4520 +4521 +4522 +4523 +4524 +4525 +4526 +4527 +4528 +4529 +4530 +4531 +4532 +4533 +4534 +4535 +4536 +4537 +4538 +4539 +4540 +4541 +4542 +4543 +4544 +4545 +4546 +4547 +4548 +4549 +4550 +4551 +4552 +4553 +4554 +4555 +4556 +4557 +4558 +4559 +4560 +4561 +4562 +4563 +4564 +4565 +4566 +4567 +4568 +4569 +4570 +4571 +4572 +4573 +4574 +4575 +4576 +4577 +4578 +4579 +4580 +4581 +4582 +4583 +4584 +4585 +4586 +4587 +4588 +4589 +4590 +4591 +4592 +4593 +4594 +4595 +4596 +4597 +4598 +4599 +4600 +4601 +4602 +4603 +4604 +4605 +4606 +4607 +4608 +4609 +4610 +4611 +4612 +4613 +4614 +4615 +4616 +4617 +4618 +4619 +4620 +4621 +4622 +4623 +4624 +4625 +4626 +4627 +4628 +4629 +4630 +4631 +4632 +4633 +4634 +4635 +4636 +4637 +4638 +4639 +4640 +4641 +4642 +4643 +4644 +4645 +4646 +4647 +4648 +4649 +4650 +4651 +4652 +4653 +4654 +4655 +4656 +4657 +4658 +4659 +4660 +4661 +4662 +4663 +4664 +4665 +4666 +4667 +4668 +4669 +4670 +4671 +4672 +4673 +4674 +4675 +4676 +4677 +4678 +4679 +4680 +4681 +4682 +4683 +4684 +4685 +4686 +4687 +4688 +4689 +4690 +4691 +4692 +4693 +4694 +4695 +4696 +4697 +4698 +4699 +4700 +4701 +4702 +4703 +4704 +4705 +4706 +4707 +4708 +4709 +4710 +4711 +4712 +4713 +4714 +4715 +4716 +4717 +4718 +4719 +4720 +4721 +4722 +4723 +4724 +4725 +4726 +4727 +4728 +4729 +4730 +4731 +4732 +4733 +4734 +4735 +4736 +4737 +4738 +4739 +4740 +4741 +4742 +4743 +4744 +4745 +4746 +4747 +4748 +4749 +4750 +4751 +4752 +4753 +4754 +4755 +4756 +4757 +4758 +4759 +4760 +4761 +4762 +4763 +4764 +4765 +4766 +4767 +4768 +4769 +4770 +4771 +4772 +4773 +4774 +4775 +4776 +4777 +4778 +4779 +4780 +4781 +4782 +4783 +4784 +4785 +4786 +4787 +4788 +4789 +4790 +4791 +4792 +4793 +4794 +4795 +4796 +4797 +4798 +4799 +4800 +4801 +4802 +4803 +4804 +4805 +4806 +4807 +4808 +4809 +4810 +4811 +4812 +4813 +4814 +4815 +4816 +4817 +4818 +4819 +4820 +4821 +4822 +4823 +4824 +4825 +4826 +4827 +4828 +4829 +4830 +4831 +4832 +4833 +4834 +4835 +4836 +4837 +4838 +4839 +4840 +4841 +4842 +4843 +4844 +4845 +4846 +4847 +4848 +4849 +4850 +4851 +4852 +4853 +4854 +4855 +4856 +4857 +4858 +4859 +4860 +4861 +4862 +4863 +4864 +4865 +4866 +4867 +4868 +4869 +4870 +4871 +4872 +4873 +4874 +4875 +4876 +4877 +4878 +4879 +4880 +4881 +4882 +4883 +4884 +4885 +4886 +4887 +4888 +4889 +4890 +4891 +4892 +4893 +4894 +4895 +4896 +4897 +4898 +4899 +4900 +4901 +4902 +4903 +4904 +4905 +4906 +4907 +4908 +4909 +4910 +4911 +4912 +4913 +4914 +4915 +4916 +4917 +4918 +4919 +4920 +4921 +4922 +4923 +4924 +4925 +4926 +4927 +4928 +4929 +4930 +4931 +4932 +4933 +4934 +4935 +4936 +4937 +4938 +4939 +4940 +4941 +4942 +4943 +4944 +4945 +4946 +4947 +4948 +4949 +4950 +4951 +4952 +4953 +4954 +4955 +4956 +4957 +4958 +4959 +4960 +4961 +4962 +4963 +4964 +4965 +4966 +4967 +4968 +4969 +4970 +4971 +4972 +4973 +4974 +4975 +4976 +4977 +4978 +4979 +4980 +4981 +4982 +4983 +4984 +4985 +4986 +4987 +4988 +4989 +4990 +4991 +4992 +4993 +4994 +4995 +4996 +4997 +4998 +4999 +5000 +5001 +5002 +5003 +5004 +5005 +5006 +5007 +5008 +5009 +5010 +5011 +5012 +5013 +5014 +5015 +5016 +5017 +5018 +5019 +5020 +5021 +5022 +5023 +5024 +5025 +5026 +5027 +5028 +5029 +5030 +5031 +5032 +5033 +5034 +5035 +5036 +5037 +5038 +5039 +5040 +5041 +5042 +5043 +5044 +5045 +5046 +5047 +5048 +5049 +5050 +5051 +5052 +5053 +5054 +5055 +5056 +5057 +5058 +5059 +5060 +5061 +5062 +5063 +5064 +5065 +5066 +5067 +5068 +5069 +5070 +5071 +5072 +5073 +5074 +5075 +5076 +5077 +5078 +5079 +5080 +5081 +5082 +5083 +5084 +5085 +5086 +5087 +5088 +5089 +5090 +5091 +5092 +5093 +5094 +5095 +5096 +5097 +5098 +5099 +5100 +5101 +5102 +5103 +5104 +5105 +-0.3497958702862998 -0.3970074580294142 0 +-0.4168282303862305 0.346280468133716 0 +0.3498832687390062 0.4049893127571638 0 +0.4234322916431661 -0.3516121315219719 0 +-0.3141526090785057 -0.2063881984494136 0 +-0.3107772909559421 0.2032395211026326 0 +-0.36675698537657 -0.279366650637845 0 +-0.2617485641626822 -0.3043879747397805 0 +0.1733829045194786 -0.2423541598558785 0 +0.2647295985014821 -0.3353182844452809 0 +0.1719773033044808 0.2420239350750671 0 +0.2645560219532806 0.3280549045652956 0 +0.3048051318973071 -0.2372442219043673 0 +0.2878774539449606 0.2291092824972678 0 +0.3583660972985119 0.2892094630804684 0 +-0.3559272585202332 0.4217043541698738 0 +0.3470993818573286 -0.4118557564801141 0 +-0.3598887003394499 0.2702779061871265 0 +-0.2601825390645822 0.2957601472355106 0 +-0.1617365412580795 0.2695333065994889 0 +-0.1650933320584841 -0.2727685225643594 0 +-0.4318879625097912 -0.3435528533979946 0 +-0.2538475893729929 -0.4157603775725625 0 +-0.2685067321471339 0.4082252671180854 0 +0.4138032484057256 -0.2547686822281993 0 +0.2505236955327206 -0.4218755276421553 0 +0.2554527264733304 0.4226462634485578 0 +0.4299031639515176 0.3474763684429932 0 +-0.2098663036975409 0.179519579826169 0 +-0.2120770715474186 -0.1821563124111884 0 +-0.4296711629691193 0.4292453723878615 0 +0.4277031049529293 -0.427630454833092 0 +-0.4333992704841064 -0.4289802330528656 0 +-0.438725914857668 0.2549288441237972 0 +-0.4429513014886957 -0.2526950600869533 0 +-0.3329526189900363 0.3477245611629712 0 +0.4350029370654079 0.4325758656459665 0 +0.3507355216847021 -0.3178555878672461 0 +0.4378264002126588 0.2560008834421256 0 +0.2499341799876277 -0.1348375614633604 0 +0.2506793161998568 0.1371039787996684 0 +-0.3057169699837403 -0.3505301198364479 0 +-0.3176419966485439 -0.293578467368419 0 +-0.3572699884835631 -0.3368353837007758 0 +-0.3990572830870555 -0.3128413834805399 0 +-0.3909300667972884 -0.3698885077689242 0 +-0.2642802544905513 -0.1944790368596051 0 +-0.2672176287283501 -0.1405052224154479 0 +-0.3181364633874966 -0.1566194781999282 0 +-0.2015408645468005 -0.1269381628809119 0 +-0.2568805080945082 -0.08478863400484947 0 +-0.2478068218641795 -0.03053153225148703 0 +-0.2567573135320769 0.08481981025403094 0 +-0.2476572242122303 0.03056936560793889 0 +-0.08019021678481074 -0.2414088767879514 0 +-0.01462956651161009 -0.2509751145333026 0 +-0.01469975643451555 0.2510432841549759 0 +-0.08090169943749485 0.2414968887489284 0 +-0.2675407823270101 0.1395979264647454 0 +-0.2610751147014638 0.1915294418104932 0 +-0.317536372711469 0.153686424203021 0 +-0.2011561476221974 0.1258608322404852 0 +0.158782973097961 0.1821540968674906 0 +0.1048731817607372 0.221009545211566 0 +0.1053104540389482 -0.2211804229104011 0 +0.1589534082159024 -0.1814936342623952 0 +0.3128613923688623 0.3099718212468627 0 +0.3532893122240146 0.3455951011925577 0 +0.3071505712183342 0.3665645625162083 0 +0.04903576487081753 -0.2553887349154744 0 +0.04891072928398642 0.2551862329521835 0 +0.1130755321816462 0.2784911842542354 0 +0.1135572550137883 -0.2786027400421182 0 +-0.3008218810198584 -0.4060988423414413 0 +-0.2573986562861211 -0.3644656341268699 0 +-0.2350756501583023 0.2377443575475856 0 +-0.2854181537736895 0.2452540683688111 0 +0.3942287477359842 0.3199264331476509 0 +0.3900741643787921 0.3759920494221135 0 +-0.2360529465012101 -0.2427871570798878 0 +-0.2879843469547347 -0.253684685111506 0 +-0.2168951086178217 0.3953265254142948 0 +-0.2640120018964567 0.3522667498052124 0 +-0.2121750750742112 0.3390377316280563 0 +-0.1854293219473139 0.2252083260677607 0 +-0.2099688492407125 0.2826748234498268 0 +0.3286179331507447 -0.278423534008257 0 +0.3811100346068942 -0.2866624668676801 0 +0.3604034403773317 -0.2444808229512715 0 +-0.1876613962017551 -0.2271374609388907 0 +-0.2134020485143152 -0.2884926781288957 0 +-0.1622676466127769 0.3208494252276712 0 +0.2134645883516056 0.1869615836216026 0 +0.270834844252163 0.1852360190064821 0 +0.2361194810737511 0.23474316782282 0 +0.2767289989701898 -0.1878656220311528 0 +0.2143854370316183 -0.1862431856406941 0 +0.2418720438954203 -0.2383376695185153 0 +-0.2104873583761946 -0.3991984076921458 0 +-0.211220451523497 -0.3435047665220339 0 +0.3985493860224789 -0.2112722829209036 0 +0.3406200787345506 -0.1982648298086006 0 +0.1998630181539552 0.1307282754355773 0 +0.1991614757799951 -0.1287170436024751 0 +-0.1636607001567286 -0.3242738329915552 0 +0.3232038198277686 0.2564309261902843 0 +0.2744230293231477 0.2755688730772311 0 +0.260993639074822 0.03292919578466037 0 +0.2611242219379848 -0.03251811290048653 0 +0.2132257069900988 -0.353431054820683 0 +0.221612011407358 -0.292530356304339 0 +0.1671512127462192 -0.3138522126679855 0 +-0.3248615874986671 -0.4484217029897319 0 +-0.3733868034713666 -0.4517576222545905 0 +-0.4613868032244018 0.3239717510614087 0 +-0.4587548559456728 0.3725953361193359 0 +0.3684665975346434 0.2303881992890504 0 +0.3291489067142231 0.1989513423020789 0 +-0.3821465325683074 -0.2272305723779277 0 +-0.3397997820764717 -0.2425551929810587 0 +-0.3551092900799945 -0.1901573461108474 0 +-0.1126734443112304 0.2890244131360598 0 +-0.1308682706290399 0.2213691936781882 0 +0.1665948604482582 0.3129743862523223 0 +-0.1325466660292419 -0.2229868016606237 0 +-0.1138893744161875 -0.2909008546046032 0 +0.3253901160984164 0.4539843772916315 0 +0.3734194349286029 0.4559247248593381 0 +0.4612113085423403 -0.3258428027892012 0 +0.4617989724140423 -0.3747984155606215 0 +0.3108538659724676 0.1494510794047584 0 +0.2808454379215807 0.09853467475602941 0 +0.2797435089812536 -0.09670971331937087 0 +0.3121143543633627 -0.1487504196436855 0 +0.4643562801239214 0.2294074920552309 0 +0.4138526749479288 0.2185573548698545 0 +-0.3517993263883417 0.188151787279567 0 +-0.2287541034027715 -0.4544137336115551 0 +-0.2342527890784636 0.4539851489484635 0 +0.2268797251078257 -0.4574231078413912 0 +0.2116390651110499 -0.4042053469415641 0 +0.2146333481217992 0.4042645399781601 0 +0.2287405871052735 0.4587949106224777 0 +0.4533331789332263 -0.2292417576182544 0 +0.3489820483202639 -0.3648929717771813 0 +0.307682213453023 -0.3266076849719545 0 +0.3059287400885848 -0.3736450688818898 0 +0.3870170369623234 -0.3346350349031422 0 +0.3852635635978851 -0.3816724188130775 0 +0.2283845392483541 -0.07421908274705731 0 +0.2289467633381752 0.07552865248938465 0 +0.4330638196258259 0.3000527984104636 0 +0.4684837320062435 0.2775592584953613 0 +0.4651837996174064 0.3236271064696318 0 +0.2810489295533983 -0.2848782287378039 0 +0.3006987702100552 0.4137811926233757 0 +0.2596404891900362 0.3764265736029251 0 +0.2188522438611392 0.2869933592844503 0 +0.2133013695745558 0.3511763300905988 0 +-0.3776627762443071 0.4609324953460651 0 +-0.328734598755785 0.4600674038322026 0 +-0.4660684792986212 -0.3714668047791923 0 +-0.4670261469610991 -0.3223020933201455 0 +0.3732873551519212 -0.4562174383646135 0 +0.323807228889093 -0.4570671321786601 0 +-0.2767990045012743 -0.4576311473205689 0 +0.257233587052614 -0.3792064137780977 0 +0.2954747627986259 -0.416971562539875 0 +-0.2836443123801827 0.4546583768692694 0 +0.4578115878444846 -0.2769624951871252 0 +-0.4619080084052124 0.461729815223177 0 +-0.4167397598655444 0.4637601754434461 0 +-0.4636186630523085 0.4157084574335508 0 +0.46510530461755 0.373536971562181 0 +0.3993402962547132 0.2701792050033428 0 +0.4605686042663389 -0.4606163310871447 0 +0.4146035003623597 -0.4628405699758468 0 +0.4632403288561413 -0.4156130925054404 0 +-0.4688079086369959 0.277080551739757 0 +-0.4283939624703825 0.2974982102035713 0 +-0.3007616965421023 0.3778378231490539 0 +-0.2966514057731705 0.3218372418350915 0 +-0.4370838639396145 -0.2967076218313135 0 +-0.4712799623169966 -0.2760024216247001 0 +0.2775664556394497 0.4608963917328116 0 +-0.3108479715877466 0.2825529371471096 0 +-0.3346487749427397 0.2362733141823378 0 +-0.3787331657384567 0.2234101571383927 0 +0.2748167004750935 -0.4602103394691711 0 +0.4203541840875042 -0.3044064840113453 0 +-0.4169905985888684 -0.4638499186279037 0 +-0.4636214189574633 -0.4619269272731444 0 +-0.4661025077172601 -0.4159889290626966 0 +-0.3132077515438649 0.4169288316825358 0 +-0.1575324699544128 -0.1771622372873878 0 +-0.4004993446050619 0.2620737058677328 0 +-0.3883029758976811 0.3078014524416121 0 +-0.4057838154399407 -0.2653026304444133 0 +-0.1579630807711806 0.1760385754090764 0 +-0.3443842642649457 0.3843821332780288 0 +0.4643631368450712 0.4635229197603359 0 +0.4666615544729089 0.4177805492107733 0 +0.4182760888210652 0.465220207714607 0 +-0.4212678543501692 0.2190657083030573 0 +-0.4666303136661815 0.2293032876409057 0 +-0.4694445516607015 -0.227525819705394 0 +-0.4235524683978978 -0.2188673983226773 0 +-0.3952361442582958 -0.4142438611866728 0 +0.3964945836623218 0.419786122594865 0 +-0.4241139421407617 0.3924782891239333 0 +0.4252073703457674 -0.3903312238492362 0 +-0.3465001551487278 0.3089247164237247 0 +-0.3720178114153715 0.3467261807470526 0 +-0.3939025628224541 0.4251150849727399 0 +-0.3852399296450376 0.3848628656335588 0 +0.3887501291323786 -0.419988400891729 0 +-0.4310394958956485 -0.3889584721515393 0 +0.4310252721246057 0.3928414116963053 0 +-0.3277200724905373 -0.3736867629079558 0 +-0.3319880674188084 -0.3443098142279518 0 +-0.3533313459093737 -0.3666215953388996 0 +-0.3102270809098988 -0.3212493452484121 0 +-0.3362805989848042 -0.3149460704326973 0 +-0.2845780182480158 -0.3273889113628352 0 +-0.2911035423041539 -0.299559386095882 0 +-0.3434426095665356 -0.2871120608461291 0 +-0.3615289907874419 -0.3074666455912989 0 +-0.3829472271794129 -0.2960387038187315 0 +-0.3783864561401956 -0.3246493356045725 0 +-0.394952246028948 -0.3410676581374244 0 +-0.3742310535806245 -0.3537278858901221 0 +-0.4152895301610888 -0.3291188639054217 0 +-0.4115335126972654 -0.3564110586636544 0 +-0.3703266208973113 -0.3833659568741939 0 +-0.2897625753059612 -0.2005442503674154 0 +-0.2913323994362875 -0.1735706990530623 0 +-0.3157246279987437 -0.182194991899172 0 +-0.2653516122529176 -0.1673502492690644 0 +-0.2944929769774391 -0.1469736494407089 0 +-0.2393854015745295 -0.1885350616212179 0 +-0.238105566413196 -0.1608914279074879 0 +-0.2963035627491834 -0.1201849599088092 0 +-0.3211458593678264 -0.1300500270574666 0 +-0.2628722272946882 -0.1125520123860914 0 +-0.2916497574997072 -0.09246726519526374 0 +-0.2337979745646573 -0.1328544279994662 0 +-0.2282505199210854 -0.1046608488019902 0 +-0.205622801057959 -0.1539824162379581 0 +-0.1976014598980217 -0.09996577628027832 0 +-0.2225759016246179 -0.07673973313690652 0 +-0.2769406550427119 -0.04171697745822792 0 +-0.2741572877052874 -0.01423453583751371 0 +-0.2820907782211666 -0.06798291167653565 0 +-0.2506540555078367 -0.05866446975607076 0 +-0.220126734036053 -0.04865275016380032 0 +-0.2230151581674467 0.07709333883802358 0 +-0.2507410274503642 0.05862659392592949 0 +-0.220438878911244 0.04852330240324652 0 +-0.2820907782211667 0.0679829116765354 0 +-0.2769681190106479 0.0416747292066272 0 +-0.2916233407131255 0.09243971367120889 0 +-0.2741771793232191 0.01414083481401342 0 +-0.09013039194430597 -0.2075447356858424 0 +-0.05743263833847434 -0.2175381648478428 0 +-0.04721929055414814 -0.2474360062103171 0 +-0.02384156788882155 -0.2233341647859063 0 +-0.07101395734378128 -0.2754366702057909 0 +-0.03885326887760508 -0.2771307097581628 0 +0.0101763146398438 -0.2252931819670343 0 +0.0102074665585367 0.2253180184036374 0 +-0.02393639426872506 0.223323154314409 0 +-0.04743988181206324 0.2476979466154087 0 +-0.05752892458034753 0.2175843519559654 0 +-0.03892029950812245 0.2772638464389948 0 +-0.0713525491562421 0.2756427927449488 0 +-0.08983345008772728 0.2075877113436966 0 +-0.2967265467774162 0.119756766655644 0 +-0.2932630046052664 0.1459220289419268 0 +-0.3215055509612766 0.1277638593626613 0 +-0.264204667313685 0.1655614410733414 0 +-0.2894991013367001 0.1716040426923165 0 +-0.2383090610160494 0.1594786455425178 0 +-0.2358327825848828 0.1855965352537088 0 +-0.2868887215700687 0.1976027135684586 0 +-0.313878491363866 0.1789136282812832 0 +-0.1975979253238402 0.09961987524178136 0 +-0.2281632443533275 0.1041405360458019 0 +-0.2336255624834713 0.1318694074696541 0 +-0.2627428080874169 0.1120805592809097 0 +-0.2050270279255183 0.1522878194179009 0 +0.1550991573626802 0.1550640415599267 0 +0.1296589337013916 0.1772136432422021 0 +0.1329532745463553 0.2030235735167973 0 +0.101196391300338 0.1955177636179952 0 +0.1642386937214911 0.2104578216226041 0 +0.1375786923080385 0.2310605759232392 0 +0.07140920612633837 0.2113488302701079 0 +0.07159117679709698 -0.2114560109860925 0 +0.1015561755966104 -0.1956962855522013 0 +0.1334848960730482 -0.2030250312738235 0 +0.1298374606313426 -0.1770847563565471 0 +0.1382879337492773 -0.2312535580750454 0 +0.1645992451875672 -0.2098319187719119 0 +0.1547918342287893 -0.1543825654505123 0 +0.2890450508772232 0.3195481091590539 0 +0.3097010430569054 0.337932577210097 0 +0.2857864414768783 0.347169118511903 0 +0.3331675127056242 0.3279940261117845 0 +0.3307637291921121 0.3567686749047019 0 +0.3361905539076798 0.2998522436674957 0 +0.3554853578519709 0.3167143266971352 0 +0.3515385040111786 0.3751461280693492 0 +0.3285441453704092 0.3857373591180367 0 +0.01719914833381408 -0.2521830670273233 0 +0.04287123242844783 -0.2289824509929411 0 +-0.007197503452081633 -0.2768407727812348 0 +0.02416064630976478 -0.2779163647063238 0 +0.05550876737443424 -0.2824284134650988 0 +0.04256390055752764 0.2282925601992298 0 +0.01724457908195143 0.2523552511390162 0 +0.02417720683371311 0.2779842456992292 0 +-0.007201860178489942 0.2769296998945704 0 +0.0555996949342704 0.283209020555273 0 +0.07649948802849767 0.2379357742547163 0 +0.1094021313669207 0.2488832446060048 0 +0.08187073022374397 0.2657340209810295 0 +0.1407553788637071 0.2618458638517026 0 +0.08669911166349109 0.2942519205237096 0 +0.08694945050266802 -0.2941849709236783 0 +0.0821189062102346 -0.2656641999511532 0 +0.1098236831935859 -0.2490604882783514 0 +0.07666556160572705 -0.2378810328471027 0 +0.1413329283524763 -0.2624344711927353 0 +-0.3252402133960137 -0.4014678080245016 0 +-0.3037092885618897 -0.3784024216206458 0 +-0.2795993223868928 -0.3836028758114022 0 +-0.2817330041202274 -0.3556616199018796 0 +-0.276334704979277 -0.4108392598006532 0 +-0.2554489883684108 -0.3923736236311902 0 +-0.2594900352547994 -0.3358214301616165 0 +-0.220989286328283 0.01715180261525524 0 +-0.2472118633800196 -7.206122908331539e-05 0 +-0.2209586368915525 -0.01714784381244627 0 +-0.2729264705571222 0.2204919393251091 0 +-0.2979867883053002 0.2224090809129664 0 +-0.2476987237425219 0.2145619540059932 0 +-0.2603033969729026 0.2436743428667015 0 +-0.2224709769279216 0.2086319686868773 0 +-0.247680323388683 0.2668567464082939 0 +-0.2728654951273749 0.2694270916190106 0 +0.3762660337660507 0.3047907975817038 0 +0.3739673733604721 0.3323551362421703 0 +0.3921219478213929 0.3473067258042201 0 +0.3718742253107763 0.3611147752757347 0 +0.4120625583527616 0.3344959827479507 0 +0.410142386806946 0.3615329962732377 0 +0.3700059419506381 0.3904511025709893 0 +-0.2489238537658826 -0.2734606169505611 0 +-0.2619576455635938 -0.2490551372347156 0 +-0.2748388590300167 -0.2790040410514936 0 +-0.2490922906202589 -0.2183931059703707 0 +-0.2750615735547272 -0.2246164392235638 0 +-0.223182619251149 -0.2121095562937481 0 +-0.3011137048768682 -0.2277213801750702 0 +-0.1918741312042031 0.3886582454776041 0 +-0.2136632563590508 0.3671624704114264 0 +-0.1875187686250311 0.3605129307457542 0 +-0.2398683309608924 0.3736227966002433 0 +-0.2379793174570868 0.3456122220526116 0 +-0.2424723280095392 0.4017042141965175 0 +-0.2660652998333337 0.3802672716498652 0 +-0.2619681366243979 0.3242642268857126 0 +-0.2364809256876576 0.3175222608749234 0 +-0.2230432095484369 0.2602477892409156 0 +-0.2346410195332134 0.289283631655931 0 +-0.2104385363180562 0.2311354003802073 0 +-0.1982157612741919 0.2537271219853077 0 +-0.1978338630876755 0.202023011519499 0 +-0.173400629944032 0.2476545883731496 0 +-0.185333462122869 0.2759949882547749 0 +0.3171457118853163 -0.2581295851688425 0 +0.3439854068928256 -0.2612785056052395 0 +0.3328880762989236 -0.2401918328582703 0 +0.3551716484093851 -0.2817596137985861 0 +0.3711387737103367 -0.2655191813194001 0 +0.3399247697909724 -0.2985583310823736 0 +0.3653990272976259 -0.3025055171729815 0 +0.3980819890120112 -0.2702416498204423 0 +0.3878002351431038 -0.2491155066323185 0 +-0.1763773641301196 -0.2499529917516251 0 +-0.2005317223580352 -0.2578150695338932 0 +-0.1892476902863997 -0.2806306003466276 0 +-0.2118157544296707 -0.2349995387211588 0 +-0.2246860805859507 -0.2656771473161614 0 +-0.1989454282733906 -0.2043219301261563 0 +-0.2375564067422308 -0.2963547559111639 0 +-0.2112768553696283 0.3109337112734593 0 +-0.186290793123106 0.3039085859991824 0 +-0.1867225985945208 0.3321660903001442 0 +-0.1616926012564842 0.2944355801709882 0 +-0.162925060851239 0.3488084715150884 0 +0.1933784961018627 0.2131948220012485 0 +0.2221112155514197 0.2119280127312351 0 +0.2056399357127208 0.238265852491622 0 +0.2410260109382752 0.1859174669941244 0 +0.2517387841717592 0.2093357263342845 0 +0.2327558178662608 0.1612128841432111 0 +0.260748420989582 0.1611861557980277 0 +0.2805575127479976 0.2087801580718589 0 +0.2650638799659829 0.2313678104718966 0 +0.2907794028422712 -0.2133000736218948 0 +0.2578985860538637 -0.2118201940379267 0 +0.2742833314771503 -0.2369765911616133 0 +0.2439669725452873 -0.1859381704916283 0 +0.2250713543976884 -0.2133194291269135 0 +0.2628678543800025 -0.1619701935856286 0 +0.2330611508733756 -0.1592667603584648 0 +0.1944380004845951 -0.2137281761429736 0 +0.2088382544878208 -0.2401678392214908 0 +-0.234615184423929 -0.3519800057171801 0 +-0.2364237852848128 -0.323892043509558 0 +-0.2328159520912161 -0.3803931166864361 0 +-0.2096459872994136 -0.3712800156726976 0 +-0.2326046335506147 -0.4076879263060924 0 +-0.1893278296620197 -0.3906139015564276 0 +-0.1864551453031701 -0.3629238102141056 0 +0.3513152519347457 -0.2220981502261482 0 +0.3220635903417264 -0.2172840672419568 0 +0.3789430119828807 -0.2269488712978544 0 +0.370519769520601 -0.2039191276773208 0 +0.4065550891231492 -0.2330200745346266 0 +0.3897956904109184 -0.1889508383248178 0 +0.3596118615903633 -0.1804125847154452 0 +0.1762714673679113 0.1289293969817173 0 +0.180878459888729 0.1576518019706619 0 +0.2066977188344741 0.1605258265777102 0 +0.1863955990896055 0.1856659207042254 0 +0.2248555004978877 0.1336908500086405 0 +0.1868182073633811 -0.1846575798850902 0 +0.2064693691912383 -0.1579705151067185 0 +0.1806656200377891 -0.156269092213222 0 +0.2237928269861234 -0.1306029942641679 0 +0.1758385365888101 -0.1279771987832871 0 +-0.1634558841761501 -0.3504780100551487 0 +-0.1875435625296005 -0.3350076437274786 0 +-0.1883995943144322 -0.3072808605952831 0 +-0.2124348472964894 -0.3158078162658098 0 +-0.1641334481197178 -0.2984109454612614 0 +0.3048068300643855 0.2414557550862693 0 +0.2993792138548647 0.2672923634785876 0 +0.28116881176126 0.2504204311923536 0 +0.3175216645523579 0.2834853116435823 0 +0.2936130976782383 0.2929740597231944 0 +0.3409694476765477 0.2727833375344448 0 +0.2690747631948991 0.3018389311042444 0 +0.2858936263663475 0.04776917462443592 0 +0.2806131696837814 0.01599280207112227 0 +0.2578062491518398 0.000191970608362444 0 +0.2806699455616012 -0.01575550138694377 0 +0.2353541109560323 0.01783394916595746 0 +0.2355812233166519 -0.01751051583280236 0 +0.2859168058276214 -0.04741754971738101 0 +0.1883739036705753 -0.3661935395358918 0 +0.1904333214964156 -0.3348214548544964 0 +0.1652612818341944 -0.3470482459563725 0 +0.2162906810135113 -0.3233140712340118 0 +0.1934560978228221 -0.3013949693824711 0 +0.2390414361908915 -0.3424587980524613 0 +0.2440235772580378 -0.3151101798869806 0 +0.1982784883212073 -0.2683450617900001 0 +0.1693257476837224 -0.2802400573956561 0 +-0.3124307937493335 -0.474210851494866 0 +-0.3374240547887653 -0.4742241485369164 0 +-0.3497107176071271 -0.4487227686479953 0 +-0.3622344079907587 -0.4745986170003422 0 +-0.337328332654088 -0.4228634755249371 0 +-0.3613400150483503 -0.4254152318517067 0 +-0.3859615788071799 -0.477192515055412 0 +-0.4815660482597514 0.3124105170360415 0 +-0.4798698884425521 0.3367518039956693 0 +-0.4592798340001497 0.3481947951969691 0 +-0.479207306442121 0.3615137634844986 0 +-0.4404933342260245 0.3349099525986083 0 +-0.439015469655345 0.3589816723547838 0 +-0.4796673252873071 0.385876315356225 0 +0.3741611051679102 0.1996752060533662 0 +0.3509542263239297 0.2125477875715209 0 +0.3515678864151502 0.1832539894171624 0 +0.3464357015466313 0.2429396292288699 0 +0.3277048664600077 0.2275624617106789 0 +0.3629449276188404 0.2606868804256663 0 +0.3065402081785861 0.2150367598524642 0 +-0.390790035385062 -0.2005698578311185 0 +-0.3696250586318408 -0.2083578108517074 0 +-0.3763570840673851 -0.1822452516695279 0 +-0.3615254825559343 -0.234817457877848 0 +-0.3485113683088269 -0.2163298996641499 0 +-0.3740259192091451 -0.253533104269975 0 +-0.3534583156879822 -0.2610631763116738 0 +-0.3265999484454727 -0.2242102886959717 0 +-0.3343770458675529 -0.1981409445796711 0 +-0.08851842615697847 0.2982596911221221 0 +-0.09633583475201465 0.2655789452095787 0 +-0.1213191203477872 0.2555150976742087 0 +-0.1058849850332674 0.2314330412135583 0 +-0.1375782135428246 0.2789653170632793 0 +-0.1463024059435597 0.2454512501388386 0 +-0.1154341353145202 0.1972871372175379 0 +0.1155473865135309 0.308598928737334 0 +0.1414334472343632 0.2941848390172617 0 +0.1413883221824498 0.3264235909716693 0 +0.1685082613046992 0.2787985528313157 0 +0.16504391846928 0.3464245440116834 0 +0.1417293534838729 -0.3266174071209703 0 +0.1419265150180468 -0.2946591300174761 0 +0.115834412743602 -0.3087095355465918 0 +-0.1162733330146208 -0.1980959412087558 0 +-0.1066083918746774 -0.232280251091206 0 +-0.1229975157479894 -0.257132705656644 0 +-0.09717503245211584 -0.2663877492007966 0 +-0.148819999043863 -0.2478776621124915 0 +-0.1396724984847788 -0.2816961833227576 0 +-0.08941097966187934 -0.2987974382617992 0 +0.3129065371098833 0.4776323784676235 0 +0.337476172826847 0.4762430014639381 0 +0.3499442260878232 0.4525011320173004 0 +0.3624020371386235 0.4763818004540999 0 +0.3377626706124979 0.4297901080925167 0 +0.3615397539439521 0.4314053087198776 0 +0.3859670961685462 0.4792130079719311 0 +0.4803979964690734 -0.3129870120940508 0 +0.4806901635550196 -0.337856234907044 0 +0.46157107039025 -0.3505790262069749 0 +0.4808293205717174 -0.362671164418492 0 +0.4420535586642814 -0.3387196446910966 0 +0.4426335596157643 -0.3625773715638202 0 +0.481020350268349 -0.38666956523337 0 +0.3427344151838866 0.1570475663509613 0 +0.3268151702202208 0.1331203073300214 0 +0.2960935988119179 0.1250532855758721 0 +0.3101360055345366 0.1078531295333368 0 +0.279687895974001 0.142711984385562 0 +0.2656991005234164 0.1183097966739481 0 +0.2953538075190652 0.0797373721976473 0 +0.2949559462189176 -0.07891123920387219 0 +0.3097171323879112 -0.1066605872363448 0 +0.2959393368953154 -0.1231838574393901 0 +0.3268832229413653 -0.1321799518547196 0 +0.2644053355678322 -0.1147433935753303 0 +0.2801866149956038 -0.1414634768012016 0 +0.3447089003761149 -0.1563380747592508 0 +0.4817628750754787 0.2152695444108399 0 +0.4529355918224454 0.2088812738201107 0 +0.4389916215635859 0.2226101937789437 0 +0.4252028799720995 0.2015011431802748 0 +0.4499505850546782 0.2432082668262903 0 +0.4272972277338432 0.2399776273308804 0 +0.39902634370831 0.1955390590152248 0 +-0.3578374732895749 -0.1652506114751372 0 +-0.3374091992424026 -0.1726924055392719 0 +-0.3393658645659073 -0.147616333890019 0 +-0.338729511478485 0.1465612610569391 0 +-0.3353794933412017 0.1710165363070688 0 +-0.3567467285699207 0.1643906704515716 0 +-0.3301378676543249 0.1954133237884768 0 +-0.3745275271503452 0.1812737964599839 0 +-0.1988460017521835 -0.4188274708620124 0 +-0.2193807311369739 -0.4275228710288109 0 +-0.2082418417659025 -0.4476797260417847 0 +-0.2410486201620433 -0.4347342747583351 0 +-0.216196975252458 -0.4753238736269104 0 +-0.2171263945392318 0.4769925744742317 0 +-0.2111363756176136 0.4465383785775217 0 +-0.2257038053069367 0.4243227591152172 0 +-0.2021605869292447 0.4170414376251101 0 +-0.2513064906069497 0.430700849329919 0 +0.2156196682647462 -0.4762371395949613 0 +0.2072946292173993 -0.449409397977065 0 +0.2181751606056251 -0.4306359248272156 0 +0.1985948337709812 -0.420557553856364 0 +0.2384695739555927 -0.4390165774968917 0 +0.2311731838661473 -0.4133495380495433 0 +0.1909733814351088 -0.3936724228102061 0 +0.1922807336016124 0.3932772289662392 0 +0.2003718625237642 0.4209216766896717 0 +0.2210916385118111 0.4314482504840736 0 +0.2084703807075829 0.4498473486131099 0 +0.2350453016815787 0.4138458579795721 0 +0.2420283701404368 0.4403297954854259 0 +0.2152485034272887 0.478472983922989 0 +0.4183433723490538 -0.1991611692606304 0 +0.4267423557849667 -0.2196525662682581 0 +0.4471323403489107 -0.208496469321427 0 +0.4330263265160359 -0.2418479208431451 0 +0.4748460719941424 -0.2164117697781014 0 +0.3481053116380447 -0.3884116637321489 0 +0.3274553942044244 -0.3692690203295355 0 +0.3265786575222052 -0.3927877122845032 0 +0.3283321308866435 -0.3457503283745679 0 +0.3068054767708039 -0.3501263769269222 0 +0.349858785002483 -0.3413742798222137 0 +0.3292088675688626 -0.3222316364196003 0 +0.2861555593371835 -0.3309837335243088 0 +0.2852788226549644 -0.3545024254792764 0 +0.405157794601134 -0.3430247584210903 0 +0.3861403002801043 -0.3581537268581099 0 +0.4042810579189149 -0.3665434503760579 0 +0.3679995426412936 -0.3497640033401618 0 +0.3671228059590745 -0.3732826952951294 0 +0.3688762793235127 -0.3262453113851942 0 +0.3662460692768553 -0.3968013872500969 0 +-0.328422033181718 -0.2677644711129141 0 +-0.3022545282276542 -0.2736678782234566 0 +-0.3143438257859157 -0.2484263288362759 0 +0.2182056356883186 -0.0406855335476035 0 +0.2430774078091642 -0.05135418590689901 0 +0.2531016213115945 -0.08371185535684325 0 +0.269133890039179 -0.06491093085518651 0 +0.2385994181690871 -0.1056047651329085 0 +0.2693987545568409 0.06571269839437723 0 +0.2537248172716315 0.08517815173545179 0 +0.2430207464492756 0.05189374354574475 0 +0.2396417065667672 0.1078121840231462 0 +0.2182784837257935 0.04111626894018141 0 +0.3020290386716169 0.1895836489408633 0 +0.2919046021094646 0.1662047695179922 0 +0.3231915581476517 0.1732403085011644 0 +0.3273148797692912 -0.1737121669617692 0 +0.2950248604124153 -0.1673892371674544 0 +0.3090380207405458 -0.192699197788563 0 +0.431158722232728 0.3232321532556421 0 +0.4492451675776701 0.3120884487668546 0 +0.4476579569263249 0.3353054573432714 0 +0.4510712459562221 0.2891682312478247 0 +0.4668848588191677 0.3005979536139958 0 +0.4351760135208051 0.2769326436797376 0 +0.4525804978633059 0.2664059729398222 0 +0.4842244271750646 0.2887300711574204 0 +0.4827075619201223 0.3119493556335491 0 +0.1928335069512064 -0.09871563294916347 0 +0.2146058054662525 -0.1003946716427253 0 +0.2061955030969729 -0.06887466485057461 0 +0.2066661329339635 0.06953627781782351 0 +0.2155770266711894 0.1023954041273221 0 +0.1934376996880631 0.09985250681428604 0 +0.2725220056575965 -0.3100621132664089 0 +0.2516852757760006 -0.2880431069856941 0 +0.2616522368898263 -0.2620492556044862 0 +0.2300915005104742 -0.2644884687825892 0 +0.2915491595843169 -0.2600636829665322 0 +0.3254607261107612 0.4093523778258042 0 +0.3049018965653509 0.3901475755467683 0 +0.2812596477602927 0.3945577919754998 0 +0.2835083224132759 0.3709747789449398 0 +0.2776676393681689 0.4181682558343793 0 +0.2574394746724038 0.4000903190263289 0 +0.2619556186927883 0.3524137992321847 0 +0.1956742012234918 0.2651062081312656 0 +0.1925192522721157 0.2992519735258166 0 +0.2156943992788021 0.3200328835044589 0 +0.1901111880632433 0.3330743120682188 0 +0.2418016230462369 0.308167421082249 0 +0.2389058425169497 0.3379834832698015 0 +0.1882744522215252 0.3653817477318342 0 +-0.3889818146300583 0.4804260885424684 0 +-0.3639818146300583 0.4804260885424684 0 +-0.3529636292601166 0.4608521770849369 0 +-0.3389818146300584 0.4804260885424684 0 +-0.36666160250437 0.4413546966082194 0 +-0.3426474202943748 0.4407362346744264 0 +-0.3142437847752172 0.4801406579269228 0 +-0.4830720450410797 -0.3852429345225484 0 +-0.4830932960380651 -0.3607849403867955 0 +-0.4663744979919641 -0.3467384691226703 0 +-0.4833423656148385 -0.3360018643619659 0 +-0.4491027189479319 -0.3572002071687884 0 +-0.4499518998324801 -0.3329725710566778 0 +-0.4838498291038998 -0.3114430254938485 0 +0.3864836521848716 -0.4782321937110765 0 +0.3617891206757269 -0.4779964953842724 0 +0.3486142874779128 -0.4559651778435583 0 +0.3368071437389565 -0.4779825889217791 0 +0.3604214312168692 -0.4339477667653374 0 +0.3353011800988441 -0.4347853962913143 0 +0.3120611363637032 -0.4789450274578245 0 +-0.2388225739877566 -0.4783522975867714 0 +-0.2520151318643081 -0.4574593218870623 0 +-0.263420009820686 -0.4789265102075559 0 +-0.2651843785555688 -0.4363132049731971 0 +-0.2883751849294195 -0.4787124410133687 0 +0.2609148287943772 -0.3572123963578817 0 +0.2816215963496284 -0.376044203465925 0 +0.2776907012503528 -0.3976355172895548 0 +0.3018454273316483 -0.3952258677166976 0 +0.2536229357828334 -0.4013384298053547 0 +0.2715691850354398 -0.4193525352665256 0 +0.3197656014926831 -0.4146952684648207 0 +-0.2915077718971361 0.4776680003635606 0 +-0.2669216808991837 0.4772117180136724 0 +-0.2590684939344419 0.4541505356684428 0 +-0.2420774234158191 0.4770398372753488 0 +-0.2758889491315576 0.4316049812755062 0 +0.4776720627587266 -0.2391500171759907 0 +0.4569320723938904 -0.2521851685690871 0 +0.4784660361969452 -0.2635925842845436 0 +0.4362266026583966 -0.2656403445862021 0 +0.4791125844519729 -0.2883947495599979 0 +-0.479941423517947 0.4798713112166862 0 +-0.4573186606569006 0.4822016234884574 0 +-0.4395053030844576 0.4636488217491972 0 +-0.4326536630969183 0.482069473291085 0 +-0.4440646646727091 0.4437413483576667 0 +-0.423774355110659 0.4457430174719876 0 +-0.4089688116239288 0.481758012157382 0 +-0.4816233482540281 0.4081566256290906 0 +-0.4819583700965789 0.4321931822043055 0 +-0.463394071211056 0.4387773122015167 0 +-0.4813355121859302 0.4563260717525116 0 +-0.4457666732768236 0.4229354266563484 0 +0.4826701235276511 0.3864152417447858 0 +0.4825526523087749 0.3617684857810905 0 +0.46510530461755 0.348536971562181 0 +0.482552652308775 0.3367684857810905 0 +0.4476579569263249 0.3603054573432715 0 +0.390564565963244 0.2205342831891144 0 +0.4047329133489433 0.2418632746845969 0 +0.384008071165691 0.2502483077177478 0 +0.4199152245829326 0.2620918320887197 0 +0.3788141634403279 0.2795435441677515 0 +0.4794826897417761 -0.4794904086522437 0 +0.4561343428367215 -0.4811067394792609 0 +0.4380325939301926 -0.462704942739999 0 +0.431817036095423 -0.4815381599740418 0 +0.4431493566239183 -0.4432368405538226 0 +0.4213937788161045 -0.4444969692209342 0 +0.4073984105160833 -0.4813808517884162 0 +0.4815782732943041 -0.4083084064097322 0 +0.4816422855147578 -0.4319811847730041 0 +0.462739848437427 -0.4383674755541174 0 +0.4810033978495295 -0.4560930152655918 0 +0.4448390517060911 -0.4221993444034115 0 +-0.4842313522841164 0.2884728994446744 0 +-0.4647337010977491 0.3002793226235705 0 +-0.448815139196175 0.287959068230695 0 +-0.4442682840214077 0.311099793056608 0 +-0.4534132818216701 0.2656593500747449 0 +-0.4338384916893068 0.2745240940067873 0 +-0.4230830817539807 0.3207793575190469 0 +-0.2846336373495147 0.3929040605229904 0 +-0.2824892691829476 0.3649731022334984 0 +-0.2986976438655115 0.3498444785572825 0 +-0.280428840255349 0.3369774436024187 0 +-0.3168897557346898 0.3627715857751174 0 +-0.314834610350224 0.3347712951181362 0 +-0.2784682011961171 0.3089031885520469 0 +-0.4345637177543097 -0.3197392380785697 0 +-0.4517203826463141 -0.30936037770526 0 +-0.4543190470150976 -0.2865328128462745 0 +-0.468742595903433 -0.2987626972059635 0 +-0.4396783476514677 -0.2736117594085913 0 +-0.4570130726260698 -0.2641009974853139 0 +-0.4855339798857803 -0.2879081828703994 0 +0.2888421810755628 0.4806377553586978 0 +0.3013719481760165 0.4573309619482246 0 +0.2896340031179565 0.4374953499811489 0 +0.3135347910911449 0.4335232807360342 0 +0.2663231067693808 0.4413351348661989 0 +-0.3008508035700195 -0.4531373617024218 0 +-0.2891638744797866 -0.4320236379060508 0 +-0.3132815973193531 -0.4273482131972878 0 +0.2106477699391491 -0.3797699396598722 0 +0.23258399803605 -0.3905127463311168 0 +0.235516813587564 -0.3673871272445335 0 +0.2253332443122198 0.2605047453984346 0 +0.254645843613573 0.2560135970761045 0 +0.2474145363229994 0.2819616857625414 0 +-0.1394122532473542 0.3315041236736127 0 +-0.1378568293201839 0.3052197180466381 0 +-0.1141995434932271 0.3145730452796704 0 +-0.1150802220610443 -0.31521343425752 0 +-0.1391199607450553 -0.3080126905398303 0 +-0.1400823156391448 -0.3330477116700674 0 +-0.3102464496996264 0.2415139605945498 0 +-0.3224359837515371 0.2194680928912 0 +-0.2979434975761243 0.2647734839423486 0 +-0.3227233068148555 0.2593712027614209 0 +-0.2856967808947994 0.2891103327844028 0 +-0.3357569753517972 0.2761345597206819 0 +-0.3472701916280728 0.253274151552407 0 +-0.3440865729503634 0.2122865238705057 0 +-0.3572011880772151 0.2293333895719809 0 +-0.3664788385780031 0.2052665064880749 0 +-0.3689893157533518 0.2472656480525176 0 +-0.3887092406777407 0.1993773452099623 0 +0.2874719890901603 -0.4802945056132908 0 +0.2625 -0.4803817235058144 0 +0.2502767974446344 -0.4600922817141507 0 +0.238101014647056 -0.479463379795865 0 +0.2620832831960341 -0.4401482867175496 0 +0.459540781762712 -0.3014274541488721 0 +0.4382660856739072 -0.2902050384653625 0 +0.4403518244133903 -0.3145030602037985 0 +0.4176784639192213 -0.2797737350985348 0 +0.4223828350626358 -0.3284887115652704 0 +-0.4083140003141227 -0.4822490252480395 0 +-0.4333140003141227 -0.4822490252480395 0 +-0.4416280006282454 -0.464498050496079 0 +-0.4583140003141227 -0.4822490252480395 0 +-0.4255264519003257 -0.4458035108878492 0 +-0.4470176117069719 -0.444320034321052 0 +-0.4806401566329333 -0.4799216941797648 0 +-0.4822279212262378 -0.456377170238685 0 +-0.4653596626522688 -0.4389161109644447 0 +-0.4829286983504963 -0.4323307644317003 0 +-0.4490340224039782 -0.4229765384571652 0 +-0.4831446917094323 -0.4083726137897785 0 +0.2392582111240798 0.4798084623469264 0 +0.2528973739439122 0.4604748672122637 0 +0.2639032522013276 0.4803917982314694 0 +0.310327586890817 -0.4367305067307934 0 +0.2860812207128443 -0.4387604825726368 0 +0.2992350108045488 -0.4586849757019437 0 +-0.3350279612707815 0.4199930046832409 0 +-0.3203905031397096 0.4381434875249668 0 +-0.298333999278367 0.4350580955647121 0 +-0.3061114874816676 0.4574423109979618 0 +-0.2910893361604616 0.413072677195286 0 +-0.1764019913574764 -0.1267130353281208 0 +-0.1795525622168157 -0.1526233973727156 0 +-0.1543261942121167 -0.1525157044672778 0 +-0.1836396294207783 -0.1791760073577656 0 +-0.1322958929060313 -0.1757731464030809 0 +-0.4081897746415958 0.3040068716141556 0 +-0.4025425882152628 0.3268548525963686 0 +-0.4140219455481802 0.280572922833525 0 +-0.393967268774821 0.2851981345552566 0 +-0.4207691382730657 0.2580519217223917 0 +-0.3799363555291591 0.2662321355998686 0 +-0.3740914792590461 0.2888270804445912 0 +-0.3862718321889131 -0.2723259774496183 0 +-0.4021936721593229 -0.2885078427991296 0 +-0.4212567386365211 -0.2817805747640773 0 +-0.4185291231310288 -0.3044073423738638 0 +-0.4251691316592185 -0.2585877689348896 0 +0.3962512459832519 0.2952121907111967 0 +0.4161520831075105 0.2865974686744237 0 +0.414185859417279 0.309712800728919 0 +-0.1314076912822211 0.175203946340613 0 +-0.1534638078263004 0.1520078111186179 0 +-0.179358353827467 0.1515460058177608 0 +-0.1761587753033684 0.126127626338218 0 +-0.1837987554111364 0.1775502069480198 0 +-0.3501998976219943 0.4032046027277005 0 +-0.3286439782578634 0.4003601977279259 0 +-0.3226632610992278 0.3813582540160512 0 +-0.3067913228702299 0.3969840441326552 0 +-0.3386146420013216 0.3658909332440688 0 +0.4809564459450628 0.480589880831086 0 +0.4826547735509441 0.4572443439400251 0 +0.4661616915565455 0.4407078425711184 0 +0.483327147893311 0.433209492111168 0 +0.4482726808137052 0.4469381769441881 0 +0.4500552463379021 0.4257776277822531 0 +0.483350643513421 0.4092134545151234 0 +0.4091163706408764 0.482566700060551 0 +0.4337509291722998 0.4832075465898386 0 +0.4425018583445997 0.4664150931796771 0 +0.4587509291722999 0.4832075465898386 0 +0.4273775457767692 0.4480132171798618 0 +-0.4110712587121959 0.1989743820783791 0 +-0.4343081318670251 0.204359907792774 0 +-0.444121983424894 0.223520963456444 0 +-0.4585740946642574 0.2103316264415839 0 +-0.4308169753496556 0.2385084344759027 0 +-0.4521661150711378 0.2423785387250387 0 +-0.4833095421363909 0.2148233723274965 0 +-0.4847527005024511 -0.2139008887776938 0 +-0.4596643371569339 -0.2095378416332493 0 +-0.4462684025041816 -0.2222039974158327 0 +-0.4355109533614965 -0.2041498974714875 0 +-0.4559814283751857 -0.2400946880816648 0 +-0.433723916274599 -0.2362598287099102 0 +-0.4123818307610254 -0.1989992182995915 0 +-0.4024050287515536 -0.2203106319065855 0 +-0.4134958500112207 -0.2399032053828575 0 +-0.3938794949232552 -0.2464103360757588 0 +0.2366469950627867 0.3642846779338705 0 +0.2349640703531008 0.3892535471824515 0 +0.2118296195243511 0.3783260829996514 0 +0.2961719266083443 -0.3062086096120782 0 +0.3178295507323929 -0.3020334260790515 0 +0.3062774089375125 -0.2815760115531971 0 +-0.3733585073254097 -0.4059946264836581 0 +-0.3838191984704731 -0.4320532212906126 0 +-0.4050882537854436 -0.4391137147981825 0 +-0.3955533697571428 -0.4575747242844536 0 +-0.4158027323060358 -0.4223092393478284 0 +0.3737613468556993 0.4125710553388249 0 +0.3844259760675104 0.4368756684420935 0 +0.4064681735265401 0.4429720047034965 0 +0.3960988449945061 0.4604882555808021 0 +0.4179531081816384 0.4269296372110842 0 +-0.4208863588558969 0.3710228646796839 0 +-0.440895037295905 0.3808015809981469 0 +-0.4438359656733196 0.4023608102609287 0 +-0.4614591635797665 0.3939201481470019 0 +-0.4269634392485409 0.4119095133369334 0 +0.4242352719333381 -0.3710484880272396 0 +0.4435160981968994 -0.3822844178893728 0 +0.4444131888456437 -0.401780426560794 0 +0.4626628297255676 -0.3945997705677576 0 +0.4262391630717699 -0.4096888846240022 0 +-0.4840674531764493 0.2391341988017886 0 +-0.4684921116200631 0.2528044447681087 0 +-0.484452604917969 0.2637470520235312 0 +-0.4856484317341465 -0.2631415606954853 0 +-0.4710775085613023 -0.2514140585042172 0 +-0.4852389590238999 -0.2384577707289414 0 +0.484182324061932 0.2640172676407697 0 +0.4677115800191157 0.2532023308741813 0 +0.4832611860117477 0.2394089120826561 0 +-0.3035897226128024 0.3022964620459945 0 +-0.3216016509172307 0.3153617058896024 0 +-0.3285234686545744 0.2958459815136592 0 +-0.3397589850380026 0.3283150324124527 0 +-0.353241325259453 0.2895344004349966 0 +-0.1447709145481295 -0.2001824863615765 0 +-0.1720809733156541 -0.2021775304833884 0 +-0.1603244860295831 -0.225053979793118 0 +-0.3894671621017196 0.2425101027566649 0 +-0.409687370631792 0.2386103675559695 0 +-0.3996365159606866 0.2190514258588333 0 +-0.1583757307729438 0.222972963346227 0 +-0.1710191065938932 0.2003718394569107 0 +-0.1438305507466013 0.1988567364583062 0 +-0.3673913711073578 0.3084181459599902 0 +-0.3605419025763172 0.327882812762724 0 +-0.3814698092790402 0.3274182575112864 0 +-0.3521211436236613 0.3471803579063881 0 +-0.3933705623247181 0.3463759482846462 0 +-0.3749094435750281 0.4234136732381092 0 +-0.3893490104304179 0.4048020122174627 0 +-0.3701020553372695 0.4036713526689736 0 +-0.4084320008767564 0.4077523437418363 0 +-0.4047595927893149 0.3867958330543653 0 +-0.4123753740798002 0.42705357456849 0 +-0.4006524940885369 0.3659162726413474 0 +-0.396528246398973 0.4625651841208119 0 +-0.4043966655180611 0.4446388654639303 0 +-0.3854995346598153 0.4430255521884653 0 +0.39363316212508 -0.4598086073079027 0 +0.4004591805112036 -0.4416346256940263 0 +0.3804403058640364 -0.4377911962296819 0 +0.4096514692837785 -0.4240514741554654 0 +0.3685253851605438 -0.4160960145001569 0 +-0.4310090410786813 -0.366717835255241 0 +-0.4117301371447991 -0.3783758831636333 0 +-0.4127964035940737 -0.4006061406728716 0 +-0.3921775106674605 -0.3918845297259551 0 +-0.431796778105467 -0.4105976527547811 0 +0.3864823882094974 -0.4007081649004361 0 +0.4065901730249273 -0.4047298450732427 0 +0.4052919090169942 -0.3856012333573171 0 +-0.3798136316206496 0.3655415305645526 0 +-0.3588397289234463 0.3656779037636096 0 +-0.3651132140053956 0.3844259614866546 0 +-0.466165125663324 -0.3934312189304172 0 +-0.4489759082643894 -0.4016055025241374 0 +-0.4488342713010302 -0.3796641719342712 0 +0.4301049615993667 0.3706612986373297 0 +0.4112186249917143 0.3832883873266438 0 +0.4131924789048207 0.4053065729139667 0 +0.3912724412423169 0.3974311102308615 0 +0.4327326009632627 0.4143814422396855 0 +0.4009866105191915 -0.2949615729967876 0 +0.3846863648799295 -0.3105269359470729 0 +0.4033578812030284 -0.3192556770634304 0 +0.4661006839372366 0.3953344846741111 0 +0.4493271925242213 0.4044517719408836 0 +0.4483294770761764 0.3826408662216242 0 +-0.3387216237439358 -0.3852650844437098 0 +-0.3408556212080713 -0.3705766101037078 0 +-0.3514836548051993 -0.3816766610165502 0 +-0.3298540699546728 -0.3589982885679538 0 +-0.3429896186722069 -0.3558881357637057 0 +-0.3167185212371388 -0.3621084413722019 0 +-0.3188525187012743 -0.3474199670321998 0 +-0.3451236161363425 -0.3411996614237037 0 +-0.3552305807717306 -0.35163520977518 0 +-0.3080700898446568 -0.3358339136555573 0 +-0.3210533817999107 -0.332746127860636 0 +-0.3232401635087048 -0.3181016887835003 0 +-0.334134883891167 -0.3296265944120321 0 +-0.312643766294267 -0.3067438599828696 0 +-0.3257866704794297 -0.3036421321750392 0 +-0.347257613600478 -0.3265111870837017 0 +-0.2953888453373433 -0.3387639463329957 0 +-0.2974586182867049 -0.3243768379694026 0 +-0.2869095165899983 -0.3131277498709216 0 +-0.2998381258494901 -0.3099981418879947 0 +-0.2734105230166829 -0.3159615631360218 0 +-0.276565682095784 -0.302063306697045 0 +-0.3045742762765035 -0.2966633080582078 0 +-0.3493916110646136 -0.3118227127436997 0 +-0.359355387258696 -0.322099321998212 0 +-0.3388645900078251 -0.30049479783387 0 +-0.3518476737510211 -0.2971547922286963 0 +-0.3307203469533307 -0.2904639314657419 0 +-0.355728666332814 -0.2834389135291606 0 +-0.3638293254249373 -0.2928298378890228 0 +-0.3748083610235889 -0.2876713719487975 0 +-0.3723065283066928 -0.3017494113938352 0 +-0.3805961390576001 -0.3102878893151361 0 +-0.370007944332405 -0.316160128771663 0 +-0.3909880935900865 -0.3044650971848492 0 +-0.3887902318141368 -0.3186808100881039 0 +-0.367850138825528 -0.3307057484608938 0 +-0.3969724729539647 -0.3269371571158213 0 +-0.3866740211141746 -0.3329681008219261 0 +-0.3846179171085018 -0.3473604807761002 0 +-0.376258166717026 -0.3391293608183515 0 +-0.3929273622318742 -0.3553812886207323 0 +-0.3825973029438333 -0.3619098887767805 0 +-0.3657719431969325 -0.3453637628830403 0 +-0.4071281302072875 -0.3211757038493043 0 +-0.4051654013866915 -0.3349691133369415 0 +-0.4134211880322758 -0.3426206877407679 0 +-0.4032052500207219 -0.3489332649526349 0 +-0.4234778835707047 -0.3366539165413257 0 +-0.4218352356472539 -0.3496723341110195 0 +-0.4012317897472769 -0.3631497832162893 0 +-0.363780314161005 -0.3601479491004323 0 +-0.372238587140005 -0.3684740360658656 0 +-0.3618642368100361 -0.3750651466371283 0 +-0.3806283438472998 -0.376627232321559 0 +-0.3600248979473227 -0.3901046814268289 0 +-0.3023881779278549 -0.2034908859541368 0 +-0.3028502985174257 -0.1905093720294 0 +-0.314858472198673 -0.1944833415903923 0 +-0.2899016053486662 -0.1869218149451796 0 +-0.3043227930279724 -0.1766809239450612 0 +-0.277127215956104 -0.1975517994856545 0 +-0.2769700410970473 -0.1838126992027974 0 +-0.3059030817985483 -0.1633823991388846 0 +-0.3165200912134412 -0.1699926289693493 0 +-0.2640020244221054 -0.1807075236576149 0 +-0.2783420058446026 -0.1704604741610633 0 +-0.2799222946151784 -0.1571619493548866 0 +-0.2929126882068633 -0.1602721742468856 0 +-0.2669319010234934 -0.1540517244628877 0 +-0.2815025833857542 -0.14386342454871 0 +-0.3074833705691241 -0.1500838743327079 0 +-0.2522206616994505 -0.1916057901495582 0 +-0.2510597145795567 -0.1776095125954624 0 +-0.2380767786976091 -0.1745098781327821 0 +-0.2523612186612326 -0.1642400243770654 0 +-0.2263697841355325 -0.1854079989717504 0 +-0.2241161528824191 -0.1713357048014819 0 +-0.2526482909746335 -0.1506931462204043 0 +-0.3090636593396999 -0.1367853495265312 0 +-0.31940008857107 -0.1437015004595837 0 +-0.296073265748015 -0.1336751246345322 0 +-0.3103521620436657 -0.1237357140614365 0 +-0.2817828468534853 -0.1303216520567565 0 +-0.3108006352583459 -0.1102982429694319 0 +-0.3233202357613696 -0.1157785241828078 0 +-0.2948295317443889 -0.1062712327524455 0 +-0.3091495465619375 -0.09627734824544673 0 +-0.2802938203328169 -0.1164296063234324 0 +-0.2773734219549537 -0.1024170412952318 0 +-0.2657242841710809 -0.1266172835921598 0 +-0.2599384644216034 -0.0985859517970884 0 +-0.2743205058904093 -0.08853174354883245 0 +-0.2511551404083279 -0.1368037032398528 0 +-0.2483643662439384 -0.1226712149793 0 +-0.2310264792291659 -0.1187374403732095 0 +-0.2455367268157428 -0.1085946464780133 0 +-0.2166037424160767 -0.1290367060378221 0 +-0.2138690295505881 -0.1149427231838151 0 +-0.2426473964793182 -0.09463163306951196 0 +-0.2365844580460671 -0.1469920031540305 0 +-0.2219898228828993 -0.1572847636089707 0 +-0.2193172881891947 -0.1431163114811213 0 +-0.2088841839212623 -0.168395434019661 0 +-0.2034482930995275 -0.1403499685014183 0 +-0.2254597863789824 -0.09072736652676527 0 +-0.2397115643764363 -0.08082686381406314 0 +-0.2111514103887565 -0.1008722526866231 0 +-0.2086989179003159 -0.08709045391091687 0 +-0.199967502335469 -0.1137660008027654 0 +-0.1943163514023472 -0.08544676834266848 0 +-0.2057396488826794 -0.07326633629615305 0 +-0.2878701285605589 -0.02044359408369984 0 +-0.2872660383607467 -0.006837139048834789 0 +-0.2895390596345651 -0.03372827948023589 0 +-0.2751240344385952 -0.02819043987433024 0 +-0.2917996948920513 -0.04685462264205016 0 +-0.262279960866335 -0.03625453439087299 0 +-0.260960722451028 -0.02215647990618657 0 +-0.2944528064816764 -0.05987336034851989 0 +-0.2792831823566558 -0.05496846748260829 0 +-0.2665031931505725 -0.06328344452697809 0 +-0.264089168918687 -0.04996160932068661 0 +-0.2697287499606568 -0.07609246300455141 0 +-0.2533142351579629 -0.07192170559465765 0 +-0.248992049813459 -0.04479262038861254 0 +-0.2986574358594546 -0.07225979907477686 0 +-0.3036524569846862 -0.08431884681965085 0 +-0.2867491171814324 -0.08021765392397984 0 +-0.2349930562099142 -0.05386303170357867 0 +-0.2342763216811072 -0.03935473284382799 0 +-0.2364073398577668 -0.06787435846143305 0 +-0.2195949053759336 -0.06375583913785327 0 +-0.2053433570971735 -0.05825438277211507 0 +-0.2065803615193489 0.07315411645481198 0 +-0.2206538608131912 0.06331035385557071 0 +-0.2058091402487222 0.05807099811791648 0 +-0.2366603074090168 0.06775251791570822 0 +-0.2352746011821034 0.05374313718584487 0 +-0.2397015628493108 0.08076979332749146 0 +-0.2533391958869601 0.07189944142897288 0 +-0.2490205777570389 0.04476842204206863 0 +-0.2343471297567836 0.03931141129524268 0 +-0.2697287499606569 0.076092463004551 0 +-0.2665182001317083 0.06327465325939508 0 +-0.2792974070010241 0.05495163822918509 0 +-0.2641227435153481 0.04992791269498965 0 +-0.2944528064816765 0.0598733603485198 0 +-0.2918145666712619 0.04683434248847832 0 +-0.2623075096613229 0.036190871973802 0 +-0.2741653724710894 0.08860550649060298 0 +-0.2867480039548175 0.08020962870831019 0 +-0.3036524569846862 0.08431884681965068 0 +-0.2986574358594547 0.07225979907477673 0 +-0.3091470704985015 0.09625345700572019 0 +-0.2751745885332174 0.02810749583082062 0 +-0.2609432064896288 0.02207187302100676 0 +-0.2895595589285611 0.03369140877535182 0 +-0.2878461077116476 0.02040858227894879 0 +-0.287273518137695 0.006789616161236373 0 +-0.09522542485937341 -0.1902780327548981 0 +-0.07930784371146229 -0.1972838455338414 0 +-0.07384957031516384 -0.2131823447556971 0 +-0.06281052324704109 -0.2026648633270961 0 +-0.08456392693719202 -0.2249095347775191 0 +-0.0687548408365739 -0.2290679230739366 0 +-0.04610145173601854 -0.2068460857970629 0 +-0.06392129281707998 -0.244966075957954 0 +-0.05227821669657113 -0.2325410817398751 0 +-0.03562059696638959 -0.2352783738919757 0 +-0.0407347067867349 -0.2208927704942772 0 +-0.03088170094972843 -0.2498006543009382 0 +-0.01892193616742232 -0.2372598213050522 0 +-0.02913253141224632 -0.2098137022424932 0 +-0.07577143407231214 -0.2584170377212064 0 +-0.05928884155380119 -0.2609505831527854 0 +-0.05484702573173773 -0.2767545710725934 0 +-0.04298699763329968 -0.2626241558913434 0 +-0.06637966274324916 -0.2923052542925506 0 +-0.05058429049314161 -0.2920199890014867 0 +-0.02686324830777668 -0.2636124217999452 0 +-0.01195667797898629 -0.2116087021569222 0 +-0.006861807153508916 -0.2247882147885069 0 +0.005186382320524635 -0.2122149976881236 0 +-0.002378391203167553 -0.2383007406158768 0 +0.02219225719900685 -0.2124481066262939 0 +0.02219846970014445 0.2124612315477487 0 +0.005229669643962756 0.2123094504188033 0 +-0.00689274598022684 0.2248670009792633 0 +-0.01203522609380713 0.2115950360318571 0 +-0.002380196255364444 0.2384426347722722 0 +-0.01904180386439477 0.2372549092723876 0 +-0.02918194712975522 0.2098192190549557 0 +-0.03103740940647324 0.2498016964210475 0 +-0.03570739747631636 0.2353522201884239 0 +-0.05239162405700466 0.2325854604432393 0 +-0.04081524045328981 0.2209215571406391 0 +-0.06410519873089879 0.244717971741885 0 +-0.0689184654376257 0.2291646336453848 0 +-0.0461455414173776 0.2068584215966861 0 +-0.02692944281217067 0.2637180730855901 0 +-0.04303129639766577 0.2628674416793934 0 +-0.05499960390896658 0.2769597463782084 0 +-0.05946661908821579 0.2610942222775491 0 +-0.05065938468825124 0.2921547236877465 0 +-0.06657797401561573 0.292715744742959 0 +-0.07612712429686848 0.2585698407469386 0 +-0.06285377407721705 0.2026761756778666 0 +-0.07405048786739202 0.2132806983161889 0 +-0.0792298529125466 0.1972098927833638 0 +-0.0843274165134847 0.2246191994887651 0 +-0.09522542485937399 0.1902780327548977 0 +-0.3111767370415513 0.1099403419791768 0 +-0.3093351488371088 0.1235446568441073 0 +-0.3234957508522637 0.1147555265473579 0 +-0.2949457379900972 0.1330064093776053 0 +-0.3076609510304586 0.1364272996159051 0 +-0.2821425089476834 0.1296675165159666 0 +-0.280464941939002 0.1427437492686775 0 +-0.3057529922403687 0.1493063913344597 0 +-0.3194130844437554 0.1408921936080404 0 +-0.265988401047659 0.1526137998029433 0 +-0.278737806181593 0.1556815640216296 0 +-0.2768712615914391 0.1685926703705604 0 +-0.2914238037548494 0.1587625768504676 0 +-0.2623605249507733 0.1784848038046569 0 +-0.2750520097929188 0.1814822093470928 0 +-0.3038054193833202 0.1621648783416711 0 +-0.2529208802696022 0.1495356447307195 0 +-0.251389949878795 0.1625423992367732 0 +-0.2369033426491083 0.1725070251617467 0 +-0.249681362291855 0.1755057740638132 0 +-0.2240685857621564 0.1695045794768693 0 +-0.2232064849842578 0.182629339682413 0 +-0.2484588208013496 0.1885640507156315 0 +-0.3018757340579519 0.1750011768690659 0 +-0.3155375713191946 0.1666304356675547 0 +-0.2877657363625723 0.1846058987950833 0 +-0.3001161750058158 0.1878462060043874 0 +-0.2743194280340878 0.1946303748244761 0 +-0.2994240487210548 0.2004219763568966 0 +-0.3118556923523418 0.1917021849970372 0 +-0.194505644014501 0.0853350750805241 0 +-0.2089763846978704 0.0870660825646369 0 +-0.2112678142060863 0.1005513215106542 0 +-0.2255184717159417 0.0905071689099223 0 +-0.1996069271631668 0.1129501524652965 0 +-0.2137018849972303 0.1141778713566607 0 +-0.2425687231813037 0.09444584604036366 0 +-0.216386196056377 0.1280199975159289 0 +-0.2309036792047081 0.118011818641525 0 +-0.2482262919335203 0.1219272564822918 0 +-0.2454183927953835 0.10813474215863 0 +-0.2509722114046262 0.135811607755492 0 +-0.2655845910246457 0.1258749789145933 0 +-0.259813735115726 0.09838498735469417 0 +-0.2029717178919219 0.1389708262462188 0 +-0.2190772120070842 0.1418666230206921 0 +-0.2217611146586723 0.1557553830823806 0 +-0.2363574851413889 0.1457502409744973 0 +-0.2074565476799346 0.165917733136341 0 +-0.2772298006270861 0.1022234599036279 0 +-0.2801750741187481 0.1159557573424433 0 +-0.2946554039478003 0.1060540493160147 0 +0.1541320088145724 0.1419220726984473 0 +0.1418362432007606 0.1539017644009952 0 +0.1427792064686642 0.1665942115281691 0 +0.1287393690107793 0.164963089873095 0 +0.1565881937239344 0.1683854087617015 0 +0.1442229859281582 0.1795703391478788 0 +0.1148414626546466 0.1749909810933046 0 +0.1462085871961417 0.192977121975513 0 +0.1309887752305553 0.189801526143949 0 +0.117121642045274 0.1992078034503423 0 +0.1156693721919619 0.1867754398146904 0 +0.1190382392884471 0.2122782867452197 0 +0.1028011064179608 0.2079412969904846 0 +0.1002031487731302 0.1839243005995765 0 +0.161308639505394 0.1961314307467432 0 +0.1485512759526305 0.2067489497212887 0 +0.1512311661685876 0.2210674371354608 0 +0.1351059349956392 0.2166746887597124 0 +0.1672421289049686 0.2249660481596733 0 +0.1543037105454013 0.2363314647442629 0 +0.1212150136325188 0.2259613161496204 0 +0.0850118209717068 0.191836150439007 0 +0.08635364239218013 0.2036056477246538 0 +0.06946958427712177 0.198936297817692 0 +0.08831708034958088 0.2162807517988408 0 +0.05391414817715769 0.2057880756729189 0 +0.05399923984484549 -0.2058531853887989 0 +0.06958166445617155 -0.1990015454212112 0 +0.08661342101183686 -0.2037335178531065 0 +0.08518263667507817 -0.19194254621068 0 +0.08861264643056918 -0.2163994736319269 0 +0.1032432813112807 -0.2081407917558378 0 +0.1004074655560016 -0.1840169611458614 0 +0.1195891969972706 -0.2124442947605452 0 +0.1176583598398928 -0.1993900472434372 0 +0.1314194817467571 -0.1897878267643811 0 +0.1159843868774452 -0.186834634869084 0 +0.1466395735598839 -0.1927229184468208 0 +0.144407818336014 -0.1792177101864753 0 +0.1149696697564848 -0.1749832636762711 0 +0.1217896386484527 -0.2261542461866619 0 +0.1357391625281882 -0.2167765143497325 0 +0.1519254516588925 -0.2210316487243905 0 +0.1490011807482186 -0.2064793264788548 0 +0.155294789168273 -0.2365646900464224 0 +0.1681289213449646 -0.2250246648465522 0 +0.1613951821775388 -0.1952468353855767 0 +0.128784966311547 -0.1648494571573125 0 +0.1427145464544048 -0.1661787156722451 0 +0.1416557575884924 -0.1535676129273975 0 +0.1565015291515387 -0.1676631071133255 0 +0.1535701258144375 -0.1412738613014676 0 +0.2769736927447269 0.3241490843222393 0 +0.2873468259099156 0.3332839340954215 0 +0.2751382338175069 0.3374812599176012 0 +0.2993384732718414 0.3286777944885612 0 +0.2978306033745667 0.3427025454156799 0 +0.3010260623656407 0.3148356651127531 0 +0.3112252599103763 0.3238908128060898 0 +0.3083649628188815 0.3521803760672774 0 +0.2964537841422967 0.356978164215294 0 +0.3229776188146578 0.3189406913486439 0 +0.3215324713707958 0.333069359742767 0 +0.331938706130681 0.3423527234476074 0 +0.3202152059590688 0.3473339489468567 0 +0.3435199821783333 0.3372198961714317 0 +0.3424502197453617 0.3517458614833115 0 +0.3189966347759069 0.3617058085188665 0 +0.3246388107014659 0.3050368393368001 0 +0.3345713086545062 0.3138102904031326 0 +0.345863611186305 0.3083440110945196 0 +0.344589744611305 0.3226939308595519 0 +0.3474690112170716 0.2943997622423363 0 +0.3568200264734531 0.3026495299394505 0 +0.3543186357435496 0.3310537705827261 0 +0.3296405033041013 0.3712387370434983 0 +0.3178473582943717 0.3761509608171225 0 +0.34138045731239 0.3662718267951913 0 +0.3403106948794183 0.3807977921070711 0 +0.3523891863442777 0.3603315686618496 0 +0.3506877958200011 0.3899616250991165 0 +0.3392409324464467 0.3953237574189509 0 +0.02618405927810521 -0.2260712042243337 0 +0.03948047639575284 -0.214996952430255 0 +0.01369293222049421 -0.2388413670900923 0 +0.02960426639184852 -0.2396485276073146 0 +0.001333319593321321 -0.251495098110405 0 +0.0329256131668456 -0.2533237636620491 0 +0.04595799522297069 -0.2422712714358951 0 +-0.01089342706995642 -0.2640759651564626 0 +0.004941927544433572 -0.2644886579950499 0 +0.008512931637345837 -0.2770788936327987 0 +0.02068128935929893 -0.2652385249285458 0 +-0.003595759356898962 -0.2888449410509547 0 +0.01207148599315052 -0.2891244964634195 0 +0.03639224535319369 -0.2665178597776039 0 +-0.02300223461213749 -0.2769471953084089 0 +-0.03494806762790513 -0.2905824763333275 0 +-0.01928704620043983 -0.2893823751013787 0 +0.03964515090581348 -0.279269065529085 0 +0.0522000561076509 -0.2686673278562588 0 +0.02766166501290169 -0.2902997031654224 0 +0.04311399471506679 -0.2924094271593595 0 +0.05882419621198422 -0.296630167466341 0 +0.03937271585044188 0.2148076440847771 0 +0.02622018206500833 0.2261317861584834 0 +0.02964657726712133 0.2398315364450542 0 +0.01371595021736949 0.2389567028168035 0 +0.04577405110714691 0.2419583979815306 0 +0.03300036617007178 0.253451812982162 0 +0.001329806934315946 0.2516155653370282 0 +0.03639341167181245 0.2665614011174416 0 +0.02073699182181104 0.2654030392058837 0 +0.008533712690570329 0.2771773915145052 0 +0.004959705372720443 0.2646195350175494 0 +0.01208234668815949 0.289170122564645 0 +-0.00359259059042183 0.288893515280928 0 +-0.01092407402175844 0.2641703440490917 0 +0.05223129299811183 0.2689942610841468 0 +0.03980550037224138 0.2797410767260952 0 +0.04314077082077013 0.2924593187455309 0 +0.02767756472987071 0.2903473476093477 0 +0.05878269627914451 0.2967823251719833 0 +-0.02303659319472937 0.2770372666386006 0 +-0.0192959525279814 0.2894356064755548 0 +-0.03498136594971855 0.2906466318403889 0 +0.05678548502697092 0.2195933310210611 0 +0.07387886045720102 0.2245764409205342 0 +0.05967598032637356 0.2331109274322312 0 +0.09064814673895397 0.2295745199859537 0 +0.06258942358575806 0.2465447516581978 0 +0.1071303573457753 0.2347717140456848 0 +0.09308643277272979 0.2433100641029894 0 +0.09549985782467305 0.2574214914815945 0 +0.07918153761037258 0.2518391831981887 0 +0.1113444397269154 0.2636204723308753 0 +0.09768101429098149 0.2719156081954577 0 +0.06555319391849435 0.2603908270726282 0 +0.1233904487513219 0.2402950080420337 0 +0.1394806108512539 0.2462026213124737 0 +0.1252013630071041 0.2551849905625371 0 +0.1554579387463496 0.2526932932513228 0 +0.1266018975819196 0.2704713318775154 0 +0.06851310776077335 0.2744031887665437 0 +0.08433029985719842 0.2801374280699999 0 +0.0713296686514648 0.2884664387024017 0 +0.09964734746692899 0.2865332188084859 0 +0.07400904919488981 0.3021127050031182 0 +0.07411522918587149 -0.3020045324741335 0 +0.07147885683747489 -0.2881490158869261 0 +0.08462727056110467 -0.2799738489376389 0 +0.06859985387290181 -0.274143769036973 0 +0.1000270258807878 -0.2865003716173221 0 +0.09804371131312101 -0.2719154675654036 0 +0.06565190693042945 -0.2602206216121566 0 +0.1117802030439704 -0.2637725016392752 0 +0.0957634855723673 -0.257497220265448 0 +0.09336164707141954 -0.2433619275556145 0 +0.07935424630563877 -0.2517681299795514 0 +0.1075381455981988 -0.2349483126624312 0 +0.09091806301246408 -0.2297063017817078 0 +0.06262278151826187 -0.2462514208882721 0 +0.1271436698995496 -0.27077225410038 0 +0.1257427834638289 -0.2554924172805598 0 +0.1403412185854222 -0.2464677581962375 0 +0.123920567206461 -0.2405627967798812 0 +0.1564969054169134 -0.2531733680506944 0 +0.05976520583044501 -0.2330210127330675 0 +0.07407651462783787 -0.2246881747877697 0 +0.05696397432073767 -0.2198329645531541 0 +-0.3372504290957511 -0.3991265319071124 0 +-0.326716231779612 -0.3876229138000548 0 +-0.3147108398152882 -0.3899807431563997 0 +-0.3157146805262135 -0.3760445922643008 0 +-0.3128378080912914 -0.4037432967241276 0 +-0.3027054478509644 -0.3923385725127447 0 +-0.304713129272815 -0.3644662707285469 0 +-0.2903364354078574 -0.3947580650548415 0 +-0.291625709754308 -0.3808824743697342 0 +-0.2806596480786131 -0.3696215159210204 0 +-0.2926920596345449 -0.36698704195577 0 +-0.2685316537872089 -0.373613367075542 0 +-0.2696138697496137 -0.3590257232764318 0 +-0.2937115780194165 -0.3528879491927929 0 +-0.2882986172307373 -0.4083861425048249 0 +-0.2780690039428582 -0.3975614798662024 0 +-0.2661281484983182 -0.4009679864402137 0 +-0.2673612799898443 -0.387754712867198 0 +-0.2645197870588935 -0.4134041571022611 0 +-0.2545437503533455 -0.4047973616543531 0 +-0.2563649778616024 -0.3788426298292961 0 +-0.2829894357321186 -0.3417032284798769 0 +-0.2706829973110519 -0.3448554098803281 0 +-0.2718724956780655 -0.3308034992513417 0 +-0.2584299104609072 -0.3502326243025899 0 +-0.2606047207825637 -0.3215735370406945 0 +-0.2064577218601641 0.04301632851611763 0 +-0.2202850886571551 0.03353885381568107 0 +-0.2089747758514292 0.02584576768573771 0 +-0.23340640782093 0.02472538671667612 0 +-0.2101232466438579 0.008634381289695207 0 +-0.2472756646575159 0.015465774662495 0 +-0.2338583284825081 0.008155661061927983 0 +-0.2339643007345568 -0.008261280011626744 0 +-0.2214475679982331 -8.695383888789278e-06 0 +-0.2473444489809741 -0.01553905894656022 0 +-0.2334971400037059 -0.02474208861133236 0 +-0.2100919371670375 -0.008635536338284729 0 +-0.2606928878968359 0.007346717112674796 0 +-0.2740321261460232 -4.423641134487325e-05 0 +-0.260733222760028 -0.007449942536335647 0 +-0.2087832318502325 -0.02585991157353572 0 +-0.2202421281593314 -0.03354637394415708 0 +-0.2064577218601638 -0.04301632851611865 0 +-0.2918518807565321 0.2118657302138708 0 +-0.3044796185495237 0.2117430377528725 0 +-0.279238007349232 0.2089007375543129 0 +-0.2855403439644223 0.2234569319846671 0 +-0.2666241339419319 0.205935744894755 0 +-0.2792288071723125 0.2350481337554633 0 +-0.2916924118835714 0.2336793264215017 0 +-0.2540102605346317 0.202970752235197 0 +-0.260312597149822 0.2175269466655512 0 +-0.2540010603577122 0.2291181484363474 0 +-0.2666149337650124 0.2320831410959053 0 +-0.2413871869504121 0.2261531557767894 0 +-0.2476895235656024 0.2407093502071436 0 +-0.2729172703802027 0.2466393355262595 0 +-0.2413963871273315 0.200005759575639 0 +-0.2287825137200314 0.1970407669160811 0 +-0.2350848503352218 0.2115969613464352 0 +-0.2161686403127313 0.1940757742565231 0 +-0.2287733135431119 0.2231881631172314 0 +-0.2666057335880929 0.2582305372970557 0 +-0.2791460877494552 0.2570179271138745 0 +-0.2539918601807928 0.2552655446374977 0 +-0.2602941967959831 0.2698217390678519 0 +-0.2413779867734926 0.2523005519779398 0 +-0.2539826600038733 0.2814129408386481 0 +-0.2665657101127809 0.2828327272415627 0 +0.3672991321019792 0.2970352540492044 0 +0.3659643987841388 0.3106828260748876 0 +0.3750710110728104 0.3184324850784391 0 +0.3647508870404416 0.3246992789183533 0 +0.3852395180254077 0.3124233076821665 0 +0.3841345947445854 0.3260244834939758 0 +0.3636496684174745 0.3389666555770995 0 +0.3931619643351266 0.3334495461788094 0 +0.3830569962873911 0.3399843542947292 0 +0.3820119629805808 0.3541970044332344 0 +0.3728698692807592 0.3466805183669435 0 +0.3910767239345269 0.3615723671709788 0 +0.3809983025516819 0.3686374016081198 0 +0.3626294178554808 0.3534658066935455 0 +0.403165252277882 0.3274344475922754 0 +0.4021279749579732 0.3408279751098253 0 +0.4110987190021295 0.3478281612471862 0 +0.4011126713526395 0.3546384580755206 0 +0.4209054069910108 0.3413392408900056 0 +0.420176498021023 0.3543034696987998 0 +0.400108275592869 0.3687625228476756 0 +0.3616912532774762 0.3681050511007108 0 +0.3709022128096218 0.3757292161842974 0 +0.3607995897675794 0.3828456253616483 0 +0.3800400531647151 0.3832215759965514 0 +0.3599718307365611 0.3976806291454272 0 +-0.2553602967439448 -0.2887911946818446 0 +-0.2618423898761519 -0.276566026337677 0 +-0.2683539497949411 -0.2914572358140488 0 +-0.2554204160365836 -0.2612683694492079 0 +-0.2683881872747009 -0.2643477029998864 0 +-0.2424881337422631 -0.2581257888703898 0 +-0.2489898889355464 -0.2459411135685279 0 +-0.2749434556984491 -0.2521004061326524 0 +-0.2814298985465732 -0.2662452455822224 0 +-0.2425578513920015 -0.2306030972329561 0 +-0.2555285145578328 -0.233726382440318 0 +-0.2620726552015061 -0.2215114203472488 0 +-0.2685077274711142 -0.2368364252738704 0 +-0.255635804198221 -0.2061758782869201 0 +-0.268626197789906 -0.209286103178919 0 +-0.2814986878567169 -0.2398710146756727 0 +-0.2296177823747693 -0.2274483602665511 0 +-0.2361232090539972 -0.2152644255639201 0 +-0.2296550170148511 -0.1999554285029221 0 +-0.2426454106065361 -0.2030656533949211 0 +-0.2171367787305167 -0.1967811940569011 0 +-0.2880538440444538 -0.2276416217720459 0 +-0.2945469560974863 -0.2407333099487485 0 +-0.2816165913815909 -0.212396328070918 0 +-0.295111809193085 -0.2146169878941914 0 +-0.307735961621781 -0.2159074610255621 0 +-0.1786468609595994 0.3853344836027582 0 +-0.1884672822537656 0.374738557378904 0 +-0.1747745233346759 0.3715657829409058 0 +-0.201475679954126 0.3779865427691823 0 +-0.2006297254741222 0.3638858359982508 0 +-0.2041771064174715 0.3920054976782852 0 +-0.2145883385871622 0.3812120290754329 0 +-0.2128599768684463 0.35309858751683 0 +-0.1999198414481285 0.3497702746776279 0 +-0.2276922330566774 0.3844178147522588 0 +-0.2267478651690834 0.3703956461913289 0 +-0.2388788950896422 0.3596106504815846 0 +-0.2258583583332922 0.3563688278496168 0 +-0.2519787878725832 0.3629082455175104 0 +-0.2509786313007604 0.3489288704663141 0 +-0.2250659454696657 0.3423303038898292 0 +-0.2295378045027936 0.3985606224940205 0 +-0.2408864699441293 0.3876013857386036 0 +-0.2541878768724882 0.3909137915978194 0 +-0.2530428012861838 0.3768725493024792 0 +-0.2555036698162358 0.4048248847470418 0 +-0.2671123294246973 0.3942727129033177 0 +-0.2650375392644166 0.3662676127346894 0 +-0.2371824144772102 0.3315856222498303 0 +-0.2244006855346615 0.3282809104123708 0 +-0.250043872887398 0.3349190021644271 0 +-0.2491754763119914 0.3208791676377983 0 +-0.2629867984430478 0.3382680561159612 0 +-0.2609664279636236 0.3102415004423986 0 +-0.2484110802316612 0.3067423932094976 0 +-0.2416641030837503 0.2781084622549589 0 +-0.2470020754617488 0.2925727291340933 0 +-0.2353617664685599 0.2635522678246048 0 +-0.2293455461636273 0.2748039836712698 0 +-0.2290594298533696 0.2489960733942506 0 +-0.2167470437782429 0.2714789931801704 0 +-0.2223031087016772 0.2859812193082554 0 +-0.2227570932381792 0.2344398789638965 0 +-0.2167408729332466 0.2456915948105615 0 +-0.2044223160131236 0.2423871162268723 0 +-0.2105990384695506 0.256951036603262 0 +-0.1981199793979332 0.2278309217965181 0 +-0.1918996642358422 0.2393676326153385 0 +-0.2043407199306166 0.2681762716643403 0 +-0.2164547566229889 0.2198836845335423 0 +-0.2101524200077985 0.2053274901031881 0 +-0.2041361997028659 0.2165792059498531 0 +-0.2038500833926082 0.190771295672834 0 +-0.1918176427827429 0.213274727366164 0 +-0.1919254366091343 0.2649119000045139 0 +-0.1976445456956708 0.2793583804693557 0 +-0.1857663146921681 0.2506322061900205 0 +-0.1795664739816898 0.2617741980725017 0 +-0.1794168114546483 0.2364321849854878 0 +-0.1674655890470703 0.2587018464014609 0 +-0.1735074114748654 0.2727496592110904 0 +0.3112182093812194 -0.2479485358355572 0 +0.3248583005397894 -0.2491417884116603 0 +0.3191512823521663 -0.2384415038295689 0 +0.330547737731534 -0.2595364879211929 0 +0.3384743003187556 -0.2509020754800487 0 +0.323009588430369 -0.2684141787673852 0 +0.3362344756850735 -0.2698289648497576 0 +0.3520993215590371 -0.2529068463607207 0 +0.3466681689524628 -0.242318494688765 0 +0.3418554233763365 -0.2799699750264923 0 +0.3496378517986005 -0.2716325839764288 0 +0.3631393855697082 -0.2736600263150599 0 +0.3576089661719895 -0.2633489740947957 0 +0.3686196026542776 -0.2839192726874898 0 +0.3765924772884313 -0.2758972803880398 0 +0.3657560348817442 -0.2550683956393055 0 +0.3343664283836269 -0.2886285140780155 0 +0.3474738273893825 -0.2901680041592808 0 +0.3528136270896788 -0.3002278278603472 0 +0.3605221825204549 -0.2921377465413145 0 +0.3454958568622915 -0.3085454108482134 0 +0.357817246877577 -0.3103549589401492 0 +0.3732322743236525 -0.2945866517958681 0 +0.3793628567763654 -0.2573455262497419 0 +0.3741572809295109 -0.2467175057066443 0 +0.3846956964045294 -0.2678233134734235 0 +0.3928587132432352 -0.2597606890465025 0 +0.3890584320537456 -0.2787328771159102 0 +0.4059730668998961 -0.2623059934793083 0 +0.4012872622320801 -0.2517728151732055 0 +-0.1707353480943019 -0.2613607571579922 0 +-0.1828125272082596 -0.2652917960491263 0 +-0.1771705111724419 -0.2766995614554935 0 +-0.1884545432440774 -0.2538840306427591 0 +-0.1948897063222174 -0.2692228349402604 0 +-0.1820193801659374 -0.2385452263452579 0 +-0.1940965592798952 -0.2424762652363919 0 +-0.2069668854361752 -0.2731538738313944 0 +-0.2013248694003574 -0.2845616392377617 0 +-0.1997385753157129 -0.2310684998300248 0 +-0.2061737383938529 -0.246407304127526 0 +-0.2182509175078107 -0.2503383430186601 0 +-0.212608901471993 -0.2617461084250273 0 +-0.2238929335436285 -0.2389305776122929 0 +-0.2303280966217685 -0.2542693819097942 0 +-0.219044064550133 -0.2770849127225286 0 +-0.1933034122375729 -0.2157296955325235 0 +-0.2053805913515307 -0.2196607344236575 0 +-0.2110698002493546 -0.2082411951083875 0 +-0.2174577704654884 -0.2235917733147917 0 +-0.2045874443092084 -0.1929141647197891 0 +-0.2311212436640908 -0.2810159516136626 0 +-0.225479227628273 -0.2924237170200298 0 +-0.2367632596999085 -0.2696081862072954 0 +-0.2431984227780486 -0.2849469905047967 0 +-0.2496335858561886 -0.300285794802298 0 +-0.2359342846010517 0.303446930536894 0 +-0.2234489290934125 0.3001571956737537 0 +-0.2238653545998745 0.3142209634346385 0 +-0.2108281549799371 0.2968220692170367 0 +-0.2116780615466634 0.3249742902613458 0 +-0.198220592828308 0.2933335105090197 0 +-0.1987350905658894 0.3076072965818732 0 +-0.1864984839500381 0.3181797497216669 0 +-0.1990454193781247 0.3215947030502931 0 +-0.1741196444748064 0.3144038247488784 0 +-0.1742470099632421 0.3282194609135198 0 +-0.1993764596913764 0.3356932010049095 0 +-0.1858452888035321 0.2895901503324294 0 +-0.1736560277833807 0.2856433827605307 0 +-0.1739168679403648 0.299258722838172 0 +-0.1616345837216833 0.2818111053414493 0 +-0.1618968522008797 0.3075708130170233 0 +-0.1870550373150954 0.3462144568983612 0 +-0.1743272401351827 0.3424610634736133 0 +-0.1744653153963329 0.3566831811881187 0 +-0.1626285233700996 0.3345447004046193 0 +-0.1629858099509302 0.3640334563166623 0 +0.1832869018651003 0.2265309713327772 0 +0.1986008255352444 0.2261918532301553 0 +0.1902662282374008 0.2399896666231721 0 +0.2079680314951948 0.2127465032697232 0 +0.2133546346269158 0.2250311743277868 0 +0.2036144937984012 0.1998428232378303 0 +0.2176079963495975 0.1995306449653299 0 +0.227999428889012 0.223585811751916 0 +0.2205758025317077 0.2365489855836306 0 +0.2272542291664044 0.1865340463344538 0 +0.231566424057223 0.1987943244834481 0 +0.2459362108464399 0.1976516438074368 0 +0.2364517165565442 0.2106140909364121 0 +0.2555388299283958 0.1849201281262244 0 +0.2609308456101211 0.1969979072194969 0 +0.2435076990027884 0.2219778628345354 0 +0.223294474187581 0.1739107839248956 0 +0.2369012863318291 0.1735899926583984 0 +0.2463762122369808 0.1610228249637878 0 +0.2507009735954725 0.1729419495899176 0 +0.2420720177166829 0.148607913622007 0 +0.2557452237031789 0.1489193737990523 0 +0.2658880411341361 0.1735059823052986 0 +0.2582151875347675 0.2206469161046249 0 +0.2511951111689293 0.2329875575620081 0 +0.2665214833460031 0.2087277227352423 0 +0.2721119382279767 0.2198638594732445 0 +0.2758753934204907 0.1970858308844592 0 +0.284620088035516 0.2197809786450539 0 +0.2778558060479039 0.2300451024756024 0 +0.2978987384056158 -0.2255855058491623 0 +0.2820385449259552 -0.2245026622377702 0 +0.2899369182360387 -0.2368724112113871 0 +0.274426913126235 -0.2115191796063401 0 +0.2648643928081519 -0.2247612058110759 0 +0.2837062186489059 -0.2006807501366065 0 +0.2673816426900457 -0.1986041229736292 0 +0.2484507769800642 -0.2255108233555693 0 +0.2581438940763244 -0.2375516592799569 0 +0.2604083179729279 -0.1856493019486275 0 +0.2509327792995755 -0.1988791822647775 0 +0.2345191634714878 -0.1996287998092709 0 +0.241484970225776 -0.2125698115824201 0 +0.2285211227806033 -0.1863912360360891 0 +0.2185852315341435 -0.2002204648988538 0 +0.2320371611519765 -0.2262604409000628 0 +0.2697375303336338 -0.174999339976656 0 +0.2534394578686351 -0.1727062310207532 0 +0.2470653518430901 -0.1598304286131927 0 +0.237942198478102 -0.1728647327745232 0 +0.2563482400326392 -0.148749717101264 0 +0.2418149673317565 -0.146495867077629 0 +0.2239758288937663 -0.1726177240449633 0 +0.2156235453238889 -0.2270100584445562 0 +0.2253845668881636 -0.23918455041922 0 +0.2088759902888475 -0.2139898715326658 0 +0.1992099294958012 -0.2277596759890496 0 +0.204555958025111 -0.1998439738723446 0 +0.1844747291198176 -0.2270664221680115 0 +0.1919845381578201 -0.2411663026013129 0 +-0.2481148175660626 -0.3283834383009828 0 +-0.2489918890170896 -0.314083316654424 0 +-0.2471405714137928 -0.3425252351678096 0 +-0.2355421367705148 -0.3379689866516864 0 +-0.2461425010552168 -0.3566066146036869 0 +-0.2229758694534875 -0.3476760832366713 0 +-0.2237437424536796 -0.3337641506663933 0 +-0.245101257455009 -0.3719915283894469 0 +-0.2336676971565894 -0.3661202037329319 0 +-0.2212674602212406 -0.3756548464217325 0 +-0.2221020375740042 -0.3616170680344611 0 +-0.2206952980531829 -0.3894791855476599 0 +-0.2091884278784337 -0.3851820808766194 0 +-0.2104669625475907 -0.3573998176933673 0 +-0.244278669086147 -0.3861760214799015 0 +-0.243723921264765 -0.3994956973013267 0 +-0.2325774991612447 -0.3944500052514848 0 +-0.243565454329049 -0.4118865672472288 0 +-0.2217127071631139 -0.4035138838751996 0 +-0.1980655721494791 -0.3671040043324474 0 +-0.1987827108363599 -0.3532171021625964 0 +-0.1976068021075472 -0.3809824435785819 0 +-0.1861437139795412 -0.3768095344100369 0 +-0.1998538508449659 -0.3949292093206096 0 +-0.1774990790596897 -0.3863429477614578 0 +-0.1742918196368843 -0.3726646850604965 0 +0.3280115184033063 -0.2291362716487927 0 +0.3130913855108388 -0.2271311630781128 0 +0.3418314274555219 -0.2313150820291988 0 +0.3371822589574116 -0.2197143982228612 0 +0.3555919761675537 -0.233529459434959 0 +0.3465963854569702 -0.210323668695268 0 +0.3312015230437326 -0.207671373293579 0 +0.3694896854707663 -0.2357263799743601 0 +0.3650563283877751 -0.2244158445812772 0 +0.3748074393702615 -0.2154283335906785 0 +0.3609982250211704 -0.2128516421617969 0 +0.3887096219844062 -0.2187351471257448 0 +0.3845278755234187 -0.2073407944358776 0 +0.3557105748060199 -0.2011427172847033 0 +0.383345224775918 -0.2381796515761213 0 +0.3970164877991713 -0.2409421996366361 0 +0.3928664181676832 -0.2299631829210124 0 +0.4103699077079119 -0.2440677376912204 0 +0.4026549192652226 -0.2222291764928309 0 +0.3652098836225353 -0.1924614084921449 0 +0.3498415997136441 -0.189212790032127 0 +0.3799316027486146 -0.1961727569589742 0 +0.3752176874467449 -0.1842234199572177 0 +0.3942201257698822 -0.2004052975529551 0 +0.3854896794155303 -0.1766399253897758 0 +0.370339881668616 -0.171564148965432 0 +0.1647096274486895 0.1285235478035663 0 +0.1661583507037662 0.1423882793385067 0 +0.178391147412209 0.1432873140406534 0 +0.1679042122493171 0.1561874113019756 0 +0.1878893568546634 0.129612948023709 0 +0.19069472222082 0.1444038064196833 0 +0.1700852520102833 0.1700869236349116 0 +0.2032327103210141 0.145732056491455 0 +0.1936400830636519 0.1589131691911801 0 +0.1966940894375078 0.1729548025633589 0 +0.1834117762648223 0.1715881440739692 0 +0.2099363681098854 0.1737741107556348 0 +0.1999556426593292 0.1865338172983261 0 +0.1726071334966495 0.1839906647289313 0 +0.2120800792633162 0.1319972493787365 0 +0.2161152389031484 0.1472749961981431 0 +0.2289165623927247 0.1478687370413287 0 +0.2196193633823457 0.1607612231068206 0 +0.2379103339299296 0.1353064747717163 0 +0.1755142908284795 0.1980343781246569 0 +0.1896228343211297 0.1992850972989008 0 +0.1788805576238386 0.2120781897566434 0 +0.1797014716520161 -0.2120639262231816 0 +0.1903778138824909 -0.1989410955110529 0 +0.1759890933749814 -0.1973488327439245 0 +0.2005828594644335 -0.1855853561107233 0 +0.172848701517489 -0.183095178711882 0 +0.2103374265166606 -0.1721127971970104 0 +0.1969173360506639 -0.1713630551067905 0 +0.193488945691938 -0.157094077498383 0 +0.1835012548368719 -0.170294331793091 0 +0.2027007011403256 -0.1434229426528988 0 +0.1902679529325895 -0.1426643836770126 0 +0.1700572271789571 -0.1690390589765104 0 +0.2196039077190604 -0.1585956681530132 0 +0.2283574475018898 -0.1452217973471015 0 +0.2153966184820993 -0.1443182550591376 0 +0.2364520031018646 -0.1320747605772049 0 +0.2112015077354005 -0.129459294704881 0 +0.1676699900047408 -0.155198964280207 0 +0.1779608288528596 -0.142005307424965 0 +0.1657177366130533 -0.1414854162970668 0 +0.1873276870492201 -0.1281414020485568 0 +0.1643970325614548 -0.1279972437643788 0 +-0.16308331558571 -0.3649481943948701 0 +-0.1747496369867896 -0.3587124089600635 0 +-0.1752059195341215 -0.3446369048696991 0 +-0.1870555860723513 -0.3490222230981473 0 +-0.163611993434672 -0.3371013444175244 0 +-0.1756187818560736 -0.3305224328050986 0 +-0.1994325474220714 -0.3393479911154772 0 +-0.1759590731761205 -0.3162229251192422 0 +-0.1879865092330473 -0.3210215072273964 0 +-0.2004342361741456 -0.311608806413739 0 +-0.1999529951447068 -0.325406945845338 0 +-0.2008854132916463 -0.2979797322990203 0 +-0.2129346345353805 -0.3020717487938103 0 +-0.211886268212887 -0.3296479103221274 0 +-0.1637823491395787 -0.3113580511811381 0 +-0.1762998247398955 -0.3027953242974757 0 +-0.1767110054980685 -0.2896462429276924 0 +-0.1888113995884666 -0.2938157332839768 0 +-0.1646016215830287 -0.2854975663506594 0 +-0.2244139434768788 -0.319895113756283 0 +-0.2249696111877814 -0.3061096577153242 0 +-0.2369982035941637 -0.3101034470581751 0 +0.2959759797406823 0.2347908387602098 0 +0.2932220973604954 0.2465391863292725 0 +0.2847973574166619 0.2387095654358949 0 +0.30219265951621 0.2542745506441327 0 +0.2901413365434845 0.2589033787737485 0 +0.31396244563383 0.2486463167171093 0 +0.3112814005156721 0.2620623573241497 0 +0.2870646483841337 0.2716079254210056 0 +0.2776025283307048 0.2626843883933597 0 +0.3203328207733212 0.2700828072784205 0 +0.3084718176584267 0.2753122437540451 0 +0.3056312104071803 0.2883351850511764 0 +0.2964037744574738 0.2800329595514502 0 +0.3147116220966629 0.2962668410560447 0 +0.3028028970170571 0.3010680723369164 0 +0.2841217494156966 0.2844268312119099 0 +0.3321450370504281 0.2642940238940313 0 +0.3292699827744019 0.278491194477726 0 +0.3383653697495804 0.2864063165762902 0 +0.3266076844219916 0.2914535882900154 0 +0.3497585381748056 0.28120004418163 0 +0.2814288716970406 0.2974546079616273 0 +0.2714990690308763 0.2884819767085485 0 +0.2909257906297598 0.3058978792364579 0 +0.2791592411207506 0.3108374462474235 0 +0.2666337993176779 0.3149835421909153 0 +0.2980640007723491 0.0549412626661121 0 +0.29433873782591 0.03936302846325571 0 +0.2826983211376065 0.0318876070480563 0 +0.2918515760598861 0.02369295075616208 0 +0.2736509776172299 0.04042191634795471 0 +0.2708525950603641 0.02431789495740261 0 +0.2905619335371635 0.007947475509253045 0 +0.2587905944647133 0.01659568116273565 0 +0.2694731088220926 0.008229325183583977 0 +0.2694429772728316 -0.007870062000017991 0 +0.2802004584661404 0.0001222322944383453 0 +0.258915403514337 -0.01620484629079023 0 +0.2710215095264334 -0.02397623604413593 0 +0.2906239723897066 -0.007817922978713667 0 +0.2485249604630021 0.0254744941145431 0 +0.2467537574545809 0.008681915111821077 0 +0.2343051415033517 0.0001818968421808814 0 +0.2469358913219083 -0.008266914558410048 0 +0.2212361934846093 0.009831150399680372 0 +0.2213766484048171 -0.009581210094452244 0 +0.248805792680312 -0.02509893305819052 0 +0.2918700319450481 -0.02355246317458113 0 +0.2828651453408542 -0.0316363983383182 0 +0.2944320106061083 -0.03921217536122785 0 +0.273809716195807 -0.04003952866825912 0 +0.2980473579894534 -0.05471251620577355 0 +0.1760656846016977 -0.3733923750723324 0 +0.1767090251591928 -0.3573022280719689 0 +0.1641626832268492 -0.3638794345486583 0 +0.1891931461358864 -0.3510192463719609 0 +0.1777778125217825 -0.340844525837775 0 +0.2007304134385424 -0.3594131842638671 0 +0.2018618538693486 -0.3448329323283991 0 +0.1789353371209561 -0.3242273344526135 0 +0.1662343556847371 -0.3304077107549435 0 +0.21471016065794 -0.3388299800057097 0 +0.2032590833014505 -0.3288448297202426 0 +0.2048558417588853 -0.3122833532140847 0 +0.1918427600189104 -0.3181296112437664 0 +0.2181596153218551 -0.3070687047899253 0 +0.2069394852456525 -0.2959052325678495 0 +0.1802163225222365 -0.3075291268660852 0 +0.2260592695496588 -0.347460580880066 0 +0.2278838073920455 -0.3332819592514347 0 +0.2412954705728949 -0.3288824804663286 0 +0.2300892142025194 -0.3186091538843988 0 +0.2520634849316374 -0.3383202925253171 0 +0.2547931621689745 -0.3255044852158662 0 +0.2329160568658099 -0.3040301046482476 0 +0.1817208258153241 -0.2907017139820285 0 +0.1681416409101446 -0.2972508967304431 0 +0.1955363516992686 -0.2847412660335739 0 +0.1836630213007978 -0.2734956801058643 0 +0.209913892800648 -0.2804113959804527 0 +0.1863604260303402 -0.2560586765399455 0 +0.1709222015220724 -0.2625176665031732 0 +-0.3062153968746667 -0.487105425747433 0 +-0.3187153968746668 -0.487105425747433 0 +-0.3249290479246989 -0.4742143079222259 0 +-0.3312149535646901 -0.4871063004498238 0 +-0.3186461906240003 -0.461316277242299 0 +-0.3311265361318999 -0.4613553937651153 0 +-0.3437135896572358 -0.4871089796019831 0 +-0.3373080750634312 -0.4485286107092217 0 +-0.3435663048958438 -0.4614749017111247 0 +-0.3559489327904721 -0.4617064225446584 0 +-0.3498546488654149 -0.474361388779006 0 +-0.3620594672551308 -0.449013375715339 0 +-0.3682540841065501 -0.4620951829292039 0 +-0.3562070846165957 -0.4871217421151559 0 +-0.3311154884820453 -0.4357537230934913 0 +-0.3434928336352854 -0.4356965505402275 0 +-0.3496780258748003 -0.4228558370888527 0 +-0.3558538379006628 -0.4359859603893326 0 +-0.3435457336923151 -0.4099522547234296 0 +-0.3554353539895335 -0.4112960873616622 0 +-0.367316711816713 -0.4387296201126855 0 +-0.3686957220965214 -0.4871438556860272 0 +-0.3744211113410461 -0.4752124326641664 0 +-0.3810201304371311 -0.487491280734778 0 +-0.3795657581866324 -0.4646673106738093 0 +-0.3926549107267673 -0.4892141765560473 0 +-0.4911478336784361 0.306353186243547 0 +-0.4905307764114653 0.318611397309371 0 +-0.4806324110653324 0.3245430203431314 0 +-0.4900334414132291 0.3309188508150525 0 +-0.4716328530539178 0.3182778804547873 0 +-0.4705244633873373 0.3303457138971164 0 +-0.4896036532210605 0.3432568817422493 0 +-0.4602366457481438 0.3360220472487059 0 +-0.4696548496139142 0.3425164615082845 0 +-0.4688912978211683 0.3547883821255405 0 +-0.4792761497239632 0.3490355249767371 0 +-0.4588246902652251 0.3604474014625466 0 +-0.4688109596631815 0.3672706452267479 0 +-0.4896036532210605 0.3557568817422493 0 +-0.4511046949440867 0.3295047749483924 0 +-0.4498919064206635 0.3415692948877638 0 +-0.4393951554042891 0.3470086117742365 0 +-0.4491125433192183 0.353712054253204 0 +-0.4295007925661796 0.3402854088438457 0 +-0.4285892130930299 0.3525175162719474 0 +-0.4487187944402133 0.3657441931208957 0 +-0.4896036532210605 0.3682568817422493 0 +-0.4792479724609486 0.3739574090539417 0 +-0.4897190105446136 0.3805970247391235 0 +-0.4693414301607335 0.3790458669653216 0 +-0.4900090538284555 0.3926951087752249 0 +0.3771085876790826 0.1837609171441303 0 +0.3640297954655597 0.1903161188295752 0 +0.3640355826091022 0.1748616657217968 0 +0.3622017639947581 0.2059221537149911 0 +0.3521506636089943 0.1976172801120338 0 +0.3712219615175139 0.215243783916005 0 +0.3598895855028888 0.2213420986816871 0 +0.340784351880497 0.2051072436986042 0 +0.3402665375259974 0.1911356576213234 0 +0.3573948526467073 0.2365429493260594 0 +0.3487111641092567 0.227981567751092 0 +0.3376265767262489 0.234888751937216 0 +0.339658449969175 0.2198143148142896 0 +0.335010754945773 0.2499064552364474 0 +0.3256065925812859 0.2420492087216416 0 +0.3288165303215477 0.2131899005676954 0 +0.3656157944522609 0.2457356145091866 0 +0.3548098566838225 0.2516286092502433 0 +0.3521558588368301 0.2665819326341481 0 +0.3440146486827597 0.2578352758447036 0 +0.3604164880141787 0.275398032100489 0 +0.3173463757222605 0.2209308089935681 0 +0.3176134259500932 0.207065339738279 0 +0.315949450861054 0.234818535745289 0 +0.3061192707627468 0.2282904611869042 0 +0.2960457882604381 0.2227738384291734 0 +-0.3953829646506848 -0.1867763853951675 0 +-0.3846416067846327 -0.1908731841014209 0 +-0.3892690761348132 -0.1773529704684535 0 +-0.3801850783579047 -0.2044199437198565 0 +-0.3733195988856297 -0.1951735941207315 0 +-0.3863751145719648 -0.2139769109262879 0 +-0.3759819737297293 -0.2177528983101556 0 +-0.3628906765701305 -0.1991278522551964 0 +-0.3654723887674362 -0.1862838375789979 0 +-0.3718641991384965 -0.2309858671793151 0 +-0.3655562156272912 -0.2216251882022448 0 +-0.355033295654665 -0.2255469677927771 0 +-0.3592259313348751 -0.21229614118106 0 +-0.3509610281787841 -0.2387118961769182 0 +-0.3442518862681461 -0.2294959312430283 0 +-0.3524559316843384 -0.2031283348832334 0 +-0.3779986577035309 -0.2404331876088922 0 +-0.3678057158468283 -0.2441653277033828 0 +-0.3638719109266676 -0.2572506280133662 0 +-0.3573857498520167 -0.2479755841862802 0 +-0.3702151269528538 -0.2665521999439516 0 +-0.3600339906372423 -0.2702367069736643 0 +-0.3465485819356282 -0.2517968971491148 0 +-0.3414823335132831 -0.2071883152386655 0 +-0.3447658877246705 -0.1940749739621653 0 +-0.3374411527858042 -0.2202990607618947 0 +-0.3306924445941722 -0.2112131419679775 0 +-0.3331018694143077 -0.2333545203531822 0 +-0.3202698639814549 -0.2151684041627584 0 +-0.3239843229714656 -0.2022718383689034 0 +-0.07699895983647388 0.3023337136593502 0 +-0.079069616813502 0.287683820975274 0 +-0.09156125961138828 0.2826518972075889 0 +-0.08384419195412837 0.2706108689772637 0 +-0.1003112905125342 0.2938792510929429 0 +-0.1040529024092745 0.2776199734399039 0 +-0.08861876709475475 0.2535379169792535 0 +-0.1165445452071608 0.2725880496722188 0 +-0.1088274775499009 0.2605470214418937 0 +-0.1136020526905273 0.2434740694438835 0 +-0.101110409892641 0.2485059932115685 0 +-0.1260936954884135 0.2384421456761984 0 +-0.1183766278311537 0.2264011174458733 0 +-0.09339334223538112 0.2364649649812434 0 +-0.1250827882267851 0.2840754675585659 0 +-0.1290361880050471 0.2675561259045338 0 +-0.1415278308029334 0.2625242021368488 0 +-0.1338107631456735 0.2504831739065236 0 +-0.1496740556937985 0.2741838291999535 0 +-0.1541333703732702 0.2579990692368624 0 +-0.1385853382862998 0.2334102219085134 0 +-0.09784020776006469 0.2194640112046724 0 +-0.1106595601738938 0.2143600892155481 0 +-0.1029424925166339 0.2023190609852229 0 +-0.12315120297178 0.2093281654478631 0 +-0.1077170676572603 0.1852461089872127 0 +0.08883327008927838 0.3082348672485885 0 +0.1014402234335614 0.3010355286913016 0 +0.1032256084819254 0.3152268496670302 0 +0.1143269689771986 0.2936610344199536 0 +0.1164872667369568 0.3235335669386511 0 +0.1275462058476189 0.2860264926705633 0 +0.1282015342946032 0.3016441614203947 0 +0.141360271695321 0.3103954038213506 0 +0.1287099038763335 0.3171976936049261 0 +0.1542818409903985 0.3033098016215039 0 +0.1538096114812236 0.3198580203204933 0 +0.1292397072167434 0.3326183986207492 0 +0.1414113682776033 0.2778643503458954 0 +0.1558112998211062 0.2691616087532123 0 +0.1548188444805768 0.2865587661046963 0 +0.1699482341549826 0.2608356621583232 0 +0.1674008436091582 0.2961011288538578 0 +0.1414060903801885 0.3424401322042439 0 +0.1534176288221615 0.3363103234186403 0 +0.1528987404206008 0.3529113621392146 0 +0.1658147447487527 0.3297346158374738 0 +0.1640430699384267 0.3635682151931626 0 +0.1530530187706726 -0.3531041050424371 0 +0.1537480810214202 -0.336583485209539 0 +0.1415991436570667 -0.3424988085296211 0 +0.1542954948625631 -0.3203164179724032 0 +0.1294423506259261 -0.3326712818031498 0 +0.1548263324396514 -0.3040049262964674 0 +0.1418087476489626 -0.3107481435516686 0 +0.1286813251431159 -0.3018285756987736 0 +0.1290352247907475 -0.3173539893561998 0 +0.1280112650835977 -0.2863338113420669 0 +0.1146563335067088 -0.2938241634106373 0 +0.1166381233523369 -0.3236065870719095 0 +0.1553385427036975 -0.2875480469018898 0 +0.1559539564453466 -0.2706397010195747 0 +0.1416178129171191 -0.2787312357623538 0 +0.1033659036344889 -0.3152938622654104 0 +0.1016378952836896 -0.3011172267304 0 +0.08890054417127413 -0.3082705635089484 0 +-0.1081366665073102 -0.1856505109828219 0 +-0.103362091366684 -0.202723462980832 0 +-0.1114987578739945 -0.215168893206766 0 +-0.09826678674803803 -0.2199318998042308 0 +-0.1244099995219313 -0.2105413714346898 0 +-0.1196354243813051 -0.2276143234326999 0 +-0.09343621598037757 -0.2369646131064261 0 +-0.1277720908886156 -0.2400597536586339 0 +-0.1148608492406789 -0.2446872754307101 0 +-0.1100862741000526 -0.2617602274287203 0 +-0.1019496075927421 -0.2493147972027864 0 +-0.1182229406073632 -0.2742056576546542 0 +-0.1053116989594264 -0.2788331794267305 0 +-0.0890383659448053 -0.2539423189748626 0 +-0.1406833325365524 -0.2354322318865576 0 +-0.1359087573959262 -0.2525051838845678 0 +-0.1440454239032367 -0.2649506141105017 0 +-0.1311341822553 -0.269578135882578 0 +-0.1570493619142256 -0.260799080428789 0 +-0.1525745330720465 -0.2770373395059356 0 +-0.1267573439923128 -0.2863428373122574 0 +-0.08426379080417906 -0.2710152709728728 0 +-0.0924004573114896 -0.2834607011988067 0 +-0.07948921566355283 -0.288088222970883 0 +-0.1013566336651099 -0.2951488727526335 0 +-0.07725191852084998 -0.302462913637104 0 +0.3065680270639838 0.4891814945192526 0 +0.3187422149403105 0.4881137694649831 0 +0.324984429880621 0.4762275389299663 0 +0.3312422149403105 0.4881137694649831 0 +0.319166891368425 0.4658660505026062 0 +0.3312266448209315 0.4643413083949494 0 +0.3437404954133711 0.488116989526407 0 +0.337468859761242 0.4524550778599326 0 +0.343707370693097 0.4643773990575137 0 +0.3561724953747029 0.4644427664843443 0 +0.3499557964717692 0.4762811620773149 0 +0.3617818618891545 0.4540288948200405 0 +0.3679326774254342 0.4660273912678677 0 +0.3562362073839743 0.4881250154775864 0 +0.3315863026864679 0.4420022241671333 0 +0.3437110747015525 0.4405688473249157 0 +0.3498664590721874 0.4291265047706356 0 +0.3557846162824214 0.4416016551181369 0 +0.3438840811370266 0.417408916314428 0 +0.3556766723979339 0.4185018766778836 0 +0.3674189011822369 0.4438568182735119 0 +0.3686595132548302 0.4882731189377374 0 +0.3742063539596477 0.4777922430408992 0 +0.3806687771817479 0.4892359984663927 0 +0.3795747757113877 0.4677858663920368 0 +0.3926947696032446 0.4901263871177469 0 +0.4899747643279612 -0.3065636335715961 0 +0.4902120294110695 -0.3189864605369992 0 +0.4805653876658998 -0.3254218790962257 0 +0.4903066739810019 -0.3314461675413976 0 +0.4707966209396489 -0.3194178881214652 0 +0.4709641060609487 -0.3318342752882252 0 +0.4903623429622901 -0.3439093819589086 0 +0.4613973230936667 -0.3382270506046479 0 +0.4711022946276174 -0.3442321373256186 0 +0.4712099515145707 -0.3565894948014419 0 +0.4807658173814657 -0.3502715400078528 0 +0.4616947901877932 -0.3627792512978538 0 +0.4712932434171084 -0.368838128685851 0 +0.4903968890406048 -0.356361061888277 0 +0.4515006891309808 -0.3323161794931406 0 +0.4518957986278384 -0.3446000462389995 0 +0.4424829689585615 -0.3508692972370985 0 +0.4520873228250137 -0.3568453119139096 0 +0.4327454332284387 -0.345045122903275 0 +0.4330240034970712 -0.3566313068960537 0 +0.4522199551385925 -0.3686671521513897 0 +0.4904245056797323 -0.3687955699597852 0 +0.4808700844149021 -0.3750569852971192 0 +0.490475228090352 -0.3810333523853238 0 +0.4714587992704928 -0.3804268262552084 0 +0.4905605871084342 -0.3930312928088939 0 +0.359991414398115 0.1606952859421457 0 +0.3516338062390665 0.148967970891012 0 +0.3350983273448989 0.1451492954864804 0 +0.342223048210294 0.1373049853417887 0 +0.3264969666667706 0.1532091783331127 0 +0.3191045056601768 0.1412232805155945 0 +0.3329579577741559 0.1251854856740587 0 +0.303538890054068 0.1374244393227955 0 +0.3112143315789689 0.1289867555166376 0 +0.3032990030963176 0.1163708251214955 0 +0.3182818004111959 0.1206976465531111 0 +0.2883594888309261 0.1121691169600894 0 +0.2956028786894431 0.1034369983729057 0 +0.3243123197982206 0.1125433146948999 0 +0.2950500628663593 0.1459665930469847 0 +0.2880644947412827 0.1338705225610447 0 +0.2729169439156728 0.1306247963223625 0 +0.280853128414619 0.1213974751714021 0 +0.2646628333932355 0.1397730146338034 0 +0.2583508344871576 0.1277275296404413 0 +0.2733531680657927 0.1081928944073142 0 +0.3161165472599207 0.09930007065805205 0 +0.3021941407644619 0.09457727770385187 0 +0.3087861551707969 0.08552888912512494 0 +0.2880669653414886 0.08917772285782267 0 +0.3028917410146107 0.07037034152469163 0 +0.3027695899945955 -0.06997976856878282 0 +0.3087228336499693 -0.08472830173103182 0 +0.3016762429478149 -0.09345968412962975 0 +0.3159207561033054 -0.09867846499424826 0 +0.2873820996951353 -0.08781004793770819 0 +0.2949159615998269 -0.1016645252729867 0 +0.324137665342629 -0.1119244039773834 0 +0.287703080980783 -0.1101385070321567 0 +0.3031757631387566 -0.1147312794255745 0 +0.3113894756299328 -0.1276391376976135 0 +0.3180692938828492 -0.1196246002919884 0 +0.3039867947796013 -0.1361141281887188 0 +0.3197807578569366 -0.1402590042459735 0 +0.3329599887483952 -0.1246606163362717 0 +0.2721138062011451 -0.1056702628681661 0 +0.2801529803539283 -0.118961193983788 0 +0.2723589326744936 -0.1281572764206615 0 +0.28813765449845 -0.1321362659625658 0 +0.257730589487366 -0.1242401064854304 0 +0.2647302173164028 -0.1381441691549332 0 +0.2960100960483493 -0.1451045978192546 0 +0.3424489003081744 -0.136823850813368 0 +0.3357658832427594 -0.1444247894291 0 +0.3523076906094449 -0.1484685329974215 0 +0.3281271659758548 -0.1526197022579069 0 +0.3621453720301555 -0.1597499111355961 0 +0.4912117545286403 0.2075830048522321 0 +0.4756436488676317 0.2058454236550212 0 +0.4670730088719161 0.2123964103636546 0 +0.4605932239396784 0.2030650822353068 0 +0.4720465232727039 0.2227000140704855 0 +0.4590129072120819 0.2189747593696452 0 +0.445883059855305 0.1992557333744913 0 +0.4516876843926823 0.2258322259116916 0 +0.4456377197910966 0.2155103246170272 0 +0.4323671178353482 0.2122711864910396 0 +0.4389155437857249 0.2054073675545572 0 +0.426319329070064 0.2204307158159263 0 +0.4193660757590374 0.2096201016648569 0 +0.4315203157660292 0.1952361160315409 0 +0.456820312001489 0.2363128222358835 0 +0.44481242459001 0.2332194307841762 0 +0.438622991629432 0.2409481473302384 0 +0.4327878200608858 0.2310934507409573 0 +0.4438393948105385 0.2498122198729083 0 +0.4330621469297036 0.2487888528126195 0 +0.4207792992222522 0.2297671464200912 0 +0.4175330712151527 0.1905241521642842 0 +0.411950036002722 0.1983074866480217 0 +0.4040004069264325 0.1858117468407554 0 +0.4067074678538285 0.2075813121851438 0 +0.3907704878635987 0.1820344488144502 0 +-0.3780100026081555 -0.1700414135214111 0 +-0.3670759828699851 -0.1738734263657892 0 +-0.3679625459984386 -0.1618995863444331 0 +-0.356787402452854 -0.1775314208539328 0 +-0.358473266044675 -0.1531934371502186 0 +-0.3464417704381421 -0.181350960405603 0 +-0.3477959279305083 -0.1687490073390966 0 +-0.3384178008101225 -0.1601988029836913 0 +-0.3485147610485335 -0.1565321203771195 0 +-0.3277603481916895 -0.1646576405681486 0 +-0.3288876771548361 -0.1519263754406091 0 +-0.349145256389766 -0.1442455793635545 0 +-0.3358943595958118 -0.1854759683486122 0 +-0.3252903658569939 -0.1899116844683963 0 +-0.3264331653498471 -0.177487553298825 0 +-0.3402322749792202 -0.1349385443722379 0 +-0.3301179042849698 -0.1389360314726213 0 +-0.3316419473124383 -0.1254282432484664 0 +-0.3316797212539844 0.1244861208607947 0 +-0.3299502862015025 0.1373076211799993 0 +-0.3400346049940796 0.1342757576171453 0 +-0.3282554821362811 0.1499292387553211 0 +-0.3488524600156896 0.1437018400847008 0 +-0.3265128794985754 0.162429883437188 0 +-0.3372305763777987 0.1587863799411289 0 +-0.3460472154485772 0.1675619050394943 0 +-0.3477343935916171 0.1555683332777964 0 +-0.3440398908646973 0.1795418216023276 0 +-0.3548030803919097 0.176168987665066 0 +-0.3579267001930631 0.1528184327047387 0 +-0.3245404692438497 0.1748631062290853 0 +-0.3223660026602737 0.1871699902818451 0 +-0.3331224128191588 0.183211352168741 0 +-0.3198100437129132 0.1993266033313261 0 +-0.3407830183413174 0.1917245106323502 0 +-0.3674938494395351 0.1614688925216662 0 +-0.3660984735614555 0.1728890239799011 0 +-0.3778126431345297 0.1696765984397431 0 +-0.3629525597006887 0.184707224699566 0 +-0.3866632717939139 0.1775288636241765 0 +-0.182991000369595 -0.4000247992001794 0 +-0.1939657380767009 -0.4045661973422197 0 +-0.1885889080033113 -0.4139187241037643 0 +-0.2044436271750083 -0.4090711360899822 0 +-0.1941234231288736 -0.4285379501701694 0 +-0.2150409383589328 -0.4133970014131824 0 +-0.2088767294194831 -0.423429012195236 0 +-0.2136373652833698 -0.4375375670610978 0 +-0.2036352185239557 -0.4333459524844033 0 +-0.2241000444464282 -0.441173671405104 0 +-0.2182752540084623 -0.4512175573292239 0 +-0.1988846129960506 -0.4433515441080988 0 +-0.2256499861339595 -0.4175599419161279 0 +-0.2366933266087566 -0.4214324164688945 0 +-0.2301689685256245 -0.4312790504279513 0 +-0.2475601790854803 -0.4248794688403232 0 +-0.2349989515007041 -0.4442685106389511 0 +-0.2030316647846145 -0.4583689588810076 0 +-0.2124295804032558 -0.4616404877637652 0 +-0.2062008706925579 -0.4726364013882955 0 +-0.2227073176773613 -0.4644926463678317 0 +-0.2087681560158601 -0.487220051435038 0 +-0.2085631972696159 0.4884962872371159 0 +-0.206891327222545 0.4729629920515084 0 +-0.2145630496337468 0.4616592869307525 0 +-0.2041750594933903 0.4576133916191224 0 +-0.2256895918088477 0.4654888617113476 0 +-0.2223670430017284 0.4503635923758912 0 +-0.2004580754401121 0.4424983355899181 0 +-0.2302281625876383 0.4390612968672941 0 +-0.2183637395607022 0.435448730371363 0 +-0.2138332488004975 0.4207496203461729 0 +-0.2069606696189477 0.4316584824641864 0 +-0.2209742377211409 0.4097554134158941 0 +-0.209127927719254 0.406263354805007 0 +-0.1958259207851259 0.4276760137684131 0 +-0.2428159863480795 0.4424814361855793 0 +-0.2382690250281106 0.4276901411678359 0 +-0.2467082410163521 0.4162262934239914 0 +-0.233632364882368 0.4130756793748884 0 +-0.2598361954019353 0.4190695699903728 0 +-0.1903999297361489 0.4131989850188486 0 +-0.1970289132042443 0.4027114333042792 0 +-0.1844465288021146 0.3991008890853985 0 +0.2085336270102074 -0.487589902959996 0 +0.2058606637877976 -0.4731783302522348 0 +0.2115943253853625 -0.4630149551300103 0 +0.2025716598742714 -0.4590965655301367 0 +0.2216550088991271 -0.4661746584829399 0 +0.2170009743968789 -0.4533485082668977 0 +0.1986903638158349 -0.4439649175530794 0 +0.2224905430144682 -0.4440887955461437 0 +0.2125678637890943 -0.4396797161267991 0 +0.2084604531713874 -0.4258061805386338 0 +0.2032261375234893 -0.4347673425811975 0 +0.2146442084475611 -0.4172246387228973 0 +0.2048393006178399 -0.4122987448751843 0 +0.1937486813762387 -0.4291862894352437 0 +0.2327257347182556 -0.4479495771076765 0 +0.2282677934324352 -0.4349985923564731 0 +0.2345659920501385 -0.4261755600893193 0 +0.2245038031792513 -0.4218379026018996 0 +0.2443492153672376 -0.4302338994711512 0 +0.2410083861988853 -0.4176687470025188 0 +0.2214270338452011 -0.4088824898731356 0 +0.1882448800603133 -0.4147411590571003 0 +0.1945812106760699 -0.4069533491503879 0 +0.1834990527785359 -0.4013147291899488 0 +0.2019138987785659 -0.3992173071482801 0 +0.1785629079235757 -0.3877958862251084 0 +0.1789965104512874 0.38752853048262 0 +0.1842174713826404 0.4012612552811785 0 +0.1961652547061558 0.4070058035843674 0 +0.1897484759890141 0.4152184189020811 0 +0.2040739257255879 0.3989615527421986 0 +0.2072127352108231 0.4125371939244345 0 +0.1948917760833936 0.4294879999852418 0 +0.2176370260493329 0.4177134458079675 0 +0.2109190839375093 0.4263826251135397 0 +0.2145835736540677 0.4405041062884351 0 +0.2048027074813953 0.4351850142021841 0 +0.2249215336453937 0.4452086501058605 0 +0.2184511078813582 0.4545730549017729 0 +0.1995795679820545 0.4439663285231069 0 +0.2249498818607685 0.4092143100943607 0 +0.2280602277942311 0.4225946844871077 0 +0.2384201153997662 0.4271115437465743 0 +0.2316384336900899 0.4360541001504825 0 +0.2452616274119563 0.4182711439465998 0 +0.2486955734171936 0.4314316341181987 0 +0.2354636349302846 0.4493306450990049 0 +0.2032427504120529 0.4592245814565785 0 +0.2117328486124251 0.4649676165257534 0 +0.2055552405454997 0.4747016044969541 0 +0.2223337450474946 0.4679767174763 0 +0.2084503298175782 0.4883277363982386 0 +0.3996209438836007 -0.1828318615974989 0 +0.4040225333596635 -0.1941206002869387 0 +0.413688487098459 -0.18867519523524 0 +0.4084469635980921 -0.2048635741524425 0 +0.4281996140566579 -0.1945724174552648 0 +0.4126571959922044 -0.2154006363019498 0 +0.422789108981651 -0.2091972233509338 0 +0.4367390633653809 -0.214038606261535 0 +0.4327193816569573 -0.2040792495128907 0 +0.4401517763053517 -0.2245564495468674 0 +0.4504137750311962 -0.2186410496082714 0 +0.4431127908400903 -0.1990096114275535 0 +0.4165198420805619 -0.2263046914155472 0 +0.4200286770083082 -0.2373163180102932 0 +0.4300454120381454 -0.2307447516942308 0 +0.4230820141733403 -0.2484308283453853 0 +0.4429936008176117 -0.2355686800425968 0 +0.4581675329806448 -0.2029969224501652 0 +0.4611414890469437 -0.2126295185582414 0 +0.4723779350045254 -0.2062958578564474 0 +0.4637401268243885 -0.2230491231434322 0 +0.4870912281590365 -0.2088158014865232 0 +0.3476669432969351 -0.4001710097096327 0 +0.337341984580125 -0.390599688008326 0 +0.336629173219627 -0.4022905597429417 0 +0.3377803529212345 -0.3788403420308422 0 +0.3270170258633148 -0.3810283663070193 0 +0.3485436799791543 -0.3766523177546651 0 +0.3382187212623441 -0.3670809960533584 0 +0.3166920671465046 -0.3714570446057126 0 +0.316253698805395 -0.3832163905831965 0 +0.3386570896034536 -0.3553216500758746 0 +0.3278937625455339 -0.3575096743520517 0 +0.3175688038287237 -0.347938352650745 0 +0.3171304354876142 -0.3596976986282289 0 +0.3180071721698333 -0.3361790066732612 0 +0.3072438451119135 -0.3383670309494383 0 +0.3063671084296944 -0.361885722904406 0 +0.3494204166613735 -0.3531336257996975 0 +0.3390954579445632 -0.3435623040983908 0 +0.3395338262856728 -0.331802958120907 0 +0.328770499227753 -0.3339909823970841 0 +0.3502971533435926 -0.3296149338447299 0 +0.3399721946267824 -0.3200436121434232 0 +0.3184455405109428 -0.3244196606957774 0 +0.2960421497128842 -0.3523144012030993 0 +0.2956037813717746 -0.3640737471805831 0 +0.2964805180539937 -0.3405550552256155 0 +0.285717190996074 -0.3427430795017926 0 +0.2969188863951033 -0.3287957092481316 0 +0.2753922322792638 -0.3331717578004859 0 +0.2749538639381542 -0.3449311037779696 0 +0.4142281734205393 -0.3472196201800643 0 +0.4047194262600244 -0.3547841043985741 0 +0.4137898050794298 -0.3589789661575481 0 +0.3956490474406191 -0.3505892426396001 0 +0.3952106790995096 -0.3623485886170839 0 +0.3960874157817287 -0.3388298966621163 0 +0.3865786686212138 -0.346394380880626 0 +0.3857019319389947 -0.3699130728355937 0 +0.3947723107584 -0.3741079345945677 0 +0.3775082898018085 -0.342199519121652 0 +0.377069921460699 -0.3539588650991358 0 +0.3675611743001841 -0.3615233493176456 0 +0.3766315531195894 -0.3657182110766196 0 +0.3584907954807788 -0.3573284875586715 0 +0.3580524271396692 -0.3690878335361553 0 +0.3761931847784798 -0.3774775570541034 0 +0.3779466581429181 -0.3304401731441682 0 +0.3684379109824032 -0.338004657362678 0 +0.3593675321629979 -0.3338097956037039 0 +0.3589291638218883 -0.3455691415811877 0 +0.3598059005041074 -0.3220504496262201 0 +0.3666844376179649 -0.3850420412726132 0 +0.3757548164373702 -0.3892369030315872 0 +0.3576140587985596 -0.3808471795136391 0 +0.35717569045745 -0.3926065254911229 0 +0.3567373221163405 -0.4043658714686067 0 +-0.3482805673713905 -0.273952954915077 0 +-0.3358089501661903 -0.2774007892350273 0 +-0.3411382237648365 -0.2645348954539393 0 +-0.3229652193555215 -0.2806059662920711 0 +-0.3340682764845521 -0.2550830500100546 0 +-0.3098840645147833 -0.2836363290542623 0 +-0.3155045883043956 -0.2708074633769375 0 +-0.30824188993028 -0.2609892809369747 0 +-0.3213000574173156 -0.2581261776402712 0 +-0.2949232091361058 -0.2637062419809064 0 +-0.3012695262010309 -0.2510385938054264 0 +-0.3271801798643235 -0.2456150142218736 0 +-0.2965333238727891 -0.2865084467060589 0 +-0.2826894414582065 -0.289161406159393 0 +-0.2887782100452242 -0.2763822099505885 0 +-0.3204652565964128 -0.2360769891540348 0 +-0.3076164178942289 -0.2383908757644417 0 +-0.313966858902703 -0.2262458998296398 0 +0.2118254942247109 -0.02209966128809582 0 +0.2259916980652538 -0.02830624081135783 0 +0.2307304964469271 -0.04575486778999423 0 +0.2391152371477436 -0.03482525830862925 0 +0.223561339722742 -0.05799017770617114 0 +0.2356273023599698 -0.06250637279022074 0 +0.2521102421697861 -0.04173401392925015 0 +0.2406321015544548 -0.07867155866796521 0 +0.2479540461614649 -0.06777205322314589 0 +0.2610897874427472 -0.07418860985149596 0 +0.256323946376757 -0.05811323335020718 0 +0.2664479910444152 -0.09009105447146273 0 +0.2742967278044279 -0.08095095970143062 0 +0.2649757211038768 -0.04888084806289282 0 +0.2335142587506651 -0.09032280635283226 0 +0.2459332842143042 -0.09453710845239656 0 +0.2515155792308192 -0.1097808264000557 0 +0.2587327944780845 -0.09956098708701874 0 +0.2442390769769108 -0.1206447423137523 0 +0.2776246278016937 -0.05606592093145158 0 +0.2822203059298657 -0.0719667275235466 0 +0.2900954972207851 -0.06316369194369043 0 +0.2902790057072249 0.06368462034449313 0 +0.2825426775244343 0.07282237921363222 0 +0.2777105143630099 0.05660259753187586 0 +0.2748472056288201 0.08215454835729229 0 +0.2649152173295588 0.04938410135576915 0 +0.2671238274872565 0.09170053618595825 0 +0.2615398889989138 0.07529737159722767 0 +0.2483399469970798 0.06877737041210696 0 +0.2564741107601897 0.05884961376806445 0 +0.2411927565496036 0.08001795623449454 0 +0.2359164839531056 0.06346263145119441 0 +0.252030296902602 0.04222678579346982 0 +0.2597160438788432 0.1017836809377633 0 +0.2525080053009968 0.1123583640672446 0 +0.2468094962517049 0.09646394339061901 0 +0.2452614839615015 0.1234057074791369 0 +0.234362460206141 0.09215069407371219 0 +0.2385554469452554 0.03499846564051067 0 +0.2307570060152486 0.04637490540991911 0 +0.2255755333299316 0.02850250393902753 0 +0.2239210608223097 0.0588784312752552 0 +0.2118234893955047 0.02229459143843911 0 +0.2941825982805498 0.2109375973343093 0 +0.2909682877403105 0.1980573055188059 0 +0.306363704720451 0.2011965434884998 0 +0.2863930059269651 0.1865209578363218 0 +0.3167885982345815 0.1934697276517715 0 +0.2815307426036284 0.1745543014925606 0 +0.2975739447774688 0.177756609695532 0 +0.3077511597830752 0.1695140016556506 0 +0.3130563816085503 0.181237665823509 0 +0.3015329849477245 0.1578282620875669 0 +0.317178382851804 0.1613967888186374 0 +0.3278207607236395 0.1853954600751313 0 +0.2763486406056042 0.1631808010649749 0 +0.2705382987412944 0.1517204770075119 0 +0.2858437475407414 0.1546021478517654 0 +0.3380748585932599 0.1776408025442451 0 +0.3327515184719972 0.1653330478228212 0 +0.3485995777936062 0.1695881870599857 0 +0.3527857114461628 -0.1683028014780347 0 +0.3360933089280267 -0.1648592967216783 0 +0.3433292310796051 -0.177083373408807 0 +0.3198025993295933 -0.1613379217714585 0 +0.3340708526509786 -0.1861030724276413 0 +0.3036654444236153 -0.1579000718577401 0 +0.3110614548902931 -0.1705496343517317 0 +0.302082195049475 -0.1801706459448831 0 +0.3180955822459879 -0.1831030945257147 0 +0.2859154471130164 -0.1775128711903351 0 +0.2928990272042631 -0.1902647589101272 0 +0.3247944653020314 -0.195483165659531 0 +0.2876052981586344 -0.154631767684633 0 +0.2717767085390699 -0.1515792783553544 0 +0.2788212831561286 -0.1646804479933436 0 +0.3156064889660655 -0.2051487797246136 0 +0.2997745350111604 -0.2028648280294389 0 +0.306535690179613 -0.2152190695908632 0 +0.4303696400731636 0.3352407421352663 0 +0.4394905741081581 0.3294064903095627 0 +0.4389613832097587 0.3412590366886841 0 +0.4402808379693746 0.3177008612017259 0 +0.4483796750229075 0.3236338708913418 0 +0.4320646289248017 0.3115373501911028 0 +0.4411678275664304 0.3061309678690185 0 +0.4572154445211942 0.3178713454909918 0 +0.4563816307719374 0.3294212144527262 0 +0.4421171584822479 0.294644391407774 0 +0.4501258857986553 0.3005694123203945 0 +0.4589828347047942 0.2949057772454256 0 +0.458074360702399 0.3063487448885918 0 +0.4598106560156788 0.2834249034288827 0 +0.4677041844755375 0.2890757572558647 0 +0.4659919727929128 0.3120595318938886 0 +0.4340379727215319 0.2884252119874171 0 +0.4430738581727208 0.2831545448230341 0 +0.4440263007053372 0.2718650764685012 0 +0.4518572775675336 0.2776652899537851 0 +0.4364834470998156 0.265804583784522 0 +0.4449067557055159 0.2610284926655183 0 +0.4604872727191447 0.2719084966548082 0 +0.4747563930653457 0.3062323751104307 0 +0.4739799323815992 0.3178277135359631 0 +0.4755799490237669 0.2946992683670626 0 +0.4834593544874518 0.3003292350216871 0 +0.4763287069161481 0.2831009488253382 0 +0.4920256169073995 0.2942625540167451 0 +0.4914571758331254 0.3060959808018336 0 +0.1741537718249195 -0.1139122374927552 0 +0.1847967912871975 -0.1136051880855335 0 +0.1827395732090317 -0.0990605342848402 0 +0.1957980878685379 -0.1137281240260043 0 +0.1901867310266486 -0.08360649483345051 0 +0.2072152729536673 -0.1143284161822948 0 +0.2034537931899412 -0.09886106128800189 0 +0.2103923673251477 -0.08483479725545413 0 +0.1998524446733188 -0.08324982598267985 0 +0.2216655932792459 -0.08718139047971862 0 +0.2170339911875751 -0.07124432224586383 0 +0.1965250148813839 -0.06802793912849531 0 +0.2190221830684637 -0.1156499740876951 0 +0.2314020964138231 -0.117840421428466 0 +0.2264682258579228 -0.102832338187391 0 +0.201906341134839 -0.0524338207220803 0 +0.2122448365579801 -0.05467826240593476 0 +0.2067304663044399 -0.03700967700777193 0 +0.2067370094369858 0.0369977771682718 0 +0.2124653627224198 0.05501997192199627 0 +0.2018407273968786 0.05160898275884331 0 +0.2175686850461219 0.07228680000015703 0 +0.1967495339541088 0.06813514483171448 0 +0.2224807847387857 0.08885472911150996 0 +0.2112258485925788 0.08636784184256438 0 +0.2044128163643313 0.1006698611282108 0 +0.2006496458010809 0.08446397887036289 0 +0.2081170712370927 0.1165110529203891 0 +0.1965131234642064 0.1153488450649904 0 +0.1905194547709368 0.08417262077169771 0 +0.2274532521508241 0.1048656490869926 0 +0.2325501840907784 0.1204893391638822 0 +0.2201597446460647 0.11797745056448 0 +0.1830394519412846 0.09962136932970286 0 +0.1852675399755809 0.1146376793651214 0 +0.174387262724692 0.1144045467917195 0 +0.2689031333205896 -0.3228214216232775 0 +0.2583228278038043 -0.3123533993833414 0 +0.2621607680245485 -0.2992681630243119 0 +0.2476230903043987 -0.3014701954221151 0 +0.2763837513204479 -0.2974343040660891 0 +0.2665099788894588 -0.2863478121814933 0 +0.2367217839734881 -0.2901303431583227 0 +0.2714576646967176 -0.2735791059186105 0 +0.2563512513135991 -0.274924460140845 0 +0.245888853022677 -0.2631391661434332 0 +0.2410433952411315 -0.2765020323103502 0 +0.251763739404209 -0.2502841192128411 0 +0.2355893104470404 -0.2512639758028712 0 +0.2255835842065411 -0.2783611584594786 0 +0.2861305626793481 -0.2723925434934614 0 +0.2769110659361985 -0.2610421272659531 0 +0.2829916765367986 -0.2487552606184245 0 +0.2676290798929509 -0.2494395466201268 0 +0.2975663871552175 -0.2482601083244438 0 +0.2141855674117992 -0.2661635442517269 0 +0.2194132152696085 -0.2524713401046429 0 +0.203005083544262 -0.2539350225323729 0 +0.3376614352104124 0.4071504140993327 0 +0.3274198080439176 0.3975288656333167 0 +0.3155986836413884 0.3997339738476824 0 +0.3167230209678801 0.3879424673324025 0 +0.3122496564951687 0.411615610337319 0 +0.3037775592388593 0.4019390820620482 0 +0.3060262338918426 0.3783560690314883 0 +0.2919564348363302 0.404144190276414 0 +0.2930807721628218 0.392352683761134 0 +0.2823839850867843 0.3827662854602198 0 +0.2942051094893134 0.380561177245854 0 +0.2704564876805744 0.3853806046588006 0 +0.2715993354174227 0.373560825449645 0 +0.2953294468158051 0.368769670730574 0 +0.2891246064903001 0.4159669162972358 0 +0.279494144284665 0.4065051614749372 0 +0.2677462785180819 0.4089855936290654 0 +0.2691533103696413 0.3972974902819424 0 +0.2663364405613581 0.4203740876431231 0 +0.2563649258166214 0.4115210546946813 0 +0.2585238293018302 0.3882654709270229 0 +0.2846326597397676 0.3591832724296599 0 +0.2727320224073613 0.3617145763801169 0 +0.2739030128385591 0.349700431571576 0 +0.260760965234242 0.3645765256062014 0 +0.2631731008970524 0.3402405629579904 0 +0.1840590633727104 0.254052786137048 0 +0.1821401054267222 0.2716153623943658 0 +0.193955205182591 0.2819985772341188 0 +0.1806715424840741 0.2889717455464432 0 +0.2073073399372983 0.2761577771714297 0 +0.205564869230672 0.2926112407672267 0 +0.1794844706653313 0.3061026802465517 0 +0.2170958278311754 0.3031958985230025 0 +0.2041367149153326 0.3096300024320764 0 +0.2028568351658348 0.3265502186602219 0 +0.1912574698730825 0.3163067544081691 0 +0.2144328816200635 0.3362019509227109 0 +0.2017178163687304 0.342527947971057 0 +0.1784621821893283 0.3230024491489007 0 +0.2303866717061446 0.2977757778320916 0 +0.2287317367141521 0.3138226196022016 0 +0.2401470389728639 0.3238818367695725 0 +0.2272648594496903 0.3298818999018353 0 +0.2534371941195043 0.3187927238387981 0 +0.2517917808271721 0.3324692007690633 0 +0.2260563603259091 0.3445234924206582 0 +0.1775199211419634 0.3396458957315595 0 +0.1891605762919953 0.3491389072989973 0 +0.1766417164637277 0.356233999133202 0 +0.2007172719300018 0.3579281799906823 0 +0.1758083607191756 0.3732446848319756 0 +-0.3944909073150292 0.4902130442712342 0 +-0.3819909073150292 0.4902130442712342 0 +-0.3764818146300584 0.4804260885424684 0 +-0.3694909073150292 0.4902130442712342 0 +-0.3831762314208043 0.4707181487491017 0 +-0.3708731646884054 0.4706656885131238 0 +-0.3569909073150292 0.4902130442712342 0 +-0.3653306512761859 0.4608876901523198 0 +-0.3584339660716519 0.47064947760817 0 +-0.3459727219450874 0.4706391328137026 0 +-0.3514818146300583 0.4804260885424684 0 +-0.3404636292601166 0.4608521770849369 0 +-0.3334727219450875 0.4706391328137026 0 +-0.3444909073150292 0.4902130442712342 0 +-0.372154407375039 0.4511453350249822 0 +-0.3598350451468386 0.4510972309756724 0 +-0.3544454438901749 0.4412782656274054 0 +-0.3474545365751457 0.4510652213561711 0 +-0.3611883576082149 0.4315540914457682 0 +-0.3497540040815886 0.4310964764532171 0 +-0.3358448617433195 0.4502563876241533 0 +-0.3319909073150292 0.4902130442712342 0 +-0.3264818146300583 0.4804260885424684 0 +-0.3194909073150292 0.4902130442712342 0 +-0.3215410689380552 0.4700335791505182 0 +-0.3071073445060254 0.4900859084563898 0 +-0.4915600212071735 -0.392316280812179 0 +-0.4915171198246553 -0.3803667011947981 0 +-0.4830342396493106 -0.3732334023895962 0 +-0.4915339502325685 -0.3678813722198344 0 +-0.4745666635458906 -0.3783731765870176 0 +-0.4745706017485309 -0.3661168162644149 0 +-0.4915721509920146 -0.3554146660028147 0 +-0.4661777524178566 -0.3590644188575794 0 +-0.4747102525471928 -0.3537398790356809 0 +-0.4748981242489749 -0.3414037688127652 0 +-0.4832065714444247 -0.3483840341681332 0 +-0.4666763226453881 -0.3345015694380776 0 +-0.4751603213339878 -0.3291317430861804 0 +-0.4916385909204478 -0.3429725841505321 0 +-0.4575855991232766 -0.3643335059739904 0 +-0.4577961749436487 -0.3520308075098804 0 +-0.4494360817635936 -0.3450629510475843 0 +-0.4581257665062088 -0.3398267197332079 0 +-0.4404958279502609 -0.3501951688365036 0 +-0.4411513139593954 -0.3382193263231343 0 +-0.4585326782536461 -0.3276683165135168 0 +-0.4917251424056902 -0.3305478254281239 0 +-0.4835635503356624 -0.323694387706481 0 +-0.4918365024139746 -0.318144683622441 0 +-0.4754912170021214 -0.3169185650600759 0 +-0.4920752464164554 -0.305852254306082 0 +0.3929753610357989 -0.489321628905828 0 +0.3808554547168801 -0.489028419640422 0 +0.3742448030423456 -0.4780306904347409 0 +0.3683897262761015 -0.4890019773839793 0 +0.3798847342185215 -0.4672254100934682 0 +0.3676459952606806 -0.4670238214199622 0 +0.3558995699045373 -0.4889943823205145 0 +0.3610475138760905 -0.4560167002430352 0 +0.3551901693443565 -0.4669897368213329 0 +0.3427107156084346 -0.4669738833826687 0 +0.3493000069081317 -0.4779880956589397 0 +0.3361925970832897 -0.4563861008435163 0 +0.3303190235865742 -0.4674793218281573 0 +0.3434017154035173 -0.4889927268936872 0 +0.3666913997215013 -0.4452083681004194 0 +0.3544576891022682 -0.4450613279277043 0 +0.3477780338170684 -0.4343149168350275 0 +0.3420114172362321 -0.4453412587382413 0 +0.3531195175226114 -0.4237976431424385 0 +0.340677983475418 -0.423805777283679 0 +0.3295850352774172 -0.4459707828423744 0 +0.3309035718694783 -0.4889912944608896 0 +0.3244223268703845 -0.4784446756693977 0 +0.3184687135307928 -0.4892223342693461 0 +0.3179618363951737 -0.4680871246707714 0 +0.3060877786713452 -0.4896553865700999 0 +-0.2202635964198915 -0.4881570568624359 0 +-0.2271748452018822 -0.4771976970047958 0 +-0.2321618367533122 -0.4888617177596302 0 +-0.23350186589383 -0.46701684967129 0 +-0.244210004910343 -0.489463255103778 0 +-0.2399081414257559 -0.4567694541901751 0 +-0.24542061349039 -0.4679058767876899 0 +-0.2577186245898713 -0.4681917537404874 0 +-0.2510503052848983 -0.4787171580883434 0 +-0.2643882302039541 -0.457605374747845 0 +-0.2701386523208532 -0.4682420851582834 0 +-0.256710004910343 -0.489463255103778 0 +-0.2462776227724983 -0.446755259040603 0 +-0.2529124923233902 -0.4363282245658245 0 +-0.2586363916781307 -0.446961763123925 0 +-0.259256906518259 -0.4252152081530562 0 +-0.2709767304455992 -0.4468803183024335 0 +-0.269210004910343 -0.489463255103778 0 +-0.2759066486467153 -0.4788297935520752 0 +-0.281710004910343 -0.489463255103778 0 +-0.2825860349886475 -0.4681712147256872 0 +-0.294165143352545 -0.4892493803334185 0 +0.2627402786922164 -0.3464313586142052 0 +0.2731252507854862 -0.355701992771294 0 +0.2712966376328182 -0.3664728817646183 0 +0.2834502095022964 -0.3652733144726007 0 +0.2590447108102475 -0.368363300806451 0 +0.2694680244801502 -0.3772437707579426 0 +0.2937751682191065 -0.3748446361739074 0 +0.2676014863646061 -0.3881616607722203 0 +0.2797171560212459 -0.3868497242784963 0 +0.2897974571676739 -0.3964187083385096 0 +0.2918094886708699 -0.3856331829747917 0 +0.2871249987661136 -0.4072563402771078 0 +0.2988380230948975 -0.4060977504185611 0 +0.3039414713849763 -0.3844321141174108 0 +0.2554093957989946 -0.3904838138829336 0 +0.2655603235028982 -0.3992139887770115 0 +0.2630454776071189 -0.4101607926481714 0 +0.2747730727649147 -0.4086244401059702 0 +0.2518610730718759 -0.4119501702555692 0 +0.2604919433985208 -0.4206140323195523 0 +0.2832509822512292 -0.4181801420627236 0 +0.3139010532327363 -0.394042624394404 0 +0.3109252446020039 -0.4049288892129739 0 +0.3236014671534638 -0.4036957311544001 0 +0.3072822019078697 -0.415857058786332 0 +0.3326226555918063 -0.413493006951611 0 +-0.2954084183327771 0.4892111768121421 0 +-0.2832703915776582 0.4888155493761874 0 +-0.2792494839560857 0.4774030680277208 0 +-0.2709058500271589 0.4886667685128206 0 +-0.2875584950384137 0.4661804903434006 0 +-0.275293381955829 0.4659185667576392 0 +-0.2584801319826843 0.4885844878161036 0 +-0.2713859624968173 0.4543740262904195 0 +-0.2629652519274912 0.4657210149274716 0 +-0.2505797730426329 0.4655926035453274 0 +-0.2545085676669058 0.4771137948291245 0 +-0.2466779435912916 0.4540392823599371 0 +-0.2381481769844515 0.4655253134532868 0 +-0.2460191553268903 0.4885413951679845 0 +-0.279775412105042 0.443087736833891 0 +-0.2675469034510264 0.4427312375263018 0 +-0.2637302126175719 0.4309716348102117 0 +-0.2551910050915601 0.4425124959800653 0 +-0.272058010324739 0.4200865678484045 0 +-0.2335444072207949 0.4885147093226042 0 +-0.2296073862635918 0.4770103621416358 0 +-0.2210568975488723 0.4885023221959313 0 +0.4878320963010577 -0.2204255472606795 0 +0.4764619952055227 -0.2275416232088349 0 +0.4884847247275486 -0.2323538582923396 0 +0.4660456555409667 -0.2339530020068084 0 +0.4889370944656452 -0.2445092425825446 0 +0.4556596470733796 -0.2403798091766514 0 +0.4672466167210158 -0.2456771489136361 0 +0.4676206570219939 -0.2579452425053921 0 +0.4782327612091238 -0.2512552878846661 0 +0.4569950012557569 -0.264667758928118 0 +0.4676990542954178 -0.2703888764268154 0 +0.4892330180984726 -0.2567962921422718 0 +0.4456417415845154 -0.2467029688280138 0 +0.4353981085908357 -0.2532777528536307 0 +0.4461650904923631 -0.2589814607113589 0 +0.4251087374046731 -0.2601035160942953 0 +0.4470756770853163 -0.2712989771973713 0 +0.4892330180984726 -0.2692962921422718 0 +0.4784660361969452 -0.2760925842845435 0 +0.4892330180984726 -0.2817962921422718 0 +0.4685006227483948 -0.2826596363404292 0 +0.4894542960603568 -0.2942269644587467 0 +-0.4893688915029892 0.4893420449417514 0 +-0.4786593303284503 0.4911008117442287 0 +-0.4698186606569006 0.4822016234884574 0 +-0.4661593303284504 0.4911008117442287 0 +-0.4708572057450607 0.4707450479670668 0 +-0.460977990985351 0.473302435232686 0 +-0.4536593303284503 0.4911008117442287 0 +-0.4509576922589167 0.4630615394122593 0 +-0.448477990985351 0.473302435232686 0 +-0.4362851188679671 0.4730193985404101 0 +-0.4448186606569007 0.4822016234884574 0 +-0.4280742282735129 0.4637894283449407 0 +-0.4245363254378499 0.4728990739778181 0 +-0.4411593303284503 0.4911008117442287 0 +-0.4526196868765076 0.4523735996071658 0 +-0.4420711959990992 0.4537681661492332 0 +-0.4339932420507034 0.4451219354399485 0 +-0.4312976955931407 0.4544671201282761 0 +-0.4361586090886767 0.4357818485889345 0 +-0.4265843118588482 0.4371021237985748 0 +-0.4205141514073412 0.4546472315780588 0 +-0.4289429907058636 0.4910142240527991 0 +-0.4207811168982388 0.4819115665041763 0 +-0.4167992631609074 0.4909221749233219 0 +-0.4129726842447272 0.4727441491968551 0 +-0.404753305876627 0.4908086334351232 0 +-0.4905625319580021 0.4044258707098937 0 +-0.4910290221068591 0.41626948400935 0 +-0.4819959219815559 0.420034566393601 0 +-0.4909933309330453 0.4286658212282548 0 +-0.4725494020379354 0.4120426542988563 0 +-0.4729083149089315 0.4237269713866961 0 +-0.4909186632426222 0.4409964212600344 0 +-0.4636797751681395 0.427286471844838 0 +-0.4726869835661226 0.4356283343434829 0 +-0.472277110636643 0.447406793409144 0 +-0.4816619986982951 0.4443074617669038 0 +-0.4626333199528628 0.4501677278657148 0 +-0.4716324501973607 0.4590992751928789 0 +-0.4907425623105954 0.4532735410996065 0 +-0.4545595473988897 0.4194807141298593 0 +-0.4544456638641264 0.4305865424491446 0 +-0.4452624732726145 0.433439004307074 0 +-0.453736646641646 0.4415142557980472 0 +-0.4371656446317017 0.4261485005854833 0 +-0.4904957023210865 0.4654833257468952 0 +-0.4807291481898205 0.4681841964276054 0 +-0.4900891942553072 0.4775634797221563 0 +0.4914600787444403 0.3928322180462649 0 +0.4912763261543875 0.3808842428905452 0 +0.4825526523087749 0.3742684857810905 0 +0.4912763261543874 0.3683842428905453 0 +0.4739187872418666 0.3798749865084272 0 +0.4738289784631625 0.3676527286716358 0 +0.4912763261543874 0.3558842428905453 0 +0.46510530461755 0.361036971562181 0 +0.4738289784631625 0.3551527286716357 0 +0.4738289784631625 0.3426527286716357 0 +0.482552652308775 0.3492684857810905 0 +0.46510530461755 0.3360369715621809 0 +0.4738718476733801 0.3302023018168628 0 +0.4912763261543874 0.3433842428905453 0 +0.4563816307719374 0.3669212144527262 0 +0.4563816307719374 0.3544212144527262 0 +0.4476579569263249 0.3478054573432715 0 +0.4563816307719374 0.3419212144527262 0 +0.4389342830807125 0.3536897002338167 0 +0.491287838955576 0.3308976804775191 0 +0.4826195783096595 0.3243463477596313 0 +0.4913453591697472 0.3184648941120888 0 +0.3865039473860403 0.1951221358146959 0 +0.3944430261829762 0.2065513609666866 0 +0.3827991888011387 0.2100030017989918 0 +0.4017403783316473 0.2176917322226787 0 +0.3794411718836688 0.2252322918365496 0 +0.408692595488485 0.2285959978041841 0 +0.3978448274947541 0.2314040225238973 0 +0.3944043959765975 0.2458553900349083 0 +0.3871274645564345 0.2354371554765509 0 +0.4017248302957275 0.2563606179432002 0 +0.3913989948690421 0.2606625480407487 0 +0.3763643396932435 0.2403864648606185 0 +0.4161205153987087 0.24056780187954 0 +0.4230465034558757 0.2505249756793367 0 +0.4125264635323239 0.2528961690402307 0 +0.4296247806848637 0.2588815075792849 0 +0.409776443377325 0.2658281416170023 0 +0.3734802448952675 0.2554168038760627 0 +0.3810869719250428 0.2653484229641252 0 +0.3708613645165268 0.2700951327790322 0 +0.3890733891215181 0.2747654284172791 0 +0.3684121416395577 0.2846147395019891 0 +0.4901126274464973 -0.4901126274464973 0 +0.478176921915602 -0.4906769219156019 0 +0.4680193096507986 -0.4805171143262144 0 +0.4656875830688041 -0.4906872816248724 0 +0.4697122521521 -0.4697385758340576 0 +0.4585146220688772 -0.4710013934176805 0 +0.4532077586697054 -0.4906863232487478 0 +0.4494795236117423 -0.4618958143167118 0 +0.4468694527732593 -0.4717444370530697 0 +0.4350013267155107 -0.4721513009412147 0 +0.4439768820946046 -0.4813348962563532 0 +0.4263228253838604 -0.4629627493324595 0 +0.4230499053065451 -0.4721978201466118 0 +0.4408249168678224 -0.4907286453519575 0 +0.4517413532446968 -0.4518133269749168 0 +0.4406960045625493 -0.4529287517901568 0 +0.4323368482114905 -0.4441462349291729 0 +0.4294611051413846 -0.4534864768238632 0 +0.4348113303187396 -0.4348842775640882 0 +0.4246687773594075 -0.4355644178952217 0 +0.4181112859980569 -0.4535252939238715 0 +0.4284703916077254 -0.4907589099165446 0 +0.4196334703203229 -0.4814622976732154 0 +0.416136699415727 -0.4907001948657307 0 +0.4110915720767309 -0.4720423895448364 0 +0.4038179240502308 -0.4905991098162079 0 +0.4907198075808215 -0.404572946831804 0 +0.4908670494812391 -0.4161747135519156 0 +0.4816892921032246 -0.4199409747015092 0 +0.4908370547222375 -0.4285806199488662 0 +0.4723739322977873 -0.412225824364423 0 +0.4724645514739114 -0.4235772913683289 0 +0.4907688045302582 -0.4408999136934645 0 +0.4631077450962169 -0.4269840197019273 0 +0.4722293283734835 -0.4353659061809179 0 +0.4717624228332372 -0.4470621232455226 0 +0.4813456723324256 -0.4441025408075628 0 +0.4618263409848047 -0.4495978825985714 0 +0.4708794582827852 -0.4585065164986447 0 +0.4905924810792927 -0.4531749561835575 0 +0.4539897788015361 -0.419082345227702 0 +0.4536732218436249 -0.4300721362411942 0 +0.4442297738463661 -0.4327268141193367 0 +0.4528933158301396 -0.4409690793403265 0 +0.4358501745697529 -0.4249143331466146 0 +0.4903532033792962 -0.4653875405766632 0 +0.4802584351902902 -0.4678070263128417 0 +0.490119609736255 -0.4776261010498494 0 +-0.4919762884866838 0.2941819321571582 0 +-0.4827664425551751 0.3003801200746335 0 +-0.474655693861174 0.2944819871658801 0 +-0.4730358278842357 0.3063183105812637 0 +-0.4765174613760989 0.2827769189054493 0 +-0.4667016419246828 0.2885964346185717 0 +-0.4629863778049749 0.3120497134748844 0 +-0.4588683310968672 0.282654097201483 0 +-0.4566784042905823 0.2941494627924972 0 +-0.4465067864284825 0.2994347271424307 0 +-0.4546295997329711 0.3057697978598515 0 +-0.4386727738207772 0.2928543715695048 0 +-0.4362226668663203 0.3043541331787415 0 +-0.4527566893091234 0.3175557723053256 0 +-0.4610879486108925 0.27136942188049 0 +-0.4511745429752899 0.2766016001398082 0 +-0.4436898414377952 0.2704025416861302 0 +-0.4412207005244277 0.2813495012208032 0 +-0.4460902147824995 0.2600735874352613 0 +-0.4364340778690203 0.2640564071538553 0 +-0.4311334140272221 0.2857553316918934 0 +-0.4422949739565673 0.3228826713706753 0 +-0.4338350433739891 0.3160483406010147 0 +-0.4314928172773648 0.3280244470028709 0 +-0.4257990192598164 0.3089482860234543 0 +-0.4203018720737295 0.3329148409366665 0 +-0.276569607753221 0.4004371792099587 0 +-0.2753792184277262 0.3865663255221428 0 +-0.2835323235890244 0.378961659300126 0 +-0.2743010788712058 0.3726025607367597 0 +-0.2926976669458085 0.3853709418360222 0 +-0.2916395814379037 0.3713945810574646 0 +-0.2732726810810485 0.3586028518961158 0 +-0.2997242016579578 0.3638454047275009 0 +-0.2906004134817318 0.3574034241245406 0 +-0.2895705286786746 0.3434051777296864 0 +-0.2814370348601449 0.3509922745418362 0 +-0.2976656448361197 0.3358478101484117 0 +-0.2885518011906067 0.329398226477371 0 +-0.2722414279043117 0.3446065018206523 0 +-0.308825726138396 0.3703047044620856 0 +-0.3077950151988236 0.3563070046289529 0 +-0.3158621830424569 0.3487714404466268 0 +-0.3067663508924721 0.3423077162293373 0 +-0.3249537853309836 0.3552384670881491 0 +-0.3239262126387507 0.3412383217596585 0 +-0.3057430080616972 0.3283042684766139 0 +-0.2712654943054889 0.3305672849365174 0 +-0.2794296465754681 0.3229553233369587 0 +-0.2703037390206036 0.3165155812598291 0 +-0.2875598034846438 0.3153702151935692 0 +-0.2693765989075904 0.3024361619105246 0 +-0.4331003442096833 -0.3312882841266268 0 +-0.4420357674905634 -0.326244129473419 0 +-0.4431777340097727 -0.3145974032786051 0 +-0.450466526700678 -0.3208868233638952 0 +-0.4358334600947609 -0.3082375170773198 0 +-0.4444305280061112 -0.3030865615748583 0 +-0.4589494068760227 -0.3155201221690971 0 +-0.4457046922197637 -0.2916076287815633 0 +-0.4530004537874275 -0.2978998705383957 0 +-0.4615400581388769 -0.2926758275673599 0 +-0.4602308640672402 -0.3040620526933958 0 +-0.4628338065269027 -0.2813173440475145 0 +-0.4700030345075342 -0.2873533342675936 0 +-0.4674322870513674 -0.310153420974299 0 +-0.4383023057286624 -0.2850827511470429 0 +-0.4470166482335003 -0.2801871934353444 0 +-0.4484329191298542 -0.269000075691295 0 +-0.4556276443826063 -0.2751966446601227 0 +-0.4412672789767287 -0.2625858250193476 0 +-0.4499804678532721 -0.2582390867916741 0 +-0.4641503727642134 -0.2700414444549945 0 +-0.4759888417552353 -0.3048490986167604 0 +-0.4771885188378368 -0.2933827027627134 0 +-0.4844570597640712 -0.2994708028228267 0 +-0.478405601210967 -0.2819521007927374 0 +-0.4926712473674149 -0.2938708217743529 0 +0.2944210905377813 0.4903188776793489 0 +0.3008377152573899 0.4790350224604492 0 +0.2951466798675183 0.4690471165541804 0 +0.3071311042602519 0.4674199803626662 0 +0.2832052424868234 0.4707683310243004 0 +0.2894353208082718 0.4591022096311069 0 +0.3133568537822784 0.4556083522429138 0 +0.2836794573473604 0.4492200847635895 0 +0.2955503546170279 0.4473520076906277 0 +0.3014898677898197 0.4355146773799257 0 +0.3074901655336571 0.4454913408833505 0 +0.2953253566223791 0.4256789674200508 0 +0.3073000856892336 0.4235600534275011 0 +0.3195292045521417 0.4436549005391246 0 +0.2719227749816595 0.4510326759158373 0 +0.2778871358187125 0.439445383613166 0 +0.2721055853856771 0.4298188879386705 0 +0.283624054467199 0.4277622935141084 0 +0.2607730695016811 0.4318238808677194 0 +0.3256558903929568 0.4315600338946788 0 +0.3194547828010427 0.421449649060341 0 +0.3317310941162662 0.4193597229062694 0 +-0.3004254017850097 -0.4765686808512109 0 +-0.2946354066953527 -0.4660319359549888 0 +-0.3066407986596765 -0.4636741065986439 0 +-0.2888454116056957 -0.4554951910587668 0 +-0.3128561955343434 -0.4507795323460768 0 +-0.2830207109724396 -0.4448628130839192 0 +-0.2950372010712762 -0.442577972292441 0 +-0.3011787560321931 -0.4296800543541056 0 +-0.3070662004446864 -0.4402427874498548 0 +-0.2951066992350033 -0.4190913970346408 0 +-0.3071206226486199 -0.4167359070993872 0 +-0.3190715924090101 -0.4378849580935098 0 +-0.2771653710932066 -0.4342654410442432 0 +-0.2713113717421534 -0.4237751319425639 0 +-0.2831636740810182 -0.4214437416003614 0 +-0.3253014515076916 -0.425079002949602 0 +-0.3192160661584337 -0.4144240394730953 0 +-0.3313137044173822 -0.4121521854337796 0 +0.18872913907757 -0.3803065837001273 0 +0.2003028635213437 -0.3863614792335347 0 +0.2001130019395262 -0.3731939276075091 0 +0.2104274650884209 -0.3918357849222542 0 +0.211982355782095 -0.3668342728828204 0 +0.2211587322764237 -0.3969766786712126 0 +0.221952871861133 -0.3852716628138289 0 +0.2340347542050574 -0.3790697780188299 0 +0.2230921079332691 -0.3732369448373252 0 +0.244786766175204 -0.3845967697723549 0 +0.2464666370922685 -0.3734312645522576 0 +0.2245061572504492 -0.3606212838459038 0 +0.2316778870034603 -0.401899796595043 0 +0.2421017054295479 -0.4069867211186764 0 +0.243363781371015 -0.3959697129175799 0 +0.2372222554220627 -0.3550237439977376 0 +0.2481986518229014 -0.3619447182048408 0 +0.2500301559756811 -0.3502318846254072 0 +0.199660672005008 0.2514902593308808 0 +0.2146558916041643 0.2494694887421815 0 +0.2106916364669164 0.2626759241463781 0 +0.2297097822101794 0.2476097880316153 0 +0.2219316506677043 0.2736693494732521 0 +0.2451828369503586 0.2455516908463569 0 +0.2402399984331658 0.2583304244317863 0 +0.2507010038675737 0.2687760323262854 0 +0.2361939525697934 0.2712594693492644 0 +0.2644826186562578 0.2659122298453231 0 +0.2611377640563911 0.2787827110507442 0 +0.2332120631771513 0.2844350107428408 0 +0.2595143577663223 0.243457633949235 0 +0.2727857820354252 0.2412455233172272 0 +0.2683666570272571 0.2532889672956005 0 +0.2444933239351269 0.2949504227554456 0 +0.2582187261900146 0.2918691941728696 0 +0.2556099049672432 0.3051003725240869 0 +-0.1518793865573576 0.354534363235119 0 +-0.1513452289998951 0.3402195385007328 0 +-0.1405424332499119 0.3453355041802221 0 +-0.1507126759678389 0.3264536787414042 0 +-0.1283928224593976 0.3355719233220865 0 +-0.150160145528311 0.313095319361246 0 +-0.1385276746983316 0.318206997245606 0 +-0.1259891939230294 0.3101612843281161 0 +-0.1270417732220565 0.3227571291908915 0 +-0.1253707082278919 0.2971303100963996 0 +-0.1131414384844314 0.3020807316731695 0 +-0.115729151510999 0.3265254012962574 0 +-0.1497687629153554 0.2999700223069504 0 +-0.1496381287053062 0.2869310166530782 0 +-0.1375055854909161 0.2921577079780873 0 +-0.1026840611632842 0.3181520073574369 0 +-0.1013904027491992 0.3062124935670627 0 +-0.08948777272114378 0.3103301157276734 0 +-0.08989156771667164 -0.3105737760517702 0 +-0.10220131774111 -0.3068913654760668 0 +-0.1031504039552628 -0.3182681502755821 0 +-0.1142354392243601 -0.3034551009336549 0 +-0.116122547385493 -0.3268665825933269 0 +-0.1267123539447245 -0.2992429185655778 0 +-0.1270597315171961 -0.3117435215965769 0 +-0.1395437628494041 -0.3203100890942359 0 +-0.1277639806924296 -0.3238816108767554 0 +-0.1515735439511862 -0.3160127364908993 0 +-0.1517392016308969 -0.3287937578631804 0 +-0.1286953047022952 -0.3361083260488056 0 +-0.139244128864281 -0.2949186578768299 0 +-0.1519619455806202 -0.2901580792485513 0 +-0.1516358960667398 -0.3031451934664183 0 +-0.1407031754259631 -0.345551630044396 0 +-0.151962893754246 -0.3420168325148004 0 +-0.1521179697165642 -0.3552806168509782 0 +-0.3103182737006279 0.2212335520128748 0 +-0.3165166585830034 0.2112161226354343 0 +-0.3040839103668663 0.2321324189474404 0 +-0.3163622169083481 0.230223612726856 0 +-0.2978622371414946 0.2435015484328987 0 +-0.3225076694633569 0.2390446599846663 0 +-0.3284350390915975 0.2277697003802825 0 +-0.2916478819075794 0.2551550396380462 0 +-0.3040878121327814 0.25296767874914 0 +-0.3103700444286889 0.2621213097779372 0 +-0.3164512925074111 0.2504707047852799 0 +-0.3043375241520232 0.273843981845849 0 +-0.3167686719561937 0.2708800319674 0 +-0.3287271260756 0.2477268146304072 0 +-0.2854422259299143 0.267139281705277 0 +-0.2792327684732488 0.2796312539869997 0 +-0.2918373780553768 0.2767063091910629 0 +-0.2730005293973654 0.2925013939275717 0 +-0.298371471306151 0.2857395208815 0 +-0.3350190212621789 0.2563552622668429 0 +-0.3409052709613965 0.2447461185933338 0 +-0.329028471614963 0.2680479388837227 0 +-0.3413791183753238 0.2649223379761775 0 +-0.3233047131388851 0.2793428213883606 0 +-0.3481447736422617 0.2729586414927385 0 +-0.3536834572486455 0.2617730067933042 0 +-0.326572935661496 0.2075200167448158 0 +-0.3330841200929107 0.2158637122329622 0 +-0.3371558218703656 0.2038361752248966 0 +-0.3395130514013898 0.224292910566373 0 +-0.3480765498100832 0.2002341261674564 0 +-0.3459103001878731 0.2327875848638879 0 +-0.3506577505285997 0.2207845164134241 0 +-0.3618349826851837 0.2173422723440485 0 +-0.3550860244430341 0.2087647772487246 0 +-0.368238839618452 0.2260420614244236 0 +-0.3729599614248537 0.2140876436669458 0 +-0.3592497995877098 0.1967015760812382 0 +-0.3522586515216587 0.2413374299534632 0 +-0.3584795140086069 0.2499860570376835 0 +-0.3633399986308907 0.2380734900299498 0 +-0.3642361961200887 0.2590189699299549 0 +-0.3738033335082683 0.2354002136580942 0 +-0.3709662508226138 0.1931358583032623 0 +-0.3776882054982845 0.2020362998123303 0 +-0.38244179371753 0.1897721209975198 0 +-0.3836103545350067 0.2115061083302258 0 +-0.3941161069634712 0.1867312674105785 0 +0.2937500000000001 -0.4901908617529072 0 +0.28125 -0.4901908617529072 0 +0.2749958471031226 -0.480365994011023 0 +0.26875 -0.4901908617529072 0 +0.2811524558361778 -0.4702752792483886 0 +0.2687237201680977 -0.4703280475662043 0 +0.25625 -0.4901908617529072 0 +0.262498558714474 -0.4602302893054773 0 +0.2563880745451885 -0.470161389596505 0 +0.2442699942389881 -0.4696319072806396 0 +0.2501774490695342 -0.4800675350285136 0 +0.2383680483817339 -0.4591036501833358 0 +0.2326297184086316 -0.4683390422700219 0 +0.24375 -0.4901908617529072 0 +0.2684114202663624 -0.4500195220206294 0 +0.2563060771212984 -0.4500378744046636 0 +0.2502501506648481 -0.4399484544940076 0 +0.2443993970477272 -0.4493580692334543 0 +0.255980823937866 -0.4306697834283955 0 +0.2318045224433546 -0.4894177164464831 0 +0.2266302555831547 -0.4780410739474062 0 +0.219971600836013 -0.4886145070941668 0 +0.4797703755965728 -0.3006840033691927 0 +0.4692790426519121 -0.2949180803879916 0 +0.4701015720764194 -0.3071363483081516 0 +0.4586721193780264 -0.2892167762731768 0 +0.4604196236130221 -0.3136099099012318 0 +0.4480029371265464 -0.2835719431732228 0 +0.4489434897213556 -0.295787768084643 0 +0.4393417862168115 -0.3024000389698073 0 +0.4499053501090088 -0.3079604780393242 0 +0.4291791765176878 -0.297219612855701 0 +0.4303158855775702 -0.3094060912530643 0 +0.4508084002311147 -0.3200865082749715 0 +0.4372736258888928 -0.2779785920565407 0 +0.4263736757250708 -0.2723809019761108 0 +0.4277953145606617 -0.2848270852480568 0 +0.4158781785085653 -0.2671199795587447 0 +0.4191308825133222 -0.2921822237281748 0 +0.4413304240823561 -0.326664600911613 0 +0.4313182533432034 -0.3214899532499623 0 +0.4321512013675439 -0.3334363221203927 0 +0.4214812792487222 -0.3165505719270796 0 +0.4230985096008233 -0.3402413698048397 0 +-0.4041570001570614 -0.4911245126240197 0 +-0.4166570001570613 -0.4911245126240197 0 +-0.4208140003141227 -0.4822490252480395 0 +-0.4291570001570614 -0.4911245126240197 0 +-0.4126496216695757 -0.4730358957081532 0 +-0.424971000471184 -0.4733735378720593 0 +-0.4416570001570613 -0.4911245126240197 0 +-0.4291280006282454 -0.464498050496079 0 +-0.437471000471184 -0.4733735378720593 0 +-0.449971000471184 -0.4733735378720593 0 +-0.4458140003141227 -0.4822490252480395 0 +-0.4541280006282454 -0.464498050496079 0 +-0.4624710004711841 -0.4733735378720593 0 +-0.4541570001570614 -0.4911245126240197 0 +-0.4213409224269379 -0.4547183147871325 0 +-0.4332850007853067 -0.4556225631200987 0 +-0.4367561977993644 -0.4457517216175059 0 +-0.4457850007853067 -0.4556225631200987 0 +-0.4294105876764027 -0.4371468031272163 0 +-0.439353959367167 -0.4359577087543186 0 +-0.4552617943622291 -0.45310530211924 0 +-0.4666570001570614 -0.4911245126240197 0 +-0.4708140003141227 -0.4822490252480395 0 +-0.4791570001570613 -0.4911245126240197 0 +-0.4720402379228737 -0.4708375353433992 0 +-0.4896339312713764 -0.4893615121034429 0 +-0.4904767412120903 -0.4776003503917475 0 +-0.4815683409872125 -0.4682542561693283 0 +-0.4909205058945146 -0.4655061610725451 0 +-0.4729826609068975 -0.4592235644043299 0 +-0.4912076912537878 -0.4532959805340163 0 +-0.4645617876707769 -0.4504179049867175 0 +-0.473683907561317 -0.4474986268422386 0 +-0.4741891544712065 -0.4357290778524753 0 +-0.4826088247129335 -0.4443759312886089 0 +-0.465817134352179 -0.4274551113252982 0 +-0.4744720977968547 -0.4239874408970662 0 +-0.491378676506876 -0.4410403558002877 0 +-0.4562784270571033 -0.4418397711978901 0 +-0.4483086301075587 -0.4336202490513634 0 +-0.4570584439016779 -0.430724099185184 0 +-0.4407579946837411 -0.4261395452244405 0 +-0.4574861848344631 -0.4196247577599502 0 +-0.4914964762353234 -0.4287738485713886 0 +-0.4830505514912533 -0.4203522682521557 0 +-0.4915471829365966 -0.4165472530437127 0 +-0.4746228022045691 -0.412296140548442 0 +-0.4915768242232725 -0.4043465299788066 0 +0.2200529895671351 0.4893839563781813 0 +0.2271853375177076 0.479145600330588 0 +0.232260707574869 0.4897286400752741 0 +0.2340501471833277 0.4693058198928675 0 +0.2445869424935854 0.4899863733791744 0 +0.2407504703894755 0.4598170590425095 0 +0.2461417221298801 0.4700263569819328 0 +0.2584005779868004 0.470430156927696 0 +0.2515458722776219 0.4801346376277121 0 +0.265196980281157 0.4607342115779667 0 +0.2707691896423331 0.4706225541818897 0 +0.2569958067551223 0.4901268927026702 0 +0.2474811696782022 0.4503296393487462 0 +0.2541560893532054 0.4410250111412546 0 +0.2596629158345861 0.4508219233437112 0 +0.2694443001033147 0.4902199548555614 0 +0.2763480463190598 0.4804878327090089 0 +0.2819259363392864 0.4902774034337695 0 +0.3277543306043135 -0.4246966089594445 0 +0.3151840233067516 -0.4257444532840096 0 +0.3225634573910022 -0.4357864003779417 0 +0.3029687425685951 -0.4268161273635643 0 +0.3171544008193821 -0.4468322470055535 0 +0.2909177814703179 -0.4279056696526081 0 +0.2980129216181481 -0.4377698579360121 0 +0.2926731151481161 -0.4486859498238281 0 +0.3048163205081622 -0.4477548785877776 0 +0.2805657964274768 -0.4495233033374859 0 +0.2869762677647141 -0.4594890056625675 0 +0.3114402020516144 -0.4578645357844601 0 +0.2790199780126931 -0.4289761440170261 0 +0.2673789016688476 -0.4299576528755165 0 +0.2739945232599433 -0.4396096368483057 0 +0.3056432559409144 -0.4687849909916848 0 +0.2933709669283483 -0.4695171421930661 0 +0.2997308544990785 -0.4795646524298121 0 +-0.3458096033427512 0.4211415050484114 0 +-0.3387002873413409 0.4302236222583805 0 +-0.3276478560885152 0.4288746114317321 0 +-0.3315948625845083 0.439573898478383 0 +-0.3241670242916919 0.4185688628454922 0 +-0.3166347403228575 0.4272723655810979 0 +-0.324504166981127 0.4491197862684858 0 +-0.3057210614297273 0.4258016031026672 0 +-0.3093784204326687 0.4366288840056334 0 +-0.3021602565540829 0.4462652671356186 0 +-0.3132718995278497 0.447746942561228 0 +-0.2910073684208989 0.4447507639544642 0 +-0.2949180370942734 0.45610133411415 0 +-0.3173908045632914 0.4588668729371297 0 +-0.3022515435632274 0.4152567130604206 0 +-0.2946810456568517 0.4241065205688865 0 +-0.2834705320611349 0.4221719885333801 0 +-0.2871928844423086 0.4334633477884733 0 +-0.2799020849255471 0.4107910750164221 0 +-0.3102146912740362 0.4687621980303315 0 +-0.2990263026903951 0.4673107953841857 0 +-0.3030541045846451 0.47870933150835 0 +-0.1847039047633665 -0.09946257584555729 0 +-0.1870462024062132 -0.1133452233075918 0 +-0.1749562421710529 -0.1135966426131909 0 +-0.1888553102859681 -0.1266932597098223 0 +-0.1645865218473146 -0.1273512068442873 0 +-0.1905875299049308 -0.1398864131236426 0 +-0.1779553722081067 -0.1396937384360288 0 +-0.1669586325013217 -0.1524124814352265 0 +-0.165633977439279 -0.1398894677941283 0 +-0.1686313327494389 -0.1651264650018561 0 +-0.1559376411856635 -0.1646799539012124 0 +-0.1534227673379243 -0.1404696695142209 0 +-0.1924014839884778 -0.1531223238329135 0 +-0.1946826963241045 -0.1667044129582947 0 +-0.1815550772328064 -0.1658116819501624 0 +-0.1975429882326603 -0.1802957671662564 0 +-0.1704227586972236 -0.1781174851774288 0 +-0.1413596656677308 -0.1530827598050137 0 +-0.1428857093962656 -0.164451888120655 0 +-0.1280574435117819 -0.1642446495099087 0 +-0.1453374188942698 -0.1764151146374367 0 +-0.1174885420448318 -0.1749023194930153 0 +-0.4127627365457791 0.3241338049276776 0 +-0.4096913916771592 0.3363544739842363 0 +-0.4155845870048128 0.3123901566063841 0 +-0.4053992439322735 0.3154383997444377 0 +-0.4183385434966033 0.3007781334397697 0 +-0.3982523884876343 0.3059932047628366 0 +-0.3954113085424256 0.3172885487473421 0 +-0.4211376919220488 0.2891473361807403 0 +-0.4111138202698091 0.2920434545945512 0 +-0.4040303790965343 0.2829529251686017 0 +-0.4010592158691467 0.294645061339025 0 +-0.4071929402221544 0.2714204992842973 0 +-0.397161398971707 0.2735832008871361 0 +-0.3911419563078875 0.2965433916551415 0 +-0.4240334258819913 0.2776491015802405 0 +-0.427051359757094 0.2666407489845701 0 +-0.4172653694320712 0.2690613959119188 0 +-0.430221688753636 0.2563848396591119 0 +-0.4106952676141636 0.2599599392313113 0 +-0.3840410609304883 0.2870867968378351 0 +-0.3811981765104748 0.2983168394273651 0 +-0.3868746899109777 0.2757462103682712 0 +-0.3769384066170958 0.2776080655216425 0 +-0.3905597765614304 0.2640488091282332 0 +-0.3698578889426938 0.2682123697917242 0 +-0.3669821457269197 0.2793573531876442 0 +-0.3765289869092587 -0.2758483839600975 0 +-0.3845223378733121 -0.2840640197635823 0 +-0.3940259466734699 -0.2805580930117624 0 +-0.3926621389207238 -0.2918714768166557 0 +-0.3961299861128989 -0.2687340341759725 0 +-0.403632249315415 -0.277143703389011 0 +-0.4008298644065768 -0.2998212266040228 0 +-0.4132725888352242 -0.2737090038768969 0 +-0.411725205397922 -0.2851442087816033 0 +-0.4198929308837749 -0.2930939585689705 0 +-0.4103613976451759 -0.2964575925864967 0 +-0.4293716509664594 -0.2896258283186309 0 +-0.428019177967172 -0.3009641889804398 0 +-0.4089975898924297 -0.3077709763913899 0 +-0.4155921160400092 -0.2618437493619363 0 +-0.4229149418664744 -0.2702099767881989 0 +-0.4323307448185874 -0.2664333578453458 0 +-0.4306960548768169 -0.2781282287852287 0 +-0.434258713880818 -0.2554765881358195 0 +-0.4171653153782827 -0.3157207261787571 0 +-0.426664862963699 -0.3122962206358113 0 +-0.4253330408641356 -0.3236704759661242 0 +0.3773597838483496 0.2922295711395302 0 +0.3872928130739631 0.2878234845165933 0 +0.3863312929169035 0.299329520775412 0 +0.397351384017105 0.2834721555876092 0 +0.3952843051775401 0.3068603835172947 0 +0.4076089803136138 0.2788166663956632 0 +0.4061734845083686 0.2910322295311976 0 +0.4150875653539906 0.298198056419466 0 +0.4052164444954726 0.302491735132927 0 +0.4247629335299721 0.2936128082692696 0 +0.4237761363245796 0.3051446074453515 0 +0.4042349682386485 0.3144450733734236 0 +0.4177922645568466 0.2742010246474178 0 +0.4274796040961274 0.2699509522033323 0 +0.4259001452554656 0.2816200182435997 0 +0.4130603621413186 0.3218500819993252 0 +0.4226696610023731 0.3167006477514692 0 +0.4217043917988926 0.3288098081299431 0 +-0.1174751040649629 0.174617818884341 0 +-0.1280120489308468 0.1639962970740016 0 +-0.1414250129083149 0.1639717124750705 0 +-0.1413142710867957 0.1528344073691066 0 +-0.1458061593658182 0.1755846332299973 0 +-0.1556150874746835 0.1638579289051125 0 +-0.1531289807199713 0.1401656043681875 0 +-0.1685556634913801 0.1640246502654578 0 +-0.1665915303107035 0.1515632536019135 0 +-0.1776268262314537 0.1388199265287451 0 +-0.1650298161200892 0.1391835438112723 0 +-0.1902795983275022 0.1387962776310178 0 +-0.1886060740003405 0.125888491172884 0 +-0.164358487947836 0.1270045640338747 0 +-0.1709688987185665 0.1766694903967715 0 +-0.1814592953376173 0.1644502220448647 0 +-0.1944102602274758 0.1650976453047613 0 +-0.1922510422562504 0.151858132743754 0 +-0.1966138013526115 0.178579529806214 0 +-0.1748400055336133 0.1133110507807474 0 +-0.1869148030509606 0.1128566485345949 0 +-0.1847208360145222 0.09927438366429775 0 +-0.3530635780711138 0.4124544784487871 0 +-0.3425485053817797 0.4114434086589522 0 +-0.3394835257671605 0.4018626655159203 0 +-0.3317874455439425 0.4101179150737826 0 +-0.3472984397946034 0.3937928467425369 0 +-0.3364937653219351 0.3923595231832301 0 +-0.3209106411551556 0.4085816552906767 0 +-0.3335448266783421 0.3829880942514089 0 +-0.3256286018143264 0.3908116183148949 0 +-0.3147136569700114 0.3891371487072418 0 +-0.3177687881085198 0.3987799146895221 0 +-0.3117423292558743 0.3796625813029145 0 +-0.3037435939973351 0.3873394478770318 0 +-0.3099736142868804 0.4069117025101187 0 +-0.3414604988450539 0.375010363266942 0 +-0.3306413094792546 0.3736224779976554 0 +-0.3278060936950618 0.3644622615066406 0 +-0.319761412567084 0.3720303108434107 0 +-0.3357854003728125 0.3567790056149197 0 +-0.2989444806389221 0.4050201997743759 0 +-0.2957694795590932 0.3950784346260912 0 +-0.2878181043977933 0.4028839377241874 0 +0.4897540088823233 0.4896147498015034 0 +0.4906532533540415 0.477961034781077 0 +0.4819558664747699 0.4690460108134556 0 +0.4911238855179633 0.4659181421251989 0 +0.4725560727612756 0.4719516308084235 0 +0.4735750046408561 0.4604659036862826 0 +0.4914232910579966 0.4537323254347221 0 +0.4653611703368744 0.4521327891911018 0 +0.4743002066562308 0.4488094443711771 0 +0.4747817112535274 0.4370637857357417 0 +0.4830208807360701 0.4452541283253628 0 +0.4665205431188421 0.4292555085644933 0 +0.4749696595892342 0.4253143008177727 0 +0.4915840992825746 0.4414745857656863 0 +0.4562072226531242 0.455179510342842 0 +0.4572598376349378 0.4440409245108444 0 +0.4494172658117206 0.4363521940816327 0 +0.4579585946401986 0.4329923656207822 0 +0.4406885733039039 0.4390200930803222 0 +0.4421475251274413 0.4293562230286891 0 +0.4582359723777889 0.4219160848488206 0 +0.4916779238051479 0.4292032180354509 0 +0.4833542374251311 0.421217788161693 0 +0.491674172797961 0.4169733780281769 0 +0.4749760314481761 0.4136026100213009 0 +0.4916476241346863 0.4047393100787167 0 +0.4046999988714748 0.4910247249998599 0 +0.4168754645861499 0.4916037732949193 0 +0.4212509291722998 0.4832075465898386 0 +0.4293754645861499 0.4916037732949193 0 +0.41376953873005 0.4737426077436097 0 +0.4256263937584497 0.4748113198847578 0 +0.4418754645861499 0.4916037732949193 0 +0.4300018583445996 0.4664150931796771 0 +0.4381263937584497 0.4748113198847578 0 +0.4506263937584497 0.4748113198847578 0 +0.4462509291722998 0.4832075465898386 0 +0.4550018583445996 0.4664150931796771 0 +0.4631263937584497 0.4748113198847578 0 +0.4543754645861499 0.4916037732949193 0 +0.4229727982692589 0.4565192054501006 0 +0.4344443600343884 0.45783147683429 0 +0.4381466312136716 0.4482969359172294 0 +0.4457624352340147 0.4568460477329839 0 +0.4313505707159008 0.4397810527784348 0 +0.4668754645861499 0.4916037732949193 0 +0.4712509291722998 0.4832075465898386 0 +0.4793754645861499 0.4916037732949193 0 +-0.405621957403326 0.1877639413998579 0 +-0.4171447253638899 0.1910660040104827 0 +-0.4225267648435881 0.2014761694495949 0 +-0.4290404271651235 0.194855348080875 0 +-0.4162855619971962 0.209274347523354 0 +-0.427749913232404 0.2114988182397818 0 +-0.4412145411338506 0.1984575687567549 0 +-0.4326565180434004 0.2212071108243014 0 +-0.4394249011439499 0.214107412927307 0 +-0.4512136931958864 0.2168152670198061 0 +-0.4464034432918221 0.2074240218451912 0 +-0.4555082291352991 0.2262610193461578 0 +-0.4631123879717141 0.2194349645875151 0 +-0.4535869465602698 0.2015349436648544 0 +-0.4261173971509913 0.2289569254480509 0 +-0.4372680495366009 0.2308561491336704 0 +-0.441509665250834 0.2400955437780481 0 +-0.4483054588824354 0.2331758810259235 0 +-0.435081987717392 0.2470798530034063 0 +-0.4453416650570637 0.248749550109306 0 +-0.4592587108758472 0.2359430717161822 0 +-0.4661076110145926 0.2042257518318199 0 +-0.4708831357247115 0.2127932567814081 0 +-0.4786994875762117 0.2062842029723881 0 +-0.4754887532487231 0.2216690951054177 0 +-0.4910825008716144 0.208002079971121 0 +-0.4923404603986559 -0.2070249339458453 0 +-0.4794440426867993 -0.2057556846433028 0 +-0.4719547328457222 -0.2120737110368911 0 +-0.4666774816117887 -0.2038104508333812 0 +-0.4763956689575884 -0.2212369770217927 0 +-0.4647171143962376 -0.218347166003727 0 +-0.4541250798285131 -0.2010494393314358 0 +-0.4578434324914623 -0.2247421058202088 0 +-0.4528370108517955 -0.2156603926032132 0 +-0.4410420070382778 -0.2132901785541382 0 +-0.447491343808465 -0.2069725349169628 0 +-0.4348328645283875 -0.2204172399977473 0 +-0.4294600870721568 -0.2112040977569944 0 +-0.4417881017637062 -0.1982446964519939 0 +-0.4625581985917973 -0.233842630755045 0 +-0.4512208928334041 -0.2313157925959749 0 +-0.4447786815573276 -0.2379818460300277 0 +-0.4399438710516568 -0.2293834804513402 0 +-0.4493313326793196 -0.2465035345589074 0 +-0.438509707298754 -0.2445966269720591 0 +-0.4288611820108534 -0.2282161498074414 0 +-0.429684343221982 -0.1948434450400862 0 +-0.4238248682171982 -0.2015772194060858 0 +-0.4178836527541002 -0.1912199403894005 0 +-0.4180904148217403 -0.2092861630541258 0 +-0.4064152291642416 -0.1877843980459942 0 +-0.4013006648634397 -0.1971312151283807 0 +-0.4069021169946235 -0.20747945289731 0 +-0.3967430429935164 -0.2103194617884164 0 +-0.4124211174875405 -0.2175895916420958 0 +-0.3923045843463353 -0.2236641241925939 0 +-0.4179612367326017 -0.2277533896456764 0 +-0.4079595854642084 -0.2301403707269139 0 +-0.4036855140231412 -0.2430333213799927 0 +-0.3980619479157715 -0.2333483617884324 0 +-0.4094279019590202 -0.2526062277034646 0 +-0.3996617620659018 -0.2559730002687453 0 +-0.3880663613463091 -0.2368338714106951 0 +-0.4233952111313666 -0.2374865099349371 0 +-0.4290519749870511 -0.2469912355472694 0 +-0.419355364297981 -0.2495697828108924 0 +-0.3839866998734495 -0.2499401031223676 0 +-0.3898553443091637 -0.2594564645264756 0 +-0.380094421749293 -0.2629736548424088 0 +0.2504830519482971 0.3455312282291531 0 +0.249305401541874 0.358045823683807 0 +0.2377330668779876 0.3513963411693835 0 +0.2481757091454165 0.3702173671955034 0 +0.2250475520086616 0.3579058902437114 0 +0.2472006307961832 0.3826199840213335 0 +0.2356345763969698 0.376884073286007 0 +0.2233139671910866 0.3838090800243393 0 +0.2241507625916773 0.3710314271608104 0 +0.2234003252887475 0.3963196060224817 0 +0.2131628944534783 0.3912064099138457 0 +0.2124318653662083 0.3649787584772827 0 +0.2463233643090189 0.394668663028503 0 +0.2457338408988516 0.4065997979400608 0 +0.2344921309783534 0.4014018649787137 0 +0.2007577106129003 0.3719458881053231 0 +0.2019452929473246 0.3855366874072506 0 +0.1896771767589709 0.3794857945875404 0 +0.2803957980289227 -0.3207771578528078 0 +0.2911591250868424 -0.3185891335766307 0 +0.2853993637785816 -0.3083825579051297 0 +0.3019224521447622 -0.3164011093004535 0 +0.2904029295282405 -0.2959879579574516 0 +0.3127221239225809 -0.3142688378164072 0 +0.3069803975258973 -0.3040896062359462 0 +0.3121042696362715 -0.2918786455131986 0 +0.3012239045856943 -0.2938890314802742 0 +0.3231649887645082 -0.2901429428895245 0 +0.3174135509015478 -0.2799493642344545 0 +0.2954064952778995 -0.2835933580097735 0 +0.3235657469681956 -0.3122073424145031 0 +0.3344891229031142 -0.3102837407670459 0 +0.328890101257135 -0.3003090495517522 0 +0.3004100610275584 -0.2711987580620955 0 +0.3113826471583024 -0.2694285759279219 0 +0.3048786960319885 -0.2591715410475655 0 +-0.3616420447927814 -0.40155620189791 0 +-0.3670545668667496 -0.4151778856237108 0 +-0.378376913057561 -0.4190555306342474 0 +-0.3727430891998066 -0.4286810542691085 0 +-0.3843919759390032 -0.4101446491373517 0 +-0.3893477429391242 -0.4229091568302051 0 +-0.3785649326159547 -0.4417748933258344 0 +-0.4000223936007954 -0.426744823636478 0 +-0.3946818745941233 -0.4356428794773897 0 +-0.4003238331311656 -0.4482347947029365 0 +-0.3895857119614687 -0.4449733929307318 0 +-0.4108427404705101 -0.4515403565619843 0 +-0.4063633741452167 -0.4607070378834118 0 +-0.3845760806977106 -0.4546557016842897 0 +-0.4058909563998869 -0.4183854526179058 0 +-0.4104262329762289 -0.4305319524886743 0 +-0.4202511613747278 -0.4340339609763678 0 +-0.4155792938223624 -0.4426236350833651 0 +-0.424816731077694 -0.4258024106641981 0 +-0.3907946039436559 -0.4672622566725131 0 +-0.4018276207981576 -0.4700509484531076 0 +-0.3972849524578241 -0.4795155724348565 0 +0.3620601334246619 0.4088979705450075 0 +0.3674242095343508 0.421494511368023 0 +0.3789811454762789 0.4248394144798663 0 +0.3731134987879395 0.4341053698318269 0 +0.3852497059061194 0.4162253319286972 0 +0.3903251860304778 0.4281797355564872 0 +0.3788993875560971 0.446272641344087 0 +0.4013885056039446 0.4314936846176853 0 +0.3956131369724901 0.4399589336867847 0 +0.4012321918572592 0.4515971122152603 0 +0.3901608786087238 0.4488231848293807 0 +0.4120454161760482 0.4542751126009378 0 +0.4072854162513256 0.4628730851337435 0 +0.3848681150413349 0.4581639178365893 0 +0.4075650189430802 0.4233943765387719 0 +0.4120669303999412 0.4347037886652685 0 +0.4220783192839958 0.4375961170860699 0 +0.4171681972387589 0.4456799076142891 0 +0.42732807254823 0.4301419126292704 0 +0.391060017730788 0.4697409877783355 0 +0.4024271483948968 0.4717723590609331 0 +0.3975608575399282 0.4808796639414091 0 +-0.4191837907302934 0.359621487478475 0 +-0.4295598493542258 0.3644305765013461 0 +-0.4308173522678716 0.3752900057700517 0 +-0.4397857699100618 0.3703216057891419 0 +-0.4225191598147199 0.3819200453666534 0 +-0.432379674286038 0.3861237973783268 0 +-0.4498051984521864 0.3766693624898829 0 +-0.433928749442986 0.3969215101523155 0 +-0.4424661065508933 0.3918792089452077 0 +-0.4525737774117214 0.3981051365139551 0 +-0.4511761563185115 0.3872783595634337 0 +-0.4537134697531247 0.4088512879149484 0 +-0.4626076204184349 0.4048933694660876 0 +-0.4601517345657123 0.3832690161861426 0 +-0.4255910272333794 0.402419695170672 0 +-0.4352904220898722 0.407185930972663 0 +-0.4364035662017017 0.4169870290132913 0 +-0.4449282740656219 0.4129668664913398 0 +-0.4282952595035236 0.4212377312789062 0 +-0.4703736029374036 0.3901050238318795 0 +-0.471636909386717 0.4008785335158593 0 +-0.4806454619419855 0.3970229129696954 0 +0.423749708822059 -0.3614963409623672 0 +0.433442380802998 -0.3666269824357476 0 +0.4339136354234663 -0.3762997938331812 0 +0.4430623990680449 -0.3725083213071622 0 +0.4247070083488576 -0.3808211106044729 0 +0.4343953243394685 -0.3859033503827882 0 +0.4526521293850677 -0.3785724299670206 0 +0.4348710041539585 -0.3955924012376142 0 +0.4439807288835891 -0.3919849688311381 0 +0.4535110480593353 -0.3983278783444358 0 +0.4530965187586526 -0.3883994806380251 0 +0.4538374119535424 -0.4085240575593655 0 +0.4629637516236893 -0.4050860718526879 0 +0.4622188327341744 -0.3847767922555524 0 +0.4257031499397081 -0.4001088326541718 0 +0.4352738114054598 -0.4056706556726004 0 +0.4356300576451129 -0.4155161923918438 0 +0.4446638833928976 -0.4121288369692862 0 +0.4268346273221546 -0.4191496516211551 0 +0.4717900202911641 -0.3909455253378283 0 +0.4721445480548366 -0.4012856028127716 0 +0.4812868820073372 -0.3975674998252219 0 +-0.4916891590657687 0.2198928387245237 0 +-0.4837338782525969 0.2269191957486264 0 +-0.4919379139796612 0.2321574501084796 0 +-0.4758298170202924 0.2338901048074081 0 +-0.4920953194949405 0.2445189508418382 0 +-0.4678121866535677 0.2409138643545105 0 +-0.4761969773789912 0.246057940037322 0 +-0.476498377685909 0.2582249700575041 0 +-0.4842560587836869 0.2514332165757185 0 +-0.4687433329551126 0.264883428095667 0 +-0.4765952342125093 0.2704748284172478 0 +-0.4921823577790497 0.2569257852555166 0 +-0.4602832263464512 0.2475572292105233 0 +-0.4530333872966555 0.2539616467499772 0 +-0.4608422147751408 0.2593725867965851 0 +-0.4922381358294057 0.2693512386601286 0 +-0.4843794868660907 0.2761035804085294 0 +-0.4921375439110494 0.2817682751117484 0 +-0.4928513012732495 -0.2815292745631522 0 +-0.4856813918429278 -0.2755587098176844 0 +-0.4928320090453339 -0.2690540310356847 0 +-0.4784871627883921 -0.2696041632271121 0 +-0.4927848989902344 -0.2566025294172573 0 +-0.4712495763343081 -0.2636833985644834 0 +-0.478358676481727 -0.2572567505926538 0 +-0.4780815629257947 -0.2449908159459505 0 +-0.4854697493807847 -0.2507699863301675 0 +-0.4706250491447291 -0.2392527066241981 0 +-0.4774874880020237 -0.232923658888993 0 +-0.4926758727909628 -0.2441856191354653 0 +-0.4639961772065729 -0.2578150097271259 0 +-0.4567016807276994 -0.2520509852153803 0 +-0.4636985074023447 -0.2456407778119264 0 +-0.492536655053244 -0.2317983600417403 0 +-0.4848623336657559 -0.2262514814079862 0 +-0.4924153582700178 -0.2194126106955442 0 +0.492177940149782 0.281950029131706 0 +0.4842897871027119 0.276387771188375 0 +0.4921175907209068 0.2694834536597769 0 +0.4763044259547908 0.27083322937168 0 +0.4920020879886217 0.2570490697574341 0 +0.4682214224100211 0.2653312899402284 0 +0.4759818976188771 0.2585573416828364 0 +0.475354970662129 0.2463558036525944 0 +0.4837672181178282 0.2516712842584762 0 +0.4665895652630442 0.241154109703739 0 +0.4742705754630225 0.2342979754347393 0 +0.4917547282351974 0.2446530271059561 0 +0.4599833637348089 0.2599247879726096 0 +0.4517156367109424 0.2547309504907707 0 +0.4587951737973464 0.2480888780172457 0 +0.4914398447195311 0.2323050866881397 0 +0.4823319959582407 0.2273252065051665 0 +0.491210971206961 0.2199697063441387 0 +-0.2820378811836156 0.2990409697712544 0 +-0.2910614196046137 0.3055708194104013 0 +-0.2946539233730781 0.2957016249862616 0 +-0.3000912958966351 0.3120885573159202 0 +-0.3071929455619736 0.292432448007181 0 +-0.3091419817790601 0.3185875144011882 0 +-0.3125817856939998 0.3088409505969834 0 +-0.3250432758181119 0.3056159668713934 0 +-0.3160947973928296 0.2990441397883371 0 +-0.3340666363480652 0.3121322452563132 0 +-0.3374861755844464 0.302403292079646 0 +-0.3196717497658467 0.2892092061925051 0 +-0.3182142385919668 0.3250693255702942 0 +-0.3272967976941133 0.3315431637652945 0 +-0.3306779797804573 0.3218402245520076 0 +-0.33638839998264 0.3380101904068168 0 +-0.3431295700933652 0.3186198744180887 0 +-0.3321042352673828 0.2860110598381848 0 +-0.3409469453883386 0.2926477997543873 0 +-0.344467694204008 0.2828542839469369 0 +-0.3498707402040904 0.2992295584293606 0 +-0.3566119103148156 0.2798392424406326 0 +-0.1228574481040977 -0.1868092683487352 0 +-0.1371947060787855 -0.1878198058512999 0 +-0.1306723213950925 -0.1991724709935602 0 +-0.1513242929880173 -0.1887379595729079 0 +-0.1385574946335147 -0.2115855754517546 0 +-0.1646302189318127 -0.1896764790099005 0 +-0.1585679701124274 -0.2011685205560839 0 +-0.1662043890713621 -0.2136272596280762 0 +-0.1524876367409549 -0.212606748537929 0 +-0.1798031087160487 -0.2146649779456707 0 +-0.1740520636314004 -0.2260892170419856 0 +-0.1465187737782724 -0.2240223093372186 0 +-0.1779280238412123 -0.1906923452620448 0 +-0.1913632857691444 -0.1917714184340623 0 +-0.1855707053588372 -0.2032263162973076 0 +-0.1545130266997877 -0.2364685186933288 0 +-0.1683167189005422 -0.2375075818582886 0 +-0.1626139927464251 -0.2489355575244165 0 +-0.3744999665489746 0.2566236794828178 0 +-0.3846690243837061 0.2542580815803027 0 +-0.3792727441732718 0.2448187964818567 0 +-0.39459225878574 0.2522581101850705 0 +-0.3841576255211724 0.2329639434534095 0 +-0.4047815717961463 0.250273067267234 0 +-0.3995708118474422 0.2403371765929074 0 +-0.40471346960245 0.2288644502902408 0 +-0.3944581574127387 0.2307864758197278 0 +-0.4149741828141231 0.2274481051200668 0 +-0.4101225548553238 0.2176982537252635 0 +-0.389172866221299 0.2210990206079938 0 +-0.4149579983557369 0.2485357410833989 0 +-0.4252766204557111 0.2476317215975321 0 +-0.4199403069701779 0.2375034731337549 0 +-0.3943507102643524 0.2092513679345795 0 +-0.4050640882400701 0.2074625579952184 0 +-0.3998692339591747 0.1972947569984449 0 +-0.1599256225379133 0.2465403787170062 0 +-0.165910983733516 0.2352680495411272 0 +-0.1523033806246315 0.2342466457523078 0 +-0.1719916015717549 0.2240205700776102 0 +-0.1446097303753045 0.2221602824670827 0 +-0.178178564615185 0.2125142809962765 0 +-0.1646464035324997 0.211728152589554 0 +-0.1576176480806104 0.1995999083940905 0 +-0.1508796161404579 0.2108974943495579 0 +-0.1642245114600855 0.1882107282150506 0 +-0.1511357172264045 0.1875340258994721 0 +-0.1368844909339726 0.2101174778315232 0 +-0.1844428652585727 0.2011754470186903 0 +-0.1906101023842956 0.1898548259155457 0 +-0.1775107066377584 0.1889720896584981 0 +-0.1291674232767127 0.1980764496011981 0 +-0.1373910118578328 0.1869134771365199 0 +-0.1229985292492393 0.186225071289533 0 +-0.3636681555464901 0.2891944168403186 0 +-0.3603150926301364 0.29895253396253 0 +-0.3707577490941446 0.2986451589617856 0 +-0.3569423846199601 0.3086831867430552 0 +-0.3778384664352746 0.3081197155636494 0 +-0.3535465147112079 0.3183969727207444 0 +-0.3639778086992665 0.3181590518396132 0 +-0.3709719900698045 0.3276550080889115 0 +-0.3744366470869062 0.3179030477267818 0 +-0.3673963918225033 0.3373672053178352 0 +-0.3778920761493664 0.3371616430626976 0 +-0.3849166434348603 0.3176186009156676 0 +-0.3501546625970798 0.3281002308211821 0 +-0.3468648263377908 0.3378039035989185 0 +-0.3570861768257955 0.3375827890141642 0 +-0.3424421385755287 0.3474333724574082 0 +-0.3619763317738688 0.3469445748574335 0 +-0.3919845430615937 0.3271500244680047 0 +-0.3884506027898051 0.3369551495663033 0 +-0.3990273495055164 0.3367294802146336 0 +-0.3825766096866282 0.3465475938760755 0 +-0.4047650739482244 0.3461369068491787 0 +-0.3654257047830898 0.4225786406494134 0 +-0.3723621585394501 0.4134259718734866 0 +-0.3628326548148699 0.4128397318144446 0 +-0.3818392304034 0.4141273986993733 0 +-0.3797893404914059 0.4041559232397877 0 +-0.3843406032842264 0.4242504998499086 0 +-0.3915047792838209 0.4150696664986515 0 +-0.3873611126654389 0.3948495672979438 0 +-0.3775817052639069 0.3943446276729957 0 +-0.401023586183222 0.4162680507461562 0 +-0.3989878586236291 0.406153745701458 0 +-0.4066269510347935 0.3975537857382632 0 +-0.3970292720496958 0.3956126913651908 0 +-0.4161585393660786 0.3998551049921866 0 +-0.4144680174324138 0.3894382185743397 0 +-0.3950624656456322 0.3855425414030041 0 +-0.4033806289766277 0.4260685101158516 0 +-0.4103806316561534 0.4176379550070398 0 +-0.4194928708758604 0.4192486763307409 0 +-0.4178364182094615 0.4098038814299015 0 +-0.4211985289178984 0.4280548294547194 0 +-0.4028256320553086 0.3763835530053223 0 +-0.3929204443677922 0.3754214974514417 0 +-0.4126299194488148 0.378026253170637 0 +-0.4107649716699917 0.367399305794012 0 +-0.4085352400210662 0.3563285517404639 0 +-0.3983293095750233 0.4812661271969692 0 +-0.402063279261467 0.4723459339587376 0 +-0.3927086675330459 0.4715084695430046 0 +-0.4063233392459877 0.4632660558187685 0 +-0.3871731239119499 0.461729334689939 0 +-0.4102230125162377 0.4542641070453595 0 +-0.4005181164584939 0.4535731030416259 0 +-0.3949450016446768 0.4438478649592113 0 +-0.3909899177901138 0.4527958076308628 0 +-0.3989125902600771 0.4349384578690628 0 +-0.3895415211292703 0.4340779034029626 0 +-0.3815962880523709 0.4519685381457026 0 +-0.4141116964234258 0.4452636461588182 0 +-0.417472356791075 0.4365314355958518 0 +-0.4082896371971814 0.4357572197977211 0 +-0.376097968333001 0.4421827039939013 0 +-0.3801583458460376 0.4332226096135684 0 +-0.3707402371082902 0.4323844025914967 0 +0.39681658106254 -0.4799043036539514 0 +0.4002295902556018 -0.4708173128470132 0 +0.3902201529320182 -0.4688955981148409 0 +0.4037122127800786 -0.4616736215398817 0 +0.3836237248014964 -0.4578868925757305 0 +0.4071995111400831 -0.4525210017360191 0 +0.3970461713181418 -0.4507216165009645 0 +0.39044974318762 -0.4397129109618541 0 +0.3870367339945582 -0.4487999017687923 0 +0.3938627523806818 -0.4306259201549159 0 +0.3838533150570982 -0.4287042054227437 0 +0.3770272966709746 -0.4468781870366201 0 +0.4111247316571927 -0.4431823136597218 0 +0.4151272746302542 -0.4343017067367757 0 +0.4049067238736941 -0.4326353815017222 0 +0.4195022346432454 -0.4259848558505203 0 +0.4000422772045691 -0.422226114586589 0 +0.3704308685404528 -0.4358694814975096 0 +0.3738438777335146 -0.4267824906905714 0 +0.363917251977717 -0.4249283237878053 0 +0.3787039527629233 -0.4180571763115071 0 +0.3594188988046783 -0.4145716858340772 0 +-0.4312133141280934 -0.3554118288573092 0 +-0.4217357042281471 -0.3608180052621597 0 +-0.4216816778466829 -0.3719268205467157 0 +-0.4115532074896761 -0.3674525055900105 0 +-0.4309292742815624 -0.3779205483273883 0 +-0.4216606005583632 -0.3831494902826785 0 +-0.4014342118244714 -0.3741163642110655 0 +-0.4218834001692278 -0.3945245880985617 0 +-0.4120449129894501 -0.3895329349290078 0 +-0.4026874018250766 -0.3962058539875688 0 +-0.401902617872545 -0.3851553450415331 0 +-0.404008639082466 -0.4073326190058973 0 +-0.3935195148254405 -0.4031042632632444 0 +-0.3914067794288468 -0.3808846133984518 0 +-0.4312875986119235 -0.4000433883109316 0 +-0.4225454969286253 -0.4055082822284165 0 +-0.4235534253622334 -0.4159588532949979 0 +-0.4140996089455535 -0.4116834311946529 0 +-0.4324922904801478 -0.4206341433584015 0 +-0.3814431367108336 -0.3876873380612977 0 +-0.3827324656186484 -0.398893839363852 0 +-0.3715879455487179 -0.3946004406846234 0 +0.3674005740051039 -0.4064809178719391 0 +0.3774610641295444 -0.4083929411646903 0 +0.3765253734743474 -0.3987983627731745 0 +0.3875516851114845 -0.4103389374905103 0 +0.385857801599877 -0.3911899336113321 0 +0.3976291253241042 -0.4123057881377299 0 +0.396656460947682 -0.4027205061298837 0 +0.4059120215356647 -0.3951961168151242 0 +0.3958910132500021 -0.3931549163249005 0 +0.4158606293458922 -0.3974450508591728 0 +0.4152882439369987 -0.3878816082792839 0 +0.3953095585203275 -0.3836301332877888 0 +0.407990991392264 -0.4144614455661739 0 +0.417703296609062 -0.4166484465529294 0 +0.4166812338972706 -0.4071804175549487 0 +0.4047810244910007 -0.3760855081688854 0 +0.414761784605118 -0.378360558280636 0 +0.4142664421416434 -0.3687522186621224 0 +-0.3976329032229398 0.3559930184507725 0 +-0.3869358521402622 0.3559875690692629 0 +-0.3902117690406408 0.3656137991012234 0 +-0.3764073365836275 0.3560979833234954 0 +-0.3827464141168061 0.3751175639455131 0 +-0.36568774318241 0.3562395428943533 0 +-0.3692764550051485 0.3655949898354929 0 +-0.3620893323847941 0.3750222616392388 0 +-0.3724754120672513 0.3750165999423182 0 +-0.3516886181904058 0.3750445607225801 0 +-0.3547341486686377 0.3844085225347871 0 +-0.3752332010620399 0.3845712244165586 0 +-0.3554592209501939 0.356397708471108 0 +-0.3455061232094471 0.3565748437006199 0 +-0.3485948053533113 0.3657660521948214 0 +-0.3676827777813147 0.3940099132951242 0 +-0.3576683305918139 0.3938090251388602 0 +-0.3602582157030626 0.4033315173489816 0 +-0.4831621434943956 -0.3965456831796099 0 +-0.4747018958869808 -0.4006523571200936 0 +-0.4746545443594154 -0.3894094038066991 0 +-0.4661494972766549 -0.4046994151109008 0 +-0.466092778531689 -0.3824174485582872 0 +-0.4575736520096563 -0.4086099754918646 0 +-0.4575626720036947 -0.3975478537849541 0 +-0.4488595071245955 -0.3907169538264115 0 +-0.4575076701059153 -0.3865334936976567 0 +-0.4400924975426708 -0.3952149247789172 0 +-0.4399629295506511 -0.3842503071655332 0 +-0.4574979390210359 -0.3754892355216506 0 +-0.4490085066628007 -0.4125057042687137 0 +-0.4405735536054678 -0.4164574783805901 0 +-0.4402891404654531 -0.4060707751696784 0 +-0.4488649025793771 -0.3685862191626433 0 +-0.4399577957859817 -0.3731570840412609 0 +-0.4401072667450543 -0.3618780572202911 0 +0.4299753367342183 0.3591484157980342 0 +0.4205980235677514 0.3653146576689297 0 +0.4210291899038155 0.3763268366494177 0 +0.4106339858146213 0.3724512423363256 0 +0.4304657249298448 0.3818474289410113 0 +0.4214849422084354 0.3874076940034022 0 +0.4007074140246314 0.3794820532520496 0 +0.4221148957909416 0.3988310726646295 0 +0.411957119121908 0.3943742192335046 0 +0.4024952966578424 0.4013100346494091 0 +0.4013065524563939 0.3902015836564237 0 +0.4044166946113669 0.4122287516663202 0 +0.3929501699249661 0.4084067911170665 0 +0.3906733028105545 0.3867115798264875 0 +0.431738367056743 0.4038634583957841 0 +0.4232891866774336 0.4097425700984662 0 +0.4250532675984854 0.420264287829242 0 +0.4152449591528041 0.4163110327096418 0 +0.4339827401340176 0.4243906830210127 0 +0.3806391915964775 0.3939411064009254 0 +0.3817152085045916 0.4047752686819799 0 +0.3707611587861753 0.4012111878722617 0 +0.407741083109417 -0.2748981620132657 0 +0.3994875677717834 -0.2827317527154167 0 +0.4092614143477111 -0.2873522613909426 0 +0.3911474209127146 -0.2906839061920647 0 +0.4106113578117075 -0.2996287959949189 0 +0.3830390419514749 -0.2985820226231719 0 +0.3926745530084915 -0.302780884385149 0 +0.3939913614236678 -0.31485876496628 0 +0.4022263860303895 -0.3072038902029377 0 +0.385906041661738 -0.32262159496269 0 +0.39511657442646 -0.326903305978971 0 +0.4118018422644201 -0.3118033274785812 0 +0.3750707971805629 -0.306480218953828 0 +0.3672768165436895 -0.3143388303106779 0 +0.3766271596220584 -0.3184421774506051 0 +0.412835778364616 -0.3238272443147655 0 +0.4043677612099305 -0.3312478654964062 0 +0.4136784753766131 -0.3356749282970726 0 +0.4831517907405374 0.3974880161854645 0 +0.4748649245616952 0.4019350871542906 0 +0.4743967844528513 0.390912588479633 0 +0.4664269479507722 0.4065253152699085 0 +0.4655988008958293 0.3844142985368975 0 +0.4580404330016521 0.4109324830686152 0 +0.4576762987032037 0.3999282071950563 0 +0.4487912742361498 0.3936278345947737 0 +0.4572146176883043 0.3889837261728182 0 +0.4402700593433916 0.3985824934692437 0 +0.4396985270619563 0.3876742282415576 0 +0.4567609716404383 0.3780090477218937 0 +0.4497085548242311 0.4153186698657557 0 +0.4415978524315665 0.4197390551230226 0 +0.4408957711905693 0.4093894818390625 0 +0.4479063185071958 0.3716119303182231 0 +0.4392505881902112 0.3766209023338635 0 +0.4389715741730048 0.3653396174440898 0 +2 5 0 225 +5106 +5107 +5108 +5109 +5110 +5111 +5112 +5113 +5114 +5115 +5116 +5117 +5118 +5119 +5120 +5121 +5122 +5123 +5124 +5125 +5126 +5127 +5128 +5129 +5130 +5131 +5132 +5133 +5134 +5135 +5136 +5137 +5138 +5139 +5140 +5141 +5142 +5143 +5144 +5145 +5146 +5147 +5148 +5149 +5150 +5151 +5152 +5153 +5154 +5155 +5156 +5157 +5158 +5159 +5160 +5161 +5162 +5163 +5164 +5165 +5166 +5167 +5168 +5169 +5170 +5171 +5172 +5173 +5174 +5175 +5176 +5177 +5178 +5179 +5180 +5181 +5182 +5183 +5184 +5185 +5186 +5187 +5188 +5189 +5190 +5191 +5192 +5193 +5194 +5195 +5196 +5197 +5198 +5199 +5200 +5201 +5202 +5203 +5204 +5205 +5206 +5207 +5208 +5209 +5210 +5211 +5212 +5213 +5214 +5215 +5216 +5217 +5218 +5219 +5220 +5221 +5222 +5223 +5224 +5225 +5226 +5227 +5228 +5229 +5230 +5231 +5232 +5233 +5234 +5235 +5236 +5237 +5238 +5239 +5240 +5241 +5242 +5243 +5244 +5245 +5246 +5247 +5248 +5249 +5250 +5251 +5252 +5253 +5254 +5255 +5256 +5257 +5258 +5259 +5260 +5261 +5262 +5263 +5264 +5265 +5266 +5267 +5268 +5269 +5270 +5271 +5272 +5273 +5274 +5275 +5276 +5277 +5278 +5279 +5280 +5281 +5282 +5283 +5284 +5285 +5286 +5287 +5288 +5289 +5290 +5291 +5292 +5293 +5294 +5295 +5296 +5297 +5298 +5299 +5300 +5301 +5302 +5303 +5304 +5305 +5306 +5307 +5308 +5309 +5310 +5311 +5312 +5313 +5314 +5315 +5316 +5317 +5318 +5319 +5320 +5321 +5322 +5323 +5324 +5325 +5326 +5327 +5328 +5329 +5330 +-1.2094325381086e-16 -0.4041610814281969 0 +-0.03679018027969943 -0.4491266031468892 0 +-0.08018220901325909 -0.3937240964658493 0 +-0.1132315030437318 -0.4424430559224156 0 +0.08027585002482537 -0.393670892988338 0 +0.03679044569844978 -0.4492106103185791 0 +0.1132301565835233 -0.4424233454517149 0 +-0.03090169943749497 -0.3569748890845831 0 +0.03090169943749454 -0.3569748890845831 0 +-0.05307338753193087 -0.4736232615108291 0 +-0.07468172680706922 -0.4459923997966255 0 +-0.09019766745440465 -0.4708215841411451 0 +-0.05797408619452863 -0.4219661144923624 0 +-0.09748183885736214 -0.4174059192428226 0 +-0.01880133086754023 -0.4261765741668744 0 +-0.03984982341813901 -0.3991339828605682 0 +-0.1204827564217197 -0.3880617009585607 0 +-0.1376283108996384 -0.4127389764897325 0 +0.1204461450941959 -0.3881018365960365 0 +0.09745661671988286 -0.4173902264032255 0 +0.1375189885809888 -0.4128953981768912 0 +0.05788015915544613 -0.4220324281998769 0 +0.07462307027641188 -0.4460431257543532 0 +0.04011000501911154 -0.3989591491334453 0 +0.0187422933473713 -0.4261979228708708 0 +0.05309332787480613 -0.4736462542840457 0 +0.09016599435842021 -0.4708424338352684 0 +-0.09879594088003481 -0.3664425432803722 0 +-0.05590169943749496 -0.3751384522847172 0 +-0.0735789955111799 -0.3484043383869131 0 +-0.01545084971874754 -0.38056798525639 0 +-0.04635254915624239 -0.3333817929127763 0 +0.04635254915624187 -0.3333817929127761 0 +0.07366696586098917 -0.3482903077875219 0 +0.0559016994374946 -0.3751384522847171 0 +0.09887042327243944 -0.3663133361829163 0 +0.01545084971874721 -0.38056798525639 0 +-0.161772318296182 -0.4690983005625053 0 +-0.1252085131642721 -0.4702296852118346 0 +-0.1519738508169705 -0.4398083069024375 0 +0.1617723182961822 -0.4690983005625052 0 +0.1522231680149295 -0.4397090379478815 0 +0.1251918772409027 -0.4702348968282187 0 +-0.01549448349145121 -0.3294143533548575 0 +-2.151057110211241e-16 -0.3569748890845831 0 +0.01549624740171771 -0.3294192187971101 0 +0.01754735676730105 -0.4750302475310542 0 +4.703271998692983e-05 -0.4504040458151626 0 +-0.01741688839921129 -0.4750575796337608 0 +-0.06042599115932992 -0.4864004123033219 0 +-0.07140711233844203 -0.4724156677767247 0 +-0.07855875819270688 -0.4853053686926882 0 +-0.06372286820411339 -0.4599102069492083 0 +-0.08261036791300205 -0.458301117106287 0 +-0.04518447285424905 -0.4611898679832656 0 +-0.05566115425679451 -0.4476446120330153 0 +-0.0939653194516275 -0.4441358571963794 0 +-0.1017122592209274 -0.4565136457205208 0 +-0.04720060814505609 -0.4357017916984287 0 +-0.06652129054471329 -0.4338114114762371 0 +-0.07767647345995121 -0.4197164859383249 0 +-0.08598748246744012 -0.4318005000021573 0 +-0.06902657191225922 -0.4079420848785104 0 +-0.08906064161472371 -0.405366287768663 0 +-0.1056073147547332 -0.429734119808415 0 +-0.0279308016476462 -0.4375052694594993 0 +-0.0384100243378809 -0.424075382688802 0 +-0.02936570408400813 -0.4127319866076669 0 +-0.0491876150885062 -0.4103705597079804 0 +-0.009732297980448201 -0.4148983294044088 0 +-0.01972108546086293 -0.4017714646675545 0 +-0.06001969483650246 -0.3964612224009786 0 +-0.1175048904027101 -0.4150016818256839 0 +-0.1254728543055276 -0.4276177046610314 0 +-0.109094568003341 -0.4027807595452175 0 +-0.129279748922638 -0.400211582991744 0 +-0.1002782140864063 -0.3909165272808028 0 +-0.1409326137563615 -0.3851937261028838 0 +-0.1497239054304307 -0.3977550699443656 0 +0.1408998635927506 -0.3852227274833399 0 +0.1292406791867786 -0.4002630935146488 0 +0.1497113365395963 -0.3977751622012637 0 +0.1089987384164164 -0.4028463246292744 0 +0.1174848509947168 -0.4150132853511591 0 +0.1002706968304478 -0.3909391644745105 0 +0.0889277035280208 -0.4054435535819223 0 +0.1055481946327423 -0.4297728413465451 0 +0.1253264090080119 -0.4277823396419437 0 +0.06900283833936188 -0.4079390289274669 0 +0.07757496901206445 -0.4197806094950075 0 +0.06641574537710543 -0.4339066013351703 0 +0.08592074427092486 -0.4318429007888868 0 +0.04713936438706627 -0.4357808994574159 0 +0.05561683783023794 -0.4477193227790133 0 +0.09391526302656697 -0.4441682949406515 0 +0.06029818550433291 -0.3962776096407077 0 +0.0491419972179773 -0.4103550511725647 0 +0.02924665497031284 -0.4127736691828852 0 +0.03834832250848644 -0.4241156796200126 0 +0.01992897806683886 -0.4016259083812693 0 +0.009486634599488335 -0.4149092466403577 0 +0.0279394659727448 -0.4375541068364619 0 +0.08255711781637434 -0.4583491516439711 0 +0.1016426620207541 -0.4565625447157442 0 +0.06372738686036768 -0.4599291459547988 0 +0.07141729728390246 -0.4724187185848523 0 +0.04520626426935248 -0.461247882698633 0 +0.06043927250459176 -0.4864047346835945 0 +0.07855875819270748 -0.4853053686926881 0 +-0.1311877895811664 -0.3733378849299241 0 +-0.1104179175133811 -0.3766728924123537 0 +-0.1202028619557725 -0.3622985657526742 0 +-0.08946682261384617 -0.3802645919012847 0 +-0.1081945180019657 -0.3523728037921415 0 +-0.06840169943749494 -0.3842202338847843 0 +-0.07739370884901767 -0.3706989125826332 0 +-0.06497718713666753 -0.3614823262382806 0 +-0.08657614393784735 -0.3568994926439927 0 +-0.04340169943749496 -0.3660566706846502 0 +-0.05194294600466982 -0.3531065013279944 0 +-0.09563427780159185 -0.3431708037013791 0 +-0.04791257553260882 -0.3871119828094739 0 +-0.02770872063041102 -0.3898098250474731 0 +-0.03559196988914642 -0.3779093922088651 0 +-0.007725424859373832 -0.3923645333422935 0 +-0.02317627457812126 -0.3687714371704865 0 +-0.0821722493828555 -0.3352246189902866 0 +-0.06017088403946082 -0.3405425099339515 0 +-0.06835140716918378 -0.3279594925539927 0 +-0.03862712429686868 -0.3451783409986797 0 +-0.0540779740156161 -0.3215852448268728 0 +0.05407797401561554 -0.3215852448268727 0 +0.06836502510853855 -0.3279430018317058 0 +0.06020199384246172 -0.3405032271544804 0 +0.08222271742539247 -0.3351650562645828 0 +0.03862712429686821 -0.3451783409986796 0 +0.05197750334467545 -0.3530609224007786 0 +0.09580450684926681 -0.342983138522281 0 +0.04340169943749457 -0.3660566706846501 0 +0.06506553719274455 -0.3613620986947569 0 +0.07756553719274457 -0.3704438802948239 0 +0.08672937494799453 -0.3566675267048637 0 +0.06840169943749462 -0.3842202338847841 0 +0.08948474013201427 -0.3802429513089401 0 +0.1081960551311473 -0.3523738975748126 0 +0.02317627457812087 -0.3687714371704865 0 +0.03562795431420032 -0.3778851276681272 0 +0.02781142917721569 -0.389739885471463 0 +0.04802516214830828 -0.387036743013874 0 +0.007725424859373544 -0.3923645333422935 0 +0.120325365592652 -0.3621639760338106 0 +0.1104617068680749 -0.3766952924328199 0 +0.1312026713590421 -0.3733311356364805 0 +-0.1821021673928471 -0.4843565534959769 0 +-0.1646248285630097 -0.4844849513528274 0 +-0.1796549932076048 -0.4689057037772296 0 +-0.1475528258147576 -0.4845491502812527 0 +-0.1759918107776064 -0.453647450843758 0 +-0.1297469699666605 -0.4849304531139225 0 +-0.1432940585644119 -0.4696914972466065 0 +-0.1384775255216175 -0.4550375042936957 0 +-0.1571626861352879 -0.4543561017384823 0 +-0.1198729409105923 -0.4558763058066966 0 +-0.1326868986328786 -0.4408743481363737 0 +-0.1712172356369801 -0.4389528195364462 0 +-0.1124032675626053 -0.4851870642872803 0 +-0.09534135289842151 -0.4853066049090289 0 +-0.1074560002018705 -0.470655815012751 0 +-0.1650657306492492 -0.4245543373820685 0 +-0.1453143795111455 -0.4258815305619718 0 +-0.1580169952162084 -0.4106237284550109 0 +0.1821021673928472 -0.4843565534959769 0 +0.1796549932076049 -0.4689057037772295 0 +0.1646248285630099 -0.4844849513528274 0 +0.1759918107776064 -0.4536474508437578 0 +0.1475528258147579 -0.4845491502812526 0 +0.1712172356369801 -0.438952819536446 0 +0.1571529433988375 -0.4543615710484994 0 +0.1384398048931015 -0.4550885854914936 0 +0.1432787708393016 -0.4697039538075936 0 +0.1326713032674273 -0.4408914645728241 0 +0.1198862147378168 -0.4558421053365956 0 +0.1297195482936287 -0.4849359606414027 0 +0.1652122364263015 -0.424455468999986 0 +0.1577232035858306 -0.410982392253002 0 +0.1451673222596052 -0.4260661370309707 0 +0.1123889039251684 -0.4851878151134325 0 +0.107432823050538 -0.470661180504033 0 +0.0953212860201656 -0.4852972871949389 0 +-0.03878315998376487 -0.3183372355502568 0 +-0.03096337691956837 -0.3310134834908509 0 +-0.02332915433343615 -0.3160548279231276 0 +-0.02319382645391745 -0.3437145204711652 0 +-0.00775519345221667 -0.3144850300084531 0 +-0.01545084971874759 -0.3569748890845831 0 +-0.007725424859373924 -0.3427311668134373 0 +0.007725424859373453 -0.3427311668134373 0 +-2.550043509685906e-16 -0.3284874445422915 0 +0.01545084971874716 -0.3569748890845831 0 +0.02319424193637579 -0.3437156507110577 0 +0.00776280980563844 -0.3145062281216648 0 +-0.007725424859373879 -0.3687714371704865 0 +-1.674046443117443e-16 -0.38056798525639 0 +0.007725424859373498 -0.3687714371704865 0 +0.02333090977374484 -0.3160596831450828 0 +0.03096410932373218 -0.331015512315688 0 +0.03878358467476018 -0.318338430166976 0 +0.04294339569948907 -0.4870126999938629 0 +0.03521301516550093 -0.4744759143200624 0 +0.02563629975700288 -0.4874808247764775 0 +0.02701040631833341 -0.4621695530832287 0 +0.008567038488425396 -0.4876095484291275 0 +0.01834779867029599 -0.4500677052937866 0 +0.009032700295370774 -0.46261386953414 0 +-0.008889571872107 -0.4626111825551915 0 +5.823311845640106e-05 -0.4750851777296637 0 +-0.01828165327953631 -0.4500102380440832 0 +-0.02694674512204347 -0.4620806974445662 0 +-0.00851241588431439 -0.4875269418960385 0 +0.009293832611124256 -0.4381435250078195 0 +-4.70909894691327e-05 -0.4264185115088839 0 +-0.009286668575513446 -0.4381096940003871 0 +-0.02561621184689085 -0.4874093455836788 0 +-0.03519040310730925 -0.4743950534563569 0 +-0.04293988782979895 -0.4869713454756978 0 +2 6 0 225 +5331 +5332 +5333 +5334 +5335 +5336 +5337 +5338 +5339 +5340 +5341 +5342 +5343 +5344 +5345 +5346 +5347 +5348 +5349 +5350 +5351 +5352 +5353 +5354 +5355 +5356 +5357 +5358 +5359 +5360 +5361 +5362 +5363 +5364 +5365 +5366 +5367 +5368 +5369 +5370 +5371 +5372 +5373 +5374 +5375 +5376 +5377 +5378 +5379 +5380 +5381 +5382 +5383 +5384 +5385 +5386 +5387 +5388 +5389 +5390 +5391 +5392 +5393 +5394 +5395 +5396 +5397 +5398 +5399 +5400 +5401 +5402 +5403 +5404 +5405 +5406 +5407 +5408 +5409 +5410 +5411 +5412 +5413 +5414 +5415 +5416 +5417 +5418 +5419 +5420 +5421 +5422 +5423 +5424 +5425 +5426 +5427 +5428 +5429 +5430 +5431 +5432 +5433 +5434 +5435 +5436 +5437 +5438 +5439 +5440 +5441 +5442 +5443 +5444 +5445 +5446 +5447 +5448 +5449 +5450 +5451 +5452 +5453 +5454 +5455 +5456 +5457 +5458 +5459 +5460 +5461 +5462 +5463 +5464 +5465 +5466 +5467 +5468 +5469 +5470 +5471 +5472 +5473 +5474 +5475 +5476 +5477 +5478 +5479 +5480 +5481 +5482 +5483 +5484 +5485 +5486 +5487 +5488 +5489 +5490 +5491 +5492 +5493 +5494 +5495 +5496 +5497 +5498 +5499 +5500 +5501 +5502 +5503 +5504 +5505 +5506 +5507 +5508 +5509 +5510 +5511 +5512 +5513 +5514 +5515 +5516 +5517 +5518 +5519 +5520 +5521 +5522 +5523 +5524 +5525 +5526 +5527 +5528 +5529 +5530 +5531 +5532 +5533 +5534 +5535 +5536 +5537 +5538 +5539 +5540 +5541 +5542 +5543 +5544 +5545 +5546 +5547 +5548 +5549 +5550 +5551 +5552 +5553 +5554 +5555 +0.4041610814281969 -5.818858729986652e-17 0 +0.3936709110882579 0.08027587801050629 0 +0.4492194795015672 0.03680826618600964 0 +0.4424234374943193 0.1132303117106039 0 +0.4492049688171096 -0.03670782209538037 0 +0.3937240768669373 -0.08018202772652314 0 +0.4424235082148131 -0.1132425183703317 0 +0.3569748890845832 -0.03090169943749492 0 +0.3569748890845831 0.03090169943749463 0 +0.3881018379519701 0.1204461471907091 0 +0.4173902463148467 0.09745664750677473 0 +0.4128953981768912 0.1375189885809888 0 +0.422041250274067 0.05789490353788752 0 +0.446045157823761 0.07462680673195302 0 +0.398961176763138 0.04011287452059446 0 +0.4262370270647773 0.01881432789303909 0 +0.4736472831262781 0.05309568778826366 0 +0.4708424338352684 0.09016599435842021 0 +0.4736231989550693 -0.05306945014166342 0 +0.446001364988867 -0.07466843560578419 0 +0.4708220376497009 -0.09019648884451131 0 +0.4220057433226369 -0.05792477379755829 0 +0.4174167953324127 -0.09747390175477824 0 +0.4263441557749931 -0.01855953462135501 0 +0.3991378083762001 -0.03983743642155035 0 +0.3880640787244524 -0.1204821913560391 0 +0.4129191280146803 -0.137505569153942 0 +0.3664425432803722 -0.09879594088003479 0 +0.3751384522847173 -0.05590169943749491 0 +0.3484043383997638 -0.07357899550611724 0 +0.38056798525639 -0.01545084971874749 0 +0.3333817929127763 -0.04635254915624235 0 +0.3333817929127761 0.04635254915624198 0 +0.348290307787522 0.07366696586098925 0 +0.3751384522847171 0.05590169943749465 0 +0.3663133361829164 0.09887042327243947 0 +0.38056798525639 0.01545084971874729 0 +0.439773108598144 -0.152076289215474 0 +0.4690983005625053 -0.161772318296182 0 +0.4702554550911933 -0.1251742014852095 0 +0.4690983005625052 0.1617723182961822 0 +0.4397090379478816 0.1522231680149295 0 +0.4702348982632909 0.1251918797824755 0 +0.3294143533548576 -0.01549448349145115 0 +0.3569748890845831 -1.457167719820518e-16 0 +0.3294192187971101 0.01549624740171778 0 +0.4750305831784999 0.01755247366802163 0 +0.4504498804822116 0.0001098547840530274 0 +0.4750071783406647 -0.01743707828152451 0 +0.3852227275558017 0.1408998637047895 0 +0.4002630951119711 0.1292406816565216 0 +0.3977751622140608 0.1497113365593829 0 +0.4028463517968935 0.1089987804881591 0 +0.4150132853511591 0.1174848509947168 0 +0.390939171094934 0.1002707070667946 0 +0.4054440368974332 0.08892847185356667 0 +0.4297731985024587 0.1055488118374457 0 +0.4277823774423641 0.1253264720680864 0 +0.407943012044622 0.06900918518550259 0 +0.4197837262683389 0.07758019788581851 0 +0.4339115196362251 0.06642446702844605 0 +0.4318447373910508 0.08592396762325905 0 +0.4357918493745429 0.04715928729993824 0 +0.4477244877388269 0.05562667876705852 0 +0.4441691123557421 0.09391672930843487 0 +0.3962776224030524 0.06029820523717757 0 +0.4103675003271532 0.04916103660451843 0 +0.4128125798374339 0.02930168210713512 0 +0.4241370430215774 0.03838428054759641 0 +0.4016353183220616 0.0199414970212475 0 +0.4150852037274025 0.009717294852719393 0 +0.4375720512514902 0.02797444584306007 0 +0.4583499594456866 0.08255863950479751 0 +0.4565625955126857 0.101642752178918 0 +0.4599311853265641 0.06373143927399598 0 +0.4724193999290162 0.07141869464068638 0 +0.4612513757672668 0.04521378351101976 0 +0.4864050691424622 0.06044002745943761 0 +0.4853053686926881 0.07855875819270748 0 +0.4863999414777377 -0.06042510132682209 0 +0.4724161031183064 -0.07140459704007315 0 +0.4853053686926883 -0.07855875819270688 0 +0.4599154830032217 -0.06371272748224664 0 +0.4583032898813031 -0.0826044962417586 0 +0.4612084175202023 -0.04515924185482119 0 +0.4476804497145404 -0.05561984566904847 0 +0.4441409283410597 -0.0939570120444839 0 +0.4565145289361416 -0.1017055889166763 0 +0.4357956549798426 -0.04710235221810469 0 +0.4338455008269542 -0.06648233625277696 0 +0.4197370672062928 -0.07765102380721856 0 +0.4318178648288214 -0.08596846437353031 0 +0.4079467153912933 -0.06902496809891409 0 +0.4053703026165697 -0.08905720658307106 0 +0.4297677477932405 -0.1055799096537976 0 +0.4377897359414236 -0.02767080162244662 0 +0.4241804566017087 -0.03828016435310286 0 +0.4127613523403497 -0.02927011445814227 0 +0.4103860972510066 -0.0491534094010103 0 +0.4149092466403577 -0.009486634599488408 0 +0.4017689840671456 -0.01969426095157637 0 +0.3964626763292499 -0.06001628251139313 0 +0.4150257225950141 -0.1174963399390537 0 +0.4277816674473218 -0.1253412214406081 0 +0.4027917635527428 -0.109087816612782 0 +0.4002215871419656 -0.1292898718280399 0 +0.3909173678754043 -0.1002777078061499 0 +0.385178624457118 -0.1409547033388714 0 +0.3977639909218998 -0.1497200004415377 0 +0.3733380120392378 -0.1311879473558066 0 +0.3766730683622522 -0.110417784718296 0 +0.3622985657526743 -0.1202028619557725 0 +0.3802646491733655 -0.08946678094315777 0 +0.3523728037921416 -0.1081945180019657 0 +0.3842202338847843 -0.0684016994374949 0 +0.3706989135141038 -0.07739370848205458 0 +0.3614823262948568 -0.06497718711437869 0 +0.3568994926439928 -0.08657614393784732 0 +0.3660566706846502 -0.04340169943749492 0 +0.3531065013403918 -0.05194294599978572 0 +0.3431708037013792 -0.09563427780159188 0 +0.3871129711111623 -0.04790836450979128 0 +0.3898102525557209 -0.02770115954177154 0 +0.3779096281771879 -0.03559000787057022 0 +0.3923645333422935 -0.007725424859373774 0 +0.3687714371704866 -0.02317627457812121 0 +0.3352246189934807 -0.08217224938159715 0 +0.340542509938918 -0.0601708840375042 0 +0.3279594925553528 -0.06835140716864793 0 +0.3451783409986797 -0.03862712429686864 0 +0.3215852448268729 -0.05407797401561606 0 +0.3215852448268727 0.05407797401561565 0 +0.3279430018317058 0.06836502510853865 0 +0.3405032271544806 0.06020199384246181 0 +0.3351650562645828 0.08222271742539256 0 +0.3451783409986796 0.03862712429686831 0 +0.3530609224007786 0.05197750334467555 0 +0.3429831385222811 0.09580450684926685 0 +0.3660566706846501 0.04340169943749464 0 +0.3613620986947569 0.0650655371927446 0 +0.3704438802948239 0.07756553719274462 0 +0.3566675267048638 0.08672937494799457 0 +0.3842202338847841 0.06840169943749466 0 +0.3802429523814249 0.08948474179026553 0 +0.3523738975748126 0.1081960551311473 0 +0.3687714371704865 0.02317627457812096 0 +0.3778856081598813 0.0356286059101 0 +0.3897420003322752 0.02781427971130009 0 +0.3870375156379746 0.04802622737569356 0 +0.3923645333422935 0.007725424859373614 0 +0.3621639760338106 0.120325365592652 0 +0.3766952924328199 0.1104617068680749 0 +0.373331135642998 0.1312026713691191 0 +0.4109779199802433 -0.1577295240130644 0 +0.4244554689999864 -0.1652122364263016 0 +0.4260267448359918 -0.1452100003481133 0 +0.4389528195364463 -0.17121723563698 0 +0.4409102763145926 -0.1326492742186962 0 +0.453647450843758 -0.1759918107776063 0 +0.4543629215270752 -0.1571502136159273 0 +0.4697018463360643 -0.1432689222269233 0 +0.4551174985364427 -0.1384076959639799 0 +0.4845491502812527 -0.1475528258147576 0 +0.4849350644226799 -0.1297203953395986 0 +0.4559537137856324 -0.119796096838146 0 +0.4689057037772296 -0.1796549932076048 0 +0.4843565534959769 -0.1821021673928471 0 +0.4844849513528274 -0.1646248285630096 0 +0.470641745695395 -0.1074548688847192 0 +0.4851835223720863 -0.1123966970493871 0 +0.4852688690232497 -0.09536404992012881 0 +0.4843565534959769 0.1821021673928472 0 +0.4689057037772295 0.1796549932076048 0 +0.4844849513528274 0.1646248285630099 0 +0.4536474508437579 0.1759918107776064 0 +0.4845491502812526 0.1475528258147579 0 +0.438952819536446 0.17121723563698 0 +0.4543615710484994 0.1571529433988375 0 +0.4550885855108695 0.1384398049230599 0 +0.4697039538075936 0.1432787708393016 0 +0.4408914711906694 0.1326713142827535 0 +0.455842105436243 0.119886214891889 0 +0.4849359606414027 0.1297195482936287 0 +0.4244554689999861 0.1652122364263015 0 +0.4109823923398119 0.157723203720054 0 +0.4260661375390326 0.1451673230451588 0 +0.4851878167671149 0.1123889068585343 0 +0.4706611895015903 0.1074328390150671 0 +0.4852972871949389 0.0953212860201656 0 +0.3183372355502569 -0.03878315998376481 0 +0.331013483490851 -0.03096337691956831 0 +0.3160548279231276 -0.02332915433343609 0 +0.3437145204711654 -0.0231938264539174 0 +0.3144850300084532 -0.007755193452216612 0 +0.3569748890845832 -0.01545084971874753 0 +0.3427311668134374 -0.007725424859373864 0 +0.3427311668134373 0.007725424859373524 0 +0.3284874445422916 -1.942890293094024e-16 0 +0.3569748890845831 0.01545084971874724 0 +0.3437156507110578 0.02319424193637587 0 +0.314506228121665 0.007762809805638506 0 +0.3687714371704865 -0.007725424859373818 0 +0.38056798525639 -1.022216395702601e-16 0 +0.3687714371704865 0.00772542485937357 0 +0.3160596831450828 0.02333090977374493 0 +0.331015512315688 0.03096410932373227 0 +0.3183384301669761 0.03878358467476029 0 +0.4870131251889816 0.04294457701345043 0 +0.4744773088497277 0.03521715768658062 0 +0.4874810328960626 0.02563710429380231 0 +0.4621751602866248 0.0270239733407072 0 +0.4876084904351732 0.008568116018503614 0 +0.4500917818812641 0.01838585549441099 0 +0.4626237443426565 0.009054875211777417 0 +0.4626252612757636 -0.008859809759049285 0 +0.47507971101344 6.437318051300336e-05 0 +0.4501049772050423 -0.01817976140948179 0 +0.4621062116718946 -0.02690941802789594 0 +0.487516376226602 -0.00851489555954708 0 +0.4382184065536224 0.009385189592347848 0 +0.4265186541175239 0.0001249469496662515 0 +0.4382376350124695 -0.009131684387869383 0 +0.4874028775703341 -0.02561811760810861 0 +0.47446202623478 -0.03513104370807847 0 +0.4869813407063202 -0.04292950768633423 0 +2 7 0 317 +5556 +5557 +5558 +5559 +5560 +5561 +5562 +5563 +5564 +5565 +5566 +5567 +5568 +5569 +5570 +5571 +5572 +5573 +5574 +5575 +5576 +5577 +5578 +5579 +5580 +5581 +5582 +5583 +5584 +5585 +5586 +5587 +5588 +5589 +5590 +5591 +5592 +5593 +5594 +5595 +5596 +5597 +5598 +5599 +5600 +5601 +5602 +5603 +5604 +5605 +5606 +5607 +5608 +5609 +5610 +5611 +5612 +5613 +5614 +5615 +5616 +5617 +5618 +5619 +5620 +5621 +5622 +5623 +5624 +5625 +5626 +5627 +5628 +5629 +5630 +5631 +5632 +5633 +5634 +5635 +5636 +5637 +5638 +5639 +5640 +5641 +5642 +5643 +5644 +5645 +5646 +5647 +5648 +5649 +5650 +5651 +5652 +5653 +5654 +5655 +5656 +5657 +5658 +5659 +5660 +5661 +5662 +5663 +5664 +5665 +5666 +5667 +5668 +5669 +5670 +5671 +5672 +5673 +5674 +5675 +5676 +5677 +5678 +5679 +5680 +5681 +5682 +5683 +5684 +5685 +5686 +5687 +5688 +5689 +5690 +5691 +5692 +5693 +5694 +5695 +5696 +5697 +5698 +5699 +5700 +5701 +5702 +5703 +5704 +5705 +5706 +5707 +5708 +5709 +5710 +5711 +5712 +5713 +5714 +5715 +5716 +5717 +5718 +5719 +5720 +5721 +5722 +5723 +5724 +5725 +5726 +5727 +5728 +5729 +5730 +5731 +5732 +5733 +5734 +5735 +5736 +5737 +5738 +5739 +5740 +5741 +5742 +5743 +5744 +5745 +5746 +5747 +5748 +5749 +5750 +5751 +5752 +5753 +5754 +5755 +5756 +5757 +5758 +5759 +5760 +5761 +5762 +5763 +5764 +5765 +5766 +5767 +5768 +5769 +5770 +5771 +5772 +5773 +5774 +5775 +5776 +5777 +5778 +5779 +5780 +5781 +5782 +5783 +5784 +5785 +5786 +5787 +5788 +5789 +5790 +5791 +5792 +5793 +5794 +5795 +5796 +5797 +5798 +5799 +5800 +5801 +5802 +5803 +5804 +5805 +5806 +5807 +5808 +5809 +5810 +5811 +5812 +5813 +5814 +5815 +5816 +5817 +5818 +5819 +5820 +5821 +5822 +5823 +5824 +5825 +5826 +5827 +5828 +5829 +5830 +5831 +5832 +5833 +5834 +5835 +5836 +5837 +5838 +5839 +5840 +5841 +5842 +5843 +5844 +5845 +5846 +5847 +5848 +5849 +5850 +5851 +5852 +5853 +5854 +5855 +5856 +5857 +5858 +5859 +5860 +5861 +5862 +5863 +5864 +5865 +5866 +5867 +5868 +5869 +5870 +5871 +5872 +-0.4063674440766498 -0.03115592425354164 0 +-0.4117571933601877 0.08573798977015037 0 +-0.3666101356718311 -0.06562225758222823 0 +-0.4032126760502749 -0.102224797960672 0 +-0.45 -0.1199358737117775 0 +-0.4523639040140627 -0.05009984872014082 0 +-0.36927605630165 0.09286899488507523 0 +-0.3577793243113044 0.04106395075899455 0 +-0.4558785966800938 0.1428689948850752 0 +-0.4058785966800939 0.1294715352635191 0 +-0.4090912727303688 0.027246737302847 0 +-0.3542943841046284 -0.01493914262875799 0 +-0.4558785966800938 0.07620232821840843 0 +-0.4532126760502749 0.01771107575110505 0 +-0.3467025274574717 -0.08281112879111416 0 +-0.3625943897172608 -0.1035218069097689 0 +-0.3841165565757614 -0.08472413935222523 0 +-0.3811904986878275 -0.1215839781827588 0 +-0.3865177438861905 -0.0484333863733423 0 +-0.4048190140754124 -0.06673465656256422 0 +-0.4016063380251375 -0.1377149393587799 0 +-0.4249999999999999 -0.1465704772343326 0 +-0.449118095489748 -0.1565605194847956 0 +-0.4747588368988832 -0.1276627501162038 0 +-0.474118095489748 -0.1632592492955737 0 +-0.4753192079499998 -0.09299621029578478 0 +-0.4265719455371496 -0.111127431043413 0 +-0.4279764110216812 -0.07590908148909294 0 +-0.4514247580743073 -0.08471499682565492 0 +-0.4291083100764821 -0.04105376342130439 0 +-0.4759330771226944 -0.05873130658855783 0 +-0.3480354877723811 0.09643449744253765 0 +-0.3390561588402846 0.07205487124510974 0 +-0.3625773264908719 0.0678689948850752 0 +-0.3326133934362725 0.04666625761895565 0 +-0.3905166248309189 0.08930349232761281 0 +-0.3859367689495043 0.06257580918789379 0 +-0.3288095006435526 0.02019061875575141 0 +-0.4779392983400469 0.1714344974425376 0 +-0.4522130979427699 0.1675200177788596 0 +-0.4308785966800939 0.1361702650742971 0 +-0.4272726755583249 0.1603968751719659 0 +-0.4338178950201408 0.1143034923276128 0 +-0.4088178950201408 0.1076047625168347 0 +-0.4029392983400469 0.1513383080102034 0 +-0.3551487606810009 0.01332794941933561 0 +-0.3273654913601414 -0.007174703600748099 0 +-0.3831244133384486 0.03452672072628365 0 +-0.3813024514396552 0.00585900085069503 0 +-0.4104242330452782 0.05649236353649869 0 +-0.4066968847258728 -0.00101362147491151 0 +-0.3814931414067109 -0.02238384521695683 0 +-0.4779392983400469 0.07143449744253746 0 +-0.4779392983400469 0.1047678307758708 0 +-0.4558785966800938 0.1095356615517418 0 +-0.4779392983400469 0.1381011641092042 0 +-0.4338178950201408 0.0809701589942794 0 +-0.4763391523490751 -0.02483894265448979 0 +-0.4529380484712132 -0.0159915872131583 0 +-0.476533938638537 0.008758050061071115 0 +-0.4298190140754124 -0.00676671970667566 0 +-0.4766063380251375 0.04218887120888577 0 +-0.4311519743903218 0.02247890652697603 0 +-0.4324849347052313 0.05172453276062772 0 +-0.4545456363651844 0.04695670198475674 0 +-0.3300137553962306 -0.03369303330136618 0 +-0.358854143615984 -0.04090555014698232 0 +-0.3367124852070087 -0.05869303330136619 0 +-0.3829681729645327 0.1347513420379559 0 +-0.387577326490872 0.1111702650742971 0 +-0.3646287325623319 0.1163993006167126 0 +-0.336748723350292 -0.09140556439555712 0 +-0.3441832530742513 -0.102148272467152 0 +-0.3544788033038651 -0.09329664892523887 0 +-0.352134055546885 -0.1125099597829095 0 +-0.3566563315646514 -0.07421669318667119 0 +-0.364602262694546 -0.08457203224599855 0 +-0.3605865167399757 -0.1224715815735392 0 +-0.3751306534332438 -0.0754056171713723 0 +-0.3734776980100636 -0.09400623708793795 0 +-0.3826713562518629 -0.1031220254278372 0 +-0.371748088752928 -0.1126949470385889 0 +-0.3934388315486786 -0.09370141392840249 0 +-0.3920541679783788 -0.1120239375298314 0 +-0.3699677119959628 -0.1314644495177685 0 +-0.3765639397790108 -0.05702782197778526 0 +-0.3854098492796234 -0.06648499619233923 0 +-0.3955671209492547 -0.05768590529981438 0 +-0.3945612029152623 -0.0756351397661606 0 +-0.3964715479933701 -0.03983895076889933 0 +-0.4056221830879811 -0.04898958586351029 0 +-0.4040158450628436 -0.08447972726161813 0 +-0.3795716870523693 -0.1402468725786189 0 +-0.391126537482186 -0.129955400003038 0 +-0.3899270261116967 -0.1481928037085135 0 +-0.4024095070377062 -0.119969868659726 0 +-0.4008031690125687 -0.1554600100578338 0 +-0.4125 -0.1598877789956102 0 +-0.4242316567634909 -0.165673191868295 0 +-0.4369499174177463 -0.1518289622753818 0 +-0.4362907045083649 -0.1706682129935265 0 +-0.4374999999999999 -0.133253175473055 0 +-0.449559047744874 -0.1382481965982865 0 +-0.4486771432346219 -0.1748728423713047 0 +-0.4623517278074605 -0.1240396112698555 0 +-0.4619732791810412 -0.1419820360913669 0 +-0.4744640006543399 -0.1453414766761317 0 +-0.4615879595473535 -0.1600397218659777 0 +-0.4872867827368813 -0.1308088313291303 0 +-0.487059047744874 -0.1482962913144537 0 +-0.461177143234622 -0.1782222072766937 0 +-0.4628060790151759 -0.106393474688031 0 +-0.475040011011282 -0.1102431103972227 0 +-0.4875610524797229 -0.09669236210745653 0 +-0.4874411138544615 -0.1136789534361135 0 +-0.4875654437457881 -0.07990396621889602 0 +-0.4740064285228689 -0.1807741107851679 0 +-0.4870218254225809 -0.1652456895887628 0 +-0.4869473807779949 -0.1824778194707145 0 +-0.4133031690125687 -0.1421427082965562 0 +-0.4140965269075469 -0.1244110117914406 0 +-0.4258031690125686 -0.1288254065352787 0 +-0.4148748498403347 -0.1066995823911123 0 +-0.438296959103973 -0.1155192745635278 0 +-0.4156266505405542 -0.08902361484038909 0 +-0.4272938536131544 -0.09349257501695515 0 +-0.4396554004200418 -0.08036657539047627 0 +-0.4390101032285495 -0.09790569643447991 0 +-0.4402100853790339 -0.0629517670814816 0 +-0.4519222340004641 -0.06736761596631827 0 +-0.4508073339943318 -0.10223452343939 0 +-0.4163461912683384 -0.07139056222482776 0 +-0.4170312185256824 -0.05380321330933532 0 +-0.428563647088902 -0.05845329475800473 0 +-0.4176974466419204 -0.03623890888005628 0 +-0.4406715103908357 -0.04566331482529908 0 +-0.4632899304419747 -0.08896624915426554 0 +-0.463721739232897 -0.07167476850760066 0 +-0.4756163086571433 -0.07585102228772302 0 +-0.464090886323196 -0.05449465819224794 0 +-0.4878581186253274 -0.06284864532340273 0 +-0.3374152035077467 0.09821724872126887 0 +-0.3319509182743019 0.08637784043330124 0 +-0.3432278433279406 0.08435945362877084 0 +-0.3272022953376428 0.07424793241157551 0 +-0.3586557720370156 0.09465174616380645 0 +-0.3544937180343714 0.08237375596867033 0 +-0.323168396512317 0.06184950064546333 0 +-0.365926691396261 0.08036899488507521 0 +-0.3508684249861018 0.06989508702408848 0 +-0.3477491344503053 0.05710407856914314 0 +-0.3355478710266686 0.05946591359459533 0 +-0.3598979455548185 0.05451792524611736 0 +-0.3452628412696897 0.04394063416934106 0 +-0.3198715845720038 0.04918849775272255 0 +-0.3798963405662844 0.09108624360634401 0 +-0.3768356222729477 0.07834150214610289 0 +-0.3879730324322757 0.07614050052077656 0 +-0.3742018980512143 0.06519987156976575 0 +-0.4011369090955533 0.08752074104888159 0 +-0.399263286789625 0.0738471378361253 0 +-0.3720838625006304 0.0516631372888296 0 +-0.3172931591037893 0.03631603304187456 0 +-0.3304367892419677 0.03350761153954489 0 +-0.3154921312015618 0.02319749786965946 0 +-0.3433942535828574 0.03050146160285762 0 +-0.3143455365183743 0.009916331040158242 0 +-0.4889696491700234 0.1857172487212688 0 +-0.4759682109368186 0.1845883080971602 0 +-0.4650232511242236 0.169768413789481 0 +-0.4630678492444793 0.1827320105570391 0 +-0.4669089475100704 0.1571517461638064 0 +-0.4540449177623382 0.1551741079735454 0 +-0.4503291032849426 0.180057681849478 0 +-0.4433785966800938 0.1395196299796862 0 +-0.4415474386686287 0.1518085336491968 0 +-0.4290739472491304 0.1483221773451223 0 +-0.4397021437878664 0.1641207802142288 0 +-0.4183785966800939 0.1328209001689081 0 +-0.4166690760130441 0.1445978891052803 0 +-0.4377788547574347 0.1766110389541415 0 +-0.4448482458501173 0.128586243606344 0 +-0.4323482458501173 0.125236878700955 0 +-0.4213178950201408 0.1109541274222238 0 +-0.4198482458501173 0.1218875137955659 0 +-0.4227875441901642 0.1000207410488816 0 +-0.4102875441901642 0.09667137614349255 0 +-0.4073482458501173 0.1185381488901769 0 +-0.4254224717356773 0.1725137699171204 0 +-0.4150031093980943 0.1561656019619105 0 +-0.4133066840211706 0.1677399715135958 0 +-0.4044089475100704 0.1404049216368612 0 +-0.4014696491700235 0.1622716943835456 0 +-0.3278621862404711 0.006542134373792703 0 +-0.3136236309794812 -0.003616667407217734 0 +-0.3420822475637995 0.01682022016208523 0 +-0.3412898301825141 0.002952394623810681 0 +-0.3563113257687829 0.02725190178875181 0 +-0.3545959607335213 -0.000780131700371398 0 +-0.3409098458275699 -0.01098541815692254 0 +-0.3704589219278799 0.03784241578568955 0 +-0.3692258738239964 0.0238013404767934 0 +-0.382169708547047 0.02022148342041652 0 +-0.3683602260437662 0.009628900059364572 0 +-0.3960261162428558 0.03097422328406875 0 +-0.3951488051983559 0.01658508556154654 0 +-0.3679039528320446 -0.004560541808748356 0 +-0.3843981667582289 0.0486336237456189 0 +-0.3979306462915242 0.05974582151997583 0 +-0.3969435547606932 0.0453737172053531 0 +-0.4110907132027329 0.07111517665332454 0 +-0.4097577528878235 0.04186955041967284 0 +-0.3811599628441451 -0.00826236029390694 0 +-0.36804189911716 -0.01863390394307178 0 +-0.3943815677705924 0.002362891736644333 0 +-0.3940841205335416 -0.01189162410369683 0 +-0.4075744417408608 0.01329779281114265 0 +-0.4062717461502432 -0.01577076254686665 0 +-0.3945014203036855 -0.0262820468261834 0 +-0.4889696491700234 0.06905058205460197 0 +-0.4889696491700234 0.08571724872126865 0 +-0.4779392983400469 0.08810116410920413 0 +-0.4889696491700234 0.1023839153879354 0 +-0.4669089475100704 0.07381841283047294 0 +-0.4669089475100704 0.09048507949713963 0 +-0.4889696491700234 0.119050582054602 0 +-0.4558785966800938 0.09286899488507513 0 +-0.4669089475100704 0.1071517461638063 0 +-0.4669089475100704 0.123818412830473 0 +-0.4779392983400469 0.1214344974425375 0 +-0.4558785966800938 0.1262023282184085 0 +-0.4669089475100704 0.1404850794971397 0 +-0.4889696491700234 0.1357172487212687 0 +-0.4448482458501173 0.07858624360634392 0 +-0.4448482458501173 0.09525291027301058 0 +-0.4338178950201408 0.09763682566094609 0 +-0.4448482458501173 0.1119195769396773 0 +-0.4227875441901642 0.08335407438221488 0 +-0.4889696491700234 0.1523839153879354 0 +-0.4779392983400469 0.1547678307758709 0 +-0.4889696491700234 0.1690505820546021 0 +-0.4880185760167193 -0.04596039787741138 0 +-0.4761559928506178 -0.04175624398122721 0 +-0.4881203169913862 -0.02915379509853731 0 +-0.464381115009128 -0.03743101323337764 0 +-0.4881954993553531 -0.012384489031165 0 +-0.4526749391659443 -0.03301317262056318 0 +-0.4645943611565969 -0.02047503797535771 0 +-0.464750068028087 -0.003598053947047433 0 +-0.4764439342473837 -0.008030264959023401 0 +-0.4531016838198164 0.0008951635400317379 0 +-0.4648484521110474 0.01320113356725708 0 +-0.4882423004830448 0.004345631315240973 0 +-0.4410437034475224 -0.02849565458175479 0 +-0.4295212097423849 -0.0238326344050221 0 +-0.4413232507698654 -0.01145320905739407 0 +-0.4181221830879811 -0.01900561743556602 0 +-0.4415158450628437 0.005472178022214697 0 +-0.488275756835201 0.02105750095890185 0 +-0.4765782425742316 0.02548437685580405 0 +-0.4882939177411899 0.0377486418824501 0 +-0.4649095070377062 0.02994997347999541 0 +-0.4883031690125688 0.05442776893777613 0 +-0.4184019469908545 -0.004015405017847023 0 +-0.4190532003326147 0.01059420477913287 0 +-0.4303540510120576 0.007976359290473575 0 +-0.4201216235603453 0.02486282191491151 0 +-0.4421823252202984 0.02009499113904054 0 +-0.4207881037178 0.03948563503173736 0 +-0.4318184545477766 0.03710171964380187 0 +-0.4435152855352079 0.04934061737269223 0 +-0.4428488053777531 0.03471780425586638 0 +-0.4441817656926625 0.06396343048951808 0 +-0.4552121165226391 0.06157951510158258 0 +-0.4538791562077296 0.03233388886793089 0 +-0.4214545838752548 0.05410844814856321 0 +-0.4221210640327095 0.06873126126538905 0 +-0.433151414862686 0.06634734587745356 0 +-0.4655759871951609 0.04457278659682126 0 +-0.4662424673526157 0.0591955997136471 0 +-0.4772728181825922 0.05681168432571162 0 +-0.314318182161094 -0.0168818832372869 0 +-0.3282129422144948 -0.02064733755236653 0 +-0.3158623915607343 -0.02989913587268826 0 +-0.3416132157232528 -0.02465764544179719 0 +-0.3184142950692085 -0.04272842116093516 0 +-0.3558029025444087 -0.02832566810646748 0 +-0.3445702894907754 -0.03726275953538634 0 +-0.3479069631492382 -0.04976616014475119 0 +-0.3332302413811764 -0.04622863810079003 0 +-0.3624130024738735 -0.05334941641159065 0 +-0.3516613104394199 -0.06215764544179721 0 +-0.3217636599745975 -0.05522842116093517 0 +-0.3695491932918998 -0.03211249839100341 0 +-0.3833063018080536 -0.03580416162894932 0 +-0.3728815221877572 -0.04461706317599965 0 +-0.3259682893523757 -0.06761485988719214 0 +-0.3414440424164223 -0.07086121137336782 0 +-0.3309633104776072 -0.07967390763206612 0 +-0.3908177224829908 0.1547520873345905 0 +-0.3927371622031954 0.1432932623646427 0 +-0.3806392505562474 0.1466320734707668 0 +-0.3946628665043028 0.1318676316477057 0 +-0.370941874320532 0.1379491556442166 0 +-0.3967279615854829 0.1203209001689081 0 +-0.3852653221003769 0.1229548871842377 0 +-0.3761151621718115 0.1137859738147997 0 +-0.373609047426454 0.1257647342238173 0 +-0.378426691396261 0.1020196299796862 0 +-0.3671974275766685 0.1043934412796415 0 +-0.361740790576553 0.1287419628388491 0 +-0.3981976107555064 0.1093875137955659 0 +-0.3996672599255298 0.09845412742222376 0 +-0.3890469756608954 0.100236878700955 0 +-0.3530583590580588 0.1190440954180221 0 +-0.3560854814631709 0.1066316564889608 0 +-0.3449383235471002 0.1088656384127424 0 +2 8 0 225 +5873 +5874 +5875 +5876 +5877 +5878 +5879 +5880 +5881 +5882 +5883 +5884 +5885 +5886 +5887 +5888 +5889 +5890 +5891 +5892 +5893 +5894 +5895 +5896 +5897 +5898 +5899 +5900 +5901 +5902 +5903 +5904 +5905 +5906 +5907 +5908 +5909 +5910 +5911 +5912 +5913 +5914 +5915 +5916 +5917 +5918 +5919 +5920 +5921 +5922 +5923 +5924 +5925 +5926 +5927 +5928 +5929 +5930 +5931 +5932 +5933 +5934 +5935 +5936 +5937 +5938 +5939 +5940 +5941 +5942 +5943 +5944 +5945 +5946 +5947 +5948 +5949 +5950 +5951 +5952 +5953 +5954 +5955 +5956 +5957 +5958 +5959 +5960 +5961 +5962 +5963 +5964 +5965 +5966 +5967 +5968 +5969 +5970 +5971 +5972 +5973 +5974 +5975 +5976 +5977 +5978 +5979 +5980 +5981 +5982 +5983 +5984 +5985 +5986 +5987 +5988 +5989 +5990 +5991 +5992 +5993 +5994 +5995 +5996 +5997 +5998 +5999 +6000 +6001 +6002 +6003 +6004 +6005 +6006 +6007 +6008 +6009 +6010 +6011 +6012 +6013 +6014 +6015 +6016 +6017 +6018 +6019 +6020 +6021 +6022 +6023 +6024 +6025 +6026 +6027 +6028 +6029 +6030 +6031 +6032 +6033 +6034 +6035 +6036 +6037 +6038 +6039 +6040 +6041 +6042 +6043 +6044 +6045 +6046 +6047 +6048 +6049 +6050 +6051 +6052 +6053 +6054 +6055 +6056 +6057 +6058 +6059 +6060 +6061 +6062 +6063 +6064 +6065 +6066 +6067 +6068 +6069 +6070 +6071 +6072 +6073 +6074 +6075 +6076 +6077 +6078 +6079 +6080 +6081 +6082 +6083 +6084 +6085 +6086 +6087 +6088 +6089 +6090 +6091 +6092 +6093 +6094 +6095 +6096 +6097 +1.103563378790027e-16 0.4041610814281968 0 +-0.0802758780105063 0.3936709110882579 0 +-0.03680826618600961 0.449219479501567 0 +-0.1132303117106039 0.4424234374943193 0 +0.03670782209538041 0.4492049688171096 0 +0.08018202772652319 0.3937240768669373 0 +0.1132425183703317 0.4424235082148131 0 +-0.03090169943749462 0.356974889084583 0 +0.03090169943749495 0.3569748890845831 0 +-0.1204461471907091 0.3881018379519701 0 +-0.09745664750677474 0.4173902463148467 0 +-0.1375189885809889 0.4128953981768912 0 +-0.05789490353788752 0.422041250274067 0 +-0.074626806731953 0.446045157823761 0 +-0.04011287452059443 0.398961176763138 0 +-0.01881432789303907 0.4262370270647772 0 +-0.05309568778826366 0.4736472831262781 0 +-0.09016599435842021 0.4708424338352684 0 +0.05306945014166345 0.4736231989550693 0 +0.07466843560578423 0.4460013649888671 0 +0.09019648884451134 0.4708220376497009 0 +0.05792477379755834 0.4220057433226367 0 +0.09747390175477828 0.4174167953324127 0 +0.01855953462135505 0.4263441557749931 0 +0.03983743642155041 0.3991378083762001 0 +0.1204821913560392 0.3880640787244524 0 +0.137505569153942 0.4129191280146803 0 +-0.04635254915624198 0.3333817929127761 0 +-0.07366696586098925 0.3482903077875219 0 +-0.05590169943749465 0.3751384522847171 0 +-0.09887042327243949 0.3663133361829163 0 +-0.01545084971874725 0.3805679852563899 0 +0.09879594088003481 0.3664425432803722 0 +0.05590169943749495 0.3751384522847172 0 +0.07357899550611725 0.3484043383997638 0 +0.01545084971874753 0.38056798525639 0 +0.04635254915624237 0.3333817929127763 0 +-0.1617723182961822 0.4690983005625052 0 +-0.1522231680149295 0.4397090379478816 0 +-0.1251918797824755 0.4702348982632908 0 +0.1520762892154741 0.439773108598144 0 +0.161772318296182 0.4690983005625053 0 +0.1251742014852095 0.4702554550911934 0 +0.01549448349145113 0.3294143533548575 0 +1.665334536937735e-16 0.3569748890845831 0 +-0.01549624740171781 0.3294192187971101 0 +-0.01755247366802162 0.4750305831784999 0 +-0.0001098547840529991 0.4504498804822115 0 +0.01743707828152453 0.4750071783406647 0 +-0.1408998637047895 0.3852227275558017 0 +-0.1292406816565217 0.4002630951119711 0 +-0.149711336559383 0.3977751622140608 0 +-0.1089987804881591 0.4028463517968934 0 +-0.1174848509947168 0.4150132853511591 0 +-0.1002707070667947 0.390939171094934 0 +-0.08892847185356667 0.4054440368974332 0 +-0.1055488118374457 0.4297731985024587 0 +-0.1253264720680864 0.427782377442364 0 +-0.06900918518550257 0.407943012044622 0 +-0.07758019788581849 0.4197837262683389 0 +-0.06642446702844604 0.4339115196362251 0 +-0.08592396762325905 0.4318447373910508 0 +-0.04715928729993821 0.4357918493745428 0 +-0.05562667876705849 0.4477244877388268 0 +-0.09391672930843487 0.4441691123557421 0 +-0.06029820523717756 0.3962776224030524 0 +-0.04916103660451841 0.4103675003271532 0 +-0.02930168210713509 0.4128125798374338 0 +-0.03838428054759638 0.4241370430215773 0 +-0.01994149702124746 0.4016353183220615 0 +-0.009717294852719351 0.4150852037274024 0 +-0.02797444584306004 0.4375720512514901 0 +-0.08255863950479751 0.4583499594456866 0 +-0.101642752178918 0.4565625955126857 0 +-0.06373143927399597 0.459931185326564 0 +-0.0714186946406864 0.4724193999290162 0 +-0.04521378351101973 0.4612513757672667 0 +-0.06044002745943761 0.4864050691424622 0 +-0.0785587581927075 0.4853053686926881 0 +0.06042510132682211 0.4863999414777377 0 +0.07140459704007318 0.4724161031183064 0 +0.0785587581927069 0.4853053686926883 0 +0.06371272748224667 0.4599154830032217 0 +0.08260449624175863 0.4583032898813033 0 +0.04515924185482123 0.4612084175202023 0 +0.05561984566904852 0.4476804497145404 0 +0.09395701204448394 0.4441409283410596 0 +0.1017055889166763 0.4565145289361416 0 +0.04710235221810474 0.4357956549798426 0 +0.066482336252777 0.4338455008269541 0 +0.07765102380721858 0.4197370672062928 0 +0.08596846437353034 0.4318178648288213 0 +0.06902496809891415 0.4079467153912932 0 +0.08905720658307111 0.4053703026165697 0 +0.1055799096537977 0.4297677477932405 0 +0.02767080162244666 0.4377897359414236 0 +0.03828016435310291 0.4241804566017087 0 +0.02927011445814232 0.4127613523403497 0 +0.04915340940101037 0.4103860972510065 0 +0.009486634599488453 0.4149092466403575 0 +0.01969426095157642 0.4017689840671456 0 +0.06001628251139318 0.3964626763292499 0 +0.1174963399390538 0.4150257225950141 0 +0.1253412214406081 0.4277816674473218 0 +0.109087816612782 0.4027917635527429 0 +0.1292898718280399 0.4002215871419657 0 +0.1002777078061499 0.3909173678754043 0 +0.1409547033388715 0.385178624457118 0 +0.1497200004415378 0.3977639909218998 0 +-0.05407797401561566 0.3215852448268727 0 +-0.06836502510853865 0.3279430018317058 0 +-0.0602019938424618 0.3405032271544804 0 +-0.08222271742539257 0.3351650562645828 0 +-0.0386271242968683 0.3451783409986796 0 +-0.05197750334467553 0.3530609224007786 0 +-0.09580450684926688 0.342983138522281 0 +-0.04340169943749463 0.3660566706846501 0 +-0.0650655371927446 0.3613620986947569 0 +-0.07756553719274462 0.3704438802948239 0 +-0.08672937494799458 0.3566675267048637 0 +-0.06840169943749466 0.3842202338847841 0 +-0.08948474179026555 0.3802429523814249 0 +-0.1081960551311473 0.3523738975748126 0 +-0.02317627457812094 0.3687714371704864 0 +-0.03562860591009998 0.3778856081598812 0 +-0.02781427971130007 0.3897420003322751 0 +-0.04802622737569355 0.3870375156379746 0 +-0.007725424859373571 0.3923645333422934 0 +-0.120325365592652 0.3621639760338106 0 +-0.1104617068680749 0.3766952924328199 0 +-0.1312026713691192 0.373331135642998 0 +0.1311879473558066 0.3733380120392378 0 +0.1104177847182961 0.3766730683622522 0 +0.1202028619557725 0.3622985657526743 0 +0.08946678094315781 0.3802646491733654 0 +0.1081945180019657 0.3523728037921415 0 +0.06840169943749495 0.3842202338847843 0 +0.0773937084820546 0.3706989135141037 0 +0.06497718711437869 0.3614823262948568 0 +0.08657614393784732 0.3568994926439927 0 +0.04340169943749495 0.3660566706846501 0 +0.05194294599978573 0.3531065013403917 0 +0.09563427780159184 0.3431708037013791 0 +0.04790836450979134 0.3871129711111623 0 +0.02770115954177158 0.3898102525557208 0 +0.03559000787057026 0.3779096281771878 0 +0.00772542485937382 0.3923645333422934 0 +0.02317627457812124 0.3687714371704865 0 +0.08217224938159715 0.3352246189934807 0 +0.06017088403750422 0.3405425099389179 0 +0.06835140716864796 0.3279594925553528 0 +0.03862712429686866 0.3451783409986797 0 +0.05407797401561608 0.3215852448268728 0 +-0.1821021673928472 0.4843565534959769 0 +-0.1796549932076049 0.4689057037772295 0 +-0.1646248285630099 0.4844849513528273 0 +-0.1759918107776064 0.4536474508437579 0 +-0.1475528258147579 0.4845491502812526 0 +-0.1712172356369801 0.438952819536446 0 +-0.1571529433988375 0.4543615710484994 0 +-0.13843980492306 0.4550885855108695 0 +-0.1432787708393017 0.4697039538075936 0 +-0.1326713142827535 0.4408914711906693 0 +-0.1198862148918891 0.4558421054362429 0 +-0.1297195482936287 0.4849359606414027 0 +-0.1652122364263016 0.4244554689999862 0 +-0.1577232037200541 0.4109823923398119 0 +-0.1451673230451589 0.4260661375390327 0 +-0.1123889068585343 0.4851878167671149 0 +-0.1074328390150671 0.4706611895015903 0 +-0.0953212860201656 0.4852972871949389 0 +0.1577295240130645 0.4109779199802433 0 +0.1652122364263016 0.4244554689999863 0 +0.1452100003481134 0.4260267448359918 0 +0.1712172356369801 0.4389528195364462 0 +0.1326492742186962 0.4409102763145926 0 +0.1759918107776064 0.453647450843758 0 +0.1571502136159273 0.4543629215270752 0 +0.1432689222269233 0.4697018463360643 0 +0.13840769596398 0.4551174985364427 0 +0.1475528258147576 0.4845491502812527 0 +0.1297203953395986 0.4849350644226799 0 +0.119796096838146 0.4559537137856325 0 +0.1796549932076048 0.4689057037772296 0 +0.1821021673928471 0.4843565534959769 0 +0.1646248285630096 0.4844849513528274 0 +0.1074548688847192 0.470641745695395 0 +0.1123966970493871 0.4851835223720863 0 +0.09536404992012881 0.4852688690232497 0 +0.03878315998376484 0.3183372355502568 0 +0.03096337691956833 0.3310134834908509 0 +0.02332915433343609 0.3160548279231275 0 +0.02319382645391741 0.3437145204711652 0 +0.007755193452216556 0.3144850300084531 0 +0.01545084971874756 0.3569748890845831 0 +0.007725424859373857 0.3427311668134373 0 +-0.007725424859373536 0.3427311668134373 0 +1.543903893619358e-16 0.3284874445422915 0 +-0.01545084971874723 0.3569748890845831 0 +-0.02319424193637587 0.3437156507110576 0 +-0.007762809805638564 0.3145062281216648 0 +0.007725424859373849 0.3687714371704865 0 +1.387655506729551e-16 0.3805679852563899 0 +-0.007725424859373543 0.3687714371704865 0 +-0.02333090977374495 0.3160596831450828 0 +-0.03096410932373228 0.3310155123156879 0 +-0.03878358467476029 0.318338430166976 0 +-0.04294457701345043 0.4870131251889816 0 +-0.0352171576865806 0.4744773088497277 0 +-0.0256371042938023 0.4874810328960626 0 +-0.02702397334070718 0.4621751602866248 0 +-0.008568116018503606 0.4876084904351733 0 +-0.01838585549441097 0.450091781881264 0 +-0.009054875211777398 0.4626237443426565 0 +0.008859809759049308 0.4626252612757636 0 +-6.43731805129889e-05 0.47507971101344 0 +0.01817976140948183 0.4501049772050422 0 +0.02690941802789597 0.4621062116718946 0 +0.008514895559547091 0.487516376226602 0 +-0.009385189592347822 0.4382184065536224 0 +-0.0001249469496662159 0.4265186541175238 0 +0.009131684387869415 0.4382376350124694 0 +0.02561811760810862 0.4874028775703341 0 +0.03513104370807851 0.47446202623478 0 +0.04292950768633425 0.4869813407063202 0 +$EndNodes +$Elements +18 12192 1 12192 +1 2 1 24 +1 3 54 +2 54 55 +3 55 56 +4 56 57 +5 57 58 +6 58 59 +7 59 60 +8 60 61 +9 61 62 +10 62 63 +11 63 64 +12 64 65 +13 65 66 +14 66 67 +15 67 68 +16 68 69 +17 69 70 +18 70 71 +19 71 72 +20 72 73 +21 73 74 +22 74 75 +23 75 76 +24 76 2 +1 3 1 24 +25 4 77 +26 77 78 +27 78 79 +28 79 80 +29 80 81 +30 81 82 +31 82 83 +32 83 84 +33 84 85 +34 85 86 +35 86 87 +36 87 88 +37 88 89 +38 89 90 +39 90 91 +40 91 92 +41 92 93 +42 93 94 +43 94 95 +44 95 96 +45 96 97 +46 97 98 +47 98 99 +48 99 3 +1 6 1 24 +49 7 146 +50 146 147 +51 147 148 +52 148 149 +53 149 150 +54 150 151 +55 151 152 +56 152 153 +57 153 154 +58 154 155 +59 155 156 +60 156 157 +61 157 158 +62 158 159 +63 159 160 +64 160 161 +65 161 162 +66 162 163 +67 163 164 +68 164 165 +69 165 166 +70 166 167 +71 167 168 +72 168 6 +1 7 1 24 +73 8 169 +74 169 170 +75 170 171 +76 171 172 +77 172 173 +78 173 174 +79 174 175 +80 175 176 +81 176 177 +82 177 178 +83 178 179 +84 179 180 +85 180 181 +86 181 182 +87 182 183 +88 183 184 +89 184 185 +90 185 186 +91 186 187 +92 187 188 +93 188 189 +94 189 190 +95 190 191 +96 191 7 +1 9 1 24 +97 10 231 +98 231 232 +99 232 233 +100 233 234 +101 234 235 +102 235 236 +103 236 237 +104 237 238 +105 238 239 +106 239 240 +107 240 241 +108 241 242 +109 242 243 +110 243 244 +111 244 245 +112 245 246 +113 246 247 +114 247 248 +115 248 249 +116 249 250 +117 250 251 +118 251 252 +119 252 253 +120 253 9 +1 10 1 24 +121 11 254 +122 254 255 +123 255 256 +124 256 257 +125 257 258 +126 258 259 +127 259 260 +128 260 261 +129 261 262 +130 262 263 +131 263 264 +132 264 265 +133 265 266 +134 266 267 +135 267 268 +136 268 269 +137 269 270 +138 270 271 +139 271 272 +140 272 273 +141 273 274 +142 274 275 +143 275 276 +144 276 10 +1 12 1 24 +145 13 316 +146 316 317 +147 317 318 +148 318 319 +149 319 320 +150 320 321 +151 321 322 +152 322 323 +153 323 324 +154 324 325 +155 325 326 +156 326 327 +157 327 328 +158 328 329 +159 329 330 +160 330 331 +161 331 332 +162 332 333 +163 333 334 +164 334 335 +165 335 336 +166 336 337 +167 337 338 +168 338 12 +1 13 1 24 +169 1 339 +170 339 340 +171 340 341 +172 341 342 +173 342 343 +174 343 344 +175 344 345 +176 345 346 +177 346 347 +178 347 348 +179 348 349 +180 349 350 +181 350 351 +182 351 352 +183 352 353 +184 353 354 +185 354 355 +186 355 356 +187 356 357 +188 357 358 +189 358 359 +190 359 360 +191 360 361 +192 361 13 +1 15 1 24 +193 2 433 +194 433 434 +195 434 435 +196 435 436 +197 436 437 +198 437 438 +199 438 439 +200 439 440 +201 440 441 +202 441 442 +203 442 443 +204 443 444 +205 444 445 +206 445 446 +207 446 447 +208 447 448 +209 448 449 +210 449 450 +211 450 451 +212 451 452 +213 452 453 +214 453 454 +215 454 455 +216 455 1 +1 16 1 24 +217 12 456 +218 456 457 +219 457 458 +220 458 459 +221 459 460 +222 460 461 +223 461 462 +224 462 463 +225 463 464 +226 464 465 +227 465 466 +228 466 467 +229 467 468 +230 468 469 +231 469 470 +232 470 471 +233 471 472 +234 472 473 +235 473 474 +236 474 475 +237 475 476 +238 476 477 +239 477 478 +240 478 11 +1 17 1 24 +241 6 479 +242 479 480 +243 480 481 +244 481 482 +245 482 483 +246 483 484 +247 484 485 +248 485 486 +249 486 487 +250 487 488 +251 488 489 +252 489 490 +253 490 491 +254 491 492 +255 492 493 +256 493 494 +257 494 495 +258 495 496 +259 496 497 +260 497 498 +261 498 499 +262 499 500 +263 500 501 +264 501 4 +1 18 1 24 +265 9 502 +266 502 503 +267 503 504 +268 504 505 +269 505 506 +270 506 507 +271 507 508 +272 508 509 +273 509 510 +274 510 511 +275 511 512 +276 512 513 +277 513 514 +278 514 515 +279 515 516 +280 516 517 +281 517 518 +282 518 519 +283 519 520 +284 520 521 +285 521 522 +286 522 523 +287 523 524 +288 524 8 +2 3 2 960 +289 401 628 630 +290 628 629 630 +291 628 547 629 +292 630 629 549 +293 547 631 629 +294 631 632 629 +295 631 548 632 +296 629 632 549 +297 547 633 631 +298 633 634 631 +299 633 529 634 +300 631 634 548 +301 549 632 636 +302 632 635 636 +303 632 548 635 +304 636 635 531 +305 529 637 634 +306 637 638 634 +307 637 550 638 +308 634 638 548 +309 550 639 638 +310 639 640 638 +311 639 551 640 +312 638 640 548 +313 550 641 639 +314 641 642 639 +315 641 530 642 +316 639 642 551 +317 548 640 635 +318 640 643 635 +319 640 551 643 +320 635 643 531 +321 529 644 637 +322 644 645 637 +323 644 552 645 +324 637 645 550 +325 552 646 645 +326 646 647 645 +327 646 553 647 +328 645 647 550 +329 552 648 646 +330 648 649 646 +331 648 527 649 +332 646 649 553 +333 550 647 641 +334 647 650 641 +335 647 553 650 +336 641 650 530 +337 531 643 652 +338 643 651 652 +339 643 551 651 +340 652 651 555 +341 551 653 651 +342 653 654 651 +343 653 554 654 +344 651 654 555 +345 551 642 653 +346 642 655 653 +347 642 530 655 +348 653 655 554 +349 555 654 657 +350 654 656 657 +351 654 554 656 +352 657 656 526 +353 401 630 400 +354 630 658 400 +355 630 549 658 +356 400 658 399 +357 549 659 658 +358 659 660 658 +359 659 556 660 +360 658 660 399 +361 549 636 659 +362 636 661 659 +363 636 531 661 +364 659 661 556 +365 399 660 398 +366 660 662 398 +367 660 556 662 +368 398 662 397 +369 531 663 661 +370 663 664 661 +371 663 557 664 +372 661 664 556 +373 557 665 664 +374 665 666 664 +375 665 558 666 +376 664 666 556 +377 557 667 665 +378 667 668 665 +379 667 532 668 +380 665 668 558 +381 556 666 662 +382 666 669 662 +383 666 558 669 +384 662 669 397 +385 531 652 663 +386 652 670 663 +387 652 555 670 +388 663 670 557 +389 555 671 670 +390 671 672 670 +391 671 559 672 +392 670 672 557 +393 555 657 671 +394 657 673 671 +395 657 526 673 +396 671 673 559 +397 557 672 667 +398 672 674 667 +399 672 559 674 +400 667 674 532 +401 397 669 396 +402 669 675 396 +403 669 558 675 +404 396 675 395 +405 558 676 675 +406 676 677 675 +407 676 560 677 +408 675 677 395 +409 558 668 676 +410 668 678 676 +411 668 532 678 +412 676 678 560 +413 395 677 394 +414 677 679 394 +415 677 560 679 +416 394 679 393 +417 525 680 682 +418 680 681 682 +419 680 561 681 +420 682 681 563 +421 561 683 681 +422 683 684 681 +423 683 562 684 +424 681 684 563 +425 561 685 683 +426 685 686 683 +427 685 533 686 +428 683 686 562 +429 563 684 688 +430 684 687 688 +431 684 562 687 +432 688 687 535 +433 533 689 686 +434 689 690 686 +435 689 564 690 +436 686 690 562 +437 564 691 690 +438 691 692 690 +439 691 565 692 +440 690 692 562 +441 564 693 691 +442 693 694 691 +443 693 534 694 +444 691 694 565 +445 562 692 687 +446 692 695 687 +447 692 565 695 +448 687 695 535 +449 533 696 689 +450 696 697 689 +451 696 566 697 +452 689 697 564 +453 566 698 697 +454 698 699 697 +455 698 567 699 +456 697 699 564 +457 566 700 698 +458 700 701 698 +459 700 527 701 +460 698 701 567 +461 564 699 693 +462 699 702 693 +463 699 567 702 +464 693 702 534 +465 535 695 704 +466 695 703 704 +467 695 565 703 +468 704 703 569 +469 565 705 703 +470 705 706 703 +471 705 568 706 +472 703 706 569 +473 565 694 705 +474 694 707 705 +475 694 534 707 +476 705 707 568 +477 569 706 709 +478 706 708 709 +479 706 568 708 +480 709 708 425 +481 425 708 424 +482 708 710 424 +483 708 568 710 +484 424 710 423 +485 568 711 710 +486 711 712 710 +487 711 570 712 +488 710 712 423 +489 568 707 711 +490 707 713 711 +491 707 534 713 +492 711 713 570 +493 423 712 422 +494 712 714 422 +495 712 570 714 +496 422 714 421 +497 534 715 713 +498 715 716 713 +499 715 571 716 +500 713 716 570 +501 571 717 716 +502 717 718 716 +503 717 572 718 +504 716 718 570 +505 571 719 717 +506 719 720 717 +507 719 536 720 +508 717 720 572 +509 570 718 714 +510 718 721 714 +511 718 572 721 +512 714 721 421 +513 534 702 715 +514 702 722 715 +515 702 567 722 +516 715 722 571 +517 567 723 722 +518 723 724 722 +519 723 573 724 +520 722 724 571 +521 567 701 723 +522 701 725 723 +523 701 527 725 +524 723 725 573 +525 571 724 719 +526 724 726 719 +527 724 573 726 +528 719 726 536 +529 421 721 420 +530 721 727 420 +531 721 572 727 +532 420 727 419 +533 572 728 727 +534 728 729 727 +535 728 574 729 +536 727 729 419 +537 572 720 728 +538 720 730 728 +539 720 536 730 +540 728 730 574 +541 419 729 418 +542 729 731 418 +543 729 574 731 +544 418 731 417 +545 14 732 734 +546 732 733 734 +547 732 575 733 +548 734 733 577 +549 575 735 733 +550 735 736 733 +551 735 576 736 +552 733 736 577 +553 575 737 735 +554 737 738 735 +555 737 537 738 +556 735 738 576 +557 577 736 740 +558 736 739 740 +559 736 576 739 +560 740 739 539 +561 537 741 738 +562 741 742 738 +563 741 578 742 +564 738 742 576 +565 578 743 742 +566 743 744 742 +567 743 579 744 +568 742 744 576 +569 578 745 743 +570 745 746 743 +571 745 538 746 +572 743 746 579 +573 576 744 739 +574 744 747 739 +575 744 579 747 +576 739 747 539 +577 537 748 741 +578 748 749 741 +579 748 580 749 +580 741 749 578 +581 580 750 749 +582 750 751 749 +583 750 581 751 +584 749 751 578 +585 580 752 750 +586 752 753 750 +587 752 528 753 +588 750 753 581 +589 578 751 745 +590 751 754 745 +591 751 581 754 +592 745 754 538 +593 539 747 756 +594 747 755 756 +595 747 579 755 +596 756 755 583 +597 579 757 755 +598 757 758 755 +599 757 582 758 +600 755 758 583 +601 579 746 757 +602 746 759 757 +603 746 538 759 +604 757 759 582 +605 583 758 761 +606 758 760 761 +607 758 582 760 +608 761 760 525 +609 526 762 764 +610 762 763 764 +611 762 584 763 +612 764 763 586 +613 584 765 763 +614 765 766 763 +615 765 585 766 +616 763 766 586 +617 584 767 765 +618 767 768 765 +619 767 540 768 +620 765 768 585 +621 586 766 770 +622 766 769 770 +623 766 585 769 +624 770 769 542 +625 540 771 768 +626 771 772 768 +627 771 587 772 +628 768 772 585 +629 587 773 772 +630 773 774 772 +631 773 588 774 +632 772 774 585 +633 587 775 773 +634 775 776 773 +635 775 541 776 +636 773 776 588 +637 585 774 769 +638 774 777 769 +639 774 588 777 +640 769 777 542 +641 540 778 771 +642 778 779 771 +643 778 589 779 +644 771 779 587 +645 589 780 779 +646 780 781 779 +647 780 590 781 +648 779 781 587 +649 589 782 780 +650 782 783 780 +651 782 528 783 +652 780 783 590 +653 587 781 775 +654 781 784 775 +655 781 590 784 +656 775 784 541 +657 542 777 786 +658 777 785 786 +659 777 588 785 +660 786 785 592 +661 588 787 785 +662 787 788 785 +663 787 591 788 +664 785 788 592 +665 588 776 787 +666 776 789 787 +667 776 541 789 +668 787 789 591 +669 592 788 791 +670 788 790 791 +671 788 591 790 +672 791 790 385 +673 369 792 368 +674 792 793 368 +675 792 593 793 +676 368 793 367 +677 593 794 793 +678 794 795 793 +679 794 594 795 +680 793 795 367 +681 593 796 794 +682 796 797 794 +683 796 543 797 +684 794 797 594 +685 367 795 366 +686 795 798 366 +687 795 594 798 +688 366 798 365 +689 543 799 797 +690 799 800 797 +691 799 595 800 +692 797 800 594 +693 595 801 800 +694 801 802 800 +695 801 596 802 +696 800 802 594 +697 595 803 801 +698 803 804 801 +699 803 537 804 +700 801 804 596 +701 594 802 798 +702 802 805 798 +703 802 596 805 +704 798 805 365 +705 543 806 799 +706 806 807 799 +707 806 597 807 +708 799 807 595 +709 597 808 807 +710 808 809 807 +711 808 580 809 +712 807 809 595 +713 597 810 808 +714 810 752 808 +715 810 528 752 +716 808 752 580 +717 595 809 803 +718 809 748 803 +719 809 580 748 +720 803 748 537 +721 365 805 364 +722 805 811 364 +723 805 596 811 +724 364 811 363 +725 596 812 811 +726 812 813 811 +727 812 575 813 +728 811 813 363 +729 596 804 812 +730 804 737 812 +731 804 537 737 +732 812 737 575 +733 363 813 362 +734 813 732 362 +735 813 575 732 +736 362 732 14 +737 409 814 408 +738 814 815 408 +739 814 598 815 +740 408 815 407 +741 598 816 815 +742 816 817 815 +743 816 599 817 +744 815 817 407 +745 598 818 816 +746 818 819 816 +747 818 544 819 +748 816 819 599 +749 407 817 406 +750 817 820 406 +751 817 599 820 +752 406 820 405 +753 544 821 819 +754 821 822 819 +755 821 600 822 +756 819 822 599 +757 600 823 822 +758 823 824 822 +759 823 601 824 +760 822 824 599 +761 600 825 823 +762 825 826 823 +763 825 529 826 +764 823 826 601 +765 599 824 820 +766 824 827 820 +767 824 601 827 +768 820 827 405 +769 544 828 821 +770 828 829 821 +771 828 602 829 +772 821 829 600 +773 602 830 829 +774 830 831 829 +775 830 552 831 +776 829 831 600 +777 602 832 830 +778 832 648 830 +779 832 527 648 +780 830 648 552 +781 600 831 825 +782 831 644 825 +783 831 552 644 +784 825 644 529 +785 405 827 404 +786 827 833 404 +787 827 601 833 +788 404 833 403 +789 601 834 833 +790 834 835 833 +791 834 547 835 +792 833 835 403 +793 601 826 834 +794 826 633 834 +795 826 529 633 +796 834 633 547 +797 403 835 402 +798 835 628 402 +799 835 547 628 +800 402 628 401 +801 385 790 384 +802 790 836 384 +803 790 591 836 +804 384 836 383 +805 591 837 836 +806 837 838 836 +807 837 603 838 +808 836 838 383 +809 591 789 837 +810 789 839 837 +811 789 541 839 +812 837 839 603 +813 383 838 382 +814 838 840 382 +815 838 603 840 +816 382 840 381 +817 541 841 839 +818 841 842 839 +819 841 604 842 +820 839 842 603 +821 604 843 842 +822 843 844 842 +823 843 605 844 +824 842 844 603 +825 604 845 843 +826 845 846 843 +827 845 545 846 +828 843 846 605 +829 603 844 840 +830 844 847 840 +831 844 605 847 +832 840 847 381 +833 541 784 841 +834 784 848 841 +835 784 590 848 +836 841 848 604 +837 590 849 848 +838 849 850 848 +839 849 606 850 +840 848 850 604 +841 590 783 849 +842 783 851 849 +843 783 528 851 +844 849 851 606 +845 604 850 845 +846 850 852 845 +847 850 606 852 +848 845 852 545 +849 381 847 380 +850 847 853 380 +851 847 605 853 +852 380 853 379 +853 605 854 853 +854 854 855 853 +855 854 607 855 +856 853 855 379 +857 605 846 854 +858 846 856 854 +859 846 545 856 +860 854 856 607 +861 379 855 378 +862 855 857 378 +863 855 607 857 +864 378 857 377 +865 393 679 392 +866 679 858 392 +867 679 560 858 +868 392 858 391 +869 560 859 858 +870 859 860 858 +871 859 608 860 +872 858 860 391 +873 560 678 859 +874 678 861 859 +875 678 532 861 +876 859 861 608 +877 391 860 390 +878 860 862 390 +879 860 608 862 +880 390 862 389 +881 532 863 861 +882 863 864 861 +883 863 609 864 +884 861 864 608 +885 609 865 864 +886 865 866 864 +887 865 610 866 +888 864 866 608 +889 609 867 865 +890 867 868 865 +891 867 542 868 +892 865 868 610 +893 608 866 862 +894 866 869 862 +895 866 610 869 +896 862 869 389 +897 532 674 863 +898 674 870 863 +899 674 559 870 +900 863 870 609 +901 559 871 870 +902 871 872 870 +903 871 586 872 +904 870 872 609 +905 559 673 871 +906 673 764 871 +907 673 526 764 +908 871 764 586 +909 609 872 867 +910 872 770 867 +911 872 586 770 +912 867 770 542 +913 389 869 388 +914 869 873 388 +915 869 610 873 +916 388 873 387 +917 610 874 873 +918 874 875 873 +919 874 592 875 +920 873 875 387 +921 610 868 874 +922 868 786 874 +923 868 542 786 +924 874 786 592 +925 387 875 386 +926 875 791 386 +927 875 592 791 +928 386 791 385 +929 14 734 432 +930 734 876 432 +931 734 577 876 +932 432 876 431 +933 577 877 876 +934 877 878 876 +935 877 611 878 +936 876 878 431 +937 577 740 877 +938 740 879 877 +939 740 539 879 +940 877 879 611 +941 431 878 430 +942 878 880 430 +943 878 611 880 +944 430 880 429 +945 539 881 879 +946 881 882 879 +947 881 612 882 +948 879 882 611 +949 612 883 882 +950 883 884 882 +951 883 613 884 +952 882 884 611 +953 612 885 883 +954 885 886 883 +955 885 535 886 +956 883 886 613 +957 611 884 880 +958 884 887 880 +959 884 613 887 +960 880 887 429 +961 539 756 881 +962 756 888 881 +963 756 583 888 +964 881 888 612 +965 583 889 888 +966 889 890 888 +967 889 563 890 +968 888 890 612 +969 583 761 889 +970 761 682 889 +971 761 525 682 +972 889 682 563 +973 612 890 885 +974 890 688 885 +975 890 563 688 +976 885 688 535 +977 429 887 428 +978 887 891 428 +979 887 613 891 +980 428 891 427 +981 613 892 891 +982 892 893 891 +983 892 569 893 +984 891 893 427 +985 613 886 892 +986 886 704 892 +987 886 535 704 +988 892 704 569 +989 427 893 426 +990 893 709 426 +991 893 569 709 +992 426 709 425 +993 526 656 895 +994 656 894 895 +995 656 554 894 +996 895 894 615 +997 554 896 894 +998 896 897 894 +999 896 614 897 +1000 894 897 615 +1001 554 655 896 +1002 655 898 896 +1003 655 530 898 +1004 896 898 614 +1005 615 897 900 +1006 897 899 900 +1007 897 614 899 +1008 900 899 546 +1009 530 901 898 +1010 901 902 898 +1011 901 616 902 +1012 898 902 614 +1013 616 903 902 +1014 903 904 902 +1015 903 617 904 +1016 902 904 614 +1017 616 905 903 +1018 905 906 903 +1019 905 533 906 +1020 903 906 617 +1021 614 904 899 +1022 904 907 899 +1023 904 617 907 +1024 899 907 546 +1025 530 650 901 +1026 650 908 901 +1027 650 553 908 +1028 901 908 616 +1029 553 909 908 +1030 909 910 908 +1031 909 566 910 +1032 908 910 616 +1033 553 649 909 +1034 649 700 909 +1035 649 527 700 +1036 909 700 566 +1037 616 910 905 +1038 910 696 905 +1039 910 566 696 +1040 905 696 533 +1041 546 907 912 +1042 907 911 912 +1043 907 617 911 +1044 912 911 618 +1045 617 913 911 +1046 913 914 911 +1047 913 561 914 +1048 911 914 618 +1049 617 906 913 +1050 906 685 913 +1051 906 533 685 +1052 913 685 561 +1053 618 914 915 +1054 914 680 915 +1055 914 561 680 +1056 915 680 525 +1057 525 760 915 +1058 760 916 915 +1059 760 582 916 +1060 915 916 618 +1061 582 917 916 +1062 917 918 916 +1063 917 619 918 +1064 916 918 618 +1065 582 759 917 +1066 759 919 917 +1067 759 538 919 +1068 917 919 619 +1069 618 918 912 +1070 918 920 912 +1071 918 619 920 +1072 912 920 546 +1073 538 921 919 +1074 921 922 919 +1075 921 620 922 +1076 919 922 619 +1077 620 923 922 +1078 923 924 922 +1079 923 621 924 +1080 922 924 619 +1081 620 925 923 +1082 925 926 923 +1083 925 540 926 +1084 923 926 621 +1085 619 924 920 +1086 924 927 920 +1087 924 621 927 +1088 920 927 546 +1089 538 754 921 +1090 754 928 921 +1091 754 581 928 +1092 921 928 620 +1093 581 929 928 +1094 929 930 928 +1095 929 589 930 +1096 928 930 620 +1097 581 753 929 +1098 753 782 929 +1099 753 528 782 +1100 929 782 589 +1101 620 930 925 +1102 930 778 925 +1103 930 589 778 +1104 925 778 540 +1105 546 927 900 +1106 927 931 900 +1107 927 621 931 +1108 900 931 615 +1109 621 932 931 +1110 932 933 931 +1111 932 584 933 +1112 931 933 615 +1113 621 926 932 +1114 926 767 932 +1115 926 540 767 +1116 932 767 584 +1117 615 933 895 +1118 933 762 895 +1119 933 584 762 +1120 895 762 526 +1121 417 731 416 +1122 731 934 416 +1123 731 574 934 +1124 416 934 415 +1125 574 935 934 +1126 935 936 934 +1127 935 622 936 +1128 934 936 415 +1129 574 730 935 +1130 730 937 935 +1131 730 536 937 +1132 935 937 622 +1133 415 936 414 +1134 936 938 414 +1135 936 622 938 +1136 414 938 413 +1137 536 939 937 +1138 939 940 937 +1139 939 623 940 +1140 937 940 622 +1141 623 941 940 +1142 941 942 940 +1143 941 624 942 +1144 940 942 622 +1145 623 943 941 +1146 943 944 941 +1147 943 544 944 +1148 941 944 624 +1149 622 942 938 +1150 942 945 938 +1151 942 624 945 +1152 938 945 413 +1153 536 726 939 +1154 726 946 939 +1155 726 573 946 +1156 939 946 623 +1157 573 947 946 +1158 947 948 946 +1159 947 602 948 +1160 946 948 623 +1161 573 725 947 +1162 725 832 947 +1163 725 527 832 +1164 947 832 602 +1165 623 948 943 +1166 948 828 943 +1167 948 602 828 +1168 943 828 544 +1169 413 945 412 +1170 945 949 412 +1171 945 624 949 +1172 412 949 411 +1173 624 950 949 +1174 950 951 949 +1175 950 598 951 +1176 949 951 411 +1177 624 944 950 +1178 944 818 950 +1179 944 544 818 +1180 950 818 598 +1181 411 951 410 +1182 951 814 410 +1183 951 598 814 +1184 410 814 409 +1185 377 857 376 +1186 857 952 376 +1187 857 607 952 +1188 376 952 375 +1189 607 953 952 +1190 953 954 952 +1191 953 625 954 +1192 952 954 375 +1193 607 856 953 +1194 856 955 953 +1195 856 545 955 +1196 953 955 625 +1197 375 954 374 +1198 954 956 374 +1199 954 625 956 +1200 374 956 373 +1201 545 957 955 +1202 957 958 955 +1203 957 626 958 +1204 955 958 625 +1205 626 959 958 +1206 959 960 958 +1207 959 627 960 +1208 958 960 625 +1209 626 961 959 +1210 961 962 959 +1211 961 543 962 +1212 959 962 627 +1213 625 960 956 +1214 960 963 956 +1215 960 627 963 +1216 956 963 373 +1217 545 852 957 +1218 852 964 957 +1219 852 606 964 +1220 957 964 626 +1221 606 965 964 +1222 965 966 964 +1223 965 597 966 +1224 964 966 626 +1225 606 851 965 +1226 851 810 965 +1227 851 528 810 +1228 965 810 597 +1229 626 966 961 +1230 966 806 961 +1231 966 597 806 +1232 961 806 543 +1233 373 963 372 +1234 963 967 372 +1235 963 627 967 +1236 372 967 371 +1237 627 968 967 +1238 968 969 967 +1239 968 593 969 +1240 967 969 371 +1241 627 962 968 +1242 962 796 968 +1243 962 543 796 +1244 968 796 593 +1245 371 969 370 +1246 969 792 370 +1247 969 593 792 +1248 370 792 369 +2 4 2 8704 +1249 970 1950 1952 +1250 1950 1951 1952 +1251 1950 1188 1951 +1252 1952 1951 1190 +1253 1188 1953 1951 +1254 1953 1954 1951 +1255 1953 1189 1954 +1256 1951 1954 1190 +1257 1188 1955 1953 +1258 1955 1956 1953 +1259 1955 1011 1956 +1260 1953 1956 1189 +1261 1190 1954 1958 +1262 1954 1957 1958 +1263 1954 1189 1957 +1264 1958 1957 1013 +1265 1011 1959 1956 +1266 1959 1960 1956 +1267 1959 1191 1960 +1268 1956 1960 1189 +1269 1191 1961 1960 +1270 1961 1962 1960 +1271 1961 1192 1962 +1272 1960 1962 1189 +1273 1191 1963 1961 +1274 1963 1964 1961 +1275 1963 1012 1964 +1276 1961 1964 1192 +1277 1189 1962 1957 +1278 1962 1965 1957 +1279 1962 1192 1965 +1280 1957 1965 1013 +1281 1011 1966 1959 +1282 1966 1967 1959 +1283 1966 1193 1967 +1284 1959 1967 1191 +1285 1193 1968 1967 +1286 1968 1969 1967 +1287 1968 1194 1969 +1288 1967 1969 1191 +1289 1193 1970 1968 +1290 1970 1971 1968 +1291 1970 977 1971 +1292 1968 1971 1194 +1293 1191 1969 1963 +1294 1969 1972 1963 +1295 1969 1194 1972 +1296 1963 1972 1012 +1297 1013 1965 1974 +1298 1965 1973 1974 +1299 1965 1192 1973 +1300 1974 1973 1196 +1301 1192 1975 1973 +1302 1975 1976 1973 +1303 1975 1195 1976 +1304 1973 1976 1196 +1305 1192 1964 1975 +1306 1964 1977 1975 +1307 1964 1012 1977 +1308 1975 1977 1195 +1309 1196 1976 1979 +1310 1976 1978 1979 +1311 1976 1195 1978 +1312 1979 1978 976 +1313 976 1980 1979 +1314 1980 1981 1979 +1315 1980 1197 1981 +1316 1979 1981 1196 +1317 1197 1982 1981 +1318 1982 1983 1981 +1319 1982 1198 1983 +1320 1981 1983 1196 +1321 1197 1984 1982 +1322 1984 1985 1982 +1323 1984 1014 1985 +1324 1982 1985 1198 +1325 1196 1983 1974 +1326 1983 1986 1974 +1327 1983 1198 1986 +1328 1974 1986 1013 +1329 1014 1987 1985 +1330 1987 1988 1985 +1331 1987 1199 1988 +1332 1985 1988 1198 +1333 1199 1989 1988 +1334 1989 1990 1988 +1335 1989 1200 1990 +1336 1988 1990 1198 +1337 1199 1991 1989 +1338 1991 1992 1989 +1339 1991 1015 1992 +1340 1989 1992 1200 +1341 1198 1990 1986 +1342 1990 1993 1986 +1343 1990 1200 1993 +1344 1986 1993 1013 +1345 1014 1994 1987 +1346 1994 1995 1987 +1347 1994 1201 1995 +1348 1987 1995 1199 +1349 1201 1996 1995 +1350 1996 1997 1995 +1351 1996 1202 1997 +1352 1995 1997 1199 +1353 1201 1998 1996 +1354 1998 1999 1996 +1355 1998 991 1999 +1356 1996 1999 1202 +1357 1199 1997 1991 +1358 1997 2000 1991 +1359 1997 1202 2000 +1360 1991 2000 1015 +1361 1013 1993 1958 +1362 1993 2001 1958 +1363 1993 1200 2001 +1364 1958 2001 1190 +1365 1200 2002 2001 +1366 2002 2003 2001 +1367 2002 1203 2003 +1368 2001 2003 1190 +1369 1200 1992 2002 +1370 1992 2004 2002 +1371 1992 1015 2004 +1372 2002 2004 1203 +1373 1190 2003 1952 +1374 2003 2005 1952 +1375 2003 1203 2005 +1376 1952 2005 970 +1377 974 2006 2008 +1378 2006 2007 2008 +1379 2006 1204 2007 +1380 2008 2007 1206 +1381 1204 2009 2007 +1382 2009 2010 2007 +1383 2009 1205 2010 +1384 2007 2010 1206 +1385 1204 2011 2009 +1386 2011 2012 2009 +1387 2011 1016 2012 +1388 2009 2012 1205 +1389 1206 2010 2014 +1390 2010 2013 2014 +1391 2010 1205 2013 +1392 2014 2013 1018 +1393 1016 2015 2012 +1394 2015 2016 2012 +1395 2015 1207 2016 +1396 2012 2016 1205 +1397 1207 2017 2016 +1398 2017 2018 2016 +1399 2017 1208 2018 +1400 2016 2018 1205 +1401 1207 2019 2017 +1402 2019 2020 2017 +1403 2019 1017 2020 +1404 2017 2020 1208 +1405 1205 2018 2013 +1406 2018 2021 2013 +1407 2018 1208 2021 +1408 2013 2021 1018 +1409 1016 2022 2015 +1410 2022 2023 2015 +1411 2022 1209 2023 +1412 2015 2023 1207 +1413 1209 2024 2023 +1414 2024 2025 2023 +1415 2024 1210 2025 +1416 2023 2025 1207 +1417 1209 2026 2024 +1418 2026 2027 2024 +1419 2026 999 2027 +1420 2024 2027 1210 +1421 1207 2025 2019 +1422 2025 2028 2019 +1423 2025 1210 2028 +1424 2019 2028 1017 +1425 1018 2021 2030 +1426 2021 2029 2030 +1427 2021 1208 2029 +1428 2030 2029 1212 +1429 1208 2031 2029 +1430 2031 2032 2029 +1431 2031 1211 2032 +1432 2029 2032 1212 +1433 1208 2020 2031 +1434 2020 2033 2031 +1435 2020 1017 2033 +1436 2031 2033 1211 +1437 1212 2032 2035 +1438 2032 2034 2035 +1439 2032 1211 2034 +1440 2035 2034 115 +1441 115 2034 2037 +1442 2034 2036 2037 +1443 2034 1211 2036 +1444 2037 2036 1214 +1445 1211 2038 2036 +1446 2038 2039 2036 +1447 2038 1213 2039 +1448 2036 2039 1214 +1449 1211 2033 2038 +1450 2033 2040 2038 +1451 2033 1017 2040 +1452 2038 2040 1213 +1453 1214 2039 2042 +1454 2039 2041 2042 +1455 2039 1213 2041 +1456 2042 2041 1020 +1457 1017 2043 2040 +1458 2043 2044 2040 +1459 2043 1215 2044 +1460 2040 2044 1213 +1461 1215 2045 2044 +1462 2045 2046 2044 +1463 2045 1216 2046 +1464 2044 2046 1213 +1465 1215 2047 2045 +1466 2047 2048 2045 +1467 2047 1019 2048 +1468 2045 2048 1216 +1469 1213 2046 2041 +1470 2046 2049 2041 +1471 2046 1216 2049 +1472 2041 2049 1020 +1473 1017 2028 2043 +1474 2028 2050 2043 +1475 2028 1210 2050 +1476 2043 2050 1215 +1477 1210 2051 2050 +1478 2051 2052 2050 +1479 2051 1217 2052 +1480 2050 2052 1215 +1481 1210 2027 2051 +1482 2027 2053 2051 +1483 2027 999 2053 +1484 2051 2053 1217 +1485 1215 2052 2047 +1486 2052 2054 2047 +1487 2052 1217 2054 +1488 2047 2054 1019 +1489 1020 2049 2056 +1490 2049 2055 2056 +1491 2049 1216 2055 +1492 2056 2055 1219 +1493 1216 2057 2055 +1494 2057 2058 2055 +1495 2057 1218 2058 +1496 2055 2058 1219 +1497 1216 2048 2057 +1498 2048 2059 2057 +1499 2048 1019 2059 +1500 2057 2059 1218 +1501 1219 2058 2061 +1502 2058 2060 2061 +1503 2058 1218 2060 +1504 2061 2060 401 +1505 5 122 2063 +1506 122 2062 2063 +1507 122 121 2062 +1508 2063 2062 1221 +1509 121 2064 2062 +1510 2064 2065 2062 +1511 2064 1220 2065 +1512 2062 2065 1221 +1513 121 120 2064 +1514 120 2066 2064 +1515 120 119 2066 +1516 2064 2066 1220 +1517 1221 2065 2068 +1518 2065 2067 2068 +1519 2065 1220 2067 +1520 2068 2067 1021 +1521 119 2069 2066 +1522 2069 2070 2066 +1523 2069 1222 2070 +1524 2066 2070 1220 +1525 1222 2071 2070 +1526 2071 2072 2070 +1527 2071 1223 2072 +1528 2070 2072 1220 +1529 1222 2073 2071 +1530 2073 2074 2071 +1531 2073 1020 2074 +1532 2071 2074 1223 +1533 1220 2072 2067 +1534 2072 2075 2067 +1535 2072 1223 2075 +1536 2067 2075 1021 +1537 119 118 2069 +1538 118 2076 2069 +1539 118 117 2076 +1540 2069 2076 1222 +1541 117 2077 2076 +1542 2077 2078 2076 +1543 2077 1214 2078 +1544 2076 2078 1222 +1545 117 116 2077 +1546 116 2037 2077 +1547 116 115 2037 +1548 2077 2037 1214 +1549 1222 2078 2073 +1550 2078 2042 2073 +1551 2078 1214 2042 +1552 2073 2042 1020 +1553 1021 2075 2080 +1554 2075 2079 2080 +1555 2075 1223 2079 +1556 2080 2079 1224 +1557 1223 2081 2079 +1558 2081 2082 2079 +1559 2081 1219 2082 +1560 2079 2082 1224 +1561 1223 2074 2081 +1562 2074 2056 2081 +1563 2074 1020 2056 +1564 2081 2056 1219 +1565 1224 2082 2083 +1566 2082 2061 2083 +1567 2082 1219 2061 +1568 2083 2061 401 +1569 393 2084 2086 +1570 2084 2085 2086 +1571 2084 1225 2085 +1572 2086 2085 1227 +1573 1225 2087 2085 +1574 2087 2088 2085 +1575 2087 1226 2088 +1576 2085 2088 1227 +1577 1225 2089 2087 +1578 2089 2090 2087 +1579 2089 1022 2090 +1580 2087 2090 1226 +1581 1227 2088 2092 +1582 2088 2091 2092 +1583 2088 1226 2091 +1584 2092 2091 1023 +1585 1022 2093 2090 +1586 2093 2094 2090 +1587 2093 1228 2094 +1588 2090 2094 1226 +1589 1228 2095 2094 +1590 2095 2096 2094 +1591 2095 1229 2096 +1592 2094 2096 1226 +1593 1228 2097 2095 +1594 2097 2098 2095 +1595 2097 126 2098 +1596 2095 2098 1229 +1597 1226 2096 2091 +1598 2096 2099 2091 +1599 2096 1229 2099 +1600 2091 2099 1023 +1601 1022 2100 2093 +1602 2100 2101 2093 +1603 2100 1230 2101 +1604 2093 2101 1228 +1605 1230 2102 2101 +1606 2102 2103 2101 +1607 2102 128 2103 +1608 2101 2103 1228 +1609 1230 2104 2102 +1610 2104 129 2102 +1611 2104 130 129 +1612 2102 129 128 +1613 1228 2103 2097 +1614 2103 127 2097 +1615 2103 128 127 +1616 2097 127 126 +1617 1023 2099 2106 +1618 2099 2105 2106 +1619 2099 1229 2105 +1620 2106 2105 1231 +1621 1229 2107 2105 +1622 2107 2108 2105 +1623 2107 124 2108 +1624 2105 2108 1231 +1625 1229 2098 2107 +1626 2098 125 2107 +1627 2098 126 125 +1628 2107 125 124 +1629 1231 2108 2109 +1630 2108 123 2109 +1631 2108 124 123 +1632 2109 123 5 +1633 409 2110 410 +1634 2110 2111 410 +1635 2110 1232 2111 +1636 410 2111 411 +1637 1232 2112 2111 +1638 2112 2113 2111 +1639 2112 1233 2113 +1640 2111 2113 411 +1641 1232 2114 2112 +1642 2114 2115 2112 +1643 2114 1024 2115 +1644 2112 2115 1233 +1645 411 2113 412 +1646 2113 2116 412 +1647 2113 1233 2116 +1648 412 2116 413 +1649 1024 2117 2115 +1650 2117 2118 2115 +1651 2117 1234 2118 +1652 2115 2118 1233 +1653 1234 2119 2118 +1654 2119 2120 2118 +1655 2119 1235 2120 +1656 2118 2120 1233 +1657 1234 2121 2119 +1658 2121 2122 2119 +1659 2121 1025 2122 +1660 2119 2122 1235 +1661 1233 2120 2116 +1662 2120 2123 2116 +1663 2120 1235 2123 +1664 2116 2123 413 +1665 1024 2124 2117 +1666 2124 2125 2117 +1667 2124 1236 2125 +1668 2117 2125 1234 +1669 1236 2126 2125 +1670 2126 2127 2125 +1671 2126 1237 2127 +1672 2125 2127 1234 +1673 1236 2128 2126 +1674 2128 2129 2126 +1675 2128 38 2129 +1676 2126 2129 1237 +1677 1234 2127 2121 +1678 2127 2130 2121 +1679 2127 1237 2130 +1680 2121 2130 1025 +1681 413 2123 414 +1682 2123 2131 414 +1683 2123 1235 2131 +1684 414 2131 415 +1685 1235 2132 2131 +1686 2132 2133 2131 +1687 2132 1238 2133 +1688 2131 2133 415 +1689 1235 2122 2132 +1690 2122 2134 2132 +1691 2122 1025 2134 +1692 2132 2134 1238 +1693 415 2133 416 +1694 2133 2135 416 +1695 2133 1238 2135 +1696 416 2135 417 +1697 377 2136 378 +1698 2136 2137 378 +1699 2136 1239 2137 +1700 378 2137 379 +1701 1239 2138 2137 +1702 2138 2139 2137 +1703 2138 1240 2139 +1704 2137 2139 379 +1705 1239 2140 2138 +1706 2140 2141 2138 +1707 2140 1026 2141 +1708 2138 2141 1240 +1709 379 2139 380 +1710 2139 2142 380 +1711 2139 1240 2142 +1712 380 2142 381 +1713 1026 2143 2141 +1714 2143 2144 2141 +1715 2143 1241 2144 +1716 2141 2144 1240 +1717 1241 2145 2144 +1718 2145 2146 2144 +1719 2145 1242 2146 +1720 2144 2146 1240 +1721 1241 2147 2145 +1722 2147 2148 2145 +1723 2147 1027 2148 +1724 2145 2148 1242 +1725 1240 2146 2142 +1726 2146 2149 2142 +1727 2146 1242 2149 +1728 2142 2149 381 +1729 1026 2150 2143 +1730 2150 2151 2143 +1731 2150 1243 2151 +1732 2143 2151 1241 +1733 1243 2152 2151 +1734 2152 2153 2151 +1735 2152 1244 2153 +1736 2151 2153 1241 +1737 1243 2154 2152 +1738 2154 2155 2152 +1739 2154 207 2155 +1740 2152 2155 1244 +1741 1241 2153 2147 +1742 2153 2156 2147 +1743 2153 1244 2156 +1744 2147 2156 1027 +1745 381 2149 382 +1746 2149 2157 382 +1747 2149 1242 2157 +1748 382 2157 383 +1749 1242 2158 2157 +1750 2158 2159 2157 +1751 2158 1245 2159 +1752 2157 2159 383 +1753 1242 2148 2158 +1754 2148 2160 2158 +1755 2148 1027 2160 +1756 2158 2160 1245 +1757 383 2159 384 +1758 2159 2161 384 +1759 2159 1245 2161 +1760 384 2161 385 +1761 130 2162 2164 +1762 2162 2163 2164 +1763 2162 1246 2163 +1764 2164 2163 1248 +1765 1246 2165 2163 +1766 2165 2166 2163 +1767 2165 1247 2166 +1768 2163 2166 1248 +1769 1246 2167 2165 +1770 2167 2168 2165 +1771 2167 1028 2168 +1772 2165 2168 1247 +1773 1248 2166 2170 +1774 2166 2169 2170 +1775 2166 1247 2169 +1776 2170 2169 1030 +1777 1028 2171 2168 +1778 2171 2172 2168 +1779 2171 1249 2172 +1780 2168 2172 1247 +1781 1249 2173 2172 +1782 2173 2174 2172 +1783 2173 1250 2174 +1784 2172 2174 1247 +1785 1249 2175 2173 +1786 2175 2176 2173 +1787 2175 1029 2176 +1788 2173 2176 1250 +1789 1247 2174 2169 +1790 2174 2177 2169 +1791 2174 1250 2177 +1792 2169 2177 1030 +1793 1028 2178 2171 +1794 2178 2179 2171 +1795 2178 1251 2179 +1796 2171 2179 1249 +1797 1251 2180 2179 +1798 2180 2181 2179 +1799 2180 1252 2181 +1800 2179 2181 1249 +1801 1251 2182 2180 +1802 2182 2183 2180 +1803 2182 998 2183 +1804 2180 2183 1252 +1805 1249 2181 2175 +1806 2181 2184 2175 +1807 2181 1252 2184 +1808 2175 2184 1029 +1809 1030 2177 2186 +1810 2177 2185 2186 +1811 2177 1250 2185 +1812 2186 2185 1254 +1813 1250 2187 2185 +1814 2187 2188 2185 +1815 2187 1253 2188 +1816 2185 2188 1254 +1817 1250 2176 2187 +1818 2176 2189 2187 +1819 2176 1029 2189 +1820 2187 2189 1253 +1821 1254 2188 2191 +1822 2188 2190 2191 +1823 2188 1253 2190 +1824 2191 2190 975 +1825 393 2192 2084 +1826 2192 2193 2084 +1827 2192 1255 2193 +1828 2084 2193 1225 +1829 1255 2194 2193 +1830 2194 2195 2193 +1831 2194 1256 2195 +1832 2193 2195 1225 +1833 1255 2196 2194 +1834 2196 2197 2194 +1835 2196 1031 2197 +1836 2194 2197 1256 +1837 1225 2195 2089 +1838 2195 2198 2089 +1839 2195 1256 2198 +1840 2089 2198 1022 +1841 1031 2199 2197 +1842 2199 2200 2197 +1843 2199 1257 2200 +1844 2197 2200 1256 +1845 1257 2201 2200 +1846 2201 2202 2200 +1847 2201 1258 2202 +1848 2200 2202 1256 +1849 1257 2203 2201 +1850 2203 2204 2201 +1851 2203 1028 2204 +1852 2201 2204 1258 +1853 1256 2202 2198 +1854 2202 2205 2198 +1855 2202 1258 2205 +1856 2198 2205 1022 +1857 1031 2206 2199 +1858 2206 2207 2199 +1859 2206 1259 2207 +1860 2199 2207 1257 +1861 1259 2208 2207 +1862 2208 2209 2207 +1863 2208 1251 2209 +1864 2207 2209 1257 +1865 1259 2210 2208 +1866 2210 2182 2208 +1867 2210 998 2182 +1868 2208 2182 1251 +1869 1257 2209 2203 +1870 2209 2178 2203 +1871 2209 1251 2178 +1872 2203 2178 1028 +1873 1022 2205 2100 +1874 2205 2211 2100 +1875 2205 1258 2211 +1876 2100 2211 1230 +1877 1258 2212 2211 +1878 2212 2213 2211 +1879 2212 1246 2213 +1880 2211 2213 1230 +1881 1258 2204 2212 +1882 2204 2167 2212 +1883 2204 1028 2167 +1884 2212 2167 1246 +1885 1230 2213 2104 +1886 2213 2162 2104 +1887 2213 1246 2162 +1888 2104 2162 130 +1889 369 2214 370 +1890 2214 2215 370 +1891 2214 1260 2215 +1892 370 2215 371 +1893 1260 2216 2215 +1894 2216 2217 2215 +1895 2216 1261 2217 +1896 2215 2217 371 +1897 1260 2218 2216 +1898 2218 2219 2216 +1899 2218 1032 2219 +1900 2216 2219 1261 +1901 371 2217 372 +1902 2217 2220 372 +1903 2217 1261 2220 +1904 372 2220 373 +1905 1032 2221 2219 +1906 2221 2222 2219 +1907 2221 1262 2222 +1908 2219 2222 1261 +1909 1262 2223 2222 +1910 2223 2224 2222 +1911 2223 1263 2224 +1912 2222 2224 1261 +1913 1262 2225 2223 +1914 2225 2226 2223 +1915 2225 1033 2226 +1916 2223 2226 1263 +1917 1261 2224 2220 +1918 2224 2227 2220 +1919 2224 1263 2227 +1920 2220 2227 373 +1921 1032 2228 2221 +1922 2228 2229 2221 +1923 2228 1264 2229 +1924 2221 2229 1262 +1925 1264 2230 2229 +1926 2230 2231 2229 +1927 2230 1265 2231 +1928 2229 2231 1262 +1929 1264 2232 2230 +1930 2232 2233 2230 +1931 2232 980 2233 +1932 2230 2233 1265 +1933 1262 2231 2225 +1934 2231 2234 2225 +1935 2231 1265 2234 +1936 2225 2234 1033 +1937 373 2227 374 +1938 2227 2235 374 +1939 2227 1263 2235 +1940 374 2235 375 +1941 1263 2236 2235 +1942 2236 2237 2235 +1943 2236 1266 2237 +1944 2235 2237 375 +1945 1263 2226 2236 +1946 2226 2238 2236 +1947 2226 1033 2238 +1948 2236 2238 1266 +1949 375 2237 376 +1950 2237 2239 376 +1951 2237 1266 2239 +1952 376 2239 377 +1953 417 2240 418 +1954 2240 2241 418 +1955 2240 1267 2241 +1956 418 2241 419 +1957 1267 2242 2241 +1958 2242 2243 2241 +1959 2242 1268 2243 +1960 2241 2243 419 +1961 1267 2244 2242 +1962 2244 2245 2242 +1963 2244 1034 2245 +1964 2242 2245 1268 +1965 419 2243 420 +1966 2243 2246 420 +1967 2243 1268 2246 +1968 420 2246 421 +1969 1034 2247 2245 +1970 2247 2248 2245 +1971 2247 1269 2248 +1972 2245 2248 1268 +1973 1269 2249 2248 +1974 2249 2250 2248 +1975 2249 1270 2250 +1976 2248 2250 1268 +1977 1269 2251 2249 +1978 2251 2252 2249 +1979 2251 1035 2252 +1980 2249 2252 1270 +1981 1268 2250 2246 +1982 2250 2253 2246 +1983 2250 1270 2253 +1984 2246 2253 421 +1985 1034 2254 2247 +1986 2254 2255 2247 +1987 2254 1271 2255 +1988 2247 2255 1269 +1989 1271 2256 2255 +1990 2256 2257 2255 +1991 2256 1272 2257 +1992 2255 2257 1269 +1993 1271 2258 2256 +1994 2258 2259 2256 +1995 2258 978 2259 +1996 2256 2259 1272 +1997 1269 2257 2251 +1998 2257 2260 2251 +1999 2257 1272 2260 +2000 2251 2260 1035 +2001 421 2253 422 +2002 2253 2261 422 +2003 2253 1270 2261 +2004 422 2261 423 +2005 1270 2262 2261 +2006 2262 2263 2261 +2007 2262 1273 2263 +2008 2261 2263 423 +2009 1270 2252 2262 +2010 2252 2264 2262 +2011 2252 1035 2264 +2012 2262 2264 1273 +2013 423 2263 424 +2014 2263 2265 424 +2015 2263 1273 2265 +2016 424 2265 425 +2017 981 2266 2268 +2018 2266 2267 2268 +2019 2266 1274 2267 +2020 2268 2267 1276 +2021 1274 2269 2267 +2022 2269 2270 2267 +2023 2269 1275 2270 +2024 2267 2270 1276 +2025 1274 2271 2269 +2026 2271 2272 2269 +2027 2271 1036 2272 +2028 2269 2272 1275 +2029 1276 2270 2274 +2030 2270 2273 2274 +2031 2270 1275 2273 +2032 2274 2273 1038 +2033 1036 2275 2272 +2034 2275 2276 2272 +2035 2275 1277 2276 +2036 2272 2276 1275 +2037 1277 2277 2276 +2038 2277 2278 2276 +2039 2277 1278 2278 +2040 2276 2278 1275 +2041 1277 2279 2277 +2042 2279 2280 2277 +2043 2279 1037 2280 +2044 2277 2280 1278 +2045 1275 2278 2273 +2046 2278 2281 2273 +2047 2278 1278 2281 +2048 2273 2281 1038 +2049 1036 2282 2275 +2050 2282 2283 2275 +2051 2282 1279 2283 +2052 2275 2283 1277 +2053 1279 2284 2283 +2054 2284 2285 2283 +2055 2284 1280 2285 +2056 2283 2285 1277 +2057 1279 2286 2284 +2058 2286 2287 2284 +2059 2286 984 2287 +2060 2284 2287 1280 +2061 1277 2285 2279 +2062 2285 2288 2279 +2063 2285 1280 2288 +2064 2279 2288 1037 +2065 1038 2281 2290 +2066 2281 2289 2290 +2067 2281 1278 2289 +2068 2290 2289 1282 +2069 1278 2291 2289 +2070 2291 2292 2289 +2071 2291 1281 2292 +2072 2289 2292 1282 +2073 1278 2280 2291 +2074 2280 2293 2291 +2075 2280 1037 2293 +2076 2291 2293 1281 +2077 1282 2292 2295 +2078 2292 2294 2295 +2079 2292 1281 2294 +2080 2295 2294 972 +2081 417 2135 2297 +2082 2135 2296 2297 +2083 2135 1238 2296 +2084 2297 2296 1284 +2085 1238 2298 2296 +2086 2298 2299 2296 +2087 2298 1283 2299 +2088 2296 2299 1284 +2089 1238 2134 2298 +2090 2134 2300 2298 +2091 2134 1025 2300 +2092 2298 2300 1283 +2093 1284 2299 2302 +2094 2299 2301 2302 +2095 2299 1283 2301 +2096 2302 2301 1039 +2097 1025 2303 2300 +2098 2303 2304 2300 +2099 2303 1285 2304 +2100 2300 2304 1283 +2101 1285 2305 2304 +2102 2305 2306 2304 +2103 2305 1286 2306 +2104 2304 2306 1283 +2105 1285 2307 2305 +2106 2307 2308 2305 +2107 2307 34 2308 +2108 2305 2308 1286 +2109 1283 2306 2301 +2110 2306 2309 2301 +2111 2306 1286 2309 +2112 2301 2309 1039 +2113 1025 2130 2303 +2114 2130 2310 2303 +2115 2130 1237 2310 +2116 2303 2310 1285 +2117 1237 2311 2310 +2118 2311 2312 2310 +2119 2311 36 2312 +2120 2310 2312 1285 +2121 1237 2129 2311 +2122 2129 37 2311 +2123 2129 38 37 +2124 2311 37 36 +2125 1285 2312 2307 +2126 2312 35 2307 +2127 2312 36 35 +2128 2307 35 34 +2129 1039 2309 2314 +2130 2309 2313 2314 +2131 2309 1286 2313 +2132 2314 2313 1287 +2133 1286 2315 2313 +2134 2315 2316 2313 +2135 2315 32 2316 +2136 2313 2316 1287 +2137 1286 2308 2315 +2138 2308 33 2315 +2139 2308 34 33 +2140 2315 33 32 +2141 1287 2316 2317 +2142 2316 31 2317 +2143 2316 32 31 +2144 2317 31 30 +2145 377 2318 2136 +2146 2318 2319 2136 +2147 2318 1288 2319 +2148 2136 2319 1239 +2149 1288 2320 2319 +2150 2320 2321 2319 +2151 2320 1289 2321 +2152 2319 2321 1239 +2153 1288 2322 2320 +2154 2322 2323 2320 +2155 2322 1040 2323 +2156 2320 2323 1289 +2157 1239 2321 2140 +2158 2321 2324 2140 +2159 2321 1289 2324 +2160 2140 2324 1026 +2161 1040 2325 2323 +2162 2325 2326 2323 +2163 2325 1290 2326 +2164 2323 2326 1289 +2165 1290 2327 2326 +2166 2327 2328 2326 +2167 2327 1291 2328 +2168 2326 2328 1289 +2169 1290 2329 2327 +2170 2329 2330 2327 +2171 2329 211 2330 +2172 2327 2330 1291 +2173 1289 2328 2324 +2174 2328 2331 2324 +2175 2328 1291 2331 +2176 2324 2331 1026 +2177 1040 2332 2325 +2178 2332 2333 2325 +2179 2332 1292 2333 +2180 2325 2333 1290 +2181 1292 2334 2333 +2182 2334 2335 2333 +2183 2334 213 2335 +2184 2333 2335 1290 +2185 1292 2336 2334 +2186 2336 214 2334 +2187 2336 215 214 +2188 2334 214 213 +2189 1290 2335 2329 +2190 2335 212 2329 +2191 2335 213 212 +2192 2329 212 211 +2193 1026 2331 2150 +2194 2331 2337 2150 +2195 2331 1291 2337 +2196 2150 2337 1243 +2197 1291 2338 2337 +2198 2338 2339 2337 +2199 2338 209 2339 +2200 2337 2339 1243 +2201 1291 2330 2338 +2202 2330 210 2338 +2203 2330 211 210 +2204 2338 210 209 +2205 1243 2339 2154 +2206 2339 208 2154 +2207 2339 209 208 +2208 2154 208 207 +2209 377 2239 2318 +2210 2239 2340 2318 +2211 2239 1266 2340 +2212 2318 2340 1288 +2213 1266 2341 2340 +2214 2341 2342 2340 +2215 2341 1293 2342 +2216 2340 2342 1288 +2217 1266 2238 2341 +2218 2238 2343 2341 +2219 2238 1033 2343 +2220 2341 2343 1293 +2221 1288 2342 2322 +2222 2342 2344 2322 +2223 2342 1293 2344 +2224 2322 2344 1040 +2225 1033 2345 2343 +2226 2345 2346 2343 +2227 2345 1294 2346 +2228 2343 2346 1293 +2229 1294 2347 2346 +2230 2347 2348 2346 +2231 2347 1295 2348 +2232 2346 2348 1293 +2233 1294 2349 2347 +2234 2349 2350 2347 +2235 2349 1041 2350 +2236 2347 2350 1295 +2237 1293 2348 2344 +2238 2348 2351 2344 +2239 2348 1295 2351 +2240 2344 2351 1040 +2241 1033 2234 2345 +2242 2234 2352 2345 +2243 2234 1265 2352 +2244 2345 2352 1294 +2245 1265 2353 2352 +2246 2353 2354 2352 +2247 2353 1296 2354 +2248 2352 2354 1294 +2249 1265 2233 2353 +2250 2233 2355 2353 +2251 2233 980 2355 +2252 2353 2355 1296 +2253 1294 2354 2349 +2254 2354 2356 2349 +2255 2354 1296 2356 +2256 2349 2356 1041 +2257 1040 2351 2332 +2258 2351 2357 2332 +2259 2351 1295 2357 +2260 2332 2357 1292 +2261 1295 2358 2357 +2262 2358 2359 2357 +2263 2358 1297 2359 +2264 2357 2359 1292 +2265 1295 2350 2358 +2266 2350 2360 2358 +2267 2350 1041 2360 +2268 2358 2360 1297 +2269 1292 2359 2336 +2270 2359 2361 2336 +2271 2359 1297 2361 +2272 2336 2361 215 +2273 30 2362 2317 +2274 2362 2363 2317 +2275 2362 1298 2363 +2276 2317 2363 1287 +2277 1298 2364 2363 +2278 2364 2365 2363 +2279 2364 1299 2365 +2280 2363 2365 1287 +2281 1298 2366 2364 +2282 2366 2367 2364 +2283 2366 1042 2367 +2284 2364 2367 1299 +2285 1287 2365 2314 +2286 2365 2368 2314 +2287 2365 1299 2368 +2288 2314 2368 1039 +2289 1042 2369 2367 +2290 2369 2370 2367 +2291 2369 1300 2370 +2292 2367 2370 1299 +2293 1300 2371 2370 +2294 2371 2372 2370 +2295 2371 1301 2372 +2296 2370 2372 1299 +2297 1300 2373 2371 +2298 2373 2374 2371 +2299 2373 1034 2374 +2300 2371 2374 1301 +2301 1299 2372 2368 +2302 2372 2375 2368 +2303 2372 1301 2375 +2304 2368 2375 1039 +2305 1042 2376 2369 +2306 2376 2377 2369 +2307 2376 1302 2377 +2308 2369 2377 1300 +2309 1302 2378 2377 +2310 2378 2379 2377 +2311 2378 1271 2379 +2312 2377 2379 1300 +2313 1302 2380 2378 +2314 2380 2258 2378 +2315 2380 978 2258 +2316 2378 2258 1271 +2317 1300 2379 2373 +2318 2379 2254 2373 +2319 2379 1271 2254 +2320 2373 2254 1034 +2321 1039 2375 2302 +2322 2375 2381 2302 +2323 2375 1301 2381 +2324 2302 2381 1284 +2325 1301 2382 2381 +2326 2382 2383 2381 +2327 2382 1267 2383 +2328 2381 2383 1284 +2329 1301 2374 2382 +2330 2374 2244 2382 +2331 2374 1034 2244 +2332 2382 2244 1267 +2333 1284 2383 2297 +2334 2383 2240 2297 +2335 2383 1267 2240 +2336 2297 2240 417 +2337 970 2384 1950 +2338 2384 2385 1950 +2339 2384 1303 2385 +2340 1950 2385 1188 +2341 1303 2386 2385 +2342 2386 2387 2385 +2343 2386 1304 2387 +2344 2385 2387 1188 +2345 1303 2388 2386 +2346 2388 2389 2386 +2347 2388 1043 2389 +2348 2386 2389 1304 +2349 1188 2387 1955 +2350 2387 2390 1955 +2351 2387 1304 2390 +2352 1955 2390 1011 +2353 1043 2391 2389 +2354 2391 2392 2389 +2355 2391 1305 2392 +2356 2389 2392 1304 +2357 1305 2393 2392 +2358 2393 2394 2392 +2359 2393 1306 2394 +2360 2392 2394 1304 +2361 1305 2395 2393 +2362 2395 2396 2393 +2363 2395 1044 2396 +2364 2393 2396 1306 +2365 1304 2394 2390 +2366 2394 2397 2390 +2367 2394 1306 2397 +2368 2390 2397 1011 +2369 1043 2398 2391 +2370 2398 2399 2391 +2371 2398 1307 2399 +2372 2391 2399 1305 +2373 1307 2400 2399 +2374 2400 2401 2399 +2375 2400 1308 2401 +2376 2399 2401 1305 +2377 1307 2402 2400 +2378 2402 2403 2400 +2379 2402 992 2403 +2380 2400 2403 1308 +2381 1305 2401 2395 +2382 2401 2404 2395 +2383 2401 1308 2404 +2384 2395 2404 1044 +2385 1011 2397 1966 +2386 2397 2405 1966 +2387 2397 1306 2405 +2388 1966 2405 1193 +2389 1306 2406 2405 +2390 2406 2407 2405 +2391 2406 1309 2407 +2392 2405 2407 1193 +2393 1306 2396 2406 +2394 2396 2408 2406 +2395 2396 1044 2408 +2396 2406 2408 1309 +2397 1193 2407 1970 +2398 2407 2409 1970 +2399 2407 1309 2409 +2400 1970 2409 977 +2401 393 2086 394 +2402 2086 2410 394 +2403 2086 1227 2410 +2404 394 2410 395 +2405 1227 2411 2410 +2406 2411 2412 2410 +2407 2411 1310 2412 +2408 2410 2412 395 +2409 1227 2092 2411 +2410 2092 2413 2411 +2411 2092 1023 2413 +2412 2411 2413 1310 +2413 395 2412 396 +2414 2412 2414 396 +2415 2412 1310 2414 +2416 396 2414 397 +2417 1023 2415 2413 +2418 2415 2416 2413 +2419 2415 1311 2416 +2420 2413 2416 1310 +2421 1311 2417 2416 +2422 2417 2418 2416 +2423 2417 1312 2418 +2424 2416 2418 1310 +2425 1311 2419 2417 +2426 2419 2420 2417 +2427 2419 1021 2420 +2428 2417 2420 1312 +2429 1310 2418 2414 +2430 2418 2421 2414 +2431 2418 1312 2421 +2432 2414 2421 397 +2433 1023 2106 2415 +2434 2106 2422 2415 +2435 2106 1231 2422 +2436 2415 2422 1311 +2437 1231 2423 2422 +2438 2423 2424 2422 +2439 2423 1221 2424 +2440 2422 2424 1311 +2441 1231 2109 2423 +2442 2109 2063 2423 +2443 2109 5 2063 +2444 2423 2063 1221 +2445 1311 2424 2419 +2446 2424 2068 2419 +2447 2424 1221 2068 +2448 2419 2068 1021 +2449 397 2421 398 +2450 2421 2425 398 +2451 2421 1312 2425 +2452 398 2425 399 +2453 1312 2426 2425 +2454 2426 2427 2425 +2455 2426 1224 2427 +2456 2425 2427 399 +2457 1312 2420 2426 +2458 2420 2080 2426 +2459 2420 1021 2080 +2460 2426 2080 1224 +2461 399 2427 400 +2462 2427 2083 400 +2463 2427 1224 2083 +2464 400 2083 401 +2465 975 2190 2429 +2466 2190 2428 2429 +2467 2190 1253 2428 +2468 2429 2428 1314 +2469 1253 2430 2428 +2470 2430 2431 2428 +2471 2430 1313 2431 +2472 2428 2431 1314 +2473 1253 2189 2430 +2474 2189 2432 2430 +2475 2189 1029 2432 +2476 2430 2432 1313 +2477 1314 2431 2434 +2478 2431 2433 2434 +2479 2431 1313 2433 +2480 2434 2433 1046 +2481 1029 2435 2432 +2482 2435 2436 2432 +2483 2435 1315 2436 +2484 2432 2436 1313 +2485 1315 2437 2436 +2486 2437 2438 2436 +2487 2437 1316 2438 +2488 2436 2438 1313 +2489 1315 2439 2437 +2490 2439 2440 2437 +2491 2439 1045 2440 +2492 2437 2440 1316 +2493 1313 2438 2433 +2494 2438 2441 2433 +2495 2438 1316 2441 +2496 2433 2441 1046 +2497 1029 2184 2435 +2498 2184 2442 2435 +2499 2184 1252 2442 +2500 2435 2442 1315 +2501 1252 2443 2442 +2502 2443 2444 2442 +2503 2443 1317 2444 +2504 2442 2444 1315 +2505 1252 2183 2443 +2506 2183 2445 2443 +2507 2183 998 2445 +2508 2443 2445 1317 +2509 1315 2444 2439 +2510 2444 2446 2439 +2511 2444 1317 2446 +2512 2439 2446 1045 +2513 1046 2441 2448 +2514 2441 2447 2448 +2515 2441 1316 2447 +2516 2448 2447 1319 +2517 1316 2449 2447 +2518 2449 2450 2447 +2519 2449 1318 2450 +2520 2447 2450 1319 +2521 1316 2440 2449 +2522 2440 2451 2449 +2523 2440 1045 2451 +2524 2449 2451 1318 +2525 1319 2450 2453 +2526 2450 2452 2453 +2527 2450 1318 2452 +2528 2453 2452 988 +2529 984 2454 2287 +2530 2454 2455 2287 +2531 2454 1320 2455 +2532 2287 2455 1280 +2533 1320 2456 2455 +2534 2456 2457 2455 +2535 2456 1321 2457 +2536 2455 2457 1280 +2537 1320 2458 2456 +2538 2458 2459 2456 +2539 2458 1047 2459 +2540 2456 2459 1321 +2541 1280 2457 2288 +2542 2457 2460 2288 +2543 2457 1321 2460 +2544 2288 2460 1037 +2545 1047 2461 2459 +2546 2461 2462 2459 +2547 2461 1322 2462 +2548 2459 2462 1321 +2549 1322 2463 2462 +2550 2463 2464 2462 +2551 2463 1323 2464 +2552 2462 2464 1321 +2553 1322 2465 2463 +2554 2465 2466 2463 +2555 2465 1048 2466 +2556 2463 2466 1323 +2557 1321 2464 2460 +2558 2464 2467 2460 +2559 2464 1323 2467 +2560 2460 2467 1037 +2561 1047 2468 2461 +2562 2468 2469 2461 +2563 2468 1324 2469 +2564 2461 2469 1322 +2565 1324 2470 2469 +2566 2470 2471 2469 +2567 2470 1325 2471 +2568 2469 2471 1322 +2569 1324 2472 2470 +2570 2472 2473 2470 +2571 2472 997 2473 +2572 2470 2473 1325 +2573 1322 2471 2465 +2574 2471 2474 2465 +2575 2471 1325 2474 +2576 2465 2474 1048 +2577 1037 2467 2293 +2578 2467 2475 2293 +2579 2467 1323 2475 +2580 2293 2475 1281 +2581 1323 2476 2475 +2582 2476 2477 2475 +2583 2476 1326 2477 +2584 2475 2477 1281 +2585 1323 2466 2476 +2586 2466 2478 2476 +2587 2466 1048 2478 +2588 2476 2478 1326 +2589 1281 2477 2294 +2590 2477 2479 2294 +2591 2477 1326 2479 +2592 2294 2479 972 +2593 977 2480 2482 +2594 2480 2481 2482 +2595 2480 1327 2481 +2596 2482 2481 1329 +2597 1327 2483 2481 +2598 2483 2484 2481 +2599 2483 1328 2484 +2600 2481 2484 1329 +2601 1327 2485 2483 +2602 2485 2486 2483 +2603 2485 1049 2486 +2604 2483 2486 1328 +2605 1329 2484 2488 +2606 2484 2487 2488 +2607 2484 1328 2487 +2608 2488 2487 1050 +2609 1049 2489 2486 +2610 2489 2490 2486 +2611 2489 1330 2490 +2612 2486 2490 1328 +2613 1330 2491 2490 +2614 2491 2492 2490 +2615 2491 1331 2492 +2616 2490 2492 1328 +2617 1330 2493 2491 +2618 2493 2494 2491 +2619 2493 1016 2494 +2620 2491 2494 1331 +2621 1328 2492 2487 +2622 2492 2495 2487 +2623 2492 1331 2495 +2624 2487 2495 1050 +2625 1049 2496 2489 +2626 2496 2497 2489 +2627 2496 1332 2497 +2628 2489 2497 1330 +2629 1332 2498 2497 +2630 2498 2499 2497 +2631 2498 1209 2499 +2632 2497 2499 1330 +2633 1332 2500 2498 +2634 2500 2026 2498 +2635 2500 999 2026 +2636 2498 2026 1209 +2637 1330 2499 2493 +2638 2499 2022 2493 +2639 2499 1209 2022 +2640 2493 2022 1016 +2641 1050 2495 2502 +2642 2495 2501 2502 +2643 2495 1331 2501 +2644 2502 2501 1333 +2645 1331 2503 2501 +2646 2503 2504 2501 +2647 2503 1204 2504 +2648 2501 2504 1333 +2649 1331 2494 2503 +2650 2494 2011 2503 +2651 2494 1016 2011 +2652 2503 2011 1204 +2653 1333 2504 2505 +2654 2504 2006 2505 +2655 2504 1204 2006 +2656 2505 2006 974 +2657 199 2506 2508 +2658 2506 2507 2508 +2659 2506 1334 2507 +2660 2508 2507 1336 +2661 1334 2509 2507 +2662 2509 2510 2507 +2663 2509 1335 2510 +2664 2507 2510 1336 +2665 1334 2511 2509 +2666 2511 2512 2509 +2667 2511 1051 2512 +2668 2509 2512 1335 +2669 1336 2510 2514 +2670 2510 2513 2514 +2671 2510 1335 2513 +2672 2514 2513 1053 +2673 1051 2515 2512 +2674 2515 2516 2512 +2675 2515 1337 2516 +2676 2512 2516 1335 +2677 1337 2517 2516 +2678 2517 2518 2516 +2679 2517 1338 2518 +2680 2516 2518 1335 +2681 1337 2519 2517 +2682 2519 2520 2517 +2683 2519 1052 2520 +2684 2517 2520 1338 +2685 1335 2518 2513 +2686 2518 2521 2513 +2687 2518 1338 2521 +2688 2513 2521 1053 +2689 1051 2522 2515 +2690 2522 2523 2515 +2691 2522 1339 2523 +2692 2515 2523 1337 +2693 1339 2524 2523 +2694 2524 2525 2523 +2695 2524 1340 2525 +2696 2523 2525 1337 +2697 1339 2526 2524 +2698 2526 2527 2524 +2699 2526 993 2527 +2700 2524 2527 1340 +2701 1337 2525 2519 +2702 2525 2528 2519 +2703 2525 1340 2528 +2704 2519 2528 1052 +2705 1053 2521 2530 +2706 2521 2529 2530 +2707 2521 1338 2529 +2708 2530 2529 1342 +2709 1338 2531 2529 +2710 2531 2532 2529 +2711 2531 1341 2532 +2712 2529 2532 1342 +2713 1338 2520 2531 +2714 2520 2533 2531 +2715 2520 1052 2533 +2716 2531 2533 1341 +2717 1342 2532 2535 +2718 2532 2534 2535 +2719 2532 1341 2534 +2720 2535 2534 988 +2721 988 2452 2537 +2722 2452 2536 2537 +2723 2452 1318 2536 +2724 2537 2536 1344 +2725 1318 2538 2536 +2726 2538 2539 2536 +2727 2538 1343 2539 +2728 2536 2539 1344 +2729 1318 2451 2538 +2730 2451 2540 2538 +2731 2451 1045 2540 +2732 2538 2540 1343 +2733 1344 2539 2542 +2734 2539 2541 2542 +2735 2539 1343 2541 +2736 2542 2541 1055 +2737 1045 2543 2540 +2738 2543 2544 2540 +2739 2543 1345 2544 +2740 2540 2544 1343 +2741 1345 2545 2544 +2742 2545 2546 2544 +2743 2545 1346 2546 +2744 2544 2546 1343 +2745 1345 2547 2545 +2746 2547 2548 2545 +2747 2547 1054 2548 +2748 2545 2548 1346 +2749 1343 2546 2541 +2750 2546 2549 2541 +2751 2546 1346 2549 +2752 2541 2549 1055 +2753 1045 2446 2543 +2754 2446 2550 2543 +2755 2446 1317 2550 +2756 2543 2550 1345 +2757 1317 2551 2550 +2758 2551 2552 2550 +2759 2551 1347 2552 +2760 2550 2552 1345 +2761 1317 2445 2551 +2762 2445 2553 2551 +2763 2445 998 2553 +2764 2551 2553 1347 +2765 1345 2552 2547 +2766 2552 2554 2547 +2767 2552 1347 2554 +2768 2547 2554 1054 +2769 1055 2549 2556 +2770 2549 2555 2556 +2771 2549 1346 2555 +2772 2556 2555 1349 +2773 1346 2557 2555 +2774 2557 2558 2555 +2775 2557 1348 2558 +2776 2555 2558 1349 +2777 1346 2548 2557 +2778 2548 2559 2557 +2779 2548 1054 2559 +2780 2557 2559 1348 +2781 1349 2558 2561 +2782 2558 2560 2561 +2783 2558 1348 2560 +2784 2561 2560 989 +2785 982 2562 2564 +2786 2562 2563 2564 +2787 2562 1350 2563 +2788 2564 2563 1352 +2789 1350 2565 2563 +2790 2565 2566 2563 +2791 2565 1351 2566 +2792 2563 2566 1352 +2793 1350 2567 2565 +2794 2567 2568 2565 +2795 2567 1056 2568 +2796 2565 2568 1351 +2797 1352 2566 2570 +2798 2566 2569 2570 +2799 2566 1351 2569 +2800 2570 2569 1058 +2801 1056 2571 2568 +2802 2571 2572 2568 +2803 2571 1353 2572 +2804 2568 2572 1351 +2805 1353 2573 2572 +2806 2573 2574 2572 +2807 2573 1354 2574 +2808 2572 2574 1351 +2809 1353 2575 2573 +2810 2575 2576 2573 +2811 2575 1057 2576 +2812 2573 2576 1354 +2813 1351 2574 2569 +2814 2574 2577 2569 +2815 2574 1354 2577 +2816 2569 2577 1058 +2817 1056 2578 2571 +2818 2578 2579 2571 +2819 2578 1355 2579 +2820 2571 2579 1353 +2821 1355 2580 2579 +2822 2580 2581 2579 +2823 2580 1356 2581 +2824 2579 2581 1353 +2825 1355 2582 2580 +2826 2582 2583 2580 +2827 2582 1007 2583 +2828 2580 2583 1356 +2829 1353 2581 2575 +2830 2581 2584 2575 +2831 2581 1356 2584 +2832 2575 2584 1057 +2833 1058 2577 2586 +2834 2577 2585 2586 +2835 2577 1354 2585 +2836 2586 2585 1358 +2837 1354 2587 2585 +2838 2587 2588 2585 +2839 2587 1357 2588 +2840 2585 2588 1358 +2841 1354 2576 2587 +2842 2576 2589 2587 +2843 2576 1057 2589 +2844 2587 2589 1357 +2845 1358 2588 2591 +2846 2588 2590 2591 +2847 2588 1357 2590 +2848 2591 2590 994 +2849 990 2592 2594 +2850 2592 2593 2594 +2851 2592 1359 2593 +2852 2594 2593 1361 +2853 1359 2595 2593 +2854 2595 2596 2593 +2855 2595 1360 2596 +2856 2593 2596 1361 +2857 1359 2597 2595 +2858 2597 2598 2595 +2859 2597 1059 2598 +2860 2595 2598 1360 +2861 1361 2596 2600 +2862 2596 2599 2600 +2863 2596 1360 2599 +2864 2600 2599 1060 +2865 1059 2601 2598 +2866 2601 2602 2598 +2867 2601 1362 2602 +2868 2598 2602 1360 +2869 1362 2603 2602 +2870 2603 2604 2602 +2871 2603 1363 2604 +2872 2602 2604 1360 +2873 1362 2605 2603 +2874 2605 2606 2603 +2875 2605 1049 2606 +2876 2603 2606 1363 +2877 1360 2604 2599 +2878 2604 2607 2599 +2879 2604 1363 2607 +2880 2599 2607 1060 +2881 1059 2608 2601 +2882 2608 2609 2601 +2883 2608 1364 2609 +2884 2601 2609 1362 +2885 1364 2610 2609 +2886 2610 2611 2609 +2887 2610 1332 2611 +2888 2609 2611 1362 +2889 1364 2612 2610 +2890 2612 2500 2610 +2891 2612 999 2500 +2892 2610 2500 1332 +2893 1362 2611 2605 +2894 2611 2496 2605 +2895 2611 1332 2496 +2896 2605 2496 1049 +2897 1060 2607 2614 +2898 2607 2613 2614 +2899 2607 1363 2613 +2900 2614 2613 1365 +2901 1363 2615 2613 +2902 2615 2616 2613 +2903 2615 1327 2616 +2904 2613 2616 1365 +2905 1363 2606 2615 +2906 2606 2485 2615 +2907 2606 1049 2485 +2908 2615 2485 1327 +2909 1365 2616 2617 +2910 2616 2480 2617 +2911 2616 1327 2480 +2912 2617 2480 977 +2913 988 2537 2535 +2914 2537 2618 2535 +2915 2537 1344 2618 +2916 2535 2618 1342 +2917 1344 2619 2618 +2918 2619 2620 2618 +2919 2619 1366 2620 +2920 2618 2620 1342 +2921 1344 2542 2619 +2922 2542 2621 2619 +2923 2542 1055 2621 +2924 2619 2621 1366 +2925 1342 2620 2530 +2926 2620 2622 2530 +2927 2620 1366 2622 +2928 2530 2622 1053 +2929 1055 2623 2621 +2930 2623 2624 2621 +2931 2623 1367 2624 +2932 2621 2624 1366 +2933 1367 2625 2624 +2934 2625 2626 2624 +2935 2625 1368 2626 +2936 2624 2626 1366 +2937 1367 2627 2625 +2938 2627 2628 2625 +2939 2627 1061 2628 +2940 2625 2628 1368 +2941 1366 2626 2622 +2942 2626 2629 2622 +2943 2626 1368 2629 +2944 2622 2629 1053 +2945 1055 2556 2623 +2946 2556 2630 2623 +2947 2556 1349 2630 +2948 2623 2630 1367 +2949 1349 2631 2630 +2950 2631 2632 2630 +2951 2631 1369 2632 +2952 2630 2632 1367 +2953 1349 2561 2631 +2954 2561 2633 2631 +2955 2561 989 2633 +2956 2631 2633 1369 +2957 1367 2632 2627 +2958 2632 2634 2627 +2959 2632 1369 2634 +2960 2627 2634 1061 +2961 1053 2629 2514 +2962 2629 2635 2514 +2963 2629 1368 2635 +2964 2514 2635 1336 +2965 1368 2636 2635 +2966 2636 2637 2635 +2967 2636 1370 2637 +2968 2635 2637 1336 +2969 1368 2628 2636 +2970 2628 2638 2636 +2971 2628 1061 2638 +2972 2636 2638 1370 +2973 1336 2637 2508 +2974 2637 2639 2508 +2975 2637 1370 2639 +2976 2508 2639 199 +2977 980 2640 2642 +2978 2640 2641 2642 +2979 2640 1371 2641 +2980 2642 2641 1373 +2981 1371 2643 2641 +2982 2643 2644 2641 +2983 2643 1372 2644 +2984 2641 2644 1373 +2985 1371 2645 2643 +2986 2645 2646 2643 +2987 2645 1062 2646 +2988 2643 2646 1372 +2989 1373 2644 2648 +2990 2644 2647 2648 +2991 2644 1372 2647 +2992 2648 2647 1064 +2993 1062 2649 2646 +2994 2649 2650 2646 +2995 2649 1374 2650 +2996 2646 2650 1372 +2997 1374 2651 2650 +2998 2651 2652 2650 +2999 2651 1375 2652 +3000 2650 2652 1372 +3001 1374 2653 2651 +3002 2653 2654 2651 +3003 2653 1063 2654 +3004 2651 2654 1375 +3005 1372 2652 2647 +3006 2652 2655 2647 +3007 2652 1375 2655 +3008 2647 2655 1064 +3009 1062 2656 2649 +3010 2656 2657 2649 +3011 2656 1376 2657 +3012 2649 2657 1374 +3013 1376 2658 2657 +3014 2658 2659 2657 +3015 2658 1377 2659 +3016 2657 2659 1374 +3017 1376 2660 2658 +3018 2660 2661 2658 +3019 2660 1010 2661 +3020 2658 2661 1377 +3021 1374 2659 2653 +3022 2659 2662 2653 +3023 2659 1377 2662 +3024 2653 2662 1063 +3025 1064 2655 2664 +3026 2655 2663 2664 +3027 2655 1375 2663 +3028 2664 2663 1379 +3029 1375 2665 2663 +3030 2665 2666 2663 +3031 2665 1378 2666 +3032 2663 2666 1379 +3033 1375 2654 2665 +3034 2654 2667 2665 +3035 2654 1063 2667 +3036 2665 2667 1378 +3037 1379 2666 2669 +3038 2666 2668 2669 +3039 2666 1378 2668 +3040 2669 2668 983 +3041 982 2670 2672 +3042 2670 2671 2672 +3043 2670 1380 2671 +3044 2672 2671 1382 +3045 1380 2673 2671 +3046 2673 2674 2671 +3047 2673 1381 2674 +3048 2671 2674 1382 +3049 1380 2675 2673 +3050 2675 2676 2673 +3051 2675 1065 2676 +3052 2673 2676 1381 +3053 1382 2674 2678 +3054 2674 2677 2678 +3055 2674 1381 2677 +3056 2678 2677 1067 +3057 1065 2679 2676 +3058 2679 2680 2676 +3059 2679 1383 2680 +3060 2676 2680 1381 +3061 1383 2681 2680 +3062 2681 2682 2680 +3063 2681 1384 2682 +3064 2680 2682 1381 +3065 1383 2683 2681 +3066 2683 2684 2681 +3067 2683 1066 2684 +3068 2681 2684 1384 +3069 1381 2682 2677 +3070 2682 2685 2677 +3071 2682 1384 2685 +3072 2677 2685 1067 +3073 1065 2686 2679 +3074 2686 2687 2679 +3075 2686 1385 2687 +3076 2679 2687 1383 +3077 1385 2688 2687 +3078 2688 2689 2687 +3079 2688 1386 2689 +3080 2687 2689 1383 +3081 1385 2690 2688 +3082 2690 2691 2688 +3083 2690 1009 2691 +3084 2688 2691 1386 +3085 1383 2689 2683 +3086 2689 2692 2683 +3087 2689 1386 2692 +3088 2683 2692 1066 +3089 1067 2685 2694 +3090 2685 2693 2694 +3091 2685 1384 2693 +3092 2694 2693 1388 +3093 1384 2695 2693 +3094 2695 2696 2693 +3095 2695 1387 2696 +3096 2693 2696 1388 +3097 1384 2684 2695 +3098 2684 2697 2695 +3099 2684 1066 2697 +3100 2695 2697 1387 +3101 1388 2696 2699 +3102 2696 2698 2699 +3103 2696 1387 2698 +3104 2699 2698 978 +3105 977 2409 2701 +3106 2409 2700 2701 +3107 2409 1309 2700 +3108 2701 2700 1390 +3109 1309 2702 2700 +3110 2702 2703 2700 +3111 2702 1389 2703 +3112 2700 2703 1390 +3113 1309 2408 2702 +3114 2408 2704 2702 +3115 2408 1044 2704 +3116 2702 2704 1389 +3117 1390 2703 2706 +3118 2703 2705 2706 +3119 2703 1389 2705 +3120 2706 2705 1069 +3121 1044 2707 2704 +3122 2707 2708 2704 +3123 2707 1391 2708 +3124 2704 2708 1389 +3125 1391 2709 2708 +3126 2709 2710 2708 +3127 2709 1392 2710 +3128 2708 2710 1389 +3129 1391 2711 2709 +3130 2711 2712 2709 +3131 2711 1068 2712 +3132 2709 2712 1392 +3133 1389 2710 2705 +3134 2710 2713 2705 +3135 2710 1392 2713 +3136 2705 2713 1069 +3137 1044 2404 2707 +3138 2404 2714 2707 +3139 2404 1308 2714 +3140 2707 2714 1391 +3141 1308 2715 2714 +3142 2715 2716 2714 +3143 2715 1393 2716 +3144 2714 2716 1391 +3145 1308 2403 2715 +3146 2403 2717 2715 +3147 2403 992 2717 +3148 2715 2717 1393 +3149 1391 2716 2711 +3150 2716 2718 2711 +3151 2716 1393 2718 +3152 2711 2718 1068 +3153 1069 2713 2720 +3154 2713 2719 2720 +3155 2713 1392 2719 +3156 2720 2719 1395 +3157 1392 2721 2719 +3158 2721 2722 2719 +3159 2721 1394 2722 +3160 2719 2722 1395 +3161 1392 2712 2721 +3162 2712 2723 2721 +3163 2712 1068 2723 +3164 2721 2723 1394 +3165 1395 2722 2725 +3166 2722 2724 2725 +3167 2722 1394 2724 +3168 2725 2724 46 +3169 982 2564 2727 +3170 2564 2726 2727 +3171 2564 1352 2726 +3172 2727 2726 1397 +3173 1352 2728 2726 +3174 2728 2729 2726 +3175 2728 1396 2729 +3176 2726 2729 1397 +3177 1352 2570 2728 +3178 2570 2730 2728 +3179 2570 1058 2730 +3180 2728 2730 1396 +3181 1397 2729 2732 +3182 2729 2731 2732 +3183 2729 1396 2731 +3184 2732 2731 1071 +3185 1058 2733 2730 +3186 2733 2734 2730 +3187 2733 1398 2734 +3188 2730 2734 1396 +3189 1398 2735 2734 +3190 2735 2736 2734 +3191 2735 1399 2736 +3192 2734 2736 1396 +3193 1398 2737 2735 +3194 2737 2738 2735 +3195 2737 1070 2738 +3196 2735 2738 1399 +3197 1396 2736 2731 +3198 2736 2739 2731 +3199 2736 1399 2739 +3200 2731 2739 1071 +3201 1058 2586 2733 +3202 2586 2740 2733 +3203 2586 1358 2740 +3204 2733 2740 1398 +3205 1358 2741 2740 +3206 2741 2742 2740 +3207 2741 1400 2742 +3208 2740 2742 1398 +3209 1358 2591 2741 +3210 2591 2743 2741 +3211 2591 994 2743 +3212 2741 2743 1400 +3213 1398 2742 2737 +3214 2742 2744 2737 +3215 2742 1400 2744 +3216 2737 2744 1070 +3217 1071 2739 2746 +3218 2739 2745 2746 +3219 2739 1399 2745 +3220 2746 2745 1402 +3221 1399 2747 2745 +3222 2747 2748 2745 +3223 2747 1401 2748 +3224 2745 2748 1402 +3225 1399 2738 2747 +3226 2738 2749 2747 +3227 2738 1070 2749 +3228 2747 2749 1401 +3229 1402 2748 2751 +3230 2748 2750 2751 +3231 2748 1401 2750 +3232 2751 2750 308 +3233 369 2752 2214 +3234 2752 2753 2214 +3235 2752 1403 2753 +3236 2214 2753 1260 +3237 1403 2754 2753 +3238 2754 2755 2753 +3239 2754 1404 2755 +3240 2753 2755 1260 +3241 1403 2756 2754 +3242 2756 2757 2754 +3243 2756 1072 2757 +3244 2754 2757 1404 +3245 1260 2755 2218 +3246 2755 2758 2218 +3247 2755 1404 2758 +3248 2218 2758 1032 +3249 1072 2759 2757 +3250 2759 2760 2757 +3251 2759 1405 2760 +3252 2757 2760 1404 +3253 1405 2761 2760 +3254 2761 2762 2760 +3255 2761 1406 2762 +3256 2760 2762 1404 +3257 1405 2763 2761 +3258 2763 2764 2761 +3259 2763 1062 2764 +3260 2761 2764 1406 +3261 1404 2762 2758 +3262 2762 2765 2758 +3263 2762 1406 2765 +3264 2758 2765 1032 +3265 1072 2766 2759 +3266 2766 2767 2759 +3267 2766 1407 2767 +3268 2759 2767 1405 +3269 1407 2768 2767 +3270 2768 2769 2767 +3271 2768 1376 2769 +3272 2767 2769 1405 +3273 1407 2770 2768 +3274 2770 2660 2768 +3275 2770 1010 2660 +3276 2768 2660 1376 +3277 1405 2769 2763 +3278 2769 2656 2763 +3279 2769 1376 2656 +3280 2763 2656 1062 +3281 1032 2765 2228 +3282 2765 2771 2228 +3283 2765 1406 2771 +3284 2228 2771 1264 +3285 1406 2772 2771 +3286 2772 2773 2771 +3287 2772 1371 2773 +3288 2771 2773 1264 +3289 1406 2764 2772 +3290 2764 2645 2772 +3291 2764 1062 2645 +3292 2772 2645 1371 +3293 1264 2773 2232 +3294 2773 2640 2232 +3295 2773 1371 2640 +3296 2232 2640 980 +3297 978 2698 2259 +3298 2698 2774 2259 +3299 2698 1387 2774 +3300 2259 2774 1272 +3301 1387 2775 2774 +3302 2775 2776 2774 +3303 2775 1408 2776 +3304 2774 2776 1272 +3305 1387 2697 2775 +3306 2697 2777 2775 +3307 2697 1066 2777 +3308 2775 2777 1408 +3309 1272 2776 2260 +3310 2776 2778 2260 +3311 2776 1408 2778 +3312 2260 2778 1035 +3313 1066 2779 2777 +3314 2779 2780 2777 +3315 2779 1409 2780 +3316 2777 2780 1408 +3317 1409 2781 2780 +3318 2781 2782 2780 +3319 2781 1410 2782 +3320 2780 2782 1408 +3321 1409 2783 2781 +3322 2783 2784 2781 +3323 2783 1073 2784 +3324 2781 2784 1410 +3325 1408 2782 2778 +3326 2782 2785 2778 +3327 2782 1410 2785 +3328 2778 2785 1035 +3329 1066 2692 2779 +3330 2692 2786 2779 +3331 2692 1386 2786 +3332 2779 2786 1409 +3333 1386 2787 2786 +3334 2787 2788 2786 +3335 2787 1411 2788 +3336 2786 2788 1409 +3337 1386 2691 2787 +3338 2691 2789 2787 +3339 2691 1009 2789 +3340 2787 2789 1411 +3341 1409 2788 2783 +3342 2788 2790 2783 +3343 2788 1411 2790 +3344 2783 2790 1073 +3345 1035 2785 2264 +3346 2785 2791 2264 +3347 2785 1410 2791 +3348 2264 2791 1273 +3349 1410 2792 2791 +3350 2792 2793 2791 +3351 2792 1412 2793 +3352 2791 2793 1273 +3353 1410 2784 2792 +3354 2784 2794 2792 +3355 2784 1073 2794 +3356 2792 2794 1412 +3357 1273 2793 2265 +3358 2793 2795 2265 +3359 2793 1412 2795 +3360 2265 2795 425 +3361 46 2796 2725 +3362 2796 2797 2725 +3363 2796 1413 2797 +3364 2725 2797 1395 +3365 1413 2798 2797 +3366 2798 2799 2797 +3367 2798 1414 2799 +3368 2797 2799 1395 +3369 1413 2800 2798 +3370 2800 2801 2798 +3371 2800 1074 2801 +3372 2798 2801 1414 +3373 1395 2799 2720 +3374 2799 2802 2720 +3375 2799 1414 2802 +3376 2720 2802 1069 +3377 1074 2803 2801 +3378 2803 2804 2801 +3379 2803 1415 2804 +3380 2801 2804 1414 +3381 1415 2805 2804 +3382 2805 2806 2804 +3383 2805 1416 2806 +3384 2804 2806 1414 +3385 1415 2807 2805 +3386 2807 2808 2805 +3387 2807 1060 2808 +3388 2805 2808 1416 +3389 1414 2806 2802 +3390 2806 2809 2802 +3391 2806 1416 2809 +3392 2802 2809 1069 +3393 1074 2810 2803 +3394 2810 2811 2803 +3395 2810 1417 2811 +3396 2803 2811 1415 +3397 1417 2812 2811 +3398 2812 2813 2811 +3399 2812 1361 2813 +3400 2811 2813 1415 +3401 1417 2814 2812 +3402 2814 2594 2812 +3403 2814 990 2594 +3404 2812 2594 1361 +3405 1415 2813 2807 +3406 2813 2600 2807 +3407 2813 1361 2600 +3408 2807 2600 1060 +3409 1069 2809 2706 +3410 2809 2815 2706 +3411 2809 1416 2815 +3412 2706 2815 1390 +3413 1416 2816 2815 +3414 2816 2817 2815 +3415 2816 1365 2817 +3416 2815 2817 1390 +3417 1416 2808 2816 +3418 2808 2614 2816 +3419 2808 1060 2614 +3420 2816 2614 1365 +3421 1390 2817 2701 +3422 2817 2617 2701 +3423 2817 1365 2617 +3424 2701 2617 977 +3425 983 2818 2820 +3426 2818 2819 2820 +3427 2818 1418 2819 +3428 2820 2819 1420 +3429 1418 2821 2819 +3430 2821 2822 2819 +3431 2821 1419 2822 +3432 2819 2822 1420 +3433 1418 2823 2821 +3434 2823 2824 2821 +3435 2823 1075 2824 +3436 2821 2824 1419 +3437 1420 2822 2826 +3438 2822 2825 2826 +3439 2822 1419 2825 +3440 2826 2825 1076 +3441 1075 2827 2824 +3442 2827 2828 2824 +3443 2827 1421 2828 +3444 2824 2828 1419 +3445 1421 2829 2828 +3446 2829 2830 2828 +3447 2829 1422 2830 +3448 2828 2830 1419 +3449 1421 2831 2829 +3450 2831 2832 2829 +3451 2831 1036 2832 +3452 2829 2832 1422 +3453 1419 2830 2825 +3454 2830 2833 2825 +3455 2830 1422 2833 +3456 2825 2833 1076 +3457 1075 2834 2827 +3458 2834 2835 2827 +3459 2834 1423 2835 +3460 2827 2835 1421 +3461 1423 2836 2835 +3462 2836 2837 2835 +3463 2836 1279 2837 +3464 2835 2837 1421 +3465 1423 2838 2836 +3466 2838 2286 2836 +3467 2838 984 2286 +3468 2836 2286 1279 +3469 1421 2837 2831 +3470 2837 2282 2831 +3471 2837 1279 2282 +3472 2831 2282 1036 +3473 1076 2833 2840 +3474 2833 2839 2840 +3475 2833 1422 2839 +3476 2840 2839 1424 +3477 1422 2841 2839 +3478 2841 2842 2839 +3479 2841 1274 2842 +3480 2839 2842 1424 +3481 1422 2832 2841 +3482 2832 2271 2841 +3483 2832 1036 2271 +3484 2841 2271 1274 +3485 1424 2842 2843 +3486 2842 2266 2843 +3487 2842 1274 2266 +3488 2843 2266 981 +3489 292 2844 293 +3490 2844 2845 293 +3491 2844 1425 2845 +3492 293 2845 294 +3493 1425 2846 2845 +3494 2846 2847 2845 +3495 2846 1426 2847 +3496 2845 2847 294 +3497 1425 2848 2846 +3498 2848 2849 2846 +3499 2848 1077 2849 +3500 2846 2849 1426 +3501 294 2847 295 +3502 2847 2850 295 +3503 2847 1426 2850 +3504 295 2850 296 +3505 1077 2851 2849 +3506 2851 2852 2849 +3507 2851 1427 2852 +3508 2849 2852 1426 +3509 1427 2853 2852 +3510 2853 2854 2852 +3511 2853 1428 2854 +3512 2852 2854 1426 +3513 1427 2855 2853 +3514 2855 2856 2853 +3515 2855 1078 2856 +3516 2853 2856 1428 +3517 1426 2854 2850 +3518 2854 2857 2850 +3519 2854 1428 2857 +3520 2850 2857 296 +3521 1077 2858 2851 +3522 2858 2859 2851 +3523 2858 1429 2859 +3524 2851 2859 1427 +3525 1429 2860 2859 +3526 2860 2861 2859 +3527 2860 1430 2861 +3528 2859 2861 1427 +3529 1429 2862 2860 +3530 2862 2863 2860 +3531 2862 14 2863 +3532 2860 2863 1430 +3533 1427 2861 2855 +3534 2861 2864 2855 +3535 2861 1430 2864 +3536 2855 2864 1078 +3537 296 2857 297 +3538 2857 2865 297 +3539 2857 1428 2865 +3540 297 2865 298 +3541 1428 2866 2865 +3542 2866 2867 2865 +3543 2866 1431 2867 +3544 2865 2867 298 +3545 1428 2856 2866 +3546 2856 2868 2866 +3547 2856 1078 2868 +3548 2866 2868 1431 +3549 298 2867 299 +3550 2867 2869 299 +3551 2867 1431 2869 +3552 299 2869 300 +3553 22 2870 2872 +3554 2870 2871 2872 +3555 2870 1432 2871 +3556 2872 2871 1434 +3557 1432 2873 2871 +3558 2873 2874 2871 +3559 2873 1433 2874 +3560 2871 2874 1434 +3561 1432 2875 2873 +3562 2875 2876 2873 +3563 2875 1079 2876 +3564 2873 2876 1433 +3565 1434 2874 2878 +3566 2874 2877 2878 +3567 2874 1433 2877 +3568 2878 2877 1081 +3569 1079 2879 2876 +3570 2879 2880 2876 +3571 2879 1435 2880 +3572 2876 2880 1433 +3573 1435 2881 2880 +3574 2881 2882 2880 +3575 2881 1436 2882 +3576 2880 2882 1433 +3577 1435 2883 2881 +3578 2883 2884 2881 +3579 2883 1080 2884 +3580 2881 2884 1436 +3581 1433 2882 2877 +3582 2882 2885 2877 +3583 2882 1436 2885 +3584 2877 2885 1081 +3585 1079 2886 2879 +3586 2886 2887 2879 +3587 2886 1437 2887 +3588 2879 2887 1435 +3589 1437 2888 2887 +3590 2888 2889 2887 +3591 2888 1438 2889 +3592 2887 2889 1435 +3593 1437 2890 2888 +3594 2890 2891 2888 +3595 2890 979 2891 +3596 2888 2891 1438 +3597 1435 2889 2883 +3598 2889 2892 2883 +3599 2889 1438 2892 +3600 2883 2892 1080 +3601 1081 2885 2894 +3602 2885 2893 2894 +3603 2885 1436 2893 +3604 2894 2893 1440 +3605 1436 2895 2893 +3606 2895 2896 2893 +3607 2895 1439 2896 +3608 2893 2896 1440 +3609 1436 2884 2895 +3610 2884 2897 2895 +3611 2884 1080 2897 +3612 2895 2897 1439 +3613 1440 2896 2899 +3614 2896 2898 2899 +3615 2896 1439 2898 +3616 2899 2898 978 +3617 69 2900 68 +3618 2900 2901 68 +3619 2900 1441 2901 +3620 68 2901 67 +3621 1441 2902 2901 +3622 2902 2903 2901 +3623 2902 1442 2903 +3624 2901 2903 67 +3625 1441 2904 2902 +3626 2904 2905 2902 +3627 2904 1082 2905 +3628 2902 2905 1442 +3629 67 2903 66 +3630 2903 2906 66 +3631 2903 1442 2906 +3632 66 2906 65 +3633 1082 2907 2905 +3634 2907 2908 2905 +3635 2907 1443 2908 +3636 2905 2908 1442 +3637 1443 2909 2908 +3638 2909 2910 2908 +3639 2909 1444 2910 +3640 2908 2910 1442 +3641 1443 2911 2909 +3642 2911 2912 2909 +3643 2911 1083 2912 +3644 2909 2912 1444 +3645 1442 2910 2906 +3646 2910 2913 2906 +3647 2910 1444 2913 +3648 2906 2913 65 +3649 1082 2914 2907 +3650 2914 2915 2907 +3651 2914 1445 2915 +3652 2907 2915 1443 +3653 1445 2916 2915 +3654 2916 2917 2915 +3655 2916 1446 2917 +3656 2915 2917 1443 +3657 1445 2918 2916 +3658 2918 2919 2916 +3659 2918 970 2919 +3660 2916 2919 1446 +3661 1443 2917 2911 +3662 2917 2920 2911 +3663 2917 1446 2920 +3664 2911 2920 1083 +3665 65 2913 64 +3666 2913 2921 64 +3667 2913 1444 2921 +3668 64 2921 63 +3669 1444 2922 2921 +3670 2922 2923 2921 +3671 2922 1447 2923 +3672 2921 2923 63 +3673 1444 2912 2922 +3674 2912 2924 2922 +3675 2912 1083 2924 +3676 2922 2924 1447 +3677 63 2923 62 +3678 2923 2925 62 +3679 2923 1447 2925 +3680 62 2925 61 +3681 161 2926 160 +3682 2926 2927 160 +3683 2926 1448 2927 +3684 160 2927 159 +3685 1448 2928 2927 +3686 2928 2929 2927 +3687 2928 1449 2929 +3688 2927 2929 159 +3689 1448 2930 2928 +3690 2930 2931 2928 +3691 2930 1084 2931 +3692 2928 2931 1449 +3693 159 2929 158 +3694 2929 2932 158 +3695 2929 1449 2932 +3696 158 2932 157 +3697 1084 2933 2931 +3698 2933 2934 2931 +3699 2933 1450 2934 +3700 2931 2934 1449 +3701 1450 2935 2934 +3702 2935 2936 2934 +3703 2935 1451 2936 +3704 2934 2936 1449 +3705 1450 2937 2935 +3706 2937 2938 2935 +3707 2937 1085 2938 +3708 2935 2938 1451 +3709 1449 2936 2932 +3710 2936 2939 2932 +3711 2936 1451 2939 +3712 2932 2939 157 +3713 1084 2940 2933 +3714 2940 2941 2933 +3715 2940 1452 2941 +3716 2933 2941 1450 +3717 1452 2942 2941 +3718 2942 2943 2941 +3719 2942 1453 2943 +3720 2941 2943 1450 +3721 1452 2944 2942 +3722 2944 2945 2942 +3723 2944 971 2945 +3724 2942 2945 1453 +3725 1450 2943 2937 +3726 2943 2946 2937 +3727 2943 1453 2946 +3728 2937 2946 1085 +3729 157 2939 156 +3730 2939 2947 156 +3731 2939 1451 2947 +3732 156 2947 155 +3733 1451 2948 2947 +3734 2948 2949 2947 +3735 2948 1454 2949 +3736 2947 2949 155 +3737 1451 2938 2948 +3738 2938 2950 2948 +3739 2938 1085 2950 +3740 2948 2950 1454 +3741 155 2949 154 +3742 2949 2951 154 +3743 2949 1454 2951 +3744 154 2951 153 +3745 284 2952 2954 +3746 2952 2953 2954 +3747 2952 1455 2953 +3748 2954 2953 1457 +3749 1455 2955 2953 +3750 2955 2956 2953 +3751 2955 1456 2956 +3752 2953 2956 1457 +3753 1455 2957 2955 +3754 2957 2958 2955 +3755 2957 1086 2958 +3756 2955 2958 1456 +3757 1457 2956 2960 +3758 2956 2959 2960 +3759 2956 1456 2959 +3760 2960 2959 1087 +3761 1086 2961 2958 +3762 2961 2962 2958 +3763 2961 1458 2962 +3764 2958 2962 1456 +3765 1458 2963 2962 +3766 2963 2964 2962 +3767 2963 1459 2964 +3768 2962 2964 1456 +3769 1458 2965 2963 +3770 2965 2966 2963 +3771 2965 1075 2966 +3772 2963 2966 1459 +3773 1456 2964 2959 +3774 2964 2967 2959 +3775 2964 1459 2967 +3776 2959 2967 1087 +3777 1086 2968 2961 +3778 2968 2969 2961 +3779 2968 1460 2969 +3780 2961 2969 1458 +3781 1460 2970 2969 +3782 2970 2971 2969 +3783 2970 1423 2971 +3784 2969 2971 1458 +3785 1460 2972 2970 +3786 2972 2838 2970 +3787 2972 984 2838 +3788 2970 2838 1423 +3789 1458 2971 2965 +3790 2971 2834 2965 +3791 2971 1423 2834 +3792 2965 2834 1075 +3793 1087 2967 2974 +3794 2967 2973 2974 +3795 2967 1459 2973 +3796 2974 2973 1461 +3797 1459 2975 2973 +3798 2975 2976 2973 +3799 2975 1418 2976 +3800 2973 2976 1461 +3801 1459 2966 2975 +3802 2966 2823 2975 +3803 2966 1075 2823 +3804 2975 2823 1418 +3805 1461 2976 2977 +3806 2976 2818 2977 +3807 2976 1418 2818 +3808 2977 2818 983 +3809 107 2978 2980 +3810 2978 2979 2980 +3811 2978 1462 2979 +3812 2980 2979 1464 +3813 1462 2981 2979 +3814 2981 2982 2979 +3815 2981 1463 2982 +3816 2979 2982 1464 +3817 1462 2983 2981 +3818 2983 2984 2981 +3819 2983 1088 2984 +3820 2981 2984 1463 +3821 1464 2982 2986 +3822 2982 2985 2986 +3823 2982 1463 2985 +3824 2986 2985 1090 +3825 1088 2987 2984 +3826 2987 2988 2984 +3827 2987 1465 2988 +3828 2984 2988 1463 +3829 1465 2989 2988 +3830 2989 2990 2988 +3831 2989 1466 2990 +3832 2988 2990 1463 +3833 1465 2991 2989 +3834 2991 2992 2989 +3835 2991 1089 2992 +3836 2989 2992 1466 +3837 1463 2990 2985 +3838 2990 2993 2985 +3839 2990 1466 2993 +3840 2985 2993 1090 +3841 1088 2994 2987 +3842 2994 2995 2987 +3843 2994 1467 2995 +3844 2987 2995 1465 +3845 1467 2996 2995 +3846 2996 2997 2995 +3847 2996 1468 2997 +3848 2995 2997 1465 +3849 1467 2998 2996 +3850 2998 2999 2996 +3851 2998 976 2999 +3852 2996 2999 1468 +3853 1465 2997 2991 +3854 2997 3000 2991 +3855 2997 1468 3000 +3856 2991 3000 1089 +3857 1090 2993 3002 +3858 2993 3001 3002 +3859 2993 1466 3001 +3860 3002 3001 1470 +3861 1466 3003 3001 +3862 3003 3004 3001 +3863 3003 1469 3004 +3864 3001 3004 1470 +3865 1466 2992 3003 +3866 2992 3005 3003 +3867 2992 1089 3005 +3868 3003 3005 1469 +3869 1470 3004 3007 +3870 3004 3006 3007 +3871 3004 1469 3006 +3872 3007 3006 974 +3873 207 3008 2155 +3874 3008 3009 2155 +3875 3008 1471 3009 +3876 2155 3009 1244 +3877 1471 3010 3009 +3878 3010 3011 3009 +3879 3010 1472 3011 +3880 3009 3011 1244 +3881 1471 3012 3010 +3882 3012 3013 3010 +3883 3012 1091 3013 +3884 3010 3013 1472 +3885 1244 3011 2156 +3886 3011 3014 2156 +3887 3011 1472 3014 +3888 2156 3014 1027 +3889 1091 3015 3013 +3890 3015 3016 3013 +3891 3015 1473 3016 +3892 3013 3016 1472 +3893 1473 3017 3016 +3894 3017 3018 3016 +3895 3017 1474 3018 +3896 3016 3018 1472 +3897 1473 3019 3017 +3898 3019 3020 3017 +3899 3019 1092 3020 +3900 3017 3020 1474 +3901 1472 3018 3014 +3902 3018 3021 3014 +3903 3018 1474 3021 +3904 3014 3021 1027 +3905 1091 3022 3015 +3906 3022 3023 3015 +3907 3022 1475 3023 +3908 3015 3023 1473 +3909 1475 3024 3023 +3910 3024 3025 3023 +3911 3024 1476 3025 +3912 3023 3025 1473 +3913 1475 3026 3024 +3914 3026 3027 3024 +3915 3026 989 3027 +3916 3024 3027 1476 +3917 1473 3025 3019 +3918 3025 3028 3019 +3919 3025 1476 3028 +3920 3019 3028 1092 +3921 1027 3021 2160 +3922 3021 3029 2160 +3923 3021 1474 3029 +3924 2160 3029 1245 +3925 1474 3030 3029 +3926 3030 3031 3029 +3927 3030 1477 3031 +3928 3029 3031 1245 +3929 1474 3020 3030 +3930 3020 3032 3030 +3931 3020 1092 3032 +3932 3030 3032 1477 +3933 1245 3031 2161 +3934 3031 3033 2161 +3935 3031 1477 3033 +3936 2161 3033 385 +3937 215 2361 216 +3938 2361 3034 216 +3939 2361 1297 3034 +3940 216 3034 217 +3941 1297 3035 3034 +3942 3035 3036 3034 +3943 3035 1478 3036 +3944 3034 3036 217 +3945 1297 2360 3035 +3946 2360 3037 3035 +3947 2360 1041 3037 +3948 3035 3037 1478 +3949 217 3036 218 +3950 3036 3038 218 +3951 3036 1478 3038 +3952 218 3038 219 +3953 1041 3039 3037 +3954 3039 3040 3037 +3955 3039 1479 3040 +3956 3037 3040 1478 +3957 1479 3041 3040 +3958 3041 3042 3040 +3959 3041 1480 3042 +3960 3040 3042 1478 +3961 1479 3043 3041 +3962 3043 3044 3041 +3963 3043 1093 3044 +3964 3041 3044 1480 +3965 1478 3042 3038 +3966 3042 3045 3038 +3967 3042 1480 3045 +3968 3038 3045 219 +3969 1041 2356 3039 +3970 2356 3046 3039 +3971 2356 1296 3046 +3972 3039 3046 1479 +3973 1296 3047 3046 +3974 3047 3048 3046 +3975 3047 1481 3048 +3976 3046 3048 1479 +3977 1296 2355 3047 +3978 2355 3049 3047 +3979 2355 980 3049 +3980 3047 3049 1481 +3981 1479 3048 3043 +3982 3048 3050 3043 +3983 3048 1481 3050 +3984 3043 3050 1093 +3985 219 3045 220 +3986 3045 3051 220 +3987 3045 1480 3051 +3988 220 3051 221 +3989 1480 3052 3051 +3990 3052 3053 3051 +3991 3052 1482 3053 +3992 3051 3053 221 +3993 1480 3044 3052 +3994 3044 3054 3052 +3995 3044 1093 3054 +3996 3052 3054 1482 +3997 221 3053 222 +3998 3053 3055 222 +3999 3053 1482 3055 +4000 222 3055 223 +4001 22 2872 23 +4002 2872 3056 23 +4003 2872 1434 3056 +4004 23 3056 24 +4005 1434 3057 3056 +4006 3057 3058 3056 +4007 3057 1483 3058 +4008 3056 3058 24 +4009 1434 2878 3057 +4010 2878 3059 3057 +4011 2878 1081 3059 +4012 3057 3059 1483 +4013 24 3058 25 +4014 3058 3060 25 +4015 3058 1483 3060 +4016 25 3060 26 +4017 1081 3061 3059 +4018 3061 3062 3059 +4019 3061 1484 3062 +4020 3059 3062 1483 +4021 1484 3063 3062 +4022 3063 3064 3062 +4023 3063 1485 3064 +4024 3062 3064 1483 +4025 1484 3065 3063 +4026 3065 3066 3063 +4027 3065 1042 3066 +4028 3063 3066 1485 +4029 1483 3064 3060 +4030 3064 3067 3060 +4031 3064 1485 3067 +4032 3060 3067 26 +4033 1081 2894 3061 +4034 2894 3068 3061 +4035 2894 1440 3068 +4036 3061 3068 1484 +4037 1440 3069 3068 +4038 3069 3070 3068 +4039 3069 1302 3070 +4040 3068 3070 1484 +4041 1440 2899 3069 +4042 2899 2380 3069 +4043 2899 978 2380 +4044 3069 2380 1302 +4045 1484 3070 3065 +4046 3070 2376 3065 +4047 3070 1302 2376 +4048 3065 2376 1042 +4049 26 3067 27 +4050 3067 3071 27 +4051 3067 1485 3071 +4052 27 3071 28 +4053 1485 3072 3071 +4054 3072 3073 3071 +4055 3072 1298 3073 +4056 3071 3073 28 +4057 1485 3066 3072 +4058 3066 2366 3072 +4059 3066 1042 2366 +4060 3072 2366 1298 +4061 28 3073 29 +4062 3073 2362 29 +4063 3073 1298 2362 +4064 29 2362 30 +4065 409 3074 2110 +4066 3074 3075 2110 +4067 3074 1486 3075 +4068 2110 3075 1232 +4069 1486 3076 3075 +4070 3076 3077 3075 +4071 3076 1487 3077 +4072 3075 3077 1232 +4073 1486 3078 3076 +4074 3078 3079 3076 +4075 3078 1094 3079 +4076 3076 3079 1487 +4077 1232 3077 2114 +4078 3077 3080 2114 +4079 3077 1487 3080 +4080 2114 3080 1024 +4081 1094 3081 3079 +4082 3081 3082 3079 +4083 3081 1488 3082 +4084 3079 3082 1487 +4085 1488 3083 3082 +4086 3083 3084 3082 +4087 3083 1489 3084 +4088 3082 3084 1487 +4089 1488 3085 3083 +4090 3085 3086 3083 +4091 3085 1095 3086 +4092 3083 3086 1489 +4093 1487 3084 3080 +4094 3084 3087 3080 +4095 3084 1489 3087 +4096 3080 3087 1024 +4097 1094 3088 3081 +4098 3088 3089 3081 +4099 3088 1490 3089 +4100 3081 3089 1488 +4101 1490 3090 3089 +4102 3090 3091 3089 +4103 3090 1491 3091 +4104 3089 3091 1488 +4105 1490 3092 3090 +4106 3092 3093 3090 +4107 3092 990 3093 +4108 3090 3093 1491 +4109 1488 3091 3085 +4110 3091 3094 3085 +4111 3091 1491 3094 +4112 3085 3094 1095 +4113 1024 3087 2124 +4114 3087 3095 2124 +4115 3087 1489 3095 +4116 2124 3095 1236 +4117 1489 3096 3095 +4118 3096 3097 3095 +4119 3096 1492 3097 +4120 3095 3097 1236 +4121 1489 3086 3096 +4122 3086 3098 3096 +4123 3086 1095 3098 +4124 3096 3098 1492 +4125 1236 3097 2128 +4126 3097 3099 2128 +4127 3097 1492 3099 +4128 2128 3099 38 +4129 246 3100 245 +4130 3100 3101 245 +4131 3100 1493 3101 +4132 245 3101 244 +4133 1493 3102 3101 +4134 3102 3103 3101 +4135 3102 1494 3103 +4136 3101 3103 244 +4137 1493 3104 3102 +4138 3104 3105 3102 +4139 3104 1096 3105 +4140 3102 3105 1494 +4141 244 3103 243 +4142 3103 3106 243 +4143 3103 1494 3106 +4144 243 3106 242 +4145 1096 3107 3105 +4146 3107 3108 3105 +4147 3107 1495 3108 +4148 3105 3108 1494 +4149 1495 3109 3108 +4150 3109 3110 3108 +4151 3109 1496 3110 +4152 3108 3110 1494 +4153 1495 3111 3109 +4154 3111 3112 3109 +4155 3111 1097 3112 +4156 3109 3112 1496 +4157 1494 3110 3106 +4158 3110 3113 3106 +4159 3110 1496 3113 +4160 3106 3113 242 +4161 1096 3114 3107 +4162 3114 3115 3107 +4163 3114 1497 3115 +4164 3107 3115 1495 +4165 1497 3116 3115 +4166 3116 3117 3115 +4167 3116 1498 3117 +4168 3115 3117 1495 +4169 1497 3118 3116 +4170 3118 3119 3116 +4171 3118 972 3119 +4172 3116 3119 1498 +4173 1495 3117 3111 +4174 3117 3120 3111 +4175 3117 1498 3120 +4176 3111 3120 1097 +4177 242 3113 241 +4178 3113 3121 241 +4179 3113 1496 3121 +4180 241 3121 240 +4181 1496 3122 3121 +4182 3122 3123 3121 +4183 3122 1499 3123 +4184 3121 3123 240 +4185 1496 3112 3122 +4186 3112 3124 3122 +4187 3112 1097 3124 +4188 3122 3124 1499 +4189 240 3123 239 +4190 3123 3125 239 +4191 3123 1499 3125 +4192 239 3125 238 +4193 331 3126 330 +4194 3126 3127 330 +4195 3126 1500 3127 +4196 330 3127 329 +4197 1500 3128 3127 +4198 3128 3129 3127 +4199 3128 1501 3129 +4200 3127 3129 329 +4201 1500 3130 3128 +4202 3130 3131 3128 +4203 3130 1098 3131 +4204 3128 3131 1501 +4205 329 3129 328 +4206 3129 3132 328 +4207 3129 1501 3132 +4208 328 3132 327 +4209 1098 3133 3131 +4210 3133 3134 3131 +4211 3133 1502 3134 +4212 3131 3134 1501 +4213 1502 3135 3134 +4214 3135 3136 3134 +4215 3135 1503 3136 +4216 3134 3136 1501 +4217 1502 3137 3135 +4218 3137 3138 3135 +4219 3137 1099 3138 +4220 3135 3138 1503 +4221 1501 3136 3132 +4222 3136 3139 3132 +4223 3136 1503 3139 +4224 3132 3139 327 +4225 1098 3140 3133 +4226 3140 3141 3133 +4227 3140 1504 3141 +4228 3133 3141 1502 +4229 1504 3142 3141 +4230 3142 3143 3141 +4231 3142 1505 3143 +4232 3141 3143 1502 +4233 1504 3144 3142 +4234 3144 3145 3142 +4235 3144 973 3145 +4236 3142 3145 1505 +4237 1502 3143 3137 +4238 3143 3146 3137 +4239 3143 1505 3146 +4240 3137 3146 1099 +4241 327 3139 326 +4242 3139 3147 326 +4243 3139 1503 3147 +4244 326 3147 325 +4245 1503 3148 3147 +4246 3148 3149 3147 +4247 3148 1506 3149 +4248 3147 3149 325 +4249 1503 3138 3148 +4250 3138 3150 3148 +4251 3138 1099 3150 +4252 3148 3150 1506 +4253 325 3149 324 +4254 3149 3151 324 +4255 3149 1506 3151 +4256 324 3151 323 +4257 284 3152 285 +4258 3152 3153 285 +4259 3152 1507 3153 +4260 285 3153 286 +4261 1507 3154 3153 +4262 3154 3155 3153 +4263 3154 1508 3155 +4264 3153 3155 286 +4265 1507 3156 3154 +4266 3156 3157 3154 +4267 3156 1100 3157 +4268 3154 3157 1508 +4269 286 3155 287 +4270 3155 3158 287 +4271 3155 1508 3158 +4272 287 3158 288 +4273 1100 3159 3157 +4274 3159 3160 3157 +4275 3159 1509 3160 +4276 3157 3160 1508 +4277 1509 3161 3160 +4278 3161 3162 3160 +4279 3161 1510 3162 +4280 3160 3162 1508 +4281 1509 3163 3161 +4282 3163 3164 3161 +4283 3163 1101 3164 +4284 3161 3164 1510 +4285 1508 3162 3158 +4286 3162 3165 3158 +4287 3162 1510 3165 +4288 3158 3165 288 +4289 1100 3166 3159 +4290 3166 3167 3159 +4291 3166 1511 3167 +4292 3159 3167 1509 +4293 1511 3168 3167 +4294 3168 3169 3167 +4295 3168 1512 3169 +4296 3167 3169 1509 +4297 1511 3170 3168 +4298 3170 3171 3168 +4299 3170 1010 3171 +4300 3168 3171 1512 +4301 1509 3169 3163 +4302 3169 3172 3163 +4303 3169 1512 3172 +4304 3163 3172 1101 +4305 288 3165 289 +4306 3165 3173 289 +4307 3165 1510 3173 +4308 289 3173 290 +4309 1510 3174 3173 +4310 3174 3175 3173 +4311 3174 1513 3175 +4312 3173 3175 290 +4313 1510 3164 3174 +4314 3164 3176 3174 +4315 3164 1101 3176 +4316 3174 3176 1513 +4317 290 3175 291 +4318 3175 3177 291 +4319 3175 1513 3177 +4320 291 3177 292 +4321 300 3178 301 +4322 3178 3179 301 +4323 3178 1514 3179 +4324 301 3179 302 +4325 1514 3180 3179 +4326 3180 3181 3179 +4327 3180 1515 3181 +4328 3179 3181 302 +4329 1514 3182 3180 +4330 3182 3183 3180 +4331 3182 1102 3183 +4332 3180 3183 1515 +4333 302 3181 303 +4334 3181 3184 303 +4335 3181 1515 3184 +4336 303 3184 304 +4337 1102 3185 3183 +4338 3185 3186 3183 +4339 3185 1516 3186 +4340 3183 3186 1515 +4341 1516 3187 3186 +4342 3187 3188 3186 +4343 3187 1517 3188 +4344 3186 3188 1515 +4345 1516 3189 3187 +4346 3189 3190 3187 +4347 3189 1103 3190 +4348 3187 3190 1517 +4349 1515 3188 3184 +4350 3188 3191 3184 +4351 3188 1517 3191 +4352 3184 3191 304 +4353 1102 3192 3185 +4354 3192 3193 3185 +4355 3192 1518 3193 +4356 3185 3193 1516 +4357 1518 3194 3193 +4358 3194 3195 3193 +4359 3194 1519 3195 +4360 3193 3195 1516 +4361 1518 3196 3194 +4362 3196 3197 3194 +4363 3196 1009 3197 +4364 3194 3197 1519 +4365 1516 3195 3189 +4366 3195 3198 3189 +4367 3195 1519 3198 +4368 3189 3198 1103 +4369 304 3191 305 +4370 3191 3199 305 +4371 3191 1517 3199 +4372 305 3199 306 +4373 1517 3200 3199 +4374 3200 3201 3199 +4375 3200 1520 3201 +4376 3199 3201 306 +4377 1517 3190 3200 +4378 3190 3202 3200 +4379 3190 1103 3202 +4380 3200 3202 1520 +4381 306 3201 307 +4382 3201 3203 307 +4383 3201 1520 3203 +4384 307 3203 308 +4385 11 3204 277 +4386 3204 3205 277 +4387 3204 1521 3205 +4388 277 3205 278 +4389 1521 3206 3205 +4390 3206 3207 3205 +4391 3206 1522 3207 +4392 3205 3207 278 +4393 1521 3208 3206 +4394 3208 3209 3206 +4395 3208 1104 3209 +4396 3206 3209 1522 +4397 278 3207 279 +4398 3207 3210 279 +4399 3207 1522 3210 +4400 279 3210 280 +4401 1104 3211 3209 +4402 3211 3212 3209 +4403 3211 1523 3212 +4404 3209 3212 1522 +4405 1523 3213 3212 +4406 3213 3214 3212 +4407 3213 1524 3214 +4408 3212 3214 1522 +4409 1523 3215 3213 +4410 3215 3216 3213 +4411 3215 1105 3216 +4412 3213 3216 1524 +4413 1522 3214 3210 +4414 3214 3217 3210 +4415 3214 1524 3217 +4416 3210 3217 280 +4417 1104 3218 3211 +4418 3218 3219 3211 +4419 3218 1525 3219 +4420 3211 3219 1523 +4421 1525 3220 3219 +4422 3220 3221 3219 +4423 3220 1526 3221 +4424 3219 3221 1523 +4425 1525 3222 3220 +4426 3222 3223 3220 +4427 3222 1008 3223 +4428 3220 3223 1526 +4429 1523 3221 3215 +4430 3221 3224 3215 +4431 3221 1526 3224 +4432 3215 3224 1105 +4433 280 3217 281 +4434 3217 3225 281 +4435 3217 1524 3225 +4436 281 3225 282 +4437 1524 3226 3225 +4438 3226 3227 3225 +4439 3226 1527 3227 +4440 3225 3227 282 +4441 1524 3216 3226 +4442 3216 3228 3226 +4443 3216 1105 3228 +4444 3226 3228 1527 +4445 282 3227 283 +4446 3227 3229 283 +4447 3227 1527 3229 +4448 283 3229 284 +4449 107 2980 108 +4450 2980 3230 108 +4451 2980 1464 3230 +4452 108 3230 109 +4453 1464 3231 3230 +4454 3231 3232 3230 +4455 3231 1528 3232 +4456 3230 3232 109 +4457 1464 2986 3231 +4458 2986 3233 3231 +4459 2986 1090 3233 +4460 3231 3233 1528 +4461 109 3232 110 +4462 3232 3234 110 +4463 3232 1528 3234 +4464 110 3234 111 +4465 1090 3235 3233 +4466 3235 3236 3233 +4467 3235 1529 3236 +4468 3233 3236 1528 +4469 1529 3237 3236 +4470 3237 3238 3236 +4471 3237 1530 3238 +4472 3236 3238 1528 +4473 1529 3239 3237 +4474 3239 3240 3237 +4475 3239 1018 3240 +4476 3237 3240 1530 +4477 1528 3238 3234 +4478 3238 3241 3234 +4479 3238 1530 3241 +4480 3234 3241 111 +4481 1090 3002 3235 +4482 3002 3242 3235 +4483 3002 1470 3242 +4484 3235 3242 1529 +4485 1470 3243 3242 +4486 3243 3244 3242 +4487 3243 1206 3244 +4488 3242 3244 1529 +4489 1470 3007 3243 +4490 3007 2008 3243 +4491 3007 974 2008 +4492 3243 2008 1206 +4493 1529 3244 3239 +4494 3244 2014 3239 +4495 3244 1206 2014 +4496 3239 2014 1018 +4497 111 3241 112 +4498 3241 3245 112 +4499 3241 1530 3245 +4500 112 3245 113 +4501 1530 3246 3245 +4502 3246 3247 3245 +4503 3246 1212 3247 +4504 3245 3247 113 +4505 1530 3240 3246 +4506 3240 2030 3246 +4507 3240 1018 2030 +4508 3246 2030 1212 +4509 113 3247 114 +4510 3247 2035 114 +4511 3247 1212 2035 +4512 114 2035 115 +4513 130 2164 131 +4514 2164 3248 131 +4515 2164 1248 3248 +4516 131 3248 132 +4517 1248 3249 3248 +4518 3249 3250 3248 +4519 3249 1531 3250 +4520 3248 3250 132 +4521 1248 2170 3249 +4522 2170 3251 3249 +4523 2170 1030 3251 +4524 3249 3251 1531 +4525 132 3250 133 +4526 3250 3252 133 +4527 3250 1531 3252 +4528 133 3252 134 +4529 1030 3253 3251 +4530 3253 3254 3251 +4531 3253 1532 3254 +4532 3251 3254 1531 +4533 1532 3255 3254 +4534 3255 3256 3254 +4535 3255 1533 3256 +4536 3254 3256 1531 +4537 1532 3257 3255 +4538 3257 3258 3255 +4539 3257 1106 3258 +4540 3255 3258 1533 +4541 1531 3256 3252 +4542 3256 3259 3252 +4543 3256 1533 3259 +4544 3252 3259 134 +4545 1030 2186 3253 +4546 2186 3260 3253 +4547 2186 1254 3260 +4548 3253 3260 1532 +4549 1254 3261 3260 +4550 3261 3262 3260 +4551 3261 1534 3262 +4552 3260 3262 1532 +4553 1254 2191 3261 +4554 2191 3263 3261 +4555 2191 975 3263 +4556 3261 3263 1534 +4557 1532 3262 3257 +4558 3262 3264 3257 +4559 3262 1534 3264 +4560 3257 3264 1106 +4561 134 3259 135 +4562 3259 3265 135 +4563 3259 1533 3265 +4564 135 3265 136 +4565 1533 3266 3265 +4566 3266 3267 3265 +4567 3266 1535 3267 +4568 3265 3267 136 +4569 1533 3258 3266 +4570 3258 3268 3266 +4571 3258 1106 3268 +4572 3266 3268 1535 +4573 136 3267 137 +4574 3267 3269 137 +4575 3267 1535 3269 +4576 137 3269 138 +4577 46 2724 47 +4578 2724 3270 47 +4579 2724 1394 3270 +4580 47 3270 48 +4581 1394 3271 3270 +4582 3271 3272 3270 +4583 3271 1536 3272 +4584 3270 3272 48 +4585 1394 2723 3271 +4586 2723 3273 3271 +4587 2723 1068 3273 +4588 3271 3273 1536 +4589 48 3272 49 +4590 3272 3274 49 +4591 3272 1536 3274 +4592 49 3274 50 +4593 1068 3275 3273 +4594 3275 3276 3273 +4595 3275 1537 3276 +4596 3273 3276 1536 +4597 1537 3277 3276 +4598 3277 3278 3276 +4599 3277 1538 3278 +4600 3276 3278 1536 +4601 1537 3279 3277 +4602 3279 3280 3277 +4603 3279 1107 3280 +4604 3277 3280 1538 +4605 1536 3278 3274 +4606 3278 3281 3274 +4607 3278 1538 3281 +4608 3274 3281 50 +4609 1068 2718 3275 +4610 2718 3282 3275 +4611 2718 1393 3282 +4612 3275 3282 1537 +4613 1393 3283 3282 +4614 3283 3284 3282 +4615 3283 1539 3284 +4616 3282 3284 1537 +4617 1393 2717 3283 +4618 2717 3285 3283 +4619 2717 992 3285 +4620 3283 3285 1539 +4621 1537 3284 3279 +4622 3284 3286 3279 +4623 3284 1539 3286 +4624 3279 3286 1107 +4625 50 3281 51 +4626 3281 3287 51 +4627 3281 1538 3287 +4628 51 3287 52 +4629 1538 3288 3287 +4630 3288 3289 3287 +4631 3288 1540 3289 +4632 3287 3289 52 +4633 1538 3280 3288 +4634 3280 3290 3288 +4635 3280 1107 3290 +4636 3288 3290 1540 +4637 52 3289 53 +4638 3289 3291 53 +4639 3289 1540 3291 +4640 53 3291 2 +4641 8 3292 192 +4642 3292 3293 192 +4643 3292 1541 3293 +4644 192 3293 193 +4645 1541 3294 3293 +4646 3294 3295 3293 +4647 3294 1542 3295 +4648 3293 3295 193 +4649 1541 3296 3294 +4650 3296 3297 3294 +4651 3296 1108 3297 +4652 3294 3297 1542 +4653 193 3295 194 +4654 3295 3298 194 +4655 3295 1542 3298 +4656 194 3298 195 +4657 1108 3299 3297 +4658 3299 3300 3297 +4659 3299 1543 3300 +4660 3297 3300 1542 +4661 1543 3301 3300 +4662 3301 3302 3300 +4663 3301 1544 3302 +4664 3300 3302 1542 +4665 1543 3303 3301 +4666 3303 3304 3301 +4667 3303 1051 3304 +4668 3301 3304 1544 +4669 1542 3302 3298 +4670 3302 3305 3298 +4671 3302 1544 3305 +4672 3298 3305 195 +4673 1108 3306 3299 +4674 3306 3307 3299 +4675 3306 1545 3307 +4676 3299 3307 1543 +4677 1545 3308 3307 +4678 3308 3309 3307 +4679 3308 1339 3309 +4680 3307 3309 1543 +4681 1545 3310 3308 +4682 3310 2526 3308 +4683 3310 993 2526 +4684 3308 2526 1339 +4685 1543 3309 3303 +4686 3309 2522 3303 +4687 3309 1339 2522 +4688 3303 2522 1051 +4689 195 3305 196 +4690 3305 3311 196 +4691 3305 1544 3311 +4692 196 3311 197 +4693 1544 3312 3311 +4694 3312 3313 3311 +4695 3312 1334 3313 +4696 3311 3313 197 +4697 1544 3304 3312 +4698 3304 2511 3312 +4699 3304 1051 2511 +4700 3312 2511 1334 +4701 197 3313 198 +4702 3313 2506 198 +4703 3313 1334 2506 +4704 198 2506 199 +4705 1 3314 15 +4706 3314 3315 15 +4707 3314 1546 3315 +4708 15 3315 16 +4709 1546 3316 3315 +4710 3316 3317 3315 +4711 3316 1547 3317 +4712 3315 3317 16 +4713 1546 3318 3316 +4714 3318 3319 3316 +4715 3318 1109 3319 +4716 3316 3319 1547 +4717 16 3317 17 +4718 3317 3320 17 +4719 3317 1547 3320 +4720 17 3320 18 +4721 1109 3321 3319 +4722 3321 3322 3319 +4723 3321 1548 3322 +4724 3319 3322 1547 +4725 1548 3323 3322 +4726 3323 3324 3322 +4727 3323 1549 3324 +4728 3322 3324 1547 +4729 1548 3325 3323 +4730 3325 3326 3323 +4731 3325 1110 3326 +4732 3323 3326 1549 +4733 1547 3324 3320 +4734 3324 3327 3320 +4735 3324 1549 3327 +4736 3320 3327 18 +4737 1109 3328 3321 +4738 3328 3329 3321 +4739 3328 1550 3329 +4740 3321 3329 1548 +4741 1550 3330 3329 +4742 3330 3331 3329 +4743 3330 1551 3331 +4744 3329 3331 1548 +4745 1550 3332 3330 +4746 3332 3333 3330 +4747 3332 995 3333 +4748 3330 3333 1551 +4749 1548 3331 3325 +4750 3331 3334 3325 +4751 3331 1551 3334 +4752 3325 3334 1110 +4753 18 3327 19 +4754 3327 3335 19 +4755 3327 1549 3335 +4756 19 3335 20 +4757 1549 3336 3335 +4758 3336 3337 3335 +4759 3336 1552 3337 +4760 3335 3337 20 +4761 1549 3326 3336 +4762 3326 3338 3336 +4763 3326 1110 3338 +4764 3336 3338 1552 +4765 20 3337 21 +4766 3337 3339 21 +4767 3337 1552 3339 +4768 21 3339 22 +4769 223 3340 224 +4770 3340 3341 224 +4771 3340 1553 3341 +4772 224 3341 225 +4773 1553 3342 3341 +4774 3342 3343 3341 +4775 3342 1554 3343 +4776 3341 3343 225 +4777 1553 3344 3342 +4778 3344 3345 3342 +4779 3344 1111 3345 +4780 3342 3345 1554 +4781 225 3343 226 +4782 3343 3346 226 +4783 3343 1554 3346 +4784 226 3346 227 +4785 1111 3347 3345 +4786 3347 3348 3345 +4787 3347 1555 3348 +4788 3345 3348 1554 +4789 1555 3349 3348 +4790 3349 3350 3348 +4791 3349 1556 3350 +4792 3348 3350 1554 +4793 1555 3351 3349 +4794 3351 3352 3349 +4795 3351 1112 3352 +4796 3349 3352 1556 +4797 1554 3350 3346 +4798 3350 3353 3346 +4799 3350 1556 3353 +4800 3346 3353 227 +4801 1111 3354 3347 +4802 3354 3355 3347 +4803 3354 1557 3355 +4804 3347 3355 1555 +4805 1557 3356 3355 +4806 3356 3357 3355 +4807 3356 1558 3357 +4808 3355 3357 1555 +4809 1557 3358 3356 +4810 3358 3359 3356 +4811 3358 996 3359 +4812 3356 3359 1558 +4813 1555 3357 3351 +4814 3357 3360 3351 +4815 3357 1558 3360 +4816 3351 3360 1112 +4817 227 3353 228 +4818 3353 3361 228 +4819 3353 1556 3361 +4820 228 3361 229 +4821 1556 3362 3361 +4822 3362 3363 3361 +4823 3362 1559 3363 +4824 3361 3363 229 +4825 1556 3352 3362 +4826 3352 3364 3362 +4827 3352 1112 3364 +4828 3362 3364 1559 +4829 229 3363 230 +4830 3363 3365 230 +4831 3363 1559 3365 +4832 230 3365 9 +4833 308 2750 309 +4834 2750 3366 309 +4835 2750 1401 3366 +4836 309 3366 310 +4837 1401 3367 3366 +4838 3367 3368 3366 +4839 3367 1560 3368 +4840 3366 3368 310 +4841 1401 2749 3367 +4842 2749 3369 3367 +4843 2749 1070 3369 +4844 3367 3369 1560 +4845 310 3368 311 +4846 3368 3370 311 +4847 3368 1560 3370 +4848 311 3370 312 +4849 1070 3371 3369 +4850 3371 3372 3369 +4851 3371 1561 3372 +4852 3369 3372 1560 +4853 1561 3373 3372 +4854 3373 3374 3372 +4855 3373 1562 3374 +4856 3372 3374 1560 +4857 1561 3375 3373 +4858 3375 3376 3373 +4859 3375 1113 3376 +4860 3373 3376 1562 +4861 1560 3374 3370 +4862 3374 3377 3370 +4863 3374 1562 3377 +4864 3370 3377 312 +4865 1070 2744 3371 +4866 2744 3378 3371 +4867 2744 1400 3378 +4868 3371 3378 1561 +4869 1400 3379 3378 +4870 3379 3380 3378 +4871 3379 1563 3380 +4872 3378 3380 1561 +4873 1400 2743 3379 +4874 2743 3381 3379 +4875 2743 994 3381 +4876 3379 3381 1563 +4877 1561 3380 3375 +4878 3380 3382 3375 +4879 3380 1563 3382 +4880 3375 3382 1113 +4881 312 3377 313 +4882 3377 3383 313 +4883 3377 1562 3383 +4884 313 3383 314 +4885 1562 3384 3383 +4886 3384 3385 3383 +4887 3384 1564 3385 +4888 3383 3385 314 +4889 1562 3376 3384 +4890 3376 3386 3384 +4891 3376 1113 3386 +4892 3384 3386 1564 +4893 314 3385 315 +4894 3385 3387 315 +4895 3385 1564 3387 +4896 315 3387 12 +4897 986 3388 3390 +4898 3388 3389 3390 +4899 3388 1565 3389 +4900 3390 3389 1567 +4901 1565 3391 3389 +4902 3391 3392 3389 +4903 3391 1566 3392 +4904 3389 3392 1567 +4905 1565 3393 3391 +4906 3393 3394 3391 +4907 3393 1114 3394 +4908 3391 3394 1566 +4909 1567 3392 3396 +4910 3392 3395 3396 +4911 3392 1566 3395 +4912 3396 3395 1116 +4913 1114 3397 3394 +4914 3397 3398 3394 +4915 3397 1568 3398 +4916 3394 3398 1566 +4917 1568 3399 3398 +4918 3399 3400 3398 +4919 3399 1569 3400 +4920 3398 3400 1566 +4921 1568 3401 3399 +4922 3401 3402 3399 +4923 3401 1115 3402 +4924 3399 3402 1569 +4925 1566 3400 3395 +4926 3400 3403 3395 +4927 3400 1569 3403 +4928 3395 3403 1116 +4929 1114 3404 3397 +4930 3404 3405 3397 +4931 3404 1570 3405 +4932 3397 3405 1568 +4933 1570 3406 3405 +4934 3406 3407 3405 +4935 3406 1571 3407 +4936 3405 3407 1568 +4937 1570 3408 3406 +4938 3408 3409 3406 +4939 3408 1007 3409 +4940 3406 3409 1571 +4941 1568 3407 3401 +4942 3407 3410 3401 +4943 3407 1571 3410 +4944 3401 3410 1115 +4945 1116 3403 3412 +4946 3403 3411 3412 +4947 3403 1569 3411 +4948 3412 3411 1573 +4949 1569 3413 3411 +4950 3413 3414 3411 +4951 3413 1572 3414 +4952 3411 3414 1573 +4953 1569 3402 3413 +4954 3402 3415 3413 +4955 3402 1115 3415 +4956 3413 3415 1572 +4957 1573 3414 3417 +4958 3414 3416 3417 +4959 3414 1572 3416 +4960 3417 3416 979 +4961 973 3418 3420 +4962 3418 3419 3420 +4963 3418 1574 3419 +4964 3420 3419 1576 +4965 1574 3421 3419 +4966 3421 3422 3419 +4967 3421 1575 3422 +4968 3419 3422 1576 +4969 1574 3423 3421 +4970 3423 3424 3421 +4971 3423 1117 3424 +4972 3421 3424 1575 +4973 1576 3422 3426 +4974 3422 3425 3426 +4975 3422 1575 3425 +4976 3426 3425 1118 +4977 1117 3427 3424 +4978 3427 3428 3424 +4979 3427 1577 3428 +4980 3424 3428 1575 +4981 1577 3429 3428 +4982 3429 3430 3428 +4983 3429 1578 3430 +4984 3428 3430 1575 +4985 1577 3431 3429 +4986 3431 3432 3429 +4987 3431 1114 3432 +4988 3429 3432 1578 +4989 1575 3430 3425 +4990 3430 3433 3425 +4991 3430 1578 3433 +4992 3425 3433 1118 +4993 1117 3434 3427 +4994 3434 3435 3427 +4995 3434 1579 3435 +4996 3427 3435 1577 +4997 1579 3436 3435 +4998 3436 3437 3435 +4999 3436 1570 3437 +5000 3435 3437 1577 +5001 1579 3438 3436 +5002 3438 3408 3436 +5003 3438 1007 3408 +5004 3436 3408 1570 +5005 1577 3437 3431 +5006 3437 3404 3431 +5007 3437 1570 3404 +5008 3431 3404 1114 +5009 1118 3433 3440 +5010 3433 3439 3440 +5011 3433 1578 3439 +5012 3440 3439 1580 +5013 1578 3441 3439 +5014 3441 3442 3439 +5015 3441 1565 3442 +5016 3439 3442 1580 +5017 1578 3432 3441 +5018 3432 3393 3441 +5019 3432 1114 3393 +5020 3441 3393 1565 +5021 1580 3442 3443 +5022 3442 3388 3443 +5023 3442 1565 3388 +5024 3443 3388 986 +5025 976 1978 2999 +5026 1978 3444 2999 +5027 1978 1195 3444 +5028 2999 3444 1468 +5029 1195 3445 3444 +5030 3445 3446 3444 +5031 3445 1581 3446 +5032 3444 3446 1468 +5033 1195 1977 3445 +5034 1977 3447 3445 +5035 1977 1012 3447 +5036 3445 3447 1581 +5037 1468 3446 3000 +5038 3446 3448 3000 +5039 3446 1581 3448 +5040 3000 3448 1089 +5041 1012 3449 3447 +5042 3449 3450 3447 +5043 3449 1582 3450 +5044 3447 3450 1581 +5045 1582 3451 3450 +5046 3451 3452 3450 +5047 3451 1583 3452 +5048 3450 3452 1581 +5049 1582 3453 3451 +5050 3453 3454 3451 +5051 3453 1050 3454 +5052 3451 3454 1583 +5053 1581 3452 3448 +5054 3452 3455 3448 +5055 3452 1583 3455 +5056 3448 3455 1089 +5057 1012 1972 3449 +5058 1972 3456 3449 +5059 1972 1194 3456 +5060 3449 3456 1582 +5061 1194 3457 3456 +5062 3457 3458 3456 +5063 3457 1329 3458 +5064 3456 3458 1582 +5065 1194 1971 3457 +5066 1971 2482 3457 +5067 1971 977 2482 +5068 3457 2482 1329 +5069 1582 3458 3453 +5070 3458 2488 3453 +5071 3458 1329 2488 +5072 3453 2488 1050 +5073 1089 3455 3005 +5074 3455 3459 3005 +5075 3455 1583 3459 +5076 3005 3459 1469 +5077 1583 3460 3459 +5078 3460 3461 3459 +5079 3460 1333 3461 +5080 3459 3461 1469 +5081 1583 3454 3460 +5082 3454 2502 3460 +5083 3454 1050 2502 +5084 3460 2502 1333 +5085 1469 3461 3006 +5086 3461 2505 3006 +5087 3461 1333 2505 +5088 3006 2505 974 +5089 14 3462 2863 +5090 3462 3463 2863 +5091 3462 1584 3463 +5092 2863 3463 1430 +5093 1584 3464 3463 +5094 3464 3465 3463 +5095 3464 1585 3465 +5096 3463 3465 1430 +5097 1584 3466 3464 +5098 3466 3467 3464 +5099 3466 1119 3467 +5100 3464 3467 1585 +5101 1430 3465 2864 +5102 3465 3468 2864 +5103 3465 1585 3468 +5104 2864 3468 1078 +5105 1119 3469 3467 +5106 3469 3470 3467 +5107 3469 1586 3470 +5108 3467 3470 1585 +5109 1586 3471 3470 +5110 3471 3472 3470 +5111 3471 1587 3472 +5112 3470 3472 1585 +5113 1586 3473 3471 +5114 3473 3474 3471 +5115 3473 1102 3474 +5116 3471 3474 1587 +5117 1585 3472 3468 +5118 3472 3475 3468 +5119 3472 1587 3475 +5120 3468 3475 1078 +5121 1119 3476 3469 +5122 3476 3477 3469 +5123 3476 1588 3477 +5124 3469 3477 1586 +5125 1588 3478 3477 +5126 3478 3479 3477 +5127 3478 1518 3479 +5128 3477 3479 1586 +5129 1588 3480 3478 +5130 3480 3196 3478 +5131 3480 1009 3196 +5132 3478 3196 1518 +5133 1586 3479 3473 +5134 3479 3192 3473 +5135 3479 1518 3192 +5136 3473 3192 1102 +5137 1078 3475 2868 +5138 3475 3481 2868 +5139 3475 1587 3481 +5140 2868 3481 1431 +5141 1587 3482 3481 +5142 3482 3483 3481 +5143 3482 1514 3483 +5144 3481 3483 1431 +5145 1587 3474 3482 +5146 3474 3182 3482 +5147 3474 1102 3182 +5148 3482 3182 1514 +5149 1431 3483 2869 +5150 3483 3178 2869 +5151 3483 1514 3178 +5152 2869 3178 300 +5153 292 3177 2844 +5154 3177 3484 2844 +5155 3177 1513 3484 +5156 2844 3484 1425 +5157 1513 3485 3484 +5158 3485 3486 3484 +5159 3485 1589 3486 +5160 3484 3486 1425 +5161 1513 3176 3485 +5162 3176 3487 3485 +5163 3176 1101 3487 +5164 3485 3487 1589 +5165 1425 3486 2848 +5166 3486 3488 2848 +5167 3486 1589 3488 +5168 2848 3488 1077 +5169 1101 3489 3487 +5170 3489 3490 3487 +5171 3489 1590 3490 +5172 3487 3490 1589 +5173 1590 3491 3490 +5174 3491 3492 3490 +5175 3491 1591 3492 +5176 3490 3492 1589 +5177 1590 3493 3491 +5178 3493 3494 3491 +5179 3493 1120 3494 +5180 3491 3494 1591 +5181 1589 3492 3488 +5182 3492 3495 3488 +5183 3492 1591 3495 +5184 3488 3495 1077 +5185 1101 3172 3489 +5186 3172 3496 3489 +5187 3172 1512 3496 +5188 3489 3496 1590 +5189 1512 3497 3496 +5190 3497 3498 3496 +5191 3497 1592 3498 +5192 3496 3498 1590 +5193 1512 3171 3497 +5194 3171 3499 3497 +5195 3171 1010 3499 +5196 3497 3499 1592 +5197 1590 3498 3493 +5198 3498 3500 3493 +5199 3498 1592 3500 +5200 3493 3500 1120 +5201 1077 3495 2858 +5202 3495 3501 2858 +5203 3495 1591 3501 +5204 2858 3501 1429 +5205 1591 3502 3501 +5206 3502 3503 3501 +5207 3502 1593 3503 +5208 3501 3503 1429 +5209 1591 3494 3502 +5210 3494 3504 3502 +5211 3494 1120 3504 +5212 3502 3504 1593 +5213 1429 3503 2862 +5214 3503 3505 2862 +5215 3503 1593 3505 +5216 2862 3505 14 +5217 983 2668 2977 +5218 2668 3506 2977 +5219 2668 1378 3506 +5220 2977 3506 1461 +5221 1378 3507 3506 +5222 3507 3508 3506 +5223 3507 1594 3508 +5224 3506 3508 1461 +5225 1378 2667 3507 +5226 2667 3509 3507 +5227 2667 1063 3509 +5228 3507 3509 1594 +5229 1461 3508 2974 +5230 3508 3510 2974 +5231 3508 1594 3510 +5232 2974 3510 1087 +5233 1063 3511 3509 +5234 3511 3512 3509 +5235 3511 1595 3512 +5236 3509 3512 1594 +5237 1595 3513 3512 +5238 3513 3514 3512 +5239 3513 1596 3514 +5240 3512 3514 1594 +5241 1595 3515 3513 +5242 3515 3516 3513 +5243 3515 1100 3516 +5244 3513 3516 1596 +5245 1594 3514 3510 +5246 3514 3517 3510 +5247 3514 1596 3517 +5248 3510 3517 1087 +5249 1063 2662 3511 +5250 2662 3518 3511 +5251 2662 1377 3518 +5252 3511 3518 1595 +5253 1377 3519 3518 +5254 3519 3520 3518 +5255 3519 1511 3520 +5256 3518 3520 1595 +5257 1377 2661 3519 +5258 2661 3170 3519 +5259 2661 1010 3170 +5260 3519 3170 1511 +5261 1595 3520 3515 +5262 3520 3166 3515 +5263 3520 1511 3166 +5264 3515 3166 1100 +5265 1087 3517 2960 +5266 3517 3521 2960 +5267 3517 1596 3521 +5268 2960 3521 1457 +5269 1596 3522 3521 +5270 3522 3523 3521 +5271 3522 1507 3523 +5272 3521 3523 1457 +5273 1596 3516 3522 +5274 3516 3156 3522 +5275 3516 1100 3156 +5276 3522 3156 1507 +5277 1457 3523 2954 +5278 3523 3152 2954 +5279 3523 1507 3152 +5280 2954 3152 284 +5281 308 3203 2751 +5282 3203 3524 2751 +5283 3203 1520 3524 +5284 2751 3524 1402 +5285 1520 3525 3524 +5286 3525 3526 3524 +5287 3525 1597 3526 +5288 3524 3526 1402 +5289 1520 3202 3525 +5290 3202 3527 3525 +5291 3202 1103 3527 +5292 3525 3527 1597 +5293 1402 3526 2746 +5294 3526 3528 2746 +5295 3526 1597 3528 +5296 2746 3528 1071 +5297 1103 3529 3527 +5298 3529 3530 3527 +5299 3529 1598 3530 +5300 3527 3530 1597 +5301 1598 3531 3530 +5302 3531 3532 3530 +5303 3531 1599 3532 +5304 3530 3532 1597 +5305 1598 3533 3531 +5306 3533 3534 3531 +5307 3533 1065 3534 +5308 3531 3534 1599 +5309 1597 3532 3528 +5310 3532 3535 3528 +5311 3532 1599 3535 +5312 3528 3535 1071 +5313 1103 3198 3529 +5314 3198 3536 3529 +5315 3198 1519 3536 +5316 3529 3536 1598 +5317 1519 3537 3536 +5318 3537 3538 3536 +5319 3537 1385 3538 +5320 3536 3538 1598 +5321 1519 3197 3537 +5322 3197 2690 3537 +5323 3197 1009 2690 +5324 3537 2690 1385 +5325 1598 3538 3533 +5326 3538 2686 3533 +5327 3538 1385 2686 +5328 3533 2686 1065 +5329 1071 3535 2732 +5330 3535 3539 2732 +5331 3535 1599 3539 +5332 2732 3539 1397 +5333 1599 3540 3539 +5334 3540 3541 3539 +5335 3540 1380 3541 +5336 3539 3541 1397 +5337 1599 3534 3540 +5338 3534 2675 3540 +5339 3534 1065 2675 +5340 3540 2675 1380 +5341 1397 3541 2727 +5342 3541 2670 2727 +5343 3541 1380 2670 +5344 2727 2670 982 +5345 997 3542 3544 +5346 3542 3543 3544 +5347 3542 1600 3543 +5348 3544 3543 1602 +5349 1600 3545 3543 +5350 3545 3546 3543 +5351 3545 1601 3546 +5352 3543 3546 1602 +5353 1600 3547 3545 +5354 3547 3548 3545 +5355 3547 1121 3548 +5356 3545 3548 1601 +5357 1602 3546 3550 +5358 3546 3549 3550 +5359 3546 1601 3549 +5360 3550 3549 1123 +5361 1121 3551 3548 +5362 3551 3552 3548 +5363 3551 1603 3552 +5364 3548 3552 1601 +5365 1603 3553 3552 +5366 3553 3554 3552 +5367 3553 1604 3554 +5368 3552 3554 1601 +5369 1603 3555 3553 +5370 3555 3556 3553 +5371 3555 1122 3556 +5372 3553 3556 1604 +5373 1601 3554 3549 +5374 3554 3557 3549 +5375 3554 1604 3557 +5376 3549 3557 1123 +5377 1121 3558 3551 +5378 3558 3559 3551 +5379 3558 1605 3559 +5380 3551 3559 1603 +5381 1605 3560 3559 +5382 3560 3561 3559 +5383 3560 1606 3561 +5384 3559 3561 1603 +5385 1605 3562 3560 +5386 3562 3563 3560 +5387 3562 1008 3563 +5388 3560 3563 1606 +5389 1603 3561 3555 +5390 3561 3564 3555 +5391 3561 1606 3564 +5392 3555 3564 1122 +5393 1123 3557 3566 +5394 3557 3565 3566 +5395 3557 1604 3565 +5396 3566 3565 1608 +5397 1604 3567 3565 +5398 3567 3568 3565 +5399 3567 1607 3568 +5400 3565 3568 1608 +5401 1604 3556 3567 +5402 3556 3569 3567 +5403 3556 1122 3569 +5404 3567 3569 1607 +5405 1608 3568 3571 +5406 3568 3570 3571 +5407 3568 1607 3570 +5408 3571 3570 261 +5409 425 2795 426 +5410 2795 3572 426 +5411 2795 1412 3572 +5412 426 3572 427 +5413 1412 3573 3572 +5414 3573 3574 3572 +5415 3573 1609 3574 +5416 3572 3574 427 +5417 1412 2794 3573 +5418 2794 3575 3573 +5419 2794 1073 3575 +5420 3573 3575 1609 +5421 427 3574 428 +5422 3574 3576 428 +5423 3574 1609 3576 +5424 428 3576 429 +5425 1073 3577 3575 +5426 3577 3578 3575 +5427 3577 1610 3578 +5428 3575 3578 1609 +5429 1610 3579 3578 +5430 3579 3580 3578 +5431 3579 1611 3580 +5432 3578 3580 1609 +5433 1610 3581 3579 +5434 3581 3582 3579 +5435 3581 1119 3582 +5436 3579 3582 1611 +5437 1609 3580 3576 +5438 3580 3583 3576 +5439 3580 1611 3583 +5440 3576 3583 429 +5441 1073 2790 3577 +5442 2790 3584 3577 +5443 2790 1411 3584 +5444 3577 3584 1610 +5445 1411 3585 3584 +5446 3585 3586 3584 +5447 3585 1588 3586 +5448 3584 3586 1610 +5449 1411 2789 3585 +5450 2789 3480 3585 +5451 2789 1009 3480 +5452 3585 3480 1588 +5453 1610 3586 3581 +5454 3586 3476 3581 +5455 3586 1588 3476 +5456 3581 3476 1119 +5457 429 3583 430 +5458 3583 3587 430 +5459 3583 1611 3587 +5460 430 3587 431 +5461 1611 3588 3587 +5462 3588 3589 3587 +5463 3588 1584 3589 +5464 3587 3589 431 +5465 1611 3582 3588 +5466 3582 3466 3588 +5467 3582 1119 3466 +5468 3588 3466 1584 +5469 431 3589 432 +5470 3589 3462 432 +5471 3589 1584 3462 +5472 432 3462 14 +5473 14 3505 362 +5474 3505 3590 362 +5475 3505 1593 3590 +5476 362 3590 363 +5477 1593 3591 3590 +5478 3591 3592 3590 +5479 3591 1612 3592 +5480 3590 3592 363 +5481 1593 3504 3591 +5482 3504 3593 3591 +5483 3504 1120 3593 +5484 3591 3593 1612 +5485 363 3592 364 +5486 3592 3594 364 +5487 3592 1612 3594 +5488 364 3594 365 +5489 1120 3595 3593 +5490 3595 3596 3593 +5491 3595 1613 3596 +5492 3593 3596 1612 +5493 1613 3597 3596 +5494 3597 3598 3596 +5495 3597 1614 3598 +5496 3596 3598 1612 +5497 1613 3599 3597 +5498 3599 3600 3597 +5499 3599 1072 3600 +5500 3597 3600 1614 +5501 1612 3598 3594 +5502 3598 3601 3594 +5503 3598 1614 3601 +5504 3594 3601 365 +5505 1120 3500 3595 +5506 3500 3602 3595 +5507 3500 1592 3602 +5508 3595 3602 1613 +5509 1592 3603 3602 +5510 3603 3604 3602 +5511 3603 1407 3604 +5512 3602 3604 1613 +5513 1592 3499 3603 +5514 3499 2770 3603 +5515 3499 1010 2770 +5516 3603 2770 1407 +5517 1613 3604 3599 +5518 3604 2766 3599 +5519 3604 1407 2766 +5520 3599 2766 1072 +5521 365 3601 366 +5522 3601 3605 366 +5523 3601 1614 3605 +5524 366 3605 367 +5525 1614 3606 3605 +5526 3606 3607 3605 +5527 3606 1403 3607 +5528 3605 3607 367 +5529 1614 3600 3606 +5530 3600 2756 3606 +5531 3600 1072 2756 +5532 3606 2756 1403 +5533 367 3607 368 +5534 3607 2752 368 +5535 3607 1403 2752 +5536 368 2752 369 +5537 979 3608 2891 +5538 3608 3609 2891 +5539 3608 1615 3609 +5540 2891 3609 1438 +5541 1615 3610 3609 +5542 3610 3611 3609 +5543 3610 1616 3611 +5544 3609 3611 1438 +5545 1615 3612 3610 +5546 3612 3613 3610 +5547 3612 1124 3613 +5548 3610 3613 1616 +5549 1438 3611 2892 +5550 3611 3614 2892 +5551 3611 1616 3614 +5552 2892 3614 1080 +5553 1124 3615 3613 +5554 3615 3616 3613 +5555 3615 1617 3616 +5556 3613 3616 1616 +5557 1617 3617 3616 +5558 3617 3618 3616 +5559 3617 1618 3618 +5560 3616 3618 1616 +5561 1617 3619 3617 +5562 3619 3620 3617 +5563 3619 1067 3620 +5564 3617 3620 1618 +5565 1616 3618 3614 +5566 3618 3621 3614 +5567 3618 1618 3621 +5568 3614 3621 1080 +5569 1124 3622 3615 +5570 3622 3623 3615 +5571 3622 1619 3623 +5572 3615 3623 1617 +5573 1619 3624 3623 +5574 3624 3625 3623 +5575 3624 1382 3625 +5576 3623 3625 1617 +5577 1619 3626 3624 +5578 3626 2672 3624 +5579 3626 982 2672 +5580 3624 2672 1382 +5581 1617 3625 3619 +5582 3625 2678 3619 +5583 3625 1382 2678 +5584 3619 2678 1067 +5585 1080 3621 2897 +5586 3621 3627 2897 +5587 3621 1618 3627 +5588 2897 3627 1439 +5589 1618 3628 3627 +5590 3628 3629 3627 +5591 3628 1388 3629 +5592 3627 3629 1439 +5593 1618 3620 3628 +5594 3620 2694 3628 +5595 3620 1067 2694 +5596 3628 2694 1388 +5597 1439 3629 2898 +5598 3629 2699 2898 +5599 3629 1388 2699 +5600 2898 2699 978 +5601 972 3630 2295 +5602 3630 3631 2295 +5603 3630 1620 3631 +5604 2295 3631 1282 +5605 1620 3632 3631 +5606 3632 3633 3631 +5607 3632 1621 3633 +5608 3631 3633 1282 +5609 1620 3634 3632 +5610 3634 3635 3632 +5611 3634 1125 3635 +5612 3632 3635 1621 +5613 1282 3633 2290 +5614 3633 3636 2290 +5615 3633 1621 3636 +5616 2290 3636 1038 +5617 1125 3637 3635 +5618 3637 3638 3635 +5619 3637 1622 3638 +5620 3635 3638 1621 +5621 1622 3639 3638 +5622 3639 3640 3638 +5623 3639 1623 3640 +5624 3638 3640 1621 +5625 1622 3641 3639 +5626 3641 3642 3639 +5627 3641 1126 3642 +5628 3639 3642 1623 +5629 1621 3640 3636 +5630 3640 3643 3636 +5631 3640 1623 3643 +5632 3636 3643 1038 +5633 1125 3644 3637 +5634 3644 3645 3637 +5635 3644 1624 3645 +5636 3637 3645 1622 +5637 1624 3646 3645 +5638 3646 3647 3645 +5639 3646 1625 3647 +5640 3645 3647 1622 +5641 1624 3648 3646 +5642 3648 3649 3646 +5643 3648 996 3649 +5644 3646 3649 1625 +5645 1622 3647 3641 +5646 3647 3650 3641 +5647 3647 1625 3650 +5648 3641 3650 1126 +5649 1038 3643 2274 +5650 3643 3651 2274 +5651 3643 1623 3651 +5652 2274 3651 1276 +5653 1623 3652 3651 +5654 3652 3653 3651 +5655 3652 1626 3653 +5656 3651 3653 1276 +5657 1623 3642 3652 +5658 3642 3654 3652 +5659 3642 1126 3654 +5660 3652 3654 1626 +5661 1276 3653 2268 +5662 3653 3655 2268 +5663 3653 1626 3655 +5664 2268 3655 981 +5665 980 3656 3049 +5666 3656 3657 3049 +5667 3656 1627 3657 +5668 3049 3657 1481 +5669 1627 3658 3657 +5670 3658 3659 3657 +5671 3658 1628 3659 +5672 3657 3659 1481 +5673 1627 3660 3658 +5674 3660 3661 3658 +5675 3660 1127 3661 +5676 3658 3661 1628 +5677 1481 3659 3050 +5678 3659 3662 3050 +5679 3659 1628 3662 +5680 3050 3662 1093 +5681 1127 3663 3661 +5682 3663 3664 3661 +5683 3663 1629 3664 +5684 3661 3664 1628 +5685 1629 3665 3664 +5686 3665 3666 3664 +5687 3665 1630 3666 +5688 3664 3666 1628 +5689 1629 3667 3665 +5690 3667 3668 3665 +5691 3667 1128 3668 +5692 3665 3668 1630 +5693 1628 3666 3662 +5694 3666 3669 3662 +5695 3666 1630 3669 +5696 3662 3669 1093 +5697 1127 3670 3663 +5698 3670 3671 3663 +5699 3670 1631 3671 +5700 3663 3671 1629 +5701 1631 3672 3671 +5702 3672 3673 3671 +5703 3672 1632 3673 +5704 3671 3673 1629 +5705 1631 3674 3672 +5706 3674 3675 3672 +5707 3674 981 3675 +5708 3672 3675 1632 +5709 1629 3673 3667 +5710 3673 3676 3667 +5711 3673 1632 3676 +5712 3667 3676 1128 +5713 1093 3669 3054 +5714 3669 3677 3054 +5715 3669 1630 3677 +5716 3054 3677 1482 +5717 1630 3678 3677 +5718 3678 3679 3677 +5719 3678 1633 3679 +5720 3677 3679 1482 +5721 1630 3668 3678 +5722 3668 3680 3678 +5723 3668 1128 3680 +5724 3678 3680 1633 +5725 1482 3679 3055 +5726 3679 3681 3055 +5727 3679 1633 3681 +5728 3055 3681 223 +5729 184 3682 183 +5730 3682 3683 183 +5731 3682 1634 3683 +5732 183 3683 182 +5733 1634 3684 3683 +5734 3684 3685 3683 +5735 3684 1635 3685 +5736 3683 3685 182 +5737 1634 3686 3684 +5738 3686 3687 3684 +5739 3686 1129 3687 +5740 3684 3687 1635 +5741 182 3685 181 +5742 3685 3688 181 +5743 3685 1635 3688 +5744 181 3688 180 +5745 1129 3689 3687 +5746 3689 3690 3687 +5747 3689 1636 3690 +5748 3687 3690 1635 +5749 1636 3691 3690 +5750 3691 3692 3690 +5751 3691 1637 3692 +5752 3690 3692 1635 +5753 1636 3693 3691 +5754 3693 3694 3691 +5755 3693 1130 3694 +5756 3691 3694 1637 +5757 1635 3692 3688 +5758 3692 3695 3688 +5759 3692 1637 3695 +5760 3688 3695 180 +5761 1129 3696 3689 +5762 3696 3697 3689 +5763 3696 1638 3697 +5764 3689 3697 1636 +5765 1638 3698 3697 +5766 3698 3699 3697 +5767 3698 1639 3699 +5768 3697 3699 1636 +5769 1638 3700 3698 +5770 3700 3701 3698 +5771 3700 985 3701 +5772 3698 3701 1639 +5773 1636 3699 3693 +5774 3699 3702 3693 +5775 3699 1639 3702 +5776 3693 3702 1130 +5777 180 3695 179 +5778 3695 3703 179 +5779 3695 1637 3703 +5780 179 3703 178 +5781 1637 3704 3703 +5782 3704 3705 3703 +5783 3704 1640 3705 +5784 3703 3705 178 +5785 1637 3694 3704 +5786 3694 3706 3704 +5787 3694 1130 3706 +5788 3704 3706 1640 +5789 178 3705 177 +5790 3705 3707 177 +5791 3705 1640 3707 +5792 177 3707 176 +5793 92 3708 91 +5794 3708 3709 91 +5795 3708 1641 3709 +5796 91 3709 90 +5797 1641 3710 3709 +5798 3710 3711 3709 +5799 3710 1642 3711 +5800 3709 3711 90 +5801 1641 3712 3710 +5802 3712 3713 3710 +5803 3712 1131 3713 +5804 3710 3713 1642 +5805 90 3711 89 +5806 3711 3714 89 +5807 3711 1642 3714 +5808 89 3714 88 +5809 1131 3715 3713 +5810 3715 3716 3713 +5811 3715 1643 3716 +5812 3713 3716 1642 +5813 1643 3717 3716 +5814 3717 3718 3716 +5815 3717 1644 3718 +5816 3716 3718 1642 +5817 1643 3719 3717 +5818 3719 3720 3717 +5819 3719 1132 3720 +5820 3717 3720 1644 +5821 1642 3718 3714 +5822 3718 3721 3714 +5823 3718 1644 3721 +5824 3714 3721 88 +5825 1131 3722 3715 +5826 3722 3723 3715 +5827 3722 1645 3723 +5828 3715 3723 1643 +5829 1645 3724 3723 +5830 3724 3725 3723 +5831 3724 1646 3725 +5832 3723 3725 1643 +5833 1645 3726 3724 +5834 3726 3727 3724 +5835 3726 991 3727 +5836 3724 3727 1646 +5837 1643 3725 3719 +5838 3725 3728 3719 +5839 3725 1646 3728 +5840 3719 3728 1132 +5841 88 3721 87 +5842 3721 3729 87 +5843 3721 1644 3729 +5844 87 3729 86 +5845 1644 3730 3729 +5846 3730 3731 3729 +5847 3730 1647 3731 +5848 3729 3731 86 +5849 1644 3720 3730 +5850 3720 3732 3730 +5851 3720 1132 3732 +5852 3730 3732 1647 +5853 86 3731 85 +5854 3731 3733 85 +5855 3731 1647 3733 +5856 85 3733 84 +5857 354 3734 353 +5858 3734 3735 353 +5859 3734 1648 3735 +5860 353 3735 352 +5861 1648 3736 3735 +5862 3736 3737 3735 +5863 3736 1649 3737 +5864 3735 3737 352 +5865 1648 3738 3736 +5866 3738 3739 3736 +5867 3738 1133 3739 +5868 3736 3739 1649 +5869 352 3737 351 +5870 3737 3740 351 +5871 3737 1649 3740 +5872 351 3740 350 +5873 1133 3741 3739 +5874 3741 3742 3739 +5875 3741 1650 3742 +5876 3739 3742 1649 +5877 1650 3743 3742 +5878 3743 3744 3742 +5879 3743 1651 3744 +5880 3742 3744 1649 +5881 1650 3745 3743 +5882 3745 3746 3743 +5883 3745 1134 3746 +5884 3743 3746 1651 +5885 1649 3744 3740 +5886 3744 3747 3740 +5887 3744 1651 3747 +5888 3740 3747 350 +5889 1133 3748 3741 +5890 3748 3749 3741 +5891 3748 1652 3749 +5892 3741 3749 1650 +5893 1652 3750 3749 +5894 3750 3751 3749 +5895 3750 1653 3751 +5896 3749 3751 1650 +5897 1652 3752 3750 +5898 3752 3753 3750 +5899 3752 986 3753 +5900 3750 3753 1653 +5901 1650 3751 3745 +5902 3751 3754 3745 +5903 3751 1653 3754 +5904 3745 3754 1134 +5905 350 3747 349 +5906 3747 3755 349 +5907 3747 1651 3755 +5908 349 3755 348 +5909 1651 3756 3755 +5910 3756 3757 3755 +5911 3756 1654 3757 +5912 3755 3757 348 +5913 1651 3746 3756 +5914 3746 3758 3756 +5915 3746 1134 3758 +5916 3756 3758 1654 +5917 348 3757 347 +5918 3757 3759 347 +5919 3757 1654 3759 +5920 347 3759 346 +5921 2 3291 76 +5922 3291 3760 76 +5923 3291 1540 3760 +5924 76 3760 75 +5925 1540 3761 3760 +5926 3761 3762 3760 +5927 3761 1655 3762 +5928 3760 3762 75 +5929 1540 3290 3761 +5930 3290 3763 3761 +5931 3290 1107 3763 +5932 3761 3763 1655 +5933 75 3762 74 +5934 3762 3764 74 +5935 3762 1655 3764 +5936 74 3764 73 +5937 1107 3765 3763 +5938 3765 3766 3763 +5939 3765 1656 3766 +5940 3763 3766 1655 +5941 1656 3767 3766 +5942 3767 3768 3766 +5943 3767 1657 3768 +5944 3766 3768 1655 +5945 1656 3769 3767 +5946 3769 3770 3767 +5947 3769 1135 3770 +5948 3767 3770 1657 +5949 1655 3768 3764 +5950 3768 3771 3764 +5951 3768 1657 3771 +5952 3764 3771 73 +5953 1107 3286 3765 +5954 3286 3772 3765 +5955 3286 1539 3772 +5956 3765 3772 1656 +5957 1539 3773 3772 +5958 3773 3774 3772 +5959 3773 1658 3774 +5960 3772 3774 1656 +5961 1539 3285 3773 +5962 3285 3775 3773 +5963 3285 992 3775 +5964 3773 3775 1658 +5965 1656 3774 3769 +5966 3774 3776 3769 +5967 3774 1658 3776 +5968 3769 3776 1135 +5969 73 3771 72 +5970 3771 3777 72 +5971 3771 1657 3777 +5972 72 3777 71 +5973 1657 3778 3777 +5974 3778 3779 3777 +5975 3778 1659 3779 +5976 3777 3779 71 +5977 1657 3770 3778 +5978 3770 3780 3778 +5979 3770 1135 3780 +5980 3778 3780 1659 +5981 71 3779 70 +5982 3779 3781 70 +5983 3779 1659 3781 +5984 70 3781 69 +5985 979 3782 3417 +5986 3782 3783 3417 +5987 3782 1660 3783 +5988 3417 3783 1573 +5989 1660 3784 3783 +5990 3784 3785 3783 +5991 3784 1661 3785 +5992 3783 3785 1573 +5993 1660 3786 3784 +5994 3786 3787 3784 +5995 3786 1136 3787 +5996 3784 3787 1661 +5997 1573 3785 3412 +5998 3785 3788 3412 +5999 3785 1661 3788 +6000 3412 3788 1116 +6001 1136 3789 3787 +6002 3789 3790 3787 +6003 3789 1662 3790 +6004 3787 3790 1661 +6005 1662 3791 3790 +6006 3791 3792 3790 +6007 3791 1663 3792 +6008 3790 3792 1661 +6009 1662 3793 3791 +6010 3793 3794 3791 +6011 3793 1137 3794 +6012 3791 3794 1663 +6013 1661 3792 3788 +6014 3792 3795 3788 +6015 3792 1663 3795 +6016 3788 3795 1116 +6017 1136 3796 3789 +6018 3796 3797 3789 +6019 3796 1664 3797 +6020 3789 3797 1662 +6021 1664 3798 3797 +6022 3798 3799 3797 +6023 3798 1665 3799 +6024 3797 3799 1662 +6025 1664 3800 3798 +6026 3800 3801 3798 +6027 3800 995 3801 +6028 3798 3801 1665 +6029 1662 3799 3793 +6030 3799 3802 3793 +6031 3799 1665 3802 +6032 3793 3802 1137 +6033 1116 3795 3396 +6034 3795 3803 3396 +6035 3795 1663 3803 +6036 3396 3803 1567 +6037 1663 3804 3803 +6038 3804 3805 3803 +6039 3804 1666 3805 +6040 3803 3805 1567 +6041 1663 3794 3804 +6042 3794 3806 3804 +6043 3794 1137 3806 +6044 3804 3806 1666 +6045 1567 3805 3390 +6046 3805 3807 3390 +6047 3805 1666 3807 +6048 3390 3807 986 +6049 176 3808 175 +6050 3808 3809 175 +6051 3808 1667 3809 +6052 175 3809 174 +6053 1667 3810 3809 +6054 3810 3811 3809 +6055 3810 1668 3811 +6056 3809 3811 174 +6057 1667 3812 3810 +6058 3812 3813 3810 +6059 3812 1138 3813 +6060 3810 3813 1668 +6061 174 3811 173 +6062 3811 3814 173 +6063 3811 1668 3814 +6064 173 3814 172 +6065 1138 3815 3813 +6066 3815 3816 3813 +6067 3815 1669 3816 +6068 3813 3816 1668 +6069 1669 3817 3816 +6070 3817 3818 3816 +6071 3817 1670 3818 +6072 3816 3818 1668 +6073 1669 3819 3817 +6074 3819 3820 3817 +6075 3819 1108 3820 +6076 3817 3820 1670 +6077 1668 3818 3814 +6078 3818 3821 3814 +6079 3818 1670 3821 +6080 3814 3821 172 +6081 1138 3822 3815 +6082 3822 3823 3815 +6083 3822 1671 3823 +6084 3815 3823 1669 +6085 1671 3824 3823 +6086 3824 3825 3823 +6087 3824 1545 3825 +6088 3823 3825 1669 +6089 1671 3826 3824 +6090 3826 3310 3824 +6091 3826 993 3310 +6092 3824 3310 1545 +6093 1669 3825 3819 +6094 3825 3306 3819 +6095 3825 1545 3306 +6096 3819 3306 1108 +6097 172 3821 171 +6098 3821 3827 171 +6099 3821 1670 3827 +6100 171 3827 170 +6101 1670 3828 3827 +6102 3828 3829 3827 +6103 3828 1541 3829 +6104 3827 3829 170 +6105 1670 3820 3828 +6106 3820 3296 3828 +6107 3820 1108 3296 +6108 3828 3296 1541 +6109 170 3829 169 +6110 3829 3292 169 +6111 3829 1541 3292 +6112 169 3292 8 +6113 12 3387 338 +6114 3387 3830 338 +6115 3387 1564 3830 +6116 338 3830 337 +6117 1564 3831 3830 +6118 3831 3832 3830 +6119 3831 1672 3832 +6120 3830 3832 337 +6121 1564 3386 3831 +6122 3386 3833 3831 +6123 3386 1113 3833 +6124 3831 3833 1672 +6125 337 3832 336 +6126 3832 3834 336 +6127 3832 1672 3834 +6128 336 3834 335 +6129 1113 3835 3833 +6130 3835 3836 3833 +6131 3835 1673 3836 +6132 3833 3836 1672 +6133 1673 3837 3836 +6134 3837 3838 3836 +6135 3837 1674 3838 +6136 3836 3838 1672 +6137 1673 3839 3837 +6138 3839 3840 3837 +6139 3839 1139 3840 +6140 3837 3840 1674 +6141 1672 3838 3834 +6142 3838 3841 3834 +6143 3838 1674 3841 +6144 3834 3841 335 +6145 1113 3382 3835 +6146 3382 3842 3835 +6147 3382 1563 3842 +6148 3835 3842 1673 +6149 1563 3843 3842 +6150 3843 3844 3842 +6151 3843 1675 3844 +6152 3842 3844 1673 +6153 1563 3381 3843 +6154 3381 3845 3843 +6155 3381 994 3845 +6156 3843 3845 1675 +6157 1673 3844 3839 +6158 3844 3846 3839 +6159 3844 1675 3846 +6160 3839 3846 1139 +6161 335 3841 334 +6162 3841 3847 334 +6163 3841 1674 3847 +6164 334 3847 333 +6165 1674 3848 3847 +6166 3848 3849 3847 +6167 3848 1676 3849 +6168 3847 3849 333 +6169 1674 3840 3848 +6170 3840 3850 3848 +6171 3840 1139 3850 +6172 3848 3850 1676 +6173 333 3849 332 +6174 3849 3851 332 +6175 3849 1676 3851 +6176 332 3851 331 +6177 7 3852 191 +6178 3852 3853 191 +6179 3852 1677 3853 +6180 191 3853 190 +6181 1677 3854 3853 +6182 3854 3855 3853 +6183 3854 1678 3855 +6184 3853 3855 190 +6185 1677 3856 3854 +6186 3856 3857 3854 +6187 3856 1140 3857 +6188 3854 3857 1678 +6189 190 3855 189 +6190 3855 3858 189 +6191 3855 1678 3858 +6192 189 3858 188 +6193 1140 3859 3857 +6194 3859 3860 3857 +6195 3859 1679 3860 +6196 3857 3860 1678 +6197 1679 3861 3860 +6198 3861 3862 3860 +6199 3861 1680 3862 +6200 3860 3862 1678 +6201 1679 3863 3861 +6202 3863 3864 3861 +6203 3863 1141 3864 +6204 3861 3864 1680 +6205 1678 3862 3858 +6206 3862 3865 3858 +6207 3862 1680 3865 +6208 3858 3865 188 +6209 1140 3866 3859 +6210 3866 3867 3859 +6211 3866 1681 3867 +6212 3859 3867 1679 +6213 1681 3868 3867 +6214 3868 3869 3867 +6215 3868 1682 3869 +6216 3867 3869 1679 +6217 1681 3870 3868 +6218 3870 3871 3868 +6219 3870 1000 3871 +6220 3868 3871 1682 +6221 1679 3869 3863 +6222 3869 3872 3863 +6223 3869 1682 3872 +6224 3863 3872 1141 +6225 188 3865 187 +6226 3865 3873 187 +6227 3865 1680 3873 +6228 187 3873 186 +6229 1680 3874 3873 +6230 3874 3875 3873 +6231 3874 1683 3875 +6232 3873 3875 186 +6233 1680 3864 3874 +6234 3864 3876 3874 +6235 3864 1141 3876 +6236 3874 3876 1683 +6237 186 3875 185 +6238 3875 3877 185 +6239 3875 1683 3877 +6240 185 3877 184 +6241 153 3878 152 +6242 3878 3879 152 +6243 3878 1684 3879 +6244 152 3879 151 +6245 1684 3880 3879 +6246 3880 3881 3879 +6247 3880 1685 3881 +6248 3879 3881 151 +6249 1684 3882 3880 +6250 3882 3883 3880 +6251 3882 1142 3883 +6252 3880 3883 1685 +6253 151 3881 150 +6254 3881 3884 150 +6255 3881 1685 3884 +6256 150 3884 149 +6257 1142 3885 3883 +6258 3885 3886 3883 +6259 3885 1686 3886 +6260 3883 3886 1685 +6261 1686 3887 3886 +6262 3887 3888 3886 +6263 3887 1687 3888 +6264 3886 3888 1685 +6265 1686 3889 3887 +6266 3889 3890 3887 +6267 3889 1140 3890 +6268 3887 3890 1687 +6269 1685 3888 3884 +6270 3888 3891 3884 +6271 3888 1687 3891 +6272 3884 3891 149 +6273 1142 3892 3885 +6274 3892 3893 3885 +6275 3892 1688 3893 +6276 3885 3893 1686 +6277 1688 3894 3893 +6278 3894 3895 3893 +6279 3894 1681 3895 +6280 3893 3895 1686 +6281 1688 3896 3894 +6282 3896 3870 3894 +6283 3896 1000 3870 +6284 3894 3870 1681 +6285 1686 3895 3889 +6286 3895 3866 3889 +6287 3895 1681 3866 +6288 3889 3866 1140 +6289 149 3891 148 +6290 3891 3897 148 +6291 3891 1687 3897 +6292 148 3897 147 +6293 1687 3898 3897 +6294 3898 3899 3897 +6295 3898 1677 3899 +6296 3897 3899 147 +6297 1687 3890 3898 +6298 3890 3856 3898 +6299 3890 1140 3856 +6300 3898 3856 1677 +6301 147 3899 146 +6302 3899 3852 146 +6303 3899 1677 3852 +6304 146 3852 7 +6305 269 3900 268 +6306 3900 3901 268 +6307 3900 1689 3901 +6308 268 3901 267 +6309 1689 3902 3901 +6310 3902 3903 3901 +6311 3902 1690 3903 +6312 3901 3903 267 +6313 1689 3904 3902 +6314 3904 3905 3902 +6315 3904 1143 3905 +6316 3902 3905 1690 +6317 267 3903 266 +6318 3903 3906 266 +6319 3903 1690 3906 +6320 266 3906 265 +6321 1143 3907 3905 +6322 3907 3908 3905 +6323 3907 1691 3908 +6324 3905 3908 1690 +6325 1691 3909 3908 +6326 3909 3910 3908 +6327 3909 1692 3910 +6328 3908 3910 1690 +6329 1691 3911 3909 +6330 3911 3912 3909 +6331 3911 1123 3912 +6332 3909 3912 1692 +6333 1690 3910 3906 +6334 3910 3913 3906 +6335 3910 1692 3913 +6336 3906 3913 265 +6337 1143 3914 3907 +6338 3914 3915 3907 +6339 3914 1693 3915 +6340 3907 3915 1691 +6341 1693 3916 3915 +6342 3916 3917 3915 +6343 3916 1602 3917 +6344 3915 3917 1691 +6345 1693 3918 3916 +6346 3918 3544 3916 +6347 3918 997 3544 +6348 3916 3544 1602 +6349 1691 3917 3911 +6350 3917 3550 3911 +6351 3917 1602 3550 +6352 3911 3550 1123 +6353 265 3913 264 +6354 3913 3919 264 +6355 3913 1692 3919 +6356 264 3919 263 +6357 1692 3920 3919 +6358 3920 3921 3919 +6359 3920 1608 3921 +6360 3919 3921 263 +6361 1692 3912 3920 +6362 3912 3566 3920 +6363 3912 1123 3566 +6364 3920 3566 1608 +6365 263 3921 262 +6366 3921 3571 262 +6367 3921 1608 3571 +6368 262 3571 261 +6369 284 3229 2952 +6370 3229 3922 2952 +6371 3229 1527 3922 +6372 2952 3922 1455 +6373 1527 3923 3922 +6374 3923 3924 3922 +6375 3923 1694 3924 +6376 3922 3924 1455 +6377 1527 3228 3923 +6378 3228 3925 3923 +6379 3228 1105 3925 +6380 3923 3925 1694 +6381 1455 3924 2957 +6382 3924 3926 2957 +6383 3924 1694 3926 +6384 2957 3926 1086 +6385 1105 3927 3925 +6386 3927 3928 3925 +6387 3927 1695 3928 +6388 3925 3928 1694 +6389 1695 3929 3928 +6390 3929 3930 3928 +6391 3929 1696 3930 +6392 3928 3930 1694 +6393 1695 3931 3929 +6394 3931 3932 3929 +6395 3931 1144 3932 +6396 3929 3932 1696 +6397 1694 3930 3926 +6398 3930 3933 3926 +6399 3930 1696 3933 +6400 3926 3933 1086 +6401 1105 3224 3927 +6402 3224 3934 3927 +6403 3224 1526 3934 +6404 3927 3934 1695 +6405 1526 3935 3934 +6406 3935 3936 3934 +6407 3935 1697 3936 +6408 3934 3936 1695 +6409 1526 3223 3935 +6410 3223 3937 3935 +6411 3223 1008 3937 +6412 3935 3937 1697 +6413 1695 3936 3931 +6414 3936 3938 3931 +6415 3936 1697 3938 +6416 3931 3938 1144 +6417 1086 3933 2968 +6418 3933 3939 2968 +6419 3933 1696 3939 +6420 2968 3939 1460 +6421 1696 3940 3939 +6422 3940 3941 3939 +6423 3940 1698 3941 +6424 3939 3941 1460 +6425 1696 3932 3940 +6426 3932 3942 3940 +6427 3932 1144 3942 +6428 3940 3942 1698 +6429 1460 3941 2972 +6430 3941 3943 2972 +6431 3941 1698 3943 +6432 2972 3943 984 +6433 13 3944 361 +6434 3944 3945 361 +6435 3944 1699 3945 +6436 361 3945 360 +6437 1699 3946 3945 +6438 3946 3947 3945 +6439 3946 1700 3947 +6440 3945 3947 360 +6441 1699 3948 3946 +6442 3948 3949 3946 +6443 3948 1145 3949 +6444 3946 3949 1700 +6445 360 3947 359 +6446 3947 3950 359 +6447 3947 1700 3950 +6448 359 3950 358 +6449 1145 3951 3949 +6450 3951 3952 3949 +6451 3951 1701 3952 +6452 3949 3952 1700 +6453 1701 3953 3952 +6454 3953 3954 3952 +6455 3953 1702 3954 +6456 3952 3954 1700 +6457 1701 3955 3953 +6458 3955 3956 3953 +6459 3955 1146 3956 +6460 3953 3956 1702 +6461 1700 3954 3950 +6462 3954 3957 3950 +6463 3954 1702 3957 +6464 3950 3957 358 +6465 1145 3958 3951 +6466 3958 3959 3951 +6467 3958 1703 3959 +6468 3951 3959 1701 +6469 1703 3960 3959 +6470 3960 3961 3959 +6471 3960 1704 3961 +6472 3959 3961 1701 +6473 1703 3962 3960 +6474 3962 3963 3960 +6475 3962 1001 3963 +6476 3960 3963 1704 +6477 1701 3961 3955 +6478 3961 3964 3955 +6479 3961 1704 3964 +6480 3955 3964 1146 +6481 358 3957 357 +6482 3957 3965 357 +6483 3957 1702 3965 +6484 357 3965 356 +6485 1702 3966 3965 +6486 3966 3967 3965 +6487 3966 1705 3967 +6488 3965 3967 356 +6489 1702 3956 3966 +6490 3956 3968 3966 +6491 3956 1146 3968 +6492 3966 3968 1705 +6493 356 3967 355 +6494 3967 3969 355 +6495 3967 1705 3969 +6496 355 3969 354 +6497 323 3970 322 +6498 3970 3971 322 +6499 3970 1706 3971 +6500 322 3971 321 +6501 1706 3972 3971 +6502 3972 3973 3971 +6503 3972 1707 3973 +6504 3971 3973 321 +6505 1706 3974 3972 +6506 3974 3975 3972 +6507 3974 1147 3975 +6508 3972 3975 1707 +6509 321 3973 320 +6510 3973 3976 320 +6511 3973 1707 3976 +6512 320 3976 319 +6513 1147 3977 3975 +6514 3977 3978 3975 +6515 3977 1708 3978 +6516 3975 3978 1707 +6517 1708 3979 3978 +6518 3979 3980 3978 +6519 3979 1709 3980 +6520 3978 3980 1707 +6521 1708 3981 3979 +6522 3981 3982 3979 +6523 3981 1145 3982 +6524 3979 3982 1709 +6525 1707 3980 3976 +6526 3980 3983 3976 +6527 3980 1709 3983 +6528 3976 3983 319 +6529 1147 3984 3977 +6530 3984 3985 3977 +6531 3984 1710 3985 +6532 3977 3985 1708 +6533 1710 3986 3985 +6534 3986 3987 3985 +6535 3986 1703 3987 +6536 3985 3987 1708 +6537 1710 3988 3986 +6538 3988 3962 3986 +6539 3988 1001 3962 +6540 3986 3962 1703 +6541 1708 3987 3981 +6542 3987 3958 3981 +6543 3987 1703 3958 +6544 3981 3958 1145 +6545 319 3983 318 +6546 3983 3989 318 +6547 3983 1709 3989 +6548 318 3989 317 +6549 1709 3990 3989 +6550 3990 3991 3989 +6551 3990 1699 3991 +6552 3989 3991 317 +6553 1709 3982 3990 +6554 3982 3948 3990 +6555 3982 1145 3948 +6556 3990 3948 1699 +6557 317 3991 316 +6558 3991 3944 316 +6559 3991 1699 3944 +6560 316 3944 13 +6561 161 3992 2926 +6562 3992 3993 2926 +6563 3992 1711 3993 +6564 2926 3993 1448 +6565 1711 3994 3993 +6566 3994 3995 3993 +6567 3994 1712 3995 +6568 3993 3995 1448 +6569 1711 3996 3994 +6570 3996 3997 3994 +6571 3996 1148 3997 +6572 3994 3997 1712 +6573 1448 3995 2930 +6574 3995 3998 2930 +6575 3995 1712 3998 +6576 2930 3998 1084 +6577 1148 3999 3997 +6578 3999 4000 3997 +6579 3999 1713 4000 +6580 3997 4000 1712 +6581 1713 4001 4000 +6582 4001 4002 4000 +6583 4001 1714 4002 +6584 4000 4002 1712 +6585 1713 4003 4001 +6586 4003 4004 4001 +6587 4003 1149 4004 +6588 4001 4004 1714 +6589 1712 4002 3998 +6590 4002 4005 3998 +6591 4002 1714 4005 +6592 3998 4005 1084 +6593 1148 4006 3999 +6594 4006 4007 3999 +6595 4006 1715 4007 +6596 3999 4007 1713 +6597 1715 4008 4007 +6598 4008 4009 4007 +6599 4008 1716 4009 +6600 4007 4009 1713 +6601 1715 4010 4008 +6602 4010 4011 4008 +6603 4010 1003 4011 +6604 4008 4011 1716 +6605 1713 4009 4003 +6606 4009 4012 4003 +6607 4009 1716 4012 +6608 4003 4012 1149 +6609 1084 4005 2940 +6610 4005 4013 2940 +6611 4005 1714 4013 +6612 2940 4013 1452 +6613 1714 4014 4013 +6614 4014 4015 4013 +6615 4014 1717 4015 +6616 4013 4015 1452 +6617 1714 4004 4014 +6618 4004 4016 4014 +6619 4004 1149 4016 +6620 4014 4016 1717 +6621 1452 4015 2944 +6622 4015 4017 2944 +6623 4015 1717 4017 +6624 2944 4017 971 +6625 993 4018 2527 +6626 4018 4019 2527 +6627 4018 1718 4019 +6628 2527 4019 1340 +6629 1718 4020 4019 +6630 4020 4021 4019 +6631 4020 1719 4021 +6632 4019 4021 1340 +6633 1718 4022 4020 +6634 4022 4023 4020 +6635 4022 1150 4023 +6636 4020 4023 1719 +6637 1340 4021 2528 +6638 4021 4024 2528 +6639 4021 1719 4024 +6640 2528 4024 1052 +6641 1150 4025 4023 +6642 4025 4026 4023 +6643 4025 1720 4026 +6644 4023 4026 1719 +6645 1720 4027 4026 +6646 4027 4028 4026 +6647 4027 1721 4028 +6648 4026 4028 1719 +6649 1720 4029 4027 +6650 4029 4030 4027 +6651 4029 1151 4030 +6652 4027 4030 1721 +6653 1719 4028 4024 +6654 4028 4031 4024 +6655 4028 1721 4031 +6656 4024 4031 1052 +6657 1150 4032 4025 +6658 4032 4033 4025 +6659 4032 1722 4033 +6660 4025 4033 1720 +6661 1722 4034 4033 +6662 4034 4035 4033 +6663 4034 1723 4035 +6664 4033 4035 1720 +6665 1722 4036 4034 +6666 4036 4037 4034 +6667 4036 1005 4037 +6668 4034 4037 1723 +6669 1720 4035 4029 +6670 4035 4038 4029 +6671 4035 1723 4038 +6672 4029 4038 1151 +6673 1052 4031 2533 +6674 4031 4039 2533 +6675 4031 1721 4039 +6676 2533 4039 1341 +6677 1721 4040 4039 +6678 4040 4041 4039 +6679 4040 1724 4041 +6680 4039 4041 1341 +6681 1721 4030 4040 +6682 4030 4042 4040 +6683 4030 1151 4042 +6684 4040 4042 1724 +6685 1341 4041 2534 +6686 4041 4043 2534 +6687 4041 1724 4043 +6688 2534 4043 988 +6689 991 4044 3727 +6690 4044 4045 3727 +6691 4044 1725 4045 +6692 3727 4045 1646 +6693 1725 4046 4045 +6694 4046 4047 4045 +6695 4046 1726 4047 +6696 4045 4047 1646 +6697 1725 4048 4046 +6698 4048 4049 4046 +6699 4048 1152 4049 +6700 4046 4049 1726 +6701 1646 4047 3728 +6702 4047 4050 3728 +6703 4047 1726 4050 +6704 3728 4050 1132 +6705 1152 4051 4049 +6706 4051 4052 4049 +6707 4051 1727 4052 +6708 4049 4052 1726 +6709 1727 4053 4052 +6710 4053 4054 4052 +6711 4053 1728 4054 +6712 4052 4054 1726 +6713 1727 4055 4053 +6714 4055 4056 4053 +6715 4055 1153 4056 +6716 4053 4056 1728 +6717 1726 4054 4050 +6718 4054 4057 4050 +6719 4054 1728 4057 +6720 4050 4057 1132 +6721 1152 4058 4051 +6722 4058 4059 4051 +6723 4058 1729 4059 +6724 4051 4059 1727 +6725 1729 4060 4059 +6726 4060 4061 4059 +6727 4060 1730 4061 +6728 4059 4061 1727 +6729 1729 4062 4060 +6730 4062 4063 4060 +6731 4062 1004 4063 +6732 4060 4063 1730 +6733 1727 4061 4055 +6734 4061 4064 4055 +6735 4061 1730 4064 +6736 4055 4064 1153 +6737 1132 4057 3732 +6738 4057 4065 3732 +6739 4057 1728 4065 +6740 3732 4065 1647 +6741 1728 4066 4065 +6742 4066 4067 4065 +6743 4066 1731 4067 +6744 4065 4067 1647 +6745 1728 4056 4066 +6746 4056 4068 4066 +6747 4056 1153 4068 +6748 4066 4068 1731 +6749 1647 4067 3733 +6750 4067 4069 3733 +6751 4067 1731 4069 +6752 3733 4069 84 +6753 246 4070 3100 +6754 4070 4071 3100 +6755 4070 1732 4071 +6756 3100 4071 1493 +6757 1732 4072 4071 +6758 4072 4073 4071 +6759 4072 1733 4073 +6760 4071 4073 1493 +6761 1732 4074 4072 +6762 4074 4075 4072 +6763 4074 1154 4075 +6764 4072 4075 1733 +6765 1493 4073 3104 +6766 4073 4076 3104 +6767 4073 1733 4076 +6768 3104 4076 1096 +6769 1154 4077 4075 +6770 4077 4078 4075 +6771 4077 1734 4078 +6772 4075 4078 1733 +6773 1734 4079 4078 +6774 4079 4080 4078 +6775 4079 1735 4080 +6776 4078 4080 1733 +6777 1734 4081 4079 +6778 4081 4082 4079 +6779 4081 1125 4082 +6780 4079 4082 1735 +6781 1733 4080 4076 +6782 4080 4083 4076 +6783 4080 1735 4083 +6784 4076 4083 1096 +6785 1154 4084 4077 +6786 4084 4085 4077 +6787 4084 1736 4085 +6788 4077 4085 1734 +6789 1736 4086 4085 +6790 4086 4087 4085 +6791 4086 1624 4087 +6792 4085 4087 1734 +6793 1736 4088 4086 +6794 4088 3648 4086 +6795 4088 996 3648 +6796 4086 3648 1624 +6797 1734 4087 4081 +6798 4087 3644 4081 +6799 4087 1624 3644 +6800 4081 3644 1125 +6801 1096 4083 3114 +6802 4083 4089 3114 +6803 4083 1735 4089 +6804 3114 4089 1497 +6805 1735 4090 4089 +6806 4090 4091 4089 +6807 4090 1620 4091 +6808 4089 4091 1497 +6809 1735 4082 4090 +6810 4082 3634 4090 +6811 4082 1125 3634 +6812 4090 3634 1620 +6813 1497 4091 3118 +6814 4091 3630 3118 +6815 4091 1620 3630 +6816 3118 3630 972 +6817 69 3781 2900 +6818 3781 4092 2900 +6819 3781 1659 4092 +6820 2900 4092 1441 +6821 1659 4093 4092 +6822 4093 4094 4092 +6823 4093 1737 4094 +6824 4092 4094 1441 +6825 1659 3780 4093 +6826 3780 4095 4093 +6827 3780 1135 4095 +6828 4093 4095 1737 +6829 1441 4094 2904 +6830 4094 4096 2904 +6831 4094 1737 4096 +6832 2904 4096 1082 +6833 1135 4097 4095 +6834 4097 4098 4095 +6835 4097 1738 4098 +6836 4095 4098 1737 +6837 1738 4099 4098 +6838 4099 4100 4098 +6839 4099 1739 4100 +6840 4098 4100 1737 +6841 1738 4101 4099 +6842 4101 4102 4099 +6843 4101 1043 4102 +6844 4099 4102 1739 +6845 1737 4100 4096 +6846 4100 4103 4096 +6847 4100 1739 4103 +6848 4096 4103 1082 +6849 1135 3776 4097 +6850 3776 4104 4097 +6851 3776 1658 4104 +6852 4097 4104 1738 +6853 1658 4105 4104 +6854 4105 4106 4104 +6855 4105 1307 4106 +6856 4104 4106 1738 +6857 1658 3775 4105 +6858 3775 2402 4105 +6859 3775 992 2402 +6860 4105 2402 1307 +6861 1738 4106 4101 +6862 4106 2398 4101 +6863 4106 1307 2398 +6864 4101 2398 1043 +6865 1082 4103 2914 +6866 4103 4107 2914 +6867 4103 1739 4107 +6868 2914 4107 1445 +6869 1739 4108 4107 +6870 4108 4109 4107 +6871 4108 1303 4109 +6872 4107 4109 1445 +6873 1739 4102 4108 +6874 4102 2388 4108 +6875 4102 1043 2388 +6876 4108 2388 1303 +6877 1445 4109 2918 +6878 4109 2384 2918 +6879 4109 1303 2384 +6880 2918 2384 970 +6881 22 3339 2870 +6882 3339 4110 2870 +6883 3339 1552 4110 +6884 2870 4110 1432 +6885 1552 4111 4110 +6886 4111 4112 4110 +6887 4111 1740 4112 +6888 4110 4112 1432 +6889 1552 3338 4111 +6890 3338 4113 4111 +6891 3338 1110 4113 +6892 4111 4113 1740 +6893 1432 4112 2875 +6894 4112 4114 2875 +6895 4112 1740 4114 +6896 2875 4114 1079 +6897 1110 4115 4113 +6898 4115 4116 4113 +6899 4115 1741 4116 +6900 4113 4116 1740 +6901 1741 4117 4116 +6902 4117 4118 4116 +6903 4117 1742 4118 +6904 4116 4118 1740 +6905 1741 4119 4117 +6906 4119 4120 4117 +6907 4119 1136 4120 +6908 4117 4120 1742 +6909 1740 4118 4114 +6910 4118 4121 4114 +6911 4118 1742 4121 +6912 4114 4121 1079 +6913 1110 3334 4115 +6914 3334 4122 4115 +6915 3334 1551 4122 +6916 4115 4122 1741 +6917 1551 4123 4122 +6918 4123 4124 4122 +6919 4123 1664 4124 +6920 4122 4124 1741 +6921 1551 3333 4123 +6922 3333 3800 4123 +6923 3333 995 3800 +6924 4123 3800 1664 +6925 1741 4124 4119 +6926 4124 3796 4119 +6927 4124 1664 3796 +6928 4119 3796 1136 +6929 1079 4121 2886 +6930 4121 4125 2886 +6931 4121 1742 4125 +6932 2886 4125 1437 +6933 1742 4126 4125 +6934 4126 4127 4125 +6935 4126 1660 4127 +6936 4125 4127 1437 +6937 1742 4120 4126 +6938 4120 3786 4126 +6939 4120 1136 3786 +6940 4126 3786 1660 +6941 1437 4127 2890 +6942 4127 3782 2890 +6943 4127 1660 3782 +6944 2890 3782 979 +6945 980 2642 3656 +6946 2642 4128 3656 +6947 2642 1373 4128 +6948 3656 4128 1627 +6949 1373 4129 4128 +6950 4129 4130 4128 +6951 4129 1743 4130 +6952 4128 4130 1627 +6953 1373 2648 4129 +6954 2648 4131 4129 +6955 2648 1064 4131 +6956 4129 4131 1743 +6957 1627 4130 3660 +6958 4130 4132 3660 +6959 4130 1743 4132 +6960 3660 4132 1127 +6961 1064 4133 4131 +6962 4133 4134 4131 +6963 4133 1744 4134 +6964 4131 4134 1743 +6965 1744 4135 4134 +6966 4135 4136 4134 +6967 4135 1745 4136 +6968 4134 4136 1743 +6969 1744 4137 4135 +6970 4137 4138 4135 +6971 4137 1076 4138 +6972 4135 4138 1745 +6973 1743 4136 4132 +6974 4136 4139 4132 +6975 4136 1745 4139 +6976 4132 4139 1127 +6977 1064 2664 4133 +6978 2664 4140 4133 +6979 2664 1379 4140 +6980 4133 4140 1744 +6981 1379 4141 4140 +6982 4141 4142 4140 +6983 4141 1420 4142 +6984 4140 4142 1744 +6985 1379 2669 4141 +6986 2669 2820 4141 +6987 2669 983 2820 +6988 4141 2820 1420 +6989 1744 4142 4137 +6990 4142 2826 4137 +6991 4142 1420 2826 +6992 4137 2826 1076 +6993 1127 4139 3670 +6994 4139 4143 3670 +6995 4139 1745 4143 +6996 3670 4143 1631 +6997 1745 4144 4143 +6998 4144 4145 4143 +6999 4144 1424 4145 +7000 4143 4145 1631 +7001 1745 4138 4144 +7002 4138 2840 4144 +7003 4138 1076 2840 +7004 4144 2840 1424 +7005 1631 4145 3674 +7006 4145 2843 3674 +7007 4145 1424 2843 +7008 3674 2843 981 +7009 199 2639 200 +7010 2639 4146 200 +7011 2639 1370 4146 +7012 200 4146 201 +7013 1370 4147 4146 +7014 4147 4148 4146 +7015 4147 1746 4148 +7016 4146 4148 201 +7017 1370 2638 4147 +7018 2638 4149 4147 +7019 2638 1061 4149 +7020 4147 4149 1746 +7021 201 4148 202 +7022 4148 4150 202 +7023 4148 1746 4150 +7024 202 4150 203 +7025 1061 4151 4149 +7026 4151 4152 4149 +7027 4151 1747 4152 +7028 4149 4152 1746 +7029 1747 4153 4152 +7030 4153 4154 4152 +7031 4153 1748 4154 +7032 4152 4154 1746 +7033 1747 4155 4153 +7034 4155 4156 4153 +7035 4155 1091 4156 +7036 4153 4156 1748 +7037 1746 4154 4150 +7038 4154 4157 4150 +7039 4154 1748 4157 +7040 4150 4157 203 +7041 1061 2634 4151 +7042 2634 4158 4151 +7043 2634 1369 4158 +7044 4151 4158 1747 +7045 1369 4159 4158 +7046 4159 4160 4158 +7047 4159 1475 4160 +7048 4158 4160 1747 +7049 1369 2633 4159 +7050 2633 3026 4159 +7051 2633 989 3026 +7052 4159 3026 1475 +7053 1747 4160 4155 +7054 4160 3022 4155 +7055 4160 1475 3022 +7056 4155 3022 1091 +7057 203 4157 204 +7058 4157 4161 204 +7059 4157 1748 4161 +7060 204 4161 205 +7061 1748 4162 4161 +7062 4162 4163 4161 +7063 4162 1471 4163 +7064 4161 4163 205 +7065 1748 4156 4162 +7066 4156 3012 4162 +7067 4156 1091 3012 +7068 4162 3012 1471 +7069 205 4163 206 +7070 4163 3008 206 +7071 4163 1471 3008 +7072 206 3008 207 +7073 38 3099 39 +7074 3099 4164 39 +7075 3099 1492 4164 +7076 39 4164 40 +7077 1492 4165 4164 +7078 4165 4166 4164 +7079 4165 1749 4166 +7080 4164 4166 40 +7081 1492 3098 4165 +7082 3098 4167 4165 +7083 3098 1095 4167 +7084 4165 4167 1749 +7085 40 4166 41 +7086 4166 4168 41 +7087 4166 1749 4168 +7088 41 4168 42 +7089 1095 4169 4167 +7090 4169 4170 4167 +7091 4169 1750 4170 +7092 4167 4170 1749 +7093 1750 4171 4170 +7094 4171 4172 4170 +7095 4171 1751 4172 +7096 4170 4172 1749 +7097 1750 4173 4171 +7098 4173 4174 4171 +7099 4173 1074 4174 +7100 4171 4174 1751 +7101 1749 4172 4168 +7102 4172 4175 4168 +7103 4172 1751 4175 +7104 4168 4175 42 +7105 1095 3094 4169 +7106 3094 4176 4169 +7107 3094 1491 4176 +7108 4169 4176 1750 +7109 1491 4177 4176 +7110 4177 4178 4176 +7111 4177 1417 4178 +7112 4176 4178 1750 +7113 1491 3093 4177 +7114 3093 2814 4177 +7115 3093 990 2814 +7116 4177 2814 1417 +7117 1750 4178 4173 +7118 4178 2810 4173 +7119 4178 1417 2810 +7120 4173 2810 1074 +7121 42 4175 43 +7122 4175 4179 43 +7123 4175 1751 4179 +7124 43 4179 44 +7125 1751 4180 4179 +7126 4180 4181 4179 +7127 4180 1413 4181 +7128 4179 4181 44 +7129 1751 4174 4180 +7130 4174 2800 4180 +7131 4174 1074 2800 +7132 4180 2800 1413 +7133 44 4181 45 +7134 4181 2796 45 +7135 4181 1413 2796 +7136 45 2796 46 +7137 975 2429 4183 +7138 2429 4182 4183 +7139 2429 1314 4182 +7140 4183 4182 1753 +7141 1314 4184 4182 +7142 4184 4185 4182 +7143 4184 1752 4185 +7144 4182 4185 1753 +7145 1314 2434 4184 +7146 2434 4186 4184 +7147 2434 1046 4186 +7148 4184 4186 1752 +7149 1753 4185 4188 +7150 4185 4187 4188 +7151 4185 1752 4187 +7152 4188 4187 1156 +7153 1046 4189 4186 +7154 4189 4190 4186 +7155 4189 1754 4190 +7156 4186 4190 1752 +7157 1754 4191 4190 +7158 4191 4192 4190 +7159 4191 1755 4192 +7160 4190 4192 1752 +7161 1754 4193 4191 +7162 4193 4194 4191 +7163 4193 1155 4194 +7164 4191 4194 1755 +7165 1752 4192 4187 +7166 4192 4195 4187 +7167 4192 1755 4195 +7168 4187 4195 1156 +7169 1046 2448 4189 +7170 2448 4196 4189 +7171 2448 1319 4196 +7172 4189 4196 1754 +7173 1319 4197 4196 +7174 4197 4198 4196 +7175 4197 1756 4198 +7176 4196 4198 1754 +7177 1319 2453 4197 +7178 2453 4199 4197 +7179 2453 988 4199 +7180 4197 4199 1756 +7181 1754 4198 4193 +7182 4198 4200 4193 +7183 4198 1756 4200 +7184 4193 4200 1155 +7185 1156 4195 4202 +7186 4195 4201 4202 +7187 4195 1755 4201 +7188 4202 4201 1758 +7189 1755 4203 4201 +7190 4203 4204 4201 +7191 4203 1757 4204 +7192 4201 4204 1758 +7193 1755 4194 4203 +7194 4194 4205 4203 +7195 4194 1155 4205 +7196 4203 4205 1757 +7197 1758 4204 4207 +7198 4204 4206 4207 +7199 4204 1757 4206 +7200 4207 4206 987 +7201 975 4183 3263 +7202 4183 4208 3263 +7203 4183 1753 4208 +7204 3263 4208 1534 +7205 1753 4209 4208 +7206 4209 4210 4208 +7207 4209 1759 4210 +7208 4208 4210 1534 +7209 1753 4188 4209 +7210 4188 4211 4209 +7211 4188 1156 4211 +7212 4209 4211 1759 +7213 1534 4210 3264 +7214 4210 4212 3264 +7215 4210 1759 4212 +7216 3264 4212 1106 +7217 1156 4213 4211 +7218 4213 4214 4211 +7219 4213 1760 4214 +7220 4211 4214 1759 +7221 1760 4215 4214 +7222 4215 4216 4214 +7223 4215 1761 4216 +7224 4214 4216 1759 +7225 1760 4217 4215 +7226 4217 4218 4215 +7227 4217 1157 4218 +7228 4215 4218 1761 +7229 1759 4216 4212 +7230 4216 4219 4212 +7231 4216 1761 4219 +7232 4212 4219 1106 +7233 1156 4202 4213 +7234 4202 4220 4213 +7235 4202 1758 4220 +7236 4213 4220 1760 +7237 1758 4221 4220 +7238 4221 4222 4220 +7239 4221 1762 4222 +7240 4220 4222 1760 +7241 1758 4207 4221 +7242 4207 4223 4221 +7243 4207 987 4223 +7244 4221 4223 1762 +7245 1760 4222 4217 +7246 4222 4224 4217 +7247 4222 1762 4224 +7248 4217 4224 1157 +7249 1106 4219 3268 +7250 4219 4225 3268 +7251 4219 1761 4225 +7252 3268 4225 1535 +7253 1761 4226 4225 +7254 4226 4227 4225 +7255 4226 1763 4227 +7256 4225 4227 1535 +7257 1761 4218 4226 +7258 4218 4228 4226 +7259 4218 1157 4228 +7260 4226 4228 1763 +7261 1535 4227 3269 +7262 4227 4229 3269 +7263 4227 1763 4229 +7264 3269 4229 138 +7265 346 4230 345 +7266 4230 4231 345 +7267 4230 1764 4231 +7268 345 4231 344 +7269 1764 4232 4231 +7270 4232 4233 4231 +7271 4232 1765 4233 +7272 4231 4233 344 +7273 1764 4234 4232 +7274 4234 4235 4232 +7275 4234 1158 4235 +7276 4232 4235 1765 +7277 344 4233 343 +7278 4233 4236 343 +7279 4233 1765 4236 +7280 343 4236 342 +7281 1158 4237 4235 +7282 4237 4238 4235 +7283 4237 1766 4238 +7284 4235 4238 1765 +7285 1766 4239 4238 +7286 4239 4240 4238 +7287 4239 1767 4240 +7288 4238 4240 1765 +7289 1766 4241 4239 +7290 4241 4242 4239 +7291 4241 1109 4242 +7292 4239 4242 1767 +7293 1765 4240 4236 +7294 4240 4243 4236 +7295 4240 1767 4243 +7296 4236 4243 342 +7297 1158 4244 4237 +7298 4244 4245 4237 +7299 4244 1768 4245 +7300 4237 4245 1766 +7301 1768 4246 4245 +7302 4246 4247 4245 +7303 4246 1550 4247 +7304 4245 4247 1766 +7305 1768 4248 4246 +7306 4248 3332 4246 +7307 4248 995 3332 +7308 4246 3332 1550 +7309 1766 4247 4241 +7310 4247 3328 4241 +7311 4247 1550 3328 +7312 4241 3328 1109 +7313 342 4243 341 +7314 4243 4249 341 +7315 4243 1767 4249 +7316 341 4249 340 +7317 1767 4250 4249 +7318 4250 4251 4249 +7319 4250 1546 4251 +7320 4249 4251 340 +7321 1767 4242 4250 +7322 4242 3318 4250 +7323 4242 1109 3318 +7324 4250 3318 1546 +7325 340 4251 339 +7326 4251 3314 339 +7327 4251 1546 3314 +7328 339 3314 1 +7329 331 3851 3126 +7330 3851 4252 3126 +7331 3851 1676 4252 +7332 3126 4252 1500 +7333 1676 4253 4252 +7334 4253 4254 4252 +7335 4253 1769 4254 +7336 4252 4254 1500 +7337 1676 3850 4253 +7338 3850 4255 4253 +7339 3850 1139 4255 +7340 4253 4255 1769 +7341 1500 4254 3130 +7342 4254 4256 3130 +7343 4254 1769 4256 +7344 3130 4256 1098 +7345 1139 4257 4255 +7346 4257 4258 4255 +7347 4257 1770 4258 +7348 4255 4258 1769 +7349 1770 4259 4258 +7350 4259 4260 4258 +7351 4259 1771 4260 +7352 4258 4260 1769 +7353 1770 4261 4259 +7354 4261 4262 4259 +7355 4261 1159 4262 +7356 4259 4262 1771 +7357 1769 4260 4256 +7358 4260 4263 4256 +7359 4260 1771 4263 +7360 4256 4263 1098 +7361 1139 3846 4257 +7362 3846 4264 4257 +7363 3846 1675 4264 +7364 4257 4264 1770 +7365 1675 4265 4264 +7366 4265 4266 4264 +7367 4265 1772 4266 +7368 4264 4266 1770 +7369 1675 3845 4265 +7370 3845 4267 4265 +7371 3845 994 4267 +7372 4265 4267 1772 +7373 1770 4266 4261 +7374 4266 4268 4261 +7375 4266 1772 4268 +7376 4261 4268 1159 +7377 1098 4263 3140 +7378 4263 4269 3140 +7379 4263 1771 4269 +7380 3140 4269 1504 +7381 1771 4270 4269 +7382 4270 4271 4269 +7383 4270 1773 4271 +7384 4269 4271 1504 +7385 1771 4262 4270 +7386 4262 4272 4270 +7387 4262 1159 4272 +7388 4270 4272 1773 +7389 1504 4271 3144 +7390 4271 4273 3144 +7391 4271 1773 4273 +7392 3144 4273 973 +7393 61 4274 60 +7394 4274 4275 60 +7395 4274 1774 4275 +7396 60 4275 59 +7397 1774 4276 4275 +7398 4276 4277 4275 +7399 4276 1775 4277 +7400 4275 4277 59 +7401 1774 4278 4276 +7402 4278 4279 4276 +7403 4278 1160 4279 +7404 4276 4279 1775 +7405 59 4277 58 +7406 4277 4280 58 +7407 4277 1775 4280 +7408 58 4280 57 +7409 1160 4281 4279 +7410 4281 4282 4279 +7411 4281 1776 4282 +7412 4279 4282 1775 +7413 1776 4283 4282 +7414 4283 4284 4282 +7415 4283 1777 4284 +7416 4282 4284 1775 +7417 1776 4285 4283 +7418 4285 4286 4283 +7419 4285 1161 4286 +7420 4283 4286 1777 +7421 1775 4284 4280 +7422 4284 4287 4280 +7423 4284 1777 4287 +7424 4280 4287 57 +7425 1160 4288 4281 +7426 4288 4289 4281 +7427 4288 1778 4289 +7428 4281 4289 1776 +7429 1778 4290 4289 +7430 4290 4291 4289 +7431 4290 1779 4291 +7432 4289 4291 1776 +7433 1778 4292 4290 +7434 4292 4293 4290 +7435 4292 1002 4293 +7436 4290 4293 1779 +7437 1776 4291 4285 +7438 4291 4294 4285 +7439 4291 1779 4294 +7440 4285 4294 1161 +7441 57 4287 56 +7442 4287 4295 56 +7443 4287 1777 4295 +7444 56 4295 55 +7445 1777 4296 4295 +7446 4296 4297 4295 +7447 4296 1780 4297 +7448 4295 4297 55 +7449 1777 4286 4296 +7450 4286 4298 4296 +7451 4286 1161 4298 +7452 4296 4298 1780 +7453 55 4297 54 +7454 4297 4299 54 +7455 4297 1780 4299 +7456 54 4299 3 +7457 3 4299 99 +7458 4299 4300 99 +7459 4299 1780 4300 +7460 99 4300 98 +7461 1780 4301 4300 +7462 4301 4302 4300 +7463 4301 1781 4302 +7464 4300 4302 98 +7465 1780 4298 4301 +7466 4298 4303 4301 +7467 4298 1161 4303 +7468 4301 4303 1781 +7469 98 4302 97 +7470 4302 4304 97 +7471 4302 1781 4304 +7472 97 4304 96 +7473 1161 4305 4303 +7474 4305 4306 4303 +7475 4305 1782 4306 +7476 4303 4306 1781 +7477 1782 4307 4306 +7478 4307 4308 4306 +7479 4307 1783 4308 +7480 4306 4308 1781 +7481 1782 4309 4307 +7482 4309 4310 4307 +7483 4309 1162 4310 +7484 4307 4310 1783 +7485 1781 4308 4304 +7486 4308 4311 4304 +7487 4308 1783 4311 +7488 4304 4311 96 +7489 1161 4294 4305 +7490 4294 4312 4305 +7491 4294 1779 4312 +7492 4305 4312 1782 +7493 1779 4313 4312 +7494 4313 4314 4312 +7495 4313 1784 4314 +7496 4312 4314 1782 +7497 1779 4293 4313 +7498 4293 4315 4313 +7499 4293 1002 4315 +7500 4313 4315 1784 +7501 1782 4314 4309 +7502 4314 4316 4309 +7503 4314 1784 4316 +7504 4309 4316 1162 +7505 96 4311 95 +7506 4311 4317 95 +7507 4311 1783 4317 +7508 95 4317 94 +7509 1783 4318 4317 +7510 4318 4319 4317 +7511 4318 1785 4319 +7512 4317 4319 94 +7513 1783 4310 4318 +7514 4310 4320 4318 +7515 4310 1162 4320 +7516 4318 4320 1785 +7517 94 4319 93 +7518 4319 4321 93 +7519 4319 1785 4321 +7520 93 4321 92 +7521 9 3365 253 +7522 3365 4322 253 +7523 3365 1559 4322 +7524 253 4322 252 +7525 1559 4323 4322 +7526 4323 4324 4322 +7527 4323 1786 4324 +7528 4322 4324 252 +7529 1559 3364 4323 +7530 3364 4325 4323 +7531 3364 1112 4325 +7532 4323 4325 1786 +7533 252 4324 251 +7534 4324 4326 251 +7535 4324 1786 4326 +7536 251 4326 250 +7537 1112 4327 4325 +7538 4327 4328 4325 +7539 4327 1787 4328 +7540 4325 4328 1786 +7541 1787 4329 4328 +7542 4329 4330 4328 +7543 4329 1788 4330 +7544 4328 4330 1786 +7545 1787 4331 4329 +7546 4331 4332 4329 +7547 4331 1154 4332 +7548 4329 4332 1788 +7549 1786 4330 4326 +7550 4330 4333 4326 +7551 4330 1788 4333 +7552 4326 4333 250 +7553 1112 3360 4327 +7554 3360 4334 4327 +7555 3360 1558 4334 +7556 4327 4334 1787 +7557 1558 4335 4334 +7558 4335 4336 4334 +7559 4335 1736 4336 +7560 4334 4336 1787 +7561 1558 3359 4335 +7562 3359 4088 4335 +7563 3359 996 4088 +7564 4335 4088 1736 +7565 1787 4336 4331 +7566 4336 4084 4331 +7567 4336 1736 4084 +7568 4331 4084 1154 +7569 250 4333 249 +7570 4333 4337 249 +7571 4333 1788 4337 +7572 249 4337 248 +7573 1788 4338 4337 +7574 4338 4339 4337 +7575 4338 1732 4339 +7576 4337 4339 248 +7577 1788 4332 4338 +7578 4332 4074 4338 +7579 4332 1154 4074 +7580 4338 4074 1732 +7581 248 4339 247 +7582 4339 4070 247 +7583 4339 1732 4070 +7584 247 4070 246 +7585 986 3807 3753 +7586 3807 4340 3753 +7587 3807 1666 4340 +7588 3753 4340 1653 +7589 1666 4341 4340 +7590 4341 4342 4340 +7591 4341 1789 4342 +7592 4340 4342 1653 +7593 1666 3806 4341 +7594 3806 4343 4341 +7595 3806 1137 4343 +7596 4341 4343 1789 +7597 1653 4342 3754 +7598 4342 4344 3754 +7599 4342 1789 4344 +7600 3754 4344 1134 +7601 1137 4345 4343 +7602 4345 4346 4343 +7603 4345 1790 4346 +7604 4343 4346 1789 +7605 1790 4347 4346 +7606 4347 4348 4346 +7607 4347 1791 4348 +7608 4346 4348 1789 +7609 1790 4349 4347 +7610 4349 4350 4347 +7611 4349 1158 4350 +7612 4347 4350 1791 +7613 1789 4348 4344 +7614 4348 4351 4344 +7615 4348 1791 4351 +7616 4344 4351 1134 +7617 1137 3802 4345 +7618 3802 4352 4345 +7619 3802 1665 4352 +7620 4345 4352 1790 +7621 1665 4353 4352 +7622 4353 4354 4352 +7623 4353 1768 4354 +7624 4352 4354 1790 +7625 1665 3801 4353 +7626 3801 4248 4353 +7627 3801 995 4248 +7628 4353 4248 1768 +7629 1790 4354 4349 +7630 4354 4244 4349 +7631 4354 1768 4244 +7632 4349 4244 1158 +7633 1134 4351 3758 +7634 4351 4355 3758 +7635 4351 1791 4355 +7636 3758 4355 1654 +7637 1791 4356 4355 +7638 4356 4357 4355 +7639 4356 1764 4357 +7640 4355 4357 1654 +7641 1791 4350 4356 +7642 4350 4234 4356 +7643 4350 1158 4234 +7644 4356 4234 1764 +7645 1654 4357 3759 +7646 4357 4230 3759 +7647 4357 1764 4230 +7648 3759 4230 346 +7649 985 4358 3701 +7650 4358 4359 3701 +7651 4358 1792 4359 +7652 3701 4359 1639 +7653 1792 4360 4359 +7654 4360 4361 4359 +7655 4360 1793 4361 +7656 4359 4361 1639 +7657 1792 4362 4360 +7658 4362 4363 4360 +7659 4362 1163 4363 +7660 4360 4363 1793 +7661 1639 4361 3702 +7662 4361 4364 3702 +7663 4361 1793 4364 +7664 3702 4364 1130 +7665 1163 4365 4363 +7666 4365 4366 4363 +7667 4365 1794 4366 +7668 4363 4366 1793 +7669 1794 4367 4366 +7670 4367 4368 4366 +7671 4367 1795 4368 +7672 4366 4368 1793 +7673 1794 4369 4367 +7674 4369 4370 4367 +7675 4369 1138 4370 +7676 4367 4370 1795 +7677 1793 4368 4364 +7678 4368 4371 4364 +7679 4368 1795 4371 +7680 4364 4371 1130 +7681 1163 4372 4365 +7682 4372 4373 4365 +7683 4372 1796 4373 +7684 4365 4373 1794 +7685 1796 4374 4373 +7686 4374 4375 4373 +7687 4374 1671 4375 +7688 4373 4375 1794 +7689 1796 4376 4374 +7690 4376 3826 4374 +7691 4376 993 3826 +7692 4374 3826 1671 +7693 1794 4375 4369 +7694 4375 3822 4369 +7695 4375 1671 3822 +7696 4369 3822 1138 +7697 1130 4371 3706 +7698 4371 4377 3706 +7699 4371 1795 4377 +7700 3706 4377 1640 +7701 1795 4378 4377 +7702 4378 4379 4377 +7703 4378 1667 4379 +7704 4377 4379 1640 +7705 1795 4370 4378 +7706 4370 3812 4378 +7707 4370 1138 3812 +7708 4378 3812 1667 +7709 1640 4379 3707 +7710 4379 3808 3707 +7711 4379 1667 3808 +7712 3707 3808 176 +7713 401 2060 402 +7714 2060 4380 402 +7715 2060 1218 4380 +7716 402 4380 403 +7717 1218 4381 4380 +7718 4381 4382 4380 +7719 4381 1797 4382 +7720 4380 4382 403 +7721 1218 2059 4381 +7722 2059 4383 4381 +7723 2059 1019 4383 +7724 4381 4383 1797 +7725 403 4382 404 +7726 4382 4384 404 +7727 4382 1797 4384 +7728 404 4384 405 +7729 1019 4385 4383 +7730 4385 4386 4383 +7731 4385 1798 4386 +7732 4383 4386 1797 +7733 1798 4387 4386 +7734 4387 4388 4386 +7735 4387 1799 4388 +7736 4386 4388 1797 +7737 1798 4389 4387 +7738 4389 4390 4387 +7739 4389 1164 4390 +7740 4387 4390 1799 +7741 1797 4388 4384 +7742 4388 4391 4384 +7743 4388 1799 4391 +7744 4384 4391 405 +7745 1019 2054 4385 +7746 2054 4392 4385 +7747 2054 1217 4392 +7748 4385 4392 1798 +7749 1217 4393 4392 +7750 4393 4394 4392 +7751 4393 1800 4394 +7752 4392 4394 1798 +7753 1217 2053 4393 +7754 2053 4395 4393 +7755 2053 999 4395 +7756 4393 4395 1800 +7757 1798 4394 4389 +7758 4394 4396 4389 +7759 4394 1800 4396 +7760 4389 4396 1164 +7761 405 4391 406 +7762 4391 4397 406 +7763 4391 1799 4397 +7764 406 4397 407 +7765 1799 4398 4397 +7766 4398 4399 4397 +7767 4398 1801 4399 +7768 4397 4399 407 +7769 1799 4390 4398 +7770 4390 4400 4398 +7771 4390 1164 4400 +7772 4398 4400 1801 +7773 407 4399 408 +7774 4399 4401 408 +7775 4399 1801 4401 +7776 408 4401 409 +7777 971 4017 4403 +7778 4017 4402 4403 +7779 4017 1717 4402 +7780 4403 4402 1803 +7781 1717 4404 4402 +7782 4404 4405 4402 +7783 4404 1802 4405 +7784 4402 4405 1803 +7785 1717 4016 4404 +7786 4016 4406 4404 +7787 4016 1149 4406 +7788 4404 4406 1802 +7789 1803 4405 4408 +7790 4405 4407 4408 +7791 4405 1802 4407 +7792 4408 4407 1166 +7793 1149 4409 4406 +7794 4409 4410 4406 +7795 4409 1804 4410 +7796 4406 4410 1802 +7797 1804 4411 4410 +7798 4411 4412 4410 +7799 4411 1805 4412 +7800 4410 4412 1802 +7801 1804 4413 4411 +7802 4413 4414 4411 +7803 4413 1165 4414 +7804 4411 4414 1805 +7805 1802 4412 4407 +7806 4412 4415 4407 +7807 4412 1805 4415 +7808 4407 4415 1166 +7809 1149 4012 4409 +7810 4012 4416 4409 +7811 4012 1716 4416 +7812 4409 4416 1804 +7813 1716 4417 4416 +7814 4417 4418 4416 +7815 4417 1806 4418 +7816 4416 4418 1804 +7817 1716 4011 4417 +7818 4011 4419 4417 +7819 4011 1003 4419 +7820 4417 4419 1806 +7821 1804 4418 4413 +7822 4418 4420 4413 +7823 4418 1806 4420 +7824 4413 4420 1165 +7825 1166 4415 4422 +7826 4415 4421 4422 +7827 4415 1805 4421 +7828 4422 4421 1808 +7829 1805 4423 4421 +7830 4423 4424 4421 +7831 4423 1807 4424 +7832 4421 4424 1808 +7833 1805 4414 4423 +7834 4414 4425 4423 +7835 4414 1165 4425 +7836 4423 4425 1807 +7837 1808 4424 4427 +7838 4424 4426 4427 +7839 4424 1807 4426 +7840 4427 4426 987 +7841 976 4428 1980 +7842 4428 4429 1980 +7843 4428 1809 4429 +7844 1980 4429 1197 +7845 1809 4430 4429 +7846 4430 4431 4429 +7847 4430 1810 4431 +7848 4429 4431 1197 +7849 1809 4432 4430 +7850 4432 4433 4430 +7851 4432 1167 4433 +7852 4430 4433 1810 +7853 1197 4431 1984 +7854 4431 4434 1984 +7855 4431 1810 4434 +7856 1984 4434 1014 +7857 1167 4435 4433 +7858 4435 4436 4433 +7859 4435 1811 4436 +7860 4433 4436 1810 +7861 1811 4437 4436 +7862 4437 4438 4436 +7863 4437 1812 4438 +7864 4436 4438 1810 +7865 1811 4439 4437 +7866 4439 4440 4437 +7867 4439 1152 4440 +7868 4437 4440 1812 +7869 1810 4438 4434 +7870 4438 4441 4434 +7871 4438 1812 4441 +7872 4434 4441 1014 +7873 1167 4442 4435 +7874 4442 4443 4435 +7875 4442 1813 4443 +7876 4435 4443 1811 +7877 1813 4444 4443 +7878 4444 4445 4443 +7879 4444 1729 4445 +7880 4443 4445 1811 +7881 1813 4446 4444 +7882 4446 4062 4444 +7883 4446 1004 4062 +7884 4444 4062 1729 +7885 1811 4445 4439 +7886 4445 4058 4439 +7887 4445 1729 4058 +7888 4439 4058 1152 +7889 1014 4441 1994 +7890 4441 4447 1994 +7891 4441 1812 4447 +7892 1994 4447 1201 +7893 1812 4448 4447 +7894 4448 4449 4447 +7895 4448 1725 4449 +7896 4447 4449 1201 +7897 1812 4440 4448 +7898 4440 4048 4448 +7899 4440 1152 4048 +7900 4448 4048 1725 +7901 1201 4449 1998 +7902 4449 4044 1998 +7903 4449 1725 4044 +7904 1998 4044 991 +7905 984 3943 2454 +7906 3943 4450 2454 +7907 3943 1698 4450 +7908 2454 4450 1320 +7909 1698 4451 4450 +7910 4451 4452 4450 +7911 4451 1814 4452 +7912 4450 4452 1320 +7913 1698 3942 4451 +7914 3942 4453 4451 +7915 3942 1144 4453 +7916 4451 4453 1814 +7917 1320 4452 2458 +7918 4452 4454 2458 +7919 4452 1814 4454 +7920 2458 4454 1047 +7921 1144 4455 4453 +7922 4455 4456 4453 +7923 4455 1815 4456 +7924 4453 4456 1814 +7925 1815 4457 4456 +7926 4457 4458 4456 +7927 4457 1816 4458 +7928 4456 4458 1814 +7929 1815 4459 4457 +7930 4459 4460 4457 +7931 4459 1121 4460 +7932 4457 4460 1816 +7933 1814 4458 4454 +7934 4458 4461 4454 +7935 4458 1816 4461 +7936 4454 4461 1047 +7937 1144 3938 4455 +7938 3938 4462 4455 +7939 3938 1697 4462 +7940 4455 4462 1815 +7941 1697 4463 4462 +7942 4463 4464 4462 +7943 4463 1605 4464 +7944 4462 4464 1815 +7945 1697 3937 4463 +7946 3937 3562 4463 +7947 3937 1008 3562 +7948 4463 3562 1605 +7949 1815 4464 4459 +7950 4464 3558 4459 +7951 4464 1605 3558 +7952 4459 3558 1121 +7953 1047 4461 2468 +7954 4461 4465 2468 +7955 4461 1816 4465 +7956 2468 4465 1324 +7957 1816 4466 4465 +7958 4466 4467 4465 +7959 4466 1600 4467 +7960 4465 4467 1324 +7961 1816 4460 4466 +7962 4460 3547 4466 +7963 4460 1121 3547 +7964 4466 3547 1600 +7965 1324 4467 2472 +7966 4467 3542 2472 +7967 4467 1600 3542 +7968 2472 3542 997 +7969 385 4468 386 +7970 4468 4469 386 +7971 4468 1817 4469 +7972 386 4469 387 +7973 1817 4470 4469 +7974 4470 4471 4469 +7975 4470 1818 4471 +7976 4469 4471 387 +7977 1817 4472 4470 +7978 4472 4473 4470 +7979 4472 1168 4473 +7980 4470 4473 1818 +7981 387 4471 388 +7982 4471 4474 388 +7983 4471 1818 4474 +7984 388 4474 389 +7985 1168 4475 4473 +7986 4475 4476 4473 +7987 4475 1819 4476 +7988 4473 4476 1818 +7989 1819 4477 4476 +7990 4477 4478 4476 +7991 4477 1820 4478 +7992 4476 4478 1818 +7993 1819 4479 4477 +7994 4479 4480 4477 +7995 4479 1031 4480 +7996 4477 4480 1820 +7997 1818 4478 4474 +7998 4478 4481 4474 +7999 4478 1820 4481 +8000 4474 4481 389 +8001 1168 4482 4475 +8002 4482 4483 4475 +8003 4482 1821 4483 +8004 4475 4483 1819 +8005 1821 4484 4483 +8006 4484 4485 4483 +8007 4484 1259 4485 +8008 4483 4485 1819 +8009 1821 4486 4484 +8010 4486 2210 4484 +8011 4486 998 2210 +8012 4484 2210 1259 +8013 1819 4485 4479 +8014 4485 2206 4479 +8015 4485 1259 2206 +8016 4479 2206 1031 +8017 389 4481 390 +8018 4481 4487 390 +8019 4481 1820 4487 +8020 390 4487 391 +8021 1820 4488 4487 +8022 4488 4489 4487 +8023 4488 1255 4489 +8024 4487 4489 391 +8025 1820 4480 4488 +8026 4480 2196 4488 +8027 4480 1031 2196 +8028 4488 2196 1255 +8029 391 4489 392 +8030 4489 2192 392 +8031 4489 1255 2192 +8032 392 2192 393 +8033 985 4490 4358 +8034 4490 4491 4358 +8035 4490 1822 4491 +8036 4358 4491 1792 +8037 1822 4492 4491 +8038 4492 4493 4491 +8039 4492 1823 4493 +8040 4491 4493 1792 +8041 1822 4494 4492 +8042 4494 4495 4492 +8043 4494 1169 4495 +8044 4492 4495 1823 +8045 1792 4493 4362 +8046 4493 4496 4362 +8047 4493 1823 4496 +8048 4362 4496 1163 +8049 1169 4497 4495 +8050 4497 4498 4495 +8051 4497 1824 4498 +8052 4495 4498 1823 +8053 1824 4499 4498 +8054 4499 4500 4498 +8055 4499 1825 4500 +8056 4498 4500 1823 +8057 1824 4501 4499 +8058 4501 4502 4499 +8059 4501 1150 4502 +8060 4499 4502 1825 +8061 1823 4500 4496 +8062 4500 4503 4496 +8063 4500 1825 4503 +8064 4496 4503 1163 +8065 1169 4504 4497 +8066 4504 4505 4497 +8067 4504 1826 4505 +8068 4497 4505 1824 +8069 1826 4506 4505 +8070 4506 4507 4505 +8071 4506 1722 4507 +8072 4505 4507 1824 +8073 1826 4508 4506 +8074 4508 4036 4506 +8075 4508 1005 4036 +8076 4506 4036 1722 +8077 1824 4507 4501 +8078 4507 4032 4501 +8079 4507 1722 4032 +8080 4501 4032 1150 +8081 1163 4503 4372 +8082 4503 4509 4372 +8083 4503 1825 4509 +8084 4372 4509 1796 +8085 1825 4510 4509 +8086 4510 4511 4509 +8087 4510 1718 4511 +8088 4509 4511 1796 +8089 1825 4502 4510 +8090 4502 4022 4510 +8091 4502 1150 4022 +8092 4510 4022 1718 +8093 1796 4511 4376 +8094 4511 4018 4376 +8095 4511 1718 4018 +8096 4376 4018 993 +8097 10 4512 276 +8098 4512 4513 276 +8099 4512 1827 4513 +8100 276 4513 275 +8101 1827 4514 4513 +8102 4514 4515 4513 +8103 4514 1828 4515 +8104 4513 4515 275 +8105 1827 4516 4514 +8106 4516 4517 4514 +8107 4516 1170 4517 +8108 4514 4517 1828 +8109 275 4515 274 +8110 4515 4518 274 +8111 4515 1828 4518 +8112 274 4518 273 +8113 1170 4519 4517 +8114 4519 4520 4517 +8115 4519 1829 4520 +8116 4517 4520 1828 +8117 1829 4521 4520 +8118 4521 4522 4520 +8119 4521 1830 4522 +8120 4520 4522 1828 +8121 1829 4523 4521 +8122 4523 4524 4521 +8123 4523 1171 4524 +8124 4521 4524 1830 +8125 1828 4522 4518 +8126 4522 4525 4518 +8127 4522 1830 4525 +8128 4518 4525 273 +8129 1170 4526 4519 +8130 4526 4527 4519 +8131 4526 1831 4527 +8132 4519 4527 1829 +8133 1831 4528 4527 +8134 4528 4529 4527 +8135 4528 1832 4529 +8136 4527 4529 1829 +8137 1831 4530 4528 +8138 4530 4531 4528 +8139 4530 1006 4531 +8140 4528 4531 1832 +8141 1829 4529 4523 +8142 4529 4532 4523 +8143 4529 1832 4532 +8144 4523 4532 1171 +8145 273 4525 272 +8146 4525 4533 272 +8147 4525 1830 4533 +8148 272 4533 271 +8149 1830 4534 4533 +8150 4534 4535 4533 +8151 4534 1833 4535 +8152 4533 4535 271 +8153 1830 4524 4534 +8154 4524 4536 4534 +8155 4524 1171 4536 +8156 4534 4536 1833 +8157 271 4535 270 +8158 4535 4537 270 +8159 4535 1833 4537 +8160 270 4537 269 +8161 238 4538 237 +8162 4538 4539 237 +8163 4538 1834 4539 +8164 237 4539 236 +8165 1834 4540 4539 +8166 4540 4541 4539 +8167 4540 1835 4541 +8168 4539 4541 236 +8169 1834 4542 4540 +8170 4542 4543 4540 +8171 4542 1172 4543 +8172 4540 4543 1835 +8173 236 4541 235 +8174 4541 4544 235 +8175 4541 1835 4544 +8176 235 4544 234 +8177 1172 4545 4543 +8178 4545 4546 4543 +8179 4545 1836 4546 +8180 4543 4546 1835 +8181 1836 4547 4546 +8182 4547 4548 4546 +8183 4547 1837 4548 +8184 4546 4548 1835 +8185 1836 4549 4547 +8186 4549 4550 4547 +8187 4549 1170 4550 +8188 4547 4550 1837 +8189 1835 4548 4544 +8190 4548 4551 4544 +8191 4548 1837 4551 +8192 4544 4551 234 +8193 1172 4552 4545 +8194 4552 4553 4545 +8195 4552 1838 4553 +8196 4545 4553 1836 +8197 1838 4554 4553 +8198 4554 4555 4553 +8199 4554 1831 4555 +8200 4553 4555 1836 +8201 1838 4556 4554 +8202 4556 4530 4554 +8203 4556 1006 4530 +8204 4554 4530 1831 +8205 1836 4555 4549 +8206 4555 4526 4549 +8207 4555 1831 4526 +8208 4549 4526 1170 +8209 234 4551 233 +8210 4551 4557 233 +8211 4551 1837 4557 +8212 233 4557 232 +8213 1837 4558 4557 +8214 4558 4559 4557 +8215 4558 1827 4559 +8216 4557 4559 232 +8217 1837 4550 4558 +8218 4550 4516 4558 +8219 4550 1170 4516 +8220 4558 4516 1827 +8221 232 4559 231 +8222 4559 4512 231 +8223 4559 1827 4512 +8224 231 4512 10 +8225 138 4560 139 +8226 4560 4561 139 +8227 4560 1839 4561 +8228 139 4561 140 +8229 1839 4562 4561 +8230 4562 4563 4561 +8231 4562 1840 4563 +8232 4561 4563 140 +8233 1839 4564 4562 +8234 4564 4565 4562 +8235 4564 1173 4565 +8236 4562 4565 1840 +8237 140 4563 141 +8238 4563 4566 141 +8239 4563 1840 4566 +8240 141 4566 142 +8241 1173 4567 4565 +8242 4567 4568 4565 +8243 4567 1841 4568 +8244 4565 4568 1840 +8245 1841 4569 4568 +8246 4569 4570 4568 +8247 4569 1842 4570 +8248 4568 4570 1840 +8249 1841 4571 4569 +8250 4571 4572 4569 +8251 4571 1174 4572 +8252 4569 4572 1842 +8253 1840 4570 4566 +8254 4570 4573 4566 +8255 4570 1842 4573 +8256 4566 4573 142 +8257 1173 4574 4567 +8258 4574 4575 4567 +8259 4574 1843 4575 +8260 4567 4575 1841 +8261 1843 4576 4575 +8262 4576 4577 4575 +8263 4576 1844 4577 +8264 4575 4577 1841 +8265 1843 4578 4576 +8266 4578 4579 4576 +8267 4578 1003 4579 +8268 4576 4579 1844 +8269 1841 4577 4571 +8270 4577 4580 4571 +8271 4577 1844 4580 +8272 4571 4580 1174 +8273 142 4573 143 +8274 4573 4581 143 +8275 4573 1842 4581 +8276 143 4581 144 +8277 1842 4582 4581 +8278 4582 4583 4581 +8279 4582 1845 4583 +8280 4581 4583 144 +8281 1842 4572 4582 +8282 4572 4584 4582 +8283 4572 1174 4584 +8284 4582 4584 1845 +8285 144 4583 145 +8286 4583 4585 145 +8287 4583 1845 4585 +8288 145 4585 6 +8289 4 4586 100 +8290 4586 4587 100 +8291 4586 1846 4587 +8292 100 4587 101 +8293 1846 4588 4587 +8294 4588 4589 4587 +8295 4588 1847 4589 +8296 4587 4589 101 +8297 1846 4590 4588 +8298 4590 4591 4588 +8299 4590 1175 4591 +8300 4588 4591 1847 +8301 101 4589 102 +8302 4589 4592 102 +8303 4589 1847 4592 +8304 102 4592 103 +8305 1175 4593 4591 +8306 4593 4594 4591 +8307 4593 1848 4594 +8308 4591 4594 1847 +8309 1848 4595 4594 +8310 4595 4596 4594 +8311 4595 1849 4596 +8312 4594 4596 1847 +8313 1848 4597 4595 +8314 4597 4598 4595 +8315 4597 1176 4598 +8316 4595 4598 1849 +8317 1847 4596 4592 +8318 4596 4599 4592 +8319 4596 1849 4599 +8320 4592 4599 103 +8321 1175 4600 4593 +8322 4600 4601 4593 +8323 4600 1850 4601 +8324 4593 4601 1848 +8325 1850 4602 4601 +8326 4602 4603 4601 +8327 4602 1851 4603 +8328 4601 4603 1848 +8329 1850 4604 4602 +8330 4604 4605 4602 +8331 4604 1004 4605 +8332 4602 4605 1851 +8333 1848 4603 4597 +8334 4603 4606 4597 +8335 4603 1851 4606 +8336 4597 4606 1176 +8337 103 4599 104 +8338 4599 4607 104 +8339 4599 1849 4607 +8340 104 4607 105 +8341 1849 4608 4607 +8342 4608 4609 4607 +8343 4608 1852 4609 +8344 4607 4609 105 +8345 1849 4598 4608 +8346 4598 4610 4608 +8347 4598 1176 4610 +8348 4608 4610 1852 +8349 105 4609 106 +8350 4609 4611 106 +8351 4609 1852 4611 +8352 106 4611 107 +8353 107 4611 2978 +8354 4611 4612 2978 +8355 4611 1852 4612 +8356 2978 4612 1462 +8357 1852 4613 4612 +8358 4613 4614 4612 +8359 4613 1853 4614 +8360 4612 4614 1462 +8361 1852 4610 4613 +8362 4610 4615 4613 +8363 4610 1176 4615 +8364 4613 4615 1853 +8365 1462 4614 2983 +8366 4614 4616 2983 +8367 4614 1853 4616 +8368 2983 4616 1088 +8369 1176 4617 4615 +8370 4617 4618 4615 +8371 4617 1854 4618 +8372 4615 4618 1853 +8373 1854 4619 4618 +8374 4619 4620 4618 +8375 4619 1855 4620 +8376 4618 4620 1853 +8377 1854 4621 4619 +8378 4621 4622 4619 +8379 4621 1167 4622 +8380 4619 4622 1855 +8381 1853 4620 4616 +8382 4620 4623 4616 +8383 4620 1855 4623 +8384 4616 4623 1088 +8385 1176 4606 4617 +8386 4606 4624 4617 +8387 4606 1851 4624 +8388 4617 4624 1854 +8389 1851 4625 4624 +8390 4625 4626 4624 +8391 4625 1813 4626 +8392 4624 4626 1854 +8393 1851 4605 4625 +8394 4605 4446 4625 +8395 4605 1004 4446 +8396 4625 4446 1813 +8397 1854 4626 4621 +8398 4626 4442 4621 +8399 4626 1813 4442 +8400 4621 4442 1167 +8401 1088 4623 2994 +8402 4623 4627 2994 +8403 4623 1855 4627 +8404 2994 4627 1467 +8405 1855 4628 4627 +8406 4628 4629 4627 +8407 4628 1809 4629 +8408 4627 4629 1467 +8409 1855 4622 4628 +8410 4622 4432 4628 +8411 4622 1167 4432 +8412 4628 4432 1809 +8413 1467 4629 2998 +8414 4629 4428 2998 +8415 4629 1809 4428 +8416 2998 4428 976 +8417 981 3655 3675 +8418 3655 4630 3675 +8419 3655 1626 4630 +8420 3675 4630 1632 +8421 1626 4631 4630 +8422 4631 4632 4630 +8423 4631 1856 4632 +8424 4630 4632 1632 +8425 1626 3654 4631 +8426 3654 4633 4631 +8427 3654 1126 4633 +8428 4631 4633 1856 +8429 1632 4632 3676 +8430 4632 4634 3676 +8431 4632 1856 4634 +8432 3676 4634 1128 +8433 1126 4635 4633 +8434 4635 4636 4633 +8435 4635 1857 4636 +8436 4633 4636 1856 +8437 1857 4637 4636 +8438 4637 4638 4636 +8439 4637 1858 4638 +8440 4636 4638 1856 +8441 1857 4639 4637 +8442 4639 4640 4637 +8443 4639 1111 4640 +8444 4637 4640 1858 +8445 1856 4638 4634 +8446 4638 4641 4634 +8447 4638 1858 4641 +8448 4634 4641 1128 +8449 1126 3650 4635 +8450 3650 4642 4635 +8451 3650 1625 4642 +8452 4635 4642 1857 +8453 1625 4643 4642 +8454 4643 4644 4642 +8455 4643 1557 4644 +8456 4642 4644 1857 +8457 1625 3649 4643 +8458 3649 3358 4643 +8459 3649 996 3358 +8460 4643 3358 1557 +8461 1857 4644 4639 +8462 4644 3354 4639 +8463 4644 1557 3354 +8464 4639 3354 1111 +8465 1128 4641 3680 +8466 4641 4645 3680 +8467 4641 1858 4645 +8468 3680 4645 1633 +8469 1858 4646 4645 +8470 4646 4647 4645 +8471 4646 1553 4647 +8472 4645 4647 1633 +8473 1858 4640 4646 +8474 4640 3344 4646 +8475 4640 1111 3344 +8476 4646 3344 1553 +8477 1633 4647 3681 +8478 4647 3340 3681 +8479 4647 1553 3340 +8480 3681 3340 223 +8481 979 3416 3608 +8482 3416 4648 3608 +8483 3416 1572 4648 +8484 3608 4648 1615 +8485 1572 4649 4648 +8486 4649 4650 4648 +8487 4649 1859 4650 +8488 4648 4650 1615 +8489 1572 3415 4649 +8490 3415 4651 4649 +8491 3415 1115 4651 +8492 4649 4651 1859 +8493 1615 4650 3612 +8494 4650 4652 3612 +8495 4650 1859 4652 +8496 3612 4652 1124 +8497 1115 4653 4651 +8498 4653 4654 4651 +8499 4653 1860 4654 +8500 4651 4654 1859 +8501 1860 4655 4654 +8502 4655 4656 4654 +8503 4655 1861 4656 +8504 4654 4656 1859 +8505 1860 4657 4655 +8506 4657 4658 4655 +8507 4657 1056 4658 +8508 4655 4658 1861 +8509 1859 4656 4652 +8510 4656 4659 4652 +8511 4656 1861 4659 +8512 4652 4659 1124 +8513 1115 3410 4653 +8514 3410 4660 4653 +8515 3410 1571 4660 +8516 4653 4660 1860 +8517 1571 4661 4660 +8518 4661 4662 4660 +8519 4661 1355 4662 +8520 4660 4662 1860 +8521 1571 3409 4661 +8522 3409 2582 4661 +8523 3409 1007 2582 +8524 4661 2582 1355 +8525 1860 4662 4657 +8526 4662 2578 4657 +8527 4662 1355 2578 +8528 4657 2578 1056 +8529 1124 4659 3622 +8530 4659 4663 3622 +8531 4659 1861 4663 +8532 3622 4663 1619 +8533 1861 4664 4663 +8534 4664 4665 4663 +8535 4664 1350 4665 +8536 4663 4665 1619 +8537 1861 4658 4664 +8538 4658 2567 4664 +8539 4658 1056 2567 +8540 4664 2567 1350 +8541 1619 4665 3626 +8542 4665 2562 3626 +8543 4665 1350 2562 +8544 3626 2562 982 +8545 970 4666 2919 +8546 4666 4667 2919 +8547 4666 1862 4667 +8548 2919 4667 1446 +8549 1862 4668 4667 +8550 4668 4669 4667 +8551 4668 1863 4669 +8552 4667 4669 1446 +8553 1862 4670 4668 +8554 4670 4671 4668 +8555 4670 1177 4671 +8556 4668 4671 1863 +8557 1446 4669 2920 +8558 4669 4672 2920 +8559 4669 1863 4672 +8560 2920 4672 1083 +8561 1177 4673 4671 +8562 4673 4674 4671 +8563 4673 1864 4674 +8564 4671 4674 1863 +8565 1864 4675 4674 +8566 4675 4676 4674 +8567 4675 1865 4676 +8568 4674 4676 1863 +8569 1864 4677 4675 +8570 4677 4678 4675 +8571 4677 1160 4678 +8572 4675 4678 1865 +8573 1863 4676 4672 +8574 4676 4679 4672 +8575 4676 1865 4679 +8576 4672 4679 1083 +8577 1177 4680 4673 +8578 4680 4681 4673 +8579 4680 1866 4681 +8580 4673 4681 1864 +8581 1866 4682 4681 +8582 4682 4683 4681 +8583 4682 1778 4683 +8584 4681 4683 1864 +8585 1866 4684 4682 +8586 4684 4292 4682 +8587 4684 1002 4292 +8588 4682 4292 1778 +8589 1864 4683 4677 +8590 4683 4288 4677 +8591 4683 1778 4288 +8592 4677 4288 1160 +8593 1083 4679 2924 +8594 4679 4685 2924 +8595 4679 1865 4685 +8596 2924 4685 1447 +8597 1865 4686 4685 +8598 4686 4687 4685 +8599 4686 1774 4687 +8600 4685 4687 1447 +8601 1865 4678 4686 +8602 4678 4278 4686 +8603 4678 1160 4278 +8604 4686 4278 1774 +8605 1447 4687 2925 +8606 4687 4274 2925 +8607 4687 1774 4274 +8608 2925 4274 61 +8609 972 4688 3119 +8610 4688 4689 3119 +8611 4688 1867 4689 +8612 3119 4689 1498 +8613 1867 4690 4689 +8614 4690 4691 4689 +8615 4690 1868 4691 +8616 4689 4691 1498 +8617 1867 4692 4690 +8618 4692 4693 4690 +8619 4692 1178 4693 +8620 4690 4693 1868 +8621 1498 4691 3120 +8622 4691 4694 3120 +8623 4691 1868 4694 +8624 3120 4694 1097 +8625 1178 4695 4693 +8626 4695 4696 4693 +8627 4695 1869 4696 +8628 4693 4696 1868 +8629 1869 4697 4696 +8630 4697 4698 4696 +8631 4697 1870 4698 +8632 4696 4698 1868 +8633 1869 4699 4697 +8634 4699 4700 4697 +8635 4699 1172 4700 +8636 4697 4700 1870 +8637 1868 4698 4694 +8638 4698 4701 4694 +8639 4698 1870 4701 +8640 4694 4701 1097 +8641 1178 4702 4695 +8642 4702 4703 4695 +8643 4702 1871 4703 +8644 4695 4703 1869 +8645 1871 4704 4703 +8646 4704 4705 4703 +8647 4704 1838 4705 +8648 4703 4705 1869 +8649 1871 4706 4704 +8650 4706 4556 4704 +8651 4706 1006 4556 +8652 4704 4556 1838 +8653 1869 4705 4699 +8654 4705 4552 4699 +8655 4705 1838 4552 +8656 4699 4552 1172 +8657 1097 4701 3124 +8658 4701 4707 3124 +8659 4701 1870 4707 +8660 3124 4707 1499 +8661 1870 4708 4707 +8662 4708 4709 4707 +8663 4708 1834 4709 +8664 4707 4709 1499 +8665 1870 4700 4708 +8666 4700 4542 4708 +8667 4700 1172 4542 +8668 4708 4542 1834 +8669 1499 4709 3125 +8670 4709 4538 3125 +8671 4709 1834 4538 +8672 3125 4538 238 +8673 971 4710 2945 +8674 4710 4711 2945 +8675 4710 1872 4711 +8676 2945 4711 1453 +8677 1872 4712 4711 +8678 4712 4713 4711 +8679 4712 1873 4713 +8680 4711 4713 1453 +8681 1872 4714 4712 +8682 4714 4715 4712 +8683 4714 1179 4715 +8684 4712 4715 1873 +8685 1453 4713 2946 +8686 4713 4716 2946 +8687 4713 1873 4716 +8688 2946 4716 1085 +8689 1179 4717 4715 +8690 4717 4718 4715 +8691 4717 1874 4718 +8692 4715 4718 1873 +8693 1874 4719 4718 +8694 4719 4720 4718 +8695 4719 1875 4720 +8696 4718 4720 1873 +8697 1874 4721 4719 +8698 4721 4722 4719 +8699 4721 1142 4722 +8700 4719 4722 1875 +8701 1873 4720 4716 +8702 4720 4723 4716 +8703 4720 1875 4723 +8704 4716 4723 1085 +8705 1179 4724 4717 +8706 4724 4725 4717 +8707 4724 1876 4725 +8708 4717 4725 1874 +8709 1876 4726 4725 +8710 4726 4727 4725 +8711 4726 1688 4727 +8712 4725 4727 1874 +8713 1876 4728 4726 +8714 4728 3896 4726 +8715 4728 1000 3896 +8716 4726 3896 1688 +8717 1874 4727 4721 +8718 4727 3892 4721 +8719 4727 1688 3892 +8720 4721 3892 1142 +8721 1085 4723 2950 +8722 4723 4729 2950 +8723 4723 1875 4729 +8724 2950 4729 1454 +8725 1875 4730 4729 +8726 4730 4731 4729 +8727 4730 1684 4731 +8728 4729 4731 1454 +8729 1875 4722 4730 +8730 4722 3882 4730 +8731 4722 1142 3882 +8732 4730 3882 1684 +8733 1454 4731 2951 +8734 4731 3878 2951 +8735 4731 1684 3878 +8736 2951 3878 153 +8737 973 4732 3145 +8738 4732 4733 3145 +8739 4732 1877 4733 +8740 3145 4733 1505 +8741 1877 4734 4733 +8742 4734 4735 4733 +8743 4734 1878 4735 +8744 4733 4735 1505 +8745 1877 4736 4734 +8746 4736 4737 4734 +8747 4736 1180 4737 +8748 4734 4737 1878 +8749 1505 4735 3146 +8750 4735 4738 3146 +8751 4735 1878 4738 +8752 3146 4738 1099 +8753 1180 4739 4737 +8754 4739 4740 4737 +8755 4739 1879 4740 +8756 4737 4740 1878 +8757 1879 4741 4740 +8758 4741 4742 4740 +8759 4741 1880 4742 +8760 4740 4742 1878 +8761 1879 4743 4741 +8762 4743 4744 4741 +8763 4743 1147 4744 +8764 4741 4744 1880 +8765 1878 4742 4738 +8766 4742 4745 4738 +8767 4742 1880 4745 +8768 4738 4745 1099 +8769 1180 4746 4739 +8770 4746 4747 4739 +8771 4746 1881 4747 +8772 4739 4747 1879 +8773 1881 4748 4747 +8774 4748 4749 4747 +8775 4748 1710 4749 +8776 4747 4749 1879 +8777 1881 4750 4748 +8778 4750 3988 4748 +8779 4750 1001 3988 +8780 4748 3988 1710 +8781 1879 4749 4743 +8782 4749 3984 4743 +8783 4749 1710 3984 +8784 4743 3984 1147 +8785 1099 4745 3150 +8786 4745 4751 3150 +8787 4745 1880 4751 +8788 3150 4751 1506 +8789 1880 4752 4751 +8790 4752 4753 4751 +8791 4752 1706 4753 +8792 4751 4753 1506 +8793 1880 4744 4752 +8794 4744 3974 4752 +8795 4744 1147 3974 +8796 4752 3974 1706 +8797 1506 4753 3151 +8798 4753 3970 3151 +8799 4753 1706 3970 +8800 3151 3970 323 +8801 6 4585 168 +8802 4585 4754 168 +8803 4585 1845 4754 +8804 168 4754 167 +8805 1845 4755 4754 +8806 4755 4756 4754 +8807 4755 1882 4756 +8808 4754 4756 167 +8809 1845 4584 4755 +8810 4584 4757 4755 +8811 4584 1174 4757 +8812 4755 4757 1882 +8813 167 4756 166 +8814 4756 4758 166 +8815 4756 1882 4758 +8816 166 4758 165 +8817 1174 4759 4757 +8818 4759 4760 4757 +8819 4759 1883 4760 +8820 4757 4760 1882 +8821 1883 4761 4760 +8822 4761 4762 4760 +8823 4761 1884 4762 +8824 4760 4762 1882 +8825 1883 4763 4761 +8826 4763 4764 4761 +8827 4763 1148 4764 +8828 4761 4764 1884 +8829 1882 4762 4758 +8830 4762 4765 4758 +8831 4762 1884 4765 +8832 4758 4765 165 +8833 1174 4580 4759 +8834 4580 4766 4759 +8835 4580 1844 4766 +8836 4759 4766 1883 +8837 1844 4767 4766 +8838 4767 4768 4766 +8839 4767 1715 4768 +8840 4766 4768 1883 +8841 1844 4579 4767 +8842 4579 4010 4767 +8843 4579 1003 4010 +8844 4767 4010 1715 +8845 1883 4768 4763 +8846 4768 4006 4763 +8847 4768 1715 4006 +8848 4763 4006 1148 +8849 165 4765 164 +8850 4765 4769 164 +8851 4765 1884 4769 +8852 164 4769 163 +8853 1884 4770 4769 +8854 4770 4771 4769 +8855 4770 1711 4771 +8856 4769 4771 163 +8857 1884 4764 4770 +8858 4764 3996 4770 +8859 4764 1148 3996 +8860 4770 3996 1711 +8861 163 4771 162 +8862 4771 3992 162 +8863 4771 1711 3992 +8864 162 3992 161 +8865 84 4069 83 +8866 4069 4772 83 +8867 4069 1731 4772 +8868 83 4772 82 +8869 1731 4773 4772 +8870 4773 4774 4772 +8871 4773 1885 4774 +8872 4772 4774 82 +8873 1731 4068 4773 +8874 4068 4775 4773 +8875 4068 1153 4775 +8876 4773 4775 1885 +8877 82 4774 81 +8878 4774 4776 81 +8879 4774 1885 4776 +8880 81 4776 80 +8881 1153 4777 4775 +8882 4777 4778 4775 +8883 4777 1886 4778 +8884 4775 4778 1885 +8885 1886 4779 4778 +8886 4779 4780 4778 +8887 4779 1887 4780 +8888 4778 4780 1885 +8889 1886 4781 4779 +8890 4781 4782 4779 +8891 4781 1175 4782 +8892 4779 4782 1887 +8893 1885 4780 4776 +8894 4780 4783 4776 +8895 4780 1887 4783 +8896 4776 4783 80 +8897 1153 4064 4777 +8898 4064 4784 4777 +8899 4064 1730 4784 +8900 4777 4784 1886 +8901 1730 4785 4784 +8902 4785 4786 4784 +8903 4785 1850 4786 +8904 4784 4786 1886 +8905 1730 4063 4785 +8906 4063 4604 4785 +8907 4063 1004 4604 +8908 4785 4604 1850 +8909 1886 4786 4781 +8910 4786 4600 4781 +8911 4786 1850 4600 +8912 4781 4600 1175 +8913 80 4783 79 +8914 4783 4787 79 +8915 4783 1887 4787 +8916 79 4787 78 +8917 1887 4788 4787 +8918 4788 4789 4787 +8919 4788 1846 4789 +8920 4787 4789 78 +8921 1887 4782 4788 +8922 4782 4590 4788 +8923 4782 1175 4590 +8924 4788 4590 1846 +8925 78 4789 77 +8926 4789 4586 77 +8927 4789 1846 4586 +8928 77 4586 4 +8929 261 3570 260 +8930 3570 4790 260 +8931 3570 1607 4790 +8932 260 4790 259 +8933 1607 4791 4790 +8934 4791 4792 4790 +8935 4791 1888 4792 +8936 4790 4792 259 +8937 1607 3569 4791 +8938 3569 4793 4791 +8939 3569 1122 4793 +8940 4791 4793 1888 +8941 259 4792 258 +8942 4792 4794 258 +8943 4792 1888 4794 +8944 258 4794 257 +8945 1122 4795 4793 +8946 4795 4796 4793 +8947 4795 1889 4796 +8948 4793 4796 1888 +8949 1889 4797 4796 +8950 4797 4798 4796 +8951 4797 1890 4798 +8952 4796 4798 1888 +8953 1889 4799 4797 +8954 4799 4800 4797 +8955 4799 1104 4800 +8956 4797 4800 1890 +8957 1888 4798 4794 +8958 4798 4801 4794 +8959 4798 1890 4801 +8960 4794 4801 257 +8961 1122 3564 4795 +8962 3564 4802 4795 +8963 3564 1606 4802 +8964 4795 4802 1889 +8965 1606 4803 4802 +8966 4803 4804 4802 +8967 4803 1525 4804 +8968 4802 4804 1889 +8969 1606 3563 4803 +8970 3563 3222 4803 +8971 3563 1008 3222 +8972 4803 3222 1525 +8973 1889 4804 4799 +8974 4804 3218 4799 +8975 4804 1525 3218 +8976 4799 3218 1104 +8977 257 4801 256 +8978 4801 4805 256 +8979 4801 1890 4805 +8980 256 4805 255 +8981 1890 4806 4805 +8982 4806 4807 4805 +8983 4806 1521 4807 +8984 4805 4807 255 +8985 1890 4800 4806 +8986 4800 3208 4806 +8987 4800 1104 3208 +8988 4806 3208 1521 +8989 255 4807 254 +8990 4807 3204 254 +8991 4807 1521 3204 +8992 254 3204 11 +8993 988 4043 4199 +8994 4043 4808 4199 +8995 4043 1724 4808 +8996 4199 4808 1756 +8997 1724 4809 4808 +8998 4809 4810 4808 +8999 4809 1891 4810 +9000 4808 4810 1756 +9001 1724 4042 4809 +9002 4042 4811 4809 +9003 4042 1151 4811 +9004 4809 4811 1891 +9005 1756 4810 4200 +9006 4810 4812 4200 +9007 4810 1891 4812 +9008 4200 4812 1155 +9009 1151 4813 4811 +9010 4813 4814 4811 +9011 4813 1892 4814 +9012 4811 4814 1891 +9013 1892 4815 4814 +9014 4815 4816 4814 +9015 4815 1893 4816 +9016 4814 4816 1891 +9017 1892 4817 4815 +9018 4817 4818 4815 +9019 4817 1181 4818 +9020 4815 4818 1893 +9021 1891 4816 4812 +9022 4816 4819 4812 +9023 4816 1893 4819 +9024 4812 4819 1155 +9025 1151 4038 4813 +9026 4038 4820 4813 +9027 4038 1723 4820 +9028 4813 4820 1892 +9029 1723 4821 4820 +9030 4821 4822 4820 +9031 4821 1894 4822 +9032 4820 4822 1892 +9033 1723 4037 4821 +9034 4037 4823 4821 +9035 4037 1005 4823 +9036 4821 4823 1894 +9037 1892 4822 4817 +9038 4822 4824 4817 +9039 4822 1894 4824 +9040 4817 4824 1181 +9041 1155 4819 4205 +9042 4819 4825 4205 +9043 4819 1893 4825 +9044 4205 4825 1757 +9045 1893 4826 4825 +9046 4826 4827 4825 +9047 4826 1895 4827 +9048 4825 4827 1757 +9049 1893 4818 4826 +9050 4818 4828 4826 +9051 4818 1181 4828 +9052 4826 4828 1895 +9053 1757 4827 4206 +9054 4827 4829 4206 +9055 4827 1895 4829 +9056 4206 4829 987 +9057 409 4401 3074 +9058 4401 4830 3074 +9059 4401 1801 4830 +9060 3074 4830 1486 +9061 1801 4831 4830 +9062 4831 4832 4830 +9063 4831 1896 4832 +9064 4830 4832 1486 +9065 1801 4400 4831 +9066 4400 4833 4831 +9067 4400 1164 4833 +9068 4831 4833 1896 +9069 1486 4832 3078 +9070 4832 4834 3078 +9071 4832 1896 4834 +9072 3078 4834 1094 +9073 1164 4835 4833 +9074 4835 4836 4833 +9075 4835 1897 4836 +9076 4833 4836 1896 +9077 1897 4837 4836 +9078 4837 4838 4836 +9079 4837 1898 4838 +9080 4836 4838 1896 +9081 1897 4839 4837 +9082 4839 4840 4837 +9083 4839 1059 4840 +9084 4837 4840 1898 +9085 1896 4838 4834 +9086 4838 4841 4834 +9087 4838 1898 4841 +9088 4834 4841 1094 +9089 1164 4396 4835 +9090 4396 4842 4835 +9091 4396 1800 4842 +9092 4835 4842 1897 +9093 1800 4843 4842 +9094 4843 4844 4842 +9095 4843 1364 4844 +9096 4842 4844 1897 +9097 1800 4395 4843 +9098 4395 2612 4843 +9099 4395 999 2612 +9100 4843 2612 1364 +9101 1897 4844 4839 +9102 4844 2608 4839 +9103 4844 1364 2608 +9104 4839 2608 1059 +9105 1094 4841 3088 +9106 4841 4845 3088 +9107 4841 1898 4845 +9108 3088 4845 1490 +9109 1898 4846 4845 +9110 4846 4847 4845 +9111 4846 1359 4847 +9112 4845 4847 1490 +9113 1898 4840 4846 +9114 4840 2597 4846 +9115 4840 1059 2597 +9116 4846 2597 1359 +9117 1490 4847 3092 +9118 4847 2592 3092 +9119 4847 1359 2592 +9120 3092 2592 990 +9121 987 4426 4223 +9122 4426 4848 4223 +9123 4426 1807 4848 +9124 4223 4848 1762 +9125 1807 4849 4848 +9126 4849 4850 4848 +9127 4849 1899 4850 +9128 4848 4850 1762 +9129 1807 4425 4849 +9130 4425 4851 4849 +9131 4425 1165 4851 +9132 4849 4851 1899 +9133 1762 4850 4224 +9134 4850 4852 4224 +9135 4850 1899 4852 +9136 4224 4852 1157 +9137 1165 4853 4851 +9138 4853 4854 4851 +9139 4853 1900 4854 +9140 4851 4854 1899 +9141 1900 4855 4854 +9142 4855 4856 4854 +9143 4855 1901 4856 +9144 4854 4856 1899 +9145 1900 4857 4855 +9146 4857 4858 4855 +9147 4857 1173 4858 +9148 4855 4858 1901 +9149 1899 4856 4852 +9150 4856 4859 4852 +9151 4856 1901 4859 +9152 4852 4859 1157 +9153 1165 4420 4853 +9154 4420 4860 4853 +9155 4420 1806 4860 +9156 4853 4860 1900 +9157 1806 4861 4860 +9158 4861 4862 4860 +9159 4861 1843 4862 +9160 4860 4862 1900 +9161 1806 4419 4861 +9162 4419 4578 4861 +9163 4419 1003 4578 +9164 4861 4578 1843 +9165 1900 4862 4857 +9166 4862 4574 4857 +9167 4862 1843 4574 +9168 4857 4574 1173 +9169 1157 4859 4228 +9170 4859 4863 4228 +9171 4859 1901 4863 +9172 4228 4863 1763 +9173 1901 4864 4863 +9174 4864 4865 4863 +9175 4864 1839 4865 +9176 4863 4865 1763 +9177 1901 4858 4864 +9178 4858 4564 4864 +9179 4858 1173 4564 +9180 4864 4564 1839 +9181 1763 4865 4229 +9182 4865 4560 4229 +9183 4865 1839 4560 +9184 4229 4560 138 +9185 989 2560 3027 +9186 2560 4866 3027 +9187 2560 1348 4866 +9188 3027 4866 1476 +9189 1348 4867 4866 +9190 4867 4868 4866 +9191 4867 1902 4868 +9192 4866 4868 1476 +9193 1348 2559 4867 +9194 2559 4869 4867 +9195 2559 1054 4869 +9196 4867 4869 1902 +9197 1476 4868 3028 +9198 4868 4870 3028 +9199 4868 1902 4870 +9200 3028 4870 1092 +9201 1054 4871 4869 +9202 4871 4872 4869 +9203 4871 1903 4872 +9204 4869 4872 1902 +9205 1903 4873 4872 +9206 4873 4874 4872 +9207 4873 1904 4874 +9208 4872 4874 1902 +9209 1903 4875 4873 +9210 4875 4876 4873 +9211 4875 1168 4876 +9212 4873 4876 1904 +9213 1902 4874 4870 +9214 4874 4877 4870 +9215 4874 1904 4877 +9216 4870 4877 1092 +9217 1054 2554 4871 +9218 2554 4878 4871 +9219 2554 1347 4878 +9220 4871 4878 1903 +9221 1347 4879 4878 +9222 4879 4880 4878 +9223 4879 1821 4880 +9224 4878 4880 1903 +9225 1347 2553 4879 +9226 2553 4486 4879 +9227 2553 998 4486 +9228 4879 4486 1821 +9229 1903 4880 4875 +9230 4880 4482 4875 +9231 4880 1821 4482 +9232 4875 4482 1168 +9233 1092 4877 3032 +9234 4877 4881 3032 +9235 4877 1904 4881 +9236 3032 4881 1477 +9237 1904 4882 4881 +9238 4882 4883 4881 +9239 4882 1817 4883 +9240 4881 4883 1477 +9241 1904 4876 4882 +9242 4876 4472 4882 +9243 4876 1168 4472 +9244 4882 4472 1817 +9245 1477 4883 3033 +9246 4883 4468 3033 +9247 4883 1817 4468 +9248 3033 4468 385 +9249 987 4829 4427 +9250 4829 4884 4427 +9251 4829 1895 4884 +9252 4427 4884 1808 +9253 1895 4885 4884 +9254 4885 4886 4884 +9255 4885 1905 4886 +9256 4884 4886 1808 +9257 1895 4828 4885 +9258 4828 4887 4885 +9259 4828 1181 4887 +9260 4885 4887 1905 +9261 1808 4886 4422 +9262 4886 4888 4422 +9263 4886 1905 4888 +9264 4422 4888 1166 +9265 1181 4889 4887 +9266 4889 4890 4887 +9267 4889 1906 4890 +9268 4887 4890 1905 +9269 1906 4891 4890 +9270 4891 4892 4890 +9271 4891 1907 4892 +9272 4890 4892 1905 +9273 1906 4893 4891 +9274 4893 4894 4891 +9275 4893 1182 4894 +9276 4891 4894 1907 +9277 1905 4892 4888 +9278 4892 4895 4888 +9279 4892 1907 4895 +9280 4888 4895 1166 +9281 1181 4824 4889 +9282 4824 4896 4889 +9283 4824 1894 4896 +9284 4889 4896 1906 +9285 1894 4897 4896 +9286 4897 4898 4896 +9287 4897 1908 4898 +9288 4896 4898 1906 +9289 1894 4823 4897 +9290 4823 4899 4897 +9291 4823 1005 4899 +9292 4897 4899 1908 +9293 1906 4898 4893 +9294 4898 4900 4893 +9295 4898 1908 4900 +9296 4893 4900 1182 +9297 1166 4895 4408 +9298 4895 4901 4408 +9299 4895 1907 4901 +9300 4408 4901 1803 +9301 1907 4902 4901 +9302 4902 4903 4901 +9303 4902 1909 4903 +9304 4901 4903 1803 +9305 1907 4894 4902 +9306 4894 4904 4902 +9307 4894 1182 4904 +9308 4902 4904 1909 +9309 1803 4903 4403 +9310 4903 4905 4403 +9311 4903 1909 4905 +9312 4403 4905 971 +9313 985 4906 4908 +9314 4906 4907 4908 +9315 4906 1910 4907 +9316 4908 4907 1912 +9317 1910 4909 4907 +9318 4909 4910 4907 +9319 4909 1911 4910 +9320 4907 4910 1912 +9321 1910 4911 4909 +9322 4911 4912 4909 +9323 4911 1183 4912 +9324 4909 4912 1911 +9325 1912 4910 4914 +9326 4910 4913 4914 +9327 4910 1911 4913 +9328 4914 4913 1184 +9329 1183 4915 4912 +9330 4915 4916 4912 +9331 4915 1913 4916 +9332 4912 4916 1911 +9333 1913 4917 4916 +9334 4917 4918 4916 +9335 4917 1914 4918 +9336 4916 4918 1911 +9337 1913 4919 4917 +9338 4919 4920 4917 +9339 4919 1179 4920 +9340 4917 4920 1914 +9341 1911 4918 4913 +9342 4918 4921 4913 +9343 4918 1914 4921 +9344 4913 4921 1184 +9345 1183 4922 4915 +9346 4922 4923 4915 +9347 4922 1915 4923 +9348 4915 4923 1913 +9349 1915 4924 4923 +9350 4924 4925 4923 +9351 4924 1876 4925 +9352 4923 4925 1913 +9353 1915 4926 4924 +9354 4926 4728 4924 +9355 4926 1000 4728 +9356 4924 4728 1876 +9357 1913 4925 4919 +9358 4925 4724 4919 +9359 4925 1876 4724 +9360 4919 4724 1179 +9361 1184 4921 4928 +9362 4921 4927 4928 +9363 4921 1914 4927 +9364 4928 4927 1916 +9365 1914 4929 4927 +9366 4929 4930 4927 +9367 4929 1872 4930 +9368 4927 4930 1916 +9369 1914 4920 4929 +9370 4920 4714 4929 +9371 4920 1179 4714 +9372 4929 4714 1872 +9373 1916 4930 4931 +9374 4930 4710 4931 +9375 4930 1872 4710 +9376 4931 4710 971 +9377 184 3877 3682 +9378 3877 4932 3682 +9379 3877 1683 4932 +9380 3682 4932 1634 +9381 1683 4933 4932 +9382 4933 4934 4932 +9383 4933 1917 4934 +9384 4932 4934 1634 +9385 1683 3876 4933 +9386 3876 4935 4933 +9387 3876 1141 4935 +9388 4933 4935 1917 +9389 1634 4934 3686 +9390 4934 4936 3686 +9391 4934 1917 4936 +9392 3686 4936 1129 +9393 1141 4937 4935 +9394 4937 4938 4935 +9395 4937 1918 4938 +9396 4935 4938 1917 +9397 1918 4939 4938 +9398 4939 4940 4938 +9399 4939 1919 4940 +9400 4938 4940 1917 +9401 1918 4941 4939 +9402 4941 4942 4939 +9403 4941 1183 4942 +9404 4939 4942 1919 +9405 1917 4940 4936 +9406 4940 4943 4936 +9407 4940 1919 4943 +9408 4936 4943 1129 +9409 1141 3872 4937 +9410 3872 4944 4937 +9411 3872 1682 4944 +9412 4937 4944 1918 +9413 1682 4945 4944 +9414 4945 4946 4944 +9415 4945 1915 4946 +9416 4944 4946 1918 +9417 1682 3871 4945 +9418 3871 4926 4945 +9419 3871 1000 4926 +9420 4945 4926 1915 +9421 1918 4946 4941 +9422 4946 4922 4941 +9423 4946 1915 4922 +9424 4941 4922 1183 +9425 1129 4943 3696 +9426 4943 4947 3696 +9427 4943 1919 4947 +9428 3696 4947 1638 +9429 1919 4948 4947 +9430 4948 4949 4947 +9431 4948 1910 4949 +9432 4947 4949 1638 +9433 1919 4942 4948 +9434 4942 4911 4948 +9435 4942 1183 4911 +9436 4948 4911 1910 +9437 1638 4949 3700 +9438 4949 4906 3700 +9439 4949 1910 4906 +9440 3700 4906 985 +9441 354 3969 3734 +9442 3969 4950 3734 +9443 3969 1705 4950 +9444 3734 4950 1648 +9445 1705 4951 4950 +9446 4951 4952 4950 +9447 4951 1920 4952 +9448 4950 4952 1648 +9449 1705 3968 4951 +9450 3968 4953 4951 +9451 3968 1146 4953 +9452 4951 4953 1920 +9453 1648 4952 3738 +9454 4952 4954 3738 +9455 4952 1920 4954 +9456 3738 4954 1133 +9457 1146 4955 4953 +9458 4955 4956 4953 +9459 4955 1921 4956 +9460 4953 4956 1920 +9461 1921 4957 4956 +9462 4957 4958 4956 +9463 4957 1922 4958 +9464 4956 4958 1920 +9465 1921 4959 4957 +9466 4959 4960 4957 +9467 4959 1185 4960 +9468 4957 4960 1922 +9469 1920 4958 4954 +9470 4958 4961 4954 +9471 4958 1922 4961 +9472 4954 4961 1133 +9473 1146 3964 4955 +9474 3964 4962 4955 +9475 3964 1704 4962 +9476 4955 4962 1921 +9477 1704 4963 4962 +9478 4963 4964 4962 +9479 4963 1923 4964 +9480 4962 4964 1921 +9481 1704 3963 4963 +9482 3963 4965 4963 +9483 3963 1001 4965 +9484 4963 4965 1923 +9485 1921 4964 4959 +9486 4964 4966 4959 +9487 4964 1923 4966 +9488 4959 4966 1185 +9489 1133 4961 3748 +9490 4961 4967 3748 +9491 4961 1922 4967 +9492 3748 4967 1652 +9493 1922 4968 4967 +9494 4968 4969 4967 +9495 4968 1924 4969 +9496 4967 4969 1652 +9497 1922 4960 4968 +9498 4960 4970 4968 +9499 4960 1185 4970 +9500 4968 4970 1924 +9501 1652 4969 3752 +9502 4969 4971 3752 +9503 4969 1924 4971 +9504 3752 4971 986 +9505 991 4972 1999 +9506 4972 4973 1999 +9507 4972 1925 4973 +9508 1999 4973 1202 +9509 1925 4974 4973 +9510 4974 4975 4973 +9511 4974 1926 4975 +9512 4973 4975 1202 +9513 1925 4976 4974 +9514 4976 4977 4974 +9515 4976 1186 4977 +9516 4974 4977 1926 +9517 1202 4975 2000 +9518 4975 4978 2000 +9519 4975 1926 4978 +9520 2000 4978 1015 +9521 1186 4979 4977 +9522 4979 4980 4977 +9523 4979 1927 4980 +9524 4977 4980 1926 +9525 1927 4981 4980 +9526 4981 4982 4980 +9527 4981 1928 4982 +9528 4980 4982 1926 +9529 1927 4983 4981 +9530 4983 4984 4981 +9531 4983 1177 4984 +9532 4981 4984 1928 +9533 1926 4982 4978 +9534 4982 4985 4978 +9535 4982 1928 4985 +9536 4978 4985 1015 +9537 1186 4986 4979 +9538 4986 4987 4979 +9539 4986 1929 4987 +9540 4979 4987 1927 +9541 1929 4988 4987 +9542 4988 4989 4987 +9543 4988 1866 4989 +9544 4987 4989 1927 +9545 1929 4990 4988 +9546 4990 4684 4988 +9547 4990 1002 4684 +9548 4988 4684 1866 +9549 1927 4989 4983 +9550 4989 4680 4983 +9551 4989 1866 4680 +9552 4983 4680 1177 +9553 1015 4985 2004 +9554 4985 4991 2004 +9555 4985 1928 4991 +9556 2004 4991 1203 +9557 1928 4992 4991 +9558 4992 4993 4991 +9559 4992 1862 4993 +9560 4991 4993 1203 +9561 1928 4984 4992 +9562 4984 4670 4992 +9563 4984 1177 4670 +9564 4992 4670 1862 +9565 1203 4993 2005 +9566 4993 4666 2005 +9567 4993 1862 4666 +9568 2005 4666 970 +9569 986 4971 3443 +9570 4971 4994 3443 +9571 4971 1924 4994 +9572 3443 4994 1580 +9573 1924 4995 4994 +9574 4995 4996 4994 +9575 4995 1930 4996 +9576 4994 4996 1580 +9577 1924 4970 4995 +9578 4970 4997 4995 +9579 4970 1185 4997 +9580 4995 4997 1930 +9581 1580 4996 3440 +9582 4996 4998 3440 +9583 4996 1930 4998 +9584 3440 4998 1118 +9585 1185 4999 4997 +9586 4999 5000 4997 +9587 4999 1931 5000 +9588 4997 5000 1930 +9589 1931 5001 5000 +9590 5001 5002 5000 +9591 5001 1932 5002 +9592 5000 5002 1930 +9593 1931 5003 5001 +9594 5003 5004 5001 +9595 5003 1180 5004 +9596 5001 5004 1932 +9597 1930 5002 4998 +9598 5002 5005 4998 +9599 5002 1932 5005 +9600 4998 5005 1118 +9601 1185 4966 4999 +9602 4966 5006 4999 +9603 4966 1923 5006 +9604 4999 5006 1931 +9605 1923 5007 5006 +9606 5007 5008 5006 +9607 5007 1881 5008 +9608 5006 5008 1931 +9609 1923 4965 5007 +9610 4965 4750 5007 +9611 4965 1001 4750 +9612 5007 4750 1881 +9613 1931 5008 5003 +9614 5008 4746 5003 +9615 5008 1881 4746 +9616 5003 4746 1180 +9617 1118 5005 3426 +9618 5005 5009 3426 +9619 5005 1932 5009 +9620 3426 5009 1576 +9621 1932 5010 5009 +9622 5010 5011 5009 +9623 5010 1877 5011 +9624 5009 5011 1576 +9625 1932 5004 5010 +9626 5004 4736 5010 +9627 5004 1180 4736 +9628 5010 4736 1877 +9629 1576 5011 3420 +9630 5011 4732 3420 +9631 5011 1877 4732 +9632 3420 4732 973 +9633 971 4905 4931 +9634 4905 5012 4931 +9635 4905 1909 5012 +9636 4931 5012 1916 +9637 1909 5013 5012 +9638 5013 5014 5012 +9639 5013 1933 5014 +9640 5012 5014 1916 +9641 1909 4904 5013 +9642 4904 5015 5013 +9643 4904 1182 5015 +9644 5013 5015 1933 +9645 1916 5014 4928 +9646 5014 5016 4928 +9647 5014 1933 5016 +9648 4928 5016 1184 +9649 1182 5017 5015 +9650 5017 5018 5015 +9651 5017 1934 5018 +9652 5015 5018 1933 +9653 1934 5019 5018 +9654 5019 5020 5018 +9655 5019 1935 5020 +9656 5018 5020 1933 +9657 1934 5021 5019 +9658 5021 5022 5019 +9659 5021 1169 5022 +9660 5019 5022 1935 +9661 1933 5020 5016 +9662 5020 5023 5016 +9663 5020 1935 5023 +9664 5016 5023 1184 +9665 1182 4900 5017 +9666 4900 5024 5017 +9667 4900 1908 5024 +9668 5017 5024 1934 +9669 1908 5025 5024 +9670 5025 5026 5024 +9671 5025 1826 5026 +9672 5024 5026 1934 +9673 1908 4899 5025 +9674 4899 4508 5025 +9675 4899 1005 4508 +9676 5025 4508 1826 +9677 1934 5026 5021 +9678 5026 4504 5021 +9679 5026 1826 4504 +9680 5021 4504 1169 +9681 1184 5023 4914 +9682 5023 5027 4914 +9683 5023 1935 5027 +9684 4914 5027 1912 +9685 1935 5028 5027 +9686 5028 5029 5027 +9687 5028 1822 5029 +9688 5027 5029 1912 +9689 1935 5022 5028 +9690 5022 4494 5028 +9691 5022 1169 4494 +9692 5028 4494 1822 +9693 1912 5029 4908 +9694 5029 4490 4908 +9695 5029 1822 4490 +9696 4908 4490 985 +9697 92 4321 3708 +9698 4321 5030 3708 +9699 4321 1785 5030 +9700 3708 5030 1641 +9701 1785 5031 5030 +9702 5031 5032 5030 +9703 5031 1936 5032 +9704 5030 5032 1641 +9705 1785 4320 5031 +9706 4320 5033 5031 +9707 4320 1162 5033 +9708 5031 5033 1936 +9709 1641 5032 3712 +9710 5032 5034 3712 +9711 5032 1936 5034 +9712 3712 5034 1131 +9713 1162 5035 5033 +9714 5035 5036 5033 +9715 5035 1937 5036 +9716 5033 5036 1936 +9717 1937 5037 5036 +9718 5037 5038 5036 +9719 5037 1938 5038 +9720 5036 5038 1936 +9721 1937 5039 5037 +9722 5039 5040 5037 +9723 5039 1186 5040 +9724 5037 5040 1938 +9725 1936 5038 5034 +9726 5038 5041 5034 +9727 5038 1938 5041 +9728 5034 5041 1131 +9729 1162 4316 5035 +9730 4316 5042 5035 +9731 4316 1784 5042 +9732 5035 5042 1937 +9733 1784 5043 5042 +9734 5043 5044 5042 +9735 5043 1929 5044 +9736 5042 5044 1937 +9737 1784 4315 5043 +9738 4315 4990 5043 +9739 4315 1002 4990 +9740 5043 4990 1929 +9741 1937 5044 5039 +9742 5044 4986 5039 +9743 5044 1929 4986 +9744 5039 4986 1186 +9745 1131 5041 3722 +9746 5041 5045 3722 +9747 5041 1938 5045 +9748 3722 5045 1645 +9749 1938 5046 5045 +9750 5046 5047 5045 +9751 5046 1925 5047 +9752 5045 5047 1645 +9753 1938 5040 5046 +9754 5040 4976 5046 +9755 5040 1186 4976 +9756 5046 4976 1925 +9757 1645 5047 3726 +9758 5047 4972 3726 +9759 5047 1925 4972 +9760 3726 4972 991 +9761 997 5048 2473 +9762 5048 5049 2473 +9763 5048 1939 5049 +9764 2473 5049 1325 +9765 1939 5050 5049 +9766 5050 5051 5049 +9767 5050 1940 5051 +9768 5049 5051 1325 +9769 1939 5052 5050 +9770 5052 5053 5050 +9771 5052 1187 5053 +9772 5050 5053 1940 +9773 1325 5051 2474 +9774 5051 5054 2474 +9775 5051 1940 5054 +9776 2474 5054 1048 +9777 1187 5055 5053 +9778 5055 5056 5053 +9779 5055 1941 5056 +9780 5053 5056 1940 +9781 1941 5057 5056 +9782 5057 5058 5056 +9783 5057 1942 5058 +9784 5056 5058 1940 +9785 1941 5059 5057 +9786 5059 5060 5057 +9787 5059 1178 5060 +9788 5057 5060 1942 +9789 1940 5058 5054 +9790 5058 5061 5054 +9791 5058 1942 5061 +9792 5054 5061 1048 +9793 1187 5062 5055 +9794 5062 5063 5055 +9795 5062 1943 5063 +9796 5055 5063 1941 +9797 1943 5064 5063 +9798 5064 5065 5063 +9799 5064 1871 5065 +9800 5063 5065 1941 +9801 1943 5066 5064 +9802 5066 4706 5064 +9803 5066 1006 4706 +9804 5064 4706 1871 +9805 1941 5065 5059 +9806 5065 4702 5059 +9807 5065 1871 4702 +9808 5059 4702 1178 +9809 1048 5061 2478 +9810 5061 5067 2478 +9811 5061 1942 5067 +9812 2478 5067 1326 +9813 1942 5068 5067 +9814 5068 5069 5067 +9815 5068 1867 5069 +9816 5067 5069 1326 +9817 1942 5060 5068 +9818 5060 4692 5068 +9819 5060 1178 4692 +9820 5068 4692 1867 +9821 1326 5069 2479 +9822 5069 4688 2479 +9823 5069 1867 4688 +9824 2479 4688 972 +9825 994 2590 4267 +9826 2590 5070 4267 +9827 2590 1357 5070 +9828 4267 5070 1772 +9829 1357 5071 5070 +9830 5071 5072 5070 +9831 5071 1944 5072 +9832 5070 5072 1772 +9833 1357 2589 5071 +9834 2589 5073 5071 +9835 2589 1057 5073 +9836 5071 5073 1944 +9837 1772 5072 4268 +9838 5072 5074 4268 +9839 5072 1944 5074 +9840 4268 5074 1159 +9841 1057 5075 5073 +9842 5075 5076 5073 +9843 5075 1945 5076 +9844 5073 5076 1944 +9845 1945 5077 5076 +9846 5077 5078 5076 +9847 5077 1946 5078 +9848 5076 5078 1944 +9849 1945 5079 5077 +9850 5079 5080 5077 +9851 5079 1117 5080 +9852 5077 5080 1946 +9853 1944 5078 5074 +9854 5078 5081 5074 +9855 5078 1946 5081 +9856 5074 5081 1159 +9857 1057 2584 5075 +9858 2584 5082 5075 +9859 2584 1356 5082 +9860 5075 5082 1945 +9861 1356 5083 5082 +9862 5083 5084 5082 +9863 5083 1579 5084 +9864 5082 5084 1945 +9865 1356 2583 5083 +9866 2583 3438 5083 +9867 2583 1007 3438 +9868 5083 3438 1579 +9869 1945 5084 5079 +9870 5084 3434 5079 +9871 5084 1579 3434 +9872 5079 3434 1117 +9873 1159 5081 4272 +9874 5081 5085 4272 +9875 5081 1946 5085 +9876 4272 5085 1773 +9877 1946 5086 5085 +9878 5086 5087 5085 +9879 5086 1574 5087 +9880 5085 5087 1773 +9881 1946 5080 5086 +9882 5080 3423 5086 +9883 5080 1117 3423 +9884 5086 3423 1574 +9885 1773 5087 4273 +9886 5087 3418 4273 +9887 5087 1574 3418 +9888 4273 3418 973 +9889 269 4537 3900 +9890 4537 5088 3900 +9891 4537 1833 5088 +9892 3900 5088 1689 +9893 1833 5089 5088 +9894 5089 5090 5088 +9895 5089 1947 5090 +9896 5088 5090 1689 +9897 1833 4536 5089 +9898 4536 5091 5089 +9899 4536 1171 5091 +9900 5089 5091 1947 +9901 1689 5090 3904 +9902 5090 5092 3904 +9903 5090 1947 5092 +9904 3904 5092 1143 +9905 1171 5093 5091 +9906 5093 5094 5091 +9907 5093 1948 5094 +9908 5091 5094 1947 +9909 1948 5095 5094 +9910 5095 5096 5094 +9911 5095 1949 5096 +9912 5094 5096 1947 +9913 1948 5097 5095 +9914 5097 5098 5095 +9915 5097 1187 5098 +9916 5095 5098 1949 +9917 1947 5096 5092 +9918 5096 5099 5092 +9919 5096 1949 5099 +9920 5092 5099 1143 +9921 1171 4532 5093 +9922 4532 5100 5093 +9923 4532 1832 5100 +9924 5093 5100 1948 +9925 1832 5101 5100 +9926 5101 5102 5100 +9927 5101 1943 5102 +9928 5100 5102 1948 +9929 1832 4531 5101 +9930 4531 5066 5101 +9931 4531 1006 5066 +9932 5101 5066 1943 +9933 1948 5102 5097 +9934 5102 5062 5097 +9935 5102 1943 5062 +9936 5097 5062 1187 +9937 1143 5099 3914 +9938 5099 5103 3914 +9939 5099 1949 5103 +9940 3914 5103 1693 +9941 1949 5104 5103 +9942 5104 5105 5103 +9943 5104 1939 5105 +9944 5103 5105 1693 +9945 1949 5098 5104 +9946 5098 5052 5104 +9947 5098 1187 5052 +9948 5104 5052 1939 +9949 1693 5105 3918 +9950 5105 5048 3918 +9951 5105 1939 5048 +9952 3918 5048 997 +2 5 2 512 +9953 440 5155 5157 +9954 5155 5156 5157 +9955 5155 5115 5156 +9956 5157 5156 5117 +9957 5115 5158 5156 +9958 5158 5159 5156 +9959 5158 5116 5159 +9960 5156 5159 5117 +9961 5115 5160 5158 +9962 5160 5161 5158 +9963 5160 5107 5161 +9964 5158 5161 5116 +9965 5117 5159 5163 +9966 5159 5162 5163 +9967 5159 5116 5162 +9968 5163 5162 5109 +9969 5107 5164 5161 +9970 5164 5165 5161 +9971 5164 5118 5165 +9972 5161 5165 5116 +9973 5118 5166 5165 +9974 5166 5167 5165 +9975 5166 5119 5167 +9976 5165 5167 5116 +9977 5118 5168 5166 +9978 5168 5169 5166 +9979 5168 5108 5169 +9980 5166 5169 5119 +9981 5116 5167 5162 +9982 5167 5170 5162 +9983 5167 5119 5170 +9984 5162 5170 5109 +9985 5107 5171 5164 +9986 5171 5172 5164 +9987 5171 5120 5172 +9988 5164 5172 5118 +9989 5120 5173 5172 +9990 5173 5174 5172 +9991 5173 5121 5174 +9992 5172 5174 5118 +9993 5120 5175 5173 +9994 5175 5176 5173 +9995 5175 5106 5176 +9996 5173 5176 5121 +9997 5118 5174 5168 +9998 5174 5177 5168 +9999 5174 5121 5177 +10000 5168 5177 5108 +10001 5109 5170 5179 +10002 5170 5178 5179 +10003 5170 5119 5178 +10004 5179 5178 5123 +10005 5119 5180 5178 +10006 5180 5181 5178 +10007 5180 5122 5181 +10008 5178 5181 5123 +10009 5119 5169 5180 +10010 5169 5182 5180 +10011 5169 5108 5182 +10012 5180 5182 5122 +10013 5123 5181 5184 +10014 5181 5183 5184 +10015 5181 5122 5183 +10016 5184 5183 46 +10017 22 5185 5187 +10018 5185 5186 5187 +10019 5185 5124 5186 +10020 5187 5186 5126 +10021 5124 5188 5186 +10022 5188 5189 5186 +10023 5188 5125 5189 +10024 5186 5189 5126 +10025 5124 5190 5188 +10026 5190 5191 5188 +10027 5190 5110 5191 +10028 5188 5191 5125 +10029 5126 5189 5193 +10030 5189 5192 5193 +10031 5189 5125 5192 +10032 5193 5192 5112 +10033 5110 5194 5191 +10034 5194 5195 5191 +10035 5194 5127 5195 +10036 5191 5195 5125 +10037 5127 5196 5195 +10038 5196 5197 5195 +10039 5196 5128 5197 +10040 5195 5197 5125 +10041 5127 5198 5196 +10042 5198 5199 5196 +10043 5198 5111 5199 +10044 5196 5199 5128 +10045 5125 5197 5192 +10046 5197 5200 5192 +10047 5197 5128 5200 +10048 5192 5200 5112 +10049 5110 5201 5194 +10050 5201 5202 5194 +10051 5201 5129 5202 +10052 5194 5202 5127 +10053 5129 5203 5202 +10054 5203 5204 5202 +10055 5203 5130 5204 +10056 5202 5204 5127 +10057 5129 5205 5203 +10058 5205 5206 5203 +10059 5205 5106 5206 +10060 5203 5206 5130 +10061 5127 5204 5198 +10062 5204 5207 5198 +10063 5204 5130 5207 +10064 5198 5207 5111 +10065 5112 5200 5209 +10066 5200 5208 5209 +10067 5200 5128 5208 +10068 5209 5208 5132 +10069 5128 5210 5208 +10070 5210 5211 5208 +10071 5210 5131 5211 +10072 5208 5211 5132 +10073 5128 5199 5210 +10074 5199 5212 5210 +10075 5199 5111 5212 +10076 5210 5212 5131 +10077 5132 5211 5214 +10078 5211 5213 5214 +10079 5211 5131 5213 +10080 5214 5213 448 +10081 46 5183 45 +10082 5183 5215 45 +10083 5183 5122 5215 +10084 45 5215 44 +10085 5122 5216 5215 +10086 5216 5217 5215 +10087 5216 5133 5217 +10088 5215 5217 44 +10089 5122 5182 5216 +10090 5182 5218 5216 +10091 5182 5108 5218 +10092 5216 5218 5133 +10093 44 5217 43 +10094 5217 5219 43 +10095 5217 5133 5219 +10096 43 5219 42 +10097 5108 5220 5218 +10098 5220 5221 5218 +10099 5220 5134 5221 +10100 5218 5221 5133 +10101 5134 5222 5221 +10102 5222 5223 5221 +10103 5222 5135 5223 +10104 5221 5223 5133 +10105 5134 5224 5222 +10106 5224 5225 5222 +10107 5224 5113 5225 +10108 5222 5225 5135 +10109 5133 5223 5219 +10110 5223 5226 5219 +10111 5223 5135 5226 +10112 5219 5226 42 +10113 5108 5177 5220 +10114 5177 5227 5220 +10115 5177 5121 5227 +10116 5220 5227 5134 +10117 5121 5228 5227 +10118 5228 5229 5227 +10119 5228 5136 5229 +10120 5227 5229 5134 +10121 5121 5176 5228 +10122 5176 5230 5228 +10123 5176 5106 5230 +10124 5228 5230 5136 +10125 5134 5229 5224 +10126 5229 5231 5224 +10127 5229 5136 5231 +10128 5224 5231 5113 +10129 42 5226 41 +10130 5226 5232 41 +10131 5226 5135 5232 +10132 41 5232 40 +10133 5135 5233 5232 +10134 5233 5234 5232 +10135 5233 5137 5234 +10136 5232 5234 40 +10137 5135 5225 5233 +10138 5225 5235 5233 +10139 5225 5113 5235 +10140 5233 5235 5137 +10141 40 5234 39 +10142 5234 5236 39 +10143 5234 5137 5236 +10144 39 5236 38 +10145 30 5237 29 +10146 5237 5238 29 +10147 5237 5138 5238 +10148 29 5238 28 +10149 5138 5239 5238 +10150 5239 5240 5238 +10151 5239 5139 5240 +10152 5238 5240 28 +10153 5138 5241 5239 +10154 5241 5242 5239 +10155 5241 5114 5242 +10156 5239 5242 5139 +10157 28 5240 27 +10158 5240 5243 27 +10159 5240 5139 5243 +10160 27 5243 26 +10161 5114 5244 5242 +10162 5244 5245 5242 +10163 5244 5140 5245 +10164 5242 5245 5139 +10165 5140 5246 5245 +10166 5246 5247 5245 +10167 5246 5141 5247 +10168 5245 5247 5139 +10169 5140 5248 5246 +10170 5248 5249 5246 +10171 5248 5110 5249 +10172 5246 5249 5141 +10173 5139 5247 5243 +10174 5247 5250 5243 +10175 5247 5141 5250 +10176 5243 5250 26 +10177 5114 5251 5244 +10178 5251 5252 5244 +10179 5251 5142 5252 +10180 5244 5252 5140 +10181 5142 5253 5252 +10182 5253 5254 5252 +10183 5253 5129 5254 +10184 5252 5254 5140 +10185 5142 5255 5253 +10186 5255 5205 5253 +10187 5255 5106 5205 +10188 5253 5205 5129 +10189 5140 5254 5248 +10190 5254 5201 5248 +10191 5254 5129 5201 +10192 5248 5201 5110 +10193 26 5250 25 +10194 5250 5256 25 +10195 5250 5141 5256 +10196 25 5256 24 +10197 5141 5257 5256 +10198 5257 5258 5256 +10199 5257 5124 5258 +10200 5256 5258 24 +10201 5141 5249 5257 +10202 5249 5190 5257 +10203 5249 5110 5190 +10204 5257 5190 5124 +10205 24 5258 23 +10206 5258 5185 23 +10207 5258 5124 5185 +10208 23 5185 22 +10209 2 433 53 +10210 433 5259 53 +10211 433 434 5259 +10212 53 5259 52 +10213 434 5260 5259 +10214 5260 5261 5259 +10215 5260 5143 5261 +10216 5259 5261 52 +10217 434 435 5260 +10218 435 5262 5260 +10219 435 436 5262 +10220 5260 5262 5143 +10221 52 5261 51 +10222 5261 5263 51 +10223 5261 5143 5263 +10224 51 5263 50 +10225 436 5264 5262 +10226 5264 5265 5262 +10227 5264 5144 5265 +10228 5262 5265 5143 +10229 5144 5266 5265 +10230 5266 5267 5265 +10231 5266 5145 5267 +10232 5265 5267 5143 +10233 5144 5268 5266 +10234 5268 5269 5266 +10235 5268 5109 5269 +10236 5266 5269 5145 +10237 5143 5267 5263 +10238 5267 5270 5263 +10239 5267 5145 5270 +10240 5263 5270 50 +10241 436 437 5264 +10242 437 5271 5264 +10243 437 438 5271 +10244 5264 5271 5144 +10245 438 5272 5271 +10246 5272 5273 5271 +10247 5272 5117 5273 +10248 5271 5273 5144 +10249 438 439 5272 +10250 439 5157 5272 +10251 439 440 5157 +10252 5272 5157 5117 +10253 5144 5273 5268 +10254 5273 5163 5268 +10255 5273 5117 5163 +10256 5268 5163 5109 +10257 50 5270 49 +10258 5270 5274 49 +10259 5270 5145 5274 +10260 49 5274 48 +10261 5145 5275 5274 +10262 5275 5276 5274 +10263 5275 5123 5276 +10264 5274 5276 48 +10265 5145 5269 5275 +10266 5269 5179 5275 +10267 5269 5109 5179 +10268 5275 5179 5123 +10269 48 5276 47 +10270 5276 5184 47 +10271 5276 5123 5184 +10272 47 5184 46 +10273 1 15 455 +10274 15 5277 455 +10275 15 16 5277 +10276 455 5277 454 +10277 16 5278 5277 +10278 5278 5279 5277 +10279 5278 5146 5279 +10280 5277 5279 454 +10281 16 17 5278 +10282 17 5280 5278 +10283 17 18 5280 +10284 5278 5280 5146 +10285 454 5279 453 +10286 5279 5281 453 +10287 5279 5146 5281 +10288 453 5281 452 +10289 18 5282 5280 +10290 5282 5283 5280 +10291 5282 5147 5283 +10292 5280 5283 5146 +10293 5147 5284 5283 +10294 5284 5285 5283 +10295 5284 5148 5285 +10296 5283 5285 5146 +10297 5147 5286 5284 +10298 5286 5287 5284 +10299 5286 5112 5287 +10300 5284 5287 5148 +10301 5146 5285 5281 +10302 5285 5288 5281 +10303 5285 5148 5288 +10304 5281 5288 452 +10305 18 19 5282 +10306 19 5289 5282 +10307 19 20 5289 +10308 5282 5289 5147 +10309 20 5290 5289 +10310 5290 5291 5289 +10311 5290 5126 5291 +10312 5289 5291 5147 +10313 20 21 5290 +10314 21 5187 5290 +10315 21 22 5187 +10316 5290 5187 5126 +10317 5147 5291 5286 +10318 5291 5193 5286 +10319 5291 5126 5193 +10320 5286 5193 5112 +10321 452 5288 451 +10322 5288 5292 451 +10323 5288 5148 5292 +10324 451 5292 450 +10325 5148 5293 5292 +10326 5293 5294 5292 +10327 5293 5132 5294 +10328 5292 5294 450 +10329 5148 5287 5293 +10330 5287 5209 5293 +10331 5287 5112 5209 +10332 5293 5209 5132 +10333 450 5294 449 +10334 5294 5214 449 +10335 5294 5132 5214 +10336 449 5214 448 +10337 38 5236 37 +10338 5236 5295 37 +10339 5236 5137 5295 +10340 37 5295 36 +10341 5137 5296 5295 +10342 5296 5297 5295 +10343 5296 5149 5297 +10344 5295 5297 36 +10345 5137 5235 5296 +10346 5235 5298 5296 +10347 5235 5113 5298 +10348 5296 5298 5149 +10349 36 5297 35 +10350 5297 5299 35 +10351 5297 5149 5299 +10352 35 5299 34 +10353 5113 5300 5298 +10354 5300 5301 5298 +10355 5300 5150 5301 +10356 5298 5301 5149 +10357 5150 5302 5301 +10358 5302 5303 5301 +10359 5302 5151 5303 +10360 5301 5303 5149 +10361 5150 5304 5302 +10362 5304 5305 5302 +10363 5304 5114 5305 +10364 5302 5305 5151 +10365 5149 5303 5299 +10366 5303 5306 5299 +10367 5303 5151 5306 +10368 5299 5306 34 +10369 5113 5231 5300 +10370 5231 5307 5300 +10371 5231 5136 5307 +10372 5300 5307 5150 +10373 5136 5308 5307 +10374 5308 5309 5307 +10375 5308 5142 5309 +10376 5307 5309 5150 +10377 5136 5230 5308 +10378 5230 5255 5308 +10379 5230 5106 5255 +10380 5308 5255 5142 +10381 5150 5309 5304 +10382 5309 5251 5304 +10383 5309 5142 5251 +10384 5304 5251 5114 +10385 34 5306 33 +10386 5306 5310 33 +10387 5306 5151 5310 +10388 33 5310 32 +10389 5151 5311 5310 +10390 5311 5312 5310 +10391 5311 5138 5312 +10392 5310 5312 32 +10393 5151 5305 5311 +10394 5305 5241 5311 +10395 5305 5114 5241 +10396 5311 5241 5138 +10397 32 5312 31 +10398 5312 5237 31 +10399 5312 5138 5237 +10400 31 5237 30 +10401 448 5213 447 +10402 5213 5313 447 +10403 5213 5131 5313 +10404 447 5313 446 +10405 5131 5314 5313 +10406 5314 5315 5313 +10407 5314 5152 5315 +10408 5313 5315 446 +10409 5131 5212 5314 +10410 5212 5316 5314 +10411 5212 5111 5316 +10412 5314 5316 5152 +10413 446 5315 445 +10414 5315 5317 445 +10415 5315 5152 5317 +10416 445 5317 444 +10417 5111 5318 5316 +10418 5318 5319 5316 +10419 5318 5153 5319 +10420 5316 5319 5152 +10421 5153 5320 5319 +10422 5320 5321 5319 +10423 5320 5154 5321 +10424 5319 5321 5152 +10425 5153 5322 5320 +10426 5322 5323 5320 +10427 5322 5107 5323 +10428 5320 5323 5154 +10429 5152 5321 5317 +10430 5321 5324 5317 +10431 5321 5154 5324 +10432 5317 5324 444 +10433 5111 5207 5318 +10434 5207 5325 5318 +10435 5207 5130 5325 +10436 5318 5325 5153 +10437 5130 5326 5325 +10438 5326 5327 5325 +10439 5326 5120 5327 +10440 5325 5327 5153 +10441 5130 5206 5326 +10442 5206 5175 5326 +10443 5206 5106 5175 +10444 5326 5175 5120 +10445 5153 5327 5322 +10446 5327 5171 5322 +10447 5327 5120 5171 +10448 5322 5171 5107 +10449 444 5324 443 +10450 5324 5328 443 +10451 5324 5154 5328 +10452 443 5328 442 +10453 5154 5329 5328 +10454 5329 5330 5328 +10455 5329 5115 5330 +10456 5328 5330 442 +10457 5154 5323 5329 +10458 5323 5160 5329 +10459 5323 5107 5160 +10460 5329 5160 5115 +10461 442 5330 441 +10462 5330 5155 441 +10463 5330 5115 5155 +10464 441 5155 440 +2 6 2 512 +10465 284 5380 5382 +10466 5380 5381 5382 +10467 5380 5340 5381 +10468 5382 5381 5342 +10469 5340 5383 5381 +10470 5383 5384 5381 +10471 5383 5341 5384 +10472 5381 5384 5342 +10473 5340 5385 5383 +10474 5385 5386 5383 +10475 5385 5332 5386 +10476 5383 5386 5341 +10477 5342 5384 5388 +10478 5384 5387 5388 +10479 5384 5341 5387 +10480 5388 5387 5334 +10481 5332 5389 5386 +10482 5389 5390 5386 +10483 5389 5343 5390 +10484 5386 5390 5341 +10485 5343 5391 5390 +10486 5391 5392 5390 +10487 5391 5344 5392 +10488 5390 5392 5341 +10489 5343 5393 5391 +10490 5393 5394 5391 +10491 5393 5333 5394 +10492 5391 5394 5344 +10493 5341 5392 5387 +10494 5392 5395 5387 +10495 5392 5344 5395 +10496 5387 5395 5334 +10497 5332 5396 5389 +10498 5396 5397 5389 +10499 5396 5345 5397 +10500 5389 5397 5343 +10501 5345 5398 5397 +10502 5398 5399 5397 +10503 5398 5346 5399 +10504 5397 5399 5343 +10505 5345 5400 5398 +10506 5400 5401 5398 +10507 5400 5331 5401 +10508 5398 5401 5346 +10509 5343 5399 5393 +10510 5399 5402 5393 +10511 5399 5346 5402 +10512 5393 5402 5333 +10513 5334 5395 5404 +10514 5395 5403 5404 +10515 5395 5344 5403 +10516 5404 5403 5348 +10517 5344 5405 5403 +10518 5405 5406 5403 +10519 5405 5347 5406 +10520 5403 5406 5348 +10521 5344 5394 5405 +10522 5394 5407 5405 +10523 5394 5333 5407 +10524 5405 5407 5347 +10525 5348 5406 5409 +10526 5406 5408 5409 +10527 5406 5347 5408 +10528 5409 5408 471 +10529 463 5410 5412 +10530 5410 5411 5412 +10531 5410 5349 5411 +10532 5412 5411 5351 +10533 5349 5413 5411 +10534 5413 5414 5411 +10535 5413 5350 5414 +10536 5411 5414 5351 +10537 5349 5415 5413 +10538 5415 5416 5413 +10539 5415 5335 5416 +10540 5413 5416 5350 +10541 5351 5414 5418 +10542 5414 5417 5418 +10543 5414 5350 5417 +10544 5418 5417 5337 +10545 5335 5419 5416 +10546 5419 5420 5416 +10547 5419 5352 5420 +10548 5416 5420 5350 +10549 5352 5421 5420 +10550 5421 5422 5420 +10551 5421 5353 5422 +10552 5420 5422 5350 +10553 5352 5423 5421 +10554 5423 5424 5421 +10555 5423 5336 5424 +10556 5421 5424 5353 +10557 5350 5422 5417 +10558 5422 5425 5417 +10559 5422 5353 5425 +10560 5417 5425 5337 +10561 5335 5426 5419 +10562 5426 5427 5419 +10563 5426 5354 5427 +10564 5419 5427 5352 +10565 5354 5428 5427 +10566 5428 5429 5427 +10567 5428 5355 5429 +10568 5427 5429 5352 +10569 5354 5430 5428 +10570 5430 5431 5428 +10571 5430 5331 5431 +10572 5428 5431 5355 +10573 5352 5429 5423 +10574 5429 5432 5423 +10575 5429 5355 5432 +10576 5423 5432 5336 +10577 5337 5425 5434 +10578 5425 5433 5434 +10579 5425 5353 5433 +10580 5434 5433 5357 +10581 5353 5435 5433 +10582 5435 5436 5433 +10583 5435 5356 5436 +10584 5433 5436 5357 +10585 5353 5424 5435 +10586 5424 5437 5435 +10587 5424 5336 5437 +10588 5435 5437 5356 +10589 5357 5436 5439 +10590 5436 5438 5439 +10591 5436 5356 5438 +10592 5439 5438 308 +10593 308 5438 307 +10594 5438 5440 307 +10595 5438 5356 5440 +10596 307 5440 306 +10597 5356 5441 5440 +10598 5441 5442 5440 +10599 5441 5358 5442 +10600 5440 5442 306 +10601 5356 5437 5441 +10602 5437 5443 5441 +10603 5437 5336 5443 +10604 5441 5443 5358 +10605 306 5442 305 +10606 5442 5444 305 +10607 5442 5358 5444 +10608 305 5444 304 +10609 5336 5445 5443 +10610 5445 5446 5443 +10611 5445 5359 5446 +10612 5443 5446 5358 +10613 5359 5447 5446 +10614 5447 5448 5446 +10615 5447 5360 5448 +10616 5446 5448 5358 +10617 5359 5449 5447 +10618 5449 5450 5447 +10619 5449 5338 5450 +10620 5447 5450 5360 +10621 5358 5448 5444 +10622 5448 5451 5444 +10623 5448 5360 5451 +10624 5444 5451 304 +10625 5336 5432 5445 +10626 5432 5452 5445 +10627 5432 5355 5452 +10628 5445 5452 5359 +10629 5355 5453 5452 +10630 5453 5454 5452 +10631 5453 5361 5454 +10632 5452 5454 5359 +10633 5355 5431 5453 +10634 5431 5455 5453 +10635 5431 5331 5455 +10636 5453 5455 5361 +10637 5359 5454 5449 +10638 5454 5456 5449 +10639 5454 5361 5456 +10640 5449 5456 5338 +10641 304 5451 303 +10642 5451 5457 303 +10643 5451 5360 5457 +10644 303 5457 302 +10645 5360 5458 5457 +10646 5458 5459 5457 +10647 5458 5362 5459 +10648 5457 5459 302 +10649 5360 5450 5458 +10650 5450 5460 5458 +10651 5450 5338 5460 +10652 5458 5460 5362 +10653 302 5459 301 +10654 5459 5461 301 +10655 5459 5362 5461 +10656 301 5461 300 +10657 292 5462 291 +10658 5462 5463 291 +10659 5462 5363 5463 +10660 291 5463 290 +10661 5363 5464 5463 +10662 5464 5465 5463 +10663 5464 5364 5465 +10664 5463 5465 290 +10665 5363 5466 5464 +10666 5466 5467 5464 +10667 5466 5339 5467 +10668 5464 5467 5364 +10669 290 5465 289 +10670 5465 5468 289 +10671 5465 5364 5468 +10672 289 5468 288 +10673 5339 5469 5467 +10674 5469 5470 5467 +10675 5469 5365 5470 +10676 5467 5470 5364 +10677 5365 5471 5470 +10678 5471 5472 5470 +10679 5471 5366 5472 +10680 5470 5472 5364 +10681 5365 5473 5471 +10682 5473 5474 5471 +10683 5473 5332 5474 +10684 5471 5474 5366 +10685 5364 5472 5468 +10686 5472 5475 5468 +10687 5472 5366 5475 +10688 5468 5475 288 +10689 5339 5476 5469 +10690 5476 5477 5469 +10691 5476 5367 5477 +10692 5469 5477 5365 +10693 5367 5478 5477 +10694 5478 5479 5477 +10695 5478 5345 5479 +10696 5477 5479 5365 +10697 5367 5480 5478 +10698 5480 5400 5478 +10699 5480 5331 5400 +10700 5478 5400 5345 +10701 5365 5479 5473 +10702 5479 5396 5473 +10703 5479 5345 5396 +10704 5473 5396 5332 +10705 288 5475 287 +10706 5475 5481 287 +10707 5475 5366 5481 +10708 287 5481 286 +10709 5366 5482 5481 +10710 5482 5483 5481 +10711 5482 5340 5483 +10712 5481 5483 286 +10713 5366 5474 5482 +10714 5474 5385 5482 +10715 5474 5332 5385 +10716 5482 5385 5340 +10717 286 5483 285 +10718 5483 5380 285 +10719 5483 5340 5380 +10720 285 5380 284 +10721 308 309 5439 +10722 309 5484 5439 +10723 309 310 5484 +10724 5439 5484 5357 +10725 310 5485 5484 +10726 5485 5486 5484 +10727 5485 5368 5486 +10728 5484 5486 5357 +10729 310 311 5485 +10730 311 5487 5485 +10731 311 312 5487 +10732 5485 5487 5368 +10733 5357 5486 5434 +10734 5486 5488 5434 +10735 5486 5368 5488 +10736 5434 5488 5337 +10737 312 5489 5487 +10738 5489 5490 5487 +10739 5489 5369 5490 +10740 5487 5490 5368 +10741 5369 5491 5490 +10742 5491 5492 5490 +10743 5491 5370 5492 +10744 5490 5492 5368 +10745 5369 5493 5491 +10746 5493 5494 5491 +10747 5493 459 5494 +10748 5491 5494 5370 +10749 5368 5492 5488 +10750 5492 5495 5488 +10751 5492 5370 5495 +10752 5488 5495 5337 +10753 312 313 5489 +10754 313 5496 5489 +10755 313 314 5496 +10756 5489 5496 5369 +10757 314 5497 5496 +10758 5497 5498 5496 +10759 5497 457 5498 +10760 5496 5498 5369 +10761 314 315 5497 +10762 315 456 5497 +10763 315 12 456 +10764 5497 456 457 +10765 5369 5498 5493 +10766 5498 458 5493 +10767 5498 457 458 +10768 5493 458 459 +10769 5337 5495 5418 +10770 5495 5499 5418 +10771 5495 5370 5499 +10772 5418 5499 5351 +10773 5370 5500 5499 +10774 5500 5501 5499 +10775 5500 461 5501 +10776 5499 5501 5351 +10777 5370 5494 5500 +10778 5494 460 5500 +10779 5494 459 460 +10780 5500 460 461 +10781 5351 5501 5412 +10782 5501 462 5412 +10783 5501 461 462 +10784 5412 462 463 +10785 11 277 478 +10786 277 5502 478 +10787 277 278 5502 +10788 478 5502 477 +10789 278 5503 5502 +10790 5503 5504 5502 +10791 5503 5371 5504 +10792 5502 5504 477 +10793 278 279 5503 +10794 279 5505 5503 +10795 279 280 5505 +10796 5503 5505 5371 +10797 477 5504 476 +10798 5504 5506 476 +10799 5504 5371 5506 +10800 476 5506 475 +10801 280 5507 5505 +10802 5507 5508 5505 +10803 5507 5372 5508 +10804 5505 5508 5371 +10805 5372 5509 5508 +10806 5509 5510 5508 +10807 5509 5373 5510 +10808 5508 5510 5371 +10809 5372 5511 5509 +10810 5511 5512 5509 +10811 5511 5334 5512 +10812 5509 5512 5373 +10813 5371 5510 5506 +10814 5510 5513 5506 +10815 5510 5373 5513 +10816 5506 5513 475 +10817 280 281 5507 +10818 281 5514 5507 +10819 281 282 5514 +10820 5507 5514 5372 +10821 282 5515 5514 +10822 5515 5516 5514 +10823 5515 5342 5516 +10824 5514 5516 5372 +10825 282 283 5515 +10826 283 5382 5515 +10827 283 284 5382 +10828 5515 5382 5342 +10829 5372 5516 5511 +10830 5516 5388 5511 +10831 5516 5342 5388 +10832 5511 5388 5334 +10833 475 5513 474 +10834 5513 5517 474 +10835 5513 5373 5517 +10836 474 5517 473 +10837 5373 5518 5517 +10838 5518 5519 5517 +10839 5518 5348 5519 +10840 5517 5519 473 +10841 5373 5512 5518 +10842 5512 5404 5518 +10843 5512 5334 5404 +10844 5518 5404 5348 +10845 473 5519 472 +10846 5519 5409 472 +10847 5519 5348 5409 +10848 472 5409 471 +10849 300 5461 299 +10850 5461 5520 299 +10851 5461 5362 5520 +10852 299 5520 298 +10853 5362 5521 5520 +10854 5521 5522 5520 +10855 5521 5374 5522 +10856 5520 5522 298 +10857 5362 5460 5521 +10858 5460 5523 5521 +10859 5460 5338 5523 +10860 5521 5523 5374 +10861 298 5522 297 +10862 5522 5524 297 +10863 5522 5374 5524 +10864 297 5524 296 +10865 5338 5525 5523 +10866 5525 5526 5523 +10867 5525 5375 5526 +10868 5523 5526 5374 +10869 5375 5527 5526 +10870 5527 5528 5526 +10871 5527 5376 5528 +10872 5526 5528 5374 +10873 5375 5529 5527 +10874 5529 5530 5527 +10875 5529 5339 5530 +10876 5527 5530 5376 +10877 5374 5528 5524 +10878 5528 5531 5524 +10879 5528 5376 5531 +10880 5524 5531 296 +10881 5338 5456 5525 +10882 5456 5532 5525 +10883 5456 5361 5532 +10884 5525 5532 5375 +10885 5361 5533 5532 +10886 5533 5534 5532 +10887 5533 5367 5534 +10888 5532 5534 5375 +10889 5361 5455 5533 +10890 5455 5480 5533 +10891 5455 5331 5480 +10892 5533 5480 5367 +10893 5375 5534 5529 +10894 5534 5476 5529 +10895 5534 5367 5476 +10896 5529 5476 5339 +10897 296 5531 295 +10898 5531 5535 295 +10899 5531 5376 5535 +10900 295 5535 294 +10901 5376 5536 5535 +10902 5536 5537 5535 +10903 5536 5363 5537 +10904 5535 5537 294 +10905 5376 5530 5536 +10906 5530 5466 5536 +10907 5530 5339 5466 +10908 5536 5466 5363 +10909 294 5537 293 +10910 5537 5462 293 +10911 5537 5363 5462 +10912 293 5462 292 +10913 471 5408 470 +10914 5408 5538 470 +10915 5408 5347 5538 +10916 470 5538 469 +10917 5347 5539 5538 +10918 5539 5540 5538 +10919 5539 5377 5540 +10920 5538 5540 469 +10921 5347 5407 5539 +10922 5407 5541 5539 +10923 5407 5333 5541 +10924 5539 5541 5377 +10925 469 5540 468 +10926 5540 5542 468 +10927 5540 5377 5542 +10928 468 5542 467 +10929 5333 5543 5541 +10930 5543 5544 5541 +10931 5543 5378 5544 +10932 5541 5544 5377 +10933 5378 5545 5544 +10934 5545 5546 5544 +10935 5545 5379 5546 +10936 5544 5546 5377 +10937 5378 5547 5545 +10938 5547 5548 5545 +10939 5547 5335 5548 +10940 5545 5548 5379 +10941 5377 5546 5542 +10942 5546 5549 5542 +10943 5546 5379 5549 +10944 5542 5549 467 +10945 5333 5402 5543 +10946 5402 5550 5543 +10947 5402 5346 5550 +10948 5543 5550 5378 +10949 5346 5551 5550 +10950 5551 5552 5550 +10951 5551 5354 5552 +10952 5550 5552 5378 +10953 5346 5401 5551 +10954 5401 5430 5551 +10955 5401 5331 5430 +10956 5551 5430 5354 +10957 5378 5552 5547 +10958 5552 5426 5547 +10959 5552 5354 5426 +10960 5547 5426 5335 +10961 467 5549 466 +10962 5549 5553 466 +10963 5549 5379 5553 +10964 466 5553 465 +10965 5379 5554 5553 +10966 5554 5555 5553 +10967 5554 5349 5555 +10968 5553 5555 465 +10969 5379 5548 5554 +10970 5548 5415 5554 +10971 5548 5335 5415 +10972 5554 5415 5349 +10973 465 5555 464 +10974 5555 5410 464 +10975 5555 5349 5410 +10976 464 5410 463 +2 7 2 704 +10977 115 5627 114 +10978 5627 5628 114 +10979 5627 5570 5628 +10980 114 5628 113 +10981 5570 5629 5628 +10982 5629 5630 5628 +10983 5629 5571 5630 +10984 5628 5630 113 +10985 5570 5631 5629 +10986 5631 5632 5629 +10987 5631 5558 5632 +10988 5629 5632 5571 +10989 113 5630 112 +10990 5630 5633 112 +10991 5630 5571 5633 +10992 112 5633 111 +10993 5558 5634 5632 +10994 5634 5635 5632 +10995 5634 5572 5635 +10996 5632 5635 5571 +10997 5572 5636 5635 +10998 5636 5637 5635 +10999 5636 5573 5637 +11000 5635 5637 5571 +11001 5572 5638 5636 +11002 5638 5639 5636 +11003 5638 5559 5639 +11004 5636 5639 5573 +11005 5571 5637 5633 +11006 5637 5640 5633 +11007 5637 5573 5640 +11008 5633 5640 111 +11009 5558 5641 5634 +11010 5641 5642 5634 +11011 5641 5574 5642 +11012 5634 5642 5572 +11013 5574 5643 5642 +11014 5643 5644 5642 +11015 5643 5575 5644 +11016 5642 5644 5572 +11017 5574 5645 5643 +11018 5645 5646 5643 +11019 5645 5556 5646 +11020 5643 5646 5575 +11021 5572 5644 5638 +11022 5644 5647 5638 +11023 5644 5575 5647 +11024 5638 5647 5559 +11025 111 5640 110 +11026 5640 5648 110 +11027 5640 5573 5648 +11028 110 5648 109 +11029 5573 5649 5648 +11030 5649 5650 5648 +11031 5649 5576 5650 +11032 5648 5650 109 +11033 5573 5639 5649 +11034 5639 5651 5649 +11035 5639 5559 5651 +11036 5649 5651 5576 +11037 109 5650 108 +11038 5650 5652 108 +11039 5650 5576 5652 +11040 108 5652 107 +11041 107 5653 106 +11042 5653 5654 106 +11043 5653 5577 5654 +11044 106 5654 105 +11045 5577 5655 5654 +11046 5655 5656 5654 +11047 5655 5578 5656 +11048 5654 5656 105 +11049 5577 5657 5655 +11050 5657 5658 5655 +11051 5657 5560 5658 +11052 5655 5658 5578 +11053 105 5656 104 +11054 5656 5659 104 +11055 5656 5578 5659 +11056 104 5659 103 +11057 5560 5660 5658 +11058 5660 5661 5658 +11059 5660 5579 5661 +11060 5658 5661 5578 +11061 5579 5662 5661 +11062 5662 5663 5661 +11063 5662 5580 5663 +11064 5661 5663 5578 +11065 5579 5664 5662 +11066 5664 5665 5662 +11067 5664 498 5665 +11068 5662 5665 5580 +11069 5578 5663 5659 +11070 5663 5666 5659 +11071 5663 5580 5666 +11072 5659 5666 103 +11073 5560 5667 5660 +11074 5667 5668 5660 +11075 5667 5581 5668 +11076 5660 5668 5579 +11077 5581 5669 5668 +11078 5669 5670 5668 +11079 5669 496 5670 +11080 5668 5670 5579 +11081 5581 5671 5669 +11082 5671 495 5669 +11083 5671 494 495 +11084 5669 495 496 +11085 5579 5670 5664 +11086 5670 497 5664 +11087 5670 496 497 +11088 5664 497 498 +11089 103 5666 102 +11090 5666 5672 102 +11091 5666 5580 5672 +11092 102 5672 101 +11093 5580 5673 5672 +11094 5673 5674 5672 +11095 5673 500 5674 +11096 5672 5674 101 +11097 5580 5665 5673 +11098 5665 499 5673 +11099 5665 498 499 +11100 5673 499 500 +11101 101 5674 100 +11102 5674 501 100 +11103 5674 500 501 +11104 100 501 4 +11105 107 5652 5653 +11106 5652 5675 5653 +11107 5652 5576 5675 +11108 5653 5675 5577 +11109 5576 5676 5675 +11110 5676 5677 5675 +11111 5676 5582 5677 +11112 5675 5677 5577 +11113 5576 5651 5676 +11114 5651 5678 5676 +11115 5651 5559 5678 +11116 5676 5678 5582 +11117 5577 5677 5657 +11118 5677 5679 5657 +11119 5677 5582 5679 +11120 5657 5679 5560 +11121 5559 5680 5678 +11122 5680 5681 5678 +11123 5680 5583 5681 +11124 5678 5681 5582 +11125 5583 5682 5681 +11126 5682 5683 5681 +11127 5682 5584 5683 +11128 5681 5683 5582 +11129 5583 5684 5682 +11130 5684 5685 5682 +11131 5684 5561 5685 +11132 5682 5685 5584 +11133 5582 5683 5679 +11134 5683 5686 5679 +11135 5683 5584 5686 +11136 5679 5686 5560 +11137 5559 5647 5680 +11138 5647 5687 5680 +11139 5647 5575 5687 +11140 5680 5687 5583 +11141 5575 5688 5687 +11142 5688 5689 5687 +11143 5688 5585 5689 +11144 5687 5689 5583 +11145 5575 5646 5688 +11146 5646 5690 5688 +11147 5646 5556 5690 +11148 5688 5690 5585 +11149 5583 5689 5684 +11150 5689 5691 5684 +11151 5689 5585 5691 +11152 5684 5691 5561 +11153 5560 5686 5667 +11154 5686 5692 5667 +11155 5686 5584 5692 +11156 5667 5692 5581 +11157 5584 5693 5692 +11158 5693 5694 5692 +11159 5693 5586 5694 +11160 5692 5694 5581 +11161 5584 5685 5693 +11162 5685 5695 5693 +11163 5685 5561 5695 +11164 5693 5695 5586 +11165 5581 5694 5671 +11166 5694 5696 5671 +11167 5694 5586 5696 +11168 5671 5696 494 +11169 130 5697 129 +11170 5697 5698 129 +11171 5697 5587 5698 +11172 129 5698 128 +11173 5587 5699 5698 +11174 5699 5700 5698 +11175 5699 5588 5700 +11176 5698 5700 128 +11177 5587 5701 5699 +11178 5701 5702 5699 +11179 5701 5562 5702 +11180 5699 5702 5588 +11181 128 5700 127 +11182 5700 5703 127 +11183 5700 5588 5703 +11184 127 5703 126 +11185 5562 5704 5702 +11186 5704 5705 5702 +11187 5704 5589 5705 +11188 5702 5705 5588 +11189 5589 5706 5705 +11190 5706 5707 5705 +11191 5706 5590 5707 +11192 5705 5707 5588 +11193 5589 5708 5706 +11194 5708 5709 5706 +11195 5708 5563 5709 +11196 5706 5709 5590 +11197 5588 5707 5703 +11198 5707 5710 5703 +11199 5707 5590 5710 +11200 5703 5710 126 +11201 5562 5711 5704 +11202 5711 5712 5704 +11203 5711 5591 5712 +11204 5704 5712 5589 +11205 5591 5713 5712 +11206 5713 5714 5712 +11207 5713 5592 5714 +11208 5712 5714 5589 +11209 5591 5715 5713 +11210 5715 5716 5713 +11211 5715 5557 5716 +11212 5713 5716 5592 +11213 5589 5714 5708 +11214 5714 5717 5708 +11215 5714 5592 5717 +11216 5708 5717 5563 +11217 126 5710 125 +11218 5710 5718 125 +11219 5710 5590 5718 +11220 125 5718 124 +11221 5590 5719 5718 +11222 5719 5720 5718 +11223 5719 5593 5720 +11224 5718 5720 124 +11225 5590 5709 5719 +11226 5709 5721 5719 +11227 5709 5563 5721 +11228 5719 5721 5593 +11229 124 5720 123 +11230 5720 5722 123 +11231 5720 5593 5722 +11232 123 5722 5 +11233 6 5723 145 +11234 5723 5724 145 +11235 5723 5594 5724 +11236 145 5724 144 +11237 5594 5725 5724 +11238 5725 5726 5724 +11239 5725 5595 5726 +11240 5724 5726 144 +11241 5594 5727 5725 +11242 5727 5728 5725 +11243 5727 5564 5728 +11244 5725 5728 5595 +11245 144 5726 143 +11246 5726 5729 143 +11247 5726 5595 5729 +11248 143 5729 142 +11249 5564 5730 5728 +11250 5730 5731 5728 +11251 5730 5596 5731 +11252 5728 5731 5595 +11253 5596 5732 5731 +11254 5732 5733 5731 +11255 5732 5597 5733 +11256 5731 5733 5595 +11257 5596 5734 5732 +11258 5734 5735 5732 +11259 5734 5565 5735 +11260 5732 5735 5597 +11261 5595 5733 5729 +11262 5733 5736 5729 +11263 5733 5597 5736 +11264 5729 5736 142 +11265 5564 5737 5730 +11266 5737 5738 5730 +11267 5737 5598 5738 +11268 5730 5738 5596 +11269 5598 5739 5738 +11270 5739 5740 5738 +11271 5739 5599 5740 +11272 5738 5740 5596 +11273 5598 5741 5739 +11274 5741 5742 5739 +11275 5741 5557 5742 +11276 5739 5742 5599 +11277 5596 5740 5734 +11278 5740 5743 5734 +11279 5740 5599 5743 +11280 5734 5743 5565 +11281 142 5736 141 +11282 5736 5744 141 +11283 5736 5597 5744 +11284 141 5744 140 +11285 5597 5745 5744 +11286 5745 5746 5744 +11287 5745 5600 5746 +11288 5744 5746 140 +11289 5597 5735 5745 +11290 5735 5747 5745 +11291 5735 5565 5747 +11292 5745 5747 5600 +11293 140 5746 139 +11294 5746 5748 139 +11295 5746 5600 5748 +11296 139 5748 138 +11297 5 5722 5750 +11298 5722 5749 5750 +11299 5722 5593 5749 +11300 5750 5749 5602 +11301 5593 5751 5749 +11302 5751 5752 5749 +11303 5751 5601 5752 +11304 5749 5752 5602 +11305 5593 5721 5751 +11306 5721 5753 5751 +11307 5721 5563 5753 +11308 5751 5753 5601 +11309 5602 5752 5755 +11310 5752 5754 5755 +11311 5752 5601 5754 +11312 5755 5754 5567 +11313 5563 5756 5753 +11314 5756 5757 5753 +11315 5756 5603 5757 +11316 5753 5757 5601 +11317 5603 5758 5757 +11318 5758 5759 5757 +11319 5758 5604 5759 +11320 5757 5759 5601 +11321 5603 5760 5758 +11322 5760 5761 5758 +11323 5760 5566 5761 +11324 5758 5761 5604 +11325 5601 5759 5754 +11326 5759 5762 5754 +11327 5759 5604 5762 +11328 5754 5762 5567 +11329 5563 5717 5756 +11330 5717 5763 5756 +11331 5717 5592 5763 +11332 5756 5763 5603 +11333 5592 5764 5763 +11334 5764 5765 5763 +11335 5764 5605 5765 +11336 5763 5765 5603 +11337 5592 5716 5764 +11338 5716 5766 5764 +11339 5716 5557 5766 +11340 5764 5766 5605 +11341 5603 5765 5760 +11342 5765 5767 5760 +11343 5765 5605 5767 +11344 5760 5767 5566 +11345 5567 5762 5769 +11346 5762 5768 5769 +11347 5762 5604 5768 +11348 5769 5768 5607 +11349 5604 5770 5768 +11350 5770 5771 5768 +11351 5770 5606 5771 +11352 5768 5771 5607 +11353 5604 5761 5770 +11354 5761 5772 5770 +11355 5761 5566 5772 +11356 5770 5772 5606 +11357 5607 5771 5774 +11358 5771 5773 5774 +11359 5771 5606 5773 +11360 5774 5773 5556 +11361 486 5775 485 +11362 5775 5776 485 +11363 5775 5608 5776 +11364 485 5776 484 +11365 5608 5777 5776 +11366 5777 5778 5776 +11367 5777 5609 5778 +11368 5776 5778 484 +11369 5608 5779 5777 +11370 5779 5780 5777 +11371 5779 5568 5780 +11372 5777 5780 5609 +11373 484 5778 483 +11374 5778 5781 483 +11375 5778 5609 5781 +11376 483 5781 482 +11377 5568 5782 5780 +11378 5782 5783 5780 +11379 5782 5610 5783 +11380 5780 5783 5609 +11381 5610 5784 5783 +11382 5784 5785 5783 +11383 5784 5611 5785 +11384 5783 5785 5609 +11385 5610 5786 5784 +11386 5786 5787 5784 +11387 5786 5564 5787 +11388 5784 5787 5611 +11389 5609 5785 5781 +11390 5785 5788 5781 +11391 5785 5611 5788 +11392 5781 5788 482 +11393 5568 5789 5782 +11394 5789 5790 5782 +11395 5789 5612 5790 +11396 5782 5790 5610 +11397 5612 5791 5790 +11398 5791 5792 5790 +11399 5791 5598 5792 +11400 5790 5792 5610 +11401 5612 5793 5791 +11402 5793 5741 5791 +11403 5793 5557 5741 +11404 5791 5741 5598 +11405 5610 5792 5786 +11406 5792 5737 5786 +11407 5792 5598 5737 +11408 5786 5737 5564 +11409 482 5788 481 +11410 5788 5794 481 +11411 5788 5611 5794 +11412 481 5794 480 +11413 5611 5795 5794 +11414 5795 5796 5794 +11415 5795 5594 5796 +11416 5794 5796 480 +11417 5611 5787 5795 +11418 5787 5727 5795 +11419 5787 5564 5727 +11420 5795 5727 5594 +11421 480 5796 479 +11422 5796 5723 479 +11423 5796 5594 5723 +11424 479 5723 6 +11425 494 5696 493 +11426 5696 5797 493 +11427 5696 5586 5797 +11428 493 5797 492 +11429 5586 5798 5797 +11430 5798 5799 5797 +11431 5798 5613 5799 +11432 5797 5799 492 +11433 5586 5695 5798 +11434 5695 5800 5798 +11435 5695 5561 5800 +11436 5798 5800 5613 +11437 492 5799 491 +11438 5799 5801 491 +11439 5799 5613 5801 +11440 491 5801 490 +11441 5561 5802 5800 +11442 5802 5803 5800 +11443 5802 5614 5803 +11444 5800 5803 5613 +11445 5614 5804 5803 +11446 5804 5805 5803 +11447 5804 5615 5805 +11448 5803 5805 5613 +11449 5614 5806 5804 +11450 5806 5807 5804 +11451 5806 5569 5807 +11452 5804 5807 5615 +11453 5613 5805 5801 +11454 5805 5808 5801 +11455 5805 5615 5808 +11456 5801 5808 490 +11457 5561 5691 5802 +11458 5691 5809 5802 +11459 5691 5585 5809 +11460 5802 5809 5614 +11461 5585 5810 5809 +11462 5810 5811 5809 +11463 5810 5616 5811 +11464 5809 5811 5614 +11465 5585 5690 5810 +11466 5690 5812 5810 +11467 5690 5556 5812 +11468 5810 5812 5616 +11469 5614 5811 5806 +11470 5811 5813 5806 +11471 5811 5616 5813 +11472 5806 5813 5569 +11473 490 5808 489 +11474 5808 5814 489 +11475 5808 5615 5814 +11476 489 5814 488 +11477 5615 5815 5814 +11478 5815 5816 5814 +11479 5815 5617 5816 +11480 5814 5816 488 +11481 5615 5807 5815 +11482 5807 5817 5815 +11483 5807 5569 5817 +11484 5815 5817 5617 +11485 488 5816 487 +11486 5816 5818 487 +11487 5816 5617 5818 +11488 487 5818 486 +11489 5556 5773 5812 +11490 5773 5819 5812 +11491 5773 5606 5819 +11492 5812 5819 5616 +11493 5606 5820 5819 +11494 5820 5821 5819 +11495 5820 5618 5821 +11496 5819 5821 5616 +11497 5606 5772 5820 +11498 5772 5822 5820 +11499 5772 5566 5822 +11500 5820 5822 5618 +11501 5616 5821 5813 +11502 5821 5823 5813 +11503 5821 5618 5823 +11504 5813 5823 5569 +11505 5566 5824 5822 +11506 5824 5825 5822 +11507 5824 5619 5825 +11508 5822 5825 5618 +11509 5619 5826 5825 +11510 5826 5827 5825 +11511 5826 5620 5827 +11512 5825 5827 5618 +11513 5619 5828 5826 +11514 5828 5829 5826 +11515 5828 5568 5829 +11516 5826 5829 5620 +11517 5618 5827 5823 +11518 5827 5830 5823 +11519 5827 5620 5830 +11520 5823 5830 5569 +11521 5566 5767 5824 +11522 5767 5831 5824 +11523 5767 5605 5831 +11524 5824 5831 5619 +11525 5605 5832 5831 +11526 5832 5833 5831 +11527 5832 5612 5833 +11528 5831 5833 5619 +11529 5605 5766 5832 +11530 5766 5793 5832 +11531 5766 5557 5793 +11532 5832 5793 5612 +11533 5619 5833 5828 +11534 5833 5789 5828 +11535 5833 5612 5789 +11536 5828 5789 5568 +11537 5569 5830 5817 +11538 5830 5834 5817 +11539 5830 5620 5834 +11540 5817 5834 5617 +11541 5620 5835 5834 +11542 5835 5836 5834 +11543 5835 5608 5836 +11544 5834 5836 5617 +11545 5620 5829 5835 +11546 5829 5779 5835 +11547 5829 5568 5779 +11548 5835 5779 5608 +11549 5617 5836 5818 +11550 5836 5775 5818 +11551 5836 5608 5775 +11552 5818 5775 486 +11553 5 5750 122 +11554 5750 5837 122 +11555 5750 5602 5837 +11556 122 5837 121 +11557 5602 5838 5837 +11558 5838 5839 5837 +11559 5838 5621 5839 +11560 5837 5839 121 +11561 5602 5755 5838 +11562 5755 5840 5838 +11563 5755 5567 5840 +11564 5838 5840 5621 +11565 121 5839 120 +11566 5839 5841 120 +11567 5839 5621 5841 +11568 120 5841 119 +11569 5567 5842 5840 +11570 5842 5843 5840 +11571 5842 5622 5843 +11572 5840 5843 5621 +11573 5622 5844 5843 +11574 5844 5845 5843 +11575 5844 5623 5845 +11576 5843 5845 5621 +11577 5622 5846 5844 +11578 5846 5847 5844 +11579 5846 5558 5847 +11580 5844 5847 5623 +11581 5621 5845 5841 +11582 5845 5848 5841 +11583 5845 5623 5848 +11584 5841 5848 119 +11585 5567 5769 5842 +11586 5769 5849 5842 +11587 5769 5607 5849 +11588 5842 5849 5622 +11589 5607 5850 5849 +11590 5850 5851 5849 +11591 5850 5574 5851 +11592 5849 5851 5622 +11593 5607 5774 5850 +11594 5774 5645 5850 +11595 5774 5556 5645 +11596 5850 5645 5574 +11597 5622 5851 5846 +11598 5851 5641 5846 +11599 5851 5574 5641 +11600 5846 5641 5558 +11601 119 5848 118 +11602 5848 5852 118 +11603 5848 5623 5852 +11604 118 5852 117 +11605 5623 5853 5852 +11606 5853 5854 5852 +11607 5853 5570 5854 +11608 5852 5854 117 +11609 5623 5847 5853 +11610 5847 5631 5853 +11611 5847 5558 5631 +11612 5853 5631 5570 +11613 117 5854 116 +11614 5854 5627 116 +11615 5854 5570 5627 +11616 116 5627 115 +11617 138 5748 137 +11618 5748 5855 137 +11619 5748 5600 5855 +11620 137 5855 136 +11621 5600 5856 5855 +11622 5856 5857 5855 +11623 5856 5624 5857 +11624 5855 5857 136 +11625 5600 5747 5856 +11626 5747 5858 5856 +11627 5747 5565 5858 +11628 5856 5858 5624 +11629 136 5857 135 +11630 5857 5859 135 +11631 5857 5624 5859 +11632 135 5859 134 +11633 5565 5860 5858 +11634 5860 5861 5858 +11635 5860 5625 5861 +11636 5858 5861 5624 +11637 5625 5862 5861 +11638 5862 5863 5861 +11639 5862 5626 5863 +11640 5861 5863 5624 +11641 5625 5864 5862 +11642 5864 5865 5862 +11643 5864 5562 5865 +11644 5862 5865 5626 +11645 5624 5863 5859 +11646 5863 5866 5859 +11647 5863 5626 5866 +11648 5859 5866 134 +11649 5565 5743 5860 +11650 5743 5867 5860 +11651 5743 5599 5867 +11652 5860 5867 5625 +11653 5599 5868 5867 +11654 5868 5869 5867 +11655 5868 5591 5869 +11656 5867 5869 5625 +11657 5599 5742 5868 +11658 5742 5715 5868 +11659 5742 5557 5715 +11660 5868 5715 5591 +11661 5625 5869 5864 +11662 5869 5711 5864 +11663 5869 5591 5711 +11664 5864 5711 5562 +11665 134 5866 133 +11666 5866 5870 133 +11667 5866 5626 5870 +11668 133 5870 132 +11669 5626 5871 5870 +11670 5871 5872 5870 +11671 5871 5587 5872 +11672 5870 5872 132 +11673 5626 5865 5871 +11674 5865 5701 5871 +11675 5865 5562 5701 +11676 5871 5701 5587 +11677 132 5872 131 +11678 5872 5697 131 +11679 5872 5587 5697 +11680 131 5697 130 +2 8 2 512 +11681 199 5922 5924 +11682 5922 5923 5924 +11683 5922 5882 5923 +11684 5924 5923 5884 +11685 5882 5925 5923 +11686 5925 5926 5923 +11687 5925 5883 5926 +11688 5923 5926 5884 +11689 5882 5927 5925 +11690 5927 5928 5925 +11691 5927 5874 5928 +11692 5925 5928 5883 +11693 5884 5926 5930 +11694 5926 5929 5930 +11695 5926 5883 5929 +11696 5930 5929 5876 +11697 5874 5931 5928 +11698 5931 5932 5928 +11699 5931 5885 5932 +11700 5928 5932 5883 +11701 5885 5933 5932 +11702 5933 5934 5932 +11703 5933 5886 5934 +11704 5932 5934 5883 +11705 5885 5935 5933 +11706 5935 5936 5933 +11707 5935 5875 5936 +11708 5933 5936 5886 +11709 5883 5934 5929 +11710 5934 5937 5929 +11711 5934 5886 5937 +11712 5929 5937 5876 +11713 5874 5938 5931 +11714 5938 5939 5931 +11715 5938 5887 5939 +11716 5931 5939 5885 +11717 5887 5940 5939 +11718 5940 5941 5939 +11719 5940 5888 5941 +11720 5939 5941 5885 +11721 5887 5942 5940 +11722 5942 5943 5940 +11723 5942 5873 5943 +11724 5940 5943 5888 +11725 5885 5941 5935 +11726 5941 5944 5935 +11727 5941 5888 5944 +11728 5935 5944 5875 +11729 5876 5937 5946 +11730 5937 5945 5946 +11731 5937 5886 5945 +11732 5946 5945 5890 +11733 5886 5947 5945 +11734 5947 5948 5945 +11735 5947 5889 5948 +11736 5945 5948 5890 +11737 5886 5936 5947 +11738 5936 5949 5947 +11739 5936 5875 5949 +11740 5947 5949 5889 +11741 5890 5948 5951 +11742 5948 5950 5951 +11743 5948 5889 5950 +11744 5951 5950 517 +11745 509 5952 5954 +11746 5952 5953 5954 +11747 5952 5891 5953 +11748 5954 5953 5893 +11749 5891 5955 5953 +11750 5955 5956 5953 +11751 5955 5892 5956 +11752 5953 5956 5893 +11753 5891 5957 5955 +11754 5957 5958 5955 +11755 5957 5877 5958 +11756 5955 5958 5892 +11757 5893 5956 5960 +11758 5956 5959 5960 +11759 5956 5892 5959 +11760 5960 5959 5879 +11761 5877 5961 5958 +11762 5961 5962 5958 +11763 5961 5894 5962 +11764 5958 5962 5892 +11765 5894 5963 5962 +11766 5963 5964 5962 +11767 5963 5895 5964 +11768 5962 5964 5892 +11769 5894 5965 5963 +11770 5965 5966 5963 +11771 5965 5878 5966 +11772 5963 5966 5895 +11773 5892 5964 5959 +11774 5964 5967 5959 +11775 5964 5895 5967 +11776 5959 5967 5879 +11777 5877 5968 5961 +11778 5968 5969 5961 +11779 5968 5896 5969 +11780 5961 5969 5894 +11781 5896 5970 5969 +11782 5970 5971 5969 +11783 5970 5897 5971 +11784 5969 5971 5894 +11785 5896 5972 5970 +11786 5972 5973 5970 +11787 5972 5873 5973 +11788 5970 5973 5897 +11789 5894 5971 5965 +11790 5971 5974 5965 +11791 5971 5897 5974 +11792 5965 5974 5878 +11793 5879 5967 5976 +11794 5967 5975 5976 +11795 5967 5895 5975 +11796 5976 5975 5899 +11797 5895 5977 5975 +11798 5977 5978 5975 +11799 5977 5898 5978 +11800 5975 5978 5899 +11801 5895 5966 5977 +11802 5966 5979 5977 +11803 5966 5878 5979 +11804 5977 5979 5898 +11805 5899 5978 5981 +11806 5978 5980 5981 +11807 5978 5898 5980 +11808 5981 5980 223 +11809 207 5982 206 +11810 5982 5983 206 +11811 5982 5900 5983 +11812 206 5983 205 +11813 5900 5984 5983 +11814 5984 5985 5983 +11815 5984 5901 5985 +11816 5983 5985 205 +11817 5900 5986 5984 +11818 5986 5987 5984 +11819 5986 5880 5987 +11820 5984 5987 5901 +11821 205 5985 204 +11822 5985 5988 204 +11823 5985 5901 5988 +11824 204 5988 203 +11825 5880 5989 5987 +11826 5989 5990 5987 +11827 5989 5902 5990 +11828 5987 5990 5901 +11829 5902 5991 5990 +11830 5991 5992 5990 +11831 5991 5903 5992 +11832 5990 5992 5901 +11833 5902 5993 5991 +11834 5993 5994 5991 +11835 5993 5874 5994 +11836 5991 5994 5903 +11837 5901 5992 5988 +11838 5992 5995 5988 +11839 5992 5903 5995 +11840 5988 5995 203 +11841 5880 5996 5989 +11842 5996 5997 5989 +11843 5996 5904 5997 +11844 5989 5997 5902 +11845 5904 5998 5997 +11846 5998 5999 5997 +11847 5998 5887 5999 +11848 5997 5999 5902 +11849 5904 6000 5998 +11850 6000 5942 5998 +11851 6000 5873 5942 +11852 5998 5942 5887 +11853 5902 5999 5993 +11854 5999 5938 5993 +11855 5999 5887 5938 +11856 5993 5938 5874 +11857 203 5995 202 +11858 5995 6001 202 +11859 5995 5903 6001 +11860 202 6001 201 +11861 5903 6002 6001 +11862 6002 6003 6001 +11863 6002 5882 6003 +11864 6001 6003 201 +11865 5903 5994 6002 +11866 5994 5927 6002 +11867 5994 5874 5927 +11868 6002 5927 5882 +11869 201 6003 200 +11870 6003 5922 200 +11871 6003 5882 5922 +11872 200 5922 199 +11873 223 5980 222 +11874 5980 6004 222 +11875 5980 5898 6004 +11876 222 6004 221 +11877 5898 6005 6004 +11878 6005 6006 6004 +11879 6005 5905 6006 +11880 6004 6006 221 +11881 5898 5979 6005 +11882 5979 6007 6005 +11883 5979 5878 6007 +11884 6005 6007 5905 +11885 221 6006 220 +11886 6006 6008 220 +11887 6006 5905 6008 +11888 220 6008 219 +11889 5878 6009 6007 +11890 6009 6010 6007 +11891 6009 5906 6010 +11892 6007 6010 5905 +11893 5906 6011 6010 +11894 6011 6012 6010 +11895 6011 5907 6012 +11896 6010 6012 5905 +11897 5906 6013 6011 +11898 6013 6014 6011 +11899 6013 5881 6014 +11900 6011 6014 5907 +11901 5905 6012 6008 +11902 6012 6015 6008 +11903 6012 5907 6015 +11904 6008 6015 219 +11905 5878 5974 6009 +11906 5974 6016 6009 +11907 5974 5897 6016 +11908 6009 6016 5906 +11909 5897 6017 6016 +11910 6017 6018 6016 +11911 6017 5908 6018 +11912 6016 6018 5906 +11913 5897 5973 6017 +11914 5973 6019 6017 +11915 5973 5873 6019 +11916 6017 6019 5908 +11917 5906 6018 6013 +11918 6018 6020 6013 +11919 6018 5908 6020 +11920 6013 6020 5881 +11921 219 6015 218 +11922 6015 6021 218 +11923 6015 5907 6021 +11924 218 6021 217 +11925 5907 6022 6021 +11926 6022 6023 6021 +11927 6022 5909 6023 +11928 6021 6023 217 +11929 5907 6014 6022 +11930 6014 6024 6022 +11931 6014 5881 6024 +11932 6022 6024 5909 +11933 217 6023 216 +11934 6023 6025 216 +11935 6023 5909 6025 +11936 216 6025 215 +11937 8 192 524 +11938 192 6026 524 +11939 192 193 6026 +11940 524 6026 523 +11941 193 6027 6026 +11942 6027 6028 6026 +11943 6027 5910 6028 +11944 6026 6028 523 +11945 193 194 6027 +11946 194 6029 6027 +11947 194 195 6029 +11948 6027 6029 5910 +11949 523 6028 522 +11950 6028 6030 522 +11951 6028 5910 6030 +11952 522 6030 521 +11953 195 6031 6029 +11954 6031 6032 6029 +11955 6031 5911 6032 +11956 6029 6032 5910 +11957 5911 6033 6032 +11958 6033 6034 6032 +11959 6033 5912 6034 +11960 6032 6034 5910 +11961 5911 6035 6033 +11962 6035 6036 6033 +11963 6035 5876 6036 +11964 6033 6036 5912 +11965 5910 6034 6030 +11966 6034 6037 6030 +11967 6034 5912 6037 +11968 6030 6037 521 +11969 195 196 6031 +11970 196 6038 6031 +11971 196 197 6038 +11972 6031 6038 5911 +11973 197 6039 6038 +11974 6039 6040 6038 +11975 6039 5884 6040 +11976 6038 6040 5911 +11977 197 198 6039 +11978 198 5924 6039 +11979 198 199 5924 +11980 6039 5924 5884 +11981 5911 6040 6035 +11982 6040 5930 6035 +11983 6040 5884 5930 +11984 6035 5930 5876 +11985 521 6037 520 +11986 6037 6041 520 +11987 6037 5912 6041 +11988 520 6041 519 +11989 5912 6042 6041 +11990 6042 6043 6041 +11991 6042 5890 6043 +11992 6041 6043 519 +11993 5912 6036 6042 +11994 6036 5946 6042 +11995 6036 5876 5946 +11996 6042 5946 5890 +11997 519 6043 518 +11998 6043 5951 518 +11999 6043 5890 5951 +12000 518 5951 517 +12001 223 224 5981 +12002 224 6044 5981 +12003 224 225 6044 +12004 5981 6044 5899 +12005 225 6045 6044 +12006 6045 6046 6044 +12007 6045 5913 6046 +12008 6044 6046 5899 +12009 225 226 6045 +12010 226 6047 6045 +12011 226 227 6047 +12012 6045 6047 5913 +12013 5899 6046 5976 +12014 6046 6048 5976 +12015 6046 5913 6048 +12016 5976 6048 5879 +12017 227 6049 6047 +12018 6049 6050 6047 +12019 6049 5914 6050 +12020 6047 6050 5913 +12021 5914 6051 6050 +12022 6051 6052 6050 +12023 6051 5915 6052 +12024 6050 6052 5913 +12025 5914 6053 6051 +12026 6053 6054 6051 +12027 6053 505 6054 +12028 6051 6054 5915 +12029 5913 6052 6048 +12030 6052 6055 6048 +12031 6052 5915 6055 +12032 6048 6055 5879 +12033 227 228 6049 +12034 228 6056 6049 +12035 228 229 6056 +12036 6049 6056 5914 +12037 229 6057 6056 +12038 6057 6058 6056 +12039 6057 503 6058 +12040 6056 6058 5914 +12041 229 230 6057 +12042 230 502 6057 +12043 230 9 502 +12044 6057 502 503 +12045 5914 6058 6053 +12046 6058 504 6053 +12047 6058 503 504 +12048 6053 504 505 +12049 5879 6055 5960 +12050 6055 6059 5960 +12051 6055 5915 6059 +12052 5960 6059 5893 +12053 5915 6060 6059 +12054 6060 6061 6059 +12055 6060 507 6061 +12056 6059 6061 5893 +12057 5915 6054 6060 +12058 6054 506 6060 +12059 6054 505 506 +12060 6060 506 507 +12061 5893 6061 5954 +12062 6061 508 5954 +12063 6061 507 508 +12064 5954 508 509 +12065 215 6025 214 +12066 6025 6062 214 +12067 6025 5909 6062 +12068 214 6062 213 +12069 5909 6063 6062 +12070 6063 6064 6062 +12071 6063 5916 6064 +12072 6062 6064 213 +12073 5909 6024 6063 +12074 6024 6065 6063 +12075 6024 5881 6065 +12076 6063 6065 5916 +12077 213 6064 212 +12078 6064 6066 212 +12079 6064 5916 6066 +12080 212 6066 211 +12081 5881 6067 6065 +12082 6067 6068 6065 +12083 6067 5917 6068 +12084 6065 6068 5916 +12085 5917 6069 6068 +12086 6069 6070 6068 +12087 6069 5918 6070 +12088 6068 6070 5916 +12089 5917 6071 6069 +12090 6071 6072 6069 +12091 6071 5880 6072 +12092 6069 6072 5918 +12093 5916 6070 6066 +12094 6070 6073 6066 +12095 6070 5918 6073 +12096 6066 6073 211 +12097 5881 6020 6067 +12098 6020 6074 6067 +12099 6020 5908 6074 +12100 6067 6074 5917 +12101 5908 6075 6074 +12102 6075 6076 6074 +12103 6075 5904 6076 +12104 6074 6076 5917 +12105 5908 6019 6075 +12106 6019 6000 6075 +12107 6019 5873 6000 +12108 6075 6000 5904 +12109 5917 6076 6071 +12110 6076 5996 6071 +12111 6076 5904 5996 +12112 6071 5996 5880 +12113 211 6073 210 +12114 6073 6077 210 +12115 6073 5918 6077 +12116 210 6077 209 +12117 5918 6078 6077 +12118 6078 6079 6077 +12119 6078 5900 6079 +12120 6077 6079 209 +12121 5918 6072 6078 +12122 6072 5986 6078 +12123 6072 5880 5986 +12124 6078 5986 5900 +12125 209 6079 208 +12126 6079 5982 208 +12127 6079 5900 5982 +12128 208 5982 207 +12129 517 5950 516 +12130 5950 6080 516 +12131 5950 5889 6080 +12132 516 6080 515 +12133 5889 6081 6080 +12134 6081 6082 6080 +12135 6081 5919 6082 +12136 6080 6082 515 +12137 5889 5949 6081 +12138 5949 6083 6081 +12139 5949 5875 6083 +12140 6081 6083 5919 +12141 515 6082 514 +12142 6082 6084 514 +12143 6082 5919 6084 +12144 514 6084 513 +12145 5875 6085 6083 +12146 6085 6086 6083 +12147 6085 5920 6086 +12148 6083 6086 5919 +12149 5920 6087 6086 +12150 6087 6088 6086 +12151 6087 5921 6088 +12152 6086 6088 5919 +12153 5920 6089 6087 +12154 6089 6090 6087 +12155 6089 5877 6090 +12156 6087 6090 5921 +12157 5919 6088 6084 +12158 6088 6091 6084 +12159 6088 5921 6091 +12160 6084 6091 513 +12161 5875 5944 6085 +12162 5944 6092 6085 +12163 5944 5888 6092 +12164 6085 6092 5920 +12165 5888 6093 6092 +12166 6093 6094 6092 +12167 6093 5896 6094 +12168 6092 6094 5920 +12169 5888 5943 6093 +12170 5943 5972 6093 +12171 5943 5873 5972 +12172 6093 5972 5896 +12173 5920 6094 6089 +12174 6094 5968 6089 +12175 6094 5896 5968 +12176 6089 5968 5877 +12177 513 6091 512 +12178 6091 6095 512 +12179 6091 5921 6095 +12180 512 6095 511 +12181 5921 6096 6095 +12182 6096 6097 6095 +12183 6096 5891 6097 +12184 6095 6097 511 +12185 5921 6090 6096 +12186 6090 5957 6096 +12187 6090 5877 5957 +12188 6096 5957 5891 +12189 511 6097 510 +12190 6097 5952 510 +12191 6097 5891 5952 +12192 510 5952 509 +$EndElements diff --git a/previews/PR798/tutorials/plasticity.ipynb b/previews/PR798/tutorials/plasticity.ipynb new file mode 100644 index 0000000000..faf13b8010 --- /dev/null +++ b/previews/PR798/tutorials/plasticity.ipynb @@ -0,0 +1,956 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Von Mises plasticity\n", + "\n", + "![Shows the von Mises stress distribution in a cantilever beam.](plasticity.png)\n", + "\n", + "*Figure 1.* A coarse mesh solution of a cantilever beam subjected to a load\n", + "causing plastic deformations. The initial yield limit is 200 MPa but due to\n", + "hardening it increases up to approximately 240 MPa." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Introduction\n", + "\n", + "This example illustrates the use of a nonlinear material model in Ferrite.\n", + "The particular model is von Mises plasticity (also know as J₂-plasticity) with\n", + "isotropic hardening. The model is fully 3D, meaning that no assumptions like *plane stress*\n", + "or *plane strain* are introduced.\n", + "\n", + "Also note that the theory of the model is not described here, instead one is\n", + "referred to standard textbooks on material modeling.\n", + "\n", + "To illustrate the use of the plasticity model, we setup and solve a FE-problem\n", + "consisting of a cantilever beam loaded at its free end. But first, we shortly\n", + "describe the parts of the implementation dealing with the material modeling." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Material modeling\n", + "This section describes the `struct`s and methods used to implement the material\n", + "model" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "### Material parameters and state variables\n", + "\n", + "Start by loading some necessary packages" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using Ferrite, Tensors, SparseArrays, LinearAlgebra, Printf" + ], + "metadata": {}, + "execution_count": 1 + }, + { + "cell_type": "markdown", + "source": [ + "We define a J₂-plasticity-material, containing material parameters and the elastic\n", + "stiffness Dᵉ (since it is constant)" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "struct J2Plasticity{T, S <: SymmetricTensor{4, 3, T}}\n", + " G::T # Shear modulus\n", + " K::T # Bulk modulus\n", + " σ₀::T # Initial yield limit\n", + " H::T # Hardening modulus\n", + " Dᵉ::S # Elastic stiffness tensor\n", + "end;" + ], + "metadata": {}, + "execution_count": 2 + }, + { + "cell_type": "markdown", + "source": [ + "Next, we define a constructor for the material instance." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function J2Plasticity(E, ν, σ₀, H)\n", + " δ(i, j) = i == j ? 1.0 : 0.0 # helper function\n", + " G = E / 2(1 + ν)\n", + " K = E / 3(1 - 2ν)\n", + "\n", + " Isymdev(i, j, k, l) = 0.5 * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k)) - 1.0 / 3.0 * δ(i, j) * δ(k, l)\n", + " temp(i, j, k, l) = 2.0G * (0.5 * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k)) + ν / (1.0 - 2.0ν) * δ(i, j) * δ(k, l))\n", + " Dᵉ = SymmetricTensor{4, 3}(temp)\n", + " return J2Plasticity(G, K, σ₀, H, Dᵉ)\n", + "end;" + ], + "metadata": {}, + "execution_count": 3 + }, + { + "cell_type": "markdown", + "source": [ + "Define a `struct` to store the material state for a Gauss point." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "struct MaterialState{T, S <: SecondOrderTensor{3, T}}\n", + " # Store \"converged\" values\n", + " ϵᵖ::S # plastic strain\n", + " σ::S # stress\n", + " k::T # hardening variable\n", + "end" + ], + "metadata": {}, + "execution_count": 4 + }, + { + "cell_type": "markdown", + "source": [ + "Constructor for initializing a material state. Every quantity is set to zero." + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "Main.var\"##327\".MaterialState" + }, + "metadata": {}, + "execution_count": 5 + } + ], + "cell_type": "code", + "source": [ + "function MaterialState()\n", + " return MaterialState(\n", + " zero(SymmetricTensor{2, 3}),\n", + " zero(SymmetricTensor{2, 3}),\n", + " 0.0\n", + " )\n", + "end" + ], + "metadata": {}, + "execution_count": 5 + }, + { + "cell_type": "markdown", + "source": [ + "For later use, during the post-processing step, we define a function to\n", + "compute the von Mises effective stress." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function vonMises(σ)\n", + " s = dev(σ)\n", + " return sqrt(3.0 / 2.0 * s ⊡ s)\n", + "end;" + ], + "metadata": {}, + "execution_count": 6 + }, + { + "cell_type": "markdown", + "source": [ + "## Constitutive driver\n", + "\n", + "This is the actual method which computes the stress and material tangent\n", + "stiffness in a given integration point.\n", + "Input is the current strain and the material state from the previous timestep." + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "compute_stress_tangent (generic function with 1 method)" + }, + "metadata": {}, + "execution_count": 7 + } + ], + "cell_type": "code", + "source": [ + "function compute_stress_tangent(ϵ::SymmetricTensor{2, 3}, material::J2Plasticity, state::MaterialState)\n", + " # unpack some material parameters\n", + " G = material.G\n", + " H = material.H\n", + "\n", + " # We use (•)ᵗ to denote *trial*-values\n", + " σᵗ = material.Dᵉ ⊡ (ϵ - state.ϵᵖ) # trial-stress\n", + " sᵗ = dev(σᵗ) # deviatoric part of trial-stress\n", + " J₂ = 0.5 * sᵗ ⊡ sᵗ # second invariant of sᵗ\n", + " σᵗₑ = sqrt(3.0 * J₂) # effective trial-stress (von Mises stress)\n", + " σʸ = material.σ₀ + H * state.k # Previous yield limit\n", + "\n", + " φᵗ = σᵗₑ - σʸ # Trial-value of the yield surface\n", + "\n", + " if φᵗ < 0.0 # elastic loading\n", + " return σᵗ, material.Dᵉ, MaterialState(state.ϵᵖ, σᵗ, state.k)\n", + " else # plastic loading\n", + " h = H + 3G\n", + " μ = φᵗ / h # plastic multiplier\n", + "\n", + " c1 = 1 - 3G * μ / σᵗₑ\n", + " s = c1 * sᵗ # updated deviatoric stress\n", + " σ = s + vol(σᵗ) # updated stress\n", + "\n", + " # Compute algorithmic tangent stiffness $D = \\frac{\\Delta \\sigma }{\\Delta \\epsilon}$\n", + " κ = H * (state.k + μ) # drag stress\n", + " σₑ = material.σ₀ + κ # updated yield surface\n", + "\n", + " δ(i, j) = i == j ? 1.0 : 0.0\n", + " Isymdev(i, j, k, l) = 0.5 * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k)) - 1.0 / 3.0 * δ(i, j) * δ(k, l)\n", + " Q(i, j, k, l) = Isymdev(i, j, k, l) - 3.0 / (2.0 * σₑ^2) * s[i, j] * s[k, l]\n", + " b = (3G * μ / σₑ) / (1.0 + 3G * μ / σₑ)\n", + "\n", + " Dtemp(i, j, k, l) = -2G * b * Q(i, j, k, l) - 9G^2 / (h * σₑ^2) * s[i, j] * s[k, l]\n", + " D = material.Dᵉ + SymmetricTensor{4, 3}(Dtemp)\n", + "\n", + " # Return new state\n", + " Δϵᵖ = 3 / 2 * μ / σₑ * s # plastic strain\n", + " ϵᵖ = state.ϵᵖ + Δϵᵖ # plastic strain\n", + " k = state.k + μ # hardening variable\n", + " return σ, D, MaterialState(ϵᵖ, σ, k)\n", + " end\n", + "end" + ], + "metadata": {}, + "execution_count": 7 + }, + { + "cell_type": "markdown", + "source": [ + "## FE-problem\n", + "What follows are methods for assembling and and solving the FE-problem." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function create_values(interpolation)\n", + " # setup quadrature rules\n", + " qr = QuadratureRule{RefTetrahedron}(2)\n", + " facet_qr = FacetQuadratureRule{RefTetrahedron}(3)\n", + "\n", + " # cell and facetvalues for u\n", + " cellvalues_u = CellValues(qr, interpolation)\n", + " facetvalues_u = FacetValues(facet_qr, interpolation)\n", + "\n", + " return cellvalues_u, facetvalues_u\n", + "end;" + ], + "metadata": {}, + "execution_count": 8 + }, + { + "cell_type": "markdown", + "source": [ + "### Add degrees of freedom" + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "create_dofhandler (generic function with 1 method)" + }, + "metadata": {}, + "execution_count": 9 + } + ], + "cell_type": "code", + "source": [ + "function create_dofhandler(grid, interpolation)\n", + " dh = DofHandler(grid)\n", + " add!(dh, :u, interpolation) # add a displacement field with 3 components\n", + " close!(dh)\n", + " return dh\n", + "end" + ], + "metadata": {}, + "execution_count": 9 + }, + { + "cell_type": "markdown", + "source": [ + "### Boundary conditions" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function create_bc(dh, grid)\n", + " dbcs = ConstraintHandler(dh)\n", + " # Clamped on the left side\n", + " dofs = [1, 2, 3]\n", + " dbc = Dirichlet(:u, getfacetset(grid, \"left\"), (x, t) -> [0.0, 0.0, 0.0], dofs)\n", + " add!(dbcs, dbc)\n", + " close!(dbcs)\n", + " return dbcs\n", + "end;" + ], + "metadata": {}, + "execution_count": 10 + }, + { + "cell_type": "markdown", + "source": [ + "### Assembling of element contributions\n", + "\n", + "* Residual vector `r`\n", + "* Tangent stiffness `K`" + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "doassemble! (generic function with 1 method)" + }, + "metadata": {}, + "execution_count": 11 + } + ], + "cell_type": "code", + "source": [ + "function doassemble!(\n", + " K::SparseMatrixCSC, r::Vector, cellvalues::CellValues, dh::DofHandler,\n", + " material::J2Plasticity, u, states, states_old\n", + " )\n", + " assembler = start_assemble(K, r)\n", + " nu = getnbasefunctions(cellvalues)\n", + " re = zeros(nu) # element residual vector\n", + " ke = zeros(nu, nu) # element tangent matrix\n", + "\n", + " for (i, cell) in enumerate(CellIterator(dh))\n", + " fill!(ke, 0)\n", + " fill!(re, 0)\n", + " eldofs = celldofs(cell)\n", + " ue = u[eldofs]\n", + " state = @view states[:, i]\n", + " state_old = @view states_old[:, i]\n", + " assemble_cell!(ke, re, cell, cellvalues, material, ue, state, state_old)\n", + " assemble!(assembler, eldofs, ke, re)\n", + " end\n", + " return K, r\n", + "end" + ], + "metadata": {}, + "execution_count": 11 + }, + { + "cell_type": "markdown", + "source": [ + "Compute element contribution to the residual and the tangent." + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "assemble_cell! (generic function with 1 method)" + }, + "metadata": {}, + "execution_count": 12 + } + ], + "cell_type": "code", + "source": [ + "function assemble_cell!(Ke, re, cell, cellvalues, material, ue, state, state_old)\n", + " n_basefuncs = getnbasefunctions(cellvalues)\n", + " reinit!(cellvalues, cell)\n", + "\n", + " for q_point in 1:getnquadpoints(cellvalues)\n", + " # For each integration point, compute stress and material stiffness\n", + " ϵ = function_symmetric_gradient(cellvalues, q_point, ue) # Total strain\n", + " σ, D, state[q_point] = compute_stress_tangent(ϵ, material, state_old[q_point])\n", + "\n", + " dΩ = getdetJdV(cellvalues, q_point)\n", + " for i in 1:n_basefuncs\n", + " δϵ = shape_symmetric_gradient(cellvalues, q_point, i)\n", + " re[i] += (δϵ ⊡ σ) * dΩ # add internal force to residual\n", + " for j in 1:i # loop only over lower half\n", + " Δϵ = shape_symmetric_gradient(cellvalues, q_point, j)\n", + " Ke[i, j] += δϵ ⊡ D ⊡ Δϵ * dΩ\n", + " end\n", + " end\n", + " end\n", + " symmetrize_lower!(Ke)\n", + " return\n", + "end" + ], + "metadata": {}, + "execution_count": 12 + }, + { + "cell_type": "markdown", + "source": [ + "Helper function to symmetrize the material tangent" + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "doassemble_neumann! (generic function with 1 method)" + }, + "metadata": {}, + "execution_count": 13 + } + ], + "cell_type": "code", + "source": [ + "function symmetrize_lower!(K)\n", + " for i in 1:size(K, 1)\n", + " for j in (i + 1):size(K, 1)\n", + " K[i, j] = K[j, i]\n", + " end\n", + " end\n", + " return\n", + "end;\n", + "\n", + "function doassemble_neumann!(r, dh, facetset, facetvalues, t)\n", + " n_basefuncs = getnbasefunctions(facetvalues)\n", + " re = zeros(n_basefuncs) # element residual vector\n", + " for fc in FacetIterator(dh, facetset)\n", + " # Add traction as a negative contribution to the element residual `re`:\n", + " reinit!(facetvalues, fc)\n", + " fill!(re, 0)\n", + " for q_point in 1:getnquadpoints(facetvalues)\n", + " dΓ = getdetJdV(facetvalues, q_point)\n", + " for i in 1:n_basefuncs\n", + " δu = shape_value(facetvalues, q_point, i)\n", + " re[i] -= (δu ⋅ t) * dΓ\n", + " end\n", + " end\n", + " assemble!(r, celldofs(fc), re)\n", + " end\n", + " return r\n", + "end" + ], + "metadata": {}, + "execution_count": 13 + }, + { + "cell_type": "markdown", + "source": [ + "Define a function which solves the FE-problem." + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "solve (generic function with 1 method)" + }, + "metadata": {}, + "execution_count": 14 + } + ], + "cell_type": "code", + "source": [ + "function solve()\n", + " # Define material parameters\n", + " E = 200.0e9 # [Pa]\n", + " H = E / 20 # [Pa]\n", + " ν = 0.3 # [-]\n", + " σ₀ = 200.0e6 # [Pa]\n", + " material = J2Plasticity(E, ν, σ₀, H)\n", + "\n", + " L = 10.0 # beam length [m]\n", + " w = 1.0 # beam width [m]\n", + " h = 1.0 # beam height[m]\n", + " n_timesteps = 10\n", + " u_max = zeros(n_timesteps)\n", + " traction_magnitude = 1.0e7 * range(0.5, 1.0, length = n_timesteps)\n", + "\n", + " # Create geometry, dofs and boundary conditions\n", + " n = 2\n", + " nels = (10n, n, 2n) # number of elements in each spatial direction\n", + " P1 = Vec((0.0, 0.0, 0.0)) # start point for geometry\n", + " P2 = Vec((L, w, h)) # end point for geometry\n", + " grid = generate_grid(Tetrahedron, nels, P1, P2)\n", + " interpolation = Lagrange{RefTetrahedron, 1}()^3\n", + "\n", + " dh = create_dofhandler(grid, interpolation) # JuaFEM helper function\n", + " dbcs = create_bc(dh, grid) # create Dirichlet boundary-conditions\n", + "\n", + " cellvalues, facetvalues = create_values(interpolation)\n", + "\n", + " # Pre-allocate solution vectors, etc.\n", + " n_dofs = ndofs(dh) # total number of dofs\n", + " u = zeros(n_dofs) # solution vector\n", + " Δu = zeros(n_dofs) # displacement correction\n", + " r = zeros(n_dofs) # residual\n", + " K = allocate_matrix(dh) # tangent stiffness matrix\n", + "\n", + " # Create material states. One array for each cell, where each element is an array of material-\n", + " # states - one for each integration point\n", + " nqp = getnquadpoints(cellvalues)\n", + " states = [MaterialState() for _ in 1:nqp, _ in 1:getncells(grid)]\n", + " states_old = [MaterialState() for _ in 1:nqp, _ in 1:getncells(grid)]\n", + "\n", + " # Newton-Raphson loop\n", + " NEWTON_TOL = 1 # 1 N\n", + " print(\"\\n Starting Netwon iterations:\\n\")\n", + "\n", + " for timestep in 1:n_timesteps\n", + " t = timestep # actual time (used for evaluating d-bndc)\n", + " traction = Vec((0.0, 0.0, traction_magnitude[timestep]))\n", + " newton_itr = -1\n", + " print(\"\\n Time step @time = $timestep:\\n\")\n", + " update!(dbcs, t) # evaluates the D-bndc at time t\n", + " apply!(u, dbcs) # set the prescribed values in the solution vector\n", + "\n", + " while true\n", + " newton_itr += 1\n", + " if newton_itr > 8\n", + " error(\"Reached maximum Newton iterations, aborting\")\n", + " break\n", + " end\n", + " # Tangent and residual contribution from the cells (volume integral)\n", + " doassemble!(K, r, cellvalues, dh, material, u, states, states_old)\n", + " # Residual contribution from the Neumann boundary (surface integral)\n", + " doassemble_neumann!(r, dh, getfacetset(grid, \"right\"), facetvalues, traction)\n", + " norm_r = norm(r[Ferrite.free_dofs(dbcs)])\n", + "\n", + " print(\"Iteration: $newton_itr \\tresidual: $(@sprintf(\"%.8f\", norm_r))\\n\")\n", + " if norm_r < NEWTON_TOL\n", + " break\n", + " end\n", + "\n", + " apply_zero!(K, r, dbcs)\n", + " Δu = Symmetric(K) \\ r\n", + " u -= Δu\n", + " end\n", + "\n", + " # Update the old states with the converged values for next timestep\n", + " states_old .= states\n", + "\n", + " u_max[timestep] = maximum(abs, u) # maximum displacement in current timestep\n", + " end\n", + "\n", + " # ## Postprocessing\n", + " # Only a vtu-file corresponding to the last time-step is exported.\n", + " #\n", + " # The following is a quick (and dirty) way of extracting average cell data for export.\n", + " mises_values = zeros(getncells(grid))\n", + " κ_values = zeros(getncells(grid))\n", + " for (el, cell_states) in enumerate(eachcol(states))\n", + " for state in cell_states\n", + " mises_values[el] += vonMises(state.σ)\n", + " κ_values[el] += state.k * material.H\n", + " end\n", + " mises_values[el] /= length(cell_states) # average von Mises stress\n", + " κ_values[el] /= length(cell_states) # average drag stress\n", + " end\n", + " VTKGridFile(\"plasticity\", dh) do vtk\n", + " write_solution(vtk, dh, u) # displacement field\n", + " write_cell_data(vtk, mises_values, \"von Mises [Pa]\")\n", + " write_cell_data(vtk, κ_values, \"Drag stress [Pa]\")\n", + " end\n", + "\n", + " return u_max, traction_magnitude\n", + "end" + ], + "metadata": {}, + "execution_count": 14 + }, + { + "cell_type": "markdown", + "source": [ + "Solve the FE-problem and for each time-step extract maximum displacement and\n", + "the corresponding traction load. Also compute the limit-traction-load" + ], + "metadata": {} + }, + { + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + " Starting Netwon iterations:\n", + "\n", + " Time step @time = 1:\n", + "Iteration: 0 \tresidual: 1435838.41167605\n", + "Iteration: 1 \tresidual: 118655.22430368\n", + "Iteration: 2 \tresidual: 59.50456058\n", + "Iteration: 3 \tresidual: 0.00002560\n", + "\n", + " Time step @time = 2:\n", + "Iteration: 0 \tresidual: 159537.60129725\n", + "Iteration: 1 \tresidual: 1706974.26597926\n", + "Iteration: 2 \tresidual: 97346.48157049\n", + "Iteration: 3 \tresidual: 37.17532011\n", + "Iteration: 4 \tresidual: 0.00001524\n", + "\n", + " Time step @time = 3:\n", + "Iteration: 0 \tresidual: 159537.60129701\n", + "Iteration: 1 \tresidual: 3033614.51718249\n", + "Iteration: 2 \tresidual: 183762.82986491\n", + "Iteration: 3 \tresidual: 187.23777242\n", + "Iteration: 4 \tresidual: 0.00023135\n", + "\n", + " Time step @time = 4:\n", + "Iteration: 0 \tresidual: 159537.60129742\n", + "Iteration: 1 \tresidual: 3668226.41190261\n", + "Iteration: 2 \tresidual: 85645.15221552\n", + "Iteration: 3 \tresidual: 33.39133787\n", + "Iteration: 4 \tresidual: 0.00002312\n", + "\n", + " Time step @time = 5:\n", + "Iteration: 0 \tresidual: 159537.60129764\n", + "Iteration: 1 \tresidual: 4942707.09611024\n", + "Iteration: 2 \tresidual: 822244.81049667\n", + "Iteration: 3 \tresidual: 2806.49948363\n", + "Iteration: 4 \tresidual: 0.04196782\n", + "\n", + " Time step @time = 6:\n", + "Iteration: 0 \tresidual: 159537.60129723\n", + "Iteration: 1 \tresidual: 6350622.82330476\n", + "Iteration: 2 \tresidual: 1433617.64531907\n", + "Iteration: 3 \tresidual: 11917.22662334\n", + "Iteration: 4 \tresidual: 0.96519065\n", + "\n", + " Time step @time = 7:\n", + "Iteration: 0 \tresidual: 159537.60130042\n", + "Iteration: 1 \tresidual: 7442093.81842929\n", + "Iteration: 2 \tresidual: 2293366.32653456\n", + "Iteration: 3 \tresidual: 27806.00144416\n", + "Iteration: 4 \tresidual: 4.78936691\n", + "Iteration: 5 \tresidual: 0.00002337\n", + "\n", + " Time step @time = 8:\n", + "Iteration: 0 \tresidual: 159537.60129787\n", + "Iteration: 1 \tresidual: 7898429.46749798\n", + "Iteration: 2 \tresidual: 2166408.36902476\n", + "Iteration: 3 \tresidual: 19078.14976325\n", + "Iteration: 4 \tresidual: 2.12913739\n", + "Iteration: 5 \tresidual: 0.00003700\n", + "\n", + " Time step @time = 9:\n", + "Iteration: 0 \tresidual: 159537.60129718\n", + "Iteration: 1 \tresidual: 9113096.78354406\n", + "Iteration: 2 \tresidual: 1942261.17847130\n", + "Iteration: 3 \tresidual: 14972.09948485\n", + "Iteration: 4 \tresidual: 1.53213288\n", + "Iteration: 5 \tresidual: 0.00003588\n", + "\n", + " Time step @time = 10:\n", + "Iteration: 0 \tresidual: 159537.60129681\n", + "Iteration: 1 \tresidual: 9810716.23843057\n", + "Iteration: 2 \tresidual: 1947382.98912119\n", + "Iteration: 3 \tresidual: 34190.85171497\n", + "Iteration: 4 \tresidual: 4.44141634\n", + "Iteration: 5 \tresidual: 0.00005322\n" + ] + } + ], + "cell_type": "code", + "source": [ + "u_max, traction_magnitude = solve();" + ], + "metadata": {}, + "execution_count": 15 + }, + { + "cell_type": "markdown", + "source": [ + "Finally we plot the load-displacement curve." + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "Plot{Plots.GRBackend() n=1}", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAlgAAAGQCAIAAAD9V4nPAAAABmJLR0QA/wD/AP+gvaeTAAAgAElEQVR4nOzdd3gU1foH8HdmN72HkCoJEAgBadI7kQRCt4EgIEpRQH5SvAKCICiCBfTiFQVpKoJUxVADJPTeO5iQhPTeSN3dmTm/PwaWJQlJgGx2N/l+nvvcJ3v27OzZMcw3M3POuxxjjAAAAGor3tADAAAAMCQEIcBzmT9//rhx49RqtaEHQjdv3pwwYcKmTZu0LX/++ee4ceNu3LhR5e81d+7ccePGiaJY5VsGqH4IQjB2p06dMq+EmTNn6nUYq1ev/vPPP0u3//333+vWrRMEQa/vXhnx8fGrVq06efKktuXUqVPr1q2Lj4+v8vfavn37unXrJEmq8i2blry8vFWrVu3evdvQA4HnojT0AAAqYG9v37VrV92WM2fOFBcXt2vXztbWVtvYoEEDvQ5j0qRJnp6eI0aMKNHeoUMHFxcXhUKh13d/Nn5+fgEBAXXq1DH0QGqsjIyMCRMm9OrVa+DAgYYeCzw7BCEYu+bNmx8+fFi3pWHDhjExMatXr27durWhRqW1du1aQw/hiaZMmTJlyhRDjwLA2CEIoSaIjo7Ozs5u2rSplZXViRMnrly5wvP85MmT5Wfj4+MvXLgQHx/PGGvUqFFgYKClpWWZ27l169aZM2cyMzPd3NxatmwpB21WVlZMTAxjTK1WX7x4Ue5pb2/fuHFj+SVFRUVt2rThOE53U9euXTtz5sz9+/e9vLyCgoLq1q2r+2xqampCQkK9evVcXV1v3bp19OhRlUrVqlWrgICAEtspx+3btw8fPiwIQuvWrbt37166Q3x8fFpaWuPGje3t7bWNCQkJ586di4+PVyqVbm5uHTt2rFevnvyUIAhXr161sbHx9/dPS0vbv39/WlpagwYN+vXrZ2VlVeF44uLiLl68KO/nxo0b9+rV60n7+ebNm2fPns3MzHR3d2/ZsmWrVq1KdMjMzAwPD09ISLCysurYsWObNm10n71//35kZGTdunW9vb1jYmLCw8MLCgrat2/fpUsXuUNRUdGePXtiY2O9vLwGDBhgZ2dXegzp6enh4eGJiYk2NjadO3cuMYacnJyoqCg3N7cXXnghNjb24MGD9+/fb9KkSXBwsFL54LCZnJws33/Ny8vT/mLIo6pwX4FxYQCmRr4KevnyZW3L0KFDiWj37t2dO3eWf7EtLS3lpzp16lTid97Nze3gwYMlthkfH9+rV68SPfv27csY27BhQ+l/OMHBwfILmzdvTkQFBQXaTWVlZfXv31+3s5WV1ZIlS3Tf7rvvviOi//73vx988IFuz6CgoMLCwgr3gCiKU6ZM0Y3MHj16yLcwJ0+erO0m/ymwd+9ebctnn31W+iruunXr5GfT0tKIqH379hs2bNBNvvr16+vubcZYkyZNiEitVmtb2rdvX2Kz7u7u4eHhJUYeFxcXEBBQoueAAQO0HSRJWrhwYYncDQ4OzsrK0vbZt28fEU2cOPHLL7/k+UcTHUaMGCEIwokTJ9zc3LSN9erVu3v3bom9N3fuXAsLC923GDRoUG5urrbP33//TUTTp09ftGiR7h5r3bp1Wlqa3GfBggVUiu7+B1OBIATT86Qg9Pb27tChw++//37q1Kk//vhDfuqll15avHjxoUOH/v3337Nnz86bN8/S0tLW1jYuLk778szMzPr16xPRq6++Gh4eHhUVdfz48W+//Xb48OGMseTk5IMHD/I8X7du3YMPXbp0SX5tiSAUBKFHjx5EFBgYeOzYscjIyPXr17u6uhLR8uXLte8oB2GDBg28vLzWrl174cKFHTt2+Pn5EdGCBQsq3AOLFi0iIj8/v71798bFxYWHh7dq1crT07P8IDxw4AARNWnS5J9//omOjo6IiAgLC5s2bdqWLVvkDnIQuri4WFpafv7555GRkbdv3/7Pf/4jp1pmZqZ2y6WDsFWrVl999ZV2P8tJY29vn5CQoO2Tnp7u4+NDRK+99tqhQ4fk/fzNN9+MGDFC22fOnDlE5O/vv2nTplu3bh0/fnzYsGFE1KtXL0mS5D5yEPr4+Dg4OCxfvvz8+fM7duyQ/wt+8cUXTk5OEyZMOHTo0MmTJ4cMGUJEffr00d1706dPJ6LmzZtv3bpVPh1/7bXXiKh///7aPnIQNmjQwMHBYdmyZefOnQsNDZX/qHrnnXfkPlFRUX/88YecjtpfjNu3b1f4nw+MDYIQTE85Qah7ZvYky5YtK5E306ZNI6JRo0ZpD7WlKRSKevXqlW4vEYTbt2+Xw6a4uFjb58SJE0Tk5OSUn58vt8hBaG1tHRsbq+125coVjuOaN29e/vhzc3NtbGzMzMyioqK0jampqfLUoXKC8JNPPiGiHTt2PGnLchAS0cyZM3Xb3333XSKaN2+etqV0EJYmf8aFCxdqWz788EM5SJ60n//991+e5729vXNycnTbBwwYQEShoaHyQzkIOY47ceKEts/+/fvlwU+dOlXbqFKp3N3dOY7TbvDq1ascxzVq1CgvL0/3LeTrAUePHpUfykHI8/yZM2e0fVJTU62srGxtbbXjj46OlkO6nP0Axg/LJ6DmmDJlirW1dYXdXnnlFSI6d+6c/JAxJq+9W7RoUeXvzz2JfAD96KOPdK+8de3atUePHtnZ2YcOHdLtPGzYMN37Sa1atXJzc4uJiSn/LQ4cOFBQUPDqq682bNhQ2+jq6jp69OjyX+jk5ERE8pG9nG48z8vnTFrySaH80SrvGfbzxo0bJUn68MMPHRwcdNsnTZpERHv37tVt7Nixo+504h49esib1R28ubl5p06dGGP37t2TWzZs2MAYmz59uu6U4ye9RY8ePTp27Kh96Orq2rp16/z8/PT09MrsATAVmCwDNUezZs1KNyYnJ3/zzTeHDx9OTEzMzMzUtmdkZMg/pKSkpKam1qlTp0rmONy6dYuIXnrppRLtbdu2PXbs2M2bNwcNGqRtlK+F6nJzc0tJScnLy7Ozs8vJydm5c6fus4GBgV5eXvJblJ5gUuEc2jfffHPhwoXffPNNSEhI//79AwICAgMDS//p4Obm5u7urtvSrFkzc3Pz27dvS5Kke09OV1JS0tdff33kyJHExMSsrCxtu3Y/JyYmZmRkuLq6enl5PWmEly9fJqLz58/LJ68lNqINM1mJvWdpaWlnZ6dWq0v8d5SnKWlPduW3OHnyZFxcnG63lJSU0m8hn/jqku8+pqamype7oWZAEELNUWJmJhElJCR06NAhJSWlQ4cO7777rpOTk1KpLCoq+vzzz7VVUe7fv09EHh4eVTKG/Px8Iip9lJQPoHl5ebqNpUNIjhn5jC0hIeGdd97RfXbPnj1eXl7yW5T+sBUemuvXr3/27NnPPvts375933///ffff29lZTVhwoQvv/zSxsZG2630lnmed3FxSUpKKigoKHMGZlxcXMeOHVNTUzt27DhmzBhnZ2eFQlFYWPjFF1881X7Ozs4motDQ0NIzepycnEo0lrn3rKysSpxuyrtUu/Y/JyeHiPbs2VM60Sv5Frpbg5oBQQg12dKlS5OTkxcuXDh37lxt4507dz7//HPtQ/kqXFJSUpW8o5wTqamp8qwQLfmEQ3cZQ4Xq169fomSJPDNTfgvtKY5Wampqhdts1qzZ9u3bi4qKzp49e/DgwbVr1y5btiw/P3/16tXaPqW3LElSenq6QqHQzUtdS5YsSUlJWbx48ezZs7WNN2/e/OKLL7QPK7Of5Y+2ZcuWvn37VvhZno18RTQkJKRnz556egswObhHCDXZ1atXiWj48OG6jZcuXdJ96O7u7uHhIS8WLGdTZmZmlamjJs+duXDhQon28+fPE1GLFi0qN3AiIltb2wGPk8/V5LeQL/HpKvG5ymFlZRUQELBo0aKLFy8qlcq//vpL99nU1NTExETdluvXr2s0mmbNmj3pumhl9rOnp6ebm1t6enqJa5K65EvKulXiqlzVvoWZmRkRGUOBPXgeCEKoyeTkiI2N1baoVCp57YEueZrJ7Nmzy5lF4uXllZmZWVRUVP47yvP1ly1bVlxcrG08evToyZMnXVxcXn755af/ECX17t3bzs5u586dkZGR2saUlBR5Kn855GuqulxcXMzNzVUqle4HZ4x9//33ut2WLl1KDz9amcrcz4sXL9btw3Hc22+/TUSffPLJk/bz6NGjFQrFypUrdTclkySpsLDwyR+ust59912O43788cfS56aiKD7tW7i7uysUioSEhOcfGBgQghBqMjl43n///b///jsiIiI0NDQwMFA3omRz5szx8/PbsmVLv3799uzZc/PmzUOHDi1evPiNN97Q9mnXrp1arX7ttdf++9//rlq1qsT0Qq2BAwcGBQVFRkYGBwcfOHDg5s2bq1atkrfz1VdfVaY+S4VsbW3nz58vCEJwcPBff/0VGRm5a9euXr16ubi4lP/CiRMnBgUFrVmz5vjx45GRkYcOHXrjjTcKCwuHDh2qe1/N1dV1xYoVs2fPvnbt2qVLlyZPnrxhw4YXXnhh6tSpT9qyvJ/Hjx+/Y8eOiIiIffv29erVq/Q3csydO7dRo0abNm0aMGDA3r17b968GR4evmjRInn1CxE1adLk888/z8jI6Nix49KlS48cOXLt2rVdu3YtWLDA19e3xJzbZ9OyZcs5c+akpKS0b9/+v//979GjR69duxYSEjJv3rwGDRqcPn36qbamVCpbt24dHR09cuTI//3vfyWKnoPJMNS6DYBn9qR1hOfPny/RUxCEkSNH6v7Ct27dWr6o2LZtW92eycnJ8mI1XW+88Ya2Q3x8fGBgoLa8VjmVZe7fv689ssvs7Ox++ukn3beT19j98MMPJQYsX7jTLXFSJkmSPvnkE90LlUFBQVu2bKFy1xHOnj1bvpSnxXHcsGHDtCvqtJVltm3bpns70M/P7+bNm7oDKLGOUKPRvPXWW7pbbtOmjVx1rEOHDrovTEpK6tevX4n9/Oabb+r2WbVqVemJP61atbpy5YrcQVtZpsRucXR0dHJyKtE4YcIE0lmDKPvxxx9L1yJv06bNrVu35A7ayjIltvb6668TkXYkjLErV660bdtW+5cEKsuYIo7hG+rB1Ny7d0+j0fj4+Jibm8stqampBQUFXl5eJepmyW7evHn16tWioiJ/f3+5Btu9e/csLCxKz+O/e/fu2bNn8/Pz69at26JFC7maqC5RFFNSUlQqlZWVlTwBMi4uTqVSNWrUqMRkxaioqNOnT+fn53t6evbs2bPEwrjc3NzMzMw6deqUaE9MTFSpVPXr13/S3ThdMTExx44dEwShefPmHTt2LCwsTElJsbe3154apqen5+TkeHl5aWc/FhQUXLx4MS4urrCw0NPTs1WrVtpCo3J/V1fX9u3bnzt3Ljs7OywsLCsrq0GDBgEBAdpdLYuNjVWr1SX2z40bN65evapSqfz9/eXVe7GxsWXu58jIyHPnzsn7uWXLlo0aNSrRobi4+MyZM9HR0ZIkeXh4vPjii3LhGFnpTyqTFz/o9iSijIyM+/fve3h4lDgdLyoqOn36tHxjWC55qrsrCgoKUlNTHRwcSuSl/Jv2wgsvlNgharU6NTVVo9GUHhUYPwQhADygG4SGHgtA9cE9QgAAqNUQhAAAUKthQT0APGBjY/P1119XVZEdAFOBe4QAAFCr4dIoAADUaghCAACo1RCEAABQqyEIAQCgVkMQAgBArYYgBACAWg1BWNKdO3c2b95cyc5YfFIa9klp2CclyJWODT0K44J9Ulq17RMEYUlXrlwJCQmpTE/GWJV8QVpNIopihd/YV9sIglD6i59qOUEQVCqVoUdhXDQajUajMfQojItara6eLz2uRUG4b9++dg/5+vqmpKQYekQAAGB4tSgI+/Xrd+HChQsXLqxYscLV1dXd3d3QIwIAAMOrRUGotXbt2rFjxxp6FAAAYBRqXRAWFRXt3r172LBhhh4IAAAYhVoXhNu3b+/du7e9vb2hBwIAAM+i/T+C8x+aqPtVNqFUX0F46dKlb7/9dtSoUb/99tuT+oSGhvbr1y8gIGDlypXaxoKCghkzZnTp0mXEiBGRkZGVea/U1NTVq1dPmDBh0qRJuu2FhYWzZs2SNxURESE34rooAIBJu6+hbBWJVbewQl/fR7hjx47U1NSoqCgXF5cyO9y4cePNN99cs2aNu7v76NGjbW1tR40aRURTp05NSEj48ccfd+7c2bt378jISDMzMyJijHEcp7sFbcuVK1cOHjxoZWV1+PBh3Q7Tp0+PiYn58ccfd+/eHRQUdPfu3cTExJSUlG7duunpUwMAgMnR7/cRfvDBB+bm5suWLSv91IcffqjRaORzwbVr165evfrMmTPZ2dmenp43btzw9fUloqZNmy5atOj1118noiFDhowYMUL+mYjmzJnj4OAwa9Ys7QYPHjw4bty4uLg4+WFOTo6np+eVK1f8/PyIqHnz5vPnz+/Xr19hYaGrq2s5Y/7ll19WrFjx5ptvyg95nn/rrbc8PT1L95TXEdrY2Dz9jqmxRFFUqVTW1taGHogREQRBo9FYWVkZeiBGRKPRiKJoaWlp6IEYEbVaTUTm5uaGHogRUalUPM/L50JE1G4nReWTIJFKImJkb0YKnojo9ABqaPfEjSiVyhInUWX0qbIhP6UrV66MGzdO/rlTp06TJ09mjN25c8fW1lZOQSLq2LHj5cuX5fD78ssve/furVarhw8fPm/evNDQ0LCwsHK2HxERYWlpKaegdlNDhw61tbUtf2AqlUqlUmVlZWlbcnNz3dzcSvdkjImiKIpipT90zSc+ZOiBGBHsk9KwT0qT9wb2iS5RFCPv07ls6WQadyqNi8p77Nn7D8sPqIXydptCoTDeIExLS3N0dJR/dnZ2VqlUOTk5qampTk5O2j7Ozs6pqanyz/7+/nv37g0ODt62bVtsbGx4eLhuz9LK2VT5XF1dW7duvXTp0gp7MsYkScJftbpEUeQ4DvtElyAICoUC+0SXQqHAGWEJPM8TzgiJREZXM9nxFHYilR1P5lOLH2WYnRl1qMt1cuV/i5QSC9j5V5S+9hwR2ZubKSpIugoYLAjt7Oy09ckKCgp4nrezs7O3t9ctWlZQUODg4KB92KJFi/79+69du3bdunXlpyARlb8pAAAwEhqJrmWxsER2IlU6mcqyH1Xf49ysqH1drpsb39WN6+jKmfFERNtiJCKyNycni6oZgMGC0MfHJyoqSv757t27Xl5eSqXSx8cnLS0tLy/Pzs6OiKKiol599VXtS+bNm3fp0qWTJ08OGzbMyspq+PDh5W8/IyMjNzdXzr+oqKj+/fvr8wMBAEBl5WnobNqD5DuRwop1rm16WFM3Nz7Ii2vvpHnRmTc303tOVWsQZmZmrlq16qOPPrKwsHjrrbfmzJkzbdo0W1vblStXjhgxgoh8fX3btGmzevXqjz766MaNG2fOnNmwYYP82rlz54aGhspXRPft2xccHGxpaakbkyXUr1+/Q4cOq1atmjFjxs2bN0+ePPnrr79W0+cEAIBSUorofLp0MpWFJbLLmUx6OFNTwVEzR66bO9fVjevpwfnYPrjQqVJRmZc8z7+qFCWyr8KryEw/5s+fr/suixYtYozdvn2biHJzcxljgiCMGTOmbt26Pj4+3bp1y8rKkl944cIFb2/vpk2bOjk5rVy5UrvBbdu2afswxq5fv3769Gn556tXr+q+V7t27eT2S5cu+fj4yJv6+eefKznyTZs2DR8+vDI9JUnKz8+v5GZrCUEQCgoKDD0K46LRaAoLCw09CuOiVquLiooMPQrjIk/TM/Qoql7Ufen3CPH940KzbRpardb+T7lW3XaHZsopYWu0mFlc9muLi4vVanU1DFK/yycqlJGRoVKpvLy8dBtFUYyLi3N1dX3+lQmiKMbHx7u4uFQ4WVRr8+bNISEhmzZtqrAnw/KJUrB8ojQsnygNyydKqzHLJ0RGd3KYfMHzSDKLL3gUMbZm1MmV6+rGdXPju7lzlooKNlVi+YT+GOweoazM5fYKhaJBgwZVsn2FQlG/fv0q2RQAAJSpUKBLGexkKjuRKp1IYTnqR0+5W1G7UrNdjI2BgxAAAEyR7myX4ylMpTPbpaEd19XtwT2/Zk4VLeIzAghCAAColORCks/5TqY+cbbLy55cPRvjz77HIAgBAOCJovOYnHwnUtitnEc3/Mx4eqkOF+T14J5fVS3pMwgEIQAAPKKd7RKWyI4kS+nFj56yM6OOTzPbxVQgCAEAajvd2S7HU1iuzmwXD2tq68LJK9xfqsPxJnbVs1IQhAAANdNXV6Ul18TZrRQzWpYxWfO+hs6lsbAk6UQKu5DxxNkuLzrVxOh7HIIQAKBmKhZYtoqKdBIuqfDB3b4nzXYJ8uICPPi6tWyFJ4IQAKAmy1ax9ZHSyVR2MJHF5D2a7WKtpJfqPDjt6+7OO5r8Uv5nhyAEAKhRvroqfXNV1EgkX+1cdkPSPlXHgrq6893duW5uXFsXI13eXv0QhAAANUF6MYUnSuFJbHuMpDvbRWtyM/7HLs/5zX01E4IQAMBUFQp0PIWFJUphSexq5qPK0S4W1N2dLxJZaAL7pJViZkueiKyUZX+ZAyAIAQBMicjoSiYLS3ww4VP7TX5WSurqxgV5PlrnMP+iGJrArJRV9gW2NRWCEADABETnsbBEFpbIwpOkrIff4c5z1NaFC/Ligjxr1Ar3aoYgBAAwUunFdCRZCktkBxLZPZ0Jnw3tuCAvLsiLC/TknZ98tvdJK8W05gorHOYrgj0EAGBEigQ6mcrCkqQSX+PuYkkve/BBXlwfL66+XaVu9lkpCSlYGdhJAAAGJjK6lEmHkrnDqUL5t/1AHxCEAACG8fhtPznlmAK3/aodghAAoPo86bZfA1sK9KQ+9RTl3/YDfUAQAgDoV6FAp8q67VfXkgIe3vbztNAQkbk5ar0YAIIQAKDq6a72O57y6LsdrJXUpazbfuqyasFA9UAQAgBUGe1tv7AkKfvhaj/d237d3TkL3PYzMghCAIDnor3ttz+BxeaXsdovyJNHbRdjhiAEACjbrxHSl5elMX783JdK3rrTve13KeNRkU/tbb/gFzgfWyx3MA0IQgCAsuWqKTqPZaoexFxlbvu1ceGQfiYHQQgAUJ5cNa26I+G2Xw2GIAQAeMyvEdLCy1KRQDlqJj/8NeLBU/6OXJAnF+TFBXjwDrX4K91rGAQhAMADWSraEy/9cEOK0VnqrjW+Cb+6O079aiAEIQDUdtF5LCSW7YyVjqcw8WECvujM1bGgY8lsjB839yUFETmY4/ZfzYQgBIBa6mY22xYj7Y5jFzMepJ+Co65u3CBv/vX6XGMHbtkN6Viy6GDONazctz2AiUIQAkAtUizSiRS2K0766x5LLHiQfzZKetmTG9qAH+zDO+LOX+2DIASAmi9LReFJ0q5YFhIr3dc8aPS25fq+wA305oJf4Mus8TnGjx/sw+GKaI2HIASAGuteHjuQyHbFSfsTmEZ60NjMkRvkww2sx3d1r2DNn4M57gvWCghCAKhpnnTzb2gD/vUGXD0bZBs8BkEIADUBbv7BM0MQAoAJe7abfwC6EIQAYHpi8tjOWLY7Xjqa/Cw3/wB0IQgBwGTg5h/oA4IQAIxa+Tf/XvFBzU94XghCADBGZd7887HlgnHzD6oaghAADGDIEcXtXGFnH8WLTo9dz8TNP6h+CEIAMIDEQhadR9rvtsXNPzAgBCEAGMzZNPZ75GM3/5wsKMiTH+jN4eYfVBsEIQBUn8EHxJvZrFCgtGKOiD449eCU0MuGe6M+N9iH7+nOKXHzD6oXghAAqklCAbuQwZILy/jO2529FW1ccP0TDANBCAD6pZZoV6y0NkI6kPDga2+9bEgtUnoxaSfLeFkjBcFgEIQAoC//5rJfI6RfI6S0IiIiCwW97s2/3Zjr7S523s3Sizkva3znLRgeghAAqliRQLvjpVV3pPBEJl8GberIvdOYH9uEr2tJRKTRiOVuAKBaIQgBoMpczGCr7kiboqQ8DRGRvRm94sOPbswHeZU87fsrQCIzC1wRBWOAIASA55Wtom0x0s+3pKtZDybCtHXh3vfnR/jytmZlv8TTmiwtkYJgFBCEAPCMJEaHktiqO1JIrKSWiIjcrejNhvz4JnwLZ4QcmAwEIQA8tYQCtvEuW3lHupfHiIjnKMiLe9+ff9WHN8MqQDA1CEIAqCyVSDvjpPWR0r74BwshGjtwI3y5MX68jy1OAcFUIQgBoGK3ctj6SGntv1JGMRGRpYJe9+bf9+cDvVAFG0weghAAnui+hjZHSesjpZOpD2bBNHPkRjfmx/vzdSwMOzSAKoMgBIAyyAshNt6VCgQiIgdzGtaQn+DPoxAa1DwIQgB4JLmQ1kdKa/6V7t5/MAumqxs3ujE/qhFvjaMF1FD41QaodSaeEC9ksJXdFO0ent6JjA4nsVV3pH9iJfnrcD2tubcbc+814X3tcQoINRyCEKDWichlFzOYXPwlIpf9GSX9GsHi8hkRKR4uhHjNh8fXIUEtgSAEqKWOJrPFV0RtOdAmDtwYP36MH+9qZeCBAVQzBCFAbSFfERUZ3cpmRPT5JZGIeI4Ge/OzWvGdXXEJFGqp2hWEkiSdPXs2ISGhXbt2DRo0MPRwAKrVnRx2MaPkl+JKjKY2RwpCrVaLglCSpDfeeMPBwaFJkyb37t2bMWOGoUcEUE0kRn9GSZH3Hzy0N6P7GtJOlvFzQApCrVaLgnDr1q116tRZs2aNoQcCUK3Ck9jMc+KlDEZETR25z9vyK25Jh5OZnwPXFosCAYhq0bSwo0ePmpmZBQcH9+7d++TJk4YeDoDe3cphb4aLQXuFSxnMy4b7pZvi+hvKoQ1q0b96gMqoRWeE2dnZmZmZe/fujY6O7tOnT1RUlFJZiz4+1CoJBWzhZWntv5LIyNaM/tOCn9VSYfXw931lN0WeBldEAR4wxiTIzc3NzMz08fFRKBRVuCl3d/c2bdqYmVZEF3MAACAASURBVJk1adLEzs4uKSnJ29u7SgYMYDzyNbT0uvjtNalIIDOexjXhF7ZVlFgRgQgE0KWXiyTbtm3jHnf9+vUSffr37699tmHDhtr27777rn79+gMHDmzcuPHNmzcr83ZLly5t3ry5Uqn8z3/+o9u+bNkyeVONGjW6fv36q6++evToUVEU7927l5eX5+Hh8fyfFMB4aCRadUfy3ar5/JJULNDQBvztIcpfupVMQQAoQS9BOHToUPbQ2rVr/f39W7RoUbrbhg0b5D7R0dFyS0xMzPz588+ePXvr1q0xY8ZMnTpV2/nKlSu6r7127ZokSfLPLVu2XLly5fDhw3U7xMbGzp079/Tp07du3XrvvfemTJkSEBAQGBgYGBg4ZsyY9evXm5mZVfHHBjAQRrQtRmq6XZhwQkwros6u3PFByq2BClRHA6gMvV8aXbt27fjx48t8SpKk/Px8W1tbbcuWLVt69erl5+dHRJMmTVqwYEFKSoq7u3t6evqgQYOWLFkip11YWNjbb7999OhRuWefPn2IaP369bob37p1a8+ePf39/Ylo4sSJn332WVJS0kcfffTRRx+VP+CYmJj9+/e3adNG2/Ltt9926tSpdE/GWFFREWMlF2bVZqIoqlQq7d8oQESCIGg0GlEU9bT9sxn8p1cUZzN4IvKzZ3NbCK/Vk4goP19Pb1gF5B0iCIKhB2JE1Go1EZmbmxt6IEZEpVLxPP+cJy3W1tY8X8Epn36DMCIi4vz583/99VeZz06aNGnixIl16tT5+uuvR4wYQUT37t1r3Lix/KyLi4uDg0NsbKy7u3vdunXDw8ODgoIEQfD29n7nnXd27Nghp+CT6G7K2dnZycnp3r17np6eFY7Z29u7ffv2ixcv1rY0b97cwqKM715jjPE8b2NjU+E2aw9RFM3MzKytrQ09ECMiB6GVVdVfoLydw+ZflLbFSETkac3Nb8OP9eOVvAkcSeUgtLS0NPRAjAiCsDQzM7PnD8LK0G8QrlmzZtCgQe7u7qWfWrZsma+vr0Kh2Llz57Bhw1588cVWrVrl5+fXqVNH28fa2jovL0/+2c/Pb//+/YGBgYIghISElHmKpis/P1/3LqDupsqnUCicnZ3btm1bmc4ABpFYwL54OCnURkn/9yL/aWuFHS72AzwTPa4oEgRhw4YNY8eOLfNZPz8/eVLo4MGDu3fvfujQISJydXXNycnR9snOznZzc9M+TEpKYoxZWFjExcVV+O7lbwrAROVr6JurUtPtwqo7Es/R+/581DCzr9sjBQGenR7PCHfv3s0YCw4OrrBnZmamfKewdevWP/zwg9x47do1pVLp6+srPzx27Njo0aN37Njh7OwcFBSk0WhGjRpVzjZbt269ZMkS+eebN28yxho1avRcnwfAoDQS/RohfXZRTC0iIhrozX3fUdEYCyEAnpsezwjXrVs3duxY3UXrP//8szxxJjc3d+7cuWFhYceOHZs4cWJ8fPwrr7xCREOGDElKSlq8ePH58+enTp06duxY+W5TWlraiBEj/v77706dOvn5+R04cGD27Nl37tyRN3vjxo1t27ZFR0dHRERs27ZNbn/99dfT0tK+/PLL8+fPT5kyZcyYMbqzcgCMXL9Qod0/Qnrxg4e74qRm24UJJ8TUIurkyh0bqNzVR4kUBKgS+joj1Gg09erVKzFf1MvLq7CwkIjMzc3z8/O/+uorURSbN29+9uxZV1dXIrK2tg4PD1+4cOHevXt79er16aefyi90dXW9du2as7Oz/NDf3//q1avah1FRUWFhYfK5Y1hYmL29vb+/v5WV1aFDh7744ovQ0NCAgIC5c+fq6ZMC6MO1LEoqZBqJnUmjGefEEymMiJo4cAvb8UMa8AhAgCrEYfZ/CZs3bw4JCdm0aVOFPRljhYWFmDWqS14+gVmjup5t1qjXn0JSIRvoze2OY0TkYklzWysmN6sh3xqPWaOlYdZoaVWyfKIyjLHEGkCt1S9USC8miVFKESOi3XGM58jdijs1WOmDS/sA+oEgBDAi8hVR3RaJUVIhM+MZES6IAugFghDAWDCikY24H24wtUQKjhMZ29dXUdeSIyL5/wFAHxCEAEYhrYjGHhP2xDMiersRH5bEkguppTPnaY0IBNCvGnHnHcDEHUxkrXdo9sQzF0sK6a1YH6BA+gFUG5wRAhhSsUgLLolLrkkSo0BP7veeCi8bjoj29VVoJFwRBagOCEIAg7mdw0YcFq9kMjOe5r3Ef/aSQrtCsKUzIhCgmiAIAQxjfaQ06aRYKFATB+7PlxVtXJB8AIaBIASobunFNO6YuCtOIqK3G/E/d1XYomQ2gOEgCAGqVVgie+eomFTIHMxpZVfFcF9MWAMwMAQhQDXRSLToirjwsiQxetmDWx+geMEGl0MBDA9BCFAd7uSwEYfFy5lMydO8l/h5LymwQgLASCAIAfRuYzQ37bxQIFB9O25jgKKLGzIQwIggCAH0KEdNE47T1hgFYV4MgLHCjXqAqvTlZan3PuFoMiOiQ0ms+V/C1hhmb8Y2BCjWByAFAYwRghCgKl3PZmGJLKmQLbgk9t4nJBawjnXpdH9xZCP8WwMwUrg0ClD1FlwUI+6TPC9mdgsmCYYeEAA8GYIQoAp8eVk6miIR0fl0RkQR98lKSc2duJc9eAUnSoYeHgCUA0EIUAXkK6K6LUUCnU9nqUXsSS8BACPxKAjDw8P/97//VfJlK1as8PT01M+QAEzPxy34lEI6liJxHDFGc1srenpwRNTCmSPCCSGAUXsUhHFxcYcPH27WrFmFrzl79uySJUv0OSoAU5JcSJNPiefTmZ0Zta7DH0+RWjhTkNeDxYICbhACGLfHLo22bt362LFjFb5GoVDobTwAJuZaFht0QIzLZw3tuF3Bis8v4fwPwMQ8CkIfH58ePXpU5jWvvfaanZ2d3oYEYDL2xbPhh4T7Gurixu0IUrpa0dzW/HtN+Bb4NkEA0/EoCHv16tWrV6/KvGb79u16Gw+AyfjhhvTRWVFiNNyX/7WHwlJBRNTCmWth6IEBwFPBrFGApyZINOW0uOK2xBHNb8MvaIObBQAmrLwgzMnJiY6OzsrK0m0MCgrS85AAjFqWioaECYeTmY2S/ghQvFYfJWMATFvZQZienj569OjQ0NDSTzGGdVFQe929zwbuF//NZZ7WXEgfRTsX3AsEMHllB+GECRMuXbr066+/bt++3c3N7dVXX92zZ8/WrVt/+umnah4fgPEIS2RDw4UcNbVy5nb2UXjbIgUBaoIyruowxvbv37906dJ33323bt267u7ugwYNWrly5dy5c7/66qvqHyKAMVjzr9R/v5Cjptfr86cGK5GCADVGGUGYlpZWWFjYvn17IrKwsMjLy5Pb33nnnevXr0dGRlbrAAEMTWT0yXnxveOiRqIpL/LbAhXWmGQGUIOU8Q/awcGB4zg5/7y8vI4fPy63q1QqIiooKKjO8QEYVr6GRhwWd8VJ5jyt6q54pzGmxgDUNGX8q7a0tGzatOmFCxeIKDg4+PDhw8uWLTt27NiECRPs7Oz8/PyqfZAA1WrUEbH3PiG1iBIKWI/dwq44qY4FHeinRAoC1EhlX+KZM2dOZmYmEXXo0OG9996bPn06EVlZWa1evdra2rpaBwhQ7U6msnt57FSqNOmkmFpEjR243X0Ufg64KQhQM5UdhCNHjtT+/PPPP8+bNy8mJqZp06ZOTk7VNTAAAxtxWCwWqbcXtzVQ6Whu6NEAgN6UDMK9e/f+8MMPERERrq6u/fv3nzNnjpmZmYeHh4eHh0HGB1BtRh0R5a8PTChgRFQsUj1bkhipREOPDAD06bEgPHbs2KBBg8zNzf38/KKjoxcsWJCenr58+XJDDQ6gOslXRHVb4vMpPp8VCYwI10UBaqzHbv6vWLHCzc3tzp07V69eTUxMHDZs2Jo1a9RqtaEGB1Cdfu+hGOjNEz1IvQ09FQf7KQ/2U7pZIQUBarLHgjAyMnL06NE+Pj5EZG5u/sknn6hUqtjYWAONDaD6iIx+i5R2x0kWCnK15IioqzsX5MUFeXFWWDUIUKM9FoQZGRlubm7ah+7u7kSUnp5e3YMCqF6CRO8eFX+NkGyUtLuPEuvlAWoV/IuH2k4t0fBD4o57koM57Q1WdnHj/uipKBYJV0QBaomSQbho0SLt7BhRFIlo6NChlpaW2g5RUVHVNjgAfSsU6LWDwoFE5mRBoX2VHepyRNTNHREIUIs8FoTdunVLTU3VbWncuHH1jgeg+hQINPiAcCiJuVnRgX7Kls7IP4Da6LEg3LBhg6HGAVDNctTUP1Q4ncY8rCmsv7KZI1IQoJbCPUKojbJU1DdUOJ/OfGy58P4KX3ukIEDt9WjWqFqt1n7jUvmys7MlSdLbkAD0K7WIAvYI59NZEwfuxCCkIEBt9ygIN27cOGDAgMq8xsXF5e7du3obEoAexeWz7ruF61msmSN3aIDiBRukIEBt99il0aKiotu3bxtqKAD6di+PBe4Vo/NYGxduf1+li2XFLwGAGu+xILxw4UKzZs0MNRQAvbqTw4L2iYkFrH1dLrSv0tnC0AMCAOPwKAiDgoJCQkIq+TIvLy/9jAegKq24LR1OYpOa8a6W1HufkFxIPT24XX2UdmaGHhkAGI1HQVivXr169eoZcCgAVe5COtsWIzV1op9uSpkq6leP+ytQidqhAKALhwSo+ZZck4oEGuTNbwtUWCgMPRoAMDIIQqiB5CuiRHQ8hRFRkUDeNpwFT6fS2MsemCYKAI9BEEINJF8R1W2JK2BxBayfN4cgBIASEIRQA01qxnd05eZeENOLiYgmNuV7eXJE1M4FKQgAJSEIoQbyd+DePy6mF5ObFaUWUfu63NAGfMUvA4BaCUcHqGlERiOPiJczma89F+iJ33AAqMATzwj37dsXEhISHx+vVqt12w8ePKj/UQE8u6mnxZ2xkosl7QtW5Gro1focrogCQDnKDsIZM2YsXbrU3d3dz8/P3Ny8mscE8My+uSr9dEuyUlJIb2VjB9wXBICKlRGEoij+/PPPEydO/PHHH5VK3EQEk7EtRppzQeQ52hCg6OKG/AOASikj5zIyMgoLC8ePH48UBBNyIoWNPiJKjL7vpHi9Pm4NAkBllXG8cHFxcXNzS0pKqv7RADybqPvs9TChWKQJ/vz05khBAHgKZRwyFArFd999N2/evPj4+OofkF5FRUVNnjx50KBBa9euNfRYoMpkFFO//WJ6MQ2ox/3UFSXUAODplH3xMyQkJCkpyc/Pr1mzZs7OzrpPme6s0YyMjMGDB//444/+/v7JycmGHg5UjSKBXjkoROayti7clkClAncGAeApPfEuYKtWrapzHNVg9erVb7/9dv369TUaTdu2bQ09HKgCEqO3j4qnUll9O253sNIGN7UB4OmVfeTYunVrNY+jGty6devOnTsxMTERERFdunRZtGiRoUcEz+vjs+JfMZKDOe3srXC3MvRoAMA01aI/oXme79q167JlyzQajbe396xZs+zt7Q09KHhqe+PZbxFS/3qcSqL/3pDMePorSNnCGZdEAeAZPTEI7927t2zZsqtXryYkJLi7u7do0eL//u//mjVrVpmNSpK0Zs0a7cPmzZt36dKldLezZ8/u27fP1dV11KhR2kwqKirauHFjfHz8yy+/HBAQUJm3U6vVN27cuHbtWpMmTTp37qxt124qICDg5Zdfbty4McdxRGRmZmZvb19QUIAgNEWRuWxbjKSS+D1xEke0prsi0BMpCADPruyJ5hcuXGjVqtXy5cuLi4tbtGjBGFu3bl2bNm32799fmY0KgjBhwoQzZ85cvHjx4sWLZc4+/fvvvwcMGKBUKg8dOtS1a1eVSkVEjLGgoKDt27dbWlqOGjVq9erVlXm74cOHDx06dN68edu3b9c2MsaCg4O3bt1qaWk5evToX3755d133922bdvevXuXLFni7u7u4eFRmY2DcdoXL4mMFrZTjG6MxRIA8HxYWTp06NC0adO7d+9qWxITE7t27ert7S2KYpkv0SWnWl5eXjl9Wrdu/fvvvzPGRFFs1arVn3/+yRg7cOBAvXr1VCoVY2zv3r0+Pj6CIDDGsrKyZs6cqVar5ddKkvTZZ5/FxcXJD4uLixlj77333kcffaTdfnh4uJeXl7yp/fv316tXTxCEyMjIxYsX//TTTwUFBU8a2IYNG4KDgy88dOnSJe37liBJUn5+foV7o1YRBKGcffs89sRJQ8OEoWFCs20aWq2m1eoGmzRDw4Rf/634F9KwNBpNYWGhoUdhXNRqdVFRkaFHYVxUKpV8vAKt4uLiJx1+q1YZl0ZzcnLOnTu3f/9+X19fbaOnp+cvv/zSvHnzf//9t2nTppWJ2I0bN5qZmXXt2rVJkyYlnsrMzLxy5Uq/fv2IiOf54ODg8PDwt95669ChQ0FBQXJ106CgoKSkpKioKD8/P3t7+/j4+OHDh2/evFmpVH744YdXr16dMWOGvDULC4vS737o0KHAwEB5U7169UpLS4uIiGjatOns2bPLH/a9e/fOnTs3fvx4bcvXX39d5qVdxlhRUZEkSaWfqrVEUVSr1aIoVvmWb6Qpt8U89usak89i8pmLmfCGh1Dlb1eFBEHQaDSCYNSDrGYajUYURY1GY+iBGBH56w1Q21mXSqXied7MzOx5NmJtba1QVLC8uIwgLCoqIqK6deuWaHd1dSWiwsLCyrx3hw4dbt68mZ2dPXXq1K+//nry5Mm6z6akpCgUChcXF/mhu7v79evXiSg5Odnd3V1uNDMzq1OnjrycUaFQ/P7778OHDx8+fLirq+utW7dCQ0NtbGzKGUBycrI8YCJSKpUuLi5JSUmViXBfX9/g4OBNmzZV2JMxplAoyh9GbSOKokqlsra2rvItv9aI1Xdmy25Ip1MZEfV+gXuvCU9Eje2VdnZGfY9QDkIrK8xqfUQOQktLS0MPxIggCEszNzd//iCsjDKC0NXV1cXFZe3atcuXL9dtX7t2rbm5eePGjSvcqLm5+dmzZ+WfDxw4MHjw4DFjxugeHHmel09I5dkroijKdU3ldm03URS1SW5mZrZp06YmTZrk5OTExMRUGD/lbApMUWMHbmOUdDqVWfJULFEzR3zXLgBUjTKCUKFQzJw5c+bMmZGRkcOGDfPw8EhPT9+1a9fff/89derUp51p+fLLL6vV6tjYWN2zMQ8PD0mSUlNT5RkrycnJnp6eROTp6RkbGyv3KS4uzs7OltuJiDE2bdq0+vXr16lTZ9y4cZs3by7/zwRPT8/IyEj5Z5VKlZWVpd0UmKId96SFlyUFR6P9+FV3cDkaAKpM2csnPv74YyL66quvDhw4ILfY2trOmjXriy++qMxGBUHQfnNFeHi4hYVF/fr1iSgpKSk/P9/Pz8/R0bFr1647duz44IMPNBrNnj17vvnmGyLq16/fG2+8UVBQYGNjs3v37kaNGjVs2JCIGGNTpky5fv36vn37rKys3n77bfl+YTlZ2K9fvxUrVuTn59va2srzbipzLgvG6VYOe/fog2+WGFiPC/LiGtsb9eVQADAhZQchx3EzZsyYNm3a9evXs7KyHBwcmjdvXvmbHOvXr1+zZk3Lli2zs7P37t37ww8/yK9dvXr1yZMn5XD9/PPPhw4deuPGjevXr7u5uQ0cOJCIunbt2rFjx549e3bu3Hnz5s0rV66Ur51mZmbm5uZq7wv+/vvvkydPjouLk6fz/Pbbbxs3brx9+7ZCobh27dr48eOHDRvWqVOnbt269ezZs0uXLlu2bFm+fLm8KTA5mSoafEC8r6G3Gz34Zgn5G3cBAKoEp3sjraoUFhaeOnXq3r17tra2nTp1kk8HiSgmJiY3N7d169byw+jo6CNHjjg7Ow8YMEB7bieK4v79+5OSkrp16+bv71+Zt4uKioqJidE+bNSokfyOkiSFhoYmJSV17dq1kjNdiWjz5s0hISGVnCxTWFiIyTK6qnyyjEai4H3C4WTW1oU7PlBpZYKlkDBZpjRMlikNk2VKq5JZo5XxKAgTEhKuXbvm7+/fsGHD8PBweS1gaf3799f3mAwLQfg8qjwIJ50UV96WPKzp/CtKLxuTPBFEEJaGICwNQVhatQXhoz+wDx48OHbs2MWLF8+ePXvEiBFpaWllvkAfZ5AAZVoXIa28LVkq6J/eppqCAGD8HgXhq6++2q5dO3kZ39GjR7HWFQzrRAqbdEIkop+7KjrURQoCgL48CkInJycnJyf550renAPQk9h89ka4oJZoZkt+jB/WCwKAHpV9iOnYseOlS5dKNF6+fFm36BqAnhQJ9EaYmFZEwS9wi9ujDAIA6FfZQRgbG1tcXFyisbCw8N69e3ofEdRujOjdY+LFDNbEgdvcS6nANVEA0LOnuOh06dIlNzc3/Q0FgIg+vyRujZbszejv3gpHTKADAP17bFnWunXrFi1aREQZGRlDhw7Vndycl5eXnp7+/vvvV/cAoTb5J1ZaeFniOdrUS9nMESeDAFAdHgtCb2/voKAgIvrjjz/at2+ve/7n4ODQokWL4cOHV/cAoda4lcPeOSJKjL7rqOhfDykIANXksSAMCgqSg1CSpI8//rj09wgC6IluHbWPWmCaKABUn7IrVq1evbqaxwG1mUaioWFC1H3W1oX7pRumiQJAtSr7T+8xY8Z88sknJRqXLVsml8YGqFpTT4uHk5mHNYX0VphiNVEAMGllBKEoips2berZs2eJ9sDAwL179z6p9BrAs/k1QlqBOmoAYDhlBGFaWppKpWrQoEGJ9gYNGjDGEhISqmVgUCucSGETT4hE9BPqqAGAgZQRhDY2NhzHab8pXkteTY8i+lBVdOuojUUdNQAwkDKOPvb29i+99NIXX3yhW1xGFMX58+e7u7v7+flV4/CgxtLWUevjhTpqAGBIZc9M+Pbbb4ODg/39/d96660XXnghJSXlr7/+un379saNGxUKHLPgeTGiMQ/rqG0JRB01ADCksoMwMDDwwIEDn3zyyTfffCN/AWGLFi3++eefV155pXqHBzXTF5ekLaijBgDG4Ylz1Xv16nXu3LmcnJysrCwHB4c6depU57CgBvsnVvrisshz9CfqqAGAEahg0Zajo6Ojo2P1DAVqA906agNQRw0AjMATg7C4uPj48ePR0dE5OTm67bNmzdL/qKBmykIdNQAwPmUH4Y0bN/r161fmkkEEITwbjURDUEcNAIxP2X+VT5gwwcnJ6caNG6NHj/7000+Tk5NXr17t7e194MCBah4f1BioowYAxqmMA5IgCOfPn//nn39efPFFnudFUXR3dx8/fryjo+Po0aPj4+OVShzG4Olo66jtCEIdNQAwLmWcEWZkZGg0mkaNGhGRra1tbm6u3N63b9+UlJQ7d+5U6wDB9J1MfVRHraMrUhAAjEsZQeji4qJUKuXi2vXq1btw4YLcHhcXR0Q8jzkO8BRi89nrYYJaohmoowYARqmMA5NSqezQocORI0eIaMiQIZcuXRo1atT3338/dOhQb2/vxo0bV/cYwWTp1lH7CnXUAMAolf0X+pIlS1q2bElEDRs2XLFixZEjR/7zn//wPL9lyxYzM7PqHSGYKtRRAwCTUPa0ly5dumh/fu+999577z2VSmVhYVFdo4KaAHXUAMAklHFGmJqa6uzsXGKlBFIQngrqqAGAqSjjjNDa2jonJ8fa2rr6RwM1w+2HddSWoo4aABi9Ms4I7ezsgoKCQkJCqn80UANkq7lBD+uo/Qd11ADA6JV9j3DatGnjxo3Lzs4ePHiwh4eH7pKJtm3bVtfYwPQIEo04xkfdZ21QRw0ATMRjQTh+/Pi+ffsOGTJk7Nixqampa9euXbt2bYkXyF9PCFCm6efYsTQeddQAwIQ8dqw6ePCgr68vEW3dulWtVhtoSGCqfo2QVtxmch21F1BHDQBMRNl/tPfo0aOaxwGmTltH7b/txY6uWGwKACYDcxmgCmjrqH3cghvdUDL0cAAAnkLJM8IjR45IUnkHsk8//VSf4wHTo1tHbVFbTsA1dQAwKSWD8MCBA+V/6SCCEHSVqqMmCoYeEgDAUykZhB9//PHEiRMNMhQwRQsvP1ZHTRQNPSAAgKdUMgidnZ3liaMAFfonVvr8EuqoAYBpw1IveEaoowYANQNmjcKzyFKRXEdtFOqoAYCJe+yM8H//+1+jRo0MNRQwFYJEQ8MF1FEDgJrhsSB85ZVXDDUOMCFTz4iHkpi7FYX0Vljj4joAmDhc1IKn82uE9PMtyVJB//RGHTUAqAkQhPAUtHXUfuqq6OiKFASAmgBBCJUV96iOGj/WD785AFBD4HAGlaJbR+3rDpggAwA1B4IQKibXUbuQwfwe1FEz9IAAAKoOghAq9qiOWpDC0dzQowEAqFIIQqhAiE4dtRedcDIIADUNVoFBeW7nsNFHRInREtRRA4AaCmeE8ERZKhr8sI7ax6ijBgA1FI5uUDa5jtpd1FEDgJoOQQhlm4Y6agBQOyAIoQy/RUg/oY4aANQOCEIo6VQqm3hSJKLlXVBHDQBqPgQhPCYun70WJqhE+rgFP64Jfj0AoObDkQ4e0dZR6406agBQayAI4QFGNPb4gzpqW1FHDQBqjVoXhJIk5eTkGHoUxujLy9LmKMkOddQAoJapXUH4448/tmnT5o033pg8ebKhx2JcQmKlBXIdtZdRRw0AapdatEDs7Nmz27dvP3funLk5znceo62j9m0HxUBvpCAA1C61KAi3bNnSr1+/RYsWmZmZTZo0qU6dOoYekVHQraM2o2XtukIAAEC16tJoYmLihg0bgoKC6tat26dPH8aYoUdkeKijBgBQi4LQyclp5MiR3bt3nzBhQkFBQXJysqFHZHioowYAoJcgjIyMHD9+fIsWLV588cVJkyZlZGSU7jNnzpzeD7399tva9hMnTnTr1q1Ro0bjxo27f/9+Zd7u4MGD06ZN69u378qVH1UWlgAAIABJREFUK3XbT5061b1790aNGo0ZMyY3N7dr166JiYlEpFKpcnNz7e3tn+9TmjxtHbUdqKMGALWYXoIwIiKicePGGzZs+Ouvv+7du/fOO++U7nPlypX27dvPmjVr1qxZEydOlBtzc3MHDx48fvz4I0eOZGdnT58+XdtfpVLpvlz34ZUrVxwdHVUqVWRkpLYxLy9v0KBBY8aMOXLkSF5e3tSpU9988824uLjhw4cHBATMmDHD1ta2ij+2SdGto9YJddQAoBbj9H2r7OTJk3379s3LyyvR3r9//5EjR44cOVK38Zdfflm/fv3JkyeJKCIionXr1ikpKfb29unp6Z07d969e7e/vz8RJSYmBgUF7dixQ34oe//99+3s7L777jv54Zo1a9asWXPmzBkiioqKevHFF1NSUhwdHZOSkqytrR0dHZ804OXLl//0008DBw7UtowZM8bX17d0T8ZYYWGhjY3N0+4Tg4svoC57ufRibloz9nXbqvwFEEVRpVJZW1tX4TZNnSAIGo3GysrK0AMxIhqNRhRFS0tLQw/EiKjVaiLCnHZdKpWK53kzM7Pn2Yi5uTnHVfC3vt7vC505c6ZFixZlPvXNN98sX77c399/9uzZfn5+RHTr1q22bdvKz/r5+SkUiujo6NatW9etW/frr78OCgrav39/nTp1+vTpM2nSJN0ULE13U76+vpaWllFRUW3btvX09Cx/wEql0tzc3MnJSdtiZWXF82WcOjPGeJ4v8yljViTQ8KOUXkxBnrS4LcfzVXk6aKL7RK/4hww9ECPC87z8q2LogRgReW9gn+iqtn87+g3Cq1evfvnll6GhoaWfeuedd9zc3CwsLDZt2tSlS5cbN264u7tnZGQ0aNBA28fR0TE9PV3+eciQIYIg9O3b18rKavLkyVOmTCn/rTMyMry8vMrcVPkcHR2bNWs2Z86cCnsyxszMzJ7zr5VqxogmnBAvZkp+Dty2IKVVVf/1yfO8JEmmtU/0Tf5rFPukhOf/S7+GkS/OYZ/okiSpen5P9BiEt2/f7tev36pVqzp27Fj62WHDhsk/dO7c+dSpUzt37nz//fcdHR0LCgq0fe7fv+/s7Kx92L17d47jsrKygoODK3z38jdVa6GOGgBACfo65YyIiOjTp8+33347dOjQCjs7OTkVFhYSka+v7+3bt+XGpKSkwsJCHx8f+WFKSkqfPn0+/vjjlStXBgUF3bx5s/xt6m4qJSUlLy+vfv36z/xxagbUUQMAKE0vQRgdHR0YGDht2rQBAwZkZ2dnZ2fLZ/2hoaE///wzERUWFh45ckQQBMbYX3/9deLEicDAQCJ66623Tpw4ce7cOcbYkiVL+vXr5+LiQkQZGRk9e/acOHHilClThgwZ8v333/ft2zcmJkZ+u6KiouzsbJVKVVxcnJ2dXVxcTETDhw8/ffr0mTNnGGNLly4NDg52dXXVx4c1Fbdz2DtHRYnR1+1RRw0A4BG9XBo9duyYubn5zz//LMceEV27ds3GxubWrVvnz5//4IMPNBrN5MmTIyMjzczM6tWrt379enlCjYeHxy+//DJw4EBBEJo0abJlyxb55c7OzsuXL+/du7f88M0336xbt6522suKFSt++ukn+efQ0NCZM2dOmDDBzc1t9erVgwcPFgTBz89v8+bN+vikpkKuo5arRh01AICS9L58ohyiKAqCYGFhUfqpwsLCqpqC/7Sb2rx5c0hIyKZNmyrsaSrLJwSJgkOFQ0msjQt3fKBSrxVksHyiNCyfKA3LJ0rD8onSqmT5RGUYsqyWQqFQKMqub1mFR1IclKejjhoAwJPhKlkN91uEtBx11AAAngxBWJOhjhoAQIUQhDVWXD57PUxQifSfFvy4JvgPDQBQNhwfa6Yigd4IE1OLqLcX93V7fNEgAMATIQhrIEY09rh4IYP5OXBbeimV+I8MAPBkOEbWQIt06qg5lbE4BQAAHkEQ1jQhsdJ81FEDAKg0LCurUbR11L7pgDpqAACVgjPCmkNbR21kI34m6qgBAFQODpc1hCDRm+HC3fusjQu3qhumiQIAVBaCsIaYfkYMRx01AICnhyCsCeQ6ahaoowYA8PQQhCYPddQAAJ4HgtC0aeuofdSCH486agAATw+HThNWJNCQ8Ad11L5BHTUAgGeCIDRVch218+msgR23CXXUAACeFQ6fpkpbR21XH0Ud1FEDAHhWCEKTtC+eLUAdNQCAqoAVZ6bnTg5767Agoo4aAEBVwBmhiclS0aADYq6ahjbgZ6COGgDAc8OR1JSIjEYeFu7eZy/V4X7rqcDJIADA80MQmpLpZ8TQBOZuRTv7oI4aAEDVQBCajN8jpR9vSuY8bQ1EHTUAgCqDIDQNp1LZhBMiEf3UVdHdHSkIAFBlEIQmIKmQDQ0XUUcNAEAfcFQ1dkUCvXpQTCpkqKMGAKAPCEKjxojGPayj9ufLqKMGAFD1cGQ1aouvSJse1lFzsTT0aAAAaiIEofEKTWDzL6KOGgCAfmExmpG6k8OGHxJERl+3Rx01AAA9whmhMdLWURvSgJ/ZCv+NAAD0CAdZoyMyGnXkQR2131FHDQBAzxCERmf6GXFfPOqoAQBUEwShcZHrqJmhjhoAQHVBEBoRbR21n1FHDQCguiAIjYW2jtr05qijBgBQfXDANQq6ddS+7YA6agAA1QdBaHioowYAYEA46Boe6qgBABgQgtDAUEcNAMCwsE7NkFBHDQDA4HBGaDCoowYAYAxw/DUM1FEDADASCELD+OhhHbUQ1FEDADAoBKEB/B4p/e9hHbV6qKMGAGBQCMLqdjoNddQAAIwIgrBaJRWyIWGoowYAYERwLK4+2jpqQaijBgBgNBCE1US3jtom1FEDADAaOB5Xk69QRw0AwCghCKtDaAL77KLIc7TxZQXqqAEAGBUsYdM7bR21r9orBnnjLw8AAOOC47J+ZevUUZuFOmoAAMYHh2Y9EhmNRB01AADjhiDUI7mOmhvqqAEAGDEEob6sRx01AABTgCDUi9Np7P0TIhH91EXRA3XUAACMGIKw6mnrqE1rzr/njz0MAGDUcJiuYrp11JagjhoAgNFDEFYl1FEDADA5OFRXJW0dtZ2oowYAYCIQhFVGt45ac9RRAwAwEVjdVjVQRw0AwEThkF0FslU0+CDqqAEAmKRadNTetWtX586dmzVr9umnnwqC8Pwb/PuetO4un1pEI48IkbmoowYAYJJqy6XRyMjIkSNHbty4sUmTJiNGjHBwcJg5c+ZzbnPxVXYpQ3k6C3XUAABMWG05I1y7du0rr7wyaNAgPz+/BQsWrFy5sqq2vCkKddQAAExYbTmFuXHjRu/eveWf27VrFxMTU1BQYGNj8wyb+vuelFFMRJRY8KDlLV/+Tg5r5shhyQQAgMmpLUGYnp7u4OAg/+zo6EhEaWlpDRo0KN3z7t27f//9t5OTk/yQ5/k//vije/fu2g5fXDS/mv3YmfT6SGl9JDWzLmrlJOnrA5gIURTVarUoioYeiBERBEGj0VTJbekaQ6PRiKKo0WgMPRAjolaricjc3NzQAzEiKpWK53kzM7Pn2Yi1tbVCUUGRr9oShI6Ojvn5+fLPeXl5RKSNuhJ8fX0HDhy4Zs0abYuDw/+3d+dRTVz7A8BvWMIiBMhCSBAQoRFQFBSwigR4IHEBWdT6RBHcoKXV1tfqO10sjx5P1QpqcX/2PHGpClWRKsgmCgKKIqAWkEWUJUgkRGRNgGR+f9z35peTBERZTe7nr8ydO5dvbiZ8MzN35hqoqf1/5ltpJZnbiQEAEmolb3pBoIUaTQcAAMzJuvp6qn52VCwWi0QiXV3d8Q5kAoGJUEdHZ7wDmUBgItTWRqdQ/h9KhPKIROLwE+FQqEoinDp1amVlJXxdWVlJJpPhcaE8AoFAJBIHSpMAgO8d/psUi/hYMR/7wVFtNlXV8x+CIMiHS1UGy6xduzYhIaGhoUEikRw4cCAkJGS8I0IQBEEmBFVJhK6urlu2bLG3t6fRaJ2dnVFRUcNvc5GxcKn+SzRARhqPx3v48OF4RzGxNDY2Pn78eLyjmFhevHhRVlY23lFMLDU1NVVVVeMdxcRSUVHx/PnzMfhDqpIIAQA7d+5saWmpr69PT08f5Mzn0M3lZ4OzkeYqf11Q2q1btw4fPjzeUUws6enp//73v8c7ioklOTn5zJkz4x3FxJKYmHjx4sXxjmJiOXfu3JUrV8bgD6nKNUJIU1NzBK+7Yhg2Uk0pDdQn8lCfyEN9Ig/1iUJj0y0qdESIIAiCIPJQIkQQBEFUGgEdj8uIjY2NiYmZMWPGW2vy+fzGxkYHB4cxiOpD0dzczOfzh9J7qoPL5ba3t9va2o53IBNIfX29UChksVjjHcgEUltbi2GYlZXVeAcygVRXVxOJRAsLi+E0EhgYGBkZOXgd1bpGOBRr166l0+kmJiZvrSkSiVpbW5lM5hhE9aHo6el58+bNUHpPdXR2dvb09NBotPEOZAJpb2/v6+ujUCjjHcgE0tbWhmHYiIzjUxoCgUBdXR1/KNj7UfgEMRnoiBBBEARRaegaIYIgCKLSUCJEEARBVBpKhAiCIIhKQ4kQQRAEUWlo1OhgGhoasrOzjY2NFy5cqKGhoK+6u7vT0tKEQiGHw5EeAldeXl5YWGhlZcVms8cw3rHQ3NyclZVFIpE4HI6WlpZ8BZFIlJ6e3t7e7u3tjQ8fvX//fnt7O3xNIpFcXFzGLuLRV11dnZ+fb25u7unpSSAoeORed3f348ePNTQ0nJycpMuLi4sfPXo0ffp0JesQAEBdXd3t27fpdLq3t7fC745QKHzy5El/f/+8efPwwry8PKFQCF9TqVQluzfpyZMnRUVFLBbL1dVVfq1IJMrLy+NyuRYWFmw2W3pHKigoqKysnDNnzsyZM8cw3rFQWVlZUFAwZcoUDw8P+e9OX19fQUFBXV0dk8n09PTEpxXMzs6WSP47+aupqekI3JuEIQO4ffu2kZHRhg0b5s6d6+Xl1d/fL1Ohra3N1taWw+GsXr2aTqdXV1fD8vj4eBqNFhERYWNjExERMeaBj6KSkhIymRwaGuru7u7s7NzT0yNToaenx9nZmc1mh4aGUiiUkpISWO7i4uLo6Ojt7e3t7R0ZGTnmgY+iy5cvUyiUzZs329vbr169Wr7CoUOHiEQimUxms9nS5b/88oupqWlERISFhUVUVNQYhTsmbt68SSaTN27c6Ozs7OPjIxaLZSqcP3+eSCRSKJTp06dLl5ubm8+dOxfuJz/88MMYhjzqjh8/TqfTIyIirK2tv/rqK/kKTCZzwYIFYWFh06ZN8/T0FIlEsPzrr7+2srKKiIhgMBhHjx4d26hHV2JiIpVKDQ8PnzFjRkhIiHyFGTNmzJ07NywsbObMmXPmzOno6IDlRCKRzWbD/SQ2Nnb4kaBEOCA2m33w4EEMw4RCobW19fXr12UqxMTEeHl5SSQSDMM+//zzzZs3YxjW19dnamp648YNDMNevXqlp6dXVVU15rGPlqCgoJ07d2IY1t/f7+TkFB8fL1MhPj5+9uzZ8EdDVFRUYGAgLHdxcYF9omQkEsm0adMuXryIYVhbWxuFQikqKpKp09zc3N7efvz4celE2N7erq+vD38oVFdX6+jotLS0jGXko2r+/PlHjhzBMKynp8fS0jItLU2mQktLS1tb26VLl+QTIf7jSZkIhUIajZaTk4NhGJfL1dHRqaurk6lTU1MDX3R1dTGZzMuXL2MY1tDQoK2tXV9fj2FYfn4+hUKR//X5gRKLxVZWVvBtvn792sjIqLS0VKYO3ie9vb02NjYnTpyAi0Qi8eXLlyMYDLpGqNibN29yc3NXrFgBANDS0vL19b1+/bpMnevXry9fvhwezq9YsQJWKCkp6ezsXLhwIQCARqO5ubmlpKSMefijAsOwlJQU2Cfq6uoBAQEK+yQgIACewVixYkVqaip+BqOioiIjI6OxsXGMwx5VVVVVtbW1/v7+AAADA4OFCxfK9wmdTtfX15cphKcN4ak/a2trGxubzMzMsYl5tLW2thYUFCxfvhwAoK2tvXTpUvk+oVKpA90l/ejRo8zMTB6PN+qBjqHCwkICgeDm5gYAYDKZc+fOTU1NlamDP1NGV1eXRCLBCevT0tKcnJzMzMwAAPPnz9fS0rp79+7Yxj5aKioqGhsbfX19AQCGhoZeXl7y+wneJ5qamlQqFfYJVFhYmJ2d3draOiLBoESoWFNTk5qaGoPBgIumpqZcLlemDpfLNTU1xSvweLz+/n4ul8tgMPBz2Qo3/EDx+XyRSCT9lt/aJyKRiM/nAwAmTZqUmpoaGxtra2s7IpNBThBNTU1UKlVb+7+TUg794+ZyuZMnT8YXlWk/aWpq0tTUNDY2hovv9NYMDQ0vXLiwZ88eKyurQ4cOjVqMYw1+L/BrYKampk1NTQNVTkxMbG9vX7x4MQCgsbFRej9hMpnKtJ8YGxsTiUS4OPh+kpWVVV5eDn9dAQBoNNrJkyf/9a9/WVpanj9/fvjBoMEyionFYgKBgO+46urq/f398nXU1NTwCvBgH26I11G44QdKLBYDAN6pTwAAsE5mZiZcLC8vd3JyWrZs2Zw5c8Ys8tHz3h+3zIYaGhrKtJ+89bszkOLiYrif5Ofn/+1vf/P39zc3Nx+tQMfQ0PeTe/fuffHFF4mJifCIWen3E3xxkD4pKysLCQn5z3/+gx+Z1NXVwf3kypUr69at8/Pzkz/p8k7QEaFiJiYmYrEYP+7m8Xj4Z4BjMBivXr3CK1AoFC0tLQaD0dLSgtdRuOEHikajqaur4++Ox+PJP2dVpk/U1dXpdDr4X1IEANjZ2c2cObO0tHSsoh5dDAZDIBDAnwjgXT5u6Y4CA3TmB8rExKS3t/fNmzdw8Z2+Avh+4urqymAwnjx5Miohjjn5j1thnxQXFwcEBMTHx3t4eMASJpOprPsJg8FobW3FL50M1CdVVVUcDic2NhZegIDw/SQwMFAsFldVVQ0zGJQIFaNSqfb29hkZGQAADMMyMzM9PT0BANLZ0dPTE1YAAGRkZMB918HBob+//+HDhwAAoVCYm5sLN1QC6urqbDZb/i1LJBJ8h/bw8MArpKenu7m54bss1N7e/uzZM+X4mQ8AYLFYZDI5JycHANDX13fr1i34cff19b1+/XqQDV1dXWtqahoaGgAAra2tJSUlSnOnDZ1Ot7W1Hfy781ZNTU3Nzc3w2pgScHJyamtrKy8vBwB0dXUVFBTAPhGJRG1tbbDO48ePly5deuLEiSVLluAburu737t3D956VFVVxePxlOZOG1tbWz09vby8PDDwd6empsbLyysqKio4OFhhI+Xl5SKRaAT2kxEceKNkLl68aGxsvG/fvuDgYBsbGzhYq6SkBADQ3d2NYVhjYyOVSv3qq6+io6NJJFJhYSHc8KeffmKxWAcOHFi4cCGHwxnP9zDSMjIyDA0Nd+/eHR4ebmZmJhAIMAyD/80bGhowDBMIBGZmZps2bdq9e7eRkVF6ejqGYZWVlRwOJzo6eteuXdOnT/fy8pIfT//h+vXXXy0sLPbv3+/r6ztv3jw4ivjGjRskEglWKCkpCQ8Pd3NzYzAY4eHh+Mi3yMhIR0fHgwcPfvzxx6GhoeMV/2g4d+4cnU6PiYlZtWqVnZ0dvBPgwYMH8N8chmHV1dXh4eELFy40MjIKDw+HI+Dz8vL8/f137doFr/0ovBflw/Xtt9/a2dkdOHDAw8MjICAAFp46dYrFYmEYJpFIqFSqjY1N+P+kpKTAOkFBQW5ubgcPHpwxY8aOHTvG7Q2MgtjYWEtLy/379y9ZssTNzQ1+d65du0ahUGCFadOmTZkyBe+TxMREDMOSkpJWrVq1e/fu7777zsTEZNu2bcOPBM0+MZg7d+6kp6dTKJSwsDA4PYpAILh8+fKGDRvggU59ff25c+d6e3tXrFghPQlfcnLyvXv3LC0tQ0NDFd51/uEqKipKTk7W19cPDQ2Fpz27urrOnz8fHBw8adIkAACPxzt9+nRHR4e/vz+8f1woFCYlJVVUVBAIhFmzZgUEBODXEZVDWlpabm6uqalpWFgY7ITGxsabN2+GhoYCAF68eIEfJQMApk6d6u3tDQCQSCQJCQmPHj2ys7Nbs2aNzKHzhy4nJyczM5NKpYaFhRkaGgIA+Hx+UlLSpk2bCARCc3Pzn3/+iVdmMpm+vr7t7e1Xr16trq7W1NR0cnJavHixwqcTfKAwDLty5UpRUZG1tXVISAgcJFJdXV1aWrpy5UoMw06ePCld39nZ2dHREQDQ19d39uzZqqoqJycnfJi60khNTc3Ly5s8efL69et1dHQAAA0NDbdv3w4JCQEAnD59WiQS4ZXt7e3nzZvH5/OTk5Nra2t1dXXnz58/IqfcUCJEEARBVJpS/TBHEARBkHeFEiGCIAii0lAiRBAEQVQaSoQIgiCISkOJEEEQBFFpKBEiCIIgKg09axRB3kdGRoaamhq8I3CC6+npOXv2rIeHB4vFGkp9DMMKCwvh/Jrr1q27evWqsbHx/PnzRzywCxcu2NjYwLvlRpBIJLp48SJ87eXlJf3Q6qHLzc19/vw5AGDy5MleXl4jGR8yAQ3/nnwEmVCuXLkyderUqVOnlpWVSZcLBAIWizV16lQ4zeQweXp6+vj4DL+dMdDc3AwAkJ88UiGJRLJkyRINDQ1LS8vZs2djGMZisdavXz/MGKqqqk6cOIFPrArp6+t/9913w2xZHpzwxNra2tXVNT8///0a+fHHH11dXUkk0qJFi0Y2PGQCQqdGEWXT0dFRW1vb1NQUHx8vXX7x4sX6+vra2trBnwI6RBs2bAgLCxt+OxPNgwcPUlNTr1y5UltbCx+ZOyIKCwsjIiJken7nzp1w5s7R8M9//jMvL++9D2Sjo6Pz8vJmzZo1slEhExNKhIhyCggIOHPmjPTELvHx8YGBgQorv3r1SnrOz6FYu3bt6tWr5cs7OjrwiRcggUDw3lPn9PT0wOMb6fa7u7uHsu2bN28EAsEgFdra2uTnv62rqwMASD8vUKHu7u7m5ua+vj6Fa4VC4SBrcdu3b8enWcAJBALp+Vvk8fn8t7Y8CAzDXr16Jd1Cf3//SM3vinygUCJElFNoaGhrayv+kM/y8vL79+/LHMPBGV50dHTodPqkSZNmz56dm5sLV/X19bm7uzs7O+NZp7m52crKCn8K/sqVK/HXt2/fJpPJqampPj4+BgYGhoaG8MmZd+/etbOzo1Ao+DlAWD8mJkZmxpl9+/bhJW1tbWQy+dixYxs3bjQwMKDRaLNmzaqpqamvr/fy8iKRSCQS6ZNPPunq6hrovfN4vMWLFxsZGVEolDlz5vz1118yFZKTk+3s7IyMjExMTMzNzfErasuWLVu/fj0AwNHRkUwmR0dHyzdeVVW1aNEiEonEYDDIZPL27dul03xNTY2fnx9cq62t7ebmJhAITpw48emnnwIA7O3tyWQymUyG6ZbFYu3ZswffNiUlxcbGhkKhGBsbW1lZ/fHHH/iqPXv2MBiMoqIie3t7Go02adKkwMDAzs7OgXpA3qeffjpv3rw///zTzMyMTqcbGRn9+uuvGIb9/PPPZDKZSqWam5vfuXNn6A0iygQlQkQ50el0Hx+f06dPw8X4+Hg7OztnZ2fpOq2trTNmzLh+/XpFRUVaWpq+vr6vr+/Lly8BAJqamr/99ltlZeXWrVsBABKJZN26dV1dXfv378e3xQ8j4MQxERER7u7uhYWFx48fz8zM3LRpU2ho6I4dOx48eBAZGbl79+4bN27A+j09PTInCaVLMAx7/fp1dHS0mpra7du3r169yuPxQkJCgoKCfHx87t+/HxcXl5SUhEciQyKRBAYGFhYWnjt37q+//goODl67dq10hatXrwYFBbm4uNy9e7e4uNjPzy84ODg9PR0AEBUV9eWXXwIAjhw5kpiYuGbNGpnGuVyum5sbn8+/du1aWVnZ3r17jx079s0338C1TU1NCxYsePTo0alTp8rKym7evOns7Nzb27tkyZItW7YAAI4dO5aYmJiYmAjnr29ubu7o6IDb5ubm+vv7m5qa5ubm5ufn29rarlq1KjU1Fa4VCoV8Pn/t2rVffvllUVHR3r17r127tm/fvsH3AWnd3d1Pnz799ttv4+LiCgsL/fz8tm3bFh4enp2dnZSUlJOTQ6VSV69eLf2IZ0SFjO8lSgQZcTD5FRcXJyQkEIlEeCaNwWD88ssv8FRhVFSUwg3b2tqIRCI+TRKGYRcuXAAAnDlzBqalrKwsfJX0YBl43Pn111/ja5cvXw4ASEpKgotisZjJZG7evBku/vTTT1paWtJ/Ojo6Gi+BQXp4eOBrd+/eDQD4/vvv8RJfX19HR0eF7yIrKwvGjJfARAUHy0gkEisrK5lhPmw229vbG74+f/48AOD58+f4WunBMlu2bDEyMmppacHX7tmzh0gkwlEwW7du1dDQqKiokI/q7NmzAID6+nrpQunBMhwOh0ajdXV1wcXe3l5zc3MXFxe4GBUVBQC4fPkyvu2yZcvs7e0V9gA8mXzy5EnpwpCQEAKBUFpaChfb2to0NDRMTEw6OzthCfwpcOfOHemt3Nzc0GAZVYBun0CUVkBAgL6+fkJCgrm5eUtLi8yBEdTS0pKQkFBbWwvPNGppadXU1OBr//73v2dkZHz22WdCofDHH38cfBg9h8PBX7NYLAKBgI8EUVNT++ijj+DEjUPk4+Mj3Zp8yb179xRuCKfMDAoKwkuWL18eExMDXz979uzZs2eLFy+G+RKaPHnyzZs3hxJVRkbGRx99VFpaipfo6Oj09vZWVlbOmTMnKytr/vz5NjY2Q2lKRmlpqZ+fn66uLlzU1NQMCgqKi4vr7u6GhQQCYdGiRXh9Ozs7/Dz2EJmYmOCDXwwMDExMTD7++GM4bRYAYNq0aQCAd/qMEKWBEiGitIhE4qpVq06fPm1mZsbVzMOfAAAEyUlEQVThcBgMhswJyfT09MDAQDMzMzc3NzKZrKampq6uDmcDx23ZsuXUqVMkEgk/ATgQOGMl/qeJRCL+TxaWvNN4HJnWZEq0tLQGao3L5err60v/aSaTib+Go2Pi4+N///13mQ0lEslb54nk8XgvXrz45JNPZEKFB2F8Pt/BwWHwFhQSiUQ8Hk/muimTyZRIJK9fv4aJUEtLC0+TYNAeGIh0BwIAiESifCe/a5uIckCJEFFmYWFhR48eLS4uhmf8ZPz888+Ojo65ublwUlwMw44cOSJdQSgUbtiwwdramsvlbt++/ejRoyMSlYaGhlgsxjAMn2QVv1Q2fMbGxp2dnUKhUFtbG5a8evUKX2tgYAAAiI2NDQ8Pf4/GSSQSm81OTk5WuNbQ0FB+GOpQaGpqwpPY0oUtLS0EAoFEIr1HgwjyTtBgGUSZOTs7BwcH+/j4LFu2TH7t8+fPHRwc8Knhc3JyZAYi/uMf/3j69OmlS5cOHjx47NgxeMlw+ExNTfv7++HISQAAhmE5OTkj0jIAAF45w4fLAgDS0tLw17a2tsbGxtIDMt+Ju7t7bm7uQNnO3d29oKCAy+XKr9LX1wcA9PT0KNxQTU3NxcUlPT0dH4CKYVhKSsr06dPhhggyqlAiRJTc77//npKSoqWlJb/KwcHh0qVLDx48EIlEt27d2rRpk46ODr720qVLx44di4uLmzVrVnh4+Jo1az777LPa2trhh+Th4UEkErdt21ZXV1dbW/vFF188ffp0+M1CHA7HxsZm69at+fn5XV1dly9fPnz4ML5WXV19165dWVlZGzdurKys7Onpqa2tPXPmzN69e4fS+Pfffy8Wi/38/O7cudPV1fXy5cuMjIxNmzbBtTt27FBXV/fz84N/ur6+/tChQ/A4z9bWlkAgxMXF5efnP3z4UH5w5vbt21+8eLFx48aGhoampqbIyMjy8vIdO3aMUK8gyGBQIkRU1/79+2k0mouLi7a2tr+//w8//IBfTquvr4+IiFi5cuXmzZthyfHjx01MTFatWjX8y0jm5uaHDx++cePGlClTrK2tW1tb4d0FI0JTUzM5OVlPT2/BggV6enqff/55XFycdIXNmzf/9ttvqampNjY2urq6VlZW33zzDbxC9lbTpk3Lzs6WSCRsNltPT4/JZPr7++O37VtbW2dmZvb29sI/bWFhgd/jwWKxYmNjr1+/7uHh4eTk1NTUJNPysmXLjh8/npycbG5ubmpqeu7cudjY2JCQkGH3B4K8HQH7302+CKKCxGLxs2fPOjs7bW1tpQ8H30oikQAA3jq6ZBCdnZ3V1dVUKtXMzOy9GxkIhmFlZWV9fX3Tp09XmOQkEkllZWVHRwedTp88eTJ+flhhTQKBgF/OhBoaGl6+fKmvr29paYlfjMTV1NQIBAIqlWppaSmz4eAti0Si8vJyiURiZ2f3Th+HtNbWViqVevLkSfxQ9b2x2exJkybhN4AiygolQgRBlApMhOrq6mpqaklJSUuXLn2PRtasWfPHH3/09/dzOByUCJUeSoQIgigVsVhcVVUFX5uZmenp6b1HI1wuF95Io6enNxqH7MiEghIhgiAIotLQYBkEQRBEpaFEiCAIgqg0lAgRBEEQlYYSIYIgCKLS/g/9YaWJRzHqkAAAAABJRU5ErkJggg==", + "text/html": [ + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ], + "image/svg+xml": [ + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ] + }, + "metadata": {}, + "execution_count": 16 + } + ], + "cell_type": "code", + "source": [ + "using Plots\n", + "plot(\n", + " vcat(0.0, u_max), # add the origin as a point\n", + " vcat(0.0, traction_magnitude),\n", + " linewidth = 2,\n", + " title = \"Traction-displacement\",\n", + " label = nothing,\n", + " markershape = :auto\n", + ")\n", + "ylabel!(\"Traction [Pa]\")\n", + "xlabel!(\"Maximum deflection [m]\")" + ], + "metadata": {}, + "execution_count": 16 + }, + { + "cell_type": "markdown", + "source": [ + "*Figure 2.* Load-displacement-curve for the beam, showing a clear decrease\n", + "in stiffness as more material starts to yield." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "---\n", + "\n", + "*This notebook was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).*" + ], + "metadata": {} + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.1" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.1", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR798/tutorials/plasticity.jl b/previews/PR798/tutorials/plasticity.jl new file mode 100644 index 0000000000..baad41f525 --- /dev/null +++ b/previews/PR798/tutorials/plasticity.jl @@ -0,0 +1,306 @@ +using Ferrite, Tensors, SparseArrays, LinearAlgebra, Printf + +struct J2Plasticity{T, S <: SymmetricTensor{4, 3, T}} + G::T # Shear modulus + K::T # Bulk modulus + σ₀::T # Initial yield limit + H::T # Hardening modulus + Dᵉ::S # Elastic stiffness tensor +end; + +function J2Plasticity(E, ν, σ₀, H) + δ(i, j) = i == j ? 1.0 : 0.0 # helper function + G = E / 2(1 + ν) + K = E / 3(1 - 2ν) + + Isymdev(i, j, k, l) = 0.5 * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k)) - 1.0 / 3.0 * δ(i, j) * δ(k, l) + temp(i, j, k, l) = 2.0G * (0.5 * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k)) + ν / (1.0 - 2.0ν) * δ(i, j) * δ(k, l)) + Dᵉ = SymmetricTensor{4, 3}(temp) + return J2Plasticity(G, K, σ₀, H, Dᵉ) +end; + +struct MaterialState{T, S <: SecondOrderTensor{3, T}} + # Store "converged" values + ϵᵖ::S # plastic strain + σ::S # stress + k::T # hardening variable +end + +function MaterialState() + return MaterialState( + zero(SymmetricTensor{2, 3}), + zero(SymmetricTensor{2, 3}), + 0.0 + ) +end + +function vonMises(σ) + s = dev(σ) + return sqrt(3.0 / 2.0 * s ⊡ s) +end; + +function compute_stress_tangent(ϵ::SymmetricTensor{2, 3}, material::J2Plasticity, state::MaterialState) + # unpack some material parameters + G = material.G + H = material.H + + # We use (•)ᵗ to denote *trial*-values + σᵗ = material.Dᵉ ⊡ (ϵ - state.ϵᵖ) # trial-stress + sᵗ = dev(σᵗ) # deviatoric part of trial-stress + J₂ = 0.5 * sᵗ ⊡ sᵗ # second invariant of sᵗ + σᵗₑ = sqrt(3.0 * J₂) # effective trial-stress (von Mises stress) + σʸ = material.σ₀ + H * state.k # Previous yield limit + + φᵗ = σᵗₑ - σʸ # Trial-value of the yield surface + + if φᵗ < 0.0 # elastic loading + return σᵗ, material.Dᵉ, MaterialState(state.ϵᵖ, σᵗ, state.k) + else # plastic loading + h = H + 3G + μ = φᵗ / h # plastic multiplier + + c1 = 1 - 3G * μ / σᵗₑ + s = c1 * sᵗ # updated deviatoric stress + σ = s + vol(σᵗ) # updated stress + + # Compute algorithmic tangent stiffness ``D = \frac{\Delta \sigma }{\Delta \epsilon}`` + κ = H * (state.k + μ) # drag stress + σₑ = material.σ₀ + κ # updated yield surface + + δ(i, j) = i == j ? 1.0 : 0.0 + Isymdev(i, j, k, l) = 0.5 * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k)) - 1.0 / 3.0 * δ(i, j) * δ(k, l) + Q(i, j, k, l) = Isymdev(i, j, k, l) - 3.0 / (2.0 * σₑ^2) * s[i, j] * s[k, l] + b = (3G * μ / σₑ) / (1.0 + 3G * μ / σₑ) + + Dtemp(i, j, k, l) = -2G * b * Q(i, j, k, l) - 9G^2 / (h * σₑ^2) * s[i, j] * s[k, l] + D = material.Dᵉ + SymmetricTensor{4, 3}(Dtemp) + + # Return new state + Δϵᵖ = 3 / 2 * μ / σₑ * s # plastic strain + ϵᵖ = state.ϵᵖ + Δϵᵖ # plastic strain + k = state.k + μ # hardening variable + return σ, D, MaterialState(ϵᵖ, σ, k) + end +end + +function create_values(interpolation) + # setup quadrature rules + qr = QuadratureRule{RefTetrahedron}(2) + facet_qr = FacetQuadratureRule{RefTetrahedron}(3) + + # cell and facetvalues for u + cellvalues_u = CellValues(qr, interpolation) + facetvalues_u = FacetValues(facet_qr, interpolation) + + return cellvalues_u, facetvalues_u +end; + +function create_dofhandler(grid, interpolation) + dh = DofHandler(grid) + add!(dh, :u, interpolation) # add a displacement field with 3 components + close!(dh) + return dh +end + +function create_bc(dh, grid) + dbcs = ConstraintHandler(dh) + # Clamped on the left side + dofs = [1, 2, 3] + dbc = Dirichlet(:u, getfacetset(grid, "left"), (x, t) -> [0.0, 0.0, 0.0], dofs) + add!(dbcs, dbc) + close!(dbcs) + return dbcs +end; + +function doassemble!( + K::SparseMatrixCSC, r::Vector, cellvalues::CellValues, dh::DofHandler, + material::J2Plasticity, u, states, states_old + ) + assembler = start_assemble(K, r) + nu = getnbasefunctions(cellvalues) + re = zeros(nu) # element residual vector + ke = zeros(nu, nu) # element tangent matrix + + for (i, cell) in enumerate(CellIterator(dh)) + fill!(ke, 0) + fill!(re, 0) + eldofs = celldofs(cell) + ue = u[eldofs] + state = @view states[:, i] + state_old = @view states_old[:, i] + assemble_cell!(ke, re, cell, cellvalues, material, ue, state, state_old) + assemble!(assembler, eldofs, ke, re) + end + return K, r +end + +function assemble_cell!(Ke, re, cell, cellvalues, material, ue, state, state_old) + n_basefuncs = getnbasefunctions(cellvalues) + reinit!(cellvalues, cell) + + for q_point in 1:getnquadpoints(cellvalues) + # For each integration point, compute stress and material stiffness + ϵ = function_symmetric_gradient(cellvalues, q_point, ue) # Total strain + σ, D, state[q_point] = compute_stress_tangent(ϵ, material, state_old[q_point]) + + dΩ = getdetJdV(cellvalues, q_point) + for i in 1:n_basefuncs + δϵ = shape_symmetric_gradient(cellvalues, q_point, i) + re[i] += (δϵ ⊡ σ) * dΩ # add internal force to residual + for j in 1:i # loop only over lower half + Δϵ = shape_symmetric_gradient(cellvalues, q_point, j) + Ke[i, j] += δϵ ⊡ D ⊡ Δϵ * dΩ + end + end + end + symmetrize_lower!(Ke) + return +end + +function symmetrize_lower!(K) + for i in 1:size(K, 1) + for j in (i + 1):size(K, 1) + K[i, j] = K[j, i] + end + end + return +end; + +function doassemble_neumann!(r, dh, facetset, facetvalues, t) + n_basefuncs = getnbasefunctions(facetvalues) + re = zeros(n_basefuncs) # element residual vector + for fc in FacetIterator(dh, facetset) + # Add traction as a negative contribution to the element residual `re`: + reinit!(facetvalues, fc) + fill!(re, 0) + for q_point in 1:getnquadpoints(facetvalues) + dΓ = getdetJdV(facetvalues, q_point) + for i in 1:n_basefuncs + δu = shape_value(facetvalues, q_point, i) + re[i] -= (δu ⋅ t) * dΓ + end + end + assemble!(r, celldofs(fc), re) + end + return r +end + +function solve() + # Define material parameters + E = 200.0e9 # [Pa] + H = E / 20 # [Pa] + ν = 0.3 # [-] + σ₀ = 200.0e6 # [Pa] + material = J2Plasticity(E, ν, σ₀, H) + + L = 10.0 # beam length [m] + w = 1.0 # beam width [m] + h = 1.0 # beam height[m] + n_timesteps = 10 + u_max = zeros(n_timesteps) + traction_magnitude = 1.0e7 * range(0.5, 1.0, length = n_timesteps) + + # Create geometry, dofs and boundary conditions + n = 2 + nels = (10n, n, 2n) # number of elements in each spatial direction + P1 = Vec((0.0, 0.0, 0.0)) # start point for geometry + P2 = Vec((L, w, h)) # end point for geometry + grid = generate_grid(Tetrahedron, nels, P1, P2) + interpolation = Lagrange{RefTetrahedron, 1}()^3 + + dh = create_dofhandler(grid, interpolation) # JuaFEM helper function + dbcs = create_bc(dh, grid) # create Dirichlet boundary-conditions + + cellvalues, facetvalues = create_values(interpolation) + + # Pre-allocate solution vectors, etc. + n_dofs = ndofs(dh) # total number of dofs + u = zeros(n_dofs) # solution vector + Δu = zeros(n_dofs) # displacement correction + r = zeros(n_dofs) # residual + K = allocate_matrix(dh) # tangent stiffness matrix + + # Create material states. One array for each cell, where each element is an array of material- + # states - one for each integration point + nqp = getnquadpoints(cellvalues) + states = [MaterialState() for _ in 1:nqp, _ in 1:getncells(grid)] + states_old = [MaterialState() for _ in 1:nqp, _ in 1:getncells(grid)] + + # Newton-Raphson loop + NEWTON_TOL = 1 # 1 N + print("\n Starting Netwon iterations:\n") + + for timestep in 1:n_timesteps + t = timestep # actual time (used for evaluating d-bndc) + traction = Vec((0.0, 0.0, traction_magnitude[timestep])) + newton_itr = -1 + print("\n Time step @time = $timestep:\n") + update!(dbcs, t) # evaluates the D-bndc at time t + apply!(u, dbcs) # set the prescribed values in the solution vector + + while true + newton_itr += 1 + if newton_itr > 8 + error("Reached maximum Newton iterations, aborting") + break + end + # Tangent and residual contribution from the cells (volume integral) + doassemble!(K, r, cellvalues, dh, material, u, states, states_old) + # Residual contribution from the Neumann boundary (surface integral) + doassemble_neumann!(r, dh, getfacetset(grid, "right"), facetvalues, traction) + norm_r = norm(r[Ferrite.free_dofs(dbcs)]) + + print("Iteration: $newton_itr \tresidual: $(@sprintf("%.8f", norm_r))\n") + if norm_r < NEWTON_TOL + break + end + + apply_zero!(K, r, dbcs) + Δu = Symmetric(K) \ r + u -= Δu + end + + # Update the old states with the converged values for next timestep + states_old .= states + + u_max[timestep] = maximum(abs, u) # maximum displacement in current timestep + end + + # ## Postprocessing + # Only a vtu-file corresponding to the last time-step is exported. + # + # The following is a quick (and dirty) way of extracting average cell data for export. + mises_values = zeros(getncells(grid)) + κ_values = zeros(getncells(grid)) + for (el, cell_states) in enumerate(eachcol(states)) + for state in cell_states + mises_values[el] += vonMises(state.σ) + κ_values[el] += state.k * material.H + end + mises_values[el] /= length(cell_states) # average von Mises stress + κ_values[el] /= length(cell_states) # average drag stress + end + VTKGridFile("plasticity", dh) do vtk + write_solution(vtk, dh, u) # displacement field + write_cell_data(vtk, mises_values, "von Mises [Pa]") + write_cell_data(vtk, κ_values, "Drag stress [Pa]") + end + + return u_max, traction_magnitude +end + +u_max, traction_magnitude = solve(); + +using Plots +plot( + vcat(0.0, u_max), # add the origin as a point + vcat(0.0, traction_magnitude), + linewidth = 2, + title = "Traction-displacement", + label = nothing, + markershape = :auto +) +ylabel!("Traction [Pa]") +xlabel!("Maximum deflection [m]") + +# This file was generated using Literate.jl, https://github.com/fredrikekre/Literate.jl diff --git a/previews/PR798/tutorials/plasticity.png b/previews/PR798/tutorials/plasticity.png new file mode 100644 index 0000000000..579e2a72d6 Binary files /dev/null and b/previews/PR798/tutorials/plasticity.png differ diff --git a/previews/PR798/tutorials/plasticity/a4438b3f.svg b/previews/PR798/tutorials/plasticity/a4438b3f.svg new file mode 100644 index 0000000000..e7dacc9dc5 --- /dev/null +++ b/previews/PR798/tutorials/plasticity/a4438b3f.svg @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/previews/PR798/tutorials/plasticity/index.html b/previews/PR798/tutorials/plasticity/index.html new file mode 100644 index 0000000000..87fd52043d --- /dev/null +++ b/previews/PR798/tutorials/plasticity/index.html @@ -0,0 +1,652 @@ + +Von Mises plasticity · Ferrite.jl

Von Mises plasticity

Shows the von Mises stress distribution in a cantilever beam.

Figure 1. A coarse mesh solution of a cantilever beam subjected to a load causing plastic deformations. The initial yield limit is 200 MPa but due to hardening it increases up to approximately 240 MPa.

Tip

This example is also available as a Jupyter notebook: plasticity.ipynb.

Introduction

This example illustrates the use of a nonlinear material model in Ferrite. The particular model is von Mises plasticity (also know as J₂-plasticity) with isotropic hardening. The model is fully 3D, meaning that no assumptions like plane stress or plane strain are introduced.

Also note that the theory of the model is not described here, instead one is referred to standard textbooks on material modeling.

To illustrate the use of the plasticity model, we setup and solve a FE-problem consisting of a cantilever beam loaded at its free end. But first, we shortly describe the parts of the implementation dealing with the material modeling.

Material modeling

This section describes the structs and methods used to implement the material model

Material parameters and state variables

Start by loading some necessary packages

using Ferrite, Tensors, SparseArrays, LinearAlgebra, Printf

We define a J₂-plasticity-material, containing material parameters and the elastic stiffness Dᵉ (since it is constant)

struct J2Plasticity{T, S <: SymmetricTensor{4, 3, T}}
+    G::T  # Shear modulus
+    K::T  # Bulk modulus
+    σ₀::T # Initial yield limit
+    H::T  # Hardening modulus
+    Dᵉ::S # Elastic stiffness tensor
+end;

Next, we define a constructor for the material instance.

function J2Plasticity(E, ν, σ₀, H)
+    δ(i, j) = i == j ? 1.0 : 0.0 # helper function
+    G = E / 2(1 + ν)
+    K = E / 3(1 - 2ν)
+
+    Isymdev(i, j, k, l) = 0.5 * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k)) - 1.0 / 3.0 * δ(i, j) * δ(k, l)
+    temp(i, j, k, l) = 2.0G * (0.5 * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k)) + ν / (1.0 - 2.0ν) * δ(i, j) * δ(k, l))
+    Dᵉ = SymmetricTensor{4, 3}(temp)
+    return J2Plasticity(G, K, σ₀, H, Dᵉ)
+end;
Note

Above, we defined a constructor J2Plasticity(E, ν, σ₀, H) in terms of the more common material parameters $E$ and $ν$ - simply as a convenience for the user.

Define a struct to store the material state for a Gauss point.

struct MaterialState{T, S <: SecondOrderTensor{3, T}}
+    # Store "converged" values
+    ϵᵖ::S # plastic strain
+    σ::S # stress
+    k::T # hardening variable
+end

Constructor for initializing a material state. Every quantity is set to zero.

function MaterialState()
+    return MaterialState(
+        zero(SymmetricTensor{2, 3}),
+        zero(SymmetricTensor{2, 3}),
+        0.0
+    )
+end
Main.MaterialState

For later use, during the post-processing step, we define a function to compute the von Mises effective stress.

function vonMises(σ)
+    s = dev(σ)
+    return sqrt(3.0 / 2.0 * s ⊡ s)
+end;

Constitutive driver

This is the actual method which computes the stress and material tangent stiffness in a given integration point. Input is the current strain and the material state from the previous timestep.

function compute_stress_tangent(ϵ::SymmetricTensor{2, 3}, material::J2Plasticity, state::MaterialState)
+    # unpack some material parameters
+    G = material.G
+    H = material.H
+
+    # We use (•)ᵗ to denote *trial*-values
+    σᵗ = material.Dᵉ ⊡ (ϵ - state.ϵᵖ) # trial-stress
+    sᵗ = dev(σᵗ)         # deviatoric part of trial-stress
+    J₂ = 0.5 * sᵗ ⊡ sᵗ   # second invariant of sᵗ
+    σᵗₑ = sqrt(3.0 * J₂) # effective trial-stress (von Mises stress)
+    σʸ = material.σ₀ + H * state.k # Previous yield limit
+
+    φᵗ = σᵗₑ - σʸ # Trial-value of the yield surface
+
+    if φᵗ < 0.0 # elastic loading
+        return σᵗ, material.Dᵉ, MaterialState(state.ϵᵖ, σᵗ, state.k)
+    else # plastic loading
+        h = H + 3G
+        μ = φᵗ / h # plastic multiplier
+
+        c1 = 1 - 3G * μ / σᵗₑ
+        s = c1 * sᵗ           # updated deviatoric stress
+        σ = s + vol(σᵗ)       # updated stress
+
+        # Compute algorithmic tangent stiffness ``D = \frac{\Delta \sigma }{\Delta \epsilon}``
+        κ = H * (state.k + μ) # drag stress
+        σₑ = material.σ₀ + κ  # updated yield surface
+
+        δ(i, j) = i == j ? 1.0 : 0.0
+        Isymdev(i, j, k, l) = 0.5 * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k)) - 1.0 / 3.0 * δ(i, j) * δ(k, l)
+        Q(i, j, k, l) = Isymdev(i, j, k, l) - 3.0 / (2.0 * σₑ^2) * s[i, j] * s[k, l]
+        b = (3G * μ / σₑ) / (1.0 + 3G * μ / σₑ)
+
+        Dtemp(i, j, k, l) = -2G * b * Q(i, j, k, l) - 9G^2 / (h * σₑ^2) * s[i, j] * s[k, l]
+        D = material.Dᵉ + SymmetricTensor{4, 3}(Dtemp)
+
+        # Return new state
+        Δϵᵖ = 3 / 2 * μ / σₑ * s # plastic strain
+        ϵᵖ = state.ϵᵖ + Δϵᵖ      # plastic strain
+        k = state.k + μ          # hardening variable
+        return σ, D, MaterialState(ϵᵖ, σ, k)
+    end
+end
compute_stress_tangent (generic function with 1 method)

FE-problem

What follows are methods for assembling and and solving the FE-problem.

function create_values(interpolation)
+    # setup quadrature rules
+    qr = QuadratureRule{RefTetrahedron}(2)
+    facet_qr = FacetQuadratureRule{RefTetrahedron}(3)
+
+    # cell and facetvalues for u
+    cellvalues_u = CellValues(qr, interpolation)
+    facetvalues_u = FacetValues(facet_qr, interpolation)
+
+    return cellvalues_u, facetvalues_u
+end;

Add degrees of freedom

function create_dofhandler(grid, interpolation)
+    dh = DofHandler(grid)
+    add!(dh, :u, interpolation) # add a displacement field with 3 components
+    close!(dh)
+    return dh
+end
create_dofhandler (generic function with 1 method)

Boundary conditions

function create_bc(dh, grid)
+    dbcs = ConstraintHandler(dh)
+    # Clamped on the left side
+    dofs = [1, 2, 3]
+    dbc = Dirichlet(:u, getfacetset(grid, "left"), (x, t) -> [0.0, 0.0, 0.0], dofs)
+    add!(dbcs, dbc)
+    close!(dbcs)
+    return dbcs
+end;

Assembling of element contributions

  • Residual vector r
  • Tangent stiffness K
function doassemble!(
+        K::SparseMatrixCSC, r::Vector, cellvalues::CellValues, dh::DofHandler,
+        material::J2Plasticity, u, states, states_old
+    )
+    assembler = start_assemble(K, r)
+    nu = getnbasefunctions(cellvalues)
+    re = zeros(nu)     # element residual vector
+    ke = zeros(nu, nu) # element tangent matrix
+
+    for (i, cell) in enumerate(CellIterator(dh))
+        fill!(ke, 0)
+        fill!(re, 0)
+        eldofs = celldofs(cell)
+        ue = u[eldofs]
+        state = @view states[:, i]
+        state_old = @view states_old[:, i]
+        assemble_cell!(ke, re, cell, cellvalues, material, ue, state, state_old)
+        assemble!(assembler, eldofs, ke, re)
+    end
+    return K, r
+end
doassemble! (generic function with 1 method)

Compute element contribution to the residual and the tangent.

Note

Due to symmetry, we only compute the lower half of the tangent and then symmetrize it.

function assemble_cell!(Ke, re, cell, cellvalues, material, ue, state, state_old)
+    n_basefuncs = getnbasefunctions(cellvalues)
+    reinit!(cellvalues, cell)
+
+    for q_point in 1:getnquadpoints(cellvalues)
+        # For each integration point, compute stress and material stiffness
+        ϵ = function_symmetric_gradient(cellvalues, q_point, ue) # Total strain
+        σ, D, state[q_point] = compute_stress_tangent(ϵ, material, state_old[q_point])
+
+        dΩ = getdetJdV(cellvalues, q_point)
+        for i in 1:n_basefuncs
+            δϵ = shape_symmetric_gradient(cellvalues, q_point, i)
+            re[i] += (δϵ ⊡ σ) * dΩ # add internal force to residual
+            for j in 1:i # loop only over lower half
+                Δϵ = shape_symmetric_gradient(cellvalues, q_point, j)
+                Ke[i, j] += δϵ ⊡ D ⊡ Δϵ * dΩ
+            end
+        end
+    end
+    symmetrize_lower!(Ke)
+    return
+end
assemble_cell! (generic function with 1 method)

Helper function to symmetrize the material tangent

function symmetrize_lower!(K)
+    for i in 1:size(K, 1)
+        for j in (i + 1):size(K, 1)
+            K[i, j] = K[j, i]
+        end
+    end
+    return
+end;
+
+function doassemble_neumann!(r, dh, facetset, facetvalues, t)
+    n_basefuncs = getnbasefunctions(facetvalues)
+    re = zeros(n_basefuncs)                      # element residual vector
+    for fc in FacetIterator(dh, facetset)
+        # Add traction as a negative contribution to the element residual `re`:
+        reinit!(facetvalues, fc)
+        fill!(re, 0)
+        for q_point in 1:getnquadpoints(facetvalues)
+            dΓ = getdetJdV(facetvalues, q_point)
+            for i in 1:n_basefuncs
+                δu = shape_value(facetvalues, q_point, i)
+                re[i] -= (δu ⋅ t) * dΓ
+            end
+        end
+        assemble!(r, celldofs(fc), re)
+    end
+    return r
+end
doassemble_neumann! (generic function with 1 method)

Define a function which solves the FE-problem.

function solve()
+    # Define material parameters
+    E = 200.0e9  # [Pa]
+    H = E / 20   # [Pa]
+    ν = 0.3      # [-]
+    σ₀ = 200.0e6 # [Pa]
+    material = J2Plasticity(E, ν, σ₀, H)
+
+    L = 10.0 # beam length [m]
+    w = 1.0  # beam width [m]
+    h = 1.0  # beam height[m]
+    n_timesteps = 10
+    u_max = zeros(n_timesteps)
+    traction_magnitude = 1.0e7 * range(0.5, 1.0, length = n_timesteps)
+
+    # Create geometry, dofs and boundary conditions
+    n = 2
+    nels = (10n, n, 2n) # number of elements in each spatial direction
+    P1 = Vec((0.0, 0.0, 0.0))  # start point for geometry
+    P2 = Vec((L, w, h))        # end point for geometry
+    grid = generate_grid(Tetrahedron, nels, P1, P2)
+    interpolation = Lagrange{RefTetrahedron, 1}()^3
+
+    dh = create_dofhandler(grid, interpolation) # JuaFEM helper function
+    dbcs = create_bc(dh, grid) # create Dirichlet boundary-conditions
+
+    cellvalues, facetvalues = create_values(interpolation)
+
+    # Pre-allocate solution vectors, etc.
+    n_dofs = ndofs(dh)  # total number of dofs
+    u = zeros(n_dofs)   # solution vector
+    Δu = zeros(n_dofs)  # displacement correction
+    r = zeros(n_dofs)   # residual
+    K = allocate_matrix(dh) # tangent stiffness matrix
+
+    # Create material states. One array for each cell, where each element is an array of material-
+    # states - one for each integration point
+    nqp = getnquadpoints(cellvalues)
+    states = [MaterialState() for _ in 1:nqp, _ in 1:getncells(grid)]
+    states_old = [MaterialState() for _ in 1:nqp, _ in 1:getncells(grid)]
+
+    # Newton-Raphson loop
+    NEWTON_TOL = 1 # 1 N
+    print("\n Starting Netwon iterations:\n")
+
+    for timestep in 1:n_timesteps
+        t = timestep # actual time (used for evaluating d-bndc)
+        traction = Vec((0.0, 0.0, traction_magnitude[timestep]))
+        newton_itr = -1
+        print("\n Time step @time = $timestep:\n")
+        update!(dbcs, t) # evaluates the D-bndc at time t
+        apply!(u, dbcs)  # set the prescribed values in the solution vector
+
+        while true
+            newton_itr += 1
+            if newton_itr > 8
+                error("Reached maximum Newton iterations, aborting")
+                break
+            end
+            # Tangent and residual contribution from the cells (volume integral)
+            doassemble!(K, r, cellvalues, dh, material, u, states, states_old)
+            # Residual contribution from the Neumann boundary (surface integral)
+            doassemble_neumann!(r, dh, getfacetset(grid, "right"), facetvalues, traction)
+            norm_r = norm(r[Ferrite.free_dofs(dbcs)])
+
+            print("Iteration: $newton_itr \tresidual: $(@sprintf("%.8f", norm_r))\n")
+            if norm_r < NEWTON_TOL
+                break
+            end
+
+            apply_zero!(K, r, dbcs)
+            Δu = Symmetric(K) \ r
+            u -= Δu
+        end
+
+        # Update the old states with the converged values for next timestep
+        states_old .= states
+
+        u_max[timestep] = maximum(abs, u) # maximum displacement in current timestep
+    end
+
+    # ## Postprocessing
+    # Only a vtu-file corresponding to the last time-step is exported.
+    #
+    # The following is a quick (and dirty) way of extracting average cell data for export.
+    mises_values = zeros(getncells(grid))
+    κ_values = zeros(getncells(grid))
+    for (el, cell_states) in enumerate(eachcol(states))
+        for state in cell_states
+            mises_values[el] += vonMises(state.σ)
+            κ_values[el] += state.k * material.H
+        end
+        mises_values[el] /= length(cell_states) # average von Mises stress
+        κ_values[el] /= length(cell_states)     # average drag stress
+    end
+    VTKGridFile("plasticity", dh) do vtk
+        write_solution(vtk, dh, u) # displacement field
+        write_cell_data(vtk, mises_values, "von Mises [Pa]")
+        write_cell_data(vtk, κ_values, "Drag stress [Pa]")
+    end
+
+    return u_max, traction_magnitude
+end
solve (generic function with 1 method)

Solve the FE-problem and for each time-step extract maximum displacement and the corresponding traction load. Also compute the limit-traction-load

u_max, traction_magnitude = solve();

+ Starting Netwon iterations:
+
+ Time step @time = 1:
+Iteration: 0 	residual: 1435838.41167605
+Iteration: 1 	residual: 118655.22430368
+Iteration: 2 	residual: 59.50456058
+Iteration: 3 	residual: 0.00002560
+
+ Time step @time = 2:
+Iteration: 0 	residual: 159537.60129725
+Iteration: 1 	residual: 1706974.26597926
+Iteration: 2 	residual: 97346.48157049
+Iteration: 3 	residual: 37.17532011
+Iteration: 4 	residual: 0.00001524
+
+ Time step @time = 3:
+Iteration: 0 	residual: 159537.60129701
+Iteration: 1 	residual: 3033614.51718249
+Iteration: 2 	residual: 183762.82986491
+Iteration: 3 	residual: 187.23777242
+Iteration: 4 	residual: 0.00023135
+
+ Time step @time = 4:
+Iteration: 0 	residual: 159537.60129742
+Iteration: 1 	residual: 3668226.41190261
+Iteration: 2 	residual: 85645.15221552
+Iteration: 3 	residual: 33.39133787
+Iteration: 4 	residual: 0.00002312
+
+ Time step @time = 5:
+Iteration: 0 	residual: 159537.60129764
+Iteration: 1 	residual: 4942707.09611024
+Iteration: 2 	residual: 822244.81049667
+Iteration: 3 	residual: 2806.49948363
+Iteration: 4 	residual: 0.04196782
+
+ Time step @time = 6:
+Iteration: 0 	residual: 159537.60129723
+Iteration: 1 	residual: 6350622.82330476
+Iteration: 2 	residual: 1433617.64531907
+Iteration: 3 	residual: 11917.22662334
+Iteration: 4 	residual: 0.96519065
+
+ Time step @time = 7:
+Iteration: 0 	residual: 159537.60130042
+Iteration: 1 	residual: 7442093.81842929
+Iteration: 2 	residual: 2293366.32653456
+Iteration: 3 	residual: 27806.00144416
+Iteration: 4 	residual: 4.78936691
+Iteration: 5 	residual: 0.00002337
+
+ Time step @time = 8:
+Iteration: 0 	residual: 159537.60129787
+Iteration: 1 	residual: 7898429.46749798
+Iteration: 2 	residual: 2166408.36902476
+Iteration: 3 	residual: 19078.14976325
+Iteration: 4 	residual: 2.12913739
+Iteration: 5 	residual: 0.00003700
+
+ Time step @time = 9:
+Iteration: 0 	residual: 159537.60129718
+Iteration: 1 	residual: 9113096.78354406
+Iteration: 2 	residual: 1942261.17847130
+Iteration: 3 	residual: 14972.09948485
+Iteration: 4 	residual: 1.53213288
+Iteration: 5 	residual: 0.00003588
+
+ Time step @time = 10:
+Iteration: 0 	residual: 159537.60129681
+Iteration: 1 	residual: 9810716.23843057
+Iteration: 2 	residual: 1947382.98912119
+Iteration: 3 	residual: 34190.85171497
+Iteration: 4 	residual: 4.44141634
+Iteration: 5 	residual: 0.00005322

Finally we plot the load-displacement curve.

using Plots
+plot(
+    vcat(0.0, u_max),                # add the origin as a point
+    vcat(0.0, traction_magnitude),
+    linewidth = 2,
+    title = "Traction-displacement",
+    label = nothing,
+    markershape = :auto
+)
+ylabel!("Traction [Pa]")
+xlabel!("Maximum deflection [m]")
Example block output

Figure 2. Load-displacement-curve for the beam, showing a clear decrease in stiffness as more material starts to yield.

Plain program

Here follows a version of the program without any comments. The file is also available here: plasticity.jl.

using Ferrite, Tensors, SparseArrays, LinearAlgebra, Printf
+
+struct J2Plasticity{T, S <: SymmetricTensor{4, 3, T}}
+    G::T  # Shear modulus
+    K::T  # Bulk modulus
+    σ₀::T # Initial yield limit
+    H::T  # Hardening modulus
+    Dᵉ::S # Elastic stiffness tensor
+end;
+
+function J2Plasticity(E, ν, σ₀, H)
+    δ(i, j) = i == j ? 1.0 : 0.0 # helper function
+    G = E / 2(1 + ν)
+    K = E / 3(1 - 2ν)
+
+    Isymdev(i, j, k, l) = 0.5 * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k)) - 1.0 / 3.0 * δ(i, j) * δ(k, l)
+    temp(i, j, k, l) = 2.0G * (0.5 * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k)) + ν / (1.0 - 2.0ν) * δ(i, j) * δ(k, l))
+    Dᵉ = SymmetricTensor{4, 3}(temp)
+    return J2Plasticity(G, K, σ₀, H, Dᵉ)
+end;
+
+struct MaterialState{T, S <: SecondOrderTensor{3, T}}
+    # Store "converged" values
+    ϵᵖ::S # plastic strain
+    σ::S # stress
+    k::T # hardening variable
+end
+
+function MaterialState()
+    return MaterialState(
+        zero(SymmetricTensor{2, 3}),
+        zero(SymmetricTensor{2, 3}),
+        0.0
+    )
+end
+
+function vonMises(σ)
+    s = dev(σ)
+    return sqrt(3.0 / 2.0 * s ⊡ s)
+end;
+
+function compute_stress_tangent(ϵ::SymmetricTensor{2, 3}, material::J2Plasticity, state::MaterialState)
+    # unpack some material parameters
+    G = material.G
+    H = material.H
+
+    # We use (•)ᵗ to denote *trial*-values
+    σᵗ = material.Dᵉ ⊡ (ϵ - state.ϵᵖ) # trial-stress
+    sᵗ = dev(σᵗ)         # deviatoric part of trial-stress
+    J₂ = 0.5 * sᵗ ⊡ sᵗ   # second invariant of sᵗ
+    σᵗₑ = sqrt(3.0 * J₂) # effective trial-stress (von Mises stress)
+    σʸ = material.σ₀ + H * state.k # Previous yield limit
+
+    φᵗ = σᵗₑ - σʸ # Trial-value of the yield surface
+
+    if φᵗ < 0.0 # elastic loading
+        return σᵗ, material.Dᵉ, MaterialState(state.ϵᵖ, σᵗ, state.k)
+    else # plastic loading
+        h = H + 3G
+        μ = φᵗ / h # plastic multiplier
+
+        c1 = 1 - 3G * μ / σᵗₑ
+        s = c1 * sᵗ           # updated deviatoric stress
+        σ = s + vol(σᵗ)       # updated stress
+
+        # Compute algorithmic tangent stiffness ``D = \frac{\Delta \sigma }{\Delta \epsilon}``
+        κ = H * (state.k + μ) # drag stress
+        σₑ = material.σ₀ + κ  # updated yield surface
+
+        δ(i, j) = i == j ? 1.0 : 0.0
+        Isymdev(i, j, k, l) = 0.5 * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k)) - 1.0 / 3.0 * δ(i, j) * δ(k, l)
+        Q(i, j, k, l) = Isymdev(i, j, k, l) - 3.0 / (2.0 * σₑ^2) * s[i, j] * s[k, l]
+        b = (3G * μ / σₑ) / (1.0 + 3G * μ / σₑ)
+
+        Dtemp(i, j, k, l) = -2G * b * Q(i, j, k, l) - 9G^2 / (h * σₑ^2) * s[i, j] * s[k, l]
+        D = material.Dᵉ + SymmetricTensor{4, 3}(Dtemp)
+
+        # Return new state
+        Δϵᵖ = 3 / 2 * μ / σₑ * s # plastic strain
+        ϵᵖ = state.ϵᵖ + Δϵᵖ      # plastic strain
+        k = state.k + μ          # hardening variable
+        return σ, D, MaterialState(ϵᵖ, σ, k)
+    end
+end
+
+function create_values(interpolation)
+    # setup quadrature rules
+    qr = QuadratureRule{RefTetrahedron}(2)
+    facet_qr = FacetQuadratureRule{RefTetrahedron}(3)
+
+    # cell and facetvalues for u
+    cellvalues_u = CellValues(qr, interpolation)
+    facetvalues_u = FacetValues(facet_qr, interpolation)
+
+    return cellvalues_u, facetvalues_u
+end;
+
+function create_dofhandler(grid, interpolation)
+    dh = DofHandler(grid)
+    add!(dh, :u, interpolation) # add a displacement field with 3 components
+    close!(dh)
+    return dh
+end
+
+function create_bc(dh, grid)
+    dbcs = ConstraintHandler(dh)
+    # Clamped on the left side
+    dofs = [1, 2, 3]
+    dbc = Dirichlet(:u, getfacetset(grid, "left"), (x, t) -> [0.0, 0.0, 0.0], dofs)
+    add!(dbcs, dbc)
+    close!(dbcs)
+    return dbcs
+end;
+
+function doassemble!(
+        K::SparseMatrixCSC, r::Vector, cellvalues::CellValues, dh::DofHandler,
+        material::J2Plasticity, u, states, states_old
+    )
+    assembler = start_assemble(K, r)
+    nu = getnbasefunctions(cellvalues)
+    re = zeros(nu)     # element residual vector
+    ke = zeros(nu, nu) # element tangent matrix
+
+    for (i, cell) in enumerate(CellIterator(dh))
+        fill!(ke, 0)
+        fill!(re, 0)
+        eldofs = celldofs(cell)
+        ue = u[eldofs]
+        state = @view states[:, i]
+        state_old = @view states_old[:, i]
+        assemble_cell!(ke, re, cell, cellvalues, material, ue, state, state_old)
+        assemble!(assembler, eldofs, ke, re)
+    end
+    return K, r
+end
+
+function assemble_cell!(Ke, re, cell, cellvalues, material, ue, state, state_old)
+    n_basefuncs = getnbasefunctions(cellvalues)
+    reinit!(cellvalues, cell)
+
+    for q_point in 1:getnquadpoints(cellvalues)
+        # For each integration point, compute stress and material stiffness
+        ϵ = function_symmetric_gradient(cellvalues, q_point, ue) # Total strain
+        σ, D, state[q_point] = compute_stress_tangent(ϵ, material, state_old[q_point])
+
+        dΩ = getdetJdV(cellvalues, q_point)
+        for i in 1:n_basefuncs
+            δϵ = shape_symmetric_gradient(cellvalues, q_point, i)
+            re[i] += (δϵ ⊡ σ) * dΩ # add internal force to residual
+            for j in 1:i # loop only over lower half
+                Δϵ = shape_symmetric_gradient(cellvalues, q_point, j)
+                Ke[i, j] += δϵ ⊡ D ⊡ Δϵ * dΩ
+            end
+        end
+    end
+    symmetrize_lower!(Ke)
+    return
+end
+
+function symmetrize_lower!(K)
+    for i in 1:size(K, 1)
+        for j in (i + 1):size(K, 1)
+            K[i, j] = K[j, i]
+        end
+    end
+    return
+end;
+
+function doassemble_neumann!(r, dh, facetset, facetvalues, t)
+    n_basefuncs = getnbasefunctions(facetvalues)
+    re = zeros(n_basefuncs)                      # element residual vector
+    for fc in FacetIterator(dh, facetset)
+        # Add traction as a negative contribution to the element residual `re`:
+        reinit!(facetvalues, fc)
+        fill!(re, 0)
+        for q_point in 1:getnquadpoints(facetvalues)
+            dΓ = getdetJdV(facetvalues, q_point)
+            for i in 1:n_basefuncs
+                δu = shape_value(facetvalues, q_point, i)
+                re[i] -= (δu ⋅ t) * dΓ
+            end
+        end
+        assemble!(r, celldofs(fc), re)
+    end
+    return r
+end
+
+function solve()
+    # Define material parameters
+    E = 200.0e9  # [Pa]
+    H = E / 20   # [Pa]
+    ν = 0.3      # [-]
+    σ₀ = 200.0e6 # [Pa]
+    material = J2Plasticity(E, ν, σ₀, H)
+
+    L = 10.0 # beam length [m]
+    w = 1.0  # beam width [m]
+    h = 1.0  # beam height[m]
+    n_timesteps = 10
+    u_max = zeros(n_timesteps)
+    traction_magnitude = 1.0e7 * range(0.5, 1.0, length = n_timesteps)
+
+    # Create geometry, dofs and boundary conditions
+    n = 2
+    nels = (10n, n, 2n) # number of elements in each spatial direction
+    P1 = Vec((0.0, 0.0, 0.0))  # start point for geometry
+    P2 = Vec((L, w, h))        # end point for geometry
+    grid = generate_grid(Tetrahedron, nels, P1, P2)
+    interpolation = Lagrange{RefTetrahedron, 1}()^3
+
+    dh = create_dofhandler(grid, interpolation) # JuaFEM helper function
+    dbcs = create_bc(dh, grid) # create Dirichlet boundary-conditions
+
+    cellvalues, facetvalues = create_values(interpolation)
+
+    # Pre-allocate solution vectors, etc.
+    n_dofs = ndofs(dh)  # total number of dofs
+    u = zeros(n_dofs)   # solution vector
+    Δu = zeros(n_dofs)  # displacement correction
+    r = zeros(n_dofs)   # residual
+    K = allocate_matrix(dh) # tangent stiffness matrix
+
+    # Create material states. One array for each cell, where each element is an array of material-
+    # states - one for each integration point
+    nqp = getnquadpoints(cellvalues)
+    states = [MaterialState() for _ in 1:nqp, _ in 1:getncells(grid)]
+    states_old = [MaterialState() for _ in 1:nqp, _ in 1:getncells(grid)]
+
+    # Newton-Raphson loop
+    NEWTON_TOL = 1 # 1 N
+    print("\n Starting Netwon iterations:\n")
+
+    for timestep in 1:n_timesteps
+        t = timestep # actual time (used for evaluating d-bndc)
+        traction = Vec((0.0, 0.0, traction_magnitude[timestep]))
+        newton_itr = -1
+        print("\n Time step @time = $timestep:\n")
+        update!(dbcs, t) # evaluates the D-bndc at time t
+        apply!(u, dbcs)  # set the prescribed values in the solution vector
+
+        while true
+            newton_itr += 1
+            if newton_itr > 8
+                error("Reached maximum Newton iterations, aborting")
+                break
+            end
+            # Tangent and residual contribution from the cells (volume integral)
+            doassemble!(K, r, cellvalues, dh, material, u, states, states_old)
+            # Residual contribution from the Neumann boundary (surface integral)
+            doassemble_neumann!(r, dh, getfacetset(grid, "right"), facetvalues, traction)
+            norm_r = norm(r[Ferrite.free_dofs(dbcs)])
+
+            print("Iteration: $newton_itr \tresidual: $(@sprintf("%.8f", norm_r))\n")
+            if norm_r < NEWTON_TOL
+                break
+            end
+
+            apply_zero!(K, r, dbcs)
+            Δu = Symmetric(K) \ r
+            u -= Δu
+        end
+
+        # Update the old states with the converged values for next timestep
+        states_old .= states
+
+        u_max[timestep] = maximum(abs, u) # maximum displacement in current timestep
+    end
+
+    # ## Postprocessing
+    # Only a vtu-file corresponding to the last time-step is exported.
+    #
+    # The following is a quick (and dirty) way of extracting average cell data for export.
+    mises_values = zeros(getncells(grid))
+    κ_values = zeros(getncells(grid))
+    for (el, cell_states) in enumerate(eachcol(states))
+        for state in cell_states
+            mises_values[el] += vonMises(state.σ)
+            κ_values[el] += state.k * material.H
+        end
+        mises_values[el] /= length(cell_states) # average von Mises stress
+        κ_values[el] /= length(cell_states)     # average drag stress
+    end
+    VTKGridFile("plasticity", dh) do vtk
+        write_solution(vtk, dh, u) # displacement field
+        write_cell_data(vtk, mises_values, "von Mises [Pa]")
+        write_cell_data(vtk, κ_values, "Drag stress [Pa]")
+    end
+
+    return u_max, traction_magnitude
+end
+
+u_max, traction_magnitude = solve();
+
+using Plots
+plot(
+    vcat(0.0, u_max),                # add the origin as a point
+    vcat(0.0, traction_magnitude),
+    linewidth = 2,
+    title = "Traction-displacement",
+    label = nothing,
+    markershape = :auto
+)
+ylabel!("Traction [Pa]")
+xlabel!("Maximum deflection [m]")

This page was generated using Literate.jl.

diff --git a/previews/PR798/tutorials/porous_media.gif b/previews/PR798/tutorials/porous_media.gif new file mode 100644 index 0000000000..4931bd63cc Binary files /dev/null and b/previews/PR798/tutorials/porous_media.gif differ diff --git a/previews/PR798/tutorials/porous_media.ipynb b/previews/PR798/tutorials/porous_media.ipynb new file mode 100644 index 0000000000..50c19e29f6 --- /dev/null +++ b/previews/PR798/tutorials/porous_media.ipynb @@ -0,0 +1,557 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Porous media" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Porous media is a two-phase material, consisting of solid parts and a liquid occupying\n", + "the pores inbetween.\n", + "Using the porous media theory, we can model such a material without explicitly\n", + "resolving the microstructure, but by considering the interactions between the\n", + "solid and liquid. In this example, we will additionally consider larger linear\n", + "elastic solid aggregates that are impermeable. Hence, there is no liquids in\n", + "these particles and the only unknown variable is the displacement field `:u`.\n", + "In the porous media, denoted the matrix, we have both the displacement field,\n", + "`:u`, as well as the liquid pressure, `:p`, as unknown. The simulation result\n", + "is shown below\n", + "\n", + "![Pressure evolution.](porous_media.gif)\n", + "\n", + "## Theory of porous media\n", + "The strong forms are given as\n", + "$$\n", + "\\begin{aligned}\n", + "\\boldsymbol{\\sigma}(\\boldsymbol{\\epsilon}, p) \\cdot \\boldsymbol{\\nabla} &= \\boldsymbol{0} \\\\\n", + "\\dot{\\Phi}(\\boldsymbol{\\epsilon}, p) + \\boldsymbol{w}(p) \\cdot \\boldsymbol{\\nabla} &= 0\n", + "\\end{aligned}\n", + "$$\n", + "where\n", + "$\\boldsymbol{\\epsilon} = \\left[\\boldsymbol{u}\\otimes\\boldsymbol{\\nabla}\\right]^\\mathrm{sym}$\n", + "The constitutive relationships are\n", + "$$\n", + "\\begin{aligned}\n", + "\\boldsymbol{\\sigma} &= \\boldsymbol{\\mathsf{C}}:\\boldsymbol{\\epsilon} - \\alpha p \\boldsymbol{I} \\\\\n", + "\\boldsymbol{w} &= - k \\boldsymbol{\\nabla} p \\\\\n", + "\\Phi &= \\phi + \\alpha \\mathrm{tr}(\\boldsymbol{\\epsilon}) + \\beta p\n", + "\\end{aligned}\n", + "$$\n", + "with\n", + "$\\boldsymbol{\\mathsf{C}}=2G \\boldsymbol{\\mathsf{I}}^\\mathrm{dev} + 3K \\boldsymbol{I}\\otimes\\boldsymbol{I}$.\n", + "The material parameters are then the\n", + "shear modulus, $G$,\n", + "bulk modulus, $K$,\n", + "permeability, $k$,\n", + "Biot's coefficient, $\\alpha$, and\n", + "liquid compressibility, $\\beta$.\n", + "The porosity, $\\phi$, doesn't enter into the equations\n", + "(A different porosity leads to different skeleton stiffness and permeability).\n", + "\n", + "\n", + "The variational (weak) form can then be derived for the variations $\\boldsymbol{\\delta u}$\n", + "and $\\delta p$ as\n", + "$$\n", + "\\begin{aligned}\n", + "\\int_\\Omega \\left[\\left[\\boldsymbol{\\delta u}\\otimes\\boldsymbol{\\nabla}\\right]^\\mathrm{sym}:\n", + "\\boldsymbol{\\mathsf{C}}:\\boldsymbol{\\epsilon} - \\boldsymbol{\\delta u} \\cdot \\boldsymbol{\\nabla} \\alpha p\\right] \\mathrm{d}\\Omega\n", + "&= \\int_\\Gamma \\boldsymbol{\\delta u} \\cdot \\boldsymbol{t} \\mathrm{d} \\Gamma \\\\\n", + "\\int_\\Omega \\left[\\delta p \\left[\\alpha \\dot{\\boldsymbol{u}} \\cdot \\boldsymbol{\\nabla} + \\beta \\dot{p}\\right] +\n", + "\\boldsymbol{\\nabla}(\\delta p) \\cdot [k \\boldsymbol{\\nabla}]\\right] \\mathrm{d}\\Omega\n", + "&= \\int_\\Gamma \\delta p w_\\mathrm{n} \\mathrm{d} \\Gamma\n", + "\\end{aligned}\n", + "$$\n", + "where $\\boldsymbol{t}=\\boldsymbol{n}\\cdot\\boldsymbol{\\sigma}$ is the traction and\n", + "$w_\\mathrm{n} = \\boldsymbol{n}\\cdot\\boldsymbol{w}$ is the normal flux.\n", + "\n", + "### Finite element form\n", + "Discretizing in space using finite elements, we obtain the vector equation\n", + "$r_i = f_i^\\mathrm{int} - f_{i}^\\mathrm{ext}$ where $f^\\mathrm{ext}$ are the external\n", + "\"forces\", and $f_i^\\mathrm{int}$ are the internal \"forces\". We split this into the\n", + "displacement part $r_i^\\mathrm{u} = f_i^\\mathrm{int,u} - f_{i}^\\mathrm{ext,u}$ and\n", + "pressure part $r_i^\\mathrm{p} = f_i^\\mathrm{int,p} - f_{i}^\\mathrm{ext,p}$\n", + "to obtain the discretized equation system\n", + "$$\n", + "\\begin{aligned}\n", + "f_i^\\mathrm{int,u} &= \\int_\\Omega [\\boldsymbol{\\delta N}^\\mathrm{u}_i\\otimes\\boldsymbol{\\nabla}]^\\mathrm{sym} : \\boldsymbol{\\mathsf{C}} : [\\boldsymbol{u}\\otimes\\boldsymbol{\\nabla}]^\\mathrm{sym} \\\n", + "- [\\boldsymbol{\\delta N}^\\mathrm{u}_i \\cdot \\boldsymbol{\\nabla}] \\alpha p \\mathrm{d}\\Omega\n", + "&= \\int_\\Gamma \\boldsymbol{\\delta N}^\\mathrm{u}_i \\cdot \\boldsymbol{t} \\mathrm{d} \\Gamma \\\\\n", + "f_i^\\mathrm{int,p} &= \\int_\\Omega \\delta N_i^\\mathrm{p} [\\alpha [\\dot{\\boldsymbol{u}}\\cdot\\boldsymbol{\\nabla}] + \\beta\\dot{p}] + \\boldsymbol{\\nabla}(\\delta N_i^\\mathrm{p}) \\cdot [k \\boldsymbol{\\nabla}(p)] \\mathrm{d}\\Omega\n", + "&= \\int_\\Gamma \\delta N_i^\\mathrm{p} w_\\mathrm{n} \\mathrm{d} \\Gamma\n", + "\\end{aligned}\n", + "$$\n", + "Approximating the time-derivatives, $\\dot{\\boldsymbol{u}}\\approx \\left[\\boldsymbol{u}-{}^n\\boldsymbol{u}\\right]/\\Delta t$\n", + "and $\\dot{p}\\approx \\left[p-{}^np\\right]/\\Delta t$, we can implement the finite element equations in the residual form\n", + "$r_i(\\boldsymbol{a}(t), t) = 0$ where the vector $\\boldsymbol{a}$ contains all unknown displacements $u_i$ and pressures $p_i$.\n", + "\n", + "The jacobian, $K_{ij} = \\partial r_i/\\partial a_j$, is then split into four parts,\n", + "$$\n", + "\\begin{aligned}\n", + "K_{ij}^\\mathrm{uu} &= \\frac{\\partial r_i^\\mathrm{u}}{\\partial u_j} = \\int_\\Omega [\\boldsymbol{\\delta N}^\\mathrm{u}_i\\otimes\\boldsymbol{\\nabla}]^\\mathrm{sym} : \\boldsymbol{\\mathsf{C}} : [\\boldsymbol{N}_j^\\mathrm{u}\\otimes\\boldsymbol{\\nabla}]^\\mathrm{sym}\\ \\mathrm{d}\\Omega \\\\\n", + "K_{ij}^\\mathrm{up} &= \\frac{\\partial r_i^\\mathrm{u}}{\\partial p_j} = - \\int_\\Omega [\\boldsymbol{\\delta N}^\\mathrm{u}_i \\cdot \\boldsymbol{\\nabla}] \\alpha N_j^\\mathrm{p}\\ \\mathrm{d}\\Omega \\\\\n", + "K_{ij}^\\mathrm{pu} &= \\frac{\\partial r_i^\\mathrm{p}}{\\partial u_j} = \\int_\\Omega \\delta N_i^\\mathrm{p} \\frac{\\alpha}{\\Delta t} [\\boldsymbol{N}_j^\\mathrm{u} \\cdot\\boldsymbol{\\nabla}]\\ \\mathrm{d}\\Omega\\\\\n", + "K_{ij}^\\mathrm{pp} &= \\frac{\\partial r_i^\\mathrm{p}}{\\partial p_j} = \\int_\\Omega \\delta N_i^\\mathrm{p} \\frac{N_j^\\mathrm{p}}{\\Delta t} + \\boldsymbol{\\nabla}(\\delta N_i^\\mathrm{p}) \\cdot [k \\boldsymbol{\\nabla}(N_j^\\mathrm{p})] \\mathrm{d}\\Omega\n", + "\\end{aligned}\n", + "$$\n", + "\n", + "We could assemble one stiffness matrix and one mass matrix, which would be constant, but for simplicity we only consider a single\n", + "system matrix that depends on the time step, and assemble this for each step. The equations are still linear, so no iterations are required.\n", + "\n", + "## Implementation\n", + "We now solve the problem step by step. The full program with fewer comments is found in\n", + "\n", + "Required packages" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using Ferrite, FerriteMeshParser, Tensors, WriteVTK" + ], + "metadata": {}, + "execution_count": 1 + }, + { + "cell_type": "markdown", + "source": [ + "### Elasticity\n", + "We start by defining the elastic material type, containing the elastic stiffness,\n", + "for the linear elastic impermeable solid aggregates." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "struct Elastic{T}\n", + " C::SymmetricTensor{4, 2, T, 9}\n", + "end\n", + "function Elastic(; E = 20.0e3, ν = 0.3)\n", + " G = E / 2(1 + ν)\n", + " K = E / 3(1 - 2ν)\n", + " I2 = one(SymmetricTensor{2, 2})\n", + " I4vol = I2 ⊗ I2\n", + " I4dev = minorsymmetric(otimesu(I2, I2)) - I4vol / 3\n", + " return Elastic(2G * I4dev + K * I4vol)\n", + "end;" + ], + "metadata": {}, + "execution_count": 2 + }, + { + "cell_type": "markdown", + "source": [ + "Next, we define the element routine for the solid aggregates, where we dispatch on the\n", + "`Elastic` material struct. Note that the unused inputs here are used for the porous matrix below." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function element_routine!(Ke, re, material::Elastic, cv, cell, a, args...)\n", + " reinit!(cv, cell)\n", + " n_basefuncs = getnbasefunctions(cv)\n", + "\n", + " for q_point in 1:getnquadpoints(cv)\n", + " dΩ = getdetJdV(cv, q_point)\n", + " ϵ = function_symmetric_gradient(cv, q_point, a)\n", + " σ = material.C ⊡ ϵ\n", + " for i in 1:n_basefuncs\n", + " δ∇N = shape_symmetric_gradient(cv, q_point, i)\n", + " re[i] += (δ∇N ⊡ σ) * dΩ\n", + " for j in 1:n_basefuncs\n", + " ∇N = shape_symmetric_gradient(cv, q_point, j)\n", + " Ke[i, j] += (δ∇N ⊡ material.C ⊡ ∇N) * dΩ\n", + " end\n", + " end\n", + " end\n", + " return\n", + "end;" + ], + "metadata": {}, + "execution_count": 3 + }, + { + "cell_type": "markdown", + "source": [ + "### PoroElasticity\n", + "To define the poroelastic material, we re-use the elastic part from above for\n", + "the skeleton, and add the additional required material parameters." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "struct PoroElastic{T}\n", + " elastic::Elastic{T} ## Skeleton stiffness\n", + " k::T ## Permeability of liquid [mm^4/(Ns)]\n", + " ϕ::T ## Porosity [-]\n", + " α::T ## Biot's coefficient [-]\n", + " β::T ## Liquid compressibility [1/MPa]\n", + "end\n", + "PoroElastic(; elastic, k, ϕ, α, β) = PoroElastic(elastic, k, ϕ, α, β);" + ], + "metadata": {}, + "execution_count": 4 + }, + { + "cell_type": "markdown", + "source": [ + "The element routine requires a few more inputs since we have two fields, as well\n", + "as the dependence on the rates of the displacements and pressure.\n", + "Again, we dispatch on the material type." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function element_routine!(Ke, re, m::PoroElastic, cvs::Tuple, cell, a, a_old, Δt, sdh)\n", + " # Setup cellvalues and give easier names\n", + " reinit!.(cvs, (cell,))\n", + " cv_u, cv_p = cvs\n", + " dr_u = dof_range(sdh, :u)\n", + " dr_p = dof_range(sdh, :p)\n", + "\n", + " C = m.elastic.C ## Elastic stiffness\n", + "\n", + " # Assemble stiffness and force vectors\n", + " for q_point in 1:getnquadpoints(cv_u)\n", + " dΩ = getdetJdV(cv_u, q_point)\n", + " p = function_value(cv_p, q_point, a, dr_p)\n", + " p_old = function_value(cv_p, q_point, a_old, dr_p)\n", + " pdot = (p - p_old) / Δt\n", + " ∇p = function_gradient(cv_p, q_point, a, dr_p)\n", + " ϵ = function_symmetric_gradient(cv_u, q_point, a, dr_u)\n", + " tr_ϵ_old = function_divergence(cv_u, q_point, a_old, dr_u)\n", + " tr_ϵ_dot = (tr(ϵ) - tr_ϵ_old) / Δt\n", + " σ_eff = C ⊡ ϵ\n", + " # Variation of u_i\n", + " for (iᵤ, Iᵤ) in pairs(dr_u)\n", + " ∇δNu = shape_symmetric_gradient(cv_u, q_point, iᵤ)\n", + " div_δNu = shape_divergence(cv_u, q_point, iᵤ)\n", + " re[Iᵤ] += (∇δNu ⊡ σ_eff - div_δNu * p * m.α) * dΩ\n", + " for (jᵤ, Jᵤ) in pairs(dr_u)\n", + " ∇Nu = shape_symmetric_gradient(cv_u, q_point, jᵤ)\n", + " Ke[Iᵤ, Jᵤ] += (∇δNu ⊡ C ⊡ ∇Nu) * dΩ\n", + " end\n", + " for (jₚ, Jₚ) in pairs(dr_p)\n", + " Np = shape_value(cv_p, q_point, jₚ)\n", + " Ke[Iᵤ, Jₚ] -= (div_δNu * m.α * Np) * dΩ\n", + " end\n", + " end\n", + " # Variation of p_i\n", + " for (iₚ, Iₚ) in pairs(dr_p)\n", + " δNp = shape_value(cv_p, q_point, iₚ)\n", + " ∇δNp = shape_gradient(cv_p, q_point, iₚ)\n", + " re[Iₚ] += (δNp * (m.α * tr_ϵ_dot + m.β * pdot) + m.k * (∇δNp ⋅ ∇p)) * dΩ\n", + " for (jᵤ, Jᵤ) in pairs(dr_u)\n", + " div_Nu = shape_divergence(cv_u, q_point, jᵤ)\n", + " Ke[Iₚ, Jᵤ] += δNp * (m.α / Δt) * div_Nu * dΩ\n", + " end\n", + " for (jₚ, Jₚ) in pairs(dr_p)\n", + " ∇Np = shape_gradient(cv_p, q_point, jₚ)\n", + " Np = shape_value(cv_p, q_point, jₚ)\n", + " Ke[Iₚ, Jₚ] += (δNp * m.β * Np / Δt + m.k * (∇δNp ⋅ ∇Np)) * dΩ\n", + " end\n", + " end\n", + " end\n", + " return\n", + "end;" + ], + "metadata": {}, + "execution_count": 5 + }, + { + "cell_type": "markdown", + "source": [ + "### Assembly\n", + "To organize the different domains, we'll first define a container type" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "struct FEDomain{M, CV, SDH <: SubDofHandler}\n", + " material::M\n", + " cellvalues::CV\n", + " sdh::SDH\n", + "end;" + ], + "metadata": {}, + "execution_count": 6 + }, + { + "cell_type": "markdown", + "source": [ + "And then we can loop over a vector of such domains, allowing us to\n", + "loop over each domain, to assemble the contributions from each\n", + "cell in that domain (given by the `SubDofHandler`'s cellset)" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function doassemble!(K, r, domains::Vector{<:FEDomain}, a, a_old, Δt)\n", + " assembler = start_assemble(K, r)\n", + " for domain in domains\n", + " doassemble!(assembler, domain, a, a_old, Δt)\n", + " end\n", + " return\n", + "end;" + ], + "metadata": {}, + "execution_count": 7 + }, + { + "cell_type": "markdown", + "source": [ + "For one domain (corresponding to a specific SubDofHandler),\n", + "we can then loop over all cells in its cellset. Doing this\n", + "in a separate function (instead of a nested loop), ensures\n", + "that the calls to the `element_routine` are type stable,\n", + "which can be important for good performance." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function doassemble!(assembler, domain::FEDomain, a, a_old, Δt)\n", + " material = domain.material\n", + " cv = domain.cellvalues\n", + " sdh = domain.sdh\n", + " n = ndofs_per_cell(sdh)\n", + " Ke = zeros(n, n)\n", + " re = zeros(n)\n", + " ae_old = zeros(n)\n", + " ae = zeros(n)\n", + " for cell in CellIterator(sdh)\n", + " # copy values from a to ae\n", + " map!(i -> a[i], ae, celldofs(cell))\n", + " map!(i -> a_old[i], ae_old, celldofs(cell))\n", + " fill!(Ke, 0)\n", + " fill!(re, 0)\n", + " element_routine!(Ke, re, material, cv, cell, ae, ae_old, Δt, sdh)\n", + " assemble!(assembler, celldofs(cell), Ke, re)\n", + " end\n", + " return\n", + "end;" + ], + "metadata": {}, + "execution_count": 8 + }, + { + "cell_type": "markdown", + "source": [ + "### Mesh import\n", + "In this example, we import the mesh from the Abaqus input file, [`porous_media_0p25.inp`](porous_media_0p25.inp) using FerriteMeshParser's\n", + "`get_ferrite_grid` function. We then create one cellset for each phase (solid and porous)\n", + "for each element type. These 4 sets will later be used in their own `SubDofHandler`" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function get_grid()\n", + " # Import grid from abaqus mesh\n", + " grid = get_ferrite_grid(joinpath(@__DIR__, \"porous_media_0p25.inp\"))\n", + "\n", + " # Create cellsets for each fieldhandler\n", + " addcellset!(grid, \"solid3\", intersect(getcellset(grid, \"solid\"), getcellset(grid, \"CPS3\")))\n", + " addcellset!(grid, \"solid4\", intersect(getcellset(grid, \"solid\"), getcellset(grid, \"CPS4R\")))\n", + " addcellset!(grid, \"porous3\", intersect(getcellset(grid, \"porous\"), getcellset(grid, \"CPS3\")))\n", + " addcellset!(grid, \"porous4\", intersect(getcellset(grid, \"porous\"), getcellset(grid, \"CPS4R\")))\n", + " return grid\n", + "end;" + ], + "metadata": {}, + "execution_count": 9 + }, + { + "cell_type": "markdown", + "source": [ + "### Problem setup\n", + "Define the finite element interpolation, integration, and boundary conditions." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function setup_problem(; t_rise = 0.1, u_max = -0.1)\n", + "\n", + " grid = get_grid()\n", + "\n", + " # Define materials\n", + " m_solid = Elastic(; E = 20.0e3, ν = 0.3)\n", + " m_porous = PoroElastic(; elastic = Elastic(; E = 10.0e3, ν = 0.3), β = 1 / 15.0e3, α = 0.9, k = 5.0e-3, ϕ = 0.8)\n", + "\n", + " # Define interpolations\n", + " ipu_quad = Lagrange{RefQuadrilateral, 2}()^2\n", + " ipu_tri = Lagrange{RefTriangle, 2}()^2\n", + " ipp_quad = Lagrange{RefQuadrilateral, 1}()\n", + " ipp_tri = Lagrange{RefTriangle, 1}()\n", + "\n", + " # Quadrature rules\n", + " qr_quad = QuadratureRule{RefQuadrilateral}(2)\n", + " qr_tri = QuadratureRule{RefTriangle}(2)\n", + "\n", + " # CellValues\n", + " cvu_quad = CellValues(qr_quad, ipu_quad)\n", + " cvu_tri = CellValues(qr_tri, ipu_tri)\n", + " cvp_quad = CellValues(qr_quad, ipp_quad)\n", + " cvp_tri = CellValues(qr_tri, ipp_tri)\n", + "\n", + " # Setup the DofHandler\n", + " dh = DofHandler(grid)\n", + " # Solid quads\n", + " sdh_solid_quad = SubDofHandler(dh, getcellset(grid, \"solid4\"))\n", + " add!(sdh_solid_quad, :u, ipu_quad)\n", + " # Solid triangles\n", + " sdh_solid_tri = SubDofHandler(dh, getcellset(grid, \"solid3\"))\n", + " add!(sdh_solid_tri, :u, ipu_tri)\n", + " # Porous quads\n", + " sdh_porous_quad = SubDofHandler(dh, getcellset(grid, \"porous4\"))\n", + " add!(sdh_porous_quad, :u, ipu_quad)\n", + " add!(sdh_porous_quad, :p, ipp_quad)\n", + " # Porous triangles\n", + " sdh_porous_tri = SubDofHandler(dh, getcellset(grid, \"porous3\"))\n", + " add!(sdh_porous_tri, :u, ipu_tri)\n", + " add!(sdh_porous_tri, :p, ipp_tri)\n", + "\n", + " close!(dh)\n", + "\n", + " # Setup the domains\n", + " domains = [\n", + " FEDomain(m_solid, cvu_quad, sdh_solid_quad),\n", + " FEDomain(m_solid, cvu_tri, sdh_solid_tri),\n", + " FEDomain(m_porous, (cvu_quad, cvp_quad), sdh_porous_quad),\n", + " FEDomain(m_porous, (cvu_tri, cvp_tri), sdh_porous_tri),\n", + " ]\n", + "\n", + " # Boundary conditions\n", + " # Sliding for u, except top which is compressed\n", + " # Sealed for p, except top with prescribed zero pressure\n", + " addfacetset!(dh.grid, \"sides\", x -> x[1] < 1.0e-6 || x[1] ≈ 5.0)\n", + " addfacetset!(dh.grid, \"top\", x -> x[2] ≈ 10.0)\n", + " ch = ConstraintHandler(dh)\n", + " add!(ch, Dirichlet(:u, getfacetset(grid, \"bottom\"), (x, t) -> zero(Vec{1}), [2]))\n", + " add!(ch, Dirichlet(:u, getfacetset(grid, \"sides\"), (x, t) -> zero(Vec{1}), [1]))\n", + " add!(ch, Dirichlet(:u, getfacetset(grid, \"top\"), (x, t) -> u_max * clamp(t / t_rise, 0, 1), [2]))\n", + " add!(ch, Dirichlet(:p, getfacetset(grid, \"top_p\"), (x, t) -> 0.0))\n", + " close!(ch)\n", + "\n", + " return dh, ch, domains\n", + "end;" + ], + "metadata": {}, + "execution_count": 10 + }, + { + "cell_type": "markdown", + "source": [ + "### Solving\n", + "Given the `DofHandler`, `ConstraintHandler`, and `CellValues`,\n", + "we can solve the problem by stepping through the time history" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function solve(dh, ch, domains; Δt = 0.025, t_total = 1.0)\n", + " K = allocate_matrix(dh)\n", + " r = zeros(ndofs(dh))\n", + " a = zeros(ndofs(dh))\n", + " a_old = copy(a)\n", + " pvd = paraview_collection(\"porous_media\")\n", + " step = 0\n", + " for t in 0:Δt:t_total\n", + " if t > 0\n", + " update!(ch, t)\n", + " apply!(a, ch)\n", + " doassemble!(K, r, domains, a, a_old, Δt)\n", + " apply_zero!(K, r, ch)\n", + " Δa = -K \\ r\n", + " apply_zero!(Δa, ch)\n", + " a .+= Δa\n", + " copyto!(a_old, a)\n", + " end\n", + " step += 1\n", + " VTKGridFile(\"porous_media_$step\", dh) do vtk\n", + " write_solution(vtk, dh, a)\n", + " pvd[t] = vtk\n", + " end\n", + " end\n", + " vtk_save(pvd)\n", + " return\n", + "end;" + ], + "metadata": {}, + "execution_count": 11 + }, + { + "cell_type": "markdown", + "source": [ + "Finally we call the functions to actually run the code" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "dh, ch, domains = setup_problem()\n", + "solve(dh, ch, domains);" + ], + "metadata": {}, + "execution_count": 12 + }, + { + "cell_type": "markdown", + "source": [ + "---\n", + "\n", + "*This notebook was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).*" + ], + "metadata": {} + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.1" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.1", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR798/tutorials/porous_media.jl b/previews/PR798/tutorials/porous_media.jl new file mode 100644 index 0000000000..d2c4f0f4e3 --- /dev/null +++ b/previews/PR798/tutorials/porous_media.jl @@ -0,0 +1,241 @@ +using Ferrite, FerriteMeshParser, Tensors, WriteVTK + +struct Elastic{T} + C::SymmetricTensor{4, 2, T, 9} +end +function Elastic(; E = 20.0e3, ν = 0.3) + G = E / 2(1 + ν) + K = E / 3(1 - 2ν) + I2 = one(SymmetricTensor{2, 2}) + I4vol = I2 ⊗ I2 + I4dev = minorsymmetric(otimesu(I2, I2)) - I4vol / 3 + return Elastic(2G * I4dev + K * I4vol) +end; + +function element_routine!(Ke, re, material::Elastic, cv, cell, a, args...) + reinit!(cv, cell) + n_basefuncs = getnbasefunctions(cv) + + for q_point in 1:getnquadpoints(cv) + dΩ = getdetJdV(cv, q_point) + ϵ = function_symmetric_gradient(cv, q_point, a) + σ = material.C ⊡ ϵ + for i in 1:n_basefuncs + δ∇N = shape_symmetric_gradient(cv, q_point, i) + re[i] += (δ∇N ⊡ σ) * dΩ + for j in 1:n_basefuncs + ∇N = shape_symmetric_gradient(cv, q_point, j) + Ke[i, j] += (δ∇N ⊡ material.C ⊡ ∇N) * dΩ + end + end + end + return +end; + +struct PoroElastic{T} + elastic::Elastic{T} ## Skeleton stiffness + k::T ## Permeability of liquid [mm^4/(Ns)] + ϕ::T ## Porosity [-] + α::T ## Biot's coefficient [-] + β::T ## Liquid compressibility [1/MPa] +end +PoroElastic(; elastic, k, ϕ, α, β) = PoroElastic(elastic, k, ϕ, α, β); + +function element_routine!(Ke, re, m::PoroElastic, cvs::Tuple, cell, a, a_old, Δt, sdh) + # Setup cellvalues and give easier names + reinit!.(cvs, (cell,)) + cv_u, cv_p = cvs + dr_u = dof_range(sdh, :u) + dr_p = dof_range(sdh, :p) + + C = m.elastic.C ## Elastic stiffness + + # Assemble stiffness and force vectors + for q_point in 1:getnquadpoints(cv_u) + dΩ = getdetJdV(cv_u, q_point) + p = function_value(cv_p, q_point, a, dr_p) + p_old = function_value(cv_p, q_point, a_old, dr_p) + pdot = (p - p_old) / Δt + ∇p = function_gradient(cv_p, q_point, a, dr_p) + ϵ = function_symmetric_gradient(cv_u, q_point, a, dr_u) + tr_ϵ_old = function_divergence(cv_u, q_point, a_old, dr_u) + tr_ϵ_dot = (tr(ϵ) - tr_ϵ_old) / Δt + σ_eff = C ⊡ ϵ + # Variation of u_i + for (iᵤ, Iᵤ) in pairs(dr_u) + ∇δNu = shape_symmetric_gradient(cv_u, q_point, iᵤ) + div_δNu = shape_divergence(cv_u, q_point, iᵤ) + re[Iᵤ] += (∇δNu ⊡ σ_eff - div_δNu * p * m.α) * dΩ + for (jᵤ, Jᵤ) in pairs(dr_u) + ∇Nu = shape_symmetric_gradient(cv_u, q_point, jᵤ) + Ke[Iᵤ, Jᵤ] += (∇δNu ⊡ C ⊡ ∇Nu) * dΩ + end + for (jₚ, Jₚ) in pairs(dr_p) + Np = shape_value(cv_p, q_point, jₚ) + Ke[Iᵤ, Jₚ] -= (div_δNu * m.α * Np) * dΩ + end + end + # Variation of p_i + for (iₚ, Iₚ) in pairs(dr_p) + δNp = shape_value(cv_p, q_point, iₚ) + ∇δNp = shape_gradient(cv_p, q_point, iₚ) + re[Iₚ] += (δNp * (m.α * tr_ϵ_dot + m.β * pdot) + m.k * (∇δNp ⋅ ∇p)) * dΩ + for (jᵤ, Jᵤ) in pairs(dr_u) + div_Nu = shape_divergence(cv_u, q_point, jᵤ) + Ke[Iₚ, Jᵤ] += δNp * (m.α / Δt) * div_Nu * dΩ + end + for (jₚ, Jₚ) in pairs(dr_p) + ∇Np = shape_gradient(cv_p, q_point, jₚ) + Np = shape_value(cv_p, q_point, jₚ) + Ke[Iₚ, Jₚ] += (δNp * m.β * Np / Δt + m.k * (∇δNp ⋅ ∇Np)) * dΩ + end + end + end + return +end; + +struct FEDomain{M, CV, SDH <: SubDofHandler} + material::M + cellvalues::CV + sdh::SDH +end; + +function doassemble!(K, r, domains::Vector{<:FEDomain}, a, a_old, Δt) + assembler = start_assemble(K, r) + for domain in domains + doassemble!(assembler, domain, a, a_old, Δt) + end + return +end; + +function doassemble!(assembler, domain::FEDomain, a, a_old, Δt) + material = domain.material + cv = domain.cellvalues + sdh = domain.sdh + n = ndofs_per_cell(sdh) + Ke = zeros(n, n) + re = zeros(n) + ae_old = zeros(n) + ae = zeros(n) + for cell in CellIterator(sdh) + # copy values from a to ae + map!(i -> a[i], ae, celldofs(cell)) + map!(i -> a_old[i], ae_old, celldofs(cell)) + fill!(Ke, 0) + fill!(re, 0) + element_routine!(Ke, re, material, cv, cell, ae, ae_old, Δt, sdh) + assemble!(assembler, celldofs(cell), Ke, re) + end + return +end; + +function get_grid() + # Import grid from abaqus mesh + grid = get_ferrite_grid(joinpath(@__DIR__, "porous_media_0p25.inp")) + + # Create cellsets for each fieldhandler + addcellset!(grid, "solid3", intersect(getcellset(grid, "solid"), getcellset(grid, "CPS3"))) + addcellset!(grid, "solid4", intersect(getcellset(grid, "solid"), getcellset(grid, "CPS4R"))) + addcellset!(grid, "porous3", intersect(getcellset(grid, "porous"), getcellset(grid, "CPS3"))) + addcellset!(grid, "porous4", intersect(getcellset(grid, "porous"), getcellset(grid, "CPS4R"))) + return grid +end; + +function setup_problem(; t_rise = 0.1, u_max = -0.1) + + grid = get_grid() + + # Define materials + m_solid = Elastic(; E = 20.0e3, ν = 0.3) + m_porous = PoroElastic(; elastic = Elastic(; E = 10.0e3, ν = 0.3), β = 1 / 15.0e3, α = 0.9, k = 5.0e-3, ϕ = 0.8) + + # Define interpolations + ipu_quad = Lagrange{RefQuadrilateral, 2}()^2 + ipu_tri = Lagrange{RefTriangle, 2}()^2 + ipp_quad = Lagrange{RefQuadrilateral, 1}() + ipp_tri = Lagrange{RefTriangle, 1}() + + # Quadrature rules + qr_quad = QuadratureRule{RefQuadrilateral}(2) + qr_tri = QuadratureRule{RefTriangle}(2) + + # CellValues + cvu_quad = CellValues(qr_quad, ipu_quad) + cvu_tri = CellValues(qr_tri, ipu_tri) + cvp_quad = CellValues(qr_quad, ipp_quad) + cvp_tri = CellValues(qr_tri, ipp_tri) + + # Setup the DofHandler + dh = DofHandler(grid) + # Solid quads + sdh_solid_quad = SubDofHandler(dh, getcellset(grid, "solid4")) + add!(sdh_solid_quad, :u, ipu_quad) + # Solid triangles + sdh_solid_tri = SubDofHandler(dh, getcellset(grid, "solid3")) + add!(sdh_solid_tri, :u, ipu_tri) + # Porous quads + sdh_porous_quad = SubDofHandler(dh, getcellset(grid, "porous4")) + add!(sdh_porous_quad, :u, ipu_quad) + add!(sdh_porous_quad, :p, ipp_quad) + # Porous triangles + sdh_porous_tri = SubDofHandler(dh, getcellset(grid, "porous3")) + add!(sdh_porous_tri, :u, ipu_tri) + add!(sdh_porous_tri, :p, ipp_tri) + + close!(dh) + + # Setup the domains + domains = [ + FEDomain(m_solid, cvu_quad, sdh_solid_quad), + FEDomain(m_solid, cvu_tri, sdh_solid_tri), + FEDomain(m_porous, (cvu_quad, cvp_quad), sdh_porous_quad), + FEDomain(m_porous, (cvu_tri, cvp_tri), sdh_porous_tri), + ] + + # Boundary conditions + # Sliding for u, except top which is compressed + # Sealed for p, except top with prescribed zero pressure + addfacetset!(dh.grid, "sides", x -> x[1] < 1.0e-6 || x[1] ≈ 5.0) + addfacetset!(dh.grid, "top", x -> x[2] ≈ 10.0) + ch = ConstraintHandler(dh) + add!(ch, Dirichlet(:u, getfacetset(grid, "bottom"), (x, t) -> zero(Vec{1}), [2])) + add!(ch, Dirichlet(:u, getfacetset(grid, "sides"), (x, t) -> zero(Vec{1}), [1])) + add!(ch, Dirichlet(:u, getfacetset(grid, "top"), (x, t) -> u_max * clamp(t / t_rise, 0, 1), [2])) + add!(ch, Dirichlet(:p, getfacetset(grid, "top_p"), (x, t) -> 0.0)) + close!(ch) + + return dh, ch, domains +end; + +function solve(dh, ch, domains; Δt = 0.025, t_total = 1.0) + K = allocate_matrix(dh) + r = zeros(ndofs(dh)) + a = zeros(ndofs(dh)) + a_old = copy(a) + pvd = paraview_collection("porous_media") + step = 0 + for t in 0:Δt:t_total + if t > 0 + update!(ch, t) + apply!(a, ch) + doassemble!(K, r, domains, a, a_old, Δt) + apply_zero!(K, r, ch) + Δa = -K \ r + apply_zero!(Δa, ch) + a .+= Δa + copyto!(a_old, a) + end + step += 1 + VTKGridFile("porous_media_$step", dh) do vtk + write_solution(vtk, dh, a) + pvd[t] = vtk + end + end + vtk_save(pvd) + return +end; + +dh, ch, domains = setup_problem() +solve(dh, ch, domains); + +# This file was generated using Literate.jl, https://github.com/fredrikekre/Literate.jl diff --git a/previews/PR798/tutorials/porous_media.pvd b/previews/PR798/tutorials/porous_media.pvd new file mode 100755 index 0000000000..d2bd513cac --- /dev/null +++ b/previews/PR798/tutorials/porous_media.pvd @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/previews/PR798/tutorials/porous_media/index.html b/previews/PR798/tutorials/porous_media/index.html new file mode 100644 index 0000000000..129bfafe01 --- /dev/null +++ b/previews/PR798/tutorials/porous_media/index.html @@ -0,0 +1,481 @@ + +Porous media · Ferrite.jl

Porous media

Porous media is a two-phase material, consisting of solid parts and a liquid occupying the pores inbetween. Using the porous media theory, we can model such a material without explicitly resolving the microstructure, but by considering the interactions between the solid and liquid. In this example, we will additionally consider larger linear elastic solid aggregates that are impermeable. Hence, there is no liquids in these particles and the only unknown variable is the displacement field :u. In the porous media, denoted the matrix, we have both the displacement field, :u, as well as the liquid pressure, :p, as unknown. The simulation result is shown below

Pressure evolution.

Theory of porous media

The strong forms are given as

\[\begin{aligned} +\boldsymbol{\sigma}(\boldsymbol{\epsilon}, p) \cdot \boldsymbol{\nabla} &= \boldsymbol{0} \\ +\dot{\Phi}(\boldsymbol{\epsilon}, p) + \boldsymbol{w}(p) \cdot \boldsymbol{\nabla} &= 0 +\end{aligned}\]

where $\boldsymbol{\epsilon} = \left[\boldsymbol{u}\otimes\boldsymbol{\nabla}\right]^\mathrm{sym}$ The constitutive relationships are

\[\begin{aligned} +\boldsymbol{\sigma} &= \boldsymbol{\mathsf{C}}:\boldsymbol{\epsilon} - \alpha p \boldsymbol{I} \\ +\boldsymbol{w} &= - k \boldsymbol{\nabla} p \\ +\Phi &= \phi + \alpha \mathrm{tr}(\boldsymbol{\epsilon}) + \beta p +\end{aligned}\]

with $\boldsymbol{\mathsf{C}}=2G \boldsymbol{\mathsf{I}}^\mathrm{dev} + 3K \boldsymbol{I}\otimes\boldsymbol{I}$. The material parameters are then the shear modulus, $G$, bulk modulus, $K$, permeability, $k$, Biot's coefficient, $\alpha$, and liquid compressibility, $\beta$. The porosity, $\phi$, doesn't enter into the equations (A different porosity leads to different skeleton stiffness and permeability).

The variational (weak) form can then be derived for the variations $\boldsymbol{\delta u}$ and $\delta p$ as

\[\begin{aligned} +\int_\Omega \left[\left[\boldsymbol{\delta u}\otimes\boldsymbol{\nabla}\right]^\mathrm{sym}: +\boldsymbol{\mathsf{C}}:\boldsymbol{\epsilon} - \boldsymbol{\delta u} \cdot \boldsymbol{\nabla} \alpha p\right] \mathrm{d}\Omega +&= \int_\Gamma \boldsymbol{\delta u} \cdot \boldsymbol{t} \mathrm{d} \Gamma \\ +\int_\Omega \left[\delta p \left[\alpha \dot{\boldsymbol{u}} \cdot \boldsymbol{\nabla} + \beta \dot{p}\right] + +\boldsymbol{\nabla}(\delta p) \cdot [k \boldsymbol{\nabla}]\right] \mathrm{d}\Omega +&= \int_\Gamma \delta p w_\mathrm{n} \mathrm{d} \Gamma +\end{aligned}\]

where $\boldsymbol{t}=\boldsymbol{n}\cdot\boldsymbol{\sigma}$ is the traction and $w_\mathrm{n} = \boldsymbol{n}\cdot\boldsymbol{w}$ is the normal flux.

Finite element form

Discretizing in space using finite elements, we obtain the vector equation $r_i = f_i^\mathrm{int} - f_{i}^\mathrm{ext}$ where $f^\mathrm{ext}$ are the external "forces", and $f_i^\mathrm{int}$ are the internal "forces". We split this into the displacement part $r_i^\mathrm{u} = f_i^\mathrm{int,u} - f_{i}^\mathrm{ext,u}$ and pressure part $r_i^\mathrm{p} = f_i^\mathrm{int,p} - f_{i}^\mathrm{ext,p}$ to obtain the discretized equation system

\[\begin{aligned} +f_i^\mathrm{int,u} &= \int_\Omega [\boldsymbol{\delta N}^\mathrm{u}_i\otimes\boldsymbol{\nabla}]^\mathrm{sym} : \boldsymbol{\mathsf{C}} : [\boldsymbol{u}\otimes\boldsymbol{\nabla}]^\mathrm{sym} \ +- [\boldsymbol{\delta N}^\mathrm{u}_i \cdot \boldsymbol{\nabla}] \alpha p \mathrm{d}\Omega +&= \int_\Gamma \boldsymbol{\delta N}^\mathrm{u}_i \cdot \boldsymbol{t} \mathrm{d} \Gamma \\ +f_i^\mathrm{int,p} &= \int_\Omega \delta N_i^\mathrm{p} [\alpha [\dot{\boldsymbol{u}}\cdot\boldsymbol{\nabla}] + \beta\dot{p}] + \boldsymbol{\nabla}(\delta N_i^\mathrm{p}) \cdot [k \boldsymbol{\nabla}(p)] \mathrm{d}\Omega +&= \int_\Gamma \delta N_i^\mathrm{p} w_\mathrm{n} \mathrm{d} \Gamma +\end{aligned}\]

Approximating the time-derivatives, $\dot{\boldsymbol{u}}\approx \left[\boldsymbol{u}-{}^n\boldsymbol{u}\right]/\Delta t$ and $\dot{p}\approx \left[p-{}^np\right]/\Delta t$, we can implement the finite element equations in the residual form $r_i(\boldsymbol{a}(t), t) = 0$ where the vector $\boldsymbol{a}$ contains all unknown displacements $u_i$ and pressures $p_i$.

The jacobian, $K_{ij} = \partial r_i/\partial a_j$, is then split into four parts,

\[\begin{aligned} +K_{ij}^\mathrm{uu} &= \frac{\partial r_i^\mathrm{u}}{\partial u_j} = \int_\Omega [\boldsymbol{\delta N}^\mathrm{u}_i\otimes\boldsymbol{\nabla}]^\mathrm{sym} : \boldsymbol{\mathsf{C}} : [\boldsymbol{N}_j^\mathrm{u}\otimes\boldsymbol{\nabla}]^\mathrm{sym}\ \mathrm{d}\Omega \\ +K_{ij}^\mathrm{up} &= \frac{\partial r_i^\mathrm{u}}{\partial p_j} = - \int_\Omega [\boldsymbol{\delta N}^\mathrm{u}_i \cdot \boldsymbol{\nabla}] \alpha N_j^\mathrm{p}\ \mathrm{d}\Omega \\ +K_{ij}^\mathrm{pu} &= \frac{\partial r_i^\mathrm{p}}{\partial u_j} = \int_\Omega \delta N_i^\mathrm{p} \frac{\alpha}{\Delta t} [\boldsymbol{N}_j^\mathrm{u} \cdot\boldsymbol{\nabla}]\ \mathrm{d}\Omega\\ +K_{ij}^\mathrm{pp} &= \frac{\partial r_i^\mathrm{p}}{\partial p_j} = \int_\Omega \delta N_i^\mathrm{p} \frac{N_j^\mathrm{p}}{\Delta t} + \boldsymbol{\nabla}(\delta N_i^\mathrm{p}) \cdot [k \boldsymbol{\nabla}(N_j^\mathrm{p})] \mathrm{d}\Omega +\end{aligned}\]

We could assemble one stiffness matrix and one mass matrix, which would be constant, but for simplicity we only consider a single system matrix that depends on the time step, and assemble this for each step. The equations are still linear, so no iterations are required.

Implementation

We now solve the problem step by step. The full program with fewer comments is found in the final section

Required packages

using Ferrite, FerriteMeshParser, Tensors, WriteVTK

Elasticity

We start by defining the elastic material type, containing the elastic stiffness, for the linear elastic impermeable solid aggregates.

struct Elastic{T}
+    C::SymmetricTensor{4, 2, T, 9}
+end
+function Elastic(; E = 20.0e3, ν = 0.3)
+    G = E / 2(1 + ν)
+    K = E / 3(1 - 2ν)
+    I2 = one(SymmetricTensor{2, 2})
+    I4vol = I2 ⊗ I2
+    I4dev = minorsymmetric(otimesu(I2, I2)) - I4vol / 3
+    return Elastic(2G * I4dev + K * I4vol)
+end;

Next, we define the element routine for the solid aggregates, where we dispatch on the Elastic material struct. Note that the unused inputs here are used for the porous matrix below.

function element_routine!(Ke, re, material::Elastic, cv, cell, a, args...)
+    reinit!(cv, cell)
+    n_basefuncs = getnbasefunctions(cv)
+
+    for q_point in 1:getnquadpoints(cv)
+        dΩ = getdetJdV(cv, q_point)
+        ϵ = function_symmetric_gradient(cv, q_point, a)
+        σ = material.C ⊡ ϵ
+        for i in 1:n_basefuncs
+            δ∇N = shape_symmetric_gradient(cv, q_point, i)
+            re[i] += (δ∇N ⊡ σ) * dΩ
+            for j in 1:n_basefuncs
+                ∇N = shape_symmetric_gradient(cv, q_point, j)
+                Ke[i, j] += (δ∇N ⊡ material.C ⊡ ∇N) * dΩ
+            end
+        end
+    end
+    return
+end;

PoroElasticity

To define the poroelastic material, we re-use the elastic part from above for the skeleton, and add the additional required material parameters.

struct PoroElastic{T}
+    elastic::Elastic{T} ## Skeleton stiffness
+    k::T     ## Permeability of liquid   [mm^4/(Ns)]
+    ϕ::T     ## Porosity                 [-]
+    α::T     ## Biot's coefficient       [-]
+    β::T     ## Liquid compressibility   [1/MPa]
+end
+PoroElastic(; elastic, k, ϕ, α, β) = PoroElastic(elastic, k, ϕ, α, β);

The element routine requires a few more inputs since we have two fields, as well as the dependence on the rates of the displacements and pressure. Again, we dispatch on the material type.

function element_routine!(Ke, re, m::PoroElastic, cvs::Tuple, cell, a, a_old, Δt, sdh)
+    # Setup cellvalues and give easier names
+    reinit!.(cvs, (cell,))
+    cv_u, cv_p = cvs
+    dr_u = dof_range(sdh, :u)
+    dr_p = dof_range(sdh, :p)
+
+    C = m.elastic.C ## Elastic stiffness
+
+    # Assemble stiffness and force vectors
+    for q_point in 1:getnquadpoints(cv_u)
+        dΩ = getdetJdV(cv_u, q_point)
+        p = function_value(cv_p, q_point, a, dr_p)
+        p_old = function_value(cv_p, q_point, a_old, dr_p)
+        pdot = (p - p_old) / Δt
+        ∇p = function_gradient(cv_p, q_point, a, dr_p)
+        ϵ = function_symmetric_gradient(cv_u, q_point, a, dr_u)
+        tr_ϵ_old = function_divergence(cv_u, q_point, a_old, dr_u)
+        tr_ϵ_dot = (tr(ϵ) - tr_ϵ_old) / Δt
+        σ_eff = C ⊡ ϵ
+        # Variation of u_i
+        for (iᵤ, Iᵤ) in pairs(dr_u)
+            ∇δNu = shape_symmetric_gradient(cv_u, q_point, iᵤ)
+            div_δNu = shape_divergence(cv_u, q_point, iᵤ)
+            re[Iᵤ] += (∇δNu ⊡ σ_eff - div_δNu * p * m.α) * dΩ
+            for (jᵤ, Jᵤ) in pairs(dr_u)
+                ∇Nu = shape_symmetric_gradient(cv_u, q_point, jᵤ)
+                Ke[Iᵤ, Jᵤ] += (∇δNu ⊡ C ⊡ ∇Nu) * dΩ
+            end
+            for (jₚ, Jₚ) in pairs(dr_p)
+                Np = shape_value(cv_p, q_point, jₚ)
+                Ke[Iᵤ, Jₚ] -= (div_δNu * m.α * Np) * dΩ
+            end
+        end
+        # Variation of p_i
+        for (iₚ, Iₚ) in pairs(dr_p)
+            δNp = shape_value(cv_p, q_point, iₚ)
+            ∇δNp = shape_gradient(cv_p, q_point, iₚ)
+            re[Iₚ] += (δNp * (m.α * tr_ϵ_dot + m.β * pdot) + m.k * (∇δNp ⋅ ∇p)) * dΩ
+            for (jᵤ, Jᵤ) in pairs(dr_u)
+                div_Nu = shape_divergence(cv_u, q_point, jᵤ)
+                Ke[Iₚ, Jᵤ] += δNp * (m.α / Δt) * div_Nu * dΩ
+            end
+            for (jₚ, Jₚ) in pairs(dr_p)
+                ∇Np = shape_gradient(cv_p, q_point, jₚ)
+                Np = shape_value(cv_p, q_point, jₚ)
+                Ke[Iₚ, Jₚ] += (δNp * m.β * Np / Δt + m.k * (∇δNp ⋅ ∇Np)) * dΩ
+            end
+        end
+    end
+    return
+end;

Assembly

To organize the different domains, we'll first define a container type

struct FEDomain{M, CV, SDH <: SubDofHandler}
+    material::M
+    cellvalues::CV
+    sdh::SDH
+end;

And then we can loop over a vector of such domains, allowing us to loop over each domain, to assemble the contributions from each cell in that domain (given by the SubDofHandler's cellset)

function doassemble!(K, r, domains::Vector{<:FEDomain}, a, a_old, Δt)
+    assembler = start_assemble(K, r)
+    for domain in domains
+        doassemble!(assembler, domain, a, a_old, Δt)
+    end
+    return
+end;

For one domain (corresponding to a specific SubDofHandler), we can then loop over all cells in its cellset. Doing this in a separate function (instead of a nested loop), ensures that the calls to the element_routine are type stable, which can be important for good performance.

function doassemble!(assembler, domain::FEDomain, a, a_old, Δt)
+    material = domain.material
+    cv = domain.cellvalues
+    sdh = domain.sdh
+    n = ndofs_per_cell(sdh)
+    Ke = zeros(n, n)
+    re = zeros(n)
+    ae_old = zeros(n)
+    ae = zeros(n)
+    for cell in CellIterator(sdh)
+        # copy values from a to ae
+        map!(i -> a[i], ae, celldofs(cell))
+        map!(i -> a_old[i], ae_old, celldofs(cell))
+        fill!(Ke, 0)
+        fill!(re, 0)
+        element_routine!(Ke, re, material, cv, cell, ae, ae_old, Δt, sdh)
+        assemble!(assembler, celldofs(cell), Ke, re)
+    end
+    return
+end;

Mesh import

In this example, we import the mesh from the Abaqus input file, porous_media_0p25.inp using FerriteMeshParser's get_ferrite_grid function. We then create one cellset for each phase (solid and porous) for each element type. These 4 sets will later be used in their own SubDofHandler

function get_grid()
+    # Import grid from abaqus mesh
+    grid = get_ferrite_grid(joinpath(@__DIR__, "porous_media_0p25.inp"))
+
+    # Create cellsets for each fieldhandler
+    addcellset!(grid, "solid3", intersect(getcellset(grid, "solid"), getcellset(grid, "CPS3")))
+    addcellset!(grid, "solid4", intersect(getcellset(grid, "solid"), getcellset(grid, "CPS4R")))
+    addcellset!(grid, "porous3", intersect(getcellset(grid, "porous"), getcellset(grid, "CPS3")))
+    addcellset!(grid, "porous4", intersect(getcellset(grid, "porous"), getcellset(grid, "CPS4R")))
+    return grid
+end;

Problem setup

Define the finite element interpolation, integration, and boundary conditions.

function setup_problem(; t_rise = 0.1, u_max = -0.1)
+
+    grid = get_grid()
+
+    # Define materials
+    m_solid = Elastic(; E = 20.0e3, ν = 0.3)
+    m_porous = PoroElastic(; elastic = Elastic(; E = 10.0e3, ν = 0.3), β = 1 / 15.0e3, α = 0.9, k = 5.0e-3, ϕ = 0.8)
+
+    # Define interpolations
+    ipu_quad = Lagrange{RefQuadrilateral, 2}()^2
+    ipu_tri = Lagrange{RefTriangle, 2}()^2
+    ipp_quad = Lagrange{RefQuadrilateral, 1}()
+    ipp_tri = Lagrange{RefTriangle, 1}()
+
+    # Quadrature rules
+    qr_quad = QuadratureRule{RefQuadrilateral}(2)
+    qr_tri = QuadratureRule{RefTriangle}(2)
+
+    # CellValues
+    cvu_quad = CellValues(qr_quad, ipu_quad)
+    cvu_tri = CellValues(qr_tri, ipu_tri)
+    cvp_quad = CellValues(qr_quad, ipp_quad)
+    cvp_tri = CellValues(qr_tri, ipp_tri)
+
+    # Setup the DofHandler
+    dh = DofHandler(grid)
+    # Solid quads
+    sdh_solid_quad = SubDofHandler(dh, getcellset(grid, "solid4"))
+    add!(sdh_solid_quad, :u, ipu_quad)
+    # Solid triangles
+    sdh_solid_tri = SubDofHandler(dh, getcellset(grid, "solid3"))
+    add!(sdh_solid_tri, :u, ipu_tri)
+    # Porous quads
+    sdh_porous_quad = SubDofHandler(dh, getcellset(grid, "porous4"))
+    add!(sdh_porous_quad, :u, ipu_quad)
+    add!(sdh_porous_quad, :p, ipp_quad)
+    # Porous triangles
+    sdh_porous_tri = SubDofHandler(dh, getcellset(grid, "porous3"))
+    add!(sdh_porous_tri, :u, ipu_tri)
+    add!(sdh_porous_tri, :p, ipp_tri)
+
+    close!(dh)
+
+    # Setup the domains
+    domains = [
+        FEDomain(m_solid, cvu_quad, sdh_solid_quad),
+        FEDomain(m_solid, cvu_tri, sdh_solid_tri),
+        FEDomain(m_porous, (cvu_quad, cvp_quad), sdh_porous_quad),
+        FEDomain(m_porous, (cvu_tri, cvp_tri), sdh_porous_tri),
+    ]
+
+    # Boundary conditions
+    # Sliding for u, except top which is compressed
+    # Sealed for p, except top with prescribed zero pressure
+    addfacetset!(dh.grid, "sides", x -> x[1] < 1.0e-6 || x[1] ≈ 5.0)
+    addfacetset!(dh.grid, "top", x -> x[2] ≈ 10.0)
+    ch = ConstraintHandler(dh)
+    add!(ch, Dirichlet(:u, getfacetset(grid, "bottom"), (x, t) -> zero(Vec{1}), [2]))
+    add!(ch, Dirichlet(:u, getfacetset(grid, "sides"), (x, t) -> zero(Vec{1}), [1]))
+    add!(ch, Dirichlet(:u, getfacetset(grid, "top"), (x, t) -> u_max * clamp(t / t_rise, 0, 1), [2]))
+    add!(ch, Dirichlet(:p, getfacetset(grid, "top_p"), (x, t) -> 0.0))
+    close!(ch)
+
+    return dh, ch, domains
+end;

Solving

Given the DofHandler, ConstraintHandler, and CellValues, we can solve the problem by stepping through the time history

function solve(dh, ch, domains; Δt = 0.025, t_total = 1.0)
+    K = allocate_matrix(dh)
+    r = zeros(ndofs(dh))
+    a = zeros(ndofs(dh))
+    a_old = copy(a)
+    pvd = paraview_collection("porous_media")
+    step = 0
+    for t in 0:Δt:t_total
+        if t > 0
+            update!(ch, t)
+            apply!(a, ch)
+            doassemble!(K, r, domains, a, a_old, Δt)
+            apply_zero!(K, r, ch)
+            Δa = -K \ r
+            apply_zero!(Δa, ch)
+            a .+= Δa
+            copyto!(a_old, a)
+        end
+        step += 1
+        VTKGridFile("porous_media_$step", dh) do vtk
+            write_solution(vtk, dh, a)
+            pvd[t] = vtk
+        end
+    end
+    vtk_save(pvd)
+    return
+end;

Finally we call the functions to actually run the code

dh, ch, domains = setup_problem()
+solve(dh, ch, domains);

Plain program

Here follows a version of the program without any comments. The file is also available here: porous_media.jl.

using Ferrite, FerriteMeshParser, Tensors, WriteVTK
+
+struct Elastic{T}
+    C::SymmetricTensor{4, 2, T, 9}
+end
+function Elastic(; E = 20.0e3, ν = 0.3)
+    G = E / 2(1 + ν)
+    K = E / 3(1 - 2ν)
+    I2 = one(SymmetricTensor{2, 2})
+    I4vol = I2 ⊗ I2
+    I4dev = minorsymmetric(otimesu(I2, I2)) - I4vol / 3
+    return Elastic(2G * I4dev + K * I4vol)
+end;
+
+function element_routine!(Ke, re, material::Elastic, cv, cell, a, args...)
+    reinit!(cv, cell)
+    n_basefuncs = getnbasefunctions(cv)
+
+    for q_point in 1:getnquadpoints(cv)
+        dΩ = getdetJdV(cv, q_point)
+        ϵ = function_symmetric_gradient(cv, q_point, a)
+        σ = material.C ⊡ ϵ
+        for i in 1:n_basefuncs
+            δ∇N = shape_symmetric_gradient(cv, q_point, i)
+            re[i] += (δ∇N ⊡ σ) * dΩ
+            for j in 1:n_basefuncs
+                ∇N = shape_symmetric_gradient(cv, q_point, j)
+                Ke[i, j] += (δ∇N ⊡ material.C ⊡ ∇N) * dΩ
+            end
+        end
+    end
+    return
+end;
+
+struct PoroElastic{T}
+    elastic::Elastic{T} ## Skeleton stiffness
+    k::T     ## Permeability of liquid   [mm^4/(Ns)]
+    ϕ::T     ## Porosity                 [-]
+    α::T     ## Biot's coefficient       [-]
+    β::T     ## Liquid compressibility   [1/MPa]
+end
+PoroElastic(; elastic, k, ϕ, α, β) = PoroElastic(elastic, k, ϕ, α, β);
+
+function element_routine!(Ke, re, m::PoroElastic, cvs::Tuple, cell, a, a_old, Δt, sdh)
+    # Setup cellvalues and give easier names
+    reinit!.(cvs, (cell,))
+    cv_u, cv_p = cvs
+    dr_u = dof_range(sdh, :u)
+    dr_p = dof_range(sdh, :p)
+
+    C = m.elastic.C ## Elastic stiffness
+
+    # Assemble stiffness and force vectors
+    for q_point in 1:getnquadpoints(cv_u)
+        dΩ = getdetJdV(cv_u, q_point)
+        p = function_value(cv_p, q_point, a, dr_p)
+        p_old = function_value(cv_p, q_point, a_old, dr_p)
+        pdot = (p - p_old) / Δt
+        ∇p = function_gradient(cv_p, q_point, a, dr_p)
+        ϵ = function_symmetric_gradient(cv_u, q_point, a, dr_u)
+        tr_ϵ_old = function_divergence(cv_u, q_point, a_old, dr_u)
+        tr_ϵ_dot = (tr(ϵ) - tr_ϵ_old) / Δt
+        σ_eff = C ⊡ ϵ
+        # Variation of u_i
+        for (iᵤ, Iᵤ) in pairs(dr_u)
+            ∇δNu = shape_symmetric_gradient(cv_u, q_point, iᵤ)
+            div_δNu = shape_divergence(cv_u, q_point, iᵤ)
+            re[Iᵤ] += (∇δNu ⊡ σ_eff - div_δNu * p * m.α) * dΩ
+            for (jᵤ, Jᵤ) in pairs(dr_u)
+                ∇Nu = shape_symmetric_gradient(cv_u, q_point, jᵤ)
+                Ke[Iᵤ, Jᵤ] += (∇δNu ⊡ C ⊡ ∇Nu) * dΩ
+            end
+            for (jₚ, Jₚ) in pairs(dr_p)
+                Np = shape_value(cv_p, q_point, jₚ)
+                Ke[Iᵤ, Jₚ] -= (div_δNu * m.α * Np) * dΩ
+            end
+        end
+        # Variation of p_i
+        for (iₚ, Iₚ) in pairs(dr_p)
+            δNp = shape_value(cv_p, q_point, iₚ)
+            ∇δNp = shape_gradient(cv_p, q_point, iₚ)
+            re[Iₚ] += (δNp * (m.α * tr_ϵ_dot + m.β * pdot) + m.k * (∇δNp ⋅ ∇p)) * dΩ
+            for (jᵤ, Jᵤ) in pairs(dr_u)
+                div_Nu = shape_divergence(cv_u, q_point, jᵤ)
+                Ke[Iₚ, Jᵤ] += δNp * (m.α / Δt) * div_Nu * dΩ
+            end
+            for (jₚ, Jₚ) in pairs(dr_p)
+                ∇Np = shape_gradient(cv_p, q_point, jₚ)
+                Np = shape_value(cv_p, q_point, jₚ)
+                Ke[Iₚ, Jₚ] += (δNp * m.β * Np / Δt + m.k * (∇δNp ⋅ ∇Np)) * dΩ
+            end
+        end
+    end
+    return
+end;
+
+struct FEDomain{M, CV, SDH <: SubDofHandler}
+    material::M
+    cellvalues::CV
+    sdh::SDH
+end;
+
+function doassemble!(K, r, domains::Vector{<:FEDomain}, a, a_old, Δt)
+    assembler = start_assemble(K, r)
+    for domain in domains
+        doassemble!(assembler, domain, a, a_old, Δt)
+    end
+    return
+end;
+
+function doassemble!(assembler, domain::FEDomain, a, a_old, Δt)
+    material = domain.material
+    cv = domain.cellvalues
+    sdh = domain.sdh
+    n = ndofs_per_cell(sdh)
+    Ke = zeros(n, n)
+    re = zeros(n)
+    ae_old = zeros(n)
+    ae = zeros(n)
+    for cell in CellIterator(sdh)
+        # copy values from a to ae
+        map!(i -> a[i], ae, celldofs(cell))
+        map!(i -> a_old[i], ae_old, celldofs(cell))
+        fill!(Ke, 0)
+        fill!(re, 0)
+        element_routine!(Ke, re, material, cv, cell, ae, ae_old, Δt, sdh)
+        assemble!(assembler, celldofs(cell), Ke, re)
+    end
+    return
+end;
+
+function get_grid()
+    # Import grid from abaqus mesh
+    grid = get_ferrite_grid(joinpath(@__DIR__, "porous_media_0p25.inp"))
+
+    # Create cellsets for each fieldhandler
+    addcellset!(grid, "solid3", intersect(getcellset(grid, "solid"), getcellset(grid, "CPS3")))
+    addcellset!(grid, "solid4", intersect(getcellset(grid, "solid"), getcellset(grid, "CPS4R")))
+    addcellset!(grid, "porous3", intersect(getcellset(grid, "porous"), getcellset(grid, "CPS3")))
+    addcellset!(grid, "porous4", intersect(getcellset(grid, "porous"), getcellset(grid, "CPS4R")))
+    return grid
+end;
+
+function setup_problem(; t_rise = 0.1, u_max = -0.1)
+
+    grid = get_grid()
+
+    # Define materials
+    m_solid = Elastic(; E = 20.0e3, ν = 0.3)
+    m_porous = PoroElastic(; elastic = Elastic(; E = 10.0e3, ν = 0.3), β = 1 / 15.0e3, α = 0.9, k = 5.0e-3, ϕ = 0.8)
+
+    # Define interpolations
+    ipu_quad = Lagrange{RefQuadrilateral, 2}()^2
+    ipu_tri = Lagrange{RefTriangle, 2}()^2
+    ipp_quad = Lagrange{RefQuadrilateral, 1}()
+    ipp_tri = Lagrange{RefTriangle, 1}()
+
+    # Quadrature rules
+    qr_quad = QuadratureRule{RefQuadrilateral}(2)
+    qr_tri = QuadratureRule{RefTriangle}(2)
+
+    # CellValues
+    cvu_quad = CellValues(qr_quad, ipu_quad)
+    cvu_tri = CellValues(qr_tri, ipu_tri)
+    cvp_quad = CellValues(qr_quad, ipp_quad)
+    cvp_tri = CellValues(qr_tri, ipp_tri)
+
+    # Setup the DofHandler
+    dh = DofHandler(grid)
+    # Solid quads
+    sdh_solid_quad = SubDofHandler(dh, getcellset(grid, "solid4"))
+    add!(sdh_solid_quad, :u, ipu_quad)
+    # Solid triangles
+    sdh_solid_tri = SubDofHandler(dh, getcellset(grid, "solid3"))
+    add!(sdh_solid_tri, :u, ipu_tri)
+    # Porous quads
+    sdh_porous_quad = SubDofHandler(dh, getcellset(grid, "porous4"))
+    add!(sdh_porous_quad, :u, ipu_quad)
+    add!(sdh_porous_quad, :p, ipp_quad)
+    # Porous triangles
+    sdh_porous_tri = SubDofHandler(dh, getcellset(grid, "porous3"))
+    add!(sdh_porous_tri, :u, ipu_tri)
+    add!(sdh_porous_tri, :p, ipp_tri)
+
+    close!(dh)
+
+    # Setup the domains
+    domains = [
+        FEDomain(m_solid, cvu_quad, sdh_solid_quad),
+        FEDomain(m_solid, cvu_tri, sdh_solid_tri),
+        FEDomain(m_porous, (cvu_quad, cvp_quad), sdh_porous_quad),
+        FEDomain(m_porous, (cvu_tri, cvp_tri), sdh_porous_tri),
+    ]
+
+    # Boundary conditions
+    # Sliding for u, except top which is compressed
+    # Sealed for p, except top with prescribed zero pressure
+    addfacetset!(dh.grid, "sides", x -> x[1] < 1.0e-6 || x[1] ≈ 5.0)
+    addfacetset!(dh.grid, "top", x -> x[2] ≈ 10.0)
+    ch = ConstraintHandler(dh)
+    add!(ch, Dirichlet(:u, getfacetset(grid, "bottom"), (x, t) -> zero(Vec{1}), [2]))
+    add!(ch, Dirichlet(:u, getfacetset(grid, "sides"), (x, t) -> zero(Vec{1}), [1]))
+    add!(ch, Dirichlet(:u, getfacetset(grid, "top"), (x, t) -> u_max * clamp(t / t_rise, 0, 1), [2]))
+    add!(ch, Dirichlet(:p, getfacetset(grid, "top_p"), (x, t) -> 0.0))
+    close!(ch)
+
+    return dh, ch, domains
+end;
+
+function solve(dh, ch, domains; Δt = 0.025, t_total = 1.0)
+    K = allocate_matrix(dh)
+    r = zeros(ndofs(dh))
+    a = zeros(ndofs(dh))
+    a_old = copy(a)
+    pvd = paraview_collection("porous_media")
+    step = 0
+    for t in 0:Δt:t_total
+        if t > 0
+            update!(ch, t)
+            apply!(a, ch)
+            doassemble!(K, r, domains, a, a_old, Δt)
+            apply_zero!(K, r, ch)
+            Δa = -K \ r
+            apply_zero!(Δa, ch)
+            a .+= Δa
+            copyto!(a_old, a)
+        end
+        step += 1
+        VTKGridFile("porous_media_$step", dh) do vtk
+            write_solution(vtk, dh, a)
+            pvd[t] = vtk
+        end
+    end
+    vtk_save(pvd)
+    return
+end;
+
+dh, ch, domains = setup_problem()
+solve(dh, ch, domains);

This page was generated using Literate.jl.

diff --git a/previews/PR798/tutorials/porous_media_0p25.inp b/previews/PR798/tutorials/porous_media_0p25.inp new file mode 100644 index 0000000000..eca3ac29f4 --- /dev/null +++ b/previews/PR798/tutorials/porous_media_0p25.inp @@ -0,0 +1,2214 @@ +*Heading +** Job name: porous_media Model name: Model-1 +** Generated by: Abaqus/CAE 2019 +*Preprint, echo=NO, model=NO, history=NO, contact=NO +** +** PARTS +** +*Part, name=Part-1 +*Node + 1, 0.574999988, 5.82499981 + 2, 3.27516675, 0. + 3, 5., 0. + 4, 5., 2.22565365 + 5, 2.09107494, 10. + 6, 3.5999999, 6.375 + 7, 0., 7.89685726 + 8, 0., 0. + 9, 5., 10. + 10, 0., 10. + 11, 0.463010848, 5.64705753 + 12, 0.440009415, 5.40309381 + 13, 0.468831539, 5.15897274 + 14, 0.52400732, 4.91928911 + 15, 0.595369935, 4.68387651 + 16, 0.677911818, 4.45213127 + 17, 0.76878196, 4.22351599 + 18, 0.866189897, 3.99760532 + 19, 0.968930066, 3.77406693 + 20, 1.07614756, 3.55263948 + 21, 1.18721187, 3.33311558 + 22, 1.30164278, 3.11532688 + 23, 1.41906571, 2.8991363 + 24, 1.53918242, 2.68443036 + 25, 1.66175091, 2.47111416 + 26, 1.78657269, 2.25910854 + 27, 1.91348267, 2.04834628 + 28, 2.04234242, 1.83877003 + 29, 2.17303514, 1.63033187 + 30, 2.30546117, 1.42299056 + 31, 2.43953538, 1.21671116 + 32, 2.57518506, 1.01146424 + 33, 2.71234751, 0.807225406 + 34, 2.85096955, 0.603974044 + 35, 2.99100518, 0.40169394 + 36, 3.13241529, 0.200372487 + 37, 3.52157164, 0. + 38, 3.76797628, 0. + 39, 4.01438093, 0. + 40, 4.26078558, 0. + 41, 4.5071907, 0. + 42, 4.75359535, 0. + 43, 5., 0.247294843 + 44, 5., 0.494589686 + 45, 5., 0.74188453 + 46, 5., 0.989179373 + 47, 5., 1.23647428 + 48, 5., 1.48376906 + 49, 5., 1.73106396 + 50, 5., 1.97835875 + 51, 4.84117937, 2.41401935 + 52, 4.68101263, 2.60124207 + 53, 4.51946688, 2.78727555 + 54, 4.35650063, 2.97206664 + 55, 4.19206667, 3.15555239 + 56, 4.02610874, 3.33766055 + 57, 3.85855985, 3.51830649 + 58, 3.68934274, 3.69739008 + 59, 3.51836467, 3.87479353 + 60, 3.3455174, 4.05037546 + 61, 3.17067027, 4.22396612 + 62, 2.9936676, 4.39535809 + 63, 2.81431985, 4.56429338 + 64, 2.63239408, 4.73044872 + 65, 2.44759941, 4.89340591 + 66, 2.25956583, 5.05261278 + 67, 2.06781244, 5.2073164 + 68, 1.87169516, 5.35644293 + 69, 1.67032278, 5.49838161 + 70, 1.46241105, 5.63052893 + 71, 1.24603188, 5.74822044 + 72, 1.01829123, 5.8416996 + 73, 0.776764631, 5.88516951 + 74, 2.24450207, 9.81127262 + 75, 2.39469981, 9.6199646 + 76, 2.54144621, 9.42599773 + 77, 2.68447018, 9.22926998 + 78, 2.82343674, 9.02965641 + 79, 2.95792747, 8.82700157 + 80, 3.08741355, 8.62111473 + 81, 3.21121407, 8.41176128 + 82, 3.32843184, 8.19865608 + 83, 3.43785286, 7.98144913 + 84, 3.5377748, 7.75971985 + 85, 3.62570524, 7.53298187 + 86, 3.69778633, 7.30074787 + 87, 3.74761868, 7.0628109 + 88, 3.76372385, 6.82042408 + 89, 3.72467685, 6.58123875 + 90, 3.37952781, 6.24754477 + 91, 3.12541127, 6.21412706 + 92, 2.86968994, 6.23905277 + 93, 2.61964083, 6.29893446 + 94, 2.37613416, 6.38168907 + 95, 2.13873982, 6.48069429 + 96, 1.90683937, 6.59199286 + 97, 1.67986631, 6.71303892 + 98, 1.45734799, 6.84209967 + 99, 1.23890054, 6.97794294 + 100, 1.02421439, 7.11965847 + 101, 0.81303972, 7.26655817 + 102, 0.605176032, 7.41810799 + 103, 0.400462031, 7.57388687 + 104, 0.198770002, 7.73356056 + 105, 0., 7.65008068 + 106, 0., 7.4033041 + 107, 0., 7.15652704 + 108, 0., 6.90975046 + 109, 0., 6.6629734 + 110, 0., 6.41619682 + 111, 0., 6.16941977 + 112, 0., 5.92264318 + 113, 0., 5.67586613 + 114, 0., 5.42908955 + 115, 0., 5.18231297 + 116, 0., 4.93553591 + 117, 0., 4.68875933 + 118, 0., 4.44198227 + 119, 0., 4.19520569 + 120, 0., 3.94842863 + 121, 0., 3.70165205 + 122, 0., 3.45487523 + 123, 0., 3.20809841 + 124, 0., 2.96132159 + 125, 0., 2.71454477 + 126, 0., 2.46776795 + 127, 0., 2.22099113 + 128, 0., 1.97421432 + 129, 0., 1.72743762 + 130, 0., 1.4806608 + 131, 0., 1.23388398 + 132, 0., 0.987107158 + 133, 0., 0.740330398 + 134, 0., 0.493553579 + 135, 0., 0.246776789 + 136, 0.251935899, 0. + 137, 0.503871799, 0. + 138, 0.755807757, 0. + 139, 1.0077436, 0. + 140, 1.25967956, 0. + 141, 1.51161551, 0. + 142, 1.76355135, 0. + 143, 2.01548719, 0. + 144, 2.26742315, 0. + 145, 2.51935911, 0. + 146, 2.77129507, 0. + 147, 3.02323103, 0. + 148, 5., 2.476439 + 149, 5., 2.72722435 + 150, 5., 2.9780097 + 151, 5., 3.22879505 + 152, 5., 3.4795804 + 153, 5., 3.73036575 + 154, 5., 3.9811511 + 155, 5., 4.23193645 + 156, 5., 4.48272181 + 157, 5., 4.73350716 + 158, 5., 4.98429251 + 159, 5., 5.23507786 + 160, 5., 5.48586321 + 161, 5., 5.73664856 + 162, 5., 5.98743391 + 163, 5., 6.23821926 + 164, 5., 6.48900509 + 165, 5., 6.73979044 + 166, 5., 6.99057579 + 167, 5., 7.24136114 + 168, 5., 7.49214649 + 169, 5., 7.74293184 + 170, 5., 7.99371719 + 171, 5., 8.24450207 + 172, 5., 8.4952879 + 173, 5., 8.74607277 + 174, 5., 8.9968586 + 175, 5., 9.24764347 + 176, 5., 9.4984293 + 177, 5., 9.74921417 + 178, 4.75758982, 10. + 179, 4.51517916, 10. + 180, 4.27276897, 10. + 181, 4.03035831, 10. + 182, 3.78794789, 10. + 183, 3.54553747, 10. + 184, 3.30312705, 10. + 185, 3.06071663, 10. + 186, 2.81830621, 10. + 187, 2.57589579, 10. + 188, 2.33348536, 10. + 189, 1.82969058, 10. + 190, 1.56830621, 10. + 191, 1.30692184, 10. + 192, 1.04553747, 10. + 193, 0.784153104, 10. + 194, 0.522768736, 10. + 195, 0.261384368, 10. + 196, 0., 9.73710728 + 197, 0., 9.47421455 + 198, 0., 9.21132183 + 199, 0., 8.94842911 + 200, 0., 8.68553638 + 201, 0., 8.42264271 + 202, 0., 8.15974998 + 203, 0.973807514, 5.64152575 + 204, 0.672649384, 5.35470104 + 205, 0.679396152, 5.14091873 + 206, 0.720911503, 4.92726994 + 207, 0.781964779, 4.7151165 + 208, 0.855159938, 4.50267982 + 209, 0.94685477, 4.29038286 + 210, 1.1606921, 3.86384463 + 211, 2.67286754, 4.39865017 + 212, 3.36048746, 3.70627141 + 213, 4.03994894, 2.99737167 + 214, 2.77565169, 1.11140943 + 215, 4.68485451, 2.27030969 + 216, 2.10387945, 2.14785957 + 217, 1.61996877, 2.993011 + 218, 1.57863486, 5.3021059 + 219, 1.38666379, 5.43362713 + 220, 1.18622959, 5.5488534 + 221, 2.14144897, 4.87226915 + 222, 1.05005181, 4.07865858 + 223, 1.27663255, 3.64762735 + 224, 1.39274657, 3.43012977 + 225, 1.5062542, 3.21150661 + 226, 1.7355994, 2.77632833 + 227, 1.85460353, 2.56274581 + 228, 1.97705257, 2.35365009 + 229, 2.23173213, 1.94199228 + 230, 2.36161566, 1.7354393 + 231, 2.49381304, 1.52864456 + 232, 2.9154892, 0.895557523 + 233, 3.01664996, 0.679168582 + 234, 3.12428594, 0.475986749 + 235, 4.03681564, 0.261101872 + 236, 3.80775428, 0.287673861 + 237, 3.6015718, 0.336293489 + 238, 4.76515532, 1.74470663 + 239, 4.76115131, 1.49393153 + 240, 4.76234913, 1.24100125 + 241, 4.76387596, 0.989404619 + 242, 4.76352215, 0.739772499 + 243, 4.76111841, 0.491587102 + 244, 4.75765705, 0.244751707 + 245, 2.63037539, 1.32145262 + 246, 4.52636909, 2.44449496 + 247, 4.36593151, 2.63153505 + 248, 4.20449257, 2.81620669 + 249, 3.87311888, 3.17682981 + 250, 3.70341086, 3.3545239 + 251, 3.53225231, 3.53093004 + 252, 3.18832397, 3.88202786 + 253, 3.01613212, 4.05721712 + 254, 2.84439182, 4.23045778 + 255, 2.50090814, 4.56281996 + 256, 2.32446527, 4.72130537 + 257, 1.95573187, 5.01833439 + 258, 1.76773417, 5.16178083 + 259, 0.880063057, 5.30150366 + 260, 1.12331378, 5.37326574 + 261, 0.867368281, 5.12396193 + 262, 1.30318725, 5.25268936 + 263, 0.913232267, 4.94071674 + 264, 1.47718477, 5.11260653 + 265, 0.97739017, 4.74718142 + 266, 1.6577419, 4.96602011 + 267, 1.02774322, 4.55594206 + 268, 1.84268188, 4.82405233 + 269, 1.12131429, 4.36567068 + 270, 2.02573419, 4.69117212 + 271, 1.23069274, 4.16896439 + 272, 2.20295191, 4.5522747 + 273, 1.34624553, 3.95992851 + 274, 2.36758304, 4.39994907 + 275, 1.47416794, 3.74867034 + 276, 1.59906876, 3.52854037 + 277, 2.52844501, 4.23871613 + 278, 1.71068442, 3.30304337 + 279, 2.6930337, 4.0715971 + 280, 1.81709468, 3.07944298 + 281, 2.86153769, 3.89719415 + 282, 1.92556727, 2.86145949 + 283, 2.04035378, 2.65055776 + 284, 3.03192496, 3.71913791 + 285, 2.16219497, 2.44817996 + 286, 3.20425892, 3.54054236 + 287, 2.2884686, 2.24853444 + 288, 2.41554999, 2.0453124 + 289, 3.37840509, 3.36535048 + 290, 2.54309368, 1.83881772 + 291, 2.67174029, 1.63165271 + 292, 3.5534482, 3.19187236 + 293, 2.80399013, 1.4247396 + 294, 2.95255184, 1.21451378 + 295, 3.72653413, 3.01811409 + 296, 3.15584826, 0.980011702 + 297, 3.42205167, 0.671850681 + 298, 3.89451694, 2.84293318 + 299, 4.05763483, 2.66507459 + 300, 4.21746731, 2.48292255 + 301, 4.3772974, 2.29167056 + 302, 3.28389192, 0.287503958 + 303, 4.78433084, 1.98283207 + 304, 4.27439785, 0.249533102 + 305, 3.84721541, 0.550268888 + 306, 4.51536608, 0.245161921 + 307, 4.06476593, 0.518047392 + 308, 4.52791452, 1.77157307 + 309, 4.52685118, 1.50613189 + 310, 4.53277349, 1.24514461 + 311, 4.53499413, 0.990917981 + 312, 4.53188801, 0.740968168 + 313, 4.52448225, 0.492647111 + 314, 0.922180414, 5.46720314 + 315, 1.05890059, 5.2251997 + 316, 1.00295722, 5.11731243 + 317, 1.3594259, 4.93706131 + 318, 1.20997536, 4.77636242 + 319, 1.54035318, 4.77381039 + 320, 1.28751743, 4.46344805 + 321, 1.73090982, 4.61930132 + 322, 1.41189182, 4.27282143 + 323, 1.91934919, 4.50623846 + 324, 1.51916087, 4.06080723 + 325, 2.0892458, 4.39481068 + 326, 1.66899478, 3.86361361 + 327, 2.23092055, 4.24662924 + 328, 1.81379342, 3.62936091 + 329, 2.37878919, 4.08412504 + 330, 1.91800821, 3.38440681 + 331, 2.54130054, 3.92124534 + 332, 2.0114634, 3.15428352 + 333, 2.70745993, 3.74560833 + 334, 2.10796428, 2.93807435 + 335, 2.21513247, 2.73488665 + 336, 2.87583184, 3.56333637 + 337, 2.33849502, 2.545156 + 338, 3.04937744, 3.37673497 + 339, 2.46854496, 2.35304713 + 340, 3.22991991, 3.20030499 + 341, 2.59382033, 2.14824486 + 342, 3.41263199, 3.03115249 + 343, 2.71753788, 1.93899107 + 344, 2.84178472, 1.73112607 + 345, 3.59043574, 2.86327219 + 346, 2.96502399, 1.52736413 + 347, 3.75794649, 2.69383216 + 348, 3.08705139, 1.33564889 + 349, 3.26469612, 1.1880734 + 350, 3.91800928, 2.52073956 + 351, 3.43564487, 0.899599791 + 352, 4.07390261, 2.34403205 + 353, 4.5474968, 2.05910373 + 354, 3.38750124, 0.453190833 + 355, 3.87520504, 0.792925715 + 356, 4.29147434, 0.500586629 + 357, 4.31443214, 0.995734751 + 358, 4.31396532, 1.24746239 + 359, 4.30196667, 1.50999486 + 360, 4.30633354, 0.748775423 + 361, 1.41927731, 4.60140657 + 362, 1.61808896, 4.38881969 + 363, 1.65455151, 4.1494112 + 364, 1.9974618, 4.26964521 + 365, 1.85506511, 4.0265007 + 366, 2.05275035, 3.72885036 + 367, 2.21838021, 3.92722082 + 368, 4.08768654, 0.766009986 + 369, 2.13132668, 3.44207287 + 370, 2.39595938, 3.78103399 + 371, 2.20699573, 3.2127564 + 372, 2.55395031, 3.60378575 + 373, 2.28589582, 3.00307894 + 374, 2.72012901, 3.42040205 + 375, 2.37313676, 2.81587243 + 376, 2.89275479, 3.21262431 + 377, 2.50317407, 2.65277672 + 378, 3.08908844, 3.03256989 + 379, 2.64976096, 2.46619415 + 380, 2.77109504, 2.24725437 + 381, 3.28326201, 2.87396622 + 382, 2.88753486, 2.0321908 + 383, 3.46391225, 2.71648526 + 384, 3.00312233, 1.82608974 + 385, 3.62819886, 2.55154085 + 386, 3.1441946, 1.62076044 + 387, 3.78378701, 2.382617 + 388, 3.18632364, 1.43532491 + 389, 3.33348799, 1.37565255 + 390, 3.9372499, 2.21105671 + 391, 3.51467776, 1.31715441 + 392, 4.22475243, 2.16486597 + 393, 3.6637764, 0.836598992 + 394, 4.26912975, 1.80300879 + 395, 4.10574055, 1.24890351 + 396, 4.1021657, 1.00794232 + 397, 4.09444952, 1.49302042 + 398, 2.28397965, 3.66375613 + 399, 2.38307381, 3.4643054 + 400, 2.41200304, 3.24926353 + 401, 2.48246503, 3.04365587 + 402, 2.57302761, 3.30517244 + 403, 2.49728537, 2.88633823 + 404, 2.71867204, 3.04038453 + 405, 2.95991707, 2.8511219 + 406, 2.85620022, 2.60618329 + 407, 3.17290306, 2.71801281 + 408, 2.94781852, 2.33141279 + 409, 3.3491528, 2.57875347 + 410, 3.05801225, 2.11588907 + 411, 3.16234899, 1.91835964 + 412, 3.50242782, 2.41727567 + 413, 3.53619814, 1.70881748 + 414, 3.65180469, 2.24827933 + 415, 3.53162074, 1.51850581 + 416, 3.71024299, 1.49244404 + 417, 4.09039736, 2.02849174 + 418, 3.68771338, 1.05820608 + 419, 4.05409193, 1.72589207 + 420, 3.90312886, 1.25781167 + 421, 3.8965621, 1.48377073 + 422, 3.89414358, 1.02854753 + 423, 3.09725618, 2.55756831 + 424, 3.12315083, 2.37929106 + 425, 3.36353683, 1.97458673 + 426, 3.67784882, 1.92867684 + 427, 3.8395679, 1.85137844 + 428, 3.69795942, 1.69847858 + 429, 3.70554543, 1.28018534 + 430, 3.80556846, 2.08412933 + 431, 3.8706305, 1.68945253 + 432, 3.95216584, 1.9279319 + 433, 4.82061434, 2.15949702 + 434, 0.757405937, 5.71067142 + 435, 0.702483416, 5.54661465 + 436, 3.6392653, 0.601778448 + 437, 3.23184657, 0.5454548 + 438, 3.20639467, 0.721225381 + 439, 1.20589948, 5.09744406 + 440, 4.35292387, 2.00907469 + 441, 3.47472072, 1.10926437 + 442, 1.08412814, 4.98405218 + 443, 1.17043638, 4.60013437 + 444, 1.84781849, 4.30042839 + 445, 3.35147524, 1.56358087 + 446, 2.07131934, 4.11075401 + 447, 3.3687861, 1.74681973 + 448, 3.22907114, 1.75865304 + 449, 3.53449011, 1.87152076 + 450, 3.516258, 2.11609292 + 451, 2.64527297, 2.79778051 + 452, 3.24403, 2.45539331 + 453, 3.22840595, 2.19126511 + 454, 3.37259936, 2.2929368 + 455, 2.4108088, 9.83638668 + 456, 2.86117649, 9.37796688 + 457, 3.28445244, 8.71049309 + 458, 3.50911093, 8.27806091 + 459, 3.72017241, 7.83064175 + 460, 3.88399744, 7.3589716 + 461, 3.92804837, 6.63220882 + 462, 3.89094019, 6.34201431 + 463, 4.32786322, 3.32551622 + 464, 3.70468998, 6.18733215 + 465, 3.12187243, 6.00620985 + 466, 2.39791179, 5.20179033 + 467, 2.2920475, 6.17575836 + 468, 1.81095326, 5.64509106 + 469, 1.80803704, 6.42075062 + 470, 1.61086559, 5.78906393 + 471, 1.41189861, 5.92716932 + 472, 1.35789883, 6.65433645 + 473, 1.23672736, 6.06458616 + 474, 0.929484665, 6.96031141 + 475, 0.957541645, 6.08234596 + 476, 0.361616492, 7.42585182 + 477, 0.212257892, 6.60427141 + 478, 0.352900445, 5.99151278 + 479, 0.241831586, 5.73433542 + 480, 0.283471107, 4.87806416 + 481, 0.249569535, 5.15631962 + 482, 1.71955645, 0.203676388 + 483, 0.221102059, 1.93454731 + 484, 0.235012665, 2.94983053 + 485, 0.215633228, 3.69619918 + 486, 0.506692231, 4.11645603 + 487, 0.407230794, 4.65146875 + 488, 2.83894563, 6.03509617 + 489, 2.95442152, 4.71990299 + 490, 3.64256597, 4.03134441 + 491, 3.93502808, 7.11075354 + 492, 3.81268239, 7.59927225 + 493, 3.61628318, 8.05726719 + 494, 3.39840722, 8.49433804 + 495, 3.16416526, 8.93145847 + 496, 3.03054976, 9.14953995 + 497, 2.62666154, 9.5164566 + 498, 2.49916673, 9.65574455 + 499, 0.172101751, 7.55041647 + 500, 0.554363906, 7.28342295 + 501, 0.739357591, 7.12680244 + 502, 1.13484716, 6.79757357 + 503, 1.58239126, 6.5421195 + 504, 2.0410533, 6.29534721 + 505, 2.56473327, 6.09430313 + 506, 0.234787583, 0.475192547 + 507, 0.230055556, 0.716940522 + 508, 0.226245776, 0.959568858 + 509, 0.222862899, 1.20320451 + 510, 0.220455259, 1.44745612 + 511, 1.73499715, 1.95080817 + 512, 0.219229534, 1.69143581 + 513, 0.227336779, 2.18094993 + 514, 0.24053365, 2.43198848 + 515, 1.24591637, 2.80860519 + 516, 0.254442602, 2.68946838 + 517, 0.212850854, 3.20173979 + 518, 0.206893757, 3.45115924 + 519, 0.797101736, 3.65922165 + 520, 0.207491204, 3.93548226 + 521, 0.232475057, 4.16935015 + 522, 0.231949061, 4.41238165 + 523, 0.173541203, 6.10972881 + 524, 0.201294869, 6.35126972 + 525, 0.604888678, 6.15721369 + 526, 0.188421533, 6.85765886 + 527, 0.327277064, 7.25619507 + 528, 2.9268589, 0.181388468 + 529, 2.69477177, 0.174815401 + 530, 2.46155739, 0.182312906 + 531, 2.21782112, 0.191521406 + 532, 2.41836572, 0.930832446 + 533, 1.96842504, 0.198488116 + 534, 1.47378075, 0.207858175 + 535, 1.22839928, 0.212011382 + 536, 0.981900871, 0.216499954 + 537, 0.73442018, 0.221566692 + 538, 0.458281279, 4.40526009 + 539, 0.663220644, 3.87107015 + 540, 0.915363371, 3.44702315 + 541, 1.02346122, 3.23474789 + 542, 1.13260829, 3.02176642 + 543, 1.36246014, 2.59207416 + 544, 1.47973073, 2.37489605 + 545, 1.60480738, 2.16099954 + 546, 1.86769164, 1.74099636 + 547, 2.00356293, 1.5321728 + 548, 2.14913392, 1.33592963 + 549, 2.2868855, 1.13770711 + 550, 2.55748582, 0.72861433 + 551, 2.70742416, 0.556133747 + 552, 2.82731247, 0.368356228 + 553, 4.52782059, 3.12249255 + 554, 4.15351152, 3.50853753 + 555, 3.98129129, 3.6849575 + 556, 3.81127501, 3.85773134 + 557, 3.47257543, 4.20437956 + 558, 3.30427003, 4.3768568 + 559, 3.13238478, 4.54891539 + 560, 2.77131772, 4.88584614 + 561, 2.58377218, 5.04432583 + 562, 2.20483875, 5.3520689 + 563, 2.00621414, 5.49791431 + 564, 4.76165819, 9.2550869 + 565, 4.76298141, 9.00559998 + 566, 4.76818132, 8.75779533 + 567, 4.77375269, 8.50925064 + 568, 4.7759223, 8.25833702 + 569, 4.77202845, 8.00309563 + 570, 4.76580143, 7.74814653 + 571, 4.76760387, 7.49257469 + 572, 4.77624559, 7.23727798 + 573, 4.78759289, 6.98741913 + 574, 4.79503679, 6.74268627 + 575, 4.78756428, 6.49545479 + 576, 4.77813721, 6.24852371 + 577, 4.77953196, 5.99803782 + 578, 4.78798962, 5.7447114 + 579, 4.79525089, 5.49355364 + 580, 4.79865551, 5.2460022 + 581, 4.79782057, 4.99835157 + 582, 4.79308271, 4.74553442 + 583, 4.79427767, 4.49273539 + 584, 4.81511688, 4.24470234 + 585, 4.81837797, 3.99204612 + 586, 4.81548214, 3.74042606 + 587, 4.83503628, 3.50449395 + 588, 4.81383085, 3.26617265 + 589, 2.84522104, 9.81955051 + 590, 3.0781517, 9.80349827 + 591, 3.31391954, 9.78490257 + 592, 3.55375147, 9.77392673 + 593, 3.79779148, 9.7695322 + 594, 4.04145813, 9.76682281 + 595, 4.28172159, 9.76296234 + 596, 4.52121878, 9.75835419 + 597, 4.05066395, 9.53560448 + 598, 3.08195305, 9.60178947 + 599, 3.31151628, 9.55638218 + 600, 2.61964202, 9.83550358 + 601, 3.55640817, 9.54206657 + 602, 3.80596209, 9.54096985 + 603, 4.28687429, 9.52443695 + 604, 0.404190481, 6.26079035 + 605, 0.369856209, 6.8381176 + 606, 0.453453898, 0.936619043 + 607, 0.443733931, 1.17720592 + 608, 0.444038987, 2.13499951 + 609, 0.462859511, 3.70046401 + 610, 0.395986676, 3.44245267 + 611, 0.467142642, 2.38104534 + 612, 0.463196486, 0.697924256 + 613, 0.438198358, 1.41761267 + 614, 0.436329693, 1.65745354 + 615, 0.532318532, 2.66879296 + 616, 0.408428103, 3.21059704 + 617, 0.456741184, 2.96636295 + 618, 0.434058249, 1.89483881 + 619, 0.385767519, 3.92023993 + 620, 0.319388658, 7.05513573 + 621, 0.455809355, 6.54825401 + 622, 2.58622074, 0.500510931 + 623, 2.25715613, 0.854063511 + 624, 2.13970184, 1.06048179 + 625, 2.00880814, 1.23956096 + 626, 1.83349729, 1.41642559 + 627, 1.69854152, 1.6437844 + 628, 3.27044582, 9.2869606 + 629, 1.56150913, 1.8502481 + 630, 1.4217062, 2.05849648 + 631, 4.44865322, 3.52125716 + 632, 4.27441931, 3.68916821 + 633, 1.29043043, 2.27866387 + 634, 4.09932041, 3.85716438 + 635, 3.38136673, 9.02196693 + 636, 1.18482828, 2.50355721 + 637, 3.93192577, 4.0219636 + 638, 3.76645541, 4.187747 + 639, 1.06945038, 2.71734929 + 640, 3.60181713, 4.35837936 + 641, 0.953534245, 2.92336774 + 642, 3.48866487, 8.78867531 + 643, 3.44520235, 4.53151894 + 644, 0.850104988, 3.13297915 + 645, 3.27819037, 4.7046442 + 646, 3.09917498, 4.87721252 + 647, 0.75533247, 3.33892965 + 648, 3.59435892, 8.57237816 + 649, 2.91527319, 5.05122662 + 650, 0.644555449, 3.53235507 + 651, 2.72114825, 5.19808292 + 652, 4.51807356, 9.01272011 + 653, 4.53145266, 8.7650404 + 654, 4.55804777, 8.27244377 + 655, 4.52686024, 7.75706244 + 656, 4.55267525, 7.2229476 + 657, 4.57393169, 6.97127247 + 658, 4.57491827, 6.51007032 + 659, 4.55811834, 6.00181484 + 660, 4.59519005, 5.49645042 + 661, 4.58414984, 4.76296902 + 662, 4.64568663, 4.24249077 + 663, 4.64205694, 4.009408 + 664, 4.59238958, 3.73225331 + 665, 4.61723328, 3.35779572 + 666, 4.56509924, 4.48793459 + 667, 4.59857893, 5.25642586 + 668, 4.54758406, 8.51896095 + 669, 4.53422403, 7.48859358 + 670, 4.59997511, 6.74414396 + 671, 4.54485226, 6.26263905 + 672, 4.58302498, 5.7439642 + 673, 4.59831905, 5.01679611 + 674, 4.76187944, 9.50524998 + 675, 4.55022669, 8.01729107 + 676, 3.69601488, 8.35623932 + 677, 2.54981256, 5.36392307 + 678, 1.19763327, 0.434967101 + 679, 1.4352088, 0.427915186 + 680, 2.17215896, 0.393190652 + 681, 0.957936943, 0.441706896 + 682, 1.67623436, 0.41645512 + 683, 1.92394233, 0.403662682 + 684, 2.62228489, 0.354183406 + 685, 3.79698086, 8.13487339 + 686, 2.34619212, 5.50862789 + 687, 2.14278865, 5.64092398 + 688, 3.90586662, 7.90707493 + 689, 1.97508311, 5.79683781 + 690, 4.01797056, 7.67531729 + 691, 0.225501388, 4.64979362 + 692, 1.7760247, 5.95136595 + 693, 0.811544597, 6.80487347 + 694, 4.08829641, 7.42218924 + 695, 1.00927007, 6.61022186 + 696, 1.25588632, 6.42738295 + 697, 1.49454367, 6.37037277 + 698, 1.70287478, 6.25214863 + 699, 1.92998147, 6.1184082 + 700, 1.57505023, 6.09141064 + 701, 4.13205004, 7.17558908 + 702, 2.19911671, 5.95445919 + 703, 2.51740789, 5.8886447 + 704, 2.80169129, 5.81228304 + 705, 4.5234704, 9.51363754 + 706, 1.08227098, 6.24690199 + 707, 4.16094065, 6.97477341 + 708, 0.715802193, 0.450016677 + 709, 3.08461976, 5.77524042 + 710, 0.127329499, 5.92354059 + 711, 0.839933276, 6.41886663 + 712, 2.40891767, 0.379607052 + 713, 4.28348446, 9.28182793 + 714, 3.81723332, 9.32044029 + 715, 3.56453133, 9.3089819 + 716, 4.05582666, 9.3121376 + 717, 2.65307784, 9.65912628 + 718, 0.679821134, 2.29384971 + 719, 0.74805361, 2.49352527 + 720, 0.759225726, 2.81331277 + 721, 0.660821676, 3.03603935 + 722, 0.656739831, 2.08078218 + 723, 0.658689499, 1.15989041 + 724, 0.651500881, 1.39113069 + 725, 0.655483484, 1.62722564 + 726, 0.638338864, 1.85876155 + 727, 0.679237723, 0.922700822 + 728, 0.658754468, 6.66701984 + 729, 0.643583238, 6.3752861 + 730, 2.0689683, 0.806528687 + 731, 1.99838483, 0.990758121 + 732, 1.87784195, 1.12849319 + 733, 1.61966538, 1.37504077 + 734, 4.52101946, 9.26456165 + 735, 1.52439559, 1.55333912 + 736, 1.39335263, 1.74326479 + 737, 4.38515854, 3.87814498 + 738, 1.24227977, 1.94515145 + 739, 4.20985365, 4.03407383 + 740, 1.07561815, 2.18382955 + 741, 4.04976463, 4.18976879 + 742, 4.35116673, 8.28272533 + 743, 4.25802135, 7.77974749 + 744, 4.30567026, 7.47222948 + 745, 4.3477335, 8.04521561 + 746, 4.40154028, 5.4976387 + 747, 4.32326651, 8.51756573 + 748, 4.34105587, 6.91436481 + 749, 4.3369832, 7.19988251 + 750, 4.29064655, 8.76015377 + 751, 4.37432432, 4.80726004 + 752, 4.31237984, 4.54991245 + 753, 4.31326628, 4.21283436 + 754, 4.25187349, 9.01787186 + 755, 4.40468121, 5.04298258 + 756, 4.39215183, 5.26788378 + 757, 4.36728907, 6.55838823 + 758, 4.27063274, 6.28614616 + 759, 4.3438921, 5.98305607 + 760, 4.39177513, 5.73028374 + 761, 3.88823676, 4.34337139 + 762, 1.01551569, 2.42892599 + 763, 3.73103642, 4.50358629 + 764, 0.895799339, 2.61999679 + 765, 1.3968389, 0.666822553 + 766, 1.63965714, 0.635258853 + 767, 1.1649574, 0.675416231 + 768, 0.933359802, 0.677797139 + 769, 3.60201526, 4.68676138 + 770, 3.43225431, 4.8657217 + 771, 3.24473882, 5.02272844 + 772, 3.61593223, 9.07766247 + 773, 3.07023811, 5.25455236 + 774, 0.646770358, 6.99278545 + 775, 3.70000291, 8.85389805 + 776, 2.75163984, 5.55192566 + 777, 4.19023705, 6.68921375 + 778, 3.80291104, 8.648633 + 779, 2.4694066, 5.68542147 + 780, 3.97379947, 8.21141052 + 781, 0.487184495, 0.22759299 + 782, 4.07542372, 7.99558258 + 783, 2.26087856, 5.76158762 + 784, 4.04563046, 9.10952091 + 785, 3.84090066, 9.11067772 + 786, 0.8607651, 1.15913522 + 787, 0.885226429, 2.38238668 + 788, 0.895114839, 2.02002692 + 789, 0.807777226, 1.84036577 + 790, 0.906976283, 1.61435091 + 791, 0.854355693, 1.35906088 + 792, 4.06642151, 6.48492193 + 793, 0.698063195, 0.685214639 + 794, 4.14694786, 5.91033983 + 795, 4.14013386, 5.26959372 + 796, 4.15875912, 4.90595961 + 797, 4.03673697, 6.13659 + 798, 4.00275707, 4.51403427 + 799, 4.08054781, 4.70310926 + 800, 4.03697729, 8.74458122 + 801, 4.1020503, 8.49376392 + 802, 4.22035313, 5.51301384 + 803, 4.2207551, 5.70079708 + 804, 1.63300276, 0.840721726 + 805, 1.51805162, 1.08886468 + 806, 1.46751332, 1.26360774 + 807, 1.2398355, 1.62062562 + 808, 1.12473977, 0.935580373 + 809, 1.37529385, 0.945088208 + 810, 1.07893109, 1.80248773 + 811, 3.84887171, 4.60929871 + 812, 3.81942821, 4.83213997 + 813, 3.58462954, 5.07029438 + 814, 3.36952829, 5.12876081 + 815, 3.30995369, 5.30425835 + 816, 3.05563951, 5.53222036 + 817, 3.8944428, 8.91836166 + 818, 4.1936779, 8.09700203 + 819, 4.05941057, 8.94575405 + 820, 1.05377555, 1.2211988 + 821, 1.25504375, 1.37549269 + 822, 3.77836943, 5.23501825 + 823, 3.97200847, 5.79699993 + 824, 3.91802955, 5.45279455 + 825, 3.82390404, 5.67002583 + 826, 3.63173127, 5.39074755 + 827, 3.48967266, 5.47939348 + 828, 3.29603839, 5.51362419 + 829, 3.55305576, 5.65447855 + 830, 3.83853531, 5.99448729 + 831, 3.66056919, 5.83413124 + 832, 1.27922451, 1.20178056 + 833, 3.72372866, 5.54110813 + 834, 3.44201612, 6.00370884 + 835, 3.34270072, 5.73890448 + 836, 1.86033368, 0.800392091 + 837, 4.46802235, 4.07679605 + 838, 0.159837723, 7.33260489 + 839, 2.1310637, 0.598815322 + 840, 1.88707006, 0.607604742 + 841, 4.8816762, 2.54674029 + 842, 4.76186705, 2.68526673 + 843, 4.86910963, 2.73588276 + 844, 0.164021879, 7.09666538 + 845, 4.76089144, 9.7537241 + 846, 4.77752447, 3.00741482 + 847, 3.99431801, 6.84189606 + 848, 4.63575172, 2.89176965 + 849, 0.23264049, 5.42377424 + 850, 0.241354629, 0.235474676 + 851, 0.788837731, 6.12942457 + 852, 2.86296678, 9.62001801 + 853, 0.497061104, 7.14849567 + 854, 0.474074334, 0.461032599 + 855, 1.39323926, 6.22695637 + 856, 0.441105545, 7.03994751 + 857, 2.38428283, 0.609665096 + 858, 0.525836945, 6.88395166 + 859, 4.69000864, 3.52213979 + 860, 3.0661304, 9.42920494 + 861, 2.84161305, 5.33058929 + 862, 4.44257975, 6.74027872 + 863, 0.538823962, 3.41336679 + 864, 0.589927793, 3.24827933 + 865, 4.16826916, 4.36735058 + 866, 3.5007441, 5.31070042 + 867, 4.07791185, 5.61504745 + 868, 1.85062718, 0.968513489 + 869, 1.67213607, 0.999986887 + 870, 4.52033567, 4.22516966 + 871, 4.42027092, 4.35567236 + 872, 0.904065907, 0.922198057 + 873, 3.8913846, 8.43128109 + 874, 1.70466316, 1.12599218 + 875, 3.96469164, 5.05720854 + 876, 0.870307028, 2.22849345 + 877, 4.23839521, 5.06487942 + 878, 1.59161544, 1.18235123 + 879, 1.79294515, 1.26881027 + 880, 1.67794836, 1.24547601 + 881, 1.37398791, 1.45580494 + 882, 4.15492487, 8.27271175 + 883, 1.11331213, 1.45796144 + 884, 3.26443172, 6.69344568 + 885, 3.44586778, 7.23210526 + 886, 3.31109977, 7.66801023 + 887, 3.12128806, 8.08964157 + 888, 2.89547586, 8.49848938 + 889, 2.5078795, 9.08352757 + 890, 2.22820139, 9.43945122 + 891, 2.64258742, 8.89311028 + 892, 3.01178551, 8.2962141 + 893, 3.22133279, 7.88075924 + 894, 3.38582253, 7.45226765 + 895, 3.48805547, 7.0092926 + 896, 2.70179415, 6.52288294 + 897, 2.25198698, 6.67098522 + 898, 1.82223403, 6.8839097 + 899, 1.41447318, 7.14160395 + 900, 2.77223063, 8.69822502 + 901, 0.839704931, 7.59581184 + 902, 2.36990523, 9.26712704 + 903, 2.07977533, 9.59036255 + 904, 1.26032722, 9.78371906 + 905, 1.50427449, 9.77897263 + 906, 1.73595548, 9.75513554 + 907, 0.575098455, 7.9433198 + 908, 0.253834099, 8.70643806 + 909, 0.250485837, 8.97101784 + 910, 0.246679634, 9.23463917 + 911, 0.246349171, 9.49458885 + 912, 0.25079903, 9.75024605 + 913, 0.250947326, 8.4354353 + 914, 0.687459946, 7.76655197 + 915, 1.01933801, 7.43295717 + 916, 1.2120316, 7.28144121 + 917, 1.61635852, 7.00796127 + 918, 2.03360891, 6.76636553 + 919, 2.47384691, 6.59030056 + 920, 2.93518472, 6.47847414 + 921, 3.20128512, 7.17105722 + 922, 3.14862323, 7.37986946 + 923, 2.76808548, 6.73091078 + 924, 3.08297133, 7.58403254 + 925, 3.00305867, 7.78620005 + 926, 2.9111762, 7.98712063 + 927, 2.81006622, 8.18641758 + 928, 2.56471491, 6.80170965 + 929, 2.70185971, 8.38173676 + 930, 2.58577275, 8.57244301 + 931, 2.4606843, 8.75747681 + 932, 2.32774782, 8.93581104 + 933, 2.19383049, 9.10267448 + 934, 2.06325507, 9.25681305 + 935, 1.80306113, 9.48761749 + 936, 2.35235882, 6.86534166 + 937, 3.5215745, 6.79820967 + 938, 1.92584395, 9.69947433 + 939, 2.14693379, 6.94228458 + 940, 1.96432817, 7.06014967 + 941, 1.77551246, 7.17559385 + 942, 1.58020365, 7.30187273 + 943, 1.39152193, 7.43288231 + 944, 1.21709216, 7.57529783 + 945, 1.06589115, 7.7374959 + 946, 0.949348867, 7.90703678 + 947, 0.860470235, 8.08385849 + 948, 3.1572957, 6.44555283 + 949, 0.243302971, 8.14115429 + 950, 1.01014817, 9.77998829 + 951, 1.21649849, 9.564538 + 952, 0.757980824, 9.77186489 + 953, 0.504517496, 9.7615881 + 954, 1.44047308, 9.56250095 + 955, 0.491437674, 9.26263428 + 956, 0.511831939, 8.46268082 + 957, 0.519837916, 8.73548222 + 958, 0.502064884, 9.00114346 + 959, 2.95562172, 7.11761761 + 960, 3.23876977, 6.94978142 + 961, 2.68320894, 7.03566837 + 962, 2.90918875, 7.3143096 + 963, 2.85547376, 7.50700283 + 964, 2.42784214, 7.07695627 + 965, 2.78364611, 7.6984477 + 966, 2.69955277, 7.8918891 + 967, 2.60601211, 8.08448696 + 968, 0.493367106, 9.51575851 + 969, 2.23394656, 7.09227085 + 970, 2.50672507, 8.27114105 + 971, 2.40023112, 8.45136261 + 972, 2.28241992, 8.62395477 + 973, 2.14772224, 8.78711414 + 974, 2.00712681, 8.93485165 + 975, 1.88419008, 9.08535099 + 976, 2.12988138, 7.2555933 + 977, 1.91670477, 7.3405385 + 978, 1.73274744, 7.46138573 + 979, 1.5569247, 7.57976055 + 980, 1.39512563, 7.69793034 + 981, 1.27387404, 7.85049295 + 982, 1.638129, 9.53847218 + 983, 2.99667573, 6.70544195 + 984, 1.18484545, 8.0159626 + 985, 1.10697722, 8.20029831 + 986, 0.521359444, 8.1929493 + 987, 0.979242027, 9.55446148 + 988, 1.56456792, 9.33289623 + 989, 0.73837465, 9.53689766 + 990, 0.744326234, 9.04840279 + 991, 0.823267519, 8.77529526 + 992, 0.775444508, 8.48760128 + 993, 3.00305986, 6.92011833 + 994, 2.6727767, 7.26510096 + 995, 2.63461566, 7.43675661 + 996, 2.5637567, 7.61359739 + 997, 2.48786426, 7.80298233 + 998, 2.43149996, 7.31560326 + 999, 2.39888573, 7.98993063 + 1000, 2.30840921, 8.16810894 + 1001, 2.21586895, 8.33919621 + 1002, 2.11677074, 8.4989624 + 1003, 2.07776213, 7.47990704 + 1004, 1.99046803, 8.64122009 + 1005, 1.80092359, 8.74830341 + 1006, 1.86615074, 7.61221886 + 1007, 1.7108326, 7.75382423 + 1008, 1.52014589, 7.79818249 + 1009, 1.47425258, 7.92851496 + 1010, 1.41126013, 8.09132481 + 1011, 1.35314918, 8.27144241 + 1012, 1.77743268, 9.22130585 + 1013, 0.792899489, 8.27680111 + 1014, 1.18474948, 9.34948826 + 1015, 0.730470181, 9.29666996 + 1016, 0.959914625, 9.32985687 + 1017, 0.998169124, 8.95279312 + 1018, 0.962593377, 9.11644936 + 1019, 2.28536439, 7.7221117 + 1020, 2.18845487, 7.90147209 + 1021, 2.10421538, 8.07550335 + 1022, 2.0274682, 8.24559689 + 1023, 1.90111172, 8.52198219 + 1024, 1.75951087, 8.53035164 + 1025, 1.96000016, 7.79748821 + 1026, 1.89227557, 7.99920273 + 1027, 1.67874849, 7.95630217 + 1028, 1.62610006, 8.1370306 + 1029, 1.58103716, 8.31645203 + 1030, 1.69321668, 8.93917942 + 1031, 1.02808058, 8.42517281 + 1032, 1.34026957, 9.13704014 + 1033, 1.20014048, 8.94735432 + 1034, 1.17010069, 9.14936066 + 1035, 1.83107018, 8.18110085 + 1036, 1.95809746, 8.4007206 + 1037, 1.78192151, 8.3526001 + 1038, 1.54320312, 8.8304863 + 1039, 1.3173995, 8.46456623 + 1040, 1.55586398, 8.6837883 + 1041, 1.55788839, 8.50111961 + 1042, 1.33076644, 8.67300224 + 1043, 1.40035295, 8.88613701 + 1044, 1.54724431, 9.09681702 + 1045, 3.5599308, 6.63727379 + 1046, 3.32554269, 6.40605688 + 1047, 1.93518257, 9.38852215 + 1048, 3.43803239, 6.52713728 + 1049, 1.7120595, 9.3373251 + 1050, 1.38626885, 9.34787846 + 1051, 2.81492043, 6.89333582 + 1052, 1.09256911, 8.69904423 + 1053, 2.34481502, 7.52696419 + 1054, 2.10873771, 7.66253757 +*Element, type=CPS3 +1, 400, 402, 399 +2, 424, 452, 423 +3, 434, 73, 1 +287, 691, 487, 480 +288, 855, 697, 696 +289, 819, 817, 800 +290, 717, 498, 497 +291, 876, 788, 740 +292, 754, 784, 819 +293, 878, 806, 805 +294, 878, 874, 880 +295, 820, 791, 786 +296, 867, 802, 803 +297, 833, 824, 825 +298, 843, 842, 841 +299, 848, 846, 553 +300, 866, 826, 827 +301, 868, 731, 732 +302, 871, 666, 752 +809, 998, 994, 995 +810, 1031, 992, 1013 +811, 1003, 977, 976 +812, 1034, 1033, 1032 +813, 1025, 1007, 1006 +*Element, type=CPS4R + 4, 314, 435, 204, 259 + 5, 206, 14, 15, 207 + 6, 13, 205, 204, 12 + 7, 14, 206, 205, 13 + 8, 16, 208, 207, 15 + 9, 255, 256, 272, 274 + 10, 210, 273, 271, 222 + 11, 252, 60, 61, 253 + 12, 249, 56, 57, 250 + 13, 245, 293, 291, 231 + 14, 246, 247, 300, 301 + 15, 215, 51, 52, 246 + 16, 227, 283, 282, 226 + 17, 223, 275, 273, 210 + 18, 260, 262, 219, 220 + 19, 70, 219, 218, 69 + 20, 71, 220, 219, 70 + 21, 73, 434, 203, 72 + 22, 67, 257, 221, 66 + 23, 17, 209, 208, 16 + 24, 224, 21, 22, 225 + 25, 23, 217, 225, 22 + 26, 222, 271, 269, 209 + 27, 25, 227, 226, 24 + 28, 27, 216, 228, 26 + 29, 225, 278, 276, 224 + 30, 28, 229, 216, 27 + 31, 30, 231, 230, 29 + 32, 228, 285, 283, 227 + 33, 233, 34, 35, 234 + 34, 33, 232, 214, 32 + 35, 302, 36, 2, 37 + 36, 302, 37, 237, 354 + 37, 42, 3, 43, 244 + 38, 244, 43, 44, 243 + 39, 368, 360, 357, 396 + 40, 304, 40, 41, 306 + 41, 355, 422, 418, 393 + 42, 34, 233, 232, 33 + 43, 4, 433, 303, 50 + 44, 50, 303, 238, 49 + 45, 214, 294, 293, 245 + 46, 239, 48, 49, 238 + 47, 241, 46, 47, 240 + 48, 45, 242, 243, 44 + 49, 32, 214, 245, 31 + 50, 229, 288, 287, 216 + 51, 53, 247, 246, 52 + 52, 55, 213, 248, 54 + 53, 56, 249, 213, 55 + 54, 59, 212, 251, 58 + 55, 60, 252, 212, 59 + 56, 63, 211, 254, 62 + 57, 209, 269, 267, 208 + 58, 208, 267, 265, 207 + 59, 69, 218, 258, 68 + 60, 205, 261, 259, 204 + 61, 11, 435, 434, 1 + 62, 314, 260, 220, 203 + 63, 263, 206, 207, 265 + 64, 317, 439, 442, 318 + 65, 209, 17, 18, 222 + 66, 206, 263, 261, 205 + 67, 222, 18, 19, 210 + 68, 210, 19, 20, 223 + 69, 256, 221, 270, 272 + 70, 223, 20, 21, 224 + 71, 66, 221, 256, 65 + 72, 211, 63, 64, 255 + 73, 255, 64, 65, 256 + 74, 24, 226, 217, 23 + 75, 229, 28, 29, 230 + 76, 220, 71, 72, 203 + 77, 315, 259, 261, 316 + 78, 257, 67, 68, 258 + 79, 224, 276, 275, 223 + 80, 330, 328, 276, 278 + 81, 227, 25, 26, 228 + 82, 217, 280, 278, 225 + 83, 226, 282, 280, 217 + 84, 250, 251, 289, 292 + 85, 216, 287, 285, 228 + 86, 231, 30, 31, 245 + 87, 230, 290, 288, 229 + 88, 247, 248, 299, 300 + 89, 234, 35, 36, 302 + 90, 232, 296, 294, 214 + 91, 299, 298, 347, 350 + 92, 38, 236, 237, 37 + 93, 39, 235, 236, 38 + 94, 235, 39, 40, 304 + 95, 240, 47, 48, 239 + 96, 358, 357, 311, 310 + 97, 305, 436, 237, 236 + 98, 380, 379, 339, 341 + 99, 355, 393, 436, 305 +100, 308, 309, 239, 238 +101, 426, 449, 413, 428 +102, 46, 241, 242, 45 +103, 311, 312, 242, 241 +104, 312, 313, 243, 242 +105, 42, 244, 306, 41 +106, 231, 291, 290, 230 +107, 247, 53, 54, 248 +108, 248, 213, 298, 299 +109, 213, 249, 295, 298 +110, 249, 250, 292, 295 +111, 250, 57, 58, 251 +112, 251, 212, 286, 289 +113, 334, 332, 280, 282 +114, 332, 330, 278, 280 +115, 253, 61, 62, 254 +116, 328, 326, 275, 276 +117, 254, 211, 277, 279 +118, 211, 255, 274, 277 +119, 221, 257, 268, 270 +120, 263, 442, 316, 261 +121, 262, 264, 218, 219 +122, 259, 315, 260, 314 +123, 264, 266, 258, 218 +124, 439, 315, 316, 442 +125, 266, 268, 257, 258 +126, 443, 267, 269, 320 +127, 317, 319, 266, 264 +128, 320, 269, 271, 322 +129, 319, 321, 268, 266 +130, 273, 275, 326, 324 +131, 321, 323, 270, 268 +132, 325, 327, 274, 272 +133, 323, 325, 272, 270 +134, 253, 254, 279, 281 +135, 364, 444, 365, 446 +136, 252, 253, 281, 284 +137, 366, 367, 446, 365 +138, 212, 252, 284, 286 +139, 398, 366, 369, 399 +140, 284, 281, 333, 336 +141, 286, 284, 336, 338 +142, 281, 279, 331, 333 +143, 335, 334, 282, 283 +144, 451, 406, 405, 404 +145, 337, 335, 283, 285 +146, 344, 343, 290, 291 +147, 339, 337, 285, 287 +148, 296, 232, 233, 438 +149, 300, 299, 350, 352 +150, 233, 234, 437, 438 +151, 349, 348, 294, 296 +152, 341, 339, 287, 288 +153, 236, 235, 307, 305 +154, 234, 302, 354, 437 +155, 343, 341, 288, 290 +156, 382, 380, 341, 343 +157, 344, 291, 293, 346 +158, 215, 246, 301, 353 +159, 308, 353, 440, 394 +160, 433, 4, 51, 215 +161, 240, 239, 309, 310 +162, 313, 306, 244, 243 +163, 235, 304, 356, 307 +164, 310, 311, 241, 240 +165, 348, 346, 293, 294 +166, 358, 310, 309, 359 +167, 368, 396, 422, 355 +168, 307, 356, 360, 368 +169, 313, 312, 360, 356 +170, 304, 306, 313, 356 +171, 262, 260, 315, 439 +172, 265, 267, 443, 318 +173, 439, 317, 264, 262 +174, 361, 318, 443, 320 +175, 319, 317, 318, 361 +176, 362, 361, 320, 322 +177, 321, 319, 361, 362 +178, 271, 273, 324, 322 +179, 362, 322, 324, 363 +180, 363, 365, 444, 362 +181, 327, 329, 277, 274 +182, 364, 446, 327, 325 +183, 329, 331, 279, 277 +184, 446, 367, 329, 327 +185, 370, 398, 399, 372 +186, 329, 367, 370, 331 +187, 371, 400, 399, 369 +188, 331, 370, 372, 333 +189, 289, 286, 338, 340 +190, 333, 372, 374, 336 +191, 292, 289, 340, 342 +192, 336, 374, 376, 338 +193, 295, 292, 342, 345 +194, 338, 376, 378, 340 +195, 298, 295, 345, 347 +196, 340, 378, 381, 342 +197, 424, 408, 410, 453 +198, 345, 383, 385, 347 +199, 423, 406, 408, 424 +200, 347, 385, 387, 350 +201, 384, 382, 343, 344 +202, 351, 296, 438, 297 +203, 391, 389, 349, 441 +204, 349, 296, 351, 441 +205, 384, 344, 346, 386 +206, 436, 297, 354, 237 +207, 355, 305, 307, 368 +208, 417, 392, 352, 390 +209, 301, 300, 352, 392 +210, 215, 353, 303, 433 +211, 437, 354, 297, 438 +212, 312, 311, 357, 360 +213, 428, 416, 421, 431 +214, 394, 359, 309, 308 +215, 389, 388, 348, 349 +216, 392, 417, 394, 440 +217, 350, 387, 390, 352 +218, 323, 321, 362, 444 +219, 365, 363, 324, 326 +220, 364, 325, 323, 444 +221, 326, 328, 366, 365 +222, 328, 330, 369, 366 +223, 396, 357, 358, 395 +224, 330, 332, 371, 369 +225, 370, 367, 366, 398 +226, 332, 334, 373, 371 +227, 334, 335, 375, 373 +228, 373, 401, 400, 371 +229, 335, 337, 377, 375 +230, 402, 400, 401, 404 +231, 337, 339, 379, 377 +232, 402, 404, 376, 374 +233, 342, 381, 383, 345 +234, 405, 378, 376, 404 +235, 384, 411, 410, 382 +236, 405, 407, 381, 378 +237, 346, 348, 388, 386 +238, 411, 448, 447, 425 +239, 407, 409, 383, 381 +240, 413, 447, 445, 415 +241, 409, 412, 385, 383 +242, 432, 427, 431, 419 +243, 397, 395, 358, 359 +244, 453, 454, 452, 424 +245, 393, 418, 441, 351 +246, 395, 397, 421, 420 +247, 353, 301, 392, 440 +248, 393, 351, 297, 436 +249, 303, 353, 308, 238 +250, 359, 394, 419, 397 +251, 389, 391, 415, 445 +252, 401, 373, 375, 403 +253, 372, 399, 402, 374 +254, 403, 375, 377, 451 +255, 451, 377, 379, 406 +256, 379, 380, 408, 406 +257, 408, 380, 382, 410 +258, 407, 405, 406, 423 +259, 409, 407, 423, 452 +260, 448, 411, 384, 386 +261, 453, 425, 450, 454 +262, 445, 386, 388, 389 +263, 414, 387, 385, 412 +264, 430, 426, 427, 432 +265, 387, 414, 430, 390 +266, 416, 415, 391, 429 +267, 428, 431, 427, 426 +268, 429, 418, 422, 420 +269, 391, 441, 418, 429 +270, 419, 394, 417, 432 +271, 396, 395, 420, 422 +272, 429, 420, 421, 416 +273, 411, 425, 453, 410 +274, 449, 425, 447, 413 +275, 450, 425, 449, 426 +276, 414, 450, 426, 430 +277, 428, 413, 415, 416 +278, 430, 432, 417, 390 +279, 421, 397, 419, 431 +280, 435, 11, 12, 204 +281, 314, 203, 434, 435 +282, 442, 263, 265, 318 +283, 386, 445, 447, 448 +284, 450, 414, 412, 454 +285, 404, 401, 403, 451 +286, 412, 409, 452, 454 +303, 187, 188, 455, 600 +304, 589, 590, 185, 186 +305, 457, 80, 81, 494 +306, 82, 458, 494, 81 +307, 567, 568, 171, 172 +308, 570, 571, 168, 169 +309, 573, 574, 165, 166 +310, 89, 462, 792, 461 +311, 848, 553, 54, 53 +312, 6, 464, 462, 89 +313, 60, 557, 558, 61 +314, 465, 834, 90, 91 +315, 646, 649, 560, 489 +316, 562, 67, 66, 466 +317, 94, 95, 504, 467 +318, 689, 692, 470, 468 +319, 69, 468, 470, 70 +320, 70, 470, 471, 71 +321, 696, 697, 503, 472 +322, 71, 471, 473, 72 +323, 100, 101, 501, 474 +324, 475, 72, 473, 706 +325, 501, 774, 693, 474 +326, 620, 844, 526, 605 +327, 525, 604, 478, 1 +328, 478, 523, 710, 479 +329, 115, 481, 849, 114 +330, 481, 115, 116, 480 +331, 547, 29, 28, 546 +332, 132, 133, 507, 508 +333, 543, 24, 23, 515 +334, 20, 540, 541, 21 +335, 120, 121, 485, 520 +336, 486, 521, 520, 619 +337, 14, 480, 487, 15 +338, 467, 505, 93, 94 +339, 489, 559, 645, 646 +340, 157, 158, 581, 582 +341, 88, 847, 491, 87 +342, 86, 460, 492, 85 +343, 84, 459, 493, 83 +344, 458, 82, 83, 493 +345, 182, 183, 592, 593 +346, 495, 79, 80, 457 +347, 77, 456, 497, 76 +348, 76, 497, 498, 75 +349, 104, 7, 105, 499 +350, 838, 106, 107, 844 +351, 476, 500, 102, 103 +352, 101, 102, 500, 501 +353, 474, 502, 99, 100 +354, 97, 98, 472, 503 +355, 505, 488, 92, 93 +356, 8, 136, 850, 135 +357, 536, 537, 138, 139 +358, 133, 134, 506, 507 +359, 627, 626, 547, 546 +360, 131, 132, 508, 509 +361, 508, 606, 607, 509 +362, 509, 510, 130, 131 +363, 483, 513, 127, 128 +364, 126, 127, 513, 514 +365, 484, 617, 616, 517 +366, 636, 633, 544, 543 +367, 484, 517, 123, 124 +368, 650, 519, 539, 609 +369, 609, 619, 520, 485 +370, 520, 521, 119, 120 +371, 521, 486, 538, 522 +372, 691, 480, 116, 117 +373, 12, 849, 481, 13 +374, 113, 114, 849, 479 +375, 710, 523, 111, 112 +376, 109, 477, 526, 108 +377, 500, 476, 527, 853 +378, 106, 838, 499, 105 +379, 147, 2, 36, 528 +380, 552, 35, 34, 551 +381, 528, 529, 146, 147 +382, 529, 530, 145, 146 +383, 530, 531, 144, 145 +384, 532, 623, 857, 550 +385, 482, 534, 141, 142 +386, 140, 141, 534, 535 +387, 679, 765, 767, 678 +388, 535, 536, 139, 140 +389, 136, 137, 781, 850 +390, 539, 486, 619, 609 +391, 518, 610, 609, 485 +392, 19, 519, 540, 20 +393, 513, 608, 611, 514 +394, 24, 543, 544, 25 +395, 612, 507, 506, 854 +396, 482, 533, 683, 682 +397, 546, 28, 27, 511 +398, 29, 547, 548, 30 +399, 532, 32, 31, 549 +400, 622, 551, 550, 857 +401, 529, 528, 552, 684 +402, 528, 36, 35, 552 +403, 51, 4, 148, 841 +404, 846, 848, 842, 843 +405, 553, 846, 588, 665 +406, 55, 463, 554, 56 +407, 56, 554, 555, 57 +408, 59, 490, 557, 60 +409, 558, 557, 640, 643 +410, 489, 63, 62, 559 +411, 63, 489, 560, 64 +412, 67, 562, 563, 68 +413, 9, 178, 845, 177 +414, 178, 179, 596, 845 +415, 594, 597, 603, 595 +416, 564, 175, 176, 674 +417, 685, 676, 458, 493 +418, 734, 564, 674, 705 +419, 754, 750, 653, 652 +420, 654, 568, 567, 668 +421, 568, 569, 170, 171 +422, 655, 570, 569, 675 +423, 569, 570, 169, 170 +424, 656, 572, 571, 669 +425, 572, 656, 657, 573 +426, 572, 573, 166, 167 +427, 574, 575, 164, 165 +428, 574, 670, 658, 575 +429, 162, 163, 576, 577 +430, 659, 577, 576, 671 +431, 161, 162, 577, 578 +432, 672, 578, 577, 659 +433, 583, 666, 662, 584 +434, 158, 159, 580, 581 +435, 156, 157, 582, 583 +436, 663, 837, 737, 664 +437, 859, 664, 631, 665 +438, 586, 587, 152, 153 +439, 665, 588, 587, 859 +440, 846, 843, 149, 150 +441, 841, 148, 149, 843 +442, 188, 5, 74, 455 +443, 600, 455, 498, 717 +444, 852, 598, 590, 589 +445, 590, 591, 184, 185 +446, 602, 593, 592, 601 +447, 183, 184, 591, 592 +448, 181, 182, 593, 594 +449, 594, 595, 180, 181 +450, 595, 596, 179, 180 +451, 455, 74, 75, 498 +452, 77, 78, 496, 456 +453, 496, 78, 79, 495 +454, 775, 817, 785, 772 +455, 173, 174, 565, 566 +456, 459, 84, 85, 492 +457, 460, 86, 87, 491 +458, 88, 89, 461, 847 +459, 464, 6, 90, 834 +460, 575, 576, 163, 164 +461, 585, 586, 153, 154 +462, 463, 55, 54, 553 +463, 160, 161, 578, 579 +464, 91, 92, 488, 465 +465, 466, 66, 65, 561 +466, 561, 65, 64, 560 +467, 469, 504, 95, 96 +468, 468, 69, 68, 563 +469, 96, 97, 503, 469 +470, 687, 689, 468, 563 +471, 98, 99, 502, 472 +472, 475, 706, 711, 851 +473, 700, 692, 699, 698 +474, 477, 621, 605, 526 +475, 72, 475, 851, 73 +476, 696, 472, 502, 695 +477, 499, 476, 103, 104 +478, 477, 109, 110, 524 +479, 524, 110, 111, 523 +480, 11, 1, 478, 479 +481, 525, 1, 73, 851 +482, 11, 479, 849, 12 +483, 480, 14, 13, 481 +484, 142, 143, 533, 482 +485, 128, 129, 512, 483 +486, 124, 125, 516, 484 +487, 121, 122, 518, 485 +488, 17, 486, 539, 18 +489, 486, 17, 16, 538 +490, 487, 538, 16, 15 +491, 117, 118, 522, 691 +492, 559, 62, 61, 558 +493, 751, 796, 799, 752 +494, 490, 59, 58, 556 +495, 556, 58, 57, 555 +496, 571, 572, 167, 168 +497, 568, 654, 675, 569 +498, 172, 173, 566, 567 +499, 564, 565, 174, 175 +500, 628, 715, 601, 599 +501, 598, 599, 591, 590 +502, 589, 600, 717, 852 +503, 853, 774, 501, 500 +504, 693, 695, 502, 474 +505, 687, 783, 702, 689 +506, 779, 776, 704, 703 +507, 771, 773, 649, 646 +508, 850, 506, 134, 135 +509, 507, 612, 606, 508 +510, 626, 625, 548, 547 +511, 129, 130, 510, 512 +512, 810, 788, 789, 790 +513, 613, 510, 509, 607 +514, 511, 27, 26, 545 +515, 545, 26, 25, 544 +516, 787, 876, 740, 762 +517, 514, 516, 125, 126 +518, 515, 23, 22, 542 +519, 542, 22, 21, 541 +520, 514, 611, 615, 516 +521, 122, 123, 517, 518 +522, 644, 541, 540, 647 +523, 519, 19, 18, 539 +524, 118, 119, 521, 522 +525, 691, 522, 538, 487 +526, 693, 728, 711, 695 +527, 838, 527, 476, 499 +528, 108, 526, 844, 107 +529, 33, 550, 551, 34 +530, 32, 532, 550, 33 +531, 531, 533, 143, 144 +532, 549, 31, 30, 548 +533, 549, 548, 625, 624 +534, 839, 857, 623, 730 +535, 681, 708, 537, 536 +536, 536, 535, 678, 681 +537, 781, 537, 708, 854 +538, 137, 138, 537, 781 +539, 517, 616, 610, 518 +540, 516, 615, 617, 484 +541, 719, 787, 762, 764 +542, 639, 636, 543, 515 +543, 809, 804, 869, 805 +544, 633, 630, 545, 544 +545, 679, 678, 535, 534 +546, 625, 732, 731, 624 +547, 624, 623, 532, 549 +548, 684, 552, 551, 622 +549, 842, 848, 53, 52 +550, 584, 585, 154, 155 +551, 583, 584, 155, 156 +552, 741, 761, 638, 637 +553, 673, 755, 751, 661 +554, 159, 160, 579, 580 +555, 559, 558, 643, 645 +556, 822, 813, 812, 875 +557, 649, 651, 561, 560 +558, 651, 677, 466, 561 +559, 677, 686, 562, 466 +560, 686, 687, 563, 562 +561, 591, 599, 601, 592 +562, 566, 653, 668, 567 +563, 690, 688, 459, 492 +564, 570, 655, 669, 571 +565, 701, 749, 744, 694 +566, 670, 862, 757, 658 +567, 573, 657, 670, 574 +568, 671, 758, 759, 659 +569, 658, 671, 576, 575 +570, 578, 672, 660, 579 +571, 829, 835, 828, 827 +572, 557, 490, 638, 640 +573, 739, 741, 637, 634 +574, 637, 638, 490, 556 +575, 634, 637, 556, 555 +576, 632, 634, 555, 554 +577, 631, 632, 554, 463 +578, 665, 631, 463, 553 +579, 586, 664, 859, 587 +580, 152, 587, 588, 151 +581, 150, 151, 588, 846 +582, 600, 589, 186, 187 +583, 860, 456, 496, 628 +584, 642, 635, 495, 457 +585, 597, 594, 593, 602 +586, 596, 705, 674, 845 +587, 595, 603, 705, 596 +588, 845, 674, 176, 177 +589, 648, 642, 457, 494 +590, 784, 754, 713, 716 +591, 598, 852, 456, 860 +592, 628, 599, 598, 860 +593, 635, 628, 496, 495 +594, 713, 734, 705, 603 +595, 604, 621, 477, 524 +596, 880, 879, 626, 733 +597, 630, 629, 511, 545 +598, 608, 513, 483, 618 +599, 618, 483, 512, 614 +600, 610, 863, 650, 609 +601, 641, 639, 515, 542 +602, 510, 613, 614, 512 +603, 738, 736, 629, 630 +604, 644, 641, 542, 541 +605, 864, 721, 644, 647 +606, 719, 764, 720, 615 +607, 721, 720, 641, 644 +608, 620, 856, 853, 527 +609, 844, 620, 527, 838 +610, 711, 729, 525, 851 +611, 684, 712, 530, 529 +612, 712, 684, 622, 857 +613, 712, 680, 531, 530 +614, 680, 683, 533, 531 +615, 766, 682, 683, 840 +616, 546, 511, 629, 627 +617, 727, 606, 612, 793 +618, 607, 723, 724, 613 +619, 613, 724, 725, 614 +620, 881, 807, 883, 821 +621, 632, 631, 664, 737 +622, 584, 662, 663, 585 +623, 608, 722, 718, 611 +624, 737, 739, 634, 632 +625, 772, 635, 642, 775 +626, 636, 762, 740, 633 +627, 582, 661, 666, 583 +628, 756, 667, 660, 746 +629, 611, 718, 719, 615 +630, 579, 660, 667, 580 +631, 715, 714, 602, 601 +632, 755, 673, 667, 756 +633, 755, 756, 795, 877 +634, 488, 704, 709, 465 +635, 650, 647, 540, 519 +636, 647, 650, 863, 864 +637, 494, 458, 676, 648 +638, 565, 652, 653, 566 +639, 816, 776, 861, 773 +640, 703, 704, 488, 505 +641, 652, 565, 564, 734 +642, 652, 734, 713, 754 +643, 818, 882, 780, 782 +644, 688, 685, 493, 459 +645, 701, 694, 460, 491 +646, 777, 748, 707, 847 +647, 757, 777, 792, 758 +648, 760, 759, 794, 803 +649, 769, 770, 645, 643 +650, 661, 582, 581, 673 +651, 673, 581, 580, 667 +652, 664, 586, 585, 663 +653, 812, 799, 796, 875 +654, 837, 753, 739, 737 +655, 801, 800, 778, 873 +656, 750, 747, 668, 653 +657, 690, 694, 744, 743 +658, 694, 690, 492, 460 +659, 707, 748, 749, 701 +660, 658, 757, 758, 671 +661, 760, 672, 659, 759 +662, 882, 742, 747, 801 +663, 745, 743, 655, 675 +664, 467, 702, 703, 505 +665, 606, 727, 723, 607 +666, 534, 482, 682, 679 +667, 836, 804, 766, 840 +668, 730, 623, 624, 731 +669, 793, 708, 681, 768 +670, 839, 840, 683, 680 +671, 747, 742, 654, 668 +672, 703, 702, 783, 779 +673, 689, 702, 699, 692 +674, 818, 745, 742, 882 +675, 742, 745, 675, 654 +676, 471, 470, 692, 700 +677, 605, 621, 728, 858 +678, 855, 696, 706, 473 +679, 503, 697, 698, 469 +680, 698, 697, 855, 700 +681, 699, 504, 469, 698 +682, 473, 471, 700, 855 +683, 707, 701, 491, 847 +684, 702, 467, 504, 699 +685, 779, 686, 677, 776 +686, 776, 677, 651, 861 +687, 770, 771, 646, 645 +688, 710, 112, 113, 479 +689, 604, 525, 729, 621 +690, 857, 839, 680, 712 +691, 603, 597, 716, 713 +692, 715, 628, 635, 772 +693, 597, 602, 714, 716 +694, 852, 717, 497, 456 +695, 722, 608, 618, 726 +696, 641, 720, 764, 639 +697, 617, 615, 720, 721 +698, 726, 618, 614, 725 +699, 633, 740, 738, 630 +700, 627, 735, 733, 626 +701, 629, 736, 735, 627 +702, 807, 810, 790, 883 +703, 767, 768, 681, 678 +704, 693, 774, 858, 728 +705, 728, 621, 729, 711 +706, 840, 839, 730, 836 +707, 625, 626, 879, 732 +708, 682, 766, 765, 679 +709, 727, 872, 786, 723 +710, 723, 786, 791, 724 +711, 726, 789, 788, 722 +712, 870, 871, 753, 837 +713, 636, 639, 764, 762 +714, 661, 751, 752, 666 +715, 669, 655, 743, 744 +716, 656, 669, 744, 749 +717, 782, 743, 745, 818 +718, 746, 660, 672, 760 +719, 826, 822, 824, 833 +720, 780, 873, 676, 685 +721, 657, 748, 862, 670 +722, 749, 748, 657, 656 +723, 648, 778, 775, 642 +724, 761, 763, 640, 638 +725, 761, 741, 865, 798 +726, 741, 739, 753, 865 +727, 763, 769, 643, 640 +728, 746, 760, 803, 802 +729, 769, 812, 813, 770 +730, 792, 777, 847, 461 +731, 831, 830, 464, 834 +732, 787, 719, 718, 876 +733, 755, 877, 796, 751 +734, 809, 808, 767, 765 +735, 768, 767, 808, 872 +736, 793, 768, 872, 727 +737, 770, 813, 814, 771 +738, 825, 824, 867, 823 +739, 771, 814, 815, 773 +740, 785, 784, 716, 714 +741, 704, 776, 816, 709 +742, 774, 853, 856, 858 +743, 778, 648, 676, 873 +744, 777, 757, 862, 748 +745, 780, 685, 688, 782 +746, 801, 747, 750, 800 +747, 506, 850, 781, 854 +748, 743, 782, 688, 690 +749, 783, 687, 686, 779 +750, 714, 715, 772, 785 +751, 778, 800, 817, 775 +752, 808, 809, 832, 820 +753, 876, 718, 722, 788 +754, 726, 725, 790, 789 +755, 738, 740, 788, 810 +756, 790, 725, 724, 791 +757, 832, 809, 805, 806 +758, 830, 823, 794, 797 +759, 797, 758, 792, 462 +760, 708, 793, 612, 854 +761, 797, 794, 759, 758 +762, 802, 795, 756, 746 +763, 763, 811, 812, 769 +764, 811, 798, 799, 812 +765, 798, 865, 752, 799 +766, 750, 754, 819, 800 +767, 831, 829, 833, 825 +768, 802, 867, 824, 795 +769, 765, 766, 804, 809 +770, 733, 735, 881, 806 +771, 808, 820, 786, 872 +772, 735, 736, 807, 881 +773, 806, 881, 821, 832 +774, 810, 807, 736, 738 +775, 763, 761, 798, 811 +776, 822, 875, 795, 824 +777, 833, 829, 827, 826 +778, 828, 816, 773, 815 +779, 784, 785, 817, 819 +780, 820, 832, 821, 883 +781, 791, 820, 883, 790 +782, 803, 794, 823, 867 +783, 834, 835, 829, 831 +784, 823, 830, 831, 825 +785, 815, 866, 827, 828 +786, 866, 813, 822, 826 +787, 866, 815, 814, 813 +788, 709, 816, 828, 835 +789, 830, 797, 462, 464 +790, 835, 834, 465, 709 +791, 836, 730, 731, 868 +792, 837, 663, 662, 870 +793, 51, 841, 842, 52 +794, 523, 478, 604, 524 +795, 706, 696, 695, 711 +796, 651, 649, 773, 861 +797, 616, 864, 863, 610 +798, 856, 620, 605, 858 +799, 865, 753, 871, 752 +800, 804, 836, 868, 869 +801, 870, 662, 666, 871 +802, 864, 616, 617, 721 +803, 801, 873, 780, 882 +804, 868, 732, 874, 869 +805, 880, 874, 732, 879 +806, 869, 874, 878, 805 +807, 733, 806, 878, 880 +808, 877, 795, 875, 796 + 814, 6, 89, 1045, 1048 + 815, 895, 937, 88, 87 + 816, 1048, 1046, 90, 6 + 817, 86, 85, 894, 885 + 818, 84, 83, 893, 886 + 819, 82, 81, 892, 887 + 820, 80, 79, 900, 888 + 821, 889, 902, 933, 932 + 822, 913, 201, 202, 949 + 823, 978, 942, 941, 977 + 824, 78, 77, 889, 891 + 825, 926, 925, 893, 887 + 826, 924, 922, 894, 886 + 827, 960, 921, 959, 993 + 828, 87, 86, 885, 895 + 829, 920, 92, 91, 948 + 830, 93, 896, 919, 94 + 831, 95, 897, 918, 96 + 832, 97, 898, 917, 98 + 833, 929, 927, 892, 888 + 834, 915, 101, 100, 916 + 835, 76, 75, 890, 902 + 836, 903, 890, 75, 74 + 837, 189, 906, 938, 5 + 838, 195, 10, 196, 912 + 839, 912, 196, 197, 911 + 840, 955, 958, 990, 1015 + 841, 193, 952, 950, 192 + 842, 934, 933, 902, 890 + 843, 191, 904, 905, 190 + 844, 949, 104, 907, 986 + 845, 103, 914, 907, 104 + 846, 933, 974, 973, 932 + 847, 200, 908, 909, 199 + 848, 198, 910, 911, 197 + 849, 102, 901, 914, 103 + 850, 99, 899, 916, 100 + 851, 940, 898, 918, 939 + 852, 925, 924, 886, 893 + 853, 922, 921, 885, 894 + 854, 923, 983, 993, 1051 + 855, 92, 920, 896, 93 + 856, 85, 84, 886, 894 + 857, 895, 885, 921, 960 + 858, 83, 82, 887, 893 + 859, 94, 919, 897, 95 + 860, 81, 80, 888, 892 + 861, 96, 918, 898, 97 + 862, 98, 917, 899, 99 + 863, 77, 76, 902, 889 + 864, 101, 915, 901, 102 + 865, 1026, 1021, 1022, 1035 + 866, 930, 929, 888, 900 + 867, 79, 78, 891, 900 + 868, 983, 920, 948, 884 + 869, 936, 897, 919, 928 + 870, 927, 926, 887, 892 + 871, 943, 916, 899, 942 + 872, 931, 930, 900, 891 + 873, 74, 5, 938, 903 + 874, 190, 905, 906, 189 + 875, 904, 191, 192, 950 + 876, 1054, 1025, 1006, 1003 + 877, 1004, 1005, 1024, 1023 + 878, 938, 906, 982, 935 + 879, 201, 913, 908, 200 + 880, 1019, 1020, 1025, 1054 + 881, 956, 957, 908, 913 + 882, 199, 909, 910, 198 + 883, 989, 987, 950, 952 + 884, 911, 910, 955, 968 + 885, 195, 912, 953, 194 + 886, 949, 202, 7, 104 + 887, 946, 914, 901, 945 + 888, 944, 915, 916, 943 + 889, 942, 899, 917, 941 + 890, 941, 917, 898, 940 + 891, 939, 918, 897, 936 + 892, 962, 963, 995, 994 + 893, 959, 921, 922, 962 + 894, 948, 91, 90, 1046 + 895, 937, 895, 960, 884 + 896, 928, 919, 896, 923 + 897, 923, 896, 920, 983 + 898, 962, 994, 961, 959 + 899, 925, 965, 963, 924 + 900, 964, 936, 928, 961 + 901, 960, 993, 983, 884 + 902, 997, 966, 967, 999 + 903, 891, 889, 932, 931 + 904, 976, 940, 939, 969 + 905, 977, 941, 940, 976 + 906, 945, 901, 915, 944 + 907, 1047, 935, 1049, 1012 + 908, 909, 908, 957, 958 + 909, 1047, 934, 890, 903 + 910, 905, 904, 951, 954 + 911, 924, 963, 962, 922 + 912, 937, 884, 1048, 1045 + 913, 89, 88, 937, 1045 + 914, 926, 966, 965, 925 + 915, 927, 967, 966, 926 + 916, 929, 970, 967, 927 + 917, 930, 971, 970, 929 + 918, 931, 972, 971, 930 + 919, 932, 973, 972, 931 + 920, 913, 949, 986, 956 + 921, 981, 945, 944, 980 + 922, 914, 946, 947, 907 + 923, 986, 907, 947, 1013 + 924, 952, 193, 194, 953 + 925, 904, 950, 987, 951 + 926, 1010, 984, 981, 1009 + 927, 968, 953, 912, 911 + 928, 982, 988, 1049, 935 + 929, 906, 905, 954, 982 + 930, 968, 989, 952, 953 + 931, 910, 909, 958, 955 + 932, 984, 946, 945, 981 + 933, 991, 990, 958, 957 + 934, 1018, 990, 991, 1017 + 935, 928, 923, 1051, 961 + 936, 969, 939, 936, 964 + 937, 997, 1019, 1053, 996 + 938, 994, 998, 964, 961 + 939, 979, 943, 942, 978 + 940, 933, 934, 975, 974 + 941, 980, 944, 943, 979 + 942, 975, 934, 1047, 1012 + 943, 992, 991, 957, 956 + 944, 988, 1044, 1012, 1049 + 945, 954, 1050, 988, 982 + 946, 965, 996, 995, 963 + 947, 966, 997, 996, 965 + 948, 970, 1000, 999, 967 + 949, 971, 1001, 1000, 970 + 950, 972, 1002, 1001, 971 + 951, 973, 1004, 1002, 972 + 952, 903, 938, 935, 1047 + 953, 1009, 981, 980, 1008 + 954, 946, 984, 985, 947 + 955, 1013, 992, 956, 986 + 956, 1013, 947, 985, 1031 + 957, 1014, 951, 987, 1016 + 958, 1016, 1018, 1034, 1014 + 959, 968, 955, 1015, 989 + 960, 1016, 1015, 990, 1018 + 961, 1016, 987, 989, 1015 + 962, 1051, 993, 959, 961 + 963, 969, 964, 998, 976 + 964, 1003, 976, 998, 1053 + 965, 977, 1003, 1006, 978 + 966, 998, 995, 996, 1053 + 967, 978, 1006, 1007, 979 + 968, 979, 1007, 1008, 980 + 969, 1004, 973, 974, 1005 + 970, 974, 975, 1030, 1005 + 971, 1021, 1026, 1025, 1020 + 972, 1020, 1019, 997, 999 + 973, 1009, 1027, 1028, 1010 + 974, 1030, 975, 1012, 1044 + 975, 1014, 1034, 1032, 1050 + 976, 1021, 1020, 999, 1000 + 977, 1021, 1000, 1001, 1022 + 978, 1036, 1022, 1001, 1002 + 979, 1023, 1036, 1002, 1004 + 980, 1010, 1028, 1029, 1011 + 981, 984, 1010, 1011, 985 + 982, 1031, 985, 1011, 1039 + 983, 1044, 1043, 1038, 1030 + 984, 1052, 991, 992, 1031 + 985, 1003, 1053, 1019, 1054 + 986, 1007, 1025, 1026, 1027 + 987, 1008, 1007, 1027, 1009 + 988, 1035, 1028, 1027, 1026 + 989, 1028, 1035, 1037, 1029 + 990, 1005, 1030, 1038, 1040 + 991, 1033, 1052, 1042, 1043 + 992, 1035, 1022, 1036, 1037 + 993, 1037, 1036, 1023, 1024 + 994, 1042, 1040, 1038, 1043 + 995, 1043, 1044, 1032, 1033 + 996, 1011, 1029, 1041, 1039 + 997, 1050, 1032, 1044, 988 + 998, 1033, 1034, 1018, 1017 + 999, 1052, 1033, 1017, 991 +1000, 1024, 1005, 1040, 1041 +1001, 1037, 1024, 1041, 1029 +1002, 1039, 1042, 1052, 1031 +1003, 1041, 1040, 1042, 1039 +1004, 884, 948, 1046, 1048 +1005, 1050, 954, 951, 1014 +*Nset, nset=porous + 1, 2, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18 + 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34 + 35, 36, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64 + 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80 + 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96 + 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112 + 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128 + 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144 + 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160 + 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176 + 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 455, 456, 457, 458 + 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474 + 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490 + 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506 + 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, 521, 522 + 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538 + 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554 + 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570 + 571, 572, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586 + 587, 588, 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, 599, 600, 601, 602 + 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613, 614, 615, 616, 617, 618 + 619, 620, 621, 622, 623, 624, 625, 626, 627, 628, 629, 630, 631, 632, 633, 634 + 635, 636, 637, 638, 639, 640, 641, 642, 643, 644, 645, 646, 647, 648, 649, 650 + 651, 652, 653, 654, 655, 656, 657, 658, 659, 660, 661, 662, 663, 664, 665, 666 + 667, 668, 669, 670, 671, 672, 673, 674, 675, 676, 677, 678, 679, 680, 681, 682 + 683, 684, 685, 686, 687, 688, 689, 690, 691, 692, 693, 694, 695, 696, 697, 698 + 699, 700, 701, 702, 703, 704, 705, 706, 707, 708, 709, 710, 711, 712, 713, 714 + 715, 716, 717, 718, 719, 720, 721, 722, 723, 724, 725, 726, 727, 728, 729, 730 + 731, 732, 733, 734, 735, 736, 737, 738, 739, 740, 741, 742, 743, 744, 745, 746 + 747, 748, 749, 750, 751, 752, 753, 754, 755, 756, 757, 758, 759, 760, 761, 762 + 763, 764, 765, 766, 767, 768, 769, 770, 771, 772, 773, 774, 775, 776, 777, 778 + 779, 780, 781, 782, 783, 784, 785, 786, 787, 788, 789, 790, 791, 792, 793, 794 + 795, 796, 797, 798, 799, 800, 801, 802, 803, 804, 805, 806, 807, 808, 809, 810 + 811, 812, 813, 814, 815, 816, 817, 818, 819, 820, 821, 822, 823, 824, 825, 826 + 827, 828, 829, 830, 831, 832, 833, 834, 835, 836, 837, 838, 839, 840, 841, 842 + 843, 844, 845, 846, 847, 848, 849, 850, 851, 852, 853, 854, 855, 856, 857, 858 + 859, 860, 861, 862, 863, 864, 865, 866, 867, 868, 869, 870, 871, 872, 873, 874 + 875, 876, 877, 878, 879, 880, 881, 882, 883 +*Elset, elset=porous, generate + 287, 808, 1 +*Nset, nset=solid + 1, 2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15, 16, 17, 18 + 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34 + 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50 + 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66 + 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82 + 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98 + 99, 100, 101, 102, 103, 104, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198 + 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214 + 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230 + 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246 + 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262 + 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278 + 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294 + 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310 + 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326 + 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342 + 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358 + 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374 + 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390 + 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406 + 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422 + 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438 + 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454 + 884, 885, 886, 887, 888, 889, 890, 891, 892, 893, 894, 895, 896, 897, 898, 899 + 900, 901, 902, 903, 904, 905, 906, 907, 908, 909, 910, 911, 912, 913, 914, 915 + 916, 917, 918, 919, 920, 921, 922, 923, 924, 925, 926, 927, 928, 929, 930, 931 + 932, 933, 934, 935, 936, 937, 938, 939, 940, 941, 942, 943, 944, 945, 946, 947 + 948, 949, 950, 951, 952, 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963 + 964, 965, 966, 967, 968, 969, 970, 971, 972, 973, 974, 975, 976, 977, 978, 979 + 980, 981, 982, 983, 984, 985, 986, 987, 988, 989, 990, 991, 992, 993, 994, 995 + 996, 997, 998, 999, 1000, 1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009, 1010, 1011 + 1012, 1013, 1014, 1015, 1016, 1017, 1018, 1019, 1020, 1021, 1022, 1023, 1024, 1025, 1026, 1027 + 1028, 1029, 1030, 1031, 1032, 1033, 1034, 1035, 1036, 1037, 1038, 1039, 1040, 1041, 1042, 1043 + 1044, 1045, 1046, 1047, 1048, 1049, 1050, 1051, 1052, 1053, 1054 +*Elset, elset=solid + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 + 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32 + 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48 + 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64 + 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80 + 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96 + 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112 + 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128 + 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144 + 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160 + 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176 + 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192 + 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208 + 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224 + 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240 + 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256 + 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272 + 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 809, 810 + 811, 812, 813, 814, 815, 816, 817, 818, 819, 820, 821, 822, 823, 824, 825, 826 + 827, 828, 829, 830, 831, 832, 833, 834, 835, 836, 837, 838, 839, 840, 841, 842 + 843, 844, 845, 846, 847, 848, 849, 850, 851, 852, 853, 854, 855, 856, 857, 858 + 859, 860, 861, 862, 863, 864, 865, 866, 867, 868, 869, 870, 871, 872, 873, 874 + 875, 876, 877, 878, 879, 880, 881, 882, 883, 884, 885, 886, 887, 888, 889, 890 + 891, 892, 893, 894, 895, 896, 897, 898, 899, 900, 901, 902, 903, 904, 905, 906 + 907, 908, 909, 910, 911, 912, 913, 914, 915, 916, 917, 918, 919, 920, 921, 922 + 923, 924, 925, 926, 927, 928, 929, 930, 931, 932, 933, 934, 935, 936, 937, 938 + 939, 940, 941, 942, 943, 944, 945, 946, 947, 948, 949, 950, 951, 952, 953, 954 + 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, 968, 969, 970 + 971, 972, 973, 974, 975, 976, 977, 978, 979, 980, 981, 982, 983, 984, 985, 986 + 987, 988, 989, 990, 991, 992, 993, 994, 995, 996, 997, 998, 999, 1000, 1001, 1002 + 1003, 1004, 1005 +*Nset, nset=bottom + 2, 3, 8, 37, 38, 39, 40, 41, 42, 136, 137, 138, 139, 140, 141, 142 + 143, 144, 145, 146, 147 +*Elset, elset=bottom + 35, 37, 40, 92, 93, 94, 105, 356, 357, 379, 381, 382, 383, 385, 386, 388 + 389, 484, 531, 538 +*Nset, nset=top_p + 5, 9, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188 +*Elset, elset=top_p + 303, 304, 345, 413, 414, 442, 445, 447, 448, 449, 450, 582 +*Nset, nset=sides_p + 4, 7, 8, 9, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116 + 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132 + 133, 134, 135, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160 + 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176 + 177, +*Elset, elset=sides_p + 307, 308, 309, 329, 330, 332, 335, 340, 349, 350, 356, 358, 360, 362, 363, 364 + 367, 370, 372, 374, 375, 376, 378, 403, 413, 416, 421, 423, 426, 427, 429, 431 + 434, 435, 438, 440, 441, 455, 460, 461, 463, 478, 479, 485, 486, 487, 491, 496 + 498, 499, 508, 511, 517, 521, 524, 528, 550, 551, 554, 580, 581, 588, 688 +*Nset, nset=bottom_p + 2, 8, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147 +*Elset, elset=bottom_p + 356, 357, 379, 381, 382, 383, 385, 386, 388, 389, 484, 531, 538 +*End Part +** +** +** ASSEMBLY +** +*Assembly, name=Assembly +** +*Instance, name=Part-1-1, part=Part-1 +*End Instance +** +*End Assembly diff --git a/previews/PR798/tutorials/reactive-surface.pvd b/previews/PR798/tutorials/reactive-surface.pvd new file mode 100755 index 0000000000..e8477f7f4c --- /dev/null +++ b/previews/PR798/tutorials/reactive-surface.pvd @@ -0,0 +1,326 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/previews/PR798/tutorials/reactive_surface.gif b/previews/PR798/tutorials/reactive_surface.gif new file mode 100644 index 0000000000..af82ee86a6 Binary files /dev/null and b/previews/PR798/tutorials/reactive_surface.gif differ diff --git a/previews/PR798/tutorials/reactive_surface.ipynb b/previews/PR798/tutorials/reactive_surface.ipynb new file mode 100644 index 0000000000..441cdef357 --- /dev/null +++ b/previews/PR798/tutorials/reactive_surface.ipynb @@ -0,0 +1,490 @@ +{ + "cells": [ + { + "outputs": [], + "cell_type": "code", + "source": [ + "if isdefined(Main, :is_ci) #hide\n", + " IS_CI = Main.is_ci #hide\n", + "else #hide\n", + " IS_CI = false #hide\n", + "end #hide\n", + "nothing #hide" + ], + "metadata": {}, + "execution_count": 1 + }, + { + "cell_type": "markdown", + "source": [ + "# Reactive surface\n", + "\n", + "![](reactive_surface.gif)\n", + "\n", + "*Figure 1*: Reactant concentration field of the Gray-Scott model on the unit sphere." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Introduction\n", + "\n", + "This tutorial gives a quick tutorial on how to assemble and solve time-dependent problems\n", + "on embedded surfaces.\n", + "\n", + "For this showcase we use the well known Gray-Scott model, which is a well-known reaction-diffusion\n", + "system to study pattern formation. The strong form is given by\n", + "\n", + "$$\n", + " \\begin{aligned}\n", + " \\partial_t r_1 &= \\nabla \\cdot (D_1 \\nabla r_1) - r_1*r_2^2 + F *(1 - r_1) \\quad \\textbf{x} \\in \\Omega, \\\\\n", + " \\partial_t r_2 &= \\nabla \\cdot (D_2 \\nabla r_2) + r_1*r_2^2 - r_2*(F + k ) \\quad \\textbf{x} \\in \\Omega,\n", + " \\end{aligned}\n", + "$$\n", + "\n", + "where $r_1$ and $r_2$ are the reaction fields, $D_1$ and $D_2$ the diffusion tensors,\n", + "$k$ is the conversion rate, $F$ is the feed rate and $\\Omega$ the domain. Depending on the choice of\n", + "parameters a different pattern can be observed. Please also note that the domain does not have a\n", + "boundary. The corresponding weak form can be derived as usual.\n", + "\n", + "For simplicity we will solve the problem with the Lie-Troter-Godunov operator splitting technique with\n", + "the classical reaction-diffusion split. In this method we split our problem in two problems, i.e. a heat\n", + "problem and a pointwise reaction problem, and solve them alternatingly to advance in time.\n", + "\n", + "## Solver details\n", + "\n", + "The main idea for the Lie-Trotter-Godunov scheme is simple. We can write down the reaction diffusion\n", + "problem in an abstract way as\n", + "$$\n", + " \\partial_t \\mathbf{r} = \\mathcal{D}\\mathbf{r} + R(\\mathbf{r}) \\quad \\textbf{x} \\in \\Omega\n", + "$$\n", + "where $\\mathcal{D}$ is the diffusion operator and $R$ is the reaction operator. Notice that the right\n", + "hand side is just the sum of two operators. Now with our operator splitting scheme we can advance a\n", + "solution $\\mathbf{r}(t_1)$ to $\\mathbf{r}(t_2)$ by first solving a heat problem\n", + "$$\n", + " \\partial_t \\mathbf{r}^{\\mathrm{\\mathrm{A}}} = \\mathcal{D}\\mathbf{r}^{\\mathrm{A}} \\quad \\textbf{x} \\in \\Omega\n", + "$$\n", + "with $\\mathbf{r}^{\\mathrm{A}}(t_1) = \\mathbf{r}(t_1)$ on the time interval $t_1$ to $t_2$ and use\n", + "the solution as the initial condition to solve the reaction problem\n", + "$$\n", + " \\partial_t \\mathbf{r}^{\\mathrm{B}} = R(\\mathbf{r}^{\\mathrm{B}}) \\quad \\textbf{x} \\in \\Omega\n", + "$$\n", + "with $\\mathbf{r}^{\\mathrm{B}}(t_1) = \\mathbf{r}^{\\mathrm{A}}(t_2)$.\n", + "This way we obtain a solution approximation $\\mathbf{r}(t_2) \\approx \\mathbf{r}^{\\mathrm{B}}(t_2)$.\n", + "\n", + "> **Note**\n", + ">\n", + "> The operator splitting itself is an approximation, so even if we solve the subproblems analytically\n", + "> we end up with having only a solution approximation. We also do not have a beginner friendly reference\n", + "> for the theory behind operator splitting and can only refer to the original papers for each method." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Commented Program\n", + "\n", + "Now we solve the problem in Ferrite. What follows is a program spliced with comments.\n", + "\n", + "First we load Ferrite, and some other packages we need" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using Ferrite, FerriteGmsh\n", + "using BlockArrays, SparseArrays, LinearAlgebra, WriteVTK" + ], + "metadata": {}, + "execution_count": 2 + }, + { + "cell_type": "markdown", + "source": [ + "### Assembly routines\n", + "Before we head into the assembly, we define a helper struct to control the dispatches." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "struct GrayScottMaterial{T}\n", + " D₁::T\n", + " D₂::T\n", + " F::T\n", + " k::T\n", + "end;" + ], + "metadata": {}, + "execution_count": 3 + }, + { + "cell_type": "markdown", + "source": [ + "The following assembly routines are written analogue to these found in previous tutorials." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function assemble_element_mass!(Me::Matrix, cellvalues::CellValues)\n", + " n_basefuncs = getnbasefunctions(cellvalues)\n", + " # The mass matrices between the reactions are not coupled, so we get a blocked-strided matrix.\n", + " num_reactants = 2\n", + " r₁range = 1:num_reactants:(num_reactants * n_basefuncs)\n", + " r₂range = 2:num_reactants:(num_reactants * n_basefuncs)\n", + " Me₁ = @view Me[r₁range, r₁range]\n", + " Me₂ = @view Me[r₂range, r₂range]\n", + " # Reset to 0\n", + " fill!(Me, 0)\n", + " # Loop over quadrature points\n", + " for q_point in 1:getnquadpoints(cellvalues)\n", + " # Get the quadrature weight\n", + " dΩ = getdetJdV(cellvalues, q_point)\n", + " # Loop over test shape functions\n", + " for i in 1:n_basefuncs\n", + " δuᵢ = shape_value(cellvalues, q_point, i)\n", + " # Loop over trial shape functions\n", + " for j in 1:n_basefuncs\n", + " δuⱼ = shape_value(cellvalues, q_point, j)\n", + " # Add contribution to Ke\n", + " Me₁[i, j] += (δuᵢ * δuⱼ) * dΩ\n", + " Me₂[i, j] += (δuᵢ * δuⱼ) * dΩ\n", + " end\n", + " end\n", + " end\n", + " return nothing\n", + "end\n", + "\n", + "function assemble_element_diffusion!(De::Matrix, cellvalues::CellValues, material::GrayScottMaterial)\n", + " n_basefuncs = getnbasefunctions(cellvalues)\n", + " D₁ = material.D₁\n", + " D₂ = material.D₂\n", + " # The diffusion between the reactions is not coupled, so we get a blocked-strided matrix.\n", + " num_reactants = 2\n", + " r₁range = 1:num_reactants:(num_reactants * n_basefuncs)\n", + " r₂range = 2:num_reactants:(num_reactants * n_basefuncs)\n", + " De₁ = @view De[r₁range, r₁range]\n", + " De₂ = @view De[r₂range, r₂range]\n", + " # Reset to 0\n", + " fill!(De, 0)\n", + " # Loop over quadrature points\n", + " for q_point in 1:getnquadpoints(cellvalues)\n", + " # Get the quadrature weight\n", + " dΩ = getdetJdV(cellvalues, q_point)\n", + " # Loop over test shape functions\n", + " for i in 1:n_basefuncs\n", + " ∇δuᵢ = shape_gradient(cellvalues, q_point, i)\n", + " # Loop over trial shape functions\n", + " for j in 1:n_basefuncs\n", + " ∇δuⱼ = shape_gradient(cellvalues, q_point, j)\n", + " # Add contribution to Ke\n", + " De₁[i, j] += D₁ * (∇δuᵢ ⋅ ∇δuⱼ) * dΩ\n", + " De₂[i, j] += D₂ * (∇δuᵢ ⋅ ∇δuⱼ) * dΩ\n", + " end\n", + " end\n", + " end\n", + " return nothing\n", + "end\n", + "\n", + "function assemble_matrices!(M::SparseMatrixCSC, D::SparseMatrixCSC, cellvalues::CellValues, dh::DofHandler, material::GrayScottMaterial)\n", + " n_basefuncs = getnbasefunctions(cellvalues)\n", + "\n", + " # Allocate the element stiffness matrix and element force vector\n", + " Me = zeros(2 * n_basefuncs, 2 * n_basefuncs)\n", + " De = zeros(2 * n_basefuncs, 2 * n_basefuncs)\n", + "\n", + " # Create an assembler\n", + " M_assembler = start_assemble(M)\n", + " D_assembler = start_assemble(D)\n", + " # Loop over all cels\n", + " for cell in CellIterator(dh)\n", + " # Reinitialize cellvalues for this cell\n", + " reinit!(cellvalues, cell)\n", + " # Compute element contribution\n", + " assemble_element_mass!(Me, cellvalues)\n", + " assemble!(M_assembler, celldofs(cell), Me)\n", + "\n", + " assemble_element_diffusion!(De, cellvalues, material)\n", + " assemble!(D_assembler, celldofs(cell), De)\n", + " end\n", + " return nothing\n", + "end;" + ], + "metadata": {}, + "execution_count": 4 + }, + { + "cell_type": "markdown", + "source": [ + "### Initial condition setup\n", + "Time-dependent problems always need an initial condition from which the time evolution starts.\n", + "In this tutorial we set the concentration of reactant 1 to $1$ and the concentration of reactant\n", + "2 to $0$ for all nodal dof with associated coordinate $z \\leq 0.9$ on the sphere. Since the\n", + "simulation would be pretty boring with a steady-state initial condition, we introduce some\n", + "heterogeneity by setting the dofs associated to top part of the sphere (i.e. dofs with $z > 0.9$\n", + "to store the reactant concentrations of $0.5$ and $0.25$ for the reactants 1 and 2 respectively." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function setup_initial_conditions!(u₀::Vector, cellvalues::CellValues, dh::DofHandler)\n", + " u₀ .= ones(ndofs(dh))\n", + " u₀[2:2:end] .= 0.0\n", + "\n", + " n_basefuncs = getnbasefunctions(cellvalues)\n", + "\n", + " for cell in CellIterator(dh)\n", + " reinit!(cellvalues, cell)\n", + "\n", + " coords = getcoordinates(cell)\n", + " dofs = celldofs(cell)\n", + " uₑ = @view u₀[dofs]\n", + " rv₀ₑ = reshape(uₑ, (2, n_basefuncs))\n", + "\n", + " for i in 1:n_basefuncs\n", + " if coords[i][3] > 0.9\n", + " rv₀ₑ[1, i] = 0.5\n", + " rv₀ₑ[2, i] = 0.25\n", + " end\n", + " end\n", + " end\n", + "\n", + " u₀ .+= 0.01 * rand(ndofs(dh))\n", + " return\n", + "end;" + ], + "metadata": {}, + "execution_count": 5 + }, + { + "cell_type": "markdown", + "source": [ + "### Mesh generation\n", + "In this section we define a routine to create a surface mesh with the help of FerriteGmsh.jl." + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "create_embedded_sphere (generic function with 1 method)" + }, + "metadata": {}, + "execution_count": 6 + } + ], + "cell_type": "code", + "source": [ + "function create_embedded_sphere(refinements)\n", + " gmsh.initialize()\n", + "\n", + " # Add a unit sphere in 3D space\n", + " gmsh.model.occ.addSphere(0.0, 0.0, 0.0, 1.0)\n", + " gmsh.model.occ.synchronize()\n", + "\n", + " # Generate nodes and surface elements only, hence we need to pass 2 into generate\n", + " gmsh.model.mesh.generate(2)\n", + "\n", + " # To get good solution quality refine the elements several times\n", + " for _ in 1:refinements\n", + " gmsh.model.mesh.refine()\n", + " end\n", + "\n", + " # Now we create a Ferrite grid out of it. Note that we also call toelements\n", + " # with our surface element dimension to obtain these.\n", + " nodes = tonodes()\n", + " elements, _ = toelements(2)\n", + " gmsh.finalize()\n", + " return Grid(elements, nodes)\n", + "end" + ], + "metadata": {}, + "execution_count": 6 + }, + { + "cell_type": "markdown", + "source": [ + "### Simulation routines\n", + "Now we define a function to setup and solve the problem with given feed and conversion rates\n", + "$F$ and $k$, as well as the time step length and for how long we want to solve the model." + ], + "metadata": {} + }, + { + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Info : Meshing 1D...\n", + "Info : [ 40%] Meshing curve 2 (Circle)\n", + "Info : Done meshing 1D (Wall 9.7833e-05s, CPU 9.9e-05s)\n", + "Info : Meshing 2D...\n", + "Info : Meshing surface 1 (Sphere, Frontal-Delaunay)\n", + "Info : Done meshing 2D (Wall 0.00964383s, CPU 0.009643s)\n", + "Info : 160 nodes 328 elements\n", + "Info : Refining mesh...\n", + "Info : Meshing order 2 (curvilinear on)...\n", + "Info : [ 0%] Meshing curve 1 order 2\n", + "Info : [ 30%] Meshing curve 2 order 2\n", + "Info : [ 50%] Meshing curve 3 order 2\n", + "Info : [ 70%] Meshing surface 1 order 2\n", + "Info : [ 90%] Meshing volume 1 order 2\n", + "Info : Surface mesh: worst distortion = 0.970866 (0 elements in ]0, 0.2]); worst gamma = 0.433887\n", + "Info : Done meshing order 2 (Wall 0.00110044s, CPU 0.001099s)\n", + "Info : Done refining mesh (Wall 0.00143527s, CPU 0.001435s)\n", + "Info : Refining mesh...\n", + "Info : Meshing order 2 (curvilinear on)...\n", + "Info : [ 0%] Meshing curve 1 order 2\n", + "Info : [ 30%] Meshing curve 2 order 2\n", + "Info : [ 50%] Meshing curve 3 order 2\n", + "Info : [ 70%] Meshing surface 1 order 2\n", + "Info : [ 90%] Meshing volume 1 order 2\n", + "Info : Surface mesh: worst distortion = 0.992408 (0 elements in ]0, 0.2]); worst gamma = 0.421451\n", + "Info : Done meshing order 2 (Wall 0.00422921s, CPU 0.004228s)\n", + "Info : Done refining mesh (Wall 0.00547198s, CPU 0.005463s)\n", + "Info : Refining mesh...\n", + "Info : Meshing order 2 (curvilinear on)...\n", + "Info : [ 0%] Meshing curve 1 order 2\n", + "Info : [ 30%] Meshing curve 2 order 2\n", + "Info : [ 50%] Meshing curve 3 order 2\n", + "Info : [ 70%] Meshing surface 1 order 2\n", + "Info : [ 90%] Meshing volume 1 order 2\n", + "Info : Surface mesh: worst distortion = 0.998082 (0 elements in ]0, 0.2]); worst gamma = 0.418317\n", + "Info : Done meshing order 2 (Wall 0.0163027s, CPU 0.016298s)\n", + "Info : Done refining mesh (Wall 0.0213046s, CPU 0.0213s)\n" + ] + } + ], + "cell_type": "code", + "source": [ + "function gray_scott_on_sphere(material::GrayScottMaterial, Δt::Real, T::Real, refinements::Integer)\n", + " # We start by setting up grid, dof handler and the matrices for the heat problem.\n", + " grid = create_embedded_sphere(refinements)\n", + "\n", + " # Next we are creating our element assembly helper for surface elements.\n", + " # The only change which we need to introduce here is to pass in a geometrical\n", + " # interpolation with the same dimension as the physical space into which our\n", + " # elements are embedded into, which is in this example 3.\n", + " ip = Lagrange{RefTriangle, 1}()\n", + " qr = QuadratureRule{RefTriangle}(2)\n", + " cellvalues = CellValues(qr, ip, ip^3)\n", + "\n", + " # We have two options to add the reactants to the dof handler, which will give us slightly\n", + " # different resulting dof distributions:\n", + " # A) We can add a scalar-valued interpolation for each reactant.\n", + " # B) We can add one vectorized interpolation whose dimension is the number of reactants\n", + " # number of reactants.\n", + " # In this tutorial we opt for B, because the dofs are distributed per cell entity -- or\n", + " # to be specific for this tutorial, we use an isoparametric concept such that the nodes\n", + " # of our grid and the nodes of our solution approximation coincide. This way a reaction\n", + " # we can create simply reshape the solution vector u to a matrix where the inner index\n", + " # corresponds to the index of the reactant. Note that we will still use the scalar\n", + " # interpolation for the assembly procedure.\n", + " dh = DofHandler(grid)\n", + " add!(dh, :reactants, ip^2)\n", + " close!(dh)\n", + "\n", + " # We can save some memory by telling the sparsity pattern that the matrices are not coupled.\n", + " M = allocate_matrix(dh; coupling = [true false; false true])\n", + " D = allocate_matrix(dh; coupling = [true false; false true])\n", + "\n", + " # Since the heat problem is linear and has no time dependent parameters, we precompute the\n", + " # decomposition of the system matrix to speed up the linear system solver.\n", + " assemble_matrices!(M, D, cellvalues, dh, material)\n", + " A = M + Δt .* D\n", + " cholA = cholesky(A)\n", + "\n", + " # Now we setup buffers for the time dependent solution and fill the initial condition.\n", + " uₜ = zeros(ndofs(dh))\n", + " uₜ₋₁ = ones(ndofs(dh))\n", + " setup_initial_conditions!(uₜ₋₁, cellvalues, dh)\n", + "\n", + " # And prepare output for visualization.\n", + " pvd = paraview_collection(\"reactive-surface\")\n", + " VTKGridFile(\"reactive-surface-0\", dh) do vtk\n", + " write_solution(vtk, dh, uₜ₋₁)\n", + " pvd[0.0] = vtk\n", + " end\n", + "\n", + " # This is now the main solve loop.\n", + " F = material.F\n", + " k = material.k\n", + " for (iₜ, t) in enumerate(Δt:Δt:T)\n", + " # First we solve the heat problem\n", + " uₜ .= cholA \\ (M * uₜ₋₁)\n", + "\n", + " # Then we solve the point-wise reaction problem with the solution of\n", + " # the heat problem as initial guess. 2 is the number of reactants.\n", + " num_individual_reaction_dofs = ndofs(dh) ÷ 2\n", + " rvₜ = reshape(uₜ, (2, num_individual_reaction_dofs))\n", + " for i in 1:num_individual_reaction_dofs\n", + " r₁ = rvₜ[1, i]\n", + " r₂ = rvₜ[2, i]\n", + " rvₜ[1, i] += Δt * (-r₁ * r₂^2 + F * (1 - r₁))\n", + " rvₜ[2, i] += Δt * (r₁ * r₂^2 - r₂ * (F + k))\n", + " end\n", + "\n", + " # The solution is then stored every 10th step to vtk files for\n", + " # later visualization purposes.\n", + " if (iₜ % 10) == 0\n", + " VTKGridFile(\"reactive-surface-$(iₜ)\", dh) do vtk\n", + " write_solution(vtk, dh, uₜ₋₁)\n", + " pvd[t] = vtk\n", + " end\n", + " end\n", + "\n", + " # Finally we totate the solution to initialize the next timestep.\n", + " uₜ₋₁ .= uₜ\n", + " end\n", + " vtk_save(pvd)\n", + " return\n", + "end\n", + "\n", + "# This parametrization gives the spot pattern shown in the gif above.\n", + "material = GrayScottMaterial(0.00016, 0.00008, 0.06, 0.062)\n", + " gray_scott_on_sphere(material, 10.0, 32000.0, 3)" + ], + "metadata": {}, + "execution_count": 7 + }, + { + "cell_type": "markdown", + "source": [ + "---\n", + "\n", + "*This notebook was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).*" + ], + "metadata": {} + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.1" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.1", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR798/tutorials/reactive_surface.jl b/previews/PR798/tutorials/reactive_surface.jl new file mode 100644 index 0000000000..7c7beb2afb --- /dev/null +++ b/previews/PR798/tutorials/reactive_surface.jl @@ -0,0 +1,238 @@ +if isdefined(Main, :is_ci) #hide + IS_CI = Main.is_ci #hide +else #hide + IS_CI = false #hide +end #hide +nothing #hide + +using Ferrite, FerriteGmsh +using BlockArrays, SparseArrays, LinearAlgebra, WriteVTK + +struct GrayScottMaterial{T} + D₁::T + D₂::T + F::T + k::T +end; + +function assemble_element_mass!(Me::Matrix, cellvalues::CellValues) + n_basefuncs = getnbasefunctions(cellvalues) + # The mass matrices between the reactions are not coupled, so we get a blocked-strided matrix. + num_reactants = 2 + r₁range = 1:num_reactants:(num_reactants * n_basefuncs) + r₂range = 2:num_reactants:(num_reactants * n_basefuncs) + Me₁ = @view Me[r₁range, r₁range] + Me₂ = @view Me[r₂range, r₂range] + # Reset to 0 + fill!(Me, 0) + # Loop over quadrature points + for q_point in 1:getnquadpoints(cellvalues) + # Get the quadrature weight + dΩ = getdetJdV(cellvalues, q_point) + # Loop over test shape functions + for i in 1:n_basefuncs + δuᵢ = shape_value(cellvalues, q_point, i) + # Loop over trial shape functions + for j in 1:n_basefuncs + δuⱼ = shape_value(cellvalues, q_point, j) + # Add contribution to Ke + Me₁[i, j] += (δuᵢ * δuⱼ) * dΩ + Me₂[i, j] += (δuᵢ * δuⱼ) * dΩ + end + end + end + return nothing +end + +function assemble_element_diffusion!(De::Matrix, cellvalues::CellValues, material::GrayScottMaterial) + n_basefuncs = getnbasefunctions(cellvalues) + D₁ = material.D₁ + D₂ = material.D₂ + # The diffusion between the reactions is not coupled, so we get a blocked-strided matrix. + num_reactants = 2 + r₁range = 1:num_reactants:(num_reactants * n_basefuncs) + r₂range = 2:num_reactants:(num_reactants * n_basefuncs) + De₁ = @view De[r₁range, r₁range] + De₂ = @view De[r₂range, r₂range] + # Reset to 0 + fill!(De, 0) + # Loop over quadrature points + for q_point in 1:getnquadpoints(cellvalues) + # Get the quadrature weight + dΩ = getdetJdV(cellvalues, q_point) + # Loop over test shape functions + for i in 1:n_basefuncs + ∇δuᵢ = shape_gradient(cellvalues, q_point, i) + # Loop over trial shape functions + for j in 1:n_basefuncs + ∇δuⱼ = shape_gradient(cellvalues, q_point, j) + # Add contribution to Ke + De₁[i, j] += D₁ * (∇δuᵢ ⋅ ∇δuⱼ) * dΩ + De₂[i, j] += D₂ * (∇δuᵢ ⋅ ∇δuⱼ) * dΩ + end + end + end + return nothing +end + +function assemble_matrices!(M::SparseMatrixCSC, D::SparseMatrixCSC, cellvalues::CellValues, dh::DofHandler, material::GrayScottMaterial) + n_basefuncs = getnbasefunctions(cellvalues) + + # Allocate the element stiffness matrix and element force vector + Me = zeros(2 * n_basefuncs, 2 * n_basefuncs) + De = zeros(2 * n_basefuncs, 2 * n_basefuncs) + + # Create an assembler + M_assembler = start_assemble(M) + D_assembler = start_assemble(D) + # Loop over all cels + for cell in CellIterator(dh) + # Reinitialize cellvalues for this cell + reinit!(cellvalues, cell) + # Compute element contribution + assemble_element_mass!(Me, cellvalues) + assemble!(M_assembler, celldofs(cell), Me) + + assemble_element_diffusion!(De, cellvalues, material) + assemble!(D_assembler, celldofs(cell), De) + end + return nothing +end; + +function setup_initial_conditions!(u₀::Vector, cellvalues::CellValues, dh::DofHandler) + u₀ .= ones(ndofs(dh)) + u₀[2:2:end] .= 0.0 + + n_basefuncs = getnbasefunctions(cellvalues) + + for cell in CellIterator(dh) + reinit!(cellvalues, cell) + + coords = getcoordinates(cell) + dofs = celldofs(cell) + uₑ = @view u₀[dofs] + rv₀ₑ = reshape(uₑ, (2, n_basefuncs)) + + for i in 1:n_basefuncs + if coords[i][3] > 0.9 + rv₀ₑ[1, i] = 0.5 + rv₀ₑ[2, i] = 0.25 + end + end + end + + u₀ .+= 0.01 * rand(ndofs(dh)) + return +end; + +function create_embedded_sphere(refinements) + gmsh.initialize() + + # Add a unit sphere in 3D space + gmsh.model.occ.addSphere(0.0, 0.0, 0.0, 1.0) + gmsh.model.occ.synchronize() + + # Generate nodes and surface elements only, hence we need to pass 2 into generate + gmsh.model.mesh.generate(2) + + # To get good solution quality refine the elements several times + for _ in 1:refinements + gmsh.model.mesh.refine() + end + + # Now we create a Ferrite grid out of it. Note that we also call toelements + # with our surface element dimension to obtain these. + nodes = tonodes() + elements, _ = toelements(2) + gmsh.finalize() + return Grid(elements, nodes) +end + +function gray_scott_on_sphere(material::GrayScottMaterial, Δt::Real, T::Real, refinements::Integer) + # We start by setting up grid, dof handler and the matrices for the heat problem. + grid = create_embedded_sphere(refinements) + + # Next we are creating our element assembly helper for surface elements. + # The only change which we need to introduce here is to pass in a geometrical + # interpolation with the same dimension as the physical space into which our + # elements are embedded into, which is in this example 3. + ip = Lagrange{RefTriangle, 1}() + qr = QuadratureRule{RefTriangle}(2) + cellvalues = CellValues(qr, ip, ip^3) + + # We have two options to add the reactants to the dof handler, which will give us slightly + # different resulting dof distributions: + # A) We can add a scalar-valued interpolation for each reactant. + # B) We can add one vectorized interpolation whose dimension is the number of reactants + # number of reactants. + # In this tutorial we opt for B, because the dofs are distributed per cell entity -- or + # to be specific for this tutorial, we use an isoparametric concept such that the nodes + # of our grid and the nodes of our solution approximation coincide. This way a reaction + # we can create simply reshape the solution vector u to a matrix where the inner index + # corresponds to the index of the reactant. Note that we will still use the scalar + # interpolation for the assembly procedure. + dh = DofHandler(grid) + add!(dh, :reactants, ip^2) + close!(dh) + + # We can save some memory by telling the sparsity pattern that the matrices are not coupled. + M = allocate_matrix(dh; coupling = [true false; false true]) + D = allocate_matrix(dh; coupling = [true false; false true]) + + # Since the heat problem is linear and has no time dependent parameters, we precompute the + # decomposition of the system matrix to speed up the linear system solver. + assemble_matrices!(M, D, cellvalues, dh, material) + A = M + Δt .* D + cholA = cholesky(A) + + # Now we setup buffers for the time dependent solution and fill the initial condition. + uₜ = zeros(ndofs(dh)) + uₜ₋₁ = ones(ndofs(dh)) + setup_initial_conditions!(uₜ₋₁, cellvalues, dh) + + # And prepare output for visualization. + pvd = paraview_collection("reactive-surface") + VTKGridFile("reactive-surface-0", dh) do vtk + write_solution(vtk, dh, uₜ₋₁) + pvd[0.0] = vtk + end + + # This is now the main solve loop. + F = material.F + k = material.k + for (iₜ, t) in enumerate(Δt:Δt:T) + # First we solve the heat problem + uₜ .= cholA \ (M * uₜ₋₁) + + # Then we solve the point-wise reaction problem with the solution of + # the heat problem as initial guess. 2 is the number of reactants. + num_individual_reaction_dofs = ndofs(dh) ÷ 2 + rvₜ = reshape(uₜ, (2, num_individual_reaction_dofs)) + for i in 1:num_individual_reaction_dofs + r₁ = rvₜ[1, i] + r₂ = rvₜ[2, i] + rvₜ[1, i] += Δt * (-r₁ * r₂^2 + F * (1 - r₁)) + rvₜ[2, i] += Δt * (r₁ * r₂^2 - r₂ * (F + k)) + end + + # The solution is then stored every 10th step to vtk files for + # later visualization purposes. + if (iₜ % 10) == 0 + VTKGridFile("reactive-surface-$(iₜ)", dh) do vtk + write_solution(vtk, dh, uₜ₋₁) + pvd[t] = vtk + end + end + + # Finally we totate the solution to initialize the next timestep. + uₜ₋₁ .= uₜ + end + vtk_save(pvd) + return +end + +# This parametrization gives the spot pattern shown in the gif above. +material = GrayScottMaterial(0.00016, 0.00008, 0.06, 0.062) + gray_scott_on_sphere(material, 10.0, 32000.0, 3) + +# This file was generated using Literate.jl, https://github.com/fredrikekre/Literate.jl diff --git a/previews/PR798/tutorials/reactive_surface/index.html b/previews/PR798/tutorials/reactive_surface/index.html new file mode 100644 index 0000000000..e6129f6f17 --- /dev/null +++ b/previews/PR798/tutorials/reactive_surface/index.html @@ -0,0 +1,487 @@ + +Reactive surface · Ferrite.jl

Reactive surface

Figure 1: Reactant concentration field of the Gray-Scott model on the unit sphere.

Tip

This example is also available as a Jupyter notebook: reactive_surface.ipynb.

Introduction

This tutorial gives a quick tutorial on how to assemble and solve time-dependent problems on embedded surfaces.

For this showcase we use the well known Gray-Scott model, which is a well-known reaction-diffusion system to study pattern formation. The strong form is given by

\[ \begin{aligned} + \partial_t r_1 &= \nabla \cdot (D_1 \nabla r_1) - r_1*r_2^2 + F *(1 - r_1) \quad \textbf{x} \in \Omega, \\ + \partial_t r_2 &= \nabla \cdot (D_2 \nabla r_2) + r_1*r_2^2 - r_2*(F + k ) \quad \textbf{x} \in \Omega, + \end{aligned}\]

where $r_1$ and $r_2$ are the reaction fields, $D_1$ and $D_2$ the diffusion tensors, $k$ is the conversion rate, $F$ is the feed rate and $\Omega$ the domain. Depending on the choice of parameters a different pattern can be observed. Please also note that the domain does not have a boundary. The corresponding weak form can be derived as usual.

For simplicity we will solve the problem with the Lie-Troter-Godunov operator splitting technique with the classical reaction-diffusion split. In this method we split our problem in two problems, i.e. a heat problem and a pointwise reaction problem, and solve them alternatingly to advance in time.

Solver details

The main idea for the Lie-Trotter-Godunov scheme is simple. We can write down the reaction diffusion problem in an abstract way as

\[ \partial_t \mathbf{r} = \mathcal{D}\mathbf{r} + R(\mathbf{r}) \quad \textbf{x} \in \Omega\]

where $\mathcal{D}$ is the diffusion operator and $R$ is the reaction operator. Notice that the right hand side is just the sum of two operators. Now with our operator splitting scheme we can advance a solution $\mathbf{r}(t_1)$ to $\mathbf{r}(t_2)$ by first solving a heat problem

\[ \partial_t \mathbf{r}^{\mathrm{\mathrm{A}}} = \mathcal{D}\mathbf{r}^{\mathrm{A}} \quad \textbf{x} \in \Omega\]

with $\mathbf{r}^{\mathrm{A}}(t_1) = \mathbf{r}(t_1)$ on the time interval $t_1$ to $t_2$ and use the solution as the initial condition to solve the reaction problem

\[ \partial_t \mathbf{r}^{\mathrm{B}} = R(\mathbf{r}^{\mathrm{B}}) \quad \textbf{x} \in \Omega\]

with $\mathbf{r}^{\mathrm{B}}(t_1) = \mathbf{r}^{\mathrm{A}}(t_2)$. This way we obtain a solution approximation $\mathbf{r}(t_2) \approx \mathbf{r}^{\mathrm{B}}(t_2)$.

Note

The operator splitting itself is an approximation, so even if we solve the subproblems analytically we end up with having only a solution approximation. We also do not have a beginner friendly reference for the theory behind operator splitting and can only refer to the original papers for each method.

Commented Program

Now we solve the problem in Ferrite. What follows is a program spliced with comments. The full program, without comments, can be found in the next section.

First we load Ferrite, and some other packages we need

using Ferrite, FerriteGmsh
+using BlockArrays, SparseArrays, LinearAlgebra, WriteVTK

Assembly routines

Before we head into the assembly, we define a helper struct to control the dispatches.

struct GrayScottMaterial{T}
+    D₁::T
+    D₂::T
+    F::T
+    k::T
+end;

The following assembly routines are written analogue to these found in previous tutorials.

function assemble_element_mass!(Me::Matrix, cellvalues::CellValues)
+    n_basefuncs = getnbasefunctions(cellvalues)
+    # The mass matrices between the reactions are not coupled, so we get a blocked-strided matrix.
+    num_reactants = 2
+    r₁range = 1:num_reactants:(num_reactants * n_basefuncs)
+    r₂range = 2:num_reactants:(num_reactants * n_basefuncs)
+    Me₁ = @view Me[r₁range, r₁range]
+    Me₂ = @view Me[r₂range, r₂range]
+    # Reset to 0
+    fill!(Me, 0)
+    # Loop over quadrature points
+    for q_point in 1:getnquadpoints(cellvalues)
+        # Get the quadrature weight
+        dΩ = getdetJdV(cellvalues, q_point)
+        # Loop over test shape functions
+        for i in 1:n_basefuncs
+            δuᵢ = shape_value(cellvalues, q_point, i)
+            # Loop over trial shape functions
+            for j in 1:n_basefuncs
+                δuⱼ = shape_value(cellvalues, q_point, j)
+                # Add contribution to Ke
+                Me₁[i, j] += (δuᵢ * δuⱼ) * dΩ
+                Me₂[i, j] += (δuᵢ * δuⱼ) * dΩ
+            end
+        end
+    end
+    return nothing
+end
+
+function assemble_element_diffusion!(De::Matrix, cellvalues::CellValues, material::GrayScottMaterial)
+    n_basefuncs = getnbasefunctions(cellvalues)
+    D₁ = material.D₁
+    D₂ = material.D₂
+    # The diffusion between the reactions is not coupled, so we get a blocked-strided matrix.
+    num_reactants = 2
+    r₁range = 1:num_reactants:(num_reactants * n_basefuncs)
+    r₂range = 2:num_reactants:(num_reactants * n_basefuncs)
+    De₁ = @view De[r₁range, r₁range]
+    De₂ = @view De[r₂range, r₂range]
+    # Reset to 0
+    fill!(De, 0)
+    # Loop over quadrature points
+    for q_point in 1:getnquadpoints(cellvalues)
+        # Get the quadrature weight
+        dΩ = getdetJdV(cellvalues, q_point)
+        # Loop over test shape functions
+        for i in 1:n_basefuncs
+            ∇δuᵢ = shape_gradient(cellvalues, q_point, i)
+            # Loop over trial shape functions
+            for j in 1:n_basefuncs
+                ∇δuⱼ = shape_gradient(cellvalues, q_point, j)
+                # Add contribution to Ke
+                De₁[i, j] += D₁ * (∇δuᵢ ⋅ ∇δuⱼ) * dΩ
+                De₂[i, j] += D₂ * (∇δuᵢ ⋅ ∇δuⱼ) * dΩ
+            end
+        end
+    end
+    return nothing
+end
+
+function assemble_matrices!(M::SparseMatrixCSC, D::SparseMatrixCSC, cellvalues::CellValues, dh::DofHandler, material::GrayScottMaterial)
+    n_basefuncs = getnbasefunctions(cellvalues)
+
+    # Allocate the element stiffness matrix and element force vector
+    Me = zeros(2 * n_basefuncs, 2 * n_basefuncs)
+    De = zeros(2 * n_basefuncs, 2 * n_basefuncs)
+
+    # Create an assembler
+    M_assembler = start_assemble(M)
+    D_assembler = start_assemble(D)
+    # Loop over all cels
+    for cell in CellIterator(dh)
+        # Reinitialize cellvalues for this cell
+        reinit!(cellvalues, cell)
+        # Compute element contribution
+        assemble_element_mass!(Me, cellvalues)
+        assemble!(M_assembler, celldofs(cell), Me)
+
+        assemble_element_diffusion!(De, cellvalues, material)
+        assemble!(D_assembler, celldofs(cell), De)
+    end
+    return nothing
+end;

Initial condition setup

Time-dependent problems always need an initial condition from which the time evolution starts. In this tutorial we set the concentration of reactant 1 to $1$ and the concentration of reactant 2 to $0$ for all nodal dof with associated coordinate $z \leq 0.9$ on the sphere. Since the simulation would be pretty boring with a steady-state initial condition, we introduce some heterogeneity by setting the dofs associated to top part of the sphere (i.e. dofs with $z > 0.9$ to store the reactant concentrations of $0.5$ and $0.25$ for the reactants 1 and 2 respectively.

function setup_initial_conditions!(u₀::Vector, cellvalues::CellValues, dh::DofHandler)
+    u₀ .= ones(ndofs(dh))
+    u₀[2:2:end] .= 0.0
+
+    n_basefuncs = getnbasefunctions(cellvalues)
+
+    for cell in CellIterator(dh)
+        reinit!(cellvalues, cell)
+
+        coords = getcoordinates(cell)
+        dofs = celldofs(cell)
+        uₑ = @view u₀[dofs]
+        rv₀ₑ = reshape(uₑ, (2, n_basefuncs))
+
+        for i in 1:n_basefuncs
+            if coords[i][3] > 0.9
+                rv₀ₑ[1, i] = 0.5
+                rv₀ₑ[2, i] = 0.25
+            end
+        end
+    end
+
+    u₀ .+= 0.01 * rand(ndofs(dh))
+    return
+end;

Mesh generation

In this section we define a routine to create a surface mesh with the help of FerriteGmsh.jl.

function create_embedded_sphere(refinements)
+    gmsh.initialize()
+
+    # Add a unit sphere in 3D space
+    gmsh.model.occ.addSphere(0.0, 0.0, 0.0, 1.0)
+    gmsh.model.occ.synchronize()
+
+    # Generate nodes and surface elements only, hence we need to pass 2 into generate
+    gmsh.model.mesh.generate(2)
+
+    # To get good solution quality refine the elements several times
+    for _ in 1:refinements
+        gmsh.model.mesh.refine()
+    end
+
+    # Now we create a Ferrite grid out of it. Note that we also call toelements
+    # with our surface element dimension to obtain these.
+    nodes = tonodes()
+    elements, _ = toelements(2)
+    gmsh.finalize()
+    return Grid(elements, nodes)
+end
create_embedded_sphere (generic function with 1 method)

Simulation routines

Now we define a function to setup and solve the problem with given feed and conversion rates $F$ and $k$, as well as the time step length and for how long we want to solve the model.

function gray_scott_on_sphere(material::GrayScottMaterial, Δt::Real, T::Real, refinements::Integer)
+    # We start by setting up grid, dof handler and the matrices for the heat problem.
+    grid = create_embedded_sphere(refinements)
+
+    # Next we are creating our element assembly helper for surface elements.
+    # The only change which we need to introduce here is to pass in a geometrical
+    # interpolation with the same dimension as the physical space into which our
+    # elements are embedded into, which is in this example 3.
+    ip = Lagrange{RefTriangle, 1}()
+    qr = QuadratureRule{RefTriangle}(2)
+    cellvalues = CellValues(qr, ip, ip^3)
+
+    # We have two options to add the reactants to the dof handler, which will give us slightly
+    # different resulting dof distributions:
+    # A) We can add a scalar-valued interpolation for each reactant.
+    # B) We can add one vectorized interpolation whose dimension is the number of reactants
+    #    number of reactants.
+    # In this tutorial we opt for B, because the dofs are distributed per cell entity -- or
+    # to be specific for this tutorial, we use an isoparametric concept such that the nodes
+    # of our grid and the nodes of our solution approximation coincide. This way a reaction
+    # we can create simply reshape the solution vector u to a matrix where the inner index
+    # corresponds to the index of the reactant. Note that we will still use the scalar
+    # interpolation for the assembly procedure.
+    dh = DofHandler(grid)
+    add!(dh, :reactants, ip^2)
+    close!(dh)
+
+    # We can save some memory by telling the sparsity pattern that the matrices are not coupled.
+    M = allocate_matrix(dh; coupling = [true false; false true])
+    D = allocate_matrix(dh; coupling = [true false; false true])
+
+    # Since the heat problem is linear and has no time dependent parameters, we precompute the
+    # decomposition of the system matrix to speed up the linear system solver.
+    assemble_matrices!(M, D, cellvalues, dh, material)
+    A = M + Δt .* D
+    cholA = cholesky(A)
+
+    # Now we setup buffers for the time dependent solution and fill the initial condition.
+    uₜ = zeros(ndofs(dh))
+    uₜ₋₁ = ones(ndofs(dh))
+    setup_initial_conditions!(uₜ₋₁, cellvalues, dh)
+
+    # And prepare output for visualization.
+    pvd = paraview_collection("reactive-surface")
+    VTKGridFile("reactive-surface-0", dh) do vtk
+        write_solution(vtk, dh, uₜ₋₁)
+        pvd[0.0] = vtk
+    end
+
+    # This is now the main solve loop.
+    F = material.F
+    k = material.k
+    for (iₜ, t) in enumerate(Δt:Δt:T)
+        # First we solve the heat problem
+        uₜ .= cholA \ (M * uₜ₋₁)
+
+        # Then we solve the point-wise reaction problem with the solution of
+        # the heat problem as initial guess. 2 is the number of reactants.
+        num_individual_reaction_dofs = ndofs(dh) ÷ 2
+        rvₜ = reshape(uₜ, (2, num_individual_reaction_dofs))
+        for i in 1:num_individual_reaction_dofs
+            r₁ = rvₜ[1, i]
+            r₂ = rvₜ[2, i]
+            rvₜ[1, i] += Δt * (-r₁ * r₂^2 + F * (1 - r₁))
+            rvₜ[2, i] += Δt * (r₁ * r₂^2 - r₂ * (F + k))
+        end
+
+        # The solution is then stored every 10th step to vtk files for
+        # later visualization purposes.
+        if (iₜ % 10) == 0
+            VTKGridFile("reactive-surface-$(iₜ)", dh) do vtk
+                write_solution(vtk, dh, uₜ₋₁)
+                pvd[t] = vtk
+            end
+        end
+
+        # Finally we totate the solution to initialize the next timestep.
+        uₜ₋₁ .= uₜ
+    end
+    vtk_save(pvd)
+    return
+end
+
+# This parametrization gives the spot pattern shown in the gif above.
+material = GrayScottMaterial(0.00016, 0.00008, 0.06, 0.062)
+    gray_scott_on_sphere(material, 10.0, 32000.0, 3)
Info    : Meshing 1D...
+Info    : [ 40%] Meshing curve 2 (Circle)
+Info    : Done meshing 1D (Wall 8.9307e-05s, CPU 9e-05s)
+Info    : Meshing 2D...
+Info    : Meshing surface 1 (Sphere, Frontal-Delaunay)
+Info    : Done meshing 2D (Wall 0.00921769s, CPU 0.009216s)
+Info    : 160 nodes 328 elements
+Info    : Refining mesh...
+Info    : Meshing order 2 (curvilinear on)...
+Info    : [  0%] Meshing curve 1 order 2
+Info    : [ 30%] Meshing curve 2 order 2
+Info    : [ 50%] Meshing curve 3 order 2
+Info    : [ 70%] Meshing surface 1 order 2
+Info    : [ 90%] Meshing volume 1 order 2
+Info    : Surface mesh: worst distortion = 0.970866 (0 elements in ]0, 0.2]); worst gamma = 0.433887
+Info    : Done meshing order 2 (Wall 0.00115278s, CPU 0.001152s)
+Info    : Done refining mesh (Wall 0.00148769s, CPU 0.001488s)
+Info    : Refining mesh...
+Info    : Meshing order 2 (curvilinear on)...
+Info    : [  0%] Meshing curve 1 order 2
+Info    : [ 30%] Meshing curve 2 order 2
+Info    : [ 50%] Meshing curve 3 order 2
+Info    : [ 70%] Meshing surface 1 order 2
+Info    : [ 90%] Meshing volume 1 order 2
+Info    : Surface mesh: worst distortion = 0.992408 (0 elements in ]0, 0.2]); worst gamma = 0.421451
+Info    : Done meshing order 2 (Wall 0.00429305s, CPU 0.004293s)
+Info    : Done refining mesh (Wall 0.00556617s, CPU 0.005565s)
+Info    : Refining mesh...
+Info    : Meshing order 2 (curvilinear on)...
+Info    : [  0%] Meshing curve 1 order 2
+Info    : [ 30%] Meshing curve 2 order 2
+Info    : [ 50%] Meshing curve 3 order 2
+Info    : [ 70%] Meshing surface 1 order 2
+Info    : [ 90%] Meshing volume 1 order 2
+Info    : Surface mesh: worst distortion = 0.998082 (0 elements in ]0, 0.2]); worst gamma = 0.418317
+Info    : Done meshing order 2 (Wall 0.0162483s, CPU 0.016247s)
+Info    : Done refining mesh (Wall 0.0212657s, CPU 0.021265s)

Plain program

Here follows a version of the program without any comments. The file is also available here: reactive_surface.jl.

using Ferrite, FerriteGmsh
+using BlockArrays, SparseArrays, LinearAlgebra, WriteVTK
+
+struct GrayScottMaterial{T}
+    D₁::T
+    D₂::T
+    F::T
+    k::T
+end;
+
+function assemble_element_mass!(Me::Matrix, cellvalues::CellValues)
+    n_basefuncs = getnbasefunctions(cellvalues)
+    # The mass matrices between the reactions are not coupled, so we get a blocked-strided matrix.
+    num_reactants = 2
+    r₁range = 1:num_reactants:(num_reactants * n_basefuncs)
+    r₂range = 2:num_reactants:(num_reactants * n_basefuncs)
+    Me₁ = @view Me[r₁range, r₁range]
+    Me₂ = @view Me[r₂range, r₂range]
+    # Reset to 0
+    fill!(Me, 0)
+    # Loop over quadrature points
+    for q_point in 1:getnquadpoints(cellvalues)
+        # Get the quadrature weight
+        dΩ = getdetJdV(cellvalues, q_point)
+        # Loop over test shape functions
+        for i in 1:n_basefuncs
+            δuᵢ = shape_value(cellvalues, q_point, i)
+            # Loop over trial shape functions
+            for j in 1:n_basefuncs
+                δuⱼ = shape_value(cellvalues, q_point, j)
+                # Add contribution to Ke
+                Me₁[i, j] += (δuᵢ * δuⱼ) * dΩ
+                Me₂[i, j] += (δuᵢ * δuⱼ) * dΩ
+            end
+        end
+    end
+    return nothing
+end
+
+function assemble_element_diffusion!(De::Matrix, cellvalues::CellValues, material::GrayScottMaterial)
+    n_basefuncs = getnbasefunctions(cellvalues)
+    D₁ = material.D₁
+    D₂ = material.D₂
+    # The diffusion between the reactions is not coupled, so we get a blocked-strided matrix.
+    num_reactants = 2
+    r₁range = 1:num_reactants:(num_reactants * n_basefuncs)
+    r₂range = 2:num_reactants:(num_reactants * n_basefuncs)
+    De₁ = @view De[r₁range, r₁range]
+    De₂ = @view De[r₂range, r₂range]
+    # Reset to 0
+    fill!(De, 0)
+    # Loop over quadrature points
+    for q_point in 1:getnquadpoints(cellvalues)
+        # Get the quadrature weight
+        dΩ = getdetJdV(cellvalues, q_point)
+        # Loop over test shape functions
+        for i in 1:n_basefuncs
+            ∇δuᵢ = shape_gradient(cellvalues, q_point, i)
+            # Loop over trial shape functions
+            for j in 1:n_basefuncs
+                ∇δuⱼ = shape_gradient(cellvalues, q_point, j)
+                # Add contribution to Ke
+                De₁[i, j] += D₁ * (∇δuᵢ ⋅ ∇δuⱼ) * dΩ
+                De₂[i, j] += D₂ * (∇δuᵢ ⋅ ∇δuⱼ) * dΩ
+            end
+        end
+    end
+    return nothing
+end
+
+function assemble_matrices!(M::SparseMatrixCSC, D::SparseMatrixCSC, cellvalues::CellValues, dh::DofHandler, material::GrayScottMaterial)
+    n_basefuncs = getnbasefunctions(cellvalues)
+
+    # Allocate the element stiffness matrix and element force vector
+    Me = zeros(2 * n_basefuncs, 2 * n_basefuncs)
+    De = zeros(2 * n_basefuncs, 2 * n_basefuncs)
+
+    # Create an assembler
+    M_assembler = start_assemble(M)
+    D_assembler = start_assemble(D)
+    # Loop over all cels
+    for cell in CellIterator(dh)
+        # Reinitialize cellvalues for this cell
+        reinit!(cellvalues, cell)
+        # Compute element contribution
+        assemble_element_mass!(Me, cellvalues)
+        assemble!(M_assembler, celldofs(cell), Me)
+
+        assemble_element_diffusion!(De, cellvalues, material)
+        assemble!(D_assembler, celldofs(cell), De)
+    end
+    return nothing
+end;
+
+function setup_initial_conditions!(u₀::Vector, cellvalues::CellValues, dh::DofHandler)
+    u₀ .= ones(ndofs(dh))
+    u₀[2:2:end] .= 0.0
+
+    n_basefuncs = getnbasefunctions(cellvalues)
+
+    for cell in CellIterator(dh)
+        reinit!(cellvalues, cell)
+
+        coords = getcoordinates(cell)
+        dofs = celldofs(cell)
+        uₑ = @view u₀[dofs]
+        rv₀ₑ = reshape(uₑ, (2, n_basefuncs))
+
+        for i in 1:n_basefuncs
+            if coords[i][3] > 0.9
+                rv₀ₑ[1, i] = 0.5
+                rv₀ₑ[2, i] = 0.25
+            end
+        end
+    end
+
+    u₀ .+= 0.01 * rand(ndofs(dh))
+    return
+end;
+
+function create_embedded_sphere(refinements)
+    gmsh.initialize()
+
+    # Add a unit sphere in 3D space
+    gmsh.model.occ.addSphere(0.0, 0.0, 0.0, 1.0)
+    gmsh.model.occ.synchronize()
+
+    # Generate nodes and surface elements only, hence we need to pass 2 into generate
+    gmsh.model.mesh.generate(2)
+
+    # To get good solution quality refine the elements several times
+    for _ in 1:refinements
+        gmsh.model.mesh.refine()
+    end
+
+    # Now we create a Ferrite grid out of it. Note that we also call toelements
+    # with our surface element dimension to obtain these.
+    nodes = tonodes()
+    elements, _ = toelements(2)
+    gmsh.finalize()
+    return Grid(elements, nodes)
+end
+
+function gray_scott_on_sphere(material::GrayScottMaterial, Δt::Real, T::Real, refinements::Integer)
+    # We start by setting up grid, dof handler and the matrices for the heat problem.
+    grid = create_embedded_sphere(refinements)
+
+    # Next we are creating our element assembly helper for surface elements.
+    # The only change which we need to introduce here is to pass in a geometrical
+    # interpolation with the same dimension as the physical space into which our
+    # elements are embedded into, which is in this example 3.
+    ip = Lagrange{RefTriangle, 1}()
+    qr = QuadratureRule{RefTriangle}(2)
+    cellvalues = CellValues(qr, ip, ip^3)
+
+    # We have two options to add the reactants to the dof handler, which will give us slightly
+    # different resulting dof distributions:
+    # A) We can add a scalar-valued interpolation for each reactant.
+    # B) We can add one vectorized interpolation whose dimension is the number of reactants
+    #    number of reactants.
+    # In this tutorial we opt for B, because the dofs are distributed per cell entity -- or
+    # to be specific for this tutorial, we use an isoparametric concept such that the nodes
+    # of our grid and the nodes of our solution approximation coincide. This way a reaction
+    # we can create simply reshape the solution vector u to a matrix where the inner index
+    # corresponds to the index of the reactant. Note that we will still use the scalar
+    # interpolation for the assembly procedure.
+    dh = DofHandler(grid)
+    add!(dh, :reactants, ip^2)
+    close!(dh)
+
+    # We can save some memory by telling the sparsity pattern that the matrices are not coupled.
+    M = allocate_matrix(dh; coupling = [true false; false true])
+    D = allocate_matrix(dh; coupling = [true false; false true])
+
+    # Since the heat problem is linear and has no time dependent parameters, we precompute the
+    # decomposition of the system matrix to speed up the linear system solver.
+    assemble_matrices!(M, D, cellvalues, dh, material)
+    A = M + Δt .* D
+    cholA = cholesky(A)
+
+    # Now we setup buffers for the time dependent solution and fill the initial condition.
+    uₜ = zeros(ndofs(dh))
+    uₜ₋₁ = ones(ndofs(dh))
+    setup_initial_conditions!(uₜ₋₁, cellvalues, dh)
+
+    # And prepare output for visualization.
+    pvd = paraview_collection("reactive-surface")
+    VTKGridFile("reactive-surface-0", dh) do vtk
+        write_solution(vtk, dh, uₜ₋₁)
+        pvd[0.0] = vtk
+    end
+
+    # This is now the main solve loop.
+    F = material.F
+    k = material.k
+    for (iₜ, t) in enumerate(Δt:Δt:T)
+        # First we solve the heat problem
+        uₜ .= cholA \ (M * uₜ₋₁)
+
+        # Then we solve the point-wise reaction problem with the solution of
+        # the heat problem as initial guess. 2 is the number of reactants.
+        num_individual_reaction_dofs = ndofs(dh) ÷ 2
+        rvₜ = reshape(uₜ, (2, num_individual_reaction_dofs))
+        for i in 1:num_individual_reaction_dofs
+            r₁ = rvₜ[1, i]
+            r₂ = rvₜ[2, i]
+            rvₜ[1, i] += Δt * (-r₁ * r₂^2 + F * (1 - r₁))
+            rvₜ[2, i] += Δt * (r₁ * r₂^2 - r₂ * (F + k))
+        end
+
+        # The solution is then stored every 10th step to vtk files for
+        # later visualization purposes.
+        if (iₜ % 10) == 0
+            VTKGridFile("reactive-surface-$(iₜ)", dh) do vtk
+                write_solution(vtk, dh, uₜ₋₁)
+                pvd[t] = vtk
+            end
+        end
+
+        # Finally we totate the solution to initialize the next timestep.
+        uₜ₋₁ .= uₜ
+    end
+    vtk_save(pvd)
+    return
+end
+
+# This parametrization gives the spot pattern shown in the gif above.
+material = GrayScottMaterial(0.00016, 0.00008, 0.06, 0.062)
+    gray_scott_on_sphere(material, 10.0, 32000.0, 3)

This page was generated using Literate.jl.

diff --git a/previews/PR798/tutorials/rve_homogenization.png b/previews/PR798/tutorials/rve_homogenization.png new file mode 100644 index 0000000000..73aca2801e Binary files /dev/null and b/previews/PR798/tutorials/rve_homogenization.png differ diff --git a/previews/PR798/tutorials/stokes-flow.ipynb b/previews/PR798/tutorials/stokes-flow.ipynb new file mode 100644 index 0000000000..41da655822 --- /dev/null +++ b/previews/PR798/tutorials/stokes-flow.ipynb @@ -0,0 +1,683 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Stokes flow\n", + "\n", + "**Keywords**: *periodic boundary conditions, multiple fields, mean value constraint*" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "![](stokes-flow.png)\n", + "*Figure 1*: Left: Computational domain $\\Omega$ with boundaries $\\Gamma_1$,\n", + "$\\Gamma_3$ (periodic boundary conditions) and $\\Gamma_2$, $\\Gamma_4$ (homogeneous\n", + "Dirichlet boundary conditions). Right: Magnitude of the resulting velocity field." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Introduction and problem formulation\n", + "\n", + "This example is a translation of the [step-45 example from\n", + "deal.ii](https://www.dealii.org/current/doxygen/deal.II/step_45.html) which solves Stokes\n", + "flow on a quarter circle. In particular it shows how to use periodic boundary conditions,\n", + "how to solve a problem with multiple unknown fields, and how to enforce a specific mean\n", + "value of the solution. For the mesh generation we use\n", + "[Gmsh.jl](https://github.com/JuliaFEM/Gmsh.jl) and then use\n", + "[FerriteGmsh.jl](https://github.com/Ferrite-FEM/FerriteGmsh.jl) to import the mesh into\n", + "Ferrite's format.\n", + "\n", + "The strong form of Stokes flow with velocity $\\boldsymbol{u}$ and pressure $p$ can be\n", + "written as follows:\n", + "$$\n", + "\\begin{align*}\n", + "-\\Delta \\boldsymbol{u} + \\boldsymbol{\\nabla} p &= \\bigl(\\exp(-100||\\boldsymbol{x} - (0.75, 0.1)||^2), 0\\bigr) =:\n", + "\\boldsymbol{b} \\quad \\forall \\boldsymbol{x} \\in \\Omega,\\\\\n", + "-\\boldsymbol{\\nabla} \\cdot \\boldsymbol{u} &= 0 \\quad \\forall \\boldsymbol{x} \\in \\Omega,\n", + "\\end{align*}\n", + "$$\n", + "where the domain is defined as $\\Omega = \\{\\boldsymbol{x} \\in (0, 1)^2:\n", + "\\ ||\\boldsymbol{x}|| \\in (0.5, 1)\\}$, see *Figure 1*. For the velocity we use periodic\n", + "boundary conditions on the inlet $\\Gamma_1$ and outlet $\\Gamma_3$:\n", + "$$\n", + "\\begin{align*}\n", + "u_x(0,\\nu) &= -u_y(\\nu, 0) \\quad & \\nu\\ \\in\\ [0.5, 1],\\\\\n", + "u_y(0,\\nu) &= u_x(\\nu, 0) \\quad & \\nu\\ \\in\\ [0.5, 1],\n", + "\\end{align*}\n", + "$$\n", + "and homogeneous Dirichlet boundary conditions for $\\Gamma_2$ and $\\Gamma_4$:\n", + "$$\n", + "\\boldsymbol{u} = \\boldsymbol{0} \\quad \\forall \\boldsymbol{x}\\ \\in\\\n", + "\\Gamma_2 \\cup \\Gamma_4 := \\{ \\boldsymbol{x}:\\ ||\\boldsymbol{x}|| \\in \\{0.5, 1\\}\\}.\n", + "$$\n", + "\n", + "The corresponding weak form reads as follows: Find $(\\boldsymbol{u}, p) \\in \\mathbb{U}\n", + "\\times \\mathrm{L}_2$ s.t.\n", + "$$\n", + "\\begin{align*}\n", + "\\int_\\Omega \\Bigl[[\\delta\\boldsymbol{u}\\otimes\\boldsymbol{\\nabla}]:[\\boldsymbol{u}\\otimes\\boldsymbol{\\nabla}] -\n", + "(\\boldsymbol{\\nabla}\\cdot\\delta\\boldsymbol{u})\\ p\\ \\Bigr] \\mathrm{d}\\Omega &=\n", + "\\int_\\Omega \\delta\\boldsymbol{u} \\cdot \\boldsymbol{b}\\ \\mathrm{d}\\Omega \\quad \\forall\n", + "\\delta \\boldsymbol{u} \\in \\mathbb{U},\\\\\n", + "\\int_\\Omega - (\\boldsymbol{\\nabla}\\cdot\\boldsymbol{u})\\ \\delta p\\ \\mathrm{d}\\Omega &= 0\n", + "\\quad \\forall \\delta p \\in \\mathrm{L}_2,\n", + "\\end{align*}\n", + "$$\n", + "where $\\mathbb{U}$ is a suitable function space, that, in particular, enforces the\n", + "Dirichlet boundary conditions, and the periodicity constraints.\n", + "This formulation is a saddle point problem, and, just like the example with\n", + "Incompressible Elasticity, we need\n", + "our formulation to fulfill the [LBB\n", + "condition](https://en.wikipedia.org/wiki/Ladyzhenskaya%E2%80%93Babu%C5%A1ka%E2%80%93Brezzi_condition).\n", + "We ensure this by using a quadratic approximation for the velocity field, and a linear\n", + "approximation for the pressure.\n", + "\n", + "With this formulation and boundary conditions for $\\boldsymbol{u}$ the pressure will\n", + "only be determined up to a constant. We will therefore add an additional constraint which\n", + "fixes this constant (see [deal.ii\n", + "step-11](https://www.dealii.org/current/doxygen/deal.II/step_11.html) for some more\n", + "discussion around this). In particular, we will enforce the mean value of the pressure on\n", + "the boundary to be 0, i.e. $\\int_{\\Gamma} p\\ \\mathrm{d}\\Gamma = 0$. One option is to\n", + "enforce this using a Lagrange multiplier. This would give a contribution $\\lambda\n", + "\\int_{\\Gamma} \\delta p\\ \\mathrm{d}\\Gamma$ to the second equation in the weak form above,\n", + "and a third equation $\\delta\\lambda \\int_{\\Gamma} p\\ \\mathrm{d}\\Gamma = 0$ so that we\n", + "can solve for $\\lambda$. However, since we in this case are not interested in computing\n", + "$\\lambda$, and since the constraint is linear, we can directly embed this constraint\n", + "using an `AffineConstraint` in Ferrite.\n", + "\n", + "After FE discretization we obtain a linear system of the form\n", + "$\\underline{\\underline{K}}\\ \\underline{a} = \\underline{f}$, where\n", + "$$\n", + "\\underline{\\underline{K}} =\n", + "\\begin{bmatrix}\n", + "\\underline{\\underline{K}}_{uu} & \\underline{\\underline{K}}_{pu}^\\textrm{T} \\\\\n", + "\\underline{\\underline{K}}_{pu} & \\underline{\\underline{0}}\n", + "\\end{bmatrix}, \\quad\n", + "\\underline{a} = \\begin{bmatrix}\n", + "\\underline{a}_{u} \\\\\n", + "\\underline{a}_{p}\n", + "\\end{bmatrix}, \\quad\n", + "\\underline{f} = \\begin{bmatrix}\n", + "\\underline{f}_{u} \\\\\n", + "\\underline{0}\n", + "\\end{bmatrix},\n", + "$$\n", + "and where\n", + "$$\n", + "\\begin{align*}\n", + "(\\underline{\\underline{K}}_{uu})_{ij} &= \\int_\\Omega [\\boldsymbol{\\phi}^u_i\\otimes\\boldsymbol{\\nabla}]:[\\boldsymbol{\\phi}^u_j\\otimes\\boldsymbol{\\nabla}] \\mathrm{d}\\Omega, \\\\\n", + "(\\underline{\\underline{K}}_{pu})_{ij} &= \\int_\\Omega - (\\boldsymbol{\\nabla}\\cdot\\boldsymbol{\\phi}^u_j)\\ \\phi^p_i\\ \\mathrm{d}\\Omega, \\\\\n", + "(\\underline{f}_{u})_{i} &= \\int_\\Omega \\boldsymbol{\\phi}^u_i \\cdot \\boldsymbol{b}\\ \\mathrm{d}\\Omega.\n", + "\\end{align*}\n", + "$$\n", + "\n", + "The affine constraint to enforce zero mean pressure on the boundary is obtained from\n", + "$\\underline{\\underline{C}}_p\\ \\underline{a}_p = \\underline{0}$, where\n", + "$$\n", + "(\\underline{\\underline{C}}_p)_{1j} = \\int_{\\Gamma} \\phi^p_j\\ \\mathrm{d}\\Gamma.\n", + "$$\n", + "\n", + "> **Note**\n", + ">\n", + "> The constraint matrix $\\underline{\\underline{C}}_p$ is the same matrix we would have\n", + "> obtained when assembling the system with the Lagrange multiplier. In that case the\n", + "> full system would be\n", + "> $$\n", + "> \\underline{\\underline{K}} =\n", + "> \\begin{bmatrix}\n", + "> \\underline{\\underline{K}}_{uu} & \\underline{\\underline{K}}_{pu}^\\textrm{T} &\n", + "> \\underline{\\underline{0}}\\\\\n", + "> \\underline{\\underline{K}}_{pu} & \\underline{\\underline{0}} & \\underline{\\underline{C}}_p^\\mathrm{T} \\\\\n", + "> \\underline{\\underline{0}} & \\underline{\\underline{C}}_p & 0 \\\\\n", + "> \\end{bmatrix}, \\quad\n", + "> \\underline{a} = \\begin{bmatrix}\n", + "> \\underline{a}_{u} \\\\\n", + "> \\underline{a}_{p} \\\\\n", + "> \\underline{a}_{\\lambda}\n", + "> \\end{bmatrix}, \\quad\n", + "> \\underline{f} = \\begin{bmatrix}\n", + "> \\underline{f}_{u} \\\\\n", + "> \\underline{0} \\\\\n", + "> \\underline{0}\n", + "> \\end{bmatrix}.\n", + "> $$" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Commented program\n", + "\n", + "What follows is a program spliced with comments." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using Ferrite, FerriteGmsh, Gmsh, Tensors, LinearAlgebra, SparseArrays" + ], + "metadata": {}, + "execution_count": 1 + }, + { + "cell_type": "markdown", + "source": [ + "### Geometry and mesh generation with `Gmsh.jl`\n", + "\n", + "In the `setup_grid` function below we use the\n", + "[`Gmsh.jl`](https://github.com/JuliaFEM/Gmsh.jl) package for setting up the geometry and\n", + "performing the meshing. We will not discuss this part in much detail but refer to the\n", + "[Gmsh API documentation](https://gmsh.info/doc/texinfo/gmsh.html#Gmsh-API) instead. The\n", + "most important thing to note is the mesh periodicity constraint that is applied between\n", + "the \"inlet\" and \"outlet\" parts using `gmsh.model.set_periodic`. This is necessary to later\n", + "on apply a periodicity constraint for the approximated velocity field." + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "setup_grid (generic function with 2 methods)" + }, + "metadata": {}, + "execution_count": 2 + } + ], + "cell_type": "code", + "source": [ + "function setup_grid(h = 0.05)\n", + " # Initialize gmsh\n", + " Gmsh.initialize()\n", + " gmsh.option.set_number(\"General.Verbosity\", 2)\n", + "\n", + " # Add the points\n", + " o = gmsh.model.geo.add_point(0.0, 0.0, 0.0, h)\n", + " p1 = gmsh.model.geo.add_point(0.5, 0.0, 0.0, h)\n", + " p2 = gmsh.model.geo.add_point(1.0, 0.0, 0.0, h)\n", + " p3 = gmsh.model.geo.add_point(0.0, 1.0, 0.0, h)\n", + " p4 = gmsh.model.geo.add_point(0.0, 0.5, 0.0, h)\n", + "\n", + " # Add the lines\n", + " l1 = gmsh.model.geo.add_line(p1, p2)\n", + " l2 = gmsh.model.geo.add_circle_arc(p2, o, p3)\n", + " l3 = gmsh.model.geo.add_line(p3, p4)\n", + " l4 = gmsh.model.geo.add_circle_arc(p4, o, p1)\n", + "\n", + " # Create the closed curve loop and the surface\n", + " loop = gmsh.model.geo.add_curve_loop([l1, l2, l3, l4])\n", + " surf = gmsh.model.geo.add_plane_surface([loop])\n", + "\n", + " # Synchronize the model\n", + " gmsh.model.geo.synchronize()\n", + "\n", + " # Create the physical domains\n", + " gmsh.model.add_physical_group(1, [l1], -1, \"Γ1\")\n", + " gmsh.model.add_physical_group(1, [l2], -1, \"Γ2\")\n", + " gmsh.model.add_physical_group(1, [l3], -1, \"Γ3\")\n", + " gmsh.model.add_physical_group(1, [l4], -1, \"Γ4\")\n", + " gmsh.model.add_physical_group(2, [surf])\n", + "\n", + " # Add the periodicity constraint using 4x4 affine transformation matrix,\n", + " # see https://en.wikipedia.org/wiki/Transformation_matrix#Affine_transformations\n", + " transformation_matrix = zeros(4, 4)\n", + " transformation_matrix[1, 2] = 1 # -sin(-pi/2)\n", + " transformation_matrix[2, 1] = -1 # cos(-pi/2)\n", + " transformation_matrix[3, 3] = 1\n", + " transformation_matrix[4, 4] = 1\n", + " transformation_matrix = vec(transformation_matrix')\n", + " gmsh.model.mesh.set_periodic(1, [l1], [l3], transformation_matrix)\n", + "\n", + " # Generate a 2D mesh\n", + " gmsh.model.mesh.generate(2)\n", + "\n", + " # Save the mesh, and read back in as a Ferrite Grid\n", + " grid = mktempdir() do dir\n", + " path = joinpath(dir, \"mesh.msh\")\n", + " gmsh.write(path)\n", + " togrid(path)\n", + " end\n", + "\n", + " # Finalize the Gmsh library\n", + " Gmsh.finalize()\n", + "\n", + " return grid\n", + "end" + ], + "metadata": {}, + "execution_count": 2 + }, + { + "cell_type": "markdown", + "source": [ + "### Degrees of freedom\n", + "\n", + "As mentioned in the introduction we will use a quadratic approximation for the velocity\n", + "field and a linear approximation for the pressure to ensure that we fulfill the LBB\n", + "condition. We create the corresponding FE values with interpolations `ipu` for the\n", + "velocity and `ipp` for the pressure. Note that we specify linear geometric mapping\n", + "(`ipg`) for both the velocity and pressure because our grid contains linear\n", + "triangles. However, since linear mapping is default this could have been skipped.\n", + "We also construct facet-values for the pressure since we need to integrate along\n", + "the boundary when assembling the constraint matrix $\\underline{\\underline{C}}$." + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "setup_fevalues (generic function with 1 method)" + }, + "metadata": {}, + "execution_count": 3 + } + ], + "cell_type": "code", + "source": [ + "function setup_fevalues(ipu, ipp, ipg)\n", + " qr = QuadratureRule{RefTriangle}(2)\n", + " cvu = CellValues(qr, ipu, ipg)\n", + " cvp = CellValues(qr, ipp, ipg)\n", + " qr_facet = FacetQuadratureRule{RefTriangle}(2)\n", + " fvp = FacetValues(qr_facet, ipp, ipg)\n", + " return cvu, cvp, fvp\n", + "end" + ], + "metadata": {}, + "execution_count": 3 + }, + { + "cell_type": "markdown", + "source": [ + "The `setup_dofs` function creates the `DofHandler`, and adds the two fields: a\n", + "vector field `:u` with interpolation `ipu`, and a scalar field `:p` with interpolation\n", + "`ipp`." + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "setup_dofs (generic function with 1 method)" + }, + "metadata": {}, + "execution_count": 4 + } + ], + "cell_type": "code", + "source": [ + "function setup_dofs(grid, ipu, ipp)\n", + " dh = DofHandler(grid)\n", + " add!(dh, :u, ipu)\n", + " add!(dh, :p, ipp)\n", + " close!(dh)\n", + " return dh\n", + "end" + ], + "metadata": {}, + "execution_count": 4 + }, + { + "cell_type": "markdown", + "source": [ + "### Boundary conditions and constraints\n", + "\n", + "Now it is time to setup the `ConstraintHandler` and add our boundary conditions and the\n", + "mean value constraint. This is perhaps the most interesting section in this example, and\n", + "deserves some attention.\n", + "\n", + "Let's first discuss the assembly of the constraint matrix $\\underline{\\underline{C}}$\n", + "and how to create an `AffineConstraint` from it. This is done in the\n", + "`setup_mean_constraint` function below. Assembling this is not so different from standard\n", + "assembly in Ferrite: we loop over all the facets, loop over the quadrature points, and loop\n", + "over the shape functions. Note that since there is only one constraint the matrix will\n", + "only have one row.\n", + "After assembling `C` we construct an `AffineConstraint` from it. We select the constrained\n", + "dof to be the one with the highest weight (just to avoid selecting one with 0 or a very\n", + "small weight), then move the remaining to the right hand side. As an example, consider the\n", + "case where the constraint equation $\\underline{\\underline{C}}_p\\ \\underline{a}_p$ is\n", + "$$\n", + "w_{10} p_{10} + w_{23} p_{23} + w_{154} p_{154} = 0\n", + "$$\n", + "i.e. dofs 10, 23, and 154, are the ones located on the boundary (all other dofs naturally\n", + "gives 0 contribution). If $w_{23}$ is the largest weight, then we select $p_{23}$ to\n", + "be the constrained one, and thus reorder the constraint to the form\n", + "$$\n", + "p_{23} = -\\frac{w_{10}}{w_{23}} p_{10} -\\frac{w_{154}}{w_{23}} p_{154} + 0,\n", + "$$\n", + "which is the form the `AffineConstraint` constructor expects.\n", + "\n", + "> **Note**\n", + ">\n", + "> If all nodes along the boundary are equidistant all the weights would be the same. In\n", + "> this case we can construct the constraint without having to do any integration by\n", + "> simply finding all degrees of freedom that are located along the boundary (and using 1\n", + "> as the weight). This is what is done in the [deal.ii step-11\n", + "> example](https://www.dealii.org/current/doxygen/deal.II/step_11.html)." + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "setup_mean_constraint (generic function with 1 method)" + }, + "metadata": {}, + "execution_count": 5 + } + ], + "cell_type": "code", + "source": [ + "function setup_mean_constraint(dh, fvp)\n", + " assembler = Ferrite.COOAssembler()\n", + " # All external boundaries\n", + " set = union(\n", + " getfacetset(dh.grid, \"Γ1\"),\n", + " getfacetset(dh.grid, \"Γ2\"),\n", + " getfacetset(dh.grid, \"Γ3\"),\n", + " getfacetset(dh.grid, \"Γ4\"),\n", + " )\n", + " # Allocate buffers\n", + " range_p = dof_range(dh, :p)\n", + " element_dofs = zeros(Int, ndofs_per_cell(dh))\n", + " element_dofs_p = view(element_dofs, range_p)\n", + " element_coords = zeros(Vec{2}, 3)\n", + " Ce = zeros(1, length(range_p)) # Local constraint matrix (only 1 row)\n", + " # Loop over all the boundaries\n", + " for (ci, fi) in set\n", + " Ce .= 0\n", + " getcoordinates!(element_coords, dh.grid, ci)\n", + " reinit!(fvp, element_coords, fi)\n", + " celldofs!(element_dofs, dh, ci)\n", + " for qp in 1:getnquadpoints(fvp)\n", + " dΓ = getdetJdV(fvp, qp)\n", + " for i in 1:getnbasefunctions(fvp)\n", + " Ce[1, i] += shape_value(fvp, qp, i) * dΓ\n", + " end\n", + " end\n", + " # Assemble to row 1\n", + " assemble!(assembler, [1], element_dofs_p, Ce)\n", + " end\n", + " C, _ = finish_assemble(assembler)\n", + " # Create an AffineConstraint from the C-matrix\n", + " _, J, V = findnz(C)\n", + " _, constrained_dof_idx = findmax(abs2, V)\n", + " constrained_dof = J[constrained_dof_idx]\n", + " V ./= V[constrained_dof_idx]\n", + " mean_value_constraint = AffineConstraint(\n", + " constrained_dof,\n", + " Pair{Int, Float64}[J[i] => -V[i] for i in 1:length(J) if J[i] != constrained_dof],\n", + " 0.0,\n", + " )\n", + " return mean_value_constraint\n", + "end" + ], + "metadata": {}, + "execution_count": 5 + }, + { + "cell_type": "markdown", + "source": [ + "We now setup all the boundary conditions in the `setup_constraints` function below.\n", + "Since the periodicity constraint for this example is between two boundaries which are not\n", + "parallel to each other we need to i) compute the mapping between each mirror facet and the\n", + "corresponding image facet (on the element level) and ii) describe the dof relation between\n", + "dofs on these two facets. In Ferrite this is done by defining a transformation of entities\n", + "on the image boundary such that they line up with the matching entities on the mirror\n", + "boundary. In this example we consider the inlet $\\Gamma_1$ to be the image, and the\n", + "outlet $\\Gamma_3$ to be the mirror. The necessary transformation to apply then becomes a\n", + "rotation of $\\pi/2$ radians around the out-of-plane axis. We set up the rotation matrix\n", + "`R`, and then compute the mapping between mirror and image facets using\n", + "`collect_periodic_facets` where the rotation is applied to the coordinates. In the\n", + "next step we construct the constraint using the `PeriodicDirichlet` constructor.\n", + "We pass the constructor the computed mapping, and also the rotation matrix. This matrix is\n", + "used to rotate the dofs on the mirror surface such that we properly constrain\n", + "$\\boldsymbol{u}_x$-dofs on the mirror to $-\\boldsymbol{u}_y$-dofs on the image, and\n", + "$\\boldsymbol{u}_y$-dofs on the mirror to $\\boldsymbol{u}_x$-dofs on the image.\n", + "\n", + "For the remaining part of the boundary we add a homogeneous Dirichlet boundary condition\n", + "on both components of the velocity field. This is done using the `Dirichlet`\n", + "constructor, which we have discussed in other tutorials." + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "setup_constraints (generic function with 1 method)" + }, + "metadata": {}, + "execution_count": 6 + } + ], + "cell_type": "code", + "source": [ + "function setup_constraints(dh, fvp)\n", + " ch = ConstraintHandler(dh)\n", + " # Periodic BC\n", + " R = rotation_tensor(π / 2)\n", + " periodic_faces = collect_periodic_facets(dh.grid, \"Γ3\", \"Γ1\", x -> R ⋅ x)\n", + " periodic = PeriodicDirichlet(:u, periodic_faces, R, [1, 2])\n", + " add!(ch, periodic)\n", + " # Dirichlet BC\n", + " Γ24 = union(getfacetset(dh.grid, \"Γ2\"), getfacetset(dh.grid, \"Γ4\"))\n", + " dbc = Dirichlet(:u, Γ24, (x, t) -> [0, 0], [1, 2])\n", + " add!(ch, dbc)\n", + " # Compute mean value constraint and add it\n", + " mean_value_constraint = setup_mean_constraint(dh, fvp)\n", + " add!(ch, mean_value_constraint)\n", + " # Finalize\n", + " close!(ch)\n", + " update!(ch, 0)\n", + " return ch\n", + "end" + ], + "metadata": {}, + "execution_count": 6 + }, + { + "cell_type": "markdown", + "source": [ + "### Global and local assembly\n", + "\n", + "Assembly of the global system is also something that we have seen in many previous\n", + "tutorials. One interesting thing to note here is that, since we have two unknown fields,\n", + "we use the `dof_range` function to make sure we assemble the element contributions\n", + "to the correct block of the local stiffness matrix `ke`." + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "assemble_system! (generic function with 1 method)" + }, + "metadata": {}, + "execution_count": 7 + } + ], + "cell_type": "code", + "source": [ + "function assemble_system!(K, f, dh, cvu, cvp)\n", + " assembler = start_assemble(K, f)\n", + " ke = zeros(ndofs_per_cell(dh), ndofs_per_cell(dh))\n", + " fe = zeros(ndofs_per_cell(dh))\n", + " range_u = dof_range(dh, :u)\n", + " ndofs_u = length(range_u)\n", + " range_p = dof_range(dh, :p)\n", + " ndofs_p = length(range_p)\n", + " ϕᵤ = Vector{Vec{2, Float64}}(undef, ndofs_u)\n", + " ∇ϕᵤ = Vector{Tensor{2, 2, Float64, 4}}(undef, ndofs_u)\n", + " divϕᵤ = Vector{Float64}(undef, ndofs_u)\n", + " ϕₚ = Vector{Float64}(undef, ndofs_p)\n", + " for cell in CellIterator(dh)\n", + " reinit!(cvu, cell)\n", + " reinit!(cvp, cell)\n", + " ke .= 0\n", + " fe .= 0\n", + " for qp in 1:getnquadpoints(cvu)\n", + " dΩ = getdetJdV(cvu, qp)\n", + " for i in 1:ndofs_u\n", + " ϕᵤ[i] = shape_value(cvu, qp, i)\n", + " ∇ϕᵤ[i] = shape_gradient(cvu, qp, i)\n", + " divϕᵤ[i] = shape_divergence(cvu, qp, i)\n", + " end\n", + " for i in 1:ndofs_p\n", + " ϕₚ[i] = shape_value(cvp, qp, i)\n", + " end\n", + " # u-u\n", + " for (i, I) in pairs(range_u), (j, J) in pairs(range_u)\n", + " ke[I, J] += (∇ϕᵤ[i] ⊡ ∇ϕᵤ[j]) * dΩ\n", + " end\n", + " # u-p\n", + " for (i, I) in pairs(range_u), (j, J) in pairs(range_p)\n", + " ke[I, J] += (-divϕᵤ[i] * ϕₚ[j]) * dΩ\n", + " end\n", + " # p-u\n", + " for (i, I) in pairs(range_p), (j, J) in pairs(range_u)\n", + " ke[I, J] += (-divϕᵤ[j] * ϕₚ[i]) * dΩ\n", + " end\n", + " # rhs\n", + " for (i, I) in pairs(range_u)\n", + " x = spatial_coordinate(cvu, qp, getcoordinates(cell))\n", + " b = exp(-100 * norm(x - Vec{2}((0.75, 0.1)))^2)\n", + " bv = Vec{2}((b, 0.0))\n", + " fe[I] += (ϕᵤ[i] ⋅ bv) * dΩ\n", + " end\n", + " end\n", + " assemble!(assembler, celldofs(cell), ke, fe)\n", + " end\n", + " return K, f\n", + "end" + ], + "metadata": {}, + "execution_count": 7 + }, + { + "cell_type": "markdown", + "source": [ + "### Running the simulation\n", + "\n", + "We now have all the puzzle pieces, and just need to define the main function, which puts\n", + "them all together." + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "main (generic function with 1 method)" + }, + "metadata": {}, + "execution_count": 8 + } + ], + "cell_type": "code", + "source": [ + "function main()\n", + " # Grid\n", + " h = 0.05 # approximate element size\n", + " grid = setup_grid(h)\n", + " # Interpolations\n", + " ipu = Lagrange{RefTriangle, 2}()^2 # quadratic\n", + " ipp = Lagrange{RefTriangle, 1}() # linear\n", + " # Dofs\n", + " dh = setup_dofs(grid, ipu, ipp)\n", + " # FE values\n", + " ipg = Lagrange{RefTriangle, 1}() # linear geometric interpolation\n", + " cvu, cvp, fvp = setup_fevalues(ipu, ipp, ipg)\n", + " # Boundary conditions\n", + " ch = setup_constraints(dh, fvp)\n", + " # Global tangent matrix and rhs\n", + " coupling = [true true; true false] # no coupling between pressure test/trial functions\n", + " K = allocate_matrix(dh, ch; coupling = coupling)\n", + " f = zeros(ndofs(dh))\n", + " # Assemble system\n", + " assemble_system!(K, f, dh, cvu, cvp)\n", + " # Apply boundary conditions and solve\n", + " apply!(K, f, ch)\n", + " u = K \\ f\n", + " apply!(u, ch)\n", + " # Export the solution\n", + " VTKGridFile(\"stokes-flow\", grid) do vtk\n", + " write_solution(vtk, dh, u)\n", + " end\n", + "\n", + "\n", + " return\n", + "end" + ], + "metadata": {}, + "execution_count": 8 + }, + { + "cell_type": "markdown", + "source": [ + "Run it!" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "main()" + ], + "metadata": {}, + "execution_count": 9 + }, + { + "cell_type": "markdown", + "source": [ + "The resulting magnitude of the velocity field is visualized in *Figure 1*." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "---\n", + "\n", + "*This notebook was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).*" + ], + "metadata": {} + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.1" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.1", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR798/tutorials/stokes-flow.jl b/previews/PR798/tutorials/stokes-flow.jl new file mode 100644 index 0000000000..79cc2b65be --- /dev/null +++ b/previews/PR798/tutorials/stokes-flow.jl @@ -0,0 +1,229 @@ +using Ferrite, FerriteGmsh, Gmsh, Tensors, LinearAlgebra, SparseArrays + +function setup_grid(h = 0.05) + # Initialize gmsh + Gmsh.initialize() + gmsh.option.set_number("General.Verbosity", 2) + + # Add the points + o = gmsh.model.geo.add_point(0.0, 0.0, 0.0, h) + p1 = gmsh.model.geo.add_point(0.5, 0.0, 0.0, h) + p2 = gmsh.model.geo.add_point(1.0, 0.0, 0.0, h) + p3 = gmsh.model.geo.add_point(0.0, 1.0, 0.0, h) + p4 = gmsh.model.geo.add_point(0.0, 0.5, 0.0, h) + + # Add the lines + l1 = gmsh.model.geo.add_line(p1, p2) + l2 = gmsh.model.geo.add_circle_arc(p2, o, p3) + l3 = gmsh.model.geo.add_line(p3, p4) + l4 = gmsh.model.geo.add_circle_arc(p4, o, p1) + + # Create the closed curve loop and the surface + loop = gmsh.model.geo.add_curve_loop([l1, l2, l3, l4]) + surf = gmsh.model.geo.add_plane_surface([loop]) + + # Synchronize the model + gmsh.model.geo.synchronize() + + # Create the physical domains + gmsh.model.add_physical_group(1, [l1], -1, "Γ1") + gmsh.model.add_physical_group(1, [l2], -1, "Γ2") + gmsh.model.add_physical_group(1, [l3], -1, "Γ3") + gmsh.model.add_physical_group(1, [l4], -1, "Γ4") + gmsh.model.add_physical_group(2, [surf]) + + # Add the periodicity constraint using 4x4 affine transformation matrix, + # see https://en.wikipedia.org/wiki/Transformation_matrix#Affine_transformations + transformation_matrix = zeros(4, 4) + transformation_matrix[1, 2] = 1 # -sin(-pi/2) + transformation_matrix[2, 1] = -1 # cos(-pi/2) + transformation_matrix[3, 3] = 1 + transformation_matrix[4, 4] = 1 + transformation_matrix = vec(transformation_matrix') + gmsh.model.mesh.set_periodic(1, [l1], [l3], transformation_matrix) + + # Generate a 2D mesh + gmsh.model.mesh.generate(2) + + # Save the mesh, and read back in as a Ferrite Grid + grid = mktempdir() do dir + path = joinpath(dir, "mesh.msh") + gmsh.write(path) + togrid(path) + end + + # Finalize the Gmsh library + Gmsh.finalize() + + return grid +end + +function setup_fevalues(ipu, ipp, ipg) + qr = QuadratureRule{RefTriangle}(2) + cvu = CellValues(qr, ipu, ipg) + cvp = CellValues(qr, ipp, ipg) + qr_facet = FacetQuadratureRule{RefTriangle}(2) + fvp = FacetValues(qr_facet, ipp, ipg) + return cvu, cvp, fvp +end + +function setup_dofs(grid, ipu, ipp) + dh = DofHandler(grid) + add!(dh, :u, ipu) + add!(dh, :p, ipp) + close!(dh) + return dh +end + +function setup_mean_constraint(dh, fvp) + assembler = Ferrite.COOAssembler() + # All external boundaries + set = union( + getfacetset(dh.grid, "Γ1"), + getfacetset(dh.grid, "Γ2"), + getfacetset(dh.grid, "Γ3"), + getfacetset(dh.grid, "Γ4"), + ) + # Allocate buffers + range_p = dof_range(dh, :p) + element_dofs = zeros(Int, ndofs_per_cell(dh)) + element_dofs_p = view(element_dofs, range_p) + element_coords = zeros(Vec{2}, 3) + Ce = zeros(1, length(range_p)) # Local constraint matrix (only 1 row) + # Loop over all the boundaries + for (ci, fi) in set + Ce .= 0 + getcoordinates!(element_coords, dh.grid, ci) + reinit!(fvp, element_coords, fi) + celldofs!(element_dofs, dh, ci) + for qp in 1:getnquadpoints(fvp) + dΓ = getdetJdV(fvp, qp) + for i in 1:getnbasefunctions(fvp) + Ce[1, i] += shape_value(fvp, qp, i) * dΓ + end + end + # Assemble to row 1 + assemble!(assembler, [1], element_dofs_p, Ce) + end + C, _ = finish_assemble(assembler) + # Create an AffineConstraint from the C-matrix + _, J, V = findnz(C) + _, constrained_dof_idx = findmax(abs2, V) + constrained_dof = J[constrained_dof_idx] + V ./= V[constrained_dof_idx] + mean_value_constraint = AffineConstraint( + constrained_dof, + Pair{Int, Float64}[J[i] => -V[i] for i in 1:length(J) if J[i] != constrained_dof], + 0.0, + ) + return mean_value_constraint +end + +function setup_constraints(dh, fvp) + ch = ConstraintHandler(dh) + # Periodic BC + R = rotation_tensor(π / 2) + periodic_faces = collect_periodic_facets(dh.grid, "Γ3", "Γ1", x -> R ⋅ x) + periodic = PeriodicDirichlet(:u, periodic_faces, R, [1, 2]) + add!(ch, periodic) + # Dirichlet BC + Γ24 = union(getfacetset(dh.grid, "Γ2"), getfacetset(dh.grid, "Γ4")) + dbc = Dirichlet(:u, Γ24, (x, t) -> [0, 0], [1, 2]) + add!(ch, dbc) + # Compute mean value constraint and add it + mean_value_constraint = setup_mean_constraint(dh, fvp) + add!(ch, mean_value_constraint) + # Finalize + close!(ch) + update!(ch, 0) + return ch +end + +function assemble_system!(K, f, dh, cvu, cvp) + assembler = start_assemble(K, f) + ke = zeros(ndofs_per_cell(dh), ndofs_per_cell(dh)) + fe = zeros(ndofs_per_cell(dh)) + range_u = dof_range(dh, :u) + ndofs_u = length(range_u) + range_p = dof_range(dh, :p) + ndofs_p = length(range_p) + ϕᵤ = Vector{Vec{2, Float64}}(undef, ndofs_u) + ∇ϕᵤ = Vector{Tensor{2, 2, Float64, 4}}(undef, ndofs_u) + divϕᵤ = Vector{Float64}(undef, ndofs_u) + ϕₚ = Vector{Float64}(undef, ndofs_p) + for cell in CellIterator(dh) + reinit!(cvu, cell) + reinit!(cvp, cell) + ke .= 0 + fe .= 0 + for qp in 1:getnquadpoints(cvu) + dΩ = getdetJdV(cvu, qp) + for i in 1:ndofs_u + ϕᵤ[i] = shape_value(cvu, qp, i) + ∇ϕᵤ[i] = shape_gradient(cvu, qp, i) + divϕᵤ[i] = shape_divergence(cvu, qp, i) + end + for i in 1:ndofs_p + ϕₚ[i] = shape_value(cvp, qp, i) + end + # u-u + for (i, I) in pairs(range_u), (j, J) in pairs(range_u) + ke[I, J] += (∇ϕᵤ[i] ⊡ ∇ϕᵤ[j]) * dΩ + end + # u-p + for (i, I) in pairs(range_u), (j, J) in pairs(range_p) + ke[I, J] += (-divϕᵤ[i] * ϕₚ[j]) * dΩ + end + # p-u + for (i, I) in pairs(range_p), (j, J) in pairs(range_u) + ke[I, J] += (-divϕᵤ[j] * ϕₚ[i]) * dΩ + end + # rhs + for (i, I) in pairs(range_u) + x = spatial_coordinate(cvu, qp, getcoordinates(cell)) + b = exp(-100 * norm(x - Vec{2}((0.75, 0.1)))^2) + bv = Vec{2}((b, 0.0)) + fe[I] += (ϕᵤ[i] ⋅ bv) * dΩ + end + end + assemble!(assembler, celldofs(cell), ke, fe) + end + return K, f +end + +function main() + # Grid + h = 0.05 # approximate element size + grid = setup_grid(h) + # Interpolations + ipu = Lagrange{RefTriangle, 2}()^2 # quadratic + ipp = Lagrange{RefTriangle, 1}() # linear + # Dofs + dh = setup_dofs(grid, ipu, ipp) + # FE values + ipg = Lagrange{RefTriangle, 1}() # linear geometric interpolation + cvu, cvp, fvp = setup_fevalues(ipu, ipp, ipg) + # Boundary conditions + ch = setup_constraints(dh, fvp) + # Global tangent matrix and rhs + coupling = [true true; true false] # no coupling between pressure test/trial functions + K = allocate_matrix(dh, ch; coupling = coupling) + f = zeros(ndofs(dh)) + # Assemble system + assemble_system!(K, f, dh, cvu, cvp) + # Apply boundary conditions and solve + apply!(K, f, ch) + u = K \ f + apply!(u, ch) + # Export the solution + VTKGridFile("stokes-flow", grid) do vtk + write_solution(vtk, dh, u) + end + + + return +end + +main() + +# This file was generated using Literate.jl, https://github.com/fredrikekre/Literate.jl diff --git a/previews/PR798/tutorials/stokes-flow.png b/previews/PR798/tutorials/stokes-flow.png new file mode 100644 index 0000000000..08ae518a86 Binary files /dev/null and b/previews/PR798/tutorials/stokes-flow.png differ diff --git a/previews/PR798/tutorials/stokes-flow/index.html b/previews/PR798/tutorials/stokes-flow/index.html new file mode 100644 index 0000000000..bdd0dc7b8b --- /dev/null +++ b/previews/PR798/tutorials/stokes-flow/index.html @@ -0,0 +1,485 @@ + +Stokes flow · Ferrite.jl

Stokes flow

Keywords: periodic boundary conditions, multiple fields, mean value constraint

Tip

This example is also available as a Jupyter notebook: stokes-flow.ipynb.

Figure 1: Left: Computational domain $\Omega$ with boundaries $\Gamma_1$, $\Gamma_3$ (periodic boundary conditions) and $\Gamma_2$, $\Gamma_4$ (homogeneous Dirichlet boundary conditions). Right: Magnitude of the resulting velocity field.

Introduction and problem formulation

This example is a translation of the step-45 example from deal.ii which solves Stokes flow on a quarter circle. In particular it shows how to use periodic boundary conditions, how to solve a problem with multiple unknown fields, and how to enforce a specific mean value of the solution. For the mesh generation we use Gmsh.jl and then use FerriteGmsh.jl to import the mesh into Ferrite's format.

The strong form of Stokes flow with velocity $\boldsymbol{u}$ and pressure $p$ can be written as follows:

\[\begin{align*} +-\Delta \boldsymbol{u} + \boldsymbol{\nabla} p &= \bigl(\exp(-100||\boldsymbol{x} - (0.75, 0.1)||^2), 0\bigr) =: +\boldsymbol{b} \quad \forall \boldsymbol{x} \in \Omega,\\ +-\boldsymbol{\nabla} \cdot \boldsymbol{u} &= 0 \quad \forall \boldsymbol{x} \in \Omega, +\end{align*}\]

where the domain is defined as $\Omega = \{\boldsymbol{x} \in (0, 1)^2: \ ||\boldsymbol{x}|| \in (0.5, 1)\}$, see Figure 1. For the velocity we use periodic boundary conditions on the inlet $\Gamma_1$ and outlet $\Gamma_3$:

\[\begin{align*} +u_x(0,\nu) &= -u_y(\nu, 0) \quad & \nu\ \in\ [0.5, 1],\\ +u_y(0,\nu) &= u_x(\nu, 0) \quad & \nu\ \in\ [0.5, 1], +\end{align*}\]

and homogeneous Dirichlet boundary conditions for $\Gamma_2$ and $\Gamma_4$:

\[\boldsymbol{u} = \boldsymbol{0} \quad \forall \boldsymbol{x}\ \in\ +\Gamma_2 \cup \Gamma_4 := \{ \boldsymbol{x}:\ ||\boldsymbol{x}|| \in \{0.5, 1\}\}.\]

The corresponding weak form reads as follows: Find $(\boldsymbol{u}, p) \in \mathbb{U} \times \mathrm{L}_2$ s.t.

\[\begin{align*} +\int_\Omega \Bigl[[\delta\boldsymbol{u}\otimes\boldsymbol{\nabla}]:[\boldsymbol{u}\otimes\boldsymbol{\nabla}] - +(\boldsymbol{\nabla}\cdot\delta\boldsymbol{u})\ p\ \Bigr] \mathrm{d}\Omega &= +\int_\Omega \delta\boldsymbol{u} \cdot \boldsymbol{b}\ \mathrm{d}\Omega \quad \forall +\delta \boldsymbol{u} \in \mathbb{U},\\ +\int_\Omega - (\boldsymbol{\nabla}\cdot\boldsymbol{u})\ \delta p\ \mathrm{d}\Omega &= 0 +\quad \forall \delta p \in \mathrm{L}_2, +\end{align*}\]

where $\mathbb{U}$ is a suitable function space, that, in particular, enforces the Dirichlet boundary conditions, and the periodicity constraints. This formulation is a saddle point problem, and, just like the example with Incompressible Elasticity, we need our formulation to fulfill the LBB condition. We ensure this by using a quadratic approximation for the velocity field, and a linear approximation for the pressure.

With this formulation and boundary conditions for $\boldsymbol{u}$ the pressure will only be determined up to a constant. We will therefore add an additional constraint which fixes this constant (see deal.ii step-11 for some more discussion around this). In particular, we will enforce the mean value of the pressure on the boundary to be 0, i.e. $\int_{\Gamma} p\ \mathrm{d}\Gamma = 0$. One option is to enforce this using a Lagrange multiplier. This would give a contribution $\lambda \int_{\Gamma} \delta p\ \mathrm{d}\Gamma$ to the second equation in the weak form above, and a third equation $\delta\lambda \int_{\Gamma} p\ \mathrm{d}\Gamma = 0$ so that we can solve for $\lambda$. However, since we in this case are not interested in computing $\lambda$, and since the constraint is linear, we can directly embed this constraint using an AffineConstraint in Ferrite.

After FE discretization we obtain a linear system of the form $\underline{\underline{K}}\ \underline{a} = \underline{f}$, where

\[\underline{\underline{K}} = +\begin{bmatrix} +\underline{\underline{K}}_{uu} & \underline{\underline{K}}_{pu}^\textrm{T} \\ +\underline{\underline{K}}_{pu} & \underline{\underline{0}} +\end{bmatrix}, \quad +\underline{a} = \begin{bmatrix} +\underline{a}_{u} \\ +\underline{a}_{p} +\end{bmatrix}, \quad +\underline{f} = \begin{bmatrix} +\underline{f}_{u} \\ +\underline{0} +\end{bmatrix},\]

and where

\[\begin{align*} +(\underline{\underline{K}}_{uu})_{ij} &= \int_\Omega [\boldsymbol{\phi}^u_i\otimes\boldsymbol{\nabla}]:[\boldsymbol{\phi}^u_j\otimes\boldsymbol{\nabla}] \mathrm{d}\Omega, \\ +(\underline{\underline{K}}_{pu})_{ij} &= \int_\Omega - (\boldsymbol{\nabla}\cdot\boldsymbol{\phi}^u_j)\ \phi^p_i\ \mathrm{d}\Omega, \\ +(\underline{f}_{u})_{i} &= \int_\Omega \boldsymbol{\phi}^u_i \cdot \boldsymbol{b}\ \mathrm{d}\Omega. +\end{align*}\]

The affine constraint to enforce zero mean pressure on the boundary is obtained from $\underline{\underline{C}}_p\ \underline{a}_p = \underline{0}$, where

\[(\underline{\underline{C}}_p)_{1j} = \int_{\Gamma} \phi^p_j\ \mathrm{d}\Gamma.\]

Note

The constraint matrix $\underline{\underline{C}}_p$ is the same matrix we would have obtained when assembling the system with the Lagrange multiplier. In that case the full system would be

\[\underline{\underline{K}} = +\begin{bmatrix} +\underline{\underline{K}}_{uu} & \underline{\underline{K}}_{pu}^\textrm{T} & +\underline{\underline{0}}\\ +\underline{\underline{K}}_{pu} & \underline{\underline{0}} & \underline{\underline{C}}_p^\mathrm{T} \\ +\underline{\underline{0}} & \underline{\underline{C}}_p & 0 \\ +\end{bmatrix}, \quad +\underline{a} = \begin{bmatrix} +\underline{a}_{u} \\ +\underline{a}_{p} \\ +\underline{a}_{\lambda} +\end{bmatrix}, \quad +\underline{f} = \begin{bmatrix} +\underline{f}_{u} \\ +\underline{0} \\ +\underline{0} +\end{bmatrix}.\]

Commented program

What follows is a program spliced with comments. The full program, without comments, can be found in the next section.

using Ferrite, FerriteGmsh, Gmsh, Tensors, LinearAlgebra, SparseArrays

Geometry and mesh generation with Gmsh.jl

In the setup_grid function below we use the Gmsh.jl package for setting up the geometry and performing the meshing. We will not discuss this part in much detail but refer to the Gmsh API documentation instead. The most important thing to note is the mesh periodicity constraint that is applied between the "inlet" and "outlet" parts using gmsh.model.set_periodic. This is necessary to later on apply a periodicity constraint for the approximated velocity field.

function setup_grid(h = 0.05)
+    # Initialize gmsh
+    Gmsh.initialize()
+    gmsh.option.set_number("General.Verbosity", 2)
+
+    # Add the points
+    o = gmsh.model.geo.add_point(0.0, 0.0, 0.0, h)
+    p1 = gmsh.model.geo.add_point(0.5, 0.0, 0.0, h)
+    p2 = gmsh.model.geo.add_point(1.0, 0.0, 0.0, h)
+    p3 = gmsh.model.geo.add_point(0.0, 1.0, 0.0, h)
+    p4 = gmsh.model.geo.add_point(0.0, 0.5, 0.0, h)
+
+    # Add the lines
+    l1 = gmsh.model.geo.add_line(p1, p2)
+    l2 = gmsh.model.geo.add_circle_arc(p2, o, p3)
+    l3 = gmsh.model.geo.add_line(p3, p4)
+    l4 = gmsh.model.geo.add_circle_arc(p4, o, p1)
+
+    # Create the closed curve loop and the surface
+    loop = gmsh.model.geo.add_curve_loop([l1, l2, l3, l4])
+    surf = gmsh.model.geo.add_plane_surface([loop])
+
+    # Synchronize the model
+    gmsh.model.geo.synchronize()
+
+    # Create the physical domains
+    gmsh.model.add_physical_group(1, [l1], -1, "Γ1")
+    gmsh.model.add_physical_group(1, [l2], -1, "Γ2")
+    gmsh.model.add_physical_group(1, [l3], -1, "Γ3")
+    gmsh.model.add_physical_group(1, [l4], -1, "Γ4")
+    gmsh.model.add_physical_group(2, [surf])
+
+    # Add the periodicity constraint using 4x4 affine transformation matrix,
+    # see https://en.wikipedia.org/wiki/Transformation_matrix#Affine_transformations
+    transformation_matrix = zeros(4, 4)
+    transformation_matrix[1, 2] = 1  # -sin(-pi/2)
+    transformation_matrix[2, 1] = -1 #  cos(-pi/2)
+    transformation_matrix[3, 3] = 1
+    transformation_matrix[4, 4] = 1
+    transformation_matrix = vec(transformation_matrix')
+    gmsh.model.mesh.set_periodic(1, [l1], [l3], transformation_matrix)
+
+    # Generate a 2D mesh
+    gmsh.model.mesh.generate(2)
+
+    # Save the mesh, and read back in as a Ferrite Grid
+    grid = mktempdir() do dir
+        path = joinpath(dir, "mesh.msh")
+        gmsh.write(path)
+        togrid(path)
+    end
+
+    # Finalize the Gmsh library
+    Gmsh.finalize()
+
+    return grid
+end

Degrees of freedom

As mentioned in the introduction we will use a quadratic approximation for the velocity field and a linear approximation for the pressure to ensure that we fulfill the LBB condition. We create the corresponding FE values with interpolations ipu for the velocity and ipp for the pressure. Note that we specify linear geometric mapping (ipg) for both the velocity and pressure because our grid contains linear triangles. However, since linear mapping is default this could have been skipped. We also construct facet-values for the pressure since we need to integrate along the boundary when assembling the constraint matrix $\underline{\underline{C}}$.

function setup_fevalues(ipu, ipp, ipg)
+    qr = QuadratureRule{RefTriangle}(2)
+    cvu = CellValues(qr, ipu, ipg)
+    cvp = CellValues(qr, ipp, ipg)
+    qr_facet = FacetQuadratureRule{RefTriangle}(2)
+    fvp = FacetValues(qr_facet, ipp, ipg)
+    return cvu, cvp, fvp
+end

The setup_dofs function creates the DofHandler, and adds the two fields: a vector field :u with interpolation ipu, and a scalar field :p with interpolation ipp.

function setup_dofs(grid, ipu, ipp)
+    dh = DofHandler(grid)
+    add!(dh, :u, ipu)
+    add!(dh, :p, ipp)
+    close!(dh)
+    return dh
+end

Boundary conditions and constraints

Now it is time to setup the ConstraintHandler and add our boundary conditions and the mean value constraint. This is perhaps the most interesting section in this example, and deserves some attention.

Let's first discuss the assembly of the constraint matrix $\underline{\underline{C}}$ and how to create an AffineConstraint from it. This is done in the setup_mean_constraint function below. Assembling this is not so different from standard assembly in Ferrite: we loop over all the facets, loop over the quadrature points, and loop over the shape functions. Note that since there is only one constraint the matrix will only have one row. After assembling C we construct an AffineConstraint from it. We select the constrained dof to be the one with the highest weight (just to avoid selecting one with 0 or a very small weight), then move the remaining to the right hand side. As an example, consider the case where the constraint equation $\underline{\underline{C}}_p\ \underline{a}_p$ is

\[w_{10} p_{10} + w_{23} p_{23} + w_{154} p_{154} = 0\]

i.e. dofs 10, 23, and 154, are the ones located on the boundary (all other dofs naturally gives 0 contribution). If $w_{23}$ is the largest weight, then we select $p_{23}$ to be the constrained one, and thus reorder the constraint to the form

\[p_{23} = -\frac{w_{10}}{w_{23}} p_{10} -\frac{w_{154}}{w_{23}} p_{154} + 0,\]

which is the form the AffineConstraint constructor expects.

Note

If all nodes along the boundary are equidistant all the weights would be the same. In this case we can construct the constraint without having to do any integration by simply finding all degrees of freedom that are located along the boundary (and using 1 as the weight). This is what is done in the deal.ii step-11 example.

function setup_mean_constraint(dh, fvp)
+    assembler = Ferrite.COOAssembler()
+    # All external boundaries
+    set = union(
+        getfacetset(dh.grid, "Γ1"),
+        getfacetset(dh.grid, "Γ2"),
+        getfacetset(dh.grid, "Γ3"),
+        getfacetset(dh.grid, "Γ4"),
+    )
+    # Allocate buffers
+    range_p = dof_range(dh, :p)
+    element_dofs = zeros(Int, ndofs_per_cell(dh))
+    element_dofs_p = view(element_dofs, range_p)
+    element_coords = zeros(Vec{2}, 3)
+    Ce = zeros(1, length(range_p)) # Local constraint matrix (only 1 row)
+    # Loop over all the boundaries
+    for (ci, fi) in set
+        Ce .= 0
+        getcoordinates!(element_coords, dh.grid, ci)
+        reinit!(fvp, element_coords, fi)
+        celldofs!(element_dofs, dh, ci)
+        for qp in 1:getnquadpoints(fvp)
+            dΓ = getdetJdV(fvp, qp)
+            for i in 1:getnbasefunctions(fvp)
+                Ce[1, i] += shape_value(fvp, qp, i) * dΓ
+            end
+        end
+        # Assemble to row 1
+        assemble!(assembler, [1], element_dofs_p, Ce)
+    end
+    C, _ = finish_assemble(assembler)
+    # Create an AffineConstraint from the C-matrix
+    _, J, V = findnz(C)
+    _, constrained_dof_idx = findmax(abs2, V)
+    constrained_dof = J[constrained_dof_idx]
+    V ./= V[constrained_dof_idx]
+    mean_value_constraint = AffineConstraint(
+        constrained_dof,
+        Pair{Int, Float64}[J[i] => -V[i] for i in 1:length(J) if J[i] != constrained_dof],
+        0.0,
+    )
+    return mean_value_constraint
+end

We now setup all the boundary conditions in the setup_constraints function below. Since the periodicity constraint for this example is between two boundaries which are not parallel to each other we need to i) compute the mapping between each mirror facet and the corresponding image facet (on the element level) and ii) describe the dof relation between dofs on these two facets. In Ferrite this is done by defining a transformation of entities on the image boundary such that they line up with the matching entities on the mirror boundary. In this example we consider the inlet $\Gamma_1$ to be the image, and the outlet $\Gamma_3$ to be the mirror. The necessary transformation to apply then becomes a rotation of $\pi/2$ radians around the out-of-plane axis. We set up the rotation matrix R, and then compute the mapping between mirror and image facets using collect_periodic_facets where the rotation is applied to the coordinates. In the next step we construct the constraint using the PeriodicDirichlet constructor. We pass the constructor the computed mapping, and also the rotation matrix. This matrix is used to rotate the dofs on the mirror surface such that we properly constrain $\boldsymbol{u}_x$-dofs on the mirror to $-\boldsymbol{u}_y$-dofs on the image, and $\boldsymbol{u}_y$-dofs on the mirror to $\boldsymbol{u}_x$-dofs on the image.

For the remaining part of the boundary we add a homogeneous Dirichlet boundary condition on both components of the velocity field. This is done using the Dirichlet constructor, which we have discussed in other tutorials.

function setup_constraints(dh, fvp)
+    ch = ConstraintHandler(dh)
+    # Periodic BC
+    R = rotation_tensor(π / 2)
+    periodic_faces = collect_periodic_facets(dh.grid, "Γ3", "Γ1", x -> R ⋅ x)
+    periodic = PeriodicDirichlet(:u, periodic_faces, R, [1, 2])
+    add!(ch, periodic)
+    # Dirichlet BC
+    Γ24 = union(getfacetset(dh.grid, "Γ2"), getfacetset(dh.grid, "Γ4"))
+    dbc = Dirichlet(:u, Γ24, (x, t) -> [0, 0], [1, 2])
+    add!(ch, dbc)
+    # Compute mean value constraint and add it
+    mean_value_constraint = setup_mean_constraint(dh, fvp)
+    add!(ch, mean_value_constraint)
+    # Finalize
+    close!(ch)
+    update!(ch, 0)
+    return ch
+end

Global and local assembly

Assembly of the global system is also something that we have seen in many previous tutorials. One interesting thing to note here is that, since we have two unknown fields, we use the dof_range function to make sure we assemble the element contributions to the correct block of the local stiffness matrix ke.

function assemble_system!(K, f, dh, cvu, cvp)
+    assembler = start_assemble(K, f)
+    ke = zeros(ndofs_per_cell(dh), ndofs_per_cell(dh))
+    fe = zeros(ndofs_per_cell(dh))
+    range_u = dof_range(dh, :u)
+    ndofs_u = length(range_u)
+    range_p = dof_range(dh, :p)
+    ndofs_p = length(range_p)
+    ϕᵤ = Vector{Vec{2, Float64}}(undef, ndofs_u)
+    ∇ϕᵤ = Vector{Tensor{2, 2, Float64, 4}}(undef, ndofs_u)
+    divϕᵤ = Vector{Float64}(undef, ndofs_u)
+    ϕₚ = Vector{Float64}(undef, ndofs_p)
+    for cell in CellIterator(dh)
+        reinit!(cvu, cell)
+        reinit!(cvp, cell)
+        ke .= 0
+        fe .= 0
+        for qp in 1:getnquadpoints(cvu)
+            dΩ = getdetJdV(cvu, qp)
+            for i in 1:ndofs_u
+                ϕᵤ[i] = shape_value(cvu, qp, i)
+                ∇ϕᵤ[i] = shape_gradient(cvu, qp, i)
+                divϕᵤ[i] = shape_divergence(cvu, qp, i)
+            end
+            for i in 1:ndofs_p
+                ϕₚ[i] = shape_value(cvp, qp, i)
+            end
+            # u-u
+            for (i, I) in pairs(range_u), (j, J) in pairs(range_u)
+                ke[I, J] += (∇ϕᵤ[i] ⊡ ∇ϕᵤ[j]) * dΩ
+            end
+            # u-p
+            for (i, I) in pairs(range_u), (j, J) in pairs(range_p)
+                ke[I, J] += (-divϕᵤ[i] * ϕₚ[j]) * dΩ
+            end
+            # p-u
+            for (i, I) in pairs(range_p), (j, J) in pairs(range_u)
+                ke[I, J] += (-divϕᵤ[j] * ϕₚ[i]) * dΩ
+            end
+            # rhs
+            for (i, I) in pairs(range_u)
+                x = spatial_coordinate(cvu, qp, getcoordinates(cell))
+                b = exp(-100 * norm(x - Vec{2}((0.75, 0.1)))^2)
+                bv = Vec{2}((b, 0.0))
+                fe[I] += (ϕᵤ[i] ⋅ bv) * dΩ
+            end
+        end
+        assemble!(assembler, celldofs(cell), ke, fe)
+    end
+    return K, f
+end

Running the simulation

We now have all the puzzle pieces, and just need to define the main function, which puts them all together.

function main()
+    # Grid
+    h = 0.05 # approximate element size
+    grid = setup_grid(h)
+    # Interpolations
+    ipu = Lagrange{RefTriangle, 2}()^2 # quadratic
+    ipp = Lagrange{RefTriangle, 1}()   # linear
+    # Dofs
+    dh = setup_dofs(grid, ipu, ipp)
+    # FE values
+    ipg = Lagrange{RefTriangle, 1}() # linear geometric interpolation
+    cvu, cvp, fvp = setup_fevalues(ipu, ipp, ipg)
+    # Boundary conditions
+    ch = setup_constraints(dh, fvp)
+    # Global tangent matrix and rhs
+    coupling = [true true; true false] # no coupling between pressure test/trial functions
+    K = allocate_matrix(dh, ch; coupling = coupling)
+    f = zeros(ndofs(dh))
+    # Assemble system
+    assemble_system!(K, f, dh, cvu, cvp)
+    # Apply boundary conditions and solve
+    apply!(K, f, ch)
+    u = K \ f
+    apply!(u, ch)
+    # Export the solution
+    VTKGridFile("stokes-flow", grid) do vtk
+        write_solution(vtk, dh, u)
+    end
+
+
+    return
+end

Run it!

main()

The resulting magnitude of the velocity field is visualized in Figure 1.

Plain program

Here follows a version of the program without any comments. The file is also available here: stokes-flow.jl.

using Ferrite, FerriteGmsh, Gmsh, Tensors, LinearAlgebra, SparseArrays
+
+function setup_grid(h = 0.05)
+    # Initialize gmsh
+    Gmsh.initialize()
+    gmsh.option.set_number("General.Verbosity", 2)
+
+    # Add the points
+    o = gmsh.model.geo.add_point(0.0, 0.0, 0.0, h)
+    p1 = gmsh.model.geo.add_point(0.5, 0.0, 0.0, h)
+    p2 = gmsh.model.geo.add_point(1.0, 0.0, 0.0, h)
+    p3 = gmsh.model.geo.add_point(0.0, 1.0, 0.0, h)
+    p4 = gmsh.model.geo.add_point(0.0, 0.5, 0.0, h)
+
+    # Add the lines
+    l1 = gmsh.model.geo.add_line(p1, p2)
+    l2 = gmsh.model.geo.add_circle_arc(p2, o, p3)
+    l3 = gmsh.model.geo.add_line(p3, p4)
+    l4 = gmsh.model.geo.add_circle_arc(p4, o, p1)
+
+    # Create the closed curve loop and the surface
+    loop = gmsh.model.geo.add_curve_loop([l1, l2, l3, l4])
+    surf = gmsh.model.geo.add_plane_surface([loop])
+
+    # Synchronize the model
+    gmsh.model.geo.synchronize()
+
+    # Create the physical domains
+    gmsh.model.add_physical_group(1, [l1], -1, "Γ1")
+    gmsh.model.add_physical_group(1, [l2], -1, "Γ2")
+    gmsh.model.add_physical_group(1, [l3], -1, "Γ3")
+    gmsh.model.add_physical_group(1, [l4], -1, "Γ4")
+    gmsh.model.add_physical_group(2, [surf])
+
+    # Add the periodicity constraint using 4x4 affine transformation matrix,
+    # see https://en.wikipedia.org/wiki/Transformation_matrix#Affine_transformations
+    transformation_matrix = zeros(4, 4)
+    transformation_matrix[1, 2] = 1  # -sin(-pi/2)
+    transformation_matrix[2, 1] = -1 #  cos(-pi/2)
+    transformation_matrix[3, 3] = 1
+    transformation_matrix[4, 4] = 1
+    transformation_matrix = vec(transformation_matrix')
+    gmsh.model.mesh.set_periodic(1, [l1], [l3], transformation_matrix)
+
+    # Generate a 2D mesh
+    gmsh.model.mesh.generate(2)
+
+    # Save the mesh, and read back in as a Ferrite Grid
+    grid = mktempdir() do dir
+        path = joinpath(dir, "mesh.msh")
+        gmsh.write(path)
+        togrid(path)
+    end
+
+    # Finalize the Gmsh library
+    Gmsh.finalize()
+
+    return grid
+end
+
+function setup_fevalues(ipu, ipp, ipg)
+    qr = QuadratureRule{RefTriangle}(2)
+    cvu = CellValues(qr, ipu, ipg)
+    cvp = CellValues(qr, ipp, ipg)
+    qr_facet = FacetQuadratureRule{RefTriangle}(2)
+    fvp = FacetValues(qr_facet, ipp, ipg)
+    return cvu, cvp, fvp
+end
+
+function setup_dofs(grid, ipu, ipp)
+    dh = DofHandler(grid)
+    add!(dh, :u, ipu)
+    add!(dh, :p, ipp)
+    close!(dh)
+    return dh
+end
+
+function setup_mean_constraint(dh, fvp)
+    assembler = Ferrite.COOAssembler()
+    # All external boundaries
+    set = union(
+        getfacetset(dh.grid, "Γ1"),
+        getfacetset(dh.grid, "Γ2"),
+        getfacetset(dh.grid, "Γ3"),
+        getfacetset(dh.grid, "Γ4"),
+    )
+    # Allocate buffers
+    range_p = dof_range(dh, :p)
+    element_dofs = zeros(Int, ndofs_per_cell(dh))
+    element_dofs_p = view(element_dofs, range_p)
+    element_coords = zeros(Vec{2}, 3)
+    Ce = zeros(1, length(range_p)) # Local constraint matrix (only 1 row)
+    # Loop over all the boundaries
+    for (ci, fi) in set
+        Ce .= 0
+        getcoordinates!(element_coords, dh.grid, ci)
+        reinit!(fvp, element_coords, fi)
+        celldofs!(element_dofs, dh, ci)
+        for qp in 1:getnquadpoints(fvp)
+            dΓ = getdetJdV(fvp, qp)
+            for i in 1:getnbasefunctions(fvp)
+                Ce[1, i] += shape_value(fvp, qp, i) * dΓ
+            end
+        end
+        # Assemble to row 1
+        assemble!(assembler, [1], element_dofs_p, Ce)
+    end
+    C, _ = finish_assemble(assembler)
+    # Create an AffineConstraint from the C-matrix
+    _, J, V = findnz(C)
+    _, constrained_dof_idx = findmax(abs2, V)
+    constrained_dof = J[constrained_dof_idx]
+    V ./= V[constrained_dof_idx]
+    mean_value_constraint = AffineConstraint(
+        constrained_dof,
+        Pair{Int, Float64}[J[i] => -V[i] for i in 1:length(J) if J[i] != constrained_dof],
+        0.0,
+    )
+    return mean_value_constraint
+end
+
+function setup_constraints(dh, fvp)
+    ch = ConstraintHandler(dh)
+    # Periodic BC
+    R = rotation_tensor(π / 2)
+    periodic_faces = collect_periodic_facets(dh.grid, "Γ3", "Γ1", x -> R ⋅ x)
+    periodic = PeriodicDirichlet(:u, periodic_faces, R, [1, 2])
+    add!(ch, periodic)
+    # Dirichlet BC
+    Γ24 = union(getfacetset(dh.grid, "Γ2"), getfacetset(dh.grid, "Γ4"))
+    dbc = Dirichlet(:u, Γ24, (x, t) -> [0, 0], [1, 2])
+    add!(ch, dbc)
+    # Compute mean value constraint and add it
+    mean_value_constraint = setup_mean_constraint(dh, fvp)
+    add!(ch, mean_value_constraint)
+    # Finalize
+    close!(ch)
+    update!(ch, 0)
+    return ch
+end
+
+function assemble_system!(K, f, dh, cvu, cvp)
+    assembler = start_assemble(K, f)
+    ke = zeros(ndofs_per_cell(dh), ndofs_per_cell(dh))
+    fe = zeros(ndofs_per_cell(dh))
+    range_u = dof_range(dh, :u)
+    ndofs_u = length(range_u)
+    range_p = dof_range(dh, :p)
+    ndofs_p = length(range_p)
+    ϕᵤ = Vector{Vec{2, Float64}}(undef, ndofs_u)
+    ∇ϕᵤ = Vector{Tensor{2, 2, Float64, 4}}(undef, ndofs_u)
+    divϕᵤ = Vector{Float64}(undef, ndofs_u)
+    ϕₚ = Vector{Float64}(undef, ndofs_p)
+    for cell in CellIterator(dh)
+        reinit!(cvu, cell)
+        reinit!(cvp, cell)
+        ke .= 0
+        fe .= 0
+        for qp in 1:getnquadpoints(cvu)
+            dΩ = getdetJdV(cvu, qp)
+            for i in 1:ndofs_u
+                ϕᵤ[i] = shape_value(cvu, qp, i)
+                ∇ϕᵤ[i] = shape_gradient(cvu, qp, i)
+                divϕᵤ[i] = shape_divergence(cvu, qp, i)
+            end
+            for i in 1:ndofs_p
+                ϕₚ[i] = shape_value(cvp, qp, i)
+            end
+            # u-u
+            for (i, I) in pairs(range_u), (j, J) in pairs(range_u)
+                ke[I, J] += (∇ϕᵤ[i] ⊡ ∇ϕᵤ[j]) * dΩ
+            end
+            # u-p
+            for (i, I) in pairs(range_u), (j, J) in pairs(range_p)
+                ke[I, J] += (-divϕᵤ[i] * ϕₚ[j]) * dΩ
+            end
+            # p-u
+            for (i, I) in pairs(range_p), (j, J) in pairs(range_u)
+                ke[I, J] += (-divϕᵤ[j] * ϕₚ[i]) * dΩ
+            end
+            # rhs
+            for (i, I) in pairs(range_u)
+                x = spatial_coordinate(cvu, qp, getcoordinates(cell))
+                b = exp(-100 * norm(x - Vec{2}((0.75, 0.1)))^2)
+                bv = Vec{2}((b, 0.0))
+                fe[I] += (ϕᵤ[i] ⋅ bv) * dΩ
+            end
+        end
+        assemble!(assembler, celldofs(cell), ke, fe)
+    end
+    return K, f
+end
+
+function main()
+    # Grid
+    h = 0.05 # approximate element size
+    grid = setup_grid(h)
+    # Interpolations
+    ipu = Lagrange{RefTriangle, 2}()^2 # quadratic
+    ipp = Lagrange{RefTriangle, 1}()   # linear
+    # Dofs
+    dh = setup_dofs(grid, ipu, ipp)
+    # FE values
+    ipg = Lagrange{RefTriangle, 1}() # linear geometric interpolation
+    cvu, cvp, fvp = setup_fevalues(ipu, ipp, ipg)
+    # Boundary conditions
+    ch = setup_constraints(dh, fvp)
+    # Global tangent matrix and rhs
+    coupling = [true true; true false] # no coupling between pressure test/trial functions
+    K = allocate_matrix(dh, ch; coupling = coupling)
+    f = zeros(ndofs(dh))
+    # Assemble system
+    assemble_system!(K, f, dh, cvu, cvp)
+    # Apply boundary conditions and solve
+    apply!(K, f, ch)
+    u = K \ f
+    apply!(u, ch)
+    # Export the solution
+    VTKGridFile("stokes-flow", grid) do vtk
+        write_solution(vtk, dh, u)
+    end
+
+
+    return
+end
+
+main()

This page was generated using Literate.jl.

diff --git a/previews/PR798/tutorials/transient-heat.pvd b/previews/PR798/tutorials/transient-heat.pvd new file mode 100755 index 0000000000..d76b90596b --- /dev/null +++ b/previews/PR798/tutorials/transient-heat.pvd @@ -0,0 +1,206 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/previews/PR798/tutorials/transient_heat.gif b/previews/PR798/tutorials/transient_heat.gif new file mode 100644 index 0000000000..bc6a9ffa16 Binary files /dev/null and b/previews/PR798/tutorials/transient_heat.gif differ diff --git a/previews/PR798/tutorials/transient_heat_colorbar.svg b/previews/PR798/tutorials/transient_heat_colorbar.svg new file mode 100644 index 0000000000..d9cec37790 --- /dev/null +++ b/previews/PR798/tutorials/transient_heat_colorbar.svg @@ -0,0 +1,184 @@ + + + + + + + + + + + + + + + + + + + 100 + 80 + 60 + 40 + 20 + 0 + + diff --git a/previews/PR798/tutorials/transient_heat_equation.ipynb b/previews/PR798/tutorials/transient_heat_equation.ipynb new file mode 100644 index 0000000000..e2aac2d6ff --- /dev/null +++ b/previews/PR798/tutorials/transient_heat_equation.ipynb @@ -0,0 +1,520 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Transient heat equation\n", + "\n", + "![](transient_heat.gif)\n", + "![](transient_heat_colorbar.svg)\n", + "\n", + "*Figure 1*: Visualization of the temperature time evolution on a unit\n", + "square where the prescribed temperature on the upper and lower parts\n", + "of the boundary increase with time." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Introduction\n", + "\n", + "In this example we extend the heat equation by a time dependent term, i.e.\n", + "$$\n", + " \\frac{\\partial u}{\\partial t}-\\nabla \\cdot (k \\nabla u) = f \\quad x \\in \\Omega,\n", + "$$\n", + "\n", + "where $u$ is the unknown temperature field, $k$ the heat conductivity,\n", + "$f$ the heat source and $\\Omega$ the domain. For simplicity, we hard code $f = 0.1$\n", + "and $k = 10^{-3}$. We define homogeneous Dirichlet boundary conditions along the left and right edge of the domain.\n", + "$$\n", + "u(x,t) = 0 \\quad x \\in \\partial \\Omega_1,\n", + "$$\n", + "where $\\partial \\Omega_1$ denotes the left and right boundary of $\\Omega$.\n", + "\n", + "Further, we define heterogeneous Dirichlet boundary conditions at the top and bottom edge $\\partial \\Omega_2$.\n", + "We choose a linearly increasing function $a(t)$ that describes the temperature at this boundary\n", + "$$\n", + "u(x,t) = a(t) \\quad x \\in \\partial \\Omega_2.\n", + "$$\n", + "The semidiscrete weak form is given by\n", + "$$\n", + "\\int_{\\Omega}v \\frac{\\partial u}{\\partial t} \\ \\mathrm{d}\\Omega + \\int_{\\Omega} k \\nabla v \\cdot \\nabla u \\ \\mathrm{d}\\Omega = \\int_{\\Omega} f v \\ \\mathrm{d}\\Omega,\n", + "$$\n", + "where $v$ is a suitable test function. Now, we still need to discretize the time derivative. An implicit Euler scheme is applied,\n", + "which yields:\n", + "$$\n", + "\\int_{\\Omega} v\\, u_{n+1}\\ \\mathrm{d}\\Omega + \\Delta t\\int_{\\Omega} k \\nabla v \\cdot \\nabla u_{n+1} \\ \\mathrm{d}\\Omega = \\Delta t\\int_{\\Omega} f v \\ \\mathrm{d}\\Omega + \\int_{\\Omega} v \\, u_{n} \\ \\mathrm{d}\\Omega.\n", + "$$\n", + "If we assemble the discrete operators, we get the following algebraic system:\n", + "$$\n", + "\\mathbf{M} \\mathbf{u}_{n+1} + Δt \\mathbf{K} \\mathbf{u}_{n+1} = Δt \\mathbf{f} + \\mathbf{M} \\mathbf{u}_{n}\n", + "$$\n", + "In this example we apply the boundary conditions to the assembled discrete operators (mass matrix $\\mathbf{M}$ and stiffnes matrix $\\mathbf{K}$)\n", + "only once. We utilize the fact that in finite element computations Dirichlet conditions can be applied by\n", + "zero out rows and columns that correspond\n", + "to a prescribed dof in the system matrix ($\\mathbf{A} = Δt \\mathbf{K} + \\mathbf{M}$) and setting the value of the right-hand side vector to the value\n", + "of the Dirichlet condition. Thus, we only need to apply in every time step the Dirichlet condition to the right-hand side of the problem." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Commented Program\n", + "\n", + "Now we solve the problem in Ferrite. What follows is a program spliced with comments.\n", + "\n", + "First we load Ferrite, and some other packages we need." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using Ferrite, SparseArrays, WriteVTK" + ], + "metadata": {}, + "execution_count": 1 + }, + { + "cell_type": "markdown", + "source": [ + "We create the same grid as in the heat equation example." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "grid = generate_grid(Quadrilateral, (100, 100));" + ], + "metadata": {}, + "execution_count": 2 + }, + { + "cell_type": "markdown", + "source": [ + "### Trial and test functions\n", + "Again, we define the structs that are responsible for the `shape_value` and `shape_gradient` evaluation." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "ip = Lagrange{RefQuadrilateral, 1}()\n", + "qr = QuadratureRule{RefQuadrilateral}(2)\n", + "cellvalues = CellValues(qr, ip);" + ], + "metadata": {}, + "execution_count": 3 + }, + { + "cell_type": "markdown", + "source": [ + "### Degrees of freedom\n", + "After this, we can define the `DofHandler` and distribute the DOFs of the problem." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "dh = DofHandler(grid)\n", + "add!(dh, :u, ip)\n", + "close!(dh);" + ], + "metadata": {}, + "execution_count": 4 + }, + { + "cell_type": "markdown", + "source": [ + "By means of the `DofHandler` we can allocate the needed `SparseMatrixCSC`.\n", + "`M` refers here to the so called mass matrix, which always occurs in time related terms, i.e.\n", + "$$\n", + "M_{ij} = \\int_{\\Omega} v_i \\, u_j \\ \\mathrm{d}\\Omega,\n", + "$$\n", + "where $u_i$ and $v_j$ are trial and test functions, respectively." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "K = allocate_matrix(dh);\n", + "M = allocate_matrix(dh);" + ], + "metadata": {}, + "execution_count": 5 + }, + { + "cell_type": "markdown", + "source": [ + "We also preallocate the right hand side" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "f = zeros(ndofs(dh));" + ], + "metadata": {}, + "execution_count": 6 + }, + { + "cell_type": "markdown", + "source": [ + "### Boundary conditions\n", + "In order to define the time dependent problem, we need some end time `T` and something that describes\n", + "the linearly increasing Dirichlet boundary condition on $\\partial \\Omega_2$." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "max_temp = 100\n", + "Δt = 1\n", + "T = 200\n", + "t_rise = 100\n", + "ch = ConstraintHandler(dh);" + ], + "metadata": {}, + "execution_count": 7 + }, + { + "cell_type": "markdown", + "source": [ + "Here, we define the boundary condition related to $\\partial \\Omega_1$." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "∂Ω₁ = union(getfacetset.((grid,), [\"left\", \"right\"])...)\n", + "dbc = Dirichlet(:u, ∂Ω₁, (x, t) -> 0)\n", + "add!(ch, dbc);" + ], + "metadata": {}, + "execution_count": 8 + }, + { + "cell_type": "markdown", + "source": [ + "While the next code block corresponds to the linearly increasing temperature description on $\\partial \\Omega_2$\n", + "until `t=t_rise`, and then keep constant" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "∂Ω₂ = union(getfacetset.((grid,), [\"top\", \"bottom\"])...)\n", + "dbc = Dirichlet(:u, ∂Ω₂, (x, t) -> max_temp * clamp(t / t_rise, 0, 1))\n", + "add!(ch, dbc)\n", + "close!(ch)\n", + "update!(ch, 0.0);" + ], + "metadata": {}, + "execution_count": 9 + }, + { + "cell_type": "markdown", + "source": [ + "### Assembling the linear system\n", + "As in the heat equation example we define a `doassemble!` function that assembles the diffusion parts of the equation:" + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "doassemble_K! (generic function with 1 method)" + }, + "metadata": {}, + "execution_count": 10 + } + ], + "cell_type": "code", + "source": [ + "function doassemble_K!(K::SparseMatrixCSC, f::Vector, cellvalues::CellValues, dh::DofHandler)\n", + "\n", + " n_basefuncs = getnbasefunctions(cellvalues)\n", + " Ke = zeros(n_basefuncs, n_basefuncs)\n", + " fe = zeros(n_basefuncs)\n", + "\n", + " assembler = start_assemble(K, f)\n", + "\n", + " for cell in CellIterator(dh)\n", + "\n", + " fill!(Ke, 0)\n", + " fill!(fe, 0)\n", + "\n", + " reinit!(cellvalues, cell)\n", + "\n", + " for q_point in 1:getnquadpoints(cellvalues)\n", + " dΩ = getdetJdV(cellvalues, q_point)\n", + "\n", + " for i in 1:n_basefuncs\n", + " v = shape_value(cellvalues, q_point, i)\n", + " ∇v = shape_gradient(cellvalues, q_point, i)\n", + " fe[i] += 0.1 * v * dΩ\n", + " for j in 1:n_basefuncs\n", + " ∇u = shape_gradient(cellvalues, q_point, j)\n", + " Ke[i, j] += 1.0e-3 * (∇v ⋅ ∇u) * dΩ\n", + " end\n", + " end\n", + " end\n", + "\n", + " assemble!(assembler, celldofs(cell), Ke, fe)\n", + " end\n", + " return K, f\n", + "end" + ], + "metadata": {}, + "execution_count": 10 + }, + { + "cell_type": "markdown", + "source": [ + "In addition to the diffusive part, we also need a function that assembles the mass matrix `M`." + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "doassemble_M! (generic function with 1 method)" + }, + "metadata": {}, + "execution_count": 11 + } + ], + "cell_type": "code", + "source": [ + "function doassemble_M!(M::SparseMatrixCSC, cellvalues::CellValues, dh::DofHandler)\n", + "\n", + " n_basefuncs = getnbasefunctions(cellvalues)\n", + " Me = zeros(n_basefuncs, n_basefuncs)\n", + "\n", + " assembler = start_assemble(M)\n", + "\n", + " for cell in CellIterator(dh)\n", + "\n", + " fill!(Me, 0)\n", + "\n", + " reinit!(cellvalues, cell)\n", + "\n", + " for q_point in 1:getnquadpoints(cellvalues)\n", + " dΩ = getdetJdV(cellvalues, q_point)\n", + "\n", + " for i in 1:n_basefuncs\n", + " v = shape_value(cellvalues, q_point, i)\n", + " for j in 1:n_basefuncs\n", + " u = shape_value(cellvalues, q_point, j)\n", + " Me[i, j] += (v * u) * dΩ\n", + " end\n", + " end\n", + " end\n", + "\n", + " assemble!(assembler, celldofs(cell), Me)\n", + " end\n", + " return M\n", + "end" + ], + "metadata": {}, + "execution_count": 11 + }, + { + "cell_type": "markdown", + "source": [ + "### Solution of the system\n", + "We first assemble all parts in the prior allocated `SparseMatrixCSC`." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "K, f = doassemble_K!(K, f, cellvalues, dh)\n", + "M = doassemble_M!(M, cellvalues, dh)\n", + "A = (Δt .* K) + M;" + ], + "metadata": {}, + "execution_count": 12 + }, + { + "cell_type": "markdown", + "source": [ + "Now, we need to save all boundary condition related values of the unaltered system matrix `A`, which is done\n", + "by `get_rhs_data`. The function returns a `RHSData` struct, which contains all needed information to apply\n", + "the boundary conditions solely on the right-hand-side vector of the problem." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "rhsdata = get_rhs_data(ch, A);" + ], + "metadata": {}, + "execution_count": 13 + }, + { + "cell_type": "markdown", + "source": [ + "We set the values at initial time step, denoted by uₙ, to a bubble-shape described by\n", + "$(x_1^2-1)(x_2^2-1)$, such that it is zero at the boundaries and the maximum temperature in the center." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "uₙ = zeros(length(f));\n", + "apply_analytical!(uₙ, dh, :u, x -> (x[1]^2 - 1) * (x[2]^2 - 1) * max_temp);" + ], + "metadata": {}, + "execution_count": 14 + }, + { + "cell_type": "markdown", + "source": [ + "Here, we apply **once** the boundary conditions to the system matrix `A`." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "apply!(A, ch);" + ], + "metadata": {}, + "execution_count": 15 + }, + { + "cell_type": "markdown", + "source": [ + "To store the solution, we initialize a paraview collection (.pvd) file," + ], + "metadata": {} + }, + { + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "VTKGridFile for the closed file \"transient-heat-0.vtu\"." + }, + "metadata": {}, + "execution_count": 16 + } + ], + "cell_type": "code", + "source": [ + "pvd = paraview_collection(\"transient-heat\")\n", + "VTKGridFile(\"transient-heat-0\", dh) do vtk\n", + " write_solution(vtk, dh, uₙ)\n", + " pvd[0.0] = vtk\n", + "end" + ], + "metadata": {}, + "execution_count": 16 + }, + { + "cell_type": "markdown", + "source": [ + "At this point everything is set up and we can finally approach the time loop." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "for (step, t) in enumerate(Δt:Δt:T)\n", + " #First of all, we need to update the Dirichlet boundary condition values.\n", + " update!(ch, t)\n", + "\n", + " #Secondly, we compute the right-hand-side of the problem.\n", + " b = Δt .* f .+ M * uₙ\n", + " #Then, we can apply the boundary conditions of the current time step.\n", + " apply_rhs!(rhsdata, b, ch)\n", + "\n", + " #Finally, we can solve the time step and save the solution afterwards.\n", + " u = A \\ b\n", + "\n", + " VTKGridFile(\"transient-heat-$step\", dh) do vtk\n", + " write_solution(vtk, dh, u)\n", + " pvd[t] = vtk\n", + " end\n", + " #At the end of the time loop, we set the previous solution to the current one and go to the next time step.\n", + " uₙ .= u\n", + "end" + ], + "metadata": {}, + "execution_count": 17 + }, + { + "cell_type": "markdown", + "source": [ + "In order to use the .pvd file we need to store it to the disk, which is done by:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "vtk_save(pvd);" + ], + "metadata": {}, + "execution_count": 18 + }, + { + "cell_type": "markdown", + "source": [ + "---\n", + "\n", + "*This notebook was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).*" + ], + "metadata": {} + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.1" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.1", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR798/tutorials/transient_heat_equation.jl b/previews/PR798/tutorials/transient_heat_equation.jl new file mode 100644 index 0000000000..caf81d3637 --- /dev/null +++ b/previews/PR798/tutorials/transient_heat_equation.jl @@ -0,0 +1,137 @@ +using Ferrite, SparseArrays, WriteVTK + +grid = generate_grid(Quadrilateral, (100, 100)); + +ip = Lagrange{RefQuadrilateral, 1}() +qr = QuadratureRule{RefQuadrilateral}(2) +cellvalues = CellValues(qr, ip); + +dh = DofHandler(grid) +add!(dh, :u, ip) +close!(dh); + +K = allocate_matrix(dh); +M = allocate_matrix(dh); + +f = zeros(ndofs(dh)); + +max_temp = 100 +Δt = 1 +T = 200 +t_rise = 100 +ch = ConstraintHandler(dh); + +∂Ω₁ = union(getfacetset.((grid,), ["left", "right"])...) +dbc = Dirichlet(:u, ∂Ω₁, (x, t) -> 0) +add!(ch, dbc); + +∂Ω₂ = union(getfacetset.((grid,), ["top", "bottom"])...) +dbc = Dirichlet(:u, ∂Ω₂, (x, t) -> max_temp * clamp(t / t_rise, 0, 1)) +add!(ch, dbc) +close!(ch) +update!(ch, 0.0); + +function doassemble_K!(K::SparseMatrixCSC, f::Vector, cellvalues::CellValues, dh::DofHandler) + + n_basefuncs = getnbasefunctions(cellvalues) + Ke = zeros(n_basefuncs, n_basefuncs) + fe = zeros(n_basefuncs) + + assembler = start_assemble(K, f) + + for cell in CellIterator(dh) + + fill!(Ke, 0) + fill!(fe, 0) + + reinit!(cellvalues, cell) + + for q_point in 1:getnquadpoints(cellvalues) + dΩ = getdetJdV(cellvalues, q_point) + + for i in 1:n_basefuncs + v = shape_value(cellvalues, q_point, i) + ∇v = shape_gradient(cellvalues, q_point, i) + fe[i] += 0.1 * v * dΩ + for j in 1:n_basefuncs + ∇u = shape_gradient(cellvalues, q_point, j) + Ke[i, j] += 1.0e-3 * (∇v ⋅ ∇u) * dΩ + end + end + end + + assemble!(assembler, celldofs(cell), Ke, fe) + end + return K, f +end + +function doassemble_M!(M::SparseMatrixCSC, cellvalues::CellValues, dh::DofHandler) + + n_basefuncs = getnbasefunctions(cellvalues) + Me = zeros(n_basefuncs, n_basefuncs) + + assembler = start_assemble(M) + + for cell in CellIterator(dh) + + fill!(Me, 0) + + reinit!(cellvalues, cell) + + for q_point in 1:getnquadpoints(cellvalues) + dΩ = getdetJdV(cellvalues, q_point) + + for i in 1:n_basefuncs + v = shape_value(cellvalues, q_point, i) + for j in 1:n_basefuncs + u = shape_value(cellvalues, q_point, j) + Me[i, j] += (v * u) * dΩ + end + end + end + + assemble!(assembler, celldofs(cell), Me) + end + return M +end + +K, f = doassemble_K!(K, f, cellvalues, dh) +M = doassemble_M!(M, cellvalues, dh) +A = (Δt .* K) + M; + +rhsdata = get_rhs_data(ch, A); + +uₙ = zeros(length(f)); +apply_analytical!(uₙ, dh, :u, x -> (x[1]^2 - 1) * (x[2]^2 - 1) * max_temp); + +apply!(A, ch); + +pvd = paraview_collection("transient-heat") +VTKGridFile("transient-heat-0", dh) do vtk + write_solution(vtk, dh, uₙ) + pvd[0.0] = vtk +end + +for (step, t) in enumerate(Δt:Δt:T) + #First of all, we need to update the Dirichlet boundary condition values. + update!(ch, t) + + #Secondly, we compute the right-hand-side of the problem. + b = Δt .* f .+ M * uₙ + #Then, we can apply the boundary conditions of the current time step. + apply_rhs!(rhsdata, b, ch) + + #Finally, we can solve the time step and save the solution afterwards. + u = A \ b + + VTKGridFile("transient-heat-$step", dh) do vtk + write_solution(vtk, dh, u) + pvd[t] = vtk + end + #At the end of the time loop, we set the previous solution to the current one and go to the next time step. + uₙ .= u +end + +vtk_save(pvd); + +# This file was generated using Literate.jl, https://github.com/fredrikekre/Literate.jl diff --git a/previews/PR798/tutorials/transient_heat_equation/index.html b/previews/PR798/tutorials/transient_heat_equation/index.html new file mode 100644 index 0000000000..6e43842b17 --- /dev/null +++ b/previews/PR798/tutorials/transient_heat_equation/index.html @@ -0,0 +1,236 @@ + +Transient heat equation · Ferrite.jl

Transient heat equation

Figure 1: Visualization of the temperature time evolution on a unit square where the prescribed temperature on the upper and lower parts of the boundary increase with time.

Tip

This example is also available as a Jupyter notebook: transient_heat_equation.ipynb.

Introduction

In this example we extend the heat equation by a time dependent term, i.e.

\[ \frac{\partial u}{\partial t}-\nabla \cdot (k \nabla u) = f \quad x \in \Omega,\]

where $u$ is the unknown temperature field, $k$ the heat conductivity, $f$ the heat source and $\Omega$ the domain. For simplicity, we hard code $f = 0.1$ and $k = 10^{-3}$. We define homogeneous Dirichlet boundary conditions along the left and right edge of the domain.

\[u(x,t) = 0 \quad x \in \partial \Omega_1,\]

where $\partial \Omega_1$ denotes the left and right boundary of $\Omega$.

Further, we define heterogeneous Dirichlet boundary conditions at the top and bottom edge $\partial \Omega_2$. We choose a linearly increasing function $a(t)$ that describes the temperature at this boundary

\[u(x,t) = a(t) \quad x \in \partial \Omega_2.\]

The semidiscrete weak form is given by

\[\int_{\Omega}v \frac{\partial u}{\partial t} \ \mathrm{d}\Omega + \int_{\Omega} k \nabla v \cdot \nabla u \ \mathrm{d}\Omega = \int_{\Omega} f v \ \mathrm{d}\Omega,\]

where $v$ is a suitable test function. Now, we still need to discretize the time derivative. An implicit Euler scheme is applied, which yields:

\[\int_{\Omega} v\, u_{n+1}\ \mathrm{d}\Omega + \Delta t\int_{\Omega} k \nabla v \cdot \nabla u_{n+1} \ \mathrm{d}\Omega = \Delta t\int_{\Omega} f v \ \mathrm{d}\Omega + \int_{\Omega} v \, u_{n} \ \mathrm{d}\Omega.\]

If we assemble the discrete operators, we get the following algebraic system:

\[\mathbf{M} \mathbf{u}_{n+1} + Δt \mathbf{K} \mathbf{u}_{n+1} = Δt \mathbf{f} + \mathbf{M} \mathbf{u}_{n}\]

In this example we apply the boundary conditions to the assembled discrete operators (mass matrix $\mathbf{M}$ and stiffnes matrix $\mathbf{K}$) only once. We utilize the fact that in finite element computations Dirichlet conditions can be applied by zero out rows and columns that correspond to a prescribed dof in the system matrix ($\mathbf{A} = Δt \mathbf{K} + \mathbf{M}$) and setting the value of the right-hand side vector to the value of the Dirichlet condition. Thus, we only need to apply in every time step the Dirichlet condition to the right-hand side of the problem.

Commented Program

Now we solve the problem in Ferrite. What follows is a program spliced with comments. The full program, without comments, can be found in the next section.

First we load Ferrite, and some other packages we need.

using Ferrite, SparseArrays, WriteVTK

We create the same grid as in the heat equation example.

grid = generate_grid(Quadrilateral, (100, 100));

Trial and test functions

Again, we define the structs that are responsible for the shape_value and shape_gradient evaluation.

ip = Lagrange{RefQuadrilateral, 1}()
+qr = QuadratureRule{RefQuadrilateral}(2)
+cellvalues = CellValues(qr, ip);

Degrees of freedom

After this, we can define the DofHandler and distribute the DOFs of the problem.

dh = DofHandler(grid)
+add!(dh, :u, ip)
+close!(dh);

By means of the DofHandler we can allocate the needed SparseMatrixCSC. M refers here to the so called mass matrix, which always occurs in time related terms, i.e.

\[M_{ij} = \int_{\Omega} v_i \, u_j \ \mathrm{d}\Omega,\]

where $u_i$ and $v_j$ are trial and test functions, respectively.

K = allocate_matrix(dh);
+M = allocate_matrix(dh);

We also preallocate the right hand side

f = zeros(ndofs(dh));

Boundary conditions

In order to define the time dependent problem, we need some end time T and something that describes the linearly increasing Dirichlet boundary condition on $\partial \Omega_2$.

max_temp = 100
+Δt = 1
+T = 200
+t_rise = 100
+ch = ConstraintHandler(dh);

Here, we define the boundary condition related to $\partial \Omega_1$.

∂Ω₁ = union(getfacetset.((grid,), ["left", "right"])...)
+dbc = Dirichlet(:u, ∂Ω₁, (x, t) -> 0)
+add!(ch, dbc);

While the next code block corresponds to the linearly increasing temperature description on $\partial \Omega_2$ until t=t_rise, and then keep constant

∂Ω₂ = union(getfacetset.((grid,), ["top", "bottom"])...)
+dbc = Dirichlet(:u, ∂Ω₂, (x, t) -> max_temp * clamp(t / t_rise, 0, 1))
+add!(ch, dbc)
+close!(ch)
+update!(ch, 0.0);

Assembling the linear system

As in the heat equation example we define a doassemble! function that assembles the diffusion parts of the equation:

function doassemble_K!(K::SparseMatrixCSC, f::Vector, cellvalues::CellValues, dh::DofHandler)
+
+    n_basefuncs = getnbasefunctions(cellvalues)
+    Ke = zeros(n_basefuncs, n_basefuncs)
+    fe = zeros(n_basefuncs)
+
+    assembler = start_assemble(K, f)
+
+    for cell in CellIterator(dh)
+
+        fill!(Ke, 0)
+        fill!(fe, 0)
+
+        reinit!(cellvalues, cell)
+
+        for q_point in 1:getnquadpoints(cellvalues)
+            dΩ = getdetJdV(cellvalues, q_point)
+
+            for i in 1:n_basefuncs
+                v = shape_value(cellvalues, q_point, i)
+                ∇v = shape_gradient(cellvalues, q_point, i)
+                fe[i] += 0.1 * v * dΩ
+                for j in 1:n_basefuncs
+                    ∇u = shape_gradient(cellvalues, q_point, j)
+                    Ke[i, j] += 1.0e-3 * (∇v ⋅ ∇u) * dΩ
+                end
+            end
+        end
+
+        assemble!(assembler, celldofs(cell), Ke, fe)
+    end
+    return K, f
+end

In addition to the diffusive part, we also need a function that assembles the mass matrix M.

function doassemble_M!(M::SparseMatrixCSC, cellvalues::CellValues, dh::DofHandler)
+
+    n_basefuncs = getnbasefunctions(cellvalues)
+    Me = zeros(n_basefuncs, n_basefuncs)
+
+    assembler = start_assemble(M)
+
+    for cell in CellIterator(dh)
+
+        fill!(Me, 0)
+
+        reinit!(cellvalues, cell)
+
+        for q_point in 1:getnquadpoints(cellvalues)
+            dΩ = getdetJdV(cellvalues, q_point)
+
+            for i in 1:n_basefuncs
+                v = shape_value(cellvalues, q_point, i)
+                for j in 1:n_basefuncs
+                    u = shape_value(cellvalues, q_point, j)
+                    Me[i, j] += (v * u) * dΩ
+                end
+            end
+        end
+
+        assemble!(assembler, celldofs(cell), Me)
+    end
+    return M
+end

Solution of the system

We first assemble all parts in the prior allocated SparseMatrixCSC.

K, f = doassemble_K!(K, f, cellvalues, dh)
+M = doassemble_M!(M, cellvalues, dh)
+A = (Δt .* K) + M;

Now, we need to save all boundary condition related values of the unaltered system matrix A, which is done by get_rhs_data. The function returns a RHSData struct, which contains all needed information to apply the boundary conditions solely on the right-hand-side vector of the problem.

rhsdata = get_rhs_data(ch, A);

We set the values at initial time step, denoted by uₙ, to a bubble-shape described by $(x_1^2-1)(x_2^2-1)$, such that it is zero at the boundaries and the maximum temperature in the center.

uₙ = zeros(length(f));
+apply_analytical!(uₙ, dh, :u, x -> (x[1]^2 - 1) * (x[2]^2 - 1) * max_temp);

Here, we apply once the boundary conditions to the system matrix A.

apply!(A, ch);

To store the solution, we initialize a paraview collection (.pvd) file,

pvd = paraview_collection("transient-heat")
+VTKGridFile("transient-heat-0", dh) do vtk
+    write_solution(vtk, dh, uₙ)
+    pvd[0.0] = vtk
+end
VTKGridFile for the closed file "transient-heat-0.vtu".

At this point everything is set up and we can finally approach the time loop.

for (step, t) in enumerate(Δt:Δt:T)
+    #First of all, we need to update the Dirichlet boundary condition values.
+    update!(ch, t)
+
+    #Secondly, we compute the right-hand-side of the problem.
+    b = Δt .* f .+ M * uₙ
+    #Then, we can apply the boundary conditions of the current time step.
+    apply_rhs!(rhsdata, b, ch)
+
+    #Finally, we can solve the time step and save the solution afterwards.
+    u = A \ b
+
+    VTKGridFile("transient-heat-$step", dh) do vtk
+        write_solution(vtk, dh, u)
+        pvd[t] = vtk
+    end
+    #At the end of the time loop, we set the previous solution to the current one and go to the next time step.
+    uₙ .= u
+end

In order to use the .pvd file we need to store it to the disk, which is done by:

vtk_save(pvd);

Plain program

Here follows a version of the program without any comments. The file is also available here: transient_heat_equation.jl.

using Ferrite, SparseArrays, WriteVTK
+
+grid = generate_grid(Quadrilateral, (100, 100));
+
+ip = Lagrange{RefQuadrilateral, 1}()
+qr = QuadratureRule{RefQuadrilateral}(2)
+cellvalues = CellValues(qr, ip);
+
+dh = DofHandler(grid)
+add!(dh, :u, ip)
+close!(dh);
+
+K = allocate_matrix(dh);
+M = allocate_matrix(dh);
+
+f = zeros(ndofs(dh));
+
+max_temp = 100
+Δt = 1
+T = 200
+t_rise = 100
+ch = ConstraintHandler(dh);
+
+∂Ω₁ = union(getfacetset.((grid,), ["left", "right"])...)
+dbc = Dirichlet(:u, ∂Ω₁, (x, t) -> 0)
+add!(ch, dbc);
+
+∂Ω₂ = union(getfacetset.((grid,), ["top", "bottom"])...)
+dbc = Dirichlet(:u, ∂Ω₂, (x, t) -> max_temp * clamp(t / t_rise, 0, 1))
+add!(ch, dbc)
+close!(ch)
+update!(ch, 0.0);
+
+function doassemble_K!(K::SparseMatrixCSC, f::Vector, cellvalues::CellValues, dh::DofHandler)
+
+    n_basefuncs = getnbasefunctions(cellvalues)
+    Ke = zeros(n_basefuncs, n_basefuncs)
+    fe = zeros(n_basefuncs)
+
+    assembler = start_assemble(K, f)
+
+    for cell in CellIterator(dh)
+
+        fill!(Ke, 0)
+        fill!(fe, 0)
+
+        reinit!(cellvalues, cell)
+
+        for q_point in 1:getnquadpoints(cellvalues)
+            dΩ = getdetJdV(cellvalues, q_point)
+
+            for i in 1:n_basefuncs
+                v = shape_value(cellvalues, q_point, i)
+                ∇v = shape_gradient(cellvalues, q_point, i)
+                fe[i] += 0.1 * v * dΩ
+                for j in 1:n_basefuncs
+                    ∇u = shape_gradient(cellvalues, q_point, j)
+                    Ke[i, j] += 1.0e-3 * (∇v ⋅ ∇u) * dΩ
+                end
+            end
+        end
+
+        assemble!(assembler, celldofs(cell), Ke, fe)
+    end
+    return K, f
+end
+
+function doassemble_M!(M::SparseMatrixCSC, cellvalues::CellValues, dh::DofHandler)
+
+    n_basefuncs = getnbasefunctions(cellvalues)
+    Me = zeros(n_basefuncs, n_basefuncs)
+
+    assembler = start_assemble(M)
+
+    for cell in CellIterator(dh)
+
+        fill!(Me, 0)
+
+        reinit!(cellvalues, cell)
+
+        for q_point in 1:getnquadpoints(cellvalues)
+            dΩ = getdetJdV(cellvalues, q_point)
+
+            for i in 1:n_basefuncs
+                v = shape_value(cellvalues, q_point, i)
+                for j in 1:n_basefuncs
+                    u = shape_value(cellvalues, q_point, j)
+                    Me[i, j] += (v * u) * dΩ
+                end
+            end
+        end
+
+        assemble!(assembler, celldofs(cell), Me)
+    end
+    return M
+end
+
+K, f = doassemble_K!(K, f, cellvalues, dh)
+M = doassemble_M!(M, cellvalues, dh)
+A = (Δt .* K) + M;
+
+rhsdata = get_rhs_data(ch, A);
+
+uₙ = zeros(length(f));
+apply_analytical!(uₙ, dh, :u, x -> (x[1]^2 - 1) * (x[2]^2 - 1) * max_temp);
+
+apply!(A, ch);
+
+pvd = paraview_collection("transient-heat")
+VTKGridFile("transient-heat-0", dh) do vtk
+    write_solution(vtk, dh, uₙ)
+    pvd[0.0] = vtk
+end
+
+for (step, t) in enumerate(Δt:Δt:T)
+    #First of all, we need to update the Dirichlet boundary condition values.
+    update!(ch, t)
+
+    #Secondly, we compute the right-hand-side of the problem.
+    b = Δt .* f .+ M * uₙ
+    #Then, we can apply the boundary conditions of the current time step.
+    apply_rhs!(rhsdata, b, ch)
+
+    #Finally, we can solve the time step and save the solution afterwards.
+    u = A \ b
+
+    VTKGridFile("transient-heat-$step", dh) do vtk
+        write_solution(vtk, dh, u)
+        pvd[t] = vtk
+    end
+    #At the end of the time loop, we set the previous solution to the current one and go to the next time step.
+    uₙ .= u
+end
+
+vtk_save(pvd);

This page was generated using Literate.jl.

diff --git a/previews/PR798/tutorials/vortex-street.pvd b/previews/PR798/tutorials/vortex-street.pvd new file mode 100755 index 0000000000..16ae1d0292 --- /dev/null +++ b/previews/PR798/tutorials/vortex-street.pvd @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +