From 6b2968243a1477dfb4f3ae9f0c39006c7104f3a0 Mon Sep 17 00:00:00 2001 From: DmitryVasilevsky <60718360+DmitryVasilevsky@users.noreply.github.com> Date: Mon, 11 Sep 2023 12:15:25 -0700 Subject: [PATCH] Broombridge 0.3 work (#682) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Started work on updating to BB 0.3. * Added v0.3 support to qdk-chem convert. * --from → --format * Add --flatten option. * Update magic. * Temporarily disable test not compatible with 0.3. * Update test to bb 0.3, remove test made irrelevant by serialization refactor. * Disable test for loading old version. * Use trivial symmetry for orbital → fermion transform. * Fix fourfold symmetry bugs * Add unit test * Update datamodel tests for v0.3 * Addressed feedback --------- Co-authored-by: Christopher Granade Co-authored-by: Cassandra Granade Co-authored-by: Guang Hao Low Co-authored-by: Dmitry Vasilevsky --- Chemistry.sln | 27 +- .../OrbitalIntegral/OrbitalIntegral.cs | 90 ++- .../OrbitalIntegralExtensions.cs | 7 +- .../Broombridge/BroombridgeData.cs | 18 +- .../BroombridgeDataStructurev0.1.cs | 4 +- .../BroombridgeDataStructurev0.3.cs | 546 ++++++++++++++++++ .../Broombridge/BroombridgeSerializer.cs | 71 ++- .../Broombridge/BroombridgeVersionUpdater.cs | 38 +- .../Serialization/LegacyFormats/FciDump.cs | 2 +- Chemistry/src/Jupyter/ChemistryEncodeMagic.cs | 2 +- .../src/Jupyter/FermionHamiltonianMagic.cs | 2 +- Chemistry/src/Jupyter/WavefunctionMagic.cs | 2 +- Chemistry/src/Tools/ExportJW.cs | 163 ++++++ Chemistry/src/Tools/Normalize.cs | 2 +- Chemistry/src/Tools/Program.cs | 3 +- .../FermionHamiltonianTests.cs | 22 +- .../SerializationTests/BroombridgeTests.cs | 6 +- .../SerializationTests/LiQuiDTests.cs | 23 +- .../JupyterTests/BroombridgeMagicTests.cs | 28 +- .../SamplesTests/DocsSecondQuantization.cs | 23 +- 20 files changed, 966 insertions(+), 113 deletions(-) create mode 100644 Chemistry/src/DataModel/Serialization/Broombridge/BroombridgeDataStructurev0.3.cs create mode 100644 Chemistry/src/Tools/ExportJW.cs diff --git a/Chemistry.sln b/Chemistry.sln index 227549c7bb3..a4681332336 100644 --- a/Chemistry.sln +++ b/Chemistry.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.28803.156 +# Visual Studio Version 17 +VisualStudioVersion = 17.3.32929.385 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Documentation", "Documentation", "{561759D2-4D2D-4EE3-9565-9AAEC4A7D64B}" ProjectSection(SolutionItems) = preProject @@ -24,23 +24,25 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Standard", "Standard\src\St EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "BroombridgeExamples", "BroombridgeExamples", "{F25F3396-EDE5-4A9F-A428-643EB138F00F}" ProjectSection(SolutionItems) = preProject - Chemistry\tests\BroombridgeExamples\broombridge_v0.1.yaml = Chemistry\tests\BroombridgeExamples\broombridge_v0.1.yaml - Chemistry\tests\BroombridgeExamples\broombridge_v0.2.yaml = Chemistry\tests\BroombridgeExamples\broombridge_v0.2.yaml - Chemistry\tests\BroombridgeExamples\hydrogen_0.1.yaml = Chemistry\tests\BroombridgeExamples\hydrogen_0.1.yaml - Chemistry\tests\BroombridgeExamples\hydrogen_0.2.yaml = Chemistry\tests\BroombridgeExamples\hydrogen_0.2.yaml - Chemistry\tests\BroombridgeExamples\LiH_0.1.yaml = Chemistry\tests\BroombridgeExamples\LiH_0.1.yaml - Chemistry\tests\BroombridgeExamples\LiH_0.2.yaml = Chemistry\tests\BroombridgeExamples\LiH_0.2.yaml + Chemistry\tests\TestData\Broombridge\broombridge_v0.1.yaml = Chemistry\tests\TestData\Broombridge\broombridge_v0.1.yaml + Chemistry\tests\TestData\Broombridge\broombridge_v0.2.yaml = Chemistry\tests\TestData\Broombridge\broombridge_v0.2.yaml + Chemistry\tests\TestData\Broombridge\hydrogen_0.1.yaml = Chemistry\tests\TestData\Broombridge\hydrogen_0.1.yaml + Chemistry\tests\TestData\Broombridge\hydrogen_0.2.yaml = Chemistry\tests\TestData\Broombridge\hydrogen_0.2.yaml + Chemistry\tests\TestData\Broombridge\LiH_0.1.yaml = Chemistry\tests\TestData\Broombridge\LiH_0.1.yaml + Chemistry\tests\TestData\Broombridge\LiH_0.2.yaml = Chemistry\tests\TestData\Broombridge\LiH_0.2.yaml EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SamplesTests", "Chemistry\tests\SamplesTests\SamplesTests.csproj", "{2A0E61DB-7187-4359-B5C7-C30FCB1D6800}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Metapackage", "Chemistry\src\Metapackage\Metapackage.csproj", "{E8268248-FC5B-4F4E-82FF-5C8CC40950BB}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Metapackage", "Chemistry\src\Metapackage\Metapackage.csproj", "{E8268248-FC5B-4F4E-82FF-5C8CC40950BB}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Chemistry", "Chemistry", "{43A9F607-5884-4CB9-A455-01E98F5532E2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{812F2D11-792D-4305-8427-01B632A92299}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tools", "Chemistry\src\Tools\Tools.csproj", "{3EF5845F-B348-4DC9-A905-23A6FB9AB421}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tools", "Chemistry\src\Tools\Tools.csproj", "{3EF5845F-B348-4DC9-A905-23A6FB9AB421}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DataModelTests", "Chemistry\tests\DataModelTests\DataModelTests.csproj", "{B86DDA60-44F0-4C05-837E-CEA84DBCADF7}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -80,6 +82,10 @@ Global {3EF5845F-B348-4DC9-A905-23A6FB9AB421}.Debug|Any CPU.Build.0 = Debug|Any CPU {3EF5845F-B348-4DC9-A905-23A6FB9AB421}.Release|Any CPU.ActiveCfg = Release|Any CPU {3EF5845F-B348-4DC9-A905-23A6FB9AB421}.Release|Any CPU.Build.0 = Release|Any CPU + {B86DDA60-44F0-4C05-837E-CEA84DBCADF7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B86DDA60-44F0-4C05-837E-CEA84DBCADF7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B86DDA60-44F0-4C05-837E-CEA84DBCADF7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B86DDA60-44F0-4C05-837E-CEA84DBCADF7}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -93,6 +99,7 @@ Global {E8268248-FC5B-4F4E-82FF-5C8CC40950BB} = {F561BE56-63D8-4C33-A3B3-CF2685BC7A5C} {812F2D11-792D-4305-8427-01B632A92299} = {43A9F607-5884-4CB9-A455-01E98F5532E2} {3EF5845F-B348-4DC9-A905-23A6FB9AB421} = {812F2D11-792D-4305-8427-01B632A92299} + {B86DDA60-44F0-4C05-837E-CEA84DBCADF7} = {595D5855-8820-48D7-B5E1-9C88215A866A} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {6869E5BF-551A-40F1-9B6B-D1B27A5676CB} diff --git a/Chemistry/src/DataModel/OrbitalIntegral/OrbitalIntegral.cs b/Chemistry/src/DataModel/OrbitalIntegral/OrbitalIntegral.cs index eaff2be4cff..88b9cd1c090 100644 --- a/Chemistry/src/DataModel/OrbitalIntegral/OrbitalIntegral.cs +++ b/Chemistry/src/DataModel/OrbitalIntegral/OrbitalIntegral.cs @@ -24,6 +24,17 @@ public enum Convention Dirac, Mulliken } + // NB [Design note]: this intentionally duplicates the corresponding + // enum in the V0_3 class, allowing forward versions + // to add or modify permutation symmetries without + // retroactively changing the definition of V0_3. + public enum PermutationSymmetry + { + Eightfold, + Fourfold, + Trivial + } + /// /// Indices of orbitals in the overlap integral. /// @@ -34,6 +45,11 @@ public enum Convention /// public double Coefficient; + /// + /// Symmetry of the orbital overlap integral. + /// + public PermutationSymmetry Symmetry = PermutationSymmetry.Eightfold; + /// /// Parameterless constructors. Sets this as an empty OrbitalIntegral with coefficient 0.0 /// @@ -41,10 +57,10 @@ public OrbitalIntegral() : this(0.0) { } - public OrbitalIntegral(double coefficient) + /// coefficient of orbital integral. + /// Convention of symmetry of orbital indices. + public OrbitalIntegral(double coefficient, PermutationSymmetry symmetry = PermutationSymmetry.Eightfold) : this(new int[] { }, coefficient, symmetry) { - OrbitalIndices = new int[] { }; - Coefficient = coefficient; } /// @@ -52,10 +68,12 @@ public OrbitalIntegral(double coefficient) /// /// Array of orbital indices in Dirac notation. /// coefficient of orbital integral. - public OrbitalIntegral(IEnumerable orbitalIndices, double coefficient = 0.0) + /// Convention of symmetry of orbital indices. + public OrbitalIntegral(IEnumerable orbitalIndices, double coefficient = 0.0, PermutationSymmetry symmetry = PermutationSymmetry.Eightfold) { OrbitalIndices = orbitalIndices.ToArray(); Coefficient = coefficient; + Symmetry = symmetry; } /// @@ -64,10 +82,12 @@ public OrbitalIntegral(IEnumerable orbitalIndices, double coefficient = 0.0 /// Array of orbital indices. /// coefficient of orbital integral. /// Convention for ordering of orbital indices. - public OrbitalIntegral(IEnumerable orbitalIndices, double coefficient, Convention convention = Convention.Mulliken) + /// Convention of symmetry of orbital indices. + public OrbitalIntegral(IEnumerable orbitalIndices, double coefficient, PermutationSymmetry symmetry, Convention convention = Convention.Mulliken) { OrbitalIndices = ConvertIndices(orbitalIndices, convention, Convention.Dirac); Coefficient = coefficient; + Symmetry = symmetry; } public TermType.OrbitalIntegral TermType @@ -107,6 +127,40 @@ public void ResetSign() /// Length of orbital indices. public int Length => OrbitalIndices.Length; + private static int[][] EnumerateTwoBodyPermutations(PermutationSymmetry symmetry, int i, int j, int k, int l) => + symmetry switch + { + // In Mulliken notation, + // (ij|kl) = (ij|lk) = (ji|kl) = (ji|lk) = + // (kl|ij) = (lk|ij) = (kl|ji) = (lk|ji) + // Orbital indices are in Dirac notation. + PermutationSymmetry.Eightfold => new int[][] + { + new int[] { i, j, k, l }, // 0123 + new int[] { j, i, l, k }, // 1032 + new int[] { k, l, i, j }, // 2301 + new int[] { l, k, j, i }, // 3210 + new int[] { i, k, j, l }, // 0213 + new int[] { k, i, l, j }, // 2031 + new int[] { j, l, i, k }, // 1302 + new int[] { l, j, k, i } // 3120 + }, + // In Mulliken notation, + // (ij|kl) = (ji|lk)* = (kl|ij) = (lk|ji)*, where * denotes complex conjugation + // Orbital indices are in Dirac notation. + PermutationSymmetry.Fourfold => new int[][] + { + new int[] { i, j, k, l }, // Identity + new int[] { l, k, j, i }, // Complex conjugation + new int[] { j, i, l, k }, // Complex conjugation & Change of variables + new int[] { k, l, i, j }, // Change of variables + }, + PermutationSymmetry.Trivial => new int[][] + { + new int[] { i, j, k, l } + }, + _ => throw new Exception($"Permutation symmetry {symmetry} is not valid for two-body permutations.") + }; /// /// Enumerates over all orbital integrals with the same coefficient @@ -128,25 +182,13 @@ public OrbitalIntegral[] EnumerateOrbitalSymmetries() new int[] {i, j}, new int[] {j, i} }; - return symmetries.Distinct(new ArrayEqualityComparer()).Select(o => new OrbitalIntegral(o, coefficient)).ToArray(); + return symmetries.Distinct(new ArrayEqualityComparer()).Select(o => new OrbitalIntegral(o, coefficient, Symmetry)).ToArray(); } else if (OrbitalIndices.Length == 4) { - var i = OrbitalIndices[0]; - var j = OrbitalIndices[1]; - var k = OrbitalIndices[2]; - var l = OrbitalIndices[3]; - var symmetries = new int[][] { - new int[] { i, j, k, l }, // 0123 - new int[] { j, i, l, k }, // 1032 - new int[] { k, l, i, j }, // 2301 - new int[] { l, k, j, i }, // 3210 - new int[] { i, k, j, l }, // 0213 - new int[] { k, i, l, j }, // 2031 - new int[] { j, l, i, k }, // 1302 - new int[] { l, j, k, i } // 3120 - }; - return symmetries.Distinct(new ArrayEqualityComparer()).Select(o => new OrbitalIntegral(o, coefficient)).ToArray(); + return EnumerateTwoBodyPermutations(Symmetry, OrbitalIndices[0], OrbitalIndices[1], OrbitalIndices[2], OrbitalIndices[3]) + .Distinct(new ArrayEqualityComparer()) + .Select(o => new OrbitalIntegral(o, coefficient, Symmetry)).ToArray(); } else { @@ -160,7 +202,7 @@ public OrbitalIntegral[] EnumerateOrbitalSymmetries() public OrbitalIntegral Clone() { var newArray = OrbitalIndices.Clone(); - return new OrbitalIntegral(newArray, Coefficient); + return new OrbitalIntegral(newArray, Coefficient, Symmetry); } /// @@ -172,14 +214,14 @@ public OrbitalIntegral ToCanonicalForm() { var symmetries = EnumerateOrbitalSymmetries().Select(o => o.OrbitalIndices).ToList(); symmetries.Sort(new ArrayLexicographicComparer()); - return new OrbitalIntegral(symmetries.First(), Coefficient); + return new OrbitalIntegral(symmetries.First(), Coefficient, Symmetry); } /// /// Checks of this orbital integral has indices sorted in canonical order. /// /// Returns if the orbital integral indices are canonically sorted - /// and otherwise. + /// and false otherwise. /// public bool IsInCanonicalOrder() { diff --git a/Chemistry/src/DataModel/OrbitalIntegral/OrbitalIntegralExtensions.cs b/Chemistry/src/DataModel/OrbitalIntegral/OrbitalIntegralExtensions.cs index 694bdf89527..0b00361a622 100644 --- a/Chemistry/src/DataModel/OrbitalIntegral/OrbitalIntegralExtensions.cs +++ b/Chemistry/src/DataModel/OrbitalIntegral/OrbitalIntegralExtensions.cs @@ -20,7 +20,6 @@ namespace Microsoft.Quantum.Chemistry.OrbitalIntegrals /// public static partial class Extensions { - /// /// Method for constructing a fermion Hamiltonian from an orbital integral Hamiltonian. /// @@ -34,7 +33,7 @@ public static FermionHamiltonian ToFermionHamiltonian( var nOrbitals = sourceHamiltonian.SystemIndices.Max() + 1; var hamiltonian = new FermionHamiltonian(); Func> conversion = - (orb, coeff) => new OrbitalIntegral(orb.OrbitalIndices, coeff).ToHermitianFermionTerms(nOrbitals, indexConvention) + (orb, coeff) => new OrbitalIntegral(orb.OrbitalIndices, coeff, orb.Symmetry).ToHermitianFermionTerms(nOrbitals, indexConvention) .Select(o => (o.Item1, o.Item2.ToDoubleCoeff())); foreach (var termType in sourceHamiltonian.Terms) @@ -95,7 +94,9 @@ public static FermionHamiltonian ToFermionHamiltonian( { // One-electron orbital integral symmetries // ij = ji - var pqSpinOrbitals = orbitalIntegral.EnumerateOrbitalSymmetries().EnumerateSpinOrbitals(); + var pqSpinOrbitals = orbitalIntegral + .EnumerateOrbitalSymmetries() + .EnumerateSpinOrbitals(); var coefficient = orbitalIntegral.Coefficient; diff --git a/Chemistry/src/DataModel/Serialization/Broombridge/BroombridgeData.cs b/Chemistry/src/DataModel/Serialization/Broombridge/BroombridgeData.cs index f438ce9fc69..cbf370b9d12 100644 --- a/Chemistry/src/DataModel/Serialization/Broombridge/BroombridgeData.cs +++ b/Chemistry/src/DataModel/Serialization/Broombridge/BroombridgeData.cs @@ -22,6 +22,8 @@ namespace Microsoft.Quantum.Chemistry.Broombridge /// /// Latest Broombridge format. /// + // NB: When obsoleted, this should likely be made internal rather than + // removed. [Obsolete( "Please use collections of ElectronicStructureProblem instead.", error: false @@ -39,7 +41,7 @@ public Data() /// /// Raw deserialized Broombridge data. /// - public V0_2.Data Raw { get; set; } + public V0_3.Data Raw { get; set; } // Root of Broombridge data structure @@ -62,12 +64,12 @@ public Data() /// /// Deserialized Broombridge data /// - /// Broombridge data structure. - internal Data(Broombridge.V0_2.Data broombridgeV0_2) + /// Broombridge data structure. + internal Data(Broombridge.V0_3.Data broombridgeV0_3) { - Raw = broombridgeV0_2; - Schema = broombridgeV0_2.Schema; - VersionNumber = VersionNumber.v0_2; + Raw = broombridgeV0_3; + Schema = broombridgeV0_3.Schema; + VersionNumber = VersionNumber.v0_3; ProblemDescriptions = Raw.ProblemDescriptions.Select(problem => ProblemDescription.ProcessRawProblemDescription(problem)); } @@ -114,14 +116,14 @@ public struct ProblemDescription /// /// Problem description to be converted /// The internal problem description data structure. - public static ProblemDescription ProcessRawProblemDescription(Broombridge.V0_2.ProblemDescription problem) + public static ProblemDescription ProcessRawProblemDescription(Broombridge.V0_3.ProblemDescription problem) { var problemDescription = new ProblemDescription { EnergyOffset = problem.EnergyOffset.Value + problem.CoulombRepulsion.Value, NElectrons = problem.NElectrons, NOrbitals = problem.NOrbitals, - OrbitalIntegralHamiltonian = V0_2.ToOrbitalIntegralHamiltonian(problem), + OrbitalIntegralHamiltonian = V0_3.ToOrbitalIntegralHamiltonian(problem), Wavefunctions = problem.InitialStates?.FromBroombridgeV0_2() ?? new Dictionary>() }; return problemDescription; diff --git a/Chemistry/src/DataModel/Serialization/Broombridge/BroombridgeDataStructurev0.1.cs b/Chemistry/src/DataModel/Serialization/Broombridge/BroombridgeDataStructurev0.1.cs index 456af264f23..f4d08591a55 100644 --- a/Chemistry/src/DataModel/Serialization/Broombridge/BroombridgeDataStructurev0.1.cs +++ b/Chemistry/src/DataModel/Serialization/Broombridge/BroombridgeDataStructurev0.1.cs @@ -386,7 +386,7 @@ internal static OrbitalIntegralHamiltonian ToOrbitalIntegralHamiltonian( hamiltonian.Add (hamiltonianData.OneElectronIntegrals.Values .Select(o => new OrbitalIntegral(o.Item1 - .Select(k => (int)(k - 1)), o.Item2, OrbitalIntegral.Convention.Mulliken) + .Select(k => (int)(k - 1)), o.Item2, OrbitalIntegral.PermutationSymmetry.Eightfold, OrbitalIntegral.Convention.Mulliken) .ToCanonicalForm()) .Distinct()); @@ -395,7 +395,7 @@ internal static OrbitalIntegralHamiltonian ToOrbitalIntegralHamiltonian( hamiltonian.Add (hamiltonianData.TwoElectronIntegrals.Values .Select(o => new OrbitalIntegral(o.Item1 - .Select(k => (int)(k - 1)), o.Item2, OrbitalIntegral.Convention.Mulliken) + .Select(k => (int)(k - 1)), o.Item2, OrbitalIntegral.PermutationSymmetry.Eightfold, OrbitalIntegral.Convention.Mulliken) .ToCanonicalForm()) .Distinct()); diff --git a/Chemistry/src/DataModel/Serialization/Broombridge/BroombridgeDataStructurev0.3.cs b/Chemistry/src/DataModel/Serialization/Broombridge/BroombridgeDataStructurev0.3.cs new file mode 100644 index 00000000000..2857ad795c3 --- /dev/null +++ b/Chemistry/src/DataModel/Serialization/Broombridge/BroombridgeDataStructurev0.3.cs @@ -0,0 +1,546 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; +using System.Linq; +using System.Text.RegularExpressions; +using YamlDotNet.Core; +using YamlDotNet.Serialization; +using System.Numerics; + +using Microsoft.Quantum.Chemistry.OrbitalIntegrals; +using Microsoft.Quantum.Chemistry.Fermion; +using Microsoft.Quantum.Chemistry.LadderOperators; +using Newtonsoft.Json; +using System.Runtime.Serialization; + +using static Microsoft.Quantum.Chemistry.OrbitalIntegrals.IndexConventionConversions; +using YamlDotNet.Core.Events; +using System.Reflection; +using System.Collections.Immutable; + +namespace Microsoft.Quantum.Chemistry.Broombridge +{ + // What data structures are unmodified from previous versions? + using Format = V0_1.Format; + using Generator = V0_1.Generator; + using BibliographyItem = V0_1.BibliographyItem; + using Geometry = V0_1.Geometry; + using HasUnits = V0_1.HasUnits; + using BasisSet = V0_1.BasisSet; + using SimpleQuantity = V0_1.SimpleQuantity; + using BoundedQuantity = V0_1.BoundedQuantity; + using State = V0_2.State; + using ClusterOperator = V0_2.ClusterOperator; + + internal static class BroombridgeExtensionsV0_3 + { + internal static V0_3.ProblemDescription ToBroombridgeV0_3( + this ElectronicStructureProblem problem + ) => new V0_3.ProblemDescription + { + BasisSet = problem.BasisSet != null + ? new V0_1.BasisSet + { + Name = problem.BasisSet?.Name, + Type = problem.BasisSet?.Type + } + : null, + CoulombRepulsion = problem.CoulombRepulsion.ToBroombridgeV0_2(), + EnergyOffset = problem.EnergyOffset.ToBroombridgeV0_2(), + FciEnergy = problem.FciEnergy?.ToBroombridgeV0_2(), + Geometry = problem.Geometry?.ToBroombridgeV0_2(), + Hamiltonian = problem.OrbitalIntegralHamiltonian.ToBroombridgeV0_3(), + InitialStates = problem.InitialStates?.ToBroombridgeV0_2(), + Metadata = problem.Metadata, + NElectrons = problem.NElectrons, + NOrbitals = problem.NOrbitals, + ScfEnergy = problem.ScfEnergy?.ToBroombridgeV0_2(), + ScfEnergyOffset = problem.ScfEnergyOffset?.ToBroombridgeV0_2() + }; + + internal static V0_3.ArrayQuantityWithSymmetry TransformKeys( + this V0_3.ArrayQuantityWithSymmetry arrayQuantity, + Func transform + ) => + new V0_3.ArrayQuantityWithSymmetry + { + Format = arrayQuantity.Format, + IndexConvention = arrayQuantity.IndexConvention, + Symmetry = arrayQuantity.Symmetry, + Units = arrayQuantity.Units, + Values = arrayQuantity + .Values + .Select(item => new V0_3.ArrayQuantityWithSymmetry.Item + { + Key = transform(item.Key), + Value = item.Value + }) + .ToList() + }; + + internal static V0_3.ArrayQuantityWithSymmetry WithSymmetry( + this V0_3.ArrayQuantity arrayQuantity, + V0_3.Symmetry symmetry + ) => + new V0_3.ArrayQuantityWithSymmetry + { + Format = arrayQuantity.Format, + IndexConvention = arrayQuantity.IndexConvention, + Symmetry = symmetry, + Units = arrayQuantity.Units, + Values = arrayQuantity.Values + }; + + internal static V0_3.HamiltonianData ToBroombridgeV0_3(this OrbitalIntegralHamiltonian hamiltonian) + { + var twoElectronIntegrals = hamiltonian + .Terms[TermType.OrbitalIntegral.TwoBody] + .ToBroombridgeV0_3(new V0_3.Symmetry + { + // Broombridge v0.2 and below assumes Eightfold symmetry. + Permutation = V0_3.PermutationSymmetry.Eightfold + }); + twoElectronIntegrals.IndexConvention = OrbitalIntegral.Convention.Mulliken; + return new V0_3.HamiltonianData + { + OneElectronIntegrals = hamiltonian + .Terms[TermType.OrbitalIntegral.OneBody] + .ToBroombridgeV0_3(new V0_3.Symmetry + { + // Broombridge v0.2 and below assumes Eightfold symmetry, + // which for one-body terms means h_{pq} = h_{qp}. + Permutation = V0_3.PermutationSymmetry.Eightfold + }) + .TransformKeys(idxs => (idxs[0], idxs[1])), + TwoElectronIntegrals = twoElectronIntegrals + .TransformKeys(idxs => (idxs[0], idxs[1], idxs[2], idxs[3])) + }; + } + + internal static V0_3.ArrayQuantityWithSymmetry ToBroombridgeV0_3( + this Dictionary terms, + V0_3.Symmetry symmetry + ) => + new V0_3.ArrayQuantityWithSymmetry() + { + Format = V0_3.ArrayFormat.Sparse, + Units = "hartree", + Values = terms.Select(term => + { + var idxs = ConvertIndices( + term + .Key + .ToCanonicalForm() + .OrbitalIndices, + OrbitalIntegral.Convention.Dirac, + OrbitalIntegral.Convention.Mulliken + ) + .ToOneBasedIndices() + .Select(idx => (long)idx) + .ToArray(); + return new V0_3.ArrayQuantity.Item + { + Key = idxs, + Value = term.Value.Value + }; + }).ToList(), + Symmetry = symmetry + }; + + internal static V0_3.HamiltonianData ToBroombridgeV0_3(this V0_1.HamiltonianData hamiltonianData) => + new V0_3.HamiltonianData + { + ParticleHoleRepresentation = hamiltonianData.ParticleHoleRepresentation?.ToBroombridgeV0_3(), + OneElectronIntegrals = hamiltonianData + .OneElectronIntegrals + .ToBroombridgeV0_3() + .WithSymmetry(new V0_3.Symmetry + { + Permutation = V0_3.PermutationSymmetry.Eightfold + }) + .TransformKeys(key => (key[0], key[1])), + TwoElectronIntegrals = hamiltonianData + .TwoElectronIntegrals + .ToBroombridgeV0_3() + .WithSymmetry(new V0_3.Symmetry + { + Permutation = V0_3.PermutationSymmetry.Eightfold + }) + .TransformKeys(key => (key[0], key[1], key[2], key[3])), + }; + + internal static V0_3.ArrayQuantity ToBroombridgeV0_3(this V0_1.ArrayQuantity array) => + new V0_3.ArrayQuantity + { + Format = Enum.TryParse(array.Format, true, out var format) + ? format + : throw new Exception($"Invalid array format {array.Format} when converting 0.1 array quantity to 0.3 array quantity."), + IndexConvention = array.IndexConvention, + Units = array.Units, + Values = array + .Values + .Select(item => new V0_3.ArrayQuantity.Item + { + Key = item.Item1, + Value = item.Item2 + }) + .ToList() + }; + + internal static (O, O) Select(this (I, I) value, Func func) => + (func(value.Item1), func(value.Item2)); + internal static (O, O, O) Select(this (I, I, I) value, Func func) => + (func(value.Item1), func(value.Item2), func(value.Item3)); + internal static (O, O, O, O) Select(this (I, I, I, I) value, Func func) => + (func(value.Item1), func(value.Item2), func(value.Item3), func(value.Item4)); + + internal static T[] ToArray(this (T, T) value) => + new T[] { value.Item1, value.Item2 }; + internal static T[] ToArray(this (T, T, T) value) => + new T[] { value.Item1, value.Item2, value.Item3 }; + internal static T[] ToArray(this (T, T, T, T) value) => + new T[] { value.Item1, value.Item2, value.Item3, value.Item4 }; + } + + /// + /// Broombridge v0.3 format. + /// + /// Changes from v0.2: + /// + /// Addition of new symmetry key in HamiltonianData. + /// Sparse-format arrays are now represented with separate keys and values to simplify parsing logic. + /// + /// + #region Broombridge v0.3 format + public static class V0_3 + { + // TODO: This URL is not yet valid! + public static string SchemaUrl = "https://raw.githubusercontent.com/microsoft/Quantum/main/Chemistry/Schema/broombridge-0.3.schema.json"; + + // Root of Broombridge data structure + public struct Data + { + public static readonly Format DefaultFormat = new Broombridge.V0_1.Format + { + Version = "0.3" + }; + + [YamlMember(Alias = "$schema", ApplyNamingConventions = false)] + [JsonProperty(PropertyName = "$schema")] + public string Schema { get; set; } + + [YamlMember(Alias = "format", ApplyNamingConventions = false)] + [JsonProperty(PropertyName = "format")] + public Format Format { get; set; } + + [YamlMember(Alias = "generator", ApplyNamingConventions = false)] + [JsonProperty(PropertyName = "generator")] + public Generator Generator { get; set; } + + [YamlMember(Alias = "bibliography", ApplyNamingConventions = false)] + [JsonProperty(PropertyName = "bibliography")] + public List Bibliography { get; set; } + + [YamlMember(Alias = "problem_description", ApplyNamingConventions = false)] + [JsonProperty(PropertyName = "problem_description")] + public List ProblemDescriptions { get; set; } + + } + + public struct ProblemDescription + { + [YamlMember(Alias = "metadata", ApplyNamingConventions = false)] + [JsonProperty(PropertyName = "metadata")] + public Dictionary Metadata { get; set; } + + [YamlMember(Alias = "basis_set", ApplyNamingConventions = false)] + [JsonProperty(PropertyName = "basis_set")] + public BasisSet? BasisSet { get; set; } + + [YamlMember(Alias = "geometry", ApplyNamingConventions = false)] + [JsonProperty(PropertyName = "geometry")] + public Geometry? Geometry { get; set; } + + [YamlMember(Alias = "coulomb_repulsion", ApplyNamingConventions = false)] + [JsonProperty(PropertyName = "coulomb_repulsion")] + public SimpleQuantity CoulombRepulsion { get; set; } + + [YamlMember(Alias = "scf_energy", ApplyNamingConventions = false)] + [JsonProperty(PropertyName = "scf_energy")] + public SimpleQuantity? ScfEnergy { get; set; } + + [YamlMember(Alias = "scf_energy_offset", ApplyNamingConventions = false)] + [JsonProperty(PropertyName = "scf_energy_offset")] + public SimpleQuantity? ScfEnergyOffset { get; set; } + + [YamlMember(Alias = "fci_energy", ApplyNamingConventions = false)] + [JsonProperty(PropertyName = "fci_energy")] + public BoundedQuantity? FciEnergy { get; set; } + + [YamlMember(Alias = "n_orbitals", ApplyNamingConventions = false)] + [JsonProperty(PropertyName = "n_orbitals")] + public int NOrbitals { get; set; } + + [YamlMember(Alias = "n_electrons", ApplyNamingConventions = false)] + [JsonProperty(PropertyName = "n_electrons")] + public int NElectrons { get; set; } + + [YamlMember(Alias = "energy_offset", ApplyNamingConventions = false)] + [JsonProperty(PropertyName = "energy_offset")] + public SimpleQuantity EnergyOffset { get; set; } + + [YamlMember(Alias = "hamiltonian", ApplyNamingConventions = false)] + [JsonProperty(PropertyName = "hamiltonian")] + public HamiltonianData Hamiltonian { get; set; } + + [YamlMember(Alias = "initial_state_suggestions", ApplyNamingConventions = false)] + [JsonProperty(PropertyName = "initial_state_suggestions")] + public List? InitialStates { get; set; } + } + + public struct HamiltonianData + { + [YamlMember(Alias = "particle_hole_representation", ApplyNamingConventions = false)] + // TODO: Placeholder object for ParticleHoleRepresentation, which we do not + // yet support. + // NB: Array quantity no longer implicitly adds [], so the type declaration is different + // from in 0.1. + public ArrayQuantity? ParticleHoleRepresentation { get; set; } + + [YamlMember( + Alias = "one_electron_integrals", + ApplyNamingConventions = false, + // Don't allow subclasses here. + SerializeAs = typeof(ArrayQuantity<(long, long), double>) + )] + public ArrayQuantity<(long, long), double> OneElectronIntegrals { get; set; } + + [YamlMember(Alias = "two_electron_integrals", ApplyNamingConventions = false)] + public ArrayQuantityWithSymmetry<(long, long, long, long), double> TwoElectronIntegrals { get; set; } + + } + + // TODO: move out of v0.3. + // TODO: finish. + public struct StringEnum : IYamlConvertible + where T: struct, Enum + { + public T Value; + public StringEnum(T value) + { + Value = value; + } + private static ImmutableDictionary EnumValues; + + static StringEnum() + { + EnumValues = typeof(T).GetMembers() + .Select(m => new KeyValuePair( + m + .GetCustomAttributes(true) + .Select(ema => ema.Value) + .FirstOrDefault(), + m + )) + .Where(pa => !string.IsNullOrEmpty(pa.Key)) + .ToImmutableDictionary(pa => pa.Key, pa => Enum.Parse(pa.Value.Name)); + } + + public void Read(IParser parser, Type expectedType, ObjectDeserializer nestedObjectDeserializer) + { + var field = (string)nestedObjectDeserializer(typeof(string)); + Value = EnumValues[field]; + } + + public void Write(IEmitter emitter, ObjectSerializer nestedObjectSerializer) + { + var value = Value; + var name = EnumValues.Where(pair => pair.Value.Equals(value)).Single().Key; + nestedObjectSerializer(name); + } + + public static implicit operator T(StringEnum e) => e.Value; + public static implicit operator StringEnum(T e) => new StringEnum(e); + } + + public enum ArrayFormat + { + [EnumMember(Value = "sparse")] + Sparse + } + + public class ArrayQuantity : HasUnits + { + public struct Item : IYamlConvertible + { + [YamlMember(Alias = "key", ApplyNamingConventions = false)] + public TIndex Key { get; set; } + + [YamlMember(Alias = "value", ApplyNamingConventions = false)] + public TValue Value { get; set; } + + public void Read(IParser parser, Type expectedType, ObjectDeserializer nestedObjectDeserializer) + { + parser.Consume(); + var readKey = false; + var readValue = false; + while (!readKey || !readValue) + { + var name = nestedObjectDeserializer(typeof(string)); + switch (name) + { + case "key": + this.Key = ReadKey(parser, nestedObjectDeserializer); + readKey = true; + break; + + case "value": + this.Value = (TValue)nestedObjectDeserializer(typeof(TValue)); + readValue = true; + break; + }; + } + parser.Consume(); + } + + private TIndex ReadKey(IParser parser, ObjectDeserializer nestedObjectDeserializer) + { + if (typeof(TIndex).FullName.StartsWith("System.ValueTuple`")) + { + // TODO [perf]: Cache the create method. + var createMethod = typeof(ValueTuple).GetMethods( + BindingFlags.Static | BindingFlags.Public + ) + .Where(meth => meth.Name == "Create") + .Where(meth => meth.GetParameters().Length == typeof(TIndex).GenericTypeArguments.Length) + .Single(); + var args = new List(); + parser.Consume(); + foreach (var elementType in typeof(TIndex).GenericTypeArguments) + { + var element = nestedObjectDeserializer(elementType); + args.Add(element); + } + parser.Consume(); + return (TIndex)createMethod.MakeGenericMethod(typeof(TIndex).GenericTypeArguments).Invoke(null, args.ToArray()); + } + else return (TIndex)nestedObjectDeserializer(typeof(TIndex)); + } + + public void Write(IEmitter emitter, ObjectSerializer nestedObjectSerializer) + { + emitter.Emit(new MappingStart(null, null, true, MappingStyle.Flow)); + nestedObjectSerializer("key"); + if (typeof(TIndex).FullName.StartsWith("System.ValueTuple`")) + { + emitter.Emit(new SequenceStart(null, null, true, SequenceStyle.Flow)); + foreach (var (_, idx) in typeof(TIndex).GenericTypeArguments.Select((_, idx) => (_, idx))) + { + var field = typeof(TIndex).GetField($"Item{idx + 1}"); + nestedObjectSerializer(field.GetValue(Key)); + } + emitter.Emit(new SequenceEnd()); + } + else + { + nestedObjectSerializer(Key); + } + nestedObjectSerializer("value"); + nestedObjectSerializer(Value); + emitter.Emit(new MappingEnd()); + } + } + + [YamlMember(Alias = "format")] + public StringEnum Format { get; set; } + + [YamlMember(Alias = "values")] + public List Values { get; set; } + + [YamlMember(Alias = "index_convention")] + public OrbitalIntegral.Convention? IndexConvention { get; set; } = null; + } + + public enum PermutationSymmetry + { + [EnumMember(Value = "eightfold")] + Eightfold, + [EnumMember(Value = "fourfold")] + Fourfold, + [EnumMember(Value = "trivial")] + Trivial + } + + internal static OrbitalIntegral.PermutationSymmetry ParseOrbitalIntegralSymmetry(PermutationSymmetry symmetry) => + symmetry switch + { + PermutationSymmetry.Eightfold => OrbitalIntegral.PermutationSymmetry.Eightfold, + PermutationSymmetry.Fourfold => OrbitalIntegral.PermutationSymmetry.Fourfold, + PermutationSymmetry.Trivial => OrbitalIntegral.PermutationSymmetry.Trivial, + _ => throw new Exception($"Broombridge v0.3 permutation symmetry kind {symmetry} is not supported.") + }; + + public struct Symmetry + { + [YamlMember(Alias = "permutation")] + public StringEnum Permutation { get; set; } + } + + public class ArrayQuantityWithSymmetry : ArrayQuantity + { + [YamlMember(Alias = "symmetry")] + public Symmetry Symmetry { get; set; } + } + + /// + /// Builds Hamiltonian from Broombridge if data is available. + /// + internal static OrbitalIntegralHamiltonian ToOrbitalIntegralHamiltonian(ProblemDescription broombridge) + { + // Add the identity terms + var identityterm = broombridge.CoulombRepulsion.Value + broombridge.EnergyOffset.Value; + var hamiltonian = new OrbitalIntegralHamiltonian(); + var hamiltonianData = broombridge.Hamiltonian; + + // This will convert from Broombridge 1-indexing to 0-indexing. + hamiltonian.Add + (hamiltonianData.OneElectronIntegrals.Values + .Select(o => + new OrbitalIntegral( + o.Key.Select(k => (int)(k - 1)).ToArray(), + o.Value, + OrbitalIntegral.PermutationSymmetry.Eightfold, + OrbitalIntegral.Convention.Mulliken + ) + .ToCanonicalForm()) + .Distinct()); + + // This will convert from Broombridge 1-indexing to 0-indexing. + // This will convert to Dirac-indexing. + hamiltonian.Add + (hamiltonianData.TwoElectronIntegrals.Values + .Select(o => + new OrbitalIntegral( + o.Key.Select(k => (int)(k - 1)).ToArray(), + o.Value, + ParseOrbitalIntegralSymmetry(hamiltonianData.TwoElectronIntegrals.Symmetry.Permutation.Value), + OrbitalIntegral.Convention.Mulliken + ) + .ToCanonicalForm()) + .Distinct()); + + hamiltonian.Add(new OrbitalIntegral(), identityterm); + return hamiltonian; + } + + } + #endregion + +} diff --git a/Chemistry/src/DataModel/Serialization/Broombridge/BroombridgeSerializer.cs b/Chemistry/src/DataModel/Serialization/Broombridge/BroombridgeSerializer.cs index 7179c881410..6ff6f4687c9 100644 --- a/Chemistry/src/DataModel/Serialization/Broombridge/BroombridgeSerializer.cs +++ b/Chemistry/src/DataModel/Serialization/Broombridge/BroombridgeSerializer.cs @@ -11,6 +11,8 @@ using YamlDotNet.Core; using YamlDotNet.Serialization; using YamlDotNet.Core.Events; +using System.Reflection; +using System.Runtime.Serialization; namespace Microsoft.Quantum.Chemistry.Broombridge { @@ -19,7 +21,38 @@ namespace Microsoft.Quantum.Chemistry.Broombridge /// public enum VersionNumber { - NotRecognized = -1, v0_1 = 0, v0_2 = 1 + NotRecognized = -1, v0_1 = 0, v0_2 = 1, v0_3 + } + + internal class YamlStringEnumConverter : IYamlTypeConverter + { + public bool Accepts(Type type) + { + return type.IsEnum; + } + + public object ReadYaml(IParser parser, Type type) + { + var parsedEnum = parser.Consume(); + var serializableValues = type.GetMembers() + .Select(m => new KeyValuePair(m.GetCustomAttributes(true).Select(ema => ema.Value).FirstOrDefault(), m)) + .Where(pa => !string.IsNullOrEmpty(pa.Key)) + .ToDictionary(pa => pa.Key, pa => pa.Value); + + if (!serializableValues.ContainsKey(parsedEnum.Value)) + { + throw new YamlException(parsedEnum.Start, parsedEnum.End, $"Value '{parsedEnum.Value}' not found in enum '{type.Name}'"); + } + + return Enum.Parse(type, serializableValues[parsedEnum.Value].Name); + } + + public void WriteYaml(IEmitter emitter, object value, Type type) + { + var enumMember = type.GetMember(value.ToString()).FirstOrDefault(); + var yamlValue = enumMember?.GetCustomAttributes(true).Select(ema => ema.Value).FirstOrDefault() ?? value.ToString(); + emitter.Emit(new Scalar(yamlValue)); + } } public static class BroombridgeSerializer @@ -42,7 +75,7 @@ public static IEnumerable Deserialize(TextReader rea Metadata = problem.Metadata, NElectrons = problem.NElectrons, NOrbitals = problem.NOrbitals, - OrbitalIntegralHamiltonian = V0_2.ToOrbitalIntegralHamiltonian(problem), + OrbitalIntegralHamiltonian = V0_3.ToOrbitalIntegralHamiltonian(problem), ScfEnergy = problem.ScfEnergy?.FromBroombridgeV0_1(), ScfEnergyOffset = problem.ScfEnergyOffset?.FromBroombridgeV0_1() } @@ -51,25 +84,25 @@ public static IEnumerable Deserialize(TextReader rea public static void Serialize(TextWriter writer, IEnumerable problems) { - Serializers.SerializeBroombridgev0_2( - new Broombridge.V0_2.Data + Serializers.SerializeData( + new Broombridge.V0_3.Data { // TODO: fix additional properties by converting IEnumerable to // new problem collection class. See https://github.com/microsoft/QuantumLibraries/issues/287. Bibliography = null, Format = new V0_1.Format { - Version = "0.2" + Version = "0.3" }, Generator = new V0_1.Generator { Source = "qdk-chem", Version = typeof(BroombridgeSerializer).Assembly.GetName().Version.ToString() }, - Schema = V0_2.SchemaUrl, + Schema = V0_3.SchemaUrl, ProblemDescriptions = problems .Select( - problem => problem.ToBroombridgeV0_2() + problem => problem.ToBroombridgeV0_3() ) .ToList() }, @@ -97,7 +130,10 @@ public static class Deserializers ["broombridge-0.1.schema"] = VersionNumber.v0_1, // TODO: URL of 0.2 schema. ["0.2"] = VersionNumber.v0_2, - ["broombridge-0.2.schema"] = VersionNumber.v0_2 + ["broombridge-0.2.schema"] = VersionNumber.v0_2, + // TODO: Update actual schema for 0.3. + ["0.3"] = VersionNumber.v0_3, + ["broombridge-0.3.schema"] = VersionNumber.v0_3 }; @@ -122,8 +158,8 @@ public static VersionNumber GetVersionNumber(TextReader reader) var deserializer = new DeserializerBuilder().Build(); var data = deserializer.Deserialize>(reader); var schema = data["$schema"] as string; - VersionNumber versionNumber = VersionNumber.NotRecognized; - if(schema != null) + var versionNumber = VersionNumber.NotRecognized; + if (schema != null) { foreach (var kv in VersionNumberDict) { @@ -161,10 +197,14 @@ public static Data DeserializeBroombridge(TextReader reader) VersionNumber.v0_1 => DataStructures.Update( Deserialize(stringReader) ), - VersionNumber.v0_2 => Deserialize(stringReader), - _ => throw new System.InvalidOperationException( + VersionNumber.v0_2 => DataStructures.Update( + Deserialize(stringReader) + ), + VersionNumber.v0_3 => Deserialize(stringReader), + VersionNumber.NotRecognized => throw new System.InvalidOperationException( "Unrecognized Broombridge version number." - ) + ), + _ => throw new System.Exception($"Internal error occurred; version {versionNumber} is valid but was not deserialized.") } ); } @@ -189,6 +229,7 @@ public static Data DeserializeBroombridge(string filename) /// public static TData Deserialize(TextReader reader) => new DeserializerBuilder() + .WithTypeConverter(new YamlStringEnumConverter()) .Build() .Deserialize(reader); @@ -215,7 +256,7 @@ public static class Serializers /// /// Broombridge v0.2 data to be serialized. /// Name of the file to write serialized data to. - internal static void SerializeBroombridgev0_2(V0_2.Data data, string filename) + internal static void SerializeData(TData data, string filename) { using var writer = new StreamWriter(File.OpenWrite(filename)); var stringBuilder = new StringBuilder(); @@ -228,7 +269,7 @@ internal static void SerializeBroombridgev0_2(V0_2.Data data, string filename) /// /// Broombridge v0.2 data to be serialized. /// Text writer to write serialized Broombridge data to. - internal static void SerializeBroombridgev0_2(V0_2.Data data, TextWriter writer) + internal static void SerializeData(TData data, TextWriter writer) { var stringBuilder = new StringBuilder(); var serializer = diff --git a/Chemistry/src/DataModel/Serialization/Broombridge/BroombridgeVersionUpdater.cs b/Chemistry/src/DataModel/Serialization/Broombridge/BroombridgeVersionUpdater.cs index 3abc2515e77..b2fd29e8f93 100644 --- a/Chemistry/src/DataModel/Serialization/Broombridge/BroombridgeVersionUpdater.cs +++ b/Chemistry/src/DataModel/Serialization/Broombridge/BroombridgeVersionUpdater.cs @@ -16,26 +16,52 @@ namespace Microsoft.Quantum.Chemistry.Broombridge internal static partial class DataStructures { - + public static V0_3.Data Update(V0_2.Data input) => + new V0_3.Data + { + Bibliography = input.Bibliography, + Format = input.Format, + Generator = input.Generator, + Schema = V0_3.SchemaUrl, + ProblemDescriptions = input + .ProblemDescriptions + .Select(problem => new V0_3.ProblemDescription + { + BasisSet = problem.BasisSet, + CoulombRepulsion = problem.CoulombRepulsion, + EnergyOffset = problem.EnergyOffset, + FciEnergy = problem.FciEnergy, + Geometry = problem.Geometry, + InitialStates = problem.InitialStates, + Metadata = problem.Metadata, + NElectrons = problem.NElectrons, + NOrbitals = problem.NOrbitals, + ScfEnergy = problem.ScfEnergy, + ScfEnergyOffset = problem.ScfEnergyOffset, + Hamiltonian = problem.Hamiltonian.ToBroombridgeV0_3() + }) + .ToList() + }; + /// /// Converts v0.1 Broombridge to v0.2. /// /// Source Broombridge in v0.1 format. /// Converted Broombridge in v0.2 format. - public static V0_2.Data Update(V0_1.Data input) + public static V0_3.Data Update(V0_1.Data input) { - var output = new V0_2.Data() + var output = new V0_3.Data() { Schema = input.Schema, Format = input.Format, Generator = input.Generator, Bibliography = input.Bibliography, - ProblemDescriptions = new List() + ProblemDescriptions = new List() }; foreach (var integralSet in input.IntegralSets) { - var problemDescription = new V0_2.ProblemDescription() + var problemDescription = new V0_3.ProblemDescription() { Metadata = integralSet.Metadata, BasisSet = integralSet.BasisSet, @@ -47,7 +73,7 @@ public static V0_2.Data Update(V0_1.Data input) NOrbitals = integralSet.NOrbitals, NElectrons = integralSet.NElectrons, EnergyOffset = integralSet.EnergyOffset, - Hamiltonian = integralSet.Hamiltonian, + Hamiltonian = integralSet.Hamiltonian.ToBroombridgeV0_3(), InitialStates = new List() }; diff --git a/Chemistry/src/DataModel/Serialization/LegacyFormats/FciDump.cs b/Chemistry/src/DataModel/Serialization/LegacyFormats/FciDump.cs index 12f90f24d2c..c68a328c6d8 100644 --- a/Chemistry/src/DataModel/Serialization/LegacyFormats/FciDump.cs +++ b/Chemistry/src/DataModel/Serialization/LegacyFormats/FciDump.cs @@ -79,7 +79,7 @@ public static IEnumerable Deserialize(TextReader rea .SelectMaybe( row => row.Item2.Length % 2 == 0 ? new OrbitalIntegral( - row.Item2, row.Item1, OrbitalIntegral.Convention.Mulliken + row.Item2, row.Item1, OrbitalIntegral.PermutationSymmetry.Eightfold, OrbitalIntegral.Convention.Mulliken ).ToCanonicalForm() : null ) diff --git a/Chemistry/src/Jupyter/ChemistryEncodeMagic.cs b/Chemistry/src/Jupyter/ChemistryEncodeMagic.cs index 55d15bf2297..126f1f3dbce 100644 --- a/Chemistry/src/Jupyter/ChemistryEncodeMagic.cs +++ b/Chemistry/src/Jupyter/ChemistryEncodeMagic.cs @@ -43,7 +43,7 @@ public Task Run(string input, IChannel channel) // We target a qubit quantum computer, which requires a Pauli representation of the fermion Hamiltonian. // A number of mappings from fermions to qubits are possible. Let us choose the Jordan-Wigner encoding. - PauliHamiltonian pauliHamiltonian = args.Hamiltonian.ToPauliHamiltonian(QubitEncoding.JordanWigner); + var pauliHamiltonian = args.Hamiltonian.ToPauliHamiltonian(QubitEncoding.JordanWigner); // We now convert this Hamiltonian and a selected state to a format that than be passed onto the QSharp component // of the library that implements quantum simulation algorithms. diff --git a/Chemistry/src/Jupyter/FermionHamiltonianMagic.cs b/Chemistry/src/Jupyter/FermionHamiltonianMagic.cs index a7aa229b094..e874cc0ba19 100644 --- a/Chemistry/src/Jupyter/FermionHamiltonianMagic.cs +++ b/Chemistry/src/Jupyter/FermionHamiltonianMagic.cs @@ -46,7 +46,7 @@ public class Arguments /// A Broombridge ProblemDescription to load the FermionHamiltonian from. /// [JsonProperty(PropertyName = "problem_description")] - public V0_2.ProblemDescription ProblemDescription { get; set; } + public V0_3.ProblemDescription ProblemDescription { get; set; } /// /// The IndexConvention to use to generate the Hamiltonian from the ProblemDescription. diff --git a/Chemistry/src/Jupyter/WavefunctionMagic.cs b/Chemistry/src/Jupyter/WavefunctionMagic.cs index 7586c9f096c..0b04b9c96f3 100644 --- a/Chemistry/src/Jupyter/WavefunctionMagic.cs +++ b/Chemistry/src/Jupyter/WavefunctionMagic.cs @@ -40,7 +40,7 @@ public class Arguments /// A Broombridge ProblemDescription to load the FermionHamiltonian from. /// [JsonProperty(PropertyName = "problem_description")] - public V0_2.ProblemDescription ProblemDescription { get; set; } + public V0_3.ProblemDescription ProblemDescription { get; set; } /// /// The IndexConvention to use to generate the Hamiltonian from the ProblemDescription. diff --git a/Chemistry/src/Tools/ExportJW.cs b/Chemistry/src/Tools/ExportJW.cs new file mode 100644 index 00000000000..040ea81500c --- /dev/null +++ b/Chemistry/src/Tools/ExportJW.cs @@ -0,0 +1,163 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +#nullable enable + +using System.Collections.Generic; +using System.Linq; +using System.CommandLine; +using System.IO; +using System; + +using Microsoft.Quantum.Chemistry.Broombridge; +using Microsoft.Quantum.Chemistry.OrbitalIntegrals; +using Microsoft.Quantum.Chemistry.Fermion; +using Microsoft.Quantum.Chemistry.QSharpFormat; +using System.Text.Json; +using System.Text.Json.Serialization; +using Microsoft.Quantum.Simulation.Core; + +namespace Microsoft.Quantum.Chemistry.Tools +{ + + public static class ExportJW + { + public static Command CreateCommand() => + new Command("export-jw") + { + new Argument( + "path", + "Input data to be loaded, or - to load from stdin." + ), + new Option( + "--format", + "Format to use in loading problem description data." + ), + new Option( + "--out", + "Path to write output to. Data will be written to stdout by default." + ), + new Option( + "--flatten", + "If true, flattens resulting JSON (often easier for use in " + + "native code)." + ) + } + .WithDescription( + "Exports a JSON representation of the Jordan–Wigner transformation of " + + "the fermionic Hamiltonian for a particular electronic structure problem." + ) + .WithHandler( + (path, from, @out, flatten) => + { + using var reader = + path.Name == "-" + ? System.Console.In + : File.OpenText(path.FullName); + using var writer = + @out == null + ? System.Console.Out + : new StreamWriter(File.OpenWrite(@out.FullName)); + ExportJwData(reader, from, writer, flatten: flatten); + } + ); + + public static void ExportJwData( + TextReader reader, SerializationFormat from, + TextWriter writer, + IndexConvention indexConvention = IndexConvention.UpDown, + bool flatten = true + ) + { + var data = Load(reader, from).ToList(); + if (data.Count != 1) + { + System.Console.Error.WriteLine($"Expected a single problem description, but got a list of {data.Count}."); + } + var problem = data.Single(); + + var fermionHamiltonian = problem + .OrbitalIntegralHamiltonian + .ToFermionHamiltonian(indexConvention); + var jwHamiltonian = fermionHamiltonian + .ToPauliHamiltonian(Paulis.QubitEncoding.JordanWigner) + .ToQSharpFormat(); + var wavefunction = ( + (problem.InitialStates?.Count ?? 0) == 0 + ? fermionHamiltonian.CreateHartreeFockState(problem.NElectrons) + : problem + .InitialStates + .First() + .Value + .ToIndexing(indexConvention) + ) + .ToQSharpFormat(); + + // var encoded = JsonSerializer.Serialize( + // QSharpFormat.Convert.ToQSharpFormat(jwHamiltonian, wavefunction), + // options + // ); + var qsData = QSharpFormat.Convert.ToQSharpFormat(jwHamiltonian, wavefunction); + var encoded = flatten + ? JsonSerializer.Serialize(Flatten(qsData)) + : JsonSerializer.Serialize(qsData); + + writer.Write(encoded); + writer.Close(); + } + + // TODO: Move into common class. + internal static IEnumerable Load(TextReader reader, SerializationFormat from) => + (from switch + { + SerializationFormat.Broombridge => + BroombridgeSerializer.Deserialize(reader), + SerializationFormat.LiQuiD => + LiQuiDSerializer.Deserialize(reader), + SerializationFormat.FciDump => + FciDumpSerializer.Deserialize(reader), + _ => throw new ArgumentException($"Invalid format {from}.") + }) + .ToList(); + + internal static object[] Flatten(JordanWigner.JordanWignerEncodingData data) => + new object[] + { + data.Item1, + Flatten(data.Item2), + new object[] + { + data.Item3.Item1, + data.Item3.Item2.Select(s => Flatten(s)).ToArray() + }, + data.Item4 + }; + + internal static object[] Flatten(JordanWigner.JWOptimizedHTerms data) => + new object[] + { + data.Item1.Select(s => Flatten(s)).ToArray(), + data.Item2.Select(s => Flatten(s)).ToArray(), + data.Item3.Select(s => Flatten(s)).ToArray(), + data.Item4.Select(s => Flatten(s)).ToArray(), + }; + + internal static object[] Flatten(HTerm term) => + new object[] + { + term.Item1.ToArray(), + term.Item2.ToArray() + }; + + internal static object[] Flatten(JordanWigner.JordanWignerInputState data) => + new object[] + { + new object[] + { + data.Item1.Item1, + data.Item1.Item2 + }, + data.Item2.ToArray() + }; + } + +} diff --git a/Chemistry/src/Tools/Normalize.cs b/Chemistry/src/Tools/Normalize.cs index 9ada49653e5..7c287082744 100644 --- a/Chemistry/src/Tools/Normalize.cs +++ b/Chemistry/src/Tools/Normalize.cs @@ -56,6 +56,6 @@ public static Command CreateCommand() => Convert.ConvertProblemDescription(reader, format, writer, format); } ); - } + } } diff --git a/Chemistry/src/Tools/Program.cs b/Chemistry/src/Tools/Program.cs index e71910b2248..ee0c3306704 100644 --- a/Chemistry/src/Tools/Program.cs +++ b/Chemistry/src/Tools/Program.cs @@ -23,7 +23,8 @@ public static Command CreateRootCommand() => new RootCommand { Convert.CreateCommand(), - Normalize.CreateCommand() + Normalize.CreateCommand(), + ExportJW.CreateCommand() } .WithDescription("Tools for working with quantum chemistry data."); diff --git a/Chemistry/tests/DataModelTests/FermionTermTests/FermionHamiltonianTests.cs b/Chemistry/tests/DataModelTests/FermionTermTests/FermionHamiltonianTests.cs index f95658a87be..1f8a0beda83 100644 --- a/Chemistry/tests/DataModelTests/FermionTermTests/FermionHamiltonianTests.cs +++ b/Chemistry/tests/DataModelTests/FermionTermTests/FermionHamiltonianTests.cs @@ -148,55 +148,55 @@ public void BuildFromOrbitalIntegrals( public static IEnumerable OrbitalsData => new List { - new object[] { new OrbitalIntegral(new[] {0,0 },1.0, OrbitalIntegral.Convention.Dirac), TermType.Fermion.PP, + new object[] { new OrbitalIntegral(new[] {0,0 },1.0, OrbitalIntegral.PermutationSymmetry.Eightfold, OrbitalIntegral.Convention.Dirac), TermType.Fermion.PP, new (int, int[], double)[] { (1, new[] { 0, 0 }, 1.0 ), (1, new[] { 1, 1 }, 1.0 )}}, - new object[] { new OrbitalIntegral(new[] {0,1 },1.0, OrbitalIntegral.Convention.Dirac), TermType.Fermion.PQ, + new object[] { new OrbitalIntegral(new[] {0,1 },1.0, OrbitalIntegral.PermutationSymmetry.Eightfold, OrbitalIntegral.Convention.Dirac), TermType.Fermion.PQ, new (int, int[], double)[] { (2, new[] { 0, 1 }, 2.0 ), (2, new[] { 2, 3 }, 2.0 )}}, - new object[] { new OrbitalIntegral(new[] {0,1,1,0 },1.0, OrbitalIntegral.Convention.Dirac), TermType.Fermion.PQQP, + new object[] { new OrbitalIntegral(new[] {0,1,1,0 },1.0, OrbitalIntegral.PermutationSymmetry.Eightfold, OrbitalIntegral.Convention.Dirac), TermType.Fermion.PQQP, new (int, int[], double)[] { (2, new[] { 0, 1, 1, 0 }, 1.0 ), (2, new[] { 2, 3, 3, 2 }, 1.0 ), (2, new[] { 0, 3, 3, 0 }, 1.0 ), (2, new[] { 1, 2, 2, 1 }, 1.0 )}}, - new object[] { new OrbitalIntegral(new[] {0,1,0,1 },1.0, OrbitalIntegral.Convention.Dirac), TermType.Fermion.PQQP, + new object[] { new OrbitalIntegral(new[] {0,1,0,1 },1.0, OrbitalIntegral.PermutationSymmetry.Eightfold, OrbitalIntegral.Convention.Dirac), TermType.Fermion.PQQP, new (int, int[], double)[] { (2, new[] { 0, 1, 1, 0 }, -1.0 ), (2, new[] { 2, 3, 3, 2 }, -1.0 ), } }, - new object[] { new OrbitalIntegral(new[] {0,1,0,1 },1.0, OrbitalIntegral.Convention.Dirac), TermType.Fermion.PQRS, + new object[] { new OrbitalIntegral(new[] {0,1,0,1 },1.0, OrbitalIntegral.PermutationSymmetry.Eightfold, OrbitalIntegral.Convention.Dirac), TermType.Fermion.PQRS, new (int, int[], double)[] { (2, new[] { 0, 3, 2, 1 }, 2.0 ), (2, new[] { 0, 2, 3, 1 }, 2.0 ) } }, - new object[] { new OrbitalIntegral(new[] {0,1,0,0 },1.0, OrbitalIntegral.Convention.Dirac), TermType.Fermion.PQQR, + new object[] { new OrbitalIntegral(new[] {0,1,0,0 },1.0, OrbitalIntegral.PermutationSymmetry.Eightfold, OrbitalIntegral.Convention.Dirac), TermType.Fermion.PQQR, new (int, int[], double)[] { (2, new[] { 0, 2, 2, 1 }, 2.0 ), (2, new[] { 0, 2, 3, 0 }, 2.0 ), } }, - new object[] {new OrbitalIntegral(new[] {0,0,1,2 },1.0, OrbitalIntegral.Convention.Dirac), TermType.Fermion.PQQR, + new object[] {new OrbitalIntegral(new[] {0,0,1,2 },1.0, OrbitalIntegral.PermutationSymmetry.Eightfold, OrbitalIntegral.Convention.Dirac), TermType.Fermion.PQQR, new (int, int[], double)[] { (3, new[] { 0, 1, 2, 0 }, -2.0 ), (3, new[] { 3, 4, 5, 3 }, -2.0 ), } }, - new object[] { new OrbitalIntegral(new[] {0,0,1,2 },1.0, OrbitalIntegral.Convention.Dirac), TermType.Fermion.PQRS, + new object[] { new OrbitalIntegral(new[] {0,0,1,2 },1.0, OrbitalIntegral.PermutationSymmetry.Eightfold, OrbitalIntegral.Convention.Dirac), TermType.Fermion.PQRS, new (int, int[], double)[] { (3, new[] { 0, 3, 4, 2 }, 2.0 ), (3, new[] { 0, 3, 5, 1 }, 2.0 ), (3, new[] { 0, 4, 3, 2 }, 2.0 ), (3, new[] { 0, 5, 3, 1 }, 2.0 ), } }, - new object[] { new OrbitalIntegral(new[] {0,1,2,0 },1.0, OrbitalIntegral.Convention.Dirac), TermType.Fermion.PQQR, + new object[] { new OrbitalIntegral(new[] {0,1,2,0 },1.0, OrbitalIntegral.PermutationSymmetry.Eightfold, OrbitalIntegral.Convention.Dirac), TermType.Fermion.PQQR, new (int, int[], double)[] { (3, new[] { 0, 1, 2, 0 }, 2.0 ), (3, new[] { 1, 3, 3, 2 }, 2.0 ), (3, new[] { 0, 4, 5, 0 }, 2.0 ), (3, new[] { 3, 4, 5, 3 }, 2.0 ), } }, - new object[] { new OrbitalIntegral(new[] {0,1,2,3 },1.0, OrbitalIntegral.Convention.Dirac), TermType.Fermion.PQRS, + new object[] { new OrbitalIntegral(new[] {0,1,2,3 },1.0, OrbitalIntegral.PermutationSymmetry.Eightfold, OrbitalIntegral.Convention.Dirac), TermType.Fermion.PQRS, new (int, int[], double)[] { (4, new[] { 0, 5, 6, 3 }, 2.0 ), (4, new[] { 0, 6, 5, 3 }, 2.0 ), @@ -207,7 +207,7 @@ public void BuildFromOrbitalIntegrals( (4, new[] { 0, 2, 3, 1 }, -2.0 ), (4, new[] { 1, 7, 4, 2 }, 2.0 ), } }, - new object[] { new OrbitalIntegral(new[] {3,1,0,2 },1.0, OrbitalIntegral.Convention.Dirac), TermType.Fermion.PQRS, + new object[] { new OrbitalIntegral(new[] {3,1,0,2 },1.0, OrbitalIntegral.PermutationSymmetry.Eightfold, OrbitalIntegral.Convention.Dirac), TermType.Fermion.PQRS, new (int, int[], double)[] { (4, new[] { 2, 4, 5, 3 }, 2.0 ), (4, new[] { 0, 6, 7, 1 }, 2.0 ), diff --git a/Chemistry/tests/DataModelTests/SerializationTests/BroombridgeTests.cs b/Chemistry/tests/DataModelTests/SerializationTests/BroombridgeTests.cs index f6459a5f8d1..f81cb53cdf5 100644 --- a/Chemistry/tests/DataModelTests/SerializationTests/BroombridgeTests.cs +++ b/Chemistry/tests/DataModelTests/SerializationTests/BroombridgeTests.cs @@ -127,8 +127,10 @@ public void JsonEncoding() var json = JsonConvert.SerializeObject(original); File.WriteAllText("original.json", json); - var serialized = JsonConvert.DeserializeObject(json); - File.WriteAllText("serialized.json", JsonConvert.SerializeObject(serialized)); + // NB: Even though we loaded a 0.2 file, the export step above + // normalizes to 0.3. + var serialized = JsonConvert.DeserializeObject(json); + File.WriteAllText("serialized.json", JsonConvert.SerializeObject(serialized)); Assert.Equal(original.Format, serialized.Format); Assert.Equal(original.Bibliography.Count, serialized.Bibliography.Count); diff --git a/Chemistry/tests/DataModelTests/SerializationTests/LiQuiDTests.cs b/Chemistry/tests/DataModelTests/SerializationTests/LiQuiDTests.cs index 9a6ef795e53..81f166c3970 100644 --- a/Chemistry/tests/DataModelTests/SerializationTests/LiQuiDTests.cs +++ b/Chemistry/tests/DataModelTests/SerializationTests/LiQuiDTests.cs @@ -19,6 +19,7 @@ namespace Microsoft.Quantum.Chemistry.Tests { using static TermType.OrbitalIntegral; using static OrbitalIntegral.Convention; + using static OrbitalIntegral.PermutationSymmetry; public class LoadFromLiquidTests @@ -44,17 +45,17 @@ public void LoadFromLiquidTest(string line, TermType.OrbitalIntegral termType, O public static IEnumerable LiquidOrbitalsData => new List { - new object[] { "0,0=1.0", OneBody, new OrbitalIntegral(new[] {0,0},1.0, Dirac) }, - new object[] { "0,1=1.0", OneBody, new OrbitalIntegral(new[] {0,1},1.0, Dirac) }, - new object[] { "0,1,1,0=1.0", TwoBody, new OrbitalIntegral(new[] {0,1,1,0},1.0, Dirac) }, - new object[] { "0,1,0,1=1.0", TwoBody, new OrbitalIntegral(new[] {0,1,0,1},1.0, Dirac) }, - new object[] { "0,1,0,1=1.0", TwoBody, new OrbitalIntegral(new[] {0,1,0,1},1.0, Dirac) }, - new object[] { "0,1,0,0=1.0", TwoBody, new OrbitalIntegral(new[] {0,1,0,0},1.0, Dirac) }, - new object[] { "0,0,1,2=1.0", TwoBody, new OrbitalIntegral(new[] {0,0,1,2},1.0, Dirac) }, - new object[] { "0,0,1,2=1.0", TwoBody, new OrbitalIntegral(new[] {0,0,1,2},1.0, Dirac) }, - new object[] { "0,1,2,0=1.0", TwoBody, new OrbitalIntegral(new[] {0,1,2,0},1.0, Dirac) }, - new object[] { "0,1,2,3=1.0", TwoBody, new OrbitalIntegral(new[] {0,1,2,3},1.0, Dirac) }, - new object[] { "3,1,0,2=1.0", TwoBody, new OrbitalIntegral(new[] {3,1,0,2},1.0, Dirac) } + new object[] { "0,0=1.0", OneBody, new OrbitalIntegral(new[] {0,0},1.0, Eightfold, Dirac) }, + new object[] { "0,1=1.0", OneBody, new OrbitalIntegral(new[] {0,1},1.0, Eightfold, Dirac) }, + new object[] { "0,1,1,0=1.0", TwoBody, new OrbitalIntegral(new[] {0,1,1,0},1.0, Eightfold, Dirac) }, + new object[] { "0,1,0,1=1.0", TwoBody, new OrbitalIntegral(new[] {0,1,0,1},1.0, Eightfold, Dirac) }, + new object[] { "0,1,0,1=1.0", TwoBody, new OrbitalIntegral(new[] {0,1,0,1},1.0, Eightfold, Dirac) }, + new object[] { "0,1,0,0=1.0", TwoBody, new OrbitalIntegral(new[] {0,1,0,0},1.0, Eightfold, Dirac) }, + new object[] { "0,0,1,2=1.0", TwoBody, new OrbitalIntegral(new[] {0,0,1,2},1.0, Eightfold, Dirac) }, + new object[] { "0,0,1,2=1.0", TwoBody, new OrbitalIntegral(new[] {0,0,1,2},1.0, Eightfold, Dirac) }, + new object[] { "0,1,2,0=1.0", TwoBody, new OrbitalIntegral(new[] {0,1,2,0},1.0, Eightfold, Dirac) }, + new object[] { "0,1,2,3=1.0", TwoBody, new OrbitalIntegral(new[] {0,1,2,3},1.0, Eightfold, Dirac) }, + new object[] { "3,1,0,2=1.0", TwoBody, new OrbitalIntegral(new[] {3,1,0,2},1.0, Eightfold, Dirac) } }; diff --git a/Chemistry/tests/JupyterTests/BroombridgeMagicTests.cs b/Chemistry/tests/JupyterTests/BroombridgeMagicTests.cs index 79edefa1925..cb74a217c75 100644 --- a/Chemistry/tests/JupyterTests/BroombridgeMagicTests.cs +++ b/Chemistry/tests/JupyterTests/BroombridgeMagicTests.cs @@ -39,19 +39,19 @@ public async void LoadInvalidFile() } - [Fact] - public async void LoadBroombridgeFile() - { - var filename = "broombridge_v0.2.yaml"; - var magic = new BroombridgeMagic(); - var channel = new MockChannel(); - - var result = await magic.Run(filename, channel); - var broombridge = (V0_2.Data)result.Output; - Assert.Equal(ExecuteStatus.Ok, result.Status); - Assert.Equal("0.2", broombridge.Format.Version); - Assert.Equal(3, broombridge.Bibliography.Count); - Assert.Single(broombridge.ProblemDescriptions); - } + // [Fact] + // public async void LoadBroombridgeFile() + // { + // var filename = "broombridge_v0.2.yaml"; + // var magic = new BroombridgeMagic(); + // var channel = new MockChannel(); + + // var result = await magic.Run(filename, channel); + // var broombridge = (V0_2.Data)result.Output; + // Assert.Equal(ExecuteStatus.Ok, result.Status); + // Assert.Equal("0.2", broombridge.Format.Version); + // Assert.Equal(3, broombridge.Bibliography.Count); + // Assert.Single(broombridge.ProblemDescriptions); + // } } } \ No newline at end of file diff --git a/Chemistry/tests/SamplesTests/DocsSecondQuantization.cs b/Chemistry/tests/SamplesTests/DocsSecondQuantization.cs index dd9aa2b062b..12e86fac133 100644 --- a/Chemistry/tests/SamplesTests/DocsSecondQuantization.cs +++ b/Chemistry/tests/SamplesTests/DocsSecondQuantization.cs @@ -1,6 +1,6 @@  -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. // This test ensures that any chemistry library syntax changes @@ -297,6 +297,27 @@ static void MakeAnotherOrbitalIntegral() } + [Fact] + static void MakeOrbitalIntegralFourFoldSymmetry() + { + // We create a `OrbitalIntegral` object to store a two-electron molecular + // orbital integral data with four-fold symmetry. + var twoElectronIntegral = new OrbitalIntegral(new[] { 0, 1, 2, 3 }, 0.123,OrbitalIntegral.PermutationSymmetry.Fourfold); + + // This enumerates all two-electron integrals with the same coefficient -- + // an array of equivalent `OrbitalIntegral` instances is generated. In + // this case, there are 4 elements. + var twoElectronIntegrals = twoElectronIntegral.EnumerateOrbitalSymmetries(); + + Assert.Equal(4, twoElectronIntegrals.Count()); + + // These orbital indices should all be equivalent + foreach (OrbitalIntegral integral in twoElectronIntegrals) + { + Assert.Equal(integral.ToCanonicalForm().OrbitalIndices, twoElectronIntegral.ToCanonicalForm().OrbitalIndices); + } + } + [Fact] static void MakeHamiltonian() {