diff --git a/CHANGELOG.md b/CHANGELOG.md index e2f3aee..987cd04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,30 @@ All notable changes to this project are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [1.0.0] — unreleased +## [1.0.1] + +### Added + +- **.NET Standard 2.0** target, reaching .NET Framework 4.6.1+, Mono, Godot and Unity + 2018–2020. The package now ships `netstandard2.0`, `netstandard2.1`, `net8.0` and `net10.0`. +- A dedicated package readme. nuget.org cannot resolve relative links, so the repository + readme rendered there with a broken logo. + +### Changed + +- The package icon is now a square 512×512 image. nuget.org renders icons in a square slot, + which letterboxed the previous 1024×363 banner. + +### Fixed + +- State and matrix strings used the current culture's decimal separator and printed full + double precision, so `ToString()` returned `0,7071067811865475|00>` on any machine with a + comma separator. Output is now culture-invariant and rounded to four decimals. +- Amplitudes that are only numerical noise are no longer printed: `H(0); H(0);` reads `|0>` + rather than including a `1E-17` term. +- A negative imaginary part reads `0.5 - 0.5i` rather than `0.5 + -0.5i`. + +## [1.0.0] First packaged release. Qubit.NET is now installable with `dotnet add package Qubit.NET` and usable from Unity. @@ -75,4 +98,8 @@ and usable from Unity. flags with the original, so gates applied to a copy also affected the source circuit. - Toffoli gates rendered their second control as a target marker in circuit diagrams. - `GetStringResult` threw an exception when every measurement count was zero. +- State and matrix strings used the current culture's decimal separator and printed full + double precision, so `ToString()` returned `0,7071067811865475|00>` on a machine with a + comma separator. Output is now culture-invariant and rounded to four decimals, and + amplitudes that are numerical noise are no longer printed at all. - Renamed the internal `ApplayGate` to `ApplyGate`. diff --git a/README.md b/README.md index a7a3a4e..ae746df 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -Qubit.NET logo +Qubit.NET logo # Qubit.NET @@ -21,11 +21,12 @@ The state vector holds 2ⁿ complex amplitudes, so memory is the limit: **20 qub dotnet add package Qubit.NET ``` -Zero dependencies. Targets **.NET Standard 2.1**, **.NET 8** and **.NET 10**. +Zero dependencies. Targets **.NET Standard 2.0 / 2.1**, **.NET 8** and **.NET 10**, so it’s +also usable from .NET Framework 4.6.1+, Mono and Godot. ### 🎮 Unity -Qubit.NET ships a `netstandard2.1` build, so it works in Unity 2021.2+. Either install it +Qubit.NET ships `netstandard2.0` and `netstandard2.1` builds, so it works in Unity 2018 and later. Either install it through [NuGetForUnity](https://github.com/GlitchEnzo/NuGetForUnity), or drop `lib/netstandard2.1/Qubit.NET.dll` from the package into `Assets/Plugins/`. diff --git a/README.nuget.md b/README.nuget.md new file mode 100644 index 0000000..6eb9200 --- /dev/null +++ b/README.nuget.md @@ -0,0 +1,124 @@ + + +# Qubit.NET + +**A quantum computing simulator for .NET — and the only one that runs in Unity.** + +Build quantum circuits, apply gates, measure results, draw circuit diagrams, and export to +OpenQASM so your circuit runs on real hardware. No dependencies. + +```bash +dotnet add package Qubit.NET +``` + +Targets **.NET Standard 2.0 / 2.1**, **.NET 8** and **.NET 10** — so it also runs on +.NET Framework 4.6.1+, Mono and Godot. + +--- + +## Quick start + +```csharp +using Qubit.NET; + +var qc = new QuantumCircuit(2); + +qc.H(0); // superposition +qc.CNOT(0, 1); // entangle + +Console.WriteLine(qc); // 0.7071|00> + 0.7071|11> +Console.WriteLine(qc.Measure()); // "00" or "11", never "01" or "10" +``` + +Run it many times and count outcomes: + +```csharp +using Qubit.NET.Simulation; + +qc.Measure(); +var result = Simulator.Run(qc, 1000)[0]; + +Console.WriteLine(result); // {'00': 512, '11': 488} +Console.WriteLine(result.Probability("11")); // 0.488 +``` + +## Draw circuits + +```csharp +qc.Draw(); // colored, to the console +qc.ToDiagram(); // the same thing as a string +``` + +``` +q2 (0): ───────────[+]──[X]──[M]─ + | | | +q1 (0): ──────[+]───@────|───[M]─ + | | | | +q0 (0): ─[H]───@────@───[X]──[M]─ +``` + +## Unity + +Qubit.NET ships a `netstandard2.1` build, so it works in Unity 2021.2+. Install through +[NuGetForUnity](https://github.com/GlitchEnzo/NuGetForUnity), or drop +`lib/netstandard2.1/Qubit.NET.dll` into `Assets/Plugins/`. + +Unity has no `Console`, so use the string-returning APIs: + +```csharp +Debug.Log(qc.ToDiagram()); + +var (x, y, z) = qc.BlochVector(0); // drive a Bloch sphere gizmo +arrow.transform.localPosition = new Vector3((float)x, (float)y, (float)z); +``` + +A qubit entangled with others sits *inside* the Bloch sphere — maximally entangled means +the origin, which makes entanglement something you can actually see. + +## Built-in algorithms + +```csharp +using Qubit.NET.Circuits; + +var grover = Algorithms.Grover(2, c => c.CZ(0, 1)); // marks |11> +Console.WriteLine(grover.ToHistogram()); // 11 | ######### 1.000 + +var bv = Algorithms.BernsteinVazirani([true, false, true]); +Console.WriteLine(bv.Measure(2, 1, 0)); // 101 +``` + +Also: Deutsch–Jozsa, teleportation, superdense coding, QFT, and the Bell and GHZ states. + +## Run on real hardware + +```csharp +using Qubit.NET.Export; + +File.WriteAllText("bell.qasm", qc.ToQasm()); +``` + +```python +# Qiskit +qc = QuantumCircuit.from_qasm_file("bell.qasm") +``` + +## What else is in the box + +- **27 gates** — Pauli, Hadamard, phase, √X/√Y/√Z, rotations, U3, controlled forms, Toffoli, Fredkin, and your own unitary matrices +- **Mid-circuit measurement and classical feedforward** — `MeasureInto` and `When`, enough for teleportation and error correction +- **Partial measurement** of any subset of qubits +- **Pluggable randomness** via `IRandomSource` — seed it for reproducible tests, or plug in a quantum RNG +- **In-place simulation** — a 20-qubit GHZ chain runs in 34 ms and allocates one state vector, not one per gate + +Up to 26 qubits; memory is the limit, since the state vector holds 2ⁿ complex amplitudes +(20 qubits ≈ 16 MB, 24 ≈ 256 MB). + +--- + +**[Full documentation, examples and source on GitHub →](https://github.com/InfoTCube/Qubit.NET)** + +MIT licensed. Issues and pull requests welcome. diff --git a/img/qubitnet-icon-512.png b/img/qubitnet-icon-512.png new file mode 100644 index 0000000..637db7a Binary files /dev/null and b/img/qubitnet-icon-512.png differ diff --git a/img/qubitnet-icon.svg b/img/qubitnet-icon.svg new file mode 100644 index 0000000..2fa2e28 --- /dev/null +++ b/img/qubitnet-icon.svg @@ -0,0 +1,16 @@ + + Qubit.NET + + + + + + + + + + + + + + diff --git a/src/Qubit.NET/Qubit.NET.csproj b/src/Qubit.NET/Qubit.NET.csproj index 68ed256..d463efc 100644 --- a/src/Qubit.NET/Qubit.NET.csproj +++ b/src/Qubit.NET/Qubit.NET.csproj @@ -1,8 +1,9 @@ - - netstandard2.1;net8.0;net10.0 + + netstandard2.0;netstandard2.1;net8.0;net10.0 latest enable enable @@ -11,7 +12,7 @@ Qubit.NET - 1.0.0 + 1.0.1 Tymoteusz Marzec A lightweight quantum computing simulation library for .NET. Build quantum circuits, apply gates, measure results, and draw ASCII circuit diagrams — no dependencies. Works in .NET and Unity. quantum;quantum-computing;qubit;simulator;quantum-circuit;unity;csharp;dotnet @@ -19,7 +20,7 @@ https://github.com/InfoTCube/Qubit.NET https://github.com/InfoTCube/Qubit.NET git - qubitnet.png + qubitnet-icon-512.png README.md See CHANGELOG.md @@ -34,8 +35,10 @@ - - + + + diff --git a/src/Qubit.NET/Utilities/Helpers.cs b/src/Qubit.NET/Utilities/Helpers.cs index fd7680b..2c8c3f5 100644 --- a/src/Qubit.NET/Utilities/Helpers.cs +++ b/src/Qubit.NET/Utilities/Helpers.cs @@ -1,3 +1,4 @@ +using System.Globalization; using Qubit.NET.Gates; namespace Qubit.NET.Utilities; @@ -16,18 +17,38 @@ internal static class Helpers /// A string representation of the complex number, in the form: "real + imaginary*i" or "real" or "imaginary*i". internal static string FormatComplex(double real, double imaginary) { + // Applying gates leaves amplitudes like 1e-17 where the exact answer is zero. + // Printing those makes state strings unreadable, so round them away. + if (System.Math.Abs(real) < Epsilon) real = 0; + if (System.Math.Abs(imaginary) < Epsilon) imaginary = 0; + if (imaginary == 0 && real == 0) return String.Empty; - + if (imaginary == 0) - return real.ToString(); - + return Number(real); + if (real == 0) - return $"{imaginary}i"; - - return $"{real} + {imaginary}i"; + return $"{Number(imaginary)}i"; + + // Keep the sign out of the term itself, so this reads "a - bi" not "a + -bi". + string sign = imaginary < 0 ? "-" : "+"; + + return $"{Number(real)} {sign} {Number(System.Math.Abs(imaginary))}i"; } + /// + /// Amplitudes below this are treated as zero when formatting. + /// + private const double Epsilon = 1e-10; + + /// + /// Formats a component to four decimal places using the invariant culture, so output + /// does not change with the machine's locale. + /// + private static string Number(double value) => + value.ToString("0.####", CultureInfo.InvariantCulture); + /// /// Returns a string array representation of a quantum gate, where each element corresponds /// to the symbol that should be displayed on a specific qubit line in a circuit diagram. diff --git a/tests/Qubit.NET.Tests/FormattingTests.cs b/tests/Qubit.NET.Tests/FormattingTests.cs new file mode 100644 index 0000000..5753810 --- /dev/null +++ b/tests/Qubit.NET.Tests/FormattingTests.cs @@ -0,0 +1,87 @@ +using System.Globalization; +using System.Numerics; +using Qubit.NET; +using Qubit.NET.Circuits; +using Qubit.NET.Gates; + +namespace QubitNet.Tests; + +/// +/// State and matrix formatting. These strings are the first thing a user sees, and they +/// must not change with the machine's locale. +/// +public class FormattingTests +{ + [Fact] + public void State_string_is_readable() + { + Assert.Equal("0.7071|00> + 0.7071|11>", BellStates.PhiPlus().ToString()); + } + + [Fact] + public void Basis_states_drop_the_redundant_coefficient() + { + QuantumCircuit qc = new(2); + qc.X(0); + + Assert.Equal("|01>", qc.ToString()); + } + + [Fact] + public void Negative_amplitudes_keep_their_sign() + { + Assert.Equal("0.7071|00> + -0.7071|11>", BellStates.PhiMinus().ToString()); + } + + [Fact] + public void Numerical_noise_is_not_printed() + { + // H twice is the identity, but leaves ~1e-17 in the other amplitude. + QuantumCircuit qc = new(1); + qc.H(0); + qc.H(0); + + Assert.Equal("|0>", qc.ToString()); + } + + [Fact] + public void Imaginary_amplitudes_are_formatted_as_complex_numbers() + { + QuantumCircuit qc = new(1); + qc.H(0); + qc.S(0); + + // (|0> + i|1>)/sqrt(2) + Assert.Equal("0.7071|0> + 0.7071i|1>", qc.ToString()); + } + + [Fact] + public void A_negative_imaginary_part_reads_as_a_subtraction() + { + // 0.5 - 0.5i, via the SX gate's off-diagonal entry. + string formatted = QuantumGates.Format(QuantumGates.SX); + + Assert.Contains("0.5 - 0.5i", formatted); + Assert.DoesNotContain("+ -", formatted); + } + + [Theory] + [InlineData("pl-PL")] // comma decimal separator + [InlineData("de-DE")] + [InlineData("en-US")] + public void Output_does_not_depend_on_the_current_culture(string culture) + { + CultureInfo original = CultureInfo.CurrentCulture; + CultureInfo.CurrentCulture = new CultureInfo(culture); + + try + { + Assert.Equal("0.7071|00> + 0.7071|11>", BellStates.PhiPlus().ToString()); + Assert.DoesNotContain(",", QuantumGates.Format(QuantumGates.H)); + } + finally + { + CultureInfo.CurrentCulture = original; + } + } +}