diff --git a/logic_programming/7_Unification.ipynb b/logic_programming/7_Unification.ipynb
new file mode 100644
index 0000000000000000000000000000000000000000..5d3c0ac869a02dc1bb8960c1336160558902ae9b
--- /dev/null
+++ b/logic_programming/7_Unification.ipynb
@@ -0,0 +1,657 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "id": "9eb5530a",
+   "metadata": {
+    "vscode": {
+     "languageId": "prolog"
+    }
+   },
+   "source": [
+    "## Substitutions\n",
+    "\n",
+    "Substitution: mapping from variables to terms\n",
+    "\n",
+    "Notation:  {X1/t1, …, Xn/tn}\n",
+    "\n",
+    "Examples: \n",
+    "{X/a}  {X/b, Y/s(0)}  {Y/Z}  {}\n",
+    "\n",
+    "In Prolog we represent this as lists of bindings:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 13,
+   "id": "78764141",
+   "metadata": {
+    "vscode": {
+     "languageId": "prolog"
+    }
+   },
+   "outputs": [],
+   "source": [
+    "is_subst([]).\n",
+    "is_subst([Var/Term|T]) :- variable(Var), term(Term), is_subst(T).\n",
+    "\n",
+    "variable('$VAR'(_)).\n",
+    "variable('X'). % a few artificial variables for easier reading\n",
+    "variable('Y').\n",
+    "variable('Z').\n",
+    "term(_)."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 14,
+   "id": "b03ac6e7",
+   "metadata": {
+    "vscode": {
+     "languageId": "prolog"
+    }
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "\u001b[1mtrue"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "\n",
+    "?- is_subst(['X'/a])."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "ab0d39bc",
+   "metadata": {},
+   "source": [
+    "Applying a substitution to a term or wff\n",
+    "Instantaneous replacement by right-hand sides\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 12,
+   "id": "f6f156a3",
+   "metadata": {
+    "vscode": {
+     "languageId": "prolog"
+    }
+   },
+   "outputs": [],
+   "source": [
+    "% apply(Term,Subst,NewTerm)\n",
+    "apply(Var,Subst,Res) :- variable(Var), !,\n",
+    "   (member(Var/New,Subst) -> Res=New ; Res=Var).\n",
+    "apply(Term,Subst,Res) :- Term =.. [Functor|Args],\n",
+    "    l_apply(Args,Subst,NewArgs),\n",
+    "    Res =.. [Functor|NewArgs].\n",
+    "\n",
+    "l_apply([],_,[]).\n",
+    "l_apply([Arg1|T],Subst,[NewArg1|NewT]) :- apply(Arg1,Subst,NewArg1),\n",
+    "   l_apply(T,Subst,NewT)."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 15,
+   "id": "c675dc2a",
+   "metadata": {
+    "vscode": {
+     "languageId": "prolog"
+    }
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "\u001b[1mRes = p(a)"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "?- apply(p('X'),['X'/a],Res)."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 16,
+   "id": "9639129f",
+   "metadata": {
+    "vscode": {
+     "languageId": "prolog"
+    }
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "\u001b[1mRes = f(a,b,$VAR(2),c,a)"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "?- apply(f('X',b,'Y',c,'X'),['X'/a],Res)."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 19,
+   "id": "a36930e8",
+   "metadata": {
+    "vscode": {
+     "languageId": "prolog"
+    }
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "\u001b[1mRes = f(Y,X)"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "?- apply(f('X','Y'),['X'/'Y','Y'/'X'],Res)."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "c2f4b9a9",
+   "metadata": {},
+   "source": [
+    "A substitution s is called a unifier of two terms (or wff’s) A and B  iff  As = Bs\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 18,
+   "id": "5a3f584e",
+   "metadata": {
+    "vscode": {
+     "languageId": "prolog"
+    }
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "\u001b[1mS = [X/a],\n",
+       "Res1 = p(a),\n",
+       "Res2 = p(a)"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "?- S=['X'/a],\n",
+    "   apply(p('X'),S,Res1),\n",
+    "   apply(p(a),S,Res2)."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 20,
+   "id": "5290b75d",
+   "metadata": {
+    "vscode": {
+     "languageId": "prolog"
+    }
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "\u001b[1mS = [X/Z,Y/Z],\n",
+       "Res1 = p(Z,Z),\n",
+       "Res2 = p(Z,Z)"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "?- S=['X'/'Z', 'Y'/'Z'],\n",
+    "   apply(p('X','Y'),S,Res1),\n",
+    "   apply(p('Z','Z'),S,Res2)."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 23,
+   "id": "347e1997",
+   "metadata": {
+    "vscode": {
+     "languageId": "prolog"
+    }
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "\u001b[1mS = [X/s(X)],\n",
+       "Res1 = p(s(X)),\n",
+       "Res2 = p(s(s(X)))"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "?- S=['X'/s('X')],\n",
+    "   apply(p('X'),S,Res1),\n",
+    "   apply(p(s('X')),S,Res2)."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "96d07660",
+   "metadata": {},
+   "source": [
+    "Composition s1s2 of two substitutions s1 and s2\n",
+    "Defined like composition of functions: A(s1s2) = (As1)s2\n",
+    "Let s1={X1/t1,...},s2={Y1/r1,...},  then s1s2 = {X1/t1s1,...}∪ {Yi/ri| Yi ∉ {X1,...} }\n",
+    "(and delete Xi/tis1 if Xi=tis1)\n",
+    "\n",
+    "In Prolog this gives this (naive) implementation:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 27,
+   "id": "d56c09cb",
+   "metadata": {
+    "vscode": {
+     "languageId": "prolog"
+    }
+   },
+   "outputs": [],
+   "source": [
+    "compose_subst(Sub1,Sub2,Res) :-\n",
+    "    apply1(Sub1,Sub2,NewSub1), % apply Sub2 to RHS of Sub1\n",
+    "    filter2(Sub2,Sub1,NewSub2), % filter unreachable parts of Sub2\n",
+    "    append(NewSub1,NewSub2,Res).\n",
+    "\n",
+    "apply1([],_,[]).\n",
+    "apply1([X1/T1|T],Sub2,Res) :- apply(T1,Sub2,NewT1),\n",
+    "   (NewT1=X1 -> apply1(T,Sub2,Res) % X1/NewT1 is useless; remove it\n",
+    "    ; Res = [X1/NewT1|ResT], apply1(T,Sub2,ResT)).\n",
+    "\n",
+    "filter2([],_,[]).\n",
+    "filter2([Y1/T1|T],Sub1,Res) :-\n",
+    "    (member(Y1/_,Sub1) -> filter2(T,Sub1,Res) % Y1 is in domain of Sub1\n",
+    "      ; Res=[Y1/T1|ResT], filter2(T,Sub1,ResT))."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 28,
+   "id": "b1f992d9",
+   "metadata": {
+    "vscode": {
+     "languageId": "prolog"
+    }
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "\u001b[1mRes = [X/a,Y/b]"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "?- compose_subst(['X'/a],['Y'/b],Res)."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 29,
+   "id": "df25466a",
+   "metadata": {
+    "vscode": {
+     "languageId": "prolog"
+    }
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "\u001b[1mRes = [X/b,Y/b]"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "?- compose_subst(['X'/'Y'],['Y'/b],Res)."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 30,
+   "id": "c6a79cdf",
+   "metadata": {
+    "vscode": {
+     "languageId": "prolog"
+    }
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "\u001b[1mRes = [Y/X]"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "?- compose_subst(['X'/'Y'],['Y'/'X'],Res)."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 31,
+   "id": "68a10c4f",
+   "metadata": {
+    "vscode": {
+     "languageId": "prolog"
+    }
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "\u001b[1mRes = [X/f(a)]"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "?- compose_subst(['X'/f('X')],['X'/a],Res)."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "778e0b71",
+   "metadata": {},
+   "source": [
+    "A (naive) Prolog version of the unification algorithm.\n",
+    "\n",
+    "It does not really compute the disagreement tuple; it traverses both terms and applies the current substitution as it goes along."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 43,
+   "id": "0f59567c",
+   "metadata": {
+    "vscode": {
+     "languageId": "prolog"
+    }
+   },
+   "outputs": [],
+   "source": [
+    "\n",
+    "unify(X,Y,SubSoFar,Res) :-\n",
+    "   apply(X,SubSoFar,NX),\n",
+    "   apply(Y,SubSoFar,NY),\n",
+    "   unify2(NX,NY,SubSoFar,Res).\n",
+    "\n",
+    "unify2(X,Y,SubSoFar,Res) :- X==Y,!, \n",
+    "   Res=SubSoFar. % there are no disagreements at this position; keep the current substitution\n",
+    "unify2(X,Y,SubSoFar,Res) :- variable(X),!, % ak is a variable which does not occur in bk \n",
+    "   not_occurs(X,Y),\n",
+    "   compose_mgu(SubSoFar,[X/Y],Res).\n",
+    "unify2(X,Y,SubSoFar,Res) :- variable(Y),!, % bk is a variable which does not occur in ak \n",
+    "   not_occurs(Y,X),\n",
+    "   compose_mgu(SubSoFar,[Y/X],Res).\n",
+    "unify2(FX,FY,SubSoFar,Res) :-\n",
+    "   FX =.. [Func|ArgsX],\n",
+    "   FY =.. [Func|ArgsY], % check we have the same function symbol and proceed unifying arguments\n",
+    "   l_unify(ArgsX,ArgsY,SubSoFar,Res).\n",
+    "\n",
+    "compose_mgu(SubSoFar,New,Res) :-\n",
+    "   compose_subst(SubSoFar,New,Res),\n",
+    "   format('Adding binding ~w, new candidate MGU ~w~n',[New,Res]).\n",
+    "\n",
+    "l_unify([],[],Sub,Sub).\n",
+    "l_unify([X1|TX],[Y1|TY],Sub,Res) :-\n",
+    "   unify(X1,Y1,Sub,Sub2),\n",
+    "   l_unify(TX,TY,Sub2,Res).\n",
+    "\n",
+    "occurs(X,Y) :- X==Y,!.\n",
+    "occurs(X,FY) :- FY =.. [_|Args], member(Y,Args), occurs(X,Y).\n",
+    "\n",
+    "not_occurs(X,Term) :- \\+ occurs(X,Term).\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 44,
+   "id": "ca8373d0",
+   "metadata": {
+    "vscode": {
+     "languageId": "prolog"
+    }
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "\u001b[1mtrue"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "?- occurs('X',f('X'))."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 45,
+   "id": "e2e2a0fe",
+   "metadata": {
+    "vscode": {
+     "languageId": "prolog"
+    }
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "Adding binding [X/a], new candidate MGU [X/a]"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/plain": [
+       "\u001b[1mRes = [X/a]"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "?- unify(f('X'),f(a),[],Res)."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 46,
+   "id": "e04a20d1",
+   "metadata": {
+    "vscode": {
+     "languageId": "prolog"
+    }
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "\u001b[1;31mfalse"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "?- unify(f('X'),'X',[],Res)."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 47,
+   "id": "c2b9d14f",
+   "metadata": {
+    "vscode": {
+     "languageId": "prolog"
+    }
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "Adding binding [Y/f(X)], new candidate MGU [Y/f(X)]"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/plain": [
+       "\u001b[1mRes = [Y/f(X)]"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "?- unify(f('X'),'Y',[],Res)."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 48,
+   "id": "126098d1",
+   "metadata": {
+    "vscode": {
+     "languageId": "prolog"
+    }
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "\u001b[1mY = f(X)"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "?- f(X)=Y."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 49,
+   "id": "679ece73",
+   "metadata": {
+    "vscode": {
+     "languageId": "prolog"
+    }
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "Adding binding [Y/X], new candidate MGU [Y/X]\n",
+       "Adding binding [X/s(0)], new candidate MGU [Y/s(0),X/s(0)]"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/plain": [
+       "\u001b[1mRes = [Y/s(0),X/s(0)]"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "?- unify(f('Y',s('Y')),f('X',s(s(0))),[],Res)."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "89c66d0d",
+   "metadata": {
+    "vscode": {
+     "languageId": "prolog"
+    }
+   },
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Prolog",
+   "language": "prolog",
+   "name": "prolog_kernel"
+  },
+  "language_info": {
+   "codemirror_mode": "prolog",
+   "file_extension": ".pl",
+   "mimetype": "text/x-prolog",
+   "name": "Prolog"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}