From 2ecf5309975a921956c8a4e95b2356f4d7a5f09f Mon Sep 17 00:00:00 2001 From: Alex Liberzon Date: Fri, 5 Apr 2024 17:50:33 +0300 Subject: [PATCH] removed obsolete lsqadj.py --- openptv_python/lsqadj.py | 270 ------------------------------------ openptv_python/track.py | 6 +- tests/test_lsqadj.ipynb | 290 --------------------------------------- tests/test_lsqadj.py | 103 -------------- 4 files changed, 3 insertions(+), 666 deletions(-) delete mode 100644 openptv_python/lsqadj.py delete mode 100644 tests/test_lsqadj.ipynb delete mode 100644 tests/test_lsqadj.py diff --git a/openptv_python/lsqadj.py b/openptv_python/lsqadj.py deleted file mode 100644 index 1616d1b..0000000 --- a/openptv_python/lsqadj.py +++ /dev/null @@ -1,270 +0,0 @@ -"""Least squares adjustment of the camera parameters.""" -import numpy as np -from numba import njit - - -@njit -def ata(a: np.ndarray, at: np.ndarray, m: int, n: int, n_large: int) -> None: - """ - Multiply transpose of a matrix A by matrix A itself, creating a symmetric matrix inplace. - - Args: - ---- - a (list): List of doubles of size (m x n_large). - ata (list): List to store the result, size (n x n). - m (int): Number of rows in matrix a. - n (int): Number of rows and columns in the output ata. - n_large (int): Number of columns in matrix a. - """ - for i in range(n): - for j in range(n): - at.flat[i * n + j] = 0.0 - for k in range(m): - at.flat[i * n + j] += a.flat[k * n_large + i] * a.flat[k * n_large + j] - - -# def ata(a: np.ndarray, ata: np.ndarray, m: int, n: int, n_large: int) -> None: -# """Return the product of the transpose of a matrix. - -# and the matrix itself, creating symmetric matrix. -# matrix a and result matrix ata = at a -# a is m * n_large, ata is an output n * n. -# """ -# if a.shape != (m, n_large): -# raise ValueError("a has wrong shape") - -# if ata.shape != (n, n): -# raise ValueError("ata has wrong shape") - -# # a = a.flatten(order='C') -# # ata = ata.flatten(order='C') - -# for i in range(n): -# for j in range(n): -# ata.flat[i * n_large + j] = 0.0 -# for k in range(m): -# ata.flat[i * n_large + j] += ( -# a.flat[k * n_large + i] * a.flat[k * n_large + j] -# ) - -@njit -def atl(u: np.ndarray, a: np.ndarray, b: np.ndarray, m: int, n: int, n_large: int): - """Multiply transpose of a matrix A by vector b, creating vector u. - - with the option of working with the sub-vector only, when n < n_large. - - Arguments: - --------- - u -- vector of doubles of the size (n x 1) - a -- matrix of doubles of the size (m x n_large) - l -- vector of doubles (m x 1) - m -- number of rows in matrix a - n -- length of the output u - the size of the sub-matrix - n_large -- number of columns in matrix a - """ - for i in range(n): - u.flat[i] = 0.0 - for k in range(m): - u.flat[i] += a.flat[k * n_large + i] * b.flat[k] - - -# # Define a matrix -# A = np.array([[1, 2], [3, 4]]) - -# # Calculate the inverse using numpy.linalg.inv -# A_inv = np.linalg.inv(A) - -# # Print the result -# print(A_inv) - - -# def matinv(a: np.ndarray, n: int, n_large: int) -> np.ndarray: -# """ -# Calculate inverse of a matrix A, with the option of working with the sub-vector only, when n < n_large. - -# Arguments: -# --------- -# a - matrix of doubles of the size (n_large x n_large). -# n - size of the output - size of the sub-matrix, number of observations -# n_large - number of rows and columns in matrix a -# """ -# if a.shape != (n_large, n_large): -# raise ValueError("a has wrong shape") - -# # a = a.flatten(order="C") - -# for ipiv in range(n): -# pivot = a[ipiv * n_large + ipiv] -# print(f"pivot = {pivot}") -# if np.all(pivot == 0.0): -# raise ValueError("pivot is zero") - -# pivot = 1.0 / pivot -# npivot = -pivot -# for irow in range(n): -# for icol in range(n): -# if irow != ipiv and icol != ipiv: -# a[irow * n_large + icol] -= ( -# a[ipiv * n_large + icol] * a[irow * n_large + ipiv] * pivot -# ) -# for icol in range(n): -# if ipiv != icol: -# a[ipiv * n_large + icol] *= npivot -# for irow in range(n): -# if ipiv != irow: -# a[irow * n_large + ipiv] *= pivot -# a[ipiv * n_large + ipiv] = pivot - -# return a.reshape(n, n) - - -# def matmul(b: np.ndarray, c: np.ndarray, m: int, n: int, k: int): -# """Multiply two matrices and store the result in a third matrix. - -# /* Calculate dot product of a matrix 'b' of the size (m_large x n_large) with -# * a vector 'c' of the size (n_large x 1) to get vector 'a' of the size -# * (m x 1), when m < m_large and n < n_large -# * i.e. one can get dot product of the submatrix of b with sub-vector of c -# * when n_large > n and m_large > m -# * Arguments: -# * a - output vector of doubles of the size (m x 1). -# * b - matrix of doubles of the size (m x n) -# * c - vector of doubles of the size (n x 1) -# * m - integer, number of rows of a -# * n - integer, number of columns in a -# * k - integer, size of the vector output 'a', typically k = 1 -# */ - -# """ -# a = np.zeros((m, 1), dtype=np.float64) -# b_sub = b[:, :n] -# c_sub = c[:n, :k] -# a_sub = np.dot(b_sub, c_sub) -# a[:m, :k] = a_sub - -# return a - -# def matmul(a, b, c, m, n, k, m_large, n_large) -> None: -# """ Multiply two matrices and store the result in a third matrix.""" -# for i in range(k): -# pb = b -# pa = a + i -# for j in range(m): -# pc = c -# x = 0.0 -# for l in range(n): -# x += pb[l] * pc -# pc += k -# for l in range(n_large-n): -# pb += 1 -# pc += k -# pa[0] = x -# pa += k -# for j in range(m_large-m): -# pa += k -# c += 1 - - -# def matmul( -# b: np.ndarray, c: np.ndarray, m: int, n: int, k: int, m_large: int, n_large: int -# ) -> np.ndarray: -# """Multiply two matrices and store the result in a third matrix.""" -# c = np.atleast_2d(c).T -# if m_large < m or n_large < n: -# raise ValueError("m_large < m or n_large < n") - -# return (b[:m, :n] @ c[:n, :k]).transpose() - - -# def matmul(a, b, c, m, n, k, m_large, n_large): -# """ -# Calculate dot product of a matrix 'b' of the size (m_large x n_large) with - -# a vector 'c' of the size (n_large x 1) to get vector 'a' of the size (m x 1), -# when m < m_large and n < n_large. -# i.e. one can get dot product of the submatrix of b with sub-vector of c when -# n_large > n and m_large > m. - -# Arguments: -# --------- -# a - output vector of doubles of the size (m x 1). -# b - matrix of doubles of the size (m_large x n_large) -# c - vector of doubles of the size (n x 1) -# m - integer, number of rows of a -# n - integer, number of columns in a -# k - integer, size of the vector output 'a', typically k = 1 -# m_large - number of rows in matrix b -# n_large - number of columns in matrix b - -# """ -# if b.shape != (m_large, n_large): -# raise ValueError("b has wrong shape") - -# c = np.atleast_2d(c).T -# if c.shape != (n_large, k): -# raise ValueError("c has wrong shape") - -# a = np.atleast_2d(a).T -# if a.shape != (m, k): -# raise ValueError("a has wrong shape") - -# b = b.flatten(order="C") -# # a = a.flatten(order="C") - -# for i in range(k): -# print(f"i = {i}") -# pb[i] = b[i] -# pa[i] = a[i] -# print(f"pb = {pb}, pa = {pa}, a={a}") -# for j in range(m): -# pc = c -# x = 0.0 -# for ll in range(n): -# x += pb[ll] * pc[ll * k] -# for ll in range(n_large - n): -# pb += 1 -# pc += k -# pa[0] = x -# pa += k -# pb += n -# for j in range(m_large - m): -# pa += k -# c += 1 - -@njit -def matmul( - a: np.ndarray, - b: np.ndarray, - c: np.ndarray, - m: int, - n: int, - k: int, - m_large: int, - n_large: int, -) -> None: - """ - Calculate dot product of a matrix 'b' of size (m_large x n_large). - - with a vector 'c' of size (n_large x 1) to get - vector 'a' of size (m x 1), when m < m_large and n < n_large. - - Arguments: - --------- - a -- output vector of doubles of size (m x 1). - b -- matrix of doubles of size (m x n) - c -- vector of doubles of size (n x 1) - m -- integer, number of rows of 'a' - n -- integer, number of columns in 'a' - k -- integer, size of the vector output 'a', typically k = 1 - m_large -- integer, number of rows in matrix 'b' - n_large -- integer, number of columns in matrix 'b' - """ - if m_large < m or n_large < n: - raise ValueError("m_large < m or n_large < n") - - for i in range(k): - for j in range(m): - x = 0.0 - for ll in range(n): - x += b.flat[j * n_large + ll] * c.flat[ll * k + i] - a.flat[j * k + i] = x diff --git a/openptv_python/track.py b/openptv_python/track.py index db90c6f..12cb5d4 100644 --- a/openptv_python/track.py +++ b/openptv_python/track.py @@ -249,10 +249,10 @@ def pos3d_in_bounds(pos: np.ndarray, bounds: TrackPar) -> bool: # return angle, acc - +@njit(float64[:](float64[:], float64[:], float64[:]), cache=True, fastmath=True, nogil=True, parallel=True) def angle_acc( start: np.ndarray, pred: np.ndarray, cand: np.ndarray -) -> Tuple[float, float]: +) -> np.ndarray: """Calculate the angle between the (1st order) numerical velocity vectors. to the predicted next_frame position and to the candidate actual position. The @@ -288,7 +288,7 @@ def angle_acc( dot_product / (norm_start_pred * norm_start_cand) ) - return float(angle), float(acc) + return np.array([angle, acc]) diff --git a/tests/test_lsqadj.ipynb b/tests/test_lsqadj.ipynb deleted file mode 100644 index 3d5a8ec..0000000 --- a/tests/test_lsqadj.ipynb +++ /dev/null @@ -1,290 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [], - "source": [ - "import numpy as np\n", - "from openptv_python.lsqadj import atl, ata\n", - "\n", - "def test_full_matrix_vector_multiplication():\n", - " # Test when n equals n_large\n", - " n = 3\n", - " n_large = 3\n", - " m = 3 # Match the number of columns in a with rows in b\n", - " u = np.zeros((n, 1))\n", - " a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])\n", - " b = np.array([[1], [2], [3]])\n", - " expected_u = np.dot(a.T, b)\n", - " atl(u, a, b, m, n, n_large)\n", - " print(u)\n", - " assert np.array_equal(u, expected_u)\n", - "\n", - "def test_sub_matrix_vector_multiplication():\n", - " # Test when n is less than n_large\n", - " n = 2\n", - " n_large = 3\n", - " m = 4 # Match the number of columns in a with rows in b\n", - " u = np.zeros((n, 1))\n", - " a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]])\n", - " b = np.array([[1], [2], [3], [4]])\n", - " expected_u = np.dot(a.T[:, :n], b)\n", - " atl(u, a, b, m, n, n_large)\n", - " print(u, expected_u)\n", - " assert np.array_equal(u, expected_u)\n", - "\n", - "def test_valid_matrix_vector_multiplication():\n", - " # Test with valid dimensions\n", - " n = 3\n", - " n_large = 3\n", - " m = 4 # Match the number of rows in a with rows in b\n", - " u = np.zeros((n, 1))\n", - " a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]])\n", - " b = np.array([[1], [2], [3], [4]])\n", - " expected_u = np.dot(a.T, b)\n", - " atl(u, a, b, m, n, n_large)\n", - " print(u)\n", - " assert np.array_equal(u, expected_u)\n", - "\n", - "def test_zero_input():\n", - " # Test with all zero input\n", - " n = 3\n", - " n_large = 3\n", - " m = 3 # Match the number of columns in a with rows in b\n", - " u = np.zeros((n, 1))\n", - " a = np.zeros((m, n_large))\n", - " b = np.zeros((m, 1))\n", - " atl(u, a, b, m, n, n_large)\n", - " print(u)\n", - " assert np.array_equal(u, np.zeros((n, 1)))" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[[30.]\n", - " [36.]\n", - " [42.]]\n" - ] - } - ], - "source": [ - "test_full_matrix_vector_multiplication()" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "ename": "IndexError", - "evalue": "index 4 is out of bounds for size 4", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mIndexError\u001b[0m Traceback (most recent call last)", - "\u001b[1;32m/home/user/Documents/repos/openptvpy/openptv-python/tests/test_lsqadj.ipynb Cell 3\u001b[0m line \u001b[0;36m3\n\u001b[1;32m 1\u001b[0m a \u001b[39m=\u001b[39m np\u001b[39m.\u001b[39marray([[\u001b[39m1\u001b[39m, \u001b[39m0\u001b[39m, \u001b[39m1\u001b[39m], [\u001b[39m2\u001b[39m, \u001b[39m2\u001b[39m, \u001b[39m4\u001b[39m],[\u001b[39m1\u001b[39m, \u001b[39m2\u001b[39m, \u001b[39m3\u001b[39m], [\u001b[39m2\u001b[39m, \u001b[39m4\u001b[39m, \u001b[39m3\u001b[39m]])\n\u001b[1;32m 2\u001b[0m b \u001b[39m=\u001b[39m np\u001b[39m.\u001b[39mzeros((\u001b[39m2\u001b[39m,\u001b[39m2\u001b[39m))\n\u001b[0;32m----> 3\u001b[0m ata(a,b,\u001b[39m4\u001b[39;49m,\u001b[39m2\u001b[39;49m,\u001b[39m3\u001b[39;49m)\n\u001b[1;32m 4\u001b[0m \u001b[39mprint\u001b[39m(b)\n", - "File \u001b[0;32m~/Documents/repos/openptvpy/openptv-python/openptv_python/lsqadj.py:17\u001b[0m, in \u001b[0;36mata\u001b[0;34m(a, ata, m, n, n_large)\u001b[0m\n\u001b[1;32m 15\u001b[0m \u001b[39mfor\u001b[39;00m i \u001b[39min\u001b[39;00m \u001b[39mrange\u001b[39m(n):\n\u001b[1;32m 16\u001b[0m \u001b[39mfor\u001b[39;00m j \u001b[39min\u001b[39;00m \u001b[39mrange\u001b[39m(n):\n\u001b[0;32m---> 17\u001b[0m ata\u001b[39m.\u001b[39;49mflat[i \u001b[39m*\u001b[39;49m n_large \u001b[39m+\u001b[39;49m j] \u001b[39m=\u001b[39m \u001b[39m0.0\u001b[39m\n\u001b[1;32m 18\u001b[0m \u001b[39mfor\u001b[39;00m k \u001b[39min\u001b[39;00m \u001b[39mrange\u001b[39m(m):\n\u001b[1;32m 19\u001b[0m ata\u001b[39m.\u001b[39mflat[i \u001b[39m*\u001b[39m n_large \u001b[39m+\u001b[39m j] \u001b[39m+\u001b[39m\u001b[39m=\u001b[39m a\u001b[39m.\u001b[39mflat[k \u001b[39m*\u001b[39m n_large \u001b[39m+\u001b[39m i] \u001b[39m*\u001b[39m a\u001b[39m.\u001b[39mflat[k \u001b[39m*\u001b[39m n_large \u001b[39m+\u001b[39m j]\n", - "\u001b[0;31mIndexError\u001b[0m: index 4 is out of bounds for size 4" - ] - } - ], - "source": [ - "a = np.array([[1, 0, 1], [2, 2, 4],[1, 2, 3], [2, 4, 3]])\n", - "b = np.zeros((2,2))\n", - "ata(a,b,4,2,3)\n", - "print(b)" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [], - "source": [ - "b = np.zeros((2,2))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0.0" - ] - }, - "execution_count": 20, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "b[1,1]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "0 0\n", - "0.0\n", - "0 1\n", - "0.0\n", - "1 0\n", - "0.0\n", - "1 1\n" - ] - }, - { - "ename": "IndexError", - "evalue": "index 4 is out of bounds for axis 0 with size 4", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mIndexError\u001b[0m Traceback (most recent call last)", - "\u001b[1;32m/home/user/Documents/repos/openptvpy/openptv-python/tests/test_lsqadj.ipynb Cell 5\u001b[0m line \u001b[0;36m4\n\u001b[1;32m 2\u001b[0m \u001b[39mfor\u001b[39;00m j \u001b[39min\u001b[39;00m \u001b[39mrange\u001b[39m(\u001b[39m2\u001b[39m):\n\u001b[1;32m 3\u001b[0m \u001b[39mprint\u001b[39m(i,j)\n\u001b[0;32m----> 4\u001b[0m \u001b[39mprint\u001b[39m(b\u001b[39m.\u001b[39;49mflat[i\u001b[39m*\u001b[39;49m\u001b[39m3\u001b[39;49m \u001b[39m+\u001b[39;49m j])\n", - "\u001b[0;31mIndexError\u001b[0m: index 4 is out of bounds for axis 0 with size 4" - ] - } - ], - "source": [ - "for i in range(2):\n", - " for j in range(2):\n", - " print(i,j)\n", - " print(b.flat[i*3 + j])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "test_valid_matrix_vector_multiplication()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "test_zero_input()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def myata(a, m, n, n_large):\n", - " ata = np.zeros((n, n_large), dtype=float)\n", - " \n", - " for i in range(n):\n", - " for j in range(n_large):\n", - " for k in range(m):\n", - " ata[i, j] += a[k, i] * a[k, j]\n", - " \n", - " return ata" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "myata(np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]), 3, 3, 3)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "myata(np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]), 3, 2, 3)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "myata(a,4,2,3)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "a = np.array([[1, 0, 1], [2, 2, 4], [1, 2, 3], [2, 4, 3]])\n", - "print(a)\n", - "myata(a,4,3,3)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "openptvpy", - "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.10.9" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/tests/test_lsqadj.py b/tests/test_lsqadj.py deleted file mode 100644 index a29dc51..0000000 --- a/tests/test_lsqadj.py +++ /dev/null @@ -1,103 +0,0 @@ -import math -import unittest - -import numpy as np - -from openptv_python.calibration import Exterior -from openptv_python.lsqadj import ata, atl, matmul - -EPS = 1e-6 - - -class TestMatmul(unittest.TestCase): - """Test the multiplication of a matrix with a vector.""" - - def test_matmul(self): - """Test the multiplication of a matrix with a vector.""" - b = np.array([0.0, 0.0, 0.0]) - d = np.array([[1, 2, 3, 99], [4, 5, 6, 99], [7, 8, 9, 99], [99, 99, 99, 99]]) - e = np.array([10, 11, 12, 99]) - f = np.array([0, 0, 0]) - expected = np.array([68, 167, 266]) - - test_Ex = Exterior.copy() - test_Ex.z0 = 100.0 - test_Ex.dm = np.array([[1.0, 0.2, -0.3], [0.2, 1.0, 0.0], [-0.3, 0.0, 1.0]]) - - b = np.array([[1.0, 0.2, -0.3], [0.2, 1.0, 0.0], [-0.3, 0.0, 1.0]]) - c = np.array([1, 1, 1]) - a = np.empty( - 3, - ) - - matmul(a, b, c, 3, 3, 1, 3, 3) - - assert np.allclose(a, [0.9, 1.2, 0.7]) - - a = np.array([1.0, 1.0, 1.0]) - matmul(b, test_Ex.dm, a, 3, 3, 1, 3, 3) - - self.assertTrue( - abs(b[0, 0] - 0.9) < EPS - and abs(b[0, 1] - 1.20) < EPS - and abs(b[0, 2] - 0.700) < EPS - ) - - matmul(f, d, e, 3, 3, 1, 4, 4) - - for i in range(3): - self.assertTrue(abs(f[i] - expected[i]) < EPS) - - -class TestAta(unittest.TestCase): - """test the multiplication of a transposed matrix with a matrix.""" - - def test_ata(self): - """Test the multiplication of a transposed matrix with a matrix.""" - a = np.array([[1, 0, 1], [2, 2, 4], [1, 2, 3], [2, 4, 3]]) # input - expected = np.array( - [[10, 14, 18], [14, 24, 26], [18, 26, 35]] - ) # expected output - - # test for n = n_large - m = 4 # rows - n = 3 # columns - n_large = 3 # columns - - b = np.zeros((n, n)) - assert np.equal(b, np.zeros((n, n))).all() - - ata(a, b, m, n, n_large) - assert np.equal(b, expected).all() - - # test for n < n_large - n = 2 - b = np.zeros((n, n)) - ata(a, b, m, n, n_large) - assert np.equal(b, expected[:n, :n]).all() - - # test numpy simle submatrix multiplication - assert np.equal(b, a[:, :n].T.dot(a[:, :n])).all() - - -class TestATL(unittest.TestCase): - """test the multiplication of a transposed matrix with a vector.""" - - def test_atl(self): - """Test the multiplication of a transposed matrix with a vector.""" - a = np.array([[1, 0, 1], [2, 2, 4], [1, 2, 3], [2, 4, 3]]) - b = np.array([1, 2, 3, 4]) - u = np.zeros( - 3, - ) - expected = np.array([16, 26, 30]) - - atl(u, a, b, 4, 3, 3) - - for i in range(3): - msg = f"wrong item [{i}] {u[i]} instead of {expected[i]}" - self.assertTrue(math.isclose(u[i], expected[i], rel_tol=EPS), msg) - - -if __name__ == "__main__": - unittest.main()