diff --git a/demos/AllFeatures.ipynb b/demos/AllFeatures.ipynb
index 21683853..5c61e916 100644
--- a/demos/AllFeatures.ipynb
+++ b/demos/AllFeatures.ipynb
@@ -16,7 +16,7 @@
},
{
"cell_type": "code",
- "execution_count": 58,
+ "execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
@@ -85,7 +85,7 @@
},
{
"cell_type": "code",
- "execution_count": 6,
+ "execution_count": 3,
"metadata": {},
"outputs": [
{
@@ -112,247 +112,247 @@
" \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
+ " \r\n",
" \r\n",
" \r\n",
" \r\n",
@@ -955,7 +955,7 @@
"\r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
+ " \r\n",
" \r\n",
" \r\n",
" \r\n",
@@ -3293,7 +3293,7 @@
"\r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
" \r\n",
" \r\n",
- " \r\n",
+ " \r\n",
" \r\n",
" \r\n",
" \r\n",
@@ -1697,7 +1697,7 @@
""
]
},
- "execution_count": 6,
+ "execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
@@ -1721,7 +1721,7 @@
},
{
"cell_type": "code",
- "execution_count": 7,
+ "execution_count": 3,
"metadata": {},
"outputs": [
{
@@ -1750,7 +1750,7 @@
},
{
"cell_type": "code",
- "execution_count": 8,
+ "execution_count": 4,
"metadata": {},
"outputs": [
{
@@ -1759,7 +1759,7 @@
"1"
]
},
- "execution_count": 8,
+ "execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
@@ -1771,7 +1771,7 @@
},
{
"cell_type": "code",
- "execution_count": 9,
+ "execution_count": 5,
"metadata": {},
"outputs": [
{
@@ -2045,13 +2045,13 @@
},
{
"cell_type": "code",
- "execution_count": 14,
+ "execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
- "model_id": "55e0a5587f674a48b96367fea8ac8dcc",
+ "model_id": "5f307792ffff42949c013992c366a5eb",
"version_major": 2,
"version_minor": 0
},
@@ -2109,18 +2109,18 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "Even though this graph is a lot compacter than the one we started out with, it no longer looks like a circuit. To fix this we need to be clever."
+ "Even though this graph is a lot compacter than the one we started out with, it no longer looks like a circuit. To fix this we need to be clever and *extract* a circuit from the ZX-diagram:"
]
},
{
"cell_type": "code",
- "execution_count": 17,
+ "execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
- "\n",
+ "\n",
"\n",
""
],
@@ -2370,7 +2370,7 @@
"source": [
"circ2 = g.copy()\n",
"circ2.normalise()\n",
- "circ2 = zx.extract.streaming_extract(circ2)\n",
+ "circ2 = zx.extract_circuit(circ2)\n",
"zx.draw(circ2)"
]
},
@@ -2383,7 +2383,7 @@
},
{
"cell_type": "code",
- "execution_count": 19,
+ "execution_count": 11,
"metadata": {},
"outputs": [
{
@@ -2392,7 +2392,7 @@
"True"
]
},
- "execution_count": 19,
+ "execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
@@ -2413,7 +2413,7 @@
},
{
"cell_type": "code",
- "execution_count": 21,
+ "execution_count": 12,
"metadata": {},
"outputs": [
{
@@ -2421,36 +2421,36 @@
"output_type": "stream",
"text": [
"Inputs: 0:Qbit, 1:Qbit, 2:Qbit, 3:Qbit\n",
- "QGate[\"H\"](3) with nocontrol\n",
- "QRot[\"exp(-i%Z)\",0.7853981633974483](3)\n",
- "QGate[\"H\"](1) with nocontrol\n",
- "QRot[\"exp(-i%Z)\",1.5707963267948966](1)\n",
- "QGate[\"Z\"](1) with controls=[+2] with nocontrol\n",
- "QGate[\"Z\"](1) with controls=[+3] with nocontrol\n",
- "QGate[\"Z\"](0) with controls=[+1] with nocontrol\n",
+ "QGate[\"not\"](3) with controls=[+2] with nocontrol\n",
+ "QGate[\"not\"](2) with controls=[+3] with nocontrol\n",
+ "QGate[\"not\"](3) with controls=[+2] with nocontrol\n",
+ "QGate[\"not\"](1) with controls=[+0] with nocontrol\n",
+ "QGate[\"not\"](0) with controls=[+1] with nocontrol\n",
+ "QGate[\"not\"](1) with controls=[+0] with nocontrol\n",
+ "QGate[\"H\"](2) with nocontrol\n",
"QGate[\"H\"](0) with nocontrol\n",
- "QRot[\"exp(-i%Z)\",1.5707963267948966](0)\n",
- "QGate[\"H\"](1) with nocontrol\n",
- "QRot[\"exp(-i%Z)\",0.7853981633974483](1)\n",
- "QGate[\"Z\"](0) with controls=[+2] with nocontrol\n",
- "QGate[\"Z\"](0) with controls=[+3] with nocontrol\n",
"QGate[\"Z\"](1) with controls=[+0] with nocontrol\n",
- "QGate[\"H\"](3) with nocontrol\n",
- "QRot[\"exp(-i%Z)\",1.5707963267948966](3)\n",
- "QGate[\"Z\"](3) with controls=[+2] with nocontrol\n",
- "QGate[\"Z\"](1) with controls=[+3] with nocontrol\n",
+ "QGate[\"H\"](1) with nocontrol\n",
+ "QGate[\"Z\"](2) with controls=[+1] with nocontrol\n",
+ "QGate[\"Z\"](2) with controls=[+0] with nocontrol\n",
+ "QRot[\"exp(-i%Z)\",0.7853981633974483](2)\n",
"QGate[\"H\"](2) with nocontrol\n",
- "QRot[\"exp(-i%Z)\",1.5707963267948966](2)\n",
"QGate[\"Z\"](3) with controls=[+2] with nocontrol\n",
- "QGate[\"H\"](2) with nocontrol\n",
+ "QGate[\"Z\"](3) with controls=[+1] with nocontrol\n",
+ "QGate[\"Z\"](3) with controls=[+0] with nocontrol\n",
+ "QRot[\"exp(-i%Z)\",1.5707963267948966](0)\n",
+ "QGate[\"H\"](0) with nocontrol\n",
+ "QGate[\"H\"](3) with nocontrol\n",
+ "QGate[\"Z\"](3) with controls=[+2] with nocontrol\n",
+ "QGate[\"Z\"](2) with controls=[+0] with nocontrol\n",
+ "QGate[\"Z\"](1) with controls=[+0] with nocontrol\n",
+ "QRot[\"exp(-i%Z)\",0.7853981633974483](0)\n",
"QGate[\"H\"](0) with nocontrol\n",
+ "QRot[\"exp(-i%Z)\",1.5707963267948966](1)\n",
"QGate[\"H\"](1) with nocontrol\n",
- "QGate[\"not\"](1) with controls=[+0] with nocontrol\n",
- "QGate[\"not\"](0) with controls=[+1] with nocontrol\n",
- "QGate[\"not\"](1) with controls=[+0] with nocontrol\n",
- "QGate[\"not\"](3) with controls=[+2] with nocontrol\n",
- "QGate[\"not\"](2) with controls=[+3] with nocontrol\n",
- "QGate[\"not\"](3) with controls=[+2] with nocontrol\n",
+ "QRot[\"exp(-i%Z)\",1.5707963267948966](2)\n",
+ "QRot[\"exp(-i%Z)\",1.5707963267948966](3)\n",
+ "QGate[\"H\"](3) with nocontrol\n",
"Outputs: 0:Qbit, 1:Qbit, 2:Qbit, 3:Qbit\n"
]
}
@@ -2468,13 +2468,13 @@
},
{
"cell_type": "code",
- "execution_count": 30,
+ "execution_count": 13,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
- "\n",
+ "\n",
"\n",
""
@@ -2731,13 +2731,13 @@
},
{
"cell_type": "code",
- "execution_count": 31,
+ "execution_count": 14,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
- "\n",
+ "\n",
"\n",
""
@@ -2995,28 +2995,28 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "We use a different extraction procedure for this circuit. Instead of producing a ZX-graph as output we get a description as a circuit:"
+ "Again, let us extract a circuit from this diagram:"
]
},
{
"cell_type": "code",
- "execution_count": 32,
+ "execution_count": 15,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
- "Circuit on 6 qubits with 55 gates.\n",
+ "Circuit on 6 qubits with 62 gates.\n",
" 8 is the T-count\n",
- " 47 Cliffords among which \n",
- " 27 2-qubit gates and 16 Hadamard gates.\n"
+ " 54 Cliffords among which \n",
+ " 28 2-qubit gates and 22 Hadamard gates.\n"
]
}
],
"source": [
"g2 = g.copy()\n",
- "c = zx.extract.streaming_extract(g2)\n",
+ "c = zx.extract_circuit(g2)\n",
"print(c.stats())"
]
},
@@ -3029,13 +3029,13 @@
},
{
"cell_type": "code",
- "execution_count": 33,
+ "execution_count": 16,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
- "\n",
+ "\n",
"\n",
""
],
@@ -3295,7 +3295,7 @@
},
{
"cell_type": "code",
- "execution_count": 34,
+ "execution_count": 18,
"metadata": {},
"outputs": [
{
@@ -3304,13 +3304,14 @@
"True"
]
},
- "execution_count": 34,
+ "execution_count": 18,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
- "zx.compare_tensors(c.to_tensor(), circ.to_tensor(),preserve_scalar=False)"
+ "# We can just feed the Circuit objects directly to compare_tensors\n",
+ "zx.compare_tensors(c, circ)"
]
},
{
@@ -3322,7 +3323,7 @@
},
{
"cell_type": "code",
- "execution_count": 35,
+ "execution_count": 19,
"metadata": {},
"outputs": [
{
@@ -3332,67 +3333,72 @@
"OPENQASM 2.0;\n",
"include \"qelib1.inc\";\n",
"qreg q[6];\n",
- "rz(0.75*pi) q[1];\n",
- "rz(1.5*pi) q[3];\n",
- "rz(0.5*pi) q[4];\n",
- "h q[2];\n",
- "h q[0];\n",
- "cz q[1], q[4];\n",
- "cz q[3], q[4];\n",
- "cz q[3], q[5];\n",
- "cz q[4], q[5];\n",
- "cz q[5], q[2];\n",
- "cz q[1], q[0];\n",
- "cz q[4], q[0];\n",
- "h q[2];\n",
- "rz(0.75*pi) q[2];\n",
+ "cx q[2], q[5];\n",
+ "cx q[5], q[2];\n",
+ "cx q[2], q[5];\n",
+ "cx q[1], q[4];\n",
+ "cx q[4], q[1];\n",
+ "cx q[1], q[4];\n",
"h q[5];\n",
- "rz(1.25*pi) q[5];\n",
+ "h q[4];\n",
+ "h q[3];\n",
"h q[1];\n",
- "rz(0.75*pi) q[1];\n",
- "cz q[4], q[2];\n",
- "cz q[3], q[5];\n",
- "cz q[4], q[5];\n",
+ "h q[1];\n",
+ "h q[3];\n",
"cz q[2], q[5];\n",
- "cz q[2], q[0];\n",
- "cx q[4], q[3];\n",
- "cx q[2], q[4];\n",
+ "cz q[2], q[3];\n",
+ "cz q[1], q[2];\n",
"h q[2];\n",
+ "h q[5];\n",
+ "cz q[2], q[5];\n",
+ "cz q[2], q[3];\n",
+ "cz q[1], q[2];\n",
"rz(1.25*pi) q[2];\n",
+ "h q[0];\n",
"h q[2];\n",
- "rz(0.5*pi) q[2];\n",
- "cz q[3], q[2];\n",
- "cx q[2], q[3];\n",
- "cx q[4], q[5];\n",
"h q[4];\n",
- "rz(0.25*pi) q[4];\n",
- "cz q[3], q[4];\n",
+ "cz q[1], q[5];\n",
+ "cz q[1], q[4];\n",
+ "cz q[1], q[3];\n",
+ "cz q[1], q[2];\n",
+ "cz q[0], q[1];\n",
+ "rz(0.5*pi) q[1];\n",
+ "h q[1];\n",
+ "cz q[1], q[3];\n",
+ "rz(1.5*pi) q[3];\n",
+ "h q[3];\n",
+ "cz q[0], q[5];\n",
"cz q[0], q[4];\n",
- "cz q[4], q[1];\n",
+ "cz q[0], q[3];\n",
"h q[0];\n",
- "rz(0.25*pi) q[0];\n",
- "h q[3];\n",
- "cz q[5], q[0];\n",
- "cz q[4], q[0];\n",
+ "cx q[0], q[2];\n",
+ "cz q[3], q[5];\n",
+ "cz q[2], q[5];\n",
+ "cz q[0], q[5];\n",
+ "rz(0.75*pi) q[5];\n",
"h q[5];\n",
"rz(1.25*pi) q[5];\n",
- "cz q[5], q[4];\n",
"h q[5];\n",
- "cz q[5], q[0];\n",
+ "cx q[5], q[1];\n",
+ "cz q[2], q[3];\n",
+ "rz(1.25*pi) q[2];\n",
+ "h q[2];\n",
+ "cz q[0], q[3];\n",
+ "cz q[0], q[2];\n",
+ "rz(0.75*pi) q[4];\n",
+ "rz(0.25*pi) q[0];\n",
+ "h q[4];\n",
"h q[0];\n",
- "rz(0.5*pi) q[0];\n",
+ "cz q[3], q[5];\n",
+ "cz q[3], q[4];\n",
+ "cz q[1], q[5];\n",
+ "rz(0.5*pi) q[5];\n",
"h q[5];\n",
+ "rz(0.75*pi) q[4];\n",
+ "rz(0.25*pi) q[3];\n",
"h q[2];\n",
+ "rz(0.5*pi) q[0];\n",
"h q[0];\n",
- "cx q[1], q[3];\n",
- "cx q[3], q[1];\n",
- "cx q[1], q[3];\n",
- "cx q[2], q[5];\n",
- "cx q[5], q[2];\n",
- "cx q[2], q[5];\n",
- "cx q[3], q[4];\n",
- "cx q[4], q[3];\n",
- "cx q[3], q[4];\n",
"\n"
]
}
diff --git a/pyzx/__init__.py b/pyzx/__init__.py
index 40e26bb1..a9540468 100644
--- a/pyzx/__init__.py
+++ b/pyzx/__init__.py
@@ -24,6 +24,7 @@
from .drawing import *
from .simplify import *
from .optimize import *
+from .extract import *
from .io import *
from .tensor import *
from .circuit.qasmparser import qasm
diff --git a/pyzx/extract.py b/pyzx/extract.py
index 1b0b64b8..28939048 100644
--- a/pyzx/extract.py
+++ b/pyzx/extract.py
@@ -17,7 +17,7 @@
from __future__ import print_function
-__all__ = ['streaming_extract', 'modified_extract']
+__all__ = ['extract_circuit']
from fractions import Fraction
import itertools
@@ -35,70 +35,6 @@
def bi_adj(g, vs, ws):
return Mat2([[1 if g.connected(v,w) else 0 for v in vs] for w in ws])
-def cut_rank(g, left, right):
- return bi_adj(g, left, right).rank()
-
-def cut_edges(g, left, right, available=None):
- m = bi_adj(g, left, right)
- max_r = max(g.row(v) for v in left)
- for v in g.vertices():
- r = g.row(v)
- if (r > max_r):
- g.set_row(v, r+2)
- x,y = m.factor()
-
- for v1 in left:
- for v2 in right:
- if (g.connected(v1,v2)):
- g.remove_edge(g.edge(v1,v2))
-
- cut_rank = y.rows()
-
- #g.add_vertices(2*cut_rank)
- left_verts = []
- right_verts = []
-
- if available == None:
- qs = range(cut_rank)
- else:
- qs = available
-
- for i in qs:
- v1 = g.add_vertex(VertexType.Z,i,max_r+1)
- v2 = g.add_vertex(VertexType.Z,i,max_r+2)
- #v = vi+cut_rank+i
- #g.add_edge((vi+i,v))
- g.add_edge((v1,v2),EdgeType.HADAMARD)
- left_verts.append(v1)
- right_verts.append(v2)
- #g.set_edge_type(g.edge(vi+i,v), EdgeType.HADAMARD)
-
- for i in range(y.rows()):
- for j in range(y.cols()):
- if (y.data[i][j]):
- g.add_edge((left[j],left_verts[i]),EdgeType.HADAMARD)
- #g.add_edge((left[j], vi + i))
- #g.set_edge_type(g.edge(left[j], vi + i), EdgeType.HADAMARD)
- for i in range(x.rows()):
- for j in range(x.cols()):
- if (x.data[i][j]):
- g.add_edge((right_verts[j],right[i]),EdgeType.HADAMARD)
- #g.add_edge((vi + cut_rank + j, right[i]))
- #g.set_edge_type(g.edge(vi + cut_rank + j, right[i]), EdgeType.HADAMARD)
- return left_verts
-
-
-def unspider_by_row(g, v):
- r = g.row(v)
- w = g.add_vertex(VertexType.Z,g.qubit(v),r-1)
- for n in list(g.neighbours(v)):
- if g.row(n) < r:
- e = g.edge(n,v)
- g.add_edge((n,w), edgetype=g.edge_type(e))
- g.remove_edge(e)
- g.add_edge((w, v))
- return w
-
def connectivity_from_biadj(g, m, left, right, edgetype=EdgeType.HADAMARD):
for i in range(len(right)):
for j in range(len(left)):
@@ -107,503 +43,9 @@ def connectivity_from_biadj(g, m, left, right, edgetype=EdgeType.HADAMARD):
elif not m.data[i][j] and g.connected(right[i],left[j]):
g.remove_edge((right[i],left[j]))
-def streaming_extract(g, allow_ancillae=False, quiet=True, stopcount=-1):
- """Given a graph put into semi-normal form by :func:`pyzx.simplify.full_reduce`,
- extracts an equivalent :class:`~pyzx.circuit.Circuit`.
- Uses a version of the algorithm in `this paper `__.
- For large graphs, this function can take a couple of minutes to finish.
-
- Args:
- g: The graph from which a circuit is to be extracted.
- allow_ancillae: Experimental feature to allow extraction for more types of diagrams. Results in post-selected circuits.
- quiet: Whether to print some progress indicators.
- stopcount: If set to a positive integer, stops the extraction after this many gates have been extracted. Useful for debugging or stopping the process when it takes too long.
-
- Note:
- The graph ``g`` is modified in-place during extraction. If you wish to preserve it, call this function with a copy of it: ``streaming_extract(g.copy())``.
- """
- g.normalise()
- qs = g.qubits() # We are assuming that these are objects that update...
- rs = g.rows() # ...to reflect changes to the graph, so that when...
- ty = g.types() # ... g.set_row/g.set_qubit is called, these things update directly to reflect that
- phases = g.phases()
- c = Circuit(g.qubit_count())
- leftrow = 1
- maxq = max(qs.values()) + 1
-
- nodestotal = tcount(g)
- nodesparsed = 0
- nodesmarker = 10
-
- # special_nodes contains the ParityPhase like nodes
- special_nodes = {}
- for v in g.vertices():
- if len(list(g.neighbours(v))) == 1 and v not in g.inputs and v not in g.outputs:
- n = list(g.neighbours(v))[0]
- special_nodes[n] = v
- if rs[v] > 1:
- g.set_row(v, rs[v]+20)
-
- tried_id_simp = False
- while True:
- left = [v for v in g.vertices() if rs[v] == leftrow]
- boundary_verts = []
- right = set()
- good_verts = []
- good_neighs = []
- postselects = []
- for v in left:
- # First we add the gates to the circuit that can be processed now,
- # and we simplify the graph to represent this.
- q = qs[v]
- phase = phases[v]
- t = ty[v]
- neigh = [w for w in g.neighbours(v) if rs[w] 2: nodesparsed += 1
- if t == VertexType.Z: c.add_gate("ZPhase", q, phase=phase)
- else: c.add_gate("XPhase", q, phase=phase)
- g.set_phase(v, 0)
- for v in left:
- q = qs[v]
- t = ty[v]
- neigh = [w for w in g.neighbours(v) if rs[w]==leftrow and wleftrow]
- right.update(d)
- if len(d) == 0:
- if not allow_ancillae: raise TypeError("Not circuit like")
- else:
- postselects.append(v)
- if len(d) == 1: # Only connected to one node in its future
- if ty[d[0]] != VertexType.BOUNDARY: # which is not an output
- good_verts.append(v) # So we can make progress
- good_neighs.append(d[0])
- else: # This node is done processing, since it is directly (and only) connected to an output
- boundary_verts.append(v)
- right.remove(d[0])
- for v in postselects:
- if not quiet: print("postselect", v, qs[v])
- c.add_gate("PostSelect", qs[v])
- left.remove(v)
- g.set_row(v, leftrow-0.5)
- if qs[v] == maxq - 1:
- maxq = maxq -1
- if not good_verts: # There are no 'easy' nodes we can use to progress
- if all(ty[v] == VertexType.BOUNDARY for v in right): break # Actually we are done, since only outputs are left
- for v in boundary_verts: left.remove(v) # We don't care about the nodes only connected to outputs
- have_removed_gadgets = False
- for n in right.intersection(special_nodes): # Neighbours that are phase gadgets
- targets = set(g.neighbours(n))
- targets.remove(special_nodes[n])
- if targets.issubset(left): # Only connectivity on the lefthandside, so we can extract it
- nphase = phases[n]
- if nphase not in (0,1):
- raise Exception("Can't parse ParityPhase with non-Pauli Phase")
- phase = phases[special_nodes[n]]
- c.add_gate("ParityPhase", phase*(-1 if nphase else 1), *[qs[t] for t in targets])
- g.remove_vertices([special_nodes[n],n])
- nodesparsed += 1
- right.remove(n)
- del special_nodes[n]
- have_removed_gadgets = True
- if stopcount != -1 and len(c.gates) > stopcount: return c
- if have_removed_gadgets: continue
- right = list(right)
- m = bi_adj(g,right,left)
- m2 = m.copy()
- m2.gauss(full_reduce=True)
- if not any(sum(l)==1 for l in m2.data):
- if not tried_id_simp:
- tried_id_simp = True
- i = id_simp(g, matchf=lambda v: rs[v]>leftrow, quiet=True)
- if i:
- if not quiet: print("id_simp found some matches")
- m = match_spider_parallel(g, matchf=lambda e: rs[g.edge_s(e)]>=leftrow and rs[g.edge_t(e)]>=leftrow)
- m = [(v1,v2) if v1 in left else (v2,v1) for v1,v2 in m]
- if not quiet and m: print("spider fusion found some matches")
- etab, rem_verts, not_needed1, not_needed2 = spider(g, m)
- g.add_edge_table(etab)
- g.remove_vertices(rem_verts)
- continue
- try:
- gates, lr = handle_phase_gadget(g, left, set(right), special_nodes, quiet=quiet)
- except ValueError:
- if not allow_ancillae:
- raise
- raise Exception
- gates, maxq = find_ancilla_qubits(g, left, set(right), special_nodes, maxq, quiet=quiet)
- c.gates.extend(gates)
- continue
- c.gates.extend(gates)
- nodesparsed += 1
- tried_id_simp = False
- if lr > leftrow:
- for v in boundary_verts:
- g.set_row(v, lr)
- leftrow = lr
- continue
- sequence = greedy_reduction(m) # Find the optimal set of CNOTs we can apply to get a frontier we can work with
- if not isinstance(sequence, list): # Couldn't find any reduction, hopefully we can fix this
- right = set(right)
- gates, success = try_greedy_cut(g, left, right, right.difference(special_nodes), quiet=quiet)
- if success:
- c.gates.extend(gates)
- continue
- raise Exception("We should never get here")
-
- if not quiet: print("Greedy reduction with {:d} CNOTs".format(len(sequence)))
- for control, target in sequence:
- c.add_gate("CNOT", qs[left[target]], qs[left[control]])
- # If a control is connected to an output, we need to add a new node.
- for v in g.neighbours(left[control]):
- if v in g.outputs:
- #print("Adding node before output")
- q = qs[v]
- r = rs[v]
- w = g.add_vertex(VertexType.Z,q,r-1)
- e = g.edge(left[control],v)
- et = g.edge_type(e)
- g.remove_edge(e)
- g.add_edge((left[control],w),EdgeType.HADAMARD)
- g.add_edge((w,v),toggle_edge(et))
- k = right.index(v)
- right[k] = w
- break
- for k in range(len(m.data[control])): # We update the graph to represent the extraction of a CNOT
- if not m.data[control][k]: continue
- if m.data[target][k]: g.remove_edge((left[target],right[k]))
- else: g.add_edge((left[target],right[k]), EdgeType.HADAMARD)
- m.row_add(control, target)
- for v in left:
- d = [w for w in g.neighbours(v) if rs[w]>leftrow]
- if len(d) == 1 and ty[d[0]] != VertexType.BOUNDARY:
- good_verts.append(v)
- good_neighs.append(d[0])
- if not good_verts: continue
-
- for v in g.vertices():
- if rs[v] < leftrow: continue
- if v in good_verts: continue
- g.set_row(v,rs[v]+1) # Push the frontier one layer up
- for i,v in enumerate(good_neighs):
- g.set_row(v,leftrow+1) # Bring the new nodes of the frontier to the correct position
- g.set_qubit(v,qs[good_verts[i]])
-
- tried_id_simp = False
-
- if not quiet and nodesparsed > nodesmarker:
- print("{:d}/{:d}".format(nodesparsed, nodestotal))
- nodesmarker = int(round(nodesparsed-5,-1))
- nodesmarker += 10
- leftrow += 1
- if stopcount != -1 and len(c.gates) > stopcount: return c
-
- swap_map = {}
- leftover_swaps = False
- for v in left: # Finally, check for the last layer of Hadamards, and see if swap gates need to be applied.
- q = qs[v]
- neigh = [w for w in g.neighbours(v) if rs[w]>leftrow]
- if len(neigh) != 1:
- raise TypeError("Algorithm failed: Not fully reducable")
- return c
- n = neigh[0]
- if ty[n] != VertexType.BOUNDARY:
- raise TypeError("Algorithm failed: Not fully reducable")
- return c
- if g.edge_type(g.edge(n,v)) == EdgeType.HADAMARD:
- c.add_gate("HAD", q)
- g.set_edge_type(g.edge(n,v),EdgeType.SIMPLE)
- if qs[n] != q: leftover_swaps = True
- swap_map[q] = qs[n]
- if leftover_swaps:
- for t1, t2 in permutation_as_swaps(swap_map):
- c.add_gate("SWAP", t1, t2)
- return c
-
-
-def try_greedy_cut(g, left, right, candidates, quiet=True):
- q = len(left)
- left = list(left)
- # Take care nothing is connected directly to an output
- for w in right.copy():
- if w in g.outputs:
- w2 = g.add_vertex(VertexType.Z, g.qubit(w), g.row(w)-1)
- n = list(g.neighbours(w))[0] # Outputs should have unique neighbours
- e = g.edge(n,w)
- et = g.edge_type(e)
- g.remove_edge(e)
- g.add_edge((n,w2),EdgeType.HADAMARD)
- g.add_edge((w2,w),toggle_edge(et))
- right.remove(w)
- right.add(w2)
- if w in candidates:
- candidates.remove(w)
- candidates.add(w2)
-
- right = list(right)
- # We want to figure out which vertices in candidates are 'pivotable'
- # That is, that removing them will decrease the cut rank of the remainder
- m = bi_adj(g, right, left)
- m.gauss(full_reduce=True) # Gaussian elimination doesn't change this property
- good_nodes = []
- for r in m.data:
- if sum(r) == 1: # Exactly one nonzero value, so removing the column with the nonzero value...
- i = next(i for i in range(len(r)) if r[i]) # ...decreases the rank of the matrix
- w = right[i]
- if w in candidates:
- good_nodes.append(w)
- if not good_nodes:
- return [], False
- right = [w for w in right if w not in good_nodes]
-
- new_right = cut_edges(g, left, right)
- leftrow = g.row(left[0])
- for w in good_nodes:
- g.set_row(w, leftrow+2)
- new_right.append(unspider_by_row(g, w))
-
- left.sort(key=g.qubit)
- qs = [g.qubit(v) for v in left]
- m = bi_adj(g, new_right, left)
- target = column_optimal_swap(m)
- for i, j in target.items():
- g.set_qubit(new_right[i],qs[j])
- new_right.sort(key=g.qubit)
- m = bi_adj(g, new_right, left)
- gates = m.to_cnots(optimize=True)
- for cnot in gates:
- cnot.target = qs[cnot.target]
- cnot.control = qs[cnot.control]
- for i in range(q):
- for j in range(q):
- if g.connected(left[i],new_right[j]):
- if i != j:
- g.remove_edge(g.edge(left[i],new_right[j]))
- elif i == j:
- g.add_edge((left[i],new_right[j]), EdgeType.HADAMARD)
- if not quiet: print("Greedy extract with {:d} nodes and {:d} CNOTs".format(len(good_nodes),len(gates)))
- return gates, True
-
-
-
-def handle_phase_gadget(g, left, neigh, special_nodes, quiet=True):
- """Tries to find a cut of the graph at the given leftrow so that a single phase-gadget can be extracted.
- Returns a list of extracted gates and modifies the graph g in place. Used by :func:`streaming_extract`"""
- q = len(left)
- qs = g.qubits() # We are assuming this thing automatically updates
- rs = g.rows()
- leftrow = rs[left[0]]
- gadgets = neigh.intersection(special_nodes) # These are the phase gadgets that are attached to the left row
- if len(gadgets) == 0: raise ValueError("No phase gadget connected to this row")
- all_verts = neigh.union(left).union(special_nodes.values())
- right = list(neigh)
- options = []
- for gadget in gadgets:
- if all(w in all_verts for w in g.neighbours(gadget)):
- options.append(gadget)
- #print(options)
- for o in options: # We move the candidates gadgets to the end of the list
- right.remove(o)
- right.append(o)
- #print(right)
- m = bi_adj(g, right, left+options)
- r = reduce_bottom_rows(m, q)
- gadget = options[r-len(left)] # This is a gadget that works
- right.remove(gadget)
-
- g.set_row(gadget,leftrow+1)
- g.set_row(special_nodes[gadget],leftrow+1)
-
- # Take care nothing is connected directly to an output
- for i in range(len(right)):
- w = right[i]
- if w in g.outputs:
- w2 = g.add_vertex(VertexType.Z, qs[w], rs[w]-1)
- n = list(g.neighbours(w))[0] # Outputs should have unique neighbours
- e = g.edge(n,w)
- et = g.edge_type(e)
- g.remove_edge(e)
- g.add_edge((n,w2),EdgeType.HADAMARD)
- g.add_edge((w2,w),toggle_edge(et))
- right[i] = w2
-
- if len(right) == q:
- if not quiet: print("No cutting necessary")
- for w in right:
- g.set_row(w, leftrow+2)
- else:
- right = cut_edges(g, left+[gadget], right)
- # We have now prepared the stage to do the extraction of the phase gadget
-
- phase = g.phase(special_nodes[gadget])
- phase = -1*phase if g.phase(gadget) != 0 else phase
- left.sort(key=g.qubit)
- qv = [qs[v] for v in left]
- m = bi_adj(g, right, left)
- target = column_optimal_swap(m)
- for i, j in target.items():
- g.set_qubit(right[i],qv[j])
- right.sort(key=g.qubit)
-
- m = bi_adj(g, right, left)
- if m.rank() != q:
- raise Exception("Rank in phase gadget reduction too low.")
- operations = Circuit(q)
- operations.row_add = lambda r1,r2: operations.gates.append((r1,r2))
- m.gauss(full_reduce=True,x=operations)
- gates = [CNOT(qv[r2],qv[r1]) for r1,r2 in operations.gates]
- m = bi_adj(g, right+[gadget], left)
- for r1,r2 in operations.gates:
- m.row_add(r1,r2)
- connectivity_from_biadj(g, m, right+[gadget], left)
-
- # Now the connections from the left to the right are like the identity
- # with some wires coming to the gadget from the left and from the right
- gadget_left = [v for v in left if g.connected(gadget, v)]
- gadget_right = [w for w in right if g.connected(gadget, w)]
- targets = [qs[v] for v in gadget_left]
- # We bring as many connections on the right to the left
- for i in reversed(range(len(gadget_right))): # The following checks if every phase connected node is on the right
- w = gadget_right[i]
- v = next(v for v in left if g.connected(w,v))
- g.set_edge_type((v,w),EdgeType.SIMPLE)
- g.set_qubit(w, qs[v])
- if qs[w] not in targets:
- gates.append(HAD(qs[w]))
- gadget_right.pop(i)
- targets.append(qs[w])
- gadget_left.append(v)
- else:
- g.set_row(w, leftrow+1)
-
- if not gadget_right: #Only connected on leftside so we are done
- if not quiet: print("Simple phase gadget")
- gate = ParityPhase(phase, *targets)
- g.remove_vertices([special_nodes[gadget],gadget])
- gates.append(gate)
- return gates, leftrow
-
- if not quiet: print("Complicated phase gadget") # targets on left and right, so need to do more
- if len(gadget_right) % 2 != 0 or len(gadget_left) == 1:
- raise Exception("Gadget seems non-unitary")
-
- #Now we can finally extract the phase gadget
- rtargets = []
- for w in gadget_right:
- t = qs[w]
- rtargets.append(t)
- gates.extend([HAD(t),ZPhase(t,Fraction(-1,2)),HAD(t)])
- if len(gadget_right)%4 != 0: # This is either 2 or 0
- phase = (-phase)%2
- gates.append(ParityPhase(phase, *targets))
- for t in rtargets:
- gates.extend([HAD(t),ZPhase(t, Fraction(1,2))])
- for v in left:
- if qs[v] not in rtargets:
- g.set_row(v, leftrow+1)
-
- g.remove_vertices([special_nodes[gadget],gadget])
- return gates, leftrow+1
-
-def reduce_bottom_rows(m, qubits):
- """Using just row_add's from the first qubit rows in m, tries to find a row that can be
- completely zero'd out. Returns the rownumber of this row when successful."""
- cols = m.cols()
- leading_one = {}
- adds = []
- for r in range(qubits):
- while True:
- i = next(i for i in range(cols) if m.data[r][i])
- if i in leading_one:
- m.row_add(leading_one[i],r)
- adds.append((leading_one[i],r))
- else:
- leading_one[i] = r
- break
- for r in range(qubits, m.rows()):
- while True:
- if not any(m.data[r]):
- return r
- i = next(i for i in range(cols) if m.data[r][i])
- if i not in leading_one: break
- m.row_add(leading_one[i], r)
- adds.append((leading_one[i],r))
- raise ValueError("Did not find any completely reducable row")
-
-def find_ancilla_qubits(g, left, right, gadgets, maxq, quiet=True):
- leftrow = g.row(left[0])
- nodes = list(right.difference(gadgets))
- right = list(right)
- for w in nodes:
- right.remove(w)
- right.append(w)
- m = bi_adj(g, right, left)
- m.gauss(full_reduce=True)
- candidates = []
- ancilla_count = 100000
- for row in m.data:
- if not any(row[:-len(nodes)]):
- verts = [right[i] for i,a in enumerate(row) if a]
- if len(verts) < ancilla_count:
- candidates = [verts]
- ancilla_count = len(verts)
- elif len(verts) == ancilla_count:
- candidates.append(verts)
- if not candidates:
- raise ValueError("No valid ancilla vertices found")
- if not quiet: print("Adding {:d} ancillas".format(ancilla_count-1))
- if len(candidates) == 1:
- ancillas = candidates[0][:-1]
- else:
- all_candidates = set()
- for cand in candidates: all_candidates.update(cand)
- best_set = None
- best_count = 100000
- for poss in itertools.combinations(all_candidates, ancilla_count-1):
- s = sum(1 for cand in candidates if all(v in cand for v in poss))
- if s < best_count:
- best_count = s
- best_set = poss
- ancillas = best_set
-
- gates = []
- for i, v in enumerate(ancillas):
- g.set_row(v, leftrow)
- g.set_qubit(v, maxq+i)
- w = g.add_vertex(VertexType.Z, maxq+i, leftrow-1)
- g.add_edge((v,w),EdgeType.SIMPLE)
- gates.append(InitAncilla(maxq+i))
- #raise Exception
- return gates, maxq+len(ancillas)
-
-
-
-
+def streaming_extract(g, optimize_czs=True, optimize_cnots=2, quiet=True):
+ print("This function is deprecated. Call extract_circuit() instead.")
+ return extract_circuit(g, optimize_czs, optimize_cnots, quiet)
def permutation_as_swaps(perm):
"""Returns a series of swaps the realises the given permutation.
@@ -799,9 +241,11 @@ def filter_duplicate_cnots(cnots):
c = basic_optimization(c,do_swaps=False)
return c.gates
-def modified_extract(g, optimize_czs=True, optimize_cnots=2, quiet=True):
+def extract_circuit(g, optimize_czs=True, optimize_cnots=2, quiet=True):
"""Given a graph put into semi-normal form by :func:`~pyzx.simplify.full_reduce`,
it extracts its equivalent set of gates into an instance of :class:`~pyzx.circuit.Circuit`.
+ This function implements a more optimized version of the algorithm described in
+ `There and back again: A circuit extraction tale `_
Args:
g: The ZX-diagram graph to be extracted into a Circuit.
diff --git a/pyzx/scripts/circ2circ.py b/pyzx/scripts/circ2circ.py
index bd3807a2..e938cca7 100644
--- a/pyzx/scripts/circ2circ.py
+++ b/pyzx/scripts/circ2circ.py
@@ -82,7 +82,7 @@ def main(args):
if options.simp == 'cliff':
simplify.clifford_simp(g,quiet=(not options.verbose))
if options.verbose: print("Extracting circuit...")
- c2 = extract.streaming_extract(g)
+ c2 = extract.extract_circuit(g)
if options.verbose: print("Optimizing...")
if options.phasepoly:
c3 = optimize.full_optimize(c2.to_basic_gates())
diff --git a/tests/long_test.py b/tests/long_test.py
index 286e12ff..393a143f 100644
--- a/tests/long_test.py
+++ b/tests/long_test.py
@@ -24,7 +24,7 @@
from pyzx.tensor import compare_tensors
from pyzx.generate import cliffordT
from pyzx.simplify import *
-from pyzx.extract import *
+from pyzx.extract import extract_circuit
from pyzx.circuit import Circuit
from pyzx.optimize import *
@@ -50,8 +50,8 @@ def do_tests(qubits, depth, iterations, test_clifford_graph=True):
steps.append("clifford_simp")
if test_clifford_graph: compare(t, g)
- c = streaming_extract(g)
- steps.append("streaming_extract")
+ c = extract_circuit(g)
+ steps.append("extract_circuit")
compare(t, c, False)
c = c.to_basic_gates()
@@ -68,18 +68,10 @@ def do_tests(qubits, depth, iterations, test_clifford_graph=True):
steps.append("full_reduce")
if test_clifford_graph: compare(t, g)
- c = modified_extract(g)
- steps.append("modified_extract")
+ c = extract_circuit(g)
+ steps.append("extract_circuit")
compare(t,c,False)
- steps = []
- g = circ.copy()
- full_reduce(g, quiet=True)
- steps.append("full_reduce")
- c = streaming_extract(g).to_basic_gates()
- steps.append("streaming_extract")
- compare(t, c, False)
-
steps = []
g = circ.copy()
#to_gh(g)
diff --git a/tests/test_circuit.py b/tests/test_circuit.py
index 905178ff..6119f291 100644
--- a/tests/test_circuit.py
+++ b/tests/test_circuit.py
@@ -30,7 +30,7 @@
from pyzx.generate import cliffordT, cliffords
from pyzx.simplify import clifford_simp
-from pyzx.extract import streaming_extract
+from pyzx.extract import extract_circuit
from pyzx.circuit import Circuit
SEED = 1337
@@ -92,7 +92,7 @@ def test_circuit_extract_preserves_semantics(self):
g = cliffordT(5, 70, 0.15)
t = g.to_tensor(False)
clifford_simp(g, quiet=True)
- c = streaming_extract(g)
+ c = extract_circuit(g)
t2 = c.to_tensor(False)
self.assertTrue(compare_tensors(t,t2,False))
diff --git a/tests/test_extract.py b/tests/test_extract.py
index e61b2970..60a46754 100644
--- a/tests/test_extract.py
+++ b/tests/test_extract.py
@@ -32,7 +32,7 @@
from pyzx.circuit.gates import CNOT
from pyzx.generate import cliffordT, cliffords
from pyzx.simplify import clifford_simp
-from pyzx.extract import streaming_extract, modified_extract
+from pyzx.extract import extract_circuit
SEED = 1337
@@ -40,55 +40,14 @@
@unittest.skipUnless(np, "numpy needs to be installed for this to run")
class TestExtract(unittest.TestCase):
- # def test_clifford_extract(self):
- # random.seed(SEED)
- # tests = 0
- # tries = 0
- # while True:
- # tries += 1
- # circ = cliffords(5,70)
- # clifford_simp(circ,quiet=True)
- # circ.normalise()
- # if circ.depth()>3: continue # It is not in normal form, so skip this one
- # tests += 1
- # with self.subTest(test=tests,tries=tries):
- # t = tensorfy(circ)
- # clifford_extract(circ,1,2)
- # t2 = tensorfy(circ)
- # self.assertTrue(compare_tensors(t,t2))
- # if tests>5: break
-
- # def test_greedy_cut_extract(self):
- # random.seed(SEED)
- # for i in range(5):
- # circ = cliffordT(4,50,0.1)
- # clifford_simp(circ,quiet=True)
- # circ.normalise()
- # with self.subTest(i=i):
- # t = tensorfy(circ)
- # greedy_cut_extract(circ)
- # t2 = tensorfy(circ)
- # self.assertTrue(compare_tensors(t,t2))
-
- # def test_circuit_extract(self):
- # random.seed(SEED)
- # for i in range(5):
- # circ = cliffordT(4,50,0.1)
- # clifford_simp(circ,quiet=True)
- # with self.subTest(i=i):
- # t = tensorfy(circ)
- # circuit_extract(circ)
- # t2 = tensorfy(circ)
- # self.assertTrue(compare_tensors(t,t2))
-
- def test_streaming_extract(self):
+ def test_extract_circuit(self):
random.seed(SEED)
for i in range(5):
circ = cliffordT(4,50,0.1)
t = tensorfy(circ,False)
clifford_simp(circ,quiet=True)
with self.subTest(i=i):
- c = streaming_extract(circ)
+ c = extract_circuit(circ)
t2 = c.to_tensor(False)
self.assertTrue(compare_tensors(t,t2,False))
@@ -101,7 +60,7 @@ def test_cz_optimise_extract(self):
g = c.to_graph()
clifford_simp(g,quiet=True)
- c2 = modified_extract(g)
+ c2 = extract_circuit(g)
cnot_count = 0
for gate in c2.gates:
if isinstance(gate, CNOT):