All articles

Google Summer of Code 2024

May 6, 2024

This summer I will be working as a Google Summer of Code mentee at the AsyncAPI Initiative to develop a UI Kit for the AsyncAPI Website using Storybook v8 in Next.js v14 and Typescript under the mentorship of Akshat Nema, Elegbede Azeez, and Aishat Muibudeen. This blog will cover my journey for the next four months working on developing this UI Kit. In this blog post, I will introduce you to the AsyncAPI Website, why we need a UI kit for this website, and what Storybook is. AsyncAPI Website: Problem & its Solution AsyncAPl is an open-source initiative that seeks to improve the current state of Event-Driven Architecture (EDA). It has a set of tools for documentation, code and model generation, event management, etc. which helps to easily build and maintain EDA. The AsyncAPI Website is the primary source of information for users and developers. Currently, the website lacks visual consistency, repeated elements lack consistency in design, and duplicate styling is used for similar visual styles. This makes the codebase and design non-modular. Existing Ul patterns are undocumented which results in miscommunication and re-inventing the wheel instead of building new features. The goal of this project is to develop a comprehensive Ul Kit that can enhance the existing design, and streamline the development process to simplify the creation and management of cohesive elements in the website. This website Ul Kit will help in preventing the process of rebuilding similar components.🚀 The current state of the project The issue #2090 in the website repository (adopted from issue #4 in the design-system repository) is currently monitoring the progress of this project. This project was divided into two sub-parts: Design Audit the current website, and audit all design patterns such as common reusable UI components and design tokens(such as brand colors, spacing, and typography). Create a Design System in Figma that includes design tokens, atomic and molecular components, and their various states. Development Develop the stories for various states of these components in the storybook. Do the visual tests, interaction tests, and accessibility tests of all these components in the storybook. Create documentation of all these components giving their appropriate usage. The design part of this project has been completed by Aishat Muibudeen (Maya) under the mentorship of Ace. This is the Figma file for the design system created by Maya. The next step and the scope of this GSoC project will be to do the development part and deliver the complete UI Kit. Approach and Implementation Modern user interfaces are assembled from hundreds of modular UI components rearranged to deliver different user experiences. AsyncAPI Website UI Kit will contain reusable UI components that will help developers build complex, durable, and accessible user interfaces across the website. It will be a source of truth for the website's common components. We will use Storybook for developing our website UI kit. Storybook provides a live, visual platform to develop and test UI components, enhancing efficiency and organization. Tech we will be using Storybook for UI component development and documentation React for developing component-centric UI Typescript Tailwind CSS for styling Prettier for automatic code formatting ESLint for JavaScript linting Chromatic to catch visual bugs in components GitHub Actions for Continuous Integration Besides Storybook and Chromatic, the technologies listed above are already used in the AsyncAPI ecosystem. The next section introduces Storybook and Chromatic and discusses why we need them. Storybook Storybook is a front-end workshop for building UI components and pages in isolation. It helps us to develop hard-to-reach states and edge cases without needing to run the whole app. It provides an interactive playground to develop, test, and browse your components, making it an invaluable tool for component-driven development in React. Using it, developers can build UI components detached from their app's business logic and context. This enhances the reusability of components and improves testing and consistency across the application. The need for a Storybook can easily be understood from the following scenario: Say we have to build a form that uses many smaller components like inputs, buttons, etc. Since the Async API Codebase is very huge, it might be difficult to check whether these smaller components are already available in the website codebase or not. Through Storybook developers can search through our project and be able to quickly kind of visually check and how it looks, its properties, play with them to get ideal props values. Chromatic Chromatic is a visual testing & review tool that scans every possible UI state across browsers to catch visual and functional bugs. It catches visual and functional bugs in stories automatically. It runs UI tests across browsers, viewports, and themes to speed up front-end teams. We can assign reviewers and resolve discussions to streamline team sign-off. It streamlines the process of shipping UI components with higher quality. Chromatic is the maintainer of Storybook. Workflow Different organizations use different workflows for building UI kits for their website. However, the engineers at Storybook after researching the best practices used by developers of successful UI Kits and Design Systems suggested the following workflow: Build stories for components Most of the components present in the Design System made by Maya are already developed on the website. Now we will be developing stories for each of these components covering all states that it can have. However, I will be developing the missing components and updating the existing components if required to make them independent of any specific business logic. Currently, I am part of the team working on the migration of the website to TypeScript and have migrated more than 35 components. Through this work, I have developed a deep understanding of the codebase which will help me in the future while working on them. Get a review of the component Once all the stories for a component are developed, get a review from mentors and the designer. However, we can move this step to the end of the loop, once tests and documentation for the component are completed. We can add the Figma frame for each of the components from the Design System in Figma to Storybook using the Figma plugin. This will help developers to cross-reference from the design of the components. Test the component to prevent UI bugs Each UI component includes stories (permutations) that describe the intended look and feel given a set of inputs (props). Stories are then rendered by a browser or device for the end-user. Keeping manual track of all these stories is an unsustainable and hectic task. Storybook enables us to automate tests which helps to detect and rectify bugs. Many types of tests can be performed on the components, but the research done by Storybook engineers suggests that these UI tests are most effective for UI Kit: Visual tests Visual tests capture an image of every UI component in a consistent browser environment. New screenshots are automatically compared to previously accepted baseline screenshots. When there are visual differences, you get notified. This will save the time and effort required for manual reviews. We will use Chromatic for this and automate this using GitHub actions. Interaction tests If our components handle state management or fetch data, we should do the interaction testing of our component using mocked data, Storybook play function, Storybook test package based on the Vitest, and Storybook test-runner for automation. However, in our case, we won’t necessarily need this test. Accessibility tests Disabilities affect 15% of the population, according to the World Health Organization. Therefore we need to check the accessibility of our components. We will do this easily using the accessibility addon by Storybook which verifies the web accessibility standards (WCAG) in real-time. We will automate all these tests using GitHub workflows. Though there are other tests like code coverage tests, snapshot tests, and end-to-end tests, they are not suitable for website UI kits and design systems since they contain atomic components with simple functionality. In our case, Visual tests and accessibility tests are best to have. Document the component To achieve the full work-saving benefits of a website UI kit, components should be easy to understand and widely reused. This can be made possible using documentation for components in the UI kit. However, maintaining and keeping the documentation up-to-date is a tedious task. Storybook enables us to auto-document the components which can be further customized. They provide boilerplate code and offer customizability so that developers don’t have to rewrite common patterns. We will generate documentation from existing stories using the docs add-on which will reduce the maintenance time. Code formatting and lining Enforcing code consistency increases the readability and maintainability of the code. Using tools that fix syntax and standardize formatting serves to improve contribution quality. To ensure a consistent code style, we will be using tools like Prettier and ESLint. These tools are widely used, support multiple languages, and seamlessly integrate with most editors. Organizing the Storybook Appropriately organizing our storybook is important for sending a clear and effective message to developers who will be using it and to those who will be maintaining it in the future. I will be using the following widely used structure for organizing our storybook: Introduce the UI Kit Storybook We will have documentation pages for the Introduction, Getting Started, and changelog at the top. This will be followed by Design Tokens Documentation. For example, Talend Coral uses a mix of DocBlocks and custom components to document their design tokens. Grouping and sorting the components in Storybook Stories for a component are automatically grouped by Storybook. Storybook also allows you to group multiple components into categories and adjust their order in the sidebar. We will be following the Atomic Design hierarchical system. It classifies components into five levels: atoms, molecules, organisms, templates, and pages. However, our UI kit will only have atoms, molecules, and organisms. Each component will have its documentation page inside its category. File Structure in the codebase As suggested by the default installation of the storybook, we can keep all stories in one folder separate folder since our website has a large number of components which can make it hard to navigate between stories and components. So instead of having a single big stories/ directory, I will keep my stories for a component in the same folder in which the component is located. References I have developed the above workflow from the following resources where engineers from Storybook have given detailed suggestions on working with Storybook after researching about more than 60 production Storybooks: Intro to Storybook Design Systems for Developers UI Testing Handbook Visual Testing Handbook Structuring your Storybook Besides these, I have studied the Design System created by Maya in Figma and the website codebase in detail. The images I have used belong to various Storybook and AsyncAPI tutorials. Conclusion With a well-defined workflow and a comprehensive understanding of the project, the next step is to integrate Storybook and Chromatic into the AsyncAPI Website codebase. In the upcoming blog post, I'll delve into the setup process, discuss the challenges I encountered, and share the valuable lessons I learned. Till then take care and don't forget to push your code before giving your laptop to your siblings for downloading and playing random games from the internet displaying ads of downloading free 64 GB RAM.

May 6, 2024

Data Structures and Algorithm

Feb 28, 2024

An algorithm can be defined as a step-by-step process of solving a problem. The problem can be from any area, including math, physics, computer science, and daily life. In this blog, I'll talk about how to analyze any algorithm in computer science. Note: This blog is part of my journey to understand data structures and algorithms, and it is the culmination of my learning notes. I am not an expert, but I will try to be as accurate as possible. In this blog, we will cover the following topics: Difference between an algorithm and a program Characteristics of an algorithm How to write an algorithm An example of writing an algorithm Different types of analysis Key Terms Explained Here's a quick explanation of some key terms you'll encounter in this blog post: Algorithm: A step-by-step process for solving a problem or completing a task. It can be written in plain English, pseudocode, or flowcharts. Program: The actual implementation of an algorithm in a specific programming language, designed to run on a computer. Pseudocode: A way of writing algorithms using keywords that resemble programming languages, but are not specific to any particular language. It's easier to understand than formal code but more precise than natural language. Input: The data or information provided to an algorithm to work with. Output: The result or answer generated by an algorithm after processing the input. Time Complexity: Describes how the execution time of an algorithm changes as the size of its input increases. Common notations include Big O notation (O). Space Complexity: Describes how the memory usage of an algorithm changes as the size of its input increases. Common notations include Big O notation (O). Difference between an algorithm and a program While we've established what an algorithm is, you might wonder how it connects to the software we use daily. Here's where the concept of a program comes in: AlgorithmProgram It is the design of the solution.It is the implementation of the solution. It can be written in human language.It is only written in a programming language. It is independent of hardware and operating systems.It is hardware-dependent and may include an operating system. Characteristics of an Algorithm Every well-defined algorithm possesses certain essential characteristics. Let's delve into these key features to understand what makes an algorithm effective: FeatureDescription InputAn algorithm can have zero or more inputs. OutputAn algorithm must generate at least one result. DefinitenessEvery statement in the algorithm should be non-ambiguous. FinitenessAlgorithms should have a limited number of steps. EffectivenessEvery statement in an algorithm must perform some task. FeasibleAn algorithm should be feasible to implement and give results using as low resources as it can. How to write an algorithm Writing an algorithm involves specifying a set of steps or instructions to solve a particular problem or perform a specific task. It can be written in any simple human-readable format like English, Hindi, Pseudocode, or flowcharts. In this series of blogs, I will mostly use pseudocode. Here is a general guideline on writing an algorithm: Step 1: Define the problem Clearly understand the problem you are trying to solve. Identify the inputs and outputs of the algorithm. Step 2: Understand the Constraints Consider any constraints or limitations that might affect your algorithm. Step 3: Break Down the Problem Divide the problem into smaller, more manageable sub-problems or tasks. Step 4: Outline and define the steps. Start with a high-level overview of the steps needed to solve the problem. Use pseudocode or natural language to describe the steps initially. Step 5: Analyze and refine Example: Finding the Largest Number Let's illustrate the steps of writing an algorithm using a simple example: finding the largest number in a list of numbers. Step 1: Define the problem Problem: Find the largest number in a given list of numbers. Inputs: A list of numbers (e.g., [5, 3, 8, 1, 9]). Output: The largest number in the list (e.g., 9). Step 2: Understand the constraints Assume the list contains only numerical values. Step 3: Break down the problem We can iterate through the list and compare each element with a variable initially set to the first element (assuming it's the largest). If any element is found to be larger, we update the variable to hold that value. Step 4: Outline and define the steps Here's the algorithm in pseudocode: function findLargest(numbers): largest = numbers[0] for i in range(1, len(numbers)): if numbers[i] > largest: largest = numbers[i] return largest Step 5: Analyze and refine This is a basic and efficient algorithm for finding the largest number. We can further analyze its time complexity, which in this case is O(n), where n is the number of elements in the list. This means the execution time increases linearly with the input size. This example demonstrates the process of breaking down a problem into steps, outlining them in an easy-to-understand format (pseudocode), and briefly analyzing its efficiency. Different types of analysis Here's the clear distinction between the different types of analysis used in computer science we will be doing on algorithms in our future blog posts of this series: Analysis/Testing TypeApplies toAnalyzesFocus Time Complexity AnalysisAlgorithmTheoretical time requiredEfficiency in terms of input size Space Complexity AnalysisAlgorithmTheoretical memory requiredEfficiency in terms of input size TestingProgramActual execution time and memory usageReal-world performance under specific conditions That's a Wrap! This blog post has been an introduction to algorithms in computer science. Remember, this journey is an ongoing exploration, and learning is an iterative process. As I continue my quest to understand data structures and algorithms, I'll share more insights and practical examples in future posts. In the next blog post in this series, I will discuss how simple algorithms utilize loops and conditional statements. Feel free to leave comments below with any questions or suggestions you may have. Happy learning! I would love to express my gratitude to Abdul Bari Sir, from whose tutorials I studied this topic. ❤️ Cover Image Background Credit: Photo by MagicPatternon Unsplash About the author I am Ashmit JaiSarita Gupta, an engineering physics undergraduate at the National Institute of Technology Hamirpur. I am passionate about Web Development and Quantum Computing. I am currently exploring Data Structures and algorithms, and spend my free time contributing to open-source (mostly at AsyncAPI and Uptane). Visit my portfolio website to learn more about me, my previous projects, and the places I have worked. Feel free to connect with me on Twitter, LinkedIn, or GitHub.

Feb 28, 2024

In this blog post, I will share my weekly report on the Implementation of Quantum Neural Network as a part of QCourse-551 under the supervision of mentors from QWorld and Classiq. Classical neural networks have brought advanced capabilities to solve ...

Sep 27, 2023

In this blog post, I will share my weekly report on the Implementation of Quantum Neural Network as a part of QCourse-551 under the supervision of mentors from QWorld and Classiq. Classical neural networks have brought advanced capabilities to solve problems we couldn't before. I will be developing this project and publishing research work in the upcoming four months starting today! I will also be participating in the Classiq Bootcamp and Hackathon in October. It's going to be a lot of fun! Project Detail Quantum Neural Networks involve combining classical neural networks with the advantage of quantum information to create more efficient algorithms. The goal of this project is to understand how we can utilize the quantum capabilities to create quantum layers. We will create a hybrid network of classical and quantum layers to classify the MNIST dataset. Mentor: Tal Michaeli Team Members: Ashmit JaiSarita Gupta (Me), Asif Saad, Roman Ledenov GitHub Repository of my work: devilkiller-ag/QNN-MNIST-Classification (github.com) Presentation Slides: QNN-MNIST-Classification/PresentationSlides at main · devilkiller-ag/QNN-MNIST-Classification (github.com) Workflow [🗸] Understanding the basics of Quantum Neural Networks and Classiq. [🗸] Decide the Quantum Neural Network Architecture. (VQC?) [🗸] Preprocess the MNIST dataset for compatibility with our quantum algorithm. Convert pixel values to a format suitable for quantum circuits. (Angle Encoding) [🗸] Develop a quantum encoding scheme to represent MNIST digits using qubits. This step involves mapping classical data to quantum states. [🗸] Create Quantum Neural Network. [...] Experimentation: Training & Testing Week 1 Installed the Classiq SDK with the QML extension. Organized the first group meeting (with Asif Asad) and decided to go through the Classiq QNN Documentation and the paper on Quantum On Chip Training. Read the Paper on Quantum On Chip Training with Parameter Shift and Gradient Pruning. Exploring the Torch Quantum Python Library (Website | Repository) and the implementation of a simple QNN for MNIST Training. Attend the two lessons of the Classiq Bootcamp. I connected to Amirali Malekani Nezhad and Roman Ledenov today. For Week 2, we plan to discuss the project in detail, create a project timeline, divide tasks among us, and start implementing it. I am participating in the Quantum Games Hackathon (30th Sept. - 8th Oct. 2023) so maybe my output will be a little low in Week 2 but I will try to cover everything in Week 3. Here are my findings from the paper I read: Parameterized Quantum Circuit (PQC) Gradient Can be obtained by parameter shift whose cost scales linearly with the number of qubits. Quantum On Chip QOC) PQC Training with parameter shift gives gradient however gradients obtained from naïve parameter shift have low fidelity and thus degrading the training accuracy. We can use probabilistic gradient pruning to identify gradients with potentially large errors and then remove them. Specifically, small gradients have larger relative errors than large ones, thus having a higher probability of being pruned. Result: The results demonstrate that our on-chip training achieves over 90% and 60% accuracy for 2-class and 4-class image classification tasks. The probabilistic gradient pruning brings up to 7% PQC accuracy improvements over no pruning. Methodology: To enable PQC on-chip learning, we first use an in-situ quantum gradient computation via parameter shift and its real QC implementation. A probabilistic gradient pruning method is then used to save the gradient computation cost with enhanced noise-robustness and training efficiency. Week 2 I wasn't able to work much in Week 2 because I was participating in a Quantum Games Hackathon 2023 where I developed QuantaVania an action-adventure 2D platformer game with the potential to evolve into an open-world sandbox game in which players can learn quantum computing from the ground up while playing, design their game level and share it with others in the quantum community via our web platform, and mine qubits, quantum gates and power-ups. Our game will not only allow them to run the game on their local device but also real quantum computers and simulators from various quantum computing providers like IBM Quantum, IONQ, Rigetti Computing, etc. We intend to teach the players quantum computing as they go through the levels. We'll expose them to qubits in the first level, and then they'll have to find the X-Gate hiding behind any box or monster. As the levels progress, the player will discover new gates that he may use in the gun circuit. And, at the end of each level, we will introduce to the user each quantum algorithm, from basic to advanced, in the form of a game problem. We won the Special Category Prize by Snarto/Onyx for demonstrating Logistics Optimization using Quantum Computing through our game. Week 3 This week was very difficult for our team. Most of our mentors and organizers of the QCourse 551-1 are based in Israel. Unfortunately, Hamas attacked Israel which led to War in the region. Due to this and lack of communication in the last week, our team came into silent mode. We weren't able to decide on common meeting timing and communication media. We almost lost our hope of being able to initiate the project. Week 4 Thanks to our mentor Tal, he didn't lose faith in us and motivated us to start from zero. He organized a team meeting guided us in detail on how to start and gave us an introduction to the Classiq Documentation on Quantum Neural Networks. We restarted our work and formed a WhatsApp discussion group. All three of us are from different backgrounds and we knew little about Quantum Neural Network. Our plan for this week was to introduce ourselves to the unfamiliar concepts we will be using in this project. Here is the list of things I did this week: They studied Tensors and PyTorch Basics from this tutorial on YouTube by Mr. P. Solver. I have studied the implementation of a Basic Neural Network by following this tutorial. Studied Cross-Entropy Loss Function (tutorial). Studied Sequential Neural Network (tutorial). A general "sequential" neural network can be expressed as $$f(x) = \underset{i=1}{\overset{n}{\Huge{\kappa}}} R_i(A_ix+b_i)$$ where $$\underset{i=1}{\overset{n}{\Huge{\kappa}}}f_i(x) = f_n \circ f_{n-1} \circ \ldots \circ f_1(x)$$ and the A_i are matrices and the b_i are bias vectors. Typically the R_i are the same for all the layers (typically ReLU) except for the last layer, where R_i is just is just the identity function. In clever architectures, like convolutional neural networks, the A_i's become sparse matrices (most of their parameters are fixed to equal zero). Implemented Sequential Neural Network for MNIST Dataset using PyTorch by following this tutorial. Here is the notebook for my implementation. Studied Quantum Neural Networks, Quantum Layer, and Datasets provided by Classiq through Classiq Documentation on QNN. Implemented Quantum Neural Network using Classiq and PyTorch to determine the correct angle for Rx Gate for performing a "NOT" Gate. Here is the notebook for my implementation. For implementing QNN for the MNIST Dataset, the first thing we need to do is encode images into Qubits. The MNIST dataset contains 28x28 px images which we want to encode in less than 6 qubits/image. We can do this by using algorithms like Quantum Probability Image Encoding (Resources: Qiskit Textbook and Medium article on QPIE and QHED by Jimin Lee) or Flexible Representation of Quantum Images or Novel Enhanced Quantum Representation of Images. We need to research which option will be best for our purpose. Code for Implemented Sequential Neural Network for MNIST Dataset using PyTorch: # Imports import torch import torch.nn as nn from torch.optim import SGD import torch.nn.functional as F from torch.utils.data import Dataset, DataLoader import torchvision import numpy as np import matplotlib.pyplot as plt class CTDataset(Dataset): def __init__(self, filepath): self.x, self.y = torch.load(filepath) self.x = self.x / 255. self.y = F.one_hot(self.y, num_classes=10).to(float) def __len__(self): return self.x.shape[0] def __getitem__(self, ix): return self.x[ix], self.y[ix] class MyNeuralNet(nn.Module): def __init__(self): super().__init__() self.Matrix1 = nn.Linear(28**2,100) self.Matrix2 = nn.Linear(100,50) self.Matrix3 = nn.Linear(50,10) self.R = nn.ReLU() def forward(self,x): x = x.view(-1,28**2) x = self.R(self.Matrix1(x)) x = self.R(self.Matrix2(x)) x = self.Matrix3(x) return x.squeeze() # Dataset and DataLoader train_ds = CTDataset('MNIST/processed/training.pt') test_ds = CTDataset('MNIST/processed/test.pt') train_dl = DataLoader(train_ds, batch_size=5) # Loss Function L = nn.CrossEntropyLoss() # Network f = MyNeuralNet() # Training def train_model(dl, f, n_epochs=20): # Optimization opt = SGD(f.parameters(), lr=0.01) L = nn.CrossEntropyLoss() # Train model losses = [] epochs = [] for epoch in range(n_epochs): print(f'Epoch {epoch}') N = len(dl) for i, (x, y) in enumerate(dl): # Update the weights of the network opt.zero_grad() loss_value = L(f(x), y) loss_value.backward() opt.step() # Store training data epochs.append(epoch+i/N) losses.append(loss_value.item()) return np.array(epochs), np.array(losses) epoch_data, loss_data = train_model(train_dl, f) # Plot of cross entropy averaged per epoch epoch_data_avgd = epoch_data.reshape(20,-1).mean(axis=1) loss_data_avgd = loss_data.reshape(20,-1).mean(axis=1) plt.plot(epoch_data_avgd, loss_data_avgd, 'o--') plt.xlabel('Epoch Number') plt.ylabel('Cross Entropy') plt.title('Cross Entropy (avgd per epoch)') # Plotting 40 training results xs, ys = train_ds[0:2000] yhats = f(xs).argmax(axis=1) fig, ax = plt.subplots(10,4,figsize=(10,15)) for i in range(40): plt.subplot(10,4,i+1) plt.imshow(xs[i]) plt.title(f'Predicted Digit: {yhats[i]}') fig.tight_layout() plt.show() # Testing and plotting 40 predictions xs, ys = test_ds[:2000] yhats = f(xs).argmax(axis=1) fig, ax = plt.subplots(10,4,figsize=(10,15)) for i in range(40): plt.subplot(10,4,i+1) plt.imshow(xs[i]) plt.title(f'Predicted Digit: {yhats[i]}') fig.tight_layout() plt.show() Code for Implementing Quantum Neural Network using Classiq and PyTorch to determine the angle of Rx Gate to make it an X Gate # Imports from typing import Dict from classiq import Model ,synthesize from classiq.builtin_functions import HardwareEfficientAnsatz from classiq import QReg from classiq.applications.qnn import QLayer from classiq.applications.qnn.datasets import DATALOADER_NOT from classiq.applications.qnn.types import ( MultipleArguments, SavedResult, ResultsCollection ) from classiq.execution import execute_qnn from classiq.synthesis import SerializedQuantumProgram import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import DataLoader # Step 1: Creating Quantum Layer ## Step 1.1: Create Parametric Quantum Circuit (PQC) _NUM_QUBITS = 1 _REPS = 1 _CONNECTIVITY_MAP = "circular" def add_rx(md: Model, prefix: str, in_wire=None) -> Dict[str, QReg]: if in_wire is not None: kwargs = { "in_wires": { "IN": in_wire["OUT"] } } else: kwargs = {} hwea_params = HardwareEfficientAnsatz( num_qubits=_NUM_QUBITS, connectivity_map=_CONNECTIVITY_MAP, reps=_REPS, one_qubit_gates="rx", two_qubit_gates=[], parameter_prefix=prefix, ) return md.HardwareEfficientAnsatz(hwea_params, **kwargs) model = Model() output_1 = add_rx(model, "input_") output_2 = add_rx(model, "weight_", output_1) quantum_program = synthesize(model.get_model()) ## Step 1.2: Create the execution and post-processing def execute(quantum_program: SerializedQuantumProgram, arguments:MultipleArguments) -> ResultsCollection: return execute_qnn(quantum_program, arguments) def post_process(result: SavedResult) -> torch.Tensor: """ Take in a `SavedResult` with `ExecutionDetails` value type, and return the probability of measuring |0> which equals the amount of `|0>` measurements divided by the total amount of measurements. """ counts: dict = result.value.counts # The probability of measuring |0> p_zero: float = counts.get("0", 0.0) / sum(counts.values()) return torch.tensor(p_zero) ## Step 1.3: Create a network class QNet(torch.nn.Module): def __init__(self, *args, **kwargs) -> None: super().__init__() self.qlayer = QLayer( quantum_program, execute, post_process, *args, **kwargs, ) def forward(self, x: torch.Tensor) -> torch.Tensor: x = self.qlayer(x) return x model = QNet() # Step 2: Choose a Dataset, Loss Function, and Optimizer _LEARNING_RATE = 1.0 data_loader = DATALOADER_NOT loss_function = nn.L1Loss() optimizer = optim.SGD(model.parameters(), lr=_LEARNING_RATE) # Step 3: Training def train(model: nn.Module, data_loader: DataLoader, loss_function: nn.modules.loss._Loss, optimizer: optim.Optimizer, epoch: int = 20) -> None: for index in range(epoch): print(index, model.qlayer.weight) for data, label in data_loader: optimizer.zero_grad() output = model(data) loss = loss_function(output, label) loss.backward() optimizer.step() train(model, data_loader, loss_function, optimizer) # Step 4: Testing def check_accuracy(model: nn.Module, data_loader: DataLoader, atol=1e-4) -> float: num_correct = 0 total = 0 model.eval() with torch.no_grad(): for data, labels in data_loader: predictions = model(data) is_prediction_correct = predictions.isclose(labels, atol=atol) print(f"data: {data}\n labels: {labels}\n is_prediction_correct: {is_prediction_correct}\n sum: {is_prediction_correct.sum()}\n item: {is_prediction_correct.sum().item()}") num_correct += is_prediction_correct.sum().item() total += labels.size(0) accuracy = float(num_correct) / float(total) print(f"Test Accuracy of the model: {accuracy*100:.2f}") return accuracy check_accuracy(model, data_loader) # The results show that the accuracy is 1, meaning a 100% success rate # at performing the required transformation (i.e. the network learned to # perform a X-gate). We may further test it by printing the value of # model.qlayer.weight, which is a tensor of shape (1,1), which should, # after training, be close to pi=3.1416. A sneak peek at Quantum Circuit Game Engine I published this week This week, I transformed a segment of my previous quantum game projects into a Python package. This move aims to facilitate quantum game developers in seamlessly incorporating quantum circuits into their Quantum games built on the Pygame platform. The features I have included are: Modular and Abstract Code. All configurations are in one place in the config.py file. Developers can create a Quantum Circuit for any number of qubit/wires and circuit width (max. number of gates which can be applied in a wire) of their choice. Easy to change UI by replacing color configs and graphics for gates with those of your choice. Easy to change the size of the Quantum Circuit by adjusting QUANTUM_CIRCUIT_TILE_SIZE, GATE_TILE_WIDTH, and GATE_TILE_HIEGHT in the config.py file. Easily change controls by changing keys in the handle_input() method of the QuantumCircuitGrid class. If this project is helpful for you or you liked my work, consider supporting me through Ko.fi🍵. Also, kindly consider giving a star to this repository.😁 You can install the QCGE python package using pip install qcge. Explore more on the PyPI page of this package and the GitHub repository. Week 5 This week I mainly researched the flow we have to take for building our Quantum Neural Network and several methods to encode dataset images into a Quantum Circuit. I am adding the flow I have decided at the top of the blog for easy access and check-marked the steps that are completed. Initially, I explored image processing encoding methods like FRQI, NERQ, and QPIXL++. However, from my research, I concluded that these techniques are primarily designed for quantum image processing tasks rather than traditional machine learning datasets like MNIST. Our MNIST Dataset contains images of 28px x 28px size. QPIXL++ is among the latest and most efficient methods to encode images into quantum circuits but it is implemented in C++ and this too is suitable for quantum image processing. I found the following methods good for our dataset images but these require too many qubits. Quantum Encoding Techniques: Amplitude Encoding: Amplitude encoding represents pixel values as probability amplitudes of quantum states. This will require around 8 to 10 qubits per pixel. For a 28x28 color image, a rough estimate suggests the need for approximately (28 x 28 x 8) to (28 x 28 x 10) qubits. Precision in representing pixel intensities plays a key role in determining the qubit requirements. Angle Encoding: Angle encoding involves mapping pixel values to angles in quantum states. Similar to amplitude encoding, the qubit requirement is (28 x 28 x 8) to (28 x 28 x 10) qubits, depending on the precision needed for the angles. Binary Encoding: Binary encoding represents each pixel with a binary string. For colorful images using 8 bits per pixel, the qubit requirement is (28 x 28 x 8) qubits. This method offers simplicity in representation. Quantum Feature Maps: Quantum feature maps transform classical data into quantum states using quantum gates. The qubit requirement is comparable to amplitude and angle encoding, ranging from (28 x 28 x 8) to (28 x 28 x 10) qubits. Quantum Circuit Encoding: Quantum circuit encoding represents the entire image with a quantum circuit. The qubit requirements depend on the circuit's depth and complexity, potentially exceeding amplitude and angle encoding. Quantum Convolutional Networks (QCN): QCNs simulate classical convolutional layers using quantum gates. Qubit requirements can be high, depending on the network's architecture and design choices. In our case, I have used 1 qubit instead of 8-10 qubits per pixel, so my implementation uses 28x28 qubits for image encoding. Initial Tal suggested using Angle Encoding for our dataset, but I am not sure if using these many qubits is practical for our approach or not. We need to discuss this in detail with him. I have written code for loading images and encoding them using Amplitude and Angle Encoding Techniques. The notebook for it is available in the GitHub repository I mentioned at the start of the blog. Imports import numpy as np import math import matplotlib.pyplot as plt from matplotlib import style from PIL import Image style.use('default') from qiskit import QuantumCircuit Image Loading # Load Image def load_image(img_path, image_size): # Load the image from filesystem image_raw = np.array(Image.open(img_path)) print('Raw Image Info: ', image_raw.shape) print('Raw Image Datatype: ', image_raw.dtype) # Convert the RBG component of the image to B&W image, as a numpy (uint8) array image = [] for i in range(image_size): image.append([]) for j in range(image_size): image[i].append(image_raw[i][j][0] / 256) image = np.array(image) print('Image shape (numpy array): ', image.shape) # Display the image plt.title('Big Image') plt.xticks(range(0, image.shape[0]+1, 32)) plt.yticks(range(0, image.shape[1]+1, 32)) plt.imshow(image, extent=[0, image.shape[0], image.shape[1], 0], cmap='viridis') plt.show() return image Plot Image def plot_image(img, title: str): plt.title(title) plt.xticks(range(img.shape[0]+1, 32)) plt.yticks(range(img.shape[1]+1, 32)) plt.imshow(img, extent=[0, img.shape[0], img.shape[1], 0], cmap='viridis') plt.show() Amplitude Encoding def amplitude_encode_image(image, size): # Create a quantum circuit num_qubits = size * size qc = QuantumCircuit(num_qubits, num_qubits) # Amplitude encode each pixel value into the quantum state for i in range(size): for j in range(size): intensity = image[i, j] amplitude = np.sqrt(intensity / 256) # Map intensity to amplitude qc.ry(2 * np.arcsin(amplitude), i * size + j) return qc ## USAGE ## image_path = './assets/mnist-0-28.png' image_size = 28 # Original Image-Width image = load_image(image_path, image_size) amplitude_encoded_circuit = amplitude_encode_image(image, size=image_size) print(amplitude_encoded_circuit) Angle Encoding def angle_encode_image(image, size): # Create a quantum circuit num_qubits = size * size qc = QuantumCircuit(num_qubits, num_qubits) # Angle encode each pixel value into the quantum state for i in range(size): for j in range(size): intensity = image[i, j] angle = intensity * (2 * np.pi / 256) # Map intensity to angle qc.ry(angle, i * size + j) return qc ## USAGE ## image_path = './assets/mnist-0-28.png' image_size = 28 # Original Image-Width image = load_image(image_path, image_size) angle_encoded_circuit = angle_encode_image(image, size=image_size) print(angle_encoded_circuit) Both techniques use 28x28 = 784 qubits for encoding the image into a quantum circuit. I have saved the output circuit drawing in Txt format in the following files located in the same directory: amplitude_encoding_output.txt and angle_encoding_output.txt. Week 6 My end-semester exams have started this week (November 6th, 2023 to November 27th, 2023). After Discussing the above encoding schemes in the bi-weekly meeting with our mentor (Tal Michaeli) We decided on the Quantum Neural Network Architecture for our purpose as given below. We also planned to go ahead with the following steps as a classical pre-processing layer: Compress/Shrink the dataset images using classical methods to bring down the number of pixels in the image and consequently the number of qubits required. Use angle encoding to encode two pixels per qubit by using two rotation gates (Rx and Ry) on each qubit. Quantum Neural Network Architecture: This Hybrid QNN architecture combines classical and quantum processing for efficient MNIST digit classification. Classical data preprocessing is followed by quantum encoding, where classical information is translated into quantum states. The Quantum Layer utilizes quantum gates to perform computations, followed by Classical Layer(s) for further processing. The Quantum/Classical interfacing or pooling layer integrates quantum and classical information, leading to classical output post-processing. Finally, the classification step provides the ultimate prediction. This hybrid approach leverages the strengths of quantum computing while maintaining compatibility with classical methods, offering a promising paradigm for enhanced machine learning tasks on quantum devices. Step 1: Classical Data Pre-Processing: Shrinking the Image: We are using PyTorch transforms to resize our dataset images of 28 x 28 px to shrink them into 10 x 10 px images. Shrinking the images more is resulting in loss of data which will increase the error rate. def resize_image(image, resize_value=4): resize_transform = transforms.Compose([ transforms.ToPILImage(), transforms.Resize((resize_value, resize_value)), transforms.ToTensor() ]) re_image = resize_transform(image) re_image = re_image.squeeze() return re_image def plot_numpy_image(image, title='image'): plt.imshow(image, cmap='viridis') plt.title(title) plt.colorbar() plt.show() This is the plot of the first 4 data images without compression: fig, ax = plt.subplots(1, 4, figsize=(10, 15)) for i in range(4): plt.subplot(1,4,i+1) plt.imshow(x[i]) plt.title(f'Digit is: {y[i]}') fig.tight_layout() plt.savefig(f"original.png") plt.show() After Resizing this the output images shrinked to 10x10px: resize_value = 10 fig, ax = plt.subplots(1, 4, figsize=(10, 15)) for i in range(4): plt.subplot(1,4,i+1) image = x[i].numpy() re_image = resize_image(image, resize_value) plt.imshow(re_image) plt.title(f'Digit is: {y[i]}') fig.tight_layout() plt.savefig(f"compressed_to_{resize_value}_px.png") plt.show() Step 2: Quantum Encoding: Angle Encoding We used angle encoding to encode two pixels per qubit by using two rotation gates (Rx and Ry) on each qubit. The angle of the rotation rate is decided according to the intensity of pixels. def angle_encode_image(image, size): # Create a quantum circuit num_qubits = size * size // 2 qc = QuantumCircuit(num_qubits, num_qubits) # Angle encode pairs of pixel values into the quantum state for i in range(0, size, 2): for j in range(0, size, 2): intensity1 = image[i, j] intensity2 = image[i, j + 1] angle_rx = intensity1 * (2 * np.pi / 256) # Map intensity to Rx angle angle_ry = intensity2 * (2 * np.pi / 256) # Map intensity to Ry angle qubit_index = (i // 2) * (size // 2) + (j // 2) qc.rx(angle_rx, qubit_index) qc.ry(angle_ry, qubit_index) return qc Week 7 (I was having my exam this week.) This Tuesday, I presented our work progress in front of all teams. This week was a little stressful for me due submission deadline for recording my three talks at the Data Science Conference 2023 and my End-Semester Exams. Asif and Roman were not able to present the work so I had to give the presentation alone. However, they helped me a lot in creating the slides for the presentation. Here is my presentation slide. I haven't worked much after this due to two exams this week. However, I had a meeting with Asif where we discussed our next step and we found an interesting paper on Efficient Learning for Deep Quantum Neural Networks which Asif will explore before our next meet. We haven't been able to contact Roman for the last week so we are unaware of his status on the project. Week 8 (I was having my exam this week.) This week, I tried to write code for every individual part of the circuit. I was a little confused about the API of Classiq and how to use them. I tried to write this with the help of internet searches and classic documentation. This is the implementation I followed to build Hybrid QNN as specified in this paper: Classical Compression Layer Center Crop the 28x28 image to 24x24 image Down Sample to 4x4 image Flatten the Image Quantum Encoding Layer Encode the flattened image into a 4-qubit circuit using RX, RY, RZ, and RY gates as discussed earlier. Quantum Entanglement Layer This itself contains four entangling layers as described in the paper: " (i) RZZ layer: add RZZ gates to all logical adjacent wires and the logical farthest wires to form a ring connection, for example, an RZZ layer in a 4-qubit circuit contains 4 RZZ gates which lie on wires 1 and 2, 2 and 3, 3 and 4, 4 and 1; (ii) RXX layer: same structure as in RZZ layer; (iii) RZX layer: same structure as in RZZ layer; (i𝑣) CZ layer: add CZ gates to all logical adjacent wires." Hybrid Neural Network To combine all these layers. import torch import torchvision.transforms as transforms from torch import nn import matplotlib.pyplot as plt from PIL import Image import numpy as np from typing import Dict import classiq from classiq import Model, QReg, RX, RY, RZ, synthesize from classiq.builtin_functions import HardwareEfficientAnsatz classiq.authenticate() _NUM_QUBITS = 4 _CONNECTIVITY_MAP = "circular" # Classical Layer for Image Commpression:The input MNIST images are all 28 × 28. # This Classical Layer will firstly center-crop them to 24 × 24 and then down-sample them to 4 × 4 for MNIST. class ClassicalCompressionLayer(nn.Module): def __init__(self): super(ClassicalCompressionLayer, self).__init__() self.center_crop = transforms.CenterCrop((24, 24)) self.down_sample = transforms.Resize((4, 4)) self.flatten = nn.Flatten() def forward(self, x): x = self.center_crop(x) x = self.down_sample(x) x = self.flatten(x) return x # Quantum Layer for Encoding: The output of Classical Compression Layer is # encoded by this quantum layer into a quantum circuit. We use Angle encoding to encode 4 pixels per qubit using RX, RY, RZ, and RX gate on each qubit. class QuantumEncodingLayer(Model): def __init__(self): super().__init__() def encode_pixels(self, pixel_values: torch.Tensor) -> Dict[str, QReg]: # Split pixel values into groups of 4 pixel_groups = pixel_values.split(4) # Initialize dictionary to store qubit outputs qubit_outputs = {} # Encode each group of 4 pixels into angles for RX, RY, RZ, RX gates for i, pixel_group in enumerate(pixel_groups): rx_angle = pixel_group[0] * (2 * torch.pi / 255) ry_angle = pixel_group[1] * (2 * torch.pi / 255) rz_angle = pixel_group[2] * (2 * torch.pi / 255) rx2_angle = pixel_group[3] * (2 * torch.pi / 255) # Apply gates to corresponding qubit qubit_outputs[f"qubit_{i}"] = RX(rx_angle) & RY(ry_angle) & RZ(rz_angle) & RX(rx2_angle) return qubit_outputs # Quantum Layer for Entanglement class QuantumEntanglementLayer(Model): def __init__(self): super().__init__() def add_entanglement_layer(self) -> Dict[str, QReg]: hwea_params = HardwareEfficientAnsatz( num_qubits=_NUM_QUBITS, connectivity_map=_CONNECTIVITY_MAP, one_qubit_gates=[], two_qubit_gates=["rzz, rxx, rzx, cz"], ) return self.HardwareEfficientAnsatz(hwea_params) # Hybrid Quantum Neural Network class HybridQuantumNeuralNetwork(Model): def __init__(self): super().__init__() # Instantiate Layer self.classical_compression_layer = ClassicalCompressionLayer() self.encoding_layer = QuantumEncodingLayer() self.entanglement_layer = QuantumEntanglementLayer() # Import Data # Add Encoding Layer encoding_out = self.encoding_layer.encode_pixels() # Add Entanglement Layer entanglement_out = self.entanglement_layer.add_rzz_layer() # Add layers to the model self.add(encoding_out, entanglement_out) def forward(self, x): # Classical Compression compressed_data = self.classical_compression_layer(x) # Quantum Encoding encoding_result = self.encoding_layer(compressed_data) # Quantum Entanglement entanglement_result = self.entanglement_layer() # Concatenate quantum and classical outputs output = self.concatenate( [encoding_result, entanglement_result, compressed_data] ) return output # hybrid_model = HybridQuantumNeuralNetwork() # quantum_program = synthesize(hybrid_model.get_model()) Upon review with Tal, I learned that my class-based implementation was wrong and that we needed to have a single model for both the encoding and entanglement layers. Week 9 In week 9, I traveled back home from my college (which took three days). We started doing peer programming on every alternate day. We tried to figure out the individual circuits for encoding, entanglement, and cz-block. We also discussed the post-processing. Here is the code we updated from the code I wrote in week 8: import torch import classiq import torch.nn as nn import torch.optim as optim from torch.utils.data import Dataset, DataLoader import torch.nn.functional as F from typing import Dict from classiq import Model, synthesize, QReg, QFunc from classiq.builtin_functions import HardwareEfficientAnsatz from classiq.applications.qnn import QLayer from classiq.execution import execute_qnn from classiq.synthesis import SerializedQuantumProgram from classiq.applications.qnn.types import ( MultipleArguments, SavedResult, ResultsCollection, ) classiq.authenticate() # constants _NUM_QUBITS = 4 _REPS = 1 _FULLY_CONNECTED_MESH = [[0, 1], [1, 2], [2, 3], [3, 0]] _LEARNING_RATE = 1.0 def add_entanglement(md: Model, prefix: str, in_wire=None) -> Dict[str, QReg]: if in_wire is not None: kwargs = { "in_wires": { "IN": in_wire["OUT"] } } else: kwargs = {} hwea_params = HardwareEfficientAnsatz( num_qubits=_NUM_QUBITS, connectivity_map=_FULLY_CONNECTED_MESH, reps=_REPS, one_qubit_gates=[], two_qubit_gates=["rzz", "rxx", "rzx"], parameter_prefix=prefix, ) return md.HardwareEfficientAnsatz(hwea_params, **kwargs) model = Model() out1 = add_entanglement(model, "input_") out2 = add_entanglement(model, "weight_", out1) quantum_program = synthesize(model.get_model()) def execute(quantum_program: SerializedQuantumProgram, arguments: MultipleArguments) -> ResultsCollection: return execute_qnn(quantum_program, arguments) # TODO: MODIFY THIS # Post-process the result, returning a dict: # Note: this function assumes that we only care about # differentiating a single state (|0>) # from all the rest of the states. # In case of a different differentiation, this function should change. def post_process(result: SavedResult) -> torch.Tensor: """ Take in a `SavedResult` with `ExecutionDetails` value type, and return the probability of measuring |0> which equals the amount of `|0>` measurements divided by the total amount of measurements. """ counts: dict = result.value.counts # The probability of measuring |0> p_zero: float = counts.get("0", 0.0) / sum(counts.values()) return torch.tensor(p_zero) However, we still were confused about how to connect all these parts of the model. Week 10 This week I successfully developed the full Model for our QNN after receiving help from Tal in the weekly meeting. Unfortunately, Asif and Roman missed this week's meeting. So I tried to ask doubts of all three from Tal. Here is the correct working implementation of the Model: import classiq import torch.nn as nn import torchvision.transforms as transforms from classiq import create_model, synthesize, show, QFunc, QArray, QBit, Output, allocate, RX, RY, RZ, RZZ, RXX, RYY, CZ classiq.authenticate() @QFunc def encoding(q: QArray[QBit]) -> None: """ This function encodes the input data into the qubits. This input data is a 4x4 image pixel values converted into angle for rotation gates (RX, RY, RZ, RX) in form of a 16x1 vector. We encode 4 pixels per qubit. Args: q (QArray[QBit]): Array of four Qubits to encode the input data into. """ RX(theta="input_0", target=q[0]) # Pixel 0 on Qubit 0 RY(theta="input_1", target=q[0]) # Pixel 1 on Qubit 0 RZ(theta="input_2", target=q[0]) # Pixel 2 on Qubit 0 RX(theta="input_3", target=q[0]) # Pixel 3 on Qubit 0 RX(theta="input_4", target=q[1]) # Pixel 4 on Qubit 1 RY(theta="input_5", target=q[1]) # Pixel 5 on Qubit 1 RZ(theta="input_6", target=q[1]) # Pixel 6 on Qubit 1 RX(theta="input_7", target=q[1]) # Pixel 7 on Qubit 1 RX(theta="input_8", target=q[2]) # Pixel 8 on Qubit 2 RY(theta="input_9", target=q[2]) # Pixel 9 on Qubit 2 RZ(theta="input_10", target=q[2]) # Pixel 10 on Qubit 2 RX(theta="input_11", target=q[2]) # Pixel 11 on Qubit 2 RX(theta="input_12", target=q[3]) # Pixel 12 on Qubit 3 RY(theta="input_13", target=q[3]) # Pixel 13 on Qubit 3 RZ(theta="input_14", target=q[3]) # Pixel 14 on Qubit 3 RX(theta="input_15", target=q[3]) # Pixel 15 on Qubit 3 @QFunc def mixing(q: QArray[QBit]) -> None: """ This function performs the mixing operation on the qubits. This is done by applying a series of RZZ, RXX, RYY gates to form a ring connection. Args: q (QArray[QBit]): Array of four Qubits to apply the mixing operation on. """ RZZ(theta="weight_0", target=q[0:2]) RZZ(theta="weight_1", target=q[1:3]) RZZ(theta="weight_2", target=q[2:4]) # RZZ(theta="weight_3", target=q[3:1]) RXX(theta="weight_4", target=q[0:2]) RXX(theta="weight_5", target=q[1:3]) RXX(theta="weight_6", target=q[2:4]) # RXX(theta="weight_7", target=q[3:1]) RYY(theta="weight_8", target=q[0:2]) RYY(theta="weight_9", target=q[1:3]) RYY(theta="weight_10", target=q[2:4]) # RYY(theta="weight_11", target=q[3:1]) @QFunc def cz_block(q: QArray[QBit]) -> None: """ This function applies CZ gates between each qubit. Args: q (QArray[QBit]): Array of four Qubits to apply the entanglement operation on. """ CZ(control=q[0], target=q[1]) CZ(control=q[1], target=q[2]) CZ(control=q[2], target=q[3]) @QFunc def main(res: Output[QArray[QBit]]) -> None: """ This is the main function from which model will be created. It calls the other functions to perform the encoding, mixing and entanglement. Args: res (Output[QArray[QBit]]): Output QArray of QBits from which the model will be created. """ allocate(4, res) encoding(q=res) mixing(q=res) cz_block(q=res) # Create a model model = create_model(main) quantum_program = synthesize(model) show(quantum_program) Here is the resultant quantum program from this model: Our next step will be to design the post-process function and then start the training part. Week 11 This week I worked on getting the dataset, pre-processing it, creating the data loader for testing and training data, quantum neural network, and training and Testing functions. Let's discuss them one by one. Setting Device Agnostic Code device = "cuda" if torch.cuda.is_available() else "cpu" device Now let's prepare the data to be passed into our quantum neural network. Defining functions for pre-processing data images and corresponding labels Before getting our dataset, let's just quickly define how we want our input MNIST images should be pre-processed. As discussed earlier, the input MNIST images are all 28 × 28 px. We want to first center-crop them to 24 × 24 and then down-sample them to 4 × 4 for MNIST. Then we convert the image pixels into angles for passing them into Rotation gates later for encoding. import torch import torchvision.transforms as transforms def input_transform(image): """ The input MNIST images are all 28 × 28 px. This function will firstly center-crop them to 24 × 24 and then down-sample them to 4 × 4 for MNIST. Then we convert the image pixels into angles for passing them into Rotation gates later for encoding. """ image = transforms.ToTensor()(image) image = transforms.CenterCrop(24)(image) image = transforms.Resize(size = (4,4))(image) image = image.squeeze() image_pixels = torch.flatten(image) angles = torch.sqrt(image_pixels / 256) return angles I have also defined an empty function in which we can define how we want to transform labels of data before comparing it from the output of our Quantum Layer. def target_transform(x): return x Getting a dataset We are using the MNIST dataset provided by torchvision.datasets. We are using the pre-processing functions input_transform and target_transform defined above to transform our data into a usable format. from torchvision import datasets # Setup training data train_data = datasets.MNIST( root="data", train=True, download=True, transform=input_transform, target_transform=target_transform ) # Setup testing data test_data = datasets.MNIST( root="data", train=False, download=True, transform=input_transform, target_transform=target_transform ) Let's see what we have got here: len(train_data), len(test_data) ## Output: (60000, 10000) Hmm.. We have got 60000 training data and 10000 testing data. Let's see what our data looks like: # See the first training example image, label = train_data[0] image, label ## OUTPUT: # (tensor([0.0000, 0.0000, 0.0317, 0.0378, 0.0000, 0.0336, 0.0000, 0.0000, # 0.0000, 0.0000, 0.0477, 0.0000, 0.0295, 0.0620, 0.0000, 0.0000]), 5) You can notice we have a tensor of 16 angles corresponding to an MNIST image of digit 5. Let's go further and prepare Dataloader for training and testing. Preparing Dataloader Let's for now create a subset of training and testing data containing only 64 data for quick experimenting. from torch.utils.data import Subset # Define the size of the subset subset_size = 64 # Create subsets of the datasets train_subset = Subset(train_data, range(subset_size)) test_subset = Subset(test_data, range(subset_size)) Let's create 2 Batches of size 32 out of these: # Setup the batch size hyperparameter BATCH_SIZE = 32 # Turn datasets into iterables (batches) train_dataloader = DataLoader(train_subset, batch_size=BATCH_SIZE, shuffle=True ) test_dataloader = DataLoader(test_subset, batch_size=BATCH_SIZE, shuffle=False ) Let's visualize this: # Let's check out what we've created print(f"Dataloaders: {train_dataloader, test_dataloader}") print(f"Length of train dataloader: {len(train_dataloader)} batches of {BATCH_SIZE}") print(f"Length of test dataloader: {len(test_dataloader)} batches of {BATCH_SIZE}") ## Output # Dataloaders: (<torch.utils.data.dataloader.DataLoader object at 0x7f905f89b9d0>, <torch.utils.data.dataloader.DataLoader object at 0x7f906042cfd0>) # Length of train dataloader: 2 batches of 32 # Length of test dataloader: 2 batches of 32 Quantum Model There is no change to the Quantum Model we discussed last week. Creating our Quantum Neural Network We have passed the Quantum Layer we created last week into a Quantum Layer in a Quantum Neural Network. import torch.nn as nn from classiq.applications.qnn import QLayer class Net(torch.nn.Module): def __init__(self, *args, **kwargs) -> None: super().__init__() self.qlayer = QLayer( quantum_program, execute, post_process, *args, **kwargs ) def forward(self, x: torch.Tensor) -> torch.Tensor: x = self.qlayer(x) return x qnn = Net() The execute function passed into the QLayer is: from classiq.execution import execute_qnn from classiq.synthesis import SerializedQuantumProgram from classiq.applications.qnn.types import ( MultipleArguments, SavedResult, ResultsCollection, ) def execute(quantum_program: SerializedQuantumProgram, arguments: MultipleArguments) -> ResultsCollection: return execute_qnn(quantum_program, arguments) We haven't defined the Post Processing function passed into the QLayer yet (this will be the task of next week). For now, we are just using a template post-processing function from QNN Documentation provided by Classiq: def post_process(result: SavedResult) -> torch.Tensor: counts: dict = result.value.counts # print(f"counts: {counts}") # The probability of measuring |0> p_zero: float = counts.get("0", 0.0) / sum(counts.values()) return torch.tensor(p_zero) Defining Loss Function and Optimizer import torch.nn as nn import torch.optim as optim _LEARNING_RATE = 1.0 # choosing our loss function loss_fn = nn.L1Loss() # choosing our optimizer optimizer = optim.SGD(qnn.parameters(), lr=_LEARNING_RATE) Defining Training and Testing Loop from tqdm.auto import tqdm ## For showing a progress bar def train( model: nn.Module, data_loader: DataLoader, loss_fn: nn.modules.loss._Loss, optimizer: optim.Optimizer, epochs: int = 20, ) -> None: train_loss = 0 model.to(device) for epoch in tqdm(range(epochs)): print(f"Epoch: {epoch}\n----------") for batch, (data, label) in enumerate(data_loader): # Send data to device (GPU or CPU) data, label = data.to(device), label.to(device) # 1. Forward pass output = model(data) # 2. Calculate loss loss = loss_fn(output, label) train_loss += loss # 3. Optimizer zero grad optimizer.zero_grad() # 4. Loss backward loss.backward() # 5. Optimizer step optimizer.step() # Calculate loss per epoch and print out what's happening train_loss /= len(data_loader) print(f"Train loss: {train_loss:.5f}") train(qnn, train_dataloader, loss_fn, optimizer, epochs=20) def test( model: nn.Module, data_loader: DataLoader, atol=1e-4 ) -> float: num_correct = 0 total = 0 # Put the model in eval mode model.eval() # Turn on inference mode context manager with torch.inference_mode(): for data, labels in data_loader: # Send data to GPU data, labels = data.to(device), labels.to(device) # 1. Forward pass: Let the model predict predictions = model(data) # Get a tensor of booleans, indicating if each label is close to the real label is_prediction_correct = torch.isclose(predictions, labels.type(torch.float32), atol=atol) print("Label: ", labels) print("predictions: ", predictions) print("is_prediction_correct: ", is_prediction_correct) # Count the amount of `True` predictions num_correct += is_prediction_correct.sum().item() # Count the total evaluations # the first dimension of `labels` is `batch_size` total += labels.size(0) # Calculate the accuracy accuracy = float(num_correct) / float(total) print(f"Test Accuracy of the model: {accuracy*100:.2f}") return accuracy test(qnn, test_dataloader) Currently, I am getting an in-accurate Train Loss as we haven't implemented the post-processing yet. Our next step will be to implement it as discussed in the meeting with Tal. Week 12 This week I focused on the Post-processing of measurement counts and the transformation of labels into one-hot encoded labels. I first converted the measurement counts dictionary into an array to get logits. I then trimmed this array to a length of 10 and then normalized it to get prediction probabilities. We optionally, converted these prediction probabilities to get prediction labels in a one-hot encoded format (but this is of no use during the training process). Here is the implementation of Post Processing Step: def post_process(result: SavedResult) -> torch.Tensor: counts: dict = result.value.counts # Calculate logits from counts logits: float = torch.zeros(16) for key, value in counts.items(): logits[int(key, 2)] = value # Trim the logits from length 16 to length 10 since we have only 10 labels trimmed_logits = logits[:10] # Calculate prediction probabilities from logits by normalizing it pred_probs = torch.nn.functional.normalize(trimmed_logits, dim=0) # Convert the prediction probabilities into prediction labels pred_labels = torch.argmax(pred_probs) ### WRITE COUNTS, OUTPUT LOGITS, PRED PROBS, PRED LABELS to a file output_file = open("post_process_output.txt", "a") print("----------------------------------------------------------------------------------------------------------------------------------------------", file=output_file) print(f"COUNTS:: \n {counts} \n", file=output_file) print(f"LOGITS:: \n {logits} \n", file=output_file) print(f"TRIMMED LOGITS:: \n {trimmed_logits} \n", file=output_file) print(f"PREDICTION PROBABILITIES:: \n {pred_probs} \n", file=output_file) print(f"PREDICTION LABELS:: \n {pred_labels} \n", file=output_file) output_file.close() return torch.tensor(pred_probs) I also updated the target transform function to convert labels from integer digits to their one-hot encoding format: def target_transform(label): label_tensor = torch.LongTensor([label]) one_hot_label = torch.nn.functional.one_hot(label_tensor, 10) return one_hot_label Besides these, I have also added functionality to save prediction outputs during the training and testing process into output test files for visualization and inferencing purposes. We will remove these when we are done experimenting. Now, here are the results of two trial runs: Learning Rate: 1.0 Batch Size: 32 Classes: 0-9 Exp. No.No. of BatchesEpochsTrain LossTrain Time (mins)Test Accuracy 1.220.2865529.375 Next, I am preparing a mini-dataset of only "0" and "1" to run quick parallel experiments to increase the accuracy of our network. Week 13 This week I focused on creating custom mnist datasets of different sizes and classes, making our code modular, and adding more visualization features for easier experimentation and inferencing. I have created the following two datasets: Mini dataset of size 128 images containing only labels 0 and 1 Mini dataset of size 128 images containing all labels from 0 to 1 I wrote multiple script files to achieve modularity: scripts/data_setup.py This file contains functionalities to load different datasets (custom datasets of different sizes and classes or default MNIST datasets provided by PyTorch). import os from typing import Callable,Optional from torchvision import datasets from torch.utils.data import DataLoader, Subset NUM_WORKERS = os.cpu_count() def create_dataloaders_from_folders( train_dir: str, test_dir: str, batch_size: int, transform: Optional[Callable] = None, target_transform: Optional[Callable] = None, num_workers: int = NUM_WORKERS ): """Creates training and testing DataLoaders. Takes in a training directory and testing directory path and turns them into PyTorch Datasets and then into PyTorch DataLoaders. Args: train_dir: Path to training directory. test_dir: Path to testing directory. transform: function having torchvision transforms to perform on training and testing data. target_transform: function having torchvision transforms to perform on training and testing data labels. batch_size: Number of samples per batch in each of the DataLoaders. num_workers: An integer for number of workers per DataLoader. Returns: A tuple of (train_dataloader, test_dataloader, class_names). Where class_names is a list of the target classes. Example usage: train_dataloader, test_dataloader, class_names = \ = create_dataloaders_from_folders(train_dir=path/to/train_dir, test_dir=path/to/test_dir, transform=some_data_transform_function, target_transform=some_label_transform_function, batch_size=32, num_workers=4) """ # Use ImageFolder to create dataset(s) train_data = datasets.ImageFolder(train_dir, transform=transform, target_transform=target_transform) test_data = datasets.ImageFolder(test_dir, transform=transform, target_transform=target_transform) # Get class names class_names = train_data.classes # Turn images into data loaders train_dataloader = DataLoader( train_data, batch_size=batch_size, shuffle=True, num_workers=num_workers, pin_memory=True, ) test_dataloader = DataLoader( test_data, batch_size=batch_size, shuffle=False, num_workers=num_workers, pin_memory=True, ) return train_dataloader, test_dataloader, class_names def create_mnist_dataloaders( batch_size: int, root: str = "data", transform: Optional[Callable] = None, target_transform: Optional[Callable] = None, num_workers: int = NUM_WORKERS, create_subset: bool = False, subset_size:int = 64 ): """Creates training and testing DataLoaders. Creates PyTorch Dataloaders from PyTorch MNIST Dataset. Args: root: folder name in which data will be downloaded. transform: torchvision transforms to perform on training and testing data. target_transform: function having torchvision transforms to perform on training and testing data labels. batch_size: Number of samples per batch in each of the DataLoaders. num_workers: An integer for number of workers per DataLoader. create_subset: If True, it create a dataloaders from small subset of data. subset_size: Size of the subset of data. Defaults to 64. Returns: A tuple of (train_dataloader, test_dataloader, class_names). Where class_names is a list of the target classes. Example usage: train_dataloader, test_dataloader, class_names = \ = create_mnist_dataloaders(root="data" transform=some_data_transform_function, target_transform=some_label_transform_function, batch_size=32, num_workers=4) """ # Setup training data train_data = datasets.MNIST( root=root, train=True, download=True, transform=transform, target_transform=target_transform ) # Setup testing data test_data = datasets.MNIST( root=root, train=False, download=True, transform=transform, target_transform=target_transform ) # Get class names class_names = train_data.classes # Create subsets of the datasets if create_subset: train_data = Subset(train_data, range(subset_size)) test_data = Subset(test_data, range(subset_size)) # Turn datasets into iterables (batches) train_dataloader = DataLoader(train_data, batch_size=batch_size, shuffle=True, num_workers=num_workers, ) test_dataloader = DataLoader(test_data, batch_size=batch_size, shuffle=False, num_workers=num_workers, ) return train_dataloader, test_dataloader, class_names scripts/data_transforms.py This file contains functionalities for transforming data images and labels as discussed earlier. import torch import torchvision.transforms as transforms def input_transform(image): """ The input MNIST images are all 28 × 28 px. This function will firstly center-crop them to 24 × 24 and then down-sample them to 4 × 4 for MNIST. Then we convert the image pixels into angles for passing them into Rotation gates later for encoding. """ image = transforms.Grayscale(num_output_channels=1)(image) image = transforms.ToTensor()(image) image = transforms.CenterCrop(24)(image) image = transforms.Resize(size = (4,4), antialias=True)(image) image = image.squeeze() image_pixels = torch.flatten(image) angles = torch.sqrt(image_pixels / 256) return angles def target_transform(label): label_tensor = torch.LongTensor([label]) one_hot_label = torch.nn.functional.one_hot(label_tensor, 10) return one_hot_label.squeeze() def target_transform_bin(label): label_tensor = torch.LongTensor([label]) one_hot_label = torch.nn.functional.one_hot(label_tensor, 2) return one_hot_label.squeeze() scripts/train.py This file contains the script for training our model. I have updated how results are saved to write them into a CSV file in further steps. import torch import torch.nn as nn from torch.utils.data import DataLoader import torch.optim as optim from typing import Dict, List from tqdm.auto import tqdm def train( model: nn.Module, data_loader: DataLoader, loss_fn: nn.modules.loss._Loss, optimizer: optim.Optimizer, writer: torch.utils.tensorboard.writer.SummaryWriter, epochs: int = 20, device: str = 'cpu', ) -> Dict[str, List]: model.to(device) # Setup train loss value train_loss = 0 # Create empty results dictionary results = { "train_loss": [], } # Loop through training steps for a number of epochs for epoch in tqdm(range(epochs)): print(f"Epoch: {epoch}\n----------") for batch, (data, label) in enumerate(data_loader): # Send data to device (GPU or CPU) data, label = data.to(device), label.to(device) # 1. Forward pass output = model(data).to(device) # 2. Calculate loss loss = loss_fn(output, label) train_loss += loss # 3. Optimizer zero grad optimizer.zero_grad() # 4. Loss backward loss.backward() # 5. Optimizer step optimizer.step() # Calculate loss per epoch and print out what's happening train_loss /= len(data_loader) # Print out what's happening print( f"Epoch: {epoch+1} | " f"Train loss: {train_loss:.5f}" ) # Update results dictionary results["train_loss"].append(train_loss.detach().item()) ### Experiment Tracking ### # See if there's a writer, if so, log to it if writer: # Add loss results to SummaryWriter writer.add_scalars( main_tag="Loss", tag_scalar_dict={"train_loss": train_loss,}, global_step=epoch ) # Close the writer writer.close() # Return the filled results at the end of the epochs return results scripts/test.py This file contains scripts for testing our model. import torch import torch.nn as nn from torch.utils.data import DataLoader def test( model: nn.Module, data_loader: DataLoader, atol=0, device: str = 'cpu', ) -> float: num_correct = 0 total = 0 # Put the model in eval mode model.eval() # Turn on inference mode context manager with torch.inference_mode(): for data, labels in data_loader: # Send data to GPU data, labels = data.to(device), labels.to(device) # 1. Forward pass: Let the model predict predictions = model(data) # Get a tensor of booleans, indicating if each label is close to the real label is_prediction_correct = torch.isclose(predictions.argmax(dim=1), labels.argmax(dim=1), atol=atol) ### WRITE OUTPUT TO A FILE # output_file = open("test_loop_output.txt", "a") # print("----------------------------------------------------------------------------------------------------------------------------------------------", file=output_file) # print(f"LABELS:: \n {labels} \n", file=output_file) # print(f"PREDICTIONS:: \n {predictions} \n", file=output_file) # print(f"IS PREDICTIONS CORRECT:: \n {is_prediction_correct} \n", file=output_file) # output_file.close() # Count the amount of `True` predictions num_correct += is_prediction_correct.sum().item() # Count the total evaluations # the first dimension of `labels` is `batch_size` total += labels.size(0) # Calculate the accuracy accuracy = float(num_correct) / float(total) print(f"Test Accuracy of the model: {accuracy * 100:.2f}%") return accuracy * 100 scripts/helper.py This file contains functionality for saving our results and and track experiments. import torch from torch.utils.tensorboard import SummaryWriter from datetime import datetime import os import pandas as pd def create_writer(experiment_name: str, model_name: str, extra: str=None) -> torch.utils.tensorboard.writer.SummaryWriter(): """Creates a torch.utils.tensorboard.writer.SummaryWriter() instance saving to a specific log_dir. log_dir is a combination of runs/timestamp/experiment_name/model_name/extra. Where timestamp is the current date in YYYY-MM-DD format. Args: experiment_name (str): Name of experiment. model_name (str): Name of model. extra (str, optional): Anything extra to add to the directory. Defaults to None. Returns: torch.utils.tensorboard.writer.SummaryWriter(): Instance of a writer saving to log_dir. Example usage: # Create a writer saving to "runs/2022-06-04/data_10_percent/leqm3/5_epochs/" writer = create_writer(experiment_name="data_10_percent", model_name="leqm3", extra="5_epochs") # The above is the same as: writer = SummaryWriter(log_dir="runs/2022-06-04/data_10_percent/leqm3/5_epochs/") """ # Get timestamp of current date (all experiments on certain day live in same folder) timestamp = datetime.now().strftime("%Y-%m-%d") # returns current date in YYYY-MM-DD format if extra: # Create log directory path log_dir = os.path.join("runs", timestamp, experiment_name, model_name, extra) else: log_dir = os.path.join("runs", timestamp, experiment_name, model_name) print(f"[INFO] Created SummaryWriter, saving to: {log_dir}...") return SummaryWriter(log_dir=log_dir) def write_train_results( experiment_name, model_name, epochs, results, ): output_dir = "outputs/train_results/" # Check if the directory exists, and create it if not if not os.path.exists(output_dir): os.makedirs(output_dir) output_file_name = f"{experiment_name}_{model_name}_epochs_{epochs}.csv" file_path = f"outputs/train_results/{output_file_name}" data = { 'Epoch': list(range(1, len(results['train_loss']) + 1)), 'Train Loss': results['train_loss'] } df = pd.DataFrame(data) if os.path.exists(file_path): # If it exists, append data df.to_csv(file_path, mode='a', index=False, header=False) else: df.to_csv(file_path, mode='w', index=False, header=True) scripts/save_model.py This file contains the script for saving our trained models. """ Contains various utility functions for PyTorch model training and saving. """ import torch from pathlib import Path def save_model(model: torch.nn.Module, target_dir: str, model_name: str): """Saves a PyTorch model to a target directory. Args: model: A target PyTorch model to save. target_dir: A directory for saving the model to. model_name: A filename for the saved model. Should include either ".pth" or ".pt" as the file extension. Example usage: save_model(model=model_0, target_dir="models", model_name="05_going_modular_tingvgg_model.pth") """ # Create target directory target_dir_path = Path(target_dir) target_dir_path.mkdir(parents=True, exist_ok=True) # Create model save path assert model_name.endswith(".pth") or model_name.endswith( ".pt" ), "model_name should end with '.pt' or '.pth'" model_save_path = target_dir_path / model_name # Save the model state_dict() print(f"[INFO] Saving model to: {model_save_path}") torch.save(obj=model.state_dict(), f=model_save_path) models/leqm3.py This file contains the script for creating a Linear Entanglement Quantum Model for MNIST Data Classification with three linear entanglement layers of RXX, RYY, and RZZ. """_summary_ Linear Entanglement Quantum Model for MNIST Data Classification with three linear entanglement layers of RXX, RYY, and RZZ. """ from classiq import create_model, QFunc, QArray, QBit, Output, allocate, RX, RY, RZ, RZZ, RXX, RYY, CZ @QFunc def encoding(q: QArray[QBit]) -> None: """ This function encodes the input data into the qubits. This input data is a 4x4 image pixel values converted into angle for rotation gates (RX, RY, RZ, RX) in form of a 16x1 vector. We encode 4 pixels per qubit. Args: q (QArray[QBit]): Array of four Qubits to encode the input data into. """ RX(theta="input_0", target=q[0]) # Pixel 0 on Qubit 0 RY(theta="input_1", target=q[0]) # Pixel 1 on Qubit 0 RZ(theta="input_2", target=q[0]) # Pixel 2 on Qubit 0 RX(theta="input_3", target=q[0]) # Pixel 3 on Qubit 0 RX(theta="input_4", target=q[1]) # Pixel 4 on Qubit 1 RY(theta="input_5", target=q[1]) # Pixel 5 on Qubit 1 RZ(theta="input_6", target=q[1]) # Pixel 6 on Qubit 1 RX(theta="input_7", target=q[1]) # Pixel 7 on Qubit 1 RX(theta="input_8", target=q[2]) # Pixel 8 on Qubit 2 RY(theta="input_9", target=q[2]) # Pixel 9 on Qubit 2 RZ(theta="input_10", target=q[2]) # Pixel 10 on Qubit 2 RX(theta="input_11", target=q[2]) # Pixel 11 on Qubit 2 RX(theta="input_12", target=q[3]) # Pixel 12 on Qubit 3 RY(theta="input_13", target=q[3]) # Pixel 13 on Qubit 3 RZ(theta="input_14", target=q[3]) # Pixel 14 on Qubit 3 RX(theta="input_15", target=q[3]) # Pixel 15 on Qubit 3 @QFunc def mixing(q: QArray[QBit]) -> None: """ This function performs the mixing operation on the qubits. This is done by applying a series of RZZ, RXX, RYY gates to form a ring connection. Args: q (QArray[QBit]): Array of four Qubits to apply the mixing operation on. """ RZZ(theta="weight_0", target=q[0:2]) RZZ(theta="weight_1", target=q[1:3]) RZZ(theta="weight_2", target=q[2:4]) RXX(theta="weight_4", target=q[0:2]) RXX(theta="weight_5", target=q[1:3]) RXX(theta="weight_6", target=q[2:4]) RYY(theta="weight_8", target=q[0:2]) RYY(theta="weight_9", target=q[1:3]) RYY(theta="weight_10", target=q[2:4]) @QFunc def cz_block(q: QArray[QBit]) -> None: """ This function applies CZ gates between each qubit. Args: q (QArray[QBit]): Array of four Qubits to apply the entanglement operation on. """ CZ(control=q[0], target=q[1]) CZ(control=q[1], target=q[2]) CZ(control=q[2], target=q[3]) @QFunc def main(res: Output[QArray[QBit]]) -> None: """ This is the main function from which model will be created. It calls the other functions to perform the encoding, mixing and entanglement. Args: res (Output[QArray[QBit]]): Output QArray of QBits from which the model will be created. """ allocate(4, res) encoding(q=res) mixing(q=res) cz_block(q=res) def linear_entanglement_r3_quantum_model(): model = create_model(main) return model models/qnn.py This file contains scripts for creating a quantum neural network based on the model passed to it. import torch from classiq.execution import execute_qnn from classiq.synthesis import SerializedQuantumProgram from classiq.applications.qnn import QLayer from classiq.applications.qnn.types import ( MultipleArguments, SavedResult, ResultsCollection, ) def execute_fn(quantum_program: SerializedQuantumProgram, arguments: MultipleArguments) -> ResultsCollection: return execute_qnn(quantum_program, arguments) def post_process_fn(result: SavedResult) -> torch.Tensor: counts: dict = result.value.counts # Calculate logits from counts logits: float = torch.zeros(16) for key, value in counts.items(): logits[int(key, 2)] = value # Trim the logits from length 16 to length 10 since we have only 10 labels trimmed_logits = logits[:10] # Calculate prediction probabilities from logits by normalizing it pred_probs = torch.nn.functional.normalize(trimmed_logits, dim=0) # Convert the prediction probabilities into prediction labels # pred_labels = torch.argmax(pred_probs) ### WRITE COUNTS, OUTPUT LOGITS, PRED PROBS, PRED LABELS to a file # output_file = open("post_process_output.txt", "a") # print("----------------------------------------------------------------------------------------------------------------------------------------------", file=output_file) # print(f"COUNTS:: \n {counts} \n", file=output_file) # print(f"LOGITS:: \n {logits} \n", file=output_file) # print(f"TRIMMED LOGITS:: \n {trimmed_logits} \n", file=output_file) # print(f"PREDICTION PROBABILITIES:: \n {pred_probs} \n", file=output_file) # print(f"PREDICTION LABELS:: \n {pred_labels} \n", file=output_file) # output_file.close() return pred_probs.clone().detach() def post_process_bin_fn(result: SavedResult) -> torch.Tensor: counts: dict = result.value.counts # Calculate logits from counts logits: float = torch.zeros(16) for key, value in counts.items(): logits[int(key, 2)] = value # Trim the logits from length 16 to length 10 since we have only 10 labels trimmed_logits = logits[:2] # Calculate prediction probabilities from logits by normalizing it pred_probs = torch.nn.functional.normalize(trimmed_logits, dim=0) # Convert the prediction probabilities into prediction labels # pred_labels = torch.argmax(pred_probs) ### WRITE COUNTS, OUTPUT LOGITS, PRED PROBS, PRED LABELS to a file # output_file = open("post_process_output.txt", "a") # print("----------------------------------------------------------------------------------------------------------------------------------------------", file=output_file) # print(f"COUNTS:: \n {counts} \n", file=output_file) # print(f"LOGITS:: \n {logits} \n", file=output_file) # print(f"TRIMMED LOGITS:: \n {trimmed_logits} \n", file=output_file) # print(f"PREDICTION PROBABILITIES:: \n {pred_probs} \n", file=output_file) # print(f"PREDICTION LABELS:: \n {pred_labels} \n", file=output_file) # output_file.close() return pred_probs.clone().detach() class QNN(torch.nn.Module): def __init__(self, quantum_program, execute, post_process, *args, **kwargs) -> None: super().__init__() self.qlayer = QLayer( quantum_program, execute=execute, post_process=post_process, *args, **kwargs ) def forward(self, x: torch.Tensor) -> torch.Tensor: x = self.qlayer(x) return x Now using these files we can run many different numbers of experiments as we want based on different datasets of custom size and class labels. Scenario 1: Experimentation using MNIST Dataset provided by Pytorch Here is an example of how to run an experiment for a small (subset of size 64) MNIST dataset provided by PyTorch for 2 epochs. import torch import classiq import torch.nn as nn import torch.optim as optim from torchinfo import summary from models.leqm3 import linear_entanglement_r3_quantum_model from models.qnn import execute_fn, post_process_fn, QNN from scripts.helper import create_writer, write_train_results from scripts.data_setup import create_mnist_dataloaders from scripts.data_transforms import input_transform, target_transform from scripts.train import train from scripts.test import test ## Authenticate Classiq classiq.authenticate() ## For setting up device agnostic code device = "cuda" if torch.cuda.is_available() else "cpu" device = 'cpu' print(device) ## Clear Output Files post_process_output_file = open("post_process_output.txt", "w") print("-----------------------------------------------------------------------------------------------------------------", file=post_process_output_file) print("--------------------------------------------POST PROCESS OUTPUT--------------------------------------------------", file=post_process_output_file) print("-----------------------------------------------------------------------------------------------------------------", file=post_process_output_file) post_process_output_file.close() test_loop_output_file = open("test_loop_output.txt", "w") print("-----------------------------------------------------------------------------------------------------------------", file=test_loop_output_file) print("-----------------------------------------------TEST LOOP OUTPUT--------------------------------------------------", file=test_loop_output_file) print("-----------------------------------------------------------------------------------------------------------------", file=test_loop_output_file) test_loop_output_file.close() # HYPER PARAMETERS _LEARNING_RATE = 1.0 BATCH_SIZE = 64 EPOCHS = 2 # Create a Linear Entanglement Quantum Model for MNIST Data Classification with three linear entanglement layers of RXX, RYY, and RZZ. quantum_model = linear_entanglement_r3_quantum_model() quantum_program = classiq.synthesize(quantum_model) # View Quantum Program on Classiq Platform classiq.show(quantum_program) qnn = QNN( quantum_program=quantum_program, execute=execute_fn, post_process=post_process_fn, ) summary(model=qnn, input_size=(32, 16), verbose=0, col_names=["input_size", "output_size", "num_params", "trainable"], col_width=20, row_settings=["var_names"]) # choosing our loss function loss_fn = nn.L1Loss() # choosing our optimizer optimizer = optim.SGD(qnn.parameters(), lr=_LEARNING_RATE) train_dataloader, test_dataloader, class_names = create_mnist_dataloaders( root="data", transform=input_transform, target_transform=target_transform, batch_size=BATCH_SIZE, create_subset=True, subset_size=64 ) # Let's check out what we've created print(f"Dataloaders: {train_dataloader, test_dataloader}") print(f"Length of train dataloader: {len(train_dataloader)} batches of {BATCH_SIZE}") print(f"Length of test dataloader: {len(test_dataloader)} batches of {BATCH_SIZE}") print(f"Our Dataset have following classes: {class_names}") data, label = next(iter(train_dataloader)) print(f"Image shape: {data.shape} -> [batch_size, pixel_angle]") print(f"Label shape: {label.shape} -> [batch_size, label_value]") # Create a writer for tracking our experiment writer = create_writer(experiment_name="data_0.1_percent", model_name="linear_entanglement_r3", extra=f"{EPOCHS}_epochs") train_results = train( model = qnn, data_loader = train_dataloader, loss_fn = loss_fn, optimizer = optimizer, writer = writer, epochs = EPOCHS, device = device ) # Check out the model results print(train_results) write_train_results(experiment_name="data_0.1_percent", model_name="linear_entanglement_r3", epochs=EPOCHS, results=train_results) # %load_ext tensorboard # %tensorboard --logdir runs test_results = test( model = qnn, data_loader = test_dataloader, device = device ) print(test_results) # Save the trained model save_model( model=qnn, target_dir='outputs/saved_models', model_name='leqmr3_subset64.pt' ) Here is the summary of our quantum neural network provided by torchinfo.summary: Scenario 2: Experimentation using custom binary MNIST Dataset Here is an example of how to run an experiment for 1280 MNIST images of 0 and 1 provided by PyTorch for 10 epochs. import torch import classiq import torch.nn as nn import torch.optim as optim from torchinfo import summary from pathlib import Path from models.leqm3 import linear_entanglement_r3_quantum_model from models.qnn import execute_fn, post_process_fn, QNN from scripts.helper import create_writer, write_train_results from scripts.data_setup import create_mnist_dataloaders from scripts.data_transforms import input_transform, target_transform from scripts.train import train from scripts.test import test ## Authenticate Classiq classiq.authenticate() ## For setting up device agnostic code device = "cuda" if torch.cuda.is_available() else "cpu" device = 'cpu' print(device) ## HYPER PARAMETERS _LEARNING_RATE = 1.0 BATCH_SIZE = 32 EPOCHS = 10 ## Create a Linear Entanglement Quantum Model for MNIST Data Classification with three linear entanglement layers of RXX, RYY, and RZZ. quantum_model = linear_entanglement_r3_quantum_model() quantum_program = classiq.synthesize(quantum_model) # View Quantum Program on Classiq Platform classiq.show(quantum_program) qnn = QNN( quantum_program=quantum_program, execute=execute_fn, post_process=post_process_fn, ) summary(model=qnn, input_size=(32, 16), verbose=0, col_names=["input_size", "output_size", "num_params", "trainable"], col_width=20, row_settings=["var_names"]) # choosing our loss function loss_fn = nn.L1Loss() # choosing our optimizer optimizer = optim.SGD(qnn.parameters(), lr=_LEARNING_RATE) train_dir = Path('mini_data_1280_bin/mini_data_1280_bin/train') test_dir = Path('mini_data_1280_bin/mini_data_1280_bin/test') train_dataloader, test_dataloader, class_names = create_dataloaders_from_folders( train_dir=train_dir, test_dir=test_dir, transform=input_transform, target_transform=target_transform_bin, batch_size=BATCH_SIZE, ) # Let's check out what we've created print(f"Dataloaders: {train_dataloader, test_dataloader}") print(f"Length of train dataloader: {len(train_dataloader)} batches of {BATCH_SIZE}") print(f"Length of test dataloader: {len(test_dataloader)} batches of {BATCH_SIZE}") print(f"Our Dataset have following classes: {class_names}") data, label = next(iter(train_dataloader)) print(f"Image shape: {data.shape} -> [batch_size, pixel_angle]") print(f"Label shape: {label.shape} -> [batch_size, label_value]") # Create a writer for tracking our experiment writer = create_writer(experiment_name="custom_data_1280", model_name="linear_entanglement_r3", extra=f"{EPOCHS}_epochs") train_results = train( model = qnn, data_loader = train_dataloader, loss_fn = loss_fn, optimizer = optimizer, writer = writer, epochs = EPOCHS, device = device ) # Check out the model results print(train_results) write_train_results(experiment_name="data_1280_bin", model_name="linear_entanglement_r3", epochs=EPOCHS, results=train_results) # %load_ext tensorboard # %tensorboard --logdir runs save_model( model=qnn, target_dir='outputs/saved_models', model_name=f'exp_1_leqmr3_data_1280_bin_epoch{EPOCHS}.pt' ) test_results = test( model = qnn, data_loader = test_dataloader, device = device ) print(test_results) When using the custom binary dataset, here is the summary of our QNN. In the upcoming weeks, I will be running different experiments, writing the final manuscript, and preparing a presentation for the final demo day. Week 14 Experimentation Learning Rate: 1.0 Batch Size: 32 Exp. No.No. of BatchesEpochsClassesTrain LossTrain Time (mins)Test Accuracy 1.220-90.2865529.375% 2.820-90.2031837.422% 3.8100-90.19270358.203% 4.80100-10.1014616550%

Sep 27, 2023

Quantum computing has gained significant attention in recent years due to its potential to solve complex problems more efficiently than classical computers. In the last blog, I gave an Introduction to Pennylane AI and Quantum Differential Programming...

Sep 1, 2023

Quantum computing has gained significant attention in recent years due to its potential to solve complex problems more efficiently than classical computers. In the last blog, I gave an Introduction to Pennylane AI and Quantum Differential Programming. This blog is part of a series of blogs to understand Quantum Differential Programming: Beyond Binary: Quantum Pioneers Unveiled. In this blog, we will delve deep into the concept of Quantum differential programming. Quantum differential programming is a fascinating subfield that combines quantum computing with the principles of differentiation, allowing us to optimize quantum algorithms and solve real-world problems. In this comprehensive guide, we will explore quantum differential programming using PennyLane AI, a versatile quantum machine learning library. We'll start with the basics of differentiation in programming and gradually delve into the automatic differentiation of quantum computations. Table of Contents: Introduction to Differentiation in Programming Symbolic Differentiation Numerical Differentiation Automatic Differentiation Examples in Python and PyTorch Automatic Differentiation of Quantum Computations Mathematical Foundations PennyLane: Quantum Machine Learning Library Quantum Gradients and Parameter Shift Rule Quantum Gradient Descent Quantum Differentiation Example with PennyLane Introduction to Differentiation in Programming In all the examples below we will be differentiating f(x)=x^2+2x+1 which can also be easily calculated manually as: $$\frac{df}{dy} = \frac{d}{dy}x^2 + \frac{d}{dy} 2x + \frac{d}{dy} (1)$$ $$\frac{df}{dy}​=2x+2$$ Symbolic Differentiation Symbolic differentiation involves computing derivatives symbolically, which means expressing the derivative as a formula or an expression. This method is precise but can be computationally expensive for complex functions. Example in Python using SymPy: import sympy as sp x = sp.Symbol('x') f = x**2 + 2*x + 1 f_prime = sp.diff(f, x) print(f_prime) # Output: 2*x + 2 Numerical Differentiation Numerical differentiation approximates derivatives by calculating the finite difference between function values at nearby points. It is less accurate than symbolic differentiation but computationally faster. The differentiation of any function can be easily calculated using the finite difference formula: $$f'(x) \simeq \frac{f(x + h) - f(x)}{h}$$ Example in Python using NumPy: import numpy as np def f(x): return x**2 + 2*x + 1 x = 2.0 h = 1e-5 f_prime_approx = (f(x + h) - f(x)) / h print(f_prime_approx) # Output: 6.00001000027 Automatic Differentiation Automatic differentiation is a middle-ground approach that combines the accuracy of symbolic differentiation with the computational efficiency of numerical differentiation. It automatically computes derivatives by applying the chain rule, step by step. You can study Chain Rules from this Lesson on Chain Rules by Khan Academy or any source on the internet. Example in Python using PyTorch: import torch x = torch.tensor(2.0, requires_grad=True) f = x**2 + 2*x + 1 f.backward() f_prime = x.grad print(f_prime.item()) # Output: 6.0 Automatic Differentiation of Quantum Computations Mathematical Foundations Before we dive into quantum differentiation, let's understand the basic mathematical concepts involved. In quantum computing, we represent computations as quantum circuits, which consist of quantum gates and qubits. To compute derivatives of quantum circuits, we rely on the principles of matrix calculus. Quantum Circuits and Parameters In a quantum circuit, we have parameters that represent angles or values associated with quantum gates. These parameters can be adjusted to optimize the circuit's behavior for a specific task, such as solving a quantum chemistry problem or training a quantum machine learning model. Let's denote these parameters collectively as θ, and our quantum circuit as U(θ). Quantum Expectation Values Quantum algorithms often involve calculating expectation values of quantum observables, which are represented as Hermitian operators. Let's denote an observable as O. The expectation value of O in the quantum state produced by U(θ) is given by: $$\langle O \rangle = \langle \psi(\boldsymbol{\theta}) | O | \psi(\boldsymbol{\theta}) \rangle$$ Where ∣ψ(θ)⟩ represents the quantum state generated by the circuit U(θ). Parameter Shift Rule The parameter shift rule is a fundamental concept in quantum automatic differentiation. It allows us to compute the gradient of an expectation value with respect to a parameter. Let's assume we have a parameter θi in our circuit: We first calculate the expectation value of the observable O for the original parameter θi​: $$E_1 = \langle O \rangle(\boldsymbol{\theta})$$ Next, we compute the expectation value for the circuit with a slight shift in the parameter θi​: $$E_2 = \langle O \rangle(\boldsymbol{\theta} + \Delta \theta_i)$$ The derivative of the expectation value with respect to θi​ can be approximated using the finite difference: $$\frac{dE}{d\theta_i} \approx \frac{E_2 - E_1}{\Delta \theta_i}$$ The parameter shift rule effectively quantifies how a small change in a parameter affects the expectation value of the quantum observable. This is essential for optimizing quantum circuits during training and variational quantum algorithms. Quantum Gradients The gradient of an expectation value with respect to all parameters θ is a vector of partial derivatives: $$\boldsymbol{\nabla}\langle O \rangle = \left[\frac{dE}{d\theta_1}, \frac{dE}{d\theta_2}, \ldots, \frac{dE}{d\theta_n}\right]$$ This gradient vector provides valuable information about how each parameter influences the expectation value. By adjusting the parameters in the direction that minimizes or maximizes the cost function, we can optimize quantum circuits for specific tasks. PennyLane: Quantum Machine Learning Library PennyLane is an open-source Python library that integrates seamlessly with popular quantum computing frameworks like Qiskit and Cirq. PennyLane provides a high-level interface for building and optimizing quantum circuits, making it an excellent choice for quantum differential programming. Quantum Gradients and Parameter Shift Rule To compute gradients of quantum circuits, we use the parameter shift rule. This rule allows us to calculate the gradient of an expectation value with respect to a parameter by applying two slightly different quantum circuits and taking the difference in their measured values. Quantum Gradient Descent With quantum gradients in hand, we can use optimization algorithms like gradient descent to find optimal parameters for quantum circuits. This is crucial for solving real-world problems efficiently using quantum computers. Quantum Differentiation Example with PennyLane Let's put our knowledge into practice with a simple example in PennyLane. We'll differentiate a quantum circuit representing a variational quantum eigensolver (VQE) for a quantum chemistry problem. You can study VQE in detail on this amazing blog by Michał Stęchły on Musty Thoughts. import pennylane as qml import numpy as np # Define a quantum device dev = qml.device("default.qubit", wires=2) # Create a quantum circuit @qml.qnode(dev) def circuit(params): qml.RX(params[0], wires=0) qml.RY(params[1], wires=1) qml.CNOT(wires=[0, 1]) return qml.expval(qml.PauliZ(0)) # Initialize parameters params = np.array([0.1, 0.2], requires_grad=True) # Define the cost function def cost(params): return circuit(params) # Compute the gradient grad_fn = qml.grad(cost) gradient = grad_fn(params) print(gradient) In this example, we've defined a quantum circuit, initialized some parameters, and computed the gradient of the circuit's expectation value with respect to those parameters using PennyLane's automatic differentiation capabilities. This example demonstrates how PennyLane simplifies the process of differentiating quantum circuits, making it accessible for quantum machine learning tasks and quantum optimization problems. In summary, the automatic differentiation of quantum computations using PennyLane empowers researchers and developers to explore the full potential of quantum computing by efficiently optimizing quantum circuits for various applications. It combines the power of quantum computing with the convenience of automatic differentiation, opening up new possibilities in the world of quantum differential programming. Conclusion Quantum differential programming is an exciting field that merges quantum computing with differentiation techniques. It enables us to optimize quantum algorithms for various applications, from quantum chemistry to machine learning. With the help of PennyLane and the principles of automatic differentiation, even beginners can dive into this cutting-edge field and start exploring the limitless possibilities of quantum computing. Know the Author I am Ashmit JaiSarita Gupta, an engineering physics undergraduate at the National Institute of Technology Hamirpur. I am passionate about Quantum Computing, Machine Learning, UI/UX, and Web Development. About two years ago, when I first discovered the field of Web Development and Quantum Computing, it totally amazed me and I have been dedicating my education to them ever since. Over the past two years, I have dedicated a considerable amount of time and effort to learning and developing skills in these fields by taking various online courses, reading different articles, making several projects, and being involved in various research internships and mentorship programs. Fast forward to today, I am currently researching QUBO Relaxation Parameter Optimisation using a Learning Surrogate Solver (QROSS). Visit my portfolio website to learn more about me, my previous projects, and the places I have worked. Feel free to connect with me on Twitter, LinkedIn, or GitHub.

Sep 1, 2023

In recent years, quantum computing has emerged as a revolutionary field with the potential to solve complex problems that are beyond the capabilities of classical computers. Quantum computers leverage the principles of quantum mechanics to perform co...

Aug 30, 2023

In recent years, quantum computing has emerged as a revolutionary field with the potential to solve complex problems that are beyond the capabilities of classical computers. Quantum computers leverage the principles of quantum mechanics to perform computations that can provide breakthroughs in various domains such as cryptography, optimization, and material science. Xanadu, a leading quantum computing company, has developed Pennylane AI, a powerful tool for quantum programming that utilizes the concept of differential programming to harness the capabilities of quantum computers. In this article, we will delve into the basics of Xanadu Pennylane AI and explore the intriguing world of differential quantum programming. Understanding Quantum Computing Before we dive into the specifics of Pennylane AI and differential quantum programming, let's first grasp the fundamental concepts of quantum computing. Quantum bits, or qubits, are the basic units of information in quantum computing. Unlike classical bits, which can either be 0 or 1, qubits can exist in a superposition of both states simultaneously. This property allows quantum computers to process a vast amount of information in parallel, making them incredibly powerful for certain types of computations. Entanglement is another crucial quantum phenomenon. When qubits become entangled, the state of one qubit becomes dependent on the state of another, regardless of the distance between them. This phenomenon enables quantum computers to perform operations that classical computers can't replicate efficiently. If you are completely new to the field of quantum computing, you may read my blog 'Getting Started with Quantum Computing Using PennyLane and Xanadu Codebook', which I published last year when I got started with it. I have covered how can one learn the fundamentals of quantum mechanics, and linear algebra, and get started with quantum computing. Introduction to Xanadu Pennylane AI Xanadu is a forefront quantum computing company that has developed Pennylane AI, a versatile open-source software library for quantum machine learning and quantum computing. Pennylane AI provides a user-friendly interface to design, simulate, and optimize quantum circuits on various quantum computing hardware platforms. Differential Quantum Programming: Bridging Quantum and Machine Learning Differential programming is a technique that combines the principles of machine learning with quantum computing. At its core, it involves calculating gradients of quantum circuits, which helps in optimizing the parameters of these circuits. This concept is particularly powerful when dealing with noisy intermediate-scale quantum (NISQ) devices, where errors in quantum computations are common due to hardware limitations. Differential quantum programming, as enabled by Pennylane AI, allows users to efficiently compute gradients of quantum circuits. These gradients provide information about how small changes in the circuit's parameters affect its output. By analyzing these gradients, it becomes possible to optimize quantum circuits for specific tasks. Key Features of Pennylane AI Pennylane AI offers a range of features that make it a valuable tool for quantum programming: Gradient-Based Optimization: Pennylane AI enables the optimization of quantum circuits using gradient-based techniques, enhancing the efficiency of quantum algorithms. Quantum Machine Learning: The library facilitates the integration of quantum circuits with classical machine learning frameworks, opening the door to hybrid quantum-classical algorithms. Flexibility: Pennylane AI supports various quantum hardware platforms, allowing users to experiment with different devices and simulators. Quantum Circuit Construction: Users can construct complex quantum circuits using a simple and intuitive syntax, making it accessible to both beginners and experts. Community and Resources: Xanadu has fostered a strong community around Pennylane AI, providing tutorials, documentation, and support to help users grasp the concepts and make the most out of the library. Getting Started with Pennylane AI To get started with Pennylane AI, follow these steps: Installation: Before we begin, you'll need to have Python installed on your system. Once you have Python set up, you can install Pennylane AI using pip, a package manager for Python. Open your terminal and run the following command: pip install pennylane --upgrade pip install pennylane-qiskit These commands install the core Pennylane library as well as the qiskit plugin, which allows Pennylane to interface with IBM's quantum hardware and simulators. For a more detailed installation guide and compatibility information, you can refer to the Pennylane Installation Documentation. Construct Quantum Circuits: Begin by constructing simple quantum circuits using the provided syntax. Pennylane AI supports a wide range of quantum operations and gates. Here is the famous quantum "Hello World" example: creating a quantum state that represents both 0 and 1 simultaneously. import pennylane as qml # Create a quantum device dev = qml.device("default.qubit", wires=1) # Create a quantum function @qml.qnode(dev) def quantum_circuit(): qml.Hadamard(wires=0) return qml.state() # Execute the quantum circuit result = quantum_circuit() print("Quantum state:", result) In this example, we've defined a simple quantum circuit with one wire (qubit). We've applied a Hadamard gate to create a superposition of states. The qml.state() function returns the quantum state of the qubit. Start exploring further with Xanadu Quantum Codebook and Pennylane AI documentation. Optimization: One of the powerful features of Pennylane AI is its ability to optimize quantum circuits using gradient-based methods. This optimization process enhances the performance of quantum algorithms. Utilize gradient-based optimization techniques to fine-tune the parameters of your quantum circuits for specific tasks. Integration with Machine Learning: Explore the integration of quantum circuits with classical machine learning algorithms, creating hybrid models that harness the strengths of both paradigms. Experimentation: Experiment with different quantum hardware platforms and simulators to observe the behavior of your circuits under various conditions. The Future of Quantum Programming As quantum computing technology advances, the capabilities of tools like Pennylane AI are set to grow exponentially. Differential quantum programming not only makes quantum computing more accessible but also paves the way for innovative applications in various domains, from optimizing complex systems to enhancing machine learning models. In conclusion, Xanadu's Pennylane AI and the concept of differential quantum programming offer an exciting avenue for quantum enthusiasts and researchers to explore the world of quantum computing and its integration with machine learning. With its user-friendly interface and powerful capabilities, Pennylane AI is a valuable tool that empowers individuals to design, simulate, and optimize quantum circuits with ease. As we continue to unlock the potential of quantum computing, tools like Pennylane AI will play a pivotal role in shaping the future of technology and scientific discovery. Know the Author I am Ashmit JaiSarita Gupta, an engineering physics undergraduate at the National Institute of Technology Hamirpur. I am passionate about Quantum Computing, Machine Learning, UI/UX, and Web Development. About two years ago, when I first discovered the field of Web Development and Quantum Computing, it totally amazed me and I have been dedicating my education to them ever since. Over the past two years, I have dedicated a considerable amount of time and effort to learning and developing skills in these fields by taking various online courses, reading different articles, making several projects, and being involved in various research internships and mentorship programs. Fast forward to today, I am currently researching QUBO Relaxation Parameter Optimisation using a Learning Surrogate Solver (QROSS). Visit my portfolio website to learn more about me, my previous projects, and the places I have worked. Feel free to connect with me on Twitter, LinkedIn, or GitHub.

Aug 30, 2023

I'm a boy who's fascinated by both computer technology and the physics that underpins it. Quantum Computing hit me first when I was about to be admitted to my college. It appeared to be pretty intriguing as to how it may affect the world we live in and propel humanity forward. However, I had no idea how to get into this field or where to look for the right tools I needed to study Quantum Computing as a fresher. In this article, I'm not going to explain what quantum computing is or what it can do. Instead, I'll show you how to get started on this topic by using Xanadu's Codebook for Quantum Computing and the Python module it provides for the same. Know the author: This is Ashmit JaiSarita Gupta, an Engineering Physics Undergraduate Sophomore at the National Institute Of Technology Hamirpur. I have been a scholar at Womanium Quantum 2022: Global Quantum Computing & Entrepreneurship Program and Y-Combinator’s Start-up School 2022. I am a Microsoft Learn Student Ambassador and an executive volunteer at the Society for Promotion of Electronics Culture. Currently, I am exploring Quantum computing and web development. Follow me on LinkedIn to know more about my professional career and on Twitter for getting my daily updates. PennyLane is a dedicated cross-platform Python library for differentiable programming of quantum computers that connects various quantum devices. It is a bridge between classical and quantum computations, making it easy to build and optimize hybrid computations. But how to start with it? Seeing the documentation of PennyLane, as a beginner in Quantum Computing and Mechanics, you'll likely decide to stop studying it on your own. I was in the same boat till I discovered Xanadu's Quantum Codebook, which is designed for the total beginner in this field provided you start learning some Linear Algebra and Quantum Mechanics parallelly. The Xanadu Quantum Codebook is a learning-by-doing tool that explores Quantum Computing by using PennyLane. It is an experimental, exercise-based introduction to quantum computing. To give you a quick introduction, the codebook is divided up into graph of modules which is further divided into various nodes. Each node has two parts - Textbook and Challenges. The textbook is the standalone resource that will help you to solve the codercises given in the Challenges part. You don't need any prior knowledge of PennyLane, which is taught in the codebook. But you should know some linear algebra and basic python programming. But is that enough to visualize the beauty of Quantum Computing and its language of linear algebra? The textbook is an excellent resource for learning, but as you progress through the nodes, you will realize that linear algebra is the language of quantum computing and should be well understood. What are the essence of applying any operation on any matrix, visualization of rank, and eigenvalues? It's important to understand and imagine linear algebra from the perspectives of physics, mathematics, and computer science students, as well as their inter-conversion. There are many resources from which you can learn linear algebra and maybe you already know some. I am not going to cover that. What you might not have is the visualization of linear algebra from the perspective of a computer science student, a physics student, and a mathematics student, all of which are required to understand and feel the operations performed on a qubit. I recommend that everyone, whether they know Linear Algebra or not, watch the free series by 3Blue1Brown called Essence of Linear Algebra on YouTube which covers everything you need to know to get the essence of the topic. You may have read elsewhere that you do not need prior knowledge of quantum mechanics to begin learning Quantum Computing. This is only true when learning the basics of quantum computing. As you read through each chapter of Quantum Computing, you may notice that having a basic understanding of quantum mechanics will help you understand the concept better. However, quantum mechanics itself is a vast field of study, and while there are numerous books and resources for studying quantum mechanics, the majority of them are too advanced for beginners to comprehend. For getting started easily, the book that I followed is Introduction to Quantum Mechanics by David J. Griffiths. This book first teaches learners how to do quantum mechanics, and then provides them with a more insightful discussion of what it means. Fundamental principles are covered, the quantum theory presented, and special techniques developed for dealing with real problems. Learning theory alone will not suffice; especially in Quantum Computing, you will need to practice many questions to solidly grasp what you have learned. This is the reason why Xanadu Codebook follows the learning-by-doing model and provides many Theoretical, Mathematical, and Coding Problems in each of its nodes while covering any topic. But what if you got stuck in any of these questions? The best answer to this will be to stop for some time and try again the problem later. However, if you feel that you need some help then you don't have to worry at all. I am preparing a GitHub repository that will contain all the solutions to codercises of the Xanadu Quantum Codebook. Perhaps by the time you read this article, it will have all the solutions, but for the time being, it contains enough solutions to keep you going on your journey. And if you want to contribute some solutions then feel free to ping me through Gmail or LinkedIn or GitHub. Finally, I'll just say that there may come a time when you feel you're not made for Quantum Computing or that it's not the right time to study. But don't give up; quantum computing will be one of the most exciting fields to work in the future. All the Best and Happy Learning 😊. You can do it 😉.

May 29, 2022

I'm a boy who's fascinated by both computer technology and the physics that underpins it. Quantum Computing hit me first when I was about to be admitted to my college. It appeared to be pretty intriguing as to how it may affect the world we live in a...

May 29, 2022