From 0452f8ada49e473bef237aaba32975caba72efb7 Mon Sep 17 00:00:00 2001 From: InfoTCube Date: Mon, 17 Aug 2026 21:22:23 +0200 Subject: [PATCH 1/2] feat: square NuGet icon, dedicated package readme, culture-safe formatting - Use the 512x512 icon for the package. The 1024x363 banner was letterboxed in nuget.org's square icon slot; the banner stays as the GitHub readme header. - Ship README.nuget.md as the package readme. nuget.org cannot resolve relative links or images, so the repo readme rendered with a broken logo, and the package page is a funnel to GitHub rather than a place for 400 lines of docs. - Made the repo readme's logo an absolute URL for the same reason. Packaging the readme exposed that the showcased ToString() output was wrong on most machines: it used the current culture's decimal separator and printed full double precision, so a Bell state read 0,7071067811865475|00> rather than 0.7071|00>. Formatting is now invariant and rounded, amplitudes that are only numerical noise are dropped instead of printed as 1E-17, and a negative imaginary part reads "a - bi" rather than "a + -bi". Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Mhwc4XeGtzM2UQx9sNLb3r --- CHANGELOG.md | 4 + README.md | 2 +- README.nuget.md | 123 +++++++++++++++++++++++ img/qubitnet-icon-512.png | Bin 0 -> 24124 bytes img/qubitnet-icon.svg | 16 +++ src/Qubit.NET/Qubit.NET.csproj | 8 +- src/Qubit.NET/Utilities/Helpers.cs | 33 ++++-- tests/Qubit.NET.Tests/FormattingTests.cs | 87 ++++++++++++++++ 8 files changed, 263 insertions(+), 10 deletions(-) create mode 100644 README.nuget.md create mode 100644 img/qubitnet-icon-512.png create mode 100644 img/qubitnet-icon.svg create mode 100644 tests/Qubit.NET.Tests/FormattingTests.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index e2f3aee..782c519 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -75,4 +75,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..ce3a926 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -Qubit.NET logo +Qubit.NET logo # Qubit.NET diff --git a/README.nuget.md b/README.nuget.md new file mode 100644 index 0000000..9c2a55a --- /dev/null +++ b/README.nuget.md @@ -0,0 +1,123 @@ + + +# 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.1**, **.NET 8** and **.NET 10**. + +--- + +## 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 0000000000000000000000000000000000000000..637db7a7a60ca73c46f53b78477c8825a6adc590 GIT binary patch literal 24124 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

Ov7bxG_* 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

GBOocfg2a9q8L{hmz{0*zz7KS&!tLKy`V}kn@ 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 JrH@R5{s;0(^X~uv literal 0 HcmV?d00001 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..9e4d6c2 100644 --- a/src/Qubit.NET/Qubit.NET.csproj +++ b/src/Qubit.NET/Qubit.NET.csproj @@ -19,7 +19,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 +34,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; + } + } +} From 56bef7d9c47f138152ab749eaa73f654a24d9c2b Mon Sep 17 00:00:00 2001 From: InfoTCube Date: Mon, 17 Aug 2026 21:26:44 +0200 Subject: [PATCH 2/2] feat: add a netstandard2.0 target and bump to 1.0.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit netstandard2.0 reaches .NET Framework 4.6.1+, Mono, Godot and Unity 2018-2020, which netstandard2.1 leaves out. It compiles with no changes and no warnings — dropping BitOperations.PopCount earlier was the only blocker — and the package still has zero dependencies. Skipped net9.0: it went out of support in May 2026, and a .NET 9 app resolves the net8.0 asset anyway. net6/net7 likewise resolve netstandard2.1. Version is 1.0.1 because 1.0.0 is already on nuget.org and NuGet never allows a version to be re-pushed. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Mhwc4XeGtzM2UQx9sNLb3r --- CHANGELOG.md | 25 ++++++++++++++++++++++++- README.md | 5 +++-- README.nuget.md | 3 ++- src/Qubit.NET/Qubit.NET.csproj | 7 ++++--- 4 files changed, 33 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 782c519..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. diff --git a/README.md b/README.md index ce3a926..ae746df 100644 --- a/README.md +++ b/README.md @@ -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 index 9c2a55a..6eb9200 100644 --- a/README.nuget.md +++ b/README.nuget.md @@ -15,7 +15,8 @@ OpenQASM so your circuit runs on real hardware. No dependencies. dotnet add package Qubit.NET ``` -Targets **.NET Standard 2.1**, **.NET 8** and **.NET 10**. +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. --- diff --git a/src/Qubit.NET/Qubit.NET.csproj b/src/Qubit.NET/Qubit.NET.csproj index 9e4d6c2..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