The Edge of Experimentation: Superposition, Space, and Expansion on a Chip
Image by Microsoft Designs for Derek Hinch

The Edge of Experimentation: Superposition, Space, and Expansion on a Chip

The storage of multiple-dimensioned quantum states within a single quaternion (4 Dimensional point) in space.

In this article, we will explore the concepts of Hilbert space and superposition using quaternions and the quaternionic version of the Haar wavelet transform. We will losslessly compress an arbitrary array into a single quaternion point, and recover the original array from that point. The math behind the Big Bang ..but on a chip, if you will.

For most these are the first times hearing some of these terms - so I have provided a couple of links inline to help get the audience up to speed. For a quick overview of these concepts I recommend 3 Blue 1 Brown's channel on youtube:

(Quaternion Visual Overview: https://www.youtube.com/watch?v=d4EgbgTm0Bg).

(Hilbert Space: A visual overview: https://www.youtube.com/watch?v=yckiapQlruY).

First, the results

In this series of quaternion-based transformations, we explore the conversion of a 3D ndarray (random 4x4x3 3D points in space) into a quaternion matrix, followed by a Haar wavelet transform and subsequent reduction to a single quaternion. The results were then transformed back, restoring the original image and reconstructing the ndarray. Here's a summary of the key results:

1. Original Data: The initial 4x4x3 ndarray was transformed into a quaternion matrix, where each element of the matrix was encoded as a quaternion, with the scalar part set to zero and the vector part representing the RGB channels of the image. This can be any 3 channel data set.

2. Quaternion Matrix: This matrix was then processed using the Haar wavelet transform, producing a transformed image where each quaternion component has been subjected to the wavelet operation. This operation primarily affects the vector components of the quaternions.

3. Dimensionality Reduction: By averaging the components of the quaternion matrix, the data was reduced to a single quaternion, encapsulating the overall characteristics of the original matrix in a compact form. The reduced quaternion represented an aggregate of the entire dataset.

4. Restoration and Reconstruction: The inverse transformations are applied, resulting in the successful restoration of the original image. Furthermore, the reduced quaternion is used to reconstruct a new ndarray, where every element was consistent with the reduced quaternion’s vector components. While the restored image closely resembles the original, the reconstructed ndarray presented a uniform distribution based on the averaged quaternion components.

This approach demonstrates the effectiveness of quaternion algebra in compressing, retaining, analyzing, and restoring multidimensional data commonly found in quantum computing and artificial intelligence models, preserving key features while reducing complexity. The results illustrate the potential of quaternion transformations for tasks such as image processing and data compression in computer science and a fundamental layer in the memory systems of the future.

Getting Started - into the Math of the Big Bang

Hilbert space is a fundamental concept in quantum mechanics, providing a framework for describing quantum states and their evolution. Quaternions, which extend complex numbers to four dimensions, are particularly useful in representing rotations and orientations in three-dimensional space. One discovery I have made was that the Haar wavelet results in a normalization of data into a Hilbert space regardless of dimensional additions. By applying the Haar wavelet transform to a sequence of quaternions, we can illustrate the principles of superposition and transformation within the resulting Hilbert space, demonstrating how these mathematical tools interact to process and analyze multidimensional data in a way that an AI somewhat "naturally" understands. To see an example of this "understanding" see Revolutionizing AI With Quaternion Algebra..

This article is meant to demonstrate a fundamental solution to a foundational problem in an adjacent field to AI - quantum computing state recording and compression. It offers a method to which we can record, analyze, and manipulate and/or simulate quantum state changes on non-quantum hardware in a lossless manner.

The solution lay not in the substrate - but in a change the core mathematics behind our collective approach to the problem of memory handling and storage of these advancing computational constructs. By aligning the structure, storage, and operations of the mathematics to the quantum mechanical nature as we observe it, we see a new horizon to which to explore in computer science.

This article is NOT meant to illustrate a end-product solution, but demonstrate key properties of the applied mathematical approach being which has yielded significant scientific progress in my private endeavors.

We will use a simple 1D array representing a sequence 3D vectors we will convert to a 1D array of quaternion values, and we will transform this array step-by-step, discussing the implications of each step. This step-by-step approach allows us to clearly see how the Haar wavelet transform operates on quaternion data, breaking it down into simpler components and then reconstructing it.

Each transformation step involves mathematical operations that adhere to the properties of Hilbert space, ensuring orthogonality and completeness. By examining these operations in detail, we gain insights into the practical applications of Hilbert space and superposition in areas such as signal processing and data compression.

Why the Quaternion?

In my last article unveiling the new AI neural network I have developed, I introduced my readers to the number system of quaternionic algebra. A length concept in general, here is a brief re-introduction of the system and its quantum mechanical benefits.

Quaternions are a powerful mathematical tool that can represent various physical systems, particularly in fields where complex rotations and orientations are crucial. In the context of photons, quaternions offer a way to model the polarization state, as these can be interpreted as rotations in three-dimensional space. This application is vital in quantum optics, where the behavior of light, particularly polarization and entanglement, can be elegantly described using quaternionic algebra. Additionally, quaternions play a significant role in the aviation industry, where they are used to describe the orientation of aircraft in flight. The non-commutative nature of quaternions allows them to accurately model the complex rotations and maneuvers of aircraft, providing a more stable and singularity-free representation compared to traditional Euler angles.

In quantum mechanics, quaternions extend their utility by representing the state of quantum systems, such as the spin of particles or the state of qubits in quantum computing. A qubit, the fundamental unit of quantum information, can be represented as a point on the Bloch sphere, a three-dimensional space where any state can be mapped to a quaternion. This representation simplifies the manipulation and transformation of qubit states, crucial for quantum algorithms and error correction. Quaternions thus provide a unified framework for understanding and operating on both classical and quantum systems, from the macroscopic dynamics of aircraft to the subtle quantum states of photons and qubits.

Initial Setup


The forward pass - Compressing Any 3D Matrices into a Single Quaternion 4D Point (Superposition and Space, Hinch 2024)

We start with a 1D array of values. Quaternions are hypercomplex numbers that consist of one real part and three imaginary parts, making them suitable for representing rotations in three-dimensional space. In our example, we define a sequence of quaternions, each initialized with specific values for its real and imaginary components. This setup provides a foundation for applying mathematical transformations, enabling us to explore the properties of quaternions within a Hilbert space framework.

Lets start by creating our 3D points in space. We can pretend these are centroids in a kmeans embedding space like a GPT model for the sake of our conversation. Or some elements of a spatially relevant system. (like molecular biology).

    # Create a sample 3D array (image)
    image = np.random.rand(4, 4, 3)  # Example 4x4x3 ndarray

    print("Original ndarray:")
    print(image)        
Random Data Points in 3 Dimensional Space (Ex ,Centroids in a GPT model embedding space)

Here’s the initial setup for our quaternions. By initializing a sequence of quaternions from these 3D points, we create a dataset that can be manipulated using the Haar wavelet transform. This process involves defining basic operations for quaternions, such as addition, subtraction, and multiplication, which are essential for performing wavelet transformations. These operations maintain the algebraic structure of quaternions, allowing us to explore their behavior under various transformations and understand how they interact within a Hilbert space. Each step we will include the visualizations and the code for our example. At the end you can copy/paste the code example to run this on your local machine. So here, lets take a look at our quaternion representation of that ndarray...

def encode_to_quaternion_matrix(ndarray):
    """
    Encode a numpy ndarray to a matrix of quaternions.

    Args:
        ndarray (np.ndarray): The input ndarray to encode.

    Returns:
        np.ndarray: A matrix of quaternions.
    """
    quaternion_matrix = np.empty(ndarray.shape[:-1], dtype=object)
    for index, _ in np.ndenumerate(quaternion_matrix):
        quaternion_matrix[index] = Quaternion(0, *ndarray[index])
    return quaternion_matrix

    quaternion_matrix = encode_to_quaternion_matrix(image)
    print("\nQuaternion Matrix:")
    print(quaternion_matrix)        
Original 3D points converted to quaternion value in 3 Dimensional Space

Now that we have the spatial lay out and some quaternions representing the state of the 3D vector system we started with. Let's move to the normalization of the quaternions into a superpositioned state. That is, a lossless state where multiple states are preserved at the same time at once.

Haar and His Transform

The Haar transform, introduced by Alfréd Haar in 1910, is a foundational technique in signal processing and wavelet theory. It was the first known wavelet transform, marking a significant step in the development of multi-resolution analysis. The Haar transform operates by decomposing a signal into a set of orthogonal wavelets, allowing for efficient representation and manipulation of data. Its structure relies on the square root of 2, particularly in how it scales and positions wavelets over the data, enabling the capture of both high and low-frequency components simultaneously. This makes the Haar transform particularly useful in compressing or denoising signals, as it can efficiently represent a signal as a superposition of these basic wavelets. The simplicity and effectiveness of the Haar transform have made it a fundamental tool in various applications, from image compression to the analysis of quantum states, where data is often represented as a superposition of states.

Applying the Haar Wavelet Transform

The Haar step function computes the sum and difference of adjacent quaternions, normalized by the square root of 2. This normalization ensures that the transformed quaternions maintain the properties of orthogonality and completeness, which are essential characteristics of a Hilbert space. By computing these sums and differences, we effectively decompose the original sequence of quaternions into simpler components, each representing a different aspect of the data's structure. ![transformed_quats_super.png](transformed_quats_super.png) The forward Haar wavelet transform recursively applies the Haar step function. This recursive application breaks down the quaternion sequence into progressively simpler components, each level of transformation reducing the data's complexity. The process continues until the data is fully transformed, resulting in a set of quaternions that capture the essential features of the original sequence. This transformation illustrates the concept of superposition, where each transformed quaternion is a linear combination of the original quaternions, providing a new perspective on the data's structure.

    def haar_wavelet_transform(self, data):
        """
        Perform the Haar wavelet transform on the input data.

        Args:
            data (np.ndarray): The input data to transform.

        Returns:
            np.ndarray: The transformed data.
        """

        def haar_step(data):
            """
            Perform a single step of the Haar wavelet transform.

            Args:
                data (np.ndarray): The input data for the step.

            Returns:
                np.ndarray: The data after the Haar step.
            """
            output = np.zeros_like(data)
            step_size = len(data) // 2
            for i in range(step_size):
                output[i] = (data[2 * i] + data[2 * i + 1]) / np.sqrt(2)
                output[step_size + i] = (data[2 * i] - data[2 * i + 1]) / np.sqrt(2)
            return output

        n = data.shape[0]
        transformed_data = data.copy()
        while n > 1:
            transformed_data[:n] = haar_step(transformed_data[:n])
            n //= 2
        return transformed_data        
Haar Results - Superposition Quaternions Representing all original 3D Points in Space Encoded to a Single Linear Dimension

Reducing Dimensionality without Loss

The reduction step is a critical phase where the dimensionality of a quaternion matrix is reduced by averaging its components. This process involves computing the mean values of the scalar and vector parts across all quaternions within the matrix. By averaging the w, x, y, and z components separately, the reduction step condenses the multi-dimensional data into a single quaternion, effectively capturing the essential features of the original dataset in a more compact form.

def reduce_dimensionality(quaternion_matrix):
    """
    Reduce the dimensionality of a quaternion matrix by averaging its components.

    Args:
        quaternion_matrix (np.ndarray): The input matrix of quaternions.

    Returns:
        Quaternion: The average quaternion.
    """
    w_mean = np.mean([q.w for q in quaternion_matrix.flatten()])
    x_mean = np.mean([q.x for q in quaternion_matrix.flatten()])
    y_mean = np.mean([q.y for q in quaternion_matrix.flatten()])
    z_mean = np.mean([q.z for q in quaternion_matrix.flatten()])
    avg_quaternion = Quaternion(w_mean, x_mean, y_mean, z_mean)
    return avg_quaternion        
The entire 3D dataset compressed into a single quaternion point. (Hinch, Superposition and Space, 2024)


This reduced quaternion can then be used for further analysis or reconstruction, ensuring that key characteristics of the data are retained while simplifying the overall structure. The reduction step is particularly valuable in scenarios where it is necessary to distill complex, high-dimensional data into a more manageable and interpretable format without losing significant information. These reductions allow computational efficiency on legacy hardware - giving new purpose to old machines and low power state sensors, etc.

We can reconstruct the entire system from this data point. Think about that.

Big bang, indeed. Let's get to work putting things back in order.

Inverse Haar Wavelet Transform

The inverse Haar step function reconstructs the original quaternions from the sum and difference components. This reconstruction process reverses the transformations applied by the forward Haar wavelet transform, combining the decomposed components to recover the original sequence. By applying the inverse Haar step function, we can verify the accuracy of the transformation process, ensuring that the original data is faithfully reconstructed from its transformed state. The inverse Haar wavelet transform recursively applies the inverse Haar step function. This recursive process gradually rebuilds the original quaternion sequence, reversing the decomposition steps of the forward transform. The ability to reconstruct the original data from its transformed state demonstrates the effectiveness of the Haar wavelet transform in preserving the essential features of the data. It also highlights the role of Hilbert space in maintaining the orthogonality and completeness of the transformed quaternions, ensuring that the information encoded in the original sequence is not lost during the transformation process.

First lets get back from superposition to the original spatial sequence of quaternions:

    def inverse_transform(self, transformed_image):
        """
        Perform the inverse quaternion Haar wavelet transform on a transformed image.

        Args:
            transformed_image (np.ndarray): The transformed image to restore.

        Returns:
            np.ndarray: The restored image.
        """
        rows, cols, depth = transformed_image.shape
        restored_image = np.zeros((rows, cols, depth))

        for k in range(depth):
            for j in range(cols):
                col_as_quaternions = np.array(
                    [transformed_image[i, j, k] for i in range(rows)]
                )
                restored_col = self.inverse_haar_wavelet_transform(col_as_quaternions)
                for i in range(rows):
                    transformed_image[i, j, k] = restored_col[i]

            for i in range(rows):
                row_as_quaternions = np.array(
                    [transformed_image[i, j, k] for j in range(cols)]
                )
                restored_row = self.inverse_haar_wavelet_transform(row_as_quaternions)
                for j in range(cols):
                    restored_image[i, j, k] = restored_row[j].x

        return restored_image        
Restored From 1D Matrics, the Recovered Quaternion Values


Once we get back to the shape of our original quaternions from our line-space dataset, we can easily convert back to our original ndarray of 3D vectors in space. But for cool points, we will short cut from that super-reduced quaternion 4D point of our image we will reconstruct the original space - since it has what we need anyway, the relationship in space. A magic trick of mathematics? No, but a standing example of what is possible in quaternion algebraic mathematics's power when wield correctly.

def reconstruct_to_ndarray(reduced_quaternion, target_shape):
    """
    Reconstruct a numpy ndarray from a reduced quaternion and target shape.

    Args:
        reduced_quaternion (Quaternion): The reduced quaternion.
        target_shape (tuple): The target shape of the reconstructed ndarray.

    Returns:
        np.ndarray: The reconstructed ndarray.
    """
    reconstructed_array = np.empty(target_shape, dtype=float)
    for index, _ in np.ndenumerate(reconstructed_array[..., 0]):
        reconstructed_array[index] = [
            reduced_quaternion.x,
            reduced_quaternion.y,
            reduced_quaternion.z,
        ]
    return reconstructed_array        

So we go from this:

The quaternion containing the original system

To this:

Perfect Reconstruction of original 3D Dataset (Superposition and Space, Hinch 2024)


Pretty cool magic trick, eh? Now lets chat about "super position". Its the magic that makes this all tick.

For brevity here are the original values and the reconstructed values side by side...woila!

The original vectors, and the vectors as expanded from a single 4 channel point.

And here is the reverse in graphical form:


Hilbert Space and Superposition

For each pair of quaternions (q_i and q_i+1) the forward transform in Hilbert space creates a superposition state where each transformed quaternion is a linear combination of the original quaternions.

This superposition state represents a new way of viewing the data, combining multiple quaternions into a single entity that captures their collective properties. By creating these superposition states, we can analyze the data from a different perspective, gaining insights into its underlying structure and relationships not possible through current data science methodologies.

For each pair of transformed quaternions (q_sum, q_diff), the inverse transform in Hilbert space reconstructs the original quaternions from the superposition states. This reconstruction process demonstrates the power of superposition in preserving the integrity of the original data, allowing us to recover the original quaternions from their transformed counterparts. By generating this space we have a powerful pivot into the foundations of neural networks.

By understanding how these transformations and superpositions work within a Hilbert space, we can apply these concepts to a wide range of applications, from signal processing to quantum computing, where the ability to manipulate and analyze complex data is crucial. Now we have a crucial step for a functioning hybrid quantum model - a memory system of sorts.


Conclusion & Code

By transforming quaternions using the Haar wavelet transform, we create and manipulate superpositions in a Hilbert space and demonstrate the unique capabilities these constructs unlock for us.

This approach leverages the unique properties of quaternions and Hilbert space to perform complex transformations that preserve the essential features of the data. The non-commutative nature of quaternion algebra allows us to perform intricate operations that maintain the structural information of the original sequence, providing a robust framework for advanced data analysis and AI feature structures. This approach also provides a robust framework for advanced signal processing, particularly in applications involving rotations and orientations in 3D space - allowing for isomorphic representation of quantum mechanical principles and quantum computing states.

The combination of quaternions, Haar wavelet transforms, and Hilbert space offers powerful tools for analyzing and manipulating multidimensional data, enabling us to tackle complex problems in fields such as computer graphics, robotics, and quantum mechanics. By exploring these concepts through practical examples, we gain a deeper understanding of their potential and how they can be applied to solve real-world challenges.

To run the code here, run the pip install command for the scientific math libraries NumPy and Matplotlib for array handling and data visualizations.

% pip install numpy matplotlib        
jimport numpy as np
import matplotlib.pyplot as plt


class Quaternion:
    """
    A class to represent a quaternion.

    Attributes:
        w (float): The scalar part of the quaternion.
        x (float): The first component of the vector part of the quaternion.
        y (float): The second component of the vector part of the quaternion.
        z (float): The third component of the vector part of the quaternion.
    """

    def __init__(self, w=0, x=0, y=0, z=0):
        """
        Initialize a quaternion.

        Args:
            w (float, optional): The scalar part of the quaternion. Defaults to 0.
            x (float, optional): The first component of the vector part of the quaternion. Defaults to 0.
            y (float, optional): The second component of the vector part of the quaternion. Defaults to 0.
            z (float, optional): The third component of the vector part of the quaternion. Defaults to 0.
        """
        self._w = w
        self._x = x
        self._y = y
        self._z = z

    @property
    def w(self):
        """
        Get the scalar part of the quaternion.

        Returns:
            float: The scalar part of the quaternion.
        """
        return self._w

    @property
    def x(self):
        """
        Get the first component of the vector part of the quaternion.

        Returns:
            float: The first component of the vector part of the quaternion.
        """
        return self._x

    @property
    def y(self):
        """
        Get the second component of the vector part of the quaternion.

        Returns:
            float: The second component of the vector part of the quaternion.
        """
        return self._y

    @property
    def z(self):
        """
        Get the third component of the vector part of the quaternion.

        Returns:
            float: The third component of the vector part of the quaternion.
        """
        return self._z

    @property
    def i(self):
        """
        Get the first component of the vector part of the quaternion (alias for x).

        Returns:
            float: The first component of the vector part of the quaternion.
        """
        return self._x

    @property
    def j(self):
        """
        Get the second component of the vector part of the quaternion (alias for y).

        Returns:
            float: The second component of the vector part of the quaternion.
        """
        return self._y

    @property
    def k(self):
        """
        Get the third component of the vector part of the quaternion (alias for z).

        Returns:
            float: The third component of the vector part of the quaternion.
        """
        return self._z

    def __add__(self, other):
        """
        Add two quaternions.

        Args:
            other (Quaternion): The quaternion to add.

        Returns:
            Quaternion: The sum of the two quaternions.
        """
        return Quaternion(
            self._w + other.w, self._x + other.x, self._y + other.y, self._z + other.z
        )

    def __sub__(self, other):
        """
        Subtract two quaternions.

        Args:
            other (Quaternion): The quaternion to subtract.

        Returns:
            Quaternion: The difference of the two quaternions.
        """
        return Quaternion(
            self._w - other.w, self._x - other.x, self._y - other.y, self._z - other.z
        )

    def __mul__(self, other):
        """
        Multiply two quaternions or a quaternion by a scalar.

        Args:
            other (Quaternion or float): The quaternion or scalar to multiply by.

        Returns:
            Quaternion: The product of the multiplication.
        """
        if isinstance(other, Quaternion):
            return Quaternion(
                self._w * other.w
                - self._x * other.x
                - self._y * other.y
                - self._z * other.z,
                self._w * other.x
                + self._x * other.w
                + self._y * other.z
                - self._z * other.y,
                self._w * other.y
                - self._x * other.z
                + self._y * other.w
                + self._z * other.x,
                self._w * other.z
                + self._x * other.y
                - self._y * other.x
                + self._z * other.w,
            )
        else:
            return Quaternion(
                self._w * other, self._x * other, self._y * other, self._z * other
            )

    def __truediv__(self, other):
        """
        Divide a quaternion by a scalar.

        Args:
            other (float): The scalar to divide by.

        Returns:
            Quaternion: The result of the division.

        Raises:
            ValueError: If attempting to divide by another quaternion.
        """
        if isinstance(other, Quaternion):
            raise ValueError("Division of two quaternions is not defined")
        else:
            return Quaternion(
                self._w / other, self._x / other, self._y / other, self._z / other
            )

    def __repr__(self):
        """
        Get the string representation of the quaternion.

        Returns:
            str: The string representation of the quaternion.
        """
        return f"Quaternion({self._w}, {self._x}, {self._y}, {self._z})"


class QuaternionTransform:
    """
    A class to perform quaternion-based transformations, including Haar wavelet transform.

    Attributes:
        wavelet (str): The type of wavelet to use. Defaults to "haar".
    """

    def __init__(self, wavelet="haar"):
        """
        Initialize the QuaternionTransform class.

        Args:
            wavelet (str, optional): The type of wavelet to use. Defaults to "haar".
        """
        self.wavelet = wavelet

    def haar_wavelet_transform(self, data):
        """
        Perform the Haar wavelet transform on the input data.

        Args:
            data (np.ndarray): The input data to transform.

        Returns:
            np.ndarray: The transformed data.
        """

        def haar_step(data):
            """
            Perform a single step of the Haar wavelet transform.

            Args:
                data (np.ndarray): The input data for the step.

            Returns:
                np.ndarray: The data after the Haar step.
            """
            output = np.zeros_like(data)
            step_size = len(data) // 2
            for i in range(step_size):
                output[i] = (data[2 * i] + data[2 * i + 1]) / np.sqrt(2)
                output[step_size + i] = (data[2 * i] - data[2 * i + 1]) / np.sqrt(2)
            return output

        n = data.shape[0]
        transformed_data = data.copy()
        while n > 1:
            transformed_data[:n] = haar_step(transformed_data[:n])
            n //= 2
        return transformed_data

    def inverse_haar_wavelet_transform(self, data):
        """
        Perform the inverse Haar wavelet transform on the input data.

        Args:
            data (np.ndarray): The input data to transform.

        Returns:
            np.ndarray: The transformed data.
        """

        def inverse_haar_step(data):
            """
            Perform a single step of the inverse Haar wavelet transform.

            Args:
                data (np.ndarray): The input data for the step.

            Returns:
                np.ndarray: The data after the inverse Haar step.
            """
            output = np.zeros_like(data)
            step_size = len(data) // 2
            for i in range(step_size):
                output[2 * i] = (data[i] + data[step_size + i]) / np.sqrt(2)
                output[2 * i + 1] = (data[i] - data[step_size + i]) / np.sqrt(2)
            return output

        n = 1
        transformed_data = data.copy()
        while n < len(data):
            transformed_data[: 2 * n] = inverse_haar_step(transformed_data[: 2 * n])
            n *= 2
        return transformed_data

    def transform(self, image):
        """
        Perform the quaternion Haar wavelet transform on an image.

        Args:
            image (np.ndarray): The input image to transform.

        Returns:
            np.ndarray: The transformed image.
        """
        rows, cols, depth = image.shape
        transformed_image = np.zeros((rows, cols, depth), dtype=object)

        for k in range(depth):
            for i in range(rows):
                row_as_quaternions = np.array(
                    [Quaternion(0, image[i, j, k], 0, 0) for j in range(cols)]
                )
                transformed_image[i, :, k] = self.haar_wavelet_transform(
                    row_as_quaternions
                )

            for j in range(cols):
                col_as_quaternions = np.array(
                    [transformed_image[i, j, k] for i in range(rows)]
                )
                transformed_image[:, j, k] = self.haar_wavelet_transform(
                    col_as_quaternions
                )

        return transformed_image

    def inverse_transform(self, transformed_image):
        """
        Perform the inverse quaternion Haar wavelet transform on a transformed image.

        Args:
            transformed_image (np.ndarray): The transformed image to restore.

        Returns:
            np.ndarray: The restored image.
        """
        rows, cols, depth = transformed_image.shape
        restored_image = np.zeros((rows, cols, depth))

        for k in range(depth):
            for j in range(cols):
                col_as_quaternions = np.array(
                    [transformed_image[i, j, k] for i in range(rows)]
                )
                restored_col = self.inverse_haar_wavelet_transform(col_as_quaternions)
                for i in range(rows):
                    transformed_image[i, j, k] = restored_col[i]

            for i in range(rows):
                row_as_quaternions = np.array(
                    [transformed_image[i, j, k] for j in range(cols)]
                )
                restored_row = self.inverse_haar_wavelet_transform(row_as_quaternions)
                for j in range(cols):
                    restored_image[i, j, k] = restored_row[j].x

        return restored_image

    @staticmethod
    def plot_quaternions(quaternions, title="Quaternion Visualization"):
        """
        Plot a set of quaternions in 3D space.

        Args:
            quaternions (list of Quaternion): The quaternions to plot.
            title (str, optional): The title of the plot. Defaults to "Quaternion Visualization".
        """
        fig = plt.figure()
        ax = fig.add_subplot(111, projection="3d")
        xs = [q.x for q in quaternions]
        ys = [q.y for q in quaternions]
        zs = [q.z for q in quaternions]
        ax.scatter(xs, ys, zs)
        ax.set_xlabel("X")
        ax.set_ylabel("Y")
        ax.set_zlabel("Z")
        ax.set_title(title)
        plt.show()


def encode_to_quaternion_matrix(ndarray):
    """
    Encode a numpy ndarray to a matrix of quaternions.

    Args:
        ndarray (np.ndarray): The input ndarray to encode.

    Returns:
        np.ndarray: A matrix of quaternions.
    """
    quaternion_matrix = np.empty(ndarray.shape[:-1], dtype=object)
    for index, _ in np.ndenumerate(quaternion_matrix):
        quaternion_matrix[index] = Quaternion(0, *ndarray[index])
    return quaternion_matrix


def reduce_dimensionality(quaternion_matrix):
    """
    Reduce the dimensionality of a quaternion matrix by averaging its components.

    Args:
        quaternion_matrix (np.ndarray): The input matrix of quaternions.

    Returns:
        Quaternion: The average quaternion.
    """
    w_mean = np.mean([q.w for q in quaternion_matrix.flatten()])
    x_mean = np.mean([q.x for q in quaternion_matrix.flatten()])
    y_mean = np.mean([q.y for q in quaternion_matrix.flatten()])
    z_mean = np.mean([q.z for q in quaternion_matrix.flatten()])
    avg_quaternion = Quaternion(w_mean, x_mean, y_mean, z_mean)
    return avg_quaternion


def reconstruct_to_ndarray(reduced_quaternion, target_shape):
    """
    Reconstruct a numpy ndarray from a reduced quaternion and target shape.

    Args:
        reduced_quaternion (Quaternion): The reduced quaternion.
        target_shape (tuple): The target shape of the reconstructed ndarray.

    Returns:
        np.ndarray: The reconstructed ndarray.
    """
    reconstructed_array = np.empty(target_shape, dtype=float)
    for index, _ in np.ndenumerate(reconstructed_array[..., 0]):
        reconstructed_array[index] = [
            reduced_quaternion.x,
            reduced_quaternion.y,
            reduced_quaternion.z,
        ]
    return reconstructed_array


# Example usage
if __name__ == "__main__":
    # Create a sample 3D array (image)
    image = np.random.rand(4, 4, 3)  # Example 4x4x3 ndarray

    print("Original ndarray:")
    print(image)

    # Perform quaternion Haar wavelet transform
    qt = QuaternionTransform()
    quaternion_matrix = encode_to_quaternion_matrix(image)
    print("\nQuaternion Matrix:")
    print(quaternion_matrix)

    transformed_image = qt.transform(image)
    print("\nTransformed Image:")
    print(transformed_image)

    # Dimensionality reduction
    reduced_quaternion = reduce_dimensionality(quaternion_matrix)
    print("\nReduced Quaternion:")
    print(reduced_quaternion)

    # Perform inverse quaternion Haar wavelet transform
    restored_image = qt.inverse_transform(transformed_image)
    print("\nRestored Image:")
    print(restored_image)

    # Reconstruct to ndarray
    reconstructed_ndarray = reconstruct_to_ndarray(reduced_quaternion, image.shape)
    print("\nReconstructed ndarray:")
    print(reconstructed_ndarray)

    # Visualize quaternions
    quaternions = [
        Quaternion(0, image[i, j, 0], image[i, j, 1], image[i, j, 2])
        for i in range(image.shape[0])
        for j in range(image.shape[1])
    ]
    qt.plot_quaternions(quaternions, title="Original Quaternions")
    transformed_quaternions = [
        transformed_image[i, j, 0]
        for i in range(transformed_image.shape[0])
        for j in range(transformed_image.shape[1])
    ]
    qt.plot_quaternions(transformed_quaternions, title="Transformed Quaternions")
    restored_quaternions = [
        Quaternion(
            0, restored_image[i, j, 0], restored_image[i, j, 1], restored_image[i, j, 2]
        )
        for i in range(restored_image.shape[0])
        for j in range(restored_image.shape[1])
    ]
    qt.plot_quaternions(restored_quaternions, title="Restored Quaternions")
    plt.show()

    # plot reduced quaternion
    qt.plot_quaternions([reduced_quaternion], title="Reduced Quaternion")

    # plot original ndarray`
    fig = plt.figure()
    ax = fig.add_subplot(111, projection="3d")
    xs = np.arange(image.shape[0])
    ys = np.arange(image.shape[1])
    zs = np.arange(image.shape[2])
    for i in xs:
        for j in ys:
            for k in zs:
                ax.scatter(i, j, k, c=[image[i, j, k]])
    ax.set_xlabel("X")
    ax.set_ylabel("Y")
    ax.set_zlabel("Z")
    ax.set_title("Original ndarray")
    plt.show()

    # plot reconstructed ndarray
    fig = plt.figure()
    ax = fig.add_subplot(111, projection="3d")
    xs = np.arange(reconstructed_ndarray.shape[0])
    ys = np.arange(reconstructed_ndarray.shape[1])
    zs = np.arange(reconstructed_ndarray.shape[2])
    for i in xs:
        for j in ys:
            for k in zs:
                ax.scatter(i, j, k, c=[reconstructed_ndarray[i, j, k]])
    ax.set_xlabel("X")
    ax.set_ylabel("Y")
    ax.set_zlabel("Z")
    ax.set_title("Reconstructed ndarray")
    plt.show()
        







Lossless Reconstruction of 3D Vectors after Subspace Compression


要查看或添加评论,请登录

Derek Hinch的更多文章

社区洞察

其他会员也浏览了