diff --git a/basic/verifiable_Lagrange_interpolation/.gitignore b/basic/verifiable_Lagrange_interpolation/.gitignore new file mode 100644 index 0000000..eb5a316 --- /dev/null +++ b/basic/verifiable_Lagrange_interpolation/.gitignore @@ -0,0 +1 @@ +target diff --git a/basic/verifiable_Lagrange_interpolation/README.md b/basic/verifiable_Lagrange_interpolation/README.md new file mode 100644 index 0000000..b81e46c --- /dev/null +++ b/basic/verifiable_Lagrange_interpolation/README.md @@ -0,0 +1,13 @@ +# **Verifiable Lagrange interpolation** + +Lagrange interpolation is a mathematical technique used to approximate a function that passes through a given set of points. It takes an input set of data points and computes a polynomial that passes through all of them. + +Given a set of $n+1$ data points (or interpolation nodes) $X_0, X_1, ..., X_n$ with corresponding function values $Y_0, Y_1, ..., Y_n$, Lagrange interpolation seeks to find a polynomial of degree at most $n$ that passes through all these points. + +Below, we provide a brief review of the implementation of a Lagrange interpolation in Python, which we will then convert to Cairo to transform it into a verifiable ZKML (Lagrange interpolation), using the Orion library. + +Content overview: + +1. Lagrange interpolation with Python: We start with the basic implementation of Lagrange interpolation using Python. +2. Convert your model to Cairo: In the subsequent stage, we will create a new scarb project and replicate our model to Cairo which is a language for creating STARK-provable programs. +3. Implementing Lagrange interpolation using Orion: To catalyze our development process, we will use the Orion Framework to construct the key functions to build our verifiable Lagrange interpolation. \ No newline at end of file diff --git a/basic/verifiable_Lagrange_interpolation/Scarb.toml b/basic/verifiable_Lagrange_interpolation/Scarb.toml new file mode 100644 index 0000000..8cb6e37 --- /dev/null +++ b/basic/verifiable_Lagrange_interpolation/Scarb.toml @@ -0,0 +1,10 @@ +[package] +name = "lagrange" +version = "0.1.0" +description = "Verifiable Lagrange Interpolation" + +[dependencies] +orion = { git = "https://github.com/gizatechxyz/orion.git", rev = "v0.1.9" } + +[scripts] +test = "scarb cairo-test -f lagrange_test" \ No newline at end of file diff --git a/basic/verifiable_Lagrange_interpolation/notebooks/lagrange.ipynb b/basic/verifiable_Lagrange_interpolation/notebooks/lagrange.ipynb new file mode 100644 index 0000000..ed002e9 --- /dev/null +++ b/basic/verifiable_Lagrange_interpolation/notebooks/lagrange.ipynb @@ -0,0 +1,351 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# **Verifiable Lagrange Interpolation** " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Lagrange interpolation is a mathematical technique used to approximate a function that passes through a given set of points. It takes an input set of data points and computes a polynomial that passes through all of them. \n", + "\n", + "Given a set of $n+1$ data points (or interpolation nodes) $X_0, X_1, ..., X_n$ with corresponding function values $Y_0, Y_1, ..., Y_n$, Lagrange interpolation seeks to find a polynomial of degree at most $n$ that passes through all these points." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Below, we provide a brief review of the implementation of a Lagrange interpolation in Python, which we will then convert to Cairo to transform it into a verifiable ZKML (Lagrange interpolation), using the Orion library. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### **Used DataSet** " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this tutorial, we will interpolate the Runge function, that we will define $f$ and uses the Chebyshev node as interpolation nodes." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import math\n", + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "# Runge function\n", + "def f(x):\n", + " return 1 / (x**2 + 1)\n", + "\n", + "# Chebyshev nodes\n", + "X = np.array( [5*math.cos(k*math.pi/10) for k in range(0,11)] )\n", + "\n", + "Y = f(X)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[ 5.00000000e+00 4.75528258e+00 4.04508497e+00 2.93892626e+00\n", + " 1.54508497e+00 3.06161700e-16 -1.54508497e+00 -2.93892626e+00\n", + " -4.04508497e+00 -4.75528258e+00 -5.00000000e+00]\n", + "[0.03846154 0.04235007 0.05759469 0.10376364 0.29522147 1.\n", + " 0.29522147 0.10376364 0.05759469 0.04235007 0.03846154]\n" + ] + } + ], + "source": [ + "print(X)\n", + "print(Y)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we will implement Lagrange interpolation function in python.\n", + "\n", + "Given a set of $n+1$ data points $X_0, X_1, ..., X_n$ with corresponding function values $Y_0, Y_1, ..., Y_n$, the Lagrange interpolating polynomial is\n", + "\n", + "$$\n", + " L(x) = \\sum_{i=0}^n Y_i \\phi_i(x), \n", + "$$\n", + "\n", + "where $\\phi_i(x)$ is the $i$-th Lagrange polynomial defined by \n", + "$$\n", + " \\phi_i(x) = \\frac{(x - X_0)\\ldots (x - X_{i-1})(x - X_{i+1})\\ldots(x - X_n)}{(X_i - X_0)\\ldots (X_i - X_{i-1})(X_i - X_{i+1})\\ldots(X_i - X_n)}\n", + "\n", + "$$" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + " \n", + "def lagrange(x,X,Y):\n", + " \n", + " n = min(len(X),len(Y))\n", + " m = len(x) \n", + " yh = np.zeros(m)\n", + " phi = np.zeros(n) \n", + "\n", + " for j in range(m):\n", + " yh[j] = 0\n", + " for i in range(n):\n", + " phi[i] = 1.0\n", + " for k in range(n):\n", + " if i != k:\n", + " phi[i] = phi[i]*(x[j]-X[k])/(X[i]-X[k])\n", + " yh[j] = yh[j] + Y[i] * phi[i]\n", + " \n", + " return yh" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will now visualize the computed Lagrange interpolation and compare it to the function we interpolated, the Runge function.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "x = np.linspace(-5,5,num=100)\n", + "fx = f(x)\n", + "y = lagrange(x,X,Y)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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QVZWFqdPn65yv19//bVBzt+nTx/c3d1Zv369zQ8Mtv6fo6Ki6NKlCzt27LCbUDYk8xuzrf+JhroOkZGRdO3alaysLL799tsaj6/P8x6azvOuqZBkpJF17NgRvV7PH3/8YfViW1xczJw5c2y+uIwePZqOHTuSlJTEiy++aLXv3//+d70/mZqz77feessqyVm9ejX//e9/6/WYttxyyy0AvPDCC1aFtrKzs3nggQds3ufWW28lJCSEZ599lvfff79KElZQUMBHH31kt1BTYzJ/erdVNMzd3Z377ruP7OxsLr/8cpstGTt27GDx4sX1OrfJZOKuu+6isLDQsm3z5s289tpr6PV6br31Vsv2adOmERYWxurVq3nrrbesHufll19m48aNREVFcdlll9V43s6dO5OdnV0l7hdffNFSlOpc1V0ne7y9vbnxxhsxmUzcfvvtFBQUWPbt37+fZ555BoA777yz1o9ZV7Nnz8bT05PPP//cUswKtGv/yCOPcOLECfr378+gQYMaLQazOXPmEBERwb59+/jmm28s20eOHAlgGWgOWrfJ/PnzLQPGz5evry/XXHMN5eXl3HPPPZSWllr2rVy50pIAn+sf//gHRqORyy+/nF27dlXZf+jQId57770GidF8HebPn2/pIgItabVXIK8+HnroIQDmzp3L7t27rfYVFBRYjaUKDg7G1dWVQ4cOWcVUk6b0vGsSnDGFpyWgYnrqwIED7X598cUXSimlbr75ZgUoT09PNXHiRHXFFVeosLAwFRwcrGbNmqUA9f7771s9/urVq5WHh4cCrXjV1VdfrXr16qXc3Nwsj/evf/3L6j411cPYt2+f8vb2VoDq1q2buuqqq9Tw4cOVTqdT9913n83pg/amDdZ0zttvv11RUfPE/DsHBQWpvn37qkGDBilAnThxwuo+a9asUYGBgZY4Jk6cqKZOnar69etnifvsmhLVqWlqry32pgaap0H6+fmpK664Qs2ePVvNnj3bst9oNKqrr75agVbkbfDgwWr69OnqwgsvtEzDnjx5cq3jODuWSy65RMXExKjw8HA1bdo0NW7cOEvhMVtTNs8ueta3b1919dVXWwrseXt717ro2SeffGJ5jg8fPlxdffXVqlu3bkqv16t77rnH5nXatGmT0ul0yt3dXU2ePNlynTIzM5VStSt6Fhoaqq688ko1YcIEy/P/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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "fig, ax = plt.subplots()\n", + "ax.set_title('Lagrange interpolation of Runge function', fontsize = 15)\n", + "ax.plot(x,fx,label='Runge function')\n", + "ax.plot(x,y,label='Lagrange interpolation')\n", + "plt.legend()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## **Convert your model to Cairo** " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### **Generating Cairo files** \n", + "\n", + "Now let's generate Cairo files for each tensor in the object." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "def decimal_to_fp16x16(num):\n", + "\n", + " whole_num = int(num)\n", + " fractional_part = int((num - whole_num) * 65536)\n", + " fp_number = (whole_num << 16) + fractional_part\n", + " return fp_number" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "import os" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "current_directory = os.getcwd()\n", + "parent_directory = os.path.dirname(current_directory)\n", + "new_directory_path = os.path.join(parent_directory, \"src/generated\")" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "os.makedirs('src/generated', exist_ok=True) " + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "tensor_name = [\"X\",\"Y\",\"x\",\"y\"]\n", + "\n", + "def generate_cairo_files(data, name):\n", + "\n", + " with open(os.path.join('src', 'generated', f\"{name}.cairo\"), \"w\") as f:\n", + " f.write(\n", + " \"use array::{ArrayTrait, SpanTrait};\\n\" +\n", + " \"use orion::operators::tensor::{core::{Tensor, TensorTrait}};\\n\" +\n", + " \"use orion::operators::tensor::FP16x16Tensor;\\n\" +\n", + " \"use orion::numbers::fixed_point::implementations::fp16x16::core::{FP16x16, FixedTrait};\\n\" +\n", + " \"\\n\" + f\"fn {name}() -> Tensor\" + \"{\\n\\n\" + \n", + " \"let mut shape = ArrayTrait::new();\\n\"\n", + " )\n", + " for dim in data.shape:\n", + " f.write(f\"shape.append({dim});\\n\")\n", + " \n", + " f.write(\"let mut data = ArrayTrait::new();\\n\")\n", + " for val in np.nditer(data.flatten()):\n", + " f.write(f\"data.append(FixedTrait::new({abs(int(decimal_to_fp16x16(val)))}, {str(val < 0).lower()}));\\n\")\n", + " f.write(\n", + " \"let tensor = TensorTrait::::new(shape.span(), data.span());\\n\" +\n", + " \"return tensor;\\n}\"\n", + " )\n", + "\n", + "with open(f\"src/generated.cairo\", \"w\") as f:\n", + " for n in tensor_name:\n", + " f.write(f\"mod {n};\\n\")\n", + "\n", + "generate_cairo_files(X, \"X\")\n", + "generate_cairo_files(Y, \"Y\")\n", + "\n", + "generate_cairo_files(x, \"x\")\n", + "generate_cairo_files(y, \"y\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " **Convert some hyperparameters to FP16x16** " + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "tol=1e-2\n", + "max_iter=500" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "655\n", + "32768000\n" + ] + } + ], + "source": [ + "print(decimal_to_fp16x16(tol))\n", + "print(decimal_to_fp16x16(max_iter))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " **Get an estimate for the first two values of eigenvalus and first eigenvector in FP16x16** " + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.6" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/basic/verifiable_Lagrange_interpolation/notebooks/src/generated.cairo b/basic/verifiable_Lagrange_interpolation/notebooks/src/generated.cairo new file mode 100644 index 0000000..07d152d --- /dev/null +++ b/basic/verifiable_Lagrange_interpolation/notebooks/src/generated.cairo @@ -0,0 +1,4 @@ +mod X; +mod Y; +mod x; +mod y; diff --git a/basic/verifiable_Lagrange_interpolation/notebooks/src/generated/X.cairo b/basic/verifiable_Lagrange_interpolation/notebooks/src/generated/X.cairo new file mode 100644 index 0000000..b89d502 --- /dev/null +++ b/basic/verifiable_Lagrange_interpolation/notebooks/src/generated/X.cairo @@ -0,0 +1,24 @@ +use array::{ArrayTrait, SpanTrait}; +use orion::operators::tensor::{core::{Tensor, TensorTrait}}; +use orion::operators::tensor::FP16x16Tensor; +use orion::numbers::fixed_point::implementations::fp16x16::core::{FP16x16, FixedTrait}; + +fn X() -> Tensor{ + +let mut shape = ArrayTrait::new(); +shape.append(11); +let mut data = ArrayTrait::new(); +data.append(FixedTrait::new(327680, false)); +data.append(FixedTrait::new(311642, false)); +data.append(FixedTrait::new(265098, false)); +data.append(FixedTrait::new(192605, false)); +data.append(FixedTrait::new(101258, false)); +data.append(FixedTrait::new(0, false)); +data.append(FixedTrait::new(101258, true)); +data.append(FixedTrait::new(192605, true)); +data.append(FixedTrait::new(265098, true)); +data.append(FixedTrait::new(311642, true)); +data.append(FixedTrait::new(327680, true)); +let tensor = TensorTrait::::new(shape.span(), data.span()); +return tensor; +} \ No newline at end of file diff --git a/basic/verifiable_Lagrange_interpolation/notebooks/src/generated/Y.cairo b/basic/verifiable_Lagrange_interpolation/notebooks/src/generated/Y.cairo new file mode 100644 index 0000000..d77e21b --- /dev/null +++ b/basic/verifiable_Lagrange_interpolation/notebooks/src/generated/Y.cairo @@ -0,0 +1,24 @@ +use array::{ArrayTrait, SpanTrait}; +use orion::operators::tensor::{core::{Tensor, TensorTrait}}; +use orion::operators::tensor::FP16x16Tensor; +use orion::numbers::fixed_point::implementations::fp16x16::core::{FP16x16, FixedTrait}; + +fn Y() -> Tensor{ + +let mut shape = ArrayTrait::new(); +shape.append(11); +let mut data = ArrayTrait::new(); +data.append(FixedTrait::new(2520, false)); +data.append(FixedTrait::new(2775, false)); +data.append(FixedTrait::new(3774, false)); +data.append(FixedTrait::new(6800, false)); +data.append(FixedTrait::new(19347, false)); +data.append(FixedTrait::new(65536, false)); +data.append(FixedTrait::new(19347, false)); +data.append(FixedTrait::new(6800, false)); +data.append(FixedTrait::new(3774, false)); +data.append(FixedTrait::new(2775, false)); +data.append(FixedTrait::new(2520, false)); +let tensor = TensorTrait::::new(shape.span(), data.span()); +return tensor; +} \ No newline at end of file diff --git a/basic/verifiable_Lagrange_interpolation/notebooks/src/generated/x.cairo b/basic/verifiable_Lagrange_interpolation/notebooks/src/generated/x.cairo new file mode 100644 index 0000000..49b7424 --- /dev/null +++ b/basic/verifiable_Lagrange_interpolation/notebooks/src/generated/x.cairo @@ -0,0 +1,113 @@ +use array::{ArrayTrait, SpanTrait}; +use orion::operators::tensor::{core::{Tensor, TensorTrait}}; +use orion::operators::tensor::FP16x16Tensor; +use orion::numbers::fixed_point::implementations::fp16x16::core::{FP16x16, FixedTrait}; + +fn x() -> Tensor{ + +let mut shape = ArrayTrait::new(); +shape.append(100); +let mut data = ArrayTrait::new(); +data.append(FixedTrait::new(327680, true)); +data.append(FixedTrait::new(321060, true)); +data.append(FixedTrait::new(314440, true)); +data.append(FixedTrait::new(307820, true)); +data.append(FixedTrait::new(301200, true)); +data.append(FixedTrait::new(294581, true)); +data.append(FixedTrait::new(287961, true)); +data.append(FixedTrait::new(281341, true)); +data.append(FixedTrait::new(274721, true)); +data.append(FixedTrait::new(268101, true)); +data.append(FixedTrait::new(261482, true)); +data.append(FixedTrait::new(254862, true)); +data.append(FixedTrait::new(248242, true)); +data.append(FixedTrait::new(241622, true)); +data.append(FixedTrait::new(235002, true)); +data.append(FixedTrait::new(228383, true)); +data.append(FixedTrait::new(221763, true)); +data.append(FixedTrait::new(215143, true)); +data.append(FixedTrait::new(208523, true)); +data.append(FixedTrait::new(201903, true)); +data.append(FixedTrait::new(195284, true)); +data.append(FixedTrait::new(188664, true)); +data.append(FixedTrait::new(182044, true)); +data.append(FixedTrait::new(175424, true)); +data.append(FixedTrait::new(168804, true)); +data.append(FixedTrait::new(162185, true)); +data.append(FixedTrait::new(155565, true)); +data.append(FixedTrait::new(148945, true)); +data.append(FixedTrait::new(142325, true)); +data.append(FixedTrait::new(135705, true)); +data.append(FixedTrait::new(129086, true)); +data.append(FixedTrait::new(122466, true)); +data.append(FixedTrait::new(115846, true)); +data.append(FixedTrait::new(109226, true)); +data.append(FixedTrait::new(102606, true)); +data.append(FixedTrait::new(95987, true)); +data.append(FixedTrait::new(89367, true)); +data.append(FixedTrait::new(82747, true)); +data.append(FixedTrait::new(76127, true)); +data.append(FixedTrait::new(69507, true)); +data.append(FixedTrait::new(62888, true)); +data.append(FixedTrait::new(56268, true)); +data.append(FixedTrait::new(49648, true)); +data.append(FixedTrait::new(43028, true)); +data.append(FixedTrait::new(36408, true)); +data.append(FixedTrait::new(29789, true)); +data.append(FixedTrait::new(23169, true)); +data.append(FixedTrait::new(16549, true)); +data.append(FixedTrait::new(9929, true)); +data.append(FixedTrait::new(3309, true)); +data.append(FixedTrait::new(3309, false)); +data.append(FixedTrait::new(9929, false)); +data.append(FixedTrait::new(16549, false)); +data.append(FixedTrait::new(23169, false)); +data.append(FixedTrait::new(29789, false)); +data.append(FixedTrait::new(36408, false)); +data.append(FixedTrait::new(43028, false)); +data.append(FixedTrait::new(49648, false)); +data.append(FixedTrait::new(56268, false)); +data.append(FixedTrait::new(62888, false)); +data.append(FixedTrait::new(69507, false)); +data.append(FixedTrait::new(76127, false)); +data.append(FixedTrait::new(82747, false)); +data.append(FixedTrait::new(89367, false)); +data.append(FixedTrait::new(95987, false)); +data.append(FixedTrait::new(102606, false)); +data.append(FixedTrait::new(109226, false)); +data.append(FixedTrait::new(115846, false)); +data.append(FixedTrait::new(122466, false)); +data.append(FixedTrait::new(129086, false)); +data.append(FixedTrait::new(135705, false)); +data.append(FixedTrait::new(142325, false)); +data.append(FixedTrait::new(148945, false)); +data.append(FixedTrait::new(155565, false)); +data.append(FixedTrait::new(162185, false)); +data.append(FixedTrait::new(168804, false)); +data.append(FixedTrait::new(175424, false)); +data.append(FixedTrait::new(182044, false)); +data.append(FixedTrait::new(188664, false)); +data.append(FixedTrait::new(195284, false)); +data.append(FixedTrait::new(201903, false)); +data.append(FixedTrait::new(208523, false)); +data.append(FixedTrait::new(215143, false)); +data.append(FixedTrait::new(221763, false)); +data.append(FixedTrait::new(228383, false)); +data.append(FixedTrait::new(235002, false)); +data.append(FixedTrait::new(241622, false)); +data.append(FixedTrait::new(248242, false)); +data.append(FixedTrait::new(254862, false)); +data.append(FixedTrait::new(261482, false)); +data.append(FixedTrait::new(268101, false)); +data.append(FixedTrait::new(274721, false)); +data.append(FixedTrait::new(281341, false)); +data.append(FixedTrait::new(287961, false)); +data.append(FixedTrait::new(294581, false)); +data.append(FixedTrait::new(301200, false)); +data.append(FixedTrait::new(307820, false)); +data.append(FixedTrait::new(314440, false)); +data.append(FixedTrait::new(321060, false)); +data.append(FixedTrait::new(327680, false)); +let tensor = TensorTrait::::new(shape.span(), data.span()); +return tensor; +} \ No newline at end of file diff --git a/basic/verifiable_Lagrange_interpolation/notebooks/src/generated/y.cairo b/basic/verifiable_Lagrange_interpolation/notebooks/src/generated/y.cairo new file mode 100644 index 0000000..dc2ff99 --- /dev/null +++ b/basic/verifiable_Lagrange_interpolation/notebooks/src/generated/y.cairo @@ -0,0 +1,113 @@ +use array::{ArrayTrait, SpanTrait}; +use orion::operators::tensor::{core::{Tensor, TensorTrait}}; +use orion::operators::tensor::FP16x16Tensor; +use orion::numbers::fixed_point::implementations::fp16x16::core::{FP16x16, FixedTrait}; + +fn y() -> Tensor{ + +let mut shape = ArrayTrait::new(); +shape.append(100); +let mut data = ArrayTrait::new(); +data.append(FixedTrait::new(2520, false)); +data.append(FixedTrait::new(3257, false)); +data.append(FixedTrait::new(3016, false)); +data.append(FixedTrait::new(2402, false)); +data.append(FixedTrait::new(1805, false)); +data.append(FixedTrait::new(1449, false)); +data.append(FixedTrait::new(1436, false)); +data.append(FixedTrait::new(1778, false)); +data.append(FixedTrait::new(2433, false)); +data.append(FixedTrait::new(3321, false)); +data.append(FixedTrait::new(4344, false)); +data.append(FixedTrait::new(5402, false)); +data.append(FixedTrait::new(6402, false)); +data.append(FixedTrait::new(7267, false)); +data.append(FixedTrait::new(7934, false)); +data.append(FixedTrait::new(8366, false)); +data.append(FixedTrait::new(8544, false)); +data.append(FixedTrait::new(8473, false)); +data.append(FixedTrait::new(8176, false)); +data.append(FixedTrait::new(7692, false)); +data.append(FixedTrait::new(7074, false)); +data.append(FixedTrait::new(6385, false)); +data.append(FixedTrait::new(5696, false)); +data.append(FixedTrait::new(5080, false)); +data.append(FixedTrait::new(4609, false)); +data.append(FixedTrait::new(4354, false)); +data.append(FixedTrait::new(4376, false)); +data.append(FixedTrait::new(4732, false)); +data.append(FixedTrait::new(5465, false)); +data.append(FixedTrait::new(6608, false)); +data.append(FixedTrait::new(8179, false)); +data.append(FixedTrait::new(10184, false)); +data.append(FixedTrait::new(12615, false)); +data.append(FixedTrait::new(15450, false)); +data.append(FixedTrait::new(18654, false)); +data.append(FixedTrait::new(22179, false)); +data.append(FixedTrait::new(25970, false)); +data.append(FixedTrait::new(29959, false)); +data.append(FixedTrait::new(34073, false)); +data.append(FixedTrait::new(38233, false)); +data.append(FixedTrait::new(42359, false)); +data.append(FixedTrait::new(46366, false)); +data.append(FixedTrait::new(50173, false)); +data.append(FixedTrait::new(53702, false)); +data.append(FixedTrait::new(56878, false)); +data.append(FixedTrait::new(59635, false)); +data.append(FixedTrait::new(61914, false)); +data.append(FixedTrait::new(63668, false)); +data.append(FixedTrait::new(64858, false)); +data.append(FixedTrait::new(65460, false)); +data.append(FixedTrait::new(65460, false)); +data.append(FixedTrait::new(64858, false)); +data.append(FixedTrait::new(63668, false)); +data.append(FixedTrait::new(61914, false)); +data.append(FixedTrait::new(59635, false)); +data.append(FixedTrait::new(56878, false)); +data.append(FixedTrait::new(53702, false)); +data.append(FixedTrait::new(50173, false)); +data.append(FixedTrait::new(46366, false)); +data.append(FixedTrait::new(42359, false)); +data.append(FixedTrait::new(38233, false)); +data.append(FixedTrait::new(34073, false)); +data.append(FixedTrait::new(29959, false)); +data.append(FixedTrait::new(25970, false)); +data.append(FixedTrait::new(22179, false)); +data.append(FixedTrait::new(18654, false)); +data.append(FixedTrait::new(15450, false)); +data.append(FixedTrait::new(12615, false)); +data.append(FixedTrait::new(10184, false)); +data.append(FixedTrait::new(8179, false)); +data.append(FixedTrait::new(6608, false)); +data.append(FixedTrait::new(5465, false)); +data.append(FixedTrait::new(4732, false)); +data.append(FixedTrait::new(4376, false)); +data.append(FixedTrait::new(4354, false)); +data.append(FixedTrait::new(4609, false)); +data.append(FixedTrait::new(5080, false)); +data.append(FixedTrait::new(5696, false)); +data.append(FixedTrait::new(6385, false)); +data.append(FixedTrait::new(7074, false)); +data.append(FixedTrait::new(7692, false)); +data.append(FixedTrait::new(8176, false)); +data.append(FixedTrait::new(8473, false)); +data.append(FixedTrait::new(8544, false)); +data.append(FixedTrait::new(8366, false)); +data.append(FixedTrait::new(7934, false)); +data.append(FixedTrait::new(7267, false)); +data.append(FixedTrait::new(6402, false)); +data.append(FixedTrait::new(5402, false)); +data.append(FixedTrait::new(4344, false)); +data.append(FixedTrait::new(3321, false)); +data.append(FixedTrait::new(2433, false)); +data.append(FixedTrait::new(1778, false)); +data.append(FixedTrait::new(1436, false)); +data.append(FixedTrait::new(1449, false)); +data.append(FixedTrait::new(1805, false)); +data.append(FixedTrait::new(2402, false)); +data.append(FixedTrait::new(3016, false)); +data.append(FixedTrait::new(3257, false)); +data.append(FixedTrait::new(2520, false)); +let tensor = TensorTrait::::new(shape.span(), data.span()); +return tensor; +} \ No newline at end of file diff --git a/basic/verifiable_Lagrange_interpolation/src/generated.cairo b/basic/verifiable_Lagrange_interpolation/src/generated.cairo new file mode 100644 index 0000000..07d152d --- /dev/null +++ b/basic/verifiable_Lagrange_interpolation/src/generated.cairo @@ -0,0 +1,4 @@ +mod X; +mod Y; +mod x; +mod y; diff --git a/basic/verifiable_Lagrange_interpolation/src/generated/X.cairo b/basic/verifiable_Lagrange_interpolation/src/generated/X.cairo new file mode 100644 index 0000000..b89d502 --- /dev/null +++ b/basic/verifiable_Lagrange_interpolation/src/generated/X.cairo @@ -0,0 +1,24 @@ +use array::{ArrayTrait, SpanTrait}; +use orion::operators::tensor::{core::{Tensor, TensorTrait}}; +use orion::operators::tensor::FP16x16Tensor; +use orion::numbers::fixed_point::implementations::fp16x16::core::{FP16x16, FixedTrait}; + +fn X() -> Tensor{ + +let mut shape = ArrayTrait::new(); +shape.append(11); +let mut data = ArrayTrait::new(); +data.append(FixedTrait::new(327680, false)); +data.append(FixedTrait::new(311642, false)); +data.append(FixedTrait::new(265098, false)); +data.append(FixedTrait::new(192605, false)); +data.append(FixedTrait::new(101258, false)); +data.append(FixedTrait::new(0, false)); +data.append(FixedTrait::new(101258, true)); +data.append(FixedTrait::new(192605, true)); +data.append(FixedTrait::new(265098, true)); +data.append(FixedTrait::new(311642, true)); +data.append(FixedTrait::new(327680, true)); +let tensor = TensorTrait::::new(shape.span(), data.span()); +return tensor; +} \ No newline at end of file diff --git a/basic/verifiable_Lagrange_interpolation/src/generated/Y.cairo b/basic/verifiable_Lagrange_interpolation/src/generated/Y.cairo new file mode 100644 index 0000000..d77e21b --- /dev/null +++ b/basic/verifiable_Lagrange_interpolation/src/generated/Y.cairo @@ -0,0 +1,24 @@ +use array::{ArrayTrait, SpanTrait}; +use orion::operators::tensor::{core::{Tensor, TensorTrait}}; +use orion::operators::tensor::FP16x16Tensor; +use orion::numbers::fixed_point::implementations::fp16x16::core::{FP16x16, FixedTrait}; + +fn Y() -> Tensor{ + +let mut shape = ArrayTrait::new(); +shape.append(11); +let mut data = ArrayTrait::new(); +data.append(FixedTrait::new(2520, false)); +data.append(FixedTrait::new(2775, false)); +data.append(FixedTrait::new(3774, false)); +data.append(FixedTrait::new(6800, false)); +data.append(FixedTrait::new(19347, false)); +data.append(FixedTrait::new(65536, false)); +data.append(FixedTrait::new(19347, false)); +data.append(FixedTrait::new(6800, false)); +data.append(FixedTrait::new(3774, false)); +data.append(FixedTrait::new(2775, false)); +data.append(FixedTrait::new(2520, false)); +let tensor = TensorTrait::::new(shape.span(), data.span()); +return tensor; +} \ No newline at end of file diff --git a/basic/verifiable_Lagrange_interpolation/src/generated/x.cairo b/basic/verifiable_Lagrange_interpolation/src/generated/x.cairo new file mode 100644 index 0000000..49b7424 --- /dev/null +++ b/basic/verifiable_Lagrange_interpolation/src/generated/x.cairo @@ -0,0 +1,113 @@ +use array::{ArrayTrait, SpanTrait}; +use orion::operators::tensor::{core::{Tensor, TensorTrait}}; +use orion::operators::tensor::FP16x16Tensor; +use orion::numbers::fixed_point::implementations::fp16x16::core::{FP16x16, FixedTrait}; + +fn x() -> Tensor{ + +let mut shape = ArrayTrait::new(); +shape.append(100); +let mut data = ArrayTrait::new(); +data.append(FixedTrait::new(327680, true)); +data.append(FixedTrait::new(321060, true)); +data.append(FixedTrait::new(314440, true)); +data.append(FixedTrait::new(307820, true)); +data.append(FixedTrait::new(301200, true)); +data.append(FixedTrait::new(294581, true)); +data.append(FixedTrait::new(287961, true)); +data.append(FixedTrait::new(281341, true)); +data.append(FixedTrait::new(274721, true)); +data.append(FixedTrait::new(268101, true)); +data.append(FixedTrait::new(261482, true)); +data.append(FixedTrait::new(254862, true)); +data.append(FixedTrait::new(248242, true)); +data.append(FixedTrait::new(241622, true)); +data.append(FixedTrait::new(235002, true)); +data.append(FixedTrait::new(228383, true)); +data.append(FixedTrait::new(221763, true)); +data.append(FixedTrait::new(215143, true)); +data.append(FixedTrait::new(208523, true)); +data.append(FixedTrait::new(201903, true)); +data.append(FixedTrait::new(195284, true)); +data.append(FixedTrait::new(188664, true)); +data.append(FixedTrait::new(182044, true)); +data.append(FixedTrait::new(175424, true)); +data.append(FixedTrait::new(168804, true)); +data.append(FixedTrait::new(162185, true)); +data.append(FixedTrait::new(155565, true)); +data.append(FixedTrait::new(148945, true)); +data.append(FixedTrait::new(142325, true)); +data.append(FixedTrait::new(135705, true)); +data.append(FixedTrait::new(129086, true)); +data.append(FixedTrait::new(122466, true)); +data.append(FixedTrait::new(115846, true)); +data.append(FixedTrait::new(109226, true)); +data.append(FixedTrait::new(102606, true)); +data.append(FixedTrait::new(95987, true)); +data.append(FixedTrait::new(89367, true)); +data.append(FixedTrait::new(82747, true)); +data.append(FixedTrait::new(76127, true)); +data.append(FixedTrait::new(69507, true)); +data.append(FixedTrait::new(62888, true)); +data.append(FixedTrait::new(56268, true)); +data.append(FixedTrait::new(49648, true)); +data.append(FixedTrait::new(43028, true)); +data.append(FixedTrait::new(36408, true)); +data.append(FixedTrait::new(29789, true)); +data.append(FixedTrait::new(23169, true)); +data.append(FixedTrait::new(16549, true)); +data.append(FixedTrait::new(9929, true)); +data.append(FixedTrait::new(3309, true)); +data.append(FixedTrait::new(3309, false)); +data.append(FixedTrait::new(9929, false)); +data.append(FixedTrait::new(16549, false)); +data.append(FixedTrait::new(23169, false)); +data.append(FixedTrait::new(29789, false)); +data.append(FixedTrait::new(36408, false)); +data.append(FixedTrait::new(43028, false)); +data.append(FixedTrait::new(49648, false)); +data.append(FixedTrait::new(56268, false)); +data.append(FixedTrait::new(62888, false)); +data.append(FixedTrait::new(69507, false)); +data.append(FixedTrait::new(76127, false)); +data.append(FixedTrait::new(82747, false)); +data.append(FixedTrait::new(89367, false)); +data.append(FixedTrait::new(95987, false)); +data.append(FixedTrait::new(102606, false)); +data.append(FixedTrait::new(109226, false)); +data.append(FixedTrait::new(115846, false)); +data.append(FixedTrait::new(122466, false)); +data.append(FixedTrait::new(129086, false)); +data.append(FixedTrait::new(135705, false)); +data.append(FixedTrait::new(142325, false)); +data.append(FixedTrait::new(148945, false)); +data.append(FixedTrait::new(155565, false)); +data.append(FixedTrait::new(162185, false)); +data.append(FixedTrait::new(168804, false)); +data.append(FixedTrait::new(175424, false)); +data.append(FixedTrait::new(182044, false)); +data.append(FixedTrait::new(188664, false)); +data.append(FixedTrait::new(195284, false)); +data.append(FixedTrait::new(201903, false)); +data.append(FixedTrait::new(208523, false)); +data.append(FixedTrait::new(215143, false)); +data.append(FixedTrait::new(221763, false)); +data.append(FixedTrait::new(228383, false)); +data.append(FixedTrait::new(235002, false)); +data.append(FixedTrait::new(241622, false)); +data.append(FixedTrait::new(248242, false)); +data.append(FixedTrait::new(254862, false)); +data.append(FixedTrait::new(261482, false)); +data.append(FixedTrait::new(268101, false)); +data.append(FixedTrait::new(274721, false)); +data.append(FixedTrait::new(281341, false)); +data.append(FixedTrait::new(287961, false)); +data.append(FixedTrait::new(294581, false)); +data.append(FixedTrait::new(301200, false)); +data.append(FixedTrait::new(307820, false)); +data.append(FixedTrait::new(314440, false)); +data.append(FixedTrait::new(321060, false)); +data.append(FixedTrait::new(327680, false)); +let tensor = TensorTrait::::new(shape.span(), data.span()); +return tensor; +} \ No newline at end of file diff --git a/basic/verifiable_Lagrange_interpolation/src/generated/y.cairo b/basic/verifiable_Lagrange_interpolation/src/generated/y.cairo new file mode 100644 index 0000000..dc2ff99 --- /dev/null +++ b/basic/verifiable_Lagrange_interpolation/src/generated/y.cairo @@ -0,0 +1,113 @@ +use array::{ArrayTrait, SpanTrait}; +use orion::operators::tensor::{core::{Tensor, TensorTrait}}; +use orion::operators::tensor::FP16x16Tensor; +use orion::numbers::fixed_point::implementations::fp16x16::core::{FP16x16, FixedTrait}; + +fn y() -> Tensor{ + +let mut shape = ArrayTrait::new(); +shape.append(100); +let mut data = ArrayTrait::new(); +data.append(FixedTrait::new(2520, false)); +data.append(FixedTrait::new(3257, false)); +data.append(FixedTrait::new(3016, false)); +data.append(FixedTrait::new(2402, false)); +data.append(FixedTrait::new(1805, false)); +data.append(FixedTrait::new(1449, false)); +data.append(FixedTrait::new(1436, false)); +data.append(FixedTrait::new(1778, false)); +data.append(FixedTrait::new(2433, false)); +data.append(FixedTrait::new(3321, false)); +data.append(FixedTrait::new(4344, false)); +data.append(FixedTrait::new(5402, false)); +data.append(FixedTrait::new(6402, false)); +data.append(FixedTrait::new(7267, false)); +data.append(FixedTrait::new(7934, false)); +data.append(FixedTrait::new(8366, false)); +data.append(FixedTrait::new(8544, false)); +data.append(FixedTrait::new(8473, false)); +data.append(FixedTrait::new(8176, false)); +data.append(FixedTrait::new(7692, false)); +data.append(FixedTrait::new(7074, false)); +data.append(FixedTrait::new(6385, false)); +data.append(FixedTrait::new(5696, false)); +data.append(FixedTrait::new(5080, false)); +data.append(FixedTrait::new(4609, false)); +data.append(FixedTrait::new(4354, false)); +data.append(FixedTrait::new(4376, false)); +data.append(FixedTrait::new(4732, false)); +data.append(FixedTrait::new(5465, false)); +data.append(FixedTrait::new(6608, false)); +data.append(FixedTrait::new(8179, false)); +data.append(FixedTrait::new(10184, false)); +data.append(FixedTrait::new(12615, false)); +data.append(FixedTrait::new(15450, false)); +data.append(FixedTrait::new(18654, false)); +data.append(FixedTrait::new(22179, false)); +data.append(FixedTrait::new(25970, false)); +data.append(FixedTrait::new(29959, false)); +data.append(FixedTrait::new(34073, false)); +data.append(FixedTrait::new(38233, false)); +data.append(FixedTrait::new(42359, false)); +data.append(FixedTrait::new(46366, false)); +data.append(FixedTrait::new(50173, false)); +data.append(FixedTrait::new(53702, false)); +data.append(FixedTrait::new(56878, false)); +data.append(FixedTrait::new(59635, false)); +data.append(FixedTrait::new(61914, false)); +data.append(FixedTrait::new(63668, false)); +data.append(FixedTrait::new(64858, false)); +data.append(FixedTrait::new(65460, false)); +data.append(FixedTrait::new(65460, false)); +data.append(FixedTrait::new(64858, false)); +data.append(FixedTrait::new(63668, false)); +data.append(FixedTrait::new(61914, false)); +data.append(FixedTrait::new(59635, false)); +data.append(FixedTrait::new(56878, false)); +data.append(FixedTrait::new(53702, false)); +data.append(FixedTrait::new(50173, false)); +data.append(FixedTrait::new(46366, false)); +data.append(FixedTrait::new(42359, false)); +data.append(FixedTrait::new(38233, false)); +data.append(FixedTrait::new(34073, false)); +data.append(FixedTrait::new(29959, false)); +data.append(FixedTrait::new(25970, false)); +data.append(FixedTrait::new(22179, false)); +data.append(FixedTrait::new(18654, false)); +data.append(FixedTrait::new(15450, false)); +data.append(FixedTrait::new(12615, false)); +data.append(FixedTrait::new(10184, false)); +data.append(FixedTrait::new(8179, false)); +data.append(FixedTrait::new(6608, false)); +data.append(FixedTrait::new(5465, false)); +data.append(FixedTrait::new(4732, false)); +data.append(FixedTrait::new(4376, false)); +data.append(FixedTrait::new(4354, false)); +data.append(FixedTrait::new(4609, false)); +data.append(FixedTrait::new(5080, false)); +data.append(FixedTrait::new(5696, false)); +data.append(FixedTrait::new(6385, false)); +data.append(FixedTrait::new(7074, false)); +data.append(FixedTrait::new(7692, false)); +data.append(FixedTrait::new(8176, false)); +data.append(FixedTrait::new(8473, false)); +data.append(FixedTrait::new(8544, false)); +data.append(FixedTrait::new(8366, false)); +data.append(FixedTrait::new(7934, false)); +data.append(FixedTrait::new(7267, false)); +data.append(FixedTrait::new(6402, false)); +data.append(FixedTrait::new(5402, false)); +data.append(FixedTrait::new(4344, false)); +data.append(FixedTrait::new(3321, false)); +data.append(FixedTrait::new(2433, false)); +data.append(FixedTrait::new(1778, false)); +data.append(FixedTrait::new(1436, false)); +data.append(FixedTrait::new(1449, false)); +data.append(FixedTrait::new(1805, false)); +data.append(FixedTrait::new(2402, false)); +data.append(FixedTrait::new(3016, false)); +data.append(FixedTrait::new(3257, false)); +data.append(FixedTrait::new(2520, false)); +let tensor = TensorTrait::::new(shape.span(), data.span()); +return tensor; +} \ No newline at end of file diff --git a/basic/verifiable_Lagrange_interpolation/src/helper.cairo b/basic/verifiable_Lagrange_interpolation/src/helper.cairo new file mode 100644 index 0000000..4561b02 --- /dev/null +++ b/basic/verifiable_Lagrange_interpolation/src/helper.cairo @@ -0,0 +1,56 @@ +use traits::TryInto; +use alexandria_data_structures::array_ext::{SpanTraitExt}; +use array::{ArrayTrait, SpanTrait}; +use orion::operators::tensor::{Tensor, TensorTrait}; +use orion::numbers::fixed_point::{core::{FixedTrait}}; + +use orion::operators::tensor::{FP16x16Tensor, FP16x16TensorDiv}; +use orion::numbers::fixed_point::implementations::fp16x16::core::{ + FP16x16, FP16x16Impl, FP16x16Add, FP16x16AddEq, FP16x16Sub, FP16x16Mul, FP16x16MulEq, + FP16x16TryIntoU128, FP16x16PartialEq, FP16x16PartialOrd, FP16x16SubEq, FP16x16Neg, FP16x16Div, + FP16x16IntoFelt252, FP16x16Print, HALF +}; + +use orion::numbers::fixed_point::implementations::fp16x16::math::trig; + +fn lagrange_interpolation(x_interpolated: @Tensor, X: @Tensor, Y: @Tensor) -> Tensor { + + let n = ((*X).data.len()); + let m = ((*x_interpolated).data.len()); + + let mut y_data = ArrayTrait::::new(); + let mut phi = ArrayTrait::::new(); + let mut j = 0; + + loop { + if j == m { + break; + } + let mut y_j = FixedTrait::new(0,false); + let mut i = 0; + loop { + if i == n { + break; + } + let mut phi_i = FixedTrait::::new(65536,false); + let mut k = 0; + loop { + if k == n { + break; + } + if i != k { + phi_i = phi_i * (*(*x_interpolated).data.at(j) - *(*X).data.at(k))/( *(*X).data.at(i) - *(*X).data.at(k)) + } + k += 1; + }; + y_j = y_j + *(*Y).data.at(i) * phi_i; + i += 1; + }; + y_data.append(y_j); + j += 1; + + }; + + return TensorTrait::new((*x_interpolated).shape, y_data.span()); + +} diff --git a/basic/verifiable_Lagrange_interpolation/src/lib.cairo b/basic/verifiable_Lagrange_interpolation/src/lib.cairo new file mode 100644 index 0000000..f62e84b --- /dev/null +++ b/basic/verifiable_Lagrange_interpolation/src/lib.cairo @@ -0,0 +1,3 @@ +mod generated; +mod helper; +mod test; diff --git a/basic/verifiable_Lagrange_interpolation/src/test.cairo b/basic/verifiable_Lagrange_interpolation/src/test.cairo new file mode 100644 index 0000000..1f5972a --- /dev/null +++ b/basic/verifiable_Lagrange_interpolation/src/test.cairo @@ -0,0 +1,45 @@ +#[cfg(test)] +mod tests { + use traits::TryInto; + use alexandria_data_structures::array_ext::{SpanTraitExt}; + use array::{ArrayTrait, SpanTrait}; + use orion::operators::tensor::{Tensor, TensorTrait}; + use orion::numbers::fixed_point::{core::{FixedTrait}}; + + use orion::operators::tensor::{FP16x16Tensor, FP16x16TensorDiv, FP16x16TensorSub}; + + use orion::numbers::fixed_point::implementations::fp16x16::core::{ + FP16x16, FP16x16Impl, FP16x16Add, FP16x16AddEq, FP16x16Sub, FP16x16Mul, FP16x16MulEq, + FP16x16TryIntoU128, FP16x16PartialEq, FP16x16PartialOrd, FP16x16SubEq, FP16x16Neg, + FP16x16Div, FP16x16IntoFelt252, FP16x16Print + }; + + use lagrange::helper::lagrange_interpolation; + use lagrange::generated::{X::X, Y::Y, x::x, y::y}; + + #[test] + #[available_gas(99999999999999999)] + fn lagrange_test() { + let tol = FixedTrait::::new(655, false); // 655 is 0.01 = 1e-2 + let max_iter = 500_usize; + + // Nodes : + let X = X(); + let Y = Y(); + + let x = x(); + let y_expected = y(); + + let y_actual = lagrange_interpolation(@x, @X, @Y); + + let mut i = 0; + loop { + if i == y_expected.data.len() { + break; + } + + assert(*y_expected.data.at(i) - *y_actual.data.at(i) < tol, 'difference below threshold'); + i += 1; + } + } +} diff --git a/basic/verifiable_Lagrange_interpolation/tutorial/LagrangeInterpolationTutorial.md b/basic/verifiable_Lagrange_interpolation/tutorial/LagrangeInterpolationTutorial.md new file mode 100755 index 0000000..38f8874 --- /dev/null +++ b/basic/verifiable_Lagrange_interpolation/tutorial/LagrangeInterpolationTutorial.md @@ -0,0 +1,350 @@ +# **Verifiable Lagrange interpolation** + +Lagrange interpolation is a mathematical technique used to approximate a function that passes through a given set of points. It takes an input set of data points and computes a polynomial that passes through all of them. + +Given a set of $n+1$ data points (or interpolation nodes) $X_0, X_1, ..., X_n$ with corresponding function values $Y_0, Y_1, ..., Y_n$, Lagrange interpolation seeks to find a polynomial of degree at most $n$ that passes through all these points. + +Below, we provide a brief review of the implementation of a Lagrange interpolation in Python, which we will then convert to Cairo to transform it into a verifiable ZKML (Lagrange interpolation), using the Orion library. + +Content overview: + +1. [Lagrange interpolation with Python:](LagrangeInterpolationTutorial.md#used-dataset) We start with the basic implementation of Lagrange interpolation using Python. +2. [Convert your model to Cairo:](LagrangeInterpolationTutorial.md#convert-your-model-to-cairo) In the subsequent stage, we will create a new scarb project and replicate our model to Cairo which is a language for creating STARK-provable programs. +3. [Implementing Lagrange interpolation using Orion:](LagrangeInterpolationTutorial.md#implementing-lagrange-using-orion) To catalyze our development process, we will use the Orion Framework to construct the key functions to build our verifiable Lagrange interpolation. + +### Used DataSet + +In this tutorial, we will interpolate the Runge function, that we will define $f$ and uses the Chebyshev node as interpolation nodes. + +```python +import numpy as np +import math +import matplotlib.pyplot as plt + +# Runge function +def f(x): + return 1 / (x**2 + 1) + +# Chebyshev nodes +X = np.array( [5*math.cos(k*math.pi/10) for k in range(0,11)] ) + +Y = f(X) + +``` + + +### Implementation of Lagrange interpolating polynomial + +Now we will implement Lagrange interpolation function in python. + +Given a set of $n+1$ data points $X_0, X_1, ..., X_n$ with corresponding function values $Y_0, Y_1, ..., Y_n$, the Lagrange interpolating polynomial is + +$$ + L(x) = \sum_{i=0}^n Y_i \phi_i(x), +$$ + +where $\phi_i(x)$ is the $i$-th Lagrange polynomial defined by +$$ + \phi_i(x) = \frac{(x - X_0)\ldots (x - X_{i-1})(x - X_{i+1})\ldots(x - X_n)}{(X_i - X_0)\ldots (X_i - X_{i-1})(X_i - X_{i+1})\ldots(X_i - X_n)} + +$$ + +```python +def lagrange(x,X,Y): + + n = min(len(X),len(Y)) + m = len(x) + yh = np.zeros(m) + phi = np.zeros(n) + for j in range(m): + yh[j] = 0 + for i in range(n): + phi[i] = 1.0 + for k in range(n): + if i != k: + phi[i] = phi[i]*(x[j]-X[k])/(X[i]-X[k]) + yh[j] = yh[j] + Y[i] * phi[i] + return yh +``` + + + + +## Visualization of the interpolation + + +```python +x = np.linspace(-5,5,num=100) +fx = f(x) +y = lagrange(x,X,Y) + +fig, ax = plt.subplots() +ax.set_title('Lagrange interpolation of Runge function', fontsize = 15) +ax.plot(x,fx,label='Runge function') +ax.plot(x,y,label='Lagrange interpolation') +plt.legend() +plt.show() +``` + +
+ + +## Convert your model to Cairo + +Now that we have a good understanding of the Lagrange interpolation, we will replicate the entire algorithm in Cairo to make it fully verifiable. Since we will be rebuilding the algorithm from scratch, this will be a good opportunity to get acquainted with Orion's built-in functions and the operators that make the transition to Cairo seamless. + +### Create a new Scarb project + +Scarb is the Cairo package manager specifically created to streamline our Cairo and Starknet development process. Scarb will typically manage project dependencies, the compilation process (both pure Cairo and Starknet contracts), downloading and building external libraries to accelerate our development with Orion.You can find all information about Scarb and Cairo installation [here](https://orion.gizatech.xyz/v/develop/framework/get-started#installations). + +To create a new Scarb project, open your terminal and run: + +``` +scarb new verifiable_Lagrange_interpolation +``` + +A new project folder will be created for you and make sure to replace the content in Scarb.toml file with the following code: + +```toml +[package] +name = "scarb new verifiable_Lagrange_interpolation" +version = "0.1.0" + +[dependencies] +orion = { git = "https://github.com/gizatechxyz/orion.git", rev = "v0.1.9" } +``` + +### Gerating the dataset in Cairo + +Now let's generate the necessary files to begin our transition to Cairo. In our Jupyter Notebook, we will run the necessary code to convert our interpolation nodes and the interpolation result as fixed point tensors in Orion. + +```python +import os +``` + +```python +os.makedirs("src/generated", exist_ok=True) +``` + +```python +tensor_name = ["X","Y","x","y"] + +def generate_cairo_files(data, name): + + with open(os.path.join('src', 'generated', f"{name}.cairo"), "w") as f: + f.write( + "use array::{ArrayTrait, SpanTrait};\n" + + "use orion::operators::tensor::{core::{Tensor, TensorTrait}};\n" + + "use orion::operators::tensor::FP16x16Tensor;\n" + + "use orion::numbers::fixed_point::implementations::fp16x16::core::{FP16x16, FixedTrait};\n" + + "\n" + f"fn {name}() -> Tensor" + "{\n\n" + + "let mut shape = ArrayTrait::new();\n" + ) + for dim in data.shape: + f.write(f"shape.append({dim});\n") + + f.write("let mut data = ArrayTrait::new();\n") + for val in np.nditer(data.flatten()): + f.write(f"data.append(FixedTrait::new({abs(int(decimal_to_fp16x16(val)))}, {str(val < 0).lower()}));\n") + f.write( + "let tensor = TensorTrait::::new(shape.span(), data.span());\n" + + "return tensor;\n}" + ) + +with open(f"src/generated.cairo", "w") as f: + for n in tensor_name: + f.write(f"mod {n};\n") + +generate_cairo_files(X, "X") +generate_cairo_files(Y, "Y") + +generate_cairo_files(x, "x") +generate_cairo_files(y, "y") +``` + +The X, Y, x, y tensor values will now be generated under `src/generated` directory. + +In `src/lib.cairo` replace the content with the following code: + +```rust +mod generated; +mod helper; +mod test; +``` + +This will tell our compiler to include the separate modules listed above during the compilation of our code. We will be covering each module in detail in the following section, but let’s first review the generated folder files. + +```rust +use array::{ArrayTrait, SpanTrait}; +use orion::operators::tensor::{core::{Tensor, TensorTrait}}; +use orion::operators::tensor::FP16x16Tensor; +use orion::numbers::fixed_point::implementations::fp16x16::core::{FP16x16, FixedTrait}; + +fn X() -> Tensor{ + +let mut shape = ArrayTrait::new(); +shape.append(11); +let mut data = ArrayTrait::new(); +data.append(FixedTrait::new(327680, false)); +data.append(FixedTrait::new(311642, false)); +data.append(FixedTrait::new(265098, false)); +data.append(FixedTrait::new(192605, false)); +data.append(FixedTrait::new(101258, false)); +data.append(FixedTrait::new(0, false)); +data.append(FixedTrait::new(101258, true)); +data.append(FixedTrait::new(192605, true)); +data.append(FixedTrait::new(265098, true)); +data.append(FixedTrait::new(311642, true)); +data.append(FixedTrait::new(327680, true)); +let tensor = TensorTrait::::new(shape.span(), data.span()); +return tensor; +} +``` + +Since Cairo does not come with built-in fixed points we have to explicitly define it for our X, Y, x, y values. Luckily, this is already implemented in Orion for us as a struct as shown below: + + +```rust +// Example of a FP16x16. +struct FP16x16 { + mag: u32, + sign: bool +} +``` + +For this tutorial, we will use fixed point numbers FP16x16 where the magnitude represents the absolute value and the boolean indicates whether the number is negative or positive. In a 16x16 fixed-point format, there are 16 bits dedicated to the integer part of the number and 16 bits for the fractional part of the number. This format allows us to work with a wide range of values and a high degree of precision for conducting the Tensor operations. To replicate the Lagrange interpolation function, we will conduct our operations using FP16x16 Tensors which are also represented as a structs in Orion. + +```rust +struct Tensor { + shape: Span, + data: Span +} +``` + +A `Tensor` in Orion takes a shape and a span array of the data. + +## Implementing Lagrange interpolation using Orion + +At this stage, we will be reproducing the Lagrange interpolation function now that we have generated our X, Y, x, y Fixedpoint Tensors. We will begin by creating a separate file for our Lagrange interpolation function file named `helper.cairo` to host all of our core functions. + +### Lagrange interpolation function + +```rust +fn lagrange_interpolation(x_interpolated: @Tensor, X: @Tensor, Y: @Tensor) -> Tensor { + + let n = ((*X).data.len()); + let m = ((*x_interpolated).data.len()); + + let mut y_data = ArrayTrait::::new(); + let mut phi = ArrayTrait::::new(); + let mut j = 0; + + loop { + if j == m { + break; + } + let mut y_j = FixedTrait::new(0,true); + let mut i = 0; + loop { + if i == n { + break; + } + let mut phi_i = FixedTrait::::new(65536,true); + let mut k = 0; + loop { + if k == n { + break; + } + if i != k { + phi_i = phi_i * (*(*x_interpolated).data.at(j) - *(*X).data.at(k))/( *(*X).data.at(i) - *(*X).data.at(k)) + } + k += 1; + }; + y_j = y_j + *(*Y).data.at(i) * phi_i; + i += 1; + }; + y_data.append(y_j); + j += 1; + + }; + + return TensorTrait::new((*x_interpolated).shape, y_data.span()); + +} + +``` + + +### Testing the model + +Now that we have implemented the Lagrange interpolation function, we can finally test it. We begin by creating a new separate test file named `test.cairo` and import all the necessary Orion libraries, including our X, Y, x and y values found in the generated folder. We also import the Lagrange function from the `helper.cairo` file, as we will rely on them to construct the model. + +```rust +#[cfg(test)] +mod tests { + use traits::TryInto; + use alexandria_data_structures::array_ext::{SpanTraitExt}; + use array::{ArrayTrait, SpanTrait}; + use orion::operators::tensor::{Tensor, TensorTrait}; + use orion::numbers::fixed_point::{core::{FixedTrait}}; + + use orion::operators::tensor::{FP16x16Tensor, FP16x16TensorDiv, FP16x16TensorSub}; + + use orion::numbers::fixed_point::implementations::fp16x16::core::{ + FP16x16, FP16x16Impl, FP16x16Add, FP16x16AddEq, FP16x16Sub, FP16x16Mul, FP16x16MulEq, + FP16x16TryIntoU128, FP16x16PartialEq, FP16x16PartialOrd, FP16x16SubEq, FP16x16Neg, + FP16x16Div, FP16x16IntoFelt252, FP16x16Print + }; + + use lagrange::helper::lagrange_interpolation; + use lagrange::generated::{X::X, Y::Y, x::x, y::y}; + + #[test] + #[available_gas(99999999999999999)] + fn lagrange_test() { + let tol = FixedTrait::::new(655, false); // 655 is 0.01 = 1e-2 + let max_iter = 500_usize; + + // Nodes : + let X = X(); + let Y = Y(); + + let x = x(); + let y_expected = y(); + + let y_actual = lagrange_interpolation(@x, @X, @Y); + + let mut i = 0; + loop { + if i == y_expected.data.len() { + break; + } + + assert(*y_expected.data.at(i) - *y_actual.data.at(i) < tol, 'difference below threshold'); + i += 1; + } + } +} + +``` + +Our model will be tested using the `lagrange_test()` function, which will follow these steps: + +1. Data retrieval: The function starts by obtaining the X, Y nodes values and x, y test values coming from the generated folder. +2. Lagrange computation : computation of the y-values from the x-axis test values using the lagrange interpolation function on the nodes X and Y. + +Finally, we can execute the test file by running `scarb test` + +```shell +scarb test +testing lagrange ... +running 1 tests +test lagrange::test::tests::lagrange_test ... ok (gas usage est.: 672062330) +test result: ok. 1 passed; 0 failed; 0 ignored; 0 filtered out; +``` + +And as we can see our test cases have passed! 👏 + +If you've made it this far, well done! You are now capable of building verifiable ML models, making them ever more reliable and transparent than ever before. + +We invite the community to join us in forging a future in making AI transparent and reliable resource for all. diff --git a/basic/verifiable_Lagrange_interpolation/tutorial/interpolation.png b/basic/verifiable_Lagrange_interpolation/tutorial/interpolation.png new file mode 100644 index 0000000..3ac385f Binary files /dev/null and b/basic/verifiable_Lagrange_interpolation/tutorial/interpolation.png differ