#!/usr/bin/env python3
# python 3.6+

# THIS SCRIPT WAS AUTOGENERATED FROM SOURCE FILES FOUND AT:
# https://github.com/Pristine-Cat/SymPad

# Copyright (c) 2019 Tomasz Pytel
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#     * Redistributions of source code must retain the above copyright
#       notice, this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above copyright
#       notice, this list of conditions and the following disclaimer in the
#       documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

_RUNNING_AS_SINGLE_SCRIPT = True

import sys
sys.path.insert (0, '') # allow importing from current directory first (for SymPy development version)

_FILES = {

	'style.css': # style.css

r"""* {
	box-sizing: border-box;
	margin: 0;
	padding: 0;
}

body {
	margin-top: 1em;
	margin-bottom: 6em;
	cursor: default;
	background-image: url('/bg.png');
}

i {
	color: #0008;
}

#Clipboard {
	position: fixed;
	bottom: -2em;
	color: transparent;
	border: 0px;
}

#Greeting {
	position: fixed;
	left: 50%;
	top: 50%;
	transform: translate(-50%, -50%);
	color: #0007;
}

.GreetingA {
	display: block;
	color: #0007;
	text-decoration: none;
	margin-bottom: 0.5em;
}

#InputCover {
	position: fixed;
	z-index: 2;
	height: 4em;
	bottom: 0;
	left: 0;
	right: 0;
	background-image: url('/bg.png');
}

#InputCoverLeft {
	position: fixed;
	z-index: 5;
	height: 4em;
	bottom: 0;
	left: 0;
	background-image: url('/bg.png');
}

#InputCoverRight {
	position: fixed;
	z-index: 5;
	height: 4em;
	bottom: 0;
	right: 0;
	background-image: url('/bg.png');
}

#Input {
	position: fixed;
	z-index: 3;
	bottom: 2em;
	left: 4em;
	right: 1em;
	border-color: transparent;
	outline-color: transparent;
	background-color: transparent;
}

#InputOverlay {
	z-index: 4;
	pointer-events: none;
}

#OverlayGood {
	white-space: pre;
	-webkit-text-fill-color: transparent;
}

#OverlayError {
	position: absolute;
	white-space: pre;
	-webkit-text-fill-color: #f44;
}

#OverlayAutocomplete {
	position: absolute;
	white-space: pre;
	-webkit-text-fill-color: #999;
}

.LogEntry {
	width: 100%;
	margin-bottom: 1.5em;
}

.LogMargin {
	display: inline-block;
	height: 100%;
	width: 4em;
	vertical-align: top;
	text-align: right;
	padding-right: 0.5em;
	color: #0008;
}

.LogBody {
	display: inline-block;
	margin-right: -9999em;
}

.LogWait {
	vertical-align: top;
}

.LogInput {
	margin-bottom: 0.75em;
	width: fit-content;
	cursor: pointer;
}

.LogEval {
	position: relative;
	margin-bottom: 0.25em;
	cursor: pointer;
}

.LogMsg {
	margin-bottom: 0.25em;
}

.LogError {
	margin-bottom: 0.25em;
	color: red;
}

.LogErrorTriange {
	position: absolute;
	left: -1.25em;
	top: 0.25em;
	font-size: 0.7em;
	color: red;
	font-weight: bold;
}

#VarDiv {
	display: none;
	position: fixed;
	right: 1em;
	top: 0;
	box-shadow: 4px 4px 12px 2px #0002;
}

#VarTab {
	border: 1px solid #0004;
	border-top: 0;
	background-color: black;
	background-image: url('/bg.png');
	color: #0008;
}

#VarTab:hover {
	color: black;
}

#VarTabShadow {
	z-index: -1;
	box-shadow: 4px 4px 12px 2px #0002;
}

#VarTabShadow, #VarTab {
	position: absolute;
	right: 0;
	bottom: -1.65em;
	padding: 0.25em;
}

#VarContent {
	/* display: none; */
	min-width: 6.1em;
	padding: 0.25em 0.25em;
	border: 1px solid #0004;
	border-top: 0;
	background-image: url('/bg.png');
}

#VarTable {
	margin-right: 0;
	margin-left: auto;
	border-spacing: 0;
}

table.VarTable td {
	padding: 0.25em;
}

.CopySpan {
	display: inline-block;
}

#ValidationError {
	color: #f99;
}

table.HelpTable td {
	vertical-align: top;
	padding: 0.25em 0;
}

table.HelpTable tr td:first-child {
	white-space: nowrap;
}

table.HelpTable tr td:nth-child(2) {
	padding: 0.25em 0.5em;
}

p span:hover {
	color: gray;
}
""".encode ("utf8"),

	'script.js': # script.js

r"""// TODO: Change how left/right arrows interact with autocomplete.
// TODO: Stupid scrollbars...
// TODO: Change input to text field for longer expression support?
// TODO: Arrow keys in Edge?
// TODO: clear() function to delete old log items?

URL              = '/';
WaitIcon         = '/wait.webp'; // 'https://i.gifer.com/origin/3f/3face8da2a6c3dcd27cb4a1aaa32c926_w200.webp';

JQInput          = null;
MJQueue          = null;
MarginTop        = Infinity;
PreventFocusOut  = true;

LogIdx           = 0;
UniqueID         = 1;

LastValidation   = null;
Validations      = [undefined];
Evaluations      = [undefined];
ErrorIdx         = null;
Autocomplete     = [];

LastClickTime    = 0;
NumClicks        = 0;

GreetingFadedOut = false;
ExceptionDone    = false;
SymPyDevVersion  = '1.7.1'

// replaced in env.js
History          = [];
HistIdx          = 0;
Version          = 'None'
SymPyVersion     = 'None'
DisplayStyle     = 1

//...............................................................................................
function copyInputStyle () {
	let left = $('#LogEntry1').position ().left;

	JQInput.css ({left: left});
	JQInput.width (window.innerWidth - left - 32);
	$('#InputCoverLeft').width (left);
	$('#InputCoverRight').css ({left: window.innerWidth - 30});

	let style   = getComputedStyle (document.getElementById ('Input'));
	let overlay = document.getElementById ('InputOverlay');

  for (let prop of style) {
    overlay.style [prop] = style [prop];
	}

	overlay.style ['z-index']        = 4;
	overlay.style ['pointer-events'] = 'none';
}

function scrollToEnd () {
	window.scrollTo (0, document.body.scrollHeight);
}

function resize () {
	copyInputStyle ();
	scrollToEnd ();
}

var LastDocHeight = undefined;
var LastWinHeight = undefined;

function monitorStuff () {
	let curDocHeight = $(document).height ();
	let curWinHeight = $(window).height ();

	if (curDocHeight != LastDocHeight || curWinHeight != LastWinHeight) {
		copyInputStyle ();

		window.LastDocHeight = curDocHeight;
		window.LastWinHeight = curWinHeight;
	}

	if (PreventFocusOut) {
		JQInput.focus ();
	}

	updateOverlayPosition ();
	setTimeout (monitorStuff, 50);
}

function readyMathJax () {
	window.MJQueue = MathJax.Hub.queue;

	if (DisplayStyle) {
		var TEX        = MathJax.InputJax.TeX;
		var PREFILTER  = TEX.prefilterMath;

		TEX.Augment ({
			prefilterMath: function (tex, displaymode, script) {
				return PREFILTER.call (TEX, '\\displaystyle{' + tex + '}', displaymode, script);
			}
		});
	}
}

function reprioritizeMJQueue () {
	let p = MJQueue.queue.pop ();

	if (p !== undefined) {
		MJQueue.queue.splice (0, 0, p);
	}
}

function escapeHTML (text) {
	const entityMap = {
		'&': '&amp;',
		'<': '&lt;',
		'>': '&gt;',
		'"': '&quot;',
		"'": '&#39;',
		'/': '&#x2F;',
		'`': '&#x60;',
		'=': '&#x3D;'
	};

	return text.replace (/[&<>"'`=\/]/g, function (s) {
		return entityMap [s];
	});
}

function escapeHTMLtex (text) {
	return text.replace (/\\text{(['"]).*?\1}/g, escapeHTML);
}

//...............................................................................................
function logResize () {
	let margin = Math.max (BodyMarginTop, Math.floor (window.innerHeight - $('body').height () - BodyMarginBottom + 3)); // +3 is fudge factor

	if (margin < MarginTop) {
		MarginTop = margin;
		$('body').css ({'margin-top': margin});
	}
}

function addLogEntry () {
	LogIdx += 1;

	$('#Log').append (`
			<div class="LogEntry"><div class="LogMargin">${LogIdx}.</div><div class="LogBody" id="LogEntry${LogIdx}"><div class="LogInput" id="LogInput${LogIdx}">
				<img class="LogWait" id="LogInputWait${LogIdx}" src="${WaitIcon}" width="16" style="visibility: hidden">
			</div></div></div>`)

	Validations.push (undefined);
	Evaluations.push (undefined);
}

function updateNumClicks () {
	let t = performance.now ();

	if ((t - LastClickTime) > 500) {
		NumClicks = 1;
	} else {
		NumClicks += 1;
	}

	LastClickTime = t;
}

function flashElement (e) {
	e.style.color      = 'white';
	e.style.background = 'black';

	setTimeout (function () {
		e.style.color      = 'black';
		e.style.background = 'transparent';
	}, 100);
}

function writeToClipboard (text) {
	PreventFocusOut = false;

	$('#Clipboard').val (text);
	$('#Clipboard').focus ();
	$('#Clipboard').select ();
	document.execCommand ('copy');

	PreventFocusOut = true;

	if (JQInput !== null) {
		JQInput.focus ();
	}
}

function cE2C (e) {
	writeToClipboard (e.textContent);
	flashElement (e);
}

function copyLogToClipboard (e, val_or_eval, idx, subidx = 0, mathidx = 0) {
	let resp = val_or_eval ? Evaluations [idx].data [subidx].math [mathidx] : Validations [idx];

	updateNumClicks ();
	writeToClipboard (NumClicks == 1 ? resp.nat : NumClicks == 2 ? resp.py : resp.tex);
	flashElement (e);
}

function copyVarToClipboard (e, full = true) {
	updateNumClicks ();

	e        = e.parentElement;
	let text = Variables.vars.get (e.name);
	text     = NumClicks == 1 ? text.nat : NumClicks == 2 ? text.py : text.tex;

	if (!full && (NumClicks == 2) && text [1].startsWith ('Lambda(')) { // special case process py Lambda body
		if (text [1] [7] === '(') {
			text = [text [0], text [1].slice (text [1].indexOf (')') + 2, -1).trim ()];
		} else {
			text = [text [0], text [1].slice (text [1].indexOf (',') + 1, -1).trim ()];
		}
	}

	writeToClipboard (full ? text.join (' = ') : text [1]);
	flashElement (full ? e : e.childNodes [2]);
}

function updateOverlayPosition () {
	let left       = -JQInput.scrollLeft ();
	let goodwidth  = $('#OverlayGood').width ();
	let errorwidth = $('#OverlayError').width ();

	$('#OverlayGood').css ({left: left})
	$('#OverlayError').css ({top: 0, left: left + goodwidth});
	$('#OverlayAutocomplete').css ({top: 0, left: left + goodwidth + errorwidth});
}

function updateOverlay (text, erridx, autocomplete) {
	ErrorIdx     = erridx;
	Autocomplete = autocomplete;

	if (ErrorIdx === null) {
		$('#OverlayGood').text (text);
		$('#OverlayError').text ('');

	} else {
		$('#OverlayGood').text (text.substr (0, ErrorIdx));
		$('#OverlayError').text (text.substr (ErrorIdx));
	}

	$('#OverlayAutocomplete').text (Autocomplete.join (''));

	updateOverlayPosition ();
}

//...............................................................................................
function ajaxValidate (resp) {
	if (Validations [resp.idx] !== undefined && Validations [resp.idx].subidx >= resp.subidx) {
		return; // ignore out of order responses (which should never happen with single threaded server)
	}

	LastValidation = resp;

	if (resp.tex !== null) {
		Validations [resp.idx] = resp;

		let eLogInput = document.getElementById ('LogInput' + resp.idx);

		let queue              = [];
		[queue, MJQueue.queue] = [MJQueue.queue, queue];

		MJQueue.queue = queue.filter (function (obj, idx, arr) { // remove previous pending updates to same element
			return obj.data [0].parentElement !== eLogInput;
		});

		let eLogInputWait              = document.getElementById ('LogInputWait' + resp.idx);
		let math                       = resp.tex ? `$${resp.tex}$` : '';
		eLogInputWait.style.visibility = '';

		$(eLogInput).append (`<span class="CopySpan" onclick="copyLogToClipboard (this, 0, ${resp.idx})" style="visibility: hidden">${escapeHTMLtex (math)}</span>`);

		let eMath = eLogInput.lastElementChild;

		MJQueue.Push (['Typeset', MathJax.Hub, eMath, function () {
			if (eMath === eLogInput.children [eLogInput.children.length - 1]) {
				eLogInput.appendChild (eLogInputWait);

				for (let i = eLogInput.children.length - 3; i >= 0; i --) {
					eLogInput.removeChild (eLogInput.children [i]);
				}

				eLogInputWait.style.visibility = 'hidden';
				eMath.style.visibility         = '';

				logResize ();
				scrollToEnd (); // ???
			}
		}]);

		reprioritizeMJQueue ();
	}

	updateOverlay (JQInput.val (), resp.erridx, resp.autocomplete);
}

function ajaxEvaluate (resp) {
	Variables.update (resp.vars);

	Evaluations [resp.idx] = resp;
	let eLogEval           = document.getElementById ('LogEval' + resp.idx);

	eLogEval.removeChild (document.getElementById ('LogEvalWait' + resp.idx));

	for (let subidx in resp.data) {
		subresp = resp.data [subidx];

		if (subresp.msg !== undefined && subresp.msg.length) { // message present?
			for (let msg of subresp.msg) {
				$(eLogEval).append (`<div class="LogMsg">${escapeHTML (msg).replace (/  /g, '&emsp;')}</div>`);
			}

			logResize ();
			scrollToEnd ();
		}

		if (subresp.math !== undefined && subresp.math.length) { // math results present?
			for (let mathidx in subresp.math) {
				$(eLogEval).append (`<div class="LogEval"></div>`);
				let eLogEvalDiv = eLogEval.lastElementChild;

				$(eLogEvalDiv).append (`<span class="CopySpan" style="visibility: hidden" onclick="copyLogToClipboard (this, 1, ${resp.idx}, ${subidx}, ${mathidx})">$${escapeHTMLtex (subresp.math [mathidx].tex)}$</span>`);
				let eLogEvalMath = eLogEvalDiv.lastElementChild;

				$(eLogEvalDiv).append (`<img class="LogWait" src="${WaitIcon}" width="16">`);
				let eLogEvalWait = eLogEvalDiv.lastElementChild;

				MJQueue.Push (['Typeset', MathJax.Hub, eLogEvalMath, function () {
					eLogEvalDiv.removeChild (eLogEvalWait);

					eLogEvalMath.style.visibility = '';

					logResize ();
					scrollToEnd ();
				}]);

				reprioritizeMJQueue ();
			}
		}

		if (subresp.err !== undefined) { // error?
			let eErrorHiddenBox, eLogErrorHidden;

			if (subresp.err.length > 1) {
				$(eLogEval).append ('<div style="position: relative"></div>');
				eErrorHiddenBox = eLogEval.lastElementChild;

				$(eErrorHiddenBox).append (`<div style="display: none"></div>`);
				eLogErrorHidden = eErrorHiddenBox.lastElementChild;

				for (let i = 0; i < subresp.err.length - 1; i ++) {
					$(eLogErrorHidden).append (`<div class="LogError">${escapeHTML (subresp.err [i]).replace (/  /g, '&emsp;')}</div>`);
				}
			}

			$(eLogEval).append (`<div class="LogError">${escapeHTML (subresp.err [subresp.err.length - 1])}</div>`);
			let eLogErrorBottom = eLogEval.lastElementChild;

			if (subresp.err.length > 1) {
				let ClickHereToOpen = null;

				if (!ExceptionDone) {
					$(eLogErrorBottom).append ('<i>&emsp;<-- click to open</i>');

					ClickHereToOpen = eLogErrorBottom.lastElementChild;
					ExceptionDone   = true;
				}

				$(eErrorHiddenBox).append (`<div class="LogErrorTriange">\u25b7</div>`);
				let eLogErrorTriangle = eErrorHiddenBox.lastElementChild;

				f = function () {
					if (eLogErrorHidden.style.display === 'none') {
						eLogErrorHidden.style.display = 'block';
						eLogErrorTriangle.innerText   = '\u25bd';
					} else {
						eLogErrorHidden.style.display = 'none';
						eLogErrorTriangle.innerText   = '\u25b7';
					}

					if (ClickHereToOpen) {
						ClickHereToOpen.parentNode.removeChild (ClickHereToOpen);
						ClickHereToOpen = null;
					}

					logResize ();
				};

				$(eLogErrorHidden).click (f);
				$(eLogErrorBottom).click (f);
				$(eLogErrorTriangle).click (f);
			}

			logResize ();
			scrollToEnd ();
		}

		if (subresp.img !== undefined) { // image present?
			$(eLogEval).append (`<div><img src='data:image/png;base64,${subresp.img}'></div>`);

			setTimeout (function () { // image seems to take some time to register size even though it is directly present
				logResize ();
				scrollToEnd ();
			}, 0);
		}
	}
}

function inputting (text, reset = false) {
	if (reset) {
		ErrorIdx     = null;
		Autocomplete = [];

		JQInput.val (text);
	}

	updateOverlay (text, ErrorIdx, Autocomplete);

	$.ajax ({
		url: URL,
		type: 'POST',
		cache: false,
		dataType: 'json',
		success: ajaxValidate,
		data: {
			mode: 'validate',
			idx: LogIdx,
			subidx: UniqueID ++,
			text: text,
		},
	});
}

function inputted (text) {
	$.ajax ({
		url: URL,
		type: 'POST',
		cache: false,
		dataType: 'json',
		success: ajaxEvaluate,
		data: {
			mode: 'evaluate',
			idx: LogIdx,
			text: text,
		},
	});

	$('#LogEntry' + LogIdx).append (`
			<div class="LogEval" id="LogEval${LogIdx}">
				<img class="LogWait" id="LogEvalWait${LogIdx}" src="${WaitIcon}" width="16">
			</div>`);

	History.push (text);

	HistIdx = History.length;

	addLogEntry ();
	logResize ();
	scrollToEnd ();
}

//...............................................................................................
function inputKeypress (e) {
	if (e.which == 13) {
		s = JQInput.val ().trim ();

		if ((s && ErrorIdx === null) || s === '?') {
			if (!GreetingFadedOut) {
				GreetingFadedOut = true;
				$('#Greeting').fadeOut (3000);
			}

			if (s === 'help' || s === '?') {
				window.open (`${URL}help.html`);
				inputting ('', true);

				return false;
			}

			if (Autocomplete.length > 0) {
				s = s + Autocomplete.join ('');
				inputting (s);
			}

			JQInput.val ('');
			updateOverlay ('', null, []);
			inputted (s);

			return false;

		} else if (LastValidation !== null && LastValidation.error) { // last validation had error, display
			let eLogInput = document.getElementById (`LogInput${LastValidation.idx}`);

			$('#ValidationError').remove ();
			$(eLogInput).append (`<span id="ValidationError">&lt;-- ${escapeHTML (LastValidation.error)}</span>`)
		}

	} else if (e.which == 32) {
		if (!JQInput.val ()) {
			return false;
		}
	}

	return true;
}

function inputKeydown (e) {
	if (e.code == 'Escape') {
		e.preventDefault ();

		if (JQInput.val ()) {
			HistIdx = History.length;
			inputting ('', true);

			return false;
		}

	} else if (e.code == 'Tab') {
		e.preventDefault ();
		$(this).focus ();

		return false;

	} else if (e.code == 'ArrowUp') {
		e.preventDefault ();

		if (HistIdx) {
			inputting (History [-- HistIdx], true);

			return false;
		}

	} else if (e.code == 'ArrowDown') {
		e.preventDefault ();

		if (HistIdx < History.length - 1) {
			inputting (History [++ HistIdx], true);

			return false;

		} else if (HistIdx != History.length) {
			HistIdx = History.length;
			inputting ('', true);

			return false;
		}

	} else if (e.code == 'ArrowRight') {
		if (JQInput.get (0).selectionStart === JQInput.val ().length && Autocomplete.length) {
			let text = JQInput.val ();

			text         = text + Autocomplete [0];
			Autocomplete = Autocomplete.slice (1);

			JQInput.val (text);
			inputting (text);
		}
	}

	setTimeout (updateOverlayPosition, 0);

	return true;
}

//...............................................................................................
class _Variables {
	constructor () {
		this.eVarDiv       = document.getElementById ('VarDiv');
		this.eVarTab       = document.getElementById ('VarTab');
		this.eVarContent   = document.getElementById ('VarContent');
		this.eVarTable     = document.getElementById ('VarTable');
		this.queued_update = null;
		this.display       = true;
		this.vars          = new Map ();
	}

	_update (vars) {
		function spliteq (text) {
			let p = text.split (' = ');

			return [p [0], p.slice (1).join (' = ')];
		}

		vars = new Map (vars.map (function (e) {
			let nat = spliteq (e.nat);

			return [nat [0], {tex: spliteq (e.tex), nat: nat, py: spliteq (e.py)}];
		}));

		let same = new Set ();

		for (let r of Array.from (this.eVarTable.childNodes)) {
			let v = vars.get (r.name);

			if (v === undefined || r.val !== v.tex.join (' = ')) {
				this.eVarTable.removeChild (r);
			} else {
				same.add (r.name);
			}
		}

		let added = false;

		for (let [n, v] of vars) {
			if (same.has (n)) {
				continue;
			}

			let inserted = false;
			let isfunc   = n.includes ('(');
			let e        = $(`<tr><td onclick="copyVarToClipboard (this)">$${escapeHTMLtex (v.tex [0])}$</td><td class="VarTCell" onclick="copyVarToClipboard (this)">$=$</td><td class="VarTCell" onclick="copyVarToClipboard (this, false)">$${escapeHTMLtex (v.tex [1])}$</td></tr>`);
			e [0].name   = n;
			e [0].val    = v.tex.join (' = ');
			added        = true;

			for (let r of this.eVarTable.childNodes) {
				let isfuncr = r.name.includes ('(');

				if ((isfunc && !isfuncr) || ((n < r.name) && (isfunc === isfuncr))) {
					e.insertBefore (r);
					inserted = true;

					break;
				}
			}

			if (!inserted) {
				e.appendTo (this.eVarTable);
			}
		}

		this.vars = vars;

		if (added) {
			MJQueue.Push (['Typeset', MathJax.Hub, this.eVarTable]);
			reprioritizeMJQueue ();
		}
	}

	update (vars) {
		if (this.display) {
			this._update (vars);
		} else {
			this.queued_update = vars;
		}

		this.eVarDiv.style.display = vars.length ? 'block' : 'none';
	}

	toggle () {
		this.eVarContent.style.minWidth = `${this.eVarTab.clientWidth + 2}px`;

		if (!this.display && this.queued_update !== null) {
			this._update (this.queued_update);
			this.queued_update = null;
		}

		this.display                   = !this.display;
		this.eVarContent.style.display = this.display ? 'block' : 'none';
	}
}

//...............................................................................................
$(function () {
	if (window.location.pathname != '/') {
		return;
	}

	window.JQInput   = $('#Input');
	window.Variables = new _Variables ();

	let margin       = $('body').css ('margin-top');
	BodyMarginTop    = Number (margin.slice (0, margin.length - 2));
	margin           = $('body').css ('margin-bottom');
	BodyMarginBottom = Number (margin.slice (0, margin.length - 2));

	$('#Clipboard').prop ('readonly', true);
	$('#InputCover') [0].height = $('#InputCover').height ();

	JQInput.keypress (inputKeypress);
	JQInput.keydown (inputKeydown);

	addLogEntry ();
	logResize ();
	resize ();
	monitorStuff ();

	function first_vars_update (resp) {
		if (MJQueue === null) { // wait for MathJax ready
			setTimeout (function () { first_vars_update (resp); }, 50);
		} else {
			Variables.update (resp.vars);
		}
	}

	$.ajax ({
		url: URL,
		type: 'POST',
		cache: false,
		dataType: 'json',
		success: first_vars_update,
		data: {mode: 'vars'},
	});
});
""".encode ("utf8"),

	'index.html': # index.html

r"""<!DOCTYPE html>
<html>
<head>

<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link rel="icon" href="https://www.sympy.org/static/SymPy-Favicon.ico">
<title>SymPad</title>
<link rel="stylesheet" type="text/css" href="style.css">

<script type="text/javascript" src="https://code.jquery.com/jquery-3.4.1.js" integrity="sha256-WpOohJOqMqqyKL9FccASB9O0KwACQJpFTUBLTYOVvVU=" crossorigin="anonymous"></script>
<script type="text/javascript" src="script.js"></script>
<script type="text/javascript" src="env.js"></script>
<script type="text/x-mathjax-config">
	MathJax.Hub.Config ({
		messageStyle: "none",
		tex2jax: {inlineMath: [["$","$"], ["\\(","\\)"]]}
	});

	MathJax.Hub.Register.StartupHook ("End", readyMathJax);
</script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/latest.js?config=TeX-AMS_CHTML-full"></script>

</head>

<body onresize="resize ()">

<input id="Clipboard">

<div id="Greeting">
	<div style="text-align: center">
		<h2>SymPad</h2>
		<h5><script type="text/javascript">document.write (Version)</script></h5>
		<h5>on SymPy <script type="text/javascript">document.write (SymPyVersion)</script></h5>
		<script type="text/javascript">if (SymPyVersion !== SymPyDevVersion) { document.write ('<br><h5 style="color: #f99">* Note: This version of SymPad was developed on SymPy 1.7.1,</h5><h5 style="color: #f99">it may not work correctly with your version of SymPy.</h5>'); }</script>
		<br><br>
		Type '<b><a class="GreetingA" style="display: inline" href="/help.html" target="_blank">help</a></b>' or '<b>?</b>' at any time for more information.
		<br>
		- or -
		<br>
		Type or click any of the following to get started:
	</div>
	<br><br>
	<a class="GreetingA" href="javascript:inputting ('cos -pi', true)">cos -pi</a>
	<a class="GreetingA" href="javascript:inputting ('N cos**-1 -\\log_2 sqrt[4] 16', true)">N cos**-1 -\log_2 sqrt[4] 16</a>
	<a class="GreetingA" href="javascript:inputting ('expand ((1 + x)**4)', true)">expand ((1 + x)**4)</a>
	<a class="GreetingA" href="javascript:inputting ('factor (x^3 + 3y x^2 + 3x y^2 + y^3)', true)">factor (x^3 + 3y x^2 + 3x y^2 + y^3)</a>
	<a class="GreetingA" href="javascript:inputting ('series (e^x, x, 0, 5)', true)">series (e^x, x, 0, 5)</a>
	<a class="GreetingA" href="javascript:inputting ('Limit (\\frac1x, x, 0, dir=\'-\')', true)">Limit (\frac1x, x, 0, dir='-')</a>
	<a class="GreetingA" href="javascript:inputting ('\\sum_{n=0}**oo x^n / n!', true)">\sum_{n=0}**oo x^n / n!</a>
	<a class="GreetingA" href="javascript:inputting ('d**6 / dx dy**2 dz**3 x^3 y^3 z^3', true)">d**6 / dx dy**2 dz**3 x^3 y^3 z^3</a>
	<a class="GreetingA" href="javascript:inputting ('Integral (e^{-x^2}, (x, 0, \\infty))', true)">Integral (e^{-x^2}, (x, 0, \infty))</a>
	<a class="GreetingA" href="javascript:inputting ('\\int_0^\\pi \\int_0^{2pi} \\int_0^1 rho**2 sin\\phi drho dtheta dphi', true)">\int_0^\pi \int_0^{2pi} \int_0^1 rho**2 sin\phi drho dtheta dphi</a>
	<a class="GreetingA" href="javascript:inputting ('\\[[1, 2], [3, 4]]**-1', true)">\[[1, 2], [3, 4]]**-1</a>
	<a class="GreetingA" href="javascript:inputting ('Matrix (4, 4, lambda r, c: c + r if c &gt; r else 0)', true)">Matrix (4, 4, lambda r, c: c + r if c &gt; r else 0)</a>
	<a class="GreetingA" href="javascript:inputting ('f (x, y) = sqrt (x**2 + y**2)', true)">f (x, y) = sqrt (x**2 + y**2)</a>
	<a class="GreetingA" href="javascript:inputting ('solve (x**2 + y = 4, x)', true)">solve (x**2 + y = 4, x)</a>
	<a class="GreetingA" href="javascript:inputting ('dsolve (y(x)\'\' + 9y(x))', true)">dsolve (y(x)'' + 9y(x))</a>
	<a class="GreetingA" href="javascript:inputting ('y = y(t); dsolve (y\'\' - 4y\' - 12y = 3e**{5t}); del y', true)">y = y(t); dsolve (y'' - 4y' - 12y = 3e**{5t}); del y</a>
	<a class="GreetingA" href="javascript:inputting ('pdsolve (x * d/dx u (x, y) - y * d/dy u (x, y) + y**2u (x, y) - y**2)', true)">pdsolve (x * d/dx u (x, y) - y * d/dy u (x, y) + y**2u (x, y) - y**2)</a>
	<a class="GreetingA" href="javascript:inputting ('(({1, 2, 3} &amp;&amp; {2, 3, 4}) ^^ {3, 4, 5}) - \\{4} || {7,}', true)">(({1, 2, 3} &amp;&amp; {2, 3, 4}) ^^ {3, 4, 5}) - \{4} || {7,}</a>
	<a class="GreetingA" href="javascript:inputting ('simplify (not (not a and not b) and not (not a or not c))', true)">simplify (not (not a and not b) and not (not a or not c))</a>
	<a class="GreetingA" href="javascript:inputting ('plotf (2pi, -2, 2, sin x, \'r=sin\', cos x, \'g=cos\', tan x, \'b=tan\')', true)">plotf (2pi, -2, 2, sin x, 'r=sin', cos x, 'g=cos', tan x, 'b=tan')</a>
	<br>
	<a class="GreetingA" href="/help.html#More%20Examples" target="_blank">More Examples...</a>
	<!--
	<a class="GreetingA" href="javascript:inputting ('
	', true)">
	</a>
	-->

	<br><br>
	<div style="text-align: center">
	Copyright (c) 2019 Tomasz Pytel. <a href="https://github.com/Pristine-Cat/SymPad" target="_blank" style="color: #0007">SymPad on GitHub</a>
	</div>
</div>

<div id="Log"></div>

<div id="InputCover"></div>
<div id="InputCoverLeft"></div>
<div id="InputCoverRight"></div>
<input id="Input" oninput="inputting (this.value)" autofocus>
<div id="InputOverlay"><span id="OverlayGood"></span><span id="OverlayError"></span><span id="OverlayAutocomplete"></span></div>

<div id="VarDiv">
	<div id="VarContent"><table id="VarTable" class="VarTable"></table></div>
	<div id="VarTab" onclick="Variables.toggle ()">Variables</div>
	<div id="VarTabShadow">Variables</div>
</div>

</body>
</html>""".encode ("utf8"),

	'help.html': # help.html

r"""<!DOCTYPE html>
<html>
<head>

<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link rel="icon" href="https://www.sympy.org/static/SymPy-Favicon.ico">
<title>SymPad Help</title>
<link rel="stylesheet" type="text/css" href="style.css">

<style>
	body { margin: 3em 4em; }
	h2 { margin: 2em 0 1em 0; }
	h4 { margin: 1.5em 0 0.75em 0; }
	p { margin: 0 0 1.2em 1em; line-height: 150%; }
	table { margin: 0 0 1.2em 1em; line-height: 150%; }
	del { color: red; }
	.red { color: red; }
</style>

<script type="text/javascript" src="https://code.jquery.com/jquery-3.4.1.js" integrity="sha256-WpOohJOqMqqyKL9FccASB9O0KwACQJpFTUBLTYOVvVU=" crossorigin="anonymous"></script>
<script type="text/javascript" src="script.js"></script>
<script type="text/javascript" src="env.js"></script>
<script type="text/x-mathjax-config">
	MathJax.Hub.Config ({
		messageStyle: "none",
		tex2jax: {inlineMath: [["$","$"], ["\\(","\\)"]]}
	});
</script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/latest.js?config=TeX-AMS_CHTML-full"></script>

</head>

<body>

<input id="Clipboard">

<h1 style="margin: 0; text-align: center">SymPad</h1>
<h4 style="margin: 0; text-align: center"><script type="text/javascript">document.write (Version)</script></h4>

<h2 id="Introduction">Introduction</h2>

<p>
SymPad is a simple single script graphical symbolic calculator / scratchpad using SymPy for the math, MathJax for the display in a browser and matplotlib for plotting.
It is a labor of love and grew out of a desire for an easy way to calculate a quick integral while studying some math without having to start a shell every time and import a package or fire up a browser and navigate to a site (technincally that last bit is exactly what happens but the response time is better :)
This desire for simplicity led to the single script option "<b>sympad.py</b>" which I could plop down on the desktop and execute when needed.
User input is intended to be quick, easy and intuitive and is displayed in symbolic form as it is being entered.
Sympad will accept Python expressions, LaTeX formatting, Unicode math symbols and a native shorthand intended for fast entry, or a mix of all of these.
The input will be evaluated symbolically or numerically with the results being copy/pasteable in Python or LaTeX formats, so it acts as a translator as well.
</p><p>
You don't need to know SymPy to use SymPad, you can just check out the examples here to see how to do something, but if you are familiar with it then it will definitely help since in theory most SymPy functionality is supported but not necessarily documented here.
For more information on SymPy functions which are accessible through SymPad see the <a href="https://docs.sympy.org/latest/index.html" target="_blank">SymPy Documentation</a>.
If you want to jump in and see what SymPad can do then just go directly to <a href="#Quick Start">Quick Start</a>.
</p>

<h2 id="Table of Contents">Table of Contents</h2>

<h4><a href="#Introduction">Introduction</a></h4>

<h4><a href="#Table of Contents">Table of Contents</a></h4>

<h4><a href="#Quick Start">Quick Start</a></h4>
&emsp;<a href="#Usage">Usage</a><br>
&emsp;<a href="#How it Works">How it Works</a><br>
&emsp;<a href="#Quick Input Mode">Quick Input Mode</a><br>

<h4><a href="#Elements">Elements</a></h4>
&emsp;<a href="#Numbers">Numbers</a><br>
&emsp;<a href="#Booleans">Booleans</a><br>
&emsp;<a href="#Vectors and Matrices">Vectors and Matrices</a><br>
&emsp;<a href="#Piecewise Expressions">Piecewise Expressions</a><br>
&emsp;<a href="#Function Types">Functions</a><br>
&emsp;<a href="#Strings, Lists, Tuples and Dictionaries">Strings, Lists, Tuples and Dictionaries</a><br>
&emsp;<a href="#Variables">Variables</a><br>
&emsp;<a href="#Symbols and Assumptions">Symbols and Assumptions</a><br>

<h4><a href="#Operations">Operations</a></h4>
&emsp;<a href="#Addition and Multiplication">Addition and Multiplication</a><br>
&emsp;<a href="#Exponentiation">Exponentiation</a><br>
&emsp;<a href="#Logarithms">Logarithms</a><br>
&emsp;<a href="#Roots">Roots</a><br>
&emsp;<a href="#Factorial">Factorial</a><br>
&emsp;<a href="#Absolute Value">Absolute Value</a><br>
&emsp;<a href="#Limits">Limits</a><br>
&emsp;<a href="#Sums">Sums</a><br>
&emsp;<a href="#Differentiation">Differentiation</a><br>
&emsp;<a href="#Integration">Integration</a><br>
&emsp;<a href="#Logic Operations">Logic Operations</a><br>
&emsp;<a href="#Comparison">Comparison</a><br>
&emsp;<a href="#Set Operations">Set Operations</a><br>
&emsp;<a href="#Substitution">Substitution</a><br>
&emsp;<a href="#Parentheses">Parentheses</a><br>
&emsp;<a href="#Indexing">Indexing</a><br>
&emsp;<a href="#Member Access">Member Access</a><br>
&emsp;<a href="#Variable Assignment">Variable Assignment</a><br>
&emsp;<a href="#Automatic Simplification">Automatic Simplification</a><br>
&emsp;<a href="#More...">More...</a><br>

<h4><a href="#Functions">Functions</a></h4>
&emsp;<a href="#SymPy Functions">SymPy Functions</a><br>
&emsp;<a href="#Lambda Functions">Lambda Functions</a><br>
&emsp;<a href="#Undefined Functions">Undefined Functions</a><br>
&emsp;<a href="#Pseudo-Functions">Pseudo-Functions</a><br>
&emsp;<a href="#Functions, Parentheses and Implicit Multiplication">Functions, Parentheses and Implicit Multiplication</a><br>

<h4><a href="#Plotting">Plotting</a></h4>
&emsp;<a href="#plotf() - Plot Function">plotf() - Plot Function</a><br>
&emsp;<a href="#plotv() - Plot Vector Field (2D)">plotv() - Plot Vector Field (2D)</a><br>
&emsp;<a href="#plotw() - Plot Walk Over Vector Field">plotw() - Plot Walk Over Vector Field</a><br>

<h4><a href="#More Examples">More Examples</a></h4>
&emsp;<a href="#Limits, Sums, Derivatives and Integrals">Limits, Sums, Derivatives and Integrals</a><br>
&emsp;<a href="#Solving Equations">Solving Equations</a><br>
&emsp;<a href="#Ordinary Differential Equations">Ordinary Differential Equations</a><br>
&emsp;<a href="#Partial Differential Equations">Partial Differential Equations</a><br>
&emsp;<a href="#Calculating Eigenvalues and Eigenvectors">Calculating Eigenvalues and Eigenvectors</a><br>
&emsp;<a href="#Lambda Function Examples">Lambda Function Examples</a><br>
&emsp;<a href="#Pseudo-Function Examples">Pseudo-Function Examples</a><br>
&emsp;<a href="#Last Expression Variable">Last Expression Variable</a><br>
&emsp;<a href="#Plotting Functions">Plotting Functions</a><br>
&emsp;<a href="#Plotting Vector Walks">Plotting Vector Walks</a><br>

<h4><a href="#Appendix">Appendix</a></h4>
&emsp;<a href="#Assumptions">Assumptions</a><br>
&emsp;<a href="#Special Characters">Special Characters</a><br>
&emsp;<a href="#Admin Functions">Admin Functions</a><br>
&emsp;<a href="#Environment Settings for env()">Environment Settings for env()</a><br>
&emsp;<a href="#Command Line Arguments">Command Line Arguments</a><br>
&emsp;<a href="#Notes">Notes</a><br>

<h2 id="Quick Start">Quick Start</h2>

<p>
The best way to see what SymPad can do is by doing, so try entering any of the following into SymPad (you can click on them to copy for pasting into SymPad):
</p><p>
<span onclick="cE2C (this)">cos -pi</span><i>&emsp; functions with a single argument don't need parentheses </i><br>
<span onclick="cE2C (this)">N cos**-1 -\log_2 sqrt[4] 16</span><i>&emsp; they can be chained</i><br>
<span onclick="cE2C (this)">factor (x^3 + 3y x^2 + 3x y^2 + y^3)</span><i>&emsp; implicit multiplication without the need for * operator</i><br>
<span onclick="cE2C (this)">Limit (\frac1x, x, 0, dir='-')</span><i>&emsp; mix Python and LaTeX</i><br>
<span onclick="cE2C (this)">\sum_{n=0}**oo x**n / n!</span><i>&emsp; ** is quicker to type than ^</i><br>
<span onclick="cE2C (this)">d**3 / dx dy^2 x^3 y^3</span><i>&emsp; standard derivative shorthand</i><br>
<span onclick="cE2C (this)">Integral (e^{-x^2}, (x, 0, \infty))</span><i>&emsp; SymPy integral object or function</i><br>
<span onclick="cE2C (this)">\int^\pi \int^{2pi} \int^1 rho**2 sin\phi drho dtheta dphi</span><i>&emsp; or standard notation</i><br>
<span onclick="cE2C (this)">\[[1, 2], [3, 4]]**-1</span><i>&emsp; quick matrix shorthand</i><br>
<span onclick="cE2C (this)">Matrix (4, 4, lambda r, c: c + r if c &gt; r else 0)</span><i>&emsp; matrix created from a lambda that populates each element based on row and column</i><br>
<span onclick="cE2C (this)">f (x, y) = sqrt (x**2 + y**2)</span><i>&emsp; assign functions intuitively</i><br>
<span onclick="cE2C (this)">f (3, 4)</span><i>&emsp; and then call them</i><br>
<span onclick="cE2C (this)">solve (x**2 + y = 4, x)</span><i>&emsp; solve equations</i><br>
<span onclick="cE2C (this)">dsolve (y(x)'' + 9y(x))</span><i>&emsp; differential equations</i><br>
<span onclick="cE2C (this)">y = y(t); dsolve (y'' - 4y' - 12y = 3e**{5t}); del y</span><i>&emsp; using prime notation</i><br>
<span onclick="cE2C (this)">pdsolve (x * d/dx u (x, y) - y * d/dy u (x, y) + y**2u (x, y) - y**2)</span><i>&emsp; partial differential equations</i><br>
<span onclick="cE2C (this)">simplify (not (not a and not b) and not (not a or not c))</span><i>&emsp; simplify complicated logic</i><br>
<span onclick="cE2C (this)">(({1, 2, 3} && {2, 3, 4}) ^^ {3, 4, 5}) - \{4} || {7,}</span><i>&emsp; set operations</i><br>
<span onclick="cE2C (this)">plotf (2pi, -2, 2, sin x, 'r=sin', cos x, 'g=cos', tan x, 'b=tan')</span><i>&emsp; plotting (if you have matplotlib installed)</i><br>
</p><p>
For more detailed examples see <a href="#More Examples">More Examples</a>.
</p>

<h4 id="Usage">Usage</h4>

<p>
You enter expresstions and they get evaluated.
The expressions may be in normal Pythonic style like "<b>a * (b + sin (x)**2 + 3/4) / 2</b>", LaTeX such as "<b>a\frac{b+\sin^2{x}+\frac34}{2}</b>" or a mix "<b>a * (b + \sin**2{x} + \frac34) / 2</b>".
The input is displayed symbolically as you type.
Input history is supported with the up and down arrows.
SymPad will give some limited options for autocompletion for certain expressions sometimes, this mostly means that you will not have to close parentheses.
Likewise, syntax and other errors in the expression will be highlited in red.
If an expression contains a some kind of usually non-grammatical error you can sometimes get some information by trying to submit the expression with the "<b>Enter</b>" key.
</p><p>
The symbolic expressions can be copied to the clipboard in various formats.
Single-click for a simple native format meant to be pasted back into the input field.
A double click-copies the expression in Python format suitable for pasting into a Python shell or source file.
Finally, a triple-click will copy the expression in LaTeX format.
The single-click native and double-click Python formats should almost always be pasteable back into SymPad, whereas the LaTeX format may or may not be depending on what elements are present.
</p><p>
Variables and functions can be assigned with the "<b>=</b>" operator and will be substituted or executed upon use after assignment, so if you assign "<b>x = 5</b>" then any expression afterwards containing "<b>x</b>" will use the value "<b>5</b>" in its place.
Likewise setting "<b>f (x) = x**2</b>" will then execute the function anytime it is used so that "<b>f (3)</b>" becomes "<b>9</b>".
Any variable assignments will appear in a hideable "<b>Variables</b>" tab in the upper right hand corner of the browser.
The multi-click copy mechanism also works in this tab where you can copy the whole assignment expression by clicking on the variable or just the contents of the variable by clicking on the expression.
</p>

<h4 id="How it Works">How it Works</h4>

<p>
If you are not familiar with SymPy then you can skip this section as it talks about internal SymPy usage.
</p><p>
SymPad will parse the expression you type into an internal representation for autocompletion, error checking and translation of certain functions and elements to be displayed symbolically.
When you attempt to evaluate an expression via the "<b>Enter</b>" key then that internal representation is converted into SymPy objects and a "<b>doit (deep = True)</b>" is executed on the top-level object.
This top-level "<b>doit ()</b>" is optional and can be turned off via the SymPad environment mechanism but is usually what you want in a calculator like SymPad.
There is another step which can be carried out automatic on the resulting object after the "<b>doit ()</b>" which is a "<b>simplify ()</b>", as many times SymPy evaluations result in messy expressions.
This simplification step is off by default since it can mess up certain equations which are specifically returned in a certain format, but can be turned on in SymPad via the environment mechanism.
After this the resulting SymPy object is converted back to the internal representation and any translations for symbolic display are carried out again.
</p><p>
SymPad does some internal gymnastics to allow certain SymPy operations to work with native Python objects with which they normally do not.
This is mostly addition or multiplication of tuples or lists, indexing with unknown symbols, extending simplification and substitution and doit into Python containers and the like, housekeeping stuff.
Arithmetic with "<b>Boolean</b>" types is hacked in as well as automatic matrix simplification (enabled or disabled in the environment) since those tend to blow up pretty quickly in SymPy.
Many SymPy functions are translated normally for display or quick shorthand for copying but SymPad attempts to preserve as much of any original Python code as possible from the expression to pass on to creating the evaluation SymPy objects.
</p><p>
Any variables used in an expression are normally converted to SymPy "<b>Symbol</b>" objects unless they are assigned in which case they are substituted for the assigned expression.
Functions assigned as variables such as "<b>f (x) = x**2</b>" are technically mapped internally as "<b>f = Lambda (x, x**2)</b>" and these lambdas can be passed into native SymPy functions and will act as expected, which means they can be used to populate a "<b>Matrix</b>" for example.
User assigned functions like this can be assigned, copied and passed into SymPy functions but the concrete SymPy classes and functions cannot.
For more on the differences between the various types of functions see the <a href="#Functions">Functions</a> section.
</p>

<h4 id="Quick Input Mode">Quick Input Mode</h4>

<p>
This is the input mode SymPad was born in for quick calculations due to speed of input.
SymPad normally allows multi-character variable names, requires spaces between them for implicit multiplication and enforces grammatical breaks between function names, variables and text operators like "<b>in</b>" and "<b>or</b>".
This can be turned off by switching into quick input mode using the function "<b>env(quick)</b>" or by using the "<b>--quick</b>" option on the command line.
When in this input mode long variable names are sacrificed for quicker input of single letter variables (Latin or Greek - with or without leading slash) and explicit space characters are no longer necessary between variables, recognized function names and text operators.
</p><p>
What quick mode allows is the input of expressions which would normally by entered in normal mode like this "<b>x y sin**-1z cos e**2</b>" without grammatical breaks - "<b>xysin**-1zcose**2</b>".
For convenience certain multi-character variables are accepted in quick mode - Greek letters (including "<b>pi</b>"), "<b>oo</b>" for infinity and "<b>zoo</b>" for complex infinity, "<b>partial</b>", "<b>True</b>", "<b>False</b>" and "<b>None</b>".
Differentials will also work so "<b>dx</b>", "<b>dtheta</b>", "<b>d\theta</b>" and the partial variations like "<b>partialx</b>" and "<b>\partial x</b>" will all be considered as a single variable.
Variables in quick mode can only be one letter but they may have optional numerical tails which will be displayed as subscripts, so "<b>x12</b>" ($x_{12}$) and "<b>alpha_{3}</b>" ($\alpha_{3}$) are valid variables in quick mode, this extends to differentials and partials as well.
</p>

<h2 id="Elements">Elements</h2>

<h4 id="Numbers">Numbers</h4>

<p>
Numbers take the standard integer or floating point form or exponential form such as 123, -2.567, 1e+100, 3E-45 or -1.521e22.
The precision for all SymPy Floats used in evaluation is set to the highest precision number present in the equation or referenced variables, so if you ask for the cosine of a number with 50 decimal digits your answer will have at least 50 decimal digits (where SymPy respects this).
</p><p>
Keep in mind that "<b>e</b>" or "<b>E</b>" is the Euler"s number constant $e$ and if you are trying to enter 2 times $e$ plus 22 then do not write it all together as "<b>2e+22</b>" as this will be interpreted to be "<b>2 * 10^22</b>".
Instead, use spaces and/or explicit multiplication: "<b>2 * e + 22</b>".
Imaginary numbers are entered using the imaginary unit "<b>i</b>" or "<b>I</b>" depending on preference, no Pythonic "<b>j</b>" option at the moment but it can be hacked by setting "<b>j = i</b>" in SymPad.
</p>

<h4 id="Booleans">Booleans</h4>

<p>
Your standard True and False with SymPy Boolean results being coerced to these.
SymPad modifies the SymPy Boolean classes to allow basic arithmetic so you can use the results of comparisons in expressions and they are automatically cast to 1 and 0 just like in Python.
</p>

<h4 id="Vectors and Matrices">Vectors and Matrices</h4>

<p>
Vectors are passed as a single level of brackets such as "<b>\[1, 2]</b>" or "<b>\[x, y, z]</b>" and these are interpreted as column matrices.
Matrices are passed as nested rows of brackets with the first bracket being preceeded by a slash.
A 2x3 matrix would be specified as  "<b>\[[1, 2, 3], [4, 5, 6]]</b>", a 1x3 would be "<b>\[[1, 2, 3]]</b>" and a 3x1 would be either "<b>\[[1], [2], [3]]</b>" or the easier to write "<b>\[1, 2, 3]</b>" since this is equivalent.
These can also be entered using LaTeX "<b>\<span></span>begin{(v|b|p|)matrix} \<span></span>end{(v|b|p|)matrix}</b>" format.
</p>

<h4 id="Piecewise Expressions">Piecewise Expressions</h4>

<p>
These are supported and can be entered as SymPy "<b>Piecewise</b>" functions, LaTeX "<b>\<span></span>begin{cases} \<span></span>end{cases}</b>" notation or the native Python-like conditional expressions of the form "<b>a if condition else b</b>".
They may be arbitrarily long - "<b>a if condition1 else b if condition2 else ...</b>" and may leave off the last "<b>else</b>" which is equivalent to a SymPy "<b>Piecewise</b>" function without a terminating "<b>True</b>" condition.
</p>

<h4 id="Function Types">Fuctions</h4>

<p>
Apart from the top level SymPy functions which are available for calling but not as objects to be passed around or for assigning, there are two other types of function objects which can be manipulated in SymPad.
There are lambda functions which can be assigned expressions and executed in SymPad like normal SymPy functions, and even passed into Python code to be executed like normal Python lambdas.
There are also undefined mathematical function objects to be used mostly in differential equations.
These last two types can be assigned and passed around in SymPy just like any other type, unlike top-level SymPy module functions which can only be called.
The details of their operations are discussed in the <a href="#Functions">Functions</a> section below.
</p>

<h4 id="Strings, Lists, Tuples and Dictionaries">Strings, Lists, Tuples and Dictionaries</h4>

<p>
These exist for the sole purpose of passing arguments to SymPy functions, not really to be operated on (though basic operations work).
Strings work as expected being enclosed by single or double quotes and supporting escape sequences, for example "<b>Limit (1/x, x, 0, '-')</b>".
Standard Python bracket enclosed lists and optionally parentheses enclosed tuples are accepted.
Like strings these exist for the purpose of passing parameters to functions like "<b>Matrix ([[1, 2], [3, 4]])</b>".
Standard Python dictionaries are entered using the same format as Python "<b>{a: b, c: d}</b>" and like strings, lists and tuples these exist for the purpose of passing and recieving parameters to and from functions.
</p>

<h4 id="Sets">Sets</h4>

<p>
These are not the standard Python sets but rather are represented internally by the SymPy object "<b>FiniteSet</b>".
They can be entered via said "<b>FiniteSet</b>" function or via standard Python syntax for sets containing more than one element like "<b>{a, b, c}</b>".
To enter a set of zero or one element use the slash-curly set override syntax to open the set as such "<b>\{1}</b>" or "<b>{1,}</b>" for a one-element set or "<b>\{}</b>" for the empty set, otherwise for one element the curlys would be interpreted as parentheses and just like in Python the "<b>{}</b>" string is an empty dictionary, not a set.
As said these are converted to "<b>FiniteSet</b>" for evaluation. If however you need a true Python set for some reason then wrap the set or any other iterable in the Python "<b>set()</b>" function, this will always result in a Python set inside the calculations, though it will be returned to SymPy as a "<b>FiniteSet</b>" after the calculation completes.
</p><p>
The following default sets are available at the top level: "<b>Complexes</b>", "<b>Reals</b>", "<b>Naturals</b>", "<b>Naturals0</b>" and "<b>Integers</b>".
</p>

<h4 id="Variables">Variables</h4>

<p>
Variable names can be a mix of Python and LaTeX format, they can be multi-character but cannot start or end with an underscore "<b>_</b>", or start with a number, though they may contain those.
Standard identifiers are accepted as well as single Greek letters optionally preceded by a slash such as "<b>\alpha</b>" ($\alpha$), "<b>epsilon</b>" ($\epsilon$) or "<b>\Psi</b>" ($\Psi$).
The Greek letters recognized and rendered in ... Greek ... are only those normally present within LaTeX which can not be visually confused with Latin letters, which Greek letters are accepted in Unicode as well.
Variables names with a trailing number are displayed with that number as a subscript, and parsing of a variable from LaTeX format with a numerical subscript like "<b>x_{2}</b>" results in a variable name of "<b>x2</b>", currently these subscripts can only be non-negative integers.
</p><p>
The variable names "<b>i</b>", "<b>e</b>" and "<b>\pi</b>" represent their respective mathematical constants $i$, $e$ and $\pi$.
"<b>pi</b>", "<b>oo</b>" and "<b>zoo</b>" are also available for $\pi$, $\infty$ and $\widetilde\infty$.
Python's "<b>None</b>", "<b>True</b>" and "<b>False</b>" are also present, as well as "<b>nan</b>".
By default, the lowercase "<b>e</b>" and "<b>i</b>" letters are used to represent Euler's number and the imaginary unit instead of the default SymPy uppercase "<b>E</b>" and "<b>I</b>".
This is objectively prettier, but can be changed via the "<b>env (EI)</b>" and "<b>env (noEI)</b>" function.
The SymPy constant usage can also be activated via the command line switch "<b>--EI</b>".
</p><p>
Differentials are entered as "<b>dx</b>", "<b>partialx</b>", "<b>\partialx</b>", "<b>\partial x</b>" or "<b>∂x</b>" and are treated as a single variable.
If you want to enter "<b>d</b>" * "<b>x</b>" multiplied implicitly then put a space between them or two spaces between the "<b>\partial</b>" and the "<b>x</b>".
There is nothing special about differential variables other than their specific meaning in differentiation and integration.
</p><p>
Variables may be assigned values, references to other variables or even entire expressions which will subsequently be substituted for those variables in any future expression evaluation.
When variable assignments exist then any instance of that variable used almost anywhere will be replaced with the value of the variable, this can lead to errors if you forgot a variable is assigned and try to use it as a free variable.
For example, when "<b>x</b>" is not assigned to anything then "<b>series (e**x, x, 0, 5)</b>" will give the correct answer, but set "<b>x = 1</b>" and all of a sudden you have a different result, set it to 2 and you have an error.
For more on this see <a href="#Variable Assignment">Variable Assignment</a>.
</p><p>
Top level SymPy function names can be used as variables with some restrictions.
This means you can do "<b>cos = 2</b>" and then "<b>cos**2</b>" will give you "<b>4</b>".
You can even reassign functions such as "<b>sin = cos</b>" and then "<b>sin (pi)</b>" will result in "<b>-1</b>".
The first restriction is that even if you have a function name mapped as a variable, because of the way parsing works parentheses following the variable name will always result in a call to the underlying function.
So even if you have set "<b>cos = 2</b>", the expression "<b>cos (pi)</b>" will still evaluate to "<b>-1</b>".
The second restriction is that if you want to assign a lambda function to a top-level SymPy function variable name you must use lambda assignment and not function assignment.
So for example "<b>cos = lambda x: x**2</b>" will work as expected and now "<b>cos (2)</b>" will result in "<b>4</b>", however trying to set this lambda by "<b>cos (x) = x**2</b>" will not work.
The final restriction is that you can not assign undefined functions to these function names, so no "<b>cos = ?(x, y)</b>".
</p>

<h4 id="Symbols and Assumptions">Symbols and Assumptions</h4>

<p>
Symbols are objects which represent a variable and if you use variables in an expression then you are implicitly using SymPy "<b>Symbol</b>" objects.
The default symbols which are created for normal variables like "<b>x</b>" however do not contain any extra information which may be useful for certain calculations.
This extra information comes in the form of SymPy assumptions which allow you to specify whether a variable is real or positive or commutative or hermitian etc...
To create a symbol with this extra information use the "<b>$</b>" symbol pseudo-function, the format is "<b>$name (assumption = True | False | None)</b>", so for example a variable "<b>x</b>" known to be real would be created as "<b>$x (real = True)</b>".
The name part of a "<b>$name (...)</b>" symbol is optional and you can use anonymous symbols without any problems.
The "<b>(...)</b>" part is also optional but if that is omitted then the symbol created is just the normal variable without any assumptions, which is normally not useful except if you have that variable already mapped to a symbol which does have some assumptions and is thus not equivalent.
</p><p>
The SymPy assumption model works on fuzzy boolean logic where "<b>True</b>" means that the assumption is true, "<b>False</b>" means that it is false and "<b>None</b>" means that it is unknown whether the assumption is true or false.
It is the true and false values for the assumptions which can simplify calculations or even make them possible whereas they would not normally be so if the information were not provided.
To get an idea for how the assumptions operate try entering "<b>$(negative = True) &lt; 0</b>" and see what you get, this evaluates to "<b>True</b>" because all negative numbers are smaller than "<b>0</b>".
The only assumption that is made for default symbols which are created is "<b>commutative = True</b>", but of course you can create a symbol where this is false, though it can not be unknown.
</p><p>
It is not convenient to have to type all this every time you reference a symbol in an expression so of course these symbols may be assigned to variables, even variables of the same name (where normally this is disallowed).
In fact for the sake of sanity it is suggested that you give "<b>$</b>" symbols the same name as the variable you assign them to, though this is not enforced and you can even reassign them from one variable to another.
Thus if you do "<b>x = $x (real = True)</b>" then from that point on any time you use the "<b>x</b>" variable in an expression it will be assumed to be real until you delete the variable.
As a convenience any time you assign an anonymous symbol "<b>$(something = True)</b>" to a variable then the symbol will take on the name of the variable you are assigning it to.
Any symbols with assumptions coming back from SymPy calculations are automatically mapped back to the variables to which they are assigned, if any, in order to keep things tidy.
</p><p>
For a list of assumptions you can assign to symbols see <a href="#Assumptions">Assumptions</a>.
</p>

<h2 id="Operations">Operations</h2>

<p>
What follows is a list of common mathematical operations which SymPy facilitates via a quick input shorthand vs. having to write out entire functions in SymPy.
This is not a list of everything which can be done in SymPad, since SymPad sits on top of SymPy which provides a large library of mathematical operations, see <a href="https://docs.sympy.org/latest/index.html" target="_blank">SymPy Documentation</a> for more information and experiment.
</p>

<h4 id="Addition and Multiplication">Addition and Multiplication</h4>

<p>
Addition is addition and subtraction is subtraction: "<b>a + b</b>", "<b>a - b</b>".
Multiplication is explicit with a "<b>*</b>" operator or implicit simply by writing two symbols next to each other with a space in between so that "<b>a * b</b>" is the same as "<b>a b</b>".
There is however a difference between the two in that the implicit version has a higher precedence than the explicit, which means that explicit multiplication will end a limit, sum, derivative or division "<b>/</b>" expression whereas implicit multiplication will not, e.g. "<b>1/x y</b>" = $\frac{1}{x y}$ whereas "<b>1/x*y</b>" = $\frac{1}{x} \cdot y$.
</p><p>
Division also has two operators, the normal "<b>/</b>" which has a fairly low precedence and the LaTeX "<b>\frac</b>" version which has a very high precedence, even higher than exponentiation.
So high in fact that parentheses are not needed if using "<b>\frac</b>" as an exponent as in "<b>x**\frac{3}{2}</b>" = $x^\frac{3}{2}$, whereas "<b>x**3/2</b>" is $\frac{x^3}{2}$.
The "<b>\frac</b>" operation also does not need parentheses if using single digit operands or single letter variables (Latin or Greek) such as "<b>\frac12</b>" = $\frac12$ or "<b>\frac\alpha\beta</b>" = $\frac\alpha\beta$.
</p>

<h4 id="Exponentiation">Exponentiation</h4>

<p>
There are two power opearators "<b>^</b>" and "<b>**</b>".
They have the same precedence and can be used interchangeably but follow slightly different parsing rules.
The "<b>^</b>" operator follows LaTeX rules which only allow a single positive digit or letter variable (Latin or Greek) without the use of curly braces whereas the "<b>**</b>" follows Python rules which allow negative values or variables or functions.
To illustrate the diffference: "<b>x**ab</b>" = $x^{ab}$ whereas "<b>x^ab</b>" parses to $x^ab$.
Also, "<b>e**ln(x)</b>" will work as expected $e^{\ln(x)}$ whereas "<b>e^ln(x)</b>" = $e^ln(x)$.
</p>

<h4 id="Logarithms">Logarithms</h4>

<p>
The natural logarithm of x is specified by "<b>ln x</b>", "<b>\ln x</b>", "<b>log x</b>", "<b>\log{x}</b>". A logarithm in a specific base is specified
by "<b>\log_b x</b>" = $\log_b x$, "<b>\log_{10}(1000)</b>" = $\log_{10}{1000}$ = 3, etc...
</p>

<h4 id="Roots">Roots</h4>

<p>
The square root of x ($\sqrt{x}$) may be entered in any of these forms "<b>sqrt x</b>", "<b>\sqrt x</b>", "<b>sqrt (x)</b>", "<b>\sqrt{x}</b>", with or without the slash.
The cube (or any other) root is similar, $\sqrt[3]x$ = "<b>sqrt[3]x</b>", "<b>sqrt[3] (x)</b>" or "<b>\sqrt[3] {x}</b>".
</p>

<h4 id="Factorial">Factorial</h4>

<p>
"<b>4!</b>" = "<b>24</b>", "<b>x!</b>" = "<b>factorial(x)</b>", "<b>(-0.5)!</b>" = "<b>1.77245385090552</b>" and "<b>simplify(x!/x)</b>" = "<b>gamma(x)</b>".
</p>

<h4 id="Absolute Value">Absolute Value</h4>

<p>
The shorthand for the absolute value of "<b>x</b>" is "<b>|x|</b>" though this may be ambiguous if using multiple absolute value bars, in which case simply wrap it in curly braces or parentheses as such "<b>{|x|}</b>".
</p>

<h4 id="Limits">Limits</h4>

<p>
To take the limit of an expression "<b>z</b>" as variable "<b>x</b>" approaches "<b>y</b>" enter "<b>\lim_{x \to y} (z)</b>" = $\lim_{x\to y} (z)$.
This will only give the limit if it exists and is the same when approaching from both directions, unlike SymPy which defaults to approaching from the positive direction.
To specify a direction add "<b>^+</b>" or "<b>^-</b>" to the equation as such: "<b>\lim_{x \to 0^+} 1/x</b>" = $\lim_{x\to 0^+} \frac1x$ = $\infty$ and "<b>\lim_{x \to 0^-} 1/x</b>" = $\lim_{x\to 0^-} \frac1x$ = $-\infty$.
Addition and explicit multiplication terminate a limit expression.
Limits may also be entered using the standard SymPy syntax "<b>Limit (expression, variable, to)</b>", this defaults to limit from positive direction like SymPy, or you may specify a direction "<b>Limit (expression, variable, to, dir='+-')</b>".
SymPad "<b>\lim</b>" expressions are delimited by explicit multiplication or any operation with lower precedence like addition, set operation, comparisons, etc... so if you want to include those in the limit you need to parenthesize the limit expression like "<b>\lim_{x\to oo} (1/x + x)</b>".
</p>

<h4 id="Sums">Sums</h4>

<p>
The summation (finite or infinite) of expression "<b>z</b>" as variable "<b>n</b>" ranges from "<b>a</b>" to "<b>b</b>" is written as "<b>\sum_{n=a}^b
(z)</b>" = $\sum_{n=a}^b (z)$. Iterated sums work as expected, "<b>\sum_{n=1}^3 \sum_{m=1}^n m</b>" = $\sum_{n=1}^3 \sum_{m=1}^n m$ = 10. Addition and
explicit multiplication terminate a sum expression.
Sums may also be entered using the standard SymPy syntax "<b>Sum (expression, (variable, from, to))</b>".
Like limits, the "<b>\sum</b>" expression is normally delimited by explicit multiplication and below, so use parentheses to include those operations in the expression which is being summed over.
</p>

<h4 id="Differentiation">Differentiation</h4>

<p>
The derivative of expression "<b>z</b>" with respect to "<b>x</b>" is entered as "<b>d/dx z</b>" or "<b>\frac{d}{dx} z</b>" = $\frac{d}{dx} z$.
The second derivative is "<b>d^2/dx^2 (z)</b>" or "<b>\frac{d^2}{dx^2} (z)</b>" = $\frac{d^2}{dx^2} (z)$. Using "<b>\partial</b>" ($\partial$) is allowed but must be consistent within the expression.
Mixed derivatives are entered as "<b>d^2 / dx dy (z)</b>" or "<b>\partial^2 / \partial x\partial y (z)</b>" = $\frac{\partial^2}{\partial x\partial y} (z)$.
Derivatives may also be entered using any of the standard SymPy syntaxes like "<b>Derivative (expression, var1, var2, power2, ...)</b>".
Derivatives can also be specified using prime notation if the given expression has exactly one free variable to derivate, line "<b> sin (x)' </b>" or "<b> (x**3)'' </b>".
Standard $\frac{dy}{dx}$ and $\frac{d^2y}{dx^2}$ forms are also accepted as "<b>dy/dx</b>" and "<b>d**2y/dx**2</b>" and if the variable "<b>y</b>" contains an expression, lambda or undefined function then that will be differentiated.
</p>

<h4 id="Integration">Integration</h4>

<p>
The anti-derivative of expression "<b>z</b>" with respect to "<b>x</b>" is written as "<b>\int z dx</b>" = $\int z\ dx$.
The definite integral from "<b>a</b>" to "<b>b</b>" is "<b>\int_a^b z dx</b>" = $\int_a^b z\ dx$. "<b>\int dx/x</b>" = $\int \frac1x\ dx$.
The definite integral can also be specified without the base value as "<b>\int^b z dx</b>" in which case it will go from "<b>0</b>" to "<b>b</b>".
Iterated and improper integrals also work. Integrals may also be entered using the standard SymPy syntax "<b>Integral (expression, (variable, from, to), ...)</b>".
</p>

<h4 id="Logic Operations">Logic Operations</h4>

<p>
The standard Python "<b>or</b>", "<b>and</b>" and "<b>not</b>" operations are present but they are implemented using SymPy "<b>Or</b>", "<b>And</b>" and "<b>Not</b>" objects.
Though by default these objects work only on booleans and not things like lists or tuples or other objects, SymPad makes them work by coercing any objects which they do not accept to booleans.
</p>

<h4 id="Comparison">Comparison</h4>

<p>
Are parsed from the standard Python "<b>=, ==, !=, &lt;, &lt;=, &gt;, &gt;=</b>", LaTeX "<b>\ne, \neq, \lt, \le, \gt, \ge</b>" and also Unicode "<b>≠, ≤, ≥, ∈, ∉</b>" symbols.
Extended multiple argument comparison is supported like in Python "<b>x &lt; y &lt; z</b>", though as it is not natively supported by SymPy, SymPad works around this by converting these to "<b>And</b>" expressions of multiple infividual comparisons.
Note that the "<b>=</b>" and "<b>==</b>" operators are equivalent for SymPy and mapped to the same "<b>Eq</b>" object in expressions but the single "<b>=</b>" operator has a lower precedence is SymPad than the others and is used by for variable assignment whereas the double "<b>==</b>" only ever implies comparison.
Also, although SymPad can generally infer the meaning of the single "<b>=</b>" whether it is an assignment, a keyword argument to a function or an equation, to be absolutely sure that an equation is treated as such use the double "<b>==</b>" operator.
</p><p>
The membership test "<b>in</b>" and "<b>not in</b>" are related to the comparison operators in that they generate a boolean value and just like in Python they live on the same precedence level.
These may be entered like in Python, using the LaTeX versions "<b>\in</b>" and "<b>\notin</b>" or directly using the Unicode characters "<b>∈</b>" and "<b>∉</b>" and they test for membership in any sequence or iterable.
</p>

<h4 id="Set Operations">Set Operations</h4>

<p>
These basic set operations are supported: "<b>+</b>", "<b>||</b>", "<b>\cup</b>" or "<b>∪</b>" for union - "<b>{1, 2} || {2, 3} = {1, 2, 3}</b>".
"<b>&&</b>", "<b>\cap</b>" or "<b>∩</b>" for intersection - "<b>{1, 2} && {2, 3} = {2,}</b>".
"<b>^^</b>", "<b>\ominus</b>" or "<b>⊖</b>" for symmetric difference - "<b>{1, 2} ^^ {2, 3} = {1, 3}</b>".
"<b>-</b>" for relative complement - "<b>{1, 2} - {2, 3} = {1,}</b>".
The comparison operators can be applied to sets to test for subset "<b>&lt;</b>", proper subset "<b>&lt;=</b>", superset "<b>&gt;</b>" or proper superset "<b>&gt;=</b>".
The membership test operators "<b>in</b>" and "<b>not in</b>" work on sets as expected.
</p><p>
SymPy sets are a little broken in that the set operations will not accept undefined variables as stand-ins for sets to be evaluated later, which would mean that lambda functions using sets would not normally be possible.
A partial fix is to wrap the set operations in a lambda or variable assignment using the double no-evaluate pseudo function like this "<b>f = lambda a, b: %%(a || b)</b>", this will allow you to use the lambda but will still error out if you try to assign it to another
variable or view it.
</p>

<h4 id="Substitution">Substitution</h4>

<p>
Explicit substitution of variables and even whole expressions with other expressions is supported with the "<b>\. expr |_{x = y}</b>" shorthand.
This maps directly to SymPy substitution of the form "<b>Subs (expr, x, y)</b>" and will replace any occurrances of the variable "<b>x</b>" in the expression with the variable "<b>y</b>", assuming that "<b>x</b>" has not been globally mapped to something else.
The LaTeX syntax for this is "<b>\left. expr \right|_{x = y}</b>" with the "<b>\substack</b>" operator being accpeted to specify multiple ordered substitutions like "<b>\left. expr \right|_{\substack{x = y \\ z = w}}</b>".
Multiple substitutions can also be specified via comma-separated assignments like "<b>\. expr |_{x = y, z = w}</b>" or as a single tuple assignment "<b>\. expr |_{x, z = y, w}</b>".
The substitutions are not limited to variables, any expression can be substituted for any other expression, so "<b>\. 2x / 3 |_{2/3 = 5/7}</b>" will result in "<b>5x / 7</b>".
The substituted expressions need not even be exact as SymPy will attempt to discover an implicit variable mapping when it is executing the substitution and thus "<b>\. x |_{1/x = 2}</b>" will result in "<b>1/2</b>" even though "<b>x</b>" is not explicitly listed as a substitution.
</p>

<h4 id="Parentheses">Parentheses</h4>

<p>
Explicit "<b>( )</b>" or implicit curly "<b>{ }</b>" parentheses allow prioritization of lower precedence operations over higher ones as usual and also delineate an expression as an input to a function.
They may be used interchangeably for single expressions, the only difference being that the implicit version is not drawn if it does not need to be.
The case where explicit "<b>( )</b>" parentheses are needed ... explicitly ... is when calling functions in general and always when calling functions which take multiple parameters like "<b>max(1, 2, 3)</b>".
The curly braces are used as shorthand for sets and dictionaries if commas are present, but that is a different syntactic usage, curlys with no commas are essentially invisible parentheses.
Parentheses following undefined variables can also create undefined function objects if the contents of the parentheses are valid for such a definition, for more on this see <a href="#Undefined Functions">Undefined Functions</a>.
For more on parentheses in general see <a href="#Functions, Parentheses and Implicit Multiplication">Functions, Parentheses and Implicit Multiplication</a>.
</p>

<h4 id="Indexing">Indexing</h4>

<p>
Python style bracket indexing is natively supported for all objects using single or tuple indices and slices - "<b>'Hello'[::-1]</b>" = "<b>'olleH'</b>", "<b>\[[1, 2, 3], [4, 5, 6]] [1, 2]</b>" = "<b>6</b>" and "<b>\[[1, 2, 3], [4, 5, 6]] [:, 1:]</b>" = "<b>\[[2, 3], [5, 6]]</b>".
</p>

<h4 id="Member Access">Member Access</h4>

<p>
You can access member data or functions of an expression just like in Python with the "<b>.</b>" operator.
If the attribute name following the dot is followed by a parenthesized expression then it will be treated as a function call instead of an implicit multiplication, otherwise it is a data member.
For example, two ways to get the transpose of a matrix are "<b>\[[1, 2, 3], [4, 5, 6]].T</b>" and "<b>\[[1, 2, 3], [4, 5, 6]].transpose ()</b>".
</p>

<h4 id="Variable Assignment">Variable Assignment</h4>

<p>
Using the syntax "<b>var = expression</b>" you can assign some value to be substituted for that variable in all expressions.
For example, doing "<b>x = pi</b>" and then evaluating "<b>cos x</b>" will give you "<b>-1</b>".
Anything can be assigned to any valid variable like mathematical expressions, Python objects like strings or lists, user lambda functions or even references to other variables.
To delete an assignment use the "<b>del var</b>" function or "<b>del (var1, var2, ...)</b>" to delete multiple variables, to delete all variables use "<b>delall()</b>".
To see what variables are currently assigned use the "<b>vars()</b>" function.
</p><p>
Tuple assignment is supported and as in Python the source can be another tuple or a single iterable object like "<b>x, y = 1, 2</b>".
A useless example of iterable assignment would be setting "<b> a, b, c = 'str' </b>" which would give you "<b> a = 's' </b>", "<b> b = 't' </b>" and "<b> c = 'r' </b>".
</p><p>
There are two distinct types of assignment that can occur and you should be aware of the difference between them.
Copy assignment is the standard type of assignment used by default in most computer languages where if you start with "<b>x = 1</b>" and you then enter "<b>y = x</b>" then the value "<b>1</b>" will be copied to the "<b>y</b>" variable.
The value of "<b>y</b>" will be independent of whatever else happens to the variable "<b>x</b>" after this.
</p><p>
The other kind of assignment is a reference assignment which will map the source variable instead of copying its value to the target.
This means that if you have a reference set like "<b>y = x</b>" and the value of "<b>x</b>" changes then the value of "<b>y</b>" will reflect this new value as well.
The reference assignment happens if you try to assign variables which do not exist, so setting "<b>y = x</b>" before "<b>x</b>" has been created will result in a reference.
Otherwise you can force a reference by using the "<b>@</b>" pseudo-function.
Doing "<b>y = @x</b>" will create a reference to "<b>x</b>" itself instead of copying the value if it exists.
The "<b>@(expr)</b>" function technically prevents variable remapping for the expression it encompasses, so if you have the variable "<b>x = 2</b>" set and you do "<b>@x</b>" then you will get "<b>x</b>" and not "<b>2</b>".
</p><p>
Another potentially useful meta-function for assignment is "<b>%(expr)</b>".
This function defers evaluation of the expression thus allowing things like assigning an integral operation to a variable instead of the result of the integral - "<b>f = %(\int x dx)</b>".
In this way you can assign different values to "<b>x</b>" and have the integration happen automatically any time you access the "<b>f</b>" variable instead of just using the result of whatever variables were defined at the time of definition.
</p><p>
One more thing to be aware of is that differentials in derivatives and integrals get remapped based on the underlying variable if possible otherwise the original differential is used.
For example the derivative "<b>d/dx (y**2)</b>" will be treated as "<b>d/dy (y**2)</b>" if you have a global variable set "<b>x = y</b>", likewise "<b>\int y dx</b>" will be remapped to "<b>\int y dy</b>" in that case.
This also happens in lambda functions so that you can specify differentials to use (as non-d variables), see the <a href="#Lambda Function Examples">Lambda Function Examples</a> section for more on this.
</p><p>
If you assign an unnamed undefined function or symbol to a variable then that undefined function or symbol will take on with the name of the variable it is being assigned to, this is done for convenience and sanity.

</p><p>
One caveat to be aware of - since SymPad supports implicit multiplication without an explicit "*" operator and parentheses are used for grouping as well as function calls and definitions of undefined functions the underlying grammar is ambiguous and dependent on the current state of ssigned variables.
Parsing as well as writing out of expressions will vary according to what variables are mapped to so for example if the variable "<b>f</b>" is unassigned then "<b>f (x)</b>" is parsed as an undefined function "<b>f</b>" of one variable "<b>x</b>".
If the variable "<b>f</b>" is assigned to a user lambda function like "<b>lambda x: x**2</b> then "<b>f (x)</b>" is parsed as a lambda call and evaluates to "<b>x**2</b>".
If however "<b>f</b>" is assigned to something like "<b>2</b>" then "<b>f (x)</b>" is parsed as "<b>f * x</b>" and the result is "<b>2x</b>".
This dependence on variable state is taken into consideration when an expression is written out to Python, LaTeX or the shorthand format since it is written to be valid in the current variable context.
So for example for the three cases given the respective Python representations emitted would be "<b>Function('f')(x)</b>", "<b>f(x)</b>" (since the variable is assumed to contain the "<b>Lambda</b>" object it is just called) and "<b>f * (x)</b>" (again the variable is assumed to be set in Python to "<b>2</b>").
</p>

<h4 id="Automatic Simplification">Automatic Simplification</h4>

<p>
Apart from the explicit simplification you can always do via the "<b>simplify()</b>" function, SymPad can try to automatically simplify the result of any calculation before returning it.
This can be useful due to the fact that many times SymPy prefers to return results quicker and leaves the option to simplify to the user, whereas SymPad is the kind of application which should always present the simplest possible representation of a result, though this may not always be desirable since certain SymPy functions return equations in a certain standard form which may be ruined by a simplification.
For that reason this behavior can be turned on or off as desired by calling "<b>env('simplify')</b>" to turn it on and "<b>env(nosimplify)</b>" to turn it off, note the string quotes since "<b>simplify</b>" is a reserved SymPy function name.
</p><p>
A separate type of simplification is done for matrix operations since native SymPy matrix operations tend to blow up fairly quickly.
This simplification is on by default and simplifies matrices any time they are multiplied which helps control intermediate results in complex matrix operations and gives an already simplified answer once the operation completes.
This tends to slow down normal matrix operations a little but prevents the kind of lockup that can occur if a complex operation leaves a matrix in a state which is impossible for the normal "<b>simplify()</b>" function to correct.
This simplification can be turned off and on via "<b>env(nomatsimp)</b>" and "<b>env(matsimp)</b>".
</p>

<h4 id="More...">More...</h4>

<p>
The operations listed above are just the ones SymPad parses and represents in a standard mathematical format but SymPy contains many more functions which carry out many different operations on expressions and equations. I have not explored all of them and can not guarantee that SymPad will work flawlessly in each case as SymPy itself has many internal inconsistencies with how it deals with its own objects. The best way to see if something you need is available and works is to go through the SymPy documentation and try the functions directly in SymPad - <a href="https://docs.sympy.org/latest/index.html" target="_blank">SymPy Documentation</a>. Also you can check the <a href="#More Examples">More Examples</a> section for things that I have tried in SymPad.
</p>

<h2 id="Functions">Functions</h2>

<p>
There are three types of functions SymPad deals with:
Native SymPy functions are available for use directly, they can be assigned to variables so aliases like "<b>D = Derivative</b>" will work.
User created concrete lambda functions can represent any expression, can be called for evaluation, passed to native SymPy functions and can be referenced by and copied to variables.
And finally, undefined functions are abstract mathematical objects used in differential equations.
In addition, member functions of objects are supported like "<b>Matrix ([1, 2, 3]).transpose ()</b>".
The first two classes of functions may take a single unparenthesized argument or multiple parenthesized comma-separated arguments and the SymPy functions take optional keyword arguments as well.
</p>

<h4 id="SymPy Functions">SymPy Functions</h4>

<p>
Almost all SymPy functions and objects from the top level of the SymPy module are made available directly for calling by their name like "<b>tan(x)</b>", the only restriction being they must be longer than a single character and may not begin with an underscore.
Many functions from the "<b>__builtins__</b>" module are made available as well as a convenience, for example "<b>print ("Hello World...")</b>".
Only the "safe" "<b>__builtin__</b>" functions are specifically made available, functions like "<b>eval</b>", "<b>exec</b>" and many more have been left out and are not accessible.
</p><p>
SymPy functions accept a standard notation for exponentiation in the form of "<b>sin**2(x)</b>", this is equivalent to "<b>(sin (x))**2</b>".
In the case of trigonometric and hyperbolic functions this shorthand changes the function to its inverse if the exponent is "<b>-1</b>" so that "<b>sin**-1 (x)</b>" is the same as "<b>acos (x)</b>".
This only applies to a "<b>-1</b>" exponent right after the function name and before the argument of a trigonometric or hyperbolic function, in all other cases a negative exponent is just a negative exponent.
This shorthand exists only for the concrete SymPy functions, it does not work with lambdas or undefined functions.
</p><p>
SymPy and user lambda functions don't require explicit parentheses if they take a single argument in order to allow quick entry like "<b>sqrt 2</b>" or "<b>sin**-1 x</b>", but for any argument more complicated than another function or variable to a power parentheses will be needed.
These unparenthesized functions can be chained like "<b>N ln sin 2</b>".
Functions which take zero or more than one argument, as well as member functions such as "<b>\[[1, 1], [0, 1]].det()</b>" always require explicit parentheses.
</p><p>
Many functions which have a standard mathematical display style are translated on the fly for rendering in that style, these include the functions "<b>abs/Abs (x)</b>" which are rendered as the standard bar syntax for absolute value "<b>|x|</b>", the "<b>factorial (x)</b>" function becomes "<b>x!</b>" and "<b>exp (x)</b>" displays as "<b>e^x</b>".
Some other functions which are translated are "<b>Derivative</b>", "<b>diff</b>", "<b>Integral</b>", "<b>integrate</b>", "<b>Limit</b>", "<b>limit</b>", "<b>Matrix</b>", "<b>Piecewise</b>", "<b>pow</b>", "<b>Pow</b>", "<b>Sum</b>" and more...
</p><p>
Top level SymPy functions with a single letter name are treated specially in SymPad, this is because single letters are very commonly used as variables.
These special single letter functions are "<b>N</b>", "<b>O</b>", "<b>S</b>", "<b>beta</b>", "<b>gamma</b>", "<b>Gamma</b>", "<b>Lambda</b>" and "<b>zeta</b>", as you can see "single letter" does not mean "single character" but rather includes Greek letters as well.
Normally these letters can be used as variables and assigned to, but if they are used in a function context (including unparenthesized calls or implicit undefined function specification) they will instead be parsed as a function call to the SymPy function instead.
SymPad allows you to disable this behavior individually for each of these functions thus freeing up the letter to be used as a variable in all contexts via the "<b>env()</b>" environment function or command line options, for more on manipulating the environment and these options see <a href="#Admin Functions">Admin Functions</a>.
</p>

<h4 id="Lambda Functions">Lambda Functions</h4>

<p>
User created lambda functions are supported and are defined in the same way as Python lambdas - "<b>lambda x, y: sqrt (x**2 + y**2)</b>" or via the SymPy "<b>Lambda</b>" function like "<b>Lambda ((x, y), sqrt (x**2 + y**2))</b>".
These functions can be defined on-the-fly in a call to a SymPy function for use within that function or they can be assigned to a variable to make them usable in SymPad.
You can assign them to a variable like "<b>f = lambda x: x**2</b>", or with the alternative shorthand "<b>f (x) = x**2</b>", now when you call "<b>f (3)</b>" you will get "<b>9</b>" as the result.
</p><p>
When a lambda function is defined any non-private lambda variables in the body of the lambda are mapped to their global values and no expressions are "<b>doit ()</b>"-ed, this means that if a lambda references an assigned global variable the variable will be replaced with its value within the lambda definition.
If you do not want to bind the value of a global variable in a lambda definition but rather have the value retrieved upon execution of the lambda then use the "<b>@</b>" pseudo-function when referencing that variable as such "<b>f = lambda: @x</b>", this will leave the lambda definition referencing an "<b>x</b>" variable which will be bound upon execution.
</p><p>
Since lambda definitions do not have "<b>.doit()</b>" executed by default, this also means that if you create a lambda like "<b>f = lambda x: \int x dx</b>", the integral will not be evaluated until the lambda is executed, since that is one of the operations that is not evaluated unless a "<b>doit ()</b>" is called.
In this case, whatever is passed in for the "<b>x</b>" variable upon evaluation will attempt to convert the "<b>dx</b>" to its own differential, and if that "<b>x</b>" variable resolves to a pure variable like "<b>y</b>" and not an expression then the "<b>dx</b>" will be changed to a "<b>dy</b>", otherwise the integration is done with respect to the original "<b>dx</b>".
This does not apply if you use the lowercase SymPy functions like "<b>integrate ()</b>" or "<b>limit ()</b>" which are always evaluated even in the definition of a lambda function unless they are prevented from doing so by a "<b>%</b>" or double "<b>%%</b>" pseudo-function, for more on that see the section on pseudo-functions just below.
</p><p>
Lambdas may be passed to SymPy and other Python functions and they will be passed as SymPy "<b>Lambda</b>" objects.
Lambda functions may reference other lambda functions as long as those are defined at the time of creation and will normally map be body of the target lambda into itself.
They may also use undefined functions in the body which, if a lambda is defined at the time of execution with the same name, will attempt to call that lambda, otherwise they will just be evaluated as undefined functions.
This is done on purpose because outside of SymPad "<b>Lambda</b>" objects can not reference other "<b>Lambda</b>" objects indirectly and thus these lambdas would not work if passed on to a SymPy function.
Lambdas can be called directly as in "<b>{lambda x: x**2} (3)</b>".
Lambda recursion is not supported because there is no underlying mechanism to make it work with SymPy "<b>Lambda</b>" objects.
</p><p>
Lambda functions can be used as expressions in most contexts, this means that the body of the function is used directly in a calculation with the non-local variables being mapped to their global values.
Specifically, lambdas are treated as expressions if they are not enclosed in parentheses, brackets, tuples, sets or dictionaries or if they are part of an argument list to a function call.
If you want the lambda treated as an expression in these contexts then wrap it in parentheses.
Note that this only applies to lambdas assigned to and accessed through variables, lambdas declared directly are always treated as lambdas.
</p><p>
Lambdas can also be called after differentiating the lambda, something with makes working with differential equations easier since you can assign the result of a dsolve to a lambda and call its derivatives to check on initial or boundary conditions.
For more on all of this see <a href="#Lambda Function Examples">Lambda Function Examples</a>.
</p>

<h4 id="Undefined Functions">Undefined Functions</h4>

<p>
Undefined functions are mathematical function objects but without any specific expression or lambda body defined, they are used mainly in differential equations.
These functions are entered explicitly as "<b>?name (x, y, ...)</b>" with the name being optional but necessary if you need to differentiate between various functions (like in a system of differential equations).
They can also be specified implicitly as "<b>name (x, y, ...)</b>" without the question mark if the name is not already defined as another lambda function.
The arguments to an undefined function can be anything, variable names, concrete immediate values to specify initial conditions or even entire expressions - though only if using the explicit form of definition with the question mark, otherwise the parenthesized expression is treated as a gouping.
Optional keyword arguments for specifying assumptions about the function are also accepted which will be passed on to the SymPy "<b>Function()</b>" call.
</p><p>
The variable list for an undefined function, just like the variable list for a lambda function, is usually not remapped (except if doing substitution or "calling" the undefined function) which means that even if you have the variable "<b>x</b>" assigned globally you can still use the variable name "<b>x</b>" in the definition of an undefined function.
Implicit assignment to an undefined function at the highest level of an expression is a shortcut for defining a lambda function, e.g. "<b>f(x) = x**2</b>" is equivalent to "<b>f = lambda x: x**2</b>".
Conversely the assignment "<b>f = f (x)</b>" will designate the variable "<b>f</b>" to always act as an undefined function of one variable "<b>x</b>".
This form of definition works if "<b>f</b>" is not already assigned to something, if it is then you need to specify the undefined function explicitly like "<b>f = ?f (x)</b>".
One point of convenience is that anonymous undefined functions will take on the name of the variable they are being assigned to, so for example the assignment "<b>u = ? (u, t)</b>" is equivalent to writing "<b>u = ?u (u, t)</b>".
For the sake of your own mental health you should try to always give an undefined function the same name as the variable it is being assigned to.
</p><p>
Undefined functions may be used directly in the "<b>dsolve()</b>" and "<b>pdsolve()</b>" functions to solve a differential equation or system of equations, but it is cleaner to first map them to a variable so that the resulting equation looks as it normally does in mathematical syntax.
For example you can do "<b>dsolve (y(x)'' + 2y(x)' - y(x))</b>", but it is much cleaner to first set "<b>y = y(x)</b>" and then do "<b>dsolve (y'' + 2y' - y)</b>". If you are solving a system of differential equations then it is even more useful to use variables since you will need to differentiate the undefined functions using names like "<b>x, y = x(t), y(t)</b>".
This will give you a clean "<b>dsolve ((x' = 12t x + 8y, y' = 21x + 7t y))</b>" instead of having to type the functions out constantly like "<b>dsolve ((x(t)' = 12t x(t) + 8 y(t), y(t)' = 21x(t) + 7t y(t)))</b>".
Initial values for differential functions are supported and can be passed of the form "<b>dsolve (y'' + 11y' + 24y, ics = {y(0): 0, y'(0): -7})</b>" if "<b>y = y(x)</b>" is already defined or "<b>dsolve (y(x)'' + 11y(x)' + 24y(x), ics = {y(0): 0, y(x)'(0): -7})</b>" if it is not, notice that the first initial condition "<b>y(0)</b>" did not need to be specified as an undefined function of a variable first.
The "<b>ics</b>" keyword argument is a dictionary of undefined functuions at specific points with their values.
</p><p>
Pure undefined functions are those which have only non-constant variables as parameters.
If a pure undefined function is assigned to a variable then that variable may be "called" with a parenthesized list of expressions matching the number of variables in the undefined function.
This will create a new undefined function with those values as parameters which can be used to set initial conditions for a differential equation solver, for example a variable mapped as "<b>y = y(x)</b>" will turn into the function "<b>y(0)</b>" if the variable "<b>y</b>" is called as "<b>y(0)</b>".
If a variable is not currently mapped to an undefined function then this initial condition can be created directly just by writing "<b>y(0)</b>".
However if the variable is mapped to something which is not an undefined function, or is not pure, or the number of arguments don't match then you will need to use the explicit "<b>?y(0)</b>" form to specify this initial condition.
</p><p>
The derivative of an undefined function may be called to specify initial conditions for that derivative of the function.
"<b>y(x)'(0)</b>" means the value of the first derivative of the function at "<b>0</b>", this is of course easier if the function is already mapped to a variable like "<b>y = y(x)</b>" so you can do "<b>y'(0)</b>".
Partial derivatives of multi-variable undefined functions can also be taken and called.
In the case of a function mapped as "<b>u = u(x, t)</b>", you can do "<b>du/dx (0,0)</b>" or $\frac{du}{dx} (0, 0)$ to specify the initial condition at x = 0 and t = 0 of the first derivative of "<b>x</b>" of the function.
If the function is not assigned to a variable then the format to do this is "<b>d/dx(u(x, t))(0,0)</b>" - $\frac{d}{dx}(u(x, t))(0, 0)$, so obviously it saves some typing to assign the function to a varaible.
Also if you have an undefined function assigned to a variable then SymPad will map any undefined functions matching it coming from a SymPy calculation back to the variable to which it is assigned, easier on the eyes this way. Whereas trying to create an undefined function implicitly like "<b>y(a + b)</b>" will be interpreted as "<b>y * (a + b)</b>", this is not the case if "<b>y</b>" is a variable containing a pure undefined function, in which case a new undefined function "<b>y(a + b)</b>" is created.
And you can always assure an undefined function is created by use of the explicit "<b>?</b>" form of the definition.
One last detail is that a derivative of a pure undefined function mapped to a variable will resolve to a derivative of that undefined functions with new elements if called with a parenthesized expression containing the same number of elements as the undefined function has varaibles.
</p><p>
The last use of undefined functions is as placeholders in concrete lambda functions for calls to be bound at evaluation.
Normally when a lambda references another lambda, that lambda needs to exist at the time of definition of the second lambda and the body of the referred lambda is simply copied into the new lambda.
If you use an undefined function in a lambda definition then upon lambda execution it will be bound to any lambda defined with the same name mapped globally, needless to say the number of parameters needs to match.
This is also useful for assigning functions returned from differential equation solvers after getting the solution.
</p><p>
For examples of the usage of undefined functions see <a href="#Ordinary Differential Equations">Ordinary Differential Equations</a> and <a href="#Partial Differential Equations">Partial Differential Equations</a>.
</p>

<h4 id="Pseudo-Functions">Pseudo-Functions</h4>

<p>
SymPad provides two pseudo-functions which control how it resolves variables and evaluates expressions, they are the "<b>@</b>" no-remap and the "<b>%</b>" no-evaluate functions.
The reason these are called pseudo-functions is because they don't actually exist, they are merely annotations for how SymPad is to operate internally.
For this reason they are never included in a Python representation of an expression and that expression pasted back into SymPad will not operate the same as it did originally.
These functions have a very high binding priority in the grammar and thus something like "<b>sin a.b [2]</b>" which normally resolves to "<b>sin (a.b [2])</b>" becomes "<b>@ (a).b [2]</b>" when written without parentheses using a pseudo-function like "<b>@a.b [2]</b>".
Note that, like other functions, they may take a non-parenthesized argument and in fact this is just a quick way to annotate variables like "<b>@x</b>" or ask that the contents of a variable not be evaluated when mapping the variable into the expression like "<b>%x</b>".
</p><p>
The "<b>@</b>" no-remap pseudo function is not actually a no variable remap but rather a scope operation where the value of that variable will be taken from the next outermost scope rather than the current, and normally would be used only once.
This results in an effective no-remap operation when used in the definition of a lambda like "<b>lambda x: x + @x</b>".
What happens in this lambda body is that the first "<b>x</b>" variable binds to the value that is passed in at the time of lambda execution and the second is taken from the global scope, so if "<b>x</b>" is set to "<b>2</b>" globally then the body of the lambda actually becomes "<b>x + 2</b>".
If the "<b>x</b>" variable is not defined globally then the lambda gets a second "<b>x</b>" variable and becomes "<b>2x</b>" since any globally undefined variables default to themselves.
Obviously this is just meant to allow binding of globally defined expressions into a lambda even if a lambda uses local variables of the same name, or for deferred evaluation of a variable name if that variable is defined globally at the time of lambda definition.
This function is not dynamic and only operates at the time of lambda definition, thus you can not access outer scope values once the lambda has been defined.
To be clear though, the use of this function is not restricted to lambdas, if you have a variable assigned to something in the global scope and wish to use the variable itself rather than its contents then prepend the variable with this function.
</p><p>
The "<b>%</b>" no-evaluate pseudo function defers any evaluation of the expression it encloses when the expression is presented to SymPy.
This means that "<b>%(\int x dx)</b>" will return $\int x\ dx$ instead of $\frac{x^2}{2}$ and even "<b>%(1 + 2)</b>" will just return $1 + 2$.
This only operates at the time of the initial evaluation and then the pseudo-function disappears, thus if you evaluate the result again like for example with an assignment from the previous variable like "<b>x = _</b>" then the expression will be fully evaluated before being assigned to "<b>x</b>".
If you want the "<b>x</b>" variable to contain the expression "<b>1 + 2</b>" instead of "<b>3</b>", for some bizarre reason, then simply do the assignment in one step as "<b>x = %(1 + 2)</b>".
</p><p>
This function takes on a different meaning when used at the top level of a lambda function body definition, in this case it actually indicates that the body of the lambda should be "<b>doit()</b>"ed in the lambda whereas normally they are not.
After this initial topmost use the function reverts to its normal mode of operation, which means that if you want to completely defer evaluation at the top level of the body of a lambda then you actually have to specify the "<b>%</b>" function twice like "<b>lambda x: %%(1 + 2 + x)</b>".
The same thing can be achieved by simply doing "<b>f = lambda x: %(1 + 2) + x</b>" instead since in this expression the "<b>%</b>" is no longer the topmost function, the addition is.
</p><p>
Use of these functions is demonstrated <a href="#Pseudo-Function Examples">Pseudo-Function Examples</a> and <a href="#Lambda Function Examples">Lambda Function Examples</a>.
</p>

<h4 id="Functions, Parentheses and Implicit Multiplication">Functions, Parentheses and Implicit Multiplication</h4>

<p>
SymPad supports implicit multiplication, that is writing two variables next to each other to indicate multiplication so "<b>x y</b>" = "<b>x * y</b>".
This brings some problems, such as deciding when "<b>x (a + b)</b>" is a function call and when it is multiplication.
Normally in an unabiguous grammar a set of parentheses will indicate a function call if it is not preceded by an operator, but since with implicit multiplication an operator is optional then interpreting the parentheses becomes trickier.
</p><p>
SymPad solves this for SymPy functions by recognizing all top-level SymPy package function names and classes accepting the next expression as the argument(s) to that function and the whole thing as a call.
Variables mapped to lambda functions are also recognized and treated as a call, whereas variables mapped to other expressions are treated as multiplies with whatever expression follows them (with the exception of undefined functions, more on that further down).
An unmapped variable followed by a parenthesized list of expressions may be treated as the creation of an undefined function if the list of expressions contain only variable names and other constant expressions, or as a multiply if the list of expressions contains any non-constant expression containing other variables.
Member references followed by parenthesized expressions are always treated as member function calls such as "<b>a.b (c)</b>".
</p><p>
When a variable is mapped to a pure undefined function or a derivative of a pure undefined function then a parenthesized list of expressions following that variable with the same number of elements as the undefined function has arguments will create another undefined function with the expressions replacing the original arguments.
However if the undefined function is not pure then the whole thing is treated as a multiply of the undefined function or derivative of the undefined function and the grouping of expressions.
</p><p>
If you wish to prevent any attempt at treating a parenthesized expression as creating a function call or undefined function then simply use the explicit "<b>*</b>" multiplication operator between a variable and the parentheses.
You can also use the curly braces "<b>{...}</b>" as parentheses in place of the normal rounded parentheses "<b>(...)</b>" as long as the expression is not a comma expression which would create a set and has no chance to create a dictionary if there are colons "<b>:</b>" present.
Or you can "parenthesize" the normal rounded parentheses with curly parentheses as "<b>a{(pi + 2)}</b>".
Parenthesizing the parentheses always prevents the parentheses from being treated as a call as well with variables mapped to lambdas and member access.
</p><p>
SymPad normally displays implicit multiplicating with a parenthesized expression without the use of an explicit multiplication operator if that will be unambiguous in the grammar, but sometimes an explicit operator is thrown in just to clarify the meaning if it would appear ambiguous visually or for aesthetic purposes.
There may be cases where a variable is mapped to something that will resolve to a Python "callable" object on evaluation but will be displayed with an explicit multiplication operator.
In those cases if the variable does in fact resolve to a callable then even though it was displayed with as an explicit multiplication the parenthesized expression will instead be used as an argument list to the callable, unless you specifically used explicit multiplication or parenthesized the parentheses.
</p>

<h2 id="Plotting">Plotting</h2>

<p>
Basic plotting functionality is available if you have the matplotlib Python module installed on your system.
These functions work more like statements in that they only work at the top level of the parse tree, which means you can not use them in other lambdas, tuples, assignments or semicolon expressions.
</p>

<h4 id="plotf() - Plot Function">plotf() - Plot Function</h4>

<p><b>Examples</b></p>

<p>
<span onclick="cE2C (this)">plotf (x**2)</span><br>
<span onclick="cE2C (this)">plotf (2pi, sin x, cos x, sinc x, tanh x)</span><br>
<span onclick="cE2C (this)">plotf (2, lambda x: 1 / x, '--r')</span><br>
<span onclick="cE2C (this)">plotf (-2, 5, (x**2 - 6x + 9) / (x**2 - 9), ':#green')</span><br>
<span onclick="cE2C (this)">plotf (4, (x**2 + x - 2) / (x - 1), 'b=line', (1, 3), 'o#red=point')</span><br>
<span onclick="cE2C (this)">plotf (-1, 2, -3, 4, (0, 0), 'xr', [1, 1, 1.9, 1], 'vb', [(0.5, 2), (1.5, 3), (1.5, 0), (-0.5, -2)], ':g')</span><br>
<span onclick="cE2C (this)">plotf (pi, -20, 20, tan x, '#c08040=tan x', fs = (12, 4.8), linewidth = 1)</span><br>
<span onclick="cE2C (this)">plotf (2pi, sin x, 'r', {'linewidth': 1}, cos x, 'g', {'linewidth': 3}, fs = 12)</span><br>
<span onclick="cE2C (this)">plotf (1.5, -1.5, 1.5, sqrt (1 - x**2), 'r', -sqrt (1 - x**2), 'r', fs = -8, res = 32)</span><br>
<span onclick="cE2C (this)">plotf (\sum_{n=0}**oo x**n / n!, 'd-#chocolate=e^x', res = 1, linewidth=0.5)</span><br>
<span onclick="cE2C (this)">plotf (4pi, d / dx (sin x / x), sin x / x, \int sin x / x dx)</span><br>
</p>

<p><b>Usage</b></p>

<p>
SymPad provides the "<b>plotf()</b>" function which can be used to plot one or more expressions or lambdas of one free variable or lists of points or lines.
This function works by sampling a given expression at regular intervals to build up a list of x, y coordinates to pass on to matplotlib for rendering, the size of this sampling interval can be adjusted with a keyword argument.
The format of this plot function is as follows: "<b>plotf(['+',] [limits,] [*plots,] fs=None, res=12, style=None, **kwargs)</b>".
</p><p>
The initial optional "<b>'+'</b>" string signifies that the plot should build upon the previous plot which allows you to build up complex plots one function at a time.
The limits are an optional zero to four numbers which specify the boundaries of the requested plot, if no limit numbers are present then the plot will range from 0 to 1 on the x axis and the y axis will be determined automatically.
If one limit number is present then the plot will range from -x to +x of this number and the y is automatic, two numbers are interpreted as x0 and x1 and y is automatic, three is -x, x, y0 and y1 and four numbers let you specify the full range x0, x1, y0, y1 of the axes.
</p><p>
The "<b>fs</b>" keyword argument is a matplotlib "<b>figsize</b>" value which lets you specify the size of the plot. This can be either a single number in which case this specifies the x size and the y size is computed from this, or it can be a tuple specifying both the x and y sizes of the plot - the default is (6.4, 4.8).
If a single number is provided and it is positive then the y size is computed as x*3/4 of this number to give a plot area with a 4:3 aspect ratio.
It the single number is negative then the y size is set equal to the positive x size and the plot area will have a square aspect ratio.
</p><p>
The "<b>res</b>" keyword argument allows you to set the sampling resolution for the plot, the default is roughly 12 samples per 50 pixels of the plot.
This is useful to increase if the function is intricate and the default resolution does not capture some point of interest correctly.
</p><p>
The "<b>style</b>" keyword allows you to change to any of the default matplotlib styles for drawing the plots.
Some available styles are: "<b>bmh</b>", "<b>classic</b>", "<b>dark_background</b>", "<b>fast</b>", "<b>fivethirtyeight</b>", "<b>ggplot</b>", "<b>grayscale</b>", see the matplotlib documentation for a full list of styles.
If you pass the name of the style starting with a '-' minus sign such as "<b>-bmh</b>" then the plot will be rendered with a transparent background, otherwise the color of the background comes from the style.
The style which is set will persist for all future plots until it is changed.
</p><p>
Other keyword arguments from "<b>kwargs</b>" are passed through on to the "<b>matplotlib.pyplot.plot()</b>" function for each expression plotted.
In addition each expression or function to be plotted can also specify its own dictionary of matplotlib keyword arguments to use for that specific expression.
</p>

<p><b>Individual Plots</b></p>

<p>
The "<b>plotf()</b>" function can take any number of expressions to plot at once with their own formatting options.
The actual source of the data to be plotted can be an expression, a lambda function or a list of coordinate points which will be rendered as either a line of a certain style or just disconnected markers.
The format of each individual plot specification to the plotting function is as follows: "<b>expression [,'format'] [,{'kwarg': value, ...}]</b>".
As mentioned the expression can be just an expression like "<b>x**2</b>", a lambda of one variable "<b>lambda x: x**2</b>" or a list of paired "<b>[(x0, y0), (x1, y1), ...]</b>" or unpaired "<b>[x0, y0, x1, y1, ...]</b>" coordinates.
</p><p>
Following the expression is an optional string specifying the color, line or marker style and optional legend text for the expression.
The format of this string is an extension of the matplotlib format string to its "<b>plot()</b>" function and is as follows: "<b>[marker][line][color][#extended color][=legend text]</b>".
The "<b>marker</b>" option specifies which type of marker (if any) to place at each computed x, y coordinate, the possible options are: '.', ',', 'o', 'v', '^', '&lt;', '&gt;', '1', '2', '3', '4', 's', 'p', '*', 'h', 'H', '+', 'x', 'D', 'd', '|' and '_'.
The "<b>line</b>" option specifies which type of line (if any) connects the various x, y coordinates, the possible options are: '-', '--', '-.' and ':'.
The options for the simple color specifier "<b>color</b>" are 'b', 'g', 'r', 'c', 'm', 'y', 'k' and 'w', which is a quick one letter way to pick different colors.
If you need more control over color then use the extended color specifier which allows you to pick from the whole list of matplotlib named colors such as 'red', 'teal', 'mediumorchid' and 'salmon'.
You can also directly specify the RGB values of the color by entering an HTML RGB color specifier like '#ff8040'.
The last specifier is optional legend text for the expression, the presence of which is signalled by the '=' character immediately preceding it.
</p><p>
Following the format specifier is an optional dictionary of keyword argument and value pairs to be passed on to the matplotlib "<b>plot()</b>" function only for this given expression.
These arguments will override any global matplotlib keyword arguments specified in the call to the "<b>plotf()</b>" function.
</p><p>
Note that the presence of any plots at all in a call to "<b>plotf()</b>" is optional, you can call "<b>plotf()</b>" with the continue option '+' without any plots in order to resize the previous plot or change the axes.
Keep in mind that this does not re-plot any previously plotted functions so that data remains at whatever resolution it was plotted.
</p>

<h4 id="plotv() - Plot Vector Field (2D)">plotv() - Plot Vector Field (2D)</h4>

<p><b>Examples</b></p>

<p>
<span onclick="cE2C (this)">plotv ((-y, x))</span><br>
<span onclick="cE2C (this)">plotv (2, y - 2 x, '#green')</span><br>
<span onclick="cE2C (this)">plotv (lambda x, y: (y, -x), 'dir')</span><br>
<span onclick="cE2C (this)">plotv (2, -2, 2, y / x, '#red=dy/dx', fs = -6, width = 0.005)</span><br>
<span onclick="cE2C (this)">plotv (4, -4, 4, (lambda a, b: a + b**2, lambda a, b: a**2 - b), lambda x, y, u, v: y, res = 31, fs = -8, width = 0.003)</span><br>
<span onclick="cE2C (this)">plotv (-2, 2, -2, 2, (v (sign (Max (u, 0)) * 2 - 1), -u (sign (Max (u, 0)) * 2 - 1)), 'dir', width = 0.003, pivot = 'mid', fs = -8)</span><br>
<span onclick="cE2C (this)">plotv (-6, 6, -2, 2, lambda x, y: (re (sin (x + i y)), im (sin (x + i y))), 'mag', '=sin (x + iy)', fs=-12, res=33)</span><br>
</p>

<p><b>Usage</b></p>

<p>
This function allows you to plot a 2-dimensional vector field specified by one or two functions or expressions via the matplotlib "<b>Quiver()</b>" function.
The format of this function is as follows: "<b>plotv (['+',] [limits,] func(s), [color,] [fmt,] [*walks,] fs = None, res = 13, style = None, resw = 1, kww = {}, **kw)</b>".
The initial optional "<b>'+'</b>" and "<b>limits</b>" fields work exactly as in the "<b>plotf()</b>" function, as do the keyword arguments "<b>fs</b>" and "<b>style</b>".
</p><p>
The actual vector field to be plotted comes from either one or two functions or expressions which provide either the u and v coordinates of the vectors or a v/u slope for the vector - in which case the vector will not have a direction arrowhead by default.
These function(s) are specified in the "<b>func(s)</b>" parameter and must be either a tuple of two functions or expressions or a single function or expression.
</p><p>
If there are two functions then they are called for each point sampled in the vector field with an x and y coordinate (the names of the symbols don't matter, the first argument is the x and the second is a y).
These functions must each return a single real value, the first function returns the u component of the vector field and the second returns the v component.
If instead of functions there is a tuple of two expressions, then the expressions together must have exactly two free variables which will be replaced with the x and y values of the vector field in alphabetical order, which means that expressions with free variables (a, b), (u, v) and (x, y) will recieve the x and y coordinates in that order.
</p><p>
If there is a single function or expression present then the interpretation of that function depends on what it returns.
If the return value is a single real number then it is interpreted as a slope at that point in the vector field and rendered as such a sloped line, this is useful for plotting things like differential functions.
If the return value has two components like a tuple or list of real values then those are treated as the u and v components of the vector field at that point directly and actually have a direction and magnitude unlike the single value slope.
</p><p>
The optional "<b>color</b>" parameter following the field functions(s) is either a function of four arguments "<b>lambda x, y, u, v: expr</b>" which will be called for each point of the vector field and should return a scalar, which scalar will be used to determine the relative color of the arrow at that point.
Or it can be a string specifying one of the pre-defined color functions: 'dir' for a direcitonal color function and 'mag' for a magnitude color function, note 'mag' option doesn't make much sense for a field function or expression which only returns a slope.
</p><p>
Following this is an optional format string which works much like the format strings from the "<b>plotf()</b>" function except omitting the "<b>[marker][line][color]</b>" prefix and using only "<b>[#extended color][=legend text]</b>".
If a color function is not specified before this string and a color is present then that color is used for the entire vector field.
A label can also be present for this particular vector field which can be useful if plotting more than one field.
</p><p>
Finally the "<b>res</b>" argument specifies a resolution - or rather the number of arrows you want horizontally in the plot.
If this argument is a single number then the number of arrows vertically across the plot is calculated from the aspect ratio of the plot as specified or not by the "<b>fs</b>" figsize argument.
If rather the "<b>res</b>" argument is a tuple of two numbers then these are used directly for the number of arrows horizontally and vertically in the plot.
</p><p>
Any keyword arguments are passed through on to the matplotlib "<b>Quiver()</b>" function which allows you to tweak certain display properties of the plot like line thicknesses and arrowheads, for a full list of these options see the matplotlib documentation.
Also notice that unlike the "<b>plotf()</b>" function, "<b>plotv()</b>" only plots one vector field at a time.
</p><p>
If any non-keyword arguments are present after the "<b>func(s)</b>", "<b>color</b>" and "<b>fmt</b>" parameters but before any keyword arguments then they are interpreted as parameters to the function "<b>plotw()</b>" which will be called after the vector field is plotted and will plot one or more walks over that vector field.
The "<b>resw</b>" parameter is passed on to "<b>plotw()</b>" as a keyword argument as well as the contents of the "<b>kww</b>" dictionary.
See the documentation for that function for usage and formatting of these parameters.
</p>

<h4 id="plotw() - Plot Walk Over Vector Field">plotw() - Plot Walk Over Vector Field</h4>

<p><b>WARNING!</b> This plotting function can be very slow due to the fact it calls very frequently into SymPy Lambda functions as it adapts the walk to minimize potential error.</p>

<p><b>Examples</b></p>

<p>
<span onclick="cE2C (this)">plotw (3, -3, 3, y - 2x, (1, 1))</span><br>
<span onclick="cE2C (this)">plotw (3, -3, 3, y - 2x, (1, 1), (1, 2), (1, 0))</span><br>
<span onclick="cE2C (this)">plotw (6, -6, 6, lambda x, y: (2x + sec**2x) / 2y, (0, -5), 'r=u(0) = -5', fs = -7)</span><br>
<span onclick="cE2C (this)">plotw (2.5, -2.5, 2.5, (-y, x), (0.5, 0), '=(0.5,0)', (1, 0), '=(1,0)', (1.5, 0), '=(1.5,0)', (2, 0), '=(2,0)', fs = -6)</span><br>
<span onclick="cE2C (this)">plotw (pi, -pi, pi, sin x - sin y, (0, 0), (1, 1), (2, 2), (-1, -1), (-2, -2), fs = -8, linewidth = 5)</span><br>
</p><p>
These will make more sense if they are put in the context of their respective vector fields:
</p><p>
<span onclick="cE2C (this)">plotv (3, -3, 3, y - 2x, (1, 1))</span><br>
<span onclick="cE2C (this)">plotv (3, -3, 3, y - 2x, (1, 1), (1, 2), (1, 0))</span><br>
<span onclick="cE2C (this)">plotv (6, -6, 6, lambda x, y: (2x + sec**2x) / 2y, (0, -5), 'r=u(0) = -5', fs = -7, res = 33)</span><br>
<span onclick="cE2C (this)">plotv (2.5, -2.5, 2.5, (-y, x), (0.5, 0), '=(0.5,0)', (1, 0), '=(1,0)', (1.5, 0), '=(1.5,0)', (2, 0), '=(2,0)', fs = -6, pivot = 'mid')</span><br>
<span onclick="cE2C (this)">plotv (pi, -pi, pi, sin x - sin y, (0, 0), (1, 1), (2, 2), (-1, -1), (-2, -2), fs = -8, kww = {'linewidth': 5})</span><br>
</p>

<p><b>Usage</b></p>

<p>
This function is normally intended to be called implicitly from the "<b>plotv()</b>" function for plotting a walk over a plotted vector field but can be called on its own to plot one or more walks without the vectory field in the background.
The format is as follows: "<b>plotw (['+',] [limits,] func(s), *points, fs = None, resw = 1, style = None, **kw)</b>"
The "<b>'+'</b>", "<b>limits</b>", "<b>fs</b>" and "<b>style</b>" fields work in the same manner as the previous two functions.
The "<b>func(s)</b>" is interpreted as a vector field function or pair of functions or expressions like in "<b>plotv()</b>".
"<b>resw</b>" is a resolution parameter - maximum pixel steps to allow walk step to deviate before drawing, smaller = better quality.
</p><p>
What this function does is take an x, y point (or points if multiple starting positions provided) and starts walking the vector field according to its value at that point - following the gradient.
It adapts the steps it takes according to how much curvature the vector field exhibits at that point and tries to reach either the edge of the graph or its own starting point to complete a loop.
The "<b>*points</b>" parameters specified in the function is either one or more tuples of x, y values optionally followed by "#color=label" formatting and dictionary keywords for the line corresponding to the walk for that point, similar to the previous functions.
An example of "<b>*points</b>": "<b>plotw(..., (0, 0), '#red=0,0', {'linewidth': 2}, (1, 1), '#green=1,1', {'linewidth': 3}, (2, 2), ...)</b>".
</p><p>
Due to numerical errors accumulating during the walk this is by no means a perfect plotting function and should be considered experimental.
Also due to the fact that the points of the vector field are gotten from SymPy operations and the number of times they are requested this function can be very slow.
It may also never finish if a complex circular vector field introduces enough errors so that the walk gets back to the starting point but not quite close enough to consider it a full loop then the walk will keep going around and around in circles and the function will not return, you have been warned.
</p>

<h2 id="More Examples">More Examples</h2>

<p>
Note that some of these are single line examples and others make use of variables.
If you see variables being assigned make sure to execute those lines before the lines which follow, also make sure to delete the variable assigment afterwards in order to not affect any other examples which expect those variables to be free.
All these examples assume you are running with default environment settings.
You can click on any of these lines to copy it to the clipboard for pasting into SymPad.
</p>

<h4 id="Limits, Sums, Derivatives and Integrals">Limits, Sums, Derivatives and Integrals</h4>

<p>
<span onclick="cE2C (this)">delall</span><i>&emsp; make sure no variables are mapped</i><br>
<span onclick="cE2C (this)">\lim_{h\to0} {(x + h)**2 - x**2} / h</span><br>
<span onclick="cE2C (this)">Limit ((1 + 1/x)**x, x, \infty)</span><br>
<span onclick="cE2C (this)">{(1 - 1/x)**x}.limit (x, \infty)</span><br>
<span onclick="cE2C (this)">limit (sin x / x, x, 0)</span><br>
<br>
<span onclick="cE2C (this)">\sum_{n=0}^\infty (-1)**n x**{2n} / (2n)!</span><br>
<span onclick="cE2C (this)">Sum ((-1)**n x**{2n + 1} / (2n + 1)!, (n, 0, oo))</span><br>
<span onclick="cE2C (this)">summation ((-3)^n / n 7^{n+1} * (x - 5)^n, (n, 1, oo))</span><br>
<br>
<span onclick="cE2C (this)">d/dx ln x</span><br>
<span onclick="cE2C (this)">Derivative (x**3y**2, x, y)</span><br>
<span onclick="cE2C (this)">diff (sin x + cos x, x, 2)</span><br>
<span onclick="cE2C (this)">{x**2sin**2y}.diff (x, y)</span><br>
<span onclick="cE2C (this)">d**3/dx**2dy x**3y**2</span><br>
<span onclick="cE2C (this)">sin (x)'</span><br>
<span onclick="cE2C (this)">(e**{2x})''</span><br>
<span onclick="cE2C (this)">d/dx ln (y(x))</span><i>&emsp; derivative of logarithm of abstract undefined function y(x)</i><br>
<br>
<span onclick="cE2C (this)">\int x**a dx</span><br>
<span onclick="cE2C (this)">Integral (a**x, x)</span><br>
<span onclick="cE2C (this)">{x**3y**2}.integrate (x, y)</span><br>
<span onclick="cE2C (this)">\int_0**oo e**{-st} dt</span><br>
<span onclick="cE2C (this)">Integral (r, (r, 0, 1), (theta, 0, 2pi))</span><br>
<span onclick="cE2C (this)">integrate (sin x / x, (x, -oo, oo))</span><br>
<br>
</p>

<h4 id="Solving Equations">Solving Equations</h4>

<p>SymPy documentation for <a href="https://docs.sympy.org/latest/modules/solvers/solvers.html" target="_blank">Equation Solvers</a>.</p>

<p>
<span onclick="cE2C (this)">delall</span><i>&emsp; make sure no variables are mapped</i><br>
<span onclick="cE2C (this)">solve (x**2 = 4)</span><i>&emsp; x**2 - 4 = 0</i><br>
<span onclick="cE2C (this)">solve (x**2 - 4)</span><i>&emsp; same</i><br>
<span onclick="cE2C (this)">solve (|x| >= x**2)</span><i>&emsp; inequality</i><br>
<span onclick="cE2C (this)">solve (x**2 + 2 x - 1 > 7)</span><br>
<span onclick="cE2C (this)">solve (y = x**2 + 2 x - 1, x)</span><i>&emsp; solve for x from y</i><br>
<span onclick="cE2C (this)">solve (x + (e**x)**2, e**x)</span><i>&emsp; solve for e**x</i><br>
<span onclick="cE2C (this)">solve (x + e**x, x)</span><i>&emsp; explicit solution</i><br>
<span onclick="cE2C (this)">solve (x + e**x, x, implicit = True)</span><i>&emsp; implicit solution</i><br>
<span onclick="cE2C (this)">solve ((x + 2y = 5, y - 2x = 0))</span><i>&emsp; system of equations, solve for x and y</i><br>
<span onclick="cE2C (this)">solve ((a + b)x - b + 2, a, b)</span><i>&emsp; one equation relating two variables</i><br>
<span onclick="cE2C (this)">solve ((a + b)x - b**2 + 2, a, b)</span><i>&emsp; non-linear</i><br>
<br>
<span onclick="cE2C (this)">a, b = x**2 + y -2, y**2 - 4</span><br>
<span onclick="cE2C (this)">solve ([a, b])</span><i>&emsp; system of two non-linear</i><br>
<span onclick="cE2C (this)">s = _</span><i>&emsp; store result</i><br>
<span onclick="cE2C (this)">a.subs (s [0]), b.subs (s [0])</span><i>&emsp; check solutions</i><br>
<span onclick="cE2C (this)">a.subs (s [1]), b.subs (s [1])</span><br>
<span onclick="cE2C (this)">a.subs (s [2]), b.subs (s [2])</span><br>
<span onclick="cE2C (this)">a.subs (s [3]), b.subs (s [3])</span><br>
<span onclick="cE2C (this)">w = {x: 1, y: 2}</span><br>
<span onclick="cE2C (this)">a.subs (w), b.subs (w)</span><i>&emsp; test incorrect solution</i><br>
<br>
</p>

<h4 id="Ordinary Differential Equations">Ordinary Differential Equations</h4>

<p>SymPy documentation for <a href="https://docs.sympy.org/latest/modules/solvers/ode.html" target="_blank">Ordinary Differential Equations</a>.</p>

<p>
<span onclick="cE2C (this)">delall</span><i>&emsp; make sure no variables are mapped</i><br>
<span onclick="cE2C (this)">dsolve (d**2/dx**2 ?(x) + 9?(x))</span><i>&emsp; using undefined functions directly</i><br>
<span onclick="cE2C (this)">dsolve (?(x)'' + 9?(x))</span><i>&emsp; quicker using prime derivative of anonymous functrion since function of only one variable</i><br>
<span onclick="cE2C (this)">dsolve (y(x)'' + 9y(x))</span><i>&emsp; or implicit named undefined function</i><br>
<span onclick="cE2C (this)">y = y(x)</span><i>&emsp; define y variable as undefined function of one variable</i><br>
<span onclick="cE2C (this)">dsolve (y'' + 9y)</span><i>&emsp; easiest like this</i><br>
<span onclick="cE2C (this)">dsolve (sin x cos y + cos x sin y y')</span><br>
<span onclick="cE2C (this)">dsolve (y' + 4/x * y = x**3 y**2)</span><br>
<span onclick="cE2C (this)">dsolve (y' + 4/x * y = x**3 y**2, ics = {y(2): -1})</span><i>&emsp; with initial condition</i><br>
<span onclick="cE2C (this)">dsolve (cos y - y' * (x sin y - y**2))</span><i>&emsp; implicit solution</i><br>
<br>

<span onclick="cE2C (this)">delall</span><i>&emsp; clean up and start again</i><br>
<span onclick="cE2C (this)">y = y(x)</span><br>
<span onclick="cE2C (this)">eq = y' + 4/x * y == x**3 y**2</span><i>&emsp; assign equality (equation) to "eq"</i><br>
<span onclick="cE2C (this)">dsolve (eq, ics = {y(2): -1})</span><i>&emsp; solve with ics to avoid constant symbol which would throw off prime derivtion</i><br>
<span onclick="cE2C (this)">sol = _.args [1]</span><i>&emsp; assign the right-hand part of the solution to "sol"</i><br>
<span onclick="cE2C (this)">checkodesol (eq, sol, y)</span><i>&emsp; check the solution for the function "y" using the checkodesol () function</i><br>
<span onclick="cE2C (this)">\. eq |_{y (x) = sol}</span><i>&emsp; check the solution by manually substituting it for the function "y (x)" in "eq"</i><br>
<span onclick="cE2C (this)">y (x) = sol</span><i>&emsp; assign solution to a concrete lambda function</i><br>
<span onclick="cE2C (this)">eq</span><i>&emsp; and check it by evaluating "eq"</i><br>
<span onclick="cE2C (this)">y' + 4/x * y, x**3 y**2</span><i>&emsp; check by eye</i><br>
<br>

<span onclick="cE2C (this)">delall</span><br>
<span onclick="cE2C (this)">x, y = x(t), y(t)</span><i>&emsp; two distinct undefined functions</i><br>
<span onclick="cE2C (this)">dsolve (y'' + 11y' + 24y, ics = {y(0): 0, y'(0): -7})</span><i>&emsp; second order with initial conditions</i><br>
<span onclick="cE2C (this)">dsolve ((x' = 12t x + 8y, y' = 21x + 7t y))</span><i>&emsp; system of two equations</i><br>
<br>
</p>

<h4 id="Partial Differential Equations">Partial Differential Equations</h4>

<p>SymPy documentation for <a href="https://docs.sympy.org/latest/modules/solvers/pde.html" target="_blank">Partial Differential Equations</a>.</p>

<p>
<span onclick="cE2C (this)">delall</span><br>
<span onclick="cE2C (this)">u, X, T = u (x, t), X (x), T (t)</span><i>&emsp; assign undefined functions</i><br>
<span onclick="cE2C (this)">eq = (du / dx = e**u * du / dt)</span><i>&emsp; assign equation</i><br>
<span onclick="cE2C (this)">pde_separate_add (eq, u, [X, T])</span><i>&emsp; separate equation into additive functions</i><br>
<span onclick="cE2C (this)">u, Y = u (x, y), Y (y)</span><br>
<span onclick="cE2C (this)">eq = d**2u / dx**2 == d**2u / dy**2</span><i>&emsp; assign equation using "=="</i><br>
<span onclick="cE2C (this)">pde_separate_mul (eq, u, [X, Y])</span><i>&emsp; separate equation into multiplicative functions</i><br>
<br>

<span onclick="cE2C (this)">eq = Eq (1 + 2 * {du/dx / u} + 3 * {du/dy / u})</span><i>&emsp; set equation using SymPy Eq()</i><br>
<span onclick="cE2C (this)">pdsolve (eq)</span><i>&emsp; solve for u (x, y)</i><br>
<span onclick="cE2C (this)">sol = _.args [1]</span><i>&emsp; assign right hand side of equation returned to "sol"ution</i><br>
<span onclick="cE2C (this)">checkpdesol (eq, sol)</span><i>&emsp; check solution using SymPy checkpdesol ()</i><br>
<span onclick="cE2C (this)">u (x, y) = sol</span><i>&emsp; assign solution to concrete lambda u (x, y)</i><br>
<span onclick="cE2C (this)">eq</span><i>&emsp; evaluate equation with u (x, y) being set to the solution</i><br>
<span onclick="cE2C (this)">simplify _</span><i>&emsp; simplify this since it comes back as a mess, will evaluate to True since eq = 0</i><br>
<br>

<span onclick="cE2C (this)">u = ?u (x, y)</span><i>&emsp; set "u" back to undefined function, need the "?" because u is currently a concrete lambda</i><br>
<span onclick="cE2C (this)">eq = x * du/dx - y * du/dy + y**2u - y**2</span><i>&emsp; just set "eq" to an expression, don't even need the equality</i><br>
<span onclick="cE2C (this)">pdsolve (eq)</span><br>
<span onclick="cE2C (this)">v (x, y) = _.args [1]</span><i>&emsp; assign solution to a different function</i><br>
<span onclick="cE2C (this)">eq</span><i>&emsp; original equation</i><br>
<span onclick="cE2C (this)">\. eq |_{u (x, y) = v}</span><i>&emsp; equation with solution, need to specify the full function "u (x, y)" here</i><br>
<span onclick="cE2C (this)">simplify _</span><i>&emsp; verify that it equals 0 with solution function</i><br>
<span onclick="cE2C (this)">F (x) = e**x</span><i>&emsp; assign arbitrary function for undefined function in solution</i><br>
<span onclick="cE2C (this)">\. eq |_{u (x, y) = v}</span><i>&emsp; see what the solution looks like with this function</i><br>
<span onclick="cE2C (this)">simplify _</span><i>&emsp; and verify again that it zeroes out</i><br>
<br>
</p>

<h4 id="Calculating Eigenvalues and Eigenvectors">Calculating Eigenvalues and Eigenvectors</h4>

<p>
<span onclick="cE2C (this)">delall</span><i>&emsp; make sure no variables are mapped</i><br>
<span onclick="cE2C (this)">m = \[[1, 2], [3, 4]]</span><br>
<span onclick="cE2C (this)">l = m - lambda eye 2</span><br>
<span onclick="cE2C (this)">l.det ()</span><br>
<span onclick="cE2C (this)">solve _</span><i>&emsp; this will give the eigenvalues</i><br>
<span onclick="cE2C (this)">a, b = _</span><i>&emsp; assign to vars</i><br>
<span onclick="cE2C (this)">m.eigenvals ().keys()</span><i>&emsp; verify eigenvalues</i><br>
<span onclick="cE2C (this)">Subs (l, lambda, a) \[x, y]</span><br>
<span onclick="cE2C (this)">solve (_ [0], _ [1], x, y)</span><i>&emsp; relation between x and y</i><br>
<span onclick="cE2C (this)">\[_ [0] [x], y].subs (y, 1)</span><i>&emsp; first eigenvector for eigenvalue a</i><br>
<span onclick="cE2C (this)">\. l |_{lambda = b} \[x, y]</span><i>&emsp; shorthand for substitution</i><br>
<span onclick="cE2C (this)">solve (_ [0], _ [1], x, y)</span><i>&emsp; relation between x and y</i><br>
<span onclick="cE2C (this)">\. \[_ [0] [x], y] |_{y = 1}</span><i>&emsp; second eigenvector for eigenvalue b</i><br>
<span onclick="cE2C (this)">m.eigenvects ()</span><i>&emsp; verify eigenvectors</i><br>
<span onclick="cE2C (this)">simplify _</span><i>&emsp; simplify result to easier see if it matches</i><br>
<br>
</p>

<h4 id="Lambda Function Examples">Lambda Function Examples</h4>

<p>
<span onclick="cE2C (this)">delall</span><i>&emsp; make sure no variables are mapped</i><br>
<span onclick="cE2C (this)">f = lambda x: x**2</span><i>&emsp; simple example of lambda which squares its argument</i><br>
<span onclick="cE2C (this)">f (2)</span><br>
<span onclick="cE2C (this)">f (x) = x**2</span><i>&emsp; differnt syntax to specify the same thing</i><br>
<span onclick="cE2C (this)">f (3)</span><br>
<span onclick="cE2C (this)">f (x, y) = sqrt (x**2 + y**2)</span><i>&emsp; function of two variables</i><br>
<span onclick="cE2C (this)">f (3, 4)</span><br>
<span onclick="cE2C (this)">f**2</span><i>&emsp; lambda functions can be used as expression in many contexts</i><br>
<span onclick="cE2C (this)">df / dx</span><i>&emsp; take derivative</i><br>
<span onclick="cE2C (this)">\int f dx</span><i>&emsp; integrate</i><br>
<span onclick="cE2C (this)">\sum_{x = 0}^2 f</span><i>&emsp; when used as an expression the lambda's variables are mapped</i><br>
<span onclick="cE2C (this)">(f)</span><i>&emsp; this is how you get the expression within a lambda</i><br>
<span onclick="cE2C (this)">x, y = 3, 4</span><br>
<span onclick="cE2C (this)">(f)</span><i>&emsp; variables are mapped to their current values</i><br>
<span onclick="cE2C (this)">del (x, y)</span><br>
<span onclick="cE2C (this)">(f)</span><i>&emsp; and now the lambda body is back</i><br>
<span onclick="cE2C (this)">f (x) = x**3</span><br>
<span onclick="cE2C (this)">f'</span><i>&emsp; only has one variable so can derivate like this</i><br>
<span onclick="cE2C (this)">f' (3)</span><i>&emsp; can also "call" the derivative of a lambda</i><br>
<span onclick="cE2C (this)">d**2f / dx**2 (3)</span><i>&emsp; also like this</i><br>
<span onclick="cE2C (this)">d**2 / dx**2 (f) (3)</span><i>&emsp; and this</i><br>
<br>

<span onclick="cE2C (this)">f (x) = lambda y: sqrt (x**2 + y**2)</span><i>&emsp; lambda returning a lambda</i><br>
<span onclick="cE2C (this)">f (3)</span><br>
<span onclick="cE2C (this)">_ (4)</span><i>&emsp; call the lambda that was returned</i><br>
<span onclick="cE2C (this)">f (3)</span><br>
<span onclick="cE2C (this)">_ * (4)</span><i>&emsp; will be multiplied instead of called if using explicit multiply operator</i><br>
<br>

<span onclick="cE2C (this)">delall</span><i>&emsp; undefined functions can be used to avoid embedding body of target lambda in parent</i><br>
<span onclick="cE2C (this)">g (x, y) = sqrt (f (x) + f (y))</span><i>&emsp; target lambda doesn't need to be defined ahead of time</i><br>
<span onclick="cE2C (this)">g (3, 4)</span><i>&emsp; will return undefined functions</i><br>
<span onclick="cE2C (this)">f (x) = x**2</span><i>&emsp; define the function</i><br>
<span onclick="cE2C (this)">g (3, 4)</span><i>&emsp; now call out to concrete defined function</i><br>
<span onclick="cE2C (this)">f (x) = x</span><i>&emsp; redefine the function to something else</i><br>
<span onclick="cE2C (this)">g (3, 4)</span><i>&emsp; now we have a different result</i><br>
<br>

<span onclick="cE2C (this)">delall</span><i>&emsp; @ no-remap pseudo-function</i><br>
<span onclick="cE2C (this)">x, y, z = 3, 4, 5</span><i>&emsp; assign global variablesy</i><br>
<span onclick="cE2C (this)">f (x, y) = sqrt (x**2 + y**2) + z</span><i>&emsp; lambda variables are not mapped but unbound variables are</i><br>
<span onclick="cE2C (this)">f (x, y) = sqrt (x**2 + y**2) + @z</span><i>&emsp; do not bind global variable</i><br>
<span onclick="cE2C (this)">f (x) = x**2</span><br>
<span onclick="cE2C (this)">f (x) = @x**2</span><i>&emsp; use global value of x</i><br>
<span onclick="cE2C (this)">f (x) = y**2</span><i>&emsp; binds global value of y into lambda since it is not a lambda variable</i><br>
<span onclick="cE2C (this)">f (2)</span><i>&emsp; doesn't matter what you pass, f(x) is set to equal 16</i><br>
<span onclick="cE2C (this)">f (x) = @y**2</span><i>&emsp; go up one more scope above global and use pure y variable</i><br>
<span onclick="cE2C (this)">f (2)</span><i>&emsp; still get 16 because global y = 4</i><br>
<span onclick="cE2C (this)">y = 6</span><i>&emsp; change global y</i><br>
<span onclick="cE2C (this)">f (2)</span><i>&emsp; global y = 6 so y**2 = 36</i><br>
<br>

<span onclick="cE2C (this)">delall</span><i>&emsp; % no-evaluate pseudo-function forcees "doit()" on lambda body</i><br>
<span onclick="cE2C (this)">f (x) = \int x dx</span><i>&emsp; normally integral, limits, derivatives and things like that are not evaluated on lambda definition</i><br>
<span onclick="cE2C (this)">f (sin x)</span><i>&emsp; only on execution</i><br>
<span onclick="cE2C (this)">f (x) = %(\int x dx)</span><i>&emsp; force evaluation of integral and store result in lambda definition, the % only works this way at the top level of the lambda body</i><br>
<span onclick="cE2C (this)">f (sin x)</span><i>&emsp; different result since integral was already evaluated</i><br>
<span onclick="cE2C (this)">f (sin y)</span><i>&emsp; this is as expected</i><br>
<span onclick="cE2C (this)">f (x) = \int x dx</span><i>&emsp; going back to the unevaluated integral</i><br>
<span onclick="cE2C (this)">f (sin x)</span><i>&emsp; as expected</i><br>
<span onclick="cE2C (this)">f (sin y)</span><i>&emsp; different, the differential is only changed to dy if the argument is a pure variable, if not then the original dx is used</i><br>
<span onclick="cE2C (this)">\int sin y dx</span><i>&emsp; which results in this expression</i><br>
<br>
</p>

<h4 id="Pseudo-Function Examples">Pseudo-Function Examples</h4>

<p>
<span onclick="cE2C (this)">delall</span><br>
<span onclick="cE2C (this)">1 + 2</span><i>&emsp; evaluated</i><br>
<span onclick="cE2C (this)">%(1 + 2)</span><i>&emsp; unevaluated</i><br>
<span onclick="cE2C (this)">_</span><i>&emsp; evaluated</i><br>
<span onclick="cE2C (this)">x, y = 1, 2</span><br>
<span onclick="cE2C (this)">x + y</span><i>&emsp; evaluated</i><br>
<span onclick="cE2C (this)">%(x + y)</span><i>&emsp; unevaluated but remapped</i><br>
<span onclick="cE2C (this)">%@(x + y)</span><i>&emsp; unevaluated and not remapped</i><br>
<span onclick="cE2C (this)">@(x + y)</span><i>&emsp; not remapped and thus unevaluated</i><br>
<br>

<span onclick="cE2C (this)">delall</span><br>
<span onclick="cE2C (this)">x</span><i>&emsp; just a happy little "x"</i><br>
<span onclick="cE2C (this)">x = 2</span><br>
<span onclick="cE2C (this)">x</span><i>&emsp; now the "x" has value</i><br>
<span onclick="cE2C (this)">@x</span><i>&emsp; no-remap removes that value</i><br>
<span onclick="cE2C (this)">lambda x: x</span><i>&emsp; local variable "x" as expected, not global</i><br>
<span onclick="cE2C (this)">lambda x: @x</span><i>&emsp; now the global value of "x"</i><br>
<span onclick="cE2C (this)">lambda x: @@x</span><i>&emsp; back to "x" and will take on the value of the local "x" since we stepped out beyond the global scope</i><br>
<br>

<span onclick="cE2C (this)">delall</span><br>
<span onclick="cE2C (this)">lambda x: \int x + 1 + 2 dx</span><i>&emsp; body is evaluated but not "doit()"ed</i><br>
<span onclick="cE2C (this)">lambda x: %(\int x + 1 + 2 dx)</span><i>&emsp; body is evaluated and "doit()"ed</i><br>
<span onclick="cE2C (this)">lambda x: %%(\int x + 1 + 2 dx)</span><i>&emsp; body is fully unevaluated</i><br>
<span onclick="cE2C (this)">lambda x: %%(\int x + y + y dx)</span><i>&emsp; same</i><br>
<span onclick="cE2C (this)">y = 2</span><br>
<span onclick="cE2C (this)">lambda x: %%(\int x + y + y dx)</span><i>&emsp; however, variables are mapped</i><br>
<span onclick="cE2C (this)">lambda x: %%@(\int x + y + y dx)</span><i>&emsp; now they are not</i><br>
<span onclick="cE2C (this)">lambda x: %%(\int x + @y + y dx)</span><i>&emsp; now just one variable is mapped</i><br>
<br>
</p>

<h4 id="Last Expression Variable">Last Expression Variable</h4>

<p>
<span onclick="cE2C (this)">1</span><i>&emsp; "evaluate" 1 to set the last expression variable "_" to this</i><br>
<span onclick="cE2C (this)">expand (_(x+1))</span><br>
<span onclick="cE2C (this)">expand (_(x+1))</span><i>&emsp; or press the up arrow</i><br>
<span onclick="cE2C (this)">expand (_(x+1))</span><i>&emsp; ...</i><br>
<br>
<span onclick="cE2C (this)">1, 1</span><i>&emsp; again, set initial values</i><br>
<span onclick="cE2C (this)">_[1], _[0] + _[1]</span><br>
<span onclick="cE2C (this)">_[1], _[0] + _[1]</span><i>&emsp; or press the up arrow</i><br>
<span onclick="cE2C (this)">_[1], _[0] + _[1]</span><br>
<span onclick="cE2C (this)">_[1], _[0] + _[1]</span><i>&emsp; notice a pattern?</i><br>
<br>
</p>

<h4 id="Plotting Functions">Plotting Functions</h4>

<p>
<span onclick="cE2C (this)">delall</span><i>&emsp; make sure no variables are mapped</i><br>
<span onclick="cE2C (this)">plotf (2, x**2)</span><i>&emsp; plot expression directly</i><br>
<span onclick="cE2C (this)">f = x**2</span><br>
<span onclick="cE2C (this)">plotf (2, f)</span><i>&emsp; plot expression from assigned variable</i><br>
<span onclick="cE2C (this)">plotf (2, lambda x: x**2)</span><i>&emsp; plot lambda function directly</i><br>
<span onclick="cE2C (this)">l = lambda x: x**2</span><br>
<span onclick="cE2C (this)">plotf (2, l)</span><i>&emsp; plot lambda function from variable</i><br>
<span onclick="cE2C (this)">plotf (2, lambda x: l (x - 1))</span><i>&emsp; plot lambda function from variable modifying the argument</i><br>
<span onclick="cE2C (this)">plotf (2, f if x &lt; 0, 'r', f if x >= 0, 'g')</span><i>&emsp; different colors on either side of x = 0</i><br>
<span onclick="cE2C (this)">plotf ('+', 2 if l (x) &lt; 2)</span><i>&emsp; add to previous plot</i><br>
<span onclick="cE2C (this)">plotf ('+', 2, -4, 4, -x**2)</span><br>
<br>
</p>

<h4 id="Plotting Vector Walks">Plotting Vector Walks</h4>

<p>
<span onclick="cE2C (this)">delall</span><i>&emsp; make sure no variables are mapped</i><br>
<span onclick="cE2C (this)">f = lambda x, y: (2x + sec**2x) / 2y</span><br>
<span onclick="cE2C (this)">plotv (6, -6, 6, f, fs = -8, res = 33)</span><i>&emsp;plot vector field</i><br>
<span onclick="cE2C (this)">plotw ('+', f, (-1, 0))</span><i>&emsp;add first walk starting at point (-1, 0)</i><br>
<span onclick="cE2C (this)">plotw ('+', f, (-2, 0))</span><br>
<span onclick="cE2C (this)">plotw ('+', f, (-3, 0))</span><br>
<span onclick="cE2C (this)">plotw ('+', f, (-4, 0))</span><br>
<span onclick="cE2C (this)">plotw ('+', f, (-5, 0))</span><br>
<br>
</p>

<h2 id="Appendix">Appendix</h2>

<h4 id="Assumptions">Assumptions</h4>

<p>This is a list of the assumptions that can be applied to symbols and undefined functions.</p>

<table class="HelpTable">
<tr><td><b>commutative</b></td><td>-</td><td>Object commutes with any other object with respect to multiplication operation.</td></tr>
<tr><td><b>complex</b></td><td>-</td><td>Object can have only values from the set of complex numbers.</td></tr>
<tr><td><b>imaginary</b></td><td>-</td><td>Object is a number that can be written as a real number multiplied by the imaginary unit "<b>i</b>" (excluding "<b>0</b>").</td></tr>
<tr><td><b>real</b></td><td>-</td><td>Object can have only values from the set of real numbers.</td></tr>
<tr><td><b>integer</b></td><td>-</td><td>Object can have only values from the set of integers.</td></tr>
<tr><td><b>odd, even</b></td><td>-</td><td>Object can have only values from the set of odd (even).</td></tr>
<tr><td><b>prime</b></td><td>-</td><td>Object is a natural number greater than "<b>1</b>" that has no positive divisors other than "<b>1</b>" and itself.</td></tr>
<tr><td><b>composite</b></td><td>-</td><td>Object is a positive integer that has at least one positive divisor other than "<b>1</b>" or the number itself.</td></tr>
<tr><td><b>zero</b></td><td>-</td><td>Object has the value of "<b>0</b>".</td></tr>
<tr><td><b>nonzero</b></td><td>-</td><td>Object is a real number that is not zero.</td></tr>
<tr><td><b>rational</b></td><td>-</td><td>Object can have only values from the set of rationals.</td></tr>
<tr><td><b>algebraic</b></td><td>-</td><td>Object can have only values from the set of algebraic numbers.</td></tr>
<tr><td><b>transcendental</b></td><td>-</td><td>Object can have only values from the set of transcendental numbers.</td></tr>
<tr><td><b>irrational</b></td><td>-</td><td>Object value cannot be represented exactly by Rational.</td></tr>
<tr><td><b>finite, infinite</b></td><td>-</td><td>Object absolute value is bounded (arbitrarily large).</td></tr>
<tr><td><b>negative, nonnegative</b></td><td>-</td><td>Object can have only negative (nonnegative) values.</td></tr>
<tr><td><b>positive, nonpositive</b></td><td>-</td><td>Object can have only positive (only nonpositive) values.</td></tr>
<tr><td><b>hermitian, antihermitian</b></td><td>-</td><td>Object belongs to the field of hermitian (antihermitian) operators.</td></tr>
</table>

<h4 id="Special Characters">Special Characters</h4>

<table class="HelpTable">
<tr><td><b>_</b></td><td>-</td><td>Underscore represents the last successfully evaluated expression, assignment to variables is not considered a successful evaluation for this purpose. Even if the object resulting from the expression is not natively known to SymPad it is stored on the server and so is available exactly as it was returned from the previous calculation by using underscore.</td></tr>
<tr><td><b>;</b></td><td>-</td><td>Semicolon is used to separate statements on a single line to be executed as if they were individual statements on separate lines like in Pyhon, e.g. "<b>1; expand (_(x+1)); expand (_(x+1)); expand (_(x+1))</b>".
There is one caveat at the moment in that parsing is influenced by the variables assigned and these assignments are not done until after the whole line has been parsed.
What this means for example is that for ambiguous expressions like "<b>f(x)</b>" which can be interpreted either as a concrete lambda call or an undefined function the parser will not get it right if the variable "<b>f</b>" is assigned a lambda and then called on the same line after a semicolon.
For this reason you should avoid using "<b>;</b>" with assignments, unless it is the last expression of the list.</td></tr>
<tr><td><b>Greek</b></td><td>-</td><td>α, β, γ, δ, ε, ζ, η, θ, ι, κ, λ, μ, ν, ξ, π, ρ, σ, τ, υ, φ, χ, ψ, ω, Γ, Δ, Θ, Λ, Ξ, Π, Σ, Υ, Φ, Ψ, Ω.</td></tr>
<tr><td><b>Symbols</b></td><td>-</td><td>∞, ≠, ≤, ≥, ∂, ∑, ∫, ∈, ∉, ∩, ∪, ⊖.</td></tr>
</table>

<h4 id="Admin Functions">Admin Functions</h4>

<table class="HelpTable">
<tr><td><b>vars ()</b></td><td>-</td><td>Show all currently mapped variables.</td></tr>
<tr><td><b>del (var1, var2, ...)</b></td><td>-</td><td>Delete individual variable assignments.</td></tr>
<tr><td><b>delall ()</b></td><td>-</td><td>Delete ALL variable assignments.</td></tr>
<tr><td><b>env ()</b></td><td>-</td><td>Show or change current SymPad runtime environment.
Called without any arguments it will show the current state of the environment.
If arguments are present they specify turning on or off a certain aspect of SymPad functionality.
For example the quick input functionality "<b>quick</b>" may be turned on by specifying "<b>env(quick)</b>", "<b>env('quick')</b>" or "<b>env(quick=True)</b>", to turn it off specify "<b>env('noquick')</b>" or "<b>env(quick=False)</b>".</td></tr>
<tr><td><b>envreset ()</b></td><td>-</td><td>Reset environment to what it was at startup.</td></tr>
</table>

<h4 id="Environment Settings for env()">Environment Settings for env()</h4>

<p>In quick input mode you should always use parentheses and in the case of the "<b>simplify</b>" option you need to use the quoted version "<b>env('simplify')</b>" due to that being a function name and how letters are parsed in quick mode.</p>

<table class="HelpTable">
<tr><td><b>EI</b></td><td>-</td><td>Use variables "<b>E</b>" and "<b>I</b>" as Euler's constant and imaginary unit as opposed to "<b>e</b>" and "<b>i</b>".
This allows the Python code copied from SymPad to work directly with SymPy where the uppercase constants are used.</td></tr>
<tr><td><b>quick</b></td><td>-</td><td>Quick single letter variable name input mode.</td></tr>
<tr><td><b>pyS</b></td><td>-</td><td>Python representation number escaping with the "<b>S()</b>" function where potentially necessary, e.g. "<b>S(2)/3</b>".</td></tr>
<tr><td><b>simplify</b></td><td>-</td><td>Post-evaluation simplification, this can sometimes cause problems if the expressions are somewhat complex since simplification can take some time, in that case simply turn this off.</td></tr>
<tr><td><b>matsimp</b></td><td>-</td><td>Matrix simplification, this turns on a patch to SymPy which does a basic simplification step on intermediate matrix multiplication products which prevents matrix operations from blowing up.</td></tr>
<tr><td><b>ufuncmap</b></td><td>-</td><td>When on, undefined functions resulting from SymPy calculations will be mapped back to the variables they are assigned to if possible.</td></tr>
<tr><td><b>prodrat</b></td><td>-</td><td>Controls whether products with leading rationals are displayed as $\frac75x^2$ (on), or $\frac{7x^2}5$ (off, default).</td></tr>
<tr><td><b>doit</b></td><td>-</td><td>Expression final SymPy doit() call. Normally after an expression is converted to an internal SymPy object that object's "<b>doit</b>" member is called to fully evaluate the expression, this can be surpressed by turning this option off.</td></tr>
<tr><td><b>strict</b></td><td>-</td><td>This controls whether the LaTeX generated for expressions is guaranteed to be pasteable into SymPad or if it looks prettier (default), it currently only affects how undefined functions are displayed.</td></tr>
<tr><td><b>N</b></td><td>-</td><td>Mapping access to the SymPy "<b>N()</b>" function via the "<b>N</b>" letter.</td></tr>
<tr><td><b>O</b></td><td>-</td><td>Mapping access to the SymPy "<b>O()</b>" function via the "<b>O</b>" letter.</td></tr>
<tr><td><b>S</b></td><td>-</td><td>Mapping access to the SymPy "<b>S()</b>" function via the "<b>S</b>" letter.</td></tr>
<tr><td><b>beta</b></td><td>-</td><td>Mapping access to the SymPy "<b>beta()</b>" function via the "<b>beta</b>" letter.</td></tr>
<tr><td><b>gamma</b></td><td>-</td><td>Mapping access to the SymPy "<b>gamma()</b>" function via the "<b>gamma</b>" letter.</td></tr>
<tr><td><b>Gamma</b></td><td>-</td><td>Mapping access to the SymPy "<b>Gamma()</b>" function via the "<b>Gamma</b>" letter.</td></tr>
<tr><td><b>Lambda</b></td><td>-</td><td>Mapping access to the SymPy "<b>Lambda()</b>" function via the "<b>Lambda</b>" letter.</td></tr>
<tr><td><b>zeta</b></td><td>-</td><td>Mapping access to the SymPy "<b>zeta()</b>" function via the "<b>zeta</b>" letter.</td></tr>
</table>

<h4 id="Command Line Arguments">Command Line Arguments</h4>

<p>
<b>sympad</b> [options] [host:port | host | :port]
</p>

<table class="HelpTable">
<tr><td><b>-h, --help</b></td><td>-</td><td>Show help information.</td></tr>
<tr><td><b>-v, --version</b></td><td>-</td><td>Show version string.</td></tr>
<tr><td><b>-n, --nobrowser</b></td><td>-</td><td>Don't start system browser to SymPad page.</td></tr>
<tr><td><b>-u, --ugly</b></td><td>-</td><td>Start in draft display style (only on command line).</td></tr>
<tr><td><b>-d, --debug</b></td><td>-</td><td>Dump debug info to server log.</td></tr>
<tr><td><b>-r, --restart</b></td><td>-</td><td>Restart server on source file changes (for development).</td></tr>
<tr><td><b>--EI, --noEI</b></td><td>-</td><td>Start with SymPy constants 'E' and 'I' or regular 'e' and 'i'.</td></tr>
<tr><td><b>--quick, --noquick</b></td><td>-</td><td>Start in/not quick input mode.</td></tr>
<tr><td><b>--pyS, --nopyS</b></td><td>-</td><td>Start with/out Python S escaping.</td></tr>
<tr><td><b>--simplify, --nosimplify</b></td><td>-</td><td>Start with/out post-evaluation simplification.</td></tr>
<tr><td><b>--matsimp, --nomatsimp</b></td><td>-</td><td>Start with/out matrix simplification.</td></tr>
<tr><td><b>--ufuncmap, --noufuncmap</b></td><td>-</td><td>Start with/out undefined function mapping back to variables.</td></tr>
<tr><td><b>--prodrat, --noprodrat</b></td><td>-</td><td>Start with/out separate product leading rational.</td></tr>
<tr><td><b>--doit, --nodoit</b></td><td>-</td><td>Start with/out automatic expression doit().</td></tr>
<tr><td><b>--strict, --nostrict</b></td><td>-</td><td>Start with/out strict LaTeX formatting.</td></tr>
<tr><td><b>--N, --noN</b></td><td>-</td><td>Start with/out N function.</td></tr>
<tr><td><b>--S, --noS</b></td><td>-</td><td>Start with/out S function.</td></tr>
<tr><td><b>--O, --noO</b></td><td>-</td><td>Start with/out O function.</td></tr>
<tr><td><b>--beta, --nobeta</b></td><td>-</td><td>Start with/out beta function.</td></tr>
<tr><td><b>--gamma, --nogamma</b></td><td>-</td><td>Start with/out gamma function.</td></tr>
<tr><td><b>--Gamma, --noGamma</b></td><td>-</td><td>Start with/out Gamma function.</td></tr>
<tr><td><b>--Lambda, --noLambda</b></td><td>-</td><td>Start with/out Lambda function.</td></tr>
<tr><td><b>--zeta, --nozeta</b></td><td>-</td><td>Start with/out zeta function.</td></tr>
</table>

<h4 id="Notes">Notes</h4>

<p>
<b>WARNING!</b> This http server implementation is nowhere near secure, this as well as the posibility of execution of arbitrary Python functions means you should never leave this server open to the internet by serving on an IP address visible to the external world.
</p><p>
Due to mixing operators from Python and LaTeX the grammar may be a little wonky in places so if something doesn't seem to work as it should try wrapping it in parentheses or putting a space between the problematic elements.
</p><p>
After filling up the browser with many pretty mathematical expressions you may note that it starts to get slower, simply reload the page, all your variables and history will be preserved.
Or try adding "<b>--ugly</b>" to the command line, this will use a quicker draft rendering for the math.
</p><p>
You can always tell whether SymPad will treat an identifier as a function or a variable by looking at the font which is used for the name, it is different for functions vs. variables.
Also, SymPad will give you an empty set of parentheses as an autocomplete option when it recognizes a function name (this only works for top-level SymPy functions).
</p><p>
If you are getting results which are just plain wrong, check to see if you have any variables or lambdas mapped which would be changing the evaluation.
</p><p>
If you are unable to use a keyword argument identifier in a function call because it is a reserved name then you can wrap it in quotes, Python strings are allowed as keyword identifiers specifically for this purpose.
</p><p>
Usually SymPad should be smart enough to figure out when a function argument is an equation and when it is a keyword assignment like in this function "<b>solve(x**2 = i)</b>".
However some room for ambiguity remains like in this function "<b>solve(i = x**2)</b>".
In cases like these you need to pass the equation followed by an explicit non-keyword argument like "<b>solve(i = x**2, x)</b>", by using the strict comparison double equals "<b>solve(i == x**2)</b>" or by wrapping the single-equals equation in parentheses "<b>solve((i = x**2))</b>".
</p><p>
The LaTeX parsing is not really comprehensive, it is geared towards being able to read back the LaTeX emitted by SymPad correctly.
</p><p>
SymPad inserts the current working directory at the beginning of the Python module search path which means that for example if you run SymPad in the SymPy development directory then the SymPy module used will be the development version.
</p><p>
The server runs a single thread for consistency and keeps a single global state (variables), this means that if have two browser windows open the results of calculations in one will be reflected in the other.
If you want a completely separate context then you can run another SymPad on a different port.
</p><p>
Keep in mind that if you copy to Python code and want it to run in SymPy you must be in uppercase "<b>EI</b>" mode or have the variables "<b>e</b>" and "<b>i</b>" in Python assigned to "<b>E</b>" and "<b>I</b>".
</p><p>
Quick input mode can cause keyword arguments to not be recognized correctly since it always separates letters into variables, if some keyword argument is not working in quick input mode then wrap it in quotes.
</p><p>
SymPad allows mixing of Python and SymPy objects in certain contexts where this would not normally be allowed by SymPy.
In these cases the Python code generated will not work in SymPy alone as those kinds of operations are not supported by SymPy, for example: "<b>x and [1, 2, 3]</b>" generates "<b>And (x, [1, 2, 3])</b>" which will not work outside of SymPad.
</p><p>
There are many SymPy objects which SymPad does not understand natively yet.
In any case where such an object is the result of an evalutation then the SymPy LaTeX representation will be used for the displayed answer and the SymPy str version of the element will be used in the native representation string, as well as the actual object which resulted from the previous calculation being stored in the "<b>_</b>" variable.
This should allow you to continue working with the calculation.
</p>

<br><br>
<div align="center">
Copyright (c) 2019 Tomasz Pytel, All rights reserved.

SymPad on GitHub: <a target="_blank" href="https://github.com/Pristine-Cat/SymPad">https://github.com/Pristine-Cat/SymPad</a>
</div>

</body>
</html>
""".encode ("utf8"),

	'bg.png': # bg.png

		b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x01\x00\x00\x00\x01\x00\x08\x02\x00\x00\x00\xd3\x10?1\x00\x00\x00\tpHYs\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\x00\x00\nOiCCPPh'
		b'otoshop ICC profile\x00\x00x\xda\x9dSgTS\xe9\x16=\xf7\xde\xf4BK\x88\x80\x94KoR\x15\x08 RB\x8b\x80\x14\x91&*!\t\x10J\x88!\xa1\xd9\x15Q\xc1'
		b'\x11EE\x04\x1b\xc8\xa0\x88\x03\x8e\x8e\x80\x8c\x15Q,\x0c\x8a\n\xd8\x07\xe4!\xa2\x8e\x83\xa3\x88\x8a\xca\xfb\xe1{\xa3k\xd6\xbc\xf7\xe6\xcd\xfe\xb5\xd7>\xe7\xac\xf3\x9d\xb3\xcf\x07\xc0\x08\x0c\x96H3Q5\x80\x0c\xa9B\x1e'
		b'\x11\xe0\x83\xc7\xc4\xc6\xe1\xe4.@\x81\n$p\x00\x10\x08\xb3d!s\xfd#\x01\x00\xf8~<<+"\xc0\x07\xbe\x00\x01x\xd3\x0b\x08\x00\xc0M\x9b\xc00\x1c\x87\xff\x0f\xeaB\x99\\\x01\x80\x84\x01\xc0t\x918K\x08'
		b"\x80\x14\x00@z\x8eB\xa6\x00@F\x01\x80\x9d\x98&S\x00\xa0\x04\x00`\xcbcb\xe3\x00P-\x00`'\x7f\xe6\xd3\x00\x80\x9d\xf8\x99{\x01\x00[\x94!\x15\x01\xa0\x91\x00 \x13e\x88D\x00h;\x00\xac\xcfV\x8a"
		b'E\x00X0\x00\x14fK\xc49\x00\xd8-\x000IWfH\x00\xb0\xb7\x00\xc0\xce\x10\x0b\xb2\x00\x08\x0c\x000Q\x88\x85)\x00\x04{\x00`\xc8##x\x00\x84\x99\x00\x14F\xf2W<\xf1+\xae\x10\xe7*\x00\x00x'
		b'\x99\xb2<\xb9$9E\x81[\x08-q\x07WW.\x1e(\xceI\x17+\x146a\x02a\x9a@.\xc2y\x99\x192\x814\x0f\xe0\xf3\xcc\x00\x00\xa0\x91\x15\x11\xe0\x83\xf3\xfdx\xce\x0e\xae\xce\xce6\x8e\xb6\x0e_-\xea'
		b'\xbf\x06\xff"bb\xe3\xfe\xe5\xcf\xabp@\x00\x00\xe1t~\xd1\xfe,/\xb3\x1a\x80;\x06\x80m\xfe\xa2%\xee\x04h^\x0b\xa0u\xf7\x8bf\xb2\x0f@\xb5\x00\xa0\xe9\xdaW\xf3p\xf8~<<E\xa1\x90\xb9\xd9\xd9\xe5'
		b'\xe4\xe4\xd8J\xc4B[a\xcaW}\xfeg\xc2_\xc0W\xfdl\xf9~<\xfc\xf7\xf5\xe0\xbe\xe2$\x812]\x81G\x04\xf8\xe0\xc2\xcc\xf4L\xa5\x1c\xcf\x92\t\x84b\xdc\xe6\x8fG\xfc\xb7\x0b\xff\xfc\x1d\xd3"\xc4Ib\xb9'
		b'X*\x14\xe3Q\x12q\x8eD\x9a\x8c\xf32\xa5"\x89B\x92)\xc5%\xd2\xffd\xe2\xdf,\xfb\x03>\xdf5\x00\xb0j>\x01{\x91-\xa8]c\x03\xf6K\'\x10Xt\xc0\xe2\xf7\x00\x00\xf2\xbbo\xc1\xd4(\x08\x03\x80'
		b'h\x83\xe1\xcfw\xff\xef?\xfdG\xa0%\x00\x80fI\x92q\x00\x00^D$.T\xca\xb3?\xc7\x08\x00\x00D\xa0\x81*\xb0A\x1b\xf4\xc1\x18,\xc0\x06\x1c\xc1\x05\xdc\xc1\x0b\xfc`6\x84B$\xc4\xc2B\x10B\nd'
		b'\x80\x1cr`)\xac\x82B(\x86\xcd\xb0\x1d*`/\xd4@\x1d4\xc0Qh\x86\x93p\x0e.\xc2U\xb8\x0e=p\x0f\xfaa\x08\x9e\xc1(\xbc\x81\t\x04A\xc8\x08\x13a!\xda\x88\x01b\x8aX#\x8e\x08\x17\x99\x85\xf8'
		b'!\xc1H\x04\x12\x8b$ \xc9\x88\x14Q"K\x915H1R\x8aT UH\x1d\xf2=r\x029\x87\\F\xba\x91;\xc8\x002\x82\xfc\x86\xbcG1\x94\x81\xb2Q=\xd4\x0c\xb5C\xb9\xa87\x1a\x84F\xa2\x0b\xd0d'
		b't1\x9a\x8f\x16\xa0\x9b\xd0r\xb4\x1a=\x8c6\xa1\xe7\xd0\xabh\x0f\xda\x8f>C\xc70\xc0\xe8\x18\x073\xc4l0.\xc6\xc3B\xb18,\t\x93c\xcb\xb1"\xac\x0c\xab\xc6\x1a\xb0V\xac\x03\xbb\x89\xf5c\xcf\xb1w\x04'
		b'\x12\x81E\xc0\t6\x04wB a\x1eAHXLXN\xd8H\xa8 \x1c$4\x11\xda\t7\t\x03\x84Q\xc2\'"\x93\xa8K\xb4&\xba\x11\xf9\xc4\x18b21\x87XH,#\xd6\x12\x8f\x13/\x10{\x88C\xc4'
		b"7$\x12\x89C2'\xb9\x90\x02I\xb1\xa4T\xd2\x12\xd2F\xd2nR#\xe9,\xa9\x9b4H\x1a#\x93\xc9\xdadk\xb2\x079\x94, +\xc8\x85\xe4\x9d\xe4\xc3\xe43\xe4\x1b\xe4!\xf2[\n\x9db@q\xa4\xf8S"
		b'\xe2(R\xcajJ\x19\xe5\x10\xe54\xe5\x06e\x982AU\xa3\x9aR\xdd\xa8\xa1T\x115\x8fZB\xad\xa1\xb6R\xafQ\x87\xa8\x134u\x9a9\xcd\x83\x16IK\xa5\xad\xa2\x95\xd3\x1ah\x17h\xf7i\xaf\xe8t\xba\x11'
		b'\xdd\x95\x1eN\x97\xd0W\xd2\xcb\xe9G\xe8\x97\xe8\x03\xf4w\x0c\r\x86\x15\x83\xc7\x88g(\x19\x9b\x18\x07\x18g\x19w\x18\xaf\x98L\xa6\x19\xd3\x8b\x19\xc7T071\xeb\x98\xe7\x99\x0f\x99oUX*\xb6*|\x15\x91\xca'
		b'\n\x95J\x95&\x95\x1b*/T\xa9\xaa\xa6\xaa\xde\xaa\x0bU\xf3U\xcbT\x8f\xa9^S}\xaeFU3S\xe3\xa9\t\xd4\x96\xabU\xaa\x9dP\xebS\x1bSg\xa9;\xa8\x87\xaag\xa8oT?\xa4~Y\xfd\x89\x06Y'
		b'\xc3L\xc3OC\xa4Q\xa0\xb1_\xe3\xbc\xc6 \x0bc\x19\xb3x,!k\r\xab\x86u\x815\xc4&\xb1\xcd\xd9|v*\xbb\x98\xfd\x1d\xbb\x8b=\xaa\xa9\xa19C3J3W\xb3R\xf3\x94f?\x07\xe3\x98q\xf8\x9c'
		b"tN\t\xe7(\xa7\x97\xf3~\x8a\xde\x14\xef)\xe2)\x1b\xa64L\xb91e\\k\xaa\x96\x97\x96X\xabH\xabQ\xabG\xeb\xbd6\xae\xed\xa7\x9d\xa6\xbdE\xbbY\xfb\x81\x0eA\xc7J'\\'Gg\x8f\xce\x05\x9d\xe7"
		b'S\xd9S\xdd\xa7\n\xa7\x16M=:\xf5\xae.\xaak\xa5\x1b\xa1\xbbDw\xbfn\xa7\xee\x98\x9e\xbe^\x80\x9eLo\xa7\xdey\xbd\xe7\xfa\x1c}/\xfdT\xfdm\xfa\xa7\xf5G\x0cX\x06\xb3\x0c$\x06\xdb\x0c\xce\x18<\xc5'
		b'5qo<\x1d/\xc7\xdb\xf1QC]\xc3@C\xa5a\x95a\x97\xe1\x84\x91\xb9\xd1<\xa3\xd5F\x8dF\x0f\x8ci\xc6\\\xe3$\xe3m\xc6m\xc6\xa3&\x06&!&KM\xeaM\xee\x9aRM\xb9\xa6)\xa6;L;'
		b'L\xc7\xcd\xcc\xcd\xa2\xcd\xd6\x995\x9b=1\xd72\xe7\x9b\xe7\x9b\xd7\x9b\xdf\xb7`ZxZ,\xb6\xa8\xb6\xb8eI\xb2\xe4Z\xa6Y\xee\xb6\xbcn\x85Z9Y\xa5XUZ]\xb3F\xad\x9d\xad%\xd6\xbb\xad\xbb\xa7\x11'
		b'\xa7\xb9N\x93N\xab\x9e\xd6g\xc3\xb0\xf1\xb6\xc9\xb6\xa9\xb7\x19\xb0\xe5\xd8\x06\xdb\xae\xb6m\xb6}agb\x17g\xb7\xc5\xae\xc3\xee\x93\xbd\x93}\xba}\x8d\xfd=\x07\r\x87\xd9\x0e\xab\x1dZ\x1d~s\xb4r\x14:V:'
		b'\xde\x9a\xce\x9c\xee?}\xc5\xf4\x96\xe9/gX\xcf\x10\xcf\xd83\xe3\xb6\x13\xcb)\xc4i\x9dS\x9b\xd3Gg\x17g\xb9s\x83\xf3\x88\x8b\x89K\x82\xcb.\x97>.\x9b\x1b\xc6\xdd\xc8\xbd\xe4Jt\xf5q]\xe1z\xd2\xf5'
		b'\x9d\x9b\xb3\x9b\xc2\xed\xa8\xdb\xaf\xee6\xeei\xee\x87\xdc\x9f\xcc4\x9f)\x9eY3s\xd0\xc3\xc8C\xe0Q\xe5\xd1?\x0b\x9f\x950k\xdf\xac~OCO\x81g\xb5\xe7#/c/\x91W\xad\xd7\xb0\xb7\xa5w\xaa\xf7a'
		b'\xef\x17>\xf6>r\x9f\xe3>\xe3<7\xde2\xdeY_\xcc7\xc0\xb7\xc8\xb7\xcbO\xc3o\x9e_\x85\xdfC\x7f#\xffd\xffz\xff\xd1\x00\xa7\x80%\x01g\x03\x89\x81A\x81[\x02\xfb\xf8z|!\xbf\x8e?:\xdbe'
		b"\xf6\xb2\xd9\xedA\x8c\xa0\xb9A\x15A\x8f\x82\xad\x82\xe5\xc1\xad!h\xc8\xec\x90\xad!\xf7\xe7\x98\xce\x91\xcei\x0e\x85P~\xe8\xd6\xd0\x07a\xe6a\x8b\xc3~\x0c'\x85\x87\x85W\x86?\x8ep\x88X\x1a\xd11\x975w"
		b'\xd1\xdcCs\xdfD\xfaD\x96D\xde\x9bg1O9\xaf-J5*>\xaa.j<\xda7\xba4\xba?\xc6.fY\xcc\xd5X\x9dXIlK\x1c9.*\xae6nl\xbe\xdf\xfc\xed\xf3\x87\xe2\x9d\xe2\x0b\xe3{'
		b'\x17\x98/\xc8]py\xa1\xce\xc2\xf4\x85\xa7\x16\xa9.\x12,:\x96@L\x88N8\x94\xf0A\x10*\xa8\x16\x8c%\xf2\x13w%\x8e\ny\xc2\x1d\xc2g"/\xd16\xd1\x88\xd8C\\*\x1eN\xf2H*Mz\x92\xec'
		b"\x91\xbc5y$\xc53\xa5,\xe5\xb9\x84'\xa9\x90\xbcL\rL\xdd\x9b:\x9e\x16\x9av m2=:\xbd1\x83\x92\x91\x90qB\xaa!M\x93\xb6g\xeag\xe6fv\xcb\xace\x85\xb2\xfe\xc5n\x8b\xb7/\x1e\x95\x07"
		b'\xc9k\xb3\x90\xac\x05Y-\n\xb6B\xa6\xe8TZ(\xd7*\x07\xb2geWf\xbf\xcd\x89\xca9\x96\xab\x9e+\xcd\xed\xcc\xb3\xca\xdb\x907\x9c\xef\x9f\xff\xed\x12\xc2\x12\xe1\x92\xb6\xa5\x86KW-\x1dX\xe6\xbd\xacj9'
		b'\xb2<qy\xdb\n\xe3\x15\x05+\x86V\x06\xac<\xb8\x8a\xb6*m\xd5O\xab\xedW\x97\xae~\xbd&zMk\x81^\xc1\xca\x82\xc1\xb5\x01k\xeb\x0bU\n\xe5\x85}\xeb\xdc\xd7\xed]OX/Y\xdf\xb5a\xfa\x86\x9d'
		b'\x1b>\x15\x89\x8a\xae\x14\xdb\x17\x97\x15\x7f\xd8(\xdcx\xe5\x1b\x87o\xca\xbf\x99\xdc\x94\xb4\xa9\xab\xc4\xb9d\xcff\xd2f\xe9\xe6\xde-\x9e[\x0e\x96\xaa\x97\xe6\x97\x0en\r\xd9\xda\xb4\r\xdfV\xb4\xed\xf5\xf6E\xdb/\x97'
		b'\xcd(\xdb\xbb\x83\xb6C\xb9\xa3\xbf<\xb8\xbce\xa7\xc9\xce\xcd;?T\xa4T\xf4T\xfaT6\xee\xd2\xdd\xb5a\xd7\xf8n\xd1\xee\x1b{\xbc\xf64\xec\xd5\xdb[\xbc\xf7\xfd>\xc9\xbe\xdbU\x01UM\xd5f\xd5e\xfbI'
		b'\xfb\xb3\xf7?\xae\x89\xaa\xe9\xf8\x96\xfbm]\xadNmq\xed\xc7\x03\xd2\x03\xfd\x07#\x0e\xb6\xd7\xb9\xd4\xd5\x1d\xd2=TR\x8f\xd6+\xebG\x0e\xc7\x1f\xbe\xfe\x9d\xefw-\r6\rU\x8d\x9c\xc6\xe2#pDy\xe4\xe9'
		b'\xf7\t\xdf\xf7\x1e\r:\xdav\x8c{\xac\xe1\x07\xd3\x1fv\x1dg\x1d/jB\x9a\xf2\x9aF\x9bS\x9a\xfb[b[\xbaO\xcc>\xd1\xd6\xea\xdez\xfcG\xdb\x1f\x0f\x9c4<YyJ\xf3T\xc9i\xda\xe9\x82\xd3\x93g'
		b'\xf2\xcf\x8c\x9d\x95\x9d}~.\xf9\xdc`\xdb\xa2\xb6{\xe7c\xce\xdfj\x0fo\xef\xba\x10t\xe1\xd2E\xff\x8b\xe7;\xbc;\xce\\\xf2\xb8t\xf2\xb2\xdb\xe5\x13W\xb8W\x9a\xaf:_m\xeat\xea<\xfe\x93\xd3O\xc7\xbb'
		b"\x9c\xbb\x9a\xae\xb9\\k\xb9\xeez\xbd\xb5{f\xf7\xe9\x1b\x9e7\xce\xdd\xf4\xbdy\xf1\x16\xff\xd6\xd5\x9e9=\xdd\xbd\xf3zo\xf7\xc5\xf7\xf5\xdf\x16\xdd~r'\xfd\xce\xcb\xbb\xd9w'\xee\xad\xbcO\xbc_\xf4@\xedA"
		b'\xd9C\xdd\x87\xd5?[\xfe\xdc\xd8\xef\xdc\x7fj\xc0w\xa0\xf3\xd1\xdcG\xf7\x06\x85\x83\xcf\xfe\x91\xf5\x8f\x0fC\x05\x8f\x99\x8f\xcb\x86\r\x86\xeb\x9e8>99\xe2?r\xfd\xe9\xfc\xa7C\xcfd\xcf&\x9e\x17\xfe\xa2\xfe\xcb'
		b'\xae\x17\x16/~\xf8\xd5\xeb\xd7\xce\xd1\x98\xd1\xa1\x97\xf2\x97\x93\xbfm|\xa5\xfd\xea\xc0\xeb\x19\xaf\xdb\xc6\xc2\xc6\x1e\xbe\xc9x31^\xf4V\xfb\xed\xc1w\xdcw\x1d\xef\xa3\xdf\x0fO\xe4| \x7f(\xffh\xf9\xb1\xf5S'
		b'\xd0\xa7\xfb\x93\x19\x93\x93\xff\x04\x03\x98\xf3\xfcc3-\xdb\x00\x00\x00 cHRM\x00\x00z%\x00\x00\x80\x83\x00\x00\xf9\xff\x00\x00\x80\xe9\x00\x00u0\x00\x00\xea`\x00\x00:\x98\x00\x00\x17o\x92_\xc5F\x00\x00\xbd'
		b'\x89IDATx\xdad\xdd\xc9zcg\x92\x84i\x02\x9c\xc9\xab\xedE\xdfuVef\xa78\x82C/>\xe8\r+\xa4\x16z\xa4\x08\x128\xe7\x1f|077?\xfc\xfc\xbc\xbe\xbd\xbd=<<\xfc\xfe\xfe~\x7f'
		b'\x7f______\x7f|||\x7f\x7f?<<|}}\x1d\x8f\xc7\xeb\xeb\xeb\x9f\x9f\x9f\xe3\xf1\xf8\xf1\xf1qww\xf7\xfe\xfe\xfe\xfb\xfb\xeb\x0f\x7f~~\xae\xae\xaennnnnn\xde\xdf\xdfoooO\xa7'
		b'\xd3\xd5\xd5\xd5\xdd\xdd\xdd\xf7\xf7\xf7\xfd\xfd\xfd\xd5\xd5\xd5\xcb\xcb\xcb\xf1x||||yy\xb9\xbe\xbe\xbe\xb9\xb9\xb9\xba\xba:\x1c\x0e\xef\xef\xefOOO}\xc2\xd7\xd7W?\xfc\xfb\xfb\xfb\xfa\xfa\xda\x8f\xfd\xfc\xfc\xdc\xdd'
		b'\xdd\xfd\xfc\xfc\xfc\xfe\xfe^]]}||\xf4T\x87\xc3\xe1\xea\xea\xea\xfa\xfa\xfa\xed\xed\xed\xe6\xe6\xe6\xfa\xfa\xfa\xeb\xeb\xab_\xf9\xfc\xfc\xec-\xde\xdf\xdf\xef\xee\xee>>>\x9e\x9e\x9e\xde\xde\xde\x0e\x87\xc3\xe1p\xb8\xbd'
		b'\xbd}{{{zz\xba\xba\xbaz}}\xed+\x0e\x87C_q<\x1e\xfb\xd8\xdb\xdb\xdb\xf7\xf7\xf7\xfe\xbcG\xbd\xbd\xbd\xfd\xf9\xf9\xe9\xe7{\xaa\xcf\xcf\xcf\xd3\xe9t8\x1c\xee\xef\xef\x7f~~N\xa7\xd3\xfd\xfd}\xdf'
		b'{uuu\x7f\x7f\xdf\xaf\x7f}}\xf5R\xd6\xa7E\xfb\xfe\xfe\xbe\xb9\xb9\xf9\xfd\xfd\xfd\xf9\xf9\xb9\xbe\xbeny\xaf\xaf\xaf\xef\xee\xee\xbe\xbe\xbeN\xa7\xd3\xef\xef\xef\xdd\xdd\xdd\xd5\xd5\xd5\xf1x\xfc\xfa\xfa\xba\xb9\xb9\xe9]'
		b'>>>noo?>>\xfa\x8a\xf7\xf7\xf7\xc7\xc7\xc7\xd7\xd7\xd7\x87\x87\x87>\xb9\x17\xe9c{\x8b\xc3\xe1p<\x1eO\xa7\xd3\xcf\xcf\xcf\xe3\xe3\xe3\xf7\xf7w\xdb\xdd\xc3\xf7\xbf___\xbf\xbf\xbf\x0f\x0f\x0f\x87\xc3\xe1'
		b'\xf7\xf7\xb7\xad\xf9\xfe\xfe\xfe\xfe\xfe\xee\x95\xfb\x90\xeb\xeb\xeb\xc3\xe1\xd0\xee;\t\xad[\xef\xd2~\xdd\xde\xde~~~\xb6)\xf7\xf7\xf7\xfd\xc7\xd7\xd7\xd7\xf7\xf7w\xa7\xe5\xf3\xf3\xb3\x8d;\x9dN}f\x0bxuu\xf5\xfd'
		b'\xfd}<\x1e[+\x0b\xde[tD\xdb\xe2\xdb\xdb\xdb\x16\xea\xe1\xe1\xe1\xfd\xfd\xbd\xc5l\xfb\x0e\x87CG\xe5x<\xf6\xc3}~\x8f\xd7\xff\xb6}\xc7\xd7\xd7\xd7\xa7\xa7\xa7\xd3\xe9\xd4*\x1f\x8fG[\xd2W\xda\x8c\xd7\xd7'
		b'\xd7\xe3\xf1\xd8Qxxx\xe8\x83Z\xbb\x9b\x9b\x9b\xb6\xe4\xf1\xf1\xf1\xe6\xe6\xa6\xeb\xf4\xfb\xfbkq\x0f\x87\xc3\xe3\xe3\xe3\xfb\xfb\xfb\xf1x\xbc\xbf\xbf?\xfd\xfd\xcf\xd3\xd3SKc\x05[Skt<\x1e\xdf\xde\xde:\xf1'
		b'\x9f\x9f\x9fWWWooo\xbf\x7f\xffs<\x1e[\xa3\xab\xab\xab\xfe\xa3\x87\xb9\xba\xbar\x0e\x1e\x1e\x1e>>>~~~:\xa6\xdf\xdf\xdfwww\xbf\xbf\xbfooo\xc7\xe3\xb1\x1f\xee\xe0\xb6\xbb\xdd\xa2\xbe\xf1\xe6'
		b'\xe6\xe6\xf1\xf1\xf1\xf6\xf6\xf6\xf6\xf6\xb6\x05\xe9`u\xc3\xaf\xae\xae\x1e\x1e\x1e:\x97\xdd\xff\xbe\xb7\xd5\xe8\x18\xfd\xfe\xfe\x9eN\xa7\xceV\xc7\xab\x05\xe9\x05[@O\xdbg\xf6\x9a-\xe3\xed\xed\xad\xcb\xd3\xa6\xb4\x1a\xfd\xd6\xf1'
		b'x|}}e;\xda\xf5\xdf\xdf\xdf\x8f\x8f\x8f\x96\xeet:e\x86:C=aw\xa9\xb5\xfa\xfe\xfe\xee\xe1???ooo\x1f\x1f\x1f;d\xfd\xaf\x1f\xee\xdd\xbb\xcc\xa7\xf9\xe7\xe1\xe1\xe1\xfe\xfe\x9ei\xf8\xfe\xfe\xee'
		b'z[\x9f\xe3\xf1\xd8Q\xfe\xfa\xfa\xea\xbdZ\xfc\xce[\xb7\xabK\xde\xe1\xf9\xfa\xfajy\xbb\xdewwwY\xae\xbe\xa2\x8b\xf7\xfd\xfd\xfd\xf4\xf4\xd4\x82|\x7f\x7f?>>v\xd6\xfb\xf0\xbb\xbb\xbb\xbe\xf4\xf9\xf9\xf9\xf3\xf3'
		b'\xb3-k\xfd\xfb\x81^\xe1\xe5\xe5\xe5\xf7\xf7\xf7\xe9\xe9\xe9\x98\t\xb9\xbd\xbd\xed|g\xfb\xdb?\xefsss\xd3+\xf5\x95\xfd\xd3\xa2\xdf\xdc\xdct\xe3s#\xdd\x99\xb7\xb7\xb7\xbb\xbb\xbb\xdb\xdb\xdb\xbe5\x8b\xf8\xf2\xf2r'
		b'ss\xe3j\xf6\xc3ooomyw\xafo\xec4___\xb7\x97www\x9d\xc8\xd6:\xa3\xf5\xf5\xf5\x95uoE\xda\x8f\x9e\xb0\x87\xbc\xbe\xbe\xce\xc6\x7f||d\x83[\xe2,VV\xb3\xb3\xd5\x9d\xef4{\x8c'
		b'\x0eq+\x98\x07\xc8\xeev\xf8\x1e\x1e\x1e2rY\x14\xab\xd7\xbd\xeau^__\xfbu\x86\xd3\t\xe8 v\xce\xb2#\xbch\xb7\xb4\xdb\xf2\xf3\xf3\xf3\xf2\xf2\x92\x81\xc8\xe0u\xe1\xbb\xa2]\xbc\x0c\xc7\xc7\xc7G\x96\xbe\xff'
		b'\xed\xdf}\xbe\xeb\xd4}\xce\x0cgM\xba\xd8\xd9\xa3\xb6\xa3\x0f\xe9\xaf:\xf1\x1d\xc7\xae\xd3\xe1px~~\xee\x14\xf6\x8b\x9d\x96\xfe\xa4/r\xd0s\xbfY\xa5\x8eP\x9f|8\x1cZ\x81\x87\x87\x87^\xbc\xf3\xdd\x03\xff\xfc'
		b'\xfct!\xb3\xbcmD\xc6;_\xd1N\x15G\xb4\xc5\xedK\xd6\xddo}\x7f\x7f\xbf\xbf\xbfw+\xae\xaf\xaf\xb3S}E\x87\xb9S\xf4\xf1\xf1q\x14\x15\x1c\x0e\x87,\xb7\x10\xa2\xc5\xb2\xe5\x9d\xf5^\xde\x06d8\xef\xee'
		b'\xee:O\xbcL\xa7\x81\xff}||\xbc\xbf\xbfo!\xee\xef\xef;\xf7\xc7\xe3\xf1\xe9\xe9\x89\x93i\xedN\xa7\xd3\xdb\xdb\x1b#t\x7f\x7f\xdfi\xe8\xee\xfe\xfe\xfe\xb6X\xbdF\xbfrww\xd7\xc3\xb7\x19y\xd2\xdch\xf1'
		b"U6\xa6\xfb\xd6\xcbv\x9439\xecP\xa6%\x9b'<\xeb\xfc}~~\xb6^___\xed\\&\x80iiAN\xa7S\xfb\xdd\x9atE[\xb4|Q\x16.\xe7\xd3i\xeeWr\x92}`\x81e\xcf\xdc\xa2\xb5\xe5"
		b'\xf911F\x9f\x99\x9b\xcd\xaf\xf6\xa8\xfd@\x7f\x98\x11\xd9\xe3\x95-\xcf\x81|~~\x16\xf9\x9cN\xa7\x1co\x81_\xcf\x93!h\xe5\xfb\xa2\xecc\xdb\xd1Y\xefWZ\xea>\xbcS(\xf6\xc8\xf4\xf4\x00>\x87/\xea\x9a'
		b'\xbd\xbd\xbd\x89\x1e3\x04\x8e_O\x95E\xf8\xfc\xfc\xec\xf8\xf6Q\xef\xef\xef\xa7\xd3\xe9\xf1\xf1\xb1\xe7l\x91]\x83.af\xf1\xe3\xe3\xa3\xa3\xd2\n\xf4W\xed\xe9\xdd\xdd\xdd\xb1\x13\x96\xa3\x17\x0c\xbc\xbf\xbfwv\xbf\xbe\xbe'
		b'\x9e\x9e\x9e>??___[\xf7\x8f\x8f\x8f\x1e:\x13\x98\xa3\xefU}\r/\xdf\xb1\xeb^u};%\xdd\xdd\xfe\x84y\xeb1:I\x19\x89\xde\xa7\xf7\xef\\\x9eN\xa7\xce\xae\x80\xadC\x96\xb1\xec\xca\x95\xc3x\xc9'
		b'~+\xe7\xde-\xea[\xae\xaf\xaf\x8b\x1f\xba?y\x83\x96\xfb\xeb\xeb\xeb\xe5\xe5%3\xdf\x17e\x0e\x0f\x7f\xff\x937\xeb\xbe\xb9\xa8\xd9QIQ\xe7\xacC\x93\x03\xe9\x01Z\x9c\x9e\xfc\x8f\x1d:\x1emgg7\xb7\xe6\xbc'
		b'v\x15\x8bs\xfapv\xba\x980[\xd8\x11\xe9\xa2\xf6-\x82]\xb6<c/\xea\xcb\x12=>>v2\x98a/\xdb)\xe7\x132\x85\xed\xb5\xb0\xa7\x87\xe9J\xdb\xebV\xb2g\xeb\x19\xb2>\x1d\xe2\xe2\x82~\xbe\x18\xac'
		b"\xdbR\xc8\xda\xe1\xc9\xaavZ\xfa^>\x93\xdb\x17k\xb4\\\xdd\xed\xe7\xe7\xe7\xa2\x86\x1e\xac7*\xc1xzz*\x03lso\xbad\x0f\x0f\x0f\xb9'\xc1\x80\xc0\xb1`#\x13\xc5\\===I\x19\x8b\xe72<y"
		b"\xc3\xd3\xe9\xd4\xe1\xe8\xcf\x05\x85r/\th\xe7\xb5;#\xb1ssJ^\xfbFK\xdc2\xb1\x10\xad\xe3\xdd\xdd\x9d\xe3\x92Y\x95\x0e\n9\xba\xc0\x99\xde.j\x11Q\xbe\xb2'\xef\x0e\xb4\xb5\xfdG\xe6\xaa\x13\xd9\t~"
		b'yyy~~v\xc7$\xfd\xfd0\xeb\xd5\x99+\x90\x95\xc8\xb6\x80\xeb\x8e\xcaz\xbb!\x85\x91\xdd\xdb\x16\xfc\xfd\xfd=\x87\xc9!\xf7\xe7\x1d\xca~\xac\x13\xd0\xb2\xf4\xe2\xc5Q=s\x9f\x9f\x07\xcb\xb7\xb7\xbc\xecw\xcf\xcc'
		b'y\xf2o\xfd\xae\x8bT\x00\xf9\xf3\xf3\xd3\xc6\xf1\x99mh\x11y\xaf\xd6\x11\xca\xb4\xb5\x9b]\xd1\xde\xab\x93\xd6V\x06u\x94\x94\xb7\xd7^\xa1\x0f\xecy^__\xbb\xf9}HqG\x0f\xe9\x04\x96\xc0\xb4P\xfdb\xbf\xdb'
		b'\xa9\xcb\xa6\x88u;\xd2\xbd\xef\xd9Yg\xe4\xdc\xa1v\xb7#\x98]\xe1|\x0bN^__\xbb\xe2\xc2\xdcN[\x0e\xd1BtF;\n\xf2\x8a\xbe\xf1\xe9\xe9\xa9\xbdiG;I};\xbb\xdb\x87\xb4X\x9cl\xef\xdfg'
		b'fr2$\xfdJ\xab\x999\xf4uY \x7f\xbe\xf1\xba\x8dyyyi\xe1\xee\xfe\xfe\xa7\xed\x97\x90\xf4\xb7///\xdd\xde\x8eu\x97\xa4\xaf\xeeO\xbe\xbe\xbe:\x9a-z\xf9(\xd4\xa80\xc0\x95\xe8a\x0e\x87C\xff'
		b"\xee\xed\xda\x8b\xf6;\xfb]\xdc(G\xeaK\xbbo\x05{\xa5\n9s\xf1\x8ct\xb6'\xec\x192\xa2<s?P\xd6t\x7f\x7f\xdf9+s\x90g\xf3'\xadF\x1f\xd2I\xd8\x14\xb9S\xc4\x96\x01*2a\xed\xa0\xb4"
		b"\x87Y\xe9z\xc0\x8b\xae\xfe\xfe\x076\x909\xee!K\xcc\n\xed\xb2,=mw\xbb\xe4!\xe4\xa0\xd5pl\xfa\xa7')\xe2z\x7f\x7f?\xbb\xe2.PG\xa7m.\xc4/\x16\xe7\x16\xbe\xbf\xbf;.\xfd@\x00Y\xe7"
		b'^\xdc\xd9G\xf52\xadEQMg\xda\xb3v}%\xfb\x96\xa6\xb3\x95i)\x85\xca6\x87!d\xad\x01d\xcet+\x92\x95\xea\xdew\x80\x8aId\xbd\xedS\x81\xb8\xa8\xb1M\xcd^\x16Z\xb4s\xe2\n\xd8n\xaf\xf9'
		b'\xf0\xf0\xf0\xf8\xf8h\xa1\xa4\x95lA\x1b\xd0\x1bu\xa6\x81\xa1\xedhQeK\xd1\xa2=??gS;\x16\xdd\x93.\x83\x85\xcdv\xb48\x1d\xee^\\\n!\t\xc9H\xf7\xa6-{\x81\x9c\x1f\xeb3\xc5f]\xa1,'
		b'h\xaf\x99O\x0e\xe5\xeb\x8bz\xa3\xac\xa9\xcdm+\xfb\xc0\x02\x1e^\xb7\x03\xd3\xcadIooo\xfb4i\x0f\xdf\x12,\xdb5\xee\xbe\x15OfI{\xaa\x02\xb9\xbe\xa8{R\x16\xdb\xf72\xbemD\xc9R\xbb\x96\xf1'
		b'\xed\xcf\xc5\xba\xfd\n\xbc\xe4\xf0\xf6\xf6OHv\x06R\xec\xd5/;X\xfdf\xd7\xabc\x17\x96Rb\xd4\xaf\xf4\xfeA\xablyk-[\xea\xdeg23!\x9d\x8c\xae\xfe\xcb\xcbK\x7f\xee\x1a\xf0}\xadr\x19\xaa\xab'
		b'R\xd8\xd0}\xeb\xf1$\x94\x19\xe6\xbc\x8a\xa3,\xeaUv\x00\xce\xf6\xed\x0f\x0f\x0f0\x87\x9e\xd9wu\xa5!\xd0\xacK\xf6\xa2HRD!\x14n\r\x0b&3]\xaf\xaf\xaf\xdd\xcc\x82\x1c\x90(\x07\xa84\xd1rA\xe4'
		b"\x80\xd9e\x0e\xadC\x0b\xd8\xe9\xec'\xfbso\x97\xb1\x97\xa0\xb7b\x99\x1e\xe5\x94n\xaf\xb0V\xd2\xc2N\xcb\xc7\xba\xd2Y\xebl\xd0\xe2%\xed\x97\xf5\x07I\xf5\xb2}{k\xd2\x87\xc8\xbeZ\x9f\xcc%\xe8/O\xdb!"
		b'\xc9\xafv\x0c\x8a\xc1>??\xfb\xf9\xf6\xb7\xac@\xd4\r\xb6.\xe2\x92S\x05\x1b\xb45g\x0f\xd0\t\x13\xd4\xb6\xf4B\xeanO\xf9Y\x06\xa6\xa3 \x86\xee~w\xfa[\x05\xd5\x8d\x96\xa9s\xdfk\xe4\x7f\xe5=\x1d\x9d'
		b'\xd7\xd7\xd7N\x0c\x9f\xde\xbdz~~\xbe\x80\xfc\x16\xf1\xb4\x04Y\xd0\x1e[B)\x06\xed\x87\x0br2l\x80\xb0\xcf\xcf\xcf\xce_\x97\xbfX3D\xa2e\xcaX\xb6\xa3\x1d\xb2\x1ckAT\xefx\xce\xa5\xfe.i}}'
		b"}\x153\x84\x17u%\x84\xd4\xbc|\xe6\xb6\x9f\x01\xa5\x97\xea\xf4\x03\xaeS\xff\xdb\x7fd\xfe\xc3X\xfb\xad\x0eh\x86\xb9W\xdbH\xc6Q\xc8\x94H\x82y\t'\xbb#\xeb\x18d\xda\xe4\xbb\xbdQ\x9e_i/SU\x1c"
		b'\xdb\xe3\x05\xcfg\x10;\x94\xe1\xa7\xadI\xf7\xf3\xe1\xe1\xa1\x0fiy[(\xb7]e\x8d\x8f\xcd\xbe\x14\x95\x81\xbf\xda\xe5\xcc_\xa7\xa2{Uv\xdb\xde\xf9\x84\x02\xa4~Q\xa8\x0cM>\x96\xff\x01.\xd6d\xf6&E '
		b'\x0e\x84L\xb1\x0f\xcdT\x14\x0e*-\xf1J\x1f\x1f\x1fl\x7fY\xb9\xa4\x82\xcd\xc8\x0e\x15\xa2\x05.\x81\xff\xfb\xdb~>\xe3\xd1k\xb7pY\xbeNLx\x8e\x82_\x96\xac\xd5\xdc\\P\x08\xd1\xfd\xe9\xf3\xf3\xef\xc0S('
		b'\r\xbf\xa1\x04\xe3(\xf7\x87\xdd\x93\xbeH@\xd8\xa1\xcc\xfavt\xca(:OB#\xa5\x95V\xb8\x84\xb8\xb7\xc8\x8f]__\x875\x95:\xb3\xebmA\xbf\xd8\xf2\x02\xacva\xd9\xe9\x85\xd2;j\xaf\xaf\xafJ\r\xc2'
		b'\xad<\x8f\xd0\xe5\xee\xee\xee\xf5\xf5\xf5\xf5\xf5Uy\xa1\xf7*@\x92\xb4\xb4\x98\xb0\xef>\xbf\nT\xd72\x87P$\xa6\x98Uh\xaa\x10\xd1m\xd9\xf2\x88\xf4I&\xa96\xdfe\xc8\xd1\xe51:\x15\xfdJ\xeb\x1f\xbeW'
		b'\xae\xd8\x9a\x80\xaa[\x99\xe2\xccs\x05\xb6\xc5\xed#*Q\xb5d\xa5\xb9\xfdU\xf6\xb8\x13\x0f\xa5.\xf2\xee\x0c\xf5\xe8\x85=\xaf\xaf\xaf\xea\xa6\x01\xd2\x8aM\xedJvB\x88\x82\xc2 \x9dj;\x155\x00\xde\xd5\x13\xfa\xd8'
		b'\x0c[\x7f\xbb\x91\x8fT)\x1b_I.{\x10f\x92\xd9S3\xee\x93U\xcbU4\xa1r\x05\xc4\xb9D`h\xc7\x17\x83\x00\xaa\rv\xcc\xc6\xf4F\x1d,\xaeF\x16!\ty~~\xce<\x83\x11\x83\xae\x83\xda\xbaN'
		b'0\x1f\xaf\\t\xa1\xc0$0\xe3W\xa1.\xe0\xd1b\xc5no\x01U+ \xdaQ\xdd\xcb\xac\xb4\xa1\x1d\x9a\xaek\xbb\xd9\x8e@u\xe5\x9ae\xb4\x1d\x80\x1e\x0f\x1e\xda\xa9\xc5*P\xb3\xeb\xb7d \x0cJW\xabc\xdd'
		b'&vI\x8a\xefY\xf7\\(\x9b\x1b\xd8\xda\x07\x8aN3p}if\xbd\xc8\xad\x0b|\xbc\xa8\x7f\x85\xbb-\x00\xec\xdexIyI[\xd2\xd3\xf0!]!5\x88\xccU+\xf8\xf6\xf6\x96\xd1\xb2\x7f\xaa6\xdd\x04\x90-'
		b"<\xb1\x8f\xea\x88\x835\xc4\x1b\x19\x12\xe9\x1a\xb2@_!2\x91\xe0\ny;\x10y\xb3~\xbdH\x83\x11B\x17)\xde\xe8'\xa5b\xc5\xd3\xbd\xe0\xd3\xd3S\x8f\xdd\xe1\x83\x8fu\xfd\xda\x00\xa7\x1f\xf4\x04\x8a\xc9\xac\x8as"
		b'\xb2\x9d9\x10at\x8fT\x1a\xd0\x07\xca\xb8zT\x18Cw@\xf2\xd7\x83asmn\x8d\xf6\xe2\xf6\xf6x\xaf\xaf\xaf\x9d\xd1\xea?\xcf\xcf\xcfY\xc6\xbeN!\xac\xef\xed\xf5}Q\x86\xafet\xe8\x9d\x8aL\x86`\xbd'
		b'\xc3# \x84\xa1\x0b\xa8\xc2\x9a\xa1Cr\xfd\xf7\xf7\xf7\x0c\x84\x17\xc9\x8e\xe0\x98\xf5\xca\x99-~\xb2\x8f\xed\xce(Q\x1f\x0e\x87sl\xad\xe0\x97\xd9\xc8\x0e\xf5;mOE\xd3N\xad*\x9aJpGd\xb7\x84C\x0c\xc9'
		b")\x9b\xe9\xb6l\x9e'\xc7R\x87R\x88\x80U\xab\xfb\xf0\xb6\xe0\xf0\xbe\xb7\xc7\xe8\xe5;\xf4\xcad\xfb\xb1`J9\t\x0cD\xce\xf7\xfd\xfd\xfd\xfc\xfc\x0c\x8c\xeb\x02\xecC\xaa\x0f\xf4\t\x18\x04\xe0\x9a\x97\x97\x97\xees\x97"
		b'\xb6\xcaF\x8e++\xb5\xcc\xbc\xbe\xa2 \xb5\'\xcf\xab\xf4\xb2\x9f\x9f\x9f\x05\xb5[\x8e\xc8"2l=\xd5\x19\xcf>\x1eq\xaez\xc8M\x1c\xfb\xf6\xf6\xa5mB\x1d\xcb\xe2(\xd6\x82\x07\xd8\x8b^Giy\xa3\xa6h\x8e'
		b'\xf9\xa8\xb7\xb7\xb7rE\xe5\xb6\xb7\xb77D\x9b\xfcp\x9f\x13\xf0\x9f\x15P\xc6\xca\xa5\xd8\xf1<\x89\x95\xefs\xca\xfa\xbax.?\x84\xa7\x9fqr\xdc\x84.\xb0<\xb8\xa4\xb1\xd5>\xa2Of\x83\x8b\xf8\xbb\xcd\xe5\xe3Y'
		b'\xb8\x1c\x90\xd4\xa1\x13\x93\x1d*[u\xbe\xf3\x18m|\xab\xd0\xe7\xf7\xa0\x19\xe9\x00\x16\x04\x87\x9eXEC\xa2\x99\x8d\x8f\xab\xd7\xca2?X\x06\xa5\xe0\x1d\x08\xe0Z\xa7\xaa\xdb\xa2j\x93yk\xa5\x9c\x95\x96\xa6\xd8\xa3\x8f'
		b'\xca\x90/h \x0c\xcd\xc3\xf6\x8e=\x12\xdekk\xc2\x10\x00\x85\xfb\xf5\xf7\xf7wU\xe7V\xbf\xbb\xa1\x80\xdf\xd3\xc2\x8e\x90G\xfaL\xd1B\x17\xaf8\x84m\x92\xd2dwsA\xf8\x88=6\xacy\xb97\x88\t\xc5*'
		b'\xa5(\xc5\xd9-\x02\x1aY\x9b\xe2O\xa4:\x1d\xe8\xc0uy\x85\xbc\xce\x9d)\x8a+\xf6\x88\xe7"1\x83S\xb5M\xc0\xd0\xeb\xeb\xeb\xa7\xa7\'\xa4&\xdc\x8d.U\x9e\xdf\x8b_\x94z\xd4\x10\xba\x8aA,\xb9\xa9\xa7\xa7'
		b"'\x05\x84\xec\xc8\xe1\xfd\xfd_8^\xe2\xf5\xee\\\xfe\x0e\x1f\x154\x9b\x11\x12ct\x08r.%\xcamU!l'>\x0f\xd5~\xbc\xbc\xbcd\x11K_\xe4F\x9d\x98\xa2=\x14\x80\xcc\xd8zU\xb9\x04jt\xa1\x94"
		b'\x1f\x10\xa1\xf6\xfe\xd0\xcf6^\x1aZ\x8c\xd7\xef2\xe4\xd8\x9ab\xa4 \xcb\xe2\x16\x01\xa1\x9a\xc6\xd2\x8f\x91\x9c\xd5\xe9\x14\xe9^^^\x9e\x9e\x9e\xda\xe9 c\xe1\x81\xdd\xed2 \x08\x08\xf4\xe1B\x1eI\x15\x19\xb3\x00i'
		b'T\xa8\x8dLn\xe5\xfb\x19h#\xa6V6^T\xd3\xdb\xf5\xca\xacu\xc7\xa0\x1d\xe9jeJ\xa4^\x19x\x10p&?_\x973\xe9\x8ea\xadu\x12P\xbe\x05l\xbd\xa6\xad/c\\\x9acf44\xb2C\xfb\xfc'
		b'\xfc\xdcO"P\xf49-5`\xb4C\x8e\xe3\x0c\xbb;cI=\x07`\xa7\x83[\x84 \xc2\xeb\xdd\xba\xc1x\x8b=\x9c=p\r:\xfd} \xd2X \x8c;\x8a\x9e*\xa3\xc8\xaa\xe1P\xe0\x84\x16\xc9e\xc0\xa2\x06'
		b'\xb4v\xd8\x88\x1d\xdf\xf6\xac\x0b\x90=p\xb6\x10\xd1\x02\xef2\x03\xe0\xa9\xea\x88!\xf4\x18\xf0\xf0\xb2\xa2\xa0\x1eU\xf8\x11\xd6\x91\x99\xc8fCK\x90G:\xa3\xb9J\x1b\xbf\x01+\xea\x9bt<\xb0\xa8\x13\xa3\xea\xd49\xeb'
		b'\xdf\xaa\xbc\xaa\xdd\xc5\xd3\x88*Q\x00\xe5\x0c\xed\x0eZ|\xa7?\xf6h.\xba}\x016\xc8\x07\x96\xaf\xb6\xe5\x88v\xb9`ik\xc6\xb2\xa6L\xcc\xcd\xcdM\x9cM9}@\xdf\x9a\xb3\xdc\xbb\xf8\xaa\xc2k\xcf\x10\xd1\xd8'
		b'\x0fg\xc8"qu\xaf\xf2\xc9=\x7f7\xa1m-L\xe2\xab\xe1?\xa2\xa9\xe2\x1aq\xc7M\x9f\xd5-\xcc\x9cK/@\x1c\x82iE"ey\xe5\xb3\xb6\xed\xed\xed\xad\x93\x91G\xd3\x0f\xd0\t`\x9b\xf1g\xa0O \x94'
		b'-\x11d\x06z\x07\x10/j\xae3\rj\xa8,\xb2D\x80\xfb\xfb\xfb\x90\xbb>\xd0\x0b\x16\xa2\xb0\xe2u\x93p\x11\x85\x92Z@T\xc4\xe4?\x19\xb0 ]\xd6\x08b\xbda\t\xceB~\xa6\xb3rq\xee;\xcd\xce\xba'
		b'\x1aE\x9b\xf2\xfc\xfc\\\x8d\x1c\xd3\xcb\xfb><<t\xa4\xa0\xf8\xd9<\xf1\xc9RMs/"\xae\x97\x97\x97\xe8\xae\xdd\xf3\xa7\xa7\'F]\x8e\xa4\xceSx\x96I\x92Pv=\xca![\x8a\x1e\xb8\'Q\xbd\xd9\x8c\xcb'
		b'+\xb4\xb0\xedf\x87\x8a3\xb1;\x1a_j`\xd2B #\x971\x16\x11\x14x\xe7\xbe|x\x96K,\xa3M\xe5O\x05\xf6\xaf\xbf\xfe\x11\xaa\x10\xc0\xa2\x86\x85{\xd3)i\x8f[\x8bM\xad\x90\xfe\xd5\xbdQ\xebJt'
		b"\x16\xcdUR\xedi\n\x8b\x03\x1cDr\x80\x02GD\xaa\xdd\xcf8\x1c.\x9e\xa2f'>o\x98\x89-=U\x96\x86>\xa1[\xab4\xb5\x8e\xfb\x8e///\x95Q\xba!XFm\x12\xbb\xd8\x1b)\xf3\xa9\x97\x83\xed"
		b'yT!\xb5bb\'\xcf\xa6z\x0b\xf0@1X\xaf\x0f\xe2\xc0Z\xeb\xbf\xb3\xe5\xad\xc9\xa6\xbcH\x84\xd1Iz\x8c\xcc\r\xb0\x0b\xa8\x8d\xfa!8\x14:\xb6\x05"\x9c\x02E.\xa5\xf3\xaai\x01Gu\xc1e\x18\x83\xf0'
		b'\xa3\xec\xb1\xdb\xab\x1e\x82\x0f\xa2\xc4\xd9j\xb4\xcb}8,k\xeb\xc7\xcf\xcf\xcf%!\x9d\xc3^\xa7\x15v{/p\xaa?F\xad[\x0e-Y\x1a\x16&\x16\xd2\xa9\xda{{\xbf\x9d\x1c\xd9\xdd*>\xadT\xae6S\xad'
		b'4\xb6f5O*7\xd0\x15\x15\x8a\x97\x93\x15\xbe/\xf9\xbe\x7fZ>\x07\x0e\xf9\xa4\xec\xb9\x94\xabe\xedxq)}~Gy\xc9\xd8\xc2Y\xbc\xc0\x02\xf7 \xb9\x1ct?)\xdcwdK\xb6\x00\xf9Ytq\x9a\x04'
		b'\xb4\x9f\xd7\xf9\xd9\xd7e\x02\x1c\x8e\xbeW\x8d<\x80AV#``\xef\xb7\x12\x97\r\xee\x91\xea\x1d\xebo\xabCm\x99OoMv$W\xbc-]\xd0\xb3\\\r\x16m\x86@\x14\x97%j\x1f\x19\x91\xde\x1aZ\xd0\x19'
		b"\x08\xdc\xeb\x00,M\x06;\xb0\x14\xce)\xe7B;\x87\x98K\xea'\xdb\x9c\xd4\xf3cJ\xf7\xbe\\\x04(\xb6\x85R\x7f\xb8\xbe\xbe>\xfc\xfc\xbc\n\x01;v*\xe7\xe74\xf9p\x80u 6wnp*;\xf4j\x1c"
		b"Ye\x8e\xb5G\x81\xff \xe8j0\x08\x11S\xdbb_%\x15\x81\xb0x\x07\xf2\x04\xaf\xe7\xc6r#\x88\xab\xfc\x1d\x04\x8d\xa9\xee\x8d:\xac\x0c\xa4z\x88*\xa66\xa2\xad\xfb\xf4.\xdd\xab\xd85\xb8\x0c}\x88'\\f"
		b"\x0e\xa8\x07\xb2\xd4^bDg\\\xd0\x13\xfa(e\xe0\xbc+\xf8\x88\xc3\x91\xbf!/\xa9u\xf0\xd8\xcaX\xcewg\xdd\x87gk\x8aW\x0bJ\xe3'\xa3\xe6\xb7eH\\\x88L\x8b\x92\xf7\x8e`_\r7\x9dZ\x9c\xf9"
		b'v\x13\x0f\x8a\xb7\x17\xa4EcY\xd8\x03\xb4\x9a\x85\xfa\xf9\xf9y~~\xee\xf8\xa1={5Pxy]\x19i\x99\x89\xd2G?|\xec\xb2\xb6\xfa\xb8\xaf\x19K\x88\x9e\xe8\x1c\xe2\xd1\xa5T\xf6\x13\xd4\x16\xec\xb6|\xed\xca'
		b'\xe3\xe3\xa3\xf5*\xa9\xd7v\xad\xc2\x95\x13/ES\xca\x85\x06(\x88\xb6^\xdd\x07\xc0T\xc6\t\xcf\xb1\xf7o\xa7e\x81}T\t\x93F\xc7>\xb6\xaa\n2\x88\xeb\xa1\xe1\xa1\xed\xdc\xd8]\xb4V\xbc\xc4\xbd*\xb2\xb4\x97'
		b'\xfdU/[5\xcd\xcd,W\xf3!\xf9wu\x86\xacc\x11v\xebV\xb1S\xac_\xf6,\x98\x04\xf8\xe6\xcc3\x8dJ\n\xcb\tm\x07\xc5\t\x98\x1d"\xc3\x9e$,!\xef\xf1\xfc\xfc\xdc\xbf\xbbN\x1d\xd0\x80\x90\x0eL'
		b'\xb66\xf0\xb4\xe7w\xfa\xe1%]\x1e\xad3\xdb\xc3\xc9\x84u\xd6\xab\x91\xf7\xbfmM\xbe\xbd\xb7\xa8B\xdc[\x8b\x81\xb3\x92\xeb=\xba\xc6\xc5\xc3E\xff5Ky\xb0\xe5n\x1d\xde\xdf\xff\xd5\x1f\xe9\xaf\x85\x94g\xda\xb7\x18'
		b'\x0e\x08\x03\x06oN,\xe0\xd3\x01\xecp\x83\x0by\xa5\x18o~\x9d\\\xc26\xe0\x02\r$\x91\x80NQD\x7f\x92w\x83\xff\x14\x0eua0p\x16\xe7^N\x07\xb6\xf0\x16\x01;(\x12!\xde\xa6X\xb9\x8d\xaf5\xac'
		b'\x9fiq\xb3\xcd<jK\x14\x14\x88I\xeaL\xd3\xb3\xf0\x9a\x99\x03\x04}\xa5\x95\x9eP\xf5\xb7\x9dr\xc9E\xf3m\xea\x123\x050\xfd\t>\x0c\xfa4\x109\x13\x03T\xd5}\xab\xda*\xc8)\x1dZ2)\x12\x80$'
		b'G\x1d\xad\xcd\x92F\x8a^\xb8\xcd\x92\xe6"\xc0HM(6\x1dk\xbcWd-\x84\xd0\x96\xa2\xb7s\x18\x88\x86\xa8\xf0j\x94Q}\x93D}}}\x1dqq+v\xb4|=_\x9b\xaa\x8a\xd1\xdf\xb2\xdc\xa27d\x92'
		b'V\xaaZ\xba\x8e\x96ny9\xca\xc6\xa9(~\x88\xe9%\xac\x05\xaf\xaaW\xb2F\x1c\x15\xcd\x03e\x1d-.\xb8\x93\x18\x86\xa3\x9f\xad\xaa>\xaa]\xb5uY\xc1\x15&<\xe0\xa2\x9b\xd9{\x91\x0c\xe9\xf2+\x12\xb7=\n'
		b'\x9c\xdb\xb4)\x9b\x0f\xc1\xe8]2\x96\xfd\xed\xd6\x95)J\xe4\xa6\xfbae \xd5heD\xf4\xc1\xa5\xc1v\x01\n\xabz\xcd\x0e_\xa7\x1c\x92\x9b%\xce$\x91Z\xc1\x84\x93\x12p jR\xa8o\xc1qJ`\xe8e'
		b'\x0ce?\xd0\xb2\xf73\x99E\x18\x11\x80\x1f\xb2\xd4\t\xe9\x95\xfb\xc3\xb6 \x94i\x9bK\xdb\xac<\xb3J\xb9\xb0<d\xa5\xa7B%F!+\xb8\xd2=R\xcd\xf7\xd8\xa9B.\xd0\xce\x97?mK\xd0-b\xc9:\xdf'
		b'\x10\xfa\xcca\x1b\xe3\x85e\x9f\xdd.\xad\xf4\xf9\x84\x9e;\x18\x98~F\xf1"\x18\xa1G\xc7)\xa7YRH\xb0}-\xce\n\xbeZ\xbb\xd5\x9b\x8b\x14\xd5\x1fXM\x8d\xc5mC\xcc\x96\xeeR\x1ey\x89\xeca\xac\xcf\xcf'
		b'\xcf\x9aq\x97\xde\x0c\x94P\x94\xc5!\xed&W\xa9\xe8\x1cg\x11\xca\x9b\xbbu\xc5<xY:\xad\xfb.]\x8b\x85L\x1d\xa3\xd82\xb9\xbe\xf2\xf5J\xce\xda\x97;\xca,\xa8\\\xae0ZEB\x14\xd4\xb1k\x07[\x93'
		b'\xb2Fd;\x05\x16aI\xd1\x7fN\xa9\x9f\xefH\xe44\xbaW\xca\xa3\x82dW\xbd\xc3\x9a_mq\xc4\xde9\xc3\xe2\x91\x1eF!R\xc9\x02\x84\x80bTb\x90%R\xd2m\xb5e\xe7\x1a\xc7\x9e\x9e\x9e\x0e\xaf\xaf\xff'
		b'\x8b\xd9\xdb\x05r\x07 \x80\x92?=\xe9\xeas[\xf7^\x7fJ\x05\xa8E\xc1\xa4\xef n\xe7\xb8\xc0\x1a\x9d\x18\x1f\x01-LSs\x87\xb2\x0f\t!\x11\xe9\xb2\x160\x81\xfc8a"@\x98\x07\xdb\xee\xb3\x1e\x03\xf1\x13'
		b'\x83Ui\xb3\xa8\x0cc\xb4o\x17\xc6t\x02`\x9d\x99\x89}\xa9\x0e.\xac\x0c&\xd8\xb9\xac\x9c\x99\xc7PPc\x05\xb2|K\x9a\xca\x9a\x02v\xc0\xb8\xf2\x07\x8eH%gq\x8e-\xf6\x83\xad\x96\xe5&^\xdf\x86\xbeu'
		b'A]Q\x84\x0b\xed2Xkp\xd8\x8c&D\x11~\x85|\xa0\xb3\xa2\xb0\xc4\xfd\x17\xb0\x81\xad\xd0\x87\xe5\xe2\xae\x10H\xa02Y)\xb2J\\\xe0xk\xc5\xf8\x9ese\x98]A\x08\x8eQ\x1b\xe0\xe9[\xf1\xecq\x16'
		b'\x08\xa3\xda\x92\xa9\xab\x0b\xdc\xbb\x94e6k\xd1c\x86JO{\xb7\n\x93\xc4N\xba\xf1[e\xf4\xc9R\x0b\x05\x11]\xad\xfd|\x9f\xd3Z\xe7X\x05\xf1tl\xda\xb6\x95p\x12ft\t\x05]*\xd0+\xb4\xd6\xe9o'
		b'K\x80\xc5tD\xbao%j\x8b\xdf\xc7\x1a\xea\x87\xb3\x0b=g\x0c\xd6\xaco>-w\x91\xafh\x9d\xf5@\x16],G\x12..\x0c\xa3\xa1 +k\x07\xc9\x1d\x00\xfe\xfbu\xc04\x90w\x017\xd0\xa1F0\xd1\x01'
		b'\xd4\xa4P9\x93\xa4\xabVu5O\xb2\xa1\xf2\x82\xdaB\x06\x91X\xc6\x08\xb4J\xc7\x0e\x8c\x81*\xa2\x86\xad\xe3\x19\\Y\xd8\xd9ap\x03\x0b\xbc\x8b5>>>\x8e\xa9D\xa8Ad\xd8Hy\x95\xbfc\xc3\xb3C\x9d'
		b'\x92\xcc\xc3\xca\xaa\xf5\xf2\x1b\xd5\x00\x94\xfabhw{\xa9^-\xc1\xed\xb22Z]\xcb:\xd6\xd7G\xe5vZ\xdf\x8ck\xb7\xa2\xbf*\xeb\xdfn \x8eE\n\xd8\xb3\x95@\xc3\x00\xa8\xdfa[l\xcbo\x99t\x19H'
		b'\xa6\xbaT>\x17O\xc7\xaf\x93Q\xb5\x01GHn\x17\xba\xafN\xf2\xf4\xf4\xd4e#+\xa6@\x9e-\xec\xf3\xdb3\xc0t\x1b\x11\x10GC\xa1\xc3J\xce\x08\xed\xbc\x98\xbbU"\r\xd4\xcf\x13Aq\xa4\xe4\xf4\x9d\xbc\xd2'
		b"*B\x12\x0b\xdbg\\40\x88x\x03j/\x80\xec\xc2'\xc0w\x16Zu\x9f%\x92\xbc\xba'\x8e>`\x10\xcc\xaaM\x07/\xd0?j\x82im\xb8o\x17\xc4\xf8$n\xfeGb\xa1\xc5\xa1#\x18\xb3W\xc0\x97e"
		b'B\x9d->V\xc4\xee\x92h\xb3(G\xf1M\x85\x10\xadi\x80\xe0R\x89\xb6\xf7\x00\x02P\x86\x80m\x0b\xe2\xdd+\x84Y\xb9\x18\xe5\x05K,\x18\x18\x9c\xdc\xb2\xfaF\t\xb7\xeeR\x15n9\xb7\x14\\S2\x13\x00k'
		b'\xd7\x87\xc5\xd8\x174z\xa3\xb6M\xbbE{\xb6D\xe8\x0e\xb4%\xc2c\xd9\x18Lf\x820\x8c\xd4I-O\xf5\xb4"\xcc\xef\xef/\xbc|K~,\xb4V/\xaa\x1c\xc8\xb6\xc5\x99g\xda\xf0\xdf\x19\x17\xd1\x07\xac^\x0f'
		b'f\x11\xf2\xfc\xf0\xd6^\xbfOP\xb4F\x19jS\xba\xb74\x8e"\xbf\xa4@\xc3K\xe7|B\xc0\x90\x00\xf0[\xc1\x12r\x1bZ\xa2"@RN>\xe4\x0fm\x95\xd3\xd4\x10\xa8o\xad\xafY\xf3\x19\x8aW\xb8\x92\xf9\x04'
		b"#\n\n\xa1\xb32\xc2>\x8a\xf0]\x14\x8c6\x80P!S*\x07B\x99,\x1a!\xc1\xa7\xb0\xdaw\x01\xe0\xecq\xedim\xbfL@\x0e';\xec+t\xd61\xf9>\xcd\x9a\xe2B/\xa5B\x0e\xed\xf5\xe5\x039\r"
		b'\x8a\x12\x14\xe9z\xd4b\x12\x8fJ2Md\xdc\xe1Ps\xd4\xbeS+s\xf1d\xfd\xb5\x00\x9c\xed\xb7*y\xa3bK\x9c/\xd7D\xd9\xaa\x05\x11^S\x00\xd1E\xd5\xf3\xe4\xf0E\xe4\xd2\x86\xf6\xa8\r\x92\xf2\x01\x00\x04'
		b'\xc3\xc0\x8c\xe5\xf7\x83@\xf4\xd6\xb6\xef]cZl45"\x1d\x15;d&\xfa\xfc\x82\x9c\xa8rxe\xda\xe5\xc8\xb7\xe0\xd5e\xce\xcei\x00>\x0fr\xb2\xe6Q\xc5\xa3N\x80X\xb9\xad\xadT\x041T&\xdc\xe6\xf1'
		b'\x1e4\x8f\x01$\xe948\xcd=\xbd\x94@\x93(&z\x9eG>Z\xc4\x82\x8e\x91\x8d\xe7p\x81b\x1dq\xa2hb!z\x01\x98\x14Qy\xf5\ra\xed\xea\x08\xe9\x86\xa8F+\x1e)4\xf2\xf5\xad\x0f\xf1,\x9b\xc7'
		b'F\xb4  )\x91\x1e\x12r\xb6\x9f\x96\x9eO`\xd2\x90\x97Z%J3\xb1\\\xc5\x8d\xd85v\x93_\xc5\xa5ik\x8a\xc48\xb4"iE(Z\xb6[Rm\xb3\xf8\xa8LRaI\xb4\x8b\xceRV_8\x84$'
		b'\x06\xaf\xa3?\x82\x01\xaaD\xdd\xb7\xeb\x93\xeeo!{\xdb2\xaa\xf0\xda\xc3\xc0\xa0\xf5p\x93\x12\xeb\x0c\x10\x06>s1\x14\x01\x88\xe7@-\x14JVJ\xc5e\n\x80\xa3~\xdc\xe9G`\xa6\xce%\x0cm/\x8b4\x82'
		b'V\xed\xee\xe6\xc7\xc83\xe0[LIF\x8e\xf4\xb1rU7g\x9b\xd1\xba\xb4\x82%\xb2"\xfd"\xe9\n\xf0\xc2\x02\xa0\x1d\x11zr\xea,(I \x8e\xde\xa8O\xc6$\x95\x13\xb3\xb5\xc2kr\xbc\x02\\\x925\x9a\x10'
		b'\xfada\xb1\x06E\xce\x81\x96\xba\x0c\x12\xab\x8c\xf2\x14\xf6\x18\xae\x8e\xe4\r\x82Yp\\\xaa\x1d\xacW\x1c\xd5}\xc8\x0f(\x02"\x81\xb6t\xc20\xb5#B\xd3\x046\x8b^8\xd55\xc6\x9d\xd7r\x98\x9eD\xb5\x98\xca'
		b'\xf9\x05\xa2\x00t\xd6\xa4\xb5=\x00=s\xbe\x08^\xd7\xb6\xe2Q#\xb7\xb7\\gO\xcbM\xb0\x1f\x05\xf7\x10(%\xcc\xd6"\x82\'\x85\x8f?\xc9\xc40r\xcb\xae2\x93\xadr\xb7\x7f\xf5\x7fJ\xfbd6\xe87\x9d6'
		b'%w6\x95\xe0\x9cN\x97\xfe\x1c\x871\xdc\xa0c\x87\x8d\x13\xef\x80\x9cw\x96\xc3\xfa\xea\x96\xca\x8f\xd1\x00\xec\x10(\xb4\xa9\xbf\x00\xbe\xc8\xc6\xc0\x9a\xc46J\x87XC(}\x90es\x0f2u*n\xae1\x81\xba\x9b'
		b'\x9b\x9b\x92Eq\xf0\xa2\xe9\x94\x04:\x9a$\xc2[@\xd1\xa0\x0e\xaf\xfc\'\x11\xb8,hf\x9e\xd1\xc9\xa9\x16\\\x01\xa9\x96\xc6\xcc\x10d\xe6\x8b\xda\x83q{\x9d\xe58\xb8\xf3\xcb"\xc1\x86\xd2p\xb3\xbd(\x82[\x90t'
		b'V\x15-E\xfd\xbb+\x11\xf9Y9E\xce\xa9\xb7Q\xd9G\x81\xab\xc3\xd6\xa5\xfa?\xc1\\O/\xb6\x96@\xb87\xab\xcf\xd3\x05\xc8\x12hQ\xd3\xe0R#l\xe4P;$[\xa7_\xa0\xf5\x89\xaf\xc8\xf0\xe7+{\x0c'
		b'|\x0fC7\x88\x98\xe3\xfd\xa2\xca\x00z[\x0e\xa2\xa2\xa4J\xf0\xd2\x88t\xa3Xn\x19.\xed\x86\x0b\xdc@X\x95\x89\x15\xd5p\x8f4F\xc3\x8b0\x1aZ\xc3\xce\x99\xd6o\xdd\xbd\x1e\xc9\tSoZ\xbdaPR\x1f'
		b'B\xad\x9a\xf8d\xcf\xb3\xfe\n\xa8E\xbb\x13k\x1a\xb9\x12iL\x17\x94\xd4_\xf6\x95Y\xec>,\xfdA\xfc\\\xe4\x86H\xd2\xfa\x03Qz;\x18z\x01^\x1fB\xce\x11\xa5\n\x81\\\xd7K\xbeH\x8d\xb9w\xa4\x80\xed'
		b"\xb2q\x1d$\xdbh\xd1\t\xc94p+\x99\x1fN\xa7\xfft\x9fVVDa\x1f\x9eC=\xf8\xe1\xe1!d&\x88\xa0\xb7\x02\x93\xad\xac,\xfbW\x10\x82B,\xcaG'\xae\xab8\xc8B\xeb\x9aE\xc7\x86\x10\xcc\xe8W"
		b"\x12\x08\xb2:\x08H2\x96J]\x02\xb3\xfe\xb7\xdf2d\x00L\xeei\x03(\xd0Np\xdf\x17\xe1F\xb5\xc8\x9ct\xff\xa5\x16*_]'q|\xc7\xcb,\x16H.\xb4\x87\x8a%\x1e\xabt\xb6\xfd\xcb\xef\xe9\xf4m\x11"
		b'\\\x00I\x05BeN\x86\xb4\x87\xeedI<\xda\x9f\x00\x8cj]PD0\x9a\xf7j\xe5\xbd]\xb9\xac>\xa1\x9d\x97\x91+k\xdd\xf4\xd3\xf8\x01\x05\x1cY\xf5R\xd9\xa8\xddd)\x08\xc9(K\xe7\x04\xf2{Q\xbc0'
		b'\xff\x8c\x0e\xda\xae\xe8\x85\xf80R\xffp\xe0\xcc\x9e\xd8hug\x13y\x7ffC\xfb\x12G\x86\xa9\xa7\xa9\x87^\r\x9a\xc0V%XPJ\x9a\xb7\x7f\xff\xb3\r_}`\x91@\xa6t\x9b\xbb\xd1H\xcaS\x8b|\xdc\xf2'
		b'n\x02\x8e\x13,\x0f\x92(At\xe7\xfd\x87\x8c\x9f\x04~!& O\x8dVq\x1e9Q\xaa\x874\xb1\xedK{\xfaI\x9d\x12\x96S\x06\xee\xb7\xa8\x89\x15J\x95f\xe0\xc3u3\xc5\xebg\xc1\xb3\xe3\x1f\xd5W\xc2\xc6'
		b'"f\x8a\xf0\n %\xaf\x85\x00\xfcm\x8b\xd0\x9f/m\xb3G\xe2jp{\xb8hs\x90\xd8\x0e\x96\xd8\x88-j\x96\xb6\x03\x05!\xeb\xee\x19V\xd4:kBX6dy\xa7H\x91\x12\x83\xa90\x04\xea\xfd\xdb\x83~'
		b'\xd4\x84Oo\x19\x19s{\xcc\xf5\xbe\x80\xab{\xa6\\p\xab\x93\t\x97\xc3\xb9\xbb\xd5\x8c\xb6--\xf0G\x13Yy\x12\xfb\xc4\xed@\x1e{\x92\x80\x0e\x06\x9b/\x06xo\x83o*\x88+\xcec\xc0\x0c\xf3\x89\xb5\x1f\x91'
		b'&\xf8\xa2\xe7\\\x89O5\xe94\x05\xc8x\xe9\xdb\x84?\x04(),\xc8\xe1\xf4\xd3i\xea}}}\xadP_l`<V\xdfNZx\xa98\xca\xa5\x90\xd3\x96\x08\x14\xdb\x8dR\x7f\xd0\xb1\xc0\xbf\xf5\x84K1\\]'
		b'\x7f\xbatB\xfc~\xbd\x98a\xf9\xaaD#\xab\xa2\xb6M+=/\x94W\xa2\xc6\xfc\xa1\xa3\x8cA(r\x16\x80\xc4t\xec\x04\x16n(%QR\xe9\xdf\xf9\xdb\xe2\x912]\xcd\xaeT\x10\xf9\x93\xd6\xa1\x8f\xe5\xb4oB'
		b'\xeb\\Y\x89\x8bK\x8f&\xa4\xdb\x95\x9a\x08\xd8\x07\x01a\xbd\xd8\x16\xcf5\xa7vk\xdb\xa4Z]\r\xc2p\xb0h\xeb\xaa\xc5\xee$)\\\x8e\xdeV\xacB\xe4l\xd5>\x0c\xbak\xb3I\xff\xb2\xf7\n\xef\xaci\x84\x9c'
		b'\xb5\x1f\x18\xd4+\xabF%R\xe4*\x0cK\x03B\x03\x87n\x8fN\x98(E3d\x8d\xde\xfa\x9b\x96\xac\x81i\xc2U\x9a"a\xa2Q\xef\x8b\xfe\xb0\x94f\xa9\x8ef\x175\x96\x80Z\x83\x0f\xdbV\xa5\x1e`q\x17\xa6'
		b'd\xc3d\x93\x0c\xc7b\xea\xd8\x87\xf9\xc6\x0e\x12N\x8eD\xd6\xdc;\xa2cV\xbb\x17\xa7\xfdV0I\x1e\x0bS\xd3\xf0\x0e\xd2\x83:\xdc=?\xcd}I\x17\x1ao6e\xc3\xbcc\x88\xa4!B%\x1f\x86\x97\x80S\xfa'
		b'\xc3\xfc~a\x16\x02`.\x02\xe2\x9b\x11\n\xbb\xc0\x06\xd5\xa0\xad\x1aON\xb5\x84Rk\xf9\xd2}368UN$\xdc\xad\xa3FEu\xa3\xc6\x0c\xa4\xa6\x13\x08\xb7\x961\x1c\x81H\x94t\xf4\x0b+{\xf1\xaeh\xee'
		b'\xb8\xe7\xec\x17\x05-\x94\x99\xc1\\z\xa0\x8d\xc1\x84EP:\x10"\xeb\x1ev\x9f\x99Le\x19\xb8\x13\xb8\x82\x97/Pt\x8d\xc9\x04Q\x0f\x07\\J\x87:\xd3)\xd3d\xe3\xbb\x0c\x9d\xdd.\xad\xba\x18\x9f\x13\xbcV\x9c'
		b')L\x95l\xf4\xb1]c\xaa\xb2}\x14X\x0ffM\xcc\x82\xed(\x95Z)\x94\xde\x88\xc4\x03q+]l\xf2L\xe5-\x98\r\x11\xfcx=nf\xbb\xa0\xbb\xc8\xb4\x94\xc3_\x7f\xfd\x03\x915\xece\xd5g1=\t'
		b"\xbfdW\xd00s\x0b\x82(\xbc\xe52'\x01\xa5\xeff&\xcd>@\xb9\xe3\xa3w0\x0c\x9es\xd8EK\xac\\]b\xb4\x03\x8b\x84\x9e$:\xa4M\x04T@\xb1=@\xd1\xb3\x06\x0b\x05\x8d\x9c5 \x9f\xcc`\xbe"
		b'\x0b\xae\xac4\x06}\xc2$\x131\x87{\xf8\xf5\xc2}\x00\xee\x85\x1aq\xef\xa5j\x0e\x807\xa6\xc5@7\xfd\xbe&\xee0~\xed\x0e\x00Mw\x7f\xd7\x83\xae\xb29+$5%\xd6\xdb\x1f\xab\x97\x17\xa7rkU[\xfc'
		b'\xce6\xe9\xfc\xa2\xb3k\xaaP\x1f\x9b\xb9\x112\xe0QK\xd8\xa0\xbd\xe2\x8b\x9d\xf1\xd8\x0fd4w\x1c\xea\x92\xbd\xa5\xdd\x0b!`\xee`%\x1fe\xeb\x9d\xc5V\x1ci\xb6\xbf2\xbb\xa6\x80{\rR\xf7\xc9\tPi\xda'
		b'q\xc2\xa5\xad\x1d&\xc9M \xa9(\x10\xe0\x80\x01\xca\xe0\xdd\xde\xdeFg\x10%\xab\x00\xd4\x7f\x93\x13\xc8]\x08\x16\xdb\x1eS^z0r=\xab\xa8*\xa7\xa7|F\x90c\xe9zx\xbcI\xbf\xb8N%\x9ax\xbc\xfa'
		b'\xe9\xbaT\xda\xfcw*p\x1e\xc6l\x1e\xd2)\x8a>\x12*\xc6\x029g\x13\xc1\xae\xd6\x0e\x15\x8eC\n\xcez~~n\xae\xa3V\xc9N\xaa6n\xb3J\x85\x7f\xab\xa7$m\xed\xf3\xf1\\:v\x94DT\xacW\x1e'
		b'X>I\xb8\x85$I\x7f\x95?i\xc79+\x8aC\x18\xb8\xe4s:$\x9d\xd84\xd6{\xcdv?\xd3L\x86\x95y\x92\x0c\xf4\xf0\x14|u\t\x1f\x9d\x1bg\xeb\xe9\xe9\x89\xc0\x8ez\x16\xf5\xb5\xdemk\x84\xba{\x04'
		b'\xaf\xf2\xaa"\x1c0\x9c\xeac\xab\xafm\x8aUF\x1b,Z0\x10;+\x98\x81T\xfc\xd2\xf3\xa5\xa1\x11\x82\xde3\x10\x96K\xee\x93\x98\xbd\x84gu\x8d\x90|\xc4u\x0b"A$;\xbe!\xdc"\xb1(\x0c"\x96\x15'
		b'\xa7_F\xa49\xc4\xf2Z\xc1@f\x85\xca\x10\x11\xa1\x0e\x01s\x00\x9e\xdfpBkb~\xc96\x93v\rAW\xb3\xeb\x8eERB\x82\xec\x05;\xee\x1d2\xa5\x03\x90@\x97\r3\x05\x87W\xf1\x04\xae`\x94\xe8N'
		b'\xbc\xcdR\xa4\x16\x8a2\x03I3\xeb\xad\xbb\x9a}D\x91\xd2\xb3F\x19\t1v\xbb\xa0\xf69\xc9\x86\xe3nY\xb7\xf2\xde\xb3\xfa\xa2"\x14m\xe1\xec}\xc7\xa5\xe7\xd0\xfb\x1c\x18\'e\x89\x95\xd5\xbb\xd55g6\xa5\xfe'
		b'Q\xd3P\xcc\xca\x8cIO\xf4b\x01\xa5,kFH\x0b<\x821~\x1fQ\t\x01UV\xcd\xe8\xa8>\xc1\xd3\x12\x19\x96\x12e\xc3\x10"\x10\xe3Z;,+\xa4\xd7-\xab\xd1\x7f\xef\x10\xacD\\\x0e$\x1b\xac\xa9\x0f'
		b'++\xd7*\xe3\xb7Jv\x9d\xdb\xbc\x98?p13F\xa1\xc6\xf5(\x9b\xeaI\x00\x94H\xfca)\x99^R\x96\xd9&W\xa5_t\xa4Vs3\xab\xa1\xb4\xa2\xdb[\x06\x9f\x93\xa9\\%\x93$\xa0\xef\xb17\xe4\x03'
		b"\xd0\x814\xf4l\xf0'\xea\xc1\xb9\x85 \xb8m\x19\xa3\x05\xb4J\x0e\x9d\xc9\xd5\xc6\xc2\xc8T?\x85\xf1\xbc\xbd\xbd\x1d~\x7f\xdfdN pE\xc4\xf6)\x9c1;-\x1a\xf6\x1c\x9d'\xadU\xdb\x8b\xa4}\x13\xbe\xc43"
		b'\xaeD\xae<\x92Z\x1b#Mi\x94\xa0,\xa99\xde\x13\xceC2D{\nvP\xa7\x93 \xd4z!\rJ\xe44t\x9fyqQ8\xa5*\xd3\xa2\xc05;\xf5\x95\x9e@\xcfs\x96\xa2\x9f\xbe\x1c\x95`\xe2j\xd0'
		b"\x05\x84\x99\xea\xf6.-)E\x02\xa3[3\x06ga\xeagV\x8b\x10V\xdc\xae\xb2 9^h\xaf\xd5su\xcb\xaf4\xe3\x13k\xd9$\xb2\x87G<Q;\x97'\x98\xce\xa4&hL2\xf6\x11i)bP|\xb8"
		b'\t7\xad\x8c\xa9E(\xdfy-\xbdJ\xcb\x821\xc0\x81>9"\xe0\x9f\xa9___\x7f!ou\xdb:\x85*);\xb1u\xd3;\xecb\x1a\xd1\xa0\xa5\xaa\xf4\xdc\xf4\xda\x95\xf2lue2\'j\xc3\x80\xf3\x826'
		b'\x81\xe6\xc6\xeb(t\xc2V\x82m{\xcd\xe8\xa1\xeb\x8d"[\xd4\xaaQ\xf3m\xe9\xc9\x1f(\x8eJ\xfa\x97\x9b]0\xdd\xce\xed\x1c.c\x18iWiZ\x17:Sh\xdab\x0b\xd8\x8a\xde:\xbd\t\xb2yY\xb20\xc1'
		b"\x1dm\x06\xe37'a\xd9\xf3\xce\x01Y\x03\x03\x9e\x89lk}\x860\x16/\xe1\xb1\xed\xb45\x19\xd1E\x07\x85;C\xe7\x06\xc9Y\x89\n1\xa1t\xab\x8b\x04\x8bs\xf4Wf\x94 \xcd\x8a\xc4\xf4\xef\x9dr\xcb1r"
		b'\x1d\xae\x04\xc0:\xbcUn\xd6\x03\x7f\x7f\x7f\x1f\xfe\xfa\xeb\x1f\x1d\xc7\x1e\xb1k\xa7\xeeM=\x93\x15\xa7\xb2F\x17\xdb-\x84\x81b\xad-<\x87L"\xf3\xd3%l\xda)\xc9Rd2\x92\xc5\xf0\x93\xf6\xacC\x1c\x81B'
		b"\xa1\xd4\xf0\x12\xf6\xb8\xdb\xa8>e\x88\x10\xeaH;dX2/\xbcs\xb0Q8wT\x1b\xbcr'Gt\x01v\xe4V\x0f#eB\nP\xd5\x17\xf4\xbb\xba\x02\x9e\xcc\xd5N\x13\xec\xc7\x94\xc9\xd0\x93H\x81\xd3\xe2\x05"
		b'\x9d-\x91S\xd5\xb6\x1f(= 0\x8cOA\xe5\x8a\xc8E\xfb\xbbZ\x88+\x8c\xd7\xf6\xed\x80\x15l\xf9~\xb8\xcb\x0c\x11r`L\r\x03\x12H9\xf4\x8em\x0b\xaf\xd6\x96NH\xa1\x0e\xf2OV\xc6\xfcR\xf0\x83\x98'
		b'\x05\xcc\x10\xba]&p\xc4\x83\xed\xd7\x80\x0f\x19\xbf\xec\x9cO\xcc\xac\xea\t\xceH\x97N\xad\xfa\xa4TO\\a"]+\x85Se\xe8gQ \xe8\xcd\x99s_\xfb\xc5\x1c\x88\x91\x079\xe8\xbc\x8d\xd0\xa2_\xc7\xc20'
		b'Vm\x93cT\x05\x06F\x14\x04\xcd\x08]\xa9\x18\xa2\x08\xda^f\xddw\x8c\xc0\xf6y\xe0\xfc)\x9f\xe5\xbe+~\x11r\xdb\xf2\xb9\xca\x0eA\xb5\x96Q\xcb\x15\xb9(\x036\x15\xb6!\x80\x0b\xce\xf4\x8e\xf1\x8d\x89\x0c\x04'
		b"\xd5\xeb2U\xed\x01a\xed\xb8\r\x13\xdcV+?/\xb4#\x03\xfb\x96Npo\x1a\x86Q}i'\xa5:\x94|\x94\xd82\xe2\x13\x14[]\x19\xf1\xb6]\x00\xf5\xf4\x93]\xa4\xf8\xbf}\xa9>R\x01p\x88\x9f~\xc0"
		b'Rp5\xbe\xcf\xcf\xcf\xc3\xef\xef\x9bne;\xdd\xd7\x10\x9c\xe2\xd6\xdd\x13\xb6\xf3b\x8dt<\x10\x82\xd4\xf3/\xf2\x16\xc3\x91\xac!(\xc9\xe2\xd2\xa9U\x05\xe41\x1dY\xfa\xfd\xb4\xa3\xf7\xa0\xa8<\x18\xe5\xd9\x12\x14\x0b'
		b'\xe6\xbb\xe40\x99a\x98\xddNXPf"\xb0\x91\xc9\x10\xf3\xa8\x87l\xcft;!HE\xd4\x03\x16\x83)p\'w\x02.\xec\xa2e\xc9.\xf4\xfa]01\xa4\x1e\xb1\x9d\xbe,\xbe\xd7\x86\x8b\x84\xb2\xea]"=I'
		b'3\xef$\xec1\xe9\xd1=\\\xde\xbb\x86/\xd0\x82\x05!\x90#|\xdd\x0ew,\x12\xddK}lm\x90Z\x82@=yo\xa2L\x9d\xf8^9LB\xddi\xa5\xdc\xf8\x19\x02[\x11p6\xde\xfe#\xcaN\x9a\x8aw'
		b'c\x92[\xfd\xe2-w\xab\r\x96\xffu^)\xa0\xd0\xc1\xc5\x81Q\x9fj\x94\xf1\x8e\x8f\xce<\xcb\x9eM\x1f\n\xa82l\xb4}\xea\x96J\xf9\xf5L\x9a\x83\xd9\xc6\x84\x03d\xc5\xf1[\xd4n\x11\x80\xeb\x96\xd0}\xbf\x15'
		b'\xabj\x05\xc8K\xd1\x84\xcc\xd8\x13\xd2\xc8\x91\x88[\x99S\xd6y2\xb0\xd1i\xee\xd2&\xa9@=\x05I\xd8`\xfa\xe4\xa9=0\x10Pu\xb2\x83"K6g\xaeX\xe8B\xbej\x1bJ\x15\xe3\xd4I|)\xc2\x9f\xe1'
		b'\x9f\xca2\xda\xaf\xd5\xd4\xe8\xc4`L\t\x0b\xf1\x9c\xfbp\xfdI[\x12\xa6\xec\xc0\xf6\x87\xaau\xdc\xc3\x94\xfaX\xa3\xde\x8d\x91\xec\x8a\xea\xcap\xbbZy\xcf\xbc|\xbb\xfe\xbdC\xd1\xdf\xde\xde\x8eJ\x18\xc56p\xb1U'
		b'\x18\r\xf9\xea;\x02\xcb \xfd\xb0v|\x92\x15o\xca{0\x8d\xad5\xba\x12P\x99J\x91|\xc8\xe0\t\xaf\x01\xaa\xa2\xe3`\x0cQ\x06\xa0ki\xea\t\xfe&Z\xd1\x85\x84`n\xd1lV\x8d\x9a\x0b\x11\xae\xcc\x06\x9f'
		b'\x16\xa7\x08\xc2\xb0\x9cY\xc3)\xfa\x90L\x86F\xf8\xceM\xe0\xf2Nt\xeb\x90U!\xe9X\xf4H}]\x1fN!Y\xe9\xc3\xe9)\x92\xd1\xc2\x0b\xae0\xa7\x15\xd7R\xbd\xcc\x8c\xad\xd5O\xee\xafV\x8c\xa4O\x88\xcc\xb3'
		b'\xaal\xf9\xc9,.\xb5~>M\x0bX?\x8c\xbcY\x0c\xb3"+\x1d!\x8d\xc2}\xb2\xbb\xaa?\x11\n\x92\x855\tW\x04\xdb~)&\xf6\xcf\x85\n\xaa>GS\x9c\xcf\x8d"+\rIc\x07\xda%\xda\xc3\x97\x96'
		b"J\xab\xb1\x89\xe7\xb0G\x8c\x17\x00\xd2\xf5\xaa\xc4J\x85\xf8\x10\x12]\x94\x1e\x1d\xe9\xa5\x0b\xc6\t\xac\xe2\xc0\x8e\x1a\xd7?P\x98ne\xf1pt\xa9*\x1e\x9b\x0eX\x97&\xd6\x00!\t\xa9'\x95O\x19y&\x04\xee\x94"
		b'\xf7\x00P\n@\xa5\xa1$\x89\\\x18\x16\x14$\x82\xeb\x86n\xa9\xee\x0b\x07TCPB\x02\xc3{\xc1\xce\xae\x14h\xb5\xa5,\xb8\xb2\x8f\xf9\\F\x13,\x8dl\xa7,w\xc2t\xc1S\xacP\x97\x04do\xde\x82\x1a\xac'
		b"G\xd9\x90\xd6ej\xc4\xce \x9a\xa6\xd5\xbb'\x11\x0c\xafb\xa9\x18\xd5\x10:H\x0c\xcc0|OEY\xbf\xb2\x1c\xe3\x88\x9c\xa8,\x85\x8b\x86NDY\xdfH/\x99\x8drz\t8!\xec\x84N\xb7\x10\xa8f\xd9\x05"
		b'\xcd]\x06TC\xb2\x01\x82.\x1b,\xc8A\xc9D)D\x90"4\xdb8A}\xeb\x95\xc5\xd5\xb0\x96\x11-\xb8\xc4\xeb\xec\xa9\ncZ\xb8\xd2\x95\xe5l\xc3\x7f\xb37\xf8*\x02\\\x8aK-1\x1a\\\xa6\x0b\xa7\x1a\x1f'
		b'\xc16\xb0\xbej\xdb\xea\'\xb4\x9a\x85\x91\xcc\x04E|\xb5a\x05\xfbR\xc3moZ\xaf\x88^*\xe3\xd4\xda\xc6\xd3*\xfc\xe7\xbbt\x9a\xab\xcd\xb3\x89bB"1\x1d\xd0,\xce\xb2\x8c\xd0\xaa\xf3\tBs\xaa\xf1-{'
		b'\x05(~\xc9\xd0*\xd1\xa9\xb7\xdb\xdas\xa9\xad\xb6\x13\xe1F\x90\x94\x91M\xf2\xf2?\x03\xf6J\rAZ\xca\xce-\x01m^\n\x84F\x8ebcK\x86\xd2\xcdD\x93\xd4\x0f \xcb\x04\xff\xa9\nuja\xba\x17\xd9\xd2'
		b'N\x10\n\x7f\xe8K\x15\x1a\xdbK\xbc\t\xaa\x94\x94Ez[\xa5\xee\x8e\x14\x04\x1d\xa5^\x16T1D\x0fq\xd4\x1a\xa6]7\xba\xfcG\x92@\xedp\x9b\xcb47\x11"\x07\x92pb\xdaJ\xa0j\xfa\x04\xda\xc5t\xca'
		b'Pn\xd8W\xf9\x065i\x1d<\xe8\xa5\xa7\xd3\xe9\xe5\xe5e\xc7=\xc9)\x15mr .UA&)>\xaa\x08-\xdd\xde[|\n\x98!\x8e\x1d\xcd<\xdc\x16\xca_\xf4\x9d\x0cT\xed\x9a\x99\xfbD\xc4\xa9\xa04\xce'
		b'\xb6!\xd8A=|\xa3\xebM\x86\x1e\x03\x12H\x83K\xc2sf\x8c\xce\xfakz\xefW\xcdT\xb5\xcf\xb4\x0b\xcd\x9d{"ym\xbc7\x0c\x93\x9e\xaf_A\x95\x11~\x99\x02\xafd\x03\xca\xdc\x92\'\x10FK\xbb\xac\x17'
		b'T\xa7\x80\x82^\x06~\xdd\xb1?\n\xfb\xad \xcaP\x94]\xd5\xb7\x1e\xb2\xc7\x969\xa1smZ\xa9\x00\xd7\x8a\xf73]EZ\xf8[\xd3\xd1\xd6D\xbdB\x96b\xffpr\xf5\xcd\xf5"9\xb1\xb6\r\xcc\xa5y\xdf\x94'
		b'E\x95ur\xd3E\xe4r\xc4\xac\x8c\xf4\x97_-\xf9\xd9\x12\x07\x199Y\xc4J\x13\x98\xf4\x8c\xca.X\x12\xa0G\xbe\x80X\xec\xfcpJ\x9d}\x14q+\xe3%W\xc6P\x8byXHF\r\x08k\x94A\xab\xd4)'
		b'\x15\xad\x80\x80\xd1\nwT\xdey\xf1\x7f~^;\x9d\xad\x0e\xc5\x07\xcc;CQ\xb1O\xfb&\xf5\xbc\x0b\xb2\xe4\xcetA\x13\xd2\x85\xb0\xe0\x0fZU\x0b\xaa\x84A\x11H\xe9\x1b\xbdq\x89\xb2B&d8\x04;\x0c\x02'
		b'\xcf\xbf\xc3\xbc\xd4#\xf1{WR\x0e\xdc\xc4\xcb\x1b +\x89\x14w\x99l l\xc0<%\xa5jTk\x0f\x89\xdd\xd4\xcd$\xc4t1@\x0e\xcbZ4\xc2\x10\x060\xec\xb4\x01\x87\xcf\x9c\x15W\xcb8Gv\x8d\x94\x03'
		b']\x1d\r+\x9d\xf5|\x8e\x10 \xe0.\xdf.~\x83m\x14\xd9R4"\xef\x85>$\xb1\xe9MQ3\\\xaa\xb6OE\x1cw}\xc9\xd2\x1c\xda\xce5\xe3r\x93 \x10N\xe3 \x93\xa3\xe3\nv\xf8\xd0y\xc5\xfa\xe5'
		b'N\x7f\xe0\x03\xce\x02\x1amq\x9b\x92\x8d\xe9I\x1a\\\xf4\\\x9a\xe9\x001\x0cC\x10\xd2i\x17\x0c\xf3QI(_\xc1`\xdb\x89hLZ\xa7d\xa7\xf4\xe8\x8e[k\xa1\x8bW\xc1\x12q5\xc4)\x0bA\xab\x1e>\xdb'
		b'\xb5w\xabi\xe3\xad\x18#\xc2\x9f\xf3g\x82\x1dm\x02%0\xfa\x90f-*\xcfa\xfc\x92\xc6`\x14\x94\x81I\xa9Q\xd6g\xc5\x8d\xaf\xdd^\x0b\xb0\x8f\xa2\x87\x8a!\xd7\xbaU6*\xfe\xa5:\x00J\xdd\xf1\xdd%\x85'
		b'\x1ae\xcd\x85h\x9c\r\xa3\\T\xac*\xd5%\x10\x14\xf9\xb9[\xb1\xb6\x0c\xffgUj\xb6AOc\xa7\xae\x0c\xe14\x12Wg5\xc2\x0f-h\xe3\xe1\xf6\x9e`\xc8R\xda;\xeaE\x80\x0fF*\x04\x0b\x00\x136@'
		b'?3\xe9\xfevd;[\xc1\xf5E\xba\xc4\x11\xd7\xc8,\xd30\x98C\xb3i\xab\xa6\xbd\x9f\x9eY(\xcdN\xdb\xd5\xa5\x01\xdf\xe8\xbcfc\xfc.9X\x80\x8cDm\x85\xff\xc56;\xef\xb6\xc3\x81\xaf\x86\xfa\xba\x12\x7f'
		b'D\xb0M.\xeaOH\xe5\x94\x9f\x95\xc3\xacv\x83\x0c\xa7\xa2\xa9\xaa\xbe6\xa8\x0bq\x9a\xce_\x17\x12\xf3\xf4Bv*\xca\xa4\xc9\x17\xaa\xe6\xa5\xf8]\xc8\xb0K\xa0d!e\xb1k\xd4N\x8b\x0c;^^\xa3\x99\xe4\xfe'
		b'\x04\xd3\xa1G\r\xa9\xec\x8b \x95\xb2\x02M\x98":\x0cP\x9cb\x8b\x0c\xd03/\xbd\x17\xcf\x9at\x8e\x15@\xf0J*\xb0T\xb3\xaa\x98\xb3\xcd\xab\x1d\xce\x02\xad\xab\xab\xab#\x8cY\x15f\xfd\xbb\xfe\xba\x1d\xb1\x14\xb5'
		b'\xa6\x8b\x9bO,\xd1\x04\x19\x15\x9c\xc9~\xfa\xa8\xb4)\xf5b\xaf\xa8\x04\xf1\x02\xe5\xb0\x8a\xf6TW\x0b\x90v\x16\xaa\xa60mh\xddC\x1cO5\x1d\x0c\x16\x91\xf1j5g?\xbau\xed\xdc2\xe7l\x0f^\x06\xb8-'
		b'\x97\xd5\x16"\xf7BW\xe5!\x91\xa9\x107\xba\xb7\xd0n-\x9am\x89\xe0d\xe7w\xe4N\xe3\x03Sw\xc2\xafVK\xce\x93\x93<\xd1\xc3.\xe6\xd4@\xdc\x956\x88\xa0\xf2B\x08\x18\xaa\x8f>Z\xbck\xf4}w\x86'
		b'8\x8d\x93\xd0b\x12c+\xcd5#P\xfd\xae\x87\xcf\xb7\x08\x0e;\xa9\xa2\xa6\xd5\xa0\xd7\xa7\xae\xc0W\xcd8\x8f\xd1s\xe2\x80(J\xe6\x12\x03\xe5\xe2]\x1b\xe8\x88az{{{\xf8\xf9y\xc5>\xd0\xf2\x93\xcdS\xd3'
		b'QB\xdf\xcc\x9a\t\xefN#\x99\xa8H\xeb\x84j\xc9\xa8\xf2"\x81Z\xca\x95\xb6-\x04"\x98U\x1e\xa35L\x0b\x1cT\x14\x91F\xaa\xa7\xb2\xe8\xea^d\xed\xb2\x11\xe2\xa4pXe PR\xd6\xab+m6\x82\t'
		b'\xc1\xfa(4\xaci\xefR\xb1\xf2v\x8f\x8f\x8f\xba7\xe5\xa6\xb2a\xe26\xc63\xb6\xa4\xa2\x02\xac\x84\xf2\x10\x01\x830\x0c\xf5p\xbb1\tP\x1b\xee\xb6>\x13I\x86(\xc6\xa6\xe0\xe21\x95\n@\x8a\xce\xc6\x12\x89V'
		b")'\x03\xf9Y~\xb8\x9b\xa0\x98\xad\xda#\xc9\xf4\xb4x\xa3;a\xc4$\xbb\x15\x15\x16\xd6\xd33\xdfTJ\xd7\x9b\xef2S\x0c!\xf2\xa8\x8a\xd1/\x9b\x08\xa6d\xa3\xf6D\xe1\x07\x9bb\x07\xad\xb1v\xbcg\xabY\x7f"
		b'\x99\xbf%\xff\xab\xbfA \x9e\x19\x03\x9e\x00\xdd\xcb\x05url\xc2\x84=\xca\xd0\x82\x92\xf8G-\xdb:B^__3\xb4\xf8\x92\xd55i\xb9\x15$Dc\xccN\xacR\x03\xb0\xb9\xe7,;\xa48\x80#\xa0w\x1e'
		b'\xbc\xb6$\x02\x11|\xce\xd0\x10\xb8\x1d3J_LP\x0b\xdb\xcd+\xca\xe9\x19H\x0c\xb3r\\Juj \xf9:Q5\xb4d\xed.\xbf\x01\x05Z\x88|3u\xd2q\xe6\xb5h\xb2\xa5\xfe\xad\xc5"\x08\xdf\xd4\xb6\xc0'
		b'C\xf5Z\xcd\x83\x99N-\xd19X\xf9I%K\xee=\x80\xbf%\xd5\xfb\x1b\x99"\xde\x17Tf\xa5s\x8d\xd2x{{;\xe22l#\x99\xd1\x96X\xec\xe8\x93}\x07\xde\x9f<]\x1f\x9d@\x85\x08\x19\xbd \xe5n'
		b'@\x10k\xa1\x8d\xc3\x94\x87\xd5\x94\xec\xf5@rJ\xd9\xca\xc0\xc5\x8b\xf4\xde:\xd0\xb2\xc9|\x88\x8a\xbd#\xe2\xd5\nQ4p1\xcc\xd4\x07\xf4\xbf\xabsC`LX\xa3\x13\x88}\xce\xab\xf4j\xbd\x82^3\te\x1b'
		b'\x8cJM\xc3k\x87a\xa9\xca\xb7\xf7y`R\xe0\xf2\x84\x8b\x12\x12\xc9\x90\xc5O\xd1`H\x12u?I_\x8a\x94\xe0\x16\x84\xc6\xb8S!\xa2\x99\x82\xad!\x1feb\xa2\x1e\xc3\x9eV$\x86tC\xf3&{\xd7\xfd\xd7'
		b'%\xa2\xc4A\xa4\xda8\x1fb"KP\xcf\xf2\xba\t\x84\xa1$3y\x8cN\xcb\x9fA\xd9\x1a\xc0\xb5\xbd*\xb6g\xb6\x03\xdawP\xbbp\x8d\xd3)\xfd\x82\xe9\x92WP(\xb1\xf7.\x12\xb41\xff\xd5\x87\xe4\xa7V&'
		b'r\x99\xa7\xab\xe9\xb0\xe3\'\xf4\xb0\xae"\x88\x9a\x0b\xcc\x0e\xcb\xa0E\xcc\xf9\x92\x15\xaa\x80\r^\xd8\xe1\x1d\xae\xa2\xb4L)\x80\xee9\xb2\x9d\x8e\xf5nH\xf7m\xe5\tt\x03\xb6\x92\x02V\xc1a4\xd2\xc2\x12cW\x94'
		b'2J\xb7L\xcb[\x02U\x96K\x8d\x1c\x06\x05\xcd\xd4\xc5\x82.\x0f\xeb\xcc\x00]P\xdc<\x18Z\x14d\xcc\xac\xef\xde\xc8E\xc5\xa91\xd9E\xc2V\xe0\xa4\x7fe\xf5D\xf4@+\x90\x0b.\xfay\xb2E\x01t\xed\x9d'
		b'\t\t.R\xbb\xd0\xdf\x06\x12\x98\xd6.\x8e\x80:\x1c\xfe\xfa\xeb\x1f\xd8\xcbPg\xd13\xfa+"t\x995\xb9\xa8~\x05\xb1$\x10\xc9w\x9b\x99Sgp\x89\x04\xe7(\x03V\x06\xc6\n\xf6\x81Zr\x89\x81\x16\x8e\xfb'
		b'u\x04\xbb\x15\x1e\x15\xc4\xeb\x9c\x14\xa4\xf5\xc3F\xf3"\x99H\x8b\x19xq9\x19\x9f\x85nz}\x05\xd4^\r\xe3\x9f\xb6\xebB\xfe2\xf8L\x14\xd1$\x85\xe1\x0cd\x0e\x81\x05\xf1\x16\x9e0\x98n\xa3,\x1d\xe84\xb9'
		b'd\xb1\xa6\xbd\xbb\xb4\x0b\xae\xdbe*\xf3;K\xbcg09\xaa`R\xffF\xd7&\xcb\xe8\xda\xe8\xa1\t,\t!-X\xd0q_\xea\xd5y0LVp\xc8"\x88\x8eT\x8a.\x1aq6.\xa5+\xa3\x14#\xcb\xa2'
		b'\x17\x84\xd2\xd2\x03\x9f\x93\x93<E\xa9\xb1\x9d\xe6O\r\xb2\xe5\xe5\xc1d\x12\x14\xf5\x17M\x0f\x1ay\xb5zIL\xbdUH\x88\xd1@\xe2\x90V9\x1d\x14\xdai\x18\xbcd\x1fw\x16"\'\xa8\x1bKQ"\x87\x1e(F'
		b'#\x11\x8b\xbb\xad\xd5\x18\xb9\xf3\x96\xe9\x11\x98N\x97\xc7\x84\xed\x98\x1a(\x10\xea\x1b\xc3\x1c\x94\xfa+\x84\xef8\xf1\xed\x9c\xd6&\xd1\xab\xd1\x90\x82\xablN\xdce\xa0Z\x8e\xd1N\xeb\x0f\x10\xb4#\x03\x03\x97\xa5\xbf\xeb\xb2'
		b'4\xc8\x1a\xd9$\xf6\xe3\xedq\xce\x10\xddZ\xff\xceka\xba\x8e\xd0\x8er_\x94\x8f2V\x14<\xbd\x94\xa4\xee\xb3\xdev~\x03\x10\xd7\x95\x16\x9bD\xad%!A\x94R\xc8\x07\x951\x11]7\xb3\xefE\xb3?\xe3Q'
		b'\x1f\x1f\xff\x86\x8cv\xa7qn\xf7\xd3\x01&E\x02F\x0c\x99\x8aW\x8d#\x8f\x03\x17\xdf\x19\x89\x86\xe1\xec\x04\xf6U?U*\xda\xa6\xbb\x00/\xb5w\x88\xf8Rn\x98\xd2\x15\xfeE"\x87{\xb2\xa0;nz+\x8b&'
		b'z\xb8\x1e\xfa\xf1\x17K\xd1\xbe\xad\xc2B]P/?(9\xfb\r\xac\xf4u*wd1\x15\x9b\xc9Y#\xde\xa0\x07\xe7\xa0:\xbe\xea\xee\x86\xa5F\x1a\xc5/\xb2\x89=\xf9\xc5<`}?=\xe4\x16\x13q\x1c\xe3B'
		b'\xb7\xad\x90\x13\xfd\xa8t{\xd6nje\xf4\xb4\xeab\x82~}\x17\x82C9\x8f\xd53O\r\xbd\x87\xa0\x03.\x10\xb6/A\x80M\xcd\x9d\xc9\xb2/\xd0\xed\x8e\xa2\xfa\xfd\xfd=\xfc\xfc\xbcj\xb6P\x1d\xd4\x84\xa5,\xa0'
		b'\x11\x9b\x9e}nK\xbf\xb3\x03\xda\xcb\xdf\xdf\xdfSQ_\x9a\r\xc3F\xf8\x80a \xc7\x82\xce\xb5B\x99\xbdI\x8d|\x19\xad\x95\xa0A+R\xe9\x90B\x98,$\x8a@\xc8\xd1\x16\xc3\x17\xa9\xdcU\xc9\xf2\xcat\x95\xb5'
		b"\xb9-\xa9\x18_\x92\xe4\x1b*\x14T\x8d\xa5@!\xd6k_\xc9,\xc8\xa1\x8d\xc1\n&\xf7\xab\xaf\x08\x85\xae'\xc9\xe9\xefhg\x86f\xadU3\x94\xda)\x8bC\xc0\x1d\xb5\x89\xed\xd0\x85\x83\xe4\x0c\xae\x85\x9fv\x8d;"
		b'\xd9\xc2\xb6\x9d\xd3\xe8y\xfad)\xa2\x9e\x10\xce\xc7\xd8\x1e\xd1]I\x85\x06Q\x85[\xa22\xb5\xc5i\xf0\x8d\xfc"@\xd5\xd8\xed\xda\xab\xcfH\xc4\xcfp\xd6\xdb\xdb?3\xfc\x8c\nQ[\x01\x033\xa6\x1f\xc5\xde\xab\xcf'
		b'ai\xeb@m\x9f\x9aW\xbc\x8a\x85N\xd5&\xb5\x14\x1f\xd0\x07(\x97\xe8\x12,\n\xea\xc5dQXS\x82\xd4\x9a\xe5q\xf7\xb3%\xc5$fk\xe3\xd2\t\x03:\xe5Yz\n\x1c\xe6I\xd2\xa0\xdd\xed)\x0f\x03\xd8\xc3'
		b"\xa3\x96\xa5\xa3\xc2\x88\x89M\xfd<\xbf\x81\xa8c\x00#)\xe9e\xcb\xad6Y\xab'%\x952\xaeDs\xb7H{\x90pt\xdb\xbbA\xb4\x98\x98\x9d\xd1mK\xe8\xd3$\x12([rb\x0edEm\xc9,\xa0\x8d,"
		b'\xed\xb9z\x88K\xae7_<Lz\x9e\xb9\xd1\x92Q^a\xb6\x88\x1d$!\nq1Dg\x8f\x99\xc1+\xc6\xf6\x1c>>\xfe-\xce\x93\xf2"\xd2X\xb5E\x8b\xd1-\xf4\xed7\xe1\x028\xcd\xe7\x12\xbd\x92\x98;\x8e'
		b'E\xf9\xfcQ\xd1^.\x15\x8e\x864\xc1]@\xe5\x0c\x15\\^]\x058\t\x19\x9b\xd4\xddP\xb7nQ\xfa\xae,G\x87/?\xb3\x9a\x08\xc4\x91\x96{\x8c\x86\xe4\x94H\xdc5\x82\xb5\x7fHr\x0bx\xc3=\x0b$0'
		b'\xa8u\x17 \xa8\xf9s5\x84\xd5l\x85\xa5\x9a\xd7\xd4!\xa3Q\xd7=\x81M9[\xf9(*\xa8\xb2&\xfbh\xaa)\xb6\x8f\xc9W\xbezg%I\xb2;\xca\xfa\xadYU\x15\xb4.\xc6*\xf6li\x1f6\xddb\xae'
		b" n?\x13\xdf\x8e\x06T'\x98\xa04\xf26\xee\x19\x83\xcb\x98\xb6\xbf\xaaFwww\x87\xb7\xb7\x7ff\xec\x8b\x93pZ\xa8p\x81w`\x02\x1a\x03t\xf8\x1b\xe8\x87\xe2\xd6\x12\xa0X\x1a\xca\xd9\r\xa1|\xed\xb2u\xa0"
		b"\r_\xd8\xa1\xb3Fvg\xbdJ\x80\xac\x0eiA\x00\x82KO\x0eI\x8c\xee6\xb6\x8e\xcb\xec\xed\xe7\xc50\x05'\x19\x1b\x14\x0f\x98\x03\xcdaB\xb6\xdd\x81m-X\x08\x15\x06\xafsj\xc9\xe7\x04Kx\xb9\x15\xeaq"
		b"\xfe\xdc@\xd0$\x11\x03\xa70G\xa1+\n\xfa\xc4'\xeb\xf5\xb3\xec\xed\x9aY\x9aYV\xd1K\xae\xc6\x84L\xfa.\x1d\x18\x11\xa6\xba\xfb\xc2\x86\x17\xe9l\xe9\x9cc\xb3R\xc1\x1a\xac\xdbz \x01nU\x91\xaa\xbe\xb6\xa0"
		b'\x11,I\x1f\xb5U\x94\x14C\x94>C\x8au\xde\xfc\x99!@\xd4\xae\xfc\x15m\x81JGV\n\xbf\xbc\xcb\x00PS\xa4\x14\xd1n\xee\x0b\x18\xe9\x8e!\xfc\x18a]\xdad\x88|4\x9583\xa6-\x18, 0\x13'
		b'\xf4\xab(g\x1a\r\xe2\x85\x07#\xbd9\xa6\x82\x8a\x9dD\x8d&e\xc2\xcd\xf1x\xec~\x8a\x82\xa4VJ\x04:\x1e%Nj\x0e\x01kf\xb9f\x934\xfef\x17\xb4\x9e\x80\xb0L\x9a\xc0\xfc\xa3\x0b\xd4#e\xe6\x953'
		b'\t\x04\xa9\xe4\x18\x0f*+\xab\\J\x8a=V}\xf7\xca\x9c\xb9"\xa5\xdc#_\xa7\xf6\xb2w\xdetW!\xab\xa2/\xb9\x94\xf2xJ6\x00\xc3"g\xd4\x18\x93\xa7\x8dp\xd41B\x94$o/\xeb\x03\xe9\xb4\xce\x92'
		b'\nMa\x95\xf0\xc90\xb6\xc2\x8by\x08\x934\xaf\x1d\x956\r\xca5\xbbW\r\xb2\x03\xda^R\x05\xd4\xab\x06\x85\xa0MP\x8c\xa4f\xbc\xdct\xb8,\xed4x\xb9\x1c\x83\x96\xa0\xca\xb1\xaadEe\x1f\xdbmi\xd5'
		b"\xd4G\xc1D!\x80\x9c\x18\x0e-'.\xf6m\xff\x96\xe6d\xae\x91A\xbcl\xaa\xd4\r\xdb\x14\xb1\x0f\x8f\xb2:\xbf\x1esV\x1c\x18\xa5'P_\xac\xe2\xab\xfeC\x86\xff\xe5\xe5\xa5>@\xd0\x99V\xc3NgH\xab\xa8"
		b"Z\xcf\x06\xf0\xad\xbbJ)_\x8e\xbb\xa4\x92~F=1'\xb6u=\xc2^\xde\xa5oD\xa6\xa2\xd1\x1d9R\x92\xc3\x15\x98\xc3\xb0 U\xf7V<\xdc\xb51\x0e=\x08{\xe7s\xae {\xc7\xba\xb4DV\x83\xca\x81"
		b'S\xa3~\xbc\x122gi\x8f\x12\xb5\xa0b\x7fA\xed5@\xa0\x8dDyW\xaa\xec@\xb7\xc1\x91\n\xb5K\xf7\xd0&\xce\x1a\x92G\xf1\x86(1z\xe3\xca\x86.\x1d\x8a\xc5\x85m\xbbB\xe5I\x99\xea|\x11\xe9\x0e\xda'
		b"Uj\x93\x12\t\x9c\x856`\x9d\x98)\x82\xdb\xb7N\\\xc0\x89\xd7\x1b\xad\xe9LW~\xf70\xa49\xcf\x8bKS\xf5\xbe-'B\xa8\xbat!D\xd5\x8bh\x9aAMU\xa3\xf0\xa5N\xa7\x84\x15[\xc1\x99\x86\xa0\xeb"
		b'\x1a5\x1e\n,K\x90X/\x8b\xfe\x07\xd7\xd5ye\xdaeD`\x03\x95i\xb7\xa8\xf3\xe0\xd3\xba\xf3\x98\xfaR\xbb\x8c&R\xb4A\x10\x1b\x92P\xc1\xd9\xe9>\x9d\xc9\xd6\xbcgxyyQ\x0bb\x1c\xdb\xaf\xb6\xc0I'
		b'8\x0b\x87\xa8)vhJ\x10\x1d\xebl\x0f6\xbc\xeeU\x08F\x15\xf8\xb2\xbdx\xcb\xfcr\xbb\xde\xef\n\x9d\xa3\xaa\xaf\xfa\x92\x0e\x92\xa2@\xe7\x8f\x1c\x8b\xdbL\xf7Bj\xbf\x8df=-\x12\x15\xdeo\x98 F4|'
		b'P\x00\xb3\xa0j\xd7\x0c!\xdeT\x08\xb3\x071y\xd0\xaaP\xa6w\xc0\xa8\x1eB\x94\xa1m\xe8\x96\xb4\x948\x15/\x89y\xea\xe0\xee\x87\xf9k=\xa8d`\xe0\xb9\xe2\x19D\xe8\x15\x94fkL\xfb\xda\x80Mx\xb0D'
		b'Q\xec\x7f){\xe9\x9cA;\xf8?5\x1b\x14_\xc1\x8e\nA\xe9V\x98d\\\xcbA(\x10,r7E"\xba\xb6\xb5O\xcb\xdc\xd0\x9b\x08\xcds\xac\x95\xb1K6B\x023\xc1Zd[\x81\x8a\x18\xe7\x005\xeb\xa2'
		b'\xb6eA!M\xb2(\xec\x8b^\x158\xd5z\xa5\xe2\x14\xb3\xc0H\x12\x85[y\xb3!s\xcc\x1b\xd2\xce\xf6I\x99\x9d\xa1(\x96\xa3\xe0\x19#\x96\xa9\x13\x91i(\xc4\xea\xb5\xa1@\x1f\x1f\x1f\xe5\x1b\xe4\xd0\x84\xd7\x9a\x1c'
		b'\xa8\x02\nr\xba\t&\xa5\xea9F\x13@\xa0\x12-0Z\xb2\x1a\x91!Y\xdf\xd5\xc50\x03\xa6\x08\xaa\xa5\xd3:\xc8\xde\xc31\x0b\xd6\xcb\xaa\xa1\xb1\xf2\x87\xac#)\x87\xce\x87\xda\xad+\x17f\xd0\x87\xf0\x03f\t\x93'
		b'Ao5\xc4\xd0JC\x17\x89>?\x1fLT\xecgp\x96\xc3\x9d\xc9\xabI\x05K\xca\xa8f2G\xbeKc\xa0\xe9\xa8\xdd\xb1\x9dE\xad\xd7\x9e\xd04\xbc\x1b \xa9\xce\xc3\xffw\x0f\xbb\x1b___\x87\xd3\xe9?x'
		b'\x1a\x9d6\xf8\x1a>7\xa0}\x15\xda\x10h\xd1\x95\xc3\x9e\xcd\xd0E\x1e&\xb0^%k\x85\x83v\xe0\x1c\xc2wg"6X\xf7\x84\xe6\x19\x9b\xad\x07W\xab+)E\xe5\xc9\xceA\x0e\x8d\x88\x15\x18\x17\xd1j\x0bIz'
		b'\x150\x17\xac\x17\xbcR>`j/\n\x86\xd6\'\x80\x92V\xf7\\\xf0\x16\xe0\xe0H\x04\xf5\x17\x1ct\xd3.f\xeb\x16\xdaaOq}\xd4\x00\x96\xd8\xec\xa4.\xf9\x82\xa6"\xa7\xda~\x01\xb2\x08\x96i\x8e\xed\xa5p\xd1'
		b']\x98R2p\x16f\x9b2\xad\x94\x940\x82\xc2?\xdfX=Nw\x04\t\x12\xdb\x81\x82\xda\xd3\x8aP4\xeeP:\xd1=LI\x7f\x8b!N2\xf5\x9d\xfb\xfb\xfb?6\t\x99iQ\x0eO\xa0Q\x10\t\x0c\xdf\xc1'
		b"\xf4\xa1\xe5]d\x02\x1d \xe3\x04\x8b\xc0\x8a1\xfe\xbb5\x11\x83\xa8v0\xdd\x18]\xfd\xc0\xb8NR\x1e\x99X\xafDm\xe3Z=\x13\x86\xefB\x94\xb3Uzt2\xa5\x9b$`\x90\x9b\x08\x9f\xad\x02\x9b\x1a\xc2'?"
		b'\xeb\xc4\xf3\xa2\xbaZ#\xf0\x18\xa3\xddI"S\xceE\xa0O\x82)\xcdB\x05,b1\x11K\xd4\xa5\xfe\xdf-\xc5\x94\xde\xa4\x19\xc0Y=\xae\xe8\'&M\xd0\x8aT\x90\t\xbf\xe7\xf3\xc1\xac\xac\xb2\x92\xc8\xb2\x9d1\x1d'
		b'\x8c\x8a\xe2v\xd0\xb3\x91\x1a$\x81\xf9jB\xdf\xad!?\x8c\xbfh\x86,\x94\xd3\xf3wH\xa8\xadP&_\xe2j/x&\x9b\x80/\xa8\xe7\xb5\xd6\xf4\x0f\xb7t\xafU\xb2+\xde\x0b u\xa2\xec\x92wUq\xd0Q'
		b'\x81\xec!\xd04$\xc2YW\xfc\xb2\xe8+\x03(\xd9j\xe7\xa4(y\xc62\x90\x1e^\xdb\x006\xb9rRw\xde)\xcf\x99R\xe3\xf0u\x1eL\x93!\xfezR%\xbc\xb90\xb25i\xc2\xa4,\x85\x1e\xcc\x1aH\xd4'
		b'\x06\xa8\x9f]\x17\xcfti\xbb~\x06{\xa9\x01u^;\xee=g\xce\xc1\xbb[\x1czG\x99\x15p\xe7\x05\x02{\x11{(\xe3\xb0\x0e\xe6\xa9\xe51L\xd9\x81\x98S\\\xd4\x85L\x9d\xa9\xbd\xa8\xd6\xa6\xc7\xd0A\xdai'
		b'\x82}\x17a}4$\xd5$1H\xd7\xa0\x1cC\x02\xc6\x8a\x01\xcd\xfa\x1c\xdc\xef*\x1b\xe7\x93\xf0\xf5\xf5\x97Vk<R\xe1\x84\xf2\xe1\x05\xa3\xd3-4\xa6\xc5\xa6r\x022\xaa<W\x90H\x89\x04\x97B\\@p)'
		b'\xf6U\x02\\a{\xa1E6\xc6\xa4&\xb2\x08\x9abU\xd4W\x86\xa0\x1b\xb2\x03\xf94\x13\xaa+\xcbq\x17Q-\xac\xec\xd7YzC\x9b\xc5\xb5\xf0D\x14\xb4~\xa0\x9c\xcf\xc1\x8aw\xd8\xe1X&\x02f\x98*\x95\xf9'
		b'p\x95\x0e\xe9\x91\x00\xe3q\x07q\x96:\xe8\x802\x92\xd1j\xb7\x99\xa4\x15)b\xc5E\x17H\xec;\xb2\x85\xce\xfbft\xd0\xb3\x1d\xa8\x03/\x0e\x18\xc4c\xdfV\x98\x96\xb1\x8b$\xbc\xe9~\x16*\x0be\xbb0\x9d\xfe'
		b'\xbe.\x8a\x1eK\xb4\xdc\xc1\x9d\xeej\xcc\xc7*@\x9aL|>\x84oo\xff\x14\xe8c\xfc\x17!E+\x88,\xa0.\x8b\xd9/\xaf2\x80i\x9b$\xa9\xe2\\\x10`\xa4\x9b\xda\xf2U\x16\r\xf9\x02\x08 \xcd\xaaN\x10'
		b'\xe1\x01e\xf2}\xb4\x03\x94Z\xd0\xcb\xe8\x05\xc8\x87\x02\xda4R8\xd9\x96"\xfe\x92\xe9\xca}\x8ew\xa7\xadK\xeb\x9c<\xa8j\xbc\xa1\x94;\xf3G\x93{kbn\x00\xa7\xba\x95D*\xa8\x08p&lk\xccUd'
		b'\x85\x95\xe1\xc0\tTt\x8d\x08\x12vN\x19\xea\xca\xa6\x04;\xa1\x03C\x0e\x1fs\x1b\xac\xcd\x1ajXI\xdf\xbe\xad\x08\n\x0bJ\x8a]\xd1\xdeTT\x0cq\x01\x8ap\x0b\xac\x92\xfe*\xc5D\x1ax\xa6=d\xe0D\xec'
		b'\xa2\x12\x83\x7f\xd8\x8b"\xd2\xc3\xfb\xfb\xbf\x14\x80\x10\x86AlK\x1cg\xe7V]\x08\xef\xc5\x94\xd9\xdeS\xf0\x80uS\xf0 +mw\xbbc\xed\xa8^xSo\x8b\x04\x8c\tqI\xda~\xb2\xe6\x98\xf1\xea\xfc\xa2U'
		b'\x12&\xcc\xf6\x8a\x90\xad`\x98\x82\x1d\xbe\xb4\xc4\xb1\xd0"\x9e\x0f#\xb4u{}\xb12B\xf0\xb6e\xe9\xce\x90\xba\xb0\xafR4E\x12\x10\xb0\xa6o\x15\x00\xed \xa5\x1cML\xa20%\x84\xd3v\x87*\xa3\xb5\x03\x87'
		b'*\x90\xc0\xf8g|\x04C\x10\xc9?\xc2\x1b\xaa\xb7\xe0\x1d\x00\xd9\x00z.\x03$\x94\xee\xc3E\x15\x85\xa9\xca\xb5\xe2G\xedD)~X\x8c\xb0\x12\x07\xab\xa0\n\xfd\x13n\xb0\x0ef\x1a\xe0\xc9\x16\rB\xd8\x0eoo\xff'
		b"\x94\x8fwL5eS\n\x00\x92\xe8*\x12\xc3hO\xe9\x90)\xa9`\xddt#\x85\xdd\xae\xafg\x82\x1e\x08\xeb\xb7\xa5\xb0\xb7\n\x142o'W\xd0Q\xe3\x94\x88x\x92I\xec{\xd5\x9e\xbc\x05Z,\xba\x81; ."
		b'\x92\xd4r\x02J\xb6\xc4\xd5\x08\x83\xe6\x85]?\x9d:\xe6G\xc0U\xe8N\xbbc\xe6A\xe4\xfdYV\xf4Ri\x03"\x96\xc1{"\n\x12\xdc\x1aS\x04\x1e\xdd\x93lJ\xdfH0\xb0e\xf4R.L\xeb\xc9\xa9RO'
		b'Ab/\xf2\x81\x07\x08\xc7My\x91\xd5\x90\xe8[jZ\xdf\xa8\xf98c\x7f1\xca\xb7c\x03\xc6\xa17\x81\x1f*pBl)\xccF]\xc3CQ\xfa5\xba\x81p\xed\xf7\xf7\xf7Y\x16\x85\xbc\xde\x05\xd7\xaf\xc0\xd4\x1c'
		b".\xa1\xb9V\x8cN[\x0f\xc7\x08\x95g\xa0\x047X\x17\xd5\xbb\x0b =\xd0\xeb\x84,$\xebo'zCw@\xb9TM\xa7\xefR\x8bup\x15\n Z%\xdc\xdb\xc5\xb69\x9f\xb1\xb5jR\x04\xbc\x94\xd0\xfb\x15"
		b'\x95;]\x94Kpj\xd7uM\x94+#i\x19W\xd8Q\xce\xac\x16\x17\xad\xda8\xbe\xd3\xc5\x04\x97\x1d\xb9\x99\x85&j\x82z@\x9cb;f@\x9f,(\xf8\xcbM\xd8L\x86\xc9[-\xca\x15\xc3\xa3\x91\xa8\x9b\xcc'
		b'\xfc\x04ei\xd0>\xa0\x02%\x93\xc1\x16S\xd9\xaf\xb5\xee\xae_\x95VQ:z\x1cQ\x1f\xc1Ha\x0b0F\xc6\xb5\xa3\x82\xdb\xa3\x87\x87\x87#\xa7\x06\xca\xd04\x94\xeb\xec\xd0\xd4N\x81\xaf\xd2\tP7\xa8f$\xe9'
		b"\xe6e\xa2^\x91FS\x84\x83\xfa\x89\xafZ\xf7\xd5oC\x13G36\xe0d\xb7\xc1X\x9e\x95\x14\xefL\x88\xe8z\xff\\\x19\x1cI\x05\x87\x1cl\x991-^\x1c\x15\x01\x83'/\x98\x16\xf8\xf5\xb1F\x06\xe6\xd09}"
		b'zl}#\xba\x87\xb9\xaem\x89\x15\xeb\x1bc\x04^\x8c\x8d\xdaD\xc2\x8eBc\xc9\xd9&\xf5\xd5\xa2\x11\xb7\x8ah\x80.\xd5e\xeb\x81;^\xd2\xd6\xe2\xbd\xf6=k\x82\x1bB\x0c\n\r\xa9[\x11\xf8f\x80\xe2\xca\xa9'
		b'\x03sp0e\xe7\xdf\xdf\xdf\xbd&\xf8{\xad5\xa2\x8a\x04\xba\x1fF\x94b^ul\xef\xe4i\xa5\xc0\xf8\x1197\xe1\x90\xf1\xc9\x87\xd3\xe9?P\x1d\xc7\xd7u\x87aa\\e\x844\xaa\x1a<\n\xca\xe0LYw'
		b'%\x82v\xbd\xad\xca\x19\xb9\x8b;\xa3\xb7?\xd4q\xa2[\x82\xe7\xd1Y\xaf\xf9H|\xbf=+\xd0L]Z\xfcR\x1f\xa5\xfc,\x17dP7\xaa\xc6\xbe\x12^#\xde\x911\xd4T\x0e\xd4o\xa1\x03C\xfbL\xad$\xc2'
		b'\x9b\xb6m\xe7?\xf7\x8b\x1d\xc1^\x7f\xa3\x1d\x1d\xd2[\xaf\xdci\xad=\x95\x9c\x18Fi\x98C\xc7n\xe5\xebV\rr\x01\xa26N\xde"\t\xf4\xe2]\x12\xa0v\xaf`\x02\xacp\xb4?T\xfc\x12\xf8Q\x7f\xb8\xa0\x88'
		b'\xae\x02\x95sHQ\x13\x8d\x87\xd2IN5\x00Js\x92\xc9,\x94\x02\xa5\xb5\xa5\x94j\x88G\t@\x98\xb1\xed\xdc"\x1c\xb6\x16\xe5a\xa4"U\x0f\xdd\x86\x1c_\x97*\x03\xb3*n\nx\x94\xc0\x96;^\xe1\x06\x0c'
		b'b\x92\x97d7\xc1\xfb\xc2\xdcN\x0f\xda\xfa\x8a\x84\xe5\x1c^___^^pTa\x1d\x1d\x02r\x99\xa4\xa3:\xa3\x90(\xc5i\xa3~*\x08\x94Y\x96\xb4\xe8v\x00\xa4R\xb4E\xe8\xed\xfc-}c\t\x08Mp'
		b'X\xb9(Se\x81TY,\x02\x16\xb8h\xfa\xe3@\x0e\x90r\xbd\x91\xfa\xe905\xf6\xda\xe0w\xe9J\xbd\x08~z;\nKme\xbd\xffn\xbe\xb8\xa5]\xa0\rnt\x83\xf6h\xf1\x1b\\\x12\xf8^u\xb2o\xec'
		b'Ky9\xa9<-\xd7\xfem0f\xe7\xa7k\xb0\x1a\xdd\x11\xa4q\xc6rV\xd8\x81???\x87\xd3\xe9?\xdd\xf2\x0c?\x82\xc0\x123\xd4\x0e\xfbw+\x15\xa5)\x0f\xe5\nqs\x9e\x18\x9c\xb2)\x0b\\\xcc\xd0.B'
		b'\x06=\x0c\xb2T\xe6\xb0U#\x9fo\xb0\x1c\x10\x1d\xc5\x85!\xec\xa2JCu\xc1-V\x80\x81\xb7\x0eDE\xd9p1\x804\xc2\x0c\x8a\xf9\xf7\xdf\xff\xa0\rC\x81\xca\xd8\x94l\x99\xd5\x9c2\xd0\x1d\xe1\\\xc0\x83\xd4\x84'
		b'\xd1\xb0\x06OV\xe0\n\xf1\t\xb2/\x91Ik\xbe\xd0\xd0\xea\xbf\xa3Qt\xbd\x89{\x1a>\x1bF\xa7e"\x9f\x8c\xf3k\xec\'\x95\x1a\xa2\xc8e\xdeK\xe5(,\xc4!p\xc0\x98*\x96^DJ\xd2\x06\xfa\x82\x12\xbb'
		b'\xb0\xac\xcazI\xc2\xa6\xfb\\\xc4\x96V:\x18\x7f\xeaw\xbf\xbfo\xa5\x9b`2\x87\x9eA\xd5\x13\x0c\x0f\xc1GU\xcb\xe8=\x89a\xb5\xbe\x9aiD\xea+\xcfb\x9c 1M\xef\xcf\xec\x11y\xc4*\xa1\x13\xc1u\xca'
		b'\xe8\xf5\xd0\x90\xe9#Q!&A\t\xd46\xe1\xdf\xfa30\x82Jy\xb9D\xae_\xf3\xae J\x8c\xc4Lt\xe6X\n\xa8\xeev\x8dH\xc0Tj\x88\x9c\xe2\xa1\x84w\x81\xa7J\xe9d\xde\x1a\x02EP\xd8u\xba\xd7'
		b'\x19u\xf0\x1f\t\x0c\xfd\xaeK\xfe\xd1\x18\xd4\x01@\xdb\xc4\x91\xeeEJT \xd7\xdb\x83\x06\'P\xf3Y\x98Q\xbd\x9f\x1al_\x01;\x02\x16\xa9\xe9V\x1e]\x99\xd7\\\x01I"\xadg\xba\xfc\xa0@\x05H\xdb\x97\xab'
		b'b{uuu\xfd\xff\xfe\xbf\xff\xcf\xea\xd4\xad>\x82\xbcANLS\x17\xa3\xa1\x00\x11\xef\xa5*\xaff\x17\xb1\xcd\xaa\x1el[\x9a\xf0@\xfe\xd0e\xcd\x11\xedlt\x16T\xe2K\x04E\xd1\x9bD\x97\xbf"\xc7 \xb3'
		b'GZ6\xc3\xb4\xd8\x8f\xaa{O\xbe\xa0u_\x87\xa8\x8c@/\xf6\xc51\x14\xd7\xe1\x1a\x90\x96Qy\xed\x9c!\x9fuW#\xab\x80\xe1\xe1EJ\xbf\xa8\xa0hdZ\xf0\xb0\xa9]\x1b\x9as\xd29\xc1F\x8b\\T\x80'
		b"w\x00\x86\xcf\x03\xacN8\x12\xbb\xf3\xb0aO\xc7n\xdb'\xf6\xe4-\x9c\x0f\x8b\xdbi\x0fm\x135\xa1\xe5D\xca\xcd\xf6F\xa9W\x10\xf9S\x07`\x04\x17\x90%\xc7_\x00,\xd0P\xdc\x88\xcf\xf8/\xb7\xc7l\x15\xbe"
		b'[\xbf\x1c\xc2\x99YW\x8a\xc4\xe1\xa4`o5/\xb6M\x0b\x92.;$o\x84\x13\x87\xbb=\xd0>o@4\xcc\x8bU.\x16\x020k\xd9^\xdd\x0e\xd4\x80\xe5e(\xc5\x9b\xc5\xc0\x1e\xe3\xd9\xee$\xc3\xc0\x99\xde\x94'
		b'\xba\x96\x0e\x01`\x08\x87\xbbhwV\xad?\xac\xf8]\x9c\xaax\xd4\x1fjP^\xb6HfO\x83\xb9i:~WQ\x05sFU\x8b\xa46\xc1\x0eq\x0b=\x15\x12\xdf\xa4\xc3\xb7\xf90\xec\xae\xa7\x92Mv"\xa1\x05'
		b'+\xe5\xd0{\x91\x81)\xc5\xcan\n\x16\x90&M\xachmI\xb3pPjgv\x01\x817\xe4\n\x9e\x96\xd5o\x95\xc4Q\xd2\x0c\x0cSr\xf0\x86\xf4\x9c\xc7\xe7\x18%YBi\x84\xf7v<\xd2\x93\x12\x99\x08\x81\x80'
		b'\xb2\n\x81\xfd\xc7"0rh\x1dd\x94\xa7\xb4\x81\x87\x03\xc60St\x84I\x97r\x11k\xc0\xa2\xdb\xe1\x8d\x1d\xd9\xf0S\xf9F\x0f\x0fu\xa5xL\xcc\xac\x1d\xd2\xa7o$\xba\xe9}\x9a\x12}#\x7fE\xb8\xceL\x03'
		b' \xd5\x0ei,VA14\x03\x8f\xa8\x7f\x06\xbbX\xa2\x00\xcc\xf0(\x14@y?\xc2\x02\x8b\xde=4\xaf\xee\xcf\xfc\xc3\xe3\xb1[\x84\x91\xd1\xc30\xb7d\x7f0\x05Wr8\xbe-\xc5\xfd\xb2|}\x8f\x98\xfa\xad\x18'
		b'\xb0\x85\xcea2\x1f\x9d\x13\xeat\x9cpt|\xf9\x0fR\xc3j\xc5\xea\xac\xe8*B{\r\x9c4\xd9\xcd\x90E\xc4\x850_\x0b\xb8\x16*\xf7\x9b\xa3;s\x81|Y[(\xae(\xa9R\xb7\xa2\xba\xd3\x8a8Ltu'
		b"\xa2\xe6\xed\x14\x89\xfe7X@)d\x85\xf0q\x95\x14hQG\x96\xe2\xc6\xce\xa9\x1fI\xfb2\x99\x9a\x1e\xd5h\x16j\xd0\xc6%\x86\xd1 \x9b/\xcah)5tQ\xf1\xb1\x89\xd12'F5\x9a\xd9\xbasx`\xe4"
		b"\x0c3\x85\xaf<\x8fA!\xf8E\xb8\x99h\xfd\x9b\xf5R\x10\xcb\xc4\x88\x13p\x929F\x87\xb5k\x1f\xfe\xb81\x0f\xba?'\xc6~\xb7/\x18\\\xc0GUNr\x15\x110\xbb\x12Z\xdd\xb9\xd0\xedhM\xc8l\x95\x9d"
		b'4\x9f(\xcaf\xe6eA\xc6\xf5\xc1gW\x8d\x94\xbfui\xc9\xd1i\x9f"\r\xb4Svv.wG1y\xa23\x15\x02\xf2\xa8\x95X\\U\xe5R\x95@\x10/\xc5\x84\xf4\xaf&\x87\xe8p\xf5\x0fQ\x9d\xa9\xb2\xb4'
		b'\x94P\x0e\x93\xb0\x8c\x05\xd0\x00@\x94O\xad\x11;\xd2Q\x86\xf8\x9a\xc8\xb9\xed\x146;\xdfWb\xba\\v\xa1\xa1\xc8\xd5\xc5^\xa9\x12<\x16%\xb9\xd5f\xca\x1a\xc1\xa1;\x91\x8a\x91\xc8\xe1\xb4\x86\xda\xa4Dm\xa1\x08'
		b"\xb2ID\x178A\x07\xa2h\xc7\x80M\x970\x0c'c/\x9d\x85\x8d\x90\xb3ESu\xdaz\xb0\xa2\xafUI\xb9\xa0\xe8\x1a\x9dH9\x81\x80\xa4jCwu\xe9\x9cA\xe1\x8a\xe8\x94\x7f\xcc\xbcAqWz[\x8a\xc4"
		b'\x8e"\xa7\xa8e\x04c\x8e\xd1\x91 \xf5\xd5m_\x9bB\xb2\xa9\xd58s\nU\xa0\xfan\xea\xe9:\xfd.\xe4\xc8\xcd\x0c\xcd\xa1\xc3qI!@\x12\x81\x00\xfa8\xf5\x8e\xb8\xc4T\xec\x0c\x83\xe9B\xeb\xab0o\xa2\x08'
		b'\xcd\xec:]\xbf\xfd@#\x84\xfbu\xc8Fm\xb5oooz\xa0\x02\x8f\xa5b;Q\xbd0f\x07\xd6\xa2\xd7\x82\x86\xb4\xf9A\x8a\x9d\xbc\xcen$x\x93 \x1a\x13D\x1e\x99Zc\xbb\xabO\xda\x06\xab\xbf\x82\x0e\xd1'
		b'\xc5;\xca\xfd\xca\xaa\xd1`M*\xae\x01:s\x9bf.)]U\xb5\xcd\xe8n\xfb\x185>P\xba\xc0\xd5\xa1\xd4\xa1OA\xb5e\x04+\x9b\xf6\xa0\xec\xaaF\xa6\x96\x8c\xce]5\xbd6t\x03\x865\xfdU\xc1\xadF'
		b'\x81\x8dk\x98\xb6\xa63\x9e\x84\x8eI\xa6\xb3\x0cx\xf9\xf3Z\xdb\xa9\xe4___\x1f>>\xfe\xads\xa5\x9cL\xd2fq\xcd+\xc7XTu\x13\xc9\xb1\xd0\xa2\xc9\x96\xd5\xac$\xb8\x98)#b\xadm\xb1U\xb7\xd7a'
		b'\xd8&\xf5&\xca\x14\xe2`\xd4h(X\xc6\xb2\x8a\xa0$\x9e\xf6\x18B\xc4j\\\xd3\xc9\x12\xe1\x18\x1dGC\xca\xa1\x17*d\xcfV~=#\xba\xc3\xa0:\xd6\x88\xbe\x9a\xdc\xe9`\x13\x9c\x12X\xf2Z\xc8-\xb8UB'
		b'\xfc\x1e8\xb3"\xd8e\\\x8a\x04\x0c\x981\xa9\xd6\xb2\x98\xcb\xb6\x9dF\xe2\x8a\xc2\xce\xe8[\x1blP\xde\xa4\xda\xb9~&\'\xa6\x05|o\x17\xa4{\x0b\xd5\xc8\xd8Z\xda\r6E\xe3\xd5\xd8\xb9\xad\xfd\xf2\xb7R\x11\xca'
		b'\xcf<d\x11J\x9eAVCc\xcfi9ku~|\xfc\xbb{\x9f\xc9\t\xa6\x10)\x12\x85\xc5\xb2B\xbeS\xac.\xa2-\xca\x94\x87\x99F\n\xa2\xceIq\x8b\xc4\\W\x08_\xbb\x16\xech[\xfd\x81\xe8\xfaq\xb5'
		b'\x95\xf5\xc3\xfd!US\xa3,]B\t\xc9zm\xe7{\xdbL\xf7\xc0\xe9[\x80\xc6\x8a\xe6\x89\x16vi\xdd\xae\xe5\xfd[w5;Q\xb2H\xd2\x1d0\xf5Z\x14\x84_\x04\\\xef\xb7\x98\xa7\xb2\x17\x82\xa1hax\\'
		b'H\x1f\x9c\xfc\xf2\xb4\xc5\x81m\xebNrP\xe6_\xb3E\xb8\xdbl`?\x03\xbdY\x05\xbe-SP\xefR\xa3\xc4b4\xc3\xdd\xe8;\xe2|+\x91\x84\xae\x060\xb4\x1d\x82\xb7\x8a\xee`z\xda\xf4xr\xdd\x873\x7f'
		b'\xf6\xeb\xeb/\xb2\x98\x9c\xbb\x9a\x00/\xa6\x80o\xfc#]\n\xc5m\xecy\x1a\xe8\x99\xa8\x8c\xb72\xdeE\xd6\x88\xbc\xda\xdamY*\xabC;m\xbbj\xb0z6\x1eh))a\tZ\xfa\x04y\xa1\xdaaXj\xfc'
		b'<CYd\xe7\x10C\\KR\x84\x9d3s\x006Q3f|\x15<;[:\x18\x85\x01\xa25<3\x1d\x0b&\x19\x06\x04C\x00\x8b\xf4\xd0\xec<\xe4\xda\x82\x15iTI\xc0c\xe7\x85\xb4\xc6\xf6\xb1L\xc3\xf6\xe9'
		b'\xca\xac\x8c\x05A\x0f[\xa6L&F\xbb\x8cj4-WE\x0c=\xfe\xbe\xba\xac\xbd\xaf\xcb\xd7\t\r\xda\xcd\xa5\xc9tEK*\x0cP\xe3\x8a\x19G\xed\xd7\x8b/!\xe6`9\xfc\xfc\xfc\x9c\x93B\x13\xec\x08`a\xba'
		b"\xd37.\xc4\xa7\x05\xb2\xdd\xcd\x98'-4\nd\x8dW\x1a\xc0)\xa6\x90\x9a3 U\xc8D\x8aB?d\xab\xd3W#\x9c9\xdc\xf2\xd1\xccv\xde\x8c\x0e\x97\x8e\xb0\\MW\xf1\xf5\xf5\x95\xc0(\x9bm\xe4\xcc\x96\x14"
		b'\xfa\xc9\xd2\xdf0\xc78$\x9d\x80\x1d\xce\xac\r\xca\x89\xd1\xa5\n*\xa5\x91\x81d\xdaV\xa9\xc4i\xfb2\xf0]\xdf\xb0x`G\x93l\xe7\x80\x1a\x10\xb1\x06\x01\x8c\x8aD\xd1\xf62\xcf\x8a\xc2\xcdR\xa8\x02\x981.\x10'
		b'@,\xfb\xf8\xf8\x086 s\x9d\x02d\xcf\x86p%\x0cS\xc2/p0a\xad\x9f\x14\xcb\x99\xc1\xc3F\x80\xdd\xfb43\xed\xc0M}\x1d\xc9\x82\xf2\xba\xeeO\x13\x19\x8dL6,Y\x95\x13\xe3A}\xf3\xa8\x8b\x0f\x83'
		b'@)qAe\x03O\xa1:+^\t5\xa2\xc4o\x12\xd1\xb22\xfa\xd8\xd5#h\xa1iT\xe9\xde0l\x02R\x01}\x8f\x94\xa2\xdaJ\xbc\xc48\x06S\xebj|\xde\xe1\x1a\x9df\xb1,\xbd\xdeU\x9e\xbb\x18\xd4\x9e'
		b'\t\xd0T\xae\x9d \xd7\xd43t\xf7\xb6n\x9d\xe7\xd5\x8aE\xdd\x80X\x03\xa1\xf3-\x08v\x0fa\xc1\xfax>>>L\x9d\xa2w\x94\x97\xcb\xfcw.{\x0c5\xcd\xae1\xdd\xec\x10\x15\xe4a\xd6\xd7P#\xd7\xb5\x1d'
		b'\xb1\xc8\xc5\xe8M\x12Jt\xcd\xc5C\x92\xd7w\xaa\xe3\x16\x1c\x89$\x87\xb1W\xc9\xccq\xf7`vS6\xaf/\xdcx\xf0\x961\xd0\xb6\xabX\xbc\r\xae\x95\xf9\xb0q\x9b\xe2\x12e\xf9\xd3\x10\xf3\xfa\xfa\xbf\xb4\x1dQ\xde'
		b"%.\x88\xe6\xae\xa6\x8e-z\xa0[\xc1\xde\xa9\xd4;`\xa6\xdc\x05cl\xc7'\xee\xd0\x84-\xc5\xe3\x9f\n\x94\xdbH\x8eu\x1bO3\xd2\x1bs\x8bzu\xd5t3\xf5y\xae6\x0eA\xd2vq{[\x81\x1e\x9a\xee"
		b'Zt\xfdh:wiq\xa2\xa3\xae\xd8\x96\xc6\x1a\xb1\x19\xb9\xe6m^YA\x11\x86Jj+\x952\xd8O\xf6\xd5R(\x06\x01\xa6\x81x\xb4\xcbwJ\x12]\xd7\r\xd0[.#u\x95\xed\xf3\xaef8\xe0\xcb *'
		b'\xefp\x8a\x02\xaa\x16\x07L\xb9\x1d\x1a\x17I\x17\xc0^\xda\x89\x14\xc87\xea\x19\x92\xec\xf6T\xf5\xc8\x17\x0e\xac\x84\x91\x9a\x1a\xf4\xd6\xe8\xa4L\xf0\xe1p8V\x88\x96D.\xeen\xdaJ[\xde\xa7\xd4YB\x8eJT\xbd'
		b'\xc2\x1eZ61 L\x9b\xeaj\xed\xc9\xe8%[,J\x13\x17\xbc\xd4\xcc\x95i\x14\xaa\x9b\x14\xd2\x1b\t\xda\xeb\x99a\xc8\x15z\xb0\xec\x81\xc8x\xcb\xd2*S\x95&\xfa\x01\xcd\xca\nR}c\x9fI\x05^;\x8b\xba'
		b'G6/\xe9\xdfN\xcf\x0e\\\xab\xd0N\xdbY_\xce\x85\xc0\x8c\x9a\xa0\xc2_\xbee\xe7X\xea\xd4\xd1DZ\x1d\x9dL\x9dS\xa2\x07h\xe7\xe6\xeau\xec\x875@S\xed\x86\xd1eM0v\xd2\x82V\xf1-\x8c!\t'
		b'\xb5\xa3(\xf84h\x9b\xb8\x08d\x82\x9f\xa2C\xba\x9f1\xb9y\xc9\xb3\xe6\xb2Q\xcc.I\x13\r\xb2\xf4\xbaX{\x1dm\xfe]\xcb\xf7\xf7\xf7\x97\x97\x97\x1b\xfdV\xe5\x7f\xb4\x8a\xd4\xd26f\x12Z\xa0@ub\xfa\x02'
		b'z\xd9K\xf5&\xf1\x00V\xc7E\xa3\xa7\x194\xb4\r\x8dT\xf0\xbb\x03\x95?\xd0\xe2I\x9e@\xf1\x18\x89\\\x8d\x91\xac4Q\x18\x0cq];$\xb0A\xaa[\x8a\xf56\xbcn\x8fR\x116\x87[n\xb0\xa4\x8c \x9a'
		b'\x15]m\xd19\xbd\x06\xd5t[\xca\x11)\x8e\x01:\xb6\xa8\xc2^\nD\xd5\x80j\xb51k\x95\xb8\xc8Z1\x04\xe3hH\xd4nL \xefp\xbbu\xc20\xc7z\x9b\x13\x88d\xeaJ\x15\x0c\xaf\x04%}+3['
		b'%\xc1zH\xaa\x97\xc9\xa0\x90\xc0\xe5-\x99j\xa4\x04F\x84\r\xdd\xd5 \x8a\xd1\xb9\xd5\x81\xd9\x97f\xce\xfc\xfa\xb9n\xfb\xfb\xfb\xb6\xf2\x15+<T\xe5l{\xf5\x17\x1d\x13\xc3!\x1b;F+\xa2\rE\x15p\x93\xac'
		b'\xf14D\xc8\xa2y\x84\xfa\xd1\xb2]D\xc8\x869\x91h\xae=Oi\xb7R\x9f\xe2\xfcN\xa6p9\xc5\x00\xc0\\cI\xa9\t\xd0\xacT\xf6\xef\xc27D\xcc\xbb\x17\x14\x99R\xd3Sa40\xb4\xa6\xa4\x18\xdc)\xe2'
		b'2\xc7\xa5\xcb\x93.\x8b\xc0\x8c\x9b\xda\\\x02\x87O#\xa8\xfd"\x8e\xc9@\xe6\xaf\x84+\xda\x059\x13\x80\x1b\x95\x10\xf6\xc2\xcf3\xc9\\b\x15\x00c\xd2\x83\x9e\xa9\x8cm\x14\xa0\xa0\xa6=#\x87f\xda\xbb\xc0\xb5\xe1|'
		b'\x86\x9a\x19=O^\x9fV\x80\xaek}\x82 \xce\xac\x0czA?\t\xcd\xa3\xae~T\x7f1\x1fS\x93.A\x18\x04/]\x14$\xe62\xf6\x9a\xfa\x84\xaaN9\\\x85"9\xdcC\x83\xe9\xb6]\xcb=\nW2\x9f'
		b'\x06\xa4\xe2\xb1\x89\x9b\xfb@\xdc\xda>\x93f5f|\xa1\x8b\x92\xca\xf2@\xe3i\xf9[l\xd3\x16N~\xcc&\x19v\xd6\x82\x14\x10\x0b^\xb3\x8efy \x05\x81\xdeA\x84y\xa7L\xa0!\\\xdb\xee\xc8\xb2\xb6\xbcQ'
		b"\xb8:Xx\x9d\x9bCc\x1d\xe7L6\x11'\xaa\xd3\r\xefc\xf1\xb7[\x9c\x96\xab\xf75/\x82\x1b1\xd0\xb2\xabntE\x17>\xeca5orbM]\x00\xefv\x82\x9b\xf1Af\x8b}\x11\xd580:E\xb7"
		b'\x84\x8a\xec\x80\xe1\x13\xd2\x98I\x12\xb3e\xc7\xebX\xe8\x0c\xe4\x1c2@\x18S\x87\xbf\xfe\xfa\x07R\x83\tY\xba\xfa\xcd:\x06\x1d8\xd3\xe5\xfb\xd4\xfc\x00\xed\x04\x15\x99"\xa3\xf2\x96o#\xd2\x95c\xe5\xc7\xcd\x91\xa6`'
		b'\xbe\xc8@>T\xa4\x98\xd3\xcf\xc2\xe1\t\x12$\xc4\xd7W^Y#-d\x07\xce\x82)\xb6\xb1F\x8b\xba\xe1"\xed\xcdF&\xc2\xc2B\xbb\xc2\x80N\x12\xa6\x13\xedA\x16\xc7\xfdoM\x84^\xc6\xbf\xca\x92\xf5\x8e\xa2H'
		b'i~\xd8!\xa7\xca\x0b\xf0P\xf4\x1e\xba\xa8\xee\x83I\xda\xaci\xb6y\xf5\x04t]1\nDH%\x8a%\x9d\x02\x1e\x82\t=y\x1f%\n5\x1ac\xd3B\xb4.\xe46\xce\x99\xf0\x046\xb5v+\xfa\xad\x02o\xd4'
		b'\xbdUg\xda>UY\xa5\xd6\xb0\xdb\xdb\xdb\xa3\xadB\xf1\x13\xd5p\xbb*_\xabO\x86uL\xb4u\r9\xe8\x8a\xa6\x08\\H\xd6\xabtPRK\xbaL\x18\x97\xa9\xc0\xa7\xcd\xe4,#7`[\xe2E:\x01\xd5G'
		b'\xc1e\xb3\x02\x12\xa8M\xcf\xde\xaav\x0f\xb9\x8c\x1a\xe6\x84\x9a\xd52\xb4t\xb5\x0b\x88\xf3\xb3\xf7\xf7\xf7\x98Kf\xf8\xe40)\xecf\x8d\xe8\xfbu\xed\x91\x91*x\xe9\xc5c\x05\xc1\x15\xfd<y\xc6m\xa4J\x13W\xba'
		b'\x89T\x97\xe1\x8f\xa1$\xcd\xcd\x1b\xf7\xfa\xadjd\xa1\x9e3\xaf[\xb2d\xa3\xb5\x160\xcf\x94ke\xa5\x11\xb1\x882\xf5i\xe1\xf4\xea\xb2:\xc5\xa9\xf8\xf3\xf9}f\x9bN\xc7\x89\xebS\x86\x12z\xc8F4K\x98\x03'
		b'K\xc7 O\xd5[\xffix\xea}\xb0A\xb6\r\xb2\xdbc\xdc]\x8f\x02T\x02\xf4\xc2\xbf\x90\xcc\x16H\x95z\xb7\xb8F\xd2\xd2\x03+s\xeaF!-a;\x97\xd1\x1aR\xa2\xbc\xa2\xbf[9\xcch\x9d\\AY\x9a'
		b's\xd3Z\x17\x82\xebh#\x86\x87\x8e\xa2\x07\xeab\x80\x8aX|\xc7\xa1\xae.~^\xb5d=\xcb\x87\xf1\x01u\xeeE\xc0\xc1T\xc5\xbbWY\x1f\xb3\x92\x98\xc6\x0c\x04\xa6\x83\xc1\xb7e8\xe5\x1bZ2\x04c\xd5\xc2\xd0'
		b'x\xc2\xb5\xcc\r\xd0\x95\x1bJ\xd3\x92\xb6G\xd5\xfe`bB\xdc\xae\x81>\xb2\x05\t\xd4\xce\xb1\\1\xe1\xf0\x0e\xd0\x93LlX=9\x83a\x00\xb8\xc4\xa2\xd9V1\x12[F\x90JSX\xef\x8e\xcd\x89\xb7\xd2\x13\x16'
		b'g\xf2\xc3g2H\x0b\x11\x04\xa4EP\x03+\xc6\xa5\t\xd2\xa6\xa3!&\xb4\xfd;\xd4\x88\x872rKUO\xd0_DK\xc5_\xd3\xb4\xf9\x0e,\xba\xf1R{\xdd3*j\xfb@\xe2\x1e8\xc1\xa9\xec\x10\xc5\x87\x0e'
		b'wG\xa4{\x82q\xa4s\x80\x0b\xee\xabi%(\x87\x9f\xe6\x9f\xf6O\x00\xa9\x81\x8eN[\x1e\xb9\xdc\x00VV\xfc\x03u\x91\x9c\xe9]n\xb9\xf4\xef\x93\x0e\x90h\xf6c\xfd{\xc3E\xd3\xd9\xa8\x8f\xe8\x0f\x84,m_'
		b'y\x97\xdf\xf6\xd1\xd8\xc3^\xd1\xe3o\xdc\x90\xd99\x92\x8a\xcc\\\xf7\x13 \x8e\xe9(\xf1\xdb^\xfeV^\xc1\x8e\xe2\xdd\x05\xe4\x05{T\xfa\xd0\xa9\xd7\xc5\xe8K;B}\x88\xa1\xd1\xd2_\xf4JM\xea9"\x99\xc3Q'
		b"s\xb1\xab\xb9\xdd\xdfz\xbe\xc8\xd9\xe1\xe7\xac\xe0L'\x1b(.O0TP\xbaC\xae\x91E\x89\xc3$[ i\x84e\xd9Y/a \xed\xc6\xf0#\xa5\xe8=\xc5\xa0J\xd5^\x80K\xc9\x1d\x07\xae\xfd\xcbW\xe8\xe2"
		b'\xdbQ\x84Py\x1a\x9d\xb2\xa3\x1d\xfbn\xec\r\xc04\xc1n\xd4\x8cJ\x07\xf49\xc8/\xe7\xa0E\x14A"\x050\x18\xda\xca\x9f\xf9\xf4\xd0-\xad$\x84{Y5e\xb2U\x1a\xcci\xe7\x16\\\xef\x9e\xbf\xb7kvS'
		b'\xa0\x10b"\x0eR\xf7\xc4\x8c\xd4\xa2\x17n\xc1\xf7j\xbe\xcb*\xf3\xc6hfTB\xccK\xdd\x11O\x90\xb7\x8a$\xfa\xc5\x81\xf7\xdd\xde~]a\xd4~QKp\x14YC\x81q\x11 \xe2\xdd\xed\xed\xed\x91\x82\x83\xec'
		b'\xd0\xac2\x8dE\x8aYd\x0b\x10r\x0c>Q\xf0\xdf9\xddF\x15\xe9\xc2D\xa9g\x90:\xee\x14\x9b\xe3\x8a\x18($\x88\x12\x97k\xcc3\xdd\x15\x1d\xa0\x83\x02\xe42@\xaa\xc7\xd6\x8b-\x9c\xf3\xbd(\xecY\x08\t\x0f'
		b'1\xc0\x8e\x0e\x86\x96\xf8m)}\xb9\xef\xfeiH\xdb2\xff\xa4h%9!E\xcc\x9b\xf9\xdb\xdd\xf9\xeed\xf5,\x08\x15y2C\tzr4\x10\xdc\x18\xf3\x01L\xee)p-\x04\xef\x80j8\\\xe8)\x14H\x87'
		b'\xa0\xfd\x82\xb9)/\xc8\x06\x95\x89zl\x035\x1a\xb4\xa1[\xc3h\xbd\x88\xf4%\x9c;#\xac\x1d\x11f\xcbwIH!?\xf7Ow\xc9\xf8k\x11\x17\xca\xf7\x7f\xb7G+P\xe2\xde\xde\xdc\xdc\x1c\x15b\xc8\xf0*\xad'
		b'\x879\xb6\xcdyF\x85\t\xd5\x07F\x88\xd4&pF\x1b\x07\xf1@\xcdu\x0b\xb1\xf7\xa5\x81\x0f\x86\xe0*\x9few;\x7fD\xcb\xee\xef\xefi!\xa1^\x16\x1b\xe4\x1c\xc3v\n:\xbbWb\xa4\x0c[\x0f\t\xc7\xc4\xd3'
		b'\xc2\xa23I\t7\x96[+\xa0\x82I\xd3\x82\xb5=|\x17\xe7i\xa0\xa7\xe7\xdcF6*\x04\x9d-\xfd\xc4\x9a\x9eK\x88!9]\xa7\xdaV\xd4V\xc3\xceU\x18\x01\xaf\x88\x06Yb\x00Z\xd7\x808\xb3:\x97\xa6\x96'
		b'\xb8q\x08\x91\xd5\xf5\x84\x13\xb4\xb7:a\x9du\xf3;\xe8\xf0q\x11B\xa3\x90_\xc3\xa1\r\xd5\xeb\xe4\xe8\x8c\xcdA)\xe3l\xaa\xad4\x01?$\xed\xd1\xb9\x05\xc6\xa0$RF\xd3\xa4\x91\xe4\xc2\xf7\xf7\xf7\x91\xa66R'
		b'a\x91e+\xd5>i\x0f@U \x02c\x0fD\xf6\x0c?\xa6\n\x80\x99\xf0\xfa\xa2`y\x0f%\xfa=\xfd\xe1\x15*D\xf4\xef\xdbH]l\x1dt\xf5\x17\x06)_\xc1\x89A\x03Z\xeb\xc0Y\x11j\xa7D\xe6\x87j'
		b'\xb1\xdd\xbd\xeb\xb56\x9c 1\xdd\xbfWv\xc1\x1c\xd8\xad7y\xeb\xaee\x9e\xa7\x14\x0b3\x8a\x96\xb7\xb1T\x08\xf1\xd4\xa1\xa9\x0fI\xdf\x85\x8eN\x06--%p\x9e\xa4\xb8Tc\xbb\xfc\x84\x00\xad\x02\x93\x10\xc8x`'
		b"\xd2@+\xbf\xe7\xf0\xe0\xe7\x82\xcb\xf5\x12\xdc\xde\xdevQw(\xa3\xbe<0\x80t\x91N\x8c\xb1T8\xd8Z\xc6!\x07\xf1t\x14\x16\n\xd2\xc0\xdcf6+_\x9cN\xa7\xc3\xe7\xe7\xff'\xc9\xd8\x89\xde+\xcd\x8e\x1c"
		b'axL\xc7NE\x93Z\xbct\x13\x97Kl \xfcX\x81b@\x18\xc5\xf3\x9dU\x13\x7f\xd5\x84S\x93]ZG\xa1\x9ez\xfbv\xdf!\xa8!\xa8\xe6\x88\xf8\x81J\xad\xc8\xa7\xb4\xbd\x10\xf5t\xe8*\xbe\xd2|^\x11'
		b'V\xadL\xc6\xd2(\xec\xd3k\xa0\x17m\x9a\xa5\xf1g\xfa\xadse\x1d\xcdl\xe4\x92R/j\xe1\xb5_\xa9\x82i\xb7%\x0f\xea\xde2:\xad\xb0\xa6-#<h\xf5e8%\x0f\xab\xbc\xb2l\xc8\x9d\xa0\xb1\xbdx\xc4'
		b"\xc2\xb4\xfe\xa5$ 0\xden,\xd3\xc8\xd5\x8e\x98'\xcd\x1e\x9a\xfb\x8aj\x84\xe8\xed\x91\x0e\x84\xbe+\x83\xc8\xb6n\t\xdf\xa8\x97\x0bq\xaes\xf0\x86\xee\xd2\x1d\xc2R\xd4\x1cHTG\xc4\xa9\x98\xdcq\x87a;7*"
		b' =A\x80\x9dL\xc3\x111\xc5M*\x8c\xbf\xda\xfe\xd1\xdf\x15Wl\xa8\xd7\xafw4\xf3\xef`\x04\x10\xb8bpie\xe7\x03/\xa5\xb8+\x9b\x91\xa9\xd0NP\x84\xad\xe4G\xd7:\xd4\x8b< C\xced\x00\xca\xb0'
		b'D\xc377\xb4\x08V\xd26i\x96^\xba\xa8\x05\xcd\x9a\xcd{B\x98A\xfb\x85\x85\xb1\xd3\x8e\xb5Mk\x88aD\x1dM\x837\xf3N$\xcf\x80\x8f+Dn\xf2\x95y\x87xA\x9c$v\xb0\xe3\xab\x11\xb9HuW'
		b"{\x87\xa2w\xc4\xb3\x9b\x9di\x9dnRM\xdd\xad\x9d\xc3\x1e\x8f\x84k\x8e\xa5\x8c\x9cw\xcd\xf9g4\xc9\x1cjL_\x8d\xc0\xb3\x88\xf4\xd7\xd7_\x080\xcb5'\x96\xdd\xb1.\x80\x11\x0c`n\xa0\xb3\xee\x98\xb0\xeb\xeb"
		b"\xeb\xf4%]\x8cp\xb4\xce\xa2R\x97)tp\t\xba\x85\xbe\xa5\xbbaD$\x12\xff\x8eq^\xbe\x8a0\x11\xdf\x90\xa6\x88\xfe\xbd\x1d^\xb2\xdd\x95r\x12\x00\\\x01\xabS\xae?C\xd9\x85j\x1a\xfat'\xc3\xe0\t$"
		b'(\x05D\xa8\xa5\xfa\xf1\x1a\xad\x85\xf6\xea\x01\xe7\x8a!$T\xe5T]4\xef\x97kU\xb9\xd7\xa0\xa8~\x82\x88\xb1\xcd7\x1a\xd3V\x8df\xc5^w4\x0b\xb9\xc86\xb7\x0f\xd15j\xd0(\x16\x10\xc1\x8b\xedWV\xca'
		b'P)3\xfcf\xa7od\xbf4\x93p\x05"\xb4\x95\xc9\x80@f\xfbXC^\x9a\x0eU\x06\xdd\x9a\x1ce\xfa\xbeCyho\xb6q\x85\xd2\x91\x1d\x8ajzv;]g\xbd\x1a8\xfd\xa9~\x00!\xc4\xed\x0c\x9a0'
		b'\x85\x1b\xdeR\x17\x92\xc8\x95\x9a\xa7\\|\xe7*o-\xeft:\x19\xdc\xed\x86\xd0\xae\xc2\x14\x08\xa6\xcc-l\xde\x86AU=\x01~\x1a\xc1fA\xe5\x1d\x84\xb1\xe1\xca\xd6)\xb1\x00ZC\xcd\x12;\xc0o{*\x04\x8a'
		b'\xa8\x07|)\xc7m\xc0c@\x85\x9d^9A\x03\xe1po\x80\x83&v\xb5>\xaa\xdat\xea\xb7[MO\x9f\x94C\xb5\xcb\xc8\x88\xdb\xdb[\xaaJ\xf0\xeee\xbc\xf3!\x98\x07\xa0\xa4\xee$U\xf0\x92{\x92\xd42\xda'
		b'b\x90\xdcZO\xa5@\x04QE1\x0c\x1b\xccW\x84G\x81\xe0w\xd8\xde\xc3\xc3\xc3\x99p\xd2\tP\xbe\xb5\xb28\'ya\x86A\x8a,s\xedg\xb6h"\x07P"\x89\xb5\xdf\xee\x966\xa9e\xae\xf8.\xae\x88\xcf'
		b'!\xa0`\x1cAg\xba\xcb\xa0\x99\x18\xf5M\xebpr\xeaJ$P\x17\xce\xd4\xec\x02\x1e\xc0a\xdd\xd2\x01\x84\x84E\x800h\xa3Y\x810\xed\x8b\xda\x02\xc1\xdb\xab\x0f@\x8b\xa1\xb3R|\xb2\xbd k_\x99\x92\xd59'
		b'3P\x87\xcc\x81.J\x12\xf0\x0e\xe5^?5)Z\xd9\x9d\xa12x\xfd\xb8\x9d\x07&\xbc\x8fJM\x99\xe2F\xabZ9\x1f\x1b\xa7\xcak\x86\x962\x1aJi\xa7\xd3\xdcH#\x11\xd6\x81\xec\x1d\x96p\xbb\x18;\r\xd2'
		b'\xf86\xa8#\x7fe\xfa\xa8\x8c\x82&\xec\xb9\xa9\xc3\x90S\x8d\x98\xabq\xdbq\xec\xa7\xdbc\x9c\xc7\x9dn\xa0\x9e\x02x\xb6\x82\xcb\x99\xc13\xc5\x1a7\xc4\x13W~\x95\xfe\xfd"\x03i\xcc\x1e\x8a\x18Q\xf5\x8b\x91\x92e'
		b'{\x8a\x15P\x05Si\x89\xb6\x17\x18\x18\xa1\xae\x8e\x81\x95\xd5\x03`8\x82\xc2\x90\x1f%\x82&\xfft\xdaJ\xce^__{_\x12\xc2A\xb1pL\xc2d\x8a_\x19c\xbd\x97jp\x8c\x0e\xd8\x9eF\x83\x9e!"v'
		b'}ZF\x01\x1e\xa0\x1b\x89\xc5\x91e\x05\xcc\xa3C\xe3\xc0\xd9\xe2\x1c\xbb\xf6B\xcab\x996a\xd5\xe2]}#2\xa6\x99\x93=\xbc\xf6\xff-\xa5\xb1\x80&af"Wg\x85\xda\xb1p\xd7\xe6\x12\xfe7\xdf\xa8\xaf6'
		b'8\x0b\r63w\xe3\xb2\x02\x95\xa8\x95\x08\x85;\x10\xa6W\x10p\xc6\xc1,\x9a\xec\x0c\xe1\x9f\xc9\xed\x80BN3\xfd\x8fb\x8c\xdct/\x9cw\x93\x1d\xea\xd7\xd6k\xdb\x9bd{\xb0&\x11\xc7\xc5\xd9\xe0\xd1@\x95\xaa'
		b"\xa7D8v\xef\xb57\x10f\x02\x8f\xee\xa4\x99\xbe\x02%\x16\x9cO\xa9Ai\xbd\xd7D\xb4Tv\xdd\xc6N\xc6\xafJ\xc5j\xb4\x80/\xf0\x02\xd0\xe9\xb6\x1fU'\x80\xe8\xbf\x10\xab\xfd\xc2n\x101Sy(?\x01p"
		b'-\x00\xbd]J\x848\x05]\xfd\xa1\xa6%w2s\xb3\xc3l\xa8\x8e\xa2WP%j\xb3\x90\xa6t\xa1\x10\x0b\x94Gur\xccT7=6O\xf2\xfc\xfc\x1c\x02K\xf7E\xfdA\xbe\xa4\xe5\x08#\x9dX\x06\xb9\xef\xc3'
		b"\xdb\xdb?9eH?\xfa\xa4\x16\x07j\x13Fw\xed\xa8\x05i\xa2_\xd73\xb9lu\xd1\xc5\xd2hw\xf8\xab2\x9e\xf4\x1a\xa3\x83*\xa5\xce\x15\x15\x13\xb3M\xa9\xdc\xc0\xbc5\xd9\x80Yw\x94t'\xcf\t\xa3\x13\xdc"
		b'\xdd\x16\xf2i\x0e\x06\xf9\x83}\xd5_y\x1b\xc9\xe5\x8e\xe8\xd3hO\xc5\x04\xf7A\xe0\x074\xd4?\xb5\x02a;\x84\x98D\x9f\x1a\xd06\x04j8\x16\xe9\xca\x97po4\xd9\xe4\x8d\x1bf\xd1\x8b+e\xf6\x8dP\xc1\x9d'
		b'-K<F\xfaa\\\x90\x85-Y7\xc1V\xe4\xd6\n\x10jG\x1e\x81\xb6S\xf8"7\xa6RI\xbfU\xb9\xb0\xb7\xce\xadQ\xe0\x83+\xf4\xd6\xde4\x9f\xb3\x04\xf5dH\xfe#\x1a\xe9\xd8I\xec\xb6\xd0k\xf2\x99\xa7'
		b'\xd9\x99\xb5\xab]\x1aZ\x8f\x8a\x8d\x92\x8a\xef*\x845+J1Nk\x98\xa9\xb1\xba\xc6P\xd2\xf5\x04\xf6\xc9\x00\x01\xf3\x94$\xac$/\x15\xb6w\x9c0a)\xfe\xaa0\x80\x16\x8d\xe9\x8b\xdb?\xc0KP\xb1\xec+\xa8'
		b'\x83)\xf1\xd0\xea\xc0\xad\xc8\x85\xea\xaa\x01r\xe7p\xd0{\xf4\xa3\xf19\x1a8V\xbd]\x88H\xe0\x009~\xd5K\xa1\x87\xceD\x07\x97\xcb\x92q\xd1\x8as\n\xa9\xc9/\xf3\x97L*!W\xd7\x8c\xda@!V)~'
		b'\xf76WO\x11\xc8\xf1\x95\xd4\x89\xc4\x94\xedZ\xde\xcc\xae3\xb6\x83pp1Q\xeb1\xb4\xd9\xa6n#E\x02\x93\x8c\xc5\xdb\xc7\xed\x9b\xc6g\xee\xdetD\x8a\xc0\xe4vJ\xd3p%3\x9e\x8ak\xb1$`>\xf0\x07'
		b'\xda\x9dBj\xa6\x0b\xab\x0c\xdd\x1c\xc5%;d\xb2\r\xeaH\xeb\xa5 \xc0k\xf5+;\x08#\xcf\x1b\xd3\x9d\xe6i\xd6\xd7\xaf\x84\x18\xd4\xdf\x9dm\xe8\xf4W\xf1\xd5!\x1dY\xcdT%(\x162\x19\x0e\xc2\xca\xcc\xac\x03'
		b'\xc1X\x84<\xa6\xd8\xd3:k\xc3\xef\x04\xd08pd\x15\xfb4cdA0\xf6d\x8aF\xb8\xb6\xce=\x1b\x93Y\xce\r1\xe4\xae\xb7%p\xeb\xe8\x00+\xa0\x9c\x10\xfc\xe9\xe9I=\xb1\x18)\xcb"\xd4F\xb0\xcd,'
		b'nH\xb3\xd3&;r\xf2\x84\x9e\xa4\xc7v\xf1t\x90#\xf6\xa8\x88\xf7\xdf\x89\x06\xc4\x96C-A\x03[\xf4\xe9\xcfDW\x94\xee\x1c1ns\xaf*\xcc\xaa\xe2\x00\x92\xebo5\x85\xc5\xd6\xe2\xb60\xc0\xe0\xbb\xbd\x7ft'
		b'\xf3\x0c\x83\xee$\xd9Bw@\x8f\x95\x16\x93\xd0\x8c%<\xd2\xb4a\x0e\x8d,\x90N\x985\x92\x9d\xebI\x02:\xda\x98\x8e8\xde^G\xf9\xf1\xf1\xf1\xe9\xe9I\xcfW\xc84aw\x93XK\xb6\x90/\x9e\x9f\x9fI\x15'
		b'\x11\x99$A^\xb4J\xb1K\xc5J|\xd2\xdb\x05\xe1\xb7a9.\xf7\x19%DdH/\xa8X\x88&\x8a\xce@4$tt\xd2c\xc6\xab\xc8k;|5\x1e\x80\x01\xfb\x9c,E8\x0f\x1e\x01\xb9\xcb\x16\xb3,\x0e'
		b'z\xe6\xdb\xfdo/X\xa8\xdc\xeb\xef\x98\x16\xe2h\xd8\xb8"\xa2\x00C\xf8\xb2~\x7f\x0eM\xc4\xae}\x82S"\xbe\xb2\xa4\x98^\x87\xee\xd0Q>\x9aM*\xcdU\xfa^\x8a\xbf\x80\xbeR\xa8\xc8\xde\xac\xe9\xde!\x8f\xaf'
		b'\x01<7\xca\xf1I\xaf\xf7\xd6j9\x97[S~m\xc6\x96\x06\x9a\x10UG\x04+\xb3\x9a\x11\xa6\xb1*\xa9\xe2\x14:\x8a\\\xb3\xcd\x032\x9aY\xaf\xb8\xd8c\x10*\x0b\xb7\xd5\xfe\xdf}\xe8\xdc\x18&\xd2\x8bW\xd26'
		b'$\xb3#kr\xcc\xce\x1b\xed\x1cdqZ\xd5\xacW\x97\x01?E#\xafbE\xf7\x87\xc3,wR\x85\x84\xb7\xb6V\x8c:\r\x85r\xd3\xccMN\x98^CO\xe5\xfc\xad~\x89\xa8\x9893g\x92>\x1a\xc0\x91\x01'
		b'%\xff\xd8\x16t\x1b\xf1@\x9d\xb1N\x7ff\x85\xf9\xebBn*U\x94/\xff^\xe1#|^\xedS8\x9a*\x9e\xc16\x01\xd0g\x8a^U[SM{[\xd5_\xf4`\x0c\x9cUP\x8b\xd8\xb4\x02\x97x\xded\xe9'
		b"I\x9fo\xd5]\xad\xad\xb3\xd5A\xec\xc4g3\xfa\x13g:k\xbaM\x0f\xd2\x92\x15l\x0c\x15\x89HCLW\xb1ly8\xbaBu\xc7-NL\xa9s\xc5\x91\xb6]\xb0\xc0\xe9\xf9\xf9\xb9K\x02\xce'\xf9M\xb9_"
		b'\xa4\xd7_\x91\x11\x0f\x1e\xa5\xaf\x8f\xa1.*#q\xeeE\x18#\xd5\xd3:?0m\xf1\xd5\xb6s\xc8P\x19\xdds\xb8\t\x999q\xb3\xc9_\xddF\xd2\xca:\xd7\x90_vd2\xc6\xab\xf61\xf0\xc6\xea\xce\xc3\x88\xf0'
		b'\x11W-\xbcM\xc9\x81\xa8\x18\x88a\x04\x08;\xf5\x99\xeb\xd8\x19d\x1a\x98\xa8\xbac\n\x9ap#l>\xeb\x97\xc1\xb3\xd0\xfd\xb3I`W=\xa6\xd4\xf6\xcafz\x94\x82~\xd3S*g0\xd8\xe8.\xc6n\x1a\r\xa4'
		b'\xb3\x9bn\x1e.\x00I\xfb\xe0\xcb\x02;\x12\xad\xd4m%\xaf\n\x1f\xe2\n\x87R(\xac\xd3\n\x16a\xcc\x1e\x0e\xe6\x8e+\x8d\x8bo\xec6_W\xcc`5\xb4\x9e\x94\xb3fz\r\x8c"\x85\xd0\x96\xac\x0c\xf2v\xafv'
		b'\xb2\x85\xb6@\xcf\xdc]\xff[\x04\xac\xeaIh\xed\xe5\xe5\xa5\x82\xb4&\x1b\xbd\x97\x1d\xbe@O\xe1\xb2A\x9d \x07}$=\xa4#\x05\x8d O\xc6.\xb4t}f\xa5z\xcb\xae\xe5%\xeb\xd9\x99&\x9b\xc5{\x98\xba'
		b'+\xf5\xd7\x03T\xbc\n\xe5#\x84C\x94\xce\xb1\xec-\xbaW\xaa\x904\xee\xd1H\x95;\xcb\x95\xbd\xf8\xd9\xb8w\xb8\xcd\xdbp\xa4r\x13\xedA\xb7Jf\x86\xb8\xb7\xc3\x0e:"Z.\\2<\xd0\x06\xe6D\x85\xd5\x97'
		b'\xb0\xcdoN\x03\x19G\xec\\\xe1\xe0\x92^v\xf4\x0b\xe6vO\x8b!\x07\x12e\xd7\x17\x1e\xd6\x86[^\xa8\xe7\xba`\x00\xda\x8d!B+\xa9\n\xfcZn\x8e[\x95ZW\x8d\xf1\r:\xe9\xa8mn\xf9\xa6z\xc26'
		b'-\xa8!\x88O\xa8\x80\xc0\x00\x96s\xc1\xf4\xf4\xf3\\\xe5\x92s\x01\xb2\x1b\xb8VW\x91\n\x92\x01w\xbd\t\x063\x91\x06\x10>>>\xa2\xeb\x88UJx\xb0\x9a1h\xe4?\xa84\x1dJ\x14F\x13\xc2\xf5B\xc1\x85'
		b'\xcd\x9dw\xb7\xa9\x13\xa8\xa2 z\xf1\xc9P\xd1\xe8*;r\xfc\\\x84>\x9d\xfe\xa3\xd2\xc94\xaa\xb3\xa2<`]\xf3\xda\xab\xeb\xed8\x02\xb6@\xfet^\r\xac4ZBk\x02\x12\xbcZ\xf2\x16\xf3\x8b\xb8\xa2\xf2\xe2'
		b'\xe2\x1b\xa3\xb2\x9a\\4\x00\xf9P\xf3\x0f\x11n[\xb8\xae\xb1y\x8c\xd9x\x92i\x02!\xb6\xd6\x04!"\xaa\x90\xc7\xaeA\x8e\x1b\t\x874\xcbN\n4\xb7t\xdbM\x8d^4\xdf\x97\xebo\r\x9d\xf5R\x1d\xf8]\x88'
		b'EO\xd2\x9a\x84\xbe\x13]Ct\xd3_\xbf,e\xf1*)\xec~\xb8\xb2\x00\xfd\xea\xed5\x85\x7fx\x0b\xc9\x0c\x86\x05\xe5/g\x00\x18@:_`\xb3\xaaP\xb8\x83}\xf8V\x85\x15\xe6\r\xa5\x14\xd4\xd1g\xc0\x19Q'
		b'\x1a/\xf2\x0c~$K\x03}\xf9\xa3I\n*\xea\x1cd\xa1W\xd4muF\x03\x01\xd1\x04\xd0\xda\xc4\x7f\xdb\xc6\xc5\xd2\xc8\xcaw.F\xa6\x0e\xb6#\x1d\x8c\xbd\x83~\x1chC\xd1_\xca\x08\x86\xc2\xa4\x17\xbd\xb8\x12\xca'
		b'\x08\xd0\x9b\xa5\xfe\xaf\x00u;\xa4N\x1c\xbe\xa4Z^\x0c\xe3\xf1\x82A\xe8\x15\xebU\x10\xc3\x00\xad\xc3F\xf3\x15\xc5\xd36cu\xe0\x08\x1f\xa9\x0c\x8a\xc8\x8d\xa6\xb4\x11\xae\x9c\xb8\xd1\x9c\xf3\xec\x824\xba\xffns5\xd3'
		b'En\xa5s\x1a\x98&\x19\x13\xd3\x03\xf1\x90y$E\xd0\xc6\xd2\x83\x9d\xaf~<\x1e\xb7\x88\xae\x10\xc9EX\x81L\xb2\xf9C\xceRy\x11B(=L\xc2u\x94&\xb6\xf5\xbc\x88]\xd7\nz[d\rj\xc1T\x9b'
		b'\xe4-\x1f\x1f\x1f\xffglj\x97\x0c3\x14\xe1\xac\xb3\xa8\xa0c\xcfb;m\x18S\xa2M}\xb7?\x0f!\x15\x08\x99\xa1\xed\xbbH\x19\xf7\xb8\xe6)\xb4\x88\x05\x9a\xa2\xcc\xfc\xe6\xeas\x15\x99\xb4j;\x8d\x0fQ\x96\xe2'
		b'bK`&Z\x0e\x84\x9c`\xf1\xb4^oUX\xe2?`\xa2\x02\x18]\x97q\xa5\xb2\x0et\x03\x88\xce\xef\xf0w\x91\xbd\x193Z\xb1\x84\x16\xda\xaf\x91\x17hR\xc0\xb3\x97\xd9\xaa\xbe\xa6\x07\\\xbd\xb9kf\x98UE'
		b'7RB\xfa\x16:|t\xe6\xf0\xe4H\xe6P\x9d1\xf2Hk%/\xbdw\xde\x86Vv\x95^\x17\x17\xd9\xe2\x8c\xb7\x88\x08.,\x10EA\xc5fG\x9e\xe3x\xd1\x1c\xe4\x1e9\x8d\xb2&\x82\xbb+s\xbd-\rG'
		b'\xcc\xaa\x9d\xde\xa3\x96\xd1C\x04`\x17\x94cqT\xf2@Q\x96Sf-\x00\x8by1lU\x15\r\t\x1f\xf9X\x9c\xf8\xaeo\xdfR\xe6\xd4\xf3\x98z\x99_\xeb*\x97\x02j1qF;@;S>\x82\xb4\xf1i'
		b"1X\xda\x8f\x9cL\xf9}\x98R9\x1cG\x89\x8c%3\x06?+\x96U\xc3BR\x08\x0c\xb5\xb0\xe2\xd4*-\xa5\xd1\xfa\xcd79\xee'\xf5\xc8\x8a9\xb1y\xf3\xec8^\xea\xa0\x85\x04Jo\x88\xfe\xbd\x82Z5\xce"
		b'\xb0\x1d\xd9\x81\xf2b]\xad\xdbl\x9cr\x18\xf55)A\xdf\xde5.!\xe1\xf9q\xa1Q`\x9c\xddvM9\x08Q\xd4Xob\x98\xa87|Q\xf7Y\x9e\x86\xa3\xb1\xe2e\xd9 <\x1d\x8a\xf9\x14\x15\xae\xaf\xaf\xcf'
		b'iJL@0\xbf\x92\n\xea\x92\x97!R\x80\xdcfDd\x1b\xbfZ\x14\xa6@\x9b2\xa2\xff\xd8;\x8b\x972\xbd\xe1H\xad\x14R\x86\xc8U\xd0\xa2\xdc[\xe5\xcf\xa08\xd2\xe7\xb9E\x14\xfc\xff\x9e0\x07\xe5\xf0E+'
		b'\x84\x0f]\xd6\x1bT\xa4\x07\xc3\x06\x98\xb0\x1a\xe1!\x8a\xffh\x1d@\x02\xa4\x9a\xedd\xd8)/\xc8T\xf9\x1f^7d\t\xe3\x92\xed\xd0\x06)\xf0[\x12\xabf\\\xd1)\x8dx\xd3%\xda\x14&\x1ccBkQ\xcb\xce'
		b'\tt\x8eu\x0eu\xd4P\xd3\xe8v\xe9\x86a\x082g\xe1\x01\xc4\xfc\xcc\xf5\xe0\xac([\xadL\xbf\xea*\x99u\xcd\x9c\xb9\xc4\xcc\xc1Z\xabv\xdc-\x92\x9d/\x95\xe6<\xfa\xf1\xf5\xf5\x7fW|\xb4\x0b\x10\xec\x98\xa1'
		b'\xd2\\\xa3W\xb8\xe0\x01\x8d,\'N\x11\xba\'\xd0\xae\xba\x045\x9c\x19h\xee\xfa)\xb1\r\x19W:\x0bY\xe8mB\x07\xdaf\xf3\x08o\x88z\xf1\xa8JzV"\xaa\xe0uSO\xb5z\x9d\xb2\x94\xe4V;\xde0'
		b'(;GX\x1c< \xac\xcf\xcb#\xe1\xe9-\x96\xd7b\r\x12\xb2\xc6\xb0\xd2\xccEqMB\x02\xe24l\xcb\xb5Y\x11o(\\\xafF8~ez\xf1\xa6\xb2\xf1\xc6a\xec0\x11\xb9~\x7f\xb5\xb3\xe4\x14\x98\x1c2'
		b'\x1d\x02\xd6\x10\x85sg\x1ffp5\xe5\x19c\xae\xaa\xe5\xf0\xb8{yo\x8cN\x83\xb9.\x04\xc3\x89\xc3\xaa\x1e\x18M\xa2\xd5\x04\x81\xef\xec\xe2\xfe\xfa\xeb\x1f\x05\xee\xc6\xe8\x99&\xb2\xf4\xb8\x16ey\xd5\xed\x93Qd\xd9'
		b'\xf8\x1d\xdcG\xcc\xd0\xde\xb4\xa6K\x84\xea\x99v\xca4vx\xaf-k$\x9a\xbb\x13\xb9;\xcd^\x18\xdfC~\to]`\xa1\xf5\x02\x1d\xaa\x1b\x18b\xb7\xed\xb6\x9e\x93\x0f\x94\x99\xd0wX\xc1\x1c\x0c\xad\xd2\x86\x0b\x8f'
		b'\x07q\x12\x17\xed\x18\x18m\xa8=\x8cX\xce\x86\xe9\xa37\xaa\xbec\xbd6h\x07`b\x98Q\xb9\x12d/\x07\xbe\xff\xd0\x8a\xa9\x0c\xaf<\xba\x93\x85\xe0r\xca>*\xac\x98\x1d9\xc0\xeaE\xc4\xae{#R?:E'
		b'\xe9.\x03\x12\x8c\xa81}\xdeb:\xe2z5\x97|i\xc1en\x18ba\xcd\xba/8\x84#Q$|\xfa\x1c\x16\xf0\xb5w\xae\xad\xc1g\x19O\x0bC\x88)\x80\xce\x90o\xbd\xd0\x8c\xa8\xba\x848\xe5\x16\x16\xba\xd1'
		b"$\xd3k\xc2\x9e\x89\x91\x00,\xddI]\xb3\xe8t\xca\x1c9\xae\x9d\xaa\xdd\x0b\xb6\xb8H\xd4Z(E\x93\xfa\x92UB\x00\x8b\xe4W\x15\xff\x8b\x9aL\\[E'\xb8\xca\xea\xc1\xb4\xcd\xfdI\xcf\xb9\xad\x17\xdb\x80\x06\xfb"
		b'\n-Y\xf9\x10\xdc\xaa-w\x04r\xb3#\xab\xd3J\xbc\xb17]Ml\x06XSeVl\xbb>\xdcp\x97\x07X\xd9\xa6\xe8\xee\xc5\xd25\x14Kue%\x05\x04\x99\xa22\x86\x80O\xa6\xd4\xb6\xb3\x882\xfc&\xf3'
		b'\xf5\x99\xc1;\xfdJ\xdf\x05\xf5\xdaY\xba\x1b\xcb\x01\xb8N\xa7\xd3\xe1\xf7\xf7M\x10\x96\x8dQ^a\x99\xd6\xc3\x82iiZ\xf97\x90\x0b\xc9\x96\x96\xc1v\x9f\xa0\xdd\xf3S\xbc^WS\x00G\x07\x05\xca\xd6\xde \xa8\x14'
		b'Td\xb9w\x82\x8b\x1a\xa7\x1e|7*\xfa\x00\x06\xb59\xa7d)\xa47\xc0~{\xbf\x03v\x08\xcc\x17\x85+\xe0\x17q\x11[G\x8bb\xaa\xe1\x86\x84r\xbb\xa5\x8e\xafP{\xfb\xa4/nT\xe1Jy\xbc\x08~\x9b'
		b'!\xd7\xdd\xe9\r*\xa6\xd7\x97\xa3\xbbZ\x92\xd0\xe2d\\\x94Gw\xa0\xaa\xc2K\xa7\x99b\x8d\x8eme\x16\x8dZ:~\xc8R\x14\x07n\xa3\xc2\x8e hs\xb3&\x00=\x95\x90U\xafZ\x05[\x94\x87\xcd\x04\xe8$'
		b' \xa5\xc3l`\\\x7f\xea\x00=%6\xf2f\xf7Xe\xb0EV\x84V\x91\xd0P\n\x8b\xc4\xc74f\xc9\xa4Y\x1dtsQE\xcft\x8c\x9d~\x1fKJ;\xbb\xb8\x8c\xab\xeeO\x91R&\xdf\x80r=\xf2r\\'
		b'"%\x82\x1c!\x93\x18\x9d\x88\x10\x8b\xbb#\x10\xb7cn\xbd\x10\x06DK\xba\xd2T\xd5X\xb0\x18\xe4\x1e\xb4n\x04\xe8\xa6\t\xear"\xb4\x88\x95\xa0\x81\xdb\x98&\xc9\xc0\xaa\xb3\xe8@\nM\xcec\xab\xd6i#\xa6t'
		b'[\xe0\xb1#mP;U`\x8a\xb8\x84C^\xdc\x93\xeb\x12)\x8d\xa1P\x82\xeb_\x8d"\xa3 \xf1\xd5\xaaq\xe1\x9f7\xdc\xed\xbc\t\x81\xfa\xc9\xb0W\xb3#0\xe7\xc9\xa6\xb3\xdd\xfdal\xb1s\x10\xf8\xfe\xfe/7'
		b"c\tF\xdd\x12\xfa~+L\xa7\xe4d\x80\xbb\x91*\xd0\x18\xee\x92'\xca\xbe\x16\xc7\x03\nL/\x15\x82\xef\xa0\xa4\x02\xc1u\x82;\x97\\\xe7\x94\x06\x1a\xec\xce y\xedi\x02\\\xe1r\xd6B\xe0^R\x9b\toW"
		b'vB\xf8b\xd2d\xb0\xf655\n{#x\x05\xdb&\tQ\xc8\x0cc\x00\x16w\xdc1\x0e`\x85\x98\x05j\xc6\xadXd\xc9\x96\xb4kF\xe6\xcd\x0e\xe2\x0e\x89\xa1Q\x9e\xb4F\xd8>-2z\xb5\xf4\xfatW\xd1'
		b'\x16\x19;\x97V}\xd3\x8c##6xZ\xb5&.\x9d\xd0\xf9R\xac\xa5\x82\x1d\x03\xc1v_\x9a\xd5\xcb]\xe3Pt\x08\xfb^u\xb4\x1d(\x86H\x17,A\xb3\xf5\xf7\xf7\xf7\xf0\xfb\xfb\x06\xdaC\xe2\xe3j\xd5/\x94'
		b'Q\x11"\x00R\x046\\\x00\x80Rg\x9a\xaez\xe6\r\x97\x0ej\xd9y-\xfen\x11\r\xe2\xddP\xa1\x8f\xd23\xd0\xaf\xf7\x87\xa1\x84}\xb2\xdf2\xe4O\xfc\x83\x0f\x8c-\xb8\x8a\x9f\x1d\x11\xbe\xa8\x95*?!\xc3\xc6'
		b'\xbf\x1b\xd4\xa3W\xa1\xa3\x1f\xa4C\xb9\xb1\xe4\x8f\xdd\xed\xbc\xae\x81p\xf3\x01\x7f\xa4\xe7Q\xc7\xf4Th\x83&\x94\xd4\x1f\x12\x84\xdb\x89\x1e\xb2I\x9cs\t\x03N\x9b2\xe8\xaa\xa6qM8/z\x17\xd5\xe6d\x08Z\xd5'
		b'P\x1e\xac0<z\x154\xb0\xbb{Y\x0017\x0eZ4\\pU\xe2$\xab;}\xc2<\xab\x15b\xea2\xab\xb7" \xca\xe6\xcf\x17\xd5\xb8i\x025:n\xe9\xf7#r\xec\xcc\x05\x91C\xefS\xee\x18\x19\x18Y'
		b'o\xd7\xf1\xf9\xf9\xb9j\xc3&\xe3]YC\xd5u\x9c\x193Zz\x9dG.\xcf\xc6\r\x96c\x18\x06l\x8ca\x8f\x91K\xe5s\xa3\xfd\x19\xcabN\x96\x11\x97\xc5N\xe6\xa8\x9a\xce\xb0\xfa\xfa=\x83\xb6@\xc3\x97*\xd6'
		b"T\xfb\x04'P\xf33\t+3/\xdb\xa1\x9c\x8e6\xd2\x83\xe1\x02\xb2\x91T\xbb\x91\xe1\xd6u`\x1a\xe3q\xa9\x97a\x1dw{Y\xdc\xf0\x89\xae\x87\xe3\x0bW\xd8\xdc\xfdb\x1cN\x1e)\xf3\x84c\xd7\xad\xc0\x03\xe8\x9e"
		b'\xd0\xec\xc8\xa9\xe6\x8e\x94\xd2\x97\x17\x1c}\x83=\xddI\x95\xfdd.\xba\\\x88\xbf\x15\xa8\xf7\x9a+\x02\x00\xdf3n\xb4\xdf\xe5C`\xbb\xc7\xcdb\x97\x9dB\xd5\xb0;\xaa]\x0bg\x01S%J\x0fB\xa8\xfa\xfcr\xfa'
		b'0\xdedN\xb9\xb0\xb0\x0e6@\xb2\x85\\$\xb8Bs_\x94wU\xd3\xba0\xd5\xc5\x08\xee\x16\xf0\xb9\x15\xf4\xd0\xcb\x17\x89\xd5\x99\x02T\xf8\x14>\x10\x8dv\x9b\xd9\xc5\x00\xb0\x7fU*C\xd2u\x17\xb4\x80$,\x9d'
		b'K\xa0\x16\xb1\x06\xe4p\x8e"\x87c\x1c|\xd6\xa4\x16\x82\x08\xa7\x14\xa4\xe9\x16\xd2\r7\xad\x88\\R\'\t\x91\x16\xfa\x94%R\xfd$\xec\xa7\xa9\xc0\xe7\xf7\x00\x9e\x99>\x8d1(\xa6D\xf7T\x8dG\xa0\xe9\xab\xb5\xad'
		b'`]\x93\xa7{\xb2\x04\xef*\xaa\xe5Z\x9a\x10I\x1b\xed\xa8Y\xe8\xeaJK-gg\xbd\x96\x9at\x8d\xa6\x12\xda\xc3\xe9\xf4\x1f\x17\xc5\x0e\x19\xd8\x06\xb4\xde\x80\x988\x8c\x0e\x12\x8b\xd8\xb1F\xabf<\xcc\xb8\xd5\xbb\xcd'
		b"5\x8bME\x90\x9b\xed\x9d'\x99\xfdMI\xff\xa3h\xf7\xb744<\xa1c\x91\t\xa4\x90lN\xe8R-4\xe6\xe77\xa3?`\x11J\xfd\xf5\x8b\xa8\xe8\xf1Np\x02\x95l\xe2\xefB\x82:\x8c#\xc0J\x8dL\x1a"
		b'\xdd\xf9v\x84D\xc9\xe2J\t:(.\xbf\xa8W\xb0\xdaF\x84\xc7g#\x01\xe1rz8\xbdO\xde\xc6\xb4\\D\x0f\xa95B\xfd\x01\x0bMj\x01\x133\xb4\xcb\xa4n>G\xe9}\xe9\x86pO \x15\xe0\xc1\x9cR'
		b'x\xda\xf6\xf5\xaf\xcc\t\x95i;\xb83\x02\x8d[_v\xa3\xac\x83\xcao^\xd7H\xae\xa3\xe0\xbe/\xebS\xc8|ozw\xf3\xf7?\xc1\x8bf\xe6H^!\xcd\xd4X\xb7\xb8Mj\xa5*A\xa6\xda]$\x19\xb0\x05'
		b'\x81\x1e\xd1L>E\x1cy^1\x0cViX\x81:\xddr\x1f\xe4\xf7znz~\xea\x8b2\xfb\xfe\xbc\xdd\rM\xea\x19\xba\xdeXw\xa4BW\xcf\x9d\xa0\x9d\xb1\x14\xf20d/=\xf5^_L\xdf!\xd6\xeb\xbcC'
		b'\x1b\x10<w d\xbf\x1e4ND\x8d%\xee\x1e..\xa4\xb6\xa3\xb2\xd6Yt\x1f\xb4\xfe\x81_\xf5\xa9e;x\x15q\x9a\x01\x92.\xa7\xce$\x8c\xd4\xff&\x07h\xedmy\xc1\xff\x8e8~\n\x06\x80\xc9b\x04\xea'
		b'\xa4C\xc4\xa0v\xf2\x92\xbe\x91\xa5\xaeU\x89"\x00ux\x7f\xffWDsi\xa8\'\x96[h0\x97Y:Ll\xb3.\xc4\x1d\x10\x86\xb3\xc1W\x884v\x1e\x04\x04&*9\xf4\x80c\x95\x98\xd2\x9ePBF\x82]'
		b'\x89\xa5 \x85.\xf0\x05\xc1\xd0\x87\xf7\x87:$\xa5\xfe\xce\x1cmW\x06\xa6\x17g\x8c\xb3\xd9A\x08\xba\x10\xd5GM3(\xeb\xed\xa9\x1a\x17+\xe3\xf2\xa5\x8b\x7f\x9b\xba^\xa3\x96:\x8e\xda\x88Z\x81\x05\xdfJ3A!'
		b'\xe9\x9c\xc4\xb7\xa3\x03\xe11\x83c\x87\x18P\'\xe8\x02\x988\r\xa5\x01\xdf\xadq]"\x03W\xdf\xc2f\xb9"\x0b\xc5\xbe\xc6\x87_\xa1\x9d%\x92 e\xe8\xae\\\x97\x15\xfb\x10\xc5\xab\x9b\xa3\x86\x83\x10\xc5\xfa\xb8f\x9a'
		b"R\xec\xc8\xe7\xe7\xe7\xe1\xfd\xfd_\xdb\xb9\xac,\x87\xbe\xaf\x88\xb8\x81\x87dE\xbdV\x16\xb2\x17C\xdc\x82\x1e\xbcV\xdcY\xc7\x1d'\x98eS\xbb\xf1\x00l6\x0ct\xcd\x9e\x95\xf2w\xfe\x10\xa1\x08\x1a\x9b2\xdb\x9b\x9b"
		b'\xfb\xab"\xb12\xbd\xa4gW{\xa7\xf5A\xde\xda)\x07H\r\x19\xf5\xb6\x07q\x15\xcd\x04\x83\xa0\xdb\xae\xb6\xc0\x03\xa3\x8a\xf0\t\xc2hA|\x07T\xe1\xc9\x18pw\x8c\xac\x88\x104\xf3\xd4Y\xc7\xed+A_\xe17'
		b'gE\xadT\xfcc&\xdf\xf6\xdc\x90\xe3d\x01\x01\xb2\xe6\x8d#58`\xa65\x92\xb8\xea\xe6\x13\xb1\xf4[h\xb9\xabc\x87\xec\xd0\x11\x821@\xcc\xc0\x06\x1a;\xa9\xa6JP!\xe9\xa7\xd3\xe9O\x89\x81P\x1eK\xcf'
		b"A\xf3\x00\xb9\x88\xed\xd7L\x80\x85['}\x03\xf7l#w\xfc\x81\x19\x1c\x19\x06&\xbc\x0c)\xbaDQD\x0f\xd0\x95(\x8b5o\x8f\xbc\x0c[N)`g\x15\xaf\xec\x0f\xf0+\xd00C\x02\x02'f\xb8\xfd@\x85"
		b'^\x90\x13\xa9\xed\x92.\xc9U\xf4[+\x1cd\x12p\x1f\xd5\x9fw?\x95\x87bF\x98e\x0ft\x92h\xe9\xfe1fF\xcf\x1dP\xa8\xd4\x7f\x13D\xc4\x84|&\xee\xd3JY\xa1!BN\xe8UJI\x95\xd2J\xf4'
		b'\xd5d\x88\x93n[B\xd6\x1d\x0c\x9d97\x0e#\x8f\x91\x97+\x04W\x92\x03piw,V\xb9h\xbe\x83>\xfbRu7\xa5\xeeL-\x98QKCKj|7\n\xc2\x9f\xc6\xe7\xa5=\x19Ck\xdeA\xc7\xc2\x96'
		b'\x14Q\tF\x83\x17\x15\xe1\xa5b\xdb^\x14\x90\xb7\x84\x132I\x85n\xaa\xdf\xf8?l\xa4u\xcf\x11\xb5^\x9a\x92\xb9`q\x94,\x02A\x95\n\x10\x96\x1e\xe7\xa3}N\x1f\xb7\x9a \x05cg\x14\xbc\x8d\x13\xb6\xfa\x98'
		b'dg W\xca\xb4A\x90\xb5\xce\xac\xb84\x98\x85\x14vU\x1e\x8d\xbf\xe7a\x86\x7fG8\x1c`@\xad\x01\xf4\xbdQ\xf8F\x90n!\xaf\xa6\xd8\xb8X-\xce\x8aaJ\xc1\xc1\xafD\x06T\xc4\x0bh\xcb\x1aW\x1f\x1f'
		b'\xcfTG"\xa2\x00$\x83:\xa7b\xe5*\xdbQ\xc0\xed]\x0cr5\x14"\xe3U.\xb1j1\xca8\xf9\x93Ld\x84\x1f\xfb\xdb\x1b\x11\xe3\xa8\xa5\xb1\x07\xf6TGRM\xb0\xadm\xe5\xce\xf3\xeeH\x190?1='
		b'"g\x92\x0f\x1e\xc0\xbc\xba\x8e \xd1}\xe4\x88,\xb7d\xab_d\x18\xc2\x9b7\xd3h\x1b\xcc\xe3\x00\x92\x089\x8a\x85\xccl\x93\xe4m\xe7\'hhe6\xdac\xbcv-|\xca\xa5\x99s\x81\x87\x11\xc8\x9d\xf2L\x1a'
		b"-0\x80\xc1\x8e\x0c\x0b\x80\x83,\x85\x04\xa8\xf4\x99\xd6j\x86M\xb9\x84\xca\x17\xbb\xb0b\xee\xdd|h\x18\xb9;x\xee6\x97\xe9\xcf\xac1\xb2\x0e\xb8\x1d\x93\x91R%;X\xe7'vV\xa1Q\x19\xa3\x99\x14\xa2\x91B"
		b" \xf8\xf2\n\xafg\xfeh\xdf:3\x9b[\xf2\x18:\xc8\xcc\xd2\xddB*\xf0G\xc5\xdd'\xc8\xee\x00b\xdd\xa5|~G4X\xbf/\xcaW\x9f\x91\x01\x11\x85\x1a\x93\x86\t$-Xa_\xacO\xd2\xfcJ\xa8\x91\x13"
		b"\xe6\xfd\xf3M\xf0\r$'\xf3&\x14\xfc\xe5\xdc\xfeVyN\x0b\x85\xb3\x08\xe2Ui\x92H\xd1c\xdcA\x85\x86\xc9u=\x94\xfc\xc8\xa6\xeb\x8a\xdav~\x9f\xac\x84G\xc5i\xbfQ\x88\x15lG`\xab\x07h'\xf63"
		b'\xcdl\x13\x00\xc0|W^S\xb0\x84\x0eM\x16N\xddGXb\nNMB[\xd5!\xa8\xb8b>\xa2\xd3\\\x19\xe9\x84B&]\x19tJ2@\xf9:\xe9\x10G\x07\xfb\xdf6\xa9\x0eO\xed\xfc\xc8\x98\xffM\x1bi'
		b'CQ\xeb\xd0\xa11\x94\xe4\x15\xba\xc3\x1c\xa1\xd63\x89\xaam\xc679\xa5\xd7,=\xcb\xfb\xe1;\x9d\x87\xd8-c\xb1(\xaa\xe3\x0e\xb2-n\xa3cQ\x18\xddw\x13\x9d\xed\x0e\x18D\x1e\x15\xac\x07%\xdcNT\x14h'
		b'Xh\x01\xf6\xde\x81j\xa2\xba\x9e^\xdbZO\xb2SxU\x15\xf4C\xd0\x1b\x8c\x85\xa6r\x84V\x89n\x8e\xf4\xd1\xcf\xf0\x9bF{\xacb+4V#\xb27\xa2\xa7\xa2G\xc2T9\xdeU?1\x8dri\xd9jB'
		b'\xb5\xa3\xa5\x83\x99\x80\x8c.\x0e#EJ\xc0\x0e\xf6\x8b2"\xec\\J\x833\xdb-\xe5\xc3M\xe9\x12\xbbc\x07g\x83\xdc\xfc\x1d\xc6\xa1\xd3\xcd\x0bv*d\xbd;=:\x8f\x87\xa9\xb6\xad\x052i\xc6\x08\xe6~A\xcf'
		b'1\x1c@m.<\x83\x92na\x0b\xd0\xd6\x18RR(Z\x14\x94\x8f\x8e;\x85\xa5\xe4\xa9dtI\xcb\x10\xc6\xc5^4L\xe4\xddZ\xb2\xf6U\x94\xa9\xe910D@\xcc\x17\xaf\xda\x8c\xa8}\x91\x10AB/\xe9\xcd'
		b'u\xb1ht(\xc7\x10t\xe6\xa3\xfa\x96X\xbe\xc8\x1d\xab\x98YV \xf6\x10P2\x8a9\xab\x95\x80.b\xa94KFE(,\xc1\xcd\xea\x83\x95\xf4!\xf4F\xfd\xf9\xcb\xcbKm\xbed\x1d\xcc{\xec-\x8a\x8b\x88'
		b'\xb4\xb6Y\xb4\n\x15\x10e\xab\x02E@$\x1e\x81F\x1f\x04\xdbT\xe5\x1a=TPZ\x06E\r\x80\x17U\xee\xec\t\xe9\x85\xe9K\xec\xd7\x0b \x05\xdc\x05Z\x94\x8b*\xfb\xac2\xdf\xc5\xd1\xdfn\n4\xa4$B2'
		b'\x1c\xdd\x04=\xa8\xd9&\xe0\x18q\xa1\x9d\x1e\xadg\x0bS}\'\xc4\x1d\x8f\xc7\x9b"f:g\xdd\x0f\x0f\x8d(jo4\xb3u\xdd\x95\x1b\xf2\x80\x1am\x8c_\xde)\x82\xcb\xf5\xcb\x1e/\x19\xa1\xd3\t\x84\xcdolg'
		b'\x1d\xea\x91E\xa4P\xd9\xcf\xf4xt\xac\xb8\xe9m\xfaV\x9c\x96\x84\x88\xd9\xd0\x9b\x95\x81W\xbb\x18\'/\xdd\xb8\x9d\xfbi\xe6\x92+Dz\x96\x80v\xeb\x1cb\xdd\xc1*E1$\xd3\x13R\xf8"@\xcd^\xee\xb03'
		b'Q\xef\x8e3\xc2\x94\xa4\xaf\x96\xd7\x12\xc8\x81z\x00e\xc8\x1d}H\xae&c\xc1\xa2\x1b\x8eTy;V\x15\xba;\xbaAw[\xedH\xcf\x9d\xb5bz\xfbI#[`n}\xfe\x8a"\xaa/U\x93\xe9fb=\xe0'
		b'\xcf\x19\x10\xd8\x11\x12\xcf\xfbC\x84Ha\xf0\x19+kJ\xe4\x0ew\xe8\x11C\x1b\xc3%5+\x84\x01;j\xe1\xbe\xaa\xbc\x9dW\x8c.\x86\xb6_\x91\xbbtq\x15q\xc8z\xa6\xd3\xd6\xeaD)\xb3I\xd9TG3\xe2'
		b"'\x05\x04\xce\x1dOI\x9c\xba\xed\x8e\xb6y\xdb\x82\x00Y\xaa\xcbY\xc7\x8eQ\xb1\x81\xbc\x93\xe2\xb9\x9e\x15\xf2-4\xe9{\xaa\xc2\xc8B5(\xad\xba\x1b\xea\xc4^0-f\x92\x84U\xbb\xd7\xc6\xd1\x85\x17s21\xd5"
		b'\x80EAB\xc4N\x86\xb9;>\x87h\x82\xae\x83m4\xedN\xca\xe5\xc4\x81+\xcb\xac1\x9f\xa4\x0fa9\xea#\xa6\x1bQ\xaf@\x17X\xe2Y\xf1p\x11Ag}\xd3\x03\x89P\x97|\x07_\x13\x9c\xd5-\xad\xe5e'
		b'\x11*\xc6\x97D\xb9\n\xcf\xcdv\xd0\x89%\x94\x9d;%\x82\x04\x85@1tgZ\xc2T|\xa9\xa0\xb8\x13S\xf6(\xf3\x12[\x02\xcc\xberjK\xfc\xd4\x96\t\x8aE\xdc\xc5\x9e%^\xa9\xecg\xb3\xa5e\xe4\xd6\xfa'
		b'p-\xea///\x12\xa3r\x12\x81\x9c4q\xdbs\xdd\x01\xde\x92\xb2\x90\x18zeb!\x01\xb2\x1d\xebV\x1ce\xf2\x80\xa6*#J\x1d\xe2\xac5*n\x90\x8b\xec\x7f\xc7\xf6\xa8\x00"\xa2n\x0c\x80jN)C\x85'
		b"U\x12\x95uDL\x04\x8b\xf1<\xcb\xebFs\xe8\xc0Q\xa1\x0bs\xcc\xdc\xa6G\x94\xa5\xa3(\xac%R>\xe0T \x98\xc8\x88\xc46B\\\x90@L'`\xc0\x8e\xf6\xf1\xe1`=\xae\xe9\xbcP\xaf\xaf\xff\xbbuM"
		b'\xc3\xb0\xc4\x9a\x94\xe9u\'p\xb2\xda\x02!\x98|t\xf2A"r\x80\tV\xa0\xa6>lo\xa5\x90n\xfc\xc2\x7fm\xc9v\xc6t\xe8\x97t\xbes7v\x02\x9f:\xa2\xa5\xd1\x8d\xb5eTj\x00b\xb3\xb6P\x93\xd4'
		b'\xb6\xae\x1a\xda\xa7\xfdB\xb74\xb0\xcf\x05\xe8l\x19\x90\xa8b\xd8S\xb9\x90+\xee\xdb\xeauz0>h\xc7\xc3[\xfa\xc6\xbe\x05\x80A4\x93\xcf\xcc`K\xbb\xf5\xe2(\xdb\xb1>x\x87\xfa\xd3aP`~-\x13Z'
		b'\x87\x89\x18P\xa4$}`U\x91\xa6PZ\x94\x8d\x81H9\x81m\xde\xd7\xa8\xadyr\xd5\x99l\x81\xe2\xcf\xc6{:\x07\xb6"n\x8e\x9b\x89*\x87\xd3\xe9?\x049\xb6\xbc\x8fm\x8b\xc6\xb3\xa3H-\r\xdc&\xed1'
		b'\xa2n\x1a#t\x8a\xd8\xf2LQK\x7fA\xd8Dg\xb7\xa0\x8c\x9f\x94\xb4=\x86\xbd\xee\xfc\xd3\xd5)b\x95\xf5y\xac6\xd8J\xba\xef,@R\xac\xd8\xd4\x05B0P\xf4\xac\xa2>\xcc\xe7\xee?\xe10\xa5\x1c\x7f\xab'
		b'\xcd`\x07\xaf\x07t\xc2\xb5x!\xd0;\xce\x19]7\x0b\xb2\x99\xb7\xee\x90\x1d\xa5\x83Q\xab\xd5A)T\x95\xc6F\xe8\x02\xc3r5\xae\x9d&\xa9\x88K\x07O;\x18\xc1\x89\x82\xaf\xe8\x88Q\xeb\xeb\xdc\xd8-(\xc1g'
		b'\xd5\xe36>\x04\xf2\xeaG\x83\x10\xa0*:\xab\xfa\xf7\x05\x96\xc4D(\xb7\x1a\x95"^8\x82\x93\xfd)\xf6"Y8%$\x11\'\xc8\x0c\x91\xbd\xf3\xd1\xe2v\x8c\xd2\r6\\\xdey\xd5\xc6\xbe\x03\xba\xab\xb4\x03\x01Y'
		b'/F\x913\r\x96.\xd4\xde\xb4\x95\xc6\xbc\x0e\x07A\xf3\x7f\x97\xc4\xb5\xb1n:\xa8V`\x08Z_\xb4\xda\x1b+T\x18\xeb\xbd?\x8fq\xe0\xf4\xc3\xec\xf2\x06;\xe7G\x12\xb9d\x87\x18\xf0\xcc\xe1\x0e\xb4T\nT\xa4'
		b'k\x95DD\x84-:d\xe2:\xa3\xc8\x8d\xd6\nD\xafqBL_\xa0\xe5\xcc\xa1$\x85\xe7R(\xd2\x00\xd0\xb5!\xfa]2\x86\x12\xdf\xd1\xb4D\x94\x1a\xf0k\x80\xec\x18\xb8\xe4-tNJ\x917\xd9P\xf40\x93'
		b'E\x12Bx]g\x0b\xc12B\x86\x9b\x14\xe1b\xfd\xfe\xfe\x1e>>\xfe\xdd\x15\xdf1\x94\xeb\x04\xb2\x9d\xcc\x92\x0c\x81\xd5\x91\xe7\xa1\x9e3\x15\x10e\x96\xafVT\xa3\xe0L1X\x88@E\x16\x88\xde\xd6\x1a \xb92'
		b'\x1e\x9a\xb5\xb7\xe1m\x0b\xd2\xe2u\xd5Y\xc4\x95\xdeB\xc7\xb7\x81\x1af\xf5`LXzJ\xe8F)\xea^U\xba\xeap\x14%k\x9b")N\xeb\x0e=\x0e\xabB\x1bxH\xd1\x8a\x17\xacP\x82\x9e\xc9e\xb9\xba\xb7'
		b'\x99\x00\xd2\x8b\xd2\x9eN\x06k\x02\x8b\\Q\x16\x8b\xaf\xd3H\x1b\x8a\xc3\xb0\x035\xdcj\xf2gK\xef\xddd\x14\x15\x02\r\x1b\xa1\x8b\xb8\xb4FBq\xb2\xf9\xd8\xaa\x84\xfaf\xdb&\x87\xcd\x7fC~\xb9MUs\x9d\xd9'
		b"\x0e\xd5\xd5\xd5\xd5\x1fv\x9e\xbe\x9e\x95'\xd1\xdd\x82\xc3\xadD\x1f@\x8b\xf0T\xa1\xaa\x97\\VO\xf0\xcb\x02\xdbM\xads\xf8V4\xa53\x9a_n-\xcc\xb3X\xedX\x08\x06Js;]d\x1fX\xae\x8e\xb8\x93b"
		b'Mb\xab\x80\x12\xb0\xbd\xe3.\x15\\;\x9d\x99j\xa3\x1a\xf1\xc6\x1b\x83\xe0c\xe39w8T\xd0\xb3OU\x1f5\x7f \xd5\xa8\xd2k\x157nC\xb5As\xd9\xfa\xe1.\x86R \xcdMe\xaf\xbe\xb4u\x00:w'
		b'\xf4\xd9\xd7\xfc\xb3Q1\x90x\xed\x8bH~p\x05T\x88\x8c\x9a^\x96.\xdb\xb6md\n\r1\xe1r\xc18\xeab\xa4\xbf\xd1\xc3t\x02\x90\xd1_\xc1\xa8\xad\xcf\x92/X\x15d\x86\x9bE\xae}\x02!J\xe6i\xa1'
		b'\x0e\xa7\xd3\x7f\xdaBT\xd5\xa5U\xae\xcc\x8b*\x86\xf0\xd4|\x11{\x13\xd9\xba\x7f\xf7\xe7\xda\x1aZ\x0bM[\xe2\xf5\xa2\xd5mg\xde1\xe2\xec\xb4\xa9l\xb8kx\xe4Y\xe2\x0e\x81\x10\x05}uIJ\xc4\x14\x90\x7f\xe0'
		b'\xbf8m\xcb\xb0\xa7\xb5&\x9f\xdb\x07\xd0-\x842\xe9\xe7E\xe1\xd0\xb3\xa5TX[\x0e\xc7\xa6f)\x00\xa6$\xdf\x16@\xebK\xcb\x07L\x1b\xa9/\x0ctK\xda\xb6\x95\xdc\x19\x08\\\x81\xea\xafNTDn \x98\x10'
		b'q\xbb>V\xf3\x82\xce\xe4j\rQ\x86\xdcJ\x9c\xce\xe3\x80HJ\x16[\xfal\xdd\n\x86\xbb!+\x8c\x80C {\\1\x1e>\xaa\xf2\xcb\xce\x89\xea\xea6\x9f\xc5\x14\xb9\xb3oQ\xa5bo\x00\xf0Yk;\x91U'
		b'\xb6\x88\xd8\xa3\x91\x8a\x85\xe6\x1d\xd9\xae\x1ay6\x9d\x19\xc5\xca&\xda\x1a\x85\xa2!\xd3\xac8]Q\xc5\xd6Q\xb8Z\x0e\xf3\xe2\xb7<\xce-\xac>;\xe8\xba:yU\xcf\x0enK\xe06J\xe8U\xa9\x85s\xd9\xb0x'
		b'\xcb\xc5\xd6\xa6\x98l{d\xff\x8b\xbc\x1dR,#\x97\xdc\x1b\xdaW\xe0^\xc8A\xa2X\x16\x8e\xe8\xb1\x17\tq(\x13\xb3EF\xa4k\x9a\x8a\x04\xae\x13\xc1\xeePv\x97\xb2\xf1H\xd1\xc8\xdbb\x98@\xa1\x1e\x92\x8e\xaf'
		b'\x94\x1a\x1c\xe4>\xf7\x9a9OMd\xfd\x07I\xd9\xdcH\xd9\x0e\x98\xb2O#7\xb4\r\x1ef\xcf\x90&\xa0\x1b\xcb\xfbu\x84H\xce@\xde\xe8sB\xa5\xb3\xc2-\x9a\x99q\x87\xc3\xe1\x0fYj\x874\xb6\x9a\xc8\xc3e'
		b'\xdf;$\xb0,\xd9\xa0\xf3\xeeO\xc6\xde\x86\xf9\xa6\xeeb\xf9xyX\xb7\xdfC\xb3\x88\xa8\xc2m\xcf\xb6\x9e\x88d\xd8\xddP\x97n\xa6\x013\xd1f6\xb22R@\xe58\x87FE\x82\xc8kWB\xa3f\xcc\x8e\x1d'
		b'\xb8\x92k\xa6\xe9\xc2\x11q\x86\xfa\x98\x82\x14\xa1[\xdeB\x9c\x10yk\x1b\xff\x01A\x94:7\xacw,\xea\xf1\xa7\xb9\xa2\x9e`Gu&t\xa0a\xfc\xf0\xf5\xa2Y\x9a\xaa=a\x84d\xc1\x8cJ\x0b\x02\xa6\x12\xb5)'
		b'\xd3\xd2\x06\xaeL\x97\xa3\xd4\x9c:\x93N\x9d\x12\x0c\xab\xd7S\xe5\xb2LdD\xe0!\x1a\xe2\x8e\x89\xca\xf0\x97\x8c\xf6\xa1Vx\xa1\xe7\xd5\x81\xa1\xd2\xd7\xe5o%\xbf\xbe\xbe\x0e\xdf\xdf/@\xf4\xa5\x00)@hM\xea\n'
		b"\x96.\x83\xdeP\x83\\\x03|\xc3\xd5\xe3\xb5X\xeaA;\r\xc5L\xe6%\xc7\xba\xe5nK'#\xe3\x07x\xed\xf3\xc5\xc1\xbeE\xc6IKG\xbb\xba+\xadJ%\xe3\x84!P\x1f\x92\x03\xc8)\xdd(\xb4a\xfc\x82\x95"
		b'\xe5\xd9\xe9\x01\xf5\xfd\xe8\x8ew%\xb6\xe4\xc4\x8ac%\xf4\xe7\xdc\xba\x1a0\xb4d\tj\xc4\xf2W\\\xec\x82 \x1d\x08\xd1\x0e\xd2lTjDR\x02I\x1b\x95[\xde)\x19\xdbQsD(T?=\xc3\x0e\x01\xd1\xd9'
		b'\xd8\xef\xaa\x18\xea~\xec\xe1\x19J\t\xf4\x8e\xfc\xa1J\x06\x99U\xb0_\x11\xe2\x0bMic\xd2s\xbc\xc1V>\xf6\xa82\xb7\xe3\xbc[t\xb66\xf4\x94\x8e\x05w\x91/.\xab y\x90\xad5\x08\xa4G\x07\x8c\xf6\xc5'
		b'@:\x8c\xd1\x1d\xa8*v\xca\xb8\xf6\xf9Z\xe0\xdb\x9b\xcc\xa7\xfe\xd7\xf5B\xfd\xbc\xeeu\xac/\xd33\xf1&\x8cQR\x83\xeb\xb1\xd5\x1c:\xe5&\xce\xa3=\x83\xf9\xfdJ\xd6K\xc9\xb9\xff\xd6\xe0G|I\xc7\x99B2'
		b'\xfa\x9aF>\xcb\x0e@,\xad2\xaa\xb0\xf0\x80\xe3*\xad\xcf\x88\x1a+\x88?K\xb2\x89\xb0\xa4\xfb\xd6B\xb1\x94\x94\xe1\x84\xc1B\x00\xbdo\xdds\x05\x8d\x1d\xd9\xa2\xa9E{\r\x13\xd0\xfa4#\x9d\x03\xf4\xf0\xe0\r\x94'
		b'\n.\x17^Lk\x11*\xdd\xcd\x81S\x99d#_\xcf,Rx\x90/\xe1\x1d\x9fc\x9c\xbf\xfe\xfaG\x9f\xb2\xc3O\x97AA\xf2\xc4t#\xd5Ad\x8fZl\xdd\xce(_\x08?\x88%\n\xef\x17\x03\tU\x18\x10'
		b'\x895\x8baSg9H2\xe1\xf0d\x03X\x85\x15NTF)ZS~\xa6\x08\xb4\xc5N\xb2%\xaa\xd7-h\xe9\xcd\xce$\x8c\x95\xe51\xfa]v\x8e\xc1\xee\x1b\t]\xed\x18t\xf9.\xff\xc3\xc8u\xf3k\xafQ'
		b'W\xea\xe7m\x10\x1bIR\x05\xdfK\xd7D\xc6\x9eW\xd9IM$!\x14\xcbV\xa3\xa5\x07.\xa5\x0e\xff\xd0p\xa8\xb9/&\x1cn&\xd3\xae\xaa\xd0Rcj\xe1\x831\xe1\xa2;%\xff\xb2\xc7V,\xebc\xce\x08%'
		b'\x88m|\xd9\x19\xa1]3P5J%e.\x83\x11\xb6\xc1\xedl\xbf:+Ef\xed\xa8\xc6\xc5%\xe5U\xdf!\x1e\x91\x05*A\x89\xf5\xaa\xf0\x81I\x8b\xeah\n\x86\x08G\xe1\x1aTjx\x8c:k\xc7\xd4:n'
		b'\x11~\xb5\x1b\x10\xf1\xb1|[\xebU\xfb\xc2Gb/\xcd\xac\xd7\x0f\xa4\xab\x9d\xef\xda\xb9\x9f\xe5\x91;\x9a\x8aV\x00R\x8dP\x18?\xa2\xf8^\x19\xaeP\x98\x85\x06\xa1R2$\x9a)MG\xdf\xef:\xe1\x95d\xf3\xf2'
		b'\xfed?\xc8\x8a\xb4J*DXL\xfd9\xfb"vU\xe7"\xf4\xa9\xa2\x97\xf7nqd\xdb\xe2^X\x99\xa2\x8d\x11i;\xdf <\xbaS\x882\xa3A\x82\x82ww@\xd1F\x0f\x17V\xb6f\x7f\xb2Y\xf0=\n'
		b'\xe7\x14\xd6@\x9f\xb8\x0b\xfaTM\xfd\xba\xbe\xbe>\xb3\x82\x97b\x9e\x95\xea\xad\xaa@uc:\x7f]\xcav\xab-\x0c\x8f\xdb\xce,h1\xa8qu\n6g0g\x92\x8d\x81m\x97o\xa8\n\xafdl\xcf\x99\xa0Z'
		b'V\xbc\x13&\xca\xef\xd9\xca\xb1ti)\xca\n\x9a=X\xdd\x02}\xaf\xb6\xa3\xe5\x93\xf5\xe4FI/\xcc\xdf~\x04gI\x1f\xad\x8fQ\x9e6&\xf3\xb1\x04\xb5\xfe\xd7\xad\xdbq!\x8a\x9d\xd0\x82\x0b\x9a\x89<D>\xaa'
		b'-P\xd9[\x15\xc9\xe8\x97V\xb2\x10\x99\xe2\x15\xc7\x0e}\xdeA\x9a\xc6\xfb\xe5IV\xecU{>\x80H\x87\x00\x1bg^|v\xb6\xcd2\x8a\x13\xb7G-\x9fxc!:!T\x0b\xb8jT\xe2("\x00\x06f\xd2'
		b'\xcb\xc9\x04[C\x9e\xed\xf0\xfa\xfa\xbf^\x83\x9d \xb9\xa8\x9b\x11\x1a\x05K)$\x05\x10\xe9\xa33=E\xb9\xc0LB\x05\xd4btQ\xd0\xf6q\x97\xed\x15Z\xe0\x81`\xc3\xbb\xebAF\xd1\xb9P\xb5B\xf4h\x80\xe9'
		b'\xb14\xac\xbc`\xa9\x13\x86X\xbf\xda\xdc\x17\xa8\xb6_\xd4\xac\xadaW\xe2\xaew\x89\xe2\xb1\xcc\xb2c\x01\x14R\xd4[\xa2Q\xe0r\xc1z\xdf~abU\xb5UU[s\xb5p\x83bUr0\xb4Q \xf54\n'
		b'\xb8\xb1-0\x00\xe4\xd9]\xd7H\xb2}\xb8\x19\xc6;\\c\xc5)\xf4y\xb9ot\x04\xc9CiLE\x83\x85\xa3H\xd6\xfb\xde@\x0e\r\t\xbc\xa2v\x16\xc0?ANj(K\x9f\x06\xfb\n\rvI\xcf\x0eV2'
		b'\xde\xd9m\x03\xda\x1eR<\xb1\x85\xe1\xb8\xdd\x84n\xb3\x84\xd8\xe8\x0e\x91q\x9b\xfa\xf8\xf8\x08-\rA_j\xdd\xe6|\x08\xf1R\x19\x02\x04N$\xff\xd0\x86\xf5\xdfOOO\x9a3\x9d0\xf8:l\xd7\x88\xe9\x1d\xd7\x95'
		b'\xf1\xde\xd1\x886\x92\xb6\x17\x12\xeb\n\xc6\xe4!C\x05\xecwG?\x91\x92\x0ee\xa6\xc8\rQ\x18\x912\x99NK\x15\x07\xe7\xfe\x82J\xa8yW=_V\xdd:sk\x045P0\xcc\x13\xe9\x19\xc2\xa3:\xbe]\xe0'
		b'\x96\xae\x1d/ \\RcK\xdd\xc5(L\x97\xb2\x97\x16\x1av\x14R\x92\x1bD\xbf) \\\x93\x8c\x93l\xcc!\xad\xbe\xe0\x1d\x01\x18{\xa7\xca\xabC\xad\xaf\xee\x81\r\xb7\xd5\xcc\x046\xf5\xa8v3\xa3p\xe6\x9c~'
		b"\x7f\xbf\xc0R\x04U\x1b\x04\x97\xdeq\xbe\xe4\xa5\xb0\xed1\xd1\xc5\xe5\xeb\xc7\x89\xd3G\x80\xeb\xf4hU\x16'\x94-\x08|I\x85\x1apB\xf0\x90\xdc\x9f\n9UP\xa2\x06\xf8\x1dF\xdcQZ\xc6\x86E\x13\xa0\xe4\x9e"
		b'\xbdGI\x02p\xf9]}a\xa0\xb7\\V\x01\x83\xce\x15J\xe5\xab\x88\xa8`\x84\x17\xc9k\x13%\xc0\x88\x94\x04\xaf\xc5*+u\x1fv\xd8\x1e\xd5\xce-\xd6Rg0W\xb3ww\xbd\xfb\x81\xe2"\x11 \x93\x94\xc3,'
		b'\xba\xe8\x95\x89\xeb\xffw\x7fHgtE\xaf\xf4\xc4\x94j_0\xdb\x81ZB&#j\xa4m&\x15@\xa8\xfd0\x86\xaf\xe9[\xba\xc3\xd4\x10@\xd2;\xdaG\xd4$ ?^\xb4}\xe8.\x95\xac\xa0\xb9\x9b\x0c\t\x17'
		b'G\xac\xa5\xa1\xc7\x13\x99\x89\xadj\xa6\xd1N(,\xe8\xc2\xc9!\xd3\x97\xde\x16\x0e\xf3\x05\xe0 \x16\xcf\xff i\xf5[t\xdd\xf4\xeet\x82\xd7&I!J\xbe\xa1\x13\x9b=\xf3\x00\xa6ih\x0f\xd7\xe6\xdb6\x07\x9c\xaf'
		b'\xe2"(\xe6\xa2\x0b\xb4RZ>\xa1)\xb9\xce\x936s\xd3\x01\xcbb\xc5l\xbe\x8e\xaeNU\xb9\x8b>\x15\x10\xf6\x85\x90\xbd\x9e\xd5\x05IT\xe8\xfd\x1b\x8b\x98\xeca\xf6\x82\xa0Sv\x17\x15<\xbb+IE\x08\x05v'
		b'\x87AK\x113\xff\xd4#}\xc8\x85pw\t\xb1\xae\xce\xa2\xc4\xfe\x84d/%S\x98\xec\x86a\xdd\x13b\x0e\xe5\x0f\xc6\xd6/\xd2x\x8c\xf8\nL\xad#1\xfc\x18\xf5T:\x05\x93q:\xdbT\xf6\xc3\x881\x1c\x12'
		b'9S\x1f\xf5\xf1\xf1\x911\xcb\xf2\x05\xd6\xe6\x04\xf7\x9a.pnY\x91&\xc8\xd2\xeb\xb6\t\x87\xee"\xb5\xf4\x88\x90\x05B\xa2\xaf}\x97-H\xe9\xfd\r\xf3\xc9\x95SgP\xf1\xedh\xc2\t2\x8a]\xec2\xec\x1d9'
		b'l\xb4\x89j\x7f)P\x0b\xae\x9b\x8c\xadjIm\x12\x01%\xde\x8c\xdf\xdfEV,\xb7\xe6\xa1\xefaz\xd2\x95\xc2\xcb\x05\xf7v\xc0\xab:\x1a\x1d\xc5\xf6.F\xccB\xa8\xd9/s\xb3\xc1zDY\xa0\x9f<\x80\xc1%'
		b'EPqr\x0c\xe61%\x1b\x053$\n\x03"\xd8\xba2\xc8\xea\xfcI\xdbL\x12Y\x1d\x1ahR\x8f\xba\xed\xc8\x85\xa6\xd7\xd7\xd7\x87\xdf\xdfsL\x8f\xe0\xb5CS.\xa8\x07\x16\x9d^)iA\xad\xf1\xd4R\xd5z'
		b'\xf4\xe7\xb7\xee\xfd@\xb6\xd9\xc6@\x15v\xb6G_\x87\xce%\xd8\x95\xc6\xa1N\xb4I]\xe6\xf0rO\xabi\x90v\xa4\xa8\x9d\x97\xa0\x7f\xb1\xa3kD\xc64\xb5\xab\x92\xc2\xa1I\x1c\xabe\x86\xe7\xf0\x00K\xf1e\xa4\xa5'
		b'(n\xb2\x81K\x0c\xedV$\xb0n\xf8\xe7x]\x94\x04\xb4\xf6\x07$\xa8\x84 \xcf\\`\x0c\x04\x0cy\t\x9d\xf5\xea\xc4\xaeV\x0c\xa5\x8b\xbe<\xd0\x85\x8e\xc4\xecw\xe1~{!\xa8#\x0fUh\x07M\x118\xf4+'
		b"\x82jMm@\x14\x03l@\xab\x1a\x18\xf2\xd8\x9df\xa6\xa4\x93C\xea\n\xe9UA\xcdO^]]\x1d)\xe2\x17\xd7\xf6\xad\x9a3\xcai\x8a\xb3\xd7\xcf\x12\xcf\x11\xc3\xe0\x1b\xd9\xf8m!'\x82I\xeaY_s\xde\x06"
		b"U\x8b`S\x19d\xd4\x17\xf2;ZE)\xe7\x90\xe8Q\xaf\xc5\xc7\xf6\xf0\xab\x91M\xdb\x8b\xaa\xbd\xc8\n\xa2R(E]'7\xa5Q\x10\x94\xa4TWRX\x0f\xc36Q\x98aC\xad\xdf\xb1#\xf8n\xe0Of\xb5"
		b'\x1f\xa3~\\&Zb\x8d\x12g\xb0\xb616RU\xa5\xd9\xec1a\xe0J\x84\xf2\x19\xb6@O)\xb9\xa1\xa2\x0e5\x10"\x9e&\xe4\x85\xb5+M\xaas\xc319%\xa4\x01\xce\xfc\xbfW[~\xbc\x13\x9a\xfd\xb0\x0e'
		b'IHnP,\x8d\t>\x93\xd8\x91q\xdc\x1d\xe6r\xd4\xe7\xe7gcK[1\xdd\x85\x87\xdf\xdfs$\n\xe2\xa09\xaa\x8b\x051\x93g\x00\xd6\xcatCZ\xfc\xb0\x9en\x91\x00S\x8a\xc1\xb2c)\x18\xdd>\xe1\xdc'
		b'\xafy8\xc4\x1b\xd1@\xddY\x87\x8e\xe9]\x84\x1e\x1aJ\xae\x03\x98\xc4\x08\xb5n\xcc-|\xcf\xcc\xb6^\x1f\xa8(p\x06\x08\xb6\x911\xe9.\xa3v\r\xff\xe94\x14\xe9)X\x02\x12\xa0@\xc8\xcc\xc0\xcd\xcc\x90\xf64'
		b'*\xd0\xa6\xe1\xea\xb0\xd9^X\xcc\xc2\xf6~\xe7g" \x89*E\x92\xdbO\x8c\x04@\x01\x1b}\x92\xfc2\x821WF\xf2\x0c\x12\xda\xfda(i]\xf2*\xe0\xb2V\xd5\xc6!\x8d\xd6\\\xb1\x03Ra\xd0\xb1\xaa\xb0'
		b'\np4v\xfaF\x0b\x0e\x973/\xac\x93\xa09\xf1O\xf4\xbb\xb3\x19\xfbVS\x10I\x07\x0b1\x01d\xfd\x8a\xb3\xb5=.\xa6\x06m\xc7\x13l$\xdb\xbc\xdd:\xf8\xd5 |>}\xc7~\xe4\x82J\x04;\x85\xdaF'
		b'1g\x00\x8e\x1e\xd8$MG\x8d\x14\x02\xbdQ\xe1_BZ\x9d\xd4\xd8Z4\\\xb5\x9e\x91\xc7\xa2\xe7E5\xa4\xef\xea\xbd\x14\xef\x94\x9f\x05<*\x80\xa5C;O\x84\xf5]1\xc9\xc7\xc7G\xfap;\x99\xa1\x9b\xf0\xfa'
		b"\xfa\x9au7\x88\x04\x95\xb5\xfc>\x0c\xda\xaa\xaen&\xbb\x83\xc2 c\xee\x18\xc4B\x07\xc8R\xa7Son\xc5\n?:\x91\x1d'a[\xa1\xaf\xee\x11\xb14\xded'\xaa\x05\xcc\xe3)-\xe7\xd005zMjh"
		b'p\x9a\xfa\x9fL|[\xe9\x07\x97\xd9\xc8\xb6\x0e^\xac$9\xfd\x9f\xceT\xfa\x16E\x96dN\xdaT>\xc8\xd86\x06\x06e\x178S H\xd7`\xe1\xaa\xbe\x9b4\x05Z\xce\xca\xbd\xc3\x07\rJ\xe9N\xaa\x9bd{'
		b'\xda\xe6\xee^\xde#\x8e\x8a\x81"\xe1\x15\xfd\xe2\x8a\x15\xf7\xdf\xa5\xc5;\xb4X=\xc4\xe4\x0b4i\xf2c\x94k\tmo\xe5\xd5\x01\x82\x1d\xe9L\x00\xf6\xe1\xd8x\xc7U%j\xb7b\x8f\xe9PEl6\x18\xaa?G'
		b'\xd1c)\xf6E\xd8E\xf8\xa0u\xd0~\x9eE\x0b\x81\xa8#\xac"\x8f\xe3\xfb\xfa\xfa\xda\xdf\x9a$\xcd\xeeh\xfd\xd3\xfbb\x1c\x1b"Y\x81\x96\x02+6\x842"\n \xf4\xd9\x915\xfa\xc4\x88\xdeUU3h\xabD'
		b'\xc5\x98\xf4\x9c6<]\xe2A0J\xeb\xef\xf3\xf3\xf3\x11x\x9f\x7f\xa1\xd0\x8b\x9f\x88\xdfO\xc3\x0c\xf7\x93\x13\xd7hg\x1a\xbb\x12,\xbdDc.\xe1\x0f\x0c\x1e.\x8a\x0f\xc4\xd2\xc1\x1e\x05\x0fW"Y$;\x1b\xdcu'
		b'\xcf\xe0\x81\xb0\nH 69GA\xf0E\x8bw\x9b]\x8c\x8b\xd2\xac\xe8Q\x1e\x829\x9b\x9f\xac"!\xfd@\x80\xc5/\xa4\xdf\xad[\x05\xc3\x94\x9e)w\xdc\x8d*G\x94\xf0\x80\x0b\xfb\xa7\x975z\x087s\tv'
		b'\xa1\x02\xd6Y\xb7\xa4\x10\xc58\xf4\xde\xa5\xf4\xa3\xb4d\xd5\x98[%p\x08!\x062\xcb\xc6\x08t<\xba?\xc4 \xb6\x00R\x88h\x92\xe4\xb6\xbf\xec\xfcL\xe2h\xfd;\x17\xa4\x7f\xc0\x8c3\x92\xf7p9\x81eL\xad'
		b"B\x0f)2\xb7\xac\x0b\xc7'\x1cA\xad;\xa2\xd44\x80\x9d\xce\x90\xcd[\xf6/\xaa=Wk\xf6\xa3\x9eq\x0f\xad\xf2GB\xab\x17\x10\x04\xf7\xed\x0e\x9fY\xf0\xa2\x02\xe5\xcf\xf2\xe9\x1d\xcfa-P'\xdcX] \xdb"
		b'\xd6\xd4\xf3\xb4\xf7\xc2_\x03\x0cq\xadi?\xc2\x9e3u-n7\xaa\x80\xb5\xb7k\xad\xf5C\xaf&\xd9\x86a;x*\x98R\xd7\xb5\xd2\xa1a\xaf\xf0l\x83\x02\x12\x15\x95}u\rL\x03\x00\x87S(iaC\xa8'
		b'h\x9a\x0b~\x14V\x1dG\xec \xa3\x91[+\xd3bd\x1d\xabg\xdc\xb7\x94\x9e\x99\xdc\x98%n\x9b\xf4=\xcb9W\xf0\x1d\xd1\xc3\xd9U\x0c&Uh~p\x1d\xa1\x9ac\xf87\x96\x858E\xab\x91U\xea1\x0c\xea'
		b";_\xef\xd3\xe9?;>\x08C]U\xb9\x8b\xa8\x10\xed\xa1\x85\xd4]\xfa\xce\xa5\xa7\xbc@*\xb4r\xe6\x16\xa2\x13\xef\xacE>N\x1bQ;\x11\x1e\x82\x9a\x0f'\x11h*j\xf0\xfe\xa2m7\x81\x93Uv5~+"
		b"\xbcu\xe5M\xf1\x07WuU-\xafU^\xae\x8b~\x1db\xf1T'\xa4\xda&\x19\x12)\xd8\x112K\xc0&\xefcD{\xe5!\x12\xdc4 LO\x0b{\x10@\x06t\x12\xc0Y\rSU\x02\x821 |\x0e\xbc"
		b'\xff\xcdr\xb5\x8fr\xd02\xb1\xa2\x08\xfb\xc25\xe1\xf6\x15\xfd\x1a\x92\xc2\xa1\xa9\xefj\x1d&\xf3\xd1\xa93TEX\x9bY\xd1\x97\xb7\xc3\xb5\x0c\xba3t\x8bi&\xe9\xa0\xb6\xb8l\\4\xecs2}:\xfd\x07w\x00'
		b'\x1e\x0f\x14\xf3\xc5F>\xe9\xf9RVD\x08S\x96W\xc9"\xc7W\xc0\r~&\x0e\x97\xa7\xce8\x99i\xack\xd1\\=#\xaf\xa9\xfd\x10jo\t.\x84)1\xcbw\xbcE\xbfh\x92\r\x8a\x18\x8ejdo\xd7\x95'
		b"\xf8\x0f/\x07\x8cR\xa2'kL\xbaCCE\x17u\xf5+q\xe2\xc5\xfdt \xcd\xb1\xc3\xac\x94t)\x8f\xe8\n\x87\x95u%\xaa\t\x90d\xcb\x0c\x89\x82\xa8W\x14\xa6\x96\xdb\xe05\xb4\xc5t\x82\xc1;\x94\x97h\xec"
		b"\xc1|\xc2\xe5\x0c:`\xc8\xe8\xd6(\xfe\x98'\x89o\xd2\xbe\x0b,\xb3\\\xb8\x9c\x8c`\x11\x07\xd5\xf1\x8d&\x96eC\x85 \xb7\x93\xb9\xcc\x1e\xe9=\xa4\xff\xa57\xad\xff\xad\xf2p<\x1e\x0f__\x7fe9\x94-\x85"
		b'\xef\xd0\x95\x02\x92mZ\x0b\xf4\xa0m\xa4Aa\x05\x9c{\x19\x07\x1dEQ\xcf\x00\x8dR\xccPP\xac\xd2\xbd\x9b\xa0H\x87\x82\xe1\x07\x9coc\xcb\x14A0\x08t0Sl.\x9e\xce\xb1(\x08\xac\xde\xad\xae\x03{o'
		b'\x08\xa9"\xa0\x05\xc5\x80R$\xc9\x82,9\xdc}\xd0:L;vg\xb1\xa9\xfaA\xcf\xd4\xcbX\x13\x8c\xee\xed\xaeZaSb\n\xe4\xf0\xa5X\x819\xa0\x0e\x82\x8a+\x11\xd0w\x89U\xf4v\x11\x93\xdb\x16GS '
		b'd\x08\xb4\xafq\x84W\xf7[\xa6+\x1b\xa9\xee{\xe1\x0c\x8b\x15\xc5\xd5\x1a\xa0\xcd\xe9\x11\xfc8?xG+\xe1\x8aH\x871\x9eQ;w\x80h\xf9\x03\xf9\xad\x9e8\xd5\x00\x04k\xd2\xbe\x0e\x19G\xe3x\t\xa2\xf0`'
		b'C$\x946\xf8b\xc2\x10J\xe5\xba.\xdd.\xb5\x8f\xa5\x19\x0b\x18JCw^\x06U]\xb3\xf7h\xc4\xc6=\xcc\\\x91#\xf6\xe2\xae\x93\xce\t\xadU\xbd\xbeQ\xf5r\x8c\x1c\xba\xe6\x07\x08\xba&\x01u\x0f\xe5\xbf\xcc'
		b"'\xe4\xdb\x18I\xed\xbf\x1dVB]\xdc\x85\x01\x10\x1bna\xbc\xb5\xc2+Q\xda}\xc6\xe1S3\xe9Ha\x92+!)\xcb\xc0%\xbb\x03\xad\xe7\xd2`M\x99\xa6`)\x14\xd1\x99\xa0\xfe\xd8\x87\xe8\xae\x84b\xb1\x95\xca"
		b'\x11\x14\xec0#\xcc\x0b\xd3\x7f\xb7\xf2r\x84p\xf2\xde\xb9SI\x82\xce\xb5H\xc1\xea\xb9\xe1=Y\xc3\xaaZo\xab\tc\xa4i\xe7r\x8bpH\x17:\x0fU\x7fb\xff\xf5\xbf\xcbt\xd04\xe4\xd7\xd9\x83\xad-\xb0\xb2'
		b'\xc2\x80^5\xe7\x8e \xee\xfc\xe5\x8b6\xc7\x08r\xe9\xc1\nc\xba\xb7\xae\xbe6\xc5\xb6Y\x8bI\xffme]\x1bC\xcf\x91\xb1\xf64\xa0\x9a\xa0\xe8\xac\x10\x9aH\xcf6\xa3\xbf\xb3\x1d(\xfe\xe7A=\x7f\xf3\xb7z5'
		b'z\xeb\x88b\x9dHv\x07,\x0b\xa5\x81^\x18\xa7\xe9\xc7\xba\x1ep\x02\xfd\x93\x8b\x1f \xe4\x15sk\x16e\nM\xa3\x80J-\x80\x8e\xa4\x80\x85\x01\xebd\x14\x02j9\x87\x8e\xbb\xa4\\\xa9k\xfd\x1b6\x11\xd0|\xad'
		b'\x0cD\x111\xecB\x02\xb5\x954\xfa\x1a\xc0\xb8\xeao\xc7\xb0\x9d\xbe\xa3\x13\x03\xe2\x08\xd3\xc8`\xc8\xf6Z\x9dV\xa4x\xc0\x84\x051\x92d\x1a\xe5Ai\x19T\\\x90\xd3\x91-\xdd\t\x8334R\xaa\x94\x97\xaf\x81\x08'
		b'0e@9\xa5\x1a\xb3\xf1Z\xc1\xc8\x9ep\xe8"T\x1c\x04\x02\xa6]\x1e\x15\x1c\x82\x81\xd8\xf0\xec\xbd\xc5Q\x84\xa2~\xa3\x14\x95%\x03\xedi\x18G#o\xb3{\x12h:\x8cHc\xb1N\xe2-!\x9b\xd6*\x10\xe2'
		b'x?\xff\xfe\x07\xeei\xb3\x94\xb7\xccAC\x80\xebC:\r=j\xad\x02\x82\x07\x05\xfe"\xbd\x0e\xc6\xce\xed\xec\xe4\xf4\xbd\xfd\xadi:\x86>\x01I\xfb\x96Lo&\x80\x84x\xfed\t]\xa6\xfb`\x07i)\x91\x14'
		b'\x81:4\x0c\xf4\x0c\xc5NT\xd2\x14pi\x82\x88G\xcer\x15h\xc3\xe2-\x14<S\xd34\xd4u\xb9\xf1\x04w\xb35d\xe41u\x91\xefT"\x89i\xae9\x86j\x90\xd6N\xd3\xe8@\xdb`\xb43\x87\x89\x0e\x1e'
		b'\x95z\xc5`%jaO@\x1e\xaa\x82\xd9\xa6\x85\xbfP\x9a>\xbf\xbd\xe9\xc9[\x01s\xa2$\x00\xa6\xd4\xc8 s\x92\xa6?\xb4\x08f\\#A\x94\xcb\xb6\xa4\x9em\xfb\x98I\xef\x83D\xcd\xbe\x85\x90\x16\x95\x99\n^'
		b'L\xd2\xfagA\x9c6\x12\x91\xac)&K1\t~\xc4v\xa9w\x8c@\xef\x90_\xb4\xc2\x0e@\xe6\x7f\x07~!\x84\xb7\x14\x19\x1a|\x16\x87\xa1e\xa7G\xe4r\xee<\\\x1c\xe1\x1d\x7f\x04\x07\x97\x7fgP\x1a\xeb\xc4'
		b'\xd2!h\xa8\x9a[dw\xe0\xcf@4\x00\x99\x18C\xbe,\xc8\x86v\x15\x18`\xb0\xe5\x8c2{y\x92\x8e\x17z\xa3\x969\xae\xaa;]\xcc\xd0*+\xfd\xc81\x90I\x94\xa8z\xd5\x92\xceV\x07\xd0&\x11$J\xae'
		b'p\xd3+\xe0N\x15G\xed\xe8Oxn\x17\xde\x80n\xf0\xcbN\x8d\xe6\xdf\x8d\xf3\xe8HEmWHY\xda\xb0(\x11\xbc\ra\x8b\xd9Q\r\xa1-TM\xef\x05Q\x94}\x82\xcfq\xb6P5\xf9w\xa4\xc8\xcc\x07\xc9'
		b'\xb4\xd5\x11\x82D\xb1\xa9\xedE\x10\x8a\x02j.\xb4\x87Q@\xa8\x8dx\xf9\x95\xa6T\xe9\xa4\xd9!\x00\xa5"\x05\xa8]]\xc2j\x0c\x84\xf6@L\xa4L\xbe\xees\x85N\xcc?,\xa9^VR\xae/\\\xba\x0f\x1fG'
		b'\xdf\xe8,\xb5\x17\xef\xef\xefG;\xdd\xda\x99d\x91\x89U32Y1\xfb\xd493\xed\x87RMI!P\x85\x9aH\xdc\xee\x8c\x87&\xff\x980\xcc$\xf6o\x98C\xb9KvB\x1f\t\xe1\xb1\x9d\xadM)\xa4(3'
		b"\x03C\x81\x1dsiK9\x8a\xaf\x88\x1b\xf0\xa8\xa0\x1e\xa93\x88pU!\xf0\xe9#\x87\x96\xa6\xfb\xdf\xd5'U\xff\xc2\xadb\x83\x11E\xd5\xf5\xc8ua\xddY\x1c\x95&)\x84\xe9;\x9d]\xfc\x88\x8e \tJ\nV"
		b'\xe6\xb7\xe2\x1d\xd2y\x0e\x8c\n=3\x90\xa6\xa2,\xdb\x84\xdb\xb7Y\x87\xf8\x1b\xb7B\x12%\xb9\x02\r\x89\xbe\x94\x90\r\xe9YF\t\x08\x91\xf1\xce\x04g\x10\xc1Y\xb8\xeb"m\xcc\xabr_\x11\xcdN\xe8QY\x83\\'
		b'\xdf\xa8+\xad\xd6\xc0N\x86\x14\xcd\x13t\xe0G\xd0W\xba\xa3\xba\xd7\xb6%\xb4\\\x07n\xc3\x9d\xf5@B\xe1\xf6\x0f!/\xa7\xaf\x96\xc1/\x95\x89\x9ad\xbc\xf6\xb8\x9dk\xe3\xcb\x01pk\x95\xc0\x80\xdf\x19\x80m\xbd\x97'
		b"'\xf5\x15]?:\\+\xf4\xd7\xcf\xc0\xca<\xbfk\xa6\x83{\x05\x00\xd5D\xcd`\xd5\x13,\x9b\x17\xf4\xd3\xfa\xa5%\x9c\xa5\xcf\xbe\xf4l\x99\xbd\x15]C\x14\xcdkQF\xea\xdaT\xcd4`\xa6\xa2\xc72\xa25E"
		b'\x14C\xfa"\x11\x9djF&_EH!\xb6\xe8\x80Ro&_-2\x0f/N[A\xd2l\rV\x0bg\x8b\x8c\xbcI\x91\x8ch\xa5lW-\x1c\xd9\x96P\xd76\x0fU2b\x98\x8e\xabd\xc8\xd5f#s('
		b'\xa5\xc2\xa4}Q\xf6\xcd?4~4\xbf)\xdc\x07\xf9\xe7L3NE\xae\xb8\x87Tw\x14\xc3\xe3*eh\xad\x82~\x80\x9c\xac\xc6v\xc0Hm\xd4\xa9\xdem\x81\x13v\xa9<\xc7!\xa8\xfc\xcb\xf8[\x9a\x02\xa4><'
		b'\x0b\x1d\xfd)/L\xda\x91\xe8"t\x85\xc0S\x16\x9a\xc2\x9c~\x0e\xd0m\tt{\x90\xdde\x0bbaP^2\x9c\x1d\xca\xd4}\x08Km\x1d\xfa\x9c\xce\x90\xc9\xd5\x1d\xb8\xcc\xa7\x06\xa8n\x9a\xf4\xa6o)O\xdd\xa1'
		b'\xe20D\x15\x83\x8b1?\xfd<\x93\xef\xf2\xe3\x8dw\x07\xfc!\xc3\xa7\xf7\x10\xef\xa6\xd8\x0f\xa1ca\xee"\xbd-JBB\xeb\x12\x91\x7f\xd3-\x05\x00hG\x81\xbb\xf0\xf6\xaa\xdaf\xd5\x1d\xb1\xa6$\x16Y/2\xa2'
		b'\xc6\xb2\xd3\xb0.A\xa1\xa0\xdd\xf9FI\xa7\xfb@\xef\xae?\x91\xcb:\xd9d\xd2h\xf7\x81\x1aAo\xc8\xa1\xd4P\xc8\xee\x15S\xa1\x92+\xc7d\xab@\x1c\x10\x80V\xb6\x07\x93\xfb\x12\xd9e\xef\xa5\xf5\x98\xa1z\x03\x94'
		b'6\xf57e\xcb\xd5\xa1w44\xc2\xa9\x16\x88\x16\x99wB\')\xc6S\xc0\xcf\nRt\xa2\xc7!\xf4\xea\xabay+\x8eil&\t\x13T\xbc\x9d\xd3\x01Q\xe80u\'[:\x9a\x93\xfd$%m"\t\xda>'
		b'\xa5\xd1\x98K\xab_\x1b\x81t\x19\xcb\xc6/h\x13#6\x85|\xd5\xb3u\x00\xb8\xa0\xd5x\xcd?#\x0b\t\xa7K\x0b{\xdf\xb2,\x16\x99K!\xfc\xa8\xb8y\xf8\xf9y\xcd\xdf\xf5\xdd\xab\x11\x8d."Q\x06\xed\xef\x18'
		b'\xca\x9dY\x10`\x8a\xc6C\xaa\x92\x00\x01\x065\x1d8\x00\xa5x\x0e\x99\x19pa\xd7\xe9\xf9\xc0\xe0\x00\x14+[\xbb3CKo\x0c\xf1-\xce\xde\x86fSFz\x97>\x07\xf5W\xdd\xa7\x1f\xebW\xdc|\xee\x98\x93\xed'
		b',\xd2VR\xb7bD\xab\xc1\xfda#\x1e\x8fj\xbd\xc0P\xe9\xa9\x9c\xa13\xaaZ\xb4\xea)\xab\x02\xa2\xde\xac\x06\xcf\xfb!\x9c\xaa\x89j\xaa\xd4\xee\xdc\x11D\x12Qk\xc3\xd04\xcb\x1aF\xa4-\x896\xdev\xdc\xe3'
		b'\xed"8Ps\x01u\x14\x04:\xe8\x10\x14\xfdU\\\xa8\xd8\x15_\x18\xb8Gl\x82\\nVR\xe5dg\xbcB2\xbf\xbf\xbf\x0f//\xff\xd3m3C\x17-\x87\x8e\r\x85\xe1e\x01H\x82[S\xed\xfa4\x81\x11'
		b'\x87\xe8\xe8\x13k\xb0\n&\r\x93W\xf1\xda=\x83\xe1e}Ho\xa8\xae\xc4\x9di\x80\xf2\xbd\xdb\x82D\x1a\x12\xba\xa7\x13\x97\xd0>\xd3\xee\xf4\xc3O0\xd7\xbb\xc6\xe5\xfa\xfd\x0cp\xd6y\xed\xd5:\xc4\xda\xd5\xb18\x15'
		b'h\x832\x97\xe6\xb5\xfa"\x84^p\xc5\xba6Jf\xa5\xa4j\x0e\xfd\xee:\xf3%H\xb3G\xfah\xa1Lt\xa3\x965\xd8\xe3\xed\xd4\xd4\x95\x0f\xa3\xbb\xa6j\x81\x17\xac*\x8c\x06"hQ`\x15<\xff!#\xfc\x8d'
		b'/\xbb\x84r\xb6\xd2Y\xf6N\x00\tX\xdfv\x0b#\xd2\xc0>\xa0\xc2\x95)\xc1B8w\tS\xc1V\x81gBt@\xb3m\x12\x00h\xc0\x92\xe3;g;\xa3J\xf0\x1aZ\nE\xae\xea\xa11\x05g\xc1\xa8\x18U'
		b'\xd2\x8cP`\xbcA\x94m\x1e\xbf\xa9\xa3eS\x1fg\xd7\x8fY\xf1\x95{\xd1\xb8#|rL\xb7\x19\xafb\x90\x89\xa8T/w\xf4\x8b\x81|\x9b\xd5\xec8\xb0\x0e1\x93\xa6a\xd2d$>\x90\xc6\x1e,aYOf'
		b'\xa2\xb90X\x9fU\xb2R\x9d\x90\x0f\xb4\xad\xa5\xd1iw\xeb"0R@\x04\xc8|\xf4\x18&\x7f\xe9,\xcb@\x88!\x8db\xc7\x8a\xdd1\xacj5$\xd4\xb5/\xe7@\xcc\x08\r\xd6\xf3\xd8[\x9d\xecv\xe9\xb0C&'
		b"W\x97\xe0<U\x1e\x9f\x9f\x9f{\xe0\xd0jJ^R)\x9d[g\x19\xde \x02\xc1Ih\xe0N\x9fF'\xce}w\x04\xd3\xf4j\xab\x10x\xca\xfc\x0c\x8e\xee(4\xfb\x84\x1b\x12Mf\xefk\x03\xed\x89\xcdH\x04c"
		b'\xbbT*/zv\xb5\x8c\x89|8\x99\x14\xec\xe4\x82\x02\x00\xaa\r\xabqT@\xd9\xf3\xec\x08\x19\xf1\xde\x8eQ\xc3\xdf\xd2\x95\xc7ZW\xdd\xc4&\xb0\xeb\xe8\\\xcbr]~\xa1\x90&\xd7\xaf8\xaawQm_\x9fC'
		b'Yf\x12#(h\xadO\x8b\x89\xf7\xaf\xf0\xa4p+\x156\xbe\x89]\xd8\xde\xcb=1K5W \n\xda\xda\x01\x84\xae"\xfa\xe3\xd6w3\xc9]\'\xd3N\x17m\xcf\xd0t\xcfan\x94\xb3\xc0n\xfc\x1b\x0f\xa6\\'
		b'\x10\xb6\x0e\xd1Y\xa5T\xf7\xaa\x03v^\xf0\x9f\x9fWN\x16\xa3\xb0P\xb8\x85&\x03\n\xca(\xd8\xaa\xb6\xa7\xcc$m\xc8\x04\x1agD\xb5*\xdbP\xf0`\xf260{\x1b\xde:Lf\x19p\x14\xc6q\xef\xf0\x8b\xb6'
		b'\xd95\x90\x8aP\x89\xe9\xf4c\x02\xf7R\x1a\xe42\xff\xa6\xed\xe2Z"\xd5\xad\xd4\xa9`\xc3/\xf6\xef\x8e,!\x13D\xf6L\xc0E\xd8\xea\x9e\x03\x01\xbb\t\x95>\xac\xb3\x1e\xc2E\x08z\xfe^j\xf5U/\x08N\xcb'
		b"\xd0\x86\xf2\xe94R\xa8B*\xd1\x0e\x01\x87\x85\xae\x96D\x16\xdd\x15[_ \xb6\xd2V\xb8\xad\xbe\x91E\xee\x97\x14-T#\x0f\xd3'\xa8%\xe3\xde\xd3\xbfQ!\xe9F\x99\xfcpQ\xa2q\xa5q\xa1\xdb)\xb1\xbdj"
		b'\xe3\x1f\x81\x1f\xf4L#\xa0{\xbe\xf6\t\x1c\xb9HKKV\xfd\xb9\x05\xb2\xe2\x12\xd3,\x10\x1dz\xae\xdc=\xd6\xc1\x8d\xe0\xb1\x90\x82\xcd\xd3\xe5\r\x0e\xc3\x9f\xcbZ\xe8\x0c\xee\x82\xe9v\x0b3\x81n\xc1\x8e\xdca\xa2N'
		b'-n\x97\\\xb5\xae\xefUZ\xf7"\n1\xc2\xb0v=\x17A\xe6\xa9\xb5\xd2Aab\x08U,\xd9\xedV\x00)\xcc\xc1\x88A\xd7\x1d\xf7\xf0{a\x1ez\xb9\x04\xa0\xda\xb9\xe1B\xfd\xa3dQ\x94\xa2\x1d9\xec\x0b\xfb'
		b"rG\x18\xd2]\xeb\xd9Hi,\xd0n$&<~\xc5wC\xae\x05*\xd7\xf3\x8f2e\x8b\x16\xa4K\x98\x07\x9b\x067\x99\xe6$r\xbeIS\xdd\xea\xde1\xdb\xdf\n`!T\xce\xf3'\x99lD\xee\xbb\xbb\xbb\xc3"
		b'\xf7\xf7KoN\xec\rb\xca1\xe5\xd0\xe5\x8ey^rT\xa6\xf1t\x92\xcc\x17\xe3\xd9CQ\xb52\xec(\x1a%\xfd\xcc3\x811\x1a;\xf2\x90\x1d\x0e\xd0\x8d\n#\xd3\x82H9\x90I\x16\xc0\x18\xa7\x03\x93\xed\xd4f'
		b"\xf5\r\x98\xf0\xe7\xb0m\xc4X\x1d\xa8\x90M:+\x8c\xd0\xa6\xda\xb21\x878\xa4+:\xb4T\x92\x86\x07d\x16\x9d8\xfe\x1f\x99\x13\xc7\xb1\xfff/\x96\x8d\xbcx\xcb\xce7\x01bJv\xf5'\x01$\x10\x1ct\x8e\x83"
		b'\\Y\xa2\xd2n,#h\xa1\x97";@\xdc\x01\xc4\x84(e\xfa\x96y\xf1e\xcc0\x12T?%\x97\xd5\x18g\x07\xd1\x0e\x8c3\x83\xd9\xd0\x860\xf2\xcc\xfd\xa7Xj\xd8\xd4\r}\x01\x81\n\x03l\xc2\x17\xb3m\xc6'
		b'\tw/\xad\xd9*\x8f\x87+\xda\x01$S)\xb4\x10x\xe4\xca\x8a1\x02\xcc\xf0\xc0\xec\xcf\xdb\xe6p\xda\x9e\xde\xd6t^\xe7C\x9f1U\x0cP\x15\xa5\x86 dx\x97Y\xd9D\xba\xa1\x84\xe2\x1c\x97du\x81(\xc7'
		b"\xe8\xee\x93\x87\xfd\xb7\x8e1\x14\x05f\x17I\x84_\x82\xfd\xb5\xf1]B\xab\x81(\x85\xd6a\xd8\xc7\xf6W\x94\x1e\xe0\xd6\x8b\x16<\x12\x1f%'!\x96\x88.\x91ik\xcbt\x8d\t\xde*\x90\xed\x8c\xa6\x82\x13\x93E\xc9"
		b'\x90\xe4$U\xd3%\x1eRd\xf3cv\x8c\xe4\x02\xc7\xc2$\x14\x04\x99\x83\x89[\xca&t\xabt\x1d\xb0\xad\x86\x91\x82V\xcf]\\//\xff\x83\x10\x9a\xcb\x8e\x06CCag\x06c\xedm{\xc4\xc2pmFW\\'
		b'\x9f\x14e\x1e&DO\xaa\xe3+ \xa1\x97\x94\x97\xe7\xdc5Xt\x97\x90\x07\x8d&\xef&\xa3Uu^i,\x9b0.\x19\x00QS\xb15\xee\xb7\x1cqE3%\x1e\x04N8@\xc2A\xb8\xd0;\x90\x18\xcfl\xd5'
		b'\xbe\xd0\xf88}\x86\n\xfbU\xa4\xb1y\x85V\xe3\xfc\xdeJA\x19\xeb\xa2N\xbfbo\xebX\xe8v\xa9\x07\x91\xae!G\xa5)L*\x89\xcf\x87y\x01\xd4W\x11/\x02\xec\xc6*H\xd1\xdc\x05n\x8aB\xf1\xc0i\x12'
		b'\xa7l)\x9bW\xca\xc0`gp\xdd\x01\x16\x99\x8a\x07\xa4\xa1\xb5Z\x04\xc9\x80&\x1d\xa4\x87\xbf\xfe\xfa\x87DJ\xc0-1\xd0\xdf\xb4\xe4\x05\xf1L\xe6P\xf2\x80\t\xd3-$\xabV< \xedP\xda4\x94r\x07\xab\xb4'
		b'\xe8Z:\x00\x9d\x1ce\x17\xb5Q\xef\x10\x8c\xfc\xaf\x86k(ux\xf9N1\x92\xe3v.i\nqG@\xc6\x12\xfd>D~\xe9\xb0\xfa\xf3\x1e,L\x06\xba\x85>\x90!`\xe1\x948\xc0\x9a\xd6\x10\xb2\xac\xef\xdb\xcf'
		b'\xec%\xd9\x1e\x9a\xad=-\xa0L\xba\x0bW\xa7\xab\xdb\xfdY\xa9\x0e\x02\xc0\xa4&i\xb0\x8aFz$\xdd\xf4\x80\xdd\x9c\x98h\xd9\xd9\xa0K%e\x17\xc5Q\xeccV:rf\xd5\xb0&\x050\x9c|WHc\xa0b'
		b'\x08\x99\x8f\xc2\xe6~\x9e.-\xa1\xd2\x1e8\xe3^\x1a\xa0a\xfa\x88\xc0\xbc\x93g\x88\x87\xc5\x15\xd9\xc1\r\x19B\xc14\xd8\x0ene0\x93\xf1x\xad\x82\x8e\r\xa1\x88\xc9\x05\xfa\xac\xbb\xeb\x12|\x86\xd95H\xd6<\x03'
		b'C\xdc\xaa\xb3\xb8\xb1\x81\xf4\x9d\xde\x04\xacMn`\xa2}\x8e\xce\x1d\x96*](\xdc\x03\x1f\x91\xba\xd4\x19\xea\xce!c\x08\xb7\x8d\xdb\x83\x86\xc56\xb7z}\x05\xfc\xc4+c;/\x9f\nmdGT\xf5< \x8a\x0c'
		b'\x9eHw\xeb$\xf80\x05l\xdb\x87\xdec\xa3\xa3\xee\x848\x8c\xf7\x16\xb9S\xf5\xf6\xf6\x16\xd4+\xc0\x0b\x84\xa04\x8cn-|-.E(\xde\n\xb4\xb8`\xfbBq\x0bb\xa9\xd8\xd6\xaa\x1c\xb8\xc9///\xc6\x92'
		b'\xb3\xa7-\xaf8\x87\xe2j\xa4\xa3\xfe\x96\xf6\xfa\xdb\xdb\xdb\xe1\xe5\xe5\x7fz s\xec\x18\x03 \x17\xaa\x82n\xee\xae\xf8\x8aOa\xec\xe4\x8b\x93hF\xc6D:\xd7\xea\x81\xfb\x80\x14\xb4\xed&@+\xba\xea\xadc\xa7\xb9'
		b'\xdb\xa5\xa5}E\\\x8c\xb4\xa0\x82Xd\xaf\xd8\x87\x14\xce\x8d\xe6L\xba\x00\x16K\x11\xb7\x13\x83\xda.\x06(\xb1\xd6R\x03\x9b\xd24\xdc\x06\x94P\x02\n1X\xdbH\x95\x10\x055m\xdd\xb4\x0cL\xf0\x95\xea\x14\x8b\xb7'
		b'\xf8$\xb8\t\xa1.f\xa5sHb\xa64\xd1\xf5 \xf7)\x9e\x94\x8c)\xb2\xe2n)\x83\xa8\xc1\x11Ih\xaf5!\x99\xa0\x01\xb8$o\xaa\x9aA\xc2uG\xd4\x19\xad\xa9G\x99>\x9f4O\x874*1c\xa4X'
		b"\xde\xd5\xda\xd1-\xb6\xd5Q9'l\x9d\x06xmDj]\x8b\xdd\x18#\xb9w\xb2][\xd5\xe5\x81\x99h`5\xd97\xcbA\xa8LF\xbb1\xe5\xb6\x9f\xaaV\xec\xf8\xf5\xe8\xe9\xbd\xc6\x0eq1\xde\x82\xcc\xe0\xe2\xa7"
		b'\xa4\x83I\x9dIm[q\x83z\xf0&:|\x91\xd5N\xa7S\xa6N\ru\x05p\x8c\xef\xc5H\x15^\xb7JQ\t\x83\x01\xdcm\x92\xa6\xb8\xa8\xd0C\xba/\x12\xd3\xdaq\x1c\x17n]/\x8eg\xf6\xb7\xc0\x99\xad\xd3'
		b'\xe7Xvv\xa5\xe7\xb9h\xac\x83\x1d\xdb\x08\x98\xa9\xb3\x9e\xcf_\xf6\x11R\x89T\xb0k\xa0K\xab\x04\xda\xb9\xcc\n \xa8mQO\x94!\x15\xbe\xf9\xfb\x1f\xcd\xd0\x99\x15\xd1\x9d\xb2f\x86\x0c~\x03@\xeb\x86\x902\xc1'
		b'\xc3o\xa9\xff\xcc\x83\xc9=\xa1}S\xecP\xf1nW\x88\x07\n\x9d\x95\xb8\xd5\xb1I^v\x8f\xf5\xd4\xd2\xb7\x92\x91l{;\x05I\xa1\xb3\xd6O\x90\xe2**w\xef5sA\xfa\xb2(\xddu\x13\xbe\x88\xcd\x93k\x95'
		b'\x04\x83#}\x14E\xba\xe7\xe7\xe7\xfcF\x1fU}\xadUN\xd0\x9c.\xf4\xd2\x9ei\xb6\xad\x1av=~\x86\x08:"U%\xc3\x88$\xf1\xac\x9a\x8eMq\xf0\xce\xba\xcb\xa6\x8aq\xa5\x01=\xe4\x8e2\xa0\xfeR#\x01'
		b"\x9c\x03\xe3\xab\x1b\x9b)\xd4\xd1J\xb0u;l\xba6\xdb\x8a\x80t\xd8'\x03s1\xa8Q\x0f\nQT\xd9\x8d_\xd9\x81\x1d\xd5\xfb\xc0b]'\x04A\xe7\xbb\xf7m\xb34\x9c\xc83\t\xf4\x1b\xbb\xddE\xed]\x14a"
		b'\xd3\x9a\xfcW\x86\x010B\xa8Q\xf5\x11VS\xb2B|ok\x870W\xd5\x8a\x1d\x1a\xa0\x19\xa0\x80o\xd3\x97\xfe\xdcd\x1e\x83\xa9\rJh\x95\xed\x19\xba,b\xa6\xec\xaa\xf0\xc0|\xcbl\x1b\r0\xb8\xcaEI\x1f'
		b"\xb7\x91|\r\xba\x81:FL2\xa1\xad\xde\x1d\x91\x06\x89\xdf\x8e\x1d\xe7\xe9\xactF\xe5\x82\xdb6\x94\rZ\xb1-\x8f\xa1\x91H\xd9\xa1\xdb\x08\x11\x87\xc9\x92\x96\x81YK1\xa1\xa8\x94'\xa1\x02J\x1f}\xf8\x922V"
		b";H\x93\xa0\x12UIg\x9f\x8c1\xda\xed\xc5\x11\x0c\xe3\xeaxP\xfb\x93\xba\xe0\xe4\xea\r\xd2\xd5I\xe5\t'\x92\x86\x1f1s|\xb3\xc5\x7fLV\x05\x8bqD\xc4>\xe4<g\xa9I\xd0\x01j\x94\x02^\xa6\xae8"
		b"\x0c\xe0\xaa9\xcb\xe2\xe2\x15V\xe3\x84\x96.\x19S\x15\x96 sw\x17\x06\xda\xba\xb7\x852B\x93\xaaZ\xac\x0c\x0cbiv\x02'\x14\x18\xba=1\xdb\x9c\xa1\x9f\xfa\xfb\xef\x7f\xb6\x95\xbb\xdd\xea\xc7\xa8\xd3\x98\x12\x80@"
		b'\xdb\x7f\xf06\xa8\xeaEt}W@\xf8\x8e\xe8\xda\xe1m\x1b#-\xa1\x7fYb\x9a\n\x04\x9f+-\xec-H\x8bvDJ\xb8\x95oW\x83\xac\xc2\xfc\x06W`1\x13\xa5\xea\xd6\xcfrU\xa3h\x01\xdb\x1aN\xa9\x8e'
		b'~e\x1c\x05D\xc3\xba3d\xb4\xd20\x05\x95\x81+\xe2\n\x02\x0b\xcd7\t\x81\xe5\x17\x92\xf1\xd2+\xd7\x80\xf4\xda\x03\xb4\xd4\x86\xb0\xd4y\xa3:V\xbf\x94\x9aC\xf6\xba\x82\xc6\x9f\xa0\xaa\x92\x87\xd6R\xb5\xad\x15\r\x17'
		b'\xa2\xa9\xd4\xaah\x00\xad5\xfe\x80\xf3H\x0c\xf5>\xb00E\xdc\xda\xc3\xf3\xb3\xc5\xb24\x0e\xd4\\ BR\xf6\r\x07\xf3\xdd\x869\x93\x19U\x1er\xaaz\x1d\xb5\x88\xfe\x1b\x05\x05\xcc\x95k\x92B)\x8e\xf6\xa8\x84D'
		b'\r\xf0\xe3\xf1T\xaf\xf9\x19T\xd3\xbeQ\xf6\xc9l\xe3\xf1\x17\xd5\xf4\xd8\xbd\x17$\xfb\xf7\xef\x7f2x\xe8O0\x90M+kO\x15\x10.\x16\xd4N\x99*\xdb\xfe\xee@h\t\xb1\x84\x92\xb4#\xed\x13h,U/C'
		b'\x9a\xc158\xe1\xcbe(\xca\x12e\x08\xdb\xfa@30\xf1\xe4(\xd5*\xddH\x9f:\x06;\x0e\xb5$\x18z\xde\x85\t\x052\x94\r\xe3\xd0\x89\xfd3q\xa9\x7fP,3]\xb6\xa7\xe3k\x99\x08y\x07\x86, \xb0'
		b'w\x9a\x1c\x88\xd2\x92|@\xcbXXro\x82\xce\x89d\x12\x0e\xbb\x8d\x04\x19\xd1\x16\xb7C\xbff\x8fT\xe0\x16\xb0\xe0\xd3D\xe3\xc0\x97u\xeb\x03\xd7I$\x11\xcc\xab/\xb4\xfe@\xee\x88\xa5t\xc8\x1ck\xa1\xb9\x9a\x17'
		b"\xf5\xd2.<\x8e\x97(Y\t\x02\xb2\xbc\x87U\xdf`G\xcd\x81&\x02'\x0bTC\x90\xc9\x10\x90\xc3\xdd ZaNL\xc6\xb8\xc4\xdat\xbd\xfe\x97\x08\x88\xd8\xcc0\xbf\x1d\xd5h\xb2K\x1f\xd8K!\x9c\xc3E\x84O"
		b'\xc23\xa4N~[\xf1\xb4\xc3\xd6r\x15\x1d\xf16\x81\xa18\xbc\x9a4\xbaQ\xd4\x12D\xe9\xe0r\xc97\xb5\x98\xfb\xfb\xfb\xc3\xe9\xf4\x9f\xc5%\x10-\x96\t\xe8\xb5\x05\x91\xa2\xde\xcc\x1e\x08\xd5\xf8xQ\xac\x12\xa0\x89w'
		b'\xab{\xe1\x85\x05\x12\x9d\x8f\x15v\xb5\xb2\xec1\x8a\x88\x00T\xe1\t,CM\x12\xa3\xb3O\xe6\xd3\xa1\x01\xe0&\n\xd8Z\x0ev\x88\xf4\xb6\rd\xc9\x94G\xb6\xec\x0fx\x015\xe2\xdb\xac\x9c\x13hU\x16d\x04\xea\xea'
		b'\xf5\xa2vg\x0b\xaa(\xf7\xbe\x1a\xd8\r`\xc6}0u\x81\xbc\xa1\x8e\x1c\x05\x01J\x02\xfa\tI\xe3\xb8\xba\x0c\x07\xbd\xb7\xce\xa2\x00\x81\xcaS\xcf\xa6V\xad\xbe\x8e\xcb \x1b\xcev@\xb1\xed)\x19<\t\xa1\x95A:'
		b'jm\xe3\x1duJW\x8d]\x7f\xf0N\x8c\xa5\xa4}\x81}\x05`\\]]\x1d~\x7f\xdfZ/\xba\xdb\xf6iy2\xc1y\x02;m1]\x18\\\x8b\xca\xab\x86\xd8 \x8ec\x05\xd3\x0c\xec\xbb\x10(\x90\x9f\x8a\x83w'
		b'v\x8b\xb3%7\x10\\:\xebZ\xd8\xd8u\xd51\x85\x08P`\x11\x94\xe2\x14\xed^\x95v\x1cI<d\xca\xb8\x19\xb3H\x07\x0b\xc8\x88\xce{)<\x0e\xe5\x0b\xa1\x85\xd2\xafy0y\xb3\xb6\xb0C,#$\x17\xae\xe9'
		b'$\xbcH\xd1\x1a$\xedM\xa5\x86N\t%\x01\xc9\xc6\x85\x1cw\x16\x07\x9d\xc9\x00\x15\xf3\xc9\xa5\xef\xa4\xa7]{\x1eFfBqq\xe5e\xd1u\xdb_\x8a\xae\x10$$\xb9\xce\xba\xec\xb9%\xed\xd9\xb6G\x07\x1b\xd7\x11'
		b'B\xadGN\xe3\x8d\xc5\x93!:E}gz\x92\xc3Wx]\x99S[\r\xb6}1\xfd\x8e\x95\x16+w\x9e\xb2\xca\xd1\x13\xcaP\x01;\xfa\xf3e9$\x0c\xa0L\xb6G3\xd7R\xb80\x7fz\x80\xb2\x94\x9c&\xf1'
		b'F\x90s\x97\x8d\xa0\xb9tJz\xdd\xdd\xee\xd7\x85\x95=m\xf5\x81\xb2v(\xb5\x1aV\xde|\xa7Uc\xb9\xa81\x9b\xedn\xca\xefNqD\x0c\xc6<\xeb\x84\x81>\xc8\x84\xf4oXS\xdf\x92\xf1[2b[\xe3\xbf'
		b'[\x87\x1d*\x83\xb6\x80\xf3Ka\x12\xe3\xc8\xdd\xa6,\r2_>\x95r\xcdv\x9c@~\x05`(\xf1\xcb\xde5\xb7\x86*\tb\x084\xc2Xh\xdb]\x82\x9e\xd5\xb7/\x01-\xc0V\xf97\xa7]\xd4*x&!'
		b'*_\xaf\x95\xe5l\x1f\x81\xebf9\x15\xf73*\xdd\xa1\xaeG\x98\xaby\x8c\n\xa5\x10w(,\x8b\xc8\xcf\nrL\xce\xb1\x13\xa8u\xfd\xae\x19\x13\x12\t,|l<\x1dUE\x02\xca\xe9I\tp\xd9j\xc6\x9e\xad'
		b'j(#\xa1\xf6g\x94"ySrk\xea\xa3:EM\xef[\xe0\x8fc\x14\xf0\xac8\x05l\x8d\x1c\x88y\xa3;\xaeOR\x91\x95b\xe3\xbb\xc6A\x05\xf5\xe2\xa9\xf4A\x81\x9a\x1c\x83\x93\xcb\x12\x9b\xafj2\xac!\x93'
		b'Z\xdd{\x129\xb7\xb6zunG\xcd\xf1uU\xb2\xee\xdds\xad\xee$0v\xe8r)i1$g\xa2\xc0\xaf\xc6\x1c\xb3\xbf\xd8\x01\xd52c\x1a\x98K>\x90vK\x075\x94\xc5\x10o$\x86\xfai\t\xc2U\x95'
		b'\x0b\xa1>\xc3\x9d&0\xaf\xbeH{@"\x0e\xd4\x00\xf0A\xe1(*@\x96\x02z \x90$\xfb!\x1cd\x90jA\xcco\x10m$H\x06?\xc5\x02\xd2R\xc4\xbd\x1a\x96\xa1\xa1{\xe5\xfc\x05QpU\x9d1\x9ar'
		b'4\xc1u.\xb5\xc9\xea\x80#\xf7\tq\x02d\x15l\xb4\xd9B87\xc4\xc4R%X\xb6\xa0X\x19\xa3\xae\x070|mu\xff\x96}\x18X\xa7\xa3Z\x98\xb1\xc3\x19\x82\xb9\xe8\xdd\x9b\x17\x8f\xac\xef\xec\xaeV\x9c3A'
		b'\x89\xad\xea{\xbfH\xb2E^\x9e\x0b"\x1a\xe9\xd8\xad,\\\xda$\xccA{D\x0b\x193\x80\x0e\xf1F\x1f\xcc\x8d\xac\x06\xaa\xab\xceMq:\x84&\xb3\xabe~\xd3k\xfaN \x93n\xf2\xf5\xf5\xf5\xd1\xe0\x1a\x19\x1e'
		b"o\xd2r\x00:\xfb\xa0\x1d\x14E'\x95l\x96J\xad\xb9\x0f\xfa\xaf\xe1\x00<rK\x10_@W\xdb\x02v\x1a\x7f\xbaH5\x92\x16K\x08\xdb\x84\xec;\xe7\x94\xfaR\x9f\x99\xe1$\x9a)\xb8\\\xbe\x97\x9d&\x95\xd5\xa5"
		b'\xcd\x8a\xa8z\xf6\x00\xba\xf8\xd4 Q\x00h/+\xb2\xb6\xbb\xfd\x87\xeeo\x1cO@\xaa\xe0\x017N\xa6[\xaa\xd7\x1e\x17\x10\xefX\xbe\x1eu]\xab\xbc\xa5C\xb0#\xca\x91\x9f\xf1\x849+\xd5\x89\xc0\x8f\xc2!zr'
		b"\xa8J\xea\x89+\xdf\x821\x8a\xf9\x07P.@ \x02\xa9\xbe\x0b\x0bR4\xa8\x10\xd1\xd9\xab.\xd1\x93\xa0\xcav\x03\x9f\x9f\x9f\x93\x0f\xa35dfk\xc7)\x0eK\xf1' \x0e\x12\xa0Q\xf8\xac\x13~\xf5\x7f\xff\x11\x82"
		b"\x07\xb3H;\x02\x92:p\xb2:\x99.\xf86\xcf@\xde\xde\x08 ww\xe1^~\\\xd2\xd6\xc6\x04;f;\x8b\xe42\xcf\xaa\x80\xba\x99h\xf7\x1a\xeb\xa00N@n%\xc8\xa9z\xf1\xbc}ly^',\xff\xbb"
		b"\xf4\x81mF[\x9c\xbb\xe3\xd8\xa5\xa2\x0f'\xfc\x85\xd5t\xdc\x8b\xeb\x9c?\xf1+\xf2\x15\xb6\x96i=\xfa\x8c\x9d\xb6\xec\x08\xf3\x8f\x9a\xc5\xf2e_:F\x91V\x18f\x13;\xab\xe8\xd3hRz\xef+\x9e\x9e\x9e\x8a\xeb"
		b'\x02vd\x11\x9dQ\xe5\x9a:\xf2x\xec\xd5\xb86^I\xe6\xad(\xcezj\x8d\xdax\x9b\xd6A\x0bk|\xfcN\xa8\x8f\x8b\xba\x1ax\x85\x0f\xaa\x84dr\x94\x84\x05\x11[\x8et\xf1\x8e(\x81\x0eh\xb1\xe6VL\xb5'
		b'S`\xe0\x10\x90\x8b-C\x84\xac\xf44\x87\x8e\xcbJ{TCP+X:\xdb\x87\xc3\xa7\x10?\xe8\x03k\xabgw\x15\xc9!\x86\x9b\xc8.#\xcds\xa2+\xba\x1b\xdc7\xcc\xc7\xa4D\x1dw\x84\xa5\xa4\x95\x99\r\xdd'
		b'X\x1bu\x08\xc6\xb2\xcdn\x1d]N\x8d\xc8\x01e\x85\xe9H\x8b\xb1\xfd\xfav\xc5oDH0\x8b\xec\\\xc7\x96\xc5_\xb9\xc5\xecT\xab\xed\x17[\xc9\xd5\xed\xc2\xeb\xee\x87+\xb7\xc3|\xe4\x8e\xfa0Qnc\xfb\xe6\x8b'
		b'H?\x98\xea\xb7m\xd3\x14H;\x9dDKi\xd0\x17\x1d\xf5\x87\x12\x15\x9d\xbe\x08\x8e`_\x97j\x0b\xb5E\x07\xc0\xa2\x1e\x18?o\x05\x02q.\xcf\x8a\xc8\xba\x07\r\xca5l5\x93\xc3q\xe0\xab)\x0b4;\x082'
		b'\xcd\x03\x90F\x14\xbfj\x02Z\xf58\x0e]\x03\xd7\xf3\xf3s\x911\xa1V\x19\xb6\xb7]!\r\xf9\xaea\xc8\x1a\xc6w6\x9e\xee\xfbU#3\x1b\xb4\x93\xa4\\\xa5\x8e\x96[h\x8f\x05\t\xfd\x95\x9fiA[\xa2\xfcR'
		b'\x8f\x87\xd3j\xfa\xf4\x0e\xca\xc5\xdf\xcek\xb3\xa9\x05\x1b\xab\xd8l\xdc\x1d\x8c\x12\xb2\xbe\xf2A\xed\x1d\x1c\xa9\xb8Y\xe3\x0bm\x8e\xf0\x80\xe5n\x18\xef\xde\xd3\xe6\xed\x8d\xd0\xc4\xff\xc3\xedE\xe3\x15/`_S\xce\x92\x93('
		b'\xdd\xb0\xb8\xaa\x07E\x10-\x82\xb2L\xabD\x8d\xe6\xf9\xf99<\x07\xef\x86#\xcdl\x19\xdb\xec\x13z\x1e\xea\x86"\x8b\x1d\xbe\xa6\xbb\xe0\\6\xeeL\x10L$\x8c^\x14\x85\xdf"\xe1P2\x04D"8\xf0!]\x95'
		b'Uh\xd9\x16\r}[\xac\x14\x8aX\x1f\x1e\x98]n-\x94\xa7\xc1\x84U\xeb@\xf7u\xdb\r\x84\xd6\xb6C\x9fL]\xa7l\x97a\xd6{j\xd7uZ)\xfd\x84Z`\x82\xec\xf6\x130\x14\x17\xb5\xaf.p\nE\x94'
		b'=+\xac\xeaL0\xd1\x96\xbf\xfeoI<\xbdQ:\x181S.4FM\xaa]\xcc\xb7s\xac.y\x017ES%+V,. Q\xf9"\xe2I\x81\xb0KH\xf9\x980\x04\xa09\x03J\xe0^\xaf\xdc2\x9a'
		b'\x96&mD\x83.\xa5~\x1d\xa7\xd8\xa8/\xdc\xd8\xec\x0b=\x81le\xc2\x19\xbc\x8d\x84S\xa1\x1d\x06s:\x9d\x0e??\xaf\xea\x17\x06\x92\x81\x9f[&S7\xd0\x0f\xf3\x0f\x17\x88\xbbnn\x1dq\x12\xd9m\xb6\\\x11'
		b'\x94\x1c\x19\xc1\x05\x1d9+*\xc1\x1e4H\x10\xda\xd8\xf6\xf8\xea\xaa-\xda\xc1\x94\xf0\x96\x8e\x02QU\xc9&\xc6\xed\x17C\xc7\xd6\x92]\xccH\xd5x\x01\x02\xd7\x93I\xeaPC=n\xcf*\x1a\x00\xda\xa1\xc6\xc0~\xec'
		b'b\xb7\x14\xfb\xb2\xac\xae@\x8b\x12\x11\xb0h\x05\xa8\r#\x14"\n#\x8d\x15\xb4\xb3\xec\xa8b(\xf6h\xcbR]RBb\xdd\x10+\x80\x19+\xbf\xde\xde\xf5\xbf\xa4~\xe0K\xc2\xf4\x0e\xb4PB;\xae\xfc\x81\nm'
		b';\xe5\x04\xba\xe7\xca\xe1\xad9\x8f\x8a\x8e\xe9X\xaegS\x168\xa3ozj\xdaf\xe8uWV\x99I\x92\x80\xca&\x06\xb0\xee}q\xacF\xd3\xdee\xdf\x9d`\xa9\x85\te\xc5!Z\xef\x08-\xf6\x87\x99\xa8\xbc\x07'
		b'Br/V\xba"\x05$mTT\x8a\x0e\x00\x0c\xc5BC\xf3\xd6\xca(\x81)\x8e\xef\xdd\x03C\xb4\x80\x00:\xabB\xd0\x18\xfb\xfe\xfe\xb6\x85\xbe\x9dI\xa3_d8\x978\x8a\xe0\x05q\x1b+\xd9\xbd\xd5\xf9\x0e\xdd\xa3'
		b'\xb1\xa5U\xbf\xbb\xa1\x15]\x1d\x94+\xc8\xa0\x08\xfd\x8d\x08\xd9\xf1\x9b\xf4\xee\xc1\xca\xf8\x0b\xe4\x1e\x88J\x94V\xc1\xf8\x13\xa8\xa3\x05$%\x83<.G\x8d\xdc\x13(\xa5\xda0a\xcc\xa5\xac]tu"\x80\xd1\x0b\xa3\x8a'
		b'\xde\xcf\x10-w1\xd0\x90\xa1\xfch\xc5g\x10\\\xe1\xa9\xbd\x01\x0bZ\xdf\x15\x81\xa29\xbe\xad*\xc1\x0b\x05U%\x9a\x11P\xbbH+\x89\x01\xda\xd7r@7J\xc3\x97&5\xc0\x1c*A\xc7(\x9ew\xd7\x9aD\x14'
		b"\xc1\x0c'\xec\xfd\xfd=\x88\xa0J\xa7\xa2\xc4\xca\x8f\x19\x19\xb4\x9a\x87\xe1'\xa63I\xce8n\xa1\xea\x99S\xfe\xf7\xb0\x13\x0e-\xd4\x123b\xc7A\xef\xc8\x02\x0c\xa5\xcd\xea\xa4O$'\xe8:\xee\xa8B\x9a\x8d;\xea"
		b'\xafc\xa4\xd1\x14\xb7\x05G=\x7f\x82\x87\xcb\xd0\xea_\x05\xb5\x19\x00\xd5\x0f4[W,\x01\xff\x8d\x10\xd9\xc5c\x11\x1c\xc1f@\xb5\x1adk34=\xb3\x14k)tzKt\xa2v\x94\xcd,m\x01)\xcd\xb8\x99'
		b'07\xb1\x96\xb3G7%b/%\xa5\x1bS\xb9\xb9x\x15\\-\x95K%\xdf\x99\xf4+}\xac\\\xa2\xa6\xb6b\xa8\xee\x12\xeb\xbb\xc2\xe2\xe4k\xec\xb4:C\xec\x17\x89\x0b\xc9\x80RCe\x1d\xfc[\x88\x81y\xac,'
		b"\x16\xab\xc0:V\x83\xeb\x84\xad\xfaC\x1ey\x99\x8f\xfc\xa6\xb4'\x03\xd1\x9f\x08\xb7\\\xa1 \x08\xaa\xf9\x15k\x85d\xb0\x1d\x90\x97?)\xab)\xeaX\xaf\xabofo;e.\xf6\xd2TL\n\x0b\xd0\xde\xe2U\x99X"
		b"\x17\x12\xda\xb1\xf3/\xd4\xc2{\xb6\xed\x01\x12\x1fn\xcbNn-2\x9c\x18R\x8dB\r\x1ez\xd1\xfd\xc1\xba/\x82\x10D\xc5\xb3B=\xd4%\xec\x9e@\xa86\xbe\xe7\xd6\x96\xfd\xaac\xdb`_\xe0u'\xfc\xe9\xe9\xe9"
		b'\xcc\xe5\xda\xae_]\x1d=\x19\xe1\xc4\x1d\x00\x8a@\x9a\x7f0=\x9b~\x1d\xf4\xc68\x16\x13\xd7\xb4{\x07W\xeb_\xe9o\xe1\x18\xe4\x15\xe4m\x85\x95\xe4\xfb(\x14Yn\xdeL+\x9d9\xdb+\xa1\xd5=D@\xe8\xba'
		b'f\xc6\x14\xeaq\x12IORp\x80Nv\xfd\x1cb=1e\xbd;\x81\x8f\x90\xe0\xca\x97\xa3\x06\xca\x82pc\xb4l\xeb\x1bF\xfc\xee\xcce8\xa5\x13\xc1\xa6X\xca`\x99rw)\xe9NTp=\xf0\xf2\xe3\x04\xe4'
		b'+\xcc\xe3\xd9\xf9|\x17g`\x95\xc3\r\x96\x8cB\x06\xe1\xc5\xaa(\xf1\xc0\xdb\xe9\xb0\x86\x07\x98\xe3\xe2\xd5\x96HFE+\xc7\xe5L\x13\xd8\x14\xaa\xf5T49\x0b{zA!\t\x0eUs\xfb\xcer\xf6\xf9\xdc\xe2\xf5'
		b'\x15\x0f\xebq7h\t\x99\t\xfbW\xe12}\xb6\xe2\xb9\x8dq#5\xe9b\x98\x92gy~~\xaez\xa5k\x9e\x07\xe8 v\x8b\xb2|\\J\x97\x8d\xf0\xc9\xaa\x88\xb1\xee\xd8\xa0\x06\xd3g\xd4\xb5w!\xf3\t\xcf\x14'
		b"S\xdaH\xf4'\x00\x94y\xa0\x84\xb1r\x17\x19\xfb~2d\x9dp\x1d\xe6\x8c^\xb36FiI\xeb\x05\xc5\x00#b\xa9mS\xd7qt*\xfeoH\xe6\xc0\x99\xa2\x80\x04\xc1Z!\xd6K\xcaQM\xb7\xd0\x06\xa5Y"
		b'\xf9\x1d\xb3Cw\xfd\xf1\x7f\xb2\x83!]\x06+\xe1;\xeeh\x96N\x17V\xc5\x85\x9cT\xcd_\xab\x06K\x0c\x02\xc5(g\xb25x\xd351\\\x00\x83\xa6\x99\xf4TE:^\xf9\xf6\xf6\xf6\xf0\xfd\xfdR,\xa1\x9f\xd5'
		b'\x81\xdeac\xf4Hh\xffB\x03\t\xbfI4\x97"\x9fO\x04D"6\xaa\t\x90\x1b!\xbbW`\r\xca0\xc2\x11Y\xda,\xaabq\xed/\x19\x00\x12\xfb\x99m\xa3nR\rp\xbe\xb3+\x90\x16\xf6\x98\xa4\x87J'
		b'\x96\\P3\xcaZA\x9aM\xa2 \xe9\x04yj}\xe5\x02\xb6\xd6\xb3h\xb0\x92s\r\xcd\xa8\x07\xd9\xce\xed3\x06\x16\xd1=\xa6\xe4n\x9c\x16"m\xebL\xae\xb9\x80\xa7\xf3t\xa1\xe8\x86C\x8fY\xd8q1p\r\xe4'
		b'\x00\xad&\x07\x84\xc5IZ\xa1\xc8\xad\x04\xcfT\x98\xa5\xe5u\x94\x15CM=\xd4*\x00\x0b"\x85\x12r\xe5J\xe8\x1f\xa2 \xe4\xb2\x99\t\xd0y0\x85\xd6P\x1cv\xaa\x0fy{{;\xbc\xbd\xfd\xb3sC\xec\xce\x9c'
		b'\xc6\xde\x16W^\xad\x04\xaf\x9a\xe8\xdf\xea\x9b\x1b2\xde\xfa\xb6\x97 \xad\x04\xb0\x10\xc2H\xf4\xb8<\x98\xa4\x17\xf1wwo\x87\xc1\xe8\x85\xefa\xc81\xa0\xc5\x8b\x89W\xa9\x8a^\xacx\x14\xebS\xa8\x83\xec\xc5\x15PN'
		b'\x16\xb9\x1a(DW8\x93\x9c\x99P\xab\xc2\x7f\xa6\x83@\x95\x12O}\x89\xab\xcc<e\xc5U\xf9]\x8e~\xf9=K\x91\xf3\xb18\xf5\xaf1\xba\x9d\x9en\xbbF\x1c\xed\x1ce\x08\xc2T\xab\xaaP\xe30,\x01.&'
		b"\xe6\x86\x94TP\xb1H\x96\x84\xb3\x8a\xf0R\xdem\x81\xearv\x14\xf5H\x01\xdf\xc4\x02\xb4>-\x0bB\x17\xc1\x01\xfa\t\xfd\nA\x80\x0e\x00\x19\xdd\xb3*\x04W\xab\xd0\x18$\xa7'\xda\x11\x0c\nP\x10\xc1\x8d\xa1\xdf"
		b'\x8bO\xb6\xa3d\xbb3\xc8\x9e\xd8\xffT\x8b\xbb\xe2&L\xb2\xdf\xcb\xeb\n\xcc\xa9\x02\x8f\x8f$z\xeeXt\xe3A\x1f\xbd\xb3\xa1\xd9\xeaG\xd4\xa3\xac\xc5\xea\x0e\xc81p\xd6\x89\xec\xf6\x9c\xad\xacYcy\x1er\x8b\x9c'
		b"\xbb\xb2.\x81\xeb\xce\x84\xb6@\xea\xbfd\xccZ\xb1\xfe7\xc3Om\t\xe5\x01'\x17\x18Z@\x15u\x07O60\xb7d\xac\x13\xa3\xf2\xbd\xf3u\xb68\xdd\xafCf9\x1cC\x01\x89+\xaeo\xacJ\x9d[v\x04]"
		b'\xf2\x9d\xe9\xc6\xb6\x9a\xb7 \ta\xda\x85\xa0\xf4\xc0\x01\xf6\x86\x07\xb7\x86*\xf7|\xddj\xa9\xf7\x9c\xfa*\x15\xbf\xa4\x8e \xd1#8b\xbb\xa4\xff[\xcd\x81\xca.\xbf\x1f\x7fC\xfb\x8b\xf4.4\xb0\xe8\x99\xf44\x0ck'
		b'\t\xd5\xa4\x9b\xe5\xfe \x08\x89\xe3\xb2\xf3\t}\xf2\x15\n\x99hg\x05\x03]\x92\xe2l\xf1=%\x05=D"\x93\xcd\xae\x8cI,\xb9,RR\xc2\xeb\x96b\x92P\x14\x84\\\xe5^\x1e\x1e\x1e\xe8\xb6n\xc0\xad\\\xd5'
		b'\x13Vp\xd8g#1V<\x8d\x85\x81;\xd9\xf5\xcb@\xf0r4\xb3\n2\xfb\xf6,\x97FJK\xaa\xf1R)\xaaN\xa6~};x\xc0h\x18\xff2]\xa3\xad!\x95\xa68J\xc3\xaa=u@We\xa3?\xdc'
		b"Z\xbe\xa8l\xfb'\xdd\x1f\xe3\xfa\xf4\x8b\x97i\x94p\xd3\xfau\xb15v.\xed\xa2\x03\\\xa1\x897>\x9dN\x87\xd7\xd7\xff\x15\xc2\x16%#\xafo\xf3\xb5\xb2(\xbf\x86\x8a\xbd\xe2\x1f\xd9-X\n\x19qz\x86\xf0Z"
		b'l\xd3Na\xf5\xac^U\x0e\xa7a\xdf\xc4\xb5\x96#K/j\x12`l\x87\xf1*\xf8\xd1\x9e0\xe6\x91)e\x95\xfb+\x8d\xde\xd4r\xcc\xa10\r\xd7\x80*:\xc6\x9d\x0f\x1a\xd7\x17\xc2;\x1a\x08\xff\x8f\xe1\xf9\xbb\x03'
		b'\t\xb5\x16\xa8\xa7Z\xe9\x8c\xb6\xe50e\x06oi\xa7\xd5\x92\x04\xeb\xc6\x9a\xec\xb4?c\xe2{5\x8dA\x14\xd2A\xc9\xc4\x93=\xea\xf6\xc4\x01\x88\x0c\x04\x01\xa4bA\x97\xa7\xd1\xcb\xe8\xa0\x1b\nZ\x1e,\xa3-\xb2`'
		b'|W^\x7fq\x1b6\xd1\x1c\x06^z\xaf\x99\x0c\xc1[\xf0`\xd4\x1a\xff\xcc\xadaHZ\x8e\xd4\x8e\xba\xe2\x08\x0b\x84\xa3\xb53W\x19\xd1\xe8\x00\xc6\x062\x94\xd2\xa9\x13\x19TV\xbc.\xfb\t \xeb\x81T\t(\x8e'
		b'\xb0U\x17\xa0S\xf7\x81em\xdd3\xd5\xa8\x01r;GY\x99P\xb39\xb9\xbb\xc5y\xcdn\xf1\x87-\x1c\x9c!\xfc\xc1\x9e-\x0e\xc65\x891 \xb3b\xdf\xbc\x07\xc9\xc7|c_\x84\xec\xd4\x7fh\x95\xc2\x8d!\xa9'
		b'\xb4J*\x1a\x0c\xa8\xb7kp3\t\xbc\xb3\xd2\xf9\xc8O"#\xe5\xe82\xf3\xa0}\x1bM\x14\x9a\x8eP\x8fJ\\\x03\xba%v\xca!\x88\xa6V\x0ct\xc5\x91\x14\xa1M0 \xb8R\xd3\xba{\x8bO\xba7\n5X'
		b'\xbf\xa5\xfec\n\xd2\xfd0\x9eX<j\xf4\xf53\xcb]\xcc\x9dAM\xe6\x04\x90\xd7\n\x96\x83g\x81\x8a\x98\x01\x97v\xaek\xd0\xf96FI\x0e\x9ai\x11\x85\x93X;\x8b\x13\xfd\x1d\x1d\xeeH{\xba\x93\xddL\xc1\x95'
		b"\x99\x1a='VI\x9f\x93\x9f)<\xc3]\x83^\xefXR\x8d5\x06W\xe6\xbb\xb05\x19H\x85B#SK\x8d:vnW\x16+L\x19i\xa2\xe5\x06\x93\xaf\xf4\xb1\xda\x9f\xf0\x14\xf5\x17\x01\xbbS\x92_\xad\x0bD"
		b'R\x04\x12\x95\x9a\xaf\xbc\xa1\xf9(\x10\x05\x83\x8f\x8c\x02\xf19,7i\x99V\x15\x04\xacr\xdc\x9aow\xd5\xc5a\xa8g\x80\xd4lk\x0b\xfd\x83\xf6j%\xad\x14\x80h\xb4dxs\x1d\x834([\x15\xc3D\xc2\xa3'
		b"'\xb9-\x1f\xe0A\x1d\xccE\xd7\xb0l|\x8a\xd3\xe9t8\x9d\xfe\x03\xd9\x0c\xc3\xe9\xe9%\x88*A\xea\x0b\x94\x1a:p\xe5@\xc6o\x18\x88\xb0\xe2\xac|\xfa\x0e\xfa\xe3\xa3\xc1\xff\xb4\x04a\x8e ^\xb9\x1d\xe9\x1e\xf6"
		b"o'\xf5\xae\xce\x1e\xd6\x94i\xd8\x85\x07\xe2E\xe3?\xe4\xa6\xbc\x01\xa7w\xe1\xd35\xf5k\x1b\xd2\xde\xae\xd1\xb1\xf0T\x1c\xa2\xc2\x80l#\x00]5?l\x9f\x16*j\xfa\x92\x850\x19iK\x01m\xb6\xffk\xdb\xbb"
		b'2\x87*\x06\xfePy>\xf7\xae\xa7V!b\xe7\xd8\xe6\n\xacjw\x1ec\x87e\xec\xd6]\x88\xd2-g\xa9t\xb9\x8f\x92\r\xd3\x8c\xe8|Gr\xa1\x01\xb3\x83)\x18\xd3\x95~\xd1m\xdc%\xcc\x7f\xae{\x04\xa7\xe6'
		b'Ut\x17*\x92\x1eEx\xc0f\xb3r\xc9\xd1 \x90\xc1\x10`\xffX\xc1\x9a57\x02\x06u\xe9B\xc8@f\xdb\xe4g\xd9\xb0\xec=\xe2{I\xeaf\x96f}J\xdd\xb8\xe3\xcc\x9e\xd9R\x18\x04\x00\xd0\x0e\\:\x17'
		b'\xa8\xed\xe1\x86\x0b\x17\x12g\xa5f\xb7\xe8\x10\x1bY\r[\xf5\x1a\xc7\xa9\x94\xa6[\xe78b\xd5\x83\x95\x85j\x18\xc8\xd8/*h\xea\x06\xab\x02R$\x86\x91\xdf[T{.)\xda\x00\xdd\xa8\xd9(\xfe\xdd\xd2\x1d\xa3]'
		b'\x08A\xb1\xb0(\xe0\xf5\xf5\xb5\xf6nZT\x9c\x0c\x8d\x96<$\xdb\x84\x14\x84\xebV@\xdb\t\xbe\x08\xcdw\xc0\x0c\x01\xe6\xee\xbc1\xb8,\xbdC\xdc3+\xd8e\x8ft\xe4\xc1\x1e\xcd\x7f\xa7q$M\xa2 \xaf\xb2t'
		b'\xf6\x00\x1f\x1f\xff\xee\xf5\xfa#\xd9g\xa7S\xd9\x08\xff\x04M\x92\xb0=H\xae{\x89\x02\xdd\x87\xe0\xb1\xad=\xd06\xba\x9eK;\x9c>\x18W\x96\xc027\xb5s\xad\x8b%\x88\xdfw8\x14\xa7\x05r\xfa\xd9\xf1\xa2\xc1'
		b'\x82J\xeb2\xce\x96\x89EY\xfe\x85Z\xac\x06<\\q!G\xce\x9d\x9al\x87\x00s\x9d[\xc7=\xc1)0\x06T\xafI\x81\x96\x1e\xf0~78\x98\x19B|\xc7U&\xa6\xd9R\x87+\xec\xb1vo\xb7L!\xf3'
		b'\xee\xe4\xb58;{]\t\xd2\xcd!\x1e\x85\x16\x19D\x91-C\x08-L\xe8\x14\xe1\xa5:\x9d\x8b\r 6\xe7\x19\xd0\xc0\x10+y\x83M\xde\xb6^$\xf5\xdf\xfa\t\xc9\x19\x1a\xcfg\xcc\x9d\xfe\xe1NM\x8c\xb7\x1d\\'
		b'\xb5\xd0,\xdf\x8a\xc8\xa9:SrC\xb4\x99L\x85bD\xb1\xcdE#6\xae\x0e\xc2s\xec\xd6\x8e\x0b\xa0\x9dl\x06\x16\xe4\n\xa9\x9b\x84\xae!\x08\x9f\xb1k\xa9\t\x98V\xe6E\xc7\xadQ\xe9YY\xd9mk\x82\xdbL'
		b'\x8ate73Q\x80vZt\x9a\xb0\n\xa0{\xbc\x18\xe0&\xa3,\xe1\xcc\xc5@\x04\xa8I\x9c\xac\x06=\x1cM\xc0\xfd\xae\x8e9t\xe8N\xcf*Dt\x99!9\xeaw\xab\xe5O9\xa2\x9bI\xddcE~\xe2h'
		b'h\xa2\xd2\x1d\x8a\x1bW2\xc6\x80jt^\x99x2\x1c\xdd+]\x1fj\xd5\xda\xf6\xcd\xd40\x12\x017y\xc5\xc3!\xa7\x08Q\xd8\xaf\xb83@$\xb9\xca\x1f\x19"\x80\t\x87[\xbd6x\x81R\xf6\xf6\xa4\n\x90\xc0'
		b'IT|\xb3\xdcd\tu\xfa\x88\xbc\x97Y\xa5\xa3\x00g\xab\x1b\x88]\xc3\xce\xa96\x18.\xbd\xb3\xaej\xbc\xca\xa4\x15\x0f \xf3H\xa0{l\xc3\xdb\x8aL\xda-\xa7P\x9fQ\x973\xe7S\x81\xbd5\x11.o` '
		b'\x92\xe9\xa5V\x1e\x10&\xdbq\x0f\x1e \xb5\xb4\x1d\xcf\xaa\xbc(\x00t\x90\x10~"P\xc9A\xbb\x18\xc0{\x06K\x9b9\xb3\x97\xc9Pj5\xf9s1J\x85H\x13\xbb\xd8\xb2v\xaaM,\x17\x97\xd5\xa8\x08\to"'
		b'\xea!\xe2\x9b)O/P\xa9~\xab\xcbA\xfb\x18\xd1\x9aR\xa4LbE]D^\xa1\xe5r\x92\xeb!\xd1H@-\xd8\x1cD\xdc\xf8\x87\x87\x87\xc3\xeb\xeb\xffvV(\xf7\xbb\xa6@\t\x91e\xa9dfRP\xab\xe2'
		b'\xa3\xe7]N\xa6\x84\xceW\xe4(\xec\xbd\xfe\t\xde3\xdf\xddc\xec\xc4\x17+n\xe4#,h\xd9\xbf:h\x056\x86\xd0l\xb7\x11Po\x07\xfd\xb6\xf7D\xf1\x0bEvp\xe2\xbefG\\|\x82\xfe`\x0e\x8f\x10\x1c'
		b'M\x08\x8e^@\x12\xac\x1c\x11\x8b\x02i\xdf\xd8\xbfQ-\x96Tgd\xe0\x7f+\x89\x1b\xb3%M\xd4\xff)4\xa7\xce\xc0m\xe6\x0c\xc5{]\xa4\x98\x11E\x0b\xabR\xb1\x01\xb0\xf2E\xdc\x87\x85\xef\xa0=\xab\x9e\xa6Z'
		b'\x07\x1d\x82\xeaB\xa8\xba-\x0bo\x08\x96\xf4\xd0\x19&\xbb\xb3F\x1d9\xe2\xaa\x8a\x00\x00+\xdc\x8d\xff\x03L\xfd\xfe\xbe\xc9\xde\xdai.^\xf8\x85EhS\x95T\xe9\xd7\xb5\xb5\xa0w{\xe3\x90\x89\xc3\x14_\xfaC\xc3'
		b"\xfd\xe8\x96\x81\xa4\xfa\x9c\x85\xf3\x96\x9aan$\xe8c\x8b\xde+\x92\x83o\xd7C*\x99\x81\xb6@O\xb8\xef\xd0\x1e\x11'\x9aM\x8f\xa4\x8a\xbc\xa3\xb5\xf0R\xcd\x06\x87\x93\x98\xbe\xaa\xe2\x06\x15X\xe66\xd5\x9a\xd5\x15\x86"
		b'\x111\x9frD@\xd3\xca*\x12\xf1DI\xd0\xa1\xbf\xdd\x98\x04\x8fW\xbf\xdf\x1dC\xc8\xcd\x1e\xe5\x1f\x0cDd\x8c\xe9B3v\xaa\xb6}#\xb4G\x1a\xe6T\xd8\xe5\x85\xf2\xf4\x00\x11c\xce\x9cY\x8a\xfc\x927\xdd\xc1'
		b'\x1c\x0c\x01\xfa\t\x08\x9808\x83\x9e\xa1<\xc3\xf3}\x9c\xe6\x1a\x1atx\xf0\x05\xc1\xfd\xf02\xef\xb1_\xb4\xcb\\\xb4rj5\xd4\xa8\x0f\x00\xa1\xa9\xa60\xae\xf6\x01\x7f\xc4^\xc4H!\xe6\x11\x1b\xcc$\xcd\x05\x1c,'
		b'G\x07+\x8e\xb80=\x9b\x84;m\x9e\x8d\xc2\xbb&Z\x8eN_\x98\xb4d\xa9D\xaa{\xad\xb2\xf1\x98z\xfc\x85\x91\xc6\xe8j\x1b\xd2\xcc\x89\xd4\x00\xbc\x83\xe5\x15\xe1p>\xf0l\x01\x12\x9f.&6H\x8a~\x8e\x86'
		b'\x8a\\D|\x07\xda\xa9\x1a\x94\xb5\xc50R+\x1d\xcb\xac\xf6\xf0\x9a\xef,\x8eJ\x13r\xb2v<VI\x9b\xff\x8eu\xe9\xa5t\x15\x87\xd5\xaa\xc9R\xf5\xa2\xf0\xa7\x82^mJ%\xa7\xb5\xca\x88\xf4\x9a\xab\x04\xd1Cb'
		b'\x9e\xbe\xbc\xbc\x9c1\xc4\x9e\xac\xac\xc5\x15\xcc\xea\xdc\xdf\xdf\x97\xd7\xab\x988\x16\xd2S-\xd5\x85R\xd5\xde\xca\xc0z\xe8\xb8(\xfe{\xcd\xf6\x0ez\xd8LEU\xdcPT\xb1{g\xa5\xb6\x84B\xd2\x12wj"\x84\t'
		b"\xcc_\xe9b/'\x076\x8a\x05\xa90\x045\xcfit\x94\x8dJ75\x1e\xd1\xedBU\x01\xc1NC\x82\xc3\xd4\x1d\x90\xfbR\xcc\xc5\xae\xf5\xf9<'\x11L4\x04\\&\xc5\xa6\xf6\x1ezc\x82`\x0fL\xca\xbb\xb7"
		b'VT*\xb1\xc1X\xb6\x95\xd85X\xc0xb\n\xbd\xa5md\x94\xa8\xb2v\xf7\xec\x82\xbeJ\xf5\r\x03\xe6P\xd9\x84\x97\xaa{\x9d\xa2,r\'G\x0f$\xae[>\xa1U"\x1d \xd0\xc7\xd2_\xd4\x18\x10\xa2\xc9\xf6'
		b'\xfe\xfe\xfe<\xd3fu\xbc,4\xa5oQ\x94y\x190\x1fD\xae\xa5\x03\x04\xaa\x04\x03\xaf\xea\xbaE4\x9f9K\xe9X\xf0\xfe\xb8\xa2z\x15\xda\xf5"\x87\x90\xcd\x97\x97\x17y[\xa8\xb3>\xd1u\x7f}\xe6\xb6\xbc\xed'
		b'p\xf3\xed%U\xa2\xfe\xef\x02V\xabi\x03\xd4q\xb3\x0b\x9e\x19?\xe7\x82~\xa3\xdc\xab\xe0 1P\xde>\x0f-<\x1eY\xcd\xbe\xce\xd0\xbb~8`m!\xd7n\x1d\xcc[\x00S\xe6\r\x11\x06\xc7!8U2\x87'
		b"\x0b\xd3\xeb5\x1e\xc1\x18IYG'{G{\x18\xbe\xc6\xbf\xa9\xeegq\xa2\xa0\x01Q@\xc6\xe8\xa2\xa5(\x95\xdb\xe8\x8e\xf9\xdb\xd2\x920\\\xad\x8b\n\xa9;\xef\x88\xff7M\x95X\xad\x91+\xda\xcd=\xd5\xe1\xed\xed"
		b'\x9f\xe6H\xf6\xe9\xc8\xd3\x94\x03\x893\n\xfe\n1\x8d\xbf\xbd\xd0\x9b\xef\x1d\xda\xa4\x12\xbe<Z/`r(\xeaAV\xd3+\xd9]\xe2"Q\x08\r\xd8Q},\x13\x15\x7f\xaf~\xbf\xea\x81pVC\x02\x04F)\x80'
		b'\xf2T\x9f\xa6\xcc\x99\x03l\x83\xbb\x90\x1b- \xd2i6 \x93FG\xa3\xb5\x02\t\x08\xd5xQ\xf5\x9d\x95\x045\xfa`Qc\x12\xbc;\x87\\k\x8e\xceF\x8a2mv4\xdb\x0c\x8d\xb2n\x8b\xa98u\x91E\xac'
		b' \xb3\xe9L\x1bq\xe9\xec#\xa4Is\x92\x01\xa5\xffc\x18f\x10\x16\xde\x07*$\x8e\xbd\x8f\x15\xdb\xa0\xee\xd2v\xef\x1e\xd2v^q\r"T\xadL\xeb\x0fW@\xbdF\xec\xed\x0c\x1f\xa1\xe0\x9a*\x88\xc0!\xf4e'
		b'\xad+1,\xadW\xf6\xad\x0c\xac>ZL\x16\x8cP\xc2\x81\xcbeB\x91x\x86\xb1\x0f\xe5E\r\xd2\x8bh\\\xb8S\x82\x88\xa1\xf4\xcdX\xd2\xb4\xa1\xcc#\xcd\xd5\xa2\x8a\xa1TO\x8f\xd8\x14\xcb\x03\x8d\xaf\xba\x04\\\x1c'
		b'w\x00\x07\x8e\x19\xce\xa6"\xea\x15\xc2\xb1\x85-Q\x9e\xca\x8c0kK\xb5\x1c[)`W\x0fxkb4A\x16G \nW0B\xa1\xd3\xd9\x1e\x93\x12\xc23\x95Z\xac\xe2\x15nE\xc7\xd1$\\l\x0bj\t\xe0'
		b'l\x90\x94t\xb1\xcfQb\xefz\xb8\xb7\x05\x05d\xb04B\xd0B\xc4\xfe*\xb8]M.\xfc<\x023\xe8\x92I\x9dv,\x0b=\xfa\xdfDf\xf5\x0f\xbc\xbe\xbe\xf6\xeb\xa8\xca\xe7\xf2Y\x1b\x9c=n\xc5)R\xec\xf6'
		b'\xb0j\xa5\n\xe7\x86\xe2\xbf?\x0e\xc5@?G\xf6`\xb5D\xbb$t\xd2e9\x1dJ\xa7v\xa5\xcet\xb2\xd1\'E\x17\xc1\x81A\x82w"\x8bj\xfa\xe7,\x03\xff7\x93\xb6\x0c\t\x90_0V\xd0\xa9\xa2\x07\x81\xd9'
		b'\xd1f*n\xea8z\x94wh)\xda\xb3\x0eCr)\x00"~F\x94\xc5\xa6\xc8\xaa\x8b\xd4Ehp\xeb.U\xb7\x08\x92\xdb+\xe7\xc4\x10..&\x17\xea\xd84}1\xeb\xb8e\xd4\xbe\xbd\xd5`G\xe5i\x1d8'
		b'\x8a\xe7\xce7\x9a\xfe\x86\x12[\xc9b\x9bL\xa9\xcam\x86\x95\xed\x85\xec\xc3\x13(\x10\x8c\xb8\x93\xb9M\x93+\xb6\xdc\xb136M\x06\xea\xc4\xaf\x0e\xe4\xc2\xcdg\xb0\xe1\xeb\xeb/\xd5l\x1a\x9a\x1c+\xaa\x06P\x8c\xbc#'
		b't\xc8\x0c%L\x15I\xa4d\x0ec{\x07t\xc2\x04\x1dt\x82\xa1-\xab\x88\xb3\x87\xee+\xb6\x89\xac\xff\x05_\xc8Ox@\xc8\xa6\xb2\x7fVD\xc3\x84\xccU(\x05-5\xc6\xc7\xad.\xed\xcb\xc3P\xd1R\xe9\x13\xd8'
		b't\xc4y\x15\x85g\xdcl\x90\xab\xe1\x1a\xd2zS\xbb\xd5F\x0cE\\*\xbc\xf6Kr@\x9cCfKf\xd2\xc2*i-\xbc\x8b\x9f\xab\xd6\xb6C\xcd\x8c`\xd1\xb6K\xc1\x13s\xd6\xf4\xd8>\x01\xd2\xdd9k\x83\xf0'
		b'&M\xd7\xdd.Y\xad\xa1x\xbb\x86\xc2KE\x98f\xc8\xe9\x0eKw,\x8bj0\x7fU\xc1\xfa"\xfcN&\xe0\xdc\xda\xe6\xf6\x93C\xa3\xe3\xc0\x84\xd0\xac\xc4\xce\xdb)]\xd2\xd0\x9eo\xb3r\x13\x87\x8c\xa1U\xe2\xd1'
		b">\xe6\xcauM=\x8f.D\xa7_\x95\x1eR\t\x92rIh\xcb\xd8\x9eNaM\x83\xadx\xecn\xd7;\xdb\x8f\xd2'Lj\xd5\xca\xc0\x08\x10\xb5Pv\xe8\xa2\x89\x1b\x9c*\xe6A\x1a3p`\x89\xf2\xfaN1\x0b"
		b'@\xe9xD\xa6\xa6\xf6<\xa6\x0b\xf3\xa8+\x12\xd8y\xa5\xa1\x890\xc7~c\xcb\x82\xceP\x0c\x0c\x12\xd5\x88,\x80\xd6\xe9\x11\xa8\x9f\xf5AF\x00\xedo\xbf<4\x96J~\x11\x8e\xea\x90F\xb3~\xacb\xad\xfc\xea\xeb'
		b'\xeb+W\xaf\xa8\xc7\xd1Q\xd7*\x88\xed\xe0\x19>\xa0r\xbc\x8b\xbc\x95\x96\x1d\x07\xfa\xf3\xf3s\xf8\xfe~\xe1\x1f\x17\x1d\xabF\xa8\xc9C\xcb\x95\xac\xc8\x8c\x16\xf9\x04\xa8\xb4[\x0b\xad\xdb\xf9Y\n{\xdbx\x9a\x01\xd3\xdb'
		b'\xd5B\xc0\x7f:\xd0F\x13(dj2\x92\x80R\x07\xc8\xc3\xaa\xce,\x01\xf3B\x0f\xb0K\xd5\xcd\xa4\xff\x85!\xbb?@1\x01\xde\xaf\xceU\xda]2\x87\xb3@\xf2_*b\xfb\xc5o]6Rg\x84\x89z#R'
		b'K;\xd8t\xe7\xd6\xa8d\xa9H,\xa3[#2,\x15\xd2\x00\x08\x06u(\xc3\x19\xbcIk\x83\xf3\x8f9"&\x84r\xe2\xa2c\xc4\xe4f\xf5\x00\xd00\xc4f\x03\x1e\xe8c\xc4\x93\xa3\x84\xe7\xc4\xf7\x93\x0c?|\x1c'
		b'\x99\xd2m\xd7t\x85Q\xcf\x1e\xb5\xd4\xb0\r\xad\x14\x87\x9f\x9f\xd7\x9d\x1b\x0c\xc1\xc0\xa3,H\xa0\xfb)\x0e\xd6\xf0\x8f|+\xea\xf5\xe7pR\x82\xfd\xb8\x0c\x19\x12\x9a\x02fH\xe9%@\xc8\xd9\x12\xe3\x8e_\xd6\xcek\x90'
		b'\xb2\xa4|ui\xb4\x16\x90\xa6\x01\xe19\xd9\x84\xce\xb3\xe8\xa0t5lza\xb2\xba\xe5\xc3.\xe7Gw\x81&\xb2\xacC\xf7Y\xc1A\x0bo\x1e\xccl,4\x04`\xc8\xd2H\xb9>\xed\xf3\x9dN\xd3eV\xdc\xae\xeb'
		b'\xa4\xad\x99:\x01\xda\xf0\x86:\xdd\xc0\xfe\x1cSc3\xb1\xd5\xca\x97\xb3\xd2\xd9\xddA\xaby\x98\x14\xddr 2@\xe1\nH\xc3\x19\xc5VZ5oGK\x05W\x00\xacm5\x87\x83A\x84\xf9\xbc40\xc3l2\xb2'
		b'\xf8`???\x87\xbf\xfe\xfa\x87~V\xc1\x994\xb4\xa8\x94\xceV\x0b\x84e\x9a\xd1*\xe55\xe4c]\xad\x18\x97\xb2\x08\x1b\xafe\t\xcfYQVw\xdf\xa2\x13$1\x90=\x8c\xd6\xd3 \x8b\xba\xd7\xbf1\xc3\x18\x8c'
		b'\x08m\x86\x15\xd0ZT\x99r\xcfu\xeea\xef\xad.\x10\xe1:\x81\xfeV\x12.\xe2\x07\xdc\x95\xe5\x0e\xd6Ol\xc6\xf8^!x+W\x03\x96\xc9-\xab`(\xd7H\xc0\xd8\xc8\x9c\x1e\xf4#\xfat\xf76\xb2\x8d\xc3\xba'
		b'\xd3\x80\xf0\x14\xa3\xcd\xf4\x1f\x85\xe0"\xb7\xec\xc8N\xdf\xc8 \x92\xea7\xfd\xc9\xeb\x03\xb81Y$\x81\x1d\x98\xe2\x85\xce7\xa9\xcc\xad\xaehiZ4\xd9t\x05j\xaa\xb9\x8bl\r\x8c(\xcb\x98q7\xbd\xae\xe3\'\x8b'
		b'\xfbSM\xe8}V\xbc@@\xbfCf nFXo\xdbW\xff\x11lWT\xd0\x7f\x93\\m_\xb5\x01\xac\x9c\x16\x01\xb0\xa2^\xa4\x17\xfc\x02-\x1d\xdb+L<\x0b\xf4[\xb6@\xaa(\xf0N)\x1a\x0e\xa0\xb1\x86'
		b'4\x9f\xb8Y\xea\xb6-\xad\x1d\xa3z\x82)\n#G\xa9\xd2k\x82!z\xc5\x16\x80\xf3*umP\xee\xfa\xb5\xb6\xd6\xc1`\xa8f\x16V\x93NEP\xa2\x0f\xc3u\x97\xda\xe9\xc4l4\x97\xadfzEn\xc2\x02\xd9'
		b"\xcb\xa0\xc3\x92lT\x00\x1c-c\xaew$\x94X\x91a.\x14\xb1A\xd88\xfai\x91\x14\x99\x95BS\xc4\x10\xeen'\xd4\xb3\xaa\xed\xe6\x9e\xccp\xf6*\xa1%\x96\x89\x83\xb4z\xeah\xdaw\x8aZ\x8f\xc7\xe3\xe1\xe3"
		b"\xe3\xdf\xe2\xf2v1\xeb\xcbL\xd6~\xd1\xedA'\x92\x98\xab0\x1b\x9d\x00\x98c\x0b\x11\xbcV\xd9k\xe7To\xaam\xfc:Mz\x88\xd3\x0e}\x10\x11\xd1YQ\xac\xa1h\t[\xa4a\xb6\xdc\xac%!\xe2\xb1\xe1Y"
		b'0\xc0\xab\t\x97\xd5\xb1%\xa6(p;\xd9\x0eT\xb0>\x96\xe8\x18\xbd\xadB\x14\x0c"\xc9\\\xe7\xd5lto\xd7\x9f0+\xa0\xe7\xceP\xfbE\x05\xdf\x8c\t\x94[\xf1\xa1\xc1\x1f;\xe4\x0f\x1e\xb0\x93F\t_o\xcf'
		b'\x93B\n\xf9\x93d\xed\xc4\x0b^gqE\xec\xee\xfc\x9e[jj\xf2J\xca\x82XJ9Lw&Pb\xf8\x97`{=\xb34\xd7\xf0ve;3\x1fp.\xcex\xcf\xef\xfc\xe3\x0e ?\xe6C\xb5u\x8bw\x89'
		b'B\xcbf\xcc\xeb5Q\x07\xe3\xa2#\x85u\xc7\xdb\xf6-\xe1\xf4\xdb\xbc\xd3w\x19g\tf\x06F\xd5\x1b\xe0\xba\xba\xd9\xd9ZwL?\x17\x8d\tA\x08\xabV\xc2\xa4\xdb\x15\x04l5$m\xbcm\x7f\x18\x12\xbas\xaf'
		b'(\xf8y` i\xcdPj\xfe\xc8\x05[\xd0\xc5\xf3)\xc2)\xd8\xeb\x88H\xe8W\xd5\x83"\x13\xb6\x05\xb6\xf9\xaa1\x0b?*\x90u\xed\xb1\xf4\xbc\x8b\n.B\x7f\x16\xcd\xf5P\xac]$\xda5\xd0\xbfF(M<'
		b'\x16\xb2\xb2}\xa1X\xeejy;\x81\xb8gS\xc0Q\xd1\xe3\xf7\x1cN\xafc\xf8Z\x8bL\xce\x1e\xeb\xd6<_\xe8\x9c\x8c\xfc\xf0\xf2\xf2?e\xf7]\xd3\xcc\x06=\xa9NR\xe9\x81\xf9\xda\xa2 \xbd\x85t\x02\xa1\x93\x9a'
		b'\x9bv\xf4\x08}\x11\xe5w}U+X\xe7Fe`J\xc8\x80EH\xb3b!FZ\x89\x84\xbdt8\xe8\xcf\x14\xb4\xe4g\xe0T\x19\xf5"\x8a\x95\xd4\xc5\\\x97\xd4\xa2\xe6S\x11e~\xb8G\xech?\xa0\xa1\xa9X'
		b'\\T\xa0_\x8c`\xf0N\xaa\xcdr_()\xb8\xc6\xa5w\x98^\xba\xda\xcd\xcf4nl\xb5\x89\xd0\n)\xa5b\xb9[+D=s\xe6\x14\x9b\x88%\xee\xf8d\xd3WU\t[^\xa6Z\xa6+\xddG\xf7`\x0b\xda'
		b'\x97\x9eV\xad\xd0w\t\xc81\x9fA\x1d\xf0\x1bU\x028\xecR\xca5\n+\xcb\x9c\xb3p\xcd\x9a\xe6\x1a\xd9\xc2Z\xabl\x18rb\xb6\xb6s\x89\x9c\xd4=)\xac\\\xe6\xb7RK!\xa0\xca\xee\xce\x0b\n\x81a\x81`'
		b'a\xe6\x94\xc0\xadh\xb2\x92=s\xa7\xab\x87g\x81\xd0Z"\xcaf\x9fj<P\xdb\xf7\x93\xddLj\x92\xd9\xc2*\x00\x00\xbe\x9d\x9b\x8d\x19\x15\x16\x8eQ\xac\x1dl\x1b\x055\xb2m?\xbe\xba\x98\xb6\xb8^<\xfec\x1e'
		b'2\xe7\xa6e\xa4w1ql\xa7\xde\xae\x167\xdd>\xd5k\xe3Kd ;\x15\xb7tv\xc5\xdc\x15n\r<5\x12\xc1\x182v\x9a\x90?)\x07l\x00\x11|\x0ep\xc5N\x04\x05m=_Tz \x90\xebg\xf2'
		b'\xf0\x86\xd8\xee\xacoa*\xe2co\xcd\xdfRW`8\xf0\xa8\x0ba\xee\xef\xef\x0f\xef\xef\xff\xda1\x8f\xb9\x02\xa4\xe8\xe2\x1f\x92i[\xfd\xce\x1e\xf7q\xfd\x16\xed\xaa\xf5h\xba3\x91\xf8x\x15\xda\xa38H"ZQ'
		b'G\xde@\xdcBWL.+k\x94\xb1\xf0\xc5+v\xb9\x93\x8eL\nbM\xb9\x14\xd9\xb9\x90\xb7o\x01\xcf\x81\x9br\x95\xc5!\xdc\xd4N84?s\xe5\xe6\xcbk+kT\x9bc\xab\xb6\x9c\xbc\x1d\xee;\xbf\x838'
		b'\xb1"n\x0b\xa8\xc1\x9cf\x87X\x85R\xec\xf2J\xac*\x86\x0b[Fi\xbd\x9b\x83\x9f\x92\xd3\xeeM\xc9\x0b`\x7f\x81\x8c\x05H\xfc\x92\xd5C]\x81\xb0\xa9\xfaS;F\x17\xc0>"\xe2\xedPe\xa3\x05Qb\x10-'
		b'\n2\xa20}\x8f\xd4\x9fl\xc5\xfd\xf3\xf3\xf3\xf0\xf1\xf1o\xef\xd0\x8d\xd7\xc8+\xd8rc@\xb9\x85@\x04g<\xab\x02\xd6\xe6@\x04\x85\xf2\xf24%%\xf2\xee\x1b%T\x92$L\xe0\x8a%\xa9F\xd1L\x17\xe6\x12'
		b'\xa10\x96\x87\xac"\x00\xa1@(cV\xf5Jw\xa9N%J5F\xdce\x02\xc0M-\x0b1\x1f\xe9AX\xfe\xbaZW:\xfb\x02\x1a_ML\xcc?\x94\xb2n\xe9\x8as\xd1Z\xf5\xe1\xa1\xab\xda&\x19\xefB\xd6'
		b'\xc0\xd0\xacR\xaf\xb9\xa0\xbb\xe0mE\xaf\\0\xe1\xeefM\xab\xa7k\xb9Dh\xba\xa2W\x9aE\x0b%\n#\xd8\x80\x04\x1b\xadb\xe3\xa5\xb5D\x93O\xee\xc0\xe8\xb3\xd1\xf7W^\xa4\n\xaeF\xaec\x96`Y{\x14'
		b'\xf4L_\xf9t:\xfd\xff\x03\x00\x19\x9b\x15l\xef\xdc\xac\x12\x00\x00\x00\x00IEND\xaeB`\x82',

	'wait.webp': # wait.webp

		b'RIFF<\x12\x00\x00WEBPVP8X\n\x00\x00\x00\x12\x00\x00\x00\xc7\x00\x00\xc7\x00\x00ANIM\x06\x00\x00\x00\xff\xff\xff\x00\x00\x00ANMFP\x01\x00\x00\x00\x00\x00\x00\x00\x00\xc7\x00\x00\xc7\x00\x00'
		b'P\x00\x00\x02VP8L7\x01\x00\x00/\xc7\xc01\x10\xc7\xa0\xb6\x8d$5y|\xf7\xfc1\x86\xaf\xb6\x8d$5y|\xf7\xfc1\x86\xaf\xb6\x8d$5y|\xf7\xfc1\x86?\xff\x01\x00\x00\xe0\x18C\xce\xd9{\xcf\xff'
		b'\xdfZKD\x98s\x9asrw)%\xa5\x14\xaa\xca\xdd\xa5\x94\x98\x19U\xa5\xaa\xdc\x9d\x88PUfFD\x98\x19@\x90m;m\xa3\x1f4\xca\xaa\x85\xfb\xdf\xeb{\x86J\xe1D\x11\xfd\x9f\x00U[)\xc5\xbc\x1f\xa4'
		b"\x9aF-\xbb\xceyl\x96\xa9R2m\xcacO\xa8\xc2\xbe\xf9\xd9a\xef\x7f7\x8b\x96\xbd\xf7_k\xbc\x81\xc3F\xbd\x81\xf6f!\xedF&\xad\xf9m \x1a\xdc\x13\xad\xc1\xe1\x86b'bH\xdb~\xf2;\x9d\xf1\xb4"
		b'\xce\x01\xaf3\x9d\xf1\xe9\x06`\xf5*1\xe0\xb8\x8a\x965K\xa3v\xb3\xf5M@fC\x95\xff\x1do\x8cx\xb3\x88o\x0bVH\xbb\x91\x15R\xdd\xa6\xdf$\xa6\x19S3\xd67\x0f+\xf8\x14\x98\xa3`u;\xd7?\\'
		b'qC]\xf1\xe5\xe6\x11\xd2\xa6m\xad\x90\xdfe\x047\x1d\xf6\x1b\xf9\x0e7\x82o\x01!u\xda\xbf&\xe4{\x8b\xe0\xab\xc6\xee\x1f8\x8d\x1b\xc1\xa5\x11\xb2My\xec\xab\xff~\xc9\x93\xc5\x8d\xcdcK\xc0\xfcI9\xab\x9b'
		b'*\x14\x01\x00ANMF\xa0\x00\x00\x00%\x00\x00\x15\x00\x001\x00\x00&\x00\x00P\x00\x00\x00VP8L\x87\x00\x00\x00/1\x80\t\x10\x8f\xa0\xa0\x91\xa46\xf7\xbd?\xe0\x18\x85m#5\xdf\xfb\x03\x1b\xb3\xba\xc2\xb6'
		b'\x91\x9a\xef\xfd\x81\x8dY}\xfe\x03\x00\x00\xc0\xff\xbf\xf7\x9e\xf7\x1ew\x97\x99\xba\xdb\xcc\xc8LfFU\xa9*w\'"T\x95\x99\xa9*`\xdbH\x92\xa2\xd9\xa7c\xca?\xde\xd9\x03\xeb\xd7\x8b\xe8\xff\x04`\x9co\x10-'
		b'\x9e^\xe1\x85c\xaf\xf0\xcf \x8a\xa3\xaf(\x15\xa2|\x1cMP\x96M\x11+\xff\xae\xee\xf8-\xfcr\xbc.\xd1\x06\x85\xc3\xa9\xf6\xd03oK\x00\x00ANMF\x9a\x00\x00\x00*\x00\x00:\x00\x00\x1e\x00\x00\x15\x00\x00'
		b'P\x00\x00\x00VP8L\x81\x00\x00\x00/\x1e@\x05\x10\x7f\xa0\xa0m\x1b\xa6\xfc\xd1v\xff\x02Q\xdb\xb6\r\xe3\xff\xafuz\x1d\xd4\xb6m\xc3\xf8\xffk\x9d^\x87\xf9\x0f\x00\x00\x8c\x08\xbb\xeb\xee\xfc\xbf\x99\x91\x99v\x17'
		b'Iwgw\xdd\x9d\xaa23H\x82\xebH\xb2\x95\xea\xe0\xee\x0eYYb\xce"\x19R7\tx.\xdf\x11\xfd\x9f\x00zn\xd94M#\xdc\x93z\x9e\xe7\xe9\x7fB\x0b\x00\x07c\x8c\xed\x00 F\x9e\xe7y\x0e\x11Q'
		b'\xe1y\x9e\xa7\xfe\xbe\x16\x00\x94\xd3\x08\x00"\x00ANMF\xc0\x00\x00\x00%\x00\x00\x11\x00\x00<\x00\x00\x82\x00\x00P\x00\x00\x00VP8L\xa8\x00\x00\x00/<\x80 \x10\x8f\xa0\xa0\x8d$5w|\xf7\xcc\xf2^A'
		b'\x1bIj\xee\xf8\xee\x99\xe5\xbd\x826\x92\xd4\xdc\xf1\xdd3\xcb\xfb\xf9\x0f\x00\x00\x00\xc7\x18\xce9\xfe\xff\xde{\xf6\xdez\xef\xccL\xadUJI)\x85\xaarw)%fFD\x98\x190\x90$)\r\xee\xfa\xff\xf7.'
		b"\x10\xf7TD\xff'\x80l\xb7\\\xd6s\xb5\xce\xff\xce\x91\xd4\xb4q\xd0\xa5/\x97\xf5O2\x18\x1c\x96\xe0(6w\x019$9\x1c\xa7\x1a[[\xef\x07I`\xdd`\x05\x0eS]\xe8\x0c\xcb\x16\x86'+\x11\xa7\x86\x1c"
		b'q\x99\xeav\xe9\x05vKp\x14\x9b\xab\x8b\xe3E\x94\x84\xf3\x12\xaeJ\tANMFN\x01\x00\x00\x00\x00\x00\x00\x00\x00\xc7\x00\x00\xc7\x00\x00P\x00\x00\x02VP8L6\x01\x00\x00/\xc7\xc01\x10\xdf\xa00\x92$'
		b'3uz\xdbP\x18I\x92\x99:\xbdm(\x8c$\xc9L\x9d\xde6\xe6?\x00\x00\x00,\xa5\xf0\xdes\xceYk9\xe7(\xa5\xf8\xff\xb3\xd6\xaa\xb5RJ\t!0\xc6p\xceQJ\xd1ZSJ1\xc6p\xce\xd1Z\xf3'
		b'\xde3\xc6\x10\x11J)Zk"Bk\r\x10\xdcHR$u.34\xce\xcd\xfc\xff\xa9\x99\xc3\xcb}\x1d\xd1\xff\t0\xa5\x95\x98\xcd\xe7\x01\xc54\xa6\xdd>L\xe3\xb1\x9d\x99K\xe5\xd4L\xe3E03[\xfc\xf7/'
		b'\x9d\xf5\x96\xbb\x11\xb8-_?\x00\xaa\x1d\x81E\x97sc\x9e\x07q2\xcc\xfc\x7f\xe3\xddk\xde[\xcd\x1f\x0b\x15b\xec)B4\x8fiN\xc2\x05.\x05n\x1f\x1e\x15|\xfd\xa7,\xc1\xcd\xe3\\\xfe\xb0\xe7\x07i\xcfw'
		b'\x0f\x0f\xc4\xd8\xf4\x1b!\xe6e\xc0\x8fg^\t\xe9\xc6\x83P\x9d\xf9\x01\xfc\x11\x80h\x9b.\x1d\xf8MP\xad\x80|\x0b\xf8\xde\xf2\xd4I\xe5x\xec$Y~\x00\x9f\x1b\x88\xa7f\x1a/\xd9\x9bt\xe3\xe1\xc1s\xe0\xb7\xbc'
		b'B\xcd\xadp\xad\xf9#\x95V|)`\xb1\xa2\xfe\x81\x8a\xe8\xd7e\x10\xd2\xa6_\xff@\x8d\xf1\x17\x12Z\x1f\xe24\x1e\x05:}\x98\xcf\xe2\xa6\x02EANMF\xa8\x00\x00\x00%\x00\x00:\x00\x001\x00\x00(\x00\x00'
		b'\xb8\x01\x00\x00VP8L\x8f\x00\x00\x00/1\x00\n\x10\x9f\xa0\xa0\x91\xa46\xf8\xbd\xd1Q\x801\x144\x92\xd4\x06\xbf7:\n0\x86\x82F\x92\xda\xe0\xf7FG\x01\xc6\x98\xff\x00\x00\x00\xf0\xff\xef\xde+"D\x84\xbd'
		b'\xb7\xf7\x1eU\xd5Z\xe3\xee2\x93\x99QU\xaa\xaa\xb5FU\x99\x19\x11af\xc0\xb8\x8d$E=\xbb\xc7\x0c\xf9\x87\xdb\xcb\xf8\x8b\xe8\xff\x04 \x91\xbfiG\xbbW~\x05s\x08_:\xcb3\xf1\xc1@M\xfcrr\x8b'
		b'!\xb8\x18\xee\xed\xec\x9c\xe8\xe8IOt\xc3..\r``8\x1b\xc1oC\xfb\xde\xd0?\x00ANMF\xf2\x04\x00\x00\x02\x00\x00\x02\x00\x00\xbe\x00\x00\xbe\x00\x00P\x00\x00\x03VP8L\xd9\x04\x00\x00/\xbe\x80/'
		b'\x10\xf7\xa0\xb8\x8d\xa46w9\x1f\xf0Cq\x1bIm\xeer>\xe0\x87\xe26\x92\xda\xdc\xe5|\xc0\x8f\xf9\x0f\x00\x00\x00\xef\xbd\xaaJf\xda]\x11\xe1\xff\xef\xde+3e\xa6\x88\x10\x11\xde{TUwsw\x99IU'
		b'\x99\x19U\xe5\xee2\x93\x99\xa9*\xeeND\xa8*3#"\xcc\x0cdI\xb6\xdd\xb6\x11\x1e\x95\xb3d[\x81\x14\x88\xfd\xafs\x04\\\xbc\xf7H\xdcI?\x11\xfd\x97 I\x92\xdc6\xe8.\xab\xed\t\x8c\x07\xeb\x0b\x0b\xc2'
		b'\x1f\x08mJ$P\x97\xfc\x12{\x98[\n\xf1\x86\xbb\x05\x8a3\xdc-U\xf1\x85\xbbE%\xaep\xb7\xa8\xc5\x13\xf2\x96\xff\xbd\x16\xf2\x16\xa3\x18\xc2\xdd\xe2 \xf6\x88\xb0G\x84="\x14\xe9\xae[\xb6\xae,\xcc\xe2\x96\xde'
		b';\x8b\xf9\xe5[Ji0E\x82|l\x8a\xec\xaa\xb9\xde\xa6\xc8/\'s\x91[.dnF-\xbbD\x82\x98\x9b\x91\xab\xba4#\x96\xdd\x8a\xc4\xb2[3J\xd99\x92\xc2\xb5\x19\xa5\xaas3:y\x82"\x9d<A'
		b'3"y\xb2H\x86I\x9aQ\xa9\x92l\xa6\xcad\x8b\xeaL\xb4Y%\x93\x8e\n\x087\xbbb\x187;\x8d\x99\x89\x17O\xb10\xe3f"\x99i\xc7\xecR\x8b"\xe3f"\xa5#\xe5f\xe0RI7\xab\\\xea9\xcd'
		b'\xaaY\x83\xc7\xf2\xfe9\xd8\xc6:|\x9b\xd5\xe1\xdb\xac\x06\xe7b\x05\xc6\xcd\n\x88\xc7\x1f\x04\xe5fr\x88\xa5)7\x13\xc9L\xbb(\x02L\xb9Yvq\xc4\xa3\x080\xe5f\xe8\xe287\xab\\\xdc)M_l\xdb\x9b'
		b"\xd7\xe7\xa0\xdb\xac\x06\xf1\xb8.\x98\xbcY\x8b\r\xca<\x0f\xbd\x9a\xfc\x19\xe6\x93S\x8a\x876\xff\xc9\x94\rwBx\x90G\xb6\x03w$3q2S'3q\n\xcf\x89\x10&F<\xa7\x10\xe89\x11\xc2\xb4\x88xN"
		b'\x81t\x9c\x15!L\x8b\x88\xdf\x14H\x9b~^\x840-"NS\xe0\xef\xcf\x87\xa9\x85\xe77L\xa1\xcat\x10\xb1M\xc1)\xd7\xd7\xc26\x05C\xba\xf3\x1aN\xb1\xbc,\xe0\x14\xeb\x0b\xaf*|\xbb\xe3\x89\xbc\xbc\xa7\xfb'
		b'\x12\xad\xd3+}1\xcb\xe0\xfd\x88\xc7\x1a\x91\x80\xf3}\xc5\xadS\xe7\xfb\x00\x87\x18\xbf#^\xa7\xef\xd8\x14\x99\x01\x85\xc1\x9e\xee\xa6\xcd?\x1d\xfcw\x7f\xbcv\xf0\xb9S<\xc9il\xa6\x9a\xeb\xb9.@\x06{\xba[\xa7'
		b'c\x91\xdd\xb7\x00\xe0s\xb3x\xca>wn(\x17\x11~\xeaq_\x14\xfb\xe92nT \x7f\x00\x1e\x7f\xf1\xf1\xd8L.\xe2\xe6\x0e\xa7\xb8W\xf6\xd3_\x9a\xaa\x08\xf2\xa5\x98\xe2\x10\x8b\x9c;6\x90+O\xdd*SD'
		b'\xd3L\xe9g)\x02<^\xc1\x95\x1cbA\xe1\x06pT=\x03\xf7W8\xc5a\xc8(=\xcc\xbc:\x1d\x0b<\xff\xa6\xabLQ\xb9\xb65\xa7\xbcPl\xd5\xf477\x9cr\xa7\xd8\xaa\x19n-)\xe5\xa5r\xab\xa6\xbd'
		b'\xcd\xa7\xd4M\xb9U\xd3o\xb2huS,\x86~\x8b;-"3\xcb\xe8\xbeK\xb5\x18\xfa\x07\x0c\xd3"2\xb3\x9c\x12\xb8o\xf7\\\x0c\xcd\x80\xf6y\xf1\x9f\x83y\xe5\x84\xef\xdb\x1d\x17\xa3>\xa0\xc3\xb4\x95\xbf\x85\x0cL'
		b'^\x0c3\x17^+Eu\x06\xb1-\x14y\x06\x91F\x1ez|7\x1b+\xf7\xb6\xc6H"\x0f\xbbm\x8f\x86\xd8\x1f\xa1\xfb\xed\xc5\xc0x\x15\x16\xc0\xdd\xdb\xb0\x17A\xee7"\n\xe3\x1di\x93U\x15\xc0\xefl\x88cD'
		b'Hw\xb1 \xc2!K\xb9\xf7\x7f\xef\xf3!\x8e\x11\xa0\xff_\x1a\xe5\xae\xa85\xe4\xaa\x7f\xa1a\xd3\xc3\xb7\x03\x8e\x11\xa1,^\xbar\xa8\xc6\x90C\xd4\xbf\xce\xb0}f\xb94BQ\xc4\xfbi\x91\xc6\x0e9\xe0\xbf\x83\xcf'
		b'x\xa2\x0e\x0fq\xdc\xe0)\xbe\x15U\x0e\xce\xfe\x90\xb9h\xff\xae\x1c\xc5s\xd7\xf9\xff\x90K1\xee\x1d\xa9x\x81\xa6\xcaV\xa8\xdd\x18\xf7\x8e\xd4#\xa6\xe1\xf8\xde;\xa1s\x85\xb6Op@\xef\x10(\xd8\x01\xbd\xd1\xca\xb0'
		b"j\xb6\xeam\x0f\n\xd0\xba\xf0jv\x8cz\xe2\t}e\xd6\xec\x18\rH\xeb~;Z\rMka\xe08\xd9\t\xe3\t\x0c\x15\x187\x8b'8\x14\xc5f\xb7\xdaq.\x87\xa2s\x82\xdb7\x8fu\xe5`b'Rz"
		b'\xae+pif\xa0\x1e+0i\xa6F\x11+Pi\xa6D\x17\x11\\NP,\xa9\xc1!p\xb2\xd7;O\xf7\x0e\xaf\x04\xadf[\xbd\xe3\x01\x7f%\xd6l\xd3\xeb\x11`n\xcd\xb6\xbd\x9e\xcc$OPA\xdc\xf8\xcd\x9a'
		b"@\xb1\xd9\xd9\x80Plv\x8c\x06\xa85\xf3\x83\xd7\t^0\xb3'\xac\x9ayB\xaa\xd9\xc1\x81f\xe5\x16\xe7B\xcb\x9f[\xd9h\\6;\xd1\xbcL\x8eN4/k4CA\xd6h\x84\x84\xac\xd1\x04\rYm\x80\x88"
		b'\xacE5Td\x8dJ\xc8\xc8\x1aU\xd0\x915* $\xa3\xebP\x92\xc9}\x15R\xb2xY\x83\x96\xc4I\x81\x98\x9c\xa0&\x17\xc8\xc9\x01z2CPF(\x8a\xfe\xf1\x7f\xc0\xdc\x0f\x83\x03M\xa9!*%T\xa5\x82'
		b'\xac\x14\xd0U\x15\xc2\xaa@Y\x10\xd2\x02\xd0V\x01qePW\xab\x04\x00ANMFP\x01\x00\x00\x02\x00\x00\x02\x00\x00\xbe\x00\x00\xbe\x00\x00P\x00\x00\x02VP8L8\x01\x00\x00/\xbe\x80/\x10\xc7\xa0\xb6\x8d$'
		b'5w\xf8\xcc\x19\x87\xaf\xb6\x8d$5w\xf8\xcc\x19\x87\xaf\xb6\x8d$5w\xf8\xcc\x19\x87?\xff\x01\x00\x00\xe0\x9c\xd3{\xcf\xff\xdf\x18\x83s\x8esNkMk\x8d\xb5\x96\xf7\x9e\xaa\x8a1\xb2\xd6\xf2\xde3\xc6PU\xaa'
		b"\xcaZKD\xa8*c\x0c\x11a\x8c\x01\x07\xb2m5\r\xcf\n\x9a\xd8\x0b\xfb\xdf\xeb\xc3\xefG\xd3KD\xff'\xc0\xfc\xfd\xff\xf7\xbfFH\xde\x02\x90\xd4\x84\xf3\xc3\x1b\x9f\xcex\x10\xed\xe1 \x16|\xb3\xb0Ne\t\xae"
		b'\x05<u\n\xcb\xc8F\xaf\xb2\t\x80\xb6\xcc+\x1c\xbeYL\r\xb7\xe0\x12\x08\xbc6\xb4=\x8f\xb5\xe5\xbdDh\xa5d\xf5\x9a\x8fb\x01\xb7\x1f\xfb\xca\x89\xce"\xf3\xc8KloG\xeeO-\xd0\x8a\x84J\x0c>\xce\xcf'
		b'k6\xdd~s\x14\x1d\x9fD\x16A\x7fk\x84^E\x8e\xf3\x0bEv\xe1\xc3mf\xfd\xbee\xb2}\xb8\xb1\xb0\xfd\xe3[\xde\xd2Ad\x1ey!\xd0\x8c\xfc\xdc\xac\xdf\xb5\x027\x82\x08\\?\xa9\xad5on\xb3\x9d\xa7'
		b"\x86[%\roo\x84\x06\x81F'x\xf6\xbd\xc2\xd5*m\x80\xd1\x96\xd6\nmdZM\xa4\x88\xf7+\x829\xfd\xcf-\xe6\x06\x940\x7f\xff\xff\xfd\xaf\x11ANMF\xf6\x04\x00\x00\x02\x00\x00\x02\x00\x00\xbf\x00\x00\xbe"
		b'\x00\x00P\x00\x00\x03VP8L\xdd\x04\x00\x00/\xbf\x80/\x10\xf7\xa0\xb8\x8d\xa46w9\x1f\xf0Cq\x1bIm\xeer>\xe0\x87\xe26\x92\xda\xdc\xe5|\xc0\x8f\xf9\x0f\x00\x00\x00\xef\xbd\xaaJf\xda]\x11\xe1\xff\xef'
		b'\xde+3e\xa6\x88\x10\x11\xde{TUwsw\x99IU\x99\x19U\xe5\xee2\x93\x99\xa9*\xeeND\xa8*3#"\xcc\x0chK\x92\xa4H\x92\xca\x9b\x99\xa9\xa0\xeb\xff\xbfs\xb3+ 3\xc3\xf7\xe0%\xa2\xff\x0e'
		b'\xdcF\x92\x94\xad\x14*\xd1\xdaV\x0f\xcb\xb1\x0b\xbc\xa0\xa9G@\xc3]\x00@\xdf\x00}\x03\xf4\r\xd07@\xdf\x00}\x03\xf4\r\xd07@\xdf\x00}\x03TM\x1d\xb0\xdf\xfe\xfb7\xf5\rN\xd17@\xdf\x00}\x03\xf4'
		b'\r\xd07@\xd8\xfb\xcbL\x81\xaf\x87\xf1:W`\xeb~\x1c\xc7\xab\x16\xf9\xda\x00\xd7\xea\x8f\xf4"\xdbl\x8ad\xb3-R\xcd\xd6H\xd2>\x88V\xedE\x8a\xd9\x1b\tf\x7f\xe4\x973Dj\xce\x05\xc1j\x7f\xea\x9cE'
		b'zy\xd8a\xdb9#\xa5\xac\x18\xd8x#\xb5\xdc\'\xc3\x1f\xc99\x17\xac\xba\xed\x84q\xb6\xfe"\xa9ni\xa0<\x91V7y\xa2M;:"\xad=\xe4\x89\xd6\xc9zdk;\xac\xba\x99\xab\x86"\xb1n\x86l\x8d'
		b',\xbb\xd9u\xa2\xb8\xc7\xec\xed\xf0{\x05\x82\x06v\x9d\xd9\xaf\xd5\x9f\xa9xu\x93?3{\x8f\x01Z\xdd\x94\xd8\x1b\xab\x93\xa9\x88u\x93\xcfZLY\x98\x8a\xd6\x1e\xce\x1d\xde+q*Jv\x17\xfb\xb52\x15\x91n\x8e'
		b'\xf1\x0e\xea\xf78\xcf\x85h&\xdd|\xd6\xe1\x91\xb3\xd9\x01\x93=|\xb6A\xd5V\x88v\xb3\xc3\xb2\x9bCgZ\xdd\xd4\xab\x0e\xc7\xcd\x1cT\xf6p\xe2\xb8\x9b\x88\xdd8v\xa3\xd1\xcd\x8f\xe7s\x87D\xb7\x0c\xb8>\xf7)'
		b't+\xc0s.N\x15;\xef\x1d\xe3-\x95/\x0fz\xc5C\x84\xf3\xde\\?J\r\x07\xf9\xe8\xca\x0f\xe2\xa1\x95\x9f\x11g\xf5\xe6\xb3R\x1c\x8aEz\n\x1a\xd9\xb4\xe2\x10\x87A|C\x99%\xab\xac\x15\x87b?XY'
		b'\xb7\xe2\x10\x874\\\xbf\x93\xb3=6\r\t6\xedd\x88\xe9\xc9\x8a\xc4%\xd9\x1e\x9b\x86\x04\xebV\x18B<\xd7\xeaw\x93\xaa\x83\xa6!\xc1\xba\x15\x87\x10O\x15\xfb\x9dT\xb5\x15\x9b\x86\x04\xa7V\x1c\xe2*\x0f\xd5\xdfG'
		b'\xe7yodj\xd0\r\xe0\xce\x15\x88E\xd3\x94%\xf4\xef>S\xd7\xc6U:\x1e\xf9\xd6\xdf\x14\xd3\xef\xea\xca\xe3\xf8\xe16\xe7\xea\x9bbJ\x13\xd7\xc6M\x9e\xdfst\xc5\x99\x8a?\xc9k\xa3\xdf(\xf3\xdb\x8f\xae8\x13'
		b'@\x86\xd6|\xe2\xaeL!\xce\x04\xf0\xc1\x18\x95)\xc4\x99\x00\x92\x88S\xc8\xcb\x9b\xac\x94`Q\x99\x02\x9a(F\xb8\xc40n^ky-\x8f\x858\xc5\xf12\xe3\x177\xdd\xf8Y\xc9+yJ\x1c\xbf\xe35\x12\xaf\x93'
		b"<\xc5\xf3\xf2\x15\xa7\xb8>\xccl\xba\x9fkqq\x1d\x12\xc74\xc5u\x1e\x86\xf7*\x1d'\xc1\x8b\x99\xc8\r\xf3\x87\xa18ub%\xaf\xe2\xb9\x90<\xc6\x89\xaf\xd5\xcfR\xe5\xaf\xc7\xce_\xf1Tqf\x88\xf2?;?"
		b'\xeb\xc9\x1a$\xa6\x07!@\x94\x7fO>\xcaG.E\xe9\xc8-\x0cQ\xfec\xeag-?\x87\x9e\x8b\xc9!\x88\x12\xc5_\xf3\x8f\xd2+g\xca5\x8dd\x8b\x87N\x9c\xe2sT^\xb5\xa7\xf4\xaa\x8d\x83\xfcg\x84\xe3\xef'
		b';\x97\x84\xb4\xe8\xb4CvE)>W\xe2\x14G\xf9\x93cv\xb9\xa6\x91J\x11(j\x8b\x9a{f\x9a\x91\x8a\xe9Q\xcc5\x05\x16,\xa2\xe5\\\xd57\x85\x129`\xbcV\xc80\x05\x91f\xd6k5\xf7\x1457\xcb'
		b"\xff\x10\xda\xaf\x95\x1dS\xe8\xc5ge\x11(\x8a2\x84k\ny\xb9\x9bn\xac'J\xcf\xb0\xfa\xa5\xb9&\x80@(\xae\xa6\x08\x04Bv\xe1\x18\x17\xc9\xb4\xa3d\xda1\xfd\x95\x87;\x93KY\xde\xc5\x9f+A\xe6\xf1g"
		b'a\xe7Z\xb8  i]\xf5\xc4~\x83x\x005\xd1m\x11o\x03j\xa2\xdb\x84\xb4\xc8\xbdx\xd5\x9b\x03l\xfb\xa1\xee\x1c\x80\xfdPG\xb7\x80]\xeb\xc9\xcej\xed\x04\xa8F`?\xd4\xdc-\x80\x02\xe4\x00\xe7\xbd]\xd5'
		b'\xddb\xdc\x1c\x87\xc2\xdd\xe2;A\xbc\xfa\xbb\xc2\xe3\xb7h7\x06N\x91x\x96\xa0\xdcM\x81o\xb7FY\xe9\xbd\xb6j\xcc\x9b\xca\xba\x85\xbb\xfb[a\x8e\xb7[\x85\xdd\xa2}\xeeT\xd9-\xd6\xe7~M\xd5x[\xa9n'
		b'L\xbc\xea\xc8\xe7\x02\xd7\x1d=\x19\x17\xa0#S\x95`]\x15\xa1\xdcM\xa1\x86\x1c\x9f\xe2\xdd\x18P\xbc\x1b\x03JV\xa3\xca@\xb9nqe\xa0T\x8e,\x03e\xba\xc5\x96\x81\x12\xdd\xa2K\xa7@5\xbe\x0c\xe4\xee\xc6@'
		b"*\xb93\x07\x19\xc8\xd9\x8d\x85\x0c\xe4\xeb\xc6C*\xf9\xaaLd O7.R\xc9\x93\xd9H'C7>R\xf1wc$\x15o\x95\x934>\xbe\xccJ\xc8\xa6\x86\x97\xb2\x99\x992\x99\x9b\xb2\x98\x9d2\x98\x9f\xdcf"
		b'(\xfan\xfe\xd70\xf5\xada)\x87y\xcal\xa62\x9a\xabLf+\x83\xf9J5c)\xe6,\xd1\xac%\x98\xb7&f\xae\x1fsWEn\x00\x00ANMFP\x01\x00\x00\x00\x00\x00\x00\x00\x00\xc7\x00\x00\xc7\x00\x00'
		b'P\x00\x00\x02VP8L7\x01\x00\x00/\xc7\xc01\x10\xc7\xa0\xb6\x8d$5y|\xf7\xfc1\x86\xaf\xb6\x8d$5y|\xf7\xfc1\x86\xaf\xb6\x8d$5y|\xf7\xfc1\x86?\xff\x01\x00\x00\xe0\x18C\xce\xd9{\xcf\xff'
		b'\xdfZKD\x98s\x9asrw)%\xa5\x14\xaa\xca\xdd\xa5\x94\x98\x19U\xa5\xaa\xdc\x9d\x88PUfFD\x98\x19@\x90m;m\xa3\x1f4\xca\xaa\x85\xfb\xdf\xeb{\x86J\xe1D\x11\xfd\x9f\x00U[)\xc5\xbc\x1f\xa4'
		b"\x9aF-\xbb\xceyl\x96\xa9R2m\xcacO\xa8\xc2\xbe\xf9\xd9a\xef\x7f7\x8b\x96\xbd\xf7_k\xbc\x81\xc3F\xbd\x81\xf6f!\xedF&\xad\xf9m \x1a\xdc\x13\xad\xc1\xe1\x86b'bH\xdb~\xf2;\x9d\xf1\xb4"
		b'\xce\x01\xaf3\x9d\xf1\xe9\x06`\xf5*1\xe0\xb8\x8a\x965K\xa3v\xb3\xf5M@fC\x95\xff\x1do\x8cx\xb3\x88o\x0bVH\xbb\x91\x15R\xdd\xa6\xdf$\xa6\x19S3\xd67\x0f+\xf8\x14\x98\xa3`u;\xd7?\\'
		b'qC]\xf1\xe5\xe6\x11\xd2\xa6m\xad\x90\xdfe\x047\x1d\xf6\x1b\xf9\x0e7\x82o\x01!u\xda\xbf&\xe4{\x8b\xe0\xab\xc6\xee\x1f8\x8d\x1b\xc1\xa5\x11\xb2My\xec\xab\xff~\xc9\x93\xc5\x8d\xcdcK\xc0\xfcI9\xab\x9b'
		b'*\x14\x01\x00',
}

# Parser for PLY generated LALR1 grammar.

import re
import types

#...............................................................................................
class Token (str):
	__slots__ = ['text', 'pos', 'grp']

	def __new__ (cls, str_, text = None, pos = None, grps = None):
		self      = str.__new__ (cls, str_)
		self.text = text or ''
		self.pos  = pos
		self.grp  = () if not grps else grps

		return self

class State:
	__slots__ = ['idx', 'sym', 'pos', 'red']

	def __init__ (self, idx, sym, pos = None, red = None): # idx = state index, sym = symbol (TOKEN or 'expression')[, pos = position in text, red = reduction]
		self.idx  = idx
		self.sym  = sym
		self.pos  = sym.pos if pos is None else pos
		self.red  = red

	def __repr__ (self):
		return f'({self.idx}, {self.sym}, {self.pos}{"" if self.red is None else f", {self.red}"})'

class Conflict (tuple):
	def __new__ (cls, conf, pos, tokidx, stidx, tokens, stack, estate):#, keep = False):
		self      = tuple.__new__ (cls, (conf, pos, tokidx, stidx, tokens, stack, estate))
		self.conf = conf
		self.pos  = pos
		# self.keep = keep # do not remove when popping conflicts

		return self

	# def __repr__ (self):
	# 	r = tuple.__repr__ (self)

	# 	return f'{r [:-1]}, keep)' if self.keep else r

# class Incomplete (Exception): # parse is head of good statement but incomplete
# 	__slots__ = ['red']

# 	def __init__ (self, red):
# 		self.red = red

# class KeepConf:
# 	__slots__ = ['red']

# 	def __init__ (self, red):
# 		self.red = red

class PopConfs:
	__slots__ = ['red']

	def __init__ (self, red):
		self.red = red

class Reduce: # returned instantiated will try conflicted reduction before rule, returned as uninstantiated class will discard results of rule and just continue with last conflict
	__slots__ = ['then', 'keep']

	def __init__ (self, then):#, keep = False):
		self.then = then
		# self.keep = keep

Reduce.red = Reduce

class LALR1:
	_rec_SYMBOL_NUMTAIL     = re.compile (r'(.*[^_\d])_?(\d+)?') # symbol names in code have extra digits at end for uniqueness which are discarded

	_PARSER_CONFLICT_REDUCE = {} # set of tokens for which a reduction will always be tried before a shift

	def set_tokens (self, tokens):
		self.tokgrps = {} # {'token': (groups pos start, groups pos end), ...}
		tokpats      = list (tokens.items ())
		pos          = 0

		for tok, pat in tokpats:
			l                   = re.compile (pat).groups + 1
			self.tokgrps [tok]  = (pos, pos + l)
			pos                += l

		self.tokre   = '|'.join (f'(?P<{tok}>{pat})' for tok, pat in tokpats)
		self.tokrec  = re.compile (self.tokre)

	def __init__ (self):
		if isinstance (self._PARSER_TABLES, bytes):
			import ast, base64, zlib
			symbols, rules, strules, terms, nterms = ast.literal_eval (zlib.decompress (base64.b64decode (self._PARSER_TABLES)).decode ('utf8'))
		else:
			symbols, rules, strules, terms, nterms = self._PARSER_TABLES

		self.set_tokens (self.TOKENS)

		self.rules   = [(0, (symbols [-1]))] + [(symbols [r [0]], tuple (symbols [s] for s in (r [1] if isinstance (r [1], tuple) else (r [1],)))) for r in rules]
		self.strules = [[t if isinstance (t, tuple) else (t, 0) for t in (sr if isinstance (sr, list) else [sr])] for sr in strules]
		states       = max (max (max (t [1]) for t in terms), max (max (t [1]) for t in nterms)) + 1
		self.terms   = [{} for _ in range (states)] # [{'symbol': [+shift or -reduce, conflict +shift or -reduce or None], ...}] - index by state num then terminal
		self.nterms  = [{} for _ in range (states)] # [{'symbol': +shift or -reduce, ...}] - index by state num then non-terminal
		self.rfuncs  = [None] # first rule is always None

		for t in terms:
			sym, sts, acts, confs = t if len (t) == 4 else t + (None,)
			sym                   = symbols [sym]

			for st, act in zip (sts, acts):
				self.terms [st] [sym] = (act, None)

			if confs:
				for st, act in confs.items ():
					self.terms [st] [sym] = (self.terms [st] [sym] [0], act)

		for sym, sts, acts in nterms:
			for st, act in zip (sts, acts):
				self.nterms [st] [symbols [sym]] = act

		prods = {} # {('production', ('symbol', ...)): func, ...}

		for name in dir (self):
			obj = getattr (self, name)

			if name [0] != '_' and type (obj) is types.MethodType and obj.__code__.co_argcount >= 1: # 2: allow empty productions
				m = LALR1._rec_SYMBOL_NUMTAIL.match (name)

				if m:
					parms = tuple (p if p in self.TOKENS else LALR1._rec_SYMBOL_NUMTAIL.match (p).group (1) \
							for p in obj.__code__.co_varnames [1 : obj.__code__.co_argcount])
					prods [(m.group (1), parms)] = obj

		for irule in range (1, len (self.rules)):
			func = prods.get (self.rules [irule] [:2])

			if not func:
				raise NameError (f"no method for rule '{self.rules [irule] [0]} -> {''' '''.join (self.rules [irule] [1])}'")

			self.rfuncs.append (func)

	def tokenize (self, text):
		tokens = []
		end    = len (text)
		pos    = 0

		while pos < end:
			m = self.tokrec.match (text, pos)

			if m is None:
				tokens.append (Token ('$err', text [pos], pos))

				break

			else:
				if m.lastgroup != 'ignore':
					tok  = m.lastgroup
					s, e = self.tokgrps [tok]
					grps = m.groups () [s : e]

					tokens.append (Token (tok, grps [0], pos, grps [1:]))

				pos += len (m.group (0))

		tokens.append (Token ('$end', '', pos))

		return tokens

	#...............................................................................................
	def parse_getextrastate (self):
		return None

	def parse_setextrastate (self, state):
		pass

	def parse_error (self):
		return False # True if state fixed to continue parsing, False to fail

	def parse_success (self, red):
		'NO PARSE_SUCCESS'
		return None # True to contunue checking conflict backtracks, False to stop and return

	def parse (self, src):
		has_parse_success = (self.parse_success.__doc__ != 'NO PARSE_SUCCESS')

		rules, terms, nterms, rfuncs = self.rules, self.terms, self.nterms, self.rfuncs

		tokens = self.tokenize (src)
		tokidx = 0
		confs  = [] # [(action, tokidx, stack, stidx, extra state), ...] # conflict backtrack stack
		stack  = self.stack = [State (0, None, 0, None)] # [(stidx, symbol, pos, reduction) or (stidx, token), ...]
		stidx  = 0
		rederr = None # reduction function raised exception (SyntaxError or Incomplete usually)
		act    = True
		pos    = 0

		# if not hasattr (self, 'reds'): # DEBUG
		# 	self.reds = {} # DEBUG

		while 1:
			if not rederr and act is not None:
				tok       = tokens [tokidx]
				act, conf = terms [stidx].get (tok, (None, None))

			if rederr or act is None:
				if rederr is not Reduce:
					self.tokens, self.tokidx, self.confs, self.stidx, self.tok, self.rederr, self.pos = \
							tokens, tokidx, confs, stidx, tok, rederr, pos

					rederr = None

					if tok == '$end' and stidx == 1 and len (stack) == 2 and stack [1].sym == rules [0] [1]:
						if not has_parse_success:
							return stack [1].red

						if not self.parse_success (stack [1].red) or not confs:
							return None

					elif self.parse_error ():
						tokidx, stidx = self.tokidx, self.stidx
						act           = True
						rederr        = None

						continue

				if not confs:
					if has_parse_success: # do not raise SyntaxError if parser relies on parse_success
						return None

					if rederr is not None:
						raise rederr # re-raise exception from last reduction function if present

					raise SyntaxError ( \
						'unexpected end of input' if tok == '$end' else \
						f'invalid token {tok.text!r}' if tok == '$err' else \
						f'invalid syntax {src [tok.pos : tok.pos + 16]!r}')

				act, _, tokidx, stidx, tokens, stack, estate = confs.pop ()
				self.stack                                   = stack
				tok                                          = tokens [tokidx]
				conf                                         = None
				rederr                                       = None

				self.parse_setextrastate (estate)

				if act is None:
					continue

			if conf is not None:
				confs.append (Conflict (conf, tok.pos, tokidx, stidx, tokens [:], stack [:], self.parse_getextrastate ()))#, keep = act < 0 and tok in self._PARSER_CONFLICT_REDUCE))

				# if conf < 0: # DEBUG
				# 	k             = (act, rules [-conf])
				# 	self.reds [k] = self.reds.get (k, 0) + 1

			if act > 0:
				tokidx += 1
				stidx   = act

				stack.append (State (stidx, tok))

			else:
				rule  = rules [-act]
				rnlen = -len (rule [1])
				prod  = rule [0]
				pos   = self.pos = stack [rnlen].pos

				try:
					red = rfuncs [-act] (*((t.sym if t.red is None else t.red for t in stack [rnlen:]) if rnlen else ()))

				except SyntaxError as e:
					rederr = e # or True

					continue

				# except Incomplete as e:
				# 	rederr = e
				# 	red    = e.red

				else:
					# if isinstance (red, KeepConf): # mark this conflict to not be removed by PopConf
					# 	red             = red.red
					# 	confs [-1].keep = True

					if isinstance (red, Reduce): # successful rule but request to follow conflicted reduction first putting results of rule on conf stack to be picked up later
						stidx     = nterms [stack [rnlen - 1].idx] [prod]
						stack     = stack [:rnlen] + [State (stidx, prod, pos, red.then)]
						tok       = tokens [tokidx]
						act, conf = terms [stidx].get (tok, (None, None))
						estate    = self.parse_getextrastate ()
						rederr    = Reduce

						if conf is not None:
							confs.insert (-1, Conflict (conf, tok.pos, tokidx, stidx, tokens [:], stack [:], estate))#, keep = red.keep))

						confs.insert (-1, Conflict (act, tok.pos, tokidx, stidx, tokens [:], stack [:], estate))#, keep = red.keep))

						continue

					if red is Reduce or isinstance (red, PopConfs): # pop all conflicts generated from parsing this rule because parse is guaranteed good
						red   = red.red
						start = stack [-1].pos if red is Reduce else pos
						i     = 0

						for i in range (len (confs) - 1, -1, -1):
							if confs [i].pos <= start:
								break

							# if not confs [i].keep: # dont remove conflicts which are marked for keeping
							# 	del confs [i]
							del confs [i]

						if red is Reduce: # if reduction only requested then don't store rule result and fall back to previous conflicted reduction
							rederr = red

							continue

				if rnlen:
					del stack [rnlen:]

				stidx = nterms [stack [-1].idx] [prod]

				stack.append (State (stidx, prod, pos, red))

class lalr1: # for single script
	Token      = Token
	State      = State
	# Incomplete = Incomplete
	PopConfs   = PopConfs
	Reduce     = Reduce
	LALR1      = LALR1

# print ('\n'.join (str (s) for s in confs + stack + [rule, pos]))# Base classes for abstract math syntax tree, tuple based.
#
# (';', (expr1, ...))                                 - semicolon expression separator
# ('=', lhs, rhs)                                     - assignment to Left-Hand-Side of Right-Hand-Side
# ('<>', lhs, (('rel1', expr1), ...))                 - comparisons of type 'rel' relating two or more expressions, potentially x < y < z is x < y and y < z
# ('#', 'num')                                        - real numbers represented as strings to pass on maximum precision to sympy
# ('@', 'var')                                        - variable name, can take forms: 'x', "x'", 'dx', '\partial x', 'something'
# ('.', expr, 'name')                                 - data attribute reference
# ('.', expr, 'name', (a1, a2, ...))                  - method attribute call
# ('"', 'str')                                        - string
# (',', (expr1, expr2, ...))                          - comma expression (tuple)
# ('{', expr)                                         - invisible implicit parentheses for grouping and isolation during parsing
# ('(', expr)                                         - explicit parentheses (not tuple)
# ('[', (expr1, expr2, ...))                          - brackets (list, not index)
# ('|', expr)                                         - absolute value
# ('-', expr)                                         - negative of expression, negative numbers are represented with this at least initially
# ('!', expr)                                         - factorial
# ('+', (expr1, expr2, ...))                          - addition
# ('*', (expr1, expr2, ...)[, {i0, ...}])             - multiplication, with optional set of indices of explicit multiplications (indexes of rhs)
# ('*exp', (expr1, expr2, ...))                       - explicit multiplication, only used during parsing then converted to '*' with indexes of exps
# ('/', numer, denom)                                 - fraction numer(ator) / denom(inator)
# ('^', base, exp)                                    - power base ^ exp(onent)
# ('-log', expr[, base])                              - logarithm of expr in base, natural log if base not present
# ('-sqrt', expr[, n])                                - nth root of expr, square root if n not present
# ('-func', 'name', (a1, a2, ...))                    - sympy or regular Python function call to 'name()', will be called with expressions a1, a2, ...
# ('-lim', expr, var, to[, 'dir'])                    - limit of expr when var approaches to from both directions, otherwise only from specified '+' or '-' dir
# ('-sum', expr, var, from, to)                       - summation of expr over variable var from from to to
# ('-diff', expr, 'd', (('v1', p1), ...))             - differentiation of expr with respect to dv(s), d is 'd' or 'partial', dvs are ('var', power) - power is int
# ('-diffp', expr, count)                             - differentiation with respect to unspecified variable count times
# ('-intg', expr, dv[, from, to])                     - indefinite or definite integral of expr (or 1 if expr is None) with respect to differential dv (('@', 'dx'), ('@', 'dy'), etc ...)
# ('-mat', ((e11, e12, ...), (e21, e22, ...), ...))   - matrix
# ('-piece', ((v1, c1), ..., (vn, True?)))            - piecewise expression: v = AST, c = condition AST, last condition may be True to catch all other cases
# ('-lamb', expr, (v1, v2, ...))                      - lambda expression: v? = 'var'
# ('-idx', expr, (i0, i1, ...))                       - indexing: expr [i0, i1, ...]
# ('-slice', start, stop, step)                       - indexing slice object: obj [start : stop : step], None or False indicates not specified, None for step means no second colon
# ('-set', (expr1, expr2, ...))                       - set
# ('-dict', ((k1, v1), (k2, v2), ...))                - python dict
# ('||', (expr1, expr2, ...))                         - set union
# ('^^', (expr1, expr2, ...))                         - set symmetric difference
# ('&&', (expr1, expr2, ...))                         - set intersection
# ('-or', (expr1, expr2, ...))                        - pythonic or
# ('-and', (expr1, expr2, ...))                       - pythonic and
# ('-not', expr)                                      - pythonic not
# ('-ufunc', 'name', (a1, ...)[, (('kw1', a1), ...)]) - undefined function object with optional keyword arguments, no arguments means 'pure' unapplied undefined function
# ('-subs', expr, ((s1, d1), ...))                    - substitution - replace all s? with d? in expr

from collections import OrderedDict
import re
import types

import sympy as sp

#...............................................................................................
class AST (tuple):
	op      = None

	OPS     = set () # these will be filled in after all classes defined
	CONSTS  = set ()

	_OP2CLS = {}
	_CLS2OP = {}

	_rec_identifier = re.compile (r'^[a-zA-Z]\w*$')

	def __new__ (cls, *args, **kw):
		op       = AST._CLS2OP.get (cls)
		cls_args = tuple (AST (*arg) if arg.__class__ is tuple else arg for arg in args)

		if op:
			args = (op,) + cls_args

		elif args:
			args = cls_args

			try:
				cls2 = AST._OP2CLS.get (args [0])
			except TypeError: # for unhashable types
				cls2 = None

			if cls2:
				cls      = cls2
				cls_args = cls_args [1:]

		if cls is not AST and '__new__' in cls.__dict__:
			self = cls.__new__ (cls, *cls_args)

		else:
			self = tuple.__new__ (cls, args)

			if self.op:
				self._init (*cls_args)

		if kw:
			self.__dict__.update (kw)

		return self

	def __add__ (self, other):
		if not isinstance (other, tuple):
			raise TypeError (f'can only concatenate tuple (not "{type (other).__name__}") to AST')

		return self if not other else AST (*tuple.__add__ (self, other))

	def __getattr__ (self, name): # calculate value for nonexistent self.name by calling self._name () and store
		func                 = getattr (self, f'_{name}') if name [0] != '_' else None
		val                  = func and func ()
		self.__dict__ [name] = val

		return val

	def setkw (self, **kw):
		self.__dict__.update (kw)

		return self # convenience

	def _is_single_unit (self): # is single positive digit, fraction or single non-differential non-subscripted variable?
		if self.op == '/':
			return True
		elif self.op == '#':
			return len (self.num) == 1
		else:
			return self.is_var_single

	def _len (self):
		return len (self)

	@staticmethod
	def tuple2ast (args):
		return args [0] if len (args) == 1 else AST (',', args)

	@staticmethod
	def tuple2argskw (args):
		args, kw = AST.args2kwargs (args)
		args     = args + [AST ('=', ('@', kw), a) for kw, a in kw.items ()]

		return args [0] if len (args) == 1 else AST (',', tuple (args))

	def _no_curlys (self): # remove ALL curlys from entire tree, not just top level
		if self.is_curly:
			return self.curly.no_curlys
		else:
			return AST (*tuple (a.no_curlys if isinstance (a, AST) else a for a in self))#, **self._kw)

	def _flat (self, op = None, seq = None, exp = None): # flatten trees of '+', '*', '||', '^^', '&&', '-or' and '-and' into single ASTs
		def subflat (op, seq, exp):
			if self.is_mul:
				for i in range (self.mul.len):
					if i in self.exp:
						exp.add (len (seq))

					self.mul [i]._flat (self.op, seq, exp)

			else:
				for e in self [1]:
					e._flat (self.op, seq, exp)

		# start here
		if self.op in {'+', '*', '||', '^^', '&&', '-or', '-and'}: # specifically not '<>' because that would be different meaning
			if self.op == op:
				subflat (op, seq, exp)

				return

			seq2, exp2 = [], set ()

			subflat (self.op, seq2, exp2)

			ast = AST (self.op, tuple (seq2), exp2) if self.is_mul else AST (self.op, tuple (seq2))

		else:
			ast = AST (*tuple (a.flat if isinstance (a, AST) else a for a in self))

		if op:
			seq.append (ast)
		else:
			return ast

	def neg (self, stack = False): # stack means stack negatives ('-', ('-', ('#', '-1')))
		if stack:
			if not self.is_num_pos:
				return AST ('-', self)
			else:
				return AST ('#', f'-{self.num}')

		else:
			if self.is_minus:
				return self.minus
			elif not self.is_num:
				return AST ('-', self)
			elif self.num [0] == '-':
				return AST ('#', self.num [1:])
			else:
				return AST ('#', f'-{self.num}')

	def _strip (self, count = None, ops = {'{', '('}, idx = 1, keeptuple = False):
		count = -1 if count is None else count

		while count and self.op in ops and not (keeptuple and self [idx].is_comma):
			self   = self [idx]
			count -= 1

		return self

	_strip_attr     = lambda self, count = None: self._strip (count, {'.'})
	_strip_attrm    = lambda self, count = None: self._strip (count, {'.', '-'})
	_strip_attrdp   = lambda self, count = None: self._strip (count, {'.', '-diffp'})
	_strip_attrpdpi = lambda self, count = None: self._strip (count, {'.', '^', '-diffp', '-idx'})
	_strip_curly    = lambda self, count = None: self._strip (count, {'{'})
	_strip_paren    = lambda self, count = None, keeptuple = False: self._strip (count, ('(',), keeptuple = keeptuple)
	_strip_paren1   = lambda self: self._strip (1, {'('})
	_strip_minus    = lambda self: self._strip (None, {'-'})
	_strip_diffp    = lambda self: self._strip (None, {'-diffp'})
	_strip_fdpi     = lambda self, count = None: self._strip (count, {'!', '-diffp', '-idx'})
	_strip_pow      = lambda self, count = None: self._strip (count, {'^'})
	_strip_afpdpi   = lambda self, count = None: self._strip (count, ('.', '!', '^', '-diffp', '-idx')) # not currently used, possibly used in future in one place

	def _strip_pseudo (self):
		while self.is_func_pseudo:
			self = self.args [0]

		return self

	def _strip_minus_retneg (self):
		neg         = lambda ast: ast
		neg.has_neg = False
		neg.is_neg  = False

		while self.is_minus:
			self         = self.minus
			is_neg       = neg.is_neg
			neg          = lambda ast, neg = neg: neg (ast.neg (stack = True))
			neg.has_neg  = True
			neg.is_neg   = not is_neg

		return self, neg

	def _strip_mmls (self): # mmls = minus, mul, lim, sum
		while self.op in {'-', '*', '-lim', '-sum'}:
			self = self.mul [-1] if self.is_mul else self [1]

		return self

	def _as_identifier (self):
		def _as_identifier (ast, recursed = False):
			if ast.op in {'#', '@', '"'}:
				name = ast [1]
			elif not ast.is_mul:
				return None

			else:
				try:
					name = ''.join (_as_identifier (m, True) for m in ast.mul)
				except TypeError:
					return None

			return name if recursed or AST._rec_identifier.match (name) else None

		return _as_identifier (self)

	def _is_const (self):
		if self.is_num:
			return True
		elif self.is_var:
			return self.is_var_const
		elif self.op in {'{', '(', '-', '!'}:
			return self [1].is_const
		elif self.op in {'+', '*', '*exp'}:
			return all (a.is_const for a in self [1])
		elif self.is_div:
			return self.numer.is_const and self.denom.is_const
		elif self.is_pow:
			return self.base.is_const and self.exp.is_const
		elif self.is_log:
			return self.log.is_const if self.base is None else (self.log.is_const and self.base.is_const)
		elif self.is_sqrt:
			return self.rad.is_const if self.idx is None else (self.rad.is_const and self.idx.is_const)
		elif self.is_func:
			return all (a.is_const for a in self.args)

		return False

	def _tail_mul (self):
		tail = self

		while 1:
			if tail.is_mul:
				tail = tail.mul [-1]
			elif tail.is_pow:
				tail = tail.exp
			elif tail.is_minus:
				tail = tail.minus
			else:
				break

		return tail

	def _has_tail_lambda_solo (self):
		return self.tail_lambda_solo != (None, None)

	def _has_tail_lambda (self):
		return self.tail_lambda != (None, None)

	def _tail_lambda_solo (self):
		return self._tail_lambda (has_var = False)

	def _tail_lambda (self, has_var = None): # look for 'lambda' or 'lambda var' at the tail end of ast (to replace) - for use only during parsing (does not handle mul exp indexes)
		tail, wrap = self, lambda ast: ast

		while 1:
			if tail.is_var:
				if tail.is_var_lambda and has_var is not True:
					return None, wrap

				break

			elif tail.op in {',', '+', '*', '*exp', '||', '^^', '&&', '-or', '-and'}:
				if tail.is_mul and has_var is not False and tail.mul [-1].is_var_nonconst:
					_, wrapl = tail.mul [-2].tail_lambda_solo

					if wrapl:
						if tail.mul.len > 2:
							wrap = lambda ast, tail = tail, wrap = wrap, wrapl = wrapl: wrap (AST ('*', tail.mul [:-2] + (wrapl (ast),)))
						else:
							wrap = lambda ast, wrap = wrap, wrapl = wrapl: wrap (wrapl (ast))

						return tail.mul [-1], wrap

				wrap = lambda ast, tail = tail, wrap = wrap: wrap (AST (tail.op, tail [1] [:-1] + (ast,)))
				tail = tail [1] [-1]

			elif tail.op in {'=', '/', '^'}:
				wrap = lambda ast, tail = tail, wrap = wrap: wrap (AST (tail.op, tail [1], ast))
				tail = tail [2]

			elif tail.op in {'-', '-not'}:
				wrap = lambda ast, tail = tail, wrap = wrap: wrap (AST (tail.op, ast))
				tail = tail [1]

			elif tail.op in {'-lim', '-sum', '-lamb'}:
				wrap = lambda ast, tail = tail, wrap = wrap: wrap (AST (tail.op, ast, *tail [2:]))
				tail = tail [1]

			elif tail.is_diff:
				if (tail.src or not tail.diff.is_var) and not (tail.src and tail.src.is_div and (tail.src.numer.is_mul or tail.src.numer.is_diff_or_part)): # check src from sparser if present
					wrap = lambda ast, tail = tail, wrap = wrap: wrap (AST (tail.op, ast, tail.d, tail.dvs))
					tail = tail.diff

				else:
					break

			elif tail.is_cmp:
				wrap = lambda ast, tail = tail, wrap = wrap: wrap (AST ('<>', tail.lhs, tail.cmp [:-1] + ((tail.cmp [-1] [0], ast),)))
				tail = tail.cmp [-1] [1]

			elif tail.op in {'-sqrt', '-log'}:
				if tail.src: # check src from sparser if present
					wrap = lambda ast, tail = tail, wrap = wrap: wrap (AST (tail.op, ast, *tail [2:]))
					tail = tail.src.mul [1]

				else:
					break

			elif tail.is_func:
				if tail.src and tail.src.mul [0].is_var and tail.src.mul [0].var == tail.func: # check src from sparser if present
					wrap = lambda ast, tail = tail, wrap = wrap: wrap (AST ('-func', tail.func, (ast,)))
					tail = tail.src.mul [1]

				else:
					break

			elif tail.is_piece:
				if tail.piece [-1] [1] is True:
					wrap = lambda ast, tail = tail, wrap = wrap: wrap (AST ('-piece', tail.piece [:-1] + ((ast, True),)))
					tail = tail.piece [-1] [0]
				else:
					wrap = lambda ast, tail = tail, wrap = wrap: wrap (AST ('-piece', tail.piece [:-1] + ((tail.piece [-1] [0], ast),)))
					tail = tail.piece [-1] [1]

			elif tail.is_slice:
				if tail.step:
					wrap = lambda ast, tail = tail, wrap = wrap: wrap (AST ('-slice', tail.start, tail.stop, ast))
					tail = tail.step

				elif tail.step is False:
					break

				elif tail.stop:
					wrap = lambda ast, tail = tail, wrap = wrap: wrap (AST ('-slice', tail.start, ast, None))
					tail = tail.stop

				else:
					break

			else:
				break

		return None, None # var, wrap

	def _as_ufunc_argskw (self):
		args, kw = AST.args2kwargs (self.comma if self.is_comma else (self,) if self.op is not None else self)

		return tuple (args), tuple (sorted (kw.items ()))

	def _free_vars (self): # return set of unique unbound variables found in tree, not reliable especially if used before sxlat due to things like ('-func', 'Derivative', ...), '-subs' is particularly problematic
		def _free_vars (ast, vars):
			if isinstance (ast, AST):
				if ast.is_var:
					if ast.is_var_nonconst and ast.var:
						vars.add (ast)

				elif ast.is_lamb:
					_free_vars (ast.lamb, vars)
					vars.difference_update (('@', v) for v in ast.vars)

				elif ast.is_subs:
					_free_vars (ast.expr, vars)

					for src, dst in ast.subs:
						if not src.is_var_nonconst:
							present = False

						else:
							present = src in vars

							if present:
								vars.remove (src)

						if present:
							_free_vars (dst, vars)

				else:
					try:
						if ast.is_intg:
							_free_vars (ast.intg, vars)

							if ast.is_intg_definite:
								vars.remove (ast.dv.as_var)
							else:
								vars.add (ast.dv.as_var)

						else:
							for e in ast:
								_free_vars (e, vars)

							if ast.is_lim:
								vars.remove (ast.lvar)
							elif ast.is_sum:
								vars.remove (ast.svar)

					except KeyError:
						pass

		vars = set ()

		_free_vars (self, vars)

		return vars

	@staticmethod
	def args2kwargs (args, func = None, ass2cmp = False): # ass2cmp means convert assignment to comparison so it can be represented as Eq() in the py representation of argument list of functions
		func  = (lambda x: x) if func is None else func
		rargs = []
		kw    = []
		itr   = reversed (args)

		for arg in itr:
			if arg.is_ass and not arg.ass_is_not_kw: # ass_is_not_kw comes from sxlat of Eq during testing
				ident = arg.lhs.as_identifier

				if ident is not None:
					kw.append ((ident, func (arg.rhs)))

					continue

				elif ass2cmp: # rewrite assignment = as == for equations passed to functions
					arg = AST ('<>', arg.lhs, (('==', arg.rhs),))

			if ass2cmp:
				rargs = [func (arg)] + [func (AST ('<>', a.lhs, (('==', a.rhs),)) if a.is_ass else a) for a in itr]
			else:
				rargs = [func (arg)] + [func (a) for a in itr]

		return rargs [::-1], OrderedDict (reversed (kw))

	@staticmethod
	def flatcat (op, ast0, ast1): # ,,,/O.o\,,,~~
		if ast0.op == op:
			if ast1.op == op:
				if ast0.is_mul:
					return AST (op, ast0 [1] + ast1 [1], ast0.exp | frozenset (i + ast0.mul.len for i in ast1.exp))
				else:
					return AST (op, ast0 [1] + ast1 [1])

			else: # ast1.op != op:
				if ast0.is_mul:
					return AST (op, ast0 [1] + (ast1,), ast0.exp)
				else:
					return AST (op, ast0 [1] + (ast1,))

		else: # ast0.op != op
			if ast1.op == op:
				if ast1.is_mul:
					return AST (op, (ast0,) + ast1 [1], frozenset (i + 1 for i in ast1.exp))
				else:
					return AST (op, (ast0,) + ast1 [1])

			else:
				return AST (op, (ast0, ast1))

	@staticmethod
	def has (ast, obj):
		if ast == obj:
			return True

		if not isinstance (ast, AST):
			return False

		return any (AST.has (a, obj) for a in ast)

	@staticmethod
	def apply_vars (ast, vars, parent = None, mode = True): # remap vars to assigned expressions and 'execute' funcs which map to lambda vars
		# print ('/n'.join (f'{v} ... {a}' for v, a in vars.items ()) + f'\n{ast}')
		def push (vars, newvars): # create new frame and add new variables, this is really overkill
			frame       = vars.copy ()
			frame ['<'] = vars
			frame ['>'] = newvars

			frame.update (newvars)

			return frame

		def pop (vars, var): # find variable and return frame just below it
			prev = vars.get ('<')

			while prev:
				if var in prev.get ('>', {}):
					return prev

				vars = prev
				prev = vars.get ('<')

			return vars

		def scopeout (vars): # scope out one layer of variables (not frames) and create new frame
			count = vars.get ('#', 1)
			frame = {'<': vars.get ('<', {}), '>': vars.get ('>', vars), '#': count + 1}

			for v in vars:
				if v not in '<>#':
					f = vars

					for _ in range (count):
						while f:
							nvs = f.get ('>', f)
							f   = f.get ('<', {})

							if not f or v in nvs:
								break

					frame [v] = f.get (v)

			return frame

		def index_by_is (seq, obj):
			for i, o in enumerate (seq):
				if o is obj:
					return i

			return None

		# start here
		if not isinstance (ast, AST): # or (ast.is_func and ast.func == AST.Func.NOREMAP): # non-AST, ufunc definition or stop remap
			return ast

		if ast.is_ufunc: # possibly convert non-explicit ufunc to concrete function call if signature matches destination lambda
			if not ((mode is True and not ast.is_ufunc_explicit) or mode == 'lambexec'): # do not map ufuncs to func calls when mapping vars onto themselves, inside lambda definition or is explicit
				return ast

			lamb = vars.get (ast.ufunc)

			if not (lamb and lamb.is_lamb and ast.matches_lamb_sig (lamb)):
				if mode != 'lambexec':
					return ast
				else:
					return AST ('-ufunc', ast.ufunc_full, tuple (AST.apply_vars (a, vars, ast, mode) for a in ast.vars), ast.kw)

			ast = AST ('-func', ast.ufunc, ast.vars)

		if ast.is_num:
			return ast

		elif ast.is_var: # regular var substitution?
			expr = vars.get (ast.var)

			if not expr:
				return ast
			elif not expr.is_lamb:
				return AST.apply_vars (expr, pop (vars, ast.var), ast, mode)

			if parent is None:
				parent = AST.Null

			i = index_by_is (parent.mul, ast) if parent.is_mul else None

			if (parent.op in {None, ';', '@', ',', '[', '-func', '-lamb', '-set', '-dict'} or
					(parent.is_piece and any (p [0] is ast for p in parent.piece)) or
					(i is not None and i < (parent.mul.len - 1) and parent.mul [i + 1].is_paren and (i + 1) not in parent.exp)): # if followed by implicit mul paren then is call not multiply
				return expr

			vars = push (vars, {v: False for v in expr.vars})

			return AST.apply_vars (expr.lamb, vars, ast, mode)

		elif ast.is_subs:
			return AST ('-subs', AST.apply_vars (ast.expr, vars, ast, mode), tuple ((src, AST.apply_vars (dst, vars, ast, mode)) for src, dst in ast.subs)) # without mapping src

		elif ast.op in {'-lim', '-sum'}:
			vars = push (vars, {ast [2].var: False})

			return AST (ast.op, AST.apply_vars (ast [1], vars, ast, mode), ast [2], *(AST.apply_vars (a, vars, ast, mode) for a in ast [3:]))

		elif ast.is_diff:
			dvs = []

			for v, p in ast.dvs: # remap differentials if possible
				a = vars.get (v)

				if a:
					if a.is_var_nonconst:
						v = a.var

				dvs.append ((v, p))

			return AST ('-diff', AST.apply_vars (ast.diff, vars, ast, mode), ast.d, tuple (dvs))

		elif ast.is_intg:
			dv = ast.dv

			if ast.is_intg_definite: # don't map bound var
				v    = dv.var_name
				vars = push (vars, {dv.var_name: False})

			else: # remap differential if indefinite integral and possible
				a = vars.get (ast.dv.var_name)

				if a:
					if a.is_var_nonconst:
						dv = AST ('@', f'd{a.var}')
					else:
						dv = ast.dv

			return AST ('-intg', AST.apply_vars (ast.intg, vars, ast, mode), dv, *(AST.apply_vars (a, vars, ast, mode) for a in ast [3:]))

		elif ast.is_lamb: # lambda definition
			vars = push (vars, {v: False for v in ast.vars})

			return AST ('-lamb', AST.apply_vars (ast.lamb, vars, ast, mode and 'lambdef'), ast.vars)

		elif ast.is_func: # function, might be user lambda call
			if ast.func == AST.Func.NOREMAP:
				return AST.apply_vars (ast.args [0], scopeout (vars), ast, mode)

			else:
				lamb = vars.get (ast.func)

				if lamb and lamb.is_lamb: # 'execute' user lambda
					if ast.args.len == lamb.vars.len:
						vars = push (vars, dict (zip (lamb.vars, ast.args)))

						return AST.apply_vars (lamb.lamb, vars, ast, mode and 'lambexec') # remap lambda vars in body to func args and return body

					elif mode:
						raise TypeError (f"lambda function '{ast.func}' takes {lamb.vars.len} argument(s)")

					return AST ('-func', ast.func,
							tuple (('(', AST.apply_vars (a, vars, ast, mode))
							if (a.is_var and (vars.get (a.var) or AST.VarNull).is_ass)
							else AST.apply_vars (a, vars, ast, mode) for a in ast.args)) # wrap var assignment args in parens to avoid creating kwargs

		return AST (*(AST.apply_vars (a, vars, ast if ast.op else parent, mode) for a in ast))#, **ast._kw)

	@staticmethod
	def register_AST (cls):
		AST._CLS2OP [cls]    = cls.op
		AST._OP2CLS [cls.op] = cls

		AST.OPS.add (cls.op)

		setattr (AST, cls.__name__ [4:], cls)

	@staticmethod
	def EI (state = True):
		AST.CONSTS.difference_update ((AST.E, AST.I))

		AST.E, AST.I = (AST ('@', 'E'), AST ('@', 'I')) if state else (AST ('@', 'e'), AST ('@', 'i'))

		AST.CONSTS.update ((AST.E, AST.I))

#...............................................................................................
class AST_SColon (AST):
	op, is_scolon = ';', True

	def _init (self, scolon):
		self.scolon = scolon

class AST_Ass (AST):
	op, is_ass = '=', True

	def _init (self, lhs, rhs):
		self.lhs, self.rhs = lhs, rhs # should be py form

	@staticmethod
	def ufunc2lamb (ufunc, lamb):
		return AST ('-lamb', lamb, tuple (v.var or 'NONVARIABLE' for v in ufunc.vars))

	def _ass_valid (self):
		def verify (ast, lhs, multi = False):
			for lhs in (lhs if multi else (lhs,)):
				if lhs.is_var_const:
					ast.error = 'The only thing that is constant is change - Heraclitus; Except for constants, they never change - Math...'
				# elif lhs.is_ufunc_explicit:
				# 	ast.error = 'cannot define an undefined function, by definition'
				elif lhs.is_ufunc_impure:
					ast.error = 'cannot assign to a function containing non-variable parameters'
				# elif lhs.is_ufunc_anonymous:
				# 	ast.error = 'cannot assign to an anonymous function'
				else:
					continue

				break

			return ast # convenience

		if self.lhs.is_var:
			return verify (self, self.lhs)

		elif self.lhs.is_comma:
			rhs = self.rhs.strip_paren

			if rhs.op not in {',', '[', '-set'}:
				return verify (self, self.lhs.comma, multi = True)

			else:
				both = min (self.lhs.comma.len, rhs [1].len)
				lrs  = []

				for l, r in zip (self.lhs.comma, rhs [1]):
					if l.is_var:
						lrs.append ((l, r))
					elif l.is_ufunc_implicit:
						lrs.append ((('@', l.ufunc), self.ufunc2lamb (l, r)))
					else:
						return None

				rhs = (rhs.op, tuple (r for _, r in lrs) + rhs [1] [both:])

				return verify (AST ('=', (',', tuple (l for l, _ in lrs) + self.lhs.comma [both:]), ('(', rhs) if self.rhs.is_paren else rhs), self.lhs.comma, multi = True)

		elif self.lhs.is_ufunc_implicit:
			return verify (AST ('=', ('@', self.lhs.ufunc), self.ufunc2lamb (self.lhs, self.rhs)), self.lhs)

		return None

class AST_Cmp (AST):
	op, is_cmp = '<>', True

	TEX2PY = {'\\ne': '!=', '\\le': '<=', '\\ge': '>=', '\\lt': '<', '\\gt': '>', '\\neq': '!=', '\\in': 'in', '\\notin': 'notin'}
	UNI2PY = {'\u2260': '!=', '\u2264': '<=', '\u2265': '>=', '\u2208': 'in', '\u2209': 'notin'}
	ANY2PY = {**UNI2PY, **TEX2PY}
	PY2TEX = {'!=': '\\ne', '<=': '\\le', '>=': '\\ge', 'in': '\\in', 'notin': '\\notin'}
	PYFMT  = {'notin': 'not in'}

	def _init (self, lhs, cmp):
		self.lhs, self.cmp = lhs, cmp # should be py forms (('!=', expr), ('<=', expr), ...)

	_is_cmp_in = lambda self: self.cmp.len == 1 and self.cmp [0] [0] in {'in', 'notin'}

class AST_Num (AST):
	op, is_num = '#', True

	_rec_num   = re.compile (r'^(-?)(\d*[^0.e])?(0*)(?:(\.)(0*)(\d*[^0e])?(0*))?(?:([eE])([+-]?)(\d+))?$') # -101000.000101000e+123 -> (-) (101) (000) (.) (000) (101) (000) (e) (+) (123)

	def _init (self, num):
		self.num = str (num)

	_grp              = lambda self: [g or '' for g in AST_Num._rec_num.match (self.num).groups ()]
	_is_num_pos       = lambda self: not self.grp [0]
	_is_num_neg       = lambda self: bool (self.grp [0])
	_is_num_int       = lambda self: not self.grp [3] and not self.grp [7] # self.num_exp_val >= -len (self.grp [2])
	_is_num_pos_int   = lambda self: self.is_num_int and not self.is_num_neg
	_num_exp          = lambda self: self.grp [8] + self.grp [9]
	_num_mant_and_exp = lambda self: (''.join (self.grp [:7]), self.num_exp)
	_num_exp_val      = lambda self: int (self.num_exp) if self.num_exp else 0
	_as_int           = lambda self: int (self.num)

class AST_Var (AST):
	op, is_var  = '@', True

	GREEK       = ('alpha', 'beta', 'gamma', 'delta', 'epsilon', 'zeta', 'eta', 'theta', 'iota', 'kappa', 'lambda', 'mu', 'nu', 'xi', 'pi', 'rho', 'sigma',
		'tau', 'upsilon', 'phi', 'chi', 'psi', 'omega', 'Gamma', 'Delta', 'Theta', 'Lambda', 'Xi', 'Pi', 'Sigma', 'Upsilon', 'Phi', 'Psi', 'Omega')
	GREEKUNI    = ('\u03b1', '\u03b2', '\u03b3', '\u03b4', '\u03b5', '\u03b6', '\u03b7', '\u03b8', '\u03b9', '\u03ba', '\u03bb', '\u03bc', '\u03bd', '\u03be', '\u03c0', '\u03c1', '\u03c3',
		'\u03c4', '\u03c5', '\u03c6', '\u03c7', '\u03c8', '\u03c9', '\u0393', '\u0394', '\u0398', '\u039b', '\u039e', '\u03a0', '\u03a3', '\u03a5', '\u03a6', '\u03a8', '\u03a9')

	PY2TEXMULTI = {
		'partial'  : ('\\partial',),
		'oo'       : ('\\infty',),
		'zoo'      : ('\\widetilde\\infty', '\\tilde\\infty', '\\overline\\infty', '\\bar\\infty'),
		'Reals'    : ('\\mathbb{R}',),
		'Complexes': ('\\mathbb{C}',),
		'Naturals' : ('\\mathbb{N}', '\\mathbb{N}^*', '\\mathbb{N}^+', '\\mathbb{N}_1', '\\mathbb{N}_{>0}', '\\mathbb{N}_{\\gt0}', '\\mathbb{Z}^+'),
		'Naturals0': ('\\mathbb{N}_0', '\\mathbb{Z}^{\\ge0}'), # , '\\mathbb{N}^0' - ambiguous
		'Integers' : ('\\mathbb{Z}',),
	}

	GREEK2TEX   = dict ((g, f'\\{g}') for g in GREEK)
	PY2TEX      = {**GREEK2TEX, **dict ((p, ts [0]) for p, ts in PY2TEXMULTI.items ())}
	TEX2PY      = {**dict ((f'\\{g}', g) for g in GREEK), **dict ((t, p) for p, ts in PY2TEXMULTI.items () for t in ts)}
	UNI2PY      = {**dict (zip (GREEKUNI, GREEK)), '\u2202': 'partial', '\u221e': 'oo'}
	ANY2PY      = {**UNI2PY, **TEX2PY}

	_rec_groups = re.compile (r"^(?:(?:(d(?!elta))|(partial))(?!_)(?!['\d]))?((.*)(?<!\d)(\d*))$")

	def _init (self, var):
		self.var = var

	_grp                  = lambda self: [g or '' for g in AST_Var._rec_groups.match (self.var).groups ()]
	_is_var_null          = lambda self: not self.var
	_is_var_long          = lambda self: len (self.var) > 1 and self.var not in AST_Var.PY2TEX
	_is_var_const         = lambda self: self in AST.CONSTS
	_is_var_nonconst      = lambda self: self not in AST.CONSTS
	_is_var_lambda        = lambda self: self.var == 'lambda' and self.text != '\\lambda'
	_is_var_single        = lambda self: len (self.var) == 1 or self.var in AST_Var.PY2TEX # is single atomic variable (non-differential, non-subscripted)?
	_is_differential      = lambda self: self.grp [0] and self.grp [2]
	_is_diff_solo         = lambda self: self.grp [0] and not self.grp [2]
	_is_diff_any          = lambda self: self.grp [0]
	_is_partial           = lambda self: self.grp [1] and self.grp [2]
	_is_part_solo         = lambda self: self.grp [1] and not self.grp [2]
	_is_part_any          = lambda self: self.grp [1]
	_is_diff_or_part      = lambda self: (self.grp [0] or self.grp [1]) and self.grp [2]
	_is_diff_or_part_solo = lambda self: (self.grp [0] or self.grp [1]) and not self.grp [2]
	_is_diff_or_part_any  = lambda self: self.grp [0] or self.grp [1]
	_diff_or_part_type    = lambda self: self.grp [0] or self.grp [1] or '' # 'dx' -> 'd', 'partialx' -> 'partial', else ''
	_var_name             = lambda self: self.grp [2]
	_as_var               = lambda self: AST ('@', self.grp [2]) if self.var else self # 'x', dx', 'partialx' -> 'x'
	_as_differential      = lambda self: AST ('@', f'd{self.grp [2]}') if self.var else self # 'x', 'dx', 'partialx' -> 'dx'
	_as_partial           = lambda self: AST ('@', f'partial{self.grp [2]}') if self.var else self # 'x', 'dx', 'partialx' -> 'partialx'
	_text_and_tail_num    = lambda self: (self.grp [3], self.grp [4]) if self.grp [3] and self.grp [3] [-1] != '_' else (self.grp [2], '')

class AST_Attr (AST):
	op, is_attr = '.', True

	def __new__ (cls, obj, attr, args = None):
		self                           = tuple.__new__ (cls, ('.', obj, attr) if args is None else ('.', obj, attr, args))
		self.obj, self.attr, self.args = obj, attr, args

		return self

	_is_attr_var  = lambda self: self.args is None
	_is_attr_func = lambda self: self.args is not None

class AST_Str (AST):
	op, is_str = '"', True

	def _init (self, str_):
		self.str_ = str_

class AST_Comma (AST):
	op, is_comma = ',', True

	def _init (self, comma):
		self.comma = comma

	_is_comma_empty = lambda self: not (self.comma.len)

class AST_Curly (AST):
	op, is_curly = '{', True

	def _init (self, curly):
		self.curly = curly

class AST_Paren (AST):
	op, is_paren = '(', True

	def __new__ (cls, paren, isolated = False):
		self                      = tuple.__new__ (cls, ('(', paren) if not isolated else ('(', paren, True))
		self.paren, self.isolated = paren, isolated

		return self

	_is_paren_isolated = lambda self: self.isolated
	_is_paren_free     = lambda self: not self.isolated

class AST_Brack (AST):
	op, is_brack = '[', True

	def _init (self, brack):
		self.brack = brack

class AST_Abs (AST):
	op, is_abs = '|', True

	def _init (self, abs):
		self.abs = abs

class AST_Minus (AST):
	op, is_minus = '-', True

	def _init (self, minus):
		self.minus = minus

class AST_Fact (AST):
	op, is_fact = '!', True

	def _init (self, fact):
		self.fact = fact

class AST_Add (AST):
	op, is_add = '+', True

	def _init (self, add):
		self.add = add

class AST_Mul (AST):
	op, is_mul = '*', True

	def __new__ (cls, mul, exp = frozenset ()):
		exp                = frozenset (exp)
		self               = tuple.__new__ (cls, ('*', mul, exp) if exp else ('*', mul))
		self.mul, self.exp = mul, exp # exp is optional set of indices of rhses of explicitly specified multiplications

		return self

	def __repr__ (self):
		if not self.exp:
			return tuple.__repr__ (self)

		return tuple.__repr__ (('*', self.mul, set (self.exp)))

	def _is_mul_has_abs (self):
		for m in self.mul:
			if m.is_abs:
				return True

class AST_MulExp (AST): # temporary for isolating explicit multiplications from implicit mul grammar rewriting rules, used only during parsing
	op, is_mulexp = '*exp', True

	def _init (self, mul):
		self.mul = mul

class AST_Div (AST):
	op, is_div = '/', True

	def _init (self, numer, denom):
		self.numer, self.denom = numer, denom

class AST_Pow (AST):
	op, is_pow = '^', True

	def _init (self, base, exp):
		self.base, self.exp = base, exp

class AST_Log (AST):
	op, is_log = '-log', True

	def __new__ (cls, log, base = None):
		self               = tuple.__new__ (cls, ('-log', log) if base is None or base == AST.E else ('-log', log, base))
		self.log, self.base = log, base

		return self

	_is_log_with_base = lambda self: self.base is not None

class AST_Sqrt (AST):
	op, is_sqrt = '-sqrt', True

	def __new__ (cls, rad, idx = None):
		self               = tuple.__new__ (cls, ('-sqrt', rad) if idx is None or idx == AST.Two else ('-sqrt', rad, idx))
		self.rad, self.idx = rad, idx

		return self

	_is_sqrt_with_idx = lambda self: self.idx is not None

class AST_Func (AST):
	op, is_func = '-func', True

	_SYMPY_OBJECTS    = dict ((name, obj) for name, obj in filter (lambda no: no [0] [0] != '_', sp.__dict__.items ()))
	_SYMPY_FUNCS      = set (no [0] for no in filter (lambda no: len (no [0]) > 1 and callable (no [1]), _SYMPY_OBJECTS.items ()))

	NOREMAP           = '@'
	NOEVAL            = '%'

	ADMIN             = {'vars', 'del', 'delall', 'env', 'envreset'}
	PLOT              = {'plotf', 'plotv', 'plotw'}
	PSEUDO            = {NOREMAP, NOEVAL}
	TEXNATIVE         = {'max', 'min', 'arg', 'deg', 'exp', 'gcd', 'Re', 'Im'}
	TRIGH             = {'sin', 'cos', 'tan', 'cot', 'sec', 'csc', 'sinh', 'cosh', 'tanh', 'coth', 'sech', 'csch'}
	BUILTINS          = {'abs', 'all', 'any', 'ascii', 'bin', 'callable', 'chr', 'dir', 'divmod', 'format', 'getattr', 'hasattr', 'hash', 'hex', 'id',
                       'isinstance', 'issubclass', 'iter', 'len', 'max', 'min', 'next', 'oct', 'pow', 'print', 'repr', 'round', 'sorted', 'sum', 'bool',
                       'bytearray', 'bytes', 'complex', 'dict', 'enumerate', 'filter', 'float', 'frozenset', 'property', 'int', 'list', 'map', 'range',
                       'reversed', 'set', 'slice', 'str', 'tuple', 'type', 'zip'}

	PY_TRIGH_INV      = {f'a{f}' for f in TRIGH}
	TEX_TRIGH_INV     = {f'arc{f}' for f in TRIGH}
	TEX2PY_TRIGH_INV  = {f'arc{f}': f'a{f}' for f in TRIGH}

	PY_TRIGH_ALL      = TRIGH | PY_TRIGH_INV
	PYBASE            = (BUILTINS | PY_TRIGH_ALL | _SYMPY_FUNCS) - {'init_printing', 'init_session', 'interactive_traversal'}
	PYALL             = PYBASE | ADMIN | PLOT
	PY                = PYALL - {'sqrt', 'log', 'ln', 'beta', 'gamma', 'zeta', 'Lambda', 'Function', 'Symbol'}
	TEX               = TEXNATIVE | TEX_TRIGH_INV | (TRIGH - {'sech', 'csch'})

	def _init (self, func, args):
		self.func, self.args = func, args

	_is_func_pseudo       = lambda self: self.func in {AST_Func.NOREMAP, AST_Func.NOEVAL}
	_is_func_trigh        = lambda self: self.func in AST_Func.PY_TRIGH_ALL
	_is_func_trigh_inv    = lambda self: self.func in AST_Func.PY_TRIGH_INV
	_is_func_trigh_noninv = lambda self: self.func in AST_Func.TRIGH

class AST_Lim (AST):
	op, is_lim = '-lim', True

	def __new__ (cls, lim, lvar, to, dir = None):
		self                                   = tuple.__new__ (cls, ('-lim', lim, lvar, to) if dir is None else ('-lim', lim, lvar, to, dir))
		self.lim, self.lvar, self.to, self.dir = lim, lvar, to, dir

		return self

class AST_Sum (AST):
	op, is_sum = '-sum', True

	def _init (self, sum, svar, from_, to):
		self.sum, self.svar, self.from_, self.to = sum, svar, from_, to

class AST_Diff (AST):
	op, is_diff = '-diff', True

	def _init (self, diff, d, dvs):
		self.diff, self.d, self.dvs = diff, d, dvs

	_is_diff_d         = lambda self: self.d == 'd'
	_is_diff_partial   = lambda self: self.d == 'partial'
	_is_diff_dvdv      = lambda self: self.d == 'd' and self.dvs.len == 1 # and self.diff.is_var and self.dvs [0] [1] == 1
	_is_diff_any_ufunc = lambda self: self.diff.strip_paren1.is_ufunc
	_diff_any          = lambda self: self.diff.strip_paren1

class AST_DiffP (AST):
	op, is_diffp = '-diffp', True

	def _init (self, diffp, count):
		self.diffp, self.count = diffp, count

	_is_diff_any_ufunc = lambda self: self.diffp.is_ufunc
	_diff_any          = lambda self: self.diffp

class AST_Intg (AST):
	op, is_intg = '-intg', True

	def __new__ (cls, intg, dv, from_ = None, to = None):
		self                                    = tuple.__new__ (cls, ('-intg', intg, dv) if from_ is None else ('-intg', intg, dv, from_, to))
		self.intg, self.dv, self.from_, self.to = intg, dv, from_, to

		return self

	_is_intg_definite = lambda self: self.from_ is not None

class AST_Mat (AST):
	op, is_mat = '-mat', True

	def _init (self, mat):
		self.mat = mat

	_rows          = lambda self: self.mat.len
	_cols          = lambda self: self.mat [0].len if self.mat else 0
	_is_mat_column = lambda self: self.rows and self.cols == 1

class AST_Piece (AST):
	op, is_piece = '-piece', True

	def _init (self, piece):
		self.piece = piece

class AST_Lamb (AST):
	op, is_lamb = '-lamb', True

	def _init (self, lamb, vars):
		self.lamb, self.vars = lamb, vars

class AST_Idx (AST):
	op, is_idx = '-idx', True

	def _init (self, obj, idx):
		self.obj, self.idx = obj, idx

class AST_Slice (AST):
	op, is_slice = '-slice', True

	def _init (self, start, stop, step):
		self.start, self.stop, self.step = start, stop, step

class AST_Set (AST):
	op, is_set = '-set', True

	def _init (self, set):
		self.set = set

class AST_Dict (AST):
	op, is_dict = '-dict', True

	def _init (self, dict):
		self.dict = dict

class AST_Union (AST):
	op, is_union = '||', True

	def _init (self, union):
		self.union = union

class AST_SDiff (AST): # symmetric difference
	op, is_sdiff = '^^', True

	def _init (self, sdiff):
		self.sdiff = sdiff

class AST_XSect (AST): # intersection
	op, is_xsect = '&&', True

	def _init (self, xsect):
		self.xsect = xsect

class AST_Or (AST):
	op, is_or = '-or', True

	def _init (self, or_):
		self.or_ = or_

class AST_And (AST):
	op, is_and = '-and', True

	def _init (self, and_):
		self.and_ = and_

class AST_Not (AST):
	op, is_not = '-not', True

	def _init (self, not_):
		self.not_ = not_

class AST_UFunc (AST):
	op, is_ufunc = '-ufunc', True

	def __new__ (cls, ufunc, vars, kw = None):
		self                           = tuple.__new__ (cls, ('-ufunc', ufunc, vars, kw) if kw else ('-ufunc', ufunc, vars))
		self.ufunc, self.vars, self.kw = ufunc.lstrip ('?'), vars, kw or AST.Null
		self.ufunc_full                = ufunc

		return self

	_is_ufunc_explicit  = lambda self: self.ufunc_full.startswith ('?')
	_is_ufunc_implicit  = lambda self: not self.ufunc_full.startswith ('?')
	_is_ufunc_named     = lambda self: self.ufunc
	_is_ufunc_anonymous = lambda self: not self.ufunc
	_is_ufunc_applied   = lambda self: self.vars
	_is_ufunc_unapplied = lambda self: not self.vars
	_is_ufunc_pure      = lambda self: self.vars and all (v.is_var_nonconst for v in self.vars)
	_is_ufunc_impure    = lambda self: self.vars and any (not v.is_var_nonconst for v in self.vars)

	matches_lamb_sig    = lambda self, lamb: self.vars and self.vars.len == lamb.vars.len

	@staticmethod
	def valid_implicit_args (args):
		return not any (not a.is_var and not a.is_const for a in args)

	def apply_argskw (self, argskw):
		if not argskw or argskw [1]:
			return None

		args = argskw [0]
		subs = []

		if self.vars:
			if len (args) != self.vars.len:
				return None

			for v, a in zip (self.vars, args):
				if not v.is_var_nonconst: # or (a.is_var_nonconst and a.var != v.var):
					return None

				if a != v:
					subs.append ((v, a))

		return AST ('-ufunc', self.ufunc_full, args, self.kw, ufunc_subs = tuple (subs))

class AST_Subs (AST):
	op, is_subs = '-subs', True

	def _init (self, expr, subs):
		self.expr, self.subs = expr, subs

	_is_subs_diff_ufunc     = lambda self: self.expr.is_diff and self.expr.diff.strip_paren1.is_ufunc
	_is_subs_diffp_ufunc    = lambda self: self.expr.is_diffp and self.expr.diffp.is_ufunc
	_is_subs_diff_d_ufunc   = lambda self: self.expr.is_diff_d and self.expr.diff.strip_paren1.is_ufunc
	_is_subs_diff_any_ufunc = lambda self: (self.expr.is_diff and self.expr.diff.strip_paren1.is_ufunc) or (self.expr.is_diffp and self.expr.diffp.is_ufunc)

class AST_Sym (AST):
	op, is_sym = '-sym', True

	def __new__ (cls, sym, kw = ()):
		self              = tuple.__new__ (cls, ('-sym', sym, kw) if kw else ('-sym', sym))
		self.sym, self.kw = sym, kw

		return self

	_is_sym_anonymous   = lambda self: not self.sym
	_is_sym_unqualified = lambda self: not self.kw

#...............................................................................................
_AST_CLASSES = [AST_SColon, AST_Ass, AST_Cmp, AST_Num, AST_Var, AST_Attr, AST_Str, AST_Comma, AST_Curly, AST_Paren,
	AST_Brack, AST_Abs, AST_Minus, AST_Fact, AST_Add, AST_Mul, AST_MulExp, AST_Div, AST_Pow, AST_Log, AST_Sqrt, AST_Func,
	AST_Lim, AST_Sum, AST_Diff, AST_DiffP, AST_Intg, AST_Mat, AST_Piece, AST_Lamb, AST_Idx, AST_Slice, AST_Set, AST_Dict,
	AST_Union, AST_SDiff, AST_XSect, AST_Or, AST_And, AST_Not, AST_UFunc, AST_Subs, AST_Sym]

for _cls in _AST_CLASSES:
	AST.register_AST (_cls)

_AST_CONSTS = (('E', 'e'), ('I', 'i'), ('Pi', 'pi'), ('Infty', 'oo'), ('CInfty', 'zoo'), ('None_', 'None'), ('True_', 'True'), ('False_', 'False'), ('NaN', 'nan'),
	('Naturals', 'Naturals'), ('Naturals0', 'Naturals0'), ('Integers', 'Integers'), ('Reals', 'Reals'), ('Complexes', 'Complexes'))

for _vp, _vv in _AST_CONSTS:
	ast = AST ('@', _vv)

	AST.CONSTS.add (ast)
	setattr (AST, _vp, ast)

AST.Null       = AST ()
AST.Zero       = AST ('#', '0')
AST.One        = AST ('#', '1')
AST.Two        = AST ('#', '2')
AST.NegOne     = AST ('#', '-1')
AST.VarNull    = AST ('@', '')
AST.CommaEmpty = AST (',', ())
AST.MatEmpty   = AST ('-mat', ())
AST.SetEmpty   = AST ('-set', ())
AST.DictEmpty  = AST ('-dict', ())

# AST translations for funtions to display or convert to internal AST or SymPy S() escaping.

import itertools as it


_SX_XLAT_AND       = True # ability to turn off And translation for testing
_SX_READ_PY_ASS_EQ = False # ability to parse py Eq() functions which were marked as assignment for testing

_AST_StrPlus = AST ('"', '+')

#...............................................................................................
def _xlat_f2a_slice (*args):
	if len (args) == 1:
		return AST ('-slice', False, False if args [0] == AST.None_ else args [0], None)
	if len (args) == 2:
		return AST ('-slice', False if args [0] == AST.None_ else args [0], False if args [1] == AST.None_ else args [1], None)
	else:
		return AST ('-slice', False if args [0] == AST.None_ else args [0], False if args [1] == AST.None_ else args [1], args [2] if args [2] != AST.None_ else None if args [1] == AST.None_ else False)

_xlat_f2a_Add_invert = {'==': '==', '!=': '!=', '<': '>', '<=': '>=', '>': '<', '>=': '<='}

def _xlat_f2a_And (*args, canon = False, force = False): # patch together out of order extended comparison objects potentially inverting comparisons
	def concat (lhs, rhs):
		return AST ('<>', lhs.lhs, lhs.cmp + rhs.cmp)

	def invert (ast):
		cmp = []
		lhs = ast.lhs

		for c in ast.cmp:
			v = _xlat_f2a_Add_invert.get (c [0])

			if v is None:
				return None

			cmp.append ((v, lhs))

			lhs = c [1]

		return AST ('<>', lhs, tuple (cmp [::-1]))

	def match (ast):
		li, ll = None, 0
		ri, rl = None, 0

		for i in range (len (args)):
			if args [i].is_cmp:
				if ast.lhs == args [i].cmp [-1] [1] and (li is None or args [i].cmp.len > ll):
					li, ll = i, args [i].cmp.len

				if ast.cmp [-1] [1] == args [i].lhs and (ri is None or args [i].cmp.len > rl):
					ri, rl = i, args [i].cmp.len

		return li, ri, ll + rl

	def canonicalize (ast):
		return invert (ast) if (canon and ast.is_cmp and sum ((r [0] == '>') - (r [0] == '<') for r, c in ast.cmp) > 0) else ast

	def count_ops (ast):
		if ast.is_and:
			return ast.and_.len - 1 + sum (count_ops (a) for a in ast.and_)
		elif ast.is_cmp:
			return ast.cmp.len + count_ops (ast.lhs) + sum (count_ops (ra [1]) for ra in ast.cmp)

		return 0

	# start here
	if not _SX_XLAT_AND and not force:
		return None # AST ('-func', 'And', args)

	and_ = AST ('-and', tuple (args)) # simple and
	andc = [args [0]] # build concatenated and from comparisons

	for arg in args [1:]:
		if arg.is_cmp and andc [-1].is_cmp and arg.lhs == andc [-1].cmp [-1] [1]:
			andc [-1] = AST ('<>', andc [-1].lhs, andc [-1].cmp + arg.cmp)
		else:
			andc.append (arg)

	andc = AST ('-and', tuple (andc)) if len (andc) > 1 else andc [0]
	itr  = iter (args)
	args = []

	for arg in itr: # build optimized and
		if not args or not arg.is_cmp:
			args.append (arg)

		else:
			while 1:
				li, ri, l = match (arg)
				argv      = invert (arg)

				if argv is not None:
					liv, riv, lv = match (argv)

					if lv > l:
						li, ri = liv, riv
						arg    = argv

				if li is None or li == ri:
					if ri is None:
						args.append (arg)
						break

					else:
						arg = concat (arg, args [ri])
						del args [ri]

				elif ri is None:
					arg = concat (args [li], arg)
					del args [li]

				else:
					i1, i2 = min (li, ri), max (li, ri)
					arg    = concat (concat (args [li], arg), args [ri])

					del args [i2], args [i1]

	if len (args) == 1:
		ast = canonicalize (args [0])
	else:
		ast = AST ('-and', tuple (canonicalize (a) for a in args))

	return min (andc, and_, ast, key = lambda a: count_ops (a))

def _xlat_f2a_Lambda (args, expr):
	args = args.strip_paren
	args = args.comma if args.is_comma else (args,)
	vars = []

	for v in args:
		if not v.is_var_nonconst:
			return None

		vars.append (v.var)

	return AST ('-lamb', expr, tuple (vars))

def _xlat_f2a_Pow (ast = AST.VarNull, exp = AST.VarNull):
	return AST ('^', ast, exp)

def _xlat_f2a_Matrix (ast = AST.VarNull):
	if ast.is_var_null:
		return AST.MatEmpty

	if ast.is_brack:
		if not ast.brack:
			return AST.MatEmpty

		elif not ast.brack [0].is_brack: # single layer or brackets, column matrix?
			return AST ('-mat', tuple ((c,) for c in ast.brack))

		elif ast.brack [0].brack:
			rows = [ast.brack [0].brack]
			cols = len (rows [0])

			for row in ast.brack [1 : -1]:
				if row.brack.len != cols:
					break

				rows.append (row.brack)

			else:
				l = ast.brack [-1].brack.len

				if l <= cols:
					if ast.brack.len > 1:
						rows.append (ast.brack [-1].brack + (AST.VarNull,) * (cols - l))

					if l != cols:
						return AST ('-mat', tuple (rows))
					elif cols > 1:
						return AST ('-mat', tuple (rows))
					else:
						return AST ('-mat', tuple ((r [0],) for r in rows))

	return None

def _xlat_f2a_Piecewise (*args):
	pcs = []

	if not args or args [0].is_var_null:
		return AST ('-piece', ((AST.VarNull, AST.VarNull),))

	if len (args) > 1:
		for c in args [:-1]:
			c = c.strip

			if not c.is_comma or c.comma.len != 2:
				return None

			pcs.append (c.comma)

	ast = args [-1]

	if not ast.is_paren:
		return None

	ast = ast.strip
	pcs = tuple (pcs)

	if not ast.is_comma:
		return AST ('-piece', pcs + ((ast, AST.VarNull),))
	elif ast.comma.len == 0:
		return AST ('-piece', pcs + ())

	if not ast.comma [0].is_comma:
		if ast.comma.len == 1:
			return AST ('-piece', pcs + ((ast.comma [0], AST.VarNull),))
		elif ast.comma.len == 2:
			return AST ('-piece', pcs + ((ast.comma [0], True if ast.comma [1] == AST.True_ else ast.comma [1]),))

	return None

def _xlat_f2a_Derivative_NAT (ast = AST.VarNull, *dvs, **kw):
	if not kw:
		return _xlat_f2a_Derivative (ast, *dvs)

def _xlat_f2a_Derivative (ast = AST.VarNull, *dvs, **kw):
	ds = []

	if not dvs:
		if ast.is_diffp:
			return AST ('-diffp', ast.diffp, ast.count + 1)
		else:
			return AST ('-diffp', ast, 1)

	else:
		dvs = list (dvs [::-1])

		while dvs:
			v = dvs.pop ()

			if not v.is_var:
				return None

			ds.append ((v.var, dvs.pop ().as_int if dvs and dvs [-1].is_num_pos_int else 1))

	return AST ('-diff', ast, 'd', tuple (ds))

def _xlat_f2a_Integral_NAT (ast = None, dvab = None, *args, **kw):
	if not kw:
		return _xlat_f2a_Integral (ast, dvab, *args, **kw)

def _xlat_f2a_Integral (ast = None, dvab = None, *args, **kw):
	if ast is None:
		return AST ('-intg', AST.VarNull, AST.VarNull)

	if dvab is None:
		vars = ast.free_vars

		if len (vars) == 1:
			return AST ('-intg', ast, ('@', f'd{vars.pop ().var}'))

		return AST ('-intg', ast, AST.VarNull)

	dvab = dvab.strip_paren
	ast2 = None

	if dvab.is_comma:
		if dvab.comma and dvab.comma [0].is_var:#_nonconst:
			if dvab.comma.len == 1:
				ast2 = AST ('-intg', ast, ('@', f'd{dvab.comma [0].var}'))
			elif dvab.comma.len == 2:
				ast2 = AST ('-intg', ast, ('@', f'd{dvab.comma [0].var}'), AST.Zero, dvab.comma [1])
			elif dvab.comma.len == 3:
				ast2 = AST ('-intg', ast, ('@', f'd{dvab.comma [0].var}'), dvab.comma [1], dvab.comma [2])

	elif dvab.is_var:
		ast2 = AST ('-intg', ast, ('@', f'd{dvab.var}'))

	if ast2 is None:
		return None

	return _xlat_f2a_Integral (ast2, *args) if args else ast2

_xlat_f2a_Limit_dirs = {AST ('"', '+'): ('+',), AST ('"', '-'): ('-',), AST ('"', '+-'): ()}

def _xlat_f2a_Limit (ast = AST.VarNull, var = AST.VarNull, to = AST.VarNull, dir = _AST_StrPlus):
	if var.is_var_nonconst:
		return AST ('-lim', ast, var, to, *_xlat_f2a_Limit_dirs [dir])

	return None

def _xlat_f2a_Sum_NAT (ast = AST.VarNull, ab = None, **kw):
	if not kw:
		return _xlat_f2a_Sum (ast, ab, **kw)

	return None

def _xlat_f2a_Sum (ast = AST.VarNull, ab = None, **kw):
	if ab is None:
		return AST ('-sum', ast, AST.VarNull, AST.VarNull, AST.VarNull)

	ab = ab.strip_paren

	if ab.is_var:
		return AST ('-sum', ast, ab, AST.VarNull, AST.VarNull)
	elif ab.is_comma and ab.comma and ab.comma.len <= 3 and ab.comma [0].is_var:
		return AST ('-sum', ast, *ab.comma, *((AST.VarNull,) * (3 - ab.comma.len)))

	return None

def _xlat_f2a_SymmetricDifference (*args):
	if len (args) != 2:
		return None

	if not args [0].is_sdiff:
		return AST ('^^', args)

	return AST ('^^', args [0].sdiff + (args [1],))

def _xlat_f2a_Subs (expr = None, src = None, dst = None):
	def parse_subs (src, dst):
		if src is None:
			return ((AST.VarNull, AST.VarNull),)

		src = src.strip_paren.comma if src.strip_paren.is_comma else (src,) # (src.strip_paren,)

		if dst is None:
			return tuple (it.zip_longest (src, (), fillvalue = AST.VarNull))

		dst = dst.strip_paren.comma if dst.strip_paren.is_comma else (dst,) # (dst.stip_paren,)

		if len (dst) > len (src):
			return None

		return tuple (it.zip_longest (src, dst, fillvalue = AST.VarNull))

	# start here
	if expr is None:
		return AST ('-subs', AST.VarNull, ((AST.VarNull, AST.VarNull),))

	subs = parse_subs (src, dst)

	if subs is None:
		return None

	if expr.is_subs:
		return AST ('-subs', expr.expr, expr.subs + subs)
	else:
		return AST ('-subs', expr, subs)

def _xlat_f2a_subs (expr, src = AST.VarNull, dst = None):
	def parse_subs (src, dst):
		if dst is not None:
			return ((src, dst),)

		src = src.strip_paren

		if src.is_dict:
			return src.dict
		elif src.op not in {',', '[', '-set'}:
			return None # ((src, AST.VarNull),)

		else:
			subs = []

			for arg in src [1]:
				ast = arg.strip_paren

				if ast.op in {',', '['} and ast [1].len <= 2:
					subs.append ((ast [1] + (AST.VarNull, AST.VarNull)) [:2])
				elif arg.is_paren and arg is src [1] [-1]:
					subs.append ((ast, AST.VarNull))
				else:
					return None

			return tuple (subs)

	# start here
	subs = parse_subs (src, dst)

	if subs is None:
		return None

	if expr.is_subs: # collapse multiple subs into one
		return AST ('-subs', expr.expr, expr.subs + subs)

	return AST ('-subs', expr, subs)

#...............................................................................................
_XLAT_FUNC2AST_REIM = {
	'Re'                   : lambda *args: AST ('-func', 're', tuple (args)),
	'Im'                   : lambda *args: AST ('-func', 'im', tuple (args)),
}

_XLAT_FUNC2AST_ALL = {
	'Subs'                 : _xlat_f2a_Subs,
	'.subs'                : _xlat_f2a_subs,
}

_XLAT_FUNC2AST_TEXNATPY = {**_XLAT_FUNC2AST_ALL,
	'slice'                : _xlat_f2a_slice,

	'Eq'                   : lambda a, b, *args: AST ('<>', a, (('==', b),)) if not args else AST ('=', a, b, ass_is_not_kw = True) if _SX_READ_PY_ASS_EQ else None, # extra *args is for marking as assignment during testing
	'Ne'                   : lambda a, b: AST ('<>', a, (('!=', b),)),
	'Lt'                   : lambda a, b: AST ('<>', a, (('<',  b),)),
	'Le'                   : lambda a, b: AST ('<>', a, (('<=', b),)),
	'Gt'                   : lambda a, b: AST ('<>', a, (('>',  b),)),
	'Ge'                   : lambda a, b: AST ('<>', a, (('>=', b),)),

	'Or'                   : lambda *args: AST ('-or', tuple (args)),
	'And'                  : _xlat_f2a_And,
	'Not'                  : lambda not_: AST ('-not', not_),
}

_XLAT_FUNC2AST_TEXNAT = {**_XLAT_FUNC2AST_TEXNATPY,
	'S'                    : lambda ast, **kw: ast if ast.is_num and not kw else None,

	'abs'                  : lambda *args: AST ('|', args [0] if len (args) == 1 else AST (',', args)),
	'Abs'                  : lambda *args: AST ('|', args [0] if len (args) == 1 else AST (',', args)),
	'exp'                  : lambda ast: AST ('^', AST.E, ast),
	'factorial'            : lambda ast: AST ('!', ast),
	'Lambda'               : _xlat_f2a_Lambda,
	'Matrix'               : _xlat_f2a_Matrix,
	'MutableDenseMatrix'   : _xlat_f2a_Matrix,
	'Piecewise'            : _xlat_f2a_Piecewise,
	'Pow'                  : _xlat_f2a_Pow,
	'pow'                  : _xlat_f2a_Pow,
	'Tuple'                : lambda *args: AST ('(', (',', args)),

	'Limit'                : _xlat_f2a_Limit,
	'limit'                : _xlat_f2a_Limit,

	'EmptySet'             : lambda *args: AST.SetEmpty,
	'FiniteSet'            : lambda *args: AST ('-set', tuple (args)),
	'Contains'             : lambda a, b: AST ('<>', a, (('in', b),)),
	'Complement'           : lambda *args: AST ('+', (args [0], ('-', args [1]))),
	'Union'                : lambda *args: AST ('||', tuple (args)), # _xlat_f2a_Union,
	'SymmetricDifference'  : _xlat_f2a_SymmetricDifference,
	'Intersection'         : lambda *args: AST ('&&', tuple (args)),
}

XLAT_FUNC2AST_TEX = {**_XLAT_FUNC2AST_TEXNAT,
	'Add'                  : lambda *args, **kw: AST ('+', args),
	'Mul'                  : lambda *args, **kw: AST ('*', args),

	'Derivative'           : _xlat_f2a_Derivative,
	'Integral'             : _xlat_f2a_Integral,
	'Sum'                  : _xlat_f2a_Sum,
	'diff'                 : _xlat_f2a_Derivative,
	'integrate'            : _xlat_f2a_Integral,
	'summation'            : _xlat_f2a_Sum,

	'SparseMatrix'         : _xlat_f2a_Matrix,
	'MutableSparseMatrix'  : _xlat_f2a_Matrix,
	'ImmutableDenseMatrix' : _xlat_f2a_Matrix,
	'ImmutableSparseMatrix': _xlat_f2a_Matrix,

	'diag'                 : True,
	'eye'                  : True,
	'ones'                 : True,
	'zeros'                : True,
}

XLAT_FUNC2AST_NAT = {**_XLAT_FUNC2AST_TEXNAT, **_XLAT_FUNC2AST_REIM,
	'Add'                  : lambda *args, **kw: None if kw else AST ('+', args),
	'Mul'                  : lambda *args, **kw: None if kw else AST ('*', args),

	'Derivative'           : _xlat_f2a_Derivative_NAT,
	'Integral'             : _xlat_f2a_Integral_NAT,
	'Sum'                  : _xlat_f2a_Sum_NAT,
}

XLAT_FUNC2AST_PY  = {**_XLAT_FUNC2AST_TEXNATPY, **_XLAT_FUNC2AST_REIM,
	'Gamma'                : lambda *args: AST ('-func', 'gamma', tuple (args)),
}

XLAT_FUNC2AST_SPARSER = {
	'Lambda'               : _xlat_f2a_Lambda,
	'Limit'                : _xlat_f2a_Limit,
	'Sum'                  : _xlat_f2a_Sum_NAT,
	'Derivative'           : _xlat_f2a_Derivative_NAT,
	'Integral'             : _xlat_f2a_Integral_NAT,
	'Subs'                 : _xlat_f2a_Subs,
	'.subs'                : _xlat_f2a_subs,
}

XLAT_FUNC2AST_SPT = XLAT_FUNC2AST_PY

def xlat_funcs2asts (ast, xlat, func_call = None, recurse = True): # translate eligible functions in tree to other AST representations
	if not isinstance (ast, AST):
		return ast

	if ast.is_func:
		xact = xlat.get (ast.func)
		args = ast.args
		ret  = lambda: AST ('-func', ast.func, args)

	elif ast.is_attr_func:
		xact = xlat.get (f'.{ast.attr}')
		args = (ast.obj,) + ast.args
		ret  = lambda: AST ('.', args [0], ast.attr, tuple (args [1:]))

	else:
		xact = None

	if xact is not None:
		if recurse:
			args = AST (*(xlat_funcs2asts (a, xlat, func_call = func_call) for a in args))

		try:
			if xact is True: # True means execute function and use return value for ast, only happens for -func
				return func_call (ast.func, args) # not checking func_call None because that should never happen

			xargs, xkw = AST.args2kwargs (args)
			ast2       = xact (*xargs, **xkw)

			if ast2 is not None:
				return ast2

		except:
			pass

		return ret ()

	if recurse:
		return AST (*(xlat_funcs2asts (a, xlat, func_call = func_call) for a in ast))#, **ast._kw)

	return ast

#...............................................................................................
_XLAT_FUNC2TEX = {
	'beta'    : lambda ast2tex, *args: f'\\beta{{\\left({ast2tex (AST.tuple2argskw (args))} \\right)}}',
	'gamma'   : lambda ast2tex, *args: f'\\Gamma{{\\left({ast2tex (AST.tuple2argskw (args))} \\right)}}',
	'Gamma'   : lambda ast2tex, *args: f'\\Gamma{{\\left({ast2tex (AST.tuple2argskw (args))} \\right)}}',
	'Lambda'  : lambda ast2tex, *args: f'\\Lambda{{\\left({ast2tex (AST.tuple2argskw (args))} \\right)}}',
	'zeta'    : lambda ast2tex, *args: f'\\zeta{{\\left({ast2tex (AST.tuple2argskw (args))} \\right)}}',

	're'      : lambda ast2tex, *args: f'\\Re{{\\left({ast2tex (AST.tuple2argskw (args))} \\right)}}',
	'im'      : lambda ast2tex, *args: f'\\Im{{\\left({ast2tex (AST.tuple2argskw (args))} \\right)}}',

	'binomial': lambda ast2tex, *args: f'\\binom{{{ast2tex (args [0])}}}{{{ast2tex (args [1])}}}' if len (args) == 2 else None,
	'set'     : lambda ast2tex, *args: '\\emptyset' if not args else None,
}

_XLAT_ATTRFUNC2TEX = {
	'diff'     : lambda ast2tex, ast, *dvs, **kw: ast2tex (_xlat_f2a_Derivative (ast, *dvs, **kw)),
	'integrate': lambda ast2tex, ast, dvab = None, *args, **kw: ast2tex (_xlat_f2a_Integral (ast, dvab, *args, **kw)),
	'limit'    : lambda ast2tex, ast, var = AST.VarNull, to = AST.VarNull, dir = _AST_StrPlus: ast2tex (_xlat_f2a_Limit (ast, var, to, dir)),
}

def xlat_func2tex (ast, ast2tex):
	xact = _XLAT_FUNC2TEX.get (ast.func)

	if xact:
		args, kw = AST.args2kwargs (ast.args)

		try:
			return xact (ast2tex, *args, **kw)
		except:
			pass

	return None

def xlat_attr2tex (ast, ast2tex):
	if ast.is_attr_func:
		xact = _XLAT_ATTRFUNC2TEX.get (ast.attr)

		if xact:
			args, kw = AST.args2kwargs (ast.args)

			try:
				return xact (ast2tex, ast.obj, *args, **kw)
			except:
				pass

	return None

#...............................................................................................
def _xlat_pyS (ast, need = False): # Python S(1)/2 escaping where necessary
	if not isinstance (ast, AST):
		return ast, False

	if ast.is_num:
		if need:
			return AST ('-func', 'S', (ast,)), True
		else:
			return ast, False

	if ast.is_comma or ast.is_brack:
		return AST (ast.op, tuple (_xlat_pyS (a) [0] for a in ast [1])), False

	if ast.is_curly or ast.is_paren or ast.is_minus:
		expr, has = _xlat_pyS (ast [1], need)

		return AST (ast.op, expr), has

	if ast.is_add or ast.is_mul:
		es  = [_xlat_pyS (a) for a in ast [1] [1:]]
		has = any (e [1] for e in es)
		e0  = _xlat_pyS (ast [1] [0], need and not has)
		es  = (e0 [0],) + tuple (e [0] for e in es)

		return (AST ('+', es) if ast.is_add else AST ('*', es, ast.exp)), has or e0 [1]

	if ast.is_div:
		denom, has = _xlat_pyS (ast.denom)
		numer      = _xlat_pyS (ast.numer, not has) [0]

		return AST ('/', numer, denom), True

	if ast.is_pow:
		exp, has = _xlat_pyS (ast.exp)
		base     = _xlat_pyS (ast.base, not (has or exp.is_num_pos)) [0]

		return AST ('^', base, exp), True

	es = [_xlat_pyS (a) for a in ast]

	return AST (*tuple (e [0] for e in es)), \
			ast.op in {'=', '<>', '@', '.', '|', '!', '-log', '-sqrt', '-func', '-lim', '-sum', '-diff', '-intg', '-mat', '-piece', '-lamb', '||', '^^', '&&', '-or', '-and', '-not', '-ufunc', '-subs'} or any (e [1] for e in es)

xlat_pyS = lambda ast: _xlat_pyS (ast) [0]

#...............................................................................................
class sxlat: # for single script
	XLAT_FUNC2AST_SPARSER = XLAT_FUNC2AST_SPARSER
	XLAT_FUNC2AST_TEX     = XLAT_FUNC2AST_TEX
	XLAT_FUNC2AST_NAT     = XLAT_FUNC2AST_NAT
	XLAT_FUNC2AST_PY      = XLAT_FUNC2AST_PY
	XLAT_FUNC2AST_SPT     = XLAT_FUNC2AST_SPT
	xlat_funcs2asts       = xlat_funcs2asts
	xlat_func2tex         = xlat_func2tex
	xlat_attr2tex         = xlat_attr2tex
	xlat_pyS              = xlat_pyS
	_xlat_f2a_And         = _xlat_f2a_And

# Convert between internal AST and SymPy expressions and write out LaTeX, native shorthand and Python code.
# Here be dragons! MUST REFACTOR AT SOME POINT FOR THE LOVE OF ALL THAT IS GOOD AND PURE!

from ast import literal_eval
from collections import OrderedDict
from functools import reduce
import re
import sympy as sp
from sympy.core.cache import clear_cache
from sympy.core.function import AppliedUndef as sp_AppliedUndef


_SYM_MARK_PY_ASS_EQ = False # for testing to write extra information into python text representation of Eq() if is assignment

_TEX_SPACE      = '\\ ' # explicit LaTeX space

_SYM_ASSUM_REDUCE = {(): ()} # cache to reduce extended lists of assumptions to single or a few assumptions which generated them

_SYM_USER_VARS  = {} # flattened user vars {name: ast, ...}
_SYM_USER_FUNCS = set () # set of user funcs present {name, ...} - including hidden N and gamma and the like
_SYM_USER_ALL   = {} # all funcs and vars dict, user funcs not in vars stored as AST.Null

_POST_SIMPLIFY  = False # post-evaluation simplification
_PYS            = True # Python S() escaping
_DOIT           = True # expression doit()
_MUL_RATIONAL   = False # products should lead with a rational fraction if one is present instead of absorbing into it
_STRICT_TEX     = False # strict LaTeX formatting to assure copy-in ability of generated tex
_QUICK_MODE     = False # quick input mode affects variable spacing in products

_None = object () # unique non-None None sentinel

class AST_Text (AST): # for displaying elements we do not know how to handle, only returned from SymPy processing, not passed in
	op, is_text = '-text', True

	def _init (self, tex = None, nat = None, py = None, spt = None):
		self.tex, self.nat, self.py, self.spt = tex, nat, py, spt

AST.register_AST (AST_Text)

class EqAss (sp.Eq): pass # explicit assignment instead of equality comparison
class EqCmp (sp.Eq): pass # explicit equality comparison instead of assignment

class IdLambda (sp.Lambda): # identity lambda - having SymPy remap Lambda (y, y) to Lambda (_x, _x) is really annoying
	def __new__ (cls, a, l, **kw):
		self = sp.Expr.__new__(cls, sp.Tuple (l), l) # skip Lambda.__new__ to avoid creating identity function

		return self

class NoEval (sp.Expr): # prevent any kind of evaluation on AST on instantiation or doit, args = (str (AST), sp.S.One)
	is_number    = False
	free_symbols = set ()

	def __new__ (cls, ast):
		self      = sp.Expr.__new__ (cls, str (ast))
		self._ast = ast

		return self

	def doit (self, *args, **kw):
		return self

	def ast (self):
		return AST (*literal_eval (self._ast)) if isinstance (self._ast, str) else self._ast # SymPy might have re-create this object using string argument

class Callable: # for wrapping SymPy concrete functions mapped to variables
	def __init__ (self, ast, callable_):
		self.ast       = ast
		self.callable_ = callable_

	def __call__ (self, *args, **kw):
		return self.callable_ (*args, **kw)

def _raise (exc):
	raise exc

def _sympify (spt, sympify = sp.sympify, fallback = None): # try to sympify argument with optional fallback conversion function
	try:
		return sympify (spt)

	except:
		if fallback is True: # True for return original value
			return spt
		elif fallback is None:
			raise

		else:
			try:
				return fallback (spt)
			except Exception as e:
				raise e from None

def _free_symbols (spt): # extend sympy .free_symbols into standard python containers
	if isinstance (spt, (None.__class__, bool, int, float, complex, str)):
		pass # nop
	elif isinstance (spt, (tuple, list, set, frozenset)):
		return set ().union (*(_free_symbols (s) for s in spt))
	elif isinstance (spt, slice):
		return _free_symbols (spt.start).union (_free_symbols (spt.stop), _free_symbols (spt.step))
	elif isinstance (spt, dict):
		return set ().union (*(_free_symbols (s) for s in sum (spt.items (), ())))
	else:
		return getattr (spt, 'free_symbols', set ())

	return set ()

def _simplify (spt): # extend sympy simplification into standard python containers
	if isinstance (spt, (None.__class__, bool, int, float, complex, str)):
		return spt
	elif isinstance (spt, (tuple, list, set, frozenset)):
		return spt.__class__ (_simplify (a) for a in spt)
	elif isinstance (spt, slice):
		return slice (_simplify (spt.start), _simplify (spt.stop), _simplify (spt.step))
	elif isinstance (spt, dict):
		return dict ((_simplify (k), _simplify (v)) for k, v in spt.items ())

	if not isinstance (spt, (sp.Naturals.__class__, sp.Integers.__class__)): # these break on count_ops()
		try:
			spt2 = sp.simplify (spt)

			if sp.count_ops (spt2) <= sp.count_ops (spt): # sometimes simplify doesn't
				spt = spt2

		except:
			pass

	return spt

def _doit (spt): # extend sympy .doit() into standard python containers
	if isinstance (spt, (None.__class__, bool, int, float, complex, str)):
		return spt
	elif isinstance (spt, (tuple, list, set, frozenset)):
		return spt.__class__ (_doit (a) for a in spt)
	elif isinstance (spt, slice):
		return slice (_doit (spt.start), _doit (spt.stop), _doit (spt.step))
	elif isinstance (spt, dict):
		return dict ((_doit (k), _doit (v)) for k, v in spt.items ())

	try:
		return spt.doit (deep = True)
	except:
		pass

	return spt

def _subs (spt, subs): # extend sympy .subs() into standard python containers, subs = [(s1, d1), (s2, d2), ...]
	if not subs:
		return spt

	if isinstance (spt, (tuple, list, set, frozenset, slice, dict)):
		for i, (s, d) in enumerate (subs):
			if s == spt:
				return _subs (d, subs [:i] + subs [i + 1:])

		if isinstance (spt, slice):
			return slice (_subs (spt.start, subs), _subs (spt.stop, subs), _subs (spt.step, subs))
		elif isinstance (spt, dict):
			return dict ((_subs (k, subs), _subs (v, subs)) for k, v in spt.items ())
		else: # isinstance (spt, (tuple, list, set, frozenset)):
			return spt.__class__ (_subs (a, subs) for a in spt)

	elif isinstance (spt, sp.Derivative) and isinstance (spt.args [0], sp_AppliedUndef): # do not subs derivative of appliedundef (d/dx (f (x, y))) to preserve info about variables
		vars     = set (spt.args [0].args)
		spt      = sp.Subs (spt, *zip (*filter (lambda sd: sd [0] in vars, subs)))
		spt.doit = lambda self = spt, *args, **kw: self # disable doit because loses information

	else:
		try:
			if isinstance (spt, (bool, int, float, complex)):
				return sp.sympify (spt).subs (subs)
			else:
				return spt.subs (subs)

		except:
			pass

	return spt

def _Mul (*args):
	itr = iter (args)
	res = next (itr)

	for arg in itr:
		try:
			res = res * arg
		except:
			res = sp.sympify (res) * sp.sympify (arg)

	return res

def _Pow (base, exp): # fix inconsistent sympy Pow (..., evaluate = True)
	return base**exp

def _bool_or_None (v):
	return None if v is None else bool (v)

def _fltoint (num):
	return int (num) if isinstance (num, int) or num.is_integer () else num

def _trail_comma (obj):
	return ',' if len (obj) == 1 else ''

def _spt_assumptions (spt):
	return tuple (filter (lambda kv: kv [1] is not None and kv != ('commutative', True), sorted (spt._assumptions.items ())))

def _ast_is_neg (ast):
	return ast.is_minus or ast.is_num_neg or (ast.is_mul and _ast_is_neg (ast.mul [0]))

def _ast_is_neg_nominus (ast):
	return ast.is_num_neg or (ast.is_mul and _ast_is_neg (ast.mul [0]))

def _ast_is_top_ass_lhs (self, ast):
	return (self.parent.is_ass and ast is self.parent.lhs and self.parents [-2].op in {None, ';'}) or \
		(self.parent.is_comma and self.parents [-2].is_ass and self.parent is self.parents [-2].lhs and self.parents [-3].op in {None, ';'})

def _ast_eqcmp2ass (ast, parent = AST.Null):
	if not isinstance (ast, AST):
		return ast

	if ast.is_cmp and not ast.is_cmp_explicit and ast.cmp.len == 1 and ast.cmp [0] [0] == '==' and (
			parent.op in {None, ';', '<>', ',', '(', '[', '-', '!', '+', '*', '/', '^', '-log', '-sqrt', '-diff', '-diffp', '-mat', '-lamb', '-idx', '-set', '||', '^^', '&&', '-subs'} or
			parent.is_attr_var or
			(parent.is_attr_func and (ast is parent.obj or not ast.lhs.as_identifier)) or
			(parent.op in {'-lim', '-sum', '-intg'} and ast is parent [1]) or
			(parent.is_func and not ast.lhs.as_identifier)):
		return AST ('=', _ast_eqcmp2ass (ast.lhs, ast), _ast_eqcmp2ass (ast.cmp [0] [1], ast))

	return AST (*(_ast_eqcmp2ass (a, ast) for a in ast))

def _ast_slice_bounds (ast, None_ = AST.VarNull, allow1 = False):
	if allow1 and not ast.start and not ast.step:
		return (ast.stop or None_,)

	return tuple (a or None_ for a in ((ast.start, ast.stop) if ast.step is None else (ast.start, ast.stop, ast.step)))

def _ast_followed_by_slice (ast, seq):
	for i in range (len (seq)):
		if seq [i] is ast:
			for i in range (i + 1, len (seq)):
				if seq [i].is_slice:
					return True
				elif not seq [i].is_var:
					break

			break

	return False

def _ast_func_call (func, args, _ast2spt = None):
	if _ast2spt is None:
		_ast2spt = ast2spt

	pyargs, pykw = AST.args2kwargs (args, _ast2spt)

	return func (*pyargs, **pykw)

def _ast_has_open_differential (ast, istex):
	if ast.is_differential or (not istex and
			((ast.is_diff_d and (not ast.is_diff_dvdv and ast.dvs [-1] [-1] == 1)) or
			(ast.is_subs_diff_d_ufunc and (not ast.expr.is_diff_dvdv and ast.expr.dvs [-1] [-1] == 1)))):
		return True
	elif istex and ast.is_div or ast.op in {'[', '|', '-log', '-sqrt', '-func', '-diff', '-intg', '-mat', '-set', '-dict', '-ufunc', '-subs'}: # specifically not checking '(' because that might be added by ast2tex/nat in subexpressions
		return False
	elif ast.op in {'.', '^', '-log', '-sqrt', '-lim', '-sum', '-diffp', '-lamb', '-idx'}:
		return _ast_has_open_differential (ast [1], istex = istex)

	return any (_ast_has_open_differential (a, istex = istex) if isinstance (a, AST) else False for a in (ast if ast.op is None else ast [1:]))

def _ast_subs2func (ast): # ast is '-subs'
	func = ast.expr

	if func.op in {'-diff', '-diffp'}:
		func = func [1]

	func = _SYM_USER_VARS.get (func.var, func)

	if func.is_lamb:
		vars = ast.subs [:func.vars.len]

		if tuple (s.var for s, _ in vars) == func.vars:
			return [AST ('=', s, d) for s, d in ast.subs [func.vars.len:]], tuple (d for _, d in vars)

	elif func.is_ufunc_applied:
		subs = []
		vars = OrderedDict ((v, v) for v in func.vars)

		for s, d in ast.subs:
			if s.is_var_nonconst and vars.get (s) == s:
				vars [s] = d
			else:
				subs.append (AST ('=', s, d))

		vars = vars.values ()

		if func.apply_argskw ((vars, ())):
			return subs, vars

	return [AST ('=', s, d) for s, d in ast.subs], None

#...............................................................................................
class ast2tex: # abstract syntax tree -> LaTeX text
	def __init__ (self): self.parent = self.ast = None # pylint medication
	def __new__ (cls, ast, retxlat = False):
		def func_call (func, args):
			return spt2ast (_ast_func_call (getattr (sp, func), args))

		self         = super ().__new__ (cls)
		self.parents = [None]
		self.parent  = self.ast = AST.Null

		astx = sxlat.xlat_funcs2asts (ast, sxlat.XLAT_FUNC2AST_TEX, func_call = func_call)
		tex  = self._ast2tex (astx)

		return tex if not retxlat else (tex, (astx if astx != ast else None))

	def _ast2tex (self, ast):
		self.parents.append (self.ast)

		self.parent = self.ast
		self.ast    = ast

		tex         = self._ast2tex_funcs [ast.op] (self, ast)

		del self.parents [-1]

		self.ast    = self.parent
		self.parent = self.parents [-1]

		return tex

	def _ast2tex_wrap (self, obj, curly = None, paren = None):
		paren = (obj.op in paren) if isinstance (paren, set) else paren
		curly = (obj.op in curly) if isinstance (curly, set) else curly
		s     = self._ast2tex (obj if not obj.is_slice or paren or not curly or obj.step is not None else AST ('-slice', obj.start, obj.stop, False)) if isinstance (obj, AST) else str (obj)

		return f'\\left({s} \\right)' if paren else f'{{{s}}}' if curly else s

	def _ast2tex_cmp (self, ast):
		return f'{self._ast2tex_cmp_hs (ast.lhs)} {" ".join (f"{AST.Cmp.PY2TEX.get (r, r)} {self._ast2tex_cmp_hs (e)}" for r, e in ast.cmp)}'

	def _ast2tex_curly (self, ast):
		if ast.is_single_unit:
			return f'{self._ast2tex (ast)}'
		elif ast.op not in {',', '-slice'}:
			return f'{{{self._ast2tex (ast)}}}'
		else:
			return f'{{\\left({self._ast2tex (ast)} \\right)}}'

	def _ast2tex_paren (self, ast, ops = {}):
		return self._ast2tex_wrap (ast, 0, not (ast.op in {'(', '-lamb'} or (ops and ast.op not in ops)))

	def _ast2tex_paren_mul_exp (self, ast, ret_has = False, also = {'=', '<>', '+', '-slice', '||', '^^', '&&', '-or', '-and', '-not'}):
		if ast.is_mul:
			s, has = self._ast2tex_mul (ast, True)
		else:
			s, has = self._ast2tex (ast), ast.op in also

		s = self._ast2tex_wrap (s, 0, has)

		return (s, has) if ret_has else s

	def _ast2tex_ass_hs (self, hs, lhs = True):
		return self._ast2tex_wrap (hs, 0, hs.is_ass or hs.is_slice or
			(lhs and (hs.is_piece or (hs.is_comma and (self.parent and not self.parent.is_scolon)))))

	def _ast2tex_cmp_hs (self, hs):
		return self._ast2tex_wrap (hs, 0, {'=', '<>', '-piece', '-slice', '-or', '-and', '-not'})

	def _ast2tex_num (self, ast):
		m, e = ast.num_mant_and_exp

		return f'{m}{{e}}{{{e}}}' if e else m

	def _ast2tex_var (self, ast):
		if ast.is_var_null:
			return '\\{' if self.parent.op in {None, ';'} else '{}'

		texmulti = AST.Var.PY2TEXMULTI.get (ast.var)

		if texmulti: # for stuff like "Naturals0"
			return texmulti [0]

		n, s = ast.text_and_tail_num
		n    = n.replace ('_', '\\_')
		t    = AST.Var.PY2TEX.get (n)

		if s:
			s = f'_{{{s}}}'

		return \
				f'{t or n}{s}'      if not ast.diff_or_part_type else \
				f'd{t or n}{s}'     if ast.is_diff_any else \
				f'\\partial'        if ast.is_part_solo else \
				f'\\partial{t}{s}'  if t else \
				f'\\partial {n}{s}' if n else \
				f'\\partial'

	def _ast2tex_attr (self, ast):
		tex = sxlat.xlat_attr2tex (ast, self._ast2tex)

		if tex is not None:
			return tex

		a = ast.attr.replace ('_', '\\_')

		if ast.is_attr_func:
			a = f'\\operatorname{{{a}}}\\left({self._ast2tex (AST.tuple2argskw (ast.args))} \\right)'

		return f'{self._ast2tex_wrap (ast.obj, ast.obj.is_pow or ast.obj.is_subs_diff_ufunc, {"=", "<>", "#", ",", "-", "+", "*", "/", "-lim", "-sum", "-diff", "-intg", "-piece", "-slice", "||", "^^", "&&", "-or", "-and", "-not"})}.{a}'

	def _ast2tex_minus (self, ast):
		s = self._ast2tex_wrap (ast.minus, ast.minus.is_mul, {"=", "<>", "+", "-slice", "||", "^^", "&&", "-or", "-and", "-not"})

		return f'-{{{s}}}' if s [:6] != '\\left(' and ast.minus.strip_fdpi.is_num_pos else f'-{s}'

	def _ast2tex_add (self, ast):
		terms = []

		for n in ast.add:
			not_first = n is not ast.add [0]
			not_last  = n is not ast.add [-1]
			op        = ' + '

			if n.is_minus and not_first: # and n.minus.is_num_pos
				op, n = ' - ', n.minus

			s = self._ast2tex (n)

			terms.extend ([op, self._ast2tex_wrap (s,
				n.is_piece or (not_first and _ast_is_neg_nominus (n)) or (not_last and s [-1:] != ')' and (n.strip_mmls.is_intg or (n.is_mul and n.mul [-1].strip_mmls.is_intg))),
				(n.is_piece and not_last) or n.op in {'=', '<>', '+', '-slice', '||', '^^', '&&', '-or', '-and', '-not'})]) # , '-subs'})])

		return ''.join (terms [1:]).replace (' + -', ' - ')

	def _ast2tex_mul (self, ast, ret_has = False):
		t   = []
		p   = None
		has = False

		for i, n in enumerate (ast.mul):
			s = self._ast2tex_wrap (n, (p and _ast_is_neg (n)),
					n.op in {'=', '<>', '+', '-slice', '||', '^^', '&&', '-or', '-and', '-not'} or (n.is_piece and n is not ast.mul [-1]))

			if ((p and n.op in {'/', '-diff'} and p.op in {'#', '/'} and n.op != p.op) or
					(n.strip_mmls.is_intg and n is not ast.mul [-1] and s [-1:] not in {'}', ')', ']'})):
				s = f'{{{s}}}'

			is_exp          = i in ast.exp
			paren_after_var = p and (s.startswith ('\\left(') and (p.tail_mul.is_var or p.tail_mul.is_attr_var))

			if paren_after_var or (p and (
					t [-1].endswith ('.') or
					s [:1].isdigit () or
					s.startswith ('\\left[') or
					_ast_is_neg (n) or
					n.is_var_null or
 					n.op in {'#', '-mat'} or
					(s.startswith ('\\left(') and (
						is_exp or
						p.is_ufunc or
						(p.strip_paren.is_lamb and n.is_paren_isolated) or
						(p.is_diff_any_ufunc and not p.diff_any.apply_argskw (n.strip_paren1.as_ufunc_argskw)))) or
 					(t [-1] [-1:] not in {'}', ')', ']'} and p.tail_mul.is_sym_unqualified) or
					p.strip_minus.op in {'-lim', '-sum', '-diff', '-intg', '-mat'} or
					(p.tail_mul.is_var and (p.tail_mul.var == '_' or p.tail_mul.var in _SYM_USER_FUNCS)) or
					(n.is_div and p.is_div) or
					(n.is_attr and n.strip_attr.strip_paren.is_comma) or
					(n.is_pow and (n.base.is_num_pos or n.base.strip_paren.is_comma)) or
					(n.is_idx and (n.obj.is_idx or n.obj.strip_paren.is_comma)))):

				v = p.tail_mul
				a = _SYM_USER_VARS.get (v.var, AST.Null)

				if paren_after_var and not (
						is_exp or
						(v.is_var and (
							AST.UFunc.valid_implicit_args (n.strip_paren1.as_ufunc_argskw [0])) or
							(n.is_diffp and n.diffp.is_paren and AST.UFunc.valid_implicit_args (n.diffp.paren.as_ufunc_argskw [0])) or
							(a.is_ufunc and a.apply_argskw (n.strip_paren1.as_ufunc_argskw)))):

					t.append (s)

					if not t [-2].startswith ('{'):
						t [-2] = f'{{{t [-2]}}}'

				elif paren_after_var and not is_exp and n.is_paren_free and a.is_diff_any_ufunc and a.diff_any.apply_argskw (n.strip_paren1.as_ufunc_argskw):
					t.append (s)

				else:
					t.extend ([' \\cdot ', s])
					has = True

			elif p and (
					p.is_sqrt or
					p.num_exp or
					(_QUICK_MODE and p.is_attr_var and not s.startswith ('\\left(')) or
					p.strip_minus.is_diff_or_part_any or
					(not _QUICK_MODE and (
						n.is_sym or
						n.strip_pseudo.is_diff_or_part_any or
						(not s.startswith ('{') and (
							(not s.startswith ('\\left(') and
								(p.tail_mul.strip_pseudo.is_var or p.tail_mul.is_attr_var) and
								(n.strip_afpdpi.op in {'@', '-ufunc'} or (n.is_subs and n.expr.strip_afpdpi.op in {'@', '-ufunc'}))) or
							(s [:6] not in {'\\left(', '\\left['} and (
								p.is_var_long or
								n.is_func_pseudo or
								(n.strip_afpdpi.is_var_long and t [-1] [-7:] not in {'\\right)', '\\right]'})))))))):

				t.extend ([_TEX_SPACE, s])

			elif p:
				t.extend ([' ', s])
			else:
				t.append (s)

			p = n

		return (''.join (t), has) if ret_has else ''.join (t)

	def _ast2tex_div (self, ast):
		false_diff = (ast.numer.base.is_diff_or_part_solo and ast.numer.exp.is_num_pos_int) if ast.numer.is_pow else \
				(ast.numer.mul.len == 2 and ast.numer.mul [1].is_var and ast.numer.mul [0].is_pow and ast.numer.mul [0].base.is_diff_or_part_solo and ast.numer.mul [0].exp.strip_curly.is_num_pos_int) if ast.numer.is_mul else \
				ast.numer.is_diff_or_part_solo

		return f'\\frac{{{self._ast2tex_wrap (ast.numer, 0, ast.numer.is_slice or false_diff)}}}{{{self._ast2tex_wrap (ast.denom, 0, {"-slice"})}}}'

	def _ast2tex_pow (self, ast, trighpow = True):
		b = self._ast2tex_wrap (ast.base, {'-mat'}, not (ast.base.op in {'@', '.', '"', '(', '[', '|', '-log', '-func', '-mat', '-lamb', '-idx', '-set', '-dict', '-ufunc'} or ast.base.is_num_pos))
		p = self._ast2tex_curly (ast.exp)

		if trighpow and ast.base.is_func_trigh_noninv and ast.exp.is_num and ast.exp.num != '-1': # and ast.exp.is_single_unit
			i = len (ast.base.func) + (15 if ast.base.func in {'sech', 'csch'} else 1)

			return f'{b [:i]}^{p}{b [i:]}'

		return f'{b}^{p}'

	def _ast2tex_log (self, ast):
		if ast.base is None:
			return f'\\ln{{\\left({self._ast2tex (ast.log)} \\right)}}'
		else:
			return f'\\log_{self._ast2tex_curly (ast.base)}{{\\left({self._ast2tex (ast.log)} \\right)}}'

	_rec_tailnum = re.compile (r'^(.+)(?<![\d_])(\d*)$')

	def _ast2tex_func (self, ast):
		if ast.is_func_trigh:
			if ast.func [0] != 'a':
				n = f'\\operatorname{{{ast.func}}}' if ast.func in {'sech', 'csch'} else f'\\{ast.func}'
			elif ast.func in {'asech', 'acsch'}:
				n = f'\\operatorname{{{ast.func [1:]}}}^{{-1}}'
			else:
				n = f'\\{ast.func [1:]}^{{-1}}'

			return f'{n}{{\\left({self._ast2tex (AST.tuple2argskw (ast.args))} \\right)}}'

		tex = sxlat.xlat_func2tex (ast, self._ast2tex)

		if tex is not None:
			return tex

		if ast.func in AST.Func.TEX:
			func = f'\\{ast.func}'

		elif ast.func in {AST.Func.NOREMAP, AST.Func.NOEVAL}:
			func = ast.func.replace (AST.Func.NOEVAL, '\\%')

			if ast.args [0].op in {'#', '@', '(', '[', '|', '-func', '-mat', '-lamb', '-set', '-dict'}:
				return f'{func}{self._ast2tex (AST.tuple2argskw (ast.args))}'

		elif ast.func not in AST.Func.PY:
			m         = self._rec_tailnum.match (ast.func)
			func, sub = m.groups () if m else (ast.func, None)
			func      = func.replace ('_', '\\_')
			func      = f'\\operatorname{{{func}_{{{sub}}}}}' if sub else f'\\operatorname{{{func}}}'

		else:
			func = ast.func.replace ('_', '\\_')
			func = f'\\operatorname{{{AST.Var.GREEK2TEX.get (ast.func, func)}}}'

		return f'{func}{{\\left({self._ast2tex (AST.tuple2argskw (ast.args))} \\right)}}'

	def _ast2tex_lim (self, ast):
		s = self._ast2tex_wrap (ast.to, False, ast.to.is_slice) if ast.dir is None else (self._ast2tex_pow (AST ('^', ast.to, AST.Zero), trighpow = False) [:-1] + ast.dir)

		return f'\\lim_{{{self._ast2tex (ast.lvar)} \\to {s}}} {self._ast2tex_paren_mul_exp (ast.lim)}'

	def _ast2tex_sum (self, ast):
		return f'\\sum_{{{self._ast2tex (ast.svar)} = {self._ast2tex (ast.from_)}}}^{self._ast2tex_curly (ast.to)} {self._ast2tex_paren_mul_exp (ast.sum)}' \

	def _ast2tex_diff (self, ast):
		if ast.diff.is_var and not ast.diff.is_diff_or_part:
			top  = self._ast2tex (ast.diff)
			topp = f' {top}'
			side = ''

		else:
			top  = topp = ''
			side = self._ast2tex_wrap (ast.diff, 0, ast.diff.op not in {'(', '-lamb'})

		ds = set ()
		dp = 0

		for v, p in ast.dvs:
			ds.add (v)
			dp += p

		if not ds:
			return f'\\frac{{d{top}}}{{}}{side}'

		is_d = len (ds) <= 1 and ast.is_diff_d

		if is_d:
			diff = _SYM_USER_VARS.get (ast.diff.var, ast.diff)

			if diff.is_lamb:
				diff = diff.lamb

			is_d = len (diff.free_vars) <= 1

		if is_d:
			dvs = " ".join (self._ast2tex (AST ('@', f'd{v}') if p == 1 else AST ('^', AST ('@', f'd{v}'), AST ('#', p))) for v, p in ast.dvs)

			return f'\\frac{{d{top if dp == 1 else f"^{dp}{topp}"}}}{{{dvs}}}{side}'

		else:
			dvs = " ".join (self._ast2tex (AST ('@', f'partial{v}') if p == 1 else AST ('^', AST ('@', f'partial{v}'), AST ('#', p))) for v, p in ast.dvs)

			return f'\\frac{{\\partial{topp if dp == 1 else f"^{dp}{topp}"}}}{{{dvs}}}{side}'

	def _ast2tex_intg (self, ast):
		if ast.intg is None:
			intg  = ' '

		else:
			curly = ast.intg.op in {"-diff", "-slice", "||", "^^", "&&", "-or", "-and", "-not"} or ast.intg.tail_mul.op in {"-lim", "-sum"}
			intg  = self._ast2tex_wrap (ast.intg, curly, {"=", "<>"})
			intg  = f' {{{intg}}} ' if not curly and _ast_has_open_differential (ast.intg, istex = True) else f' {intg} '

		if ast.from_ is None:
			return f'\\int{intg}\\ {self._ast2tex (ast.dv)}'
		else:
			return f'\\int_{self._ast2tex_curly (ast.from_)}^{self._ast2tex_curly (ast.to)}{intg}\\ {self._ast2tex (ast.dv)}'

	def _ast2tex_idx (self, ast):
		obj = self._ast2tex_wrap (ast.obj,
			ast.obj.op in {"^", "-slice"} or ast.obj.is_subs_diff_ufunc or (ast.obj.is_var and ast.obj.var in _SYM_USER_FUNCS),
			ast.obj.is_num_neg or ast.obj.op in {"=", "<>", ",", "-", "+", "*", "/", "-lim", "-sum", "-diff", "-intg", "-piece", "||", "^^", "&&", "-or", "-and", "-not"})
		idx = '[' if ast.idx.len and ast.idx [0].is_var_null else f'\\left[{self._ast2tex (AST.tuple2ast (ast.idx))} \\right]'

		return f'{obj}{idx}'

	def _ast2tex_ufunc (self, ast):
		user       = _SYM_USER_ALL.get (ast.ufunc)
		is_top_ass = _ast_is_top_ass_lhs (self, ast)

		if (not ast.ufunc or
				(_STRICT_TEX and
				((ast.is_ufunc_explicit and is_top_ass) or
					((user and (not user.is_ufunc or not user.apply_argskw ((ast.vars, ast.kw))) or
							(not user and not AST.UFunc.valid_implicit_args (ast.vars))) and
						not is_top_ass)))):
			pre = '?'
		else:
			pre = ''

		name = self._ast2tex (AST ("@", ast.ufunc)) if ast.ufunc else ""
		args = ", ".join (tuple (self._ast2tex (v) for v in ast.vars) + tuple (" = ".join ((k.replace ("_", "\\_"), self._ast2tex_wrap (a, 0, a.is_comma))) for k, a in ast.kw))

		return f'{pre}{name}\\left({args} \\right)'

	def _ast2tex_subs (self, ast):
		subs, vars = _ast_subs2func (ast)

		if len (subs) == ast.subs.len:
			expr = self._ast2tex (ast.expr)

		else:
			expr = f'{self._ast2tex (ast.expr)}\\left({", ".join (self._ast2tex (v) for v in vars)} \\right)'

			if not subs:
				return expr

		if len (subs) == 1:
			subs = self._ast2tex (subs [0])
		else:
			subs = '\\substack{' + ' \\\\ '.join (self._ast2tex (s) for s in subs) + '}'

		return f'\\left. {expr} \\right|_{{{subs}}}'

	_ast2tex_funcs = {
		';'     : lambda self, ast: ';\\: '.join (self._ast2tex (a) for a in ast.scolon),
		'='     : lambda self, ast: f'{self._ast2tex_ass_hs (ast.lhs)} = {self._ast2tex_ass_hs (ast.rhs, False)}',
		'<>'    : _ast2tex_cmp,
		'#'     : _ast2tex_num,
		'@'     : _ast2tex_var,
		'.'     : _ast2tex_attr,
		'"'     : lambda self, ast: '\\text{' + repr (ast.str_).replace ('}', '\\}') + '}',
		','     : lambda self, ast: f'{", ".join (self._ast2tex (c) for c in ast.comma)}{_trail_comma (ast.comma)}',
		'('     : lambda self, ast: '(' if ast.paren.is_var_null else self._ast2tex_wrap (ast.paren, 0, 1), # not ast.paren.is_lamb),
		'['     : lambda self, ast: '[' if ast.brack.len == 1 and ast.brack [0].is_var_null else f'\\left[{", ".join (self._ast2tex (b) for b in ast.brack)} \\right]',
		'|'     : lambda self, ast: '|' if ast.abs.is_var_null else f'\\left|{self._ast2tex (ast.abs)} \\right|',
		'-'     : _ast2tex_minus,
		'!'     : lambda self, ast: self._ast2tex_wrap (ast.fact, {'^'}, (ast.fact.op not in {'#', '@', '.', '"', '(', '[', '|', '!', '^', '-func', '-mat', '-idx', '-set', '-dict', '-sym'} or ast.fact.is_num_neg)) + '!',
		'+'     : _ast2tex_add,
		'*'     : _ast2tex_mul,
		'/'     : _ast2tex_div,
		'^'     : _ast2tex_pow,
		'-log'  : _ast2tex_log,
		'-sqrt' : lambda self, ast: f'\\sqrt{{{self._ast2tex_wrap (ast.rad, 0, {",", "-slice"})}}}' if ast.idx is None else f'\\sqrt[{self._ast2tex (ast.idx)}]{{{self._ast2tex_wrap (ast.rad, 0, {",", "-slice"})}}}',
		'-func' : _ast2tex_func,
		'-lim'  : _ast2tex_lim,
		'-sum'  : _ast2tex_sum,
		'-diff' : _ast2tex_diff,
		'-diffp': lambda self, ast: self._ast2tex_wrap (ast.diffp, ast.diffp.is_subs_diff_any_ufunc, ast.diffp.is_num_neg or ast.diffp.op in {"=", "<>", "-", "+", "*", "/", "^", "-sqrt", "-lim", "-sum", "-diff", "-intg", "-piece", "-slice", "||", "^^", "&&", "-or", "-and", "-not"}) + "'" * ast.count,
		'-intg' : _ast2tex_intg,
		'-mat'  : lambda self, ast: '\\begin{bmatrix} ' + r' \\ '.join (' & '.join (self._ast2tex_wrap (e, 0, e.is_slice) for e in row) for row in ast.mat) + f'{" " if ast.mat else ""}\\end{{bmatrix}}',
		'-piece': lambda self, ast: '\\begin{cases} ' + r' \\ '.join (f'{self._ast2tex_wrap (p [0], 0, {"=", "<>", ",", "-slice"})} & \\text{{otherwise}}' if p [1] is True else f'{self._ast2tex_wrap (p [0], 0, {"=", "<>", ",", "-slice"})} & \\text{{for}}\\: {self._ast2tex_wrap (p [1], 0, {"-slice"})}' for p in ast.piece) + ' \\end{cases}',
		'-lamb' : lambda self, ast: f'\\left({self._ast2tex (AST ("@", ast.vars [0]) if ast.vars.len == 1 else AST ("(", (",", tuple (("@", v) for v in ast.vars))))} \\mapsto {self._ast2tex (ast.lamb)} \\right)',
		'-idx'  : _ast2tex_idx,
		'-slice': lambda self, ast: self._ast2tex_wrap ('{:}'.join (self._ast2tex_wrap (a, a and _ast_is_neg (a), a and a.op in {'=', ',', '-slice'}) for a in _ast_slice_bounds (ast, '')), 0, not ast.start and self.parent.is_comma and ast is self.parent.comma [0] and self.parents [-2].is_ass and self.parent is self.parents [-2].rhs),
		'-set'  : lambda self, ast: f'\\left\\{{{", ".join (self._ast2tex_wrap (c, 0, c.is_slice) for c in ast.set)} \\right\\}}' if ast.set else '\\emptyset',
		'-dict' : lambda self, ast: f'\\left\\{{{", ".join (f"{self._ast2tex_wrap (k, 0, k.is_slice)}{{:}} {self._ast2tex_wrap (v, 0, v.is_slice)}" for k, v in ast.dict)} \\right\\}}',
		'||'    : lambda self, ast: ' \\cup '.join (self._ast2tex_wrap (a, 0, a.op in {'=', '<>', ',', '-slice', '-or', '-and', '-not'} or (a.is_piece and a is not ast.union [-1])) for a in ast.union),
		'^^'    : lambda self, ast: ' \\ominus '.join (self._ast2tex_wrap (a, 0, a.op in {'=', '<>', ',', '-slice', '||', '-or', '-and', '-not'} or (a.is_piece and a is not ast.sdiff [-1])) for a in ast.sdiff),
		'&&'    : lambda self, ast: ' \\cap '.join (self._ast2tex_wrap (a, 0, a.op in {'=', '<>', ',', '-slice', '||', '^^', '-or', '-and', '-not'} or (a.is_piece and a is not ast.xsect [-1])) for a in ast.xsect),
		'-or'   : lambda self, ast: ' \\vee '.join (self._ast2tex_wrap (a, 0, a.op in {'=', ',', '-slice'} or (a.is_piece and a is not ast.or_ [-1])) for a in ast.or_),
		'-and'  : lambda self, ast: ' \\wedge '.join (self._ast2tex_wrap (a, 0, a.op in {'=', ',', '-slice', '-or'} or (a.is_piece and a is not ast.and_ [-1])) for a in ast.and_),
		'-not'  : lambda self, ast: f'\\neg\\ {self._ast2tex_wrap (ast.not_, 0, ast.not_.op in {"=", ",", "-slice", "-or", "-and"})}',
		'-ufunc': _ast2tex_ufunc,
		'-subs' : _ast2tex_subs,
		'-sym'  : lambda self, ast: f'\\${self._ast2tex (AST ("@", ast.sym)) if ast.sym else ""}' + ('' if ast.is_sym_unqualified else f'\\left({", ".join (tuple (f"{k} = {self._ast2tex_wrap (a, 0, a.is_comma)}" for k, a in ast.kw))} \\right)'),

		'-text' : lambda self, ast: ast.tex,
	}

#...............................................................................................
class ast2nat: # abstract syntax tree -> native text
	def __init__ (self): self.parent = self.ast = None # pylint droppings
	def __new__ (cls, ast, retxlat = False):
		self         = super ().__new__ (cls)
		self.parents = [None]
		self.parent  = self.ast = AST.Null

		astx = sxlat.xlat_funcs2asts (ast, sxlat.XLAT_FUNC2AST_NAT)
		nat  = self._ast2nat (astx)

		return nat if not retxlat else (nat, (astx if astx != ast else None))

	def _ast2nat (self, ast):
		self.parents.append (self.ast)

		self.parent = self.ast
		self.ast    = ast

		nat         = self._ast2nat_funcs [ast.op] (self, ast)

		del self.parents [-1]

		self.ast    = self.parent
		self.parent = self.parents [-1]

		return nat

	def _ast2nat_wrap (self, obj, curly = None, paren = None):
		isast = isinstance (obj, AST)
		paren = (obj.op in paren) if isinstance (paren, set) else paren
		curly = ((obj.op in curly) if isinstance (curly, set) else curly) and not (isast and obj.is_abs)
		s     = self._ast2nat (obj if not obj.is_slice or paren or not curly or obj.step is not None else AST ('-slice', obj.start, obj.stop, False)) if isast else str (obj)

		return f'({s})' if paren else f'{{{s}}}' if curly else s

	def _ast2nat_curly (self, ast, ops = {}):
		if ast.is_slice:
			ast = AST ('(', ast)

		return self._ast2nat_wrap (ast, ops if ops else (ast.is_div or not ast.is_single_unit or (ast.is_var and ast.var in AST.Var.PY2TEX)))

	def _ast2nat_paren (self, ast, ops = {}):
		return self._ast2nat_wrap (ast, 0, not (ast.is_paren or (ops and ast.op not in ops)))

	def _ast2nat_curly_mul_exp (self, ast, ret_has = False, also = {}):
		if ast.is_mul:
			s, has = self._ast2nat_mul (ast, True)
		else:
			s, has = self._ast2nat (ast), False

		has = has or ((ast.op in also) if isinstance (also, set) else also)
		s   = self._ast2nat_wrap (s, has, ast.is_slice)

		return (s, has) if ret_has else s

	def _ast2nat_ass_hs (self, hs, lhs = True):
		return self._ast2nat_wrap (hs, 0, hs.is_ass or hs.is_slice or (lhs and (hs.op in {'-piece', '-lamb'}) or (hs.is_comma and (self.parent and not self.parent.is_scolon))) or \
				(not lhs and hs.is_lamb and self.parent.op in {'-set', '-dict'}))

	def _ast2nat_cmp_hs (self, hs):
		return self._ast2nat_wrap (hs, 0, {'=', '<>', '-piece', '-lamb', '-slice', '-or', '-and', '-not'})

	def _ast2nat_cmp (self, ast):
		return f'{self._ast2nat_cmp_hs (ast.lhs)} {" ".join (f"{AST.Cmp.PYFMT.get (r, r)} {self._ast2nat_cmp_hs (e)}" for r, e in ast.cmp)}'

	def _ast2nat_attr (self, ast):
		obj = self._ast2nat_wrap (ast.obj, ast.obj.is_pow or ast.obj.is_subs_diff_ufunc, {"=", "<>", "#", ",", "-", "+", "*", "/", "-lim", "-sum", "-diff", "-intg", "-piece", "-lamb", "-slice", "||", "^^", "&&", "-or", "-and", "-not"})

		if ast.is_attr_var:
			return f'{obj}.{ast.attr}'
		else:
			return f'{obj}.{ast.attr}({self._ast2nat (AST.tuple2argskw (ast.args))})'

	def _ast2nat_minus (self, ast):
		s = self._ast2nat_wrap (ast.minus, ast.minus.op in {"*", "-diff", "-piece", "||", "^^", "&&", "-or", "-and"}, {"=", "<>", "+", "-lamb", "-slice", "-not"})

		return f'-{{{s}}}' if s [:1] not in {'{', '('} and ast.minus.strip_fdpi.is_num_pos else f'-{s}'

	def _ast2nat_str (self, ast):
		s = repr (ast.str_)

		return s if s [0] != "'" else f' {s}'

	def _ast2nat_add (self, ast):
		terms = []

		for n in ast.add:
			not_first = n is not ast.add [0]
			not_last  = n is not ast.add [-1]
			op        = ' + '

			if n.is_minus and not_first: # and n.minus.is_num_pos
				op, n = ' - ', n.minus

			terms.extend ([op, self._ast2nat_wrap (n,
				n.is_piece or (not_first and _ast_is_neg_nominus (n)) or ((n.strip_mmls.is_intg or (n.is_mul and n.mul [-1].strip_mmls.is_intg)) and not_last),
				(n.op in ('-piece', '-lamb') and not_last) or n.op in {'=', '<>', '+', '-lamb', '-slice', '||', '^^', '&&', '-or', '-and', '-not'})])

		return ''.join (terms [1:]).replace (' + -', ' - ')

	def _ast2nat_mul (self, ast, ret_has = False):
		t   = []
		p   = None
		has = False

		for i, n in enumerate (ast.mul):
			s = self._ast2nat_wrap (n,
				n.op in {'+', '-piece', '-lamb', '-slice', '||', '^^', '&&', '-or', '-and', '-not'} or
				(p and (
					_ast_is_neg (n) or
					(p.is_div and (p.denom.is_intg or (p.denom.is_mul and p.denom.mul [-1].is_intg)) and n.is_diff and n.diff.is_var and n.dvs [-1] [1] == 1))),
				n.op in {'=', '<>'})

			if n.strip_mmls.is_intg and n is not ast.mul [-1] and s [-1:] not in {'}', ')', ']'}:
				s = f'{{{s}}}'

			is_exp          = i in ast.exp
			paren_after_var = p and (s.startswith ('(') and (p.tail_mul.is_var or p.tail_mul.is_attr_var))

			if paren_after_var or (p and (
					s.startswith ('[') or
					s [:1].isdigit () or
					t [-1] [-1:] == '.' or
					n.is_num or
					n.is_var_null or
					n.op in {'/', '-diff'} or
					(s.startswith ('(') and (
						is_exp or
						p.is_ufunc or
						(p.strip_paren.is_lamb and n.is_paren_isolated) or
						(p.is_diff_any_ufunc and not p.diff_any.apply_argskw (n.strip_paren1.as_ufunc_argskw)))) or
					n.strip_attrdp.is_subs_diff_ufunc or
 					(t [-1] [-1:] not in {'}', ')', ']'} and p.tail_mul.is_sym_unqualified) or
					p.strip_minus.op in {'/', '-lim', '-sum', '-diff', '-intg'} or
					(n.is_pow and (n.base.strip_paren.is_comma or n.base.is_num_pos)) or
					(n.is_attr and n.strip_attr.strip_paren.is_comma) or
					(n.is_idx and (n.obj.is_idx or n.obj.strip_paren.is_comma)) or
					(p.has_tail_lambda and n is ast.mul [-1] and t [-1] [-6:] == 'lambda') or
					(p.tail_mul.is_var and p.tail_mul.var in _SYM_USER_FUNCS) or
					s [:1] in {'e', 'E'} and t [-1] [-1].isdigit ())):

				v = p.tail_mul
				a = _SYM_USER_VARS.get (v.var, AST.Null)

				if paren_after_var and not (
						is_exp or
						(v.is_var and (
							AST.UFunc.valid_implicit_args (n.strip_paren1.as_ufunc_argskw [0])) or
							(n.is_diffp and n.diffp.is_paren and AST.UFunc.valid_implicit_args (n.diffp.paren.as_ufunc_argskw [0])) or
							(a.is_ufunc and a.apply_argskw (n.strip_paren1.as_ufunc_argskw)))):

					t.append (f'{{{s}}}')

				elif paren_after_var and not is_exp and n.is_paren_free and a.is_diff_any_ufunc and a.diff_any.apply_argskw (n.strip_paren1.as_ufunc_argskw):
					t.append (s)

				else:
					t.extend ([' * ', s])
					has = True

			elif p and (
					p.is_diff_or_part_solo or
					n.op not in {'#', '|', '^'} or
					p.op not in {'#', '|'}):
				t.extend ([' ', s])

			else:
				t.append (s)

			p = n

		return (''.join (t), has) if ret_has else ''.join (t)

	def _ast2nat_div (self, ast):
		false_diff = (ast.numer.base.is_diff_or_part_solo and ast.numer.exp.is_num_pos_int) if ast.numer.is_pow else \
				(ast.numer.mul.len == 2 and ast.numer.mul [1].is_var and ast.numer.mul [0].is_pow and ast.numer.mul [0].base.is_diff_or_part_solo and ast.numer.mul [0].exp.strip_curly.is_num_pos_int) if ast.numer.is_mul else \
				ast.numer.is_diff_or_part_solo

		n, ns = (self._ast2nat_wrap (ast.numer, 1), True) if _ast_is_neg (ast.numer) or (ast.numer.is_mul and ast.numer.mul [-1].op in {'-lim', '-sum'}) else \
				(self._ast2nat_wrap (ast.numer, 0, 1), True) if (ast.numer.is_slice or false_diff) else \
				self._ast2nat_curly_mul_exp (ast.numer, True, {'=', '<>', '+', '/', '-lim', '-sum', '-diff', '-intg', '-piece', '-lamb', '||', '^^', '&&', '-or', '-and', '-not'})

		d, ds = (self._ast2nat_wrap (ast.denom, 1), True) if ((_ast_is_neg (ast.denom) and ast.denom.strip_minus.is_div) or ast.denom.strip_minus.is_subs_diff_ufunc or (ast.denom.is_mul and ast.denom.mul [0].is_subs_diff_ufunc)) else \
				(self._ast2nat_wrap (ast.denom, 0, 1), True) if ast.denom.is_slice else \
				self._ast2nat_curly_mul_exp (ast.denom, True, {'=', '<>', '+', '/', '-lim', '-sum', '-diff', '-intg', '-piece', '-lamb', '||', '^^', '&&', '-or', '-and', '-not'})

		s     = ns or ds or ast.numer.strip_minus.op not in {'#', '@'} or ast.denom.strip_minus.op not in {'#', '@'}

		return f'{n}{" / " if s else "/"}{d}'

	def _ast2nat_pow (self, ast, trighpow = True):
		b = self._ast2nat_wrap (ast.base, 0, not (ast.base.op in {'@', '.', '"', '(', '[', '|', '-func', '-mat', '-idx', '-set', '-dict', '-ufunc'} or ast.base.is_num_pos))
		p = self._ast2nat_wrap (ast.exp,
				ast.exp.op in {'<>', '=', '+', '-lamb', '-slice', '-not'} or
				ast.exp.strip_minus.op in {'*', '/', '-lim', '-sum', '-diff', '-intg', '-piece', '||', '^^', '&&', '-or', '-and'} or
				ast.exp.strip_minus.is_subs_diff_ufunc,
				{","})

		if trighpow and ast.base.is_func_trigh_noninv and ast.exp.is_num and ast.exp.num != '-1': # and ast.exp.is_single_unit
			i = len (ast.base.func)

			return f'{b [:i]}**{p}{b [i:]}'

		return f'{b}**{p}'

	def _ast2nat_log (self, ast):
		if ast.base is None:
			return f'ln({self._ast2nat (ast.log)})'
		else:
			return f'\\log_{self._ast2nat_curly (ast.base)}({self._ast2nat (ast.log)})'

	def _ast2nat_lim (self, ast):
		s = self._ast2nat_wrap (ast.to, (ast.to.is_ass and ast.to.rhs.is_lamb) or ast.to.op in {'-lamb', '-piece'}, ast.to.is_slice) if ast.dir is None else (self._ast2nat_pow (AST ('^', ast.to, AST.Zero), trighpow = False) [:-1] + ast.dir)

		return f'\\lim_{{{self._ast2nat (ast.lvar)} \\to {s}}} {self._ast2nat_curly_mul_exp (ast.lim, False, ast.lim.op in {"=", "<>", "+", "-piece", "-lamb", "-slice", "||", "^^", "&&", "-or", "-and", "-not"} or ast.lim.is_mul_has_abs)}'

	def _ast2nat_sum (self, ast):
		return f'\\sum_{{{self._ast2nat (ast.svar)} = {self._ast2nat_curly (ast.from_, {"-lamb", "-piece"})}}}^{self._ast2nat_curly (ast.to)} {self._ast2nat_curly_mul_exp (ast.sum, False, ast.sum.op in {"=", "<>", "+", "-piece", "-lamb", "-slice", "||", "^^", "&&", "-or", "-and", "-not"} or ast.sum.is_mul_has_abs)}'

	def _ast2nat_diff (self, ast):
		if ast.diff.is_var and not ast.diff.is_diff_or_part:
			top  = self._ast2nat (ast.diff)
			topp = f' {top}'
			side = ''

		else:
			top  = topp = ''
			side = f' {self._ast2nat_paren (ast.diff)}'

		dp  = sum (dv [1] for dv in ast.dvs)
		dv  = (lambda v: AST ('@', f'd{v}')) if ast.is_diff_d else (lambda v: AST ('@', f'partial{v}'))
		dvs = " ".join (self._ast2nat (dv (v) if p == 1 else AST ('^', dv (v), AST ('#', p))) for v, p in ast.dvs)

		return f'{ast.d}{top if dp == 1 else f"**{dp}{topp}"} / {dvs}{side}'

	def _ast2nat_intg (self, ast):
		if ast.intg is None:
			intg  = ' '
		else:
			curly = (ast.intg.op in {"-piece", "-lamb", "-slice", "||", "^^", "&&", "-or", "-and", "-not"} or
				ast.intg.is_mul_has_abs or
				ast.intg.tail_mul.op in {"-lim", "-sum"} or
				(ast.intg.tail_mul.is_var and ast.intg.tail_mul.var in _SYM_USER_FUNCS))
			intg  = self._ast2nat_wrap (ast.intg, curly, {"=", "<>"})
			intg  = f' {{{intg}}} ' if not curly and _ast_has_open_differential (ast.intg, istex = False) else f' {intg} '

		if ast.from_ is None:
			return f'\\int{intg}{self._ast2nat (ast.dv)}'
		else:
			return f'\\int_{self._ast2nat_curly (ast.from_)}^{self._ast2nat_curly (ast.to)}{intg}{self._ast2nat (ast.dv)}'

	def _ast2nat_mat (self, ast):
		if not ast.rows:
			return '\\[]'
		elif ast.is_mat_column and not any (r [0].is_brack for r in ast.mat): # (ast.rows > 1 or not ast.mat [0] [0].is_brack):
			return f"\\[{', '.join (self._ast2nat (row [0]) for row in ast.mat)}]"
		else:
			return f"""\\[{', '.join (f'[{", ".join (self._ast2nat (e) for e in row)}]' for row in ast.mat)}]"""

	def _ast2nat_idx (self, ast):
		obj = self._ast2nat_wrap (ast.obj,
			ast.obj.op in {"^", "-slice"} or ast.obj.is_subs_diff_ufunc or (ast.obj.is_var and ast.obj.var in _SYM_USER_FUNCS),
			ast.obj.is_num_neg or ast.obj.op in {"=", "<>", ",", "+", "*", "/", "-", "-lim", "-sum", "-diff", "-intg", "-piece", "-lamb", "||", "^^", "&&", "-or", "-and", "-not"})
		idx = '[' if ast.idx.len and ast.idx [0].is_var_null else f'[{self._ast2nat (AST.tuple2ast (ast.idx))}]'

		return f'{obj}{idx}'

	def _ast2nat_slice (self, ast):
		b = _ast_slice_bounds (ast)
		s = ':'.join (self._ast2nat_wrap (a, a is not b [-1] and not a.op in {'/', '-diff'} and a.has_tail_lambda_solo, a.op in {'=', ',', '-lamb', '-slice'}) for a in b)

		return self._ast2nat_wrap (s, 0, not ast.start and self.parent.is_comma and ast is self.parent.comma [0] and self.parents [-2].is_ass and self.parent is self.parents [-2].rhs)

	def _ast2nat_dict (self, ast):
		items = []

		for k, v in ast.dict:
			if k.op in {'-lamb', '-slice'}:
				k       = self._ast2nat_paren (k)
			else:
				_, wrap = k.tail_lambda_solo
				k       = self._ast2nat (wrap (AST ('@', '\\lambda')) if wrap else k)

			items.append ((k, self._ast2nat_wrap (v, v.is_lamb, v.is_slice)))

		return f'''{{{", ".join (f'{k}: {v}' for k, v in items)}}}'''

	def _ast2nat_ufunc (self, ast):
		user       = _SYM_USER_ALL.get (ast.ufunc)
		is_top_ass = _ast_is_top_ass_lhs (self, ast)

		if (not ast.ufunc or
				((ast.is_ufunc_explicit and is_top_ass) or
						((user and (not user.is_ufunc or not user.apply_argskw ((ast.vars, ast.kw))) or
							(not user and not AST.UFunc.valid_implicit_args (ast.vars))) and
					not is_top_ass))):
			pre = '?'
		else:
			pre = ''

		args = ", ".join (tuple (self._ast2nat (v) for v in ast.vars) + tuple (f"{k} = {self._ast2nat_wrap (a, 0, a.is_comma)}" for k, a in ast.kw))

		return f'{pre}{ast.ufunc}({args})'

	def _ast2nat_subs (self, ast):
		subs, vars = _ast_subs2func (ast)

		if len (subs) == ast.subs.len:
			expr = self._ast2nat (ast.expr)

		else:
			expr = f'{self._ast2nat (ast.expr)}({", ".join (self._ast2nat (v) for v in vars)})'

			if not subs:
				return expr

		if len (subs) == 1:
			subs = self._ast2nat (subs [0])
		else:
			subs = self._ast2nat (AST (',', tuple (subs)))

		return f'\\. {expr} |_{{{subs}}}'

	_ast2nat_funcs = {
		';'     : lambda self, ast: '; '.join (self._ast2nat (a) for a in ast.scolon),
		'='     : lambda self, ast: f'{self._ast2nat_ass_hs (ast.lhs)} = {self._ast2nat_ass_hs (ast.rhs, False)}',
		'<>'    : _ast2nat_cmp,
		'#'     : lambda self, ast: ast.num,
		'@'     : lambda self, ast: '{' if ast.is_var_null and self.parent.op in {None, ';'} else ast.var,
		'.'     : _ast2nat_attr,
		'"'     : _ast2nat_str,
		','     : lambda self, ast: f'{", ".join (self._ast2nat (c) for c in ast.comma)}{_trail_comma (ast.comma)}',
		'('     : lambda self, ast: '(' if ast.paren.is_var_null else f'({self._ast2nat (ast.paren)})',
		'['     : lambda self, ast: '[' if ast.brack.len == 1 and ast.brack [0].is_var_null else f'[{", ".join (self._ast2nat (b) for b in ast.brack)}]',
		'|'     : lambda self, ast: '|' if ast.abs.is_var_null else f'{{|{self._ast2nat (ast.abs)}|}}',
		'-'     : _ast2nat_minus,
		'!'     : lambda self, ast: self._ast2nat_wrap (ast.fact, {'^'}, ast.fact.op not in {'#', '@', '.', '"', '(', '[', '|', '!', '^', '-func', '-mat', '-idx', '-set', '-dict', '-sym'} or ast.fact.is_num_neg) + '!',
		'+'     : _ast2nat_add,
		'*'     : _ast2nat_mul,
		'/'     : _ast2nat_div,
		'^'     : _ast2nat_pow,
		'-log'  : _ast2nat_log,
		'-sqrt' : lambda self, ast: f'sqrt{"" if ast.idx is None else f"[{self._ast2nat (ast.idx)}]"}({self._ast2nat (ast.rad)})',
		'-func' : lambda self, ast: f"{ast.func}{self._ast2nat_wrap (AST.tuple2argskw (ast.args), 0, not (ast.func in {AST.Func.NOREMAP, AST.Func.NOEVAL} and ast.args [0].op in {'#', '@', '(', '[', '|', '-func', '-mat', '-set', '-dict'}))}",
		'-lim'  : _ast2nat_lim,
		'-sum'  : _ast2nat_sum,
		'-diff' : _ast2nat_diff,
		'-diffp': lambda self, ast: self._ast2nat_wrap (ast.diffp, ast.diffp.is_subs_diff_any_ufunc, ast.diffp.is_num_neg or ast.diffp.op in {"=", "<>", "-", "+", "*", "/", "^", "-lim", "-sum", "-diff", "-intg", "-piece", "-lamb", "-slice", "||", "^^", "&&", "-or", "-and", "-not"}) + "'" * ast.count,
		'-intg' : _ast2nat_intg,
		'-mat'  : _ast2nat_mat,
		'-piece': lambda self, ast: ' else '.join (f'{self._ast2nat_wrap (p [0], p [0].op in {"=", "-piece", "-lamb"}, {",", "-slice"})}' if p [1] is True else f'{self._ast2nat_wrap (p [0], p [0].op in {"=", "-piece", "-lamb"}, {",", "-slice"})} if {self._ast2nat_wrap (p [1], p [1].op in {"=", "-piece", "-lamb"}, {",", "-slice"})}' for p in ast.piece),
		'-lamb' : lambda self, ast: f'lambda{" " + ", ".join (ast.vars) if ast.vars else ""}: {self._ast2nat_wrap (ast.lamb, ast.lamb.op in {"=", "<>", "-lamb"}, ast.lamb.is_slice)}',
		'-idx'  : _ast2nat_idx,
		'-slice': _ast2nat_slice,
		'-set'  : lambda self, ast: f'{{{", ".join (self._ast2nat_wrap (c, 0, c.is_slice) for c in ast.set)}{_trail_comma (ast.set)}}}' if ast.set else '\\{}',
		'-dict' : _ast2nat_dict,
		'||'    : lambda self, ast: ' || '.join (self._ast2nat_wrap (a, 0, {'=', '<>', ',', '-slice', '-piece', '-lamb', '-or', '-and', '-not'}) for a in ast.union),
		'^^'    : lambda self, ast: ' ^^ '.join (self._ast2nat_wrap (a, 0, {'=', '<>', ',', '-slice', '-piece', '-lamb', '||', '-or', '-and', '-not'}) for a in ast.sdiff),
		'&&'    : lambda self, ast: ' && '.join (self._ast2nat_wrap (a, 0, {'=', '<>', ',', '-slice', '-piece', '-lamb', '||', '^^', '-or', '-and', '-not'}) for a in ast.xsect),
		'-or'   : lambda self, ast: ' or '.join (self._ast2nat_wrap (a, 0, {'=', ',', '-slice', '-piece', '-lamb'}) for a in ast.or_),
		'-and'  : lambda self, ast: ' and '.join (self._ast2nat_wrap (a, 0, {'=', ',', '-slice', '-piece', '-lamb', '-or'}) for a in ast.and_),
		'-not'  : lambda self, ast: f'not {self._ast2nat_wrap (ast.not_, 0, {"=", ",", "-slice", "-piece", "-lamb", "-or", "-and"})}',
		'-ufunc': _ast2nat_ufunc,
		'-subs' : _ast2nat_subs,
		'-sym'  : lambda self, ast: f'${ast.sym}' if ast.is_sym_unqualified else f'${ast.sym}({", ".join (tuple (f"{k} = {self._ast2nat_wrap (a, 0, a.is_comma)}" for k, a in ast.kw))})',

		'-text' : lambda self, ast: ast.nat,
	}

#...............................................................................................
class ast2py: # abstract syntax tree -> Python code text
	def __init__ (self): self.parent = self.ast = None # pylint droppings
	def __new__ (cls, ast, retxlat = False, ass2cmp = True):
		self         = super ().__new__ (cls)
		self.ass2cmp  = ass2cmp
		self.parents = [None]
		self.parent  = self.ast = AST.Null

		astx = sxlat.xlat_funcs2asts (ast, sxlat.XLAT_FUNC2AST_PY)
		astS = sxlat.xlat_pyS (astx) if _PYS else astx # 1/2 -> S(1)/2
		py   = self._ast2py (astS)

		return py if not retxlat else (py, (astx if astx != ast else None))

	def _ast2py (self, ast):
		self.parents.append (self.ast)

		self.parent = self.ast
		self.ast    = ast

		py          = self._ast2py_funcs [ast.op] (self, ast)

		del self.parents [-1]

		self.ast    = self.parent
		self.parent = self.parents [-1]

		return py

	def _ast2py_curly (self, ast):
		if ast.is_cmp_in or ast.strip_minus.op in {',', '+', '*', '/'} or ast.strip_minus.is_log_with_base:
			return self._ast2py_paren (ast)
		else:
			return self._ast2py (ast)

	def _ast2py_paren (self, ast, paren = _None):
		if paren is _None:
			return self._ast2py (ast) if ast.is_paren else f'({self._ast2py (ast)})'

		if ((ast.op in paren) if isinstance (paren, set) else paren):
			return f'({self._ast2py (ast)})'

		return self._ast2py (ast)

	def _ast2py_ass (self, ast):
		is_top_ass = self.parent.op in {None, ';'}

		if is_top_ass:
			if ast.ass_valid:
				ast = ast.ass_valid
			else:
				is_top_ass = False

		if is_top_ass or self.parent.is_func: # present assignment with = instead of Eq for keyword argument or at top level?
			return f'{self._ast2py_paren (ast.lhs) if ast.lhs.is_lamb else self._ast2py (ast.lhs)} = {self._ast2py (ast.rhs)}'

		return f'Eq({self._ast2py_paren (ast.lhs, bool (ast.lhs.is_comma))}, {self._ast2py_paren (ast.rhs, bool (ast.rhs.is_comma))}{", 1" if _SYM_MARK_PY_ASS_EQ else ""})' # _SYM_MARK_PY_ASS_EQ for when testing only

	_ast2py_cmpfuncs = {'==': 'Eq', '!=': 'Ne', '<': 'Lt', '<=': 'Le', '>': 'Gt', '>=': 'Ge'}

	def _ast2py_cmp (self, ast):
		def cmppy (lhs, rel, rhs):
			if rel in {'in', 'notin'}:
				return f'{self._ast2py_paren (lhs, lhs.is_cmp_in)} {AST.Cmp.PYFMT.get (rel, rel)} {self._ast2py_paren (rhs, rhs.is_cmp_in)}'
			else:
				return f'{self._ast2py_cmpfuncs [rel]}({self._ast2py_paren (lhs, bool (lhs.is_comma))}, {self._ast2py_paren (rhs, bool (rhs.is_comma))})'

		if ast.cmp.len == 1:
			return cmppy (ast.lhs, *ast.cmp [0])
		else:
			return f'And({cmppy (ast.lhs, *ast.cmp [0])}, {", ".join (cmppy (ast.cmp [i - 1] [1], *ast.cmp [i]) for i in range (1, ast.cmp.len))})'

	def _ast2py_attr (self, ast):
		obj = self._ast2py_paren (ast.obj, ast.obj.is_log_with_base or ast.obj.op in {"=", "<>", "#", ",", "-", "+", "*", "/", "^"})

		if ast.is_attr_func:
			args, kw = AST.args2kwargs (ast.args, self._ast2py, ass2cmp = self.ass2cmp)

			return f'{obj}.{ast.attr}({", ".join (args + [f"{k} = {a}" for k, a in kw.items ()])})'

		return f'{obj}.{ast.attr}'

	def _ast2py_minus (self, ast):
		s = self._ast2py_paren (ast.minus, ast.minus.is_add or ast.minus.is_cmp_in or (ast.minus.is_idx and ast.minus.obj.is_num) or
				(ast.minus.is_mul and (not self.parent.is_add or ast is self.parent.add [0] or (ast.minus.mul [0].is_idx and ast.minus.mul [0].obj.is_num))))

		return f'-({s})' if s [:1] != '(' and (ast.minus.strip_fdpi.is_num_pos and not self.parent.is_add) else f'-{s}'

	def _ast2py_add (self, ast):
		return ' + '.join (self._ast2py_paren (n, n.is_cmp_in or (n is not ast.add [0] and (n.is_num_neg or (n.is_mul and _ast_is_neg (n.mul [0]))))) for n in ast.add).replace (' + -', ' - ')

	def _ast2py_mul (self, ast):
		def py (a):
			return self._ast2py_paren (a, a.is_cmp_in or a.op in {',', '+'} or (a is not ast.mul [0] and (a.is_div or a.is_log_with_base)))

		mul  = [py (ast.mul [0])]
		lamb = ast.mul [0]

		for i in range (1, ast.mul.len):
			a = ast.mul [i]

			if a.is_paren_free and i not in ast.exp:
				if lamb.is_lamb: # lambda call so merge into one string without '*'
					mul [-1] += py (a)
					lamb      = lamb.lamb

					continue

				lamb = AST.Null

				if ast.mul [i - 1].is_var:
					diff = _SYM_USER_VARS.get (ast.mul [i - 1].var, AST.Null)

					if diff.is_diff_any_ufunc: # possible ics call to derivative of ufunc
						if diff.diff_any.apply_argskw (a.paren.as_ufunc_argskw):
							subs = []

							for s, d in zip (diff.diff_any.vars, a.paren.comma if isinstance (a.paren.comma, tuple) else (a.paren,)):
								if s != d:
									subs.append ((s, d))

							if subs:
								mul [-1] = self._ast2py_subs (AST ('-subs', None, tuple (subs)), exprstr = mul [-1])

						continue

			mul.append (py (a))

		return mul [0] if len (mul) == 1 else '*'.join (mul)

	def _ast2py_div (self, ast):
		nn = _ast_is_neg (ast.numer)
		n  = self._ast2py_paren (ast.numer) if nn else self._ast2py_curly (ast.numer)
		d  = self._ast2py_curly (ast.denom)
		s  = " / " if nn or (ast.numer.strip_minus.strip_pseudo.op not in {"#", "@"} and not (ast.numer.is_func and ast.numer.func == 'S' and ast.numer.args.len == 1 and ast.numer.args [0].op in {"#", "@"})) or \
			ast.denom.strip_minus.strip_pseudo.op not in {"#", "@"} or d.lstrip ("-") [:1] == "(" else "/"

		return f'{n}{s}{d}'

	def _ast2py_pow (self, ast):
		b = self._ast2py_paren (ast.base) if _ast_is_neg (ast.base) or ast.base.is_pow else self._ast2py_curly (ast.base)
		e = self._ast2py_paren (ast.exp) if ast.exp.strip_minus.is_sqrt_with_idx else self._ast2py_curly (ast.exp)

		return f'{b}**{e}'

	def _ast2py_log (self, ast):
		if ast.base is None:
			return f'ln({self._ast2py (ast.log)})'
		else:
			return f'ln({self._ast2py (ast.log)}) / ln({self._ast2py (ast.base)})'

	def _ast2py_func (self, ast):
		if ast.func in {AST.Func.NOREMAP, AST.Func.NOEVAL}:
			return self._ast2py (ast.args [0])

		args, kw = AST.args2kwargs (ast.args, self._ast2py, ass2cmp = self.ass2cmp)

		return f'{ast.func}({", ".join (args + [f"{k} = {a}" for k, a in kw.items ()])})'

	def _ast2py_lim (self, ast):
		return \
				f'''Limit({self._ast2py (ast.lim)}, {self._ast2py (ast.lvar)}, {self._ast2py_paren (ast.to, ast.to.is_comma)}''' \
				f'''{", dir = '+-'" if ast.dir is None else ", dir = '-'" if ast.dir == '-' else ""})'''

	def _ast2py_diff (self, ast):
		args = sum (((self._ast2py (AST ('@', v)),) if p == 1 else (self._ast2py (AST ('@', v)), str (p)) for v, p in ast.dvs), ())

		return f'Derivative({self._ast2py (ast.diff._strip_paren (keeptuple = True))}, {", ".join (args)})'

	def _ast2py_intg (self, ast):
		if ast.intg is not None and ast.intg.is_intg:
			intg = self._ast2py_intg (ast.intg)
		else:
			intg = f'Integral({1 if ast.intg is None else self._ast2py (ast.intg)})'

		if ast.from_ is None:
			intg = intg [:-1] + f', {self._ast2py (ast.dv.as_var)})'
		else:
			intg = intg [:-1] + f', ({self._ast2py (ast.dv.as_var)}, {self._ast2py (ast.from_)}, {self._ast2py (ast.to)}))'

		return intg

	def _ast2py_mat (self, ast):
		if not ast.rows:
			return 'Matrix()'
		elif ast.is_mat_column and not any (r [0].is_brack for r in ast.mat):
			return f"Matrix([{', '.join (self._ast2py (row [0]) for row in ast.mat)}])"
		else:
			return f"""Matrix([{', '.join (f'[{", ".join (self._ast2py (e) for e in row)}]' for row in ast.mat)}])"""

	def _ast2py_idx (self, ast):
		obj = self._ast2py_paren (ast.obj, ast.obj.is_num_neg or ast.obj.is_log_with_base or ast.obj.is_sqrt_with_idx or (ast.obj.is_var and ast.obj.var in _SYM_USER_FUNCS) or
			ast.obj.op in {"=", "<>", ",", "+", "*", "/", "^", "-", "-lim", "-sum", "-diff", "-intg", "-piece"})

		if ast.idx.len and ast.idx [0].is_var_null:
			idx = '['
		elif ast.idx.len == 1 and ast.idx [0].strip_paren.is_slice:
			idx = f'[{self._ast2py (ast.idx [0])}]'
		else:
			idx = f'[{self._ast2py (AST.tuple2ast (ast.idx))}]'

		return f'{obj}{idx}'

	def _ast2py_slice (self, ast):
		if self.parent.is_idx and any (i is ast for i in self.parent.idx) or \
				self.parent.is_comma and len (self.parents) > 1 and self.parents [-2].is_idx and ast in self.parents [-2].idx:
			b = _ast_slice_bounds (ast)

			return ':'.join (self._ast2py_paren (a, a is not b [-1] and a.has_tail_lambda_solo or a.op in {'=', ',', '-slice'}) for a in b)

		args = _ast_slice_bounds (ast, AST.None_, allow1 = True)

		if len (args) == 3 and args [2] is AST.None_:
			args = args [:2]

		return f'slice({", ".join (self._ast2py_paren (a, a.op in {"=", ",", "-slice"}) for a in args)})'

	def _ast2py_sdiff (self, ast):
		sdiff = f'SymmetricDifference({self._ast2py (ast.sdiff [0])}, {self._ast2py (ast.sdiff [1])})'

		for i in range (2, ast.sdiff.len):
			sdiff = f'SymmetricDifference({sdiff}, {self._ast2py (ast.sdiff [i])})'

		return sdiff

	def _ast2py_subs (self, ast, exprstr = None):
		def tupletuple (a):
			return self._ast2py (AST ('(', (',', (a,))) if a.strip_paren.is_comma else a)

		if ast.subs.len > 1:
			subs = f'({", ".join (self._ast2py (s) for s, d in ast.subs)}), ({", ".join (self._ast2py (d) for s, d in ast.subs)})'
		else:
			subs = f'{tupletuple (ast.subs [0] [0])}, {tupletuple (ast.subs [0] [1])}'

		return f'Subs({exprstr or self._ast2py_paren (ast.expr, ast.expr.is_comma)}, {subs})'

	_ast2py_funcs = {
		';'     : lambda self, ast: '; '.join (self._ast2py (a) for a in ast.scolon),
		'='     : _ast2py_ass,
		'<>'    : _ast2py_cmp,
		'#'     : lambda self, ast: ast.num,
		'@'     : lambda self, ast: '{' if ast.is_var_null and self.parent.op in {None, ';'} else ast.var,
		'.'     : _ast2py_attr,
		'"'     : lambda self, ast: repr (ast.str_),
		','     : lambda self, ast: f'{", ".join (self._ast2py (parm) for parm in ast.comma)}{_trail_comma (ast.comma)}',
		'('     : lambda self, ast: '(' if ast.paren.is_var_null else f'({self._ast2py (ast.paren)})',
		'['     : lambda self, ast: '[' if ast.brack.len == 1 and ast.brack [0].is_var_null else f'[{", ".join (self._ast2py (b) for b in ast.brack)}]',
		'|'     : lambda self, ast: '|' if ast.abs.is_var_null else f'abs({self._ast2py (ast.abs)})',
		'-'     : _ast2py_minus,
		'!'     : lambda self, ast: f'factorial({self._ast2py (ast.fact)})',
		'+'     : _ast2py_add,
		'*'     : _ast2py_mul,
		'/'     : _ast2py_div,
		'^'     : _ast2py_pow,
		'-log'  : _ast2py_log,
		'-sqrt' : lambda self, ast: f'sqrt({self._ast2py (ast.rad)})' if ast.idx is None else self._ast2py (AST ('^', ast.rad.strip_paren1, ('/', AST.One, ast.idx))),
		'-func' : _ast2py_func,
		'-lim'  : _ast2py_lim,
		'-sum'  : lambda self, ast: f'Sum({self._ast2py (ast.sum)}, ({self._ast2py (ast.svar)}, {self._ast2py_paren (ast.from_, ast.from_.is_comma)}, {self._ast2py (ast.to)}))',
		'-diff' : _ast2py_diff,
		'-diffp': lambda self, ast: f'{"diff(" * ast.count}{self._ast2py (ast.diffp)}{")" * ast.count}',
		'-intg' : _ast2py_intg,
		'-mat'  : _ast2py_mat,
		'-piece': lambda self, ast: 'Piecewise(' + ', '.join (f'({self._ast2py (p [0])}, {True if p [1] is True else self._ast2py (p [1])})' for p in ast.piece) + ')',
		'-lamb' : lambda self, ast: f"""Lambda({ast.vars [0] if ast.vars.len == 1 else f'({", ".join (ast.vars)})'}, {self._ast2py (ast.lamb)})""",
		'-idx'  : _ast2py_idx,
		'-slice': _ast2py_slice,
		'-set'  : lambda self, ast: f'FiniteSet({", ".join (self._ast2py (c) for c in ast.set)})',
		'-dict' : lambda self, ast: f'{{{", ".join (f"{self._ast2py_paren (k, k.has_tail_lambda_solo)}: {self._ast2py (v)}" for k, v in ast.dict)}}}',
		'||'    : lambda self, ast: f'Union({", ".join (self._ast2py (a) for a in ast.union)})',
		'^^'    : _ast2py_sdiff,
		'&&'    : lambda self, ast: f'Intersection({", ".join (self._ast2py (a) for a in ast.xsect)})',
		'-or'   : lambda self, ast: f'Or({", ".join (self._ast2py_paren (a, a.is_comma) for a in ast.or_)})',
		'-and'  : lambda self, ast: f'And({", ".join (self._ast2py_paren (a, a.is_comma) for a in ast.and_)})',
		'-not'  : lambda self, ast: f'Not({self._ast2py_paren (ast.not_, ast.not_.is_ass or ast.not_.is_comma)})',
		'-ufunc': lambda self, ast: f'Function({", ".join ((f"{ast.ufunc!r}",) + tuple (f"{k} = {self._ast2py_paren (a, a.is_comma)}" for k, a in ast.kw))})' + (f'({", ".join (self._ast2py (v) for v in ast.vars)})' if ast.vars else ''),
		'-subs' : _ast2py_subs,
		'-sym'  : lambda self, ast: f'Symbol({", ".join ((f"{ast.sym!r}",) + tuple (f"{k} = {self._ast2py_paren (a, a.is_comma)}" for k, a in ast.kw))})',

		'-text' : lambda self, ast: ast.py,
	}

#...............................................................................................
# Potentially bad __builtins__: eval, exec, globals, locals, vars, setattr, delattr, exit, help, input, license, open, quit, __import__
_builtins_dict         = __builtins__ if isinstance (__builtins__, dict) else __builtins__.__dict__
_ast2spt_func_builtins = dict (no for no in filter (lambda no: no [1], ((n, _builtins_dict.get (n)) for n in AST.Func.BUILTINS)))
_ast2spt_pyfuncs       = {**_ast2spt_func_builtins, **sp.__dict__, 'simplify': _simplify}

class ast2spt: # abstract syntax tree -> sympy tree (expression)
	_SYMPY_FLOAT_PRECISION = None

	@staticmethod
	def set_precision (ast): # recurse through ast to set sympy float precision according to longest string of digits found
		prec  = 15
		stack = [ast]

		while stack:
			ast = stack.pop ()

			if not isinstance (ast, AST):
				pass # nop
			elif ast.is_num:
				prec = max (prec, len (ast.num)) # will be a little more than number of digits to compensate for falling precision with some calculations
			else:
				stack.extend (ast if ast.op is None else ast [1:])

		ast2spt._SYMPY_FLOAT_PRECISION = prec if prec > 15 else None

	def __init__ (self): self.parent = self.ast = None # pylint kibble
	def __new__ (cls, ast, retxlat = False):
		self         = super ().__new__ (cls)
		self.parents = [None]
		self.parent  = self.ast = AST.Null

		clear_cache () # don't want sympy object annotations to stick around like ?F(x) coming back as ?F(xi_1)

		astx = sxlat.xlat_funcs2asts (ast, sxlat.XLAT_FUNC2AST_SPT)
		spt  = self._ast2spt (astx)

		if _DOIT:
			spt = _doit (spt)

		if _POST_SIMPLIFY:
			spt = _simplify (spt)

		return spt if not retxlat else (spt, (astx if astx != ast else None))

	def _ast2spt (self, ast):
		self.parents.append (self.ast)

		self.parent = self.ast
		self.ast    = ast

		spt         = self._ast2spt_funcs [ast.op] (self, ast)

		del self.parents [-1]

		self.ast    = self.parent
		self.parent = self.parents [-1]

		return spt

	def _ast2spt_ass (self, ast):
		lhs, rhs = self._ast2spt (ast.lhs), self._ast2spt (ast.rhs)

		try:
			return EqAss (lhs, rhs) # try to use SymPy comparison object
		except:
			return lhs == rhs # fall back to Python comparison

	_ast2spt_cmpfuncs = {
		'=='   : (EqCmp, lambda a, b: a == b),
		'!='   : (sp.Ne, lambda a, b: a != b),
		'<'    : (sp.Lt, lambda a, b: a < b),
		'<='   : (sp.Le, lambda a, b: a <= b),
		'>'    : (sp.Gt, lambda a, b: a > b),
		'>='   : (sp.Ge, lambda a, b: a >= b),
		'in'   : (sp.Contains, lambda a, b: a in b),
		'notin': (lambda a, b: sp.Not (sp.Contains (a, b)), lambda a, b: a not in b),
	}

	def _ast2spt_cmp (self, ast):
		def cmpspt (lhs, rel, rhs):
			fspt, fpy = self._ast2spt_cmpfuncs [rel]

			try:
				return fspt (lhs, rhs) # try to use SymPy comparison object
			except:
				return fpy (lhs, rhs) # fall back to Python comparison

		hss = [self._ast2spt (ast.lhs)] + [self._ast2spt (cmp [1]) for cmp in ast.cmp]

		if ast.cmp.len == 1:
			return cmpspt (hss [0], ast.cmp [0] [0], hss [1])
		else:
			return sp.And (*(_sympify (cmpspt (hss [i], ast.cmp [i] [0], hss [i + 1])) for i in range (ast.cmp.len)))

	_ast2spt_consts = { # 'e' and 'i' dynamically set on use from AST.E or AST.I
		'pi'   : sp.pi,
		'oo'   : sp.oo,
		'zoo'  : sp.zoo,
		'None' : None,
		'True' : True,
		'False': False,
		'nan'  : sp.nan,
	}

	def _ast2spt_var (self, ast):
		spt = {**self._ast2spt_consts, AST.E.var: sp.E, AST.I.var: sp.I}.get (ast.var, _None)

		if spt is _None:
			if len (ast.var) > 1 and ast.var not in AST.Var.GREEK:
				spt = getattr (sp, ast.var, _None)

			if spt is _None:
				spt = sp.Symbol (ast.var)
			elif callable (spt):
				spt = Callable (ast, spt)

		return spt

	def _ast2spt_attr (self, ast):
		obj = ast.obj
		spt = None

		while obj.is_func and obj.args and (obj.func == AST.Func.NOEVAL or obj.func == AST.Func.NOREMAP):
			obj = obj.args [0]

		if obj.is_var: # and obj.var not in self.vars: # always support S.Half and the like unless base object redefined by assignment
			spt = getattr (sp, obj.var, None)

		if spt is None:
			spt = self._ast2spt (ast.obj)

		try:
			attr = getattr (spt, ast.attr)

			return attr if ast.is_attr_var else _ast_func_call (attr, ast.args, self._ast2spt)

		except AttributeError as e: # unresolved attributes of expressions with free vars remaining should not raise
			try:
				if not isinstance (spt, NoEval) and not spt.free_symbols:
					raise e

			except:
				raise e from None

		return NoEval (AST ('.', spt2ast (spt), *ast [2:]))

	def _ast2spt_add (self, ast): # specifically try to subtract negated objects (because of sets)
		itr = iter (ast.add)
		res = self._ast2spt (next (itr))

		for arg in itr:
			arg, neg    = arg.strip_minus_retneg
			arg, is_neg = self._ast2spt (arg), neg.is_neg

			try:
				res = res - arg if is_neg else res + arg
			except:
				res = sp.sympify (res) - sp.sympify (arg) if is_neg else sp.sympify (res) + sp.sympify (arg)

		return res

	def _ast2spt_mul (self, ast): # handle dynamic cases of function calls due to usage of implicit multiplication
		mul = [self._ast2spt (ast.mul [0])]

		for i in range (1, ast.mul.len):
			a = ast.mul [i]
			m = self._ast2spt (a)

			if a.is_paren_free and i not in ast.exp: # non-explicit multiplication with tuple - possible function call intended: "y (...)"
				if callable (mul [-1]): # isinstance (o, sp.Lambda): # Lambda or other callable being called
					mul [-1] = mul [-1] (*m) if isinstance (m, tuple) else mul [-1] (m)

					continue

				if ast.mul [i - 1].is_diff_any_ufunc: # possible ics call to derivative of ufunc
					if ast.mul [i - 1].diff_any.apply_argskw (a.paren.as_ufunc_argskw):
						src, dst = [], []

						if isinstance (mul [-1], sp.Derivative) and isinstance (mul [-1].args [0], sp_AppliedUndef):
							for s, d in zip (mul [-1].args [0].args, m if isinstance (m, tuple) else (m,)): # ast.mul [i - 1].diff_any.vars,
								if s != d:
									src.append (s)
									dst.append (d)

					if src:
						mul [-1] = spt = sp.Subs (mul [-1], tuple (src), tuple (dst))
						spt.doit = lambda self = spt, *args, **kw: self # disable doit because loses information

					continue

			mul.append (m)

		return mul [0] if len (mul) == 1 else _Mul (*mul)

	def _ast2spt_func (self, ast):
		if ast.func == AST.Func.NOREMAP: # special reference meta-function
			return self._ast2spt (ast.args [0])

		if ast.func == AST.Func.NOEVAL: # special no-evaluate meta-function
			if self.parent.is_lamb and ast is self.parent.lamb: # if at top-level in lambda body then lambda handles differnt meaning of this pseudo-func
				return self._ast2spt (ast.args [0])
			else:
				return NoEval (ast.args [0])

		func = ast.func

		if func in _SYM_USER_FUNCS and _SYM_USER_VARS.get (func, AST.Null).is_var: # concrete function mapped to user var
			func = _SYM_USER_VARS [func].var

		func = _ast2spt_pyfuncs.get (func)

		if func is not None:
			return _ast_func_call (func, ast.args, self._ast2spt)

		if ast.func in _SYM_USER_FUNCS: # user lambda, within other lambda if it got here
			return NoEval (ast)

		raise NameError (f'function {ast.func!r} is not defined')

	def _ast2spt_diff (self, ast):
		args     = sum (((self._ast2spt (AST ('@', v)),) if p == 1 else (self._ast2spt (AST ('@', v)), sp.Integer (p)) for v, p in ast.dvs), ())
		spt      = self._ast2spt (ast.diff)

		return spt.diff (*args) if isinstance (spt, sp_AppliedUndef) else sp.Derivative (spt, *args)

	def _ast2spt_diffp (self, ast):
		spt      = self._ast2spt (ast.diffp)
		is_ufunc = isinstance (spt, sp_AppliedUndef)

		for _ in range (ast.count):
			spt = spt.diff () if is_ufunc else sp.Derivative (spt)

		return spt

	def _ast2spt_intg (self, ast):
		if ast.from_ is None:
			if ast.intg is None:
				return sp.Integral (1, sp.Symbol (ast.dv.var_name))
			else:
				return sp.Integral (self._ast2spt (ast.intg), sp.Symbol (ast.dv.var_name))

		else:
			if ast.intg is None:
				return sp.Integral (1, (sp.Symbol (ast.dv.var_name), self._ast2spt (ast.from_), self._ast2spt (ast.to)))
			else:
				return sp.Integral (self._ast2spt (ast [1]), (sp.Symbol (ast.dv.var_name), self._ast2spt (ast.from_), self._ast2spt (ast.to)))

	def _ast2spt_lamb (self, ast):
		if ast.vars.len == 1 and ast.lamb.strip_paren.is_var and ast.lamb.strip_paren.var == ast.vars [0]: # identity lambda
			spt = IdLambda (None, sp.Symbol (ast.vars [0]))

		else:
			spt = sp.Lambda (tuple (sp.Symbol (v) for v in ast.vars), self._ast2spt (ast.lamb))

			if not (ast.lamb.is_func and ast.lamb.func == AST.Func.NOEVAL):
				spt.doit = lambda self, *args, **kw: self # disable doit for lambda definition

		return spt

	def _ast2spt_idx (self, ast):
		spt = self._ast2spt (ast.obj)
		idx = self._ast2spt (ast.idx [0]) if len (ast.idx) == 1 else tuple (self._ast2spt (i) for i in ast.idx)

		try:
			return spt [idx]
		except TypeError: # invalid indexing of expressions with free vars remaining should not raise
			if not isinstance (spt, NoEval) and not getattr (spt, 'free_symbols', None) and not hasattr (spt, '__getitem__'):
				raise

		return NoEval (AST ('-idx', spt2ast (spt), ast.idx))

	def _ast2spt_sdiff (self, ast):
		sdiff = sp.SymmetricDifference (self._ast2spt (ast.sdiff [0]), self._ast2spt (ast.sdiff [1]))

		for i in range (2, ast.sdiff.len):
			sdiff = sp.SymmetricDifference (sdiff, self._ast2spt (ast.sdiff [i]))

		return sdiff

	def _ast2spt_ufunc (self, ast):
		spt                   = sp.Function (ast.ufunc, **{k: _bool_or_None (self._ast2spt (a)) for k, a in ast.kw}) (*(self._ast2spt (v) for v in ast.vars))
		spt.is_ufunc_explicit = ast.is_ufunc_explicit # try to pass explicit state of ufunc through
		assum                 = _spt_assumptions (spt)

		if assum not in _SYM_ASSUM_REDUCE:
			_SYM_ASSUM_REDUCE [assum] = ast.kw

		return spt

	def _ast2spt_subs (self, ast):
		return _subs (self._ast2spt (ast.expr), [(self._ast2spt (s), self._ast2spt (d)) for s, d in ast.subs])

	_ast2spt_funcs = {
		';'     : lambda self, ast: _raise (RuntimeError ('semicolon expression should never get here')),
		'='     : _ast2spt_ass,
		'<>'    : _ast2spt_cmp,
		'#'     : lambda self, ast: sp.Integer (ast.num) if ast.is_num_int else sp.Float (ast.num, ast2spt._SYMPY_FLOAT_PRECISION),
		'@'     : _ast2spt_var,
		'.'     : _ast2spt_attr,
		'"'     : lambda self, ast: ast.str_,
		','     : lambda self, ast: tuple (self._ast2spt (p) for p in ast.comma),
		'{'     : lambda self, ast: self._ast2spt (ast.curly),
		'('     : lambda self, ast: self._ast2spt (ast.paren),
		'['     : lambda self, ast: [self._ast2spt (b) for b in ast.brack],
		'|'     : lambda self, ast: sp.Abs (self._ast2spt (ast.abs)),
		'-'     : lambda self, ast: -self._ast2spt (ast.minus),
		'!'     : lambda self, ast: sp.factorial (self._ast2spt (ast.fact)),
		'+'     : _ast2spt_add,
		'*'     : _ast2spt_mul,
		'/'     : lambda self, ast: _Mul (self._ast2spt (ast.numer), _Pow (self._ast2spt (ast.denom), -1)),
		'^'     : lambda self, ast: _Pow (self._ast2spt (ast.base), self._ast2spt (ast.exp)),
		'-log'  : lambda self, ast: sp.log (self._ast2spt (ast.log)) if ast.base is None else sp.log (self._ast2spt (ast.log), self._ast2spt (ast.base)),
		'-sqrt' : lambda self, ast: _Pow (self._ast2spt (ast.rad), _Pow (sp.S (2), -1)) if ast.idx is None else _Pow (self._ast2spt (ast.rad), _Pow (self._ast2spt (ast.idx), -1)),
		'-func' : _ast2spt_func,
		'-lim'  : lambda self, ast: (sp.Limit if ast.dir else sp.limit) (self._ast2spt (ast.lim), self._ast2spt (ast.lvar), self._ast2spt (ast.to), dir = ast.dir or '+-'),
		'-sum'  : lambda self, ast: sp.Sum (self._ast2spt (ast.sum), (self._ast2spt (ast.svar), self._ast2spt (ast.from_), self._ast2spt (ast.to))),
		'-diff' : _ast2spt_diff,
		'-diffp': _ast2spt_diffp,
		'-intg' : _ast2spt_intg,
		'-mat'  : lambda self, ast: sp.Matrix ([[self._ast2spt (e) for e in row] for row in ast.mat]),
		'-piece': lambda self, ast: sp.Piecewise (*((self._ast2spt (p [0]), True if p [1] is True else self._ast2spt (p [1])) for p in ast.piece)),
		'-lamb' : _ast2spt_lamb,
		'-idx'  : _ast2spt_idx,
		'-slice': lambda self, ast: slice (*(self._ast2spt (a) if a else a for a in _ast_slice_bounds (ast, None))),
		'-set'  : lambda self, ast: sp.FiniteSet (*(self._ast2spt (a) for a in ast.set)),
		'-dict' : lambda self, ast: dict ((self._ast2spt (k), self._ast2spt (v)) for k, v in ast.dict),
		'||'    : lambda self, ast: sp.Union (*(self._ast2spt (a) for a in ast.union)),
		'^^'    : _ast2spt_sdiff,
		'&&'    : lambda self, ast: sp.Intersection (*(self._ast2spt (a) for a in ast.xsect)),
		'-or'   : lambda self, ast: sp.Or (*(_sympify (self._ast2spt (a), sp.Or, bool) for a in ast.or_)),
		'-and'  : lambda self, ast: sp.And (*(_sympify (self._ast2spt (a), sp.And, bool) for a in ast.and_)),
		'-not'  : lambda self, ast: _sympify (self._ast2spt (ast.not_), sp.Not, lambda x: not x),
		'-ufunc': _ast2spt_ufunc,
		'-subs' : _ast2spt_subs,
		'-sym'  : lambda self, ast: sp.Symbol (ast.sym, **{k: _bool_or_None (self._ast2spt (a)) for k, a in ast.kw}),

		'-text' : lambda self, ast: ast.spt,
	}

#...............................................................................................
class spt2ast:
	def __init__ (self): self.parent = self.spt = None # pylint droppings
	def __new__ (cls, spt):
		self         = super ().__new__ (cls)
		self.parents = [None]
		self.parent  = self.spt = None

		return _ast_eqcmp2ass (self._spt2ast (spt))

	def _spt2ast (self, spt): # sympy tree (expression) -> abstract syntax tree
		def __spt2ast (spt):
			for cls in spt.__class__.__mro__:
				func = spt2ast._spt2ast_funcs.get (cls)

				if func:
					return func (self, spt)

			tex  = sp.latex (spt)
			text = str (spt)

			if tex [0] == '<' and tex [-1] == '>': # for Python repr style of objects <class something> TODO: Move this to Javascript.
				tex = '\\text{' + tex.replace ("<", "&lt;").replace (">", "&gt;").replace ("\n", "") + '}'

			return AST ('-text', tex, text, text, spt)

		self.parents.append (self.spt)

		self.parent = self.spt
		self.spt    = spt

		spt         = __spt2ast (spt)

		del self.parents [-1]

		self.spt    = self.parent
		self.parent = self.parents [-1]

		return spt

	def _spt2ast_num (self, spt):
		s = str (spt)

		if s [:3] == '0.e' or s [:4] == '-0.e':
			return AST ('#', '0')

		num = AST ('#', s)

		if num.grp [5]:
			return AST ('#', ''.join (num.grp [:6] + num.grp [7:]))

		e = len (num.grp [2]) + num.num_exp_val

		return AST ('#', \
				f'{num.grp [0]}{num.grp [1]}e+{e}'     if e >= 16 else \
				f'{num.grp [0]}{num.grp [1]}{"0" * e}' if e >= 0 else \
				f'{num.grp [0]}{num.grp [1]}e{e}')

	def _spt2ast_Symbol (self, spt):
		if isinstance (spt, sp.Dummy):
			spt = sp.Symbol (spt.name, **spt._assumptions.generator)

		if spt.name and spt == sp.Symbol (spt.name):
			return AST ('@', spt.name)
		else:
			return AST ('-sym', spt.name, tuple (sorted ((k, self._spt2ast (v)) for k, v in spt._assumptions.generator.items ())))

	def _spt2ast_Not (self, spt):
		if isinstance (spt.args [0], sp.Contains): # "not in" compound operator
			return AST ('<>', self._spt2ast (spt.args [0].args [0]), (('notin', self._spt2ast (spt.args [0].args [1])),))
		else:
			return AST ('-not', self._spt2ast (spt.args [0]))

	def _spt2ast_Union (self, spt): # convert union of complements to symmetric difference if present
		if len (spt.args) == 2 and spt.args [0].is_Complement and spt.args [1].is_Complement and \
				spt.args [0].args [0] == spt.args [1].args [1] and spt.args [0].args [1] == spt.args [1].args [0]:
			return AST ('^^', (self._spt2ast (spt.args [0].args [0]), self._spt2ast (spt.args [0].args [1])))

		return self._spt2ast (spt.args [0]) if len (spt.args) == 1 else AST ('||', tuple (self._spt2ast (a) for a in spt.args))

	def _spt2ast_MatrixBase (self, spt):
		if not spt.rows or not spt.cols:
			return AST.MatEmpty
		if spt.cols > 1:
			return AST ('-mat', tuple (tuple (self._spt2ast (e) for e in spt [row, :]) for row in range (spt.rows)))
		else:
			return AST ('-mat', tuple ((self._spt2ast (e),) for e in spt))

	def _spt2ast_Add (self, spt):
		terms = []
		hasO  = False

		for arg in spt.args: # spt._sorted_args: #
			ast  = self._spt2ast (arg)
			hasO = hasO or isinstance (arg, sp.Order)

			if ast.is_num_neg:
				ast = AST ('-', ast.neg ())
			elif ast.is_mul and _ast_is_neg (ast.mul [0]):
				ast = AST ('-', ('*', (ast.mul [0].neg (),) + ast.mul [1:]))

			terms.append (ast)

		if not hasO: # try to order so negative is not first, but not extensively where it might change standard equation forms returned from SymPy
			if spt.args [0].is_number and (not _ast_is_neg (terms [1]) or _ast_is_neg (terms [0])): # spt.args [0] < 0):
				terms = terms [1:] + [terms [0]]
			elif len (terms) == 2 and _ast_is_neg (terms [0]) and not _ast_is_neg (terms [1]):
				terms = terms [::-1]

		return AST ('+', tuple (terms))

	def _spt2ast_Mul (self, spt):
		if spt.args [0] == -1:
			return AST ('-', self._spt2ast (sp.Mul (*spt.args [1:])))

		if spt.args [0].is_negative and isinstance (spt, sp.Number):
			return AST ('-', self._spt2ast (sp.Mul (-spt.args [0], *spt.args [1:])))

		args = spt.args [1:] if spt.args [0] == 1 else spt.args # sometimes we get Mul (1, ...), strip the 1

		if len (spt.args) == 1:
			return self._spt2ast (args [0])

		numer = []
		denom = []
		neg = False

		for arg in args: # absorb products into rational
			if isinstance (arg, sp.Pow) and arg.args [1].is_negative:
				denom.append (self._spt2ast (arg.args [0] if arg.args [1] is sp.S.NegativeOne else _Pow (arg.args [0], -arg.args [1])))
			elif not isinstance (arg, sp.Rational) or arg.q == 1:
				numer.append (self._spt2ast (arg))

			else:
				denom.append (self._spt2ast (arg.q))

				p = arg.p

				if p < 0:
					neg = not neg
					p   = -p

				if p != 1:
					numer.append (self._spt2ast (p))

		neg = (lambda ast: AST ('-', ast)) if neg else (lambda ast: ast)

		if not denom:
			return neg (AST ('*', tuple (numer)) if len (numer) > 1 else numer [0])

		if not numer:
			return neg (AST ('/', AST.One, AST ('*', tuple (denom)) if len (denom) > 1 else denom [0]))

		if _MUL_RATIONAL and len (denom) == 1 and denom [0].is_num: # leading rational enabled?
			if numer [0].is_num:
				return neg (AST ('*', (('/', numer [0], denom [0]), AST ('*', tuple (numer [1:])) if len (numer) > 2 else numer [1])))
			else:
				return neg (AST ('*', (('/', AST.One, denom [0]), AST ('*', tuple (numer)) if len (numer) > 1 else numer [0])))

		return neg (AST ('/', AST ('*', tuple (numer)) if len (numer) > 1 else numer [0], AST ('*', tuple (denom)) if len (denom) > 1 else denom [0]))

	def _spt2ast_Pow (self, spt):
		if spt.args [1].is_negative:
			return AST ('/', AST.One, self._spt2ast (spt.args [0] if spt.args [1] is sp.S.NegativeOne else _Pow (spt.args [0], -spt.args [1])))

		if spt.args [1] == 0.5:
			return AST ('-sqrt', self._spt2ast (spt.args [0]))

		return AST ('^', self._spt2ast (spt.args [0]), self._spt2ast (spt.args [1]))

	def _spt2ast_MatPow (self, spt):
		try: # compensate for some MatPow.doit() != mat**pow
			return self._spt2ast (spt.args [0]**spt.args [1])
		except:
			return AST ('^', self._spt2ast (spt.args [0]), self._spt2ast (spt.args [1]))

	def _spt2ast_Derivative (self, spt):
		if len (spt.args) == 2:
			syms = _free_symbols (spt.args [0])

			if len (syms) == 1 and spt.args [1] [0] == syms.pop ():
				return AST ('-diffp', self._spt2ast (spt.args [0]), int (spt.args [1] [1]))

		return AST ('-diff', self._spt2ast (spt.args [0]), 'd', tuple ((s.name, int (p)) for s, p in spt.args [1:]))

	def _spt2ast_Integral (self, spt):
		if len (spt.args [1]) == 3:
			return AST ('-intg', self._spt2ast (spt.args [0]), AST ('@', f'd{self._spt2ast (spt.args [1] [0]) [1]}'), self._spt2ast (spt.args [1] [1]), self._spt2ast (spt.args [1] [2]))
		else:
			return AST ('-intg', self._spt2ast (spt.args [0]), AST ('@', f'd{self._spt2ast (spt.args [1] [0]) [1]}'))

	def _spt2ast_Function (self, spt, name = None, args = None, kw = None):
		if name is None:
			name = spt.__class__.__name__

		if args is None:
			args = spt.args

		if kw:
			return AST ('-func', name, tuple (self._spt2ast (arg) for arg in spt.args) + tuple (AST ('=', ('@', kw), a) for kw, a in kw))
		else:
			return AST ('-func', name, tuple (self._spt2ast (arg) for arg in spt.args))

	def _spt2ast_AppliedUndef (self, spt):
		if spt.__class__.__name__ == 'slice': # special cased converted slice object with start, stop and step present, this is REALLY unnecessary...
			return AST ('-slice', *tuple (self._spt2ast (s) for s in spt.args))

		name  = f'?{spt.name}' if not spt.name or getattr (spt, 'is_ufunc_explicit', False) else spt.name
		assum = _spt_assumptions (spt)
		kw    = _SYM_ASSUM_REDUCE [assum] if assum in _SYM_ASSUM_REDUCE else tuple (sorted ((k, self._spt2ast (a))
				for k, a in filter (lambda kv: kv [1] is not None and kv != ('commutative', True), spt._assumptions.items ())))

		return AST ('-ufunc', name, tuple (self._spt2ast (a) for a in spt.args), kw)

	_dict_keys   = {}.keys ().__class__
	_dict_values = {}.values ().__class__
	_dict_items  = {}.items ().__class__

	_spt2ast_Limit_dirs = {'+': ('+',), '-': ('-',), '+-': ()}

	_spt2ast_funcs = {
		NoEval: lambda self, spt: spt.ast (),
		Callable: lambda self, spt: spt.ast,

		None.__class__: lambda self, spt: AST.None_,
		bool: lambda self, spt: AST.True_ if spt else AST.False_,
		int: lambda self, spt: AST ('#', str (spt)),
		float: lambda self, spt: AST ('#', str (_fltoint (spt))),
		complex: lambda self, spt: AST ('#', str (_fltoint (spt.real))) if not spt.imag else AST ('+', (('#', str (_fltoint (spt.real))), AST.I if spt.imag == 1 else ('*', (('#', str (_fltoint (spt.imag))), AST.I)))),
		str: lambda self, spt: AST ('"', spt),
		tuple: lambda self, spt: AST ('(', (',', tuple (self._spt2ast (e) for e in spt))),
		list: lambda self, spt: AST ('[', tuple (self._spt2ast (e) for e in spt)),
		set: lambda self, spt: AST ('-set', tuple (self._spt2ast (e) for e in spt)),
		frozenset: lambda self, spt: AST ('-set', tuple (self._spt2ast (e) for e in spt)),
		dict: lambda self, spt: AST ('-dict', tuple ((self._spt2ast (k), self._spt2ast (v)) for k, v in spt.items ())),
		slice: lambda self, spt: AST ('-slice', False if spt.start is None else self._spt2ast (spt.start), False if spt.stop is None else self._spt2ast (spt.stop), None if spt.step is None else self._spt2ast (spt.step)),
		_dict_keys: lambda self, spt: AST ('[', tuple (self._spt2ast (e) for e in spt)),
		_dict_values: lambda self, spt: AST ('[', tuple (self._spt2ast (e) for e in spt)),
		_dict_items: lambda self, spt: AST ('[', tuple (self._spt2ast (e) for e in spt)),
		sp.Tuple: lambda self, spt: self._spt2ast (spt.args),

		sp.Integer: _spt2ast_num,
		sp.Float: _spt2ast_num,
		sp.Rational: lambda self, spt: AST ('/', ('#', str (spt.p)), ('#', str (spt.q))) if spt.p >= 0 else AST ('-', ('/', ('#', str (-spt.p)), ('#', str (spt.q)))),
		sp.S.ImaginaryUnit: lambda self, spt: AST.I,
		sp.S.Pi: lambda self, spt: AST.Pi,
		sp.S.Exp1: lambda self, spt: AST.E,
		sp.S.Infinity: lambda self, spt: AST.Infty,
		sp.S.NegativeInfinity: lambda self, spt: AST ('-', AST.Infty),
		sp.S.ComplexInfinity: lambda self, spt: AST.CInfty,
		sp.S.NaN: lambda self, spt: AST.NaN,

		sp.Symbol: _spt2ast_Symbol,

		sp.S.BooleanTrue: lambda self, spt: AST.True_,
		sp.S.BooleanFalse: lambda self, spt: AST.False_,
		sp.Or: lambda self, spt: AST ('-or', tuple (self._spt2ast (a) for a in spt.args)),
		sp.And: lambda self, spt: (lambda args: sxlat._xlat_f2a_And (*args, canon = True) or AST ('-and', args)) (tuple (self._spt2ast (a) for a in spt.args)), # collapse possibly previously segmented extended comparison
		sp.Not: _spt2ast_Not,

		EqAss: lambda self, spt: AST ('=', self._spt2ast (spt.args [0]), self._spt2ast (spt.args [1])),
		EqCmp: lambda self, spt: AST ('<>', self._spt2ast (spt.args [0]), (('==', self._spt2ast (spt.args [1])),), is_cmp_explicit = True),
		sp.Eq: lambda self, spt: AST ('<>', self._spt2ast (spt.args [0]), (('==', self._spt2ast (spt.args [1])),)),
		sp.Ne: lambda self, spt: AST ('<>', self._spt2ast (spt.args [0]), (('!=', self._spt2ast (spt.args [1])),)),
		sp.Lt: lambda self, spt: AST ('<>', self._spt2ast (spt.args [0]), (('<',  self._spt2ast (spt.args [1])),)),
		sp.Le: lambda self, spt: AST ('<>', self._spt2ast (spt.args [0]), (('<=', self._spt2ast (spt.args [1])),)),
		sp.Gt: lambda self, spt: AST ('<>', self._spt2ast (spt.args [0]), (('>',  self._spt2ast (spt.args [1])),)),
		sp.Ge: lambda self, spt: AST ('<>', self._spt2ast (spt.args [0]), (('>=', self._spt2ast (spt.args [1])),)),

		sp.sets.EmptySet: lambda self, spt: AST.SetEmpty,
		sp.S.Complexes: lambda self, spt: AST.Complexes,
		sp.S.Reals: lambda self, spt: AST.Reals,
		sp.S.Integers: lambda self, spt: AST.Integers,
		sp.S.Naturals: lambda self, spt: AST.Naturals,
		sp.S.Naturals0: lambda self, spt: AST.Naturals0,
		sp.FiniteSet: lambda self, spt: AST ('-set', tuple (self._spt2ast (arg) for arg in spt.args)),
		sp.Contains: lambda self, spt: AST ('<>', self._spt2ast (spt.args [0]), (('in', self._spt2ast (spt.args [1])),)),
		sp.Union: _spt2ast_Union,
		sp.Intersection: lambda self, spt: self._spt2ast (spt.args [0]) if len (spt.args) == 1 else AST.flatcat ('&&', self._spt2ast (spt.args [0]), self._spt2ast (spt.args [1])),
		sp.Complement: lambda self, spt: AST ('+', (self._spt2ast (spt.args [0]), ('-', self._spt2ast (spt.args [1])))),

		sp.matrices.MatrixBase: _spt2ast_MatrixBase,

		sp.Poly: lambda self, spt: self._spt2ast_Function (spt, args = spt.args + spt.gens, kw = (('domain', AST ('"', str (spt.domain))),)),

		sp.Add: _spt2ast_Add,
		sp.Mul: _spt2ast_Mul,
		sp.Pow: _spt2ast_Pow,
		sp.MatPow: _spt2ast_MatPow,

		sp.Abs: lambda self, spt: AST ('|', self._spt2ast (spt.args [0])),
		sp.arg: lambda self, spt: AST ('-func', 'arg', (self._spt2ast (spt.args [0]),)),
		sp.exp: lambda self, spt: AST ('^', AST.E, self._spt2ast (spt.args [0])),
		sp.factorial: lambda self, spt: AST ('!', self._spt2ast (spt.args [0])),
		sp.functions.elementary.trigonometric.TrigonometricFunction: _spt2ast_Function,
		sp.functions.elementary.hyperbolic.HyperbolicFunction: _spt2ast_Function,
		sp.functions.elementary.trigonometric.InverseTrigonometricFunction: _spt2ast_Function,
		sp.functions.elementary.hyperbolic.InverseHyperbolicFunction: _spt2ast_Function,
		sp.log: lambda self, spt: AST ('-log', self._spt2ast (spt.args [0])) if len (spt.args) == 1 else AST ('-log', self._spt2ast (spt.args [0]), self._spt2ast (spt.args [1])),
		sp.Min: lambda self, spt: AST ('-func', 'Min', tuple (self._spt2ast (arg) for arg in spt.args)),
		sp.Max: lambda self, spt: AST ('-func', 'Max', tuple (self._spt2ast (arg) for arg in spt.args)),

		sp.Limit: lambda self, spt: AST (*(('-lim', self._spt2ast (spt.args [0]), self._spt2ast (spt.args [1]), self._spt2ast (spt.args [2])) + spt2ast._spt2ast_Limit_dirs [spt.args [3].name])),
		sp.Sum: lambda self, spt: AST ('-sum', self._spt2ast (spt.args [0]), self._spt2ast (spt.args [1] [0]), self._spt2ast (spt.args [1] [1]), self._spt2ast (spt.args [1] [2])),
		sp.Derivative: _spt2ast_Derivative,
		sp.Integral: _spt2ast_Integral,

		sp.Lambda: lambda self, spt: AST ('-lamb', self._spt2ast (spt.args [1]), tuple (v.name for v in spt.args [0])),
		sp.Order: lambda self, spt: AST ('-func', 'O', ((self._spt2ast (spt.args [0]) if spt.args [1] [1] == 0 else self._spt2ast (spt.args)),)),
		sp.Piecewise: lambda self, spt: AST ('-piece', tuple ((self._spt2ast (t [0]), True if isinstance (t [1], sp.S.BooleanTrue) else self._spt2ast (t [1])) for t in spt.args)),
		sp.Subs: lambda self, spt: AST ('-subs', self._spt2ast (spt.args [0]), tuple ((self._spt2ast (s), self._spt2ast (d)) for s, d in zip (spt.args [1], spt.args [2]))),

		sp.Function: _spt2ast_Function,
		sp_AppliedUndef: _spt2ast_AppliedUndef,
	}

#...............................................................................................
def set_sym_user_vars (user_vars):
	global _SYM_USER_VARS, _SYM_USER_ALL
	_SYM_USER_VARS = user_vars
	_SYM_USER_ALL   = {**_SYM_USER_VARS, **{f: _SYM_USER_VARS.get (f, AST.VarNull) for f in _SYM_USER_FUNCS}}

def set_sym_user_funcs (user_funcs):
	global _SYM_USER_FUNCS, _SYM_USER_ALL
	_SYM_USER_FUNCS = user_funcs
	_SYM_USER_ALL   = {**_SYM_USER_VARS, **{f: _SYM_USER_VARS.get (f, AST.VarNull) for f in _SYM_USER_FUNCS}}

def set_pyS (state):
	global _PYS
	_PYS = state

def set_simplify (state):
	global _POST_SIMPLIFY
	_POST_SIMPLIFY = state

def set_doit (state):
	global _DOIT
	_DOIT = state

def set_prodrat (state):
	global _MUL_RATIONAL
	_MUL_RATIONAL = state

def set_strict (state):
	global _STRICT_TEX
	_STRICT_TEX = state

def set_quick (state):
	global _QUICK_MODE
	_QUICK_MODE = state

class sym: # for single script
	set_sym_user_vars  = set_sym_user_vars
	set_sym_user_funcs = set_sym_user_funcs
	set_pyS            = set_pyS
	set_simplify       = set_simplify
	set_doit           = set_doit
	set_prodrat        = set_prodrat
	set_strict         = set_strict
	set_quick          = set_quick
	ast2tex            = ast2tex
	ast2nat            = ast2nat
	ast2py             = ast2py
	ast2spt            = ast2spt
	spt2ast            = spt2ast

# Builds expression tree from text, nodes are nested AST tuples.

import ast as py_ast
from collections import OrderedDict
import os
import re
import sys


ALL_FUNC_NAMES = AST.Func.PY | AST.Func.TEX | {'sech', 'csch', AST.Func.NOREMAP, AST.Func.NOEVAL}
RESERVED_FUNCS = {'sqrt', 'log', 'ln', 'Function', 'Symbol'} | AST.Func.PY
RESERVED_WORDS = {'in', 'if', 'else', 'or', 'and', 'not', 'Function', 'Symbol'}
RESERVED_ALL   = RESERVED_WORDS | RESERVED_FUNCS

_SP_USER_VARS  = {} # flattened user vars {name: ast, ...}
_SP_USER_FUNCS = set () # set of user funcs present {name, ...} - including hidden Gamma and the like

def _raise (exc):
	raise exc

def _is_valid_var_name (self, text, allow_funcs = False):
	toks = self.tokenize (text)

	if allow_funcs:
		return ((toks [0] == 'VAR' and not toks [0].grp [4]) or toks [0] in {'SQRT', 'LN', 'LOG', 'FUNC'}) and toks [1] == '$end'
	else:
		return toks == ['VAR', '$end'] and not toks [0].grp [4]

def _FUNC_name (FUNC):
	if FUNC.grp [1]:
		return AST.Func.TEX2PY_TRIGH_INV.get (FUNC.grp [1], FUNC.grp [1])

	else:
		func = (FUNC.grp [0] or FUNC.grp [2] or FUNC.text).replace ('\\', '')

		return f'{func}{FUNC.grp [3]}' if FUNC.grp [3] else func

def _ast_from_tok_digit_or_var (tok, i = 0, noerr = False):
	return AST ('#', tok.grp [i]) if tok.grp [i] else \
			AST ('@', AST.Var.ANY2PY.get (tok.grp [i + 2].replace (' ', ''), tok.grp [i + 1]) if tok.grp [i + 2] else tok.grp [i + 1])

def _ast_func_tuple_args (ast):
	ast = ast.strip_curly.strip_paren1

	return ast.comma if ast.is_comma else (ast,)

def _ast_func_sxlat (func, args, **kw):
	return sxlat.xlat_funcs2asts (AST ('-func', func, args, **kw), sxlat.XLAT_FUNC2AST_SPARSER, recurse = False)

def _ast_func_reorder (ast, unconditional = False):
	wrap2 = None

	if ast.is_diffp:
		ast2, wrap2 = ast.diffp, lambda a, count = ast.count: AST ('-diffp', a, count)
	elif ast.is_fact:
		ast2, wrap2 = ast.fact, lambda a: AST ('!', a)
	elif ast.is_pow:
		ast2, wrap2 = ast.base, lambda a: AST ('^', a, ast.exp, is_pypow = ast.is_pypow)
	elif ast.is_attr:
		ast2, wrap2 = ast.obj, lambda a: AST ('.', a, *ast [2:])
	elif ast.is_idx:
		ast2, wrap2 = ast.obj, lambda a: AST ('-idx', a, ast.idx)

	if wrap2:
		ast3, wrap3 = _ast_func_reorder (ast2, unconditional = unconditional)

		if unconditional or ast3.op in {'{', '(', '[', '-lamb'}:
			return ast3, lambda a: wrap2 (wrap3 (a))

	return ast, lambda a: a

def _ast_var_as_ufunc (var, arg, rhs, force_implicit = False): # var guaranteed not to be in _SP_USER_FUNCS
	if var.var != '_' and arg.is_paren and var.is_var_nonconst: # f (vars[, kws]) -> ('-ufunc', 'f', (vars)[, kws]) ... implicit undefined function
		argskw = arg.paren.as_ufunc_argskw
		ufunc  = _SP_USER_VARS.get (var.var, AST.Null)

		if ufunc.is_ufunc:
			ast = ufunc.apply_argskw (argskw)

			if ast:
				return ast.setkw (src_rhs = rhs, src_var_name = var.var)

		elif ufunc.op is None and (force_implicit or AST.UFunc.valid_implicit_args (argskw [0])):
			return AST ('-ufunc', var.var, *argskw, src_rhs = rhs)

	return None

def _ast_diff_func_apply_call (var, expr, arg):
	func = _SP_USER_VARS.get (var, expr [1])
	args = arg.comma if arg.is_comma else (arg,)

	if func.is_lamb:
		if len (args) == func.vars.len:
			subs = tuple (filter (lambda va: va [1] != va [0], zip ((AST ('@', v) for v in func.vars), args)))

			return AST ('-subs', expr, subs) if subs else expr

	elif func.is_ufunc_applied:
		ast = func.apply_argskw (arg.as_ufunc_argskw)

		if ast:
			return AST ('-subs', expr, ast.ufunc_subs) if ast.ufunc_subs else expr

	return None

def _ast_pre_slice (pre, post):
	if not post.is_slice:
		return AST ('-slice', pre, post, None)
	elif post.step is None:
		return AST ('-slice', pre, post.start, post.stop)

	raise SyntaxError ('invalid slice')

def _ast_mulexps_to_muls (ast): # convert explicit multiplication ASTs to normal multiplication ASTs with index information for explicit muls
	if not isinstance (ast, AST):
		return ast
	elif ast.is_mulexp:
		return AST ('*', tuple (_ast_mulexps_to_muls (a) for a in ast.mul), frozenset (range (1, ast.mul.len)))
	else:
		return AST (*tuple (_ast_mulexps_to_muls (a) for a in ast))

def _ast_tail_differential (self, want_pre = False, from_add = False): # find first instance of concatenated differential for integral expression -> pre, dv, wrap -> wrap (\int pre dv), pre may be None, if dv is None then rest are undefined
	lself = lambda a: a

	if self.is_differential or self.is_var_null: # AST.VarNull is for autocomplete
		return None, self, lself, lself

	elif self.is_minus:
		pre, dv, wrap, wrapp = self.minus.tail_differential

		if dv:
			return pre, dv.setkw (dv_has_neg = dv.dv_has_neg or not pre), wrap, lambda a: AST ('-', wrapp (a)) if (not a.is_num_pos or from_add) else wrapp (AST ('#', f'-{a.num}'))

	elif self.is_fact:
		pre, dv, wrap, wrapp = self.fact.tail_differential

		if dv:
			return pre, dv, lambda a: AST ('!', wrap (a)), wrapp

	elif self.is_add:
		pre, dv, wrap, wrapp = self.add [-1].tail_differential_from_add

		if dv and pre:
			return AST ('+', (*self.add [:-1], wrapp (pre))), dv, wrap, lself

	elif self.op in {'*', '*exp'}:
		for i, ast in enumerate (self.mul):
			pre, dv, wrap, wrapp = ast.tail_differential

			if dv:
				if want_pre and (pre or not i):
					want_pre = False

					if ast is not self.mul [-1]:
						continue

				if not i:
					return pre, dv, lambda a: AST (self.op, (wrap (a), *self.mul [1:])), wrapp

				if pre:
					pre = AST (self.op, (*self.mul [:i], pre))
				elif i > 1:
					pre = AST (self.op, self.mul [:i])
				else:
					pre = self.mul [0]

				if i < self.mul.len - 1:
					return pre, dv, lambda a: AST (self.op, (wrap (a), *self.mul [i + 1:])), wrapp

				else:
					return pre, dv, wrap, wrapp

	elif self.is_div:
		pre, dv, wrap, wrapp = self.numer.tail_differential

		if dv:
			return pre, dv, lambda a: AST ('/', wrap (a), self.denom), wrapp

		pre, dv, wrap, wrapp = self.denom.tail_differential_want_pre

		if dv and pre:
			return AST ('/', self.numer, wrapp (pre)), dv, wrap, lself

	elif self.is_pow:
		pre, dv, wrap, wrapp = self.base.tail_differential

		if dv and (pre or not want_pre):
			return pre, dv, lambda a: AST ('^', wrap (a), self.exp), wrapp

		pre, dv, wrap, wrapp = self.exp.tail_differential_want_pre

		if dv and pre:
			return AST ('^', self.base, wrapp (pre)), dv, wrap, lself

	elif self.is_func:
		if self.src:
			if not want_pre and self.src.mul [0].is_differential and self.func in _SP_USER_FUNCS:
				return None, self.src.mul [0], lambda a: AST ('*', (a, self.src.mul [1])), lself

			pre, dv, wrap, wrapp = self.src.mul [1].tail_differential

			if dv:
				if pre:
					pre = wrapp (pre)

					return AST ('-func', self.func, (pre,), src = AST ('*', (self.func, pre))), dv, wrap, lself

				elif self.func in _SP_USER_FUNCS:
					return AST ('@', self.func), dv, wrap, wrapp

	elif self.is_diff:
		if self.src:
			pre, dv, wrap, wrapp = self.src.tail_differential

			if dv and pre:
				pre = _expr_diff (wrapp (pre))

				if pre.is_diff:
					return pre, dv, wrap, lself

	elif self.op in {'-lim', '-sum'}:
		pre, dv, wrap, wrapp = self [1].tail_differential_want_pre

		if dv and pre:
			return AST (self.op, wrapp (pre), *self [2:]), dv, wrap, lself

	return None, None, None, None

AST._tail_differential          = _ast_tail_differential # adding to AST class so it can be cached and accessed as member
AST._tail_differential_want_pre = lambda self: self._tail_differential (want_pre = True)
AST._tail_differential_from_add = lambda self: self._tail_differential (from_add = True)
AST._has_tail_differential      = lambda self: self.tail_differential [1]

#...............................................................................................
def _expr_ass_lvals (ast, allow_lexprs = False): # process assignment lvalues ... {a}, {b = x}, {y} -> {a, b} = {x, y}
	def can_be_ufunc (ast):
		return (
			(ast.is_func and ast.func in _SP_USER_FUNCS and all (a.is_var_nonconst for a in ast.args)) or
			(ast.is_mul and ast.mul.len == 2 and ast.mul [1].is_paren and ast.mul [0].is_var_nonconst and ast.mul [1].paren.as_ufunc_argskw))

	def as_ufunc (ast):
		if ast.is_func:
			return AST ('-ufunc', ast.func, ast.args)
		else: # is_mul
			return AST ('-ufunc', ast.mul [0].var, *ast.mul [1].paren.as_ufunc_argskw)

	def lhs_ufunc_explicitize (ast):
		if not allow_lexprs and (ast.is_ufunc_py or (ast.is_ufunc and ast.kw)):
			return AST ('-ufunc', f'?{ast.ufunc}', *ast [2:])
		elif ast.src_var_name:
			return AST ('-ufunc', f'{ast.src_var_name}', *ast [2:])
		else:
			return ast

	# start here
	if ast.is_ass: # if assigning to function call then is assignment to function instead, rewrite
		if can_be_ufunc (ast.lhs):
			ast = AST ('=', as_ufunc (ast.lhs), ast.rhs)
		else:
			ast = AST ('=', lhs_ufunc_explicitize (ast.lhs), ast.rhs)

	elif ast.is_comma: # tuple assignment? ('x, y = y, x' comes from parsing as ('x', 'y = y', 'x')) so rewrite
		vars = []
		itr  = iter (ast.comma)

		for c in itr:
			if c.op in {'@', '-ufunc'}:
				vars.append (lhs_ufunc_explicitize (c))
			elif can_be_ufunc (c):
				vars.append (as_ufunc (c))

			elif c.is_ass:
				t = (c.rhs,) + tuple (itr)
				v = lhs_ufunc_explicitize (c.lhs) if c.lhs.op in {'@', '-ufunc'} else as_ufunc (c.lhs) if can_be_ufunc (c.lhs) else c.lhs if allow_lexprs else None

				if v:
					ast = AST ('=', (',', tuple (vars) + (v,)) if len (vars) else v, t [0] if len (t) == 1 else AST (',', t))

					vars.append (c.lhs)

				break

			elif allow_lexprs:
				vars.append (c)
			else:
				break

	return ast

def _expr_comma (lhs, rhs):
	if rhs.is_slice and rhs.step is None and rhs.stop and rhs.start and rhs.start.is_var_nonconst:
		if lhs.is_comma:
			for i in range (lhs.comma.len - 1, -1, -1):
				first_var, wrap = lhs.comma [i].tail_lambda

				if first_var and wrap:
					ast = wrap (AST ('-lamb', rhs.stop, (first_var.var, *(v.var for v in lhs.comma [i + 1:]), rhs.start.var)))

					return ast if not i else AST (',', lhs.comma [:i] + (ast,))

				if not lhs.comma [i].is_var_nonconst:
					break

		else:
			first_var, wrap = lhs.tail_lambda

			if first_var and wrap:
				return wrap (AST ('-lamb', rhs.stop, (first_var.var, rhs.start.var)))

	return AST.flatcat (',', lhs, rhs)

def _expr_colon (lhs, rhs):
	first_var, wrap = lhs.tail_lambda

	if wrap is None:
		return _ast_pre_slice (lhs, rhs)

	return wrap (AST ('-lamb', rhs, () if first_var is None else (first_var.var,)))

def _expr_mapsto (args, lamb):
	if args.is_var_nonconst:
		return AST ('-lamb', lamb, (args.var,), is_lamb_mapsto = True)
	elif args.is_comma and all (v.is_var_nonconst for v in args.comma):
		return AST ('-lamb', lamb, tuple (v.var for v in args.comma), is_lamb_mapsto = True)

	raise SyntaxError ('mapsto parameters can only be variables')

def _expr_piece (expr, expr_if, expr_else):
	if expr_else.is_piece:
		return AST ('-piece', ((expr, expr_if),) + expr_else.piece)
	else:
		return AST ('-piece', ((expr, expr_if), (expr_else, True)))

def _expr_cmp (lhs, CMP, rhs):
	cmp = AST.Cmp.ANY2PY.get (CMP.text.replace (' ', ''), CMP.text.replace (' ', ''))

	if lhs.is_cmp:
		return AST ('<>', lhs.lhs, lhs.cmp + ((cmp, rhs),))
	else:
		return AST ('<>', lhs, ((cmp, rhs),))

def _expr_add (self, lhs, rhs):
	ast = AST.flatcat ('+', lhs, rhs)

	if self.in_intg () and lhs.has_tail_differential:
		return Reduce (ast)

	return PopConfs (ast)

def _expr_mul_exp (self, lhs, rhs): # fix side-effect of integral parsing
	if lhs.is_mulexp:
		if lhs.mul [-1].is_differential and self.in_intg ():
			return Reduce (AST.flatcat ('*exp', lhs, rhs))

	return PopConfs (AST.flatcat ('*exp', lhs, rhs))

def _expr_neg (expr): # conditionally push negation into certain operations to make up for grammar higherarchy missing negative numbers
	if expr.op in {'!', '-diffp', '-idx'}:
		if expr [1].is_num_pos:
			return AST (expr.op, expr [1].neg (), *expr [2:])

	elif expr.is_mul:
		return AST ('*', (_expr_neg (expr.mul [0]),) + expr.mul [1:])

	return expr.neg (stack = True)

def _expr_diff (ast): # convert possible cases of derivatives in ast: ('*', ('/', 'd', 'dx'), expr) -> ('-diff', expr, 'd', ('x', 1))
	def interpret_divide (ast):
		numer = ast.numer.strip_curly
		d     = e = None
		p     = 1

		if numer.is_var:
			if numer.is_diff_or_part_solo:
				d = numer.var

			elif numer.is_diff_or_part:
				d = numer.diff_or_part_type
				e = numer.as_var

		elif numer.is_pow:
			if numer.base.is_diff_or_part_solo and numer.exp.strip_curly.is_num_pos_int:
				d = numer.base.var
				p = numer.exp.strip_curly.as_int

		elif numer.is_mul and numer.mul.len == 2 and numer.mul [1].is_var and numer.mul [0].is_pow and numer.mul [0].base.is_diff_or_part_solo and numer.mul [0].exp.strip_curly.is_num_pos_int:
			d = numer.mul [0].base.var
			p = numer.mul [0].exp.strip_curly.as_int
			e = numer.mul [1]

		if d is None:
			return None, None

		ast_dv_check = (lambda n: n.is_differential) if d == 'd' else (lambda n: n.is_partial)

		denom = ast.denom.strip_curly
		ns    = denom.mul if denom.is_mul else (denom,)
		ds    = []
		cp    = p

		for i, n in enumerate (ns):
			if n.is_ufunc: # implicit ufunc created for last differential reverts back to differential followed by paren
				if n.is_ufunc_explicit or cp != 1:
					break

				v   = n.src_rhs
				n   = AST ('@', n.ufunc)
				ns  = ns [:i] + (n, v) + ns [i + 1:]
				ast = AST ('/', ast.numer, AST ('*', ns))

			if ast_dv_check (n):
				dec = 1
				ds.append ((n.var_name, 1))

			elif n.is_pow and ast_dv_check (n.base) and n.exp.strip_curly.is_num_pos_int:
				dec = n.exp.strip_curly.as_int
				ds.append ((n.base.var_name, dec))

			else:
				break

			cp -= dec

			if cp < 0:
				break

			if not cp:
				if i == len (ns) - 1:
					return AST ('-diff', e, d, tuple (ds), src = ast), None

				elif not ast.denom.is_curly:
					if e:
						return AST ('-diff', e, d, tuple (ds), src = AST ('/', ast.numer, ('*', ns [:i + 1]) if i else ns [0])), ns [i + 1:]
					elif i == len (ns) - 2:
						return AST ('-diff', ns [-1], d, tuple (ds), src = ast), None
					elif not ns [i + 1].is_paren:
						return AST ('-diff', AST ('*', ns [i + 1:]), d, tuple (ds), src = ast), None
					else:
						return AST ('-diff', ns [i + 1], d, tuple (ds), src = AST ('/', ast.numer, ('*', ns [:i + 2]) if i < len (ns) - 3 else ns [i + 2])), ns [i + 2:]

				break

		return None, None

	def try_apply_ics (ast, arg): # {d/dx u (x, t)} * (0, t) -> \. d/dx u (x, t) |_{x = 0}, {d/dx u (x, t)} * (0, 0) -> \. d/dx u (x, 0) |_{x = 0}
		if arg.is_paren:
			diff = ast.diff.strip_paren1
			ast2 = _ast_diff_func_apply_call (diff.var, AST ('-diff', diff, ast.d, ast.dvs), arg.paren)

			if ast2:
				return ast2

		return AST ('*', (ast, arg))

	# start here
	if ast.is_div: # this part handles d/dx y and dy/dx
		diff, tail = interpret_divide (ast)

		if diff and diff.diff:
			if not tail:
				return diff
			elif len (tail) == 1:
				return try_apply_ics (diff, tail [0])
			else:
				return AST.flatcat ('*', try_apply_ics (diff, tail [0]), AST ('*', tail [1:])) # only reanalyzes first element of tail for diff of ufunc ics

	elif ast.is_mul: # this part needed to handle \frac{d}{dx}
		mul = []
		end = ast.mul.len

		for i in range (end - 2, -1, -1):
			if ast.mul [i].is_div:
				diff, tail = interpret_divide (ast.mul [i])

				if diff:
					if diff.diff:
						if i < end - 1:
							mul [0:0] = ast.mul [i + 1 : end]

						if tail:
							mul [0:0] = tail

						mul.insert (0, diff)

					if i == end - 2:
						mul.insert (0, AST ('-diff', ast.mul [i + 1], diff.d, diff.dvs, src = AST ('*', ast.mul [i : end])))
					elif not ast.mul [i + 1].is_paren:
						mul.insert (0, AST ('-diff', AST ('*', ast.mul [i + 1 : end]), diff.d, diff.dvs, src = AST ('*', ast.mul [i : end])))

					else:
						diff = AST ('-diff', ast.mul [i + 1], diff.d, diff.dvs, src = AST ('*', ast.mul [i : i + 2]))
						expr = try_apply_ics (diff, ast.mul [i + 2]) # only reanalyzes first element of tail for diff of ufunc ics

						if expr.is_mul:
							mul [0:0] = ast.mul [i + 2 : end]
							mul.insert (0, diff)

						else:
							mul [0:0] = ast.mul [i + 3 : end]
							mul.insert (0, expr)

					end = i

		if mul:
			mul = mul [0] if len (mul) == 1 else AST ('*', tuple (mul))

			return mul if end == 0 else AST.flatcat ('*', ast.mul [0], mul) if end == 1 else AST.flatcat ('*', AST ('*', ast.mul [:end]), mul)

	return ast

def _expr_div (numer, denom):
	if denom.is_mulexp: # correct for wonky \int ... dv parsing
		return AST ('*exp', (('/', numer, denom.mul [0]), *denom.mul [1:]))

	if denom.is_mul:
		for i, ast in enumerate (denom.mul):
			if ast.is_mulexp:
				return AST ('*exp', (('/', numer, ('*', (*denom.mul [:i], ast.mul [0]))), *ast.mul [1:]))

	return AST ('/', numer, denom)

def _expr_mul_imp (self, lhs, rhs): # fix side-effect of integral parsing
	if rhs.is_div:
		if rhs.numer.is_intg:
			return PopConfs (AST ('/', AST.flatcat ('*', lhs, rhs.numer), rhs.denom))
		elif rhs.numer.is_mul and rhs.numer.mul [0].is_intg:
			return PopConfs (AST ('/', AST.flatcat ('*', lhs, rhs.numer), rhs.denom))

	elif rhs.is_mulexp:
		if rhs.mul [0].is_div and rhs.mul [0].numer.is_intg:
			return PopConfs (AST ('*exp', (('/', ('*', (lhs, rhs.mul [0].numer)), rhs.mul [0].denom), *rhs.mul [1:])))

	elif lhs.is_mul:
		if lhs.mul [-1].is_differential and self.in_intg ():
			return Reduce (AST.flatcat ('*', lhs, rhs))

	return PopConfs (AST.flatcat ('*', lhs, rhs))

def _expr_intg (ast, from_to = ()): # find differential for integration if present in ast and return integral ast
	pre, dv, wrap, wrapp = ast.tail_differential

	if dv:
		if pre:
			return wrap (AST ('-intg', wrapp (pre), dv, *from_to))
		elif dv.dv_has_neg:
			return wrap (AST ('-intg', wrapp (AST.One), dv, *from_to))
		else:
			return wrap (AST ('-intg', None, dv, *from_to))

	raise SyntaxError ('integration expecting a differential')

def _expr_diffp_ics (lhs, commas): # f (x)' * (0) -> \. f (x) |_{x = 0}
	if lhs.is_diffp:
		ast = _ast_diff_func_apply_call (lhs.diffp.var, lhs, commas)

		if ast:
			return ast

	return Reduce

def _expr_func (iparm, *args, is_operatorname = False): # rearrange ast tree for explicit parentheses like func (x)^y to give (func (x))^y instead of func((x)^y)
	is_func    = args [0] == '-func'
	is_pseudo  = is_func and args [1] in {AST.Func.NOREMAP, AST.Func.NOEVAL}
	rhs        = args [iparm]
	arg, wrapa = _ast_func_reorder (rhs, unconditional = is_pseudo)

	if is_func:
		name = args [1]

		if is_operatorname and name not in _SP_USER_FUNCS and name not in ALL_FUNC_NAMES: # \operatorname ufunc like SymPy writes out, will not catch unparend arg - shouldn't need to
			ast = _ast_var_as_ufunc (AST ('@', name), arg, rhs, force_implicit = True)

			if ast:
				return wrapa (ast)

		src = AST ('*', (('@', name), rhs))

		if arg.is_paren:
			ast2 = _ast_func_sxlat (name, _ast_func_tuple_args (arg), src = src)
		else:
			ast2 = AST ('-func', name, _ast_func_tuple_args (arg), src = src)

		if is_pseudo and ast2.is_func and ast2.args.len != 1:
			raise SyntaxError (f'no-{"remap" if ast2.func == AST.Func.NOREMAP else "eval"} pseudo-function takes a single argument')

	else: # args [0] in {'-sqrt', '-log'}:
		fargs    = arg.strip_curly.strip_paren1 if args [0] == '-log' or (not arg.is_paren_tex or arg.paren.op in {',', '-slice'}) else arg.strip_curly
		ast2     = AST (*(args [:iparm] + (fargs,) + args [iparm + 1:]))
		ast2.src = AST ('*', (AST.VarNull, rhs)) # VarNull is placeholder

	return wrapa (ast2)

def _expr_func_func (FUNC, args, expr_super = None):
	istok = isinstance (FUNC, Token)
	func  = _FUNC_name (FUNC) if istok else FUNC

	if expr_super is None:
		return _expr_func (2, '-func', func, args, is_operatorname = istok and FUNC.grp [2])
	elif expr_super.strip_curly != AST.NegOne or not AST ('-func', func, ()).is_func_trigh_noninv:
		return AST ('^', _expr_func_func (FUNC, args), expr_super, is_pypow = expr_super.is_pypow)
	else:
		return _expr_func_func (f'a{func}', args)

def _expr_ufunc_ics (self, lhs, commas): # ufunc ('f', ()) * (x) -> ufunc ('f', (x,)), ufunc ('f', (x,)) * (0) -> ufunc ('f', (0,)), ...
	if lhs.is_ufunc_py:
		ast = lhs.apply_argskw (commas.as_ufunc_argskw)

		if ast:
			return PopConfs (AST ('-ufunc', lhs.ufunc_full, (commas.comma if commas.is_comma else (commas,)), lhs.kw))

	return Reduce

def _expr_ufunc (self, args, py = False, name = ''):
	if py:
		args, kw = AST.args2kwargs (args.comma if args.is_comma else (args,))

		if len (args) != 1 or not args [0].is_str:
			raise SyntaxError ('Function() takes a single string name argument')

		name   = args [0].str_
		argskw = ((), tuple (sorted (kw.items ())))

	else:
		argskw = args.as_ufunc_argskw

		if not argskw:
			raise SyntaxError ('invalid undefined function arguments')

	if name and not _is_valid_var_name (self, name):
		raise SyntaxError (f'invalid undefined function name {name!r}')

	if AST ('@', name).is_var_const:
		raise SyntaxError ('cannot use constant as undefined function name')

	return AST ('-ufunc', f'?{name}', *argskw, is_ufunc_py = py)

def _expr_varfunc (self, var, rhs): # user_func *imp* (...) -> user_func (...)
	arg, wrapa = _ast_func_reorder (rhs)

	if var.var in _SP_USER_FUNCS:
		if arg.is_paren:
			return PopConfs (wrapa (_ast_func_sxlat (var.var, _ast_func_tuple_args (arg), src = AST ('*', (var, arg)))))
		elif not (arg.is_curly and arg.strip_curly.is_paren) and var.var not in {'beta', 'Lambda'}: # special case beta and Lambda reject if they don't have parenthesized args (because they take two)
			return PopConfs (wrapa (AST ('-func', var.var, (arg,), src = AST ('*', (var, arg)))))

	else:
		ast = _ast_var_as_ufunc (var, arg, rhs)

		if ast:
			return PopConfs (wrapa (ast))

	return Reduce

def _expr_sym (self, args, py = False, name = ''):
	args, kw = AST.args2kwargs (args.comma if args.is_comma else (args,))

	if py:
		if len (args) != 1 or not args [0].is_str:
			raise SyntaxError ('Symbol() takes a single string name argument')

		name = args [0].str_

	elif args:
		raise SyntaxError ('$ does not take direct arguments, only keyword assumptions')

	if name and not _is_valid_var_name (self, name, allow_funcs = True):
		raise SyntaxError (f'invalid symbol name {name!r}')

	if AST ('@', name).is_var_const:
		raise SyntaxError ('cannot use constant as symbol name')

	return AST ('-sym', name, tuple (sorted (kw.items ())))

def _expr_subs (expr, subs):
	def asslist2srcdst (asslist):
		subs = []

		for ast in asslist:
			if ast.is_ass:
				subs.append (_expr_ass_lvals (ast, True) [1:])
			else:
				raise SyntaxError ('expecting assignment')

		return tuple (subs)

	# start here
	if not isinstance (subs, AST):
		subs = asslist2srcdst (subs)

	elif subs.is_ass:
		subs = (_expr_ass_lvals (subs, True) [1:],)

	elif subs.is_comma:
		if not subs.comma:
			return expr

		if subs.comma [0].is_ass:
			subs = asslist2srcdst (subs.comma)

		else:
			subs = _expr_ass_lvals (subs, True)

			if subs.is_ass and subs.lhs.is_comma and subs.rhs.is_comma and subs.rhs.comma.len == subs.lhs.comma.len:
				subs = tuple (zip (subs.lhs.comma, subs.rhs.comma))
			else:
				raise SyntaxError ('invalid tuple assignment')

	else:
		raise SyntaxError ('expecting assignment')

	if expr.strip_curly.is_subs: # collapse multiple subs into one
		return AST ('-subs', expr.strip_curly.expr, expr.strip_curly.subs + subs)

	return AST ('-subs', expr, subs)

def _expr_mat (mat_rows):
	if not mat_rows:
		return AST.MatEmpty
	elif len (mat_rows [0]) > 1:
		return AST ('-mat', mat_rows)
	else:
		return AST ('-mat', tuple ((c [0],) for c in mat_rows))

def _expr_vec (self, ast):
	e = ast.comma if ast.is_comma else (ast,)

	if all (r.is_brack for r in e):
		if len (e) == 0:
			return AST.MatEmpty

		elif len (e) == 1 or len (set (r.brack.len for r in e)) == 1:
			if e [0].brack.len == 1:
				return AST ('-mat', tuple ((r.brack [0],) for r in e))
			elif e [0].brack.len:
				return AST ('-mat', tuple (r.brack for r in e))
			else:
				return AST.MatEmpty

		cols = max (r.brack.len for r in e)

		if not all (r.brack.len == cols for r in e):
			return self.mark_incomplete (AST ('-mat', tuple (r.brack + (AST.VarNull,) * (cols - r.brack.len) for r in e)))

	return AST ('-mat', tuple ((r,) for r in e))

def _expr_curly (ast, forceset = False):
	e   = ast.comma if ast.is_comma else (ast,)
	kvs = []

	for kv in e:
		if not kv.is_slice or kv.step is not None or kv.start is False or kv.stop is False:
			if ast.is_comma:
				return AST ('-set', ast.comma)
			elif forceset:
				return AST ('-set', e)
			else:
				return AST ('{', ast if not ast.is_paren else ('(', ast.paren, True))

		kvs.append ((kv.start, kv.stop))

	return AST ('-dict', tuple (kvs))

def _expr_var (VAR):
	if VAR.grp [0]:
		var = 'partial' + AST.Var.ANY2PY.get (VAR.grp [2], VAR.grp [2].replace ('\\_', '_'))
	elif VAR.grp [1]:
		var = 'd' + AST.Var.ANY2PY.get (VAR.grp [2], VAR.grp [2].replace ('\\_', '_'))
	else:
		var = AST.Var.ANY2PY.get (VAR.grp [3].replace (' ', ''), VAR.grp [3].replace ('\\_', '_'))

	return AST ('@', f'{var}{VAR.grp [4]}' if VAR.grp [4] else var, text = VAR.text) # include original 'text' for check to prevent \lambda from creating lambda functions

def _expr_num (NUM):
	num = NUM.grp [1] or (NUM.grp [0] if NUM.text [0] != '.' else f'0{NUM.grp [0]}')

	if not NUM.grp [2]:
		return AST ('#', num)

	g2  = NUM.grp [2].replace ('{', '').replace ('}', '')

	if g2 [1] in {'-', '+'}:
		return AST ('#', f'{num}{g2.lower ()}')
	else:
		return AST ('#', f'{num}{g2 [0].lower ()}+{g2 [1:]}')

#...............................................................................................
class Parser (LALR1):
	def __init__ (self):
		LALR1.__init__ (self)

		self.TOKENS_LONG.update ([(v, self.TOKENS [v]) for v in self.TOKENS_QUICK])

	def set_quick (self, state = True):
		self.TOKENS.update (self.TOKENS_QUICK if state else self.TOKENS_LONG)
		self.set_tokens (self.TOKENS)

	_PARSER_TABLES = \
			b'eJztnXmP5DaW4L/MAl0FKACKpCgp/ysfPWtM+RgfvdMoGEb56IGxvuBre9CY777vpEgFFYorIyMyiFSkJIri+fh+FPVIvXjzl8/e/fj1xx/9pfnL//rup29hp6fvfvHp67+/hoPXX33y6tP3P8LDePD6q3c+ffXuv+NhPPjir1989O4nf9cj2H/08efw/7Mv' \
			b'3sH/r1999r/l8O8fkjfYw/+/vfoUj//jU/T7GmN+/fG/wX8J4/VX73EoeD8fvvP+v3317qvP3v9Mjj98pa7vTId/mw4/4UMKIab2r3Agu1b2FvYffvDRFxTuBx99/KHuWz2wlCAKKBbP7OxzzM37H37y+d8/ex9j/eCjzzE3H32Bob3+4EMqkA+5CCnn/P/d' \
			b'jz/88JWWOjp8yqX+qZY6u1HyP9VSF7dXvJ8S8mmWLErQf8C/Vx9+Av/fe+c1XUPXj96TwsOjd6bDv02HUnjvv/7sfXHRoseAPufEQwLR04evPvns848xxZ9+8CF5/893sVBeff559IZl+d4Hf/vgPbz+rtQtB/PJayp6KDethf/87P13ycN7H/z1ryhYH33A' \
			b'skk5efXRe1jAeOFjvJ+i/j8sY7hrWfgg9Hf/HQ5/++Pr3/58++tvfybHv8HxN29//e73r37+9atvv/7ht9/f/ppchsPv/vkLXPn+zx/h+Me3v3/1zc8/yNGvP/+/6YgD+u273377Jh79Eo80HAh3Ovzlu1/15Kfv/uurt7/+l56+/Tre8f23/4yuv/8eb/jH' \
			b'229+1+NfKB3s/MdP30xp/sc/fslOvvr+myQlP+rhD9/Hw58m19+/+zUe//jHD199/2MM7Js/fv3hv5Oi0cOvv//p5x+TLMVU/fr2mzQoONDTP7+LV95++2309DZm7p+/fTfllAoz5gDzlBRsvPDHT9///FO88N8xRd//9HtMElQu1nhSH99M+YOLaUH++Xaq' \
			b'p59jWv5Ivbz96dvMPS3pr7/5+ccf38bTn2NgX0Ox/N/vpmrM/f3y/XfffBdPQER/mkrnl99+/zlGHaUr5uTnH6b8U6DZyW/JnV/98OfbH6YS5Tu/bN682ATXBPey4QOPB12H/0LThZdyCmd01KO3Bh3kkjrwWdD74IZenfCQfG1o37XNC++ajWu69mV08Ojg' \
			b'vTpsRjxwLf/r0bt7mTrBNnfqfeqEh3DUBv7Xj82mHV6KEx7CESZ+aDq4j6/0eAB7C3fj3o/4zzdhgDs4sZOTnm46Q6H65kWgTIwv9bynXBpOEiTEYcAD/iYnl546LwfohoHADa2PRx0lbmheWDiDSgj2pbhsHCXCtvyvBxfLdQJneIjFSpWAd75MTvU4cbe8' \
			b'21CVe/r5Ti97PMBEijsnvdl4ceRzSKPrKPd2cqDSCdM5FVfnZg6Tj42nTPmO/wW+gEnxnDQvp1o3kDbPiev5X28gF3zRYWWQVAUoJBQQuRBaveAHqVsWXY8pYDmEiKiQMYGSPHQa01OuAefJDW+H2kVRkxKIp27uJFKSO/fbp7Mbh+3Tgg+fO43bp9lNG89t' \
			b'1EhxttIiMXUtiRNUsRslYKxRLsTovO00nW48SZa30N6hBUPoYzOA3IUGY3YiX9tXUTH1bg9PUKu5p42ndoz1OjYhyjA2Z64tg40Wk9aBHui17aJzdJxcPLsMk0vHLsldgVyCUZeNozw7x//6NiYvOtnUCQ/xaOR/UGwvX06HeNSx9Oot2Mq4zqBM2kmK8BT9' \
			b'TadQoaRJBvan2hBVBVUtFCg3uNbxvwFub1m+W0eHVI7Y3OSA8tZie2pYgffafNFRnOI5KQQv0RqJA1tog22w4K4u4EAJ7EUqRWfAOTRS8kM/8MiZRZVHMtfzvx5qxXIlwRkeYolACx6wwEXEfTNQ+UAVvrCG+dS0JGTBarboYsusKl0EXUwpgxIE8WP57Lgc' \
			b'pO2rcDqtD3B8EaZGSKd+ShYwmZIL0veCakGaE556Cn/Lae5DT5kkKMaWNRTk0rd6ZK0csTjhAZctFOLEPzwbs2jVJZ5tLEtRJ6d6BRU1BW07/tejXxYPbEmWLkLIzDk8CHoQLw16wIViKX98xGKJB1z5RjxIE8I6tUwpaBo2Y7Ol8kZPI//ru0lxsQ5DUhj+' \
			b'N3V/ghFKdiP/w+6ONH44w0M8GvTiSzmFM73A4UCKho6kyas0Be2FBMQcKC+5HYUjUAmFTi9g10pc4Ah6dW8MlAL+2gY2TwAHCQSZBMEDFAJdQaVAHUOjx8JswPPQN8MApQi3tSj5HfwC/Hr4Yek6+HnUsVBcrQGfBm6D3gL4AVS2HqsJKgiOUNTgbo9XwMnA' \
			b'sYFjKK0WWjqoLABpC+q2hQJooWBaoGYLzi26Q+paSF7rUBDgDmyFoL1aaAktNKUWSr+FHLcYL9RiC2BuqdIhY7CBj4C/rhnGZjTNCBm0zeiaEeOABDWYMUwN3APtbuyaMTRjD52phjrAyInWQMQGyq5vIHiQANDk2K/tScMNwAkENqRsHJoR0gkF2UJJtlC+' \
			b'LRQwdDZBf4H6HLCkoFhBn4F6gSRhmjG/BrUC5AQyhi0cNGCPvcwwYpcFIAFQAOJBjxYEscdKwIoBhBlUlZBbyCy0Y2hAPdYDaqLWYmceDkEOqOpBDF5g/aMD795gNxtPsXJekoYiX5BiuoolD85Y+C+Jr3j1DUKczinsKjvPVHZejCwELde2SNCbF0MvIsWy' \
			b'MrBwUE3TXiSLqpwcguzlPqxsCogEAeWIQqgq6T7ECuvd1Qq/rwr3tcLvp8LxKZO6DVZIYJggBACUB8tkGMYqFfcjFW9wKKTW9x3Vd+ula+ipoScV9yWNRbJy4CeQ7CK6SufRk8wUyzwPrpPepe9V6VAIWt9az1KHUkdaO2ldxDrQcuey5sJNC/MyBZm0gbnc' \
			b'z+R9Luki4fvJ9m6ZZlkuyLDIbSquqZjOBHQumiCOVNmGnxAwYfxgEORZgp44a6Gfv9BfjILkIE9rTh7SOh0n6KTtdvoU6Irt1Ek71ftlxKDlIYM92q3T8LtWHiutpkC7Dx09LrwIHEXgGAJHYFCpYaawjCEExAEUXBWJY9phx4NBgWWjN7WEz17C3Fx6lvWe' \
			b'xz96t6ugWxynhwKoBX5UgbPS6Fmy+66K9LlLuPbq76xXb/ta4XdV4W2t7/up7xdUGNTvtk6eEby+9yMHtOyA9DRdE2q9P5965+c7fPjWRzp5BpfnQJGBkZ8K8OEAe7HYhcVHMXxmwAc0SEuVieciE1Dbba3tO6ptW2v7jmrb1dq+l9p+MUa6E85HZvwYdMTd' \
			b'yGBffeVxmVGTYOtD1R01P8wQNTh5O8nVf5jc5DWzXfIQDXfd37zg2M4fvCoRw+PXnaoPGRI6RxzOcBz8FHqWIK28YpJXxONQXyo+klrzdWjwjrQa1De/KaSXxrXa76TaX+ibeq9WGlb2MmugZ6kY+PLAQwjyFjN7s1mtbp5MV78YpNKM9EgG7qAMPNw3dGLc' \
			b'H8QoI1qBSJ2z+dYLMdSxHJgMGPcUZFrTVGaoMMTCQF6Ds4VHbhAixh69RCyTD9hMBSdc0kUdoa565470zhuaxWRf1vlr91TnUPSWZ6bZOtfsGdczFJ7lqYa1np9vPePUUSuTCmFfK/TWK5Smg9YW+5wrGGvNykRe2NeqfUZVawi3UhF1qsyVP7TzlHlui64+' \
			b'+t5PS8UlErTaee+lG0XwrS332lsuTnmmChuo31ut6O6p8drAjTY2Yh0v10bsSThmE+O83CUDojo5S6cQ6cwWrEcKpTQ9Tu+uM17OyOBsepzWSy3hM5ZwaXqcCn6dJvcYBe6T6XGqWqpInw//rPLf4BR31PS1h3aFVRSM4Lijg1o511Q5TvtKTgaj+H3BfDGB' \
			b'IL4sPSbXqruCqgtSJ0EeVSeo1NI8pvfJDxF11vU9PULiSK2d98lq8zlqraGtB+JajkeVoz/JEKzlNQJrPZxaD7zI4vGBNOv28y3P+8aBDyJ5byrJT+8XQcFwadpamucYwnC1GM+hT9jE9nH1iaz/2sszXd/VqjvHEJO8MLC9vGaQZ+FAxVyfgZ+wbpzjumCO' \
			b'ymCrWNjL3DQmrE7HJf+1mi6u/Xwt9ycp966W+xOopVFHvPVdprx4I1sEWYl3FBdWTS9aeb88tmzHwOZIo+i3UUheq+8izWYMtbgv2MEyhjtWwcqINu3P3T1+U7+Hc1dju7rKOS7aQGLVUaumr0jW1vx4ypMfk86zwAS/kxl553uuR8+mfvQ2+UueFm2z0VCa' \
			b'PWHqw9lTS8LQyivl4oKHA5vcDjwPcRADjqLixlpYukoj5ZZHykk8+BU1DpVvvcfuRYJ4nKSC4E5A0LGIdWLiL70NqFvHE2Bxh7X/kr7TjLuBzniKjpMpOu6lzuxw6TeXUApcNCFnk3I9d2JSLjfgznYc8sARjazE8LWzREiJrKJ5J6KJxe7EgM0l3wlhaymX' \
			b'fbfDiVd0ccn6wWgBQhcCSx6NV1K/WgVeJjc41cfs3nMEPYfWi7habiy9pIcFkwY/WZolDUGkuqTbcViOM8VBUberLmr8jOQWx1WJuXXi4vOqVoHW1jKWvuzcFZyRaU5Hv6yMepHRIBZanYDzrMQlVA3wrKoUx58djz87Hn+WBu64gTsZp8O97DrpuvKHzerw' \
			b'yiM/VPN34+pim483o5R7ul52/JzWCdN6Oq3DSk86rGSWHz1oWMnxsJLjYSVHz0KW61EfnbSLArf5ulzO7XOLR058HHf5kodY/Evpjn7Z6EANPSb7lzrkUq2jTreO8jJA4Olhn9SljhLIWNj0lpu7EN0gFmpcRctN2XNT9tyUfe1qPqsma6n662DnfVQ40baT' \
			b'hRpwD0Wgp56XVuheypcvO1zEA8oD4qjicR/iwV/ArBV/hxUPMXUM+o5Bj1LQWOm3k46QlRSw/AKdS5cB8hpkme1QTbYu9wzmqRqgJAP163h2mqweDgGFmUEGVVzPo8L8Yh7XxMACxblsWHa1MT+LxkwtskfRcLzzvOuk9iHqvrbTiz2d9U7fxXRiiRxk38sT' \
			b'ma75j+VdK+ZyCrSrxX3JUYoRi5vag9f2YKUdII0qfZ4HfSCWkXEzSu3yWKBp3hjsUtJHttEWBT+0PSYtgAtG+yEcOmcekspVzoXLlZc+ifDjiTytcLlQpvhVmJRqJxnE3HHWgqQey4obu8fiYSGcV53WEb72Idl0M/nk4kGjtta2UR7i14FITrjcuV/mWMZi' \
			b'nXmR3zbpmcE5fmsIv1mD7WLARy44x28X4zdOUZ7wnT+++8B0jVh0UHYoNmh6ZKAEcRFFkHwLMmBxTWQ0vkMTNzSWw0lhOEEJl7/G9Vax6LHsLVYw+MFKwFrAJoZV4bBe4BqqJVytCBclwtWK8NMGkA+Ln7LwWHFwD678iAs9os0wLg+JxsIgrRZtADtcLgH8' \
			b'oD04VJBFw1FSKnANO6eQd4tqAerGohkx5NPijBDIp8PFYKHVu9aAVG08vf7ZYIlv8AexbiAFG4/HptmM4GzwerOBAtygZtlg9W2wzjaQlw3kZYNqZIOlv2nRE7SxDWqNDaqMDdbQZsRrAb2jS0e+0MnhkcOjDgPtMDDI3QbVxAb1xAYVxQY1xAbFYePQHfK6' \
			b'GekAf+AKGdugY0BHg9FAOkFaNpgUChkzg3Kywca9GSjRcG3AENAzRoUR9+gA9bjBxELdbaAMsZQ2UCcbTC0l1mNy0AUVZDnFplvKCqYZU0NZcVhMWCiBbiInuAm0yAYkedNThoyUImYVSwJkdTNQJcABhoUl02NWwTuVIVYa5paCh7AG3FNxGcwPVSWmrcXw' \
			b'4RpI6MZSZcGBo7RSEkk2IJxAtUmlhrdhxeOT0QaUwabHPSYAPeMFrAVH3jBADAuSjMKEggCeR6wxdKPqw9Dhh2WDIgHtZIOyg9KEaRgwexQxpxBlp8c6aCk6FEysLZIPKl6sVYwYqx8U0mbovkRFiuqzKs+qPM+rPKvmrJrzmWtOi5pzSWNyP7xPVGfStz1G' \
			b'gYa8e55q0nZZmWon+wCV6nOtSk8L7ozatduhYf2eWtYmmvYQDdteqZYFd5zKmGnbblvj4qwrqL1lzdtjrfUrOpb/Mk1LDo+obSXKZZ3b76F1KYiTNe+U/5jtR9G8/S7d2++pfRP9e6Da7a9d8VKqx1wD9wUdLIKzpIebf4UH7Fr0D9i5GGHnhv/BSV+onHtS' \
			b'y6yTZwo5amMa5JnGQqJCZm0cX0zu0MYtaWMe8qBxFFHLJtHMNPKyOhQyaenSKOG6uh7j4M5k+9TTWNakvv2CCi+p73QUKlXjVlT5wsjWfJxuPmamI2o0AuZ5lEyHG3HxjPiJDzdDgrxPimjwq3jIUTDHQLeH6g+J+keJgWu4sPIcAfiRPsj3KgpwhnMRB90M' \
			b'CW2OBbTGx+WAIx68IKKAB/y8HH40hTDRJaiADgeUh4PyYGwMCTpQKDrRaIoLdkNngYU40JAp/yZYiM8IigiJyStzwk7hphvRgeLyubsgQn0JJlAyEBW8F1zQCYop7lFkYyDb3EBXRUeL6MABTNKuGACmdwkhxeTnWVGmcL4XuDLLvWTP8N7pzTSeTEjJ/Rsu' \
			b'pj5hDLr3sdroCuKg90vJNFxUnGTmkd4pWKJSSshEaep7LlNBUw4hARCH2Ql49mFOS8Ur4HEuJjInjwrFCn/E24xAlJsJQswap+FqA1IcaQISGrETtiquM8mnkCmtTa6NSCnrHqiD5doHKmo3PFDnydsHEg2P1zu0jHkgAfHdA/ecwB1a2ABgw5cDtmXvoKvZ' \
			b'f0sB0yXPd4D4PlBXBPThw4YQB3vSKxCmDf9Dy6ldFR33eW4xBz66rLHQXpCHSyzcl4NzBt4h//bi3sHMw1dlc+ahpLC7Qi9kW0I8Pi8QTzxG3oXCxrwLzLvUXXknvnY9Fk3eCnwLhUcj8rqEtVIq8xRjDdJjgffisEC2PJ+SESOlIvc6vhzm3o1eF6gFhpoG' \
			b'JkArJ9BIHAqzMIEszEAWmGNhjWM6hHU8x1Qaco5JZa9xjL3NORZyjoWEY9PjlEac8iupFd0Lu5IrXOhXyq6usqty6865hYU551YvW8RWn20Jtvi8gC3xGLHVFzbGVs/YSt0VW+JrJ7biTQVs9SVs9TuwVUplnuL4NNbvQFaeR8mEkRKRex35I2Rl3o1eF2T1' \
			b'jCwNTJBVTpyROBRZ/YSsfoasnpHVryFLB/+OR5ZKQo4sqeg1ZLG3ObL6HFl9gqx+QpZEnCIrqRXJmyIrucKFfqXICvsiawlWN02qSqn7pBRmuJ1RyuqmlFJP9O5JCSWO24TK3jfZKbR0I0BRDD53F0Cpr12Amm7aBhS6poBCgdj5xqmYyjzFq6+gZhmUHBje' \
			b'Z++e0CXkN2AK2SPjCY/7JDTG00LSjEQieCKrDsYTFUThpRUV2y4+cXin8EkTl/NJ63mFT+JtxifKzcQnCl74RBliPmnECZ/SepG8CZ/SK1zqkU/KpX15FB64H7QXkJB1qJm2QdRXEFUQ3R+IMLNzEFnZIojEUw4idlwFEX1i1c43JpFlEqXuSiLxtZNE8aYC' \
			b'iWxOIrsGoq0UbiV4FURZTolEYgJB+5xElklUiEZBZBlEGpiAqJw0I3EoiOwEIrsAIrsGInsyiDRDOYikmtdAxN7mILI5iGwCoundlEacgiipFsmbgii5wqX+1CAaKogqiO4PRBjpHESdbBFE4ikHETuuPxF1hY05xJYVmbtySHzt5FC8qcChrjBkt5tEpWTm' \
			b'SV5/JMpzKFkwvM9BxOYT+Q2klxIUselEDE5QVE6bkVgURd2Eom4BRWvmEvZkcwlN3AxFUtNrKGJvcxTl5hI2MZegDAmKJOIURUnFSN4URckVLvVjUbQPggrkGSt5KnnujzxoPjgnT5Atkkc85eRhx3XyhMLG5GEbh8xdySO+dpIneiuQp2TjsJs8pWTmSV4n' \
			b'T55DyYLhfU4etm/IbzBefAp52L4hBifkKafNSCxKnsm+wYYF8qwZOMQ5OseTRwKZkUdqeo087G1OntzAwSYGDom9uEackiepGN0LeZIrXOoXJk9rBD0X5s7jTQ86J3FKU4VOoMyR04mujiKrU4fa/amxRYwtSiBWUkKMskVCiIecEGMzmyO0PEwWst8Ufssy' \
			b'T5wYE3flxLgCCWyWcsvJM4TSBBS3vYbJ8t+umUJSEpNXtCZwqQWcExO4KTCxKHDKBK2CtQlFVEA7aTCeTAMpoxkNpD5PnDYk5RQBoAKbAEAFSvKSThrK5g3tpcUT7d1exYPD0ROJbugh4hlOH6IXAeMzfbhA+Z4Pa7Gbm4a11FOGDnFcBUcMLd0IGo6HtTJ3' \
			b'gYb6Em6QPmMnpQfdbGiH7TUGsI0Qd/AQVzHJefJXKTLLrWSHE5xzxPEQV36D8fFQSOJ4lCsNlEmykEJjpsQyXeRUGOMWhrvc2nCXO3q4S2fCKW5iDjPcqCSs4EZzkwPH5cNeLhn2ctOwl0acsCctV8mj4Ce9wtVw6YcPW/FV8fXUTzPXha3QxJ55xFaQLWJL' \
			b'POXYYsd1bIXCxtjiMbHMXbElvhRbgbEVEmwFxlZgbMVbCtg6eHysmOQ8+evYynMr2eEEz7DFT0D5DcbHQ8UWP/+kgQq2yik0ZkqsYItPFVsLY2VubazMnTxWFnOW40okYA1XkosZrvKxMpeMlSWPShpxiqukPHUvuEqucPFfGld7r99QX9PU1zTPB0l94+YT' \
			b'e9jNTRN71FOOJHZcR1Jf2BhJPKcnc1ckia9dI3DTTQUMleb07MZQKZl5ktcxlOdQsmB4n2OI5/TkNxgvPoVBPKcnBicAKqfNSCxKn2lOj+sX0LM2qcedPKlHEzdDj9T0GnrY2xw9+aQel0zqcdOkHo04RU9SMZI3RU9yhUv90ujZe3GEip6KnueDnqEhUGTo' \
			b'GWSL6BFPOXrYcR09Q2Fj9AyMntRd0SO+dqIn3lRAz3Aweta2fdCT51CyYHifo2dg9GQ3GC8+BT0Do0eDE/SU02YkFkXPMKFn4Z0QFdxO9Awno0cSN0OP1PQaetjbHD1Djh5NOIcb0SMRp+hJKkbypuhJrnCpXxo9e69tEOo43c2N0wVaXqwS6lhCoUzP55N6' \
			b'3ZRQ6smPvFdIifs2pMiXIsq3hY0Q5XlCaeYuiFJfgihSvB3vBFSeWYU7OyYBbOMKXQ/DVTHJefJXcTXLrWTHSNmkuKJczW8wPh4KsfC4zwNlaC2k0JgpscwtORV0UbFM3mcAo6LcBTAO+BSAxfxlAFM5WAGY5iUHGOVpAhhnzGm4CrCY5QlgaalK3gRg6RWu' \
			b'hC2AHbgQwrEg23vFgwqyCrK7A5ltSN9nILOyRZCJJ6y5fNU5kp0iy9p81TnxqL8pCgKaZaDZxF2X8GHP01KrLb+D4r0wjU5opVR+DTXFUFrUZ75owh5cs4VNIokJXLfDm5UBreyjiye026sntLJ8QhIR+0KbPNtqCYtdniykECPIFqibxZynwnDBcSaYeDEE' \
			b'Xf1H1ldg3REK2LNr2LMnY0/kZYY9kZYV7MXczlcCmi21QDHoUkDJWgsaeYq+KAXMvmSO66yW6fYnw9/e6yxU/FX83R3+sNnN8edki/gTT4S/PsOfpV8Rf32Gv/w3RUH4c4w/l7hH/Fn2FvHXC/76FH+94K8X/MU0l/BnD8afK2wSSUzgHvjLy4DxZxV/dht/' \
			b'VvAXI2JfhD+rJaz4s4I/m1yM+NuugSQ4LjjOhOJPQoj4s4I/qYEt/Lk1/LmT8SfyMsOfSMsq/iS3W/izOf5cij874U8iT/EXpYDx51L8ZbVMtz8Z/vZe3aHir+Lv7vCHzW2OPy9bxJ94IvwNGf4c/Yr4y+ZaiUf9TVEQ/jzjL9km/Dn2FvE3CP6GFH+D4G8Q' \
			b'/GkMRfwdPAfL+8ImkcQE7oG/vAwYf07x57bx5wR/MSL2ZeSkTd7K0RXCn0suRvy5xZ/gb5BMKP4khIg/J/gbJOQ5/vwa/vzJ+BN5meFPpGUVf5LbLfy5HH8+xZ+b8CeRp/iLUsD48yn+slqm258Mf3svMVHxV/F3d/jDZjbHXydbxJ94IvyltiYsL76MvzHD' \
			b'n89+UxSEP541Fh19l+DPs7eIv1HwN6b4GwV/o+BPAyvizx+Mv66wSSQxgXvgLy8Dxp9X/Plt/HnBX4yIfRkJg0tY8ecFf+nFiL/ln+BvlEwo/iSEiD8v+NOKneNvbYaZP3qGWcSfyMsMfyItq/iT3G7hz+f4SyaX0TXFn0Se4i9KAeMvmV42q2Uu2yfCnzXV' \
			b'frLaT94f10Lj57PJ2M1Ps8nUU2Y/KY6r9pMxtHRjnPFsssxdjVPE1y77yemmgkHKwTPIisnMk7xukJLnULJgeJ8bpPAMsvwG48WnWKPw9LEYnJiilNNmJBa1Q5leyPmFWWN+bdaYP3nWmCZuhiKp6TXzE/Y2Nz/JZ435ZNaYn2aNacQphpKK0b2YnyRXuNQv' \
			b'bD9pr2ONjoqeip6Loqdv/HzWGLv5adaYesrRw47r6OkLG6OHZ41l7ooe8bUTPfGmAnoOnjVWTGae5HX05DmULBje5+jhWWP5DcaLT0EPzxqLwQl6ymkzEouiZ3oZ5hdmjfm1WWP+5FljmrgZeqSm19DD3uboyWeN+WTWmJ9mjWnEKXqSipG8KXqSK1zql0bP' \
			b'dayvUdFT0XNJ9KBQzm3yO90UPeopQ484rqKnawsboadjk/zMXdCjvnahZ7ppGz3dweaKxWTmSV5FzyyHkgXD+ww9HVsp5jcYLz4ZPR0bJ8bgGD0LaTMSi6Cnm0zvu7aMnm7N6L472eheE5ejR2t6BT3ibYaeLjc97BKj+26yPNSIE/SkFSN5E/SkV7jUL42e' \
			b'ulZGRc8dosc33dyOgt26yY4CRaXzW+xhP+vs8YWN2cP2E5m7skd87WRPvKnAnoPfFhWTmSd5nT15DiULhvc5e/glUX6Dkb2yh98NxeCEPeW0GYlF2eMn9vgF9qzZPnQn2z5o4mbskZpeYw97m7Mnf/PTJYYP3fTiRyNO2ZNUjORN2ZNc4VK/NHvqYhmVPXfI' \
			b'nq7p5kYM7NZNRgzqKUcPO66jpytsjB62XcjcFT3iayd64k0F9HQHo6eUzDzJ6+jJcyhZMLzP0dMxerIbjBefgp6O0aPBCXrKaTMSi6Knm9DTLaBnze6gO9nuQBM3Q4/U9Bp62NscPV2OnsTogDIk6JGIU/QkFSN5U/QkV7jUL42evRfLqOip6Hk+6AkoizP0' \
			b'BNkiesRTjh52XEdPKGyMHrYzyNwVPeJrJ3qitwJ6DrYzKCYzT/I6evIcShYM73P0sJ1BfoPx4lPQw3YGMThBTzltRmJR9Ex2Bt2CnUG3ZmfQnWxnoImboUdqeg097G2OntzOoEvsDLrJzkAjTtGTVIzuBT3JFS71S6Nn7+UtKnoqep4Pevqmm9sZsFs32Rmo' \
			b'pxw97LiOnr6wMXrYziBzV/SIr53oiTcV0HOwnUExmXmS19GT51CyYHifo4ftDPIbjBefgh62M4jBCXrKaTMSi6JnsjPoFuwMujU7g+5kOwNN3Aw9UtNr6GFvc/TkdgZdYmfQTXYGGnGKnqRiJG+KnuQKl/ql0bP30hIVPRU9zwY9KHlzO4Ogm6JHPWXoEcdV' \
			b'9IS2sBF6AtsZZO6CHvW1Cz3TTdvoCQfbGRSTmSd5FT2zHEoWDO8z9AS2M8hvMF58MnoC2xnE4Bg9C2kzEougJ0x2BmHBziCs2RmEk+0MNHE5erSmV9Aj3mboCbmdQUjsDMJkZ6ARJ+hJK0byJuhJr3CpXxo9ey/rUNFzBHrONZG1IuiREOSbMLc3YLcw2Ruo' \
			b'pxxB7LiOIF/YGEFsbpC5K4LE104ExZsKCDrY3KCYzDzJ6wjKcyhZMLzPEcTmBvkNZjpUCrHFQRqogKicQiNxKYgmo4OwYHQQ1owOwslGBzFPOYikvtdAxN7mIMqNDkJidBAmowONOAVRUpKSNwVRcoUL/tIg2nuBhQqiCqLnB6KAa3rMQBRkiyASTzmI2HEd' \
			b'RKGwMYj4DVDmriASXztBFL0VQHTwG6BiMvMkr4Moz6FkwfA+BxG/AcpvMD4eKoj4JVAaqIConEIjcSmIpvdAYeE9UFh7DxROfg8U85SDSOp7DUTsbQ6i/D1QSN4Dhek9kEacgigpSd0LiJIrXPAXBpGrSx1UEN0xiPomzN8HsVuY3geppxxE7LgOor6wMYj4' \
			b'fVDmriASXztBFG8qgOjg90HFZOZJXgdRnkPJguF9DiJ+H5TfYHw8VBDxK6E0UAFROYVG4lIQTW+FwsJbobD2Viic/FYo5ikHkdT3GojY2xxE+VuhkLwVCtNbIY04BVFSkpI3BVFyhQv+0iC61MIH7l5Z9FxeDLU3wiE4h3KwUA4HMsmgPM6YZGSLTBJPOZOM' \
			b'KL9VKrFkTL8pfCKTITK10VWuKZ3E5y46oSYQfwU8mYPxZFa2PfA0zzMhygiizBaiDC8kx4Uz3YMLyaUf1W3lq7pJqMyorFiTdBpNL0MK5TBSylCZb1PKrFHKnEIpitO5mMIZqaTuV0hF+UBBm7NK8qSsMhOrWvnAbpuUTgordbUkzhCEYMsk2ErkdmS5Umyt' \
			b'4WofToFie8B+AyiEBySu8AoVwAMglrh1Hasm5OukPjd0PeYKqfVR6kyPUiMuzzvD1ihbxJZ4yrHFjuuPUmNhY2CN/CiVuiusxJfAKvBSqGEsPFDFWwvEGg8mVimxecLXH6jyfEpGDO9zWo38QJXdYHw81AeqkR+okkDlgaqcQmOmxMozVUKrceGZalyj1Xjy' \
			b'M5VmKyeVVPzaMxV7m3NqzJ+pxuSZapyeqSTiFFNJYUreFE7JFd5f+pnqOpZVqGyqbHpaNuGK8XPbu143ZZN6ytgkjqts6tvCRmzq2fYucxc2qS9hEx5DEdBuxqbp1m029Qdb4BUTmyd8lU2zfEpGDO8zNvVsgZffYHw8FDb1bISXBspsWkihMVNimU39ZIrX' \
			b'L5ji9WumeP3JpngxWxmbtOJX2CTeZmzqc1O8PjHF6ydTPI04YVNamJI3YVN6hcv+0my6jmUXKpsqm56YTRZlbsYmK1tkk3jK2cSO62wqbcwmy2xK3ZVN4kvZZJlNtsCmeGuBTfZgNi2kN0n4OpvyfEpGDO9zNllmU3aD8fFQ2WSZTUmgwqZyCo2ZEitsshOb' \
			b'7AKb7Bqb7Mls0mzlbJKKX2MTe5uzyeZssgmb7MQmiThlU1KYkjdlU3KFy/7SbLqOdRnqt4/O/+2jSqkjKeVR4maU8rJFSomnnFLsuE4pX9iYUmw6nrkrpcSXUsozpfxEKbrZ0A6VSQygwKqDzciLSc6Tv86qPLeSHU7wjFVsRp7fYKZDZRWbkaeBCqvKKTRm' \
			b'Sqywik8VVwvG5P2aMXl/sjF5zFmOK5GANVxJLma4yo3J+8SYvJ+MyTXiFFdJeUreFFfJFS7+S+PqOtZyqLiquLoaXHUN6ZEMV51sEVfiKccVO67jqitsjKuOcZW6K67El+KqY1wl9hN0s6EdKpMYQAFX3cG4KiU5T/46rvLcSnY4wTNcdYyr7Abj46HiqmNc' \
			b'JYEKrsopNGZKrOCKTxVX3QKuujVcdSfjSnOW40okYA1XkosZrrocV12Cq27ClUSc4iopT8mb4iq5wsV/aVxdx/oPFVcVV1eDqwGlbIarQbaIK/GU44od13E1FDbG1cC4St0VV+JLcTUwroYEVwPjamBcxQAKuBoOxtXatg+u8txKdjjBM1wNjKvsBuPjoeJq' \
			b'YFwlgQquyik0Zkqs4IpPFVfDAq6GNVwNJ+NKc5bjSiRgDVeSixmuhhxXmnAON+JKIk5xlZSn5E1xlVzh4r80rq5jzYiKq4qrq8HViCI2w9UoW8SVeMpxxY7ruBoLG+OKTf0yd8WV+FJcsalfn5j60c2GdjYNoICrgw3+iknOk7+Oqzy3kh1O8AxXbPCX32B8' \
			b'PFRcscFfGqjgqpxCY6bECq74VHG1YPPXr9n89Sfb/MWc5bgSCVjDleRihqvc5q9PbP76yeZPI05xlZSn5E1xlVzh/aVxVVeWOAeiOsZSxJEX9FzrBKnnihoUkvlkKHYbpslQ6ilDjTiuoqakh6cYUEIGng4VHYdpKpT62jUVarppGzPDwTOh0lQUtz0wU3az' \
			b'khUJJcPNwLOh6IJJXjZtyGrPF5NhNDkMElK0TBHKdYEiw9o8p+HgeU4JQDRdOUC0PlcAIt5mAKGMTAAZkslNmFwliMacECSVMMmXECS9MnIMc4IIIUT7o9b317GMQ31IqQ8pV0MO2wxzuzp2Gya7OvWUk4MdV8kxlDYmBtvVZe5KDPElxBjYrm5I7OroZkM7' \
			b'VAAxgAI9DrauKyY5T/4qPWa5lexwgmfUYOu6/Abj46FAZGDrujRQfkhZSKExU2KFLXyqeFkwsBvWDOyGkw3sYs5yxogErDFGcjFjTG5gNyQGdsNkYKcRp4hJylPypohJrnDxX/ghxV9qsYf6kFIfUi6AGmhSbo4aJ1tEjXjKUcOOxz2kxBgIOY6R4xJ3RY74' \
			b'2vmQEm8qYMYdjBm3sp3ykOIEN24LN45x45YeUgrJMBKQgsRNFHELFHFrFHGnPKRIumYAkfpcA4hkYAYQlwPELTykSMwpQRIJk3wpQZIrI8ewx0PKdSyVULV+1fpn0frQbZ/bmLHbMNmYqadc67PjcVo/xkBan23NouMw2Zqpr51aP95U0PoH25elqShup2h9' \
			b'sTMbtuzMBrYzG7olrV9IhpGAVOtP5mPDgvnYsGY+NhxsPpZqfUnXTOtLfa5pffY21/q55diQWI5lWl9iTrV+ImGSL9X6yZWRY9hD61/HIgS3rvWf+6puz40OKCh+Rgd2Q2ehg3rK6CCOq3SIoaUbUYFimLkLFdTXLipMN21TAV0Po0IxmXmSV6kwy6FkwfA+' \
			b'owG6hPkNRvZCBjzu2yk4HmxaSJuRWAQVeCyooKIooIIKbhcqOLxThpk0cTkvtKZXeCHeZryg3Ey8oOCFF5QhxoVGnOAirRjJm+AivcKlfulhJlpjALjQQEgNJJUg1CqH/BaKTEIjnwDJJEzyOZbMTjKZJ111dDgeUeYJMAXXsYofCVctlEg7Yj1jRUNNQ5lY' \
			b'KJMcY30BZSAMICuMtHDji5WGM+ENFRuqMEjwBlUjQgINbORd/TAk2NugaGGDoE3Zt0EhFCcG4JBAkFyNflUgstCFJR62zeIjE6U18A8ld4Mi7dnsGYLtprT5yfZZU7a2yKn42yalP9jgWYJCEGLG3JglbErhPk9Rplu4YCVbhvcZPD3bQOeRgQj4xADaswG0' \
			b'XhV4ooxCgaDMlxJsJDaBqJ/sn/2C/bNP7Z/xhRgmEa2paEVryuEwx2vbR8R6tY52lHEjrEV9djhwW/p2lYu5yamrYrNCXcrQtgmazy2mY4Zmj2kadTt7VkvKWHMt8E2vcK1F+PYPcAoExuuW/jv67+l/R//Jjx/wf09Xx0D/yR1xTDvLO8c7uh8xjLuA4RCE' \
			b'O9AWOxAcyZsw9zDgMmcZsgzWJaSmMF0lKQH0OEr2Mzq68pAdEU1ppiQrUexYgh1Drzm5ZsRaI1WRUimhSnSaUYlIpBQK6UNVOIA4GWgKiDkbXyaoRKIoRdb5USDHgdhQWJzKhU27vQr2Rj8RtDWsRko9anRV50VdfooiP0mLzxX4XHWvKu2Sxk7VdVFXb2tp' \
			b'Us5RM5NCVm2MCjLsVpDdZXVkd6Ca1EcNWhn90bWlL2hMeydaM9WYs748lEXenwc/UGkWysVCzdlhyLSqHQdaVd1BWTgoCwdtiDVtn/bz4deC1j2so0/d7NtQvZjS09Vv0l+3Zq/hrLOrYZ98fk1VMcrQnaljbA6ZJqZySbUx+QjT8yA/+GIy8AlZWkiurqdn' \
			b'YMwiSYDORty09KwwzaHHkx562NBeH7AAqf/b71bv4Yq7wJNux3Gj7kZ6w+4RxnQeW7/7I3vEz1Y3n1UvU1M3e76Avs4uMnYBduhlf/26eTytlzzsVqP9TajRcCMq9Ja6xlVtPpbatJylW1WZt9qNPUlNjnuOtrbXrCavZdz1lLeH16gqjx13PVBdZqqSeyb3' \
			b'oS5Pfzn3eOqSBjueq8q0tBjRkWoTn9fdA6QDH9ch4P0UqD2rAt1hB3KcDqV0n0uHLpickx61T9TlHJ5/t/Na9Kh/Al1KJfFIutTv1Ke2NJp60S4oCtfT69RTuqEgC4/80j88WjfUp1r0EXuhXXvEA3v3THqil9aej6k5/W7teRHNGfLxzov2Qq0VG6/DtCYK' \
			b'wvPriZ6kNe31mEr5ozuel35wb+sY502py+t6ZX8ND+xY73WMc3816a5HTR5tLXVxNXkPVlJVTT5vNemqmjxATfrmzTWoyFs3ur9VpTicyeg+UYyk0m5ZL96Rsf2z0IOWvokkuvBkY/vmX+MDsJ1e83RVOZbGJMEtoFFqd0eKMu09osi1pynJ2+893qiWpBnW' \
			b'lGzWWfepMtOuo5TGCX3IUNVkSU1CeLgsBST2dtRlYfmBc6jMgLbJ44GqM51ylE81qmr06dUoJpTWptjg4hu3qU7D42jUDeYPbxsOU6yObMPixKF0uhCq2f5SatY/gqY9ejGZPbUtpPUimtaMj9c5pYVeagd1p2ZFW7KnUa+9vbiOJXXQXk7DWgr0sTutuFDT' \
			b'efTswRp2q+s6XEqndjfWe011aQg30HOtuvSGdOllx0UzFYpScXMd1WtWoeOlVGi4ZRXaVxVaVeizUaG2qtAzqlAoqwup0P6WVehQVWhVoc9GhbqqQs+pQtv6Duqe7ZhOM+68eh15B+rxJF1o6RsYN/4i/th56Qa/e9DSxwwgtqoGC6/iu+l1/NWrxIWvAJz1' \
			b'NXxVkbenIlE6NtjoT1WWl+84Pvqr9mN7je761OU+3295ZI057Og8uifWluFC6yivGMLj2smju06NSR/TSX7n1pYUZqoxyeGcCrOQg0VlyddTXanfGymrSU79tajK4jdEzq8p97OKt9iSubiXu5trX+MawgMRdJ/PcYHwPiDsqOt6hTORqi6uuvhEXQzRpr+z' \
			b'62I8y3SxP3PntZCDZV1M1w/QxZT6qouLuthqcT+BLu6aN9OneFn7OtW7+3x4dyRde74xgsM/lfs4A6Tlz9na5vCPnF9CT+7zZM/tMirCqAOzr83OtN4hnyJnhYea4pIP6Md/Q/zxhy1z5ZUprmM+C34xBbX3Q3WoyuNOlMcm+3L17SiPlr5WXJXHlSmP5l9D' \
			b'/wCKnbogfdUiVYtcqxbBirl0D4TirDpkXYfoQ0/6QDNUbVK1ydVqk/HyzzMUZ9Umx2mT8cLaZI9x6MdRKMePPp+mWMITK5edo8lnVDBc2tPvPMqFQsoHhduTFUwhqcvKha4fMg7cXkDLZOO7T6NqpOAuP6IL5VA7QLUDdJkOUIsttw7oPpMeUCAt0pIWaasW' \
			b'qVrk/FpEpsXU90JnVCNYpvq7BjXSjQ8IC5Bw7MGglD+glgNJx94KaRdbtUvVLo+gXbCFVO1yXu0C7vq7Ee3izqJdzjr15XkpmEuaAKZKxpDhKhpMXYHCIf/3Y+rCxXMBpUMRnevpiAJbUjySpUnxWFSuWOK7VRA0sAfUwSC8D7wsuXsAJUa6x1fd8zx1z9Pr' \
			b'G7zzjvRNuFAnh827z6VvqACX9A1nac+Ozi4tcwar3UeY33sbiuYaFi+4amVDTk8y2/aGH6soSdexwMCJVr39wVa9l1o/4Pr1yzUtkPLEOoYE/1qn9d+mouEivbLlTE7VNntZ/z7RgiXXr3CubVWmCyodViG3tqDIjWoeLmvz/NTPLnPhK1gyqWqgq9JALEu3' \
			b'v6zRbWqh613T7VQtVDAzvr7l265p/crbW6/yvHqJ2vozXnft2pekpKzc6gqUJ2qrIbMwvuL1JqvCenqFxbpBO1PPfLXIa9daj7mmzvWrrbaqraq29lZbtqqtqrauQW3Z5g024pkisF1PQrsgUrb3dLODmz1OKwXpwjWgcAyDf3TZzy8PMhV0oMtd88aiBcJg' \
			b'v8Qj1JBwHxy/2IAypUWl0N8Gkgj6q0NPoDUCuTl0Qw2GiqYhVdCAdEHkEAFIH4gClD2IORQq1g+9RO1xMUPUKvgFFGjBMXK4DsJEq9RBo0N5wKaKrYomV+L0OKjRFvy3A6YB9DboKdRNg9yJC2JD2x/whyFT4W78WVMJVNj3j2LvMPZ+3/hB8kARYPNZT0mH' \
			b'a1V2i38tBjQOtGLk7BolLOxOGBQ7SBt+6TLgl4Z2pLPltOI3gefpjcuf45dz0LzFz/IQmu2/lsxouuw394NLYIbsCt5laYZEx1/XgXZBVyiz/ZNnFnDU4TehIdPQGzjbH31UveUmOpyeyXE8PJ+h3ZFX6Pbs/Yfrmy794TqhyTnldyzm1yLt8z7cUgkYKgQn' \
			b'5QAqcrEorBRHz0Wy1TsL9PoEyYOMpCJzOGe351WPaRo3rr6JcmrIkAQ7DwhVes2Fckv29zzdsh12FLcfpYfUcdGTvM+KH6jDVQBdXdCMzmh1oELd3qhzWfgVPZt4bfKy6Dn3uRRPEt+al5XbduetkNJCtIaEC1yvrzXRU8NTbVwsbbnRYTe9wZUeqPWB3EO3' \
			b'+Lg2CF2QfZshPuEtNkVz5uZoHqtJ2qawpc+H4jjyU1/R++qGz3prnuyCa3dgXOnNWKzH3kwbS52tqv5guXLNvW4sM+4aFbhvnm7jYvFnb0pDeJzWNFxbixqaM284Cnctoey1sQiVn2mrNt4lO/h4c58by8wZhhuKgnGqRsZB7LNt9oh7uHjKAxS1Se1oUvjM' \
			b'cacby0x5vAdk5vRW1Z+jZflme8OXW8UL+274cuqwe7iwFgaL4hPrIY+rwTchNCDo/YWanMmbHY7u72x6bmp+/cBNkNY57h+rKfbN9oaP53KEbwNLXm5j6/A3nBgMv0QpDypVNb9LtvCt7n1uLDMLI27XoubRnOLpNy6q8wwT3aJ6f5Smh1w8eWNTlOl3lkDn' \
			b'EZRPyr7OkgiWt/IQk8jb+VV4KkfdeVXy/jJhm+KGtg1L18QHZHW3j3Nte8bEdXj+8bBnj2Q0nLvTjWVmcQDsbEhGWTgNy755pA3tAQ+5gYusPP5Tm9muZjY297qxzCwOip21mZEsnNLU0Gr40Ta0vD3kBi65xaGh2tqWWhvcfK8by8yiOdX5W5s9scWhSf5j' \
			b'bmjmfsgNbA1bB3cObnQ4seJON5aZ4wd3TnxwxKrdVa1LD4ZTG3TNcRvORTnoBpxIQvNAQuliX76Li7faDR3eJIfmXjeWmcVBncdukml1n948x+ZiWzrD68B7uchvaAzm0LHax26uOB3x+jacE5ichEeNjWXo8DEZ8ygtN635Y1rwck275qAN54Eeek9x00ma' \
			b'B9wBZQi7ZPYlnHI1nWcciF/RXGWDd5do9F1z+sZJDWcI6YgNJ/ueKSyWq8PHim6t+Q/NbW5cP+cZkaqvZkUacNmBM22U0H48W3jHbTjj/+RQWNQOH8h6fFWwFw4OVQnYAp50Q/k97EK+8fzs84ybVdWgUhGauuUby9niWFuVs6PkrG/qlm8sZzsHHZ+nVRAu' \
			b'ePQcNq7AOzTrwrWqnsPGFXhD44nXMuyPK5UtbDwTc/rt8Hrothi0nMyvnyUZqzezENVZjocLUd/c68YyU63cDpeZobnXjWWmTv08XGbG5l43lplq33ewzODyq3e6sczU5dIOl5m2udeNF6+sJo2Hy4xt7nVjmTnPMOt9yYxr7nVjmcEFkBPR2KpJqr3lmoPS' \
			b'phXP0BXu0oXQZ+fbC5e7fNHygUnZuTw1vMTVdppwcB1tXWhZZUyhpgplidZcGJpt94FkBGSA49panjlQqLDn613zpmvxQseGDtBG1EF89OiAAkmOkA155d5BJzFtV9BE0EeALASIJIBLwOWBuUsJcl/0DUnJf7x4sNnyjU2B7oAMz35W3swGXJ4fV2r3I60P' \
			b'gmsqsNKARGXLUAc44lxApHCOebPSkYEA4bijqeeMqdBNLhDely//P44Hrc0='

	_UPARTIAL = '\u2202' # \partial
	_USUM     = '\u2211' # \sum
	_UINTG    = '\u222b' # \int
	_UUNION   = '\u222a' # ||
	_USYMDIFF = '\u2296' # ^^
	_UXSECT   = '\u2229' # &&
	_UOR      = '\u2228' # or
	_UAND     = '\u2227' # and
	_UNOT     = '\u00ac' # not

	_LTR      = fr'[a-zA-Z]'
	_LTRD     = fr'[a-zA-Z0-9]'
	_LTRU     = fr'(?:[a-zA-Z_]|\\_)'

	_VARTEX   = '(?:' + '|'.join (sorted ((x.replace ('\\', '\\\\').replace ('+', '\\+').replace ('*', '\\*').replace ('^', '\\^') for x in AST.Var.TEX2PY), reverse = True)) + ')'
	_VARTEX1  = fr'(?:(\d)|({_LTR})|(\\partial|\\infty))'
	_VARPY    = fr'(?:{_LTR}(?:\w|\\_)*(?<!_))'
	_VARUNI   = fr'(?:{"|".join (AST.Var.UNI2PY)})'
	_VAR      = fr'(?:{_VARTEX}(?!{_LTR})|{_VARPY}|{_VARUNI})'

	_STRS     = r"'(?:\\.|[^'])*'"
	_STRD     = r'"(?:\\.|[^"])*"'

	_FUNCPY   = f"(?:{'|'.join (sorted (AST.Func.PY, reverse = True))})"
	_FUNCTEX  = f"(?:{'|'.join (sorted (AST.Func.TEX, reverse = True))})"

	TOKENS    = OrderedDict ([ # order matters due to Python regex non-greedy or operator '|'
		('UFUNC',        fr'\?'),
		('UFUNCPY',       r'Function(?!\w|\\_)'),
		('SYM',          fr'\$|\\\$'),
		('SYMPY',         r'Symbol(?!\w|\\_)'),
		('FUNC',         fr'(@|\%|\\\%|{_FUNCPY}(?!\w|\\_))|\\({_FUNCTEX})(?!{_LTRU})|\\operatorname\s*{{\s*({_LTR}(?:(?:\w|\\_)*{_LTRD})?)(?:_{{(\d+)}})?\s*}}'), # AST.Func.NOREMAP, AST.Func.NOEVAL HERE!

		('LIM',          fr'(?:\\lim)_'),
		('SUM',          fr'(?:\\sum(?:\s*\\limits)?|{_USUM})_'),
		('INTG',         fr'\\int(?:\s*\\limits)?(?!{_LTR})|{_UINTG}'),
		('L_DOT',         r'\\left\s*\.'),
		('L_PARENL',      r'\\left\s*\('),
		('R_PARENR',      r'\\right\s*\)'),
		('L_BRACKL',      r'\\left\s*\['),
		('R_BRACKR',      r'\\right\s*\]'),
		('L_BAR',         r'\\left\s*\|'),
		('R_BAR',         r'\\right\s*\|'),
		('L_SLASHCURLYL', r'\\left\s*\\{'),
		('R_SLASHCURLYR', r'\\right\s*\\}'),
		('CDOT',         fr'\\cdot(?!{_LTRU})'),
		('TO',           fr'\\to(?!{_LTRU})'),
		('UNION',        fr'\\cup(?!{_LTRU})|\|\||{_UUNION}'),
		('SDIFF',        fr'\\ominus(?!{_LTRU})|\^\^|{_USYMDIFF}'),
		('XSECT',        fr'\\cap(?!{_LTRU})|&&|{_UXSECT}'),
		('MAPSTO',       fr'\\mapsto(?!{_LTRU})'),
		('EMPTYSET',     fr'\\emptyset(?!{_LTRU})'),
		('SETMINUS',     fr'\\setminus(?!{_LTRU})'),
		('SUBSTACK',     fr'\\substack(?!{_LTRU})'),

		('BEG_MAT',       r'\\begin\s*{\s*matrix\s*}'),
		('END_MAT',       r'\\end\s*{\s*matrix\s*}'),
		('BEG_BMAT',      r'\\begin\s*{\s*bmatrix\s*}'),
		('END_BMAT',      r'\\end\s*{\s*bmatrix\s*}'),
		('BEG_VMAT',      r'\\begin\s*{\s*vmatrix\s*}'),
		('END_VMAT',      r'\\end\s*{\s*vmatrix\s*}'),
		('BEG_PMAT',      r'\\begin\s*{\s*pmatrix\s*}'),
		('END_PMAT',      r'\\end\s*{\s*pmatrix\s*}'),
		('BEG_CASES',     r'\\begin\s*{\s*cases\s*}'),
		('END_CASES',     r'\\end\s*{\s*cases\s*}'),
		('FRAC2',        fr'\\frac\s*{_VARTEX1}\s*{_VARTEX1}'),
		('FRAC1',        fr'\\frac\s*{_VARTEX1}'),
		('FRAC',         fr'\\frac(?!{_LTRU})'),
		('BINOM2',       fr'\\binom\s*{_VARTEX1}\s*{_VARTEX1}'),
		('BINOM1',       fr'\\binom\s*{_VARTEX1}'),
		('BINOM',        fr'\\binom(?!{_LTRU})'),

		('CMP',          fr'==|!=|<=|<|>=|>|(?:in|not\s+in)(?!{_LTRU})|(?:\\ne(?!g)q?|\\le|\\lt|\\ge|\\gt|\\in(?!fty)|\\notin)(?!{_LTRU})|{"|".join (AST.Cmp.UNI2PY)}'),
		('IF',            r'if(?!\w|\\_)'),
		('ELSE',          r'else(?!\w|\\_)'),
		('OR',           fr'or(?!\w|\\_)|\\vee(?!{_LTRU})|{_UOR}'),
		('AND',          fr'and(?!\w|\\_)|\\wedge(?!{_LTRU})|{_UAND}'),
		('NOT',          fr'not(?!\w|\\_)|\\neg(?!{_LTRU})|{_UNOT}'),
		('SQRT',         fr'sqrt(?!\w|\\_)|\\sqrt(?!{_LTRU})'),
		('LOG',          fr'log(?!\w|\\_)|\\log(?!{_LTR})'),
		('LN',           fr'ln(?!\w|\\_)|\\ln(?!{_LTRU})'),

		('NUM',           r'(?:(\d*\.\d+)|(\d+\.?))((?:[eE]|{[eE]})(?:[+-]?\d+|{[+-]?\d+}))?'),
		('VAR',          fr"(?:(?:(\\partial\s?|partial|{_UPARTIAL})|(d))({_VAR})|({_VAR}))(?:_{{(\d+)}})?"),
		('ATTR',         fr'(?<!\s)\.(?:({_LTRU}(?:\w|\\_)*)|\\operatorname\s*{{\s*({_LTR}(?:\w|\\_)*)\s*}})'),
		('STR',          fr"((?<![.'|!)}}\]\w]){_STRS}|{_STRD})|\\text\s*{{\s*({_STRS}|{_STRD})\s*}}"),

		('WSUB1',        fr'(?<=\w)_{_VARTEX1}'),
		('WSUB',          r'(?<=\w)_'),
		('SUB',           r'_'),
		('SLASHSUB',      r'\\_'),
		('SLASHDOT',      r'\\\.'),
		('COLON',         r'{:}|:'),
		('SCOLON',        r';'),
		('CARET1',       fr'\^{_VARTEX1}'),
		('CARET',         r'\^'),
		('DBLSLASH',      r'\\\\'),
		('DBLSTAR',       r'\*\*'),
		('SLASHCURLYL',   r'\\{'),
		('SLASHCURLYR',   r'\\}'),
		('SLASHBRACKL',   r'\\\['),
		('CURLYL',        r'{'),
		('CURLYR',        r'}'),
		('PARENL',        r'\('),
		('PARENR',        r'\)'),
		('BRACKL',        r'\['),
		('BRACKR',        r'\]'),
		('BAR',           r'\|'),
		('PLUS',          r'\+'),
		('MINUS',         r'-'),
		('STAR',          r'\*'),
		('DIVIDE',        r'/'),
		('EXCL',          r'!'),
		('AMP',           r'&'),
		('COMMA',         r','),
		('PRIME',         r"'"),
		('EQ',            r'='),
		('ignore',        r'\\[,:;]|\\?\s+|\\text\s*{\s*[^}]*\s*}'),
	])

	_PYGREEK_QUICK = '(?:' + '|'.join (sorted ((g for g in AST.Var.GREEK), reverse = True)) + ')'
	_PYMULTI_QUICK = '(?:' + '|'.join (sorted ((g for g in AST.Var.PY2TEXMULTI), reverse = True)) + ')'
	_VARPY_QUICK   = fr'(?:{_PYGREEK_QUICK}\d*|None|True|False|nan|{_LTR}\d*)'
	_VAR_QUICK     = fr'(?:{_VARTEX}|{_PYMULTI_QUICK}\d*|{_VARPY_QUICK}|{_VARUNI})'

	_FUNCPY_QUICK  = _FUNCPY.replace (r'|del|', r'|del(?!ta)|').replace (r'|det|', r'|det(?!a)|').replace (r'|Integer|', r'|Integer(?!s)|').replace (r'|Si|', r'|Si(?!gma)|')

	_IN_QUICK      = r"in(?!teger_|tegrate|teractive_traversal|terpolate|tersecti|tervals|v_quick|verse_|vert)"

	TOKENS_QUICK   = OrderedDict ([ # quick input mode different tokens (differences from normal)
		('FUNC',         fr'(@|\%|{_FUNCPY_QUICK})|\\({_FUNCTEX})|\\operatorname\s*{{\s*({_LTR}(?:(?:\w|\\_)*{_LTRD})?)(?:_{{(\d+)}})?\s*}}'), # AST.Func.NOREMAP, AST.Func.NOEVAL HERE!

		('LIM',          fr'\\lim_'),
		('SUM',          fr'(?:\\sum(?:\s*\\limits)?|{_USUM})_'),
		('INTG',         fr'\\int(?:\s*\\limits)?|{_UINTG}'),
		('CDOT',         fr'\\cdot'),
		('TO',           fr'\\to'),
		('UNION',        fr'\\cup|\|\||{_UUNION}'),
		('SDIFF',        fr'\\ominus|\^\^|{_USYMDIFF}'),
		('XSECT',        fr'\\cap|&&|{_UXSECT}'),
		('MAPSTO',       fr'\\mapsto'),
		('EMPTYSET',     fr'\\emptyset'),
		('SETMINUS',     fr'\\setminus'),
		('SUBSTACK',      r'\\substack'),

		('CMP',          fr'==|!=|<=|<|>=|>|{_IN_QUICK}|not\s+{_IN_QUICK}|(?:\\ne(?!g)q?|\\le|\\lt|\\ge|\\gt|\\in(?!fty)|\\notin)|{"|".join (AST.Cmp.UNI2PY)}'),
		('OR',           fr'\\vee|{_UOR}|or(?!dered)'),
		('AND',          fr'\\wedge|{_UAND}|and'),
		('NOT',          fr'\\neg|{_UNOT}|not(?!_empty_in)'),
		('SQRT',          r'\\sqrt|sqrt(?!_mod|_mod_iter|denest)'),
		('LOG',           r'\\log|log(?!combine|gamma)'),
		('LN',            r'\\ln|ln'),

		('VAR',          fr"(?:(?:(\\partial\s?|partial|{_UPARTIAL})|(d(?!elta)))(partial|{_VAR_QUICK})|({_VAR_QUICK}))(?:(?<!\d)_{{(\d+)}})?"),
	])

	TOKENS_LONG    = OrderedDict () # initialized in __init__()

	_PARSER_TOP             = 'expr_scolon'
	_PARSER_CONFLICT_REDUCE = {'BAR'}

	# grammar definition and implementation

	def expr_scolon_1      (self, expr_scolon, SCOLON, expr_ass_lvals):                return expr_scolon if expr_ass_lvals == AST.CommaEmpty else AST.flatcat (';', expr_scolon, expr_ass_lvals)
	def expr_scolon_2      (self, expr_ass_lvals):                                     return AST ('(', expr_ass_lvals.curly) if expr_ass_lvals.is_curly and expr_ass_lvals.curly.ass_valid else expr_ass_lvals

	def expr_ass_lvals     (self, expr_commas):                                        return _expr_ass_lvals (expr_commas)

	def expr_commas_1      (self, expr_comma, COMMA):                                  return expr_comma if expr_comma.is_comma else AST (',', (expr_comma,))
	def expr_commas_2      (self, expr_comma):                                         return expr_comma
	def expr_commas_3      (self):                                                     return AST.CommaEmpty
	def expr_comma_1       (self, expr_comma, COMMA, expr_colon):                      return _expr_comma (expr_comma, expr_colon)
	def expr_comma_2       (self, expr_colon):                                         return expr_colon

	def expr_colon_1       (self, expr, COLON, expr_colon):                            return _expr_colon (expr, expr_colon)
	def expr_colon_2       (self, expr, COLON):                                        return AST ('-slice', expr, False, None)
	def expr_colon_3       (self, COLON, expr_colon):                                  return _ast_pre_slice (False, expr_colon)
	def expr_colon_4       (self, COLON):                                              return AST ('-slice', False, False, None)
	def expr_colon_5       (self, expr):                                               return expr

	def expr               (self, expr_ass):                                           return expr_ass

	def expr_ass_1         (self, expr_mapsto1, EQ, expr_mapsto2):                     return AST ('=', expr_mapsto1, expr_mapsto2)
	def expr_ass_2         (self, expr_mapsto):                                        return expr_mapsto

	def expr_mapsto_1      (self, expr_paren, MAPSTO, expr_colon):                     return _expr_mapsto (expr_paren.strip, expr_colon)
	def expr_mapsto_2      (self, expr_piece):                                         return expr_piece

	def expr_piece_1       (self, expr_or, IF, expr, ELSE, expr_mapsto):               return _expr_piece (expr_or, expr, expr_mapsto)
	def expr_piece_2       (self, expr_or, IF, expr):                                  return AST ('-piece', ((expr_or, expr),))
	def expr_piece_3       (self, expr_or):                                            return expr_or

	def expr_or_1          (self, expr_or, OR, expr_and):                              return AST.flatcat ('-or', expr_or, expr_and)
	def expr_or_2          (self, expr_and):                                           return expr_and

	def expr_and_1         (self, expr_and, AND, expr_not):                            return AST.flatcat ('-and', expr_and, expr_not)
	def expr_and_2         (self, expr_not):                                           return expr_not

	def expr_not_1         (self, NOT, expr_not):                                      return AST ('-not', expr_not)
	def expr_not_2         (self, expr_cmp):                                           return expr_cmp

	def expr_cmp_1         (self, expr_cmp, CMP, expr_union):                          return _expr_cmp (expr_cmp, CMP, expr_union)
	def expr_cmp_2         (self, expr_union):                                         return expr_union

	def expr_union_1       (self, expr_union, UNION, expr_sdiff):                      return AST.flatcat ('||', expr_union, expr_sdiff)
	def expr_union_2       (self, expr_sdiff):                                         return expr_sdiff

	def expr_sdiff_1       (self, expr_sdiff, SDIFF, expr_xsect):                      return AST.flatcat ('^^', expr_sdiff, expr_xsect)
	def expr_sdiff_2       (self, expr_xsect):                                         return expr_xsect

	def expr_xsect_1       (self, expr_xsect, XSECT, expr_add):                        return AST.flatcat ('&&', expr_xsect, expr_add)
	def expr_xsect_2       (self, expr_add):                                           return expr_add

	def expr_add_1         (self, expr_add, PLUS, expr_mul_exp):                       return _expr_add (self, expr_add, expr_mul_exp)
	def expr_add_2         (self, expr_add, MINUS, expr_mul_exp):                      return _expr_add (self, expr_add, AST ('-', expr_mul_exp))
	def expr_add_3         (self, expr_add, SETMINUS, expr_mul_exp):                   return _expr_add (self, expr_add, AST ('-', expr_mul_exp))
	def expr_add_4         (self, expr_mul_exp):                                       return expr_mul_exp

	def expr_mul_exp_1     (self, expr_mul_exp, CDOT, expr_neg):                       return _expr_mul_exp (self, expr_mul_exp, expr_neg)
	def expr_mul_exp_2     (self, expr_mul_exp, STAR, expr_neg):                       return _expr_mul_exp (self, expr_mul_exp, expr_neg)
	def expr_mul_exp_3     (self, expr_neg):                                           return expr_neg

	def expr_neg_1         (self, MINUS, expr_neg):                                    return _expr_neg (expr_neg)
	def expr_neg_2         (self, expr_div):                                           return expr_div

	def expr_div_1         (self, expr_div, DIVIDE, expr_divm):                        return PopConfs (_expr_diff (_expr_div (expr_div, expr_divm))) # d / dx *imp* y
	def expr_div_2         (self, expr_mul_imp):                                       return PopConfs (_expr_diff (expr_mul_imp)) # \frac{d}{dx} *imp* y
	def expr_divm_1        (self, MINUS, expr_divm):                                   return PopConfs (_expr_neg (expr_divm))
	def expr_divm_2        (self, expr_mul_imp):                                       return expr_mul_imp

	def expr_mul_imp_1     (self, expr_mul_imp, expr_intg):                            return _expr_mul_imp (self, expr_mul_imp, expr_intg)
	def expr_mul_imp_2     (self, expr_intg):                                          return expr_intg

	def expr_intg_1        (self, INTG, expr_sub, expr_super, expr_add):               return _expr_intg (expr_add, (expr_sub, expr_super))
	def expr_intg_2        (self, INTG, expr_super, expr_add):                         return _expr_intg (expr_add, (AST.Zero, expr_super))
	def expr_intg_3        (self, INTG, expr_add):                                     return _expr_intg (expr_add)
	def expr_intg_4        (self, expr_lim):                                           return expr_lim

	def expr_lim_1         (self, LIM, CURLYL, expr_var, TO, expr, CURLYR, expr_neg):                          return AST ('-lim', expr_neg, expr_var, expr)
	def expr_lim_2         (self, LIM, CURLYL, expr_var, TO, expr, caret_or_dblstar, PLUS, CURLYR, expr_neg):  return AST ('-lim', expr_neg, expr_var, expr, '+')
	def expr_lim_3         (self, LIM, CURLYL, expr_var, TO, expr, caret_or_dblstar, MINUS, CURLYR, expr_neg): return AST ('-lim', expr_neg, expr_var, expr, '-')
	def expr_lim_4         (self, expr_sum):                                                                   return expr_sum

	def expr_sum_1         (self, SUM, CURLYL, expr_var, EQ, expr_commas, CURLYR, expr_super, expr_neg):       return AST ('-sum', expr_neg, expr_var, expr_commas, expr_super)
	def expr_sum_2         (self, expr_diffp_ics):                                                             return expr_diffp_ics

	def expr_diffp_ics_1   (self, expr_diffp, expr_pcommas):                           return PopConfs (_expr_diffp_ics (expr_diffp, expr_pcommas))
	def expr_diffp_ics_2   (self, expr_diffp):                                         return expr_diffp

	def expr_diffp_1       (self, expr_diffp, PRIME):                                  return AST ('-diffp', expr_diffp.diffp, expr_diffp.count + 1) if expr_diffp.is_diffp else AST ('-diffp', expr_diffp, 1)
	def expr_diffp_2       (self, expr_func):                                          return expr_func

	def expr_func_1        (self, SQRT, expr_neg_arg):                                 return _expr_func (1, '-sqrt', expr_neg_arg)
	def expr_func_2        (self, SQRT, expr_super, expr_neg_arg):                     return AST ('^', _expr_func (1, '-sqrt', expr_neg_arg), expr_super, is_pypow = expr_super.is_dblstar)
	def expr_func_3        (self, SQRT, BRACKL, expr_commas, BRACKR, expr_neg_arg):    return _expr_func (1, '-sqrt', expr_neg_arg, expr_commas)
	def expr_func_4        (self, LN, expr_neg_arg):                                   return _expr_func (1, '-log', expr_neg_arg)
	def expr_func_5        (self, LN, expr_super, expr_neg_arg):                       return AST ('^', _expr_func (1, '-log', expr_neg_arg), expr_super, is_pypow = expr_super.is_dblstar)
	def expr_func_6        (self, LOG, expr_neg_arg):                                  return _expr_func (1, '-log', expr_neg_arg)
	def expr_func_7        (self, LOG, expr_super, expr_neg_arg):                      return AST ('^', _expr_func (1, '-log', expr_neg_arg), expr_super, is_pypow = expr_super.is_dblstar)
	def expr_func_8        (self, LOG, expr_sub, expr_neg_arg):                        return _expr_func (1, '-log', expr_neg_arg, expr_sub)
	def expr_func_9        (self, FUNC, expr_neg_arg):                                 return _expr_func_func (FUNC, expr_neg_arg)
	def expr_func_10       (self, FUNC, expr_super, expr_neg_arg):                     return _expr_func_func (FUNC, expr_neg_arg, expr_super)
	def expr_func_11       (self, expr_pow):                                           return expr_pow

	def expr_pow_1         (self, expr_diffp_ics, expr_super):                         return AST ('^', expr_diffp_ics, expr_super, is_pypow = expr_super.is_dblstar)
	def expr_pow_2         (self, expr_fact):                                          return expr_fact

	def expr_fact_1        (self, expr_diffp_ics, EXCL):                               return AST ('!', expr_diffp_ics)
	def expr_fact_2        (self, expr_attr):                                          return expr_attr

	def expr_attr_1        (self, expr_diffp_ics, ATTR, expr_pcommas):                 return PopConfs (AST ('.', expr_diffp_ics, (ATTR.grp [0] or ATTR.grp [1]).replace ('\\', ''), expr_pcommas.comma if expr_pcommas.is_comma else (expr_pcommas,)))
	def expr_attr_2        (self, expr_diffp_ics, ATTR):                               return PopConfs (AST ('.', expr_diffp_ics, (ATTR.grp [0] or ATTR.grp [1]).replace ('\\', '')))
	def expr_attr_3        (self, expr_idx):                                           return expr_idx

	def expr_idx_1         (self, expr_diffp_ics, expr_bcommas):                       return PopConfs (AST ('-idx', expr_diffp_ics, expr_bcommas.comma if expr_bcommas.is_comma else (expr_bcommas,)))
	def expr_idx_2         (self, expr_abs):                                           return expr_abs

	def expr_abs_1         (self, L_BAR, expr_commas, R_BAR):                          return AST ('|', expr_commas) if not expr_commas.is_comma else _raise (SyntaxError ('absolute value does not take a comma expression'))
	def expr_abs_2         (self, BAR1, expr_commas, BAR2):                            return AST ('|', expr_commas) if not expr_commas.is_comma else _raise (SyntaxError ('absolute value does not take a comma expression'))
	def expr_abs_3         (self, expr_paren):                                         return expr_paren

	def expr_paren_1       (self, expr_pcommas):                                       return AST ('(', expr_pcommas, is_paren_tex = expr_pcommas.is_commas_tex) if not expr_pcommas.is_lamb_mapsto else expr_pcommas.setkw (is_lamb_mapsto = False)
	def expr_paren_2       (self, expr_bracket):                                       return expr_bracket
	def expr_pcommas_1     (self, CURLYL, L_PARENL, expr_commas, R_PARENR, CURLYR):    return expr_commas.setkw (is_commas_tex = True)
	def expr_pcommas_2     (self, L_PARENL, expr_commas, R_PARENR):                    return expr_commas
	def expr_pcommas_3     (self, PARENL, expr_commas, PARENR):                        return expr_commas

	def expr_bracket_1     (self, expr_bcommas):                                       return AST ('[', expr_bcommas.comma if expr_bcommas.is_comma else (expr_bcommas,))
	def expr_bracket_2     (self, expr_ufunc_ics):                                     return expr_ufunc_ics
	def expr_bcommas_1     (self, L_BRACKL, expr_commas, R_BRACKR):                    return expr_commas
	def expr_bcommas_2     (self, BRACKL, expr_commas, BRACKR):                        return expr_commas

	def expr_ufunc_ics_1   (self, expr_ufunc, expr_pcommas):                           return _expr_ufunc_ics (self, expr_ufunc, expr_pcommas)
	def expr_ufunc_ics_2   (self, expr_ufunc):                                         return expr_ufunc

	def expr_ufunc_1       (self, UFUNCPY, expr_pcommas):                              return _expr_ufunc (self, expr_pcommas, py = True)
	def expr_ufunc_2       (self, UFUNC, expr_var, expr_pcommas):                      return _expr_ufunc (self, expr_pcommas, name = expr_var.var)
	def expr_ufunc_3       (self, UFUNC, expr_pcommas):                                return _expr_ufunc (self, expr_pcommas)
	def expr_ufunc_4       (self, expr_varfunc):                                       return expr_varfunc

	def expr_varfunc_1     (self, expr_var_or_sub, expr_intg):                         return _expr_varfunc (self, expr_var_or_sub, expr_intg)
	def expr_varfunc_2     (self, expr_sym):                                           return expr_sym

	def expr_sym_1         (self, SYMPY, expr_pcommas):                                return _expr_sym (self, expr_pcommas, py = True)
	def expr_sym_2         (self, SYM, expr_var, expr_pcommas):                        return _expr_sym (self, expr_pcommas, name = expr_var.var)
	def expr_sym_3         (self, SYM, expr_pcommas):                                  return _expr_sym (self, expr_pcommas)
	def expr_sym_4         (self, SYM, expr_var):                                      return _expr_sym (self, AST.CommaEmpty, name = expr_var.var)
	def expr_sym_5         (self, SYM):                                                return _expr_sym (self, AST.CommaEmpty, name = '')
	def expr_sym_6         (self, expr_subs):                                          return expr_subs

	def expr_subs_1        (self, L_DOT, expr_commas, R_BAR, SUB, CURLYL, subsvars, CURLYR):  return _expr_subs (expr_commas, subsvars)
	def expr_subs_2        (self, SLASHDOT, expr_commas, BAR, SUB, CURLYL, subsvars, CURLYR): return _expr_subs (expr_commas, subsvars)
	def expr_subs_3        (self, expr_cases):                                         return expr_cases
	def subsvars_1         (self, SUBSTACK, CURLYL, subsvarss, CURLYR):                return subsvarss
	def subsvars_2         (self, expr_commas):                                        return expr_commas
	def subsvarss_1        (self, subsvarsv, DBLSLASH):                                return subsvarsv
	def subsvarss_2        (self, subsvarsv):                                          return subsvarsv
	def subsvarsv_1        (self, subsvarsv, DBLSLASH, expr_ass):                      return subsvarsv + (expr_ass,) if expr_ass.is_ass else _raise (SyntaxError ('expecting assignment'))
	def subsvarsv_2        (self, expr_ass):                                           return (expr_ass,) if expr_ass.is_ass else _raise (SyntaxError ('expecting assignment'))

	def expr_cases_1       (self, BEG_CASES, casess, END_CASES):                       return AST ('-piece', casess) # AST ('{', ('-piece', casess))
	def expr_cases_2       (self, expr_mat):                                           return expr_mat
	def casess_1           (self, casessp, DBLSLASH):                                  return casessp
	def casess_2           (self, casessp):                                            return casessp
	def casessp_1          (self, casessp, DBLSLASH, casessc):                         return casessp + (casessc,)
	def casessp_2          (self, casessc):                                            return (casessc,)
	def casessc_1          (self, expr1, AMP, expr2):                                  return (expr1, expr2)
	def casessc_2          (self, expr, AMP):                                          return (expr, True)

	def expr_mat_1         (self, L_BRACKL, BEG_MAT, mat_rows, END_MAT, R_BRACKR):     return _expr_mat (mat_rows)
	def expr_mat_2         (self, BEG_MAT, mat_rows, END_MAT):                         return _expr_mat (mat_rows)
	def expr_mat_3         (self, BEG_BMAT, mat_rows, END_BMAT):                       return _expr_mat (mat_rows)
	def expr_mat_4         (self, BEG_VMAT, mat_rows, END_VMAT):                       return _expr_mat (mat_rows)
	def expr_mat_5         (self, BEG_PMAT, mat_rows, END_PMAT):                       return _expr_mat (mat_rows)
	def expr_mat_6         (self, expr_vec):                                           return expr_vec
	def mat_rows_1         (self, mat_row, DBLSLASH):                                  return mat_row
	def mat_rows_2         (self, mat_row):                                            return mat_row
	def mat_rows_3         (self):                                                     return ()
	def mat_row_1          (self, mat_row, DBLSLASH, mat_col):                         return mat_row + (mat_col,)
	def mat_row_2          (self, mat_col):                                            return (mat_col,)
	def mat_col_1          (self, mat_col, AMP, expr):                                 return mat_col + (expr,)
	def mat_col_2          (self, expr):                                               return (expr,)

	def expr_vec_1         (self, SLASHBRACKL, expr_commas, BRACKR):                   return _expr_vec (self, expr_commas)
	def expr_vec_2         (self, expr_frac):                                          return expr_frac

	def expr_frac_1        (self, FRAC, expr_binom1, expr_binom2):                     return _expr_diff (AST ('/', expr_binom1, expr_binom2))
	def expr_frac_2        (self, FRAC1, expr_binom):                                  return _expr_diff (AST ('/', _ast_from_tok_digit_or_var (FRAC1), expr_binom))
	def expr_frac_3        (self, FRAC2):                                              return _expr_diff (AST ('/', _ast_from_tok_digit_or_var (FRAC2), _ast_from_tok_digit_or_var (FRAC2, 3)))
	def expr_frac_4        (self, expr_binom):                                         return expr_binom

	def expr_binom_1       (self, BINOM, expr_curly1, expr_curly2):                    return AST ('-func', 'binomial', (expr_curly1, expr_curly2))
	def expr_binom_2       (self, BINOM1, expr_curly):                                 return AST ('-func', 'binomial', (_ast_from_tok_digit_or_var (BINOM1), expr_curly))
	def expr_binom_3       (self, BINOM2):                                             return AST ('-func', 'binomial', (_ast_from_tok_digit_or_var (BINOM2), _ast_from_tok_digit_or_var (BINOM2, 3)))
	def expr_binom_4       (self, expr_curly):                                         return expr_curly

	def expr_curly_1       (self, L_SLASHCURLYL, expr_commas, R_SLASHCURLYR):          return _expr_curly (expr_commas, forceset = True)
	def expr_curly_2       (self, CURLYL, expr_commas, CURLYR):                        return _expr_curly (expr_commas)
	def expr_curly_3       (self, SLASHCURLYL, expr_commas, SLASHCURLYR):              return AST ('-set', expr_commas.comma) if expr_commas.is_comma else AST ('-set', (expr_commas,))
	def expr_curly_4       (self, SLASHCURLYL, expr_commas, CURLYR):                   return AST ('-set', expr_commas.comma) if expr_commas.is_comma else AST ('-set', (expr_commas,))
	def expr_curly_5       (self, expr_term):                                          return expr_term

	def expr_term_1        (self, expr_num):                                           return expr_num
	def expr_term_2        (self, expr_var_or_sub):                                    return expr_var_or_sub
	def expr_term_3        (self, STR):                                                return AST ('"', py_ast.literal_eval (STR.grp [0] or STR.grp [1].replace ('\\}', '}')))
	def expr_term_4        (self, EMPTYSET):                                           return AST.SetEmpty

	def expr_var_or_sub_1  (self, expr_var):                                           return expr_var
	def expr_var_or_sub_2  (self, SUB):                                                return AST ('@', '_') # special cased for last expression variable
	def expr_var_or_sub_3  (self, SLASHSUB):                                           return AST ('@', '_') # special cased for last expression variable

	def expr_num           (self, NUM):                                                return _expr_num (NUM)

	def expr_var_1         (self, VAR):                                                return _expr_var (VAR)
	def expr_var_2         (self, SQRT):                                               return AST ('@', 'sqrt')
	def expr_var_3         (self, LN):                                                 return AST ('@', 'ln')
	def expr_var_4         (self, LOG):                                                return AST ('@', 'log')
	def expr_var_5         (self, FUNC):                                               return AST ('@', _FUNC_name (FUNC))

	def expr_sub_1         (self, WSUB, expr_frac):                                    return expr_frac
	def expr_sub_2         (self, WSUB1):                                              return _ast_from_tok_digit_or_var (WSUB1)

	def expr_super_1       (self, DBLSTAR, expr_neg_arg):                              expr_neg_arg.is_dblstar = True; return expr_neg_arg
	def expr_super_3       (self, CARET, expr_frac):                                   return expr_frac
	def expr_super_4       (self, CARET1):                                             return _ast_from_tok_digit_or_var (CARET1)

	def expr_neg_arg_1     (self, NOT, expr_neg_arg):                                  return AST ('-not', expr_neg_arg)
	def expr_neg_arg_2     (self, MINUS, expr_neg_arg):                                return _expr_neg (expr_neg_arg)
	def expr_neg_arg_3     (self, expr_diffp_ics):                                     return expr_diffp_ics

	def caret_or_dblstar_1 (self, DBLSTAR):                                            return '**'
	def caret_or_dblstar_2 (self, CARET):                                              return '^'

	#...............................................................................................
	# autocomplete means autocomplete AST tree so it can be rendered, not necessarily expression

	def mark_incomplete (self, ast):
		self.incomplete = self.pos

		return ast # convenienve

	def stack_has_sym (self, sym):
		return any (st.sym == sym for st in self.stack)

	def in_intg (self):
		for st in reversed (self.stack):
			if st.sym == 'INTG':
				return True

			if st.sym in {'LIM', 'SUM', 'L_DOT', 'L_PARENL', 'L_BRACKL', 'L_BAR', 'L_SLASHCURLYL', 'TO', 'UNION', 'SDIFF', 'XSECT', 'BEG_MAT',
					'BEG_BMAT', 'BEG_VMAT', 'BEG_PMAT', 'BEG_CASES', 'SLASHDOT', 'SCOLON', 'SLASHCURLYL', 'SLASHBRACKL', 'CURLYL', 'PARENL', 'BRACKL'}:
				break

		return False

	#...............................................................................................

	_AUTOCOMPLETE_SUBSTITUTE = {
		'CARET1'          : 'CARET',
		'WSUB1'           : 'SUB',
		'FRAC2'           : 'FRAC',
		'FRAC1'           : 'FRAC',
		'BINOM2'          : 'BINOM',
		'BINOM1'          : 'BINOM',
		'expr_super'      : 'CARET',
		'caret_or_dblstar': 'CARET',
	}

	_AUTOCOMPLETE_CONTINUE = {
		'COMMA'        : ',',
		'PARENL'       : '(',
		'PARENR'       : ')',
		'CURLYR'       : '}',
		'BRACKR'       : ']',
		'BAR'          : '|',
		'SLASHCURLYR'  : '\\}',
		'L_PARENL'     : '\\left(',
		'L_BAR'        : '\\left|',
		'R_PARENR'     : ' \\right)',
		'R_CURLYR'     : ' \\right}',
		'R_BRACKR'     : ' \\right]',
		'R_BAR'        : ' \\right|',
		'R_SLASHCURLYR': ' \\right\\}',
	}

	_AUTOCOMPLETE_COMMA_CLOSE = {
		'CURLYL'       : 'CURLYR',
		'PARENL'       : 'PARENR',
		'BRACKL'       : 'BRACKR',
		'SLASHCURLYL'  : 'CURLYR',
		'SLASHBRACKL'  : 'BRACKR',
		'L_PARENL'     : 'R_PARENR',
		'L_BRACKL'     : 'R_BRACKR',
		'L_SLASHCURLYL': 'R_SLASHCURLYR',
	}

	def _insert_symbol (self, sym, tokinc = 0):
		tokidx       = self.tokidx
		self.tokidx += tokinc

		for sym in ((sym,) if isinstance (sym, str) else sym):
			if sym in self.TOKENS:
				self.tokens.insert (tokidx, sym if isinstance (sym, Token) else Token (self._AUTOCOMPLETE_SUBSTITUTE.get (sym, sym), '', self.tok.pos))

				if self.autocompleting:
					if sym not in self._AUTOCOMPLETE_CONTINUE:
						self.autocompleting = False
					else:
						self.autocomplete.append (self._AUTOCOMPLETE_CONTINUE [sym])

			else:
				self.tokens.insert (tokidx, Token (self._AUTOCOMPLETE_SUBSTITUTE.get (sym, 'VAR'), '', self.tok.pos, ('', '', '', '', '')))
				self._mark_error ()

			tokidx += 1

		return True # for convenience

	def _mark_error (self, sym_ins = None, tokinc = 0, at = None):
		self.autocompleting = False

		if self.erridx is None:
			self.erridx = self.tokens [self.tokidx].pos if at is None else at

		if sym_ins is not None:
			return self._insert_symbol (sym_ins, tokinc)

	def _parse_autocomplete_expr_commas (self, rule, pos):
		idx = -pos

		if self.tokens [self.tokidx - 1] == 'COMMA' and (self.stack [idx].sym not in {'CURLYL', 'PARENL', 'L_SLASHCURLYL', 'L_PARENL'} or \
				not self.stack [-1].red.is_comma or self.stack [-1].red.comma.len > 1):
			self._mark_error ()

		return self._insert_symbol (self._AUTOCOMPLETE_COMMA_CLOSE [self.stack [idx].sym])

	def _parse_autocomplete_expr_intg (self):
		s               = self.stack [-1]
		self.stack [-1] = State (s.idx, s.sym, s.pos, AST ('*', (s.red, AST.VarNull)))

		if self.autocompleting:
			vars = set (filter (lambda a: not (a.is_differential or a.is_part_any or a.var == '_'), s.red.free_vars))
		else:
			vars = set ()

		if len (vars) == 1:
			self.autocomplete.append (f' d{vars.pop ().var}')
		else:
			self._mark_error ()

		return True

	def parse_getextrastate (self):
		return (self.autocomplete [:], self.autocompleting, self.erridx, self.has_error, self.incomplete)

	def parse_setextrastate (self, state):
		self.autocomplete, self.autocompleting, self.erridx, self.has_error, self.incomplete = state

	def parse_result (self, red, erridx, autocomplete, rederr = None):
		res             = (red is None, not rederr, -erridx if erridx is not None else float ('-inf'), len (autocomplete), self.parse_idx, (red, erridx, autocomplete, rederr))
		self.parse_idx += 1

		if self.parse_best is None or res < self.parse_best:
			self.parse_best = res

		if os.environ.get ('SYMPAD_DEBUG'):
			if self.parse_idx <= 32:
				print ('parse:', res [-1], file = sys.stderr)

			elif self.parse_idx == 33:
				sys.stdout.write ('... |')
				sys.stdout.flush ()
			else:
				sys.stdout.write ('\x08' + '\\|/-' [self.parse_idx & 3])
				sys.stdout.flush ()

		return False # for convenience

	def parse_error (self): # add tokens to continue parsing for autocomplete if syntax allows
		self.has_error = True
		stack          = self.stack

		if self.tok != '$end':
			return self.parse_result (None, self.pos, [], self.rederr)

		irule, pos = self.strules [self.stidx] [0]
		rule       = self.rules [irule]

		if pos == 1:
			# if rule == ('expr_func', ('FUNC', 'expr_neg_arg')): # autocomplete parentheses for function name
			# 	return self._insert_symbol (('PARENL', 'PARENR'))

			if rule == ('expr_paren', ('PARENL', 'expr_commas', 'PARENR')) and stack [-2].sym == 'expr_mul_imp' and \
					(stack [-2].red.is_attr or (stack [-2].red.is_var and stack [-2].red.var in _SP_USER_FUNCS)):
				return self._insert_symbol ('PARENR')

		if pos and rule [1] [pos - 1] == 'expr_commas' and rule [0] not in {'expr_sum', 'expr_abs', 'expr_func', 'expr_subs', 'subsvars'}: # {'expr_abs', 'expr_ufunc', 'varass'}:
			return self._parse_autocomplete_expr_commas (rule, pos)

		if pos >= len (rule [1]): # end of rule
			if rule [0] == 'expr_sub' and stack [-1 - len (rule [1])].sym == 'INTG':
				return self._insert_symbol ('CARET1')

			if rule [0] == 'expr_intg':
				return self._parse_autocomplete_expr_intg ()

		if pos >= len (rule [1]) or (pos == 2 and rule [0] == 'expr_func'):
			return self.parse_result (None, self.pos, [], self.rederr) if self.rederr else False

		return self._insert_symbol (rule [1] [pos])

	def parse_success (self, red):
		if self.incomplete is not None:
			erridx, rederr = self.incomplete, 'incomplete'
		else:
			erridx, rederr = self.erridx, None

		self.parse_result (red, erridx, self.autocomplete, rederr)

		return True # continue parsing if conflict branches remain to find best resolution

	def parse (self, text):
		def postprocess (res):
			return (_ast_mulexps_to_muls (res [0].no_curlys).flat.setkw (pre_parse_postprocess = res [0]),) + res [1:] if isinstance (res [0], AST) else res

		if not text.strip:
			return (AST.VarNull, 0, [])

		self.parse_idx      = 0
		self.parse_best     = None # (sort keys ..., (reduction, erridx, autocomplete, rederr))
		self.autocomplete   = []
		self.autocompleting = True
		self.erridx         = None
		self.has_error      = False
		self.incomplete     = None # None or index of start of incomplete

		if os.environ.get ('SYMPAD_DEBUG'):
			print (file = sys.stderr)

		LALR1.parse (self, text)

		res = self.parse_best [-1] if self.parse_best is not None else (None, 0, [], None)

		if os.environ.get ('SYMPAD_DEBUG'):
			if self.parse_idx >= 33:
				print (f'\x08total {self.parse_idx}', file = sys.stderr)
			elif self.parse_best is None:
				print (f'no parse', file = sys.stderr)

			print (file = sys.stderr)

		return postprocess (res)

def set_sp_user_vars (user_vars):
	global _SP_USER_VARS
	_SP_USER_VARS = user_vars

def set_sp_user_funcs (user_funcs):
	global _SP_USER_FUNCS
	_SP_USER_FUNCS = user_funcs

class sparser: # for single script
	RESERVED_WORDS    = RESERVED_WORDS
	RESERVED_FUNCS    = RESERVED_FUNCS
	RESERVED_ALL      = RESERVED_ALL
	set_sp_user_vars  = set_sp_user_vars
	set_sp_user_funcs = set_sp_user_funcs
	Parser            = Parser

# Patch SymPy bugs and inconveniences.

from collections import defaultdict

#...............................................................................................
def _Complement__new__ (cls, a, b, evaluate = True): # sets.Complement patched to sympify args
	if evaluate:
		return Complement.reduce (sympify (a), sympify (b))

	return Basic.__new__ (cls, a, b)

#...............................................................................................
# matrix multiplication itermediate simplification routines

def _dotprodsimp(expr, withsimp=False):
	def count_ops_alg(expr):
		ops  = 0
		args = [expr]

		while args:
			a = args.pop()

			if not isinstance(a, Basic):
				continue

			if a.is_Rational:
				if a is not S.One: # -1/3 = NEG + DIV
					ops += bool (a.p < 0) + bool (a.q != 1)

			elif a.is_Mul:
				if _coeff_isneg(a):
					ops += 1
					if a.args[0] is S.NegativeOne:
						a = a.as_two_terms()[1]
					else:
						a = -a

				n, d = fraction(a)

				if n.is_Integer:
					ops += 1 + bool (n < 0)
					args.append(d) # won't be -Mul but could be Add

				elif d is not S.One:
					if not d.is_Integer:
						args.append(d)
					ops += 1
					args.append(n) # could be -Mul

				else:
					ops += len(a.args) - 1
					args.extend(a.args)

			elif a.is_Add:
				laargs = len(a.args)
				negs   = 0

				for ai in a.args:
					if _coeff_isneg(ai):
						negs += 1
						ai	= -ai
					args.append(ai)

				ops += laargs - (negs != laargs) # -x - y = NEG + SUB

			elif a.is_Pow:
				ops += 1
				args.append(a.base)

		return ops

	def nonalg_subs_dummies(expr, dummies):
		if not expr.args:
			return expr

		if expr.is_Add or expr.is_Mul or expr.is_Pow:
			args = None

			for i, a in enumerate(expr.args):
				c = nonalg_subs_dummies(a, dummies)

				if c is a:
					continue

				if args is None:
					args = list(expr.args)

				args[i] = c

			if args is None:
				return expr

			return expr.func(*args)

		return dummies.setdefault(expr, Dummy())

	simplified = False # doesn't really mean simplified, rather "can simplify again"

	if isinstance(expr, Basic) and (expr.is_Add or expr.is_Mul or expr.is_Pow):
		expr2 = expr.expand(deep=True, modulus=None, power_base=False,
			power_exp=False, mul=True, log=False, multinomial=True, basic=False)

		if expr2 != expr:
			expr	   = expr2
			simplified = True

		exprops = count_ops_alg(expr)

		if exprops >= 6: # empirically tested cutoff for expensive simplification
			dummies = {}
			expr2   = nonalg_subs_dummies(expr, dummies)

			if expr2 is expr or count_ops_alg(expr2) >= 6: # check again after substitution
				expr3 = cancel(expr2)

				if expr3 != expr2:
					expr	   = expr3.subs([(d, e) for e, d in dummies.items()])
					simplified = True

		# very special case: x/(x-1) - 1/(x-1) -> 1
		elif (exprops == 5 and expr.is_Add and expr.args [0].is_Mul and
				expr.args [1].is_Mul and expr.args [0].args [-1].is_Pow and
				expr.args [1].args [-1].is_Pow and
				expr.args [0].args [-1].exp is S.NegativeOne and
				expr.args [1].args [-1].exp is S.NegativeOne):

			expr2	= together (expr)
			expr2ops = count_ops_alg(expr2)

			if expr2ops < exprops:
				expr	   = expr2
				simplified = True

		else:
			simplified = True

	return (expr, simplified) if withsimp else expr

def _MatrixArithmetic__mul__(self, other):
	other = _matrixify(other)
	# matrix-like objects can have shapes.  This is
	# our first sanity check.
	if hasattr(other, 'shape') and len(other.shape) == 2:
		if self.shape[1] != other.shape[0]:
			raise ShapeError("Matrix size mismatch: %s * %s." % (
				self.shape, other.shape))

	# honest sympy matrices defer to their class's routine
	if getattr(other, 'is_Matrix', False):
		m = self._eval_matrix_mul(other)
		return m.applyfunc(_dotprodsimp)

	# Matrix-like objects can be passed to CommonMatrix routines directly.
	if getattr(other, 'is_MatrixLike', False):
		return MatrixArithmetic._eval_matrix_mul(self, other)

	# if 'other' is not iterable then scalar multiplication.
	if not isinstance(other, Iterable):
		try:
			return self._eval_scalar_mul(other)
		except TypeError:
			pass

	raise NotImplementedError()

def _MatrixArithmetic_eval_pow_by_recursion(self, num, prevsimp=None):
	if prevsimp is None:
		prevsimp = [True]*len(self)

	if num == 1:
		return self

	if num % 2 == 1:
		a, b = self, self._eval_pow_by_recursion(num - 1, prevsimp=prevsimp)
	else:
		a = b = self._eval_pow_by_recursion(num // 2, prevsimp=prevsimp)

	m     = a.multiply(b)
	lenm  = len(m)
	elems = [None]*lenm

	for i in range(lenm):
		if prevsimp[i]:
			elems[i], prevsimp[i] = _dotprodsimp(m[i], withsimp=True)
		else:
			elems[i] = m[i]

	return m._new(m.rows, m.cols, elems)

def _MatrixReductions_row_reduce(self, iszerofunc, simpfunc, normalize_last=True,
				normalize=True, zero_above=True):
	def get_col(i):
		return mat[i::cols]

	def row_swap(i, j):
		mat[i*cols:(i + 1)*cols], mat[j*cols:(j + 1)*cols] = \
			mat[j*cols:(j + 1)*cols], mat[i*cols:(i + 1)*cols]

	def cross_cancel(a, i, b, j):
		"""Does the row op row[i] = a*row[i] - b*row[j]"""
		q = (j - i)*cols
		for p in range(i*cols, (i + 1)*cols):
			mat[p] = _dotprodsimp(a*mat[p] - b*mat[p + q])

	rows, cols = self.rows, self.cols
	mat = list(self)
	piv_row, piv_col = 0, 0
	pivot_cols = []
	swaps = []

	# use a fraction free method to zero above and below each pivot
	while piv_col < cols and piv_row < rows:
		pivot_offset, pivot_val, \
		_, newly_determined = _find_reasonable_pivot(
			get_col(piv_col)[piv_row:], iszerofunc, simpfunc)

		# _find_reasonable_pivot may have simplified some things
		# in the process.  Let's not let them go to waste
		for (offset, val) in newly_determined:
			offset += piv_row
			mat[offset*cols + piv_col] = val

		if pivot_offset is None:
			piv_col += 1
			continue

		pivot_cols.append(piv_col)
		if pivot_offset != 0:
			row_swap(piv_row, pivot_offset + piv_row)
			swaps.append((piv_row, pivot_offset + piv_row))

		# if we aren't normalizing last, we normalize
		# before we zero the other rows
		if normalize_last is False:
			i, j = piv_row, piv_col
			mat[i*cols + j] = self.one
			for p in range(i*cols + j + 1, (i + 1)*cols):
				mat[p] = _dotprodsimp(mat[p] / pivot_val)
			# after normalizing, the pivot value is 1
			pivot_val = self.one

		# zero above and below the pivot
		for row in range(rows):
			# don't zero our current row
			if row == piv_row:
				continue
			# don't zero above the pivot unless we're told.
			if zero_above is False and row < piv_row:
				continue
			# if we're already a zero, don't do anything
			val = mat[row*cols + piv_col]
			if iszerofunc(val):
				continue

			cross_cancel(pivot_val, row, val, piv_row)
		piv_row += 1

	# normalize each row
	if normalize_last is True and normalize is True:
		for piv_i, piv_j in enumerate(pivot_cols):
			pivot_val = mat[piv_i*cols + piv_j]
			mat[piv_i*cols + piv_j] = self.one
			for p in range(piv_i*cols + piv_j + 1, (piv_i + 1)*cols):
				mat[p] = _dotprodsimp(mat[p] / pivot_val)

	return self._new(self.rows, self.cols, mat), tuple(pivot_cols), tuple(swaps)

#...............................................................................................
SPATCHED = False

try: # try to patch and fail silently if sympy has changed too much since this was written
	from sympy import sympify, S, cancel, together, Basic, Complement, boolalg, Dummy
	from sympy.core.compatibility import Iterable
	from sympy.core.function import _coeff_isneg
	from sympy.matrices.common import MatrixArithmetic, ShapeError, _matrixify, classof
	from sympy.matrices.matrices import MatrixReductions, _find_reasonable_pivot
	from sympy.matrices.dense import DenseMatrix
	from sympy.matrices.sparse import SparseMatrix
	from sympy.simplify.radsimp import fraction

	Complement.__new__ = _Complement__new__ # sets.Complement sympify args fix

	# enable math on booleans
	boolalg.BooleanTrue.__int__       = lambda self: 1
	boolalg.BooleanTrue.__float__     = lambda self: 1.0
	boolalg.BooleanTrue.__complex__   = lambda self: 1+0j
	boolalg.BooleanTrue.as_coeff_Add  = lambda self, *a, **kw: (S.Zero, S.One)
	boolalg.BooleanTrue.as_coeff_Mul  = lambda self, *a, **kw: (S.One, S.One)
	boolalg.BooleanTrue._eval_evalf   = lambda self, *a, **kw: S.One

	boolalg.BooleanFalse.__int__      = lambda self: 0
	boolalg.BooleanFalse.__float__    = lambda self: 0.0
	boolalg.BooleanFalse.__complex__  = lambda self: 0j
	boolalg.BooleanFalse.as_coeff_Mul = lambda self, *a, **kw: (S.Zero, S.Zero)
	boolalg.BooleanFalse.as_coeff_Add = lambda self, *a, **kw: (S.Zero, S.Zero)
	boolalg.BooleanFalse._eval_evalf  = lambda self, *a, **kw: S.Zero

	boolalg.BooleanAtom.__add__       = lambda self, other: self.__int__ () + other
	boolalg.BooleanAtom.__radd__      = lambda self, other: other + self.__int__ ()
	boolalg.BooleanAtom.__sub__       = lambda self, other: self.__int__ () - other
	boolalg.BooleanAtom.__rsub__      = lambda self, other: other - self.__int__ ()
	boolalg.BooleanAtom.__mul__       = lambda self, other: self.__int__ () * other
	boolalg.BooleanAtom.__rmul__      = lambda self, other: other * self.__int__ ()
	boolalg.BooleanAtom.__pow__       = lambda self, other: self.__int__ () ** other
	boolalg.BooleanAtom.__rpow__      = lambda self, other: other ** self.__int__ ()
	boolalg.BooleanAtom.__div__       = lambda self, other: self.__int__ () / other
	boolalg.BooleanAtom.__rdiv__      = lambda self, other: other / self.__int__ ()
	boolalg.BooleanAtom.__truediv__   = lambda self, other: self.__int__ () / other
	boolalg.BooleanAtom.__rtruediv__  = lambda self, other: other / self.__int__ ()
	boolalg.BooleanAtom.__floordiv__  = lambda self, other: self.__int__ ()          // other
	boolalg.BooleanAtom.__rfloordiv__ = lambda self, other: other                    // self.__int__ ()
	boolalg.BooleanAtom.__mod__       = lambda self, other: self.__int__ () % other
	boolalg.BooleanAtom.__rmod__      = lambda self, other: other % self.__int__ ()

	# itermediate matrix multiplication simplification
	_SYMPY_MatrixArithmetic__mul__                = MatrixArithmetic.__mul__
	_SYMPY_MatrixArithmetic_eval_pow_by_recursion = MatrixArithmetic._eval_pow_by_recursion
	_SYMPY_MatrixReductions_row_reduce            = MatrixReductions._row_reduce
	MatrixArithmetic.__mul__                      = _MatrixArithmetic__mul__
	MatrixArithmetic._eval_pow_by_recursion       = _MatrixArithmetic_eval_pow_by_recursion
	MatrixReductions._row_reduce                  = _MatrixReductions_row_reduce

	SPATCHED = True

except:
	pass

def set_matmulsimp (state):
	if SPATCHED:
		idx                                     = bool (state)
		MatrixArithmetic.__mul__                = (_SYMPY_MatrixArithmetic__mul__, _MatrixArithmetic__mul__) [idx]
		MatrixArithmetic._eval_pow_by_recursion = (_SYMPY_MatrixArithmetic_eval_pow_by_recursion, _MatrixArithmetic_eval_pow_by_recursion) [idx]
		MatrixReductions._row_reduce            = (_SYMPY_MatrixReductions_row_reduce, _MatrixReductions_row_reduce) [idx]

class spatch: # for single script
	SPATCHED       = SPATCHED
	set_matmulsimp = set_matmulsimp
# Plot functions and expressions to image using matplotlib.

import base64
from io import BytesIO
import itertools as it
import math

import sympy as sp

_SPLOT = False

try:
	import matplotlib
	import matplotlib.pyplot as plt

	matplotlib.style.use ('bmh') # ('seaborn') # ('classic') # ('fivethirtyeight')

	_SPLOT       = True
	_FIGURE      = None
	_TRANSPARENT = True

except:
	pass

#...............................................................................................
def _cast_num (arg):
	try:
		return float (arg)
	except:
		return None

def _process_head (obj, args, fs, style = None, ret_xrng = False, ret_yrng = False, kw = {}):
	global _FIGURE, _TRANSPARENT

	if style is not None:
		if style [:1] == '-':
			style, _TRANSPARENT = style [1:], True
		else:
			_TRANSPARENT = False

		matplotlib.style.use (style)

	args = list (reversed (args))

	if args and args [-1] == '+': # continuing plot on previous figure?
		args.pop ()

	elif _FIGURE:
		plt.close (_FIGURE)

		_FIGURE = None

	if not _FIGURE:
		_FIGURE = plt.figure ()

	if fs is not None: # process figsize if present
		if isinstance (fs, (sp.Tuple, tuple)):
			fs = (_cast_num (fs [0]), _cast_num (fs [1]))

		else:
			fs = _cast_num (fs)

			if fs >= 0:
				fs = (fs, fs * 3 / 4)
			else:
				fs = (-fs, -fs)

		_FIGURE.set_figwidth (fs [0])
		_FIGURE.set_figheight (fs [1])

	xmax, ymin, ymax = None, None, None
	xmin             = _cast_num (args [-1]) if args else None

	if xmin is not None: # process xmin / xmax, ymin, ymax if present
		args = args [:-1]
		xmax = _cast_num (args [-1]) if args else None

		if xmax is not None:
			args = args [:-1]
			ymin = _cast_num (args [-1]) if args else None

			if ymin is not None:
				args = args [:-1]
				ymax = _cast_num (args [-1]) if args else None

				if ymax is not None:
					args = args [:-1]
				else:
					xmin, xmax, ymin, ymax = -xmin, xmin, xmax, ymin
		else:
			xmin, xmax = -xmin, xmin

	if xmin is not None:
		obj.xlim (xmin, xmax)
	elif ret_xrng:
		xmin, xmax = obj.xlim ()

	if ymin is not None:
		obj.ylim (ymin, ymax)
	elif ret_yrng:
		ymin, ymax = obj.ylim ()

	kw = dict ((k, # cast certain sympy objects which don't play nice with matplotlib using numpy
		int (v) if isinstance (v, sp.Integer) else
		float (v) if isinstance (v, (sp.Float, sp.Rational)) else
		v) for k, v in kw.items ())

	return args, xmin, xmax, ymin, ymax, kw

def _process_fmt (args, kw = {}):
	kw    = kw.copy ()
	fargs = []

	if args and isinstance (args [-1], str):
		fmt, lbl = (args.pop ().split ('=', 1) + [None]) [:2]
		fmt, clr = (fmt.split ('#', 1) + [None]) [:2]

		if lbl:
			kw ['label'] = lbl.strip ()

		if clr:
			clr = clr.strip ()

			if len (clr) == 6:
				try:
					_   = int (clr, 16)
					clr = f'#{clr}'
				except:
					pass

			kw ['color'] = clr

		fargs = [fmt.strip ()]

	if args and isinstance (args [-1], dict):
		kw.update (args.pop ())

	return args, fargs, kw

def _figure_to_image ():
	data = BytesIO ()

	_FIGURE.savefig (data, format = 'png', bbox_inches = 'tight', facecolor = 'none', edgecolor = 'none', transparent = _TRANSPARENT)

	return base64.b64encode (data.getvalue ()).decode ()

#...............................................................................................
def plotf (*args, fs = None, res = 12, style = None, **kw):
	"""Plot function(s), point(s) and / or line(s).

plotf ([+,] [limits,] *args, fs = None, res = 12, **kw)

limits  = set absolute axis bounds: (default x is (0, 1), y is automatic)
  x              -> (-x, x, y auto)
  x0, x1         -> (x0, x1, y auto)
  x, y0, y1      -> (-x, x, y0, y1)
  x0, x1, y0, y1 -> (x0, x1, y0, y1)

fs      = set figure figsize if present: (default is (6.4, 4.8))
  x      -> (x, x * 3 / 4)
  -x     -> (x, x)
  (x, y) -> (x, y)

res     = minimum target resolution points per 50 x pixels (more or less 1 figsize x unit),
          may be raised a little to align with grid
style   = optional matplotlib plot style

*args   = functions and their formatting: (func, ['fmt',] [{kw},] func, ['fmt',] [{kw},] ...)
  func                      -> callable function takes x and returns y
	(x, y)                    -> point at x, y
	(x0, y0, x1, y1, ...)     -> connected lines from x0, y1 to x1, y1 to etc...
	((x0, y0), (x1, y1), ...) -> same thing

	fmt                       = 'fmt[#color][=label]'
	"""

	if not _SPLOT:
		return None

	obj    = plt
	legend = False

	args, xmin, xmax, ymin, ymax, kw = _process_head (obj, args, fs, style, ret_xrng = True, kw = kw)

	while args:
		arg = args.pop ()

		if isinstance (arg, (sp.Tuple, tuple, list)): # list of x, y coords
			if isinstance (arg [0], (sp.Tuple, tuple, list)):
				arg = list (it.chain.from_iterable (arg))

			pargs = [arg [0::2], arg [1::2]]

		else: # y = function (x)
			if not callable (arg):
				if len (arg.free_symbols) != 1:
					raise ValueError ('expression must have exactly one free variable')

				arg = sp.Lambda (arg.free_symbols.pop (), arg)

			win = _FIGURE.axes [-1].get_window_extent ()
			xrs = (win.x1 - win.x0) // 50 # scale resolution to roughly 'res' points every 50 pixels
			rng = res * xrs
			dx  = dx2 = xmax - xmin

			while dx2 < (res * xrs) / 2: # align sampling grid on integers and fractions of integers while rng stays small enough
				rng = int (rng + (dx2 - (rng % dx2)) % dx2)
				dx2 = dx2 * 2

			xs = [xmin + dx * i / rng for i in range (rng + 1)]
			ys = [None] * len (xs)

			for i in range (len (xs)):
				try:
					ys [i] = _cast_num (arg (xs [i]))
				except (ValueError, ZeroDivisionError, FloatingPointError):
					pass

			# remove lines crossing graph vertically due to poles (more or less)
			if ymin is not None:
				for i in range (1, len (xs)):
					if ys [i] is not None and ys [i-1] is not None:
						if ys [i] < ymin and ys [i-1] > ymax:
							ys [i] = None
						elif ys [i] > ymax and ys [i-1] < ymin:
							ys [i] = None

			pargs = [xs, ys]

		args, fargs, kwf = _process_fmt (args, kw)
		legend           = legend or ('label' in kwf)

		obj.plot (*(pargs + fargs), **kwf)

	if legend or 'label' in kw:
		obj.legend ()

	return _figure_to_image ()

#...............................................................................................
def __fxfy2fxy (f1, f2): # u = f1 (x, y), v = f2 (x, y) -> (u, v) = f' (x, y)
	return lambda x, y, f1 = f1, f2 = f2: (float (f1 (x, y)), float (f2 (x, y)))

def __fxy2fxy (f): # (u, v) = f (x, y) -> (u, v) = f' (x, y)
	return lambda x, y, f = f: tuple (float (v) for v in f (x, y))

def __fdy2fxy (f): # v/u = f (x, y) -> (u, v) = f' (x, y)
	return lambda x, y, f = f: tuple ((math.cos (t), math.sin (t)) for t in (math.atan2 (f (x, y), 1),)) [0]

def _process_funcxy (args, testx, testy):
	isdy = False
	f    = args.pop ()

	if isinstance (f, (sp.Tuple, tuple, list)): # if (f1 (x, y), f2 (x, y)) functions or expressions present in args they are individual u and v functions
		c1, c2 = callable (f [0]), callable (f [1])

		if c1 and c2: # two Lambdas
			f = __fxfy2fxy (f [0], f [1])

		elif not (c1 or c2): # two expressions
			vars = tuple (sorted (sp.Tuple (f [0], f [1]).free_symbols, key = lambda s: s.name))

			if len (vars) != 2:
				raise ValueError ('expression must have exactly two free variables')

			return args, __fxfy2fxy (sp.Lambda (vars, f [0]), sp.Lambda (vars, f [1])), False

		else:
			raise ValueError ('field must be specified by two lambdas or two expressions, not a mix')

	# one function or expression
	if not callable (f): # convert expression to function
		if len (f.free_symbols) != 2:
			raise ValueError ('expression must have exactly two free variables')

		f = sp.Lambda (tuple (sorted (f.free_symbols, key = lambda s: s.name)), f)

	for y in testy: # check if returns 1 dy or 2 u and v values
		for x in testx:
			try:
				v = f (x, y)
			except (ValueError, ZeroDivisionError, FloatingPointError):
				continue

			try:
				_, _ = v
				f    = __fxy2fxy (f)

				break

			except:
				f    = __fdy2fxy (f)
				isdy = True

				break

		else:
			continue

		break

	return args, f, isdy

_plotv_clr_mag  = lambda x, y, u, v: math.sqrt (u**2 + v**2)
_plotv_clr_dir  = lambda x, y, u, v: math.atan2 (v, u)

_plotv_clr_func = {'mag': _plotv_clr_mag, 'dir': _plotv_clr_dir}

#...............................................................................................
def plotv (*args, fs = None, res = 13, style = None, resw = 1, kww = {}, **kw):
	"""Plot vector field.

plotv (['+',] [limits,] func(s), [color,] [fmt,] [*walks,] fs = None, res = 13, style = None, resw = 1, kww = {}, **kw)

limits  = set absolute axis bounds: (default x is (0, 1), y is automatic)
  x              -> (-x, x, y auto)
  x0, x1         -> (x0, x1, y auto)
  x, y0, y1      -> (-x, x, y0, y1)
  x0, x1, y0, y1 -> (x0, x1, y0, y1)

fs      = set figure figsize if present: (default is (6.4, 4.8))
  x      -> (x, x / 6 * 4)
  -x     -> (x, x)
  (x, y) -> (x, y)

res     = (w, h) number of arrows across x and y dimensions, if single digit then h will be w*3/4
resw    = resolution for optional plotw, see plotw for meaning
kww     = optional keyword arguments to be passed to plotw if that is being called
style   = optional matplotlib plot style

func(s) = function or two functions or expressions returning either (u, v) or v/u
	f (x, y)               -> returning (u, v)
	f (x, y)               -> returning v/u will be interpreted without direction
	(f1 (x, y), f2 (x, y)) -> returning u and v respectively

color   = followed optionally by individual arrow color selection function (can not be expression)
	'mag'               -> color by magnitude of (u, v) vector
	'dir'               -> color by direction of (u, v) vector
  f (x, y, u, v)      -> relative scalar, will be scaled according to whole field to select color

fmt     = followed optionally by color and label format string '[#color][=label]'

*walks  = followed optionally by arguments to plotw for individual x, y walks and formatting
	"""

	if not _SPLOT:
		return None

	obj = plt

	args, xmin, xmax, ymin, ymax, kw = _process_head (obj, args, fs, style, ret_xrng = True, ret_yrng = True, kw = kw)

	if not isinstance (res, (sp.Tuple, tuple, list)):
		win = _FIGURE.axes [-1].get_window_extent ()
		res = (int (res), int ((win.y1 - win.y0) // ((win.x1 - win.x0) / (res + 1))))
	else:
		res = (int (res [0]), int (res [1]))

	xs = (xmax - xmin) / (res [0] + 1)
	ys = (ymax - ymin) / (res [1] + 1)
	x0 = xmin + xs / 2
	y0 = ymin + ys / 2
	xd = (xmax - xs / 2) - x0
	yd = (ymax - ys / 2) - y0
	X  = [[x0 + xd * i / (res [0] - 1)] * res [1] for i in range (res [0])]
	Y  = [y0 + yd * i / (res [1] - 1) for i in range (res [1])]
	Y  = [Y [:] for _ in range (res [0])]
	U  = [[0] * res [1] for _ in range (res [0])]
	V  = [[0] * res [1] for _ in range (res [0])]

	args, f, isdy = _process_funcxy (args, [x [0] for x in X], Y [0])

	if isdy:
		d, kw = kw, {'headwidth': 0, 'headlength': 0, 'headaxislength': 0, 'pivot': 'middle'}
		kw.update (d)

	# populate U and Vs from X, Y grid
	for j in range (res [1]):
		for i in range (res [0]):
			try:
				U [i] [j], V [i] [j] = f (X [i] [j], Y [i] [j])
			except (ValueError, ZeroDivisionError, FloatingPointError):
				U [i] [j] = V [i] [j] = 0

	clrf = None

	if args:
		if callable (args [-1]): # color function present? f (x, y, u, v)
			clrf = args.pop ()

		elif isinstance (args [-1], str): # pre-defined color function string?
			clrf = _plotv_clr_func.get (args [-1])

			if clrf:
				args.pop ()

	args, _, kw = _process_fmt (args, kw)

	if clrf:
		C = [[float (clrf (X [i] [j], Y [i] [j], U [i] [j], V [i] [j])) for j in range (res [1])] for i in range (res [0])]

		obj.quiver (X, Y, U, V, C, **kw)

	else:
		obj.quiver (X, Y, U, V, **kw)

	if 'label' in kw:
		obj.legend ()

	if args: # if arguments remain, pass them on to plotw to draw differential curves
		plotw (resw = resw, from_plotv = (args, xmin, xmax, ymin, ymax, f), **kww)

	return _figure_to_image ()

#...............................................................................................
def plotw (*args, fs = None, resw = 1, style = None, from_plotv = False, **kw):
	"""Plot walk(s) over vector field.

plotw (['+',] [limits,] func(s), *args, fs = None, resw = 1, style = None, **kw)

limits  = set absolute axis bounds: (default x is (0, 1), y is automatic)
  x              -> (-x, x, y auto)
  x0, x1         -> (x0, x1, y auto)
  x, y0, y1      -> (-x, x, y0, y1)
  x0, x1, y0, y1 -> (x0, x1, y0, y1)

fs      = set figure figsize if present: (default is (6.4, 4.8))
  x      -> (x, x / 6 * 4)
  -x     -> (x, x)
  (x, y) -> (x, y)

resw    = maximum pixel steps to allow walk step to deviate before drawing, smaller = better quality
style   = optional matplotlib plot style

func(s) = function or two functions returning either (u, v) or v/u
	f (x, y)            -> returning (u, v)
	f (x, y)            -> returning v/u will be interpreted without direction
	f (x, y), f2 (x, y) -> returning u and v respectively

*args   = followed by initial x, y points for walks (x, y, ['fmt',] [{kw},] x, y, ['fmt',] [{kw},] ...)
	fmt   = 'fmt[#color][=label]'

HACK: Python complex type used as 2D vector.
	"""

	def dot (p0, p1): # dot product of two 2d vectors stored as complexes
		return p0.real * p1.real + p0.imag * p1.imag

	def walk (x, y, f, o = 1): # returns [(x, y), (x, y), ...], True if looped else False
		def delta (p, d = None):
			try:
				t = math.atan2 (*(f (p.real, p.imag) [::-1]))

				return complex (math.cos (t), math.sin (t))

			except (ValueError, ZeroDivisionError, FloatingPointError):
				if d is not None:
					return d

			raise FloatingPointError

		xys = [(x, y)]
		err = 0
		p0  = complex (x, y)
		p   = p0
#		d   = pxs
		d   = delta (p, pxs)

		while 1:
#			d  = delta (p, d)
			s  = 0
			o2 = o
			p2 = p
			d2 = d

			while 1:
				st = 0.25 * pxm
				d3 = o2 * d2

				while 1:
					p3 = p2 + d3 * st # * pxm

					try:
						d4 = delta (p3)
						dc = math.acos (dot (d2, d4))

						if dc > 2.748893571891069: # (7 * pi / 8), abrupt reverse of direction?
							o2 = -o2

						elif dc > 0.005:
							st = st * (0.004 / dc)
							continue

						err = err + dc * st # * pxm
						d2  = d4

						break

					except FloatingPointError:
						break

				s      = s + st
				isloop = (dot (d3, p0 - p2) > 0) and abs (p3 - p0) < (2 * err) # (8 * pxm)
				p2     = p3

				if isloop or p2.real < xmin or p2.real > xmax or p2.imag < ymin or p2.imag > ymax:
					xys.extend ([(p2.real, p2.imag)] + [(x, y)] * bool (isloop))
					return xys, isloop

				if abs (p2 - (p + o * d * s)) >= resw: # * pxm)) >= resw:
					xys.append ((p2.real, p2.imag))

					o = o2
					p = p2
					d = d2

					break

	if not _SPLOT:
		return None

	obj = plt

	if from_plotv:
		args, xmin, xmax, ymin, ymax, f  = from_plotv
	else:
		args, xmin, xmax, ymin, ymax, kw = _process_head (obj, args, fs, style, ret_xrng = True, ret_yrng = True, kw = kw)
		args, f, _                       = _process_funcxy (args, [xmin + (xmax - xmin) * i / 4 for i in range (5)], [ymin + (ymax - ymin) * i / 4 for i in range (5)])

	win  = _FIGURE.axes [-1].get_window_extent ()
	pxs  = complex ((xmax - xmin) / (win.x1 - win.x0), (ymax - ymin) / (win.y1 - win.y0)) # pixel scale from xmin/max ymin/max scale
	pxm  = abs (pxs)
	resw = resw * pxm

	leg = False

	while args:
		x, y        = args.pop ()
		xys, isloop = walk (x, y, f)

		if not isloop:
			xys = xys [::-1] [:-1] + walk (x, y, f, -1) [0]

		args, fargs, kwf = _process_fmt (args, kw)
		leg              = leg or ('label' in kwf)

		obj.plot (*([[xy [0] for xy in xys], [xy [1] for xy in xys]] + fargs), **kwf)

	if leg or 'label' in kw:
		obj.legend ()

	return _figure_to_image ()

#...............................................................................................
class splot: # for single script
	plotf = plotf
	plotv = plotv
	plotw = plotw
#!/usr/bin/env python3
# python 3.6+

# Server for web component and state machine for expressions.

import getopt
import io
import json
import os
import re
import subprocess
import sys
import time
import threading
import traceback
import webbrowser

from collections import OrderedDict
from http.server import HTTPServer, SimpleHTTPRequestHandler
from socketserver import ThreadingMixIn
from urllib.parse import parse_qs


_VERSION         = '1.1.6'

_ONE_FUNCS       = {'N', 'O', 'S', 'beta', 'gamma', 'Gamma', 'Lambda', 'zeta'}
_ENV_OPTS        = {'EI', 'quick', 'pyS', 'simplify', 'matsimp', 'ufuncmap', 'prodrat', 'doit', 'strict', *_ONE_FUNCS}
_ENV_OPTS_ALL    = _ENV_OPTS.union (f'no{opt}' for opt in _ENV_OPTS)

__OPTS, __ARGV   = getopt.getopt (sys.argv [1:], 'hvnudr', ['child', 'firstrun', 'help', 'version', 'nobrowser', 'ugly', 'debug', 'restert', *_ENV_OPTS_ALL])
__IS_MAIN        = __name__ == '__main__'
__IS_MODULE_RUN  = sys.argv [0] == '-m'

_SERVER_DEBUG    = __IS_MAIN and __ARGV and __ARGV [0] == 'server-debug'

_SYMPAD_PATH     = os.path.dirname (sys.argv [0])
_SYMPAD_NAME     = os.path.basename (sys.argv [0])
_SYMPAD_RESTART  = not __IS_MODULE_RUN and (('-r', '') in __OPTS or ('--restart', '') in __OPTS)
_SYMPAD_CHILD    = not _SYMPAD_RESTART or ('--child', '') in __OPTS
_SYMPAD_FIRSTRUN = not _SYMPAD_RESTART or ('--firstrun', '') in __OPTS
_SYMPAD_DEBUG    = os.environ.get ('SYMPAD_DEBUG')

_DEFAULT_ADDRESS = ('localhost', 9000)
_STATIC_FILES    = {'/style.css': 'text/css', '/script.js': 'text/javascript', '/index.html': 'text/html',
	'/help.html': 'text/html', '/bg.png': 'image/png', '/wait.webp': 'image/webp'}

_HELP            = f'usage: sympad [options] [host:port | host | :port]' '''

  -h, --help               - Show help information
  -v, --version            - Show version string
  -n, --nobrowser          - Don't start system browser to SymPad page
  -u, --ugly               - Start in draft display style (only on command line)
  -d, --debug              - Dump debug info to server log
  -r, --restart            - Restart server on source file changes (for development)
  --EI, --noEI             - Start with SymPy constants 'E' and 'I' or regular 'e' and 'i'
  --quick, --noquick       - Start in/not quick input mode
  --pyS, --nopyS           - Start with/out Python S escaping
  --simplify, --nosimplify - Start with/out post-evaluation simplification
  --matsimp, --nomatsimp   - Start with/out matrix simplification
  --ufuncmap, --noufuncmap - Start with/out undefined function mapping back to variables
  --prodrat, --noprodrat   - Start with/out separate product leading rational
  --doit, --nodoit         - Start with/out automatic expression doit()
  --strict, --nostrict     - Start with/out strict LaTeX formatting
  --N, --noN               - Start with/out N function
  --S, --noS               - Start with/out S function
  --O, --noO               - Start with/out O function
  --beta, --nobeta         - Start with/out beta function
  --gamma, --nogamma       - Start with/out gamma function
  --Gamma, --noGamma       - Start with/out Gamma function
  --Lambda, --noLambda     - Start with/out Lambda function
  --zeta, --nozeta         - Start with/out zeta function
'''.lstrip ()

if _SYMPAD_CHILD: # sympy slow to import so don't do it for watcher process as is unnecessary there

	import sympy as sp

	_SYS_STDOUT    = sys.stdout
	_DISPLAYSTYLE  = [1] # use "\displaystyle{}" formatting in MathJax
	_HISTORY       = []  # persistent history across browser closings

	_UFUNC_MAPBACK = True # map undefined functions from SymPy back to variables if possible
	_UFUNC_MAP     = {} # map of ufunc asts to ordered sequence of variable names
	_SYM_MAP       = {} # map of sym asts to ordered sequence of variable names
	_SYM_VARS      = set () # set of all variables mapped to symbols

	_PARSER        = sparser.Parser ()
	_START_ENV     = OrderedDict ([
		('EI', False), ('quick', False), ('pyS', True), ('simplify', False), ('matsimp', True), ('ufuncmap', True), ('prodrat', False), ('doit', True), ('strict', False),
		('N', True), ('O', True), ('S', True), ('beta', True), ('gamma', True), ('Gamma', True), ('Lambda', True), ('zeta', True)])

	_ENV           = _START_ENV.copy () # This is individual session STATE! Threading can corrupt this! It is GLOBAL to survive multiple Handlers.
	_VARS          = {'_': AST.Zero} # This also!
	_VARS_FLAT     = _VARS.copy () # Flattened vars.

#...............................................................................................
def _admin_vars (*args):
	asts = _sorted_vars ()

	if not asts:
		return 'No variables defined.'

	return asts

def _admin_del (*args):
	vars = OrderedDict ()
	msgs = []

	for arg in args:
		var = arg.as_identifier

		if var is None or var == '_':
			raise TypeError (f'invalid argument {sym.ast2nat (arg)!r}')

		vars [var] = _VARS.get (var)

		if vars [var] is None:
			raise AE35UnitError (f'Variable {var!r} is not defined, it can only be attributable to human error.')

	for var, ast in vars.items ():
		msgs.append (f'{"Lambda function" if ast.is_lamb else "Undefined function" if ast.is_ufunc else "Variable"} {var!r} deleted.')

		del _VARS [var]

	_vars_updated ()

	if not msgs:
		msgs.append ('No variables specified!')

	return msgs

def _admin_delall (*args):
	last_var    = _VARS ['_']

	_VARS.clear ()

	_VARS ['_'] = last_var

	_vars_updated ()

	return 'All variables deleted.'

def _admin_env (*args):
	vars_updated = False

	def _envop (env, apply):
		nonlocal vars_updated

		msgs = []

		for var, state in env.items ():
			if apply:
				_ENV [var] = state

			if var == 'EI':
				msgs.append (f'Uppercase E and I is {"on" if state else "off"}.')

				if apply:
					AST.EI (state)

					for var in (AST.E.var, AST.I.var):
						if var in _VARS:
							del _VARS [var]

			elif var == 'quick':
				msgs.append (f'Quick input mode is {"on" if state else "off"}.')

				if apply:
					sym.set_quick (state)
					_PARSER.set_quick (state)

					vars_updated = True

			elif var == 'pyS':
				msgs.append (f'Python S escaping is {"on" if state else "off"}.')

				if apply:
					sym.set_pyS (state)

			elif var == 'simplify':
				msgs.append (f'Post-evaluation simplify is {"on" if state else "off"}.')

				if apply:
					sym.set_simplify (state)

			elif var == 'matsimp':
				msgs.append (f'Matrix simplify is {"broken" if not spatch.SPATCHED else "on" if state else "off"}.')

				if apply:
					spatch.set_matmulsimp (state)

			elif var == 'ufuncmap':
				msgs.append (f'Undefined function map to variable is {"on" if state else "off"}.')

				if apply:
					global _UFUNC_MAPBACK
					_UFUNC_MAPBACK = state

			elif var == 'prodrat':
				msgs.append (f'Leading product rational is {"on" if state else "off"}.')

				if apply:
					sym.set_prodrat (state)

			elif var == 'doit':
				msgs.append (f'Expression doit is {"on" if state else "off"}.')

				if apply:
					sym.set_doit (state)

			elif var == 'strict':
				msgs.append (f'Strict LaTeX formatting is {"on" if state else "off"}.')

				if apply:
					sym.set_strict (state)

			elif var in _ONE_FUNCS:
				msgs.append (f'Function {var} is {"on" if state else "off"}.')

				if apply:
					vars_updated = True

		return msgs

	# start here
	if not args:
		return _envop (_ENV, False)

	env = OrderedDict ()

	for arg in args:
		if arg.is_ass:
			var = arg.lhs.as_identifier

			if var:
				state = bool (sym.ast2spt (arg.rhs))

		else:
			var = arg.as_identifier

			if var:
				if var [:2] == 'no':
					var, state = var [2:], False
				else:
					state = True

		if var is None:
			raise TypeError (f'invalid argument {sym.ast2nat (arg)!r}')
		elif var not in _ENV_OPTS:
			raise NameError (f'invalid environment setting {var!r}')

		env [var] = state

	ret = _envop (env, True)

	if vars_updated:
		_vars_updated ()

	return ret

def _admin_envreset (*args):
	return ['Environment has been reset.'] + _admin_env (*(AST ('@', var if state else f'no{var}') for var, state in _START_ENV.items ()))

#...............................................................................................
class RealityRedefinitionError (NameError):	pass
class CircularReferenceError (RecursionError): pass
class AE35UnitError (Exception): pass

def _mapback (ast, assvar = None, exclude = set ()): # map back ufuncs and symbols to the variables they are assigned to if possible
	if not isinstance (ast, AST):
		return ast

	if ast.is_var:
		if ast.var not in _SYM_VARS:
			return ast

		if ast.var == assvar:
			raise CircularReferenceError ('trying to assign unqualified symbol to variable of the same name')

		return AST ('-sym', ast.var)

	if ast.is_sym:
		vars = _SYM_MAP.get (ast)

		if not vars:
			return ast

		if ast.sym in vars:
			return AST ('@', ast.sym)

		return AST ('@', next (iter (vars)))

	if _UFUNC_MAPBACK:
		if ast.is_ass and ast.lhs.is_ufunc:
			return AST ('=', ast.lhs, _mapback (ast.rhs, assvar, exclude))
		elif not ast.is_ufunc:
			return AST (*(_mapback (a, assvar, exclude) for a in ast))

		vars = _UFUNC_MAP.get (ast)

		if vars: # prevent mapping to self on assignment
			if ast.ufunc in vars and ast.ufunc not in exclude:
				return AST ('@', ast.ufunc)

			for var in vars:
				if var not in exclude:
					return AST ('@', var)

	return AST (*(_mapback (a, assvar, exclude) for a in ast))

def _present_vars (vars):
	asts = []

	for v, e in vars:
		if v != '_':
			if e.is_lamb:
				asts.append (AST ('=', ('-ufunc', v, tuple (('@', vv) for vv in e.vars)), e.lamb))
			else:
				asts.append (AST ('=', ('@', v), e))

	return asts

def _sorted_vars ():
	return _present_vars (sorted (_VARS.items (), key = lambda kv: (kv [1].op not in {'-lamb', '-ufunc'}, kv [0])))

def _vars_updated ():
	global _VARS_FLAT

	vars    = {v: a if a.is_lamb else AST.apply_vars (a, _VARS, mode = False) for v, a in _VARS.items ()} # flattened vars so sym and sparser don't need to do apply_vars()
	one     = (f for f in filter (lambda f: _ENV.get (f), _ONE_FUNCS)) # hidden functions for stuff like Gamma
	lamb    = (va [0] for va in filter (lambda va: va [1].is_lamb, vars.items ())) # user lambda functions
	assfunc = (va [0] for va in filter (lambda va: va [1].is_var and va [1].var in AST.Func.PYBASE, vars.items ())) # user variables assigned to concrete functions
	funcs   = {*one, *lamb, *assfunc}

	sym.set_sym_user_vars (vars)
	sym.set_sym_user_funcs (funcs)
	sparser.set_sp_user_vars (vars)
	sparser.set_sp_user_funcs (funcs)

	_UFUNC_MAP.clear ()
	_SYM_MAP.clear ()
	_SYM_VARS.clear ()

	_VARS_FLAT = vars

	for v, a in vars.items (): # build ufunc and sym mapback dict
		if v != '_':
			if a.is_ufunc:
				_UFUNC_MAP.setdefault (a, set ()).add (v)

			elif a.is_sym:
				_SYM_MAP.setdefault (a, set ()).add (v)
				_SYM_VARS.add (v)

def _prepare_ass (ast): # check and prepare for simple or tuple assignment
	if not ast.ass_valid:
		vars = None
	elif ast.ass_valid.error:
		raise RealityRedefinitionError (ast.ass_valid.error)

	else:
		vars, ast = ast.ass_valid.lhs, ast.ass_valid.rhs
		vars      = list (vars.comma) if vars.is_comma else [vars]

	return AST.apply_vars (ast, _VARS_FLAT), vars

def _execute_ass (ast, vars): # execute assignment if it was detected
	def set_vars (vars):
		nvars = {}

		for v, a in vars.items ():
			v = v.var

			if a.is_ufunc:
				if v in sparser.RESERVED_FUNCS:
					raise NameError (f'cannot assign undefined function to concrete function name {v!r}')

				if a.is_ufunc_anonymous:
					a = AST (a.op, v, *a [2:])

			elif a.is_sym_anonymous:
				if a.is_sym_unqualified:
					raise CircularReferenceError ('cannot asign unqualified anonymous symbol')

				a = AST (a.op, v, *a [2:])

			nvars [v] = a

		try: # check for circular references
			AST.apply_vars (AST (',', tuple (('@', v) for v in nvars)), {**_VARS, **nvars})
		except RecursionError:
			raise CircularReferenceError ("I'm sorry, Dave. I'm afraid I can't do that.") from None

		_VARS.update (nvars)

		return list (nvars.items ())

	# start here
	if not vars: # no assignment
		if not ast.is_ufunc:
			ast = _mapback (ast)

		_VARS ['_'] = ast

		_vars_updated ()

		return [ast]

	if len (vars) == 1: # simple assignment
		if ast.op not in {'-ufunc', '-sym'}:
			ast = _mapback (ast, vars [0].var, {vars [0].var})

		vars = set_vars ({vars [0]: ast})

	else: # tuple assignment
		ast = ast.strip_paren

		if ast.op in {',', '[', '-set'}:
			asts = ast [1]

		else:
			asts = []
			itr  = iter (sym.ast2spt (ast))

			for i in range (len (vars) + 1):
				try:
					ast = sym.spt2ast (next (itr))
				except StopIteration:
					break

				if vars [i].is_ufunc_named:
					asts.append (AST.Ass.ufunc2lamb (vars [i], ast))

					vars [i] = AST ('@', vars [i].ufunc)

				else:
					asts.append (ast)

		if len (vars) < len (asts):
			raise ValueError (f'too many values to unpack (expected {len (vars)})')
		elif len (vars) > len (asts):
			raise ValueError (f'not enough values to unpack (expected {len (vars)}, got {len (asts)})')

		vasts   = list (zip (vars, asts))
		exclude = set (va [0].var for va in filter (lambda va: va [1].is_ufunc, vasts))
		asts    = [a if a.op in {'-ufunc', '-sym'} else _mapback (a, v.var, exclude) for v, a in vasts]
		vars    = set_vars (dict (zip (vars, asts)))

	_vars_updated ()

	return _present_vars (vars)

#...............................................................................................
class Handler (SimpleHTTPRequestHandler):
	def vars (self, request):
		asts = _sorted_vars ()

		return {'vars': [{
			'tex': sym.ast2tex (ast),
			'nat': sym.ast2nat (ast),
			'py' : sym.ast2py (ast),
			} for ast in asts]}

	def validate (self, request):
		ast, erridx, autocomplete, error = _PARSER.parse (request ['text'])
		tex = nat = py                   = None

		if ast is not None:
			tex, xlattex = sym.ast2tex (ast, retxlat = True)
			nat, xlatnat = sym.ast2nat (ast, retxlat = True)
			py, xlatpy  = sym.ast2py (ast, retxlat = True)

			if _SYMPAD_DEBUG:
				print ('free:', list (v.var for v in ast.free_vars), file = sys.stderr)
				print ('ast: ', ast, file = sys.stderr)

				if xlattex:
					print ('astt:', repr (xlattex), file = sys.stderr)

				if xlatnat:
					print ('astn:', repr (xlatnat), file = sys.stderr)

				if xlatpy:
					print ('astp:', repr (xlatpy), file = sys.stderr)

				print ('tex: ', tex, file = sys.stderr)
				print ('nat: ', nat, file = sys.stderr)
				print ('py:  ', py, file = sys.stderr)
				print (file = sys.stderr)

		if isinstance (error, Exception):
			error = (f'{error.__class__.__name__}: ' if not isinstance (error, SyntaxError) else '') + error.args [0].replace ('\n', ' ').strip ()

		return {
			'tex'         : tex,
			'nat'         : nat,
			'py'          : py,
			'erridx'      : erridx,
			'autocomplete': autocomplete,
			'error'       : error,
		}

	def evaluate (self, request):
		def evalexpr (ast):
			sym.ast2spt.set_precision (ast)

			if ast.is_func and ast.func in AST.Func.PLOT: # plotting?
				args, kw = AST.args2kwargs (AST.apply_vars (ast.args, _VARS), sym.ast2spt)
				ret      = getattr (splot, ast.func) (*args, **kw)

				return {'msg': ['Plotting not available because matplotlib is not installed.']} if ret is None else {'img': ret}

			elif ast.op in {'@', '-func'} and ast [1] in AST.Func.ADMIN: # special admin function?
				asts = globals () [f'_admin_{ast [1]}'] (*(ast.args if ast.is_func else ()))

				if isinstance (asts, str):
					return {'msg': [asts]}
				elif isinstance (asts, list) and isinstance (asts [0], str):
					return {'msg': asts}

			else: # not admin function, normal evaluation
				ast, vars = _prepare_ass (ast)

				if _SYMPAD_DEBUG:
					print ('ast:       ', ast, file = sys.stderr)

				try:
					spt, xlat = sym.ast2spt (ast, retxlat = True) # , _VARS)

					if _SYMPAD_DEBUG and xlat:
						print ('xlat:      ', xlat, file = sys.stderr)

					sptast = sym.spt2ast (spt)

				except:
					if _SYMPAD_DEBUG:
						print (file = sys.stderr)

					raise

				if _SYMPAD_DEBUG:
					try:
						print ('spt:       ', repr (spt), file = sys.stderr)
					except:
						pass

					print ('spt type:  ', type (spt), file = sys.stderr)

					try:
						print ('spt args:  ', repr (spt.args), file = sys.stderr)
					except:
						pass

					print ('spt latex: ', sp.latex (spt), file = sys.stderr)
					print ('spt ast:   ', sptast, file = sys.stderr)
					print ('spt tex:   ', sym.ast2tex (sptast), file = sys.stderr)
					print ('spt nat:   ', sym.ast2nat (sptast), file = sys.stderr)
					print ('spt py:    ', sym.ast2py (sptast), file = sys.stderr)
					print (file = sys.stderr)

				asts = _execute_ass (sptast, vars)

			response = {}

			if asts and asts [0] != AST.None_:
				response.update ({'math': [{
					'tex': sym.ast2tex (ast),
					'nat': sym.ast2nat (ast),
					'py' : sym.ast2py (ast),
					} for ast in asts]})

			return response

		# start here
		responses = []

		try:
			_HISTORY.append (request ['text'])

			ast, _, _, _ = _PARSER.parse (request ['text'])

			if ast:
				for ast in (ast.scolon if ast.is_scolon else (ast,)):
					sys.stdout = _SYS_STDOUT if _SERVER_DEBUG else io.StringIO ()
					response   = evalexpr (ast)

					if sys.stdout.tell ():
						responses.append ({'msg': sys.stdout.getvalue ().strip ().split ('\n')})

					responses.append (response)

		except Exception:
			if sys.stdout is not _SYS_STDOUT and sys.stdout.tell (): # flush any printed messages before exception
				responses.append ({'msg': sys.stdout.getvalue ().strip ().split ('\n')})

			etype, exc, tb = sys.exc_info ()

			if exc.args and isinstance (exc.args [0], str):
				exc = etype (exc.args [0].replace ('\n', ' ').strip (), *exc.args [1:]).with_traceback (tb) # reformat text to remove newlines

			responses.append ({'err': ''.join (traceback.format_exception (etype, exc, tb)).strip ().split ('\n')})

		finally:
			sys.stdout = _SYS_STDOUT

		return {'data': responses} if responses else {}

	def do_GET (self):
		if self.path == '/':
			self.path = '/index.html'

		fnm = os.path.join (_SYMPAD_PATH, self.path.lstrip ('/'))

		if self.path != '/env.js' and (self.path not in _STATIC_FILES or (not _RUNNING_AS_SINGLE_SCRIPT and not os.path.isfile (fnm))):
			self.send_error (404, f'Invalid path {self.path!r}')

		else:
			self.send_response (200)

			if self.path == '/env.js':
				content = 'text/javascript'
				data    = f'History = {_HISTORY}\nHistIdx = {len (_HISTORY)}\nVersion = {_VERSION!r}\nSymPyVersion = {sp.__version__!r}\nDisplayStyle = {_DISPLAYSTYLE [0]}'.encode ('utf8')

				self.send_header ('Cache-Control', 'no-store')

			else:
				content = _STATIC_FILES [self.path]

				if _RUNNING_AS_SINGLE_SCRIPT:
					data = _FILES [self.path [1:]]
				else:
					data = open (fnm, 'rb').read ()

			self.send_header ('Content-type', f'{content}')
			self.end_headers ()
			self.wfile.write (data)

	def do_POST (self):
		request = parse_qs (self.rfile.read (int (self.headers ['Content-Length'])).decode ('utf8'), keep_blank_values = True)

		for key, val in list (request.items ()):
			if isinstance (val, list) and len (val) == 1:
				request [key] = val [0]

		if request ['mode'] == 'vars':
			response = self.vars (request)

		else:
			if request ['mode'] == 'validate':
				response = self.validate (request)
			else: # if request ['mode'] == 'evaluate':
				response = {**self.evaluate (request), **self.vars (request)}

			response ['idx']  = request ['idx']
			response ['text'] = request ['text']

		response ['mode'] = request ['mode']

		self.send_response (200)
		self.send_header ('Content-type', 'application/json')
		self.send_header ('Cache-Control', 'no-store')
		self.end_headers ()
		self.wfile.write (json.dumps (response).encode ('utf8'))
		# self.wfile.write (json.dumps ({**request, **response}).encode ('utf8'))

#...............................................................................................
def start_server (logging = True):
	if not logging:
		Handler.log_message = lambda *args, **kwargs: None

	if ('--ugly', '') in __OPTS or ('-u', '') in __OPTS:
		_DISPLAYSTYLE [0] = 0

	for opt, _ in __OPTS:
		opt = opt.lstrip ('-')

		if opt in _ENV_OPTS_ALL:
			_admin_env (AST ('@', opt))

	_START_ENV.update (_ENV)
	_vars_updated ()

	if not __ARGV:
		host, port = _DEFAULT_ADDRESS
	else:
		host, port = (re.split (r'(?<=\]):' if __ARGV [0].startswith ('[') else ':', __ARGV [0]) + [_DEFAULT_ADDRESS [1]]) [:2]
		host, port = host.strip ('[]'), int (port)

	try:
		httpd  = HTTPServer ((host, port), Handler)
		thread = threading.Thread (target = httpd.serve_forever, daemon = True)

		thread.start ()

		return httpd

	except OSError as e:
		if e.errno != 98:
			raise

		print (f'Port {port} seems to be in use, try specifying different port as a command line parameter, e.g. localhost:9001')

		sys.exit (-1)

_MONTH_NAME = (None, 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec')

def child ():
	def log_message (msg):
		y, m, d, hh, mm, ss, _, _, _ = time.localtime (time.time ())

		sys.stderr.write (f'{httpd.server_address [0]} - - ' \
				f'[{"%02d/%3s/%04d %02d:%02d:%02d" % (d, _MONTH_NAME [m], y, hh, mm, ss)}] {msg}\n')

	# start here
	httpd = start_server ()

	if _SYMPAD_FIRSTRUN and ('--nobrowser', '') not in __OPTS and ('-n', '') not in __OPTS:
		webbrowser.open (f'http://{httpd.server_address [0] if httpd.server_address [0] != "0.0.0.0" else "127.0.0.1"}:{httpd.server_address [1]}')

	if _SYMPAD_FIRSTRUN:
		print (f'SymPad v{_VERSION} server running. If a browser window does not automatically open to the address below then try navigating to that URL manually.\n')

	log_message (f'Serving at http://{httpd.server_address [0]}:{httpd.server_address [1]}/')

	if not _SYMPAD_RESTART:
		try:
			while 1:
				time.sleep (0.5) # thread.join () doesn't catch KeyboardInterupt on Windows

		except KeyboardInterrupt:
			sys.exit (0)

	else:
		fnms    = (_SYMPAD_NAME,) if _RUNNING_AS_SINGLE_SCRIPT else (_SYMPAD_NAME, 'splot.py', 'spatch.py', 'sparser.py', 'sym.py', 'sxlat.py', 'sast.py', 'lalr1.py')
		watch   = [os.path.join (_SYMPAD_PATH, fnm) for fnm in fnms]
		tstamps = [os.stat (fnm).st_mtime for fnm in watch]

		try:
			while 1:
				time.sleep (0.5)

				if [os.stat (fnm).st_mtime for fnm in watch] != tstamps:
					log_message ('Files changed, restarting...')
					sys.exit (0)

		except KeyboardInterrupt:
			sys.exit (0)

	sys.exit (-1)

def parent ():
	if not _SYMPAD_RESTART or __IS_MODULE_RUN:
		child () # does not return

	# continue as parent process and wait for child process to return due to file changes and restart it
	base      = [sys.executable] + sys.argv [:1] + ['--child'] # (['--child'] if __IS_MAIN else ['sympad', '--child'])
	opts      = [o [0] for o in __OPTS]
	first_run = ['--firstrun']

	try:
		while 1:
			ret       = subprocess.run (base + opts + first_run + __ARGV)
			first_run = []

			if ret.returncode != 0 and not _SYMPAD_DEBUG:
				sys.exit (0)

	except KeyboardInterrupt:
		sys.exit (0)

#...............................................................................................

def main ():
	if ('--help', '') in __OPTS or ('-h', '') in __OPTS:
		print (_HELP)
		sys.exit (0)

	if ('--version', '') in __OPTS or ('-v', '') in __OPTS:
		print (_VERSION)
		sys.exit (0)

	if ('--debug', '') in __OPTS or ('-d', '') in __OPTS:
		_SYMPAD_DEBUG = os.environ ['SYMPAD_DEBUG'] = '1'

	if _SYMPAD_CHILD:
		child ()
	else:
		parent ()

if __IS_MAIN:
	main ()
