From 13ef180e2b0fdcda034cc421a2c7e3ce052f3311 Mon Sep 17 00:00:00 2001 From: GreshmaShaji Date: Thu, 6 Feb 2025 16:23:05 +0100 Subject: [PATCH 01/23] Adjusted files with PEP8 compliance --- .settings/module_db.json | 400 +++++++++--------- README.md | 8 +- docs/conf.py | 4 +- docs/tutorial.rst | 12 +- src/Installer.py | 16 +- ...nchmarkManager.py => benchmark_manager.py} | 8 +- ...BenchmarkRecord.py => benchmark_record.py} | 2 +- src/{ConfigManager.py => config_manager.py} | 4 +- src/demo/instruction_demo.py | 4 +- src/main.py | 12 +- src/modules/applications/Application.py | 2 +- src/modules/applications/Mapping.py | 2 +- .../applications/optimization/ACL/ACL.py | 8 +- .../optimization/ACL/mappings/ISING.py | 8 +- .../optimization/ACL/mappings/QUBO.py | 4 +- .../applications/optimization/MIS/MIS.py | 10 +- .../optimization/MIS/mappings/QIRO.py | 4 +- .../{NeutralAtom.py => neutral_atom.py} | 4 +- .../applications/optimization/Optimization.py | 2 +- .../applications/optimization/PVC/PVC.py | 14 +- .../optimization/PVC/mappings/ISING.py | 8 +- .../optimization/PVC/mappings/QUBO.py | 4 +- .../applications/optimization/SAT/SAT.py | 16 +- .../optimization/SAT/mappings/ChoiISING.py | 8 +- .../optimization/SAT/mappings/ChoiQUBO.py | 4 +- .../optimization/SAT/mappings/DinneenISING.py | 8 +- .../optimization/SAT/mappings/DinneenQUBO.py | 4 +- .../optimization/SAT/mappings/Direct.py | 6 +- .../optimization/SAT/mappings/QubovertQUBO.py | 4 +- .../applications/optimization/SCP/SCP.py | 6 +- .../optimization/SCP/mappings/qubovertQUBO.py | 4 +- .../applications/optimization/TSP/TSP.py | 14 +- .../optimization/TSP/mappings/ISING.py | 10 +- .../optimization/TSP/mappings/QUBO.py | 4 +- src/modules/applications/qml/Circuit.py | 2 +- src/modules/applications/qml/QML.py | 2 +- .../qml/{DataHandler.py => data_handler.py} | 0 ...tCardinality.py => circuit_cardinality.py} | 10 +- .../{CircuitCopula.py => circuit_copula.py} | 10 +- ...uitGenerative.py => circuit_generative.py} | 4 +- ...CircuitStandard.py => circuit_standard.py} | 10 +- .../{ContinuousData.py => continuous_data.py} | 6 +- ...nerative.py => data_handler_generative.py} | 4 +- .../{DiscreteData.py => discrete_data.py} | 6 +- ...tiveModeling.py => generative_modeling.py} | 6 +- ...kend.py => custom_qiskit_noisy_backend.py} | 6 +- ...aryGenerative.py => library_generative.py} | 4 +- ...braryPennylane.py => library_pennylane.py} | 8 +- .../{LibraryQiskit.py => library_qiskit.py} | 12 +- ...kend.py => preset_qiskit_noisy_backend.py} | 6 +- ...alization.py => metrics_generalization.py} | 0 .../generative_modeling/training/Inference.py | 2 +- .../qml/generative_modeling/training/QCBM.py | 2 +- .../qml/generative_modeling/training/QGAN.py | 2 +- ...ngGenerative.py => training_generative.py} | 4 +- .../transformations/PIT.py | 4 +- .../transformations/Transformation.py | 2 +- .../transformations/{MinMax.py => min_max.py} | 6 +- src/modules/devices/Device.py | 2 +- src/modules/devices/Local.py | 2 +- src/modules/devices/braket/Braket.py | 2 +- src/modules/devices/braket/Ionq.py | 2 +- src/modules/devices/braket/OQC.py | 2 +- src/modules/devices/braket/Rigetti.py | 2 +- src/modules/devices/braket/SV1.py | 2 +- src/modules/devices/braket/TN1.py | 2 +- .../{LocalSimulator.py => local_simulator.py} | 2 +- .../{HelperClass.py => helper_class.py} | 2 +- src/modules/devices/pulser/Pulser.py | 2 +- ...mDevice.py => mock_neutral_atom_device.py} | 2 +- .../{QrispSimulator.py => qrisp_simulator.py} | 4 +- ...pler.py => simulated_annealing_sampler.py} | 2 +- src/modules/solvers/Annealer.py | 6 +- src/modules/solvers/QAOA.py | 14 +- src/modules/solvers/Solver.py | 2 +- .../{ClassicalSAT.py => classical_sat.py} | 6 +- ...lassicalPVC.py => greedy_classical_pvc.py} | 6 +- ...lassicalTSP.py => greedy_classical_tsp.py} | 6 +- .../{MIPsolverACL.py => mip_solver_acl.py} | 6 +- ...{NeutralAtomMIS.py => neutral_atom_mis.py} | 6 +- .../{PennylaneQAOA.py => pennylane_qaoa.py} | 26 +- .../solvers/{QiskitQAOA.py => qiskit_qaoa.py} | 6 +- .../solvers/{QrispQIRO.py => qrisp_qiro.py} | 4 +- ...lassicalPVC.py => random_classical_pvc.py} | 6 +- ...lassicalSAT.py => random_classical_sat.py} | 6 +- ...lassicalTSP.py => random_classical_tsp.py} | 6 +- ...PVC.py => reverse_greedy_classical_pvc.py} | 6 +- ...TSP.py => reverse_greedy_classical_tsp.py} | 6 +- src/quark2_adapter/adapters.py | 18 +- ...veModeling.yml => generative_modeling.yml} | 0 .../optimization/ACL/mappings/test_ISING.py | 2 +- .../optimization/ACL/mappings/test_QUBO.py | 2 +- .../applications/optimization/ACL/test_ACL.py | 2 +- ...st_NeutralAtom.py => test_neutral_atom.py} | 2 +- .../applications/optimization/MIS/test_MIS.py | 2 +- .../optimization/PVC/mappings/test_ISING.py | 4 +- .../optimization/PVC/mappings/test_QUBO.py | 2 +- .../applications/optimization/PVC/test_PVC.py | 2 +- .../SAT/mappings/test_ChoiISING.py | 2 +- .../SAT/mappings/test_ChoiQUBO.py | 2 +- .../SAT/mappings/test_DinneenISING.py | 2 +- .../SAT/mappings/test_DinneenQUBO.py | 2 +- .../optimization/SAT/mappings/test_Direct.py | 2 +- .../SAT/mappings/test_QubovertQUBO.py | 2 +- .../applications/optimization/SAT/test_SAT.py | 2 +- .../SCP/mappings/test_qubovertQUBO.py | 2 +- .../applications/optimization/SCP/test_SCP.py | 2 +- .../optimization/TSP/mappings/test_ISING.py | 2 +- .../optimization/TSP/mappings/test_QUBO.py | 2 +- .../applications/optimization/TSP/test_TSP.py | 2 +- ...inality.py => test_circuit_cardinality.py} | 2 +- ...ircuitCopula.py => test_circuit_copula.py} | 2 +- ...itStandard.py => test_circuit_standard.py} | 2 +- ...tinuousData.py => test_continuous_data.py} | 6 +- ..._DiscreteData.py => test_discrete_data.py} | 4 +- ...py => test_custom_qiskit_noisy_backend.py} | 24 +- ...Pennylane.py => test_library_pennylane.py} | 8 +- ...ibraryQiskit.py => test_library_qiskit.py} | 14 +- ...py => test_preset_qiskit_noisy_backend.py} | 10 +- ...tion.py => test_metrics_generalization.py} | 0 .../training/test_Inference.py | 2 +- .../generative_modeling/training/test_QCBM.py | 4 +- .../generative_modeling/training/test_QGAN.py | 2 +- .../transformation/test_MinMax.py | 6 +- .../transformation/test_PIT.py | 4 +- tests/test_BenchmarkManager.py | 2 +- tests/test_ConfigManager.py | 4 +- 127 files changed, 525 insertions(+), 525 deletions(-) rename src/{BenchmarkManager.py => benchmark_manager.py} (99%) rename src/{BenchmarkRecord.py => benchmark_record.py} (99%) rename src/{ConfigManager.py => config_manager.py} (99%) rename src/modules/applications/optimization/MIS/mappings/{NeutralAtom.py => neutral_atom.py} (94%) rename src/modules/applications/qml/{DataHandler.py => data_handler.py} (100%) rename src/modules/applications/qml/generative_modeling/circuits/{CircuitCardinality.py => circuit_cardinality.py} (90%) rename src/modules/applications/qml/generative_modeling/circuits/{CircuitCopula.py => circuit_copula.py} (90%) rename src/modules/applications/qml/generative_modeling/circuits/{CircuitGenerative.py => circuit_generative.py} (97%) rename src/modules/applications/qml/generative_modeling/circuits/{CircuitStandard.py => circuit_standard.py} (89%) rename src/modules/applications/qml/generative_modeling/data/data_handler/{ContinuousData.py => continuous_data.py} (98%) rename src/modules/applications/qml/generative_modeling/data/data_handler/{DataHandlerGenerative.py => data_handler_generative.py} (98%) rename src/modules/applications/qml/generative_modeling/data/data_handler/{DiscreteData.py => discrete_data.py} (96%) rename src/modules/applications/qml/generative_modeling/{GenerativeModeling.py => generative_modeling.py} (97%) rename src/modules/applications/qml/generative_modeling/mappings/{CustomQiskitNoisyBackend.py => custom_qiskit_noisy_backend.py} (98%) rename src/modules/applications/qml/generative_modeling/mappings/{LibraryGenerative.py => library_generative.py} (95%) rename src/modules/applications/qml/generative_modeling/mappings/{LibraryPennylane.py => library_pennylane.py} (96%) rename src/modules/applications/qml/generative_modeling/mappings/{LibraryQiskit.py => library_qiskit.py} (96%) rename src/modules/applications/qml/generative_modeling/mappings/{PresetQiskitNoisyBackend.py => preset_qiskit_noisy_backend.py} (98%) rename src/modules/applications/qml/generative_modeling/metrics/{MetricsGeneralization.py => metrics_generalization.py} (100%) rename src/modules/applications/qml/generative_modeling/training/{TrainingGenerative.py => training_generative.py} (98%) rename src/modules/applications/qml/generative_modeling/transformations/{MinMax.py => min_max.py} (97%) rename src/modules/devices/braket/{LocalSimulator.py => local_simulator.py} (97%) rename src/modules/devices/{HelperClass.py => helper_class.py} (97%) rename src/modules/devices/pulser/{MockNeutralAtomDevice.py => mock_neutral_atom_device.py} (98%) rename src/modules/devices/qrisp_simulator/{QrispSimulator.py => qrisp_simulator.py} (97%) rename src/modules/devices/{SimulatedAnnealingSampler.py => simulated_annealing_sampler.py} (97%) rename src/modules/solvers/{ClassicalSAT.py => classical_sat.py} (95%) rename src/modules/solvers/{GreedyClassicalPVC.py => greedy_classical_pvc.py} (96%) rename src/modules/solvers/{GreedyClassicalTSP.py => greedy_classical_tsp.py} (95%) rename src/modules/solvers/{MIPsolverACL.py => mip_solver_acl.py} (96%) rename src/modules/solvers/{NeutralAtomMIS.py => neutral_atom_mis.py} (97%) rename src/modules/solvers/{PennylaneQAOA.py => pennylane_qaoa.py} (95%) rename src/modules/solvers/{QiskitQAOA.py => qiskit_qaoa.py} (98%) rename src/modules/solvers/{QrispQIRO.py => qrisp_qiro.py} (97%) rename src/modules/solvers/{RandomClassicalPVC.py => random_classical_pvc.py} (96%) rename src/modules/solvers/{RandomClassicalSAT.py => random_classical_sat.py} (95%) rename src/modules/solvers/{RandomClassicalTSP.py => random_classical_tsp.py} (95%) rename src/modules/solvers/{ReverseGreedyClassicalPVC.py => reverse_greedy_classical_pvc.py} (96%) rename src/modules/solvers/{ReverseGreedyClassicalTSP.py => reverse_greedy_classical_tsp.py} (96%) rename tests/configs/valid/{GenerativeModeling.yml => generative_modeling.yml} (100%) rename tests/modules/applications/optimization/MIS/mappings/{test_NeutralAtom.py => test_neutral_atom.py} (95%) rename tests/modules/applications/qml/generative_modeling/circuits/{test_CircuitCardinality.py => test_circuit_cardinality.py} (97%) rename tests/modules/applications/qml/generative_modeling/circuits/{test_CircuitCopula.py => test_circuit_copula.py} (97%) rename tests/modules/applications/qml/generative_modeling/circuits/{test_CircuitStandard.py => test_circuit_standard.py} (97%) rename tests/modules/applications/qml/generative_modeling/data/data_handler/{test_ContinuousData.py => test_continuous_data.py} (97%) rename tests/modules/applications/qml/generative_modeling/data/data_handler/{test_DiscreteData.py => test_discrete_data.py} (96%) rename tests/modules/applications/qml/generative_modeling/mappings/{test_CustomQiskitNoisyBackend.py => test_custom_qiskit_noisy_backend.py} (92%) rename tests/modules/applications/qml/generative_modeling/mappings/{test_LibraryPennylane.py => test_library_pennylane.py} (93%) rename tests/modules/applications/qml/generative_modeling/mappings/{test_LibraryQiskit.py => test_library_qiskit.py} (96%) rename tests/modules/applications/qml/generative_modeling/mappings/{test_PresetQiskitNoisyBackend.py => test_preset_qiskit_noisy_backend.py} (97%) rename tests/modules/applications/qml/generative_modeling/metrics/{test_MetricsGeneralization.py => test_metrics_generalization.py} (100%) diff --git a/.settings/module_db.json b/.settings/module_db.json index 5c449d8a..186d5427 100644 --- a/.settings/module_db.json +++ b/.settings/module_db.json @@ -1,18 +1,18 @@ { - "build_number": 18, - "build_date": "22-01-2025 13:49:05", - "git_revision_number": "c817270e589cd85c29fa8644cd14eeafde2215f1", + "build_number": 19, + "build_date": "06-02-2025 14:08:22", + "git_revision_number": "4771e55f1ea6c3b87ee020004a1ff21630824081", "modules": [ { "name": "PVC", "class": "PVC", - "module": "modules.applications.optimization.PVC.PVC", + "module": "modules.applications.optimization.pvc.pvc", "submodules": [ { "name": "Ising", "class": "Ising", "args": {}, - "module": "modules.applications.optimization.PVC.mappings.ISING", + "module": "modules.applications.optimization.pvc.mappings.ising", "requirements": [ { "name": "networkx", @@ -36,7 +36,7 @@ "name": "QAOA", "class": "QAOA", "args": {}, - "module": "modules.solvers.QAOA", + "module": "modules.solvers.qaoa", "requirements": [ { "name": "amazon-braket-sdk", @@ -58,7 +58,7 @@ "args": { "device_name": "LocalSimulator" }, - "module": "modules.devices.braket.LocalSimulator", + "module": "modules.devices.braket.local_simulator", "requirements": [ { "name": "amazon-braket-sdk", @@ -82,7 +82,7 @@ "device_name": "SV1", "arn": "arn:aws:braket:::device/quantum-simulator/amazon/sv1" }, - "module": "modules.devices.braket.SV1", + "module": "modules.devices.braket.sv1", "requirements": [ { "name": "amazon-braket-sdk", @@ -106,7 +106,7 @@ "device_name": "TN1", "arn": "arn:aws:braket:::device/quantum-simulator/amazon/tn1" }, - "module": "modules.devices.braket.TN1", + "module": "modules.devices.braket.tn1", "requirements": [ { "name": "amazon-braket-sdk", @@ -130,7 +130,7 @@ "device_name": "ionQ", "arn": "arn:aws:braket:us-east-1::device/qpu/ionq/Harmony" }, - "module": "modules.devices.braket.Ionq", + "module": "modules.devices.braket.ionq", "requirements": [ { "name": "amazon-braket-sdk", @@ -154,7 +154,7 @@ "device_name": "Rigetti Aspen-9", "arn": "arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-3" }, - "module": "modules.devices.braket.Rigetti", + "module": "modules.devices.braket.rigetti", "requirements": [ { "name": "amazon-braket-sdk", @@ -177,7 +177,7 @@ "name": "PennylaneQAOA", "class": "PennylaneQAOA", "args": {}, - "module": "modules.solvers.PennylaneQAOA", + "module": "modules.solvers.pennylane_qaoa", "requirements": [ { "name": "pennylane", @@ -204,7 +204,7 @@ "device_name": "SV1", "arn": "arn:aws:braket:::device/quantum-simulator/amazon/sv1" }, - "module": "modules.devices.braket.SV1", + "module": "modules.devices.braket.sv1", "requirements": [ { "name": "amazon-braket-sdk", @@ -228,7 +228,7 @@ "device_name": "TN1", "arn": "arn:aws:braket:::device/quantum-simulator/amazon/tn1" }, - "module": "modules.devices.braket.TN1", + "module": "modules.devices.braket.tn1", "requirements": [ { "name": "amazon-braket-sdk", @@ -252,7 +252,7 @@ "device_name": "ionq", "arn": "arn:aws:braket:us-east-1::device/qpu/ionq/Harmony" }, - "module": "modules.devices.braket.Ionq", + "module": "modules.devices.braket.ionq", "requirements": [ { "name": "amazon-braket-sdk", @@ -276,7 +276,7 @@ "device_name": "Rigetti", "arn": "arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-3" }, - "module": "modules.devices.braket.Rigetti", + "module": "modules.devices.braket.rigetti", "requirements": [ { "name": "amazon-braket-sdk", @@ -300,7 +300,7 @@ "device_name": "OQC", "arn": "arn:aws:braket:eu-west-2::device/qpu/oqc/Lucy" }, - "module": "modules.devices.braket.OQC", + "module": "modules.devices.braket.oqc", "requirements": [ { "name": "amazon-braket-sdk", @@ -323,7 +323,7 @@ "args": { "device_name": "braket.local.qubit" }, - "module": "modules.devices.HelperClass", + "module": "modules.devices.helper_class", "requirements": [], "submodules": [] }, @@ -333,7 +333,7 @@ "args": { "device_name": "default.qubit" }, - "module": "modules.devices.HelperClass", + "module": "modules.devices.helper_class", "requirements": [], "submodules": [] }, @@ -343,7 +343,7 @@ "args": { "device_name": "default.qubit.autograd" }, - "module": "modules.devices.HelperClass", + "module": "modules.devices.helper_class", "requirements": [], "submodules": [] }, @@ -353,7 +353,7 @@ "args": { "device_name": "qulacs.simulator" }, - "module": "modules.devices.HelperClass", + "module": "modules.devices.helper_class", "requirements": [], "submodules": [] }, @@ -363,7 +363,7 @@ "args": { "device_name": "lightning.gpu" }, - "module": "modules.devices.HelperClass", + "module": "modules.devices.helper_class", "requirements": [], "submodules": [] }, @@ -373,7 +373,7 @@ "args": { "device_name": "lightning.qubit" }, - "module": "modules.devices.HelperClass", + "module": "modules.devices.helper_class", "requirements": [], "submodules": [] } @@ -385,7 +385,7 @@ "name": "QUBO", "class": "QUBO", "args": {}, - "module": "modules.applications.optimization.PVC.mappings.QUBO", + "module": "modules.applications.optimization.pvc.mappings.qubo", "requirements": [ { "name": "networkx", @@ -397,14 +397,14 @@ "name": "Annealer", "class": "Annealer", "args": {}, - "module": "modules.solvers.Annealer", + "module": "modules.solvers.annealer", "requirements": [], "submodules": [ { "name": "Simulated Annealer", "class": "SimulatedAnnealingSampler", "args": {}, - "module": "modules.devices.SimulatedAnnealingSampler", + "module": "modules.devices.simulated_annealing_sampler", "requirements": [ { "name": "dwave-samplers", @@ -421,7 +421,7 @@ "name": "GreedyClassicalPVC", "class": "GreedyClassicalPVC", "args": {}, - "module": "modules.solvers.GreedyClassicalPVC", + "module": "modules.solvers.greedy_classical_pvc", "requirements": [ { "name": "networkx", @@ -433,7 +433,7 @@ "name": "Local", "class": "Local", "args": {}, - "module": "modules.devices.Local", + "module": "modules.devices.local", "requirements": [], "submodules": [] } @@ -443,7 +443,7 @@ "name": "ReverseGreedyClassicalPVC", "class": "ReverseGreedyClassicalPVC", "args": {}, - "module": "modules.solvers.ReverseGreedyClassicalPVC", + "module": "modules.solvers.reverse_greedy_classical_pvc", "requirements": [ { "name": "networkx", @@ -455,7 +455,7 @@ "name": "Local", "class": "Local", "args": {}, - "module": "modules.devices.Local", + "module": "modules.devices.local", "requirements": [], "submodules": [] } @@ -465,7 +465,7 @@ "name": "RandomPVC", "class": "RandomPVC", "args": {}, - "module": "modules.solvers.RandomClassicalPVC", + "module": "modules.solvers.random_classical_pvc", "requirements": [ { "name": "networkx", @@ -477,7 +477,7 @@ "name": "Local", "class": "Local", "args": {}, - "module": "modules.devices.Local", + "module": "modules.devices.local", "requirements": [], "submodules": [] } @@ -498,13 +498,13 @@ { "name": "SAT", "class": "SAT", - "module": "modules.applications.optimization.SAT.SAT", + "module": "modules.applications.optimization.sat.sat", "submodules": [ { "name": "QubovertQUBO", "class": "QubovertQUBO", "args": {}, - "module": "modules.applications.optimization.SAT.mappings.QubovertQUBO", + "module": "modules.applications.optimization.sat.mappings.qubovertqubo", "requirements": [ { "name": "nnf", @@ -520,14 +520,14 @@ "name": "Annealer", "class": "Annealer", "args": {}, - "module": "modules.solvers.Annealer", + "module": "modules.solvers.annealer", "requirements": [], "submodules": [ { "name": "Simulated Annealer", "class": "SimulatedAnnealingSampler", "args": {}, - "module": "modules.devices.SimulatedAnnealingSampler", + "module": "modules.devices.simulated_annealing_sampler", "requirements": [ { "name": "dwave-samplers", @@ -544,7 +544,7 @@ "name": "Direct", "class": "Direct", "args": {}, - "module": "modules.applications.optimization.SAT.mappings.Direct", + "module": "modules.applications.optimization.sat.mappings.direct", "requirements": [ { "name": "nnf", @@ -560,7 +560,7 @@ "name": "ClassicalSAT", "class": "ClassicalSAT", "args": {}, - "module": "modules.solvers.ClassicalSAT", + "module": "modules.solvers.classical_sat", "requirements": [ { "name": "python-sat", @@ -572,7 +572,7 @@ "name": "Local", "class": "Local", "args": {}, - "module": "modules.devices.Local", + "module": "modules.devices.local", "requirements": [], "submodules": [] } @@ -582,7 +582,7 @@ "name": "RandomSAT", "class": "RandomSAT", "args": {}, - "module": "modules.solvers.RandomClassicalSAT", + "module": "modules.solvers.random_classical_sat", "requirements": [ { "name": "python-sat", @@ -598,7 +598,7 @@ "name": "Local", "class": "Local", "args": {}, - "module": "modules.devices.Local", + "module": "modules.devices.local", "requirements": [], "submodules": [] } @@ -610,7 +610,7 @@ "name": "ChoiQUBO", "class": "ChoiQUBO", "args": {}, - "module": "modules.applications.optimization.SAT.mappings.ChoiQUBO", + "module": "modules.applications.optimization.sat.mappings.choiqubo", "requirements": [ { "name": "nnf", @@ -622,14 +622,14 @@ "name": "Annealer", "class": "Annealer", "args": {}, - "module": "modules.solvers.Annealer", + "module": "modules.solvers.annealer", "requirements": [], "submodules": [ { "name": "Simulated Annealer", "class": "SimulatedAnnealingSampler", "args": {}, - "module": "modules.devices.SimulatedAnnealingSampler", + "module": "modules.devices.simulated_annealing_sampler", "requirements": [ { "name": "dwave-samplers", @@ -646,7 +646,7 @@ "name": "DinneenQUBO", "class": "DinneenQUBO", "args": {}, - "module": "modules.applications.optimization.SAT.mappings.DinneenQUBO", + "module": "modules.applications.optimization.sat.mappings.dinneenqubo", "requirements": [ { "name": "nnf", @@ -658,14 +658,14 @@ "name": "Annealer", "class": "Annealer", "args": {}, - "module": "modules.solvers.Annealer", + "module": "modules.solvers.annealer", "requirements": [], "submodules": [ { "name": "Simulated Annealer", "class": "SimulatedAnnealingSampler", "args": {}, - "module": "modules.devices.SimulatedAnnealingSampler", + "module": "modules.devices.simulated_annealing_sampler", "requirements": [ { "name": "dwave-samplers", @@ -682,7 +682,7 @@ "name": "ChoiIsing", "class": "ChoiIsing", "args": {}, - "module": "modules.applications.optimization.SAT.mappings.ChoiISING", + "module": "modules.applications.optimization.sat.mappings.choiIsing", "requirements": [ { "name": "numpy", @@ -702,7 +702,7 @@ "name": "QAOA", "class": "QAOA", "args": {}, - "module": "modules.solvers.QAOA", + "module": "modules.solvers.qaoa", "requirements": [ { "name": "amazon-braket-sdk", @@ -724,7 +724,7 @@ "args": { "device_name": "LocalSimulator" }, - "module": "modules.devices.braket.LocalSimulator", + "module": "modules.devices.braket.local_simulator", "requirements": [ { "name": "amazon-braket-sdk", @@ -748,7 +748,7 @@ "device_name": "SV1", "arn": "arn:aws:braket:::device/quantum-simulator/amazon/sv1" }, - "module": "modules.devices.braket.SV1", + "module": "modules.devices.braket.sv1", "requirements": [ { "name": "amazon-braket-sdk", @@ -772,7 +772,7 @@ "device_name": "TN1", "arn": "arn:aws:braket:::device/quantum-simulator/amazon/tn1" }, - "module": "modules.devices.braket.TN1", + "module": "modules.devices.braket.tn1", "requirements": [ { "name": "amazon-braket-sdk", @@ -796,7 +796,7 @@ "device_name": "ionQ", "arn": "arn:aws:braket:us-east-1::device/qpu/ionq/Harmony" }, - "module": "modules.devices.braket.Ionq", + "module": "modules.devices.braket.ionq", "requirements": [ { "name": "amazon-braket-sdk", @@ -820,7 +820,7 @@ "device_name": "Rigetti Aspen-9", "arn": "arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-3" }, - "module": "modules.devices.braket.Rigetti", + "module": "modules.devices.braket.rigetti", "requirements": [ { "name": "amazon-braket-sdk", @@ -843,7 +843,7 @@ "name": "PennylaneQAOA", "class": "PennylaneQAOA", "args": {}, - "module": "modules.solvers.PennylaneQAOA", + "module": "modules.solvers.pennylane_qaoa", "requirements": [ { "name": "pennylane", @@ -870,7 +870,7 @@ "device_name": "SV1", "arn": "arn:aws:braket:::device/quantum-simulator/amazon/sv1" }, - "module": "modules.devices.braket.SV1", + "module": "modules.devices.braket.sv1", "requirements": [ { "name": "amazon-braket-sdk", @@ -894,7 +894,7 @@ "device_name": "TN1", "arn": "arn:aws:braket:::device/quantum-simulator/amazon/tn1" }, - "module": "modules.devices.braket.TN1", + "module": "modules.devices.braket.tn1", "requirements": [ { "name": "amazon-braket-sdk", @@ -918,7 +918,7 @@ "device_name": "ionq", "arn": "arn:aws:braket:us-east-1::device/qpu/ionq/Harmony" }, - "module": "modules.devices.braket.Ionq", + "module": "modules.devices.braket.ionq", "requirements": [ { "name": "amazon-braket-sdk", @@ -942,7 +942,7 @@ "device_name": "Rigetti", "arn": "arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-3" }, - "module": "modules.devices.braket.Rigetti", + "module": "modules.devices.braket.rigetti", "requirements": [ { "name": "amazon-braket-sdk", @@ -966,7 +966,7 @@ "device_name": "OQC", "arn": "arn:aws:braket:eu-west-2::device/qpu/oqc/Lucy" }, - "module": "modules.devices.braket.OQC", + "module": "modules.devices.braket.oqc", "requirements": [ { "name": "amazon-braket-sdk", @@ -989,7 +989,7 @@ "args": { "device_name": "braket.local.qubit" }, - "module": "modules.devices.HelperClass", + "module": "modules.devices.helper_class", "requirements": [], "submodules": [] }, @@ -999,7 +999,7 @@ "args": { "device_name": "default.qubit" }, - "module": "modules.devices.HelperClass", + "module": "modules.devices.helper_class", "requirements": [], "submodules": [] }, @@ -1009,7 +1009,7 @@ "args": { "device_name": "default.qubit.autograd" }, - "module": "modules.devices.HelperClass", + "module": "modules.devices.helper_class", "requirements": [], "submodules": [] }, @@ -1019,7 +1019,7 @@ "args": { "device_name": "qulacs.simulator" }, - "module": "modules.devices.HelperClass", + "module": "modules.devices.helper_class", "requirements": [], "submodules": [] }, @@ -1029,7 +1029,7 @@ "args": { "device_name": "lightning.gpu" }, - "module": "modules.devices.HelperClass", + "module": "modules.devices.helper_class", "requirements": [], "submodules": [] }, @@ -1039,7 +1039,7 @@ "args": { "device_name": "lightning.qubit" }, - "module": "modules.devices.HelperClass", + "module": "modules.devices.helper_class", "requirements": [], "submodules": [] } @@ -1051,7 +1051,7 @@ "name": "DinneenIsing", "class": "DinneenIsing", "args": {}, - "module": "modules.applications.optimization.SAT.mappings.DinneenISING", + "module": "modules.applications.optimization.sat.mappings.dinneenising", "requirements": [ { "name": "nnf", @@ -1075,7 +1075,7 @@ "name": "QAOA", "class": "QAOA", "args": {}, - "module": "modules.solvers.QAOA", + "module": "modules.solvers.qaoa", "requirements": [ { "name": "amazon-braket-sdk", @@ -1097,7 +1097,7 @@ "args": { "device_name": "LocalSimulator" }, - "module": "modules.devices.braket.LocalSimulator", + "module": "modules.devices.braket.local_simulator", "requirements": [ { "name": "amazon-braket-sdk", @@ -1121,7 +1121,7 @@ "device_name": "SV1", "arn": "arn:aws:braket:::device/quantum-simulator/amazon/sv1" }, - "module": "modules.devices.braket.SV1", + "module": "modules.devices.braket.sv1", "requirements": [ { "name": "amazon-braket-sdk", @@ -1145,7 +1145,7 @@ "device_name": "TN1", "arn": "arn:aws:braket:::device/quantum-simulator/amazon/tn1" }, - "module": "modules.devices.braket.TN1", + "module": "modules.devices.braket.tn1", "requirements": [ { "name": "amazon-braket-sdk", @@ -1169,7 +1169,7 @@ "device_name": "ionQ", "arn": "arn:aws:braket:us-east-1::device/qpu/ionq/Harmony" }, - "module": "modules.devices.braket.Ionq", + "module": "modules.devices.braket.ionq", "requirements": [ { "name": "amazon-braket-sdk", @@ -1193,7 +1193,7 @@ "device_name": "Rigetti Aspen-9", "arn": "arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-3" }, - "module": "modules.devices.braket.Rigetti", + "module": "modules.devices.braket.rigetti", "requirements": [ { "name": "amazon-braket-sdk", @@ -1216,7 +1216,7 @@ "name": "PennylaneQAOA", "class": "PennylaneQAOA", "args": {}, - "module": "modules.solvers.PennylaneQAOA", + "module": "modules.solvers.pennylane_qaoa", "requirements": [ { "name": "pennylane", @@ -1243,7 +1243,7 @@ "device_name": "SV1", "arn": "arn:aws:braket:::device/quantum-simulator/amazon/sv1" }, - "module": "modules.devices.braket.SV1", + "module": "modules.devices.braket.sv1", "requirements": [ { "name": "amazon-braket-sdk", @@ -1267,7 +1267,7 @@ "device_name": "TN1", "arn": "arn:aws:braket:::device/quantum-simulator/amazon/tn1" }, - "module": "modules.devices.braket.TN1", + "module": "modules.devices.braket.tn1", "requirements": [ { "name": "amazon-braket-sdk", @@ -1291,7 +1291,7 @@ "device_name": "ionq", "arn": "arn:aws:braket:us-east-1::device/qpu/ionq/Harmony" }, - "module": "modules.devices.braket.Ionq", + "module": "modules.devices.braket.ionq", "requirements": [ { "name": "amazon-braket-sdk", @@ -1315,7 +1315,7 @@ "device_name": "Rigetti", "arn": "arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-3" }, - "module": "modules.devices.braket.Rigetti", + "module": "modules.devices.braket.rigetti", "requirements": [ { "name": "amazon-braket-sdk", @@ -1339,7 +1339,7 @@ "device_name": "OQC", "arn": "arn:aws:braket:eu-west-2::device/qpu/oqc/Lucy" }, - "module": "modules.devices.braket.OQC", + "module": "modules.devices.braket.oqc", "requirements": [ { "name": "amazon-braket-sdk", @@ -1362,7 +1362,7 @@ "args": { "device_name": "braket.local.qubit" }, - "module": "modules.devices.HelperClass", + "module": "modules.devices.helper_class", "requirements": [], "submodules": [] }, @@ -1372,7 +1372,7 @@ "args": { "device_name": "default.qubit" }, - "module": "modules.devices.HelperClass", + "module": "modules.devices.helper_class", "requirements": [], "submodules": [] }, @@ -1382,7 +1382,7 @@ "args": { "device_name": "default.qubit.autograd" }, - "module": "modules.devices.HelperClass", + "module": "modules.devices.helper_class", "requirements": [], "submodules": [] }, @@ -1392,7 +1392,7 @@ "args": { "device_name": "qulacs.simulator" }, - "module": "modules.devices.HelperClass", + "module": "modules.devices.helper_class", "requirements": [], "submodules": [] }, @@ -1402,7 +1402,7 @@ "args": { "device_name": "lightning.gpu" }, - "module": "modules.devices.HelperClass", + "module": "modules.devices.helper_class", "requirements": [], "submodules": [] }, @@ -1412,7 +1412,7 @@ "args": { "device_name": "lightning.qubit" }, - "module": "modules.devices.HelperClass", + "module": "modules.devices.helper_class", "requirements": [], "submodules": [] } @@ -1435,13 +1435,13 @@ { "name": "TSP", "class": "TSP", - "module": "modules.applications.optimization.TSP.TSP", + "module": "modules.applications.optimization.tsp.tsp", "submodules": [ { "name": "Ising", "class": "Ising", "args": {}, - "module": "modules.applications.optimization.TSP.mappings.ISING", + "module": "modules.applications.optimization.tsp.mappings.ising", "requirements": [ { "name": "networkx", @@ -1477,7 +1477,7 @@ "name": "QAOA", "class": "QAOA", "args": {}, - "module": "modules.solvers.QAOA", + "module": "modules.solvers.qaoa", "requirements": [ { "name": "amazon-braket-sdk", @@ -1499,7 +1499,7 @@ "args": { "device_name": "LocalSimulator" }, - "module": "modules.devices.braket.LocalSimulator", + "module": "modules.devices.braket.local_simulator", "requirements": [ { "name": "amazon-braket-sdk", @@ -1523,7 +1523,7 @@ "device_name": "SV1", "arn": "arn:aws:braket:::device/quantum-simulator/amazon/sv1" }, - "module": "modules.devices.braket.SV1", + "module": "modules.devices.braket.sv1", "requirements": [ { "name": "amazon-braket-sdk", @@ -1547,7 +1547,7 @@ "device_name": "TN1", "arn": "arn:aws:braket:::device/quantum-simulator/amazon/tn1" }, - "module": "modules.devices.braket.TN1", + "module": "modules.devices.braket.tn1", "requirements": [ { "name": "amazon-braket-sdk", @@ -1571,7 +1571,7 @@ "device_name": "ionQ", "arn": "arn:aws:braket:us-east-1::device/qpu/ionq/Harmony" }, - "module": "modules.devices.braket.Ionq", + "module": "modules.devices.braket.ionq", "requirements": [ { "name": "amazon-braket-sdk", @@ -1595,7 +1595,7 @@ "device_name": "Rigetti Aspen-9", "arn": "arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-3" }, - "module": "modules.devices.braket.Rigetti", + "module": "modules.devices.braket.rigetti", "requirements": [ { "name": "amazon-braket-sdk", @@ -1618,7 +1618,7 @@ "name": "PennylaneQAOA", "class": "PennylaneQAOA", "args": {}, - "module": "modules.solvers.PennylaneQAOA", + "module": "modules.solvers.pennylane_qaoa", "requirements": [ { "name": "pennylane", @@ -1645,7 +1645,7 @@ "device_name": "SV1", "arn": "arn:aws:braket:::device/quantum-simulator/amazon/sv1" }, - "module": "modules.devices.braket.SV1", + "module": "modules.devices.braket.sv1", "requirements": [ { "name": "amazon-braket-sdk", @@ -1669,7 +1669,7 @@ "device_name": "TN1", "arn": "arn:aws:braket:::device/quantum-simulator/amazon/tn1" }, - "module": "modules.devices.braket.TN1", + "module": "modules.devices.braket.tn1", "requirements": [ { "name": "amazon-braket-sdk", @@ -1693,7 +1693,7 @@ "device_name": "ionq", "arn": "arn:aws:braket:us-east-1::device/qpu/ionq/Harmony" }, - "module": "modules.devices.braket.Ionq", + "module": "modules.devices.braket.ionq", "requirements": [ { "name": "amazon-braket-sdk", @@ -1717,7 +1717,7 @@ "device_name": "Rigetti", "arn": "arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-3" }, - "module": "modules.devices.braket.Rigetti", + "module": "modules.devices.braket.rigetti", "requirements": [ { "name": "amazon-braket-sdk", @@ -1741,7 +1741,7 @@ "device_name": "OQC", "arn": "arn:aws:braket:eu-west-2::device/qpu/oqc/Lucy" }, - "module": "modules.devices.braket.OQC", + "module": "modules.devices.braket.oqc", "requirements": [ { "name": "amazon-braket-sdk", @@ -1764,7 +1764,7 @@ "args": { "device_name": "braket.local.qubit" }, - "module": "modules.devices.HelperClass", + "module": "modules.devices.helper_class", "requirements": [], "submodules": [] }, @@ -1774,7 +1774,7 @@ "args": { "device_name": "default.qubit" }, - "module": "modules.devices.HelperClass", + "module": "modules.devices.helper_class", "requirements": [], "submodules": [] }, @@ -1784,7 +1784,7 @@ "args": { "device_name": "default.qubit.autograd" }, - "module": "modules.devices.HelperClass", + "module": "modules.devices.helper_class", "requirements": [], "submodules": [] }, @@ -1794,7 +1794,7 @@ "args": { "device_name": "qulacs.simulator" }, - "module": "modules.devices.HelperClass", + "module": "modules.devices.helper_class", "requirements": [], "submodules": [] }, @@ -1804,7 +1804,7 @@ "args": { "device_name": "lightning.gpu" }, - "module": "modules.devices.HelperClass", + "module": "modules.devices.helper_class", "requirements": [], "submodules": [] }, @@ -1814,7 +1814,7 @@ "args": { "device_name": "lightning.qubit" }, - "module": "modules.devices.HelperClass", + "module": "modules.devices.helper_class", "requirements": [], "submodules": [] } @@ -1824,7 +1824,7 @@ "name": "QiskitQAOA", "class": "QiskitQAOA", "args": {}, - "module": "modules.solvers.QiskitQAOA", + "module": "modules.solvers.qiskit_qaoa", "requirements": [ { "name": "qiskit", @@ -1850,7 +1850,7 @@ "args": { "device_name": "qasm_simulator" }, - "module": "modules.devices.HelperClass", + "module": "modules.devices.helper_class", "requirements": [], "submodules": [] }, @@ -1860,7 +1860,7 @@ "args": { "device_name": "qasm_simulator_gpu" }, - "module": "modules.devices.HelperClass", + "module": "modules.devices.helper_class", "requirements": [], "submodules": [] } @@ -1872,7 +1872,7 @@ "name": "QUBO", "class": "QUBO", "args": {}, - "module": "modules.applications.optimization.TSP.mappings.QUBO", + "module": "modules.applications.optimization.tsp.mappings.qubo", "requirements": [ { "name": "networkx", @@ -1888,14 +1888,14 @@ "name": "Annealer", "class": "Annealer", "args": {}, - "module": "modules.solvers.Annealer", + "module": "modules.solvers.annealer", "requirements": [], "submodules": [ { "name": "Simulated Annealer", "class": "SimulatedAnnealingSampler", "args": {}, - "module": "modules.devices.SimulatedAnnealingSampler", + "module": "modules.devices.simulated_annealing_sampler", "requirements": [ { "name": "dwave-samplers", @@ -1912,7 +1912,7 @@ "name": "GreedyClassicalTSP", "class": "GreedyClassicalTSP", "args": {}, - "module": "modules.solvers.GreedyClassicalTSP", + "module": "modules.solvers.greedy_classical_tsp", "requirements": [ { "name": "networkx", @@ -1924,7 +1924,7 @@ "name": "Local", "class": "Local", "args": {}, - "module": "modules.devices.Local", + "module": "modules.devices.local", "requirements": [], "submodules": [] } @@ -1934,7 +1934,7 @@ "name": "ReverseGreedyClassicalTSP", "class": "ReverseGreedyClassicalTSP", "args": {}, - "module": "modules.solvers.ReverseGreedyClassicalTSP", + "module": "modules.solvers.reverse_greedy_classical_tsp", "requirements": [ { "name": "networkx", @@ -1946,7 +1946,7 @@ "name": "Local", "class": "Local", "args": {}, - "module": "modules.devices.Local", + "module": "modules.devices.local", "requirements": [], "submodules": [] } @@ -1956,7 +1956,7 @@ "name": "RandomTSP", "class": "RandomTSP", "args": {}, - "module": "modules.solvers.RandomClassicalTSP", + "module": "modules.solvers.random_classical_tsp", "requirements": [ { "name": "networkx", @@ -1968,7 +1968,7 @@ "name": "Local", "class": "Local", "args": {}, - "module": "modules.devices.Local", + "module": "modules.devices.local", "requirements": [], "submodules": [] } @@ -1989,13 +1989,13 @@ { "name": "ACL", "class": "ACL", - "module": "modules.applications.optimization.ACL.ACL", + "module": "modules.applications.optimization.acl.acl", "submodules": [ { "name": "MIPsolverACL", "class": "MIPaclp", "args": {}, - "module": "modules.solvers.MIPsolverACL", + "module": "modules.solvers.mip_solver_acl", "requirements": [ { "name": "pulp", @@ -2007,7 +2007,7 @@ "name": "Local", "class": "Local", "args": {}, - "module": "modules.devices.Local", + "module": "modules.devices.local", "requirements": [], "submodules": [] } @@ -2017,7 +2017,7 @@ "name": "QUBO", "class": "Qubo", "args": {}, - "module": "modules.applications.optimization.ACL.mappings.QUBO", + "module": "modules.applications.optimization.acl.mappings.qubo", "requirements": [ { "name": "numpy", @@ -2033,14 +2033,14 @@ "name": "Annealer", "class": "Annealer", "args": {}, - "module": "modules.solvers.Annealer", + "module": "modules.solvers.annealer", "requirements": [], "submodules": [ { "name": "Simulated Annealer", "class": "SimulatedAnnealingSampler", "args": {}, - "module": "modules.devices.SimulatedAnnealingSampler", + "module": "modules.devices.simulated_annealing_sampler", "requirements": [ { "name": "dwave-samplers", @@ -2076,13 +2076,13 @@ { "name": "MIS", "class": "MIS", - "module": "modules.applications.optimization.MIS.MIS", + "module": "modules.applications.optimization.mis.mis", "submodules": [ { "name": "QIRO", "class": "QIRO", "args": {}, - "module": "modules.applications.optimization.MIS.mappings.QIRO", + "module": "modules.applications.optimization.mis.mappings.qiro", "requirements": [ { "name": "qrisp", @@ -2094,7 +2094,7 @@ "name": "QrispQIRO", "class": "QIROSolver", "args": {}, - "module": "modules.solvers.QrispQIRO", + "module": "modules.solvers.qrisp_qiro", "requirements": [ { "name": "qrisp", @@ -2106,7 +2106,7 @@ "name": "qrisp_simulator", "class": "QrispSimulator", "args": {}, - "module": "modules.devices.qrisp_simulator.QrispSimulator", + "module": "modules.devices.qrisp_simulator.qrisp_simulator", "requirements": [ { "name": "qrisp", @@ -2123,7 +2123,7 @@ "name": "NeutralAtom", "class": "NeutralAtom", "args": {}, - "module": "modules.applications.optimization.MIS.mappings.NeutralAtom", + "module": "modules.applications.optimization.mis.mappings.neutralatom", "requirements": [ { "name": "pulser", @@ -2135,7 +2135,7 @@ "name": "NeutralAtomMIS", "class": "NeutralAtomMIS", "args": {}, - "module": "modules.solvers.NeutralAtomMIS", + "module": "modules.solvers.neutral_atom_mis", "requirements": [ { "name": "pulser", @@ -2147,7 +2147,7 @@ "name": "MockNeutralAtomDevice", "class": "MockNeutralAtomDevice", "args": {}, - "module": "modules.devices.pulser.MockNeutralAtomDevice", + "module": "modules.devices.pulser.mock_neutral_atom_device", "requirements": [ { "name": "pulser", @@ -2166,13 +2166,13 @@ { "name": "SCP", "class": "SCP", - "module": "modules.applications.optimization.SCP.SCP", + "module": "modules.applications.optimization.scp.scp", "submodules": [ { "name": "qubovertQUBO", "class": "QubovertQUBO", "args": {}, - "module": "modules.applications.optimization.SCP.mappings.qubovertQUBO", + "module": "modules.applications.optimization.scp.mappings.qubovertqubo", "requirements": [ { "name": "qubovert", @@ -2184,14 +2184,14 @@ "name": "Annealer", "class": "Annealer", "args": {}, - "module": "modules.solvers.Annealer", + "module": "modules.solvers.annealer", "requirements": [], "submodules": [ { "name": "Simulated Annealer", "class": "SimulatedAnnealingSampler", "args": {}, - "module": "modules.devices.SimulatedAnnealingSampler", + "module": "modules.devices.simulated_annealing_sampler", "requirements": [ { "name": "dwave-samplers", @@ -2210,13 +2210,13 @@ { "name": "GenerativeModeling", "class": "GenerativeModeling", - "module": "modules.applications.qml.generative_modeling.GenerativeModeling", + "module": "modules.applications.qml.generative_modeling.generative_modeling", "submodules": [ { "name": "Continuous Data", "class": "ContinuousData", "args": {}, - "module": "modules.applications.qml.generative_modeling.data.data_handler.ContinuousData", + "module": "modules.applications.qml.generative_modeling.data.data_handler.continuous_data", "requirements": [ { "name": "numpy", @@ -2228,7 +2228,7 @@ "name": "PIT", "class": "PIT", "args": {}, - "module": "modules.applications.qml.generative_modeling.transformations.PIT", + "module": "modules.applications.qml.generative_modeling.transformations.pit", "requirements": [ { "name": "numpy", @@ -2244,7 +2244,7 @@ "name": "CircuitCopula", "class": "CircuitCopula", "args": {}, - "module": "modules.applications.qml.generative_modeling.circuits.CircuitCopula", + "module": "modules.applications.qml.generative_modeling.circuits.circuit_copula", "requirements": [ { "name": "scipy", @@ -2256,7 +2256,7 @@ "name": "LibraryQiskit", "class": "LibraryQiskit", "args": {}, - "module": "modules.applications.qml.generative_modeling.mappings.LibraryQiskit", + "module": "modules.applications.qml.generative_modeling.mappings.library_qiskit", "requirements": [ { "name": "qiskit", @@ -2272,7 +2272,7 @@ "name": "QCBM", "class": "QCBM", "args": {}, - "module": "modules.applications.qml.generative_modeling.training.QCBM", + "module": "modules.applications.qml.generative_modeling.training.qcbm", "requirements": [ { "name": "numpy", @@ -2301,7 +2301,7 @@ "name": "QGAN", "class": "QGAN", "args": {}, - "module": "modules.applications.qml.generative_modeling.training.QGAN", + "module": "modules.applications.qml.generative_modeling.training.qgan", "requirements": [ { "name": "numpy", @@ -2330,7 +2330,7 @@ "name": "Inference", "class": "Inference", "args": {}, - "module": "modules.applications.qml.generative_modeling.training.Inference", + "module": "modules.applications.qml.generative_modeling.training.inference", "requirements": [ { "name": "numpy", @@ -2345,7 +2345,7 @@ "name": "LibraryPennylane", "class": "LibraryPennylane", "args": {}, - "module": "modules.applications.qml.generative_modeling.mappings.LibraryPennylane", + "module": "modules.applications.qml.generative_modeling.mappings.library_pennylane", "requirements": [ { "name": "pennylane", @@ -2373,7 +2373,7 @@ "name": "QCBM", "class": "QCBM", "args": {}, - "module": "modules.applications.qml.generative_modeling.training.QCBM", + "module": "modules.applications.qml.generative_modeling.training.qcbm", "requirements": [ { "name": "numpy", @@ -2402,7 +2402,7 @@ "name": "QGAN", "class": "QGAN", "args": {}, - "module": "modules.applications.qml.generative_modeling.training.QGAN", + "module": "modules.applications.qml.generative_modeling.training.qgan", "requirements": [ { "name": "numpy", @@ -2431,7 +2431,7 @@ "name": "Inference", "class": "Inference", "args": {}, - "module": "modules.applications.qml.generative_modeling.training.Inference", + "module": "modules.applications.qml.generative_modeling.training.inference", "requirements": [ { "name": "numpy", @@ -2446,7 +2446,7 @@ "name": "CustomQiskitNoisyBackend", "class": "CustomQiskitNoisyBackend", "args": {}, - "module": "modules.applications.qml.generative_modeling.mappings.CustomQiskitNoisyBackend", + "module": "modules.applications.qml.generative_modeling.mappings.custom_qiskit_noisy_backend", "requirements": [ { "name": "qiskit", @@ -2466,7 +2466,7 @@ "name": "QCBM", "class": "QCBM", "args": {}, - "module": "modules.applications.qml.generative_modeling.training.QCBM", + "module": "modules.applications.qml.generative_modeling.training.qcbm", "requirements": [ { "name": "numpy", @@ -2495,7 +2495,7 @@ "name": "Inference", "class": "Inference", "args": {}, - "module": "modules.applications.qml.generative_modeling.training.Inference", + "module": "modules.applications.qml.generative_modeling.training.inference", "requirements": [ { "name": "numpy", @@ -2510,7 +2510,7 @@ "name": "PresetQiskitNoisyBackend", "class": "PresetQiskitNoisyBackend", "args": {}, - "module": "modules.applications.qml.generative_modeling.mappings.PresetQiskitNoisyBackend", + "module": "modules.applications.qml.generative_modeling.mappings.preset_qiskit_noisy_backend", "requirements": [ { "name": "qiskit", @@ -2534,7 +2534,7 @@ "name": "QCBM", "class": "QCBM", "args": {}, - "module": "modules.applications.qml.generative_modeling.training.QCBM", + "module": "modules.applications.qml.generative_modeling.training.qcbm", "requirements": [ { "name": "numpy", @@ -2563,7 +2563,7 @@ "name": "Inference", "class": "Inference", "args": {}, - "module": "modules.applications.qml.generative_modeling.training.Inference", + "module": "modules.applications.qml.generative_modeling.training.inference", "requirements": [ { "name": "numpy", @@ -2582,7 +2582,7 @@ "name": "MinMax", "class": "MinMax", "args": {}, - "module": "modules.applications.qml.generative_modeling.transformations.MinMax", + "module": "modules.applications.qml.generative_modeling.transformations.min_max", "requirements": [ { "name": "numpy", @@ -2594,14 +2594,14 @@ "name": "CircuitStandard", "class": "CircuitStandard", "args": {}, - "module": "modules.applications.qml.generative_modeling.circuits.CircuitStandard", + "module": "modules.applications.qml.generative_modeling.circuits.circuit_standard", "requirements": [], "submodules": [ { "name": "LibraryQiskit", "class": "LibraryQiskit", "args": {}, - "module": "modules.applications.qml.generative_modeling.mappings.LibraryQiskit", + "module": "modules.applications.qml.generative_modeling.mappings.library_qiskit", "requirements": [ { "name": "qiskit", @@ -2617,7 +2617,7 @@ "name": "QCBM", "class": "QCBM", "args": {}, - "module": "modules.applications.qml.generative_modeling.training.QCBM", + "module": "modules.applications.qml.generative_modeling.training.qcbm", "requirements": [ { "name": "numpy", @@ -2646,7 +2646,7 @@ "name": "QGAN", "class": "QGAN", "args": {}, - "module": "modules.applications.qml.generative_modeling.training.QGAN", + "module": "modules.applications.qml.generative_modeling.training.qgan", "requirements": [ { "name": "numpy", @@ -2675,7 +2675,7 @@ "name": "Inference", "class": "Inference", "args": {}, - "module": "modules.applications.qml.generative_modeling.training.Inference", + "module": "modules.applications.qml.generative_modeling.training.inference", "requirements": [ { "name": "numpy", @@ -2690,7 +2690,7 @@ "name": "LibraryPennylane", "class": "LibraryPennylane", "args": {}, - "module": "modules.applications.qml.generative_modeling.mappings.LibraryPennylane", + "module": "modules.applications.qml.generative_modeling.mappings.library_pennylane", "requirements": [ { "name": "pennylane", @@ -2718,7 +2718,7 @@ "name": "QCBM", "class": "QCBM", "args": {}, - "module": "modules.applications.qml.generative_modeling.training.QCBM", + "module": "modules.applications.qml.generative_modeling.training.qcbm", "requirements": [ { "name": "numpy", @@ -2747,7 +2747,7 @@ "name": "QGAN", "class": "QGAN", "args": {}, - "module": "modules.applications.qml.generative_modeling.training.QGAN", + "module": "modules.applications.qml.generative_modeling.training.qgan", "requirements": [ { "name": "numpy", @@ -2776,7 +2776,7 @@ "name": "Inference", "class": "Inference", "args": {}, - "module": "modules.applications.qml.generative_modeling.training.Inference", + "module": "modules.applications.qml.generative_modeling.training.inference", "requirements": [ { "name": "numpy", @@ -2791,7 +2791,7 @@ "name": "CustomQiskitNoisyBackend", "class": "CustomQiskitNoisyBackend", "args": {}, - "module": "modules.applications.qml.generative_modeling.mappings.CustomQiskitNoisyBackend", + "module": "modules.applications.qml.generative_modeling.mappings.custom_qiskit_noisy_backend", "requirements": [ { "name": "qiskit", @@ -2811,7 +2811,7 @@ "name": "QCBM", "class": "QCBM", "args": {}, - "module": "modules.applications.qml.generative_modeling.training.QCBM", + "module": "modules.applications.qml.generative_modeling.training.qcbm", "requirements": [ { "name": "numpy", @@ -2840,7 +2840,7 @@ "name": "Inference", "class": "Inference", "args": {}, - "module": "modules.applications.qml.generative_modeling.training.Inference", + "module": "modules.applications.qml.generative_modeling.training.inference", "requirements": [ { "name": "numpy", @@ -2855,7 +2855,7 @@ "name": "PresetQiskitNoisyBackend", "class": "PresetQiskitNoisyBackend", "args": {}, - "module": "modules.applications.qml.generative_modeling.mappings.PresetQiskitNoisyBackend", + "module": "modules.applications.qml.generative_modeling.mappings.preset_qiskit_noisy_backend", "requirements": [ { "name": "qiskit", @@ -2879,7 +2879,7 @@ "name": "QCBM", "class": "QCBM", "args": {}, - "module": "modules.applications.qml.generative_modeling.training.QCBM", + "module": "modules.applications.qml.generative_modeling.training.qcbm", "requirements": [ { "name": "numpy", @@ -2908,7 +2908,7 @@ "name": "Inference", "class": "Inference", "args": {}, - "module": "modules.applications.qml.generative_modeling.training.Inference", + "module": "modules.applications.qml.generative_modeling.training.inference", "requirements": [ { "name": "numpy", @@ -2925,14 +2925,14 @@ "name": "CircuitCardinality", "class": "CircuitCardinality", "args": {}, - "module": "modules.applications.qml.generative_modeling.circuits.CircuitCardinality", + "module": "modules.applications.qml.generative_modeling.circuits.circuit_cardinality", "requirements": [], "submodules": [ { "name": "LibraryQiskit", "class": "LibraryQiskit", "args": {}, - "module": "modules.applications.qml.generative_modeling.mappings.LibraryQiskit", + "module": "modules.applications.qml.generative_modeling.mappings.library_qiskit", "requirements": [ { "name": "qiskit", @@ -2948,7 +2948,7 @@ "name": "QCBM", "class": "QCBM", "args": {}, - "module": "modules.applications.qml.generative_modeling.training.QCBM", + "module": "modules.applications.qml.generative_modeling.training.qcbm", "requirements": [ { "name": "numpy", @@ -2977,7 +2977,7 @@ "name": "QGAN", "class": "QGAN", "args": {}, - "module": "modules.applications.qml.generative_modeling.training.QGAN", + "module": "modules.applications.qml.generative_modeling.training.qgan", "requirements": [ { "name": "numpy", @@ -3006,7 +3006,7 @@ "name": "Inference", "class": "Inference", "args": {}, - "module": "modules.applications.qml.generative_modeling.training.Inference", + "module": "modules.applications.qml.generative_modeling.training.inference", "requirements": [ { "name": "numpy", @@ -3021,7 +3021,7 @@ "name": "LibraryPennylane", "class": "LibraryPennylane", "args": {}, - "module": "modules.applications.qml.generative_modeling.mappings.LibraryPennylane", + "module": "modules.applications.qml.generative_modeling.mappings.library_pennylane", "requirements": [ { "name": "pennylane", @@ -3049,7 +3049,7 @@ "name": "QCBM", "class": "QCBM", "args": {}, - "module": "modules.applications.qml.generative_modeling.training.QCBM", + "module": "modules.applications.qml.generative_modeling.training.qcbm", "requirements": [ { "name": "numpy", @@ -3078,7 +3078,7 @@ "name": "QGAN", "class": "QGAN", "args": {}, - "module": "modules.applications.qml.generative_modeling.training.QGAN", + "module": "modules.applications.qml.generative_modeling.training.qgan", "requirements": [ { "name": "numpy", @@ -3107,7 +3107,7 @@ "name": "Inference", "class": "Inference", "args": {}, - "module": "modules.applications.qml.generative_modeling.training.Inference", + "module": "modules.applications.qml.generative_modeling.training.inference", "requirements": [ { "name": "numpy", @@ -3122,7 +3122,7 @@ "name": "CustomQiskitNoisyBackend", "class": "CustomQiskitNoisyBackend", "args": {}, - "module": "modules.applications.qml.generative_modeling.mappings.CustomQiskitNoisyBackend", + "module": "modules.applications.qml.generative_modeling.mappings.custom_qiskit_noisy_backend", "requirements": [ { "name": "qiskit", @@ -3142,7 +3142,7 @@ "name": "QCBM", "class": "QCBM", "args": {}, - "module": "modules.applications.qml.generative_modeling.training.QCBM", + "module": "modules.applications.qml.generative_modeling.training.qcbm", "requirements": [ { "name": "numpy", @@ -3171,7 +3171,7 @@ "name": "Inference", "class": "Inference", "args": {}, - "module": "modules.applications.qml.generative_modeling.training.Inference", + "module": "modules.applications.qml.generative_modeling.training.inference", "requirements": [ { "name": "numpy", @@ -3186,7 +3186,7 @@ "name": "PresetQiskitNoisyBackend", "class": "PresetQiskitNoisyBackend", "args": {}, - "module": "modules.applications.qml.generative_modeling.mappings.PresetQiskitNoisyBackend", + "module": "modules.applications.qml.generative_modeling.mappings.preset_qiskit_noisy_backend", "requirements": [ { "name": "qiskit", @@ -3210,7 +3210,7 @@ "name": "QCBM", "class": "QCBM", "args": {}, - "module": "modules.applications.qml.generative_modeling.training.QCBM", + "module": "modules.applications.qml.generative_modeling.training.qcbm", "requirements": [ { "name": "numpy", @@ -3239,7 +3239,7 @@ "name": "Inference", "class": "Inference", "args": {}, - "module": "modules.applications.qml.generative_modeling.training.Inference", + "module": "modules.applications.qml.generative_modeling.training.inference", "requirements": [ { "name": "numpy", @@ -3260,7 +3260,7 @@ "name": "Discrete Data", "class": "DiscreteData", "args": {}, - "module": "modules.applications.qml.generative_modeling.data.data_handler.DiscreteData", + "module": "modules.applications.qml.generative_modeling.data.data_handler.discrete_data", "requirements": [ { "name": "numpy", @@ -3272,14 +3272,14 @@ "name": "CircuitCardinality", "class": "CircuitCardinality", "args": {}, - "module": "modules.applications.qml.generative_modeling.circuits.CircuitCardinality", + "module": "modules.applications.qml.generative_modeling.circuits.circuit_cardinality", "requirements": [], "submodules": [ { "name": "LibraryQiskit", "class": "LibraryQiskit", "args": {}, - "module": "modules.applications.qml.generative_modeling.mappings.LibraryQiskit", + "module": "modules.applications.qml.generative_modeling.mappings.library_qiskit", "requirements": [ { "name": "qiskit", @@ -3295,7 +3295,7 @@ "name": "QCBM", "class": "QCBM", "args": {}, - "module": "modules.applications.qml.generative_modeling.training.QCBM", + "module": "modules.applications.qml.generative_modeling.training.qcbm", "requirements": [ { "name": "numpy", @@ -3324,7 +3324,7 @@ "name": "QGAN", "class": "QGAN", "args": {}, - "module": "modules.applications.qml.generative_modeling.training.QGAN", + "module": "modules.applications.qml.generative_modeling.training.qgan", "requirements": [ { "name": "numpy", @@ -3353,7 +3353,7 @@ "name": "Inference", "class": "Inference", "args": {}, - "module": "modules.applications.qml.generative_modeling.training.Inference", + "module": "modules.applications.qml.generative_modeling.training.inference", "requirements": [ { "name": "numpy", @@ -3368,7 +3368,7 @@ "name": "LibraryPennylane", "class": "LibraryPennylane", "args": {}, - "module": "modules.applications.qml.generative_modeling.mappings.LibraryPennylane", + "module": "modules.applications.qml.generative_modeling.mappings.library_pennylane", "requirements": [ { "name": "pennylane", @@ -3396,7 +3396,7 @@ "name": "QCBM", "class": "QCBM", "args": {}, - "module": "modules.applications.qml.generative_modeling.training.QCBM", + "module": "modules.applications.qml.generative_modeling.training.qcbm", "requirements": [ { "name": "numpy", @@ -3425,7 +3425,7 @@ "name": "QGAN", "class": "QGAN", "args": {}, - "module": "modules.applications.qml.generative_modeling.training.QGAN", + "module": "modules.applications.qml.generative_modeling.training.qgan", "requirements": [ { "name": "numpy", @@ -3454,7 +3454,7 @@ "name": "Inference", "class": "Inference", "args": {}, - "module": "modules.applications.qml.generative_modeling.training.Inference", + "module": "modules.applications.qml.generative_modeling.training.inference", "requirements": [ { "name": "numpy", @@ -3469,7 +3469,7 @@ "name": "CustomQiskitNoisyBackend", "class": "CustomQiskitNoisyBackend", "args": {}, - "module": "modules.applications.qml.generative_modeling.mappings.CustomQiskitNoisyBackend", + "module": "modules.applications.qml.generative_modeling.mappings.custom_qiskit_noisy_backend", "requirements": [ { "name": "qiskit", @@ -3489,7 +3489,7 @@ "name": "QCBM", "class": "QCBM", "args": {}, - "module": "modules.applications.qml.generative_modeling.training.QCBM", + "module": "modules.applications.qml.generative_modeling.training.qcbm", "requirements": [ { "name": "numpy", @@ -3518,7 +3518,7 @@ "name": "Inference", "class": "Inference", "args": {}, - "module": "modules.applications.qml.generative_modeling.training.Inference", + "module": "modules.applications.qml.generative_modeling.training.inference", "requirements": [ { "name": "numpy", @@ -3533,7 +3533,7 @@ "name": "PresetQiskitNoisyBackend", "class": "PresetQiskitNoisyBackend", "args": {}, - "module": "modules.applications.qml.generative_modeling.mappings.PresetQiskitNoisyBackend", + "module": "modules.applications.qml.generative_modeling.mappings.preset_qiskit_noisy_backend", "requirements": [ { "name": "qiskit", @@ -3557,7 +3557,7 @@ "name": "QCBM", "class": "QCBM", "args": {}, - "module": "modules.applications.qml.generative_modeling.training.QCBM", + "module": "modules.applications.qml.generative_modeling.training.qcbm", "requirements": [ { "name": "numpy", @@ -3586,7 +3586,7 @@ "name": "Inference", "class": "Inference", "args": {}, - "module": "modules.applications.qml.generative_modeling.training.Inference", + "module": "modules.applications.qml.generative_modeling.training.inference", "requirements": [ { "name": "numpy", @@ -3605,4 +3605,4 @@ "requirements": [] } ] -} +} \ No newline at end of file diff --git a/README.md b/README.md index 8b198ea5..ae68787b 100644 --- a/README.md +++ b/README.md @@ -169,7 +169,7 @@ Example run (You need to check at least one option with an ``X`` for the checkbo SCP GenerativeModeling -2024-10-09 15:05:52,610 [INFO] Import module modules.applications.optimization.TSP.TSP +2024-10-09 15:05:52,610 [INFO] Import module modules.applications.optimization.tsp.tsp [?] (Option for TSP) How many nodes does you graph need?: > [X] 3 [ ] 4 @@ -187,13 +187,13 @@ Example run (You need to check at least one option with an ``X`` for the checkbo [ ] ReverseGreedyClassicalTSP [ ] RandomTSP -2024-10-09 15:06:20,897 [INFO] Import module modules.solvers.GreedyClassicalTSP +2024-10-09 15:06:20,897 [INFO] Import module modules.solvers.greedy_classical_tsp 2024-10-09 15:06:20,933 [INFO] Skipping asking for submodule, since only 1 option (Local) is available. 2024-10-09 15:06:20,933 [INFO] Import module modules.devices.Local 2024-10-09 15:06:20,946 [INFO] Submodule configuration finished [?] How many repetitions do you want?: 1P -2024-10-09 15:07:11,573 [INFO] Import module modules.applications.optimization.TSP.TSP -2024-10-09 15:07:11,573 [INFO] Import module modules.solvers.GreedyClassicalTSP +2024-10-09 15:07:11,573 [INFO] Import module modules.applications.optimization.tsp.tsp +2024-10-09 15:07:11,573 [INFO] Import module modules.solvers.greedy_classical_tsp 2024-10-09 15:07:11,574 [INFO] Import module modules.devices.Local 2024-10-09 15:07:12,194 [INFO] [INFO] Created Benchmark run directory /Users/user1/quark/benchmark_runs/tsp-2024-10-09-15-07-11 2024-10-09 15:07:12,194 [INFO] Codebase is based on revision 1d9d17aad7ddff623ff51f62ca3ec2756621c345 and has no uncommitted changes diff --git a/docs/conf.py b/docs/conf.py index c4710193..034322e2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -45,8 +45,8 @@ autosummary_generate = True # Turn on sphinx.ext.autosummary autosummary_mock_imports = [ - 'modules.applications.optimization.PVC.createReferenceGraph', - 'modules.applications.optimization.TSP.createReferenceGraph' + 'modules.applications.optimization.pvc.createReferenceGraph', + 'modules.applications.optimization.tsp.createReferenceGraph' ] # If true, the current module name will be prepended to all description # unit titles (such as .. function::). diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 5b6bdfda..f677c584 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -182,7 +182,7 @@ Example run (You need to check at least one option with an ``X`` for the checkbo SCP GenerativeModeling - 2024-10-09 15:05:52,610 [INFO] Import module modules.applications.optimization.TSP.TSP + 2024-10-09 15:05:52,610 [INFO] Import module modules.applications.optimization.tsp.tsp [?] (Option for TSP) How many nodes does you graph need?: > [X] 3 [ ] 4 @@ -200,13 +200,13 @@ Example run (You need to check at least one option with an ``X`` for the checkbo [ ] ReverseGreedyClassicalTSP [ ] RandomTSP - 2024-10-09 15:06:20,897 [INFO] Import module modules.solvers.GreedyClassicalTSP + 2024-10-09 15:06:20,897 [INFO] Import module modules.solvers.greedy_classical_tsp 2024-10-09 15:06:20,933 [INFO] Skipping asking for submodule, since only 1 option (Local) is available. 2024-10-09 15:06:20,933 [INFO] Import module modules.devices.Local 2024-10-09 15:06:20,946 [INFO] Submodule configuration finished [?] How many repetitions do you want?: 1P - 2024-10-09 15:07:11,573 [INFO] Import module modules.applications.optimization.TSP.TSP - 2024-10-09 15:07:11,573 [INFO] Import module modules.solvers.GreedyClassicalTSP + 2024-10-09 15:07:11,573 [INFO] Import module modules.applications.optimization.tsp.tsp + 2024-10-09 15:07:11,573 [INFO] Import module modules.solvers.greedy_classical_tsp 2024-10-09 15:07:11,574 [INFO] Import module modules.devices.Local 2024-10-09 15:07:12,194 [INFO] [INFO] Created Benchmark run directory /Users/user1/quark/benchmark_runs/tsp-2024-10-09-15-07-11 2024-10-09 15:07:12,194 [INFO] Codebase is based on revision 1d9d17aad7ddff623ff51f62ca3ec2756621c345 and has no uncommitted changes @@ -359,12 +359,12 @@ An example for this would be: [ { "name": "TSP", - "module": "modules.applications.optimization.TSP.TSP", + "module": "modules.applications.optimization.tsp.tsp", "dir": "src", "submodules": [ { "name": "GreedyClassicalTSP", - "module": "modules.solvers.GreedyClassicalTSP", + "module": "modules.solvers.greedy_classical_tsp", "submodules": [] } ] diff --git a/src/Installer.py b/src/Installer.py index 33c10ca3..e3c941e1 100644 --- a/src/Installer.py +++ b/src/Installer.py @@ -23,7 +23,7 @@ from packaging import version import inquirer -from modules.Core import Core +from modules.core import Core from utils import _get_instance_with_sub_options, get_git_revision, checkbox @@ -40,14 +40,14 @@ def __init__(self): self.python_version = "3.12.7" self.pip_version = "23.0" self.default_app_modules = [ - {"name": "PVC", "class": "PVC", "module": "modules.applications.optimization.PVC.PVC"}, - {"name": "SAT", "class": "SAT", "module": "modules.applications.optimization.SAT.SAT"}, - {"name": "TSP", "class": "TSP", "module": "modules.applications.optimization.TSP.TSP"}, - {"name": "ACL", "class": "ACL", "module": "modules.applications.optimization.ACL.ACL"}, - {"name": "MIS", "class": "MIS", "module": "modules.applications.optimization.MIS.MIS"}, - {"name": "SCP", "class": "SCP", "module": "modules.applications.optimization.SCP.SCP"}, + {"name": "PVC", "class": "PVC", "module": "modules.applications.optimization.pvc.pvc"}, + {"name": "SAT", "class": "SAT", "module": "modules.applications.optimization.sat.sat"}, + {"name": "TSP", "class": "TSP", "module": "modules.applications.optimization.tsp.tsp"}, + {"name": "ACL", "class": "ACL", "module": "modules.applications.optimization.acl.acl"}, + {"name": "MIS", "class": "MIS", "module": "modules.applications.optimization.mis.mis"}, + {"name": "SCP", "class": "SCP", "module": "modules.applications.optimization.scp.scp"}, {"name": "GenerativeModeling", "class": "GenerativeModeling", - "module": "modules.applications.qml.generative_modeling.GenerativeModeling"} + "module": "modules.applications.qml.generative_modeling.generative_modeling"} ] self.core_requirements = [ diff --git a/src/BenchmarkManager.py b/src/benchmark_manager.py similarity index 99% rename from src/BenchmarkManager.py rename to src/benchmark_manager.py index 2d8b3f2e..f76f4273 100644 --- a/src/BenchmarkManager.py +++ b/src/benchmark_manager.py @@ -25,10 +25,10 @@ import numpy as np -from ConfigManager import ConfigManager -from BenchmarkRecord import BenchmarkRecord, BenchmarkRecordStored -from Plotter import Plotter -from modules.Core import Core +from config_manager import ConfigManager +from benchmark_record import BenchmarkRecord, BenchmarkRecordStored +from plotter import Plotter +from modules.core import Core from utils import get_git_revision from utils_mpi import get_comm diff --git a/src/BenchmarkRecord.py b/src/benchmark_record.py similarity index 99% rename from src/BenchmarkRecord.py rename to src/benchmark_record.py index d822c806..9aecd2bf 100644 --- a/src/BenchmarkRecord.py +++ b/src/benchmark_record.py @@ -17,7 +17,7 @@ import json from copy import deepcopy -from Metrics import Metrics +from metrics import Metrics class BenchmarkRecord: diff --git a/src/ConfigManager.py b/src/config_manager.py similarity index 99% rename from src/ConfigManager.py rename to src/config_manager.py index 73efb771..63ab03de 100644 --- a/src/ConfigManager.py +++ b/src/config_manager.py @@ -23,8 +23,8 @@ import yaml from typing_extensions import TypedDict, NotRequired, Self -from modules.Core import Core -from modules.applications import Application +from modules.core import Core +from modules.applications.application import Application from utils import _get_instance_with_sub_options, checkbox diff --git a/src/demo/instruction_demo.py b/src/demo/instruction_demo.py index 24e102aa..d5f265c3 100644 --- a/src/demo/instruction_demo.py +++ b/src/demo/instruction_demo.py @@ -1,8 +1,8 @@ import logging from BenchmarkManager import Instruction -from modules.Core import Core -from modules.applications.Application import Application +from modules.core import Core +from modules.applications.application import Application class InstructionDemo(Application): diff --git a/src/main.py b/src/main.py index d08b23cd..d93a12a0 100644 --- a/src/main.py +++ b/src/main.py @@ -20,7 +20,7 @@ from collections.abc import Iterable import yaml -from Installer import Installer +from installer import Installer from utils import _expand_paths from utils_mpi import MPIStreamHandler, MPIFileHandler, get_comm @@ -96,8 +96,8 @@ def start_benchmark_run(config_file: str = None, store_dir: str = None, benchmark_config = json.loads(benchmark_config["config"]) - from BenchmarkManager import BenchmarkManager # pylint: disable=C0415 - from ConfigManager import ConfigManager # pylint: disable=C0415 + from benchmark_manager import BenchmarkManager # pylint: disable=C0415 + from config_manager import ConfigManager # pylint: disable=C0415 config_manager = ConfigManager() config_manager.set_config(benchmark_config) @@ -146,15 +146,15 @@ def handle_benchmark_run(args: argparse.Namespace) -> None: :param args: Namespace with the arguments given by the user """ - from BenchmarkManager import BenchmarkManager # pylint: disable=C0415 - from Plotter import Plotter # pylint: disable=C0415 + from benchmark_manager import BenchmarkManager # pylint: disable=C0415 + from plotter import Plotter # pylint: disable=C0415 benchmark_manager = BenchmarkManager(fail_fast=args.failfast) if args.summarize: benchmark_manager.summarize_results(args.summarize) else: - from ConfigManager import ConfigManager # pylint: disable=C0415 + from config_manager import ConfigManager # pylint: disable=C0415 config_manager = ConfigManager() if args.modules: logging.info(f"Load application modules configuration from {args.modules}") diff --git a/src/modules/applications/Application.py b/src/modules/applications/Application.py index c1a93b77..35b3f136 100644 --- a/src/modules/applications/Application.py +++ b/src/modules/applications/Application.py @@ -13,7 +13,7 @@ # limitations under the License. from abc import ABC, abstractmethod -from modules.Core import Core +from modules.core import Core class Application(Core, ABC): diff --git a/src/modules/applications/Mapping.py b/src/modules/applications/Mapping.py index 25d18d94..67ab5366 100644 --- a/src/modules/applications/Mapping.py +++ b/src/modules/applications/Mapping.py @@ -13,7 +13,7 @@ # limitations under the License. from abc import ABC, abstractmethod -from modules.Core import Core +from modules.core import Core class Mapping(Core, ABC): diff --git a/src/modules/applications/optimization/ACL/ACL.py b/src/modules/applications/optimization/ACL/ACL.py index 0dcc3073..1581e932 100644 --- a/src/modules/applications/optimization/ACL/ACL.py +++ b/src/modules/applications/optimization/ACL/ACL.py @@ -34,8 +34,8 @@ import numpy as np import pulp -from modules.applications.Application import Core -from modules.applications.optimization.Optimization import Optimization +from modules.applications.application import Core +from modules.applications.optimization.optimization import Optimization from utils import start_time_measurement, end_time_measurement @@ -84,10 +84,10 @@ def get_default_submodule(self, option: str) -> Core: :raises NotImplementedError: If the option is not recognized """ if option == "MIPsolverACL": - from modules.solvers.MIPsolverACL import MIPaclp # pylint: disable=C0415 + from modules.solvers.mip_solver_acl import MIPaclp # pylint: disable=C0415 return MIPaclp() elif option == "QUBO": - from modules.applications.optimization.ACL.mappings.QUBO import Qubo # pylint: disable=C0415 + from modules.applications.optimization.acl.mappings.qubo import Qubo # pylint: disable=C0415 return Qubo() else: raise NotImplementedError(f"Submodule Option {option} not implemented") diff --git a/src/modules/applications/optimization/ACL/mappings/ISING.py b/src/modules/applications/optimization/ACL/mappings/ISING.py index 3a831f0d..9995707e 100644 --- a/src/modules/applications/optimization/ACL/mappings/ISING.py +++ b/src/modules/applications/optimization/ACL/mappings/ISING.py @@ -20,8 +20,8 @@ from qiskit_optimization import QuadraticProgram from qiskit_optimization.converters import QuadraticProgramToQubo -from modules.applications.Mapping import Mapping -from modules.Core import Core +from modules.applications.mapping import Mapping +from modules.core import Core from utils import start_time_measurement, end_time_measurement @@ -200,10 +200,10 @@ def get_default_submodule(self, option: str) -> Core: :raises NotImplementedError: If the option is not recognized """ if option == "QAOA": - from modules.solvers.QAOA import QAOA # pylint: disable=C0415 + from modules.solvers.qaoa import QAOA # pylint: disable=C0415 return QAOA() elif option == "QiskitQAOA": - from modules.solvers.QiskitQAOA import QiskitQAOA # pylint: disable=C0415 + from modules.solvers.qiskit_qaoa import QiskitQAOA # pylint: disable=C0415 return QiskitQAOA() else: raise NotImplementedError(f"Solver Option {option} not implemented") diff --git a/src/modules/applications/optimization/ACL/mappings/QUBO.py b/src/modules/applications/optimization/ACL/mappings/QUBO.py index 1ba26dea..632c1f4b 100644 --- a/src/modules/applications/optimization/ACL/mappings/QUBO.py +++ b/src/modules/applications/optimization/ACL/mappings/QUBO.py @@ -23,7 +23,7 @@ LinearEqualityToPenalty ) -from modules.applications.Mapping import Mapping, Core +from modules.applications.mapping import Mapping, Core from utils import start_time_measurement, end_time_measurement # TODO Large chunks of this code is duplicated in ACL.mappings.ISING -> unify @@ -269,7 +269,7 @@ def get_default_submodule(self, option: str) -> Core: :raises NotImplementedError: If the option is not recognized """ if option == "Annealer": - from modules.solvers.Annealer import Annealer # pylint: disable=C0415 + from modules.solvers.annealer import Annealer # pylint: disable=C0415 return Annealer() else: raise NotImplementedError(f"Solver Option {option} not implemented") diff --git a/src/modules/applications/optimization/MIS/MIS.py b/src/modules/applications/optimization/MIS/MIS.py index b0083fe1..e146ba9d 100644 --- a/src/modules/applications/optimization/MIS/MIS.py +++ b/src/modules/applications/optimization/MIS/MIS.py @@ -20,9 +20,9 @@ import matplotlib.pyplot as plt from matplotlib.lines import Line2D -from modules.applications.Application import Core -from modules.applications.optimization.Optimization import Optimization -from modules.applications.optimization.MIS.data.graph_layouts import generate_hexagonal_graph +from modules.applications.application import Core +from modules.applications.optimization.optimization import Optimization +from modules.applications.optimization.mis.data.graph_layouts import generate_hexagonal_graph from utils import start_time_measurement, end_time_measurement # Define R_rydberg @@ -82,10 +82,10 @@ def get_default_submodule(self, option: str) -> Core: :raises NotImplementedError: If the option is not recognized """ if option == "QIRO": - from modules.applications.optimization.MIS.mappings.QIRO import QIRO # pylint: disable=C0415 + from modules.applications.optimization.mis.mappings.qiro import QIRO # pylint: disable=C0415 return QIRO() elif option == "NeutralAtom": - from modules.applications.optimization.MIS.mappings.NeutralAtom import NeutralAtom # pylint: disable=C0415 + from QUARK.src.modules.applications.optimization.mis.mappings.neutral_atom import NeutralAtom # pylint: disable=C0415 return NeutralAtom() else: raise NotImplementedError(f"Mapping Option {option} not implemented") diff --git a/src/modules/applications/optimization/MIS/mappings/QIRO.py b/src/modules/applications/optimization/MIS/mappings/QIRO.py index b3666203..e0e9ee1a 100644 --- a/src/modules/applications/optimization/MIS/mappings/QIRO.py +++ b/src/modules/applications/optimization/MIS/mappings/QIRO.py @@ -15,7 +15,7 @@ from typing import TypedDict import networkx -from modules.applications.Mapping import Core, Mapping +from modules.applications.mapping import Core, Mapping from utils import start_time_measurement, end_time_measurement @@ -90,7 +90,7 @@ def get_default_submodule(self, option: str) -> Core: :raises NotImplementedError: If the option is not recognized """ if option == "QrispQIRO": - from modules.solvers.QrispQIRO import QIROSolver # pylint: disable=C0415 + from modules.solvers.qrisp_qiro import QIROSolver # pylint: disable=C0415 return QIROSolver() else: raise NotImplementedError(f"Solver Option {option} not implemented") diff --git a/src/modules/applications/optimization/MIS/mappings/NeutralAtom.py b/src/modules/applications/optimization/MIS/mappings/neutral_atom.py similarity index 94% rename from src/modules/applications/optimization/MIS/mappings/NeutralAtom.py rename to src/modules/applications/optimization/MIS/mappings/neutral_atom.py index 58e70513..f51af4e2 100644 --- a/src/modules/applications/optimization/MIS/mappings/NeutralAtom.py +++ b/src/modules/applications/optimization/MIS/mappings/neutral_atom.py @@ -17,7 +17,7 @@ import networkx as nx import pulser -from modules.applications.Mapping import Mapping, Core +from modules.applications.mapping import Mapping, Core from utils import start_time_measurement, end_time_measurement @@ -85,7 +85,7 @@ def get_default_submodule(self, option: str) -> Core: :raises NotImplementedError: If the option is not recognized """ if option == "NeutralAtomMIS": - from modules.solvers.NeutralAtomMIS import NeutralAtomMIS # pylint: disable=C0415 + from modules.solvers.neutral_atom_mis import NeutralAtomMIS # pylint: disable=C0415 return NeutralAtomMIS() else: raise NotImplementedError(f"Solver Option {option} not implemented") diff --git a/src/modules/applications/optimization/Optimization.py b/src/modules/applications/optimization/Optimization.py index cfc3dada..5c87949b 100644 --- a/src/modules/applications/optimization/Optimization.py +++ b/src/modules/applications/optimization/Optimization.py @@ -14,7 +14,7 @@ from abc import ABC, abstractmethod import logging -from modules.applications.Application import Application +from modules.applications.application import Application from utils import start_time_measurement, end_time_measurement diff --git a/src/modules/applications/optimization/PVC/PVC.py b/src/modules/applications/optimization/PVC/PVC.py index 767a6963..87d1c6a7 100644 --- a/src/modules/applications/optimization/PVC/PVC.py +++ b/src/modules/applications/optimization/PVC/PVC.py @@ -24,8 +24,8 @@ from matplotlib.patches import Patch import numpy as np -from modules.applications.Application import Core -from modules.applications.optimization.Optimization import Optimization +from modules.applications.application import Core +from modules.applications.optimization.optimization import Optimization from utils import start_time_measurement, end_time_measurement @@ -84,19 +84,19 @@ def get_default_submodule(self, option: str) -> Core: :raises NotImplementedError: If the option is not recognized """ if option == "Ising": - from modules.applications.optimization.PVC.mappings.ISING import Ising # pylint: disable=C0415 + from modules.applications.optimization.pvc.mappings.ising import Ising # pylint: disable=C0415 return Ising() elif option == "QUBO": - from modules.applications.optimization.PVC.mappings.QUBO import QUBO # pylint: disable=C0415 + from modules.applications.optimization.pvc.mappings.qubo import QUBO # pylint: disable=C0415 return QUBO() elif option == "GreedyClassicalPVC": - from modules.solvers.GreedyClassicalPVC import GreedyClassicalPVC # pylint: disable=C0415 + from modules.solvers.greedy_classical_pvc import GreedyClassicalPVC # pylint: disable=C0415 return GreedyClassicalPVC() elif option == "ReverseGreedyClassicalPVC": - from modules.solvers.ReverseGreedyClassicalPVC import ReverseGreedyClassicalPVC # pylint: disable=C0415 + from modules.solvers.reverse_greedy_classical_pvc import ReverseGreedyClassicalPVC # pylint: disable=C0415 return ReverseGreedyClassicalPVC() elif option == "RandomPVC": - from modules.solvers.RandomClassicalPVC import RandomPVC # pylint: disable=C0415 + from modules.solvers.random_classical_pvc import RandomPVC # pylint: disable=C0415 return RandomPVC() else: raise NotImplementedError(f"Mapping Option {option} not implemented") diff --git a/src/modules/applications/optimization/PVC/mappings/ISING.py b/src/modules/applications/optimization/PVC/mappings/ISING.py index 5d2e32c7..68574679 100644 --- a/src/modules/applications/optimization/PVC/mappings/ISING.py +++ b/src/modules/applications/optimization/PVC/mappings/ISING.py @@ -19,8 +19,8 @@ import numpy as np from dimod import qubo_to_ising -from modules.applications.Mapping import Mapping, Core -from modules.applications.optimization.PVC.mappings.QUBO import QUBO +from modules.applications.mapping import Mapping, Core +from modules.applications.optimization.pvc.mappings.qubo import QUBO from utils import start_time_measurement, end_time_measurement @@ -150,10 +150,10 @@ def get_default_submodule(self, option: str) -> Core: :raises NotImplementedError: If the option is not recognized """ if option == "QAOA": - from modules.solvers.QAOA import QAOA # pylint: disable=C0415 + from modules.solvers.qaoa import QAOA # pylint: disable=C0415 return QAOA() if option == "PennylaneQAOA": - from modules.solvers.PennylaneQAOA import PennylaneQAOA # pylint: disable=C0415 + from modules.solvers.pennylane_qaoa import PennylaneQAOA # pylint: disable=C0415 return PennylaneQAOA() else: raise NotImplementedError(f"Solver Option {option} not implemented") diff --git a/src/modules/applications/optimization/PVC/mappings/QUBO.py b/src/modules/applications/optimization/PVC/mappings/QUBO.py index 2cd0b487..c62cf779 100644 --- a/src/modules/applications/optimization/PVC/mappings/QUBO.py +++ b/src/modules/applications/optimization/PVC/mappings/QUBO.py @@ -19,7 +19,7 @@ import networkx as nx -from modules.applications.Mapping import Mapping, Core +from modules.applications.mapping import Mapping, Core from utils import start_time_measurement, end_time_measurement @@ -195,7 +195,7 @@ def get_default_submodule(self, option: str) -> Core: :raises NotImplementedError: If the option is not recognized """ if option == "Annealer": - from modules.solvers.Annealer import Annealer # pylint: disable=C0415 + from modules.solvers.annealer import Annealer # pylint: disable=C0415 return Annealer() else: raise NotImplementedError(f"Solver Option {option} not implemented") diff --git a/src/modules/applications/optimization/SAT/SAT.py b/src/modules/applications/optimization/SAT/SAT.py index abb62310..eebdc9d1 100644 --- a/src/modules/applications/optimization/SAT/SAT.py +++ b/src/modules/applications/optimization/SAT/SAT.py @@ -20,8 +20,8 @@ from nnf import Var, And, Or from nnf.dimacs import dump -from modules.Core import Core -from modules.applications.optimization.Optimization import Optimization +from modules.core import Core +from modules.applications.optimization.optimization import Optimization from utils import start_time_measurement, end_time_measurement @@ -88,23 +88,23 @@ def get_default_submodule(self, option: str) -> Core: :raises NotImplementedError: If the option is not recognized """ if option == "QubovertQUBO": - from modules.applications.optimization.SAT.mappings.QubovertQUBO import \ + from modules.applications.optimization.sat.mappings.qubovertqubo import \ QubovertQUBO # pylint: disable=C0415 return QubovertQUBO() elif option == "Direct": - from modules.applications.optimization.SAT.mappings.Direct import Direct # pylint: disable=C0415 + from modules.applications.optimization.sat.mappings.direct import Direct # pylint: disable=C0415 return Direct() elif option == "ChoiQUBO": - from modules.applications.optimization.SAT.mappings.ChoiQUBO import ChoiQUBO # pylint: disable=C0415 + from modules.applications.optimization.sat.mappings.choiqubo import ChoiQUBO # pylint: disable=C0415 return ChoiQUBO() elif option == "ChoiIsing": - from modules.applications.optimization.SAT.mappings.ChoiISING import ChoiIsing # pylint: disable=C0415 + from modules.applications.optimization.sat.mappings.choiIsing import ChoiIsing # pylint: disable=C0415 return ChoiIsing() elif option == "DinneenQUBO": - from modules.applications.optimization.SAT.mappings.DinneenQUBO import DinneenQUBO # pylint: disable=C0415 + from modules.applications.optimization.sat.mappings.dinneenqubo import DinneenQUBO # pylint: disable=C0415 return DinneenQUBO() elif option == "DinneenIsing": - from modules.applications.optimization.SAT.mappings.DinneenISING import \ + from modules.applications.optimization.sat.mappings.dinneenising import \ DinneenIsing # pylint: disable=C0415 return DinneenIsing() else: diff --git a/src/modules/applications/optimization/SAT/mappings/ChoiISING.py b/src/modules/applications/optimization/SAT/mappings/ChoiISING.py index 0a74b79c..62639ebe 100644 --- a/src/modules/applications/optimization/SAT/mappings/ChoiISING.py +++ b/src/modules/applications/optimization/SAT/mappings/ChoiISING.py @@ -17,8 +17,8 @@ import numpy as np from dimod import qubo_to_ising -from modules.applications.Mapping import Mapping, Core -from modules.applications.optimization.SAT.mappings.ChoiQUBO import ChoiQUBO +from modules.applications.mapping import Mapping, Core +from modules.applications.optimization.sat.mappings.choiqubo import ChoiQUBO from utils import start_time_measurement, end_time_measurement @@ -147,10 +147,10 @@ def get_default_submodule(self, option: str) -> Core: :raises NotImplementedError: If the option is not recognized """ if option == "QAOA": - from modules.solvers.QAOA import QAOA # pylint: disable=C0415 + from modules.solvers.qaoa import QAOA # pylint: disable=C0415 return QAOA() if option == "PennylaneQAOA": - from modules.solvers.PennylaneQAOA import PennylaneQAOA # pylint: disable=C0415 + from modules.solvers.pennylane_qaoa import PennylaneQAOA # pylint: disable=C0415 return PennylaneQAOA() else: raise NotImplementedError(f"Solver Option {option} not implemented") diff --git a/src/modules/applications/optimization/SAT/mappings/ChoiQUBO.py b/src/modules/applications/optimization/SAT/mappings/ChoiQUBO.py index e8a626ee..2a1651a5 100644 --- a/src/modules/applications/optimization/SAT/mappings/ChoiQUBO.py +++ b/src/modules/applications/optimization/SAT/mappings/ChoiQUBO.py @@ -18,7 +18,7 @@ from nnf import Var, And -from modules.applications.Mapping import Mapping, Core +from modules.applications.mapping import Mapping, Core from utils import start_time_measurement, end_time_measurement @@ -244,7 +244,7 @@ def get_default_submodule(self, option: str) -> Core: :raises NotImplementedError: If the option is not recognized """ if option == "Annealer": - from modules.solvers.Annealer import Annealer # pylint: disable=C0415 + from modules.solvers.annealer import Annealer # pylint: disable=C0415 return Annealer() else: raise NotImplementedError(f"Solver Option {option} not implemented") diff --git a/src/modules/applications/optimization/SAT/mappings/DinneenISING.py b/src/modules/applications/optimization/SAT/mappings/DinneenISING.py index f4767551..89e58672 100644 --- a/src/modules/applications/optimization/SAT/mappings/DinneenISING.py +++ b/src/modules/applications/optimization/SAT/mappings/DinneenISING.py @@ -18,8 +18,8 @@ from dimod import qubo_to_ising from nnf import And -from modules.applications.Mapping import Mapping, Core -from modules.applications.optimization.SAT.mappings.DinneenQUBO import DinneenQUBO +from modules.applications.mapping import Mapping, Core +from modules.applications.optimization.sat.mappings.dinneenqubo import DinneenQUBO from utils import start_time_measurement, end_time_measurement @@ -139,10 +139,10 @@ def get_default_submodule(self, option: str) -> Core: :raises NotImplementedError: If the option is not recognized """ if option == "QAOA": - from modules.solvers.QAOA import QAOA # pylint: disable=C0415 + from modules.solvers.qaoa import QAOA # pylint: disable=C0415 return QAOA() if option == "PennylaneQAOA": - from modules.solvers.PennylaneQAOA import PennylaneQAOA # pylint: disable=C0415 + from modules.solvers.pennylane_qaoa import PennylaneQAOA # pylint: disable=C0415 return PennylaneQAOA() else: raise NotImplementedError(f"Solver Option {option} not implemented") diff --git a/src/modules/applications/optimization/SAT/mappings/DinneenQUBO.py b/src/modules/applications/optimization/SAT/mappings/DinneenQUBO.py index 5a700f29..791e30f9 100644 --- a/src/modules/applications/optimization/SAT/mappings/DinneenQUBO.py +++ b/src/modules/applications/optimization/SAT/mappings/DinneenQUBO.py @@ -18,7 +18,7 @@ from nnf import And -from modules.applications.Mapping import Mapping, Core +from modules.applications.mapping import Mapping, Core from utils import start_time_measurement, end_time_measurement @@ -192,7 +192,7 @@ def get_default_submodule(self, option: str) -> Core: :raises NotImplementedError: If the option is not recognized """ if option == "Annealer": - from modules.solvers.Annealer import Annealer # pylint: disable=C0415 + from modules.solvers.annealer import Annealer # pylint: disable=C0415 return Annealer() else: raise NotImplementedError(f"Solver Option {option} not implemented") diff --git a/src/modules/applications/optimization/SAT/mappings/Direct.py b/src/modules/applications/optimization/SAT/mappings/Direct.py index fb889e10..85b874c4 100644 --- a/src/modules/applications/optimization/SAT/mappings/Direct.py +++ b/src/modules/applications/optimization/SAT/mappings/Direct.py @@ -20,7 +20,7 @@ from nnf.dimacs import dump from pysat.formula import CNF, WCNF -from modules.applications.Mapping import Mapping, Core +from modules.applications.mapping import Mapping, Core from utils import start_time_measurement, end_time_measurement @@ -119,10 +119,10 @@ def get_default_submodule(self, option: str) -> Core: :raises NotImplementedError: If the option is not recognized """ if option == "ClassicalSAT": - from modules.solvers.ClassicalSAT import ClassicalSAT # pylint: disable=C0415 + from modules.solvers.classical_sat import ClassicalSAT # pylint: disable=C0415 return ClassicalSAT() elif option == "RandomSAT": - from modules.solvers.RandomClassicalSAT import RandomSAT # pylint: disable=C0415 + from modules.solvers.random_classical_sat import RandomSAT # pylint: disable=C0415 return RandomSAT() else: raise NotImplementedError(f"Solver Option {option} not implemented") diff --git a/src/modules/applications/optimization/SAT/mappings/QubovertQUBO.py b/src/modules/applications/optimization/SAT/mappings/QubovertQUBO.py index 4d763d3b..3b7e2250 100644 --- a/src/modules/applications/optimization/SAT/mappings/QubovertQUBO.py +++ b/src/modules/applications/optimization/SAT/mappings/QubovertQUBO.py @@ -18,7 +18,7 @@ from qubovert.sat import NOT, OR, AND from nnf import And -from modules.applications.Mapping import Mapping, Core +from modules.applications.mapping import Mapping, Core from utils import start_time_measurement, end_time_measurement @@ -189,7 +189,7 @@ def get_default_submodule(self, option: str) -> Core: :raises NotImplementedError: If the option is not recognized """ if option == "Annealer": - from modules.solvers.Annealer import Annealer # pylint: disable=C0415 + from modules.solvers.annealer import Annealer # pylint: disable=C0415 return Annealer() else: raise NotImplementedError(f"Solver Option {option} not implemented") diff --git a/src/modules/applications/optimization/SCP/SCP.py b/src/modules/applications/optimization/SCP/SCP.py index 7ac11ada..cf533dbb 100644 --- a/src/modules/applications/optimization/SCP/SCP.py +++ b/src/modules/applications/optimization/SCP/SCP.py @@ -16,8 +16,8 @@ import pickle import os -from modules.applications.Application import Application -from modules.applications.optimization.Optimization import Optimization +from modules.applications.application import Application +from modules.applications.optimization.optimization import Optimization from utils import start_time_measurement, end_time_measurement @@ -60,7 +60,7 @@ def get_default_submodule(self, option: str) -> Application: :raises NotImplementedError: If the option is not recognized """ if option == "qubovertQUBO": - from modules.applications.optimization.SCP.mappings.qubovertQUBO import QubovertQUBO # pylint: disable=C0415 + from modules.applications.optimization.scp.mappings.qubovertqubo import QubovertQUBO # pylint: disable=C0415 return QubovertQUBO() else: raise NotImplementedError(f"Mapping Option {option} not implemented") diff --git a/src/modules/applications/optimization/SCP/mappings/qubovertQUBO.py b/src/modules/applications/optimization/SCP/mappings/qubovertQUBO.py index d6e0e312..6e852018 100644 --- a/src/modules/applications/optimization/SCP/mappings/qubovertQUBO.py +++ b/src/modules/applications/optimization/SCP/mappings/qubovertQUBO.py @@ -16,7 +16,7 @@ from typing import TypedDict from qubovert.problems import SetCover -from modules.applications.Mapping import Mapping, Core +from modules.applications.mapping import Mapping, Core from utils import start_time_measurement, end_time_measurement @@ -139,7 +139,7 @@ def get_default_submodule(self, option: str) -> Core: :raises NotImplementedError: If the option is not recognized """ if option == "Annealer": - from modules.solvers.Annealer import Annealer # pylint: disable=C0415 + from modules.solvers.annealer import Annealer # pylint: disable=C0415 return Annealer() else: raise NotImplementedError(f"Solver Option {option} not implemented") diff --git a/src/modules/applications/optimization/TSP/TSP.py b/src/modules/applications/optimization/TSP/TSP.py index 32392e59..cab27b28 100644 --- a/src/modules/applications/optimization/TSP/TSP.py +++ b/src/modules/applications/optimization/TSP/TSP.py @@ -21,8 +21,8 @@ import matplotlib.pyplot as plt import numpy as np -from modules.applications.Application import Core -from modules.applications.optimization.Optimization import Optimization +from modules.applications.application import Core +from modules.applications.optimization.optimization import Optimization from utils import start_time_measurement, end_time_measurement @@ -78,19 +78,19 @@ def get_default_submodule(self, option: str) -> Core: :raises NotImplemented: If the provided option is not implemented """ if option == "Ising": - from modules.applications.optimization.TSP.mappings.ISING import Ising # pylint: disable=C0415 + from modules.applications.optimization.tsp.mappings.ising import Ising # pylint: disable=C0415 return Ising() elif option == "QUBO": - from modules.applications.optimization.TSP.mappings.QUBO import QUBO # pylint: disable=C0415 + from modules.applications.optimization.tsp.mappings.qubo import QUBO # pylint: disable=C0415 return QUBO() elif option == "GreedyClassicalTSP": - from modules.solvers.GreedyClassicalTSP import GreedyClassicalTSP # pylint: disable=C0415 + from modules.solvers.greedy_classical_tsp import GreedyClassicalTSP # pylint: disable=C0415 return GreedyClassicalTSP() elif option == "ReverseGreedyClassicalTSP": - from modules.solvers.ReverseGreedyClassicalTSP import ReverseGreedyClassicalTSP # pylint: disable=C0415 + from modules.solvers.reverse_greedy_classical_tsp import ReverseGreedyClassicalTSP # pylint: disable=C0415 return ReverseGreedyClassicalTSP() elif option == "RandomTSP": - from modules.solvers.RandomClassicalTSP import RandomTSP # pylint: disable=C0415 + from modules.solvers.random_classical_tsp import RandomTSP # pylint: disable=C0415 return RandomTSP() else: raise NotImplementedError(f"Mapping Option {option} not implemented") diff --git a/src/modules/applications/optimization/TSP/mappings/ISING.py b/src/modules/applications/optimization/TSP/mappings/ISING.py index 056f21df..3b4d01fa 100644 --- a/src/modules/applications/optimization/TSP/mappings/ISING.py +++ b/src/modules/applications/optimization/TSP/mappings/ISING.py @@ -23,8 +23,8 @@ from qiskit_optimization.applications import Tsp from qiskit_optimization.converters import QuadraticProgramToQubo -from modules.applications.Mapping import Mapping, Core -from modules.applications.optimization.TSP.mappings.QUBO import QUBO +from modules.applications.mapping import Mapping, Core +from modules.applications.optimization.tsp.mappings.qubo import QUBO from utils import start_time_measurement, end_time_measurement @@ -261,13 +261,13 @@ def get_default_submodule(self, option: str) -> Core: :raises NotImplemented: If the provided option is not implemented """ if option == "QAOA": - from modules.solvers.QAOA import QAOA # pylint: disable=C0415 + from modules.solvers.qaoa import QAOA # pylint: disable=C0415 return QAOA() elif option == "PennylaneQAOA": - from modules.solvers.PennylaneQAOA import PennylaneQAOA # pylint: disable=C0415 + from modules.solvers.pennylane_qaoa import PennylaneQAOA # pylint: disable=C0415 return PennylaneQAOA() elif option == "QiskitQAOA": - from modules.solvers.QiskitQAOA import QiskitQAOA # pylint: disable=C0415 + from modules.solvers.qiskit_qaoa import QiskitQAOA # pylint: disable=C0415 return QiskitQAOA() else: raise NotImplementedError(f"Solver Option {option} not implemented") diff --git a/src/modules/applications/optimization/TSP/mappings/QUBO.py b/src/modules/applications/optimization/TSP/mappings/QUBO.py index 9adde638..59eaa831 100644 --- a/src/modules/applications/optimization/TSP/mappings/QUBO.py +++ b/src/modules/applications/optimization/TSP/mappings/QUBO.py @@ -18,7 +18,7 @@ import dwave_networkx as dnx import networkx -from modules.applications.Mapping import Mapping, Core +from modules.applications.mapping import Mapping, Core from utils import start_time_measurement, end_time_measurement @@ -119,7 +119,7 @@ def get_default_submodule(self, option: str) -> Core: """ if option == "Annealer": - from modules.solvers.Annealer import Annealer # pylint: disable=C0415 + from modules.solvers.annealer import Annealer # pylint: disable=C0415 return Annealer() else: raise NotImplementedError(f"Solver Option {option} not implemented") diff --git a/src/modules/applications/qml/Circuit.py b/src/modules/applications/qml/Circuit.py index 0a7b4c3f..7ff2cf6c 100644 --- a/src/modules/applications/qml/Circuit.py +++ b/src/modules/applications/qml/Circuit.py @@ -13,7 +13,7 @@ # limitations under the License. from abc import ABC, abstractmethod -from modules.Core import Core +from modules.core import Core class Circuit(Core, ABC): diff --git a/src/modules/applications/qml/QML.py b/src/modules/applications/qml/QML.py index 91f34fc5..5720fcce 100644 --- a/src/modules/applications/qml/QML.py +++ b/src/modules/applications/qml/QML.py @@ -14,7 +14,7 @@ from abc import ABC, abstractmethod -from modules.applications.Application import Application +from modules.applications.application import Application class QML(Application, ABC): diff --git a/src/modules/applications/qml/DataHandler.py b/src/modules/applications/qml/data_handler.py similarity index 100% rename from src/modules/applications/qml/DataHandler.py rename to src/modules/applications/qml/data_handler.py diff --git a/src/modules/applications/qml/generative_modeling/circuits/CircuitCardinality.py b/src/modules/applications/qml/generative_modeling/circuits/circuit_cardinality.py similarity index 90% rename from src/modules/applications/qml/generative_modeling/circuits/CircuitCardinality.py rename to src/modules/applications/qml/generative_modeling/circuits/circuit_cardinality.py index 06c73c3d..fc990fb8 100644 --- a/src/modules/applications/qml/generative_modeling/circuits/CircuitCardinality.py +++ b/src/modules/applications/qml/generative_modeling/circuits/circuit_cardinality.py @@ -14,11 +14,11 @@ from typing import Union, TypedDict -from modules.applications.qml.generative_modeling.circuits.CircuitGenerative import CircuitGenerative -from modules.applications.qml.generative_modeling.mappings.LibraryQiskit import LibraryQiskit -from modules.applications.qml.generative_modeling.mappings.LibraryPennylane import LibraryPennylane -from modules.applications.qml.generative_modeling.mappings.PresetQiskitNoisyBackend import PresetQiskitNoisyBackend -from modules.applications.qml.generative_modeling.mappings.CustomQiskitNoisyBackend import CustomQiskitNoisyBackend +from modules.applications.qml.generative_modeling.circuits.circuit_generative import CircuitGenerative +from modules.applications.qml.generative_modeling.mappings.library_qiskit import LibraryQiskit +from modules.applications.qml.generative_modeling.mappings.library_pennylane import LibraryPennylane +from modules.applications.qml.generative_modeling.mappings.preset_qiskit_noisy_backend import PresetQiskitNoisyBackend +from modules.applications.qml.generative_modeling.mappings.custom_qiskit_noisy_backend import CustomQiskitNoisyBackend class CircuitCardinality(CircuitGenerative): diff --git a/src/modules/applications/qml/generative_modeling/circuits/CircuitCopula.py b/src/modules/applications/qml/generative_modeling/circuits/circuit_copula.py similarity index 90% rename from src/modules/applications/qml/generative_modeling/circuits/CircuitCopula.py rename to src/modules/applications/qml/generative_modeling/circuits/circuit_copula.py index c2368474..ea0e9f0c 100644 --- a/src/modules/applications/qml/generative_modeling/circuits/CircuitCopula.py +++ b/src/modules/applications/qml/generative_modeling/circuits/circuit_copula.py @@ -16,11 +16,11 @@ from itertools import combinations from scipy.special import binom -from modules.applications.qml.generative_modeling.circuits.CircuitGenerative import CircuitGenerative -from modules.applications.qml.generative_modeling.mappings.LibraryQiskit import LibraryQiskit -from modules.applications.qml.generative_modeling.mappings.LibraryPennylane import LibraryPennylane -from modules.applications.qml.generative_modeling.mappings.PresetQiskitNoisyBackend import PresetQiskitNoisyBackend -from modules.applications.qml.generative_modeling.mappings.CustomQiskitNoisyBackend import CustomQiskitNoisyBackend +from modules.applications.qml.generative_modeling.circuits.circuit_generative import CircuitGenerative +from modules.applications.qml.generative_modeling.mappings.library_qiskit import LibraryQiskit +from modules.applications.qml.generative_modeling.mappings.library_pennylane import LibraryPennylane +from modules.applications.qml.generative_modeling.mappings.preset_qiskit_noisy_backend import PresetQiskitNoisyBackend +from modules.applications.qml.generative_modeling.mappings.custom_qiskit_noisy_backend import CustomQiskitNoisyBackend class CircuitCopula(CircuitGenerative): diff --git a/src/modules/applications/qml/generative_modeling/circuits/CircuitGenerative.py b/src/modules/applications/qml/generative_modeling/circuits/circuit_generative.py similarity index 97% rename from src/modules/applications/qml/generative_modeling/circuits/CircuitGenerative.py rename to src/modules/applications/qml/generative_modeling/circuits/circuit_generative.py index f6824ffa..c28457b5 100644 --- a/src/modules/applications/qml/generative_modeling/circuits/CircuitGenerative.py +++ b/src/modules/applications/qml/generative_modeling/circuits/circuit_generative.py @@ -13,10 +13,10 @@ # limitations under the License. from abc import ABC -from modules.Core import Core +from modules.core import Core from utils import start_time_measurement, end_time_measurement -from modules.applications.qml.Circuit import Circuit +from modules.applications.qml.circuit import Circuit class CircuitGenerative(Circuit, Core, ABC): diff --git a/src/modules/applications/qml/generative_modeling/circuits/CircuitStandard.py b/src/modules/applications/qml/generative_modeling/circuits/circuit_standard.py similarity index 89% rename from src/modules/applications/qml/generative_modeling/circuits/CircuitStandard.py rename to src/modules/applications/qml/generative_modeling/circuits/circuit_standard.py index 6d419de9..23f61424 100644 --- a/src/modules/applications/qml/generative_modeling/circuits/CircuitStandard.py +++ b/src/modules/applications/qml/generative_modeling/circuits/circuit_standard.py @@ -14,11 +14,11 @@ from typing import Union, TypedDict -from modules.applications.qml.generative_modeling.circuits.CircuitGenerative import CircuitGenerative -from modules.applications.qml.generative_modeling.mappings.LibraryQiskit import LibraryQiskit -from modules.applications.qml.generative_modeling.mappings.LibraryPennylane import LibraryPennylane -from modules.applications.qml.generative_modeling.mappings.PresetQiskitNoisyBackend import PresetQiskitNoisyBackend -from modules.applications.qml.generative_modeling.mappings.CustomQiskitNoisyBackend import CustomQiskitNoisyBackend +from modules.applications.qml.generative_modeling.circuits.circuit_generative import CircuitGenerative +from modules.applications.qml.generative_modeling.mappings.library_qiskit import LibraryQiskit +from modules.applications.qml.generative_modeling.mappings.library_pennylane import LibraryPennylane +from modules.applications.qml.generative_modeling.mappings.preset_qiskit_noisy_backend import PresetQiskitNoisyBackend +from modules.applications.qml.generative_modeling.mappings.custom_qiskit_noisy_backend import CustomQiskitNoisyBackend class CircuitStandard(CircuitGenerative): diff --git a/src/modules/applications/qml/generative_modeling/data/data_handler/ContinuousData.py b/src/modules/applications/qml/generative_modeling/data/data_handler/continuous_data.py similarity index 98% rename from src/modules/applications/qml/generative_modeling/data/data_handler/ContinuousData.py rename to src/modules/applications/qml/generative_modeling/data/data_handler/continuous_data.py index 46aa51c5..3a76a35e 100644 --- a/src/modules/applications/qml/generative_modeling/data/data_handler/ContinuousData.py +++ b/src/modules/applications/qml/generative_modeling/data/data_handler/continuous_data.py @@ -19,9 +19,9 @@ import pkg_resources from utils import start_time_measurement, end_time_measurement -from modules.applications.qml.generative_modeling.transformations.MinMax import MinMax -from modules.applications.qml.generative_modeling.transformations.PIT import PIT -from modules.applications.qml.generative_modeling.data.data_handler.DataHandlerGenerative import DataHandlerGenerative +from modules.applications.qml.generative_modeling.transformations.min_max import MinMax +from modules.applications.qml.generative_modeling.transformations.pit import PIT +from modules.applications.qml.generative_modeling.data.data_handler.data_handler_generative import DataHandlerGenerative class ContinuousData(DataHandlerGenerative): diff --git a/src/modules/applications/qml/generative_modeling/data/data_handler/DataHandlerGenerative.py b/src/modules/applications/qml/generative_modeling/data/data_handler/data_handler_generative.py similarity index 98% rename from src/modules/applications/qml/generative_modeling/data/data_handler/DataHandlerGenerative.py rename to src/modules/applications/qml/generative_modeling/data/data_handler/data_handler_generative.py index 09c4c491..be55733c 100644 --- a/src/modules/applications/qml/generative_modeling/data/data_handler/DataHandlerGenerative.py +++ b/src/modules/applications/qml/generative_modeling/data/data_handler/data_handler_generative.py @@ -19,8 +19,8 @@ import numpy as np -from modules.Core import Core -from modules.applications.qml.DataHandler import DataHandler +from modules.core import Core +from modules.applications.qml.data_handler import DataHandler from utils import start_time_measurement, end_time_measurement diff --git a/src/modules/applications/qml/generative_modeling/data/data_handler/DiscreteData.py b/src/modules/applications/qml/generative_modeling/data/data_handler/discrete_data.py similarity index 96% rename from src/modules/applications/qml/generative_modeling/data/data_handler/DiscreteData.py rename to src/modules/applications/qml/generative_modeling/data/data_handler/discrete_data.py index cf7fdebf..3336003d 100644 --- a/src/modules/applications/qml/generative_modeling/data/data_handler/DiscreteData.py +++ b/src/modules/applications/qml/generative_modeling/data/data_handler/discrete_data.py @@ -19,9 +19,9 @@ import numpy as np -from modules.applications.qml.generative_modeling.circuits.CircuitCardinality import CircuitCardinality -from modules.applications.qml.generative_modeling.data.data_handler.DataHandlerGenerative import DataHandlerGenerative -from modules.applications.qml.generative_modeling.metrics.MetricsGeneralization import MetricsGeneralization +from modules.applications.qml.generative_modeling.circuits.circuit_cardinality import CircuitCardinality +from modules.applications.qml.generative_modeling.data.data_handler.data_handler_generative import DataHandlerGenerative +from modules.applications.qml.generative_modeling.metrics.metrics_generalization import MetricsGeneralization from utils import start_time_measurement, end_time_measurement diff --git a/src/modules/applications/qml/generative_modeling/GenerativeModeling.py b/src/modules/applications/qml/generative_modeling/generative_modeling.py similarity index 97% rename from src/modules/applications/qml/generative_modeling/GenerativeModeling.py rename to src/modules/applications/qml/generative_modeling/generative_modeling.py index 8c61280a..9f5506b3 100644 --- a/src/modules/applications/qml/generative_modeling/GenerativeModeling.py +++ b/src/modules/applications/qml/generative_modeling/generative_modeling.py @@ -15,9 +15,9 @@ from typing import Union from utils import start_time_measurement, end_time_measurement -from modules.applications.qml.QML import QML -from modules.applications.qml.generative_modeling.data.data_handler.DiscreteData import DiscreteData -from modules.applications.qml.generative_modeling.data.data_handler.ContinuousData import ContinuousData +from modules.applications.qml.qml import QML +from modules.applications.qml.generative_modeling.data.data_handler.discrete_data import DiscreteData +from modules.applications.qml.generative_modeling.data.data_handler.continuous_data import ContinuousData class GenerativeModeling(QML): diff --git a/src/modules/applications/qml/generative_modeling/mappings/CustomQiskitNoisyBackend.py b/src/modules/applications/qml/generative_modeling/mappings/custom_qiskit_noisy_backend.py similarity index 98% rename from src/modules/applications/qml/generative_modeling/mappings/CustomQiskitNoisyBackend.py rename to src/modules/applications/qml/generative_modeling/mappings/custom_qiskit_noisy_backend.py index 55c4e23a..1f4f8d19 100644 --- a/src/modules/applications/qml/generative_modeling/mappings/CustomQiskitNoisyBackend.py +++ b/src/modules/applications/qml/generative_modeling/mappings/custom_qiskit_noisy_backend.py @@ -26,9 +26,9 @@ from qiskit_aer import Aer, AerSimulator, noise from qiskit_aer.noise import NoiseModel -from modules.applications.qml.generative_modeling.training.QCBM import QCBM -from modules.applications.qml.generative_modeling.training.Inference import Inference -from modules.applications.qml.generative_modeling.mappings.LibraryGenerative import LibraryGenerative +from modules.applications.qml.generative_modeling.training.qcbm import QCBM +from modules.applications.qml.generative_modeling.training.inference import Inference +from modules.applications.qml.generative_modeling.mappings.library_generative import LibraryGenerative logging.getLogger("NoisyQiskit").setLevel(logging.WARNING) diff --git a/src/modules/applications/qml/generative_modeling/mappings/LibraryGenerative.py b/src/modules/applications/qml/generative_modeling/mappings/library_generative.py similarity index 95% rename from src/modules/applications/qml/generative_modeling/mappings/LibraryGenerative.py rename to src/modules/applications/qml/generative_modeling/mappings/library_generative.py index d429f758..9c5d9c17 100644 --- a/src/modules/applications/qml/generative_modeling/mappings/LibraryGenerative.py +++ b/src/modules/applications/qml/generative_modeling/mappings/library_generative.py @@ -17,8 +17,8 @@ from typing import TypedDict from utils import start_time_measurement, end_time_measurement -from modules.Core import Core -from modules.applications.qml.Model import Model +from modules.core import Core +from modules.applications.qml.model import Model class LibraryGenerative(Core, Model, ABC): diff --git a/src/modules/applications/qml/generative_modeling/mappings/LibraryPennylane.py b/src/modules/applications/qml/generative_modeling/mappings/library_pennylane.py similarity index 96% rename from src/modules/applications/qml/generative_modeling/mappings/LibraryPennylane.py rename to src/modules/applications/qml/generative_modeling/mappings/library_pennylane.py index 345133d7..3c74b4e4 100644 --- a/src/modules/applications/qml/generative_modeling/mappings/LibraryPennylane.py +++ b/src/modules/applications/qml/generative_modeling/mappings/library_pennylane.py @@ -19,10 +19,10 @@ from jax import numpy as jnp import jax -from modules.applications.qml.generative_modeling.mappings.LibraryGenerative import LibraryGenerative -from modules.applications.qml.generative_modeling.training.Inference import Inference -from modules.applications.qml.generative_modeling.training.QGAN import QGAN -from modules.applications.qml.generative_modeling.training.QCBM import QCBM +from modules.applications.qml.generative_modeling.mappings.library_generative import LibraryGenerative +from modules.applications.qml.generative_modeling.training.inference import Inference +from modules.applications.qml.generative_modeling.training.qgan import QGAN +from modules.applications.qml.generative_modeling.training.qcbm import QCBM jax.config.update("jax_enable_x64", True) diff --git a/src/modules/applications/qml/generative_modeling/mappings/LibraryQiskit.py b/src/modules/applications/qml/generative_modeling/mappings/library_qiskit.py similarity index 96% rename from src/modules/applications/qml/generative_modeling/mappings/LibraryQiskit.py rename to src/modules/applications/qml/generative_modeling/mappings/library_qiskit.py index 55f81d76..8f502143 100644 --- a/src/modules/applications/qml/generative_modeling/mappings/LibraryQiskit.py +++ b/src/modules/applications/qml/generative_modeling/mappings/library_qiskit.py @@ -21,10 +21,10 @@ from qiskit.providers import Backend from qiskit.quantum_info import Statevector -from modules.applications.qml.generative_modeling.training.QCBM import QCBM -from modules.applications.qml.generative_modeling.training.QGAN import QGAN -from modules.applications.qml.generative_modeling.training.Inference import Inference -from modules.applications.qml.generative_modeling.mappings.LibraryGenerative import LibraryGenerative +from modules.applications.qml.generative_modeling.training.qcbm import QCBM +from modules.applications.qml.generative_modeling.training.qgan import QGAN +from modules.applications.qml.generative_modeling.training.inference import Inference +from modules.applications.qml.generative_modeling.mappings.library_generative import LibraryGenerative logging.getLogger("qiskit").setLevel(logging.WARNING) @@ -201,7 +201,7 @@ def select_backend(config: str, n_qubits: int) -> any: backend = Aer.get_backend('statevector_simulator') backend.set_options(device="CPU") elif config == "ionQ_Harmony": - from modules.devices.braket.Ionq import Ionq # pylint: disable=C0415 + from modules.devices.braket.ionq import Ionq # pylint: disable=C0415 from qiskit_braket_provider import AWSBraketBackend, AWSBraketProvider # pylint: disable=C0415 device_wrapper = Ionq("ionQ", "arn:aws:braket:::device/qpu/ionq/ionQdevice") backend = AWSBraketBackend( @@ -213,7 +213,7 @@ def select_backend(config: str, n_qubits: int) -> any: backend_version="2", ) elif config == "Amazon_SV1": - from modules.devices.braket.SV1 import SV1 # pylint: disable=C0415 + from modules.devices.braket.sv1 import SV1 # pylint: disable=C0415 from qiskit_braket_provider import AWSBraketBackend, AWSBraketProvider # pylint: disable=C0415 device_wrapper = SV1("SV1", "arn:aws:braket:::device/quantum-simulator/amazon/sv1") backend = AWSBraketBackend( diff --git a/src/modules/applications/qml/generative_modeling/mappings/PresetQiskitNoisyBackend.py b/src/modules/applications/qml/generative_modeling/mappings/preset_qiskit_noisy_backend.py similarity index 98% rename from src/modules/applications/qml/generative_modeling/mappings/PresetQiskitNoisyBackend.py rename to src/modules/applications/qml/generative_modeling/mappings/preset_qiskit_noisy_backend.py index 0730fbce..dfa6529c 100644 --- a/src/modules/applications/qml/generative_modeling/mappings/PresetQiskitNoisyBackend.py +++ b/src/modules/applications/qml/generative_modeling/mappings/preset_qiskit_noisy_backend.py @@ -23,9 +23,9 @@ from qiskit_aer import Aer, AerSimulator from qiskit_aer.noise import NoiseModel -from modules.applications.qml.generative_modeling.training.QCBM import QCBM -from modules.applications.qml.generative_modeling.training.Inference import Inference -from modules.applications.qml.generative_modeling.mappings.LibraryGenerative import LibraryGenerative +from modules.applications.qml.generative_modeling.training.qcbm import QCBM +from modules.applications.qml.generative_modeling.training.inference import Inference +from modules.applications.qml.generative_modeling.mappings.library_generative import LibraryGenerative logging.getLogger("NoisyQiskit").setLevel(logging.WARNING) diff --git a/src/modules/applications/qml/generative_modeling/metrics/MetricsGeneralization.py b/src/modules/applications/qml/generative_modeling/metrics/metrics_generalization.py similarity index 100% rename from src/modules/applications/qml/generative_modeling/metrics/MetricsGeneralization.py rename to src/modules/applications/qml/generative_modeling/metrics/metrics_generalization.py diff --git a/src/modules/applications/qml/generative_modeling/training/Inference.py b/src/modules/applications/qml/generative_modeling/training/Inference.py index 1330efbb..4bc221ad 100644 --- a/src/modules/applications/qml/generative_modeling/training/Inference.py +++ b/src/modules/applications/qml/generative_modeling/training/Inference.py @@ -14,7 +14,7 @@ from typing import TypedDict import numpy as np -from modules.applications.qml.generative_modeling.training.TrainingGenerative import TrainingGenerative, Core, GPU +from modules.applications.qml.generative_modeling.training.training_generative import TrainingGenerative, Core, GPU class Inference(TrainingGenerative): diff --git a/src/modules/applications/qml/generative_modeling/training/QCBM.py b/src/modules/applications/qml/generative_modeling/training/QCBM.py index a46db4b7..c9fab5cb 100644 --- a/src/modules/applications/qml/generative_modeling/training/QCBM.py +++ b/src/modules/applications/qml/generative_modeling/training/QCBM.py @@ -21,7 +21,7 @@ from matplotlib import pyplot as plt from matplotlib import figure, axes -from modules.applications.qml.generative_modeling.training.TrainingGenerative import TrainingGenerative, Core, GPU +from modules.applications.qml.generative_modeling.training.training_generative import TrainingGenerative, Core, GPU from utils_mpi import is_running_mpi, get_comm matplotlib.use('Agg') diff --git a/src/modules/applications/qml/generative_modeling/training/QGAN.py b/src/modules/applications/qml/generative_modeling/training/QGAN.py index 6c862d13..c743d1b7 100644 --- a/src/modules/applications/qml/generative_modeling/training/QGAN.py +++ b/src/modules/applications/qml/generative_modeling/training/QGAN.py @@ -24,7 +24,7 @@ import matplotlib import matplotlib.pyplot as plt -from modules.applications.qml.generative_modeling.training.TrainingGenerative import TrainingGenerative, Core +from modules.applications.qml.generative_modeling.training.training_generative import TrainingGenerative, Core from utils_mpi import is_running_mpi, get_comm matplotlib.use('Agg') diff --git a/src/modules/applications/qml/generative_modeling/training/TrainingGenerative.py b/src/modules/applications/qml/generative_modeling/training/training_generative.py similarity index 98% rename from src/modules/applications/qml/generative_modeling/training/TrainingGenerative.py rename to src/modules/applications/qml/generative_modeling/training/training_generative.py index bcd1d39b..7e45bb4e 100644 --- a/src/modules/applications/qml/generative_modeling/training/TrainingGenerative.py +++ b/src/modules/applications/qml/generative_modeling/training/training_generative.py @@ -25,8 +25,8 @@ GPU = False logging.info("CuPy not available, using vanilla numpy, data processing on CPU") -from modules.Core import Core -from modules.applications.qml.Training import Training +from modules.core import Core +from modules.applications.qml.training import Training from utils import start_time_measurement, end_time_measurement diff --git a/src/modules/applications/qml/generative_modeling/transformations/PIT.py b/src/modules/applications/qml/generative_modeling/transformations/PIT.py index 415a137c..23534ea1 100644 --- a/src/modules/applications/qml/generative_modeling/transformations/PIT.py +++ b/src/modules/applications/qml/generative_modeling/transformations/PIT.py @@ -15,8 +15,8 @@ import numpy as np import pandas as pd -from modules.applications.qml.generative_modeling.transformations.Transformation import Transformation -from modules.applications.qml.generative_modeling.circuits.CircuitCopula import CircuitCopula +from modules.applications.qml.generative_modeling.transformations.transformation import Transformation +from modules.applications.qml.generative_modeling.circuits.circuit_copula import CircuitCopula class PIT(Transformation): # pylint disable=R0902 diff --git a/src/modules/applications/qml/generative_modeling/transformations/Transformation.py b/src/modules/applications/qml/generative_modeling/transformations/Transformation.py index d76f4a2c..1a09d73c 100644 --- a/src/modules/applications/qml/generative_modeling/transformations/Transformation.py +++ b/src/modules/applications/qml/generative_modeling/transformations/Transformation.py @@ -17,7 +17,7 @@ import numpy as np -from modules.Core import Core +from modules.core import Core from utils import start_time_measurement, end_time_measurement diff --git a/src/modules/applications/qml/generative_modeling/transformations/MinMax.py b/src/modules/applications/qml/generative_modeling/transformations/min_max.py similarity index 97% rename from src/modules/applications/qml/generative_modeling/transformations/MinMax.py rename to src/modules/applications/qml/generative_modeling/transformations/min_max.py index 1b895f23..f4ee1fba 100644 --- a/src/modules/applications/qml/generative_modeling/transformations/MinMax.py +++ b/src/modules/applications/qml/generative_modeling/transformations/min_max.py @@ -15,9 +15,9 @@ from typing import Union import numpy as np -from modules.applications.qml.generative_modeling.transformations.Transformation import Transformation -from modules.applications.qml.generative_modeling.circuits.CircuitStandard import CircuitStandard -from modules.applications.qml.generative_modeling.circuits.CircuitCardinality import CircuitCardinality +from modules.applications.qml.generative_modeling.transformations.transformation import Transformation +from modules.applications.qml.generative_modeling.circuits.circuit_standard import CircuitStandard +from modules.applications.qml.generative_modeling.circuits.circuit_cardinality import CircuitCardinality class MinMax(Transformation): # pylint: disable=R0902 diff --git a/src/modules/devices/Device.py b/src/modules/devices/Device.py index a7d1b76e..4b9c402f 100644 --- a/src/modules/devices/Device.py +++ b/src/modules/devices/Device.py @@ -13,7 +13,7 @@ # limitations under the License. from abc import ABC -from modules.Core import Core +from modules.core import Core from utils import start_time_measurement, end_time_measurement diff --git a/src/modules/devices/Local.py b/src/modules/devices/Local.py index 65ee88de..bc04a427 100644 --- a/src/modules/devices/Local.py +++ b/src/modules/devices/Local.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from modules.devices.Device import Device +from modules.devices.device import Device class Local(Device): diff --git a/src/modules/devices/braket/Braket.py b/src/modules/devices/braket/Braket.py index b70c8bd2..00f755b9 100644 --- a/src/modules/devices/braket/Braket.py +++ b/src/modules/devices/braket/Braket.py @@ -23,7 +23,7 @@ from botocore.exceptions import ProfileNotFound from braket.aws import AwsSession -from modules.devices.Device import Device +from modules.devices.device import Device class Braket(Device, ABC): diff --git a/src/modules/devices/braket/Ionq.py b/src/modules/devices/braket/Ionq.py index d07a9cfa..ddbe03a3 100644 --- a/src/modules/devices/braket/Ionq.py +++ b/src/modules/devices/braket/Ionq.py @@ -15,7 +15,7 @@ import os from braket.aws import AwsDevice -from modules.devices.braket.Braket import Braket +from modules.devices.braket.braket import Braket class Ionq(Braket): diff --git a/src/modules/devices/braket/OQC.py b/src/modules/devices/braket/OQC.py index a5dd5f69..474f8fc5 100644 --- a/src/modules/devices/braket/OQC.py +++ b/src/modules/devices/braket/OQC.py @@ -15,7 +15,7 @@ import os from braket.aws import AwsDevice -from modules.devices.braket.Braket import Braket +from modules.devices.braket.braket import Braket class OQC(Braket): diff --git a/src/modules/devices/braket/Rigetti.py b/src/modules/devices/braket/Rigetti.py index d120811d..dad24949 100644 --- a/src/modules/devices/braket/Rigetti.py +++ b/src/modules/devices/braket/Rigetti.py @@ -15,7 +15,7 @@ import os from braket.aws import AwsDevice -from modules.devices.braket.Braket import Braket +from modules.devices.braket.braket import Braket class Rigetti(Braket): diff --git a/src/modules/devices/braket/SV1.py b/src/modules/devices/braket/SV1.py index 4258f46a..1b356fc5 100644 --- a/src/modules/devices/braket/SV1.py +++ b/src/modules/devices/braket/SV1.py @@ -15,7 +15,7 @@ import os from braket.aws import AwsDevice -from modules.devices.braket.Braket import Braket +from modules.devices.braket.braket import Braket class SV1(Braket): diff --git a/src/modules/devices/braket/TN1.py b/src/modules/devices/braket/TN1.py index 44f499f5..4bcad081 100644 --- a/src/modules/devices/braket/TN1.py +++ b/src/modules/devices/braket/TN1.py @@ -15,7 +15,7 @@ import os from braket.aws import AwsDevice -from modules.devices.braket.Braket import Braket +from modules.devices.braket.braket import Braket class TN1(Braket): diff --git a/src/modules/devices/braket/LocalSimulator.py b/src/modules/devices/braket/local_simulator.py similarity index 97% rename from src/modules/devices/braket/LocalSimulator.py rename to src/modules/devices/braket/local_simulator.py index df82288d..0590f802 100644 --- a/src/modules/devices/braket/LocalSimulator.py +++ b/src/modules/devices/braket/local_simulator.py @@ -14,7 +14,7 @@ from braket.devices import LocalSimulator as LocalSimulatorBraket -from modules.devices.braket.Braket import Braket +from modules.devices.braket.braket import Braket class LocalSimulator(Braket): diff --git a/src/modules/devices/HelperClass.py b/src/modules/devices/helper_class.py similarity index 97% rename from src/modules/devices/HelperClass.py rename to src/modules/devices/helper_class.py index a1d1e051..cb70669c 100644 --- a/src/modules/devices/HelperClass.py +++ b/src/modules/devices/helper_class.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from modules.devices.Device import Device +from modules.devices.device import Device class HelperClass(Device): diff --git a/src/modules/devices/pulser/Pulser.py b/src/modules/devices/pulser/Pulser.py index 11064cfc..0cae17aa 100644 --- a/src/modules/devices/pulser/Pulser.py +++ b/src/modules/devices/pulser/Pulser.py @@ -14,7 +14,7 @@ from abc import ABC, abstractmethod -from modules.devices.Device import Device +from modules.devices.device import Device class Pulser(Device, ABC): diff --git a/src/modules/devices/pulser/MockNeutralAtomDevice.py b/src/modules/devices/pulser/mock_neutral_atom_device.py similarity index 98% rename from src/modules/devices/pulser/MockNeutralAtomDevice.py rename to src/modules/devices/pulser/mock_neutral_atom_device.py index 9d5f0520..98869cd4 100644 --- a/src/modules/devices/pulser/MockNeutralAtomDevice.py +++ b/src/modules/devices/pulser/mock_neutral_atom_device.py @@ -20,7 +20,7 @@ from pulser.noise_model import NoiseModel from pulser.backend.config import EmulatorConfig -from modules.devices.pulser.Pulser import Pulser +from modules.devices.pulser.pulser import Pulser class MockNeutralAtomDevice(Pulser): diff --git a/src/modules/devices/qrisp_simulator/QrispSimulator.py b/src/modules/devices/qrisp_simulator/qrisp_simulator.py similarity index 97% rename from src/modules/devices/qrisp_simulator/QrispSimulator.py rename to src/modules/devices/qrisp_simulator/qrisp_simulator.py index 18bdfc2b..e5afed76 100644 --- a/src/modules/devices/qrisp_simulator/QrispSimulator.py +++ b/src/modules/devices/qrisp_simulator/qrisp_simulator.py @@ -15,8 +15,8 @@ from abc import ABC from typing import TypedDict -from modules.Core import Core -from modules.devices.Device import Device +from modules.core import Core +from modules.devices.device import Device class QrispSimulator(Device, ABC): diff --git a/src/modules/devices/SimulatedAnnealingSampler.py b/src/modules/devices/simulated_annealing_sampler.py similarity index 97% rename from src/modules/devices/SimulatedAnnealingSampler.py rename to src/modules/devices/simulated_annealing_sampler.py index b51aab3e..d73a81f1 100644 --- a/src/modules/devices/SimulatedAnnealingSampler.py +++ b/src/modules/devices/simulated_annealing_sampler.py @@ -14,7 +14,7 @@ import dwave.samplers -from modules.devices.Device import Device +from modules.devices.device import Device class SimulatedAnnealingSampler(Device): diff --git a/src/modules/solvers/Annealer.py b/src/modules/solvers/Annealer.py index ac08e540..c53314f0 100644 --- a/src/modules/solvers/Annealer.py +++ b/src/modules/solvers/Annealer.py @@ -15,8 +15,8 @@ from typing import TypedDict import logging -from modules.solvers.Solver import Solver -from modules.Core import Core +from modules.solvers.solver import Solver +from modules.core import Core from utils import start_time_measurement, end_time_measurement @@ -40,7 +40,7 @@ def get_default_submodule(self, option: str) -> Core: :return: Instance of the default submodule """ if option == "Simulated Annealer": - from modules.devices.SimulatedAnnealingSampler import SimulatedAnnealingSampler # pylint: disable=C0415 + from modules.devices.simulated_annealing_sampler import SimulatedAnnealingSampler # pylint: disable=C0415 return SimulatedAnnealingSampler() else: raise NotImplementedError(f"Device Option {option} not implemented") diff --git a/src/modules/solvers/QAOA.py b/src/modules/solvers/QAOA.py index 4d0ffc88..f447b812 100644 --- a/src/modules/solvers/QAOA.py +++ b/src/modules/solvers/QAOA.py @@ -21,8 +21,8 @@ from braket.aws import AwsDevice from scipy.optimize import minimize -from modules.solvers.Solver import Solver -from modules.Core import Core +from modules.solvers.solver import Solver +from modules.core import Core from utils import start_time_measurement, end_time_measurement @@ -65,19 +65,19 @@ def get_default_submodule(self, option: str) -> Core: """ if option == "arn:aws:braket:us-east-1::device/qpu/ionq/Harmony": - from modules.devices.braket.Ionq import Ionq # pylint: disable=C0415 + from modules.devices.braket.ionq import Ionq # pylint: disable=C0415 return Ionq("ionQ", "arn:aws:braket:us-east-1::device/qpu/ionq/Harmony") elif option == "arn:aws:braket:::device/quantum-simulator/amazon/sv1": - from modules.devices.braket.SV1 import SV1 # pylint: disable=C0415 + from modules.devices.braket.sv1 import SV1 # pylint: disable=C0415 return SV1("SV1", "arn:aws:braket:::device/quantum-simulator/amazon/sv1") elif option == "arn:aws:braket:::device/quantum-simulator/amazon/tn1": - from modules.devices.braket.TN1 import TN1 # pylint: disable=C0415 + from modules.devices.braket.tn1 import TN1 # pylint: disable=C0415 return TN1("TN1", "arn:aws:braket:::device/quantum-simulator/amazon/tn1") elif option == "arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-3": - from modules.devices.braket.Rigetti import Rigetti # pylint: disable=C0415 + from modules.devices.braket.rigetti import Rigetti # pylint: disable=C0415 return Rigetti("Rigetti Aspen-9", "arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-3") elif option == "LocalSimulator": - from modules.devices.braket.LocalSimulator import LocalSimulator # pylint: disable=C0415 + from modules.devices.braket.local_simulator import LocalSimulator # pylint: disable=C0415 return LocalSimulator("LocalSimulator") else: raise NotImplementedError(f"Device Option {option} not implemented") diff --git a/src/modules/solvers/Solver.py b/src/modules/solvers/Solver.py index 30238f6d..1ae3811e 100644 --- a/src/modules/solvers/Solver.py +++ b/src/modules/solvers/Solver.py @@ -13,7 +13,7 @@ # limitations under the License. from abc import ABC, abstractmethod -from modules.Core import Core +from modules.core import Core class Solver(Core, ABC): diff --git a/src/modules/solvers/ClassicalSAT.py b/src/modules/solvers/classical_sat.py similarity index 95% rename from src/modules/solvers/ClassicalSAT.py rename to src/modules/solvers/classical_sat.py index afc70f53..5afd345d 100644 --- a/src/modules/solvers/ClassicalSAT.py +++ b/src/modules/solvers/classical_sat.py @@ -18,8 +18,8 @@ from pysat.examples.rc2 import RC2 from pysat.formula import WCNF -from modules.solvers.Solver import Solver -from modules.Core import Core +from modules.solvers.solver import Solver +from modules.core import Core from utils import start_time_measurement, end_time_measurement @@ -52,7 +52,7 @@ def get_default_submodule(self, option: str) -> Core: :return: Instance of the default submodule """ if option == "Local": - from modules.devices.Local import Local # pylint: disable=C0415 + from modules.devices.local import Local # pylint: disable=C0415 return Local() else: raise NotImplementedError(f"Device Option {option} not implemented") diff --git a/src/modules/solvers/GreedyClassicalPVC.py b/src/modules/solvers/greedy_classical_pvc.py similarity index 96% rename from src/modules/solvers/GreedyClassicalPVC.py rename to src/modules/solvers/greedy_classical_pvc.py index 650d70cf..7d6482b2 100644 --- a/src/modules/solvers/GreedyClassicalPVC.py +++ b/src/modules/solvers/greedy_classical_pvc.py @@ -16,8 +16,8 @@ import networkx as nx -from modules.solvers.Solver import Solver -from modules.Core import Core +from modules.solvers.solver import Solver +from modules.core import Core from utils import start_time_measurement, end_time_measurement @@ -50,7 +50,7 @@ def get_default_submodule(self, option: str) -> Core: :return: Instance of the default submodule """ if option == "Local": - from modules.devices.Local import Local # pylint: disable=C0415 + from modules.devices.local import Local # pylint: disable=C0415 return Local() else: raise NotImplementedError(f"Device Option {option} not implemented") diff --git a/src/modules/solvers/GreedyClassicalTSP.py b/src/modules/solvers/greedy_classical_tsp.py similarity index 95% rename from src/modules/solvers/GreedyClassicalTSP.py rename to src/modules/solvers/greedy_classical_tsp.py index 528e48fb..bca0f024 100644 --- a/src/modules/solvers/GreedyClassicalTSP.py +++ b/src/modules/solvers/greedy_classical_tsp.py @@ -17,8 +17,8 @@ import networkx as nx from networkx.algorithms import approximation as approx -from modules.solvers.Solver import Solver -from modules.Core import Core +from modules.solvers.solver import Solver +from modules.core import Core from utils import start_time_measurement, end_time_measurement @@ -51,7 +51,7 @@ def get_default_submodule(self, option: str) -> Core: :return: Instance of the default submodule """ if option == "Local": - from modules.devices.Local import Local # pylint: disable=C0415 + from modules.devices.local import Local # pylint: disable=C0415 return Local() else: raise NotImplementedError(f"Device Option {option} not implemented") diff --git a/src/modules/solvers/MIPsolverACL.py b/src/modules/solvers/mip_solver_acl.py similarity index 96% rename from src/modules/solvers/MIPsolverACL.py rename to src/modules/solvers/mip_solver_acl.py index b6b93493..6ca30106 100644 --- a/src/modules/solvers/MIPsolverACL.py +++ b/src/modules/solvers/mip_solver_acl.py @@ -30,8 +30,8 @@ from typing import TypedDict import pulp -from modules.solvers.Solver import Solver -from modules.Core import Core +from modules.solvers.solver import Solver +from modules.core import Core from utils import start_time_measurement, end_time_measurement @@ -64,7 +64,7 @@ def get_default_submodule(self, option: str) -> Core: :return: Instance of the default submodule """ if option == "Local": - from modules.devices.Local import Local # pylint: disable=C0415 + from modules.devices.local import Local # pylint: disable=C0415 return Local() else: raise NotImplementedError(f"Device Option {option} not implemented") diff --git a/src/modules/solvers/NeutralAtomMIS.py b/src/modules/solvers/neutral_atom_mis.py similarity index 97% rename from src/modules/solvers/NeutralAtomMIS.py rename to src/modules/solvers/neutral_atom_mis.py index 1820ece0..ddfc46c6 100644 --- a/src/modules/solvers/NeutralAtomMIS.py +++ b/src/modules/solvers/neutral_atom_mis.py @@ -18,8 +18,8 @@ import numpy as np import pulser -from modules.solvers.Solver import Solver -from modules.Core import Core +from modules.solvers.solver import Solver +from modules.core import Core from utils import start_time_measurement, end_time_measurement @@ -52,7 +52,7 @@ def get_default_submodule(self, option: str) -> Core: :return: Instance of the default submodule """ if option == "MockNeutralAtomDevice": - from modules.devices.pulser.MockNeutralAtomDevice import MockNeutralAtomDevice # pylint: disable=C0415 + from modules.devices.pulser.mock_neutral_atom_device import MockNeutralAtomDevice # pylint: disable=C0415 return MockNeutralAtomDevice() else: raise NotImplementedError(f"Device Option {option} not implemented") diff --git a/src/modules/solvers/PennylaneQAOA.py b/src/modules/solvers/pennylane_qaoa.py similarity index 95% rename from src/modules/solvers/PennylaneQAOA.py rename to src/modules/solvers/pennylane_qaoa.py index 79253176..3eb655bc 100644 --- a/src/modules/solvers/PennylaneQAOA.py +++ b/src/modules/solvers/pennylane_qaoa.py @@ -28,8 +28,8 @@ import pennylane as qml from pennylane import numpy as npqml -from modules.solvers.Solver import Solver -from modules.Core import Core +from modules.solvers.solver import Solver +from modules.core import Core from utils import start_time_measurement, end_time_measurement matplotlib.use('Agg') @@ -82,37 +82,37 @@ def get_default_submodule(self, option: str) -> Core: """ if option == "arn:aws:braket:us-east-1::device/qpu/ionq/Harmony": - from modules.devices.braket.Ionq import Ionq # pylint: disable=C0415 + from modules.devices.braket.ionq import Ionq # pylint: disable=C0415 return Ionq("ionq", "arn:aws:braket:us-east-1::device/qpu/ionq/Harmony") elif option == "arn:aws:braket:::device/quantum-simulator/amazon/sv1": - from modules.devices.braket.SV1 import SV1 # pylint: disable=C0415 + from modules.devices.braket.sv1 import SV1 # pylint: disable=C0415 return SV1("SV1", "arn:aws:braket:::device/quantum-simulator/amazon/sv1") elif option == "arn:aws:braket:::device/quantum-simulator/amazon/tn1": - from modules.devices.braket.TN1 import TN1 # pylint: disable=C0415 + from modules.devices.braket.tn1 import TN1 # pylint: disable=C0415 return TN1("TN1", "arn:aws:braket:::device/quantum-simulator/amazon/tn1") elif option == "arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-3": - from modules.devices.braket.Rigetti import Rigetti # pylint: disable=C0415 + from modules.devices.braket.rigetti import Rigetti # pylint: disable=C0415 return Rigetti("Rigetti", "arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-3") elif option == "arn:aws:braket:eu-west-2::device/qpu/oqc/Lucy": - from modules.devices.braket.OQC import OQC # pylint: disable=C0415 + from modules.devices.braket.oqc import OQC # pylint: disable=C0415 return OQC("OQC", "arn:aws:braket:eu-west-2::device/qpu/oqc/Lucy") elif option == "braket.local.qubit": - from modules.devices.HelperClass import HelperClass # pylint: disable=C0415 + from modules.devices.helper_class import HelperClass # pylint: disable=C0415 return HelperClass("braket.local.qubit") elif option == "default.qubit": - from modules.devices.HelperClass import HelperClass # pylint: disable=C0415 + from modules.devices.helper_class import HelperClass # pylint: disable=C0415 return HelperClass("default.qubit") elif option == "default.qubit.autograd": - from modules.devices.HelperClass import HelperClass # pylint: disable=C0415 + from modules.devices.helper_class import HelperClass # pylint: disable=C0415 return HelperClass("default.qubit.autograd") elif option == "qulacs.simulator": - from modules.devices.HelperClass import HelperClass # pylint: disable=C0415 + from modules.devices.helper_class import HelperClass # pylint: disable=C0415 return HelperClass("qulacs.simulator") elif option == "lightning.gpu": - from modules.devices.HelperClass import HelperClass # pylint: disable=C0415 + from modules.devices.helper_class import HelperClass # pylint: disable=C0415 return HelperClass("lightning.gpu") elif option == "lightning.qubit": - from modules.devices.HelperClass import HelperClass # pylint: disable=C0415 + from modules.devices.helper_class import HelperClass # pylint: disable=C0415 return HelperClass("lightning.qubit") else: raise NotImplementedError(f"Device Option {option} not implemented") diff --git a/src/modules/solvers/QiskitQAOA.py b/src/modules/solvers/qiskit_qaoa.py similarity index 98% rename from src/modules/solvers/QiskitQAOA.py rename to src/modules/solvers/qiskit_qaoa.py index 11e65c86..4000c916 100644 --- a/src/modules/solvers/QiskitQAOA.py +++ b/src/modules/solvers/qiskit_qaoa.py @@ -24,8 +24,8 @@ from qiskit_algorithms.optimizers import POWELL, SPSA, COBYLA from qiskit_algorithms.minimum_eigensolvers import VQE, QAOA, NumPyMinimumEigensolver -from modules.solvers.Solver import Solver -from modules.Core import Core +from modules.solvers.solver import Solver +from modules.core import Core from utils import start_time_measurement, end_time_measurement @@ -64,7 +64,7 @@ def get_default_submodule(self, option: str) -> Core: :return: Instance of the default submodule """ if option in ["qasm_simulator", "qasm_simulator_gpu"]: - from modules.devices.HelperClass import HelperClass # pylint: disable=C0415 + from modules.devices.helper_class import HelperClass # pylint: disable=C0415 return HelperClass(option) else: raise NotImplementedError(f"Device Option {option} not implemented") diff --git a/src/modules/solvers/QrispQIRO.py b/src/modules/solvers/qrisp_qiro.py similarity index 97% rename from src/modules/solvers/QrispQIRO.py rename to src/modules/solvers/qrisp_qiro.py index 5b0cf859..eb04ffeb 100644 --- a/src/modules/solvers/QrispQIRO.py +++ b/src/modules/solvers/qrisp_qiro.py @@ -25,7 +25,7 @@ ) from qrisp.qaoa import create_max_indep_set_cl_cost_function -from modules.solvers.Solver import Solver, Core +from modules.solvers.solver import Solver, Core from utils import start_time_measurement, end_time_measurement @@ -59,7 +59,7 @@ def get_default_submodule(self, option: str) -> Core: :raises NotImplemented: If the provided option is not implemented """ if option == "qrisp_simulator": - from modules.devices.qrisp_simulator.QrispSimulator import QrispSimulator # pylint: disable=C0415 + from modules.devices.qrisp_simulator.qrisp_simulator import QrispSimulator # pylint: disable=C0415 return QrispSimulator() # pylint: disable=E1102 else: diff --git a/src/modules/solvers/RandomClassicalPVC.py b/src/modules/solvers/random_classical_pvc.py similarity index 96% rename from src/modules/solvers/RandomClassicalPVC.py rename to src/modules/solvers/random_classical_pvc.py index 05fd819d..b7d02aa6 100644 --- a/src/modules/solvers/RandomClassicalPVC.py +++ b/src/modules/solvers/random_classical_pvc.py @@ -16,8 +16,8 @@ import random import networkx as nx -from modules.solvers.Solver import Solver -from modules.Core import Core +from modules.solvers.solver import Solver +from modules.core import Core from utils import start_time_measurement, end_time_measurement @@ -50,7 +50,7 @@ def get_default_submodule(self, option: str) -> Core: :return: Instance of the default submodule """ if option == "Local": - from modules.devices.Local import Local # pylint: disable=C0415 + from modules.devices.local import Local # pylint: disable=C0415 return Local() else: raise NotImplementedError(f"Device Option {option} not implemented") diff --git a/src/modules/solvers/RandomClassicalSAT.py b/src/modules/solvers/random_classical_sat.py similarity index 95% rename from src/modules/solvers/RandomClassicalSAT.py rename to src/modules/solvers/random_classical_sat.py index 4dc94dea..6b823efb 100644 --- a/src/modules/solvers/RandomClassicalSAT.py +++ b/src/modules/solvers/random_classical_sat.py @@ -19,8 +19,8 @@ from pysat.formula import WCNF -from modules.solvers.Solver import Solver -from modules.Core import Core +from modules.solvers.solver import Solver +from modules.core import Core from utils import start_time_measurement, end_time_measurement @@ -50,7 +50,7 @@ def get_requirements() -> list[dict]: def get_default_submodule(self, option: str) -> Core: if option == "Local": - from modules.devices.Local import Local # pylint: disable=C0415 + from modules.devices.local import Local # pylint: disable=C0415 return Local() else: raise NotImplementedError(f"Device Option {option} not implemented") diff --git a/src/modules/solvers/RandomClassicalTSP.py b/src/modules/solvers/random_classical_tsp.py similarity index 95% rename from src/modules/solvers/RandomClassicalTSP.py rename to src/modules/solvers/random_classical_tsp.py index 19a79b6d..e12cdb89 100644 --- a/src/modules/solvers/RandomClassicalTSP.py +++ b/src/modules/solvers/random_classical_tsp.py @@ -16,8 +16,8 @@ import random import networkx as nx -from modules.solvers.Solver import Solver -from modules.Core import Core +from modules.solvers.solver import Solver +from modules.core import Core from utils import start_time_measurement, end_time_measurement @@ -44,7 +44,7 @@ def get_requirements() -> list[dict]: def get_default_submodule(self, option: str) -> Core: if option == "Local": - from modules.devices.Local import Local # pylint: disable=C0415 + from modules.devices.local import Local # pylint: disable=C0415 return Local() else: raise NotImplementedError(f"Device Option {option} not implemented") diff --git a/src/modules/solvers/ReverseGreedyClassicalPVC.py b/src/modules/solvers/reverse_greedy_classical_pvc.py similarity index 96% rename from src/modules/solvers/ReverseGreedyClassicalPVC.py rename to src/modules/solvers/reverse_greedy_classical_pvc.py index 8f60ed4a..ab8091b7 100644 --- a/src/modules/solvers/ReverseGreedyClassicalPVC.py +++ b/src/modules/solvers/reverse_greedy_classical_pvc.py @@ -16,8 +16,8 @@ import networkx as nx -from modules.solvers.Solver import Solver -from modules.Core import Core +from modules.solvers.solver import Solver +from modules.core import Core from utils import start_time_measurement, end_time_measurement @@ -41,7 +41,7 @@ def get_default_submodule(self, option: str) -> Core: :return: Instance of the default submodule """ if option == "Local": - from modules.devices.Local import Local # pylint: disable=C0415 + from modules.devices.local import Local # pylint: disable=C0415 return Local() else: raise NotImplementedError(f"Device Option {option} not implemented") diff --git a/src/modules/solvers/ReverseGreedyClassicalTSP.py b/src/modules/solvers/reverse_greedy_classical_tsp.py similarity index 96% rename from src/modules/solvers/ReverseGreedyClassicalTSP.py rename to src/modules/solvers/reverse_greedy_classical_tsp.py index e53784be..47f57e43 100644 --- a/src/modules/solvers/ReverseGreedyClassicalTSP.py +++ b/src/modules/solvers/reverse_greedy_classical_tsp.py @@ -17,8 +17,8 @@ import networkx as nx from networkx.algorithms import approximation as approx -from modules.solvers.Solver import Solver -from modules.Core import Core +from modules.solvers.solver import Solver +from modules.core import Core from utils import start_time_measurement, end_time_measurement @@ -52,7 +52,7 @@ def get_default_submodule(self, option: str) -> Core: :return: Instance of the default submodule """ if option == "Local": - from modules.devices.Local import Local # pylint: disable=C0415 + from modules.devices.local import Local # pylint: disable=C0415 return Local() else: raise NotImplementedError(f"Device Option {option} not implemented") diff --git a/src/quark2_adapter/adapters.py b/src/quark2_adapter/adapters.py index 6e864738..f6f2b3a7 100644 --- a/src/quark2_adapter/adapters.py +++ b/src/quark2_adapter/adapters.py @@ -18,15 +18,15 @@ import logging from time import time -from modules.Core import Core -from modules.applications.Application import Application as Application_NEW -from modules.applications.Mapping import Mapping as Mapping_NEW -from modules.solvers.Solver import Solver as Solver_NEW -from modules.devices.Device import Device as Device_NEW -from quark2_adapter.legacy_classes.Application import Application as Application_OLD -from quark2_adapter.legacy_classes.Mapping import Mapping as Mapping_OLD -from quark2_adapter.legacy_classes.Solver import Solver as Solver_OLD -from quark2_adapter.legacy_classes.Device import Device as Device_OLD +from modules.core import Core +from modules.applications.application import Application as Application_NEW +from modules.applications.mapping import Mapping as Mapping_NEW +from modules.solvers.solver import Solver as Solver_NEW +from modules.devices.device import Device as Device_NEW +from quark2_adapter.legacy_classes.application import Application as Application_OLD +from quark2_adapter.legacy_classes.mapping import Mapping as Mapping_OLD +from quark2_adapter.legacy_classes.solver import Solver as Solver_OLD +from quark2_adapter.legacy_classes.device import Device as Device_OLD WARNING_MSG = 'Class "%s" is inheriting from deprecated base class. Please refactor your class.' diff --git a/tests/configs/valid/GenerativeModeling.yml b/tests/configs/valid/generative_modeling.yml similarity index 100% rename from tests/configs/valid/GenerativeModeling.yml rename to tests/configs/valid/generative_modeling.yml diff --git a/tests/modules/applications/optimization/ACL/mappings/test_ISING.py b/tests/modules/applications/optimization/ACL/mappings/test_ISING.py index 81916969..0d6a1d8b 100644 --- a/tests/modules/applications/optimization/ACL/mappings/test_ISING.py +++ b/tests/modules/applications/optimization/ACL/mappings/test_ISING.py @@ -2,7 +2,7 @@ import numpy as np from qiskit_optimization import QuadraticProgram -from modules.applications.optimization.ACL.mappings.ISING import Ising +from src.modules.applications.optimization.acl.mappings.ising import Ising class TestIsing(unittest.TestCase): diff --git a/tests/modules/applications/optimization/ACL/mappings/test_QUBO.py b/tests/modules/applications/optimization/ACL/mappings/test_QUBO.py index 98bd0dc3..82a701fa 100644 --- a/tests/modules/applications/optimization/ACL/mappings/test_QUBO.py +++ b/tests/modules/applications/optimization/ACL/mappings/test_QUBO.py @@ -2,7 +2,7 @@ import numpy as np from qiskit_optimization import QuadraticProgram -from modules.applications.optimization.ACL.mappings.QUBO import Qubo +from src.modules.applications.optimization.acl.mappings.qubo import Qubo class TestQubo(unittest.TestCase): diff --git a/tests/modules/applications/optimization/ACL/test_ACL.py b/tests/modules/applications/optimization/ACL/test_ACL.py index 3b28c15b..6e2757d6 100644 --- a/tests/modules/applications/optimization/ACL/test_ACL.py +++ b/tests/modules/applications/optimization/ACL/test_ACL.py @@ -2,7 +2,7 @@ import os import pandas as pd from tempfile import TemporaryDirectory -from modules.applications.optimization.ACL.ACL import ACL +from src.modules.applications.optimization.acl.acl import ACL class TestACL(unittest.TestCase): diff --git a/tests/modules/applications/optimization/MIS/mappings/test_NeutralAtom.py b/tests/modules/applications/optimization/MIS/mappings/test_neutral_atom.py similarity index 95% rename from tests/modules/applications/optimization/MIS/mappings/test_NeutralAtom.py rename to tests/modules/applications/optimization/MIS/mappings/test_neutral_atom.py index f543ff76..15b7b3f2 100644 --- a/tests/modules/applications/optimization/MIS/mappings/test_NeutralAtom.py +++ b/tests/modules/applications/optimization/MIS/mappings/test_neutral_atom.py @@ -1,7 +1,7 @@ import unittest import pickle -from modules.applications.optimization.MIS.mappings.NeutralAtom import NeutralAtom +from modules.applications.optimization.mis.mappings.neutral_atom import NeutralAtom class TestNeutralAtom(unittest.TestCase): diff --git a/tests/modules/applications/optimization/MIS/test_MIS.py b/tests/modules/applications/optimization/MIS/test_MIS.py index 2230a96f..da72bc45 100644 --- a/tests/modules/applications/optimization/MIS/test_MIS.py +++ b/tests/modules/applications/optimization/MIS/test_MIS.py @@ -4,7 +4,7 @@ from tempfile import TemporaryDirectory import logging -from modules.applications.optimization.MIS.MIS import MIS +from modules.applications.optimization.mis.mis import MIS class TestMIS(unittest.TestCase): diff --git a/tests/modules/applications/optimization/PVC/mappings/test_ISING.py b/tests/modules/applications/optimization/PVC/mappings/test_ISING.py index 89067c82..c404260c 100644 --- a/tests/modules/applications/optimization/PVC/mappings/test_ISING.py +++ b/tests/modules/applications/optimization/PVC/mappings/test_ISING.py @@ -2,8 +2,8 @@ import numpy as np import pickle -from modules.applications.optimization.PVC.mappings.ISING import Ising -from modules.applications.optimization.PVC.mappings.QUBO import QUBO +from modules.applications.optimization.pvc.mappings.ISING import Ising +from modules.applications.optimization.pvc.mappings.qubo import QUBO class TestIsing(unittest.TestCase): diff --git a/tests/modules/applications/optimization/PVC/mappings/test_QUBO.py b/tests/modules/applications/optimization/PVC/mappings/test_QUBO.py index 16b57ad6..16c7d344 100644 --- a/tests/modules/applications/optimization/PVC/mappings/test_QUBO.py +++ b/tests/modules/applications/optimization/PVC/mappings/test_QUBO.py @@ -1,7 +1,7 @@ import unittest import pickle -from modules.applications.optimization.PVC.mappings.QUBO import QUBO +from modules.applications.optimization.pvc.mappings.qubo import QUBO class TestQUBO(unittest.TestCase): diff --git a/tests/modules/applications/optimization/PVC/test_PVC.py b/tests/modules/applications/optimization/PVC/test_PVC.py index cb492a0a..a4821b32 100644 --- a/tests/modules/applications/optimization/PVC/test_PVC.py +++ b/tests/modules/applications/optimization/PVC/test_PVC.py @@ -3,7 +3,7 @@ from tempfile import TemporaryDirectory from networkx import Graph -from modules.applications.optimization.PVC.PVC import PVC +from modules.applications.optimization.pvc.pvc import PVC class TestPVC(unittest.TestCase): diff --git a/tests/modules/applications/optimization/SAT/mappings/test_ChoiISING.py b/tests/modules/applications/optimization/SAT/mappings/test_ChoiISING.py index 77279480..4b55363e 100644 --- a/tests/modules/applications/optimization/SAT/mappings/test_ChoiISING.py +++ b/tests/modules/applications/optimization/SAT/mappings/test_ChoiISING.py @@ -2,7 +2,7 @@ import numpy as np from nnf import Var, And, Or -from modules.applications.optimization.SAT.mappings.ChoiISING import ChoiIsing +from modules.applications.optimization.sat.mappings.choiising import ChoiIsing class TestChoiIsing(unittest.TestCase): diff --git a/tests/modules/applications/optimization/SAT/mappings/test_ChoiQUBO.py b/tests/modules/applications/optimization/SAT/mappings/test_ChoiQUBO.py index 7d599f5a..2bd25e90 100644 --- a/tests/modules/applications/optimization/SAT/mappings/test_ChoiQUBO.py +++ b/tests/modules/applications/optimization/SAT/mappings/test_ChoiQUBO.py @@ -1,7 +1,7 @@ import unittest from nnf import Var, And, Or -from modules.applications.optimization.SAT.mappings.ChoiQUBO import ChoiQUBO +from modules.applications.optimization.sat.mappings.choiqubo import ChoiQUBO class TestChoiQUBO(unittest.TestCase): diff --git a/tests/modules/applications/optimization/SAT/mappings/test_DinneenISING.py b/tests/modules/applications/optimization/SAT/mappings/test_DinneenISING.py index 2d12fe66..36f8035a 100644 --- a/tests/modules/applications/optimization/SAT/mappings/test_DinneenISING.py +++ b/tests/modules/applications/optimization/SAT/mappings/test_DinneenISING.py @@ -2,7 +2,7 @@ import numpy as np from nnf import Var, And, Or -from modules.applications.optimization.SAT.mappings.DinneenISING import DinneenIsing +from modules.applications.optimization.sat.mappings.DinneenISING import DinneenIsing class TestDinneenIsing(unittest.TestCase): diff --git a/tests/modules/applications/optimization/SAT/mappings/test_DinneenQUBO.py b/tests/modules/applications/optimization/SAT/mappings/test_DinneenQUBO.py index 9f518cb6..b626eb0e 100644 --- a/tests/modules/applications/optimization/SAT/mappings/test_DinneenQUBO.py +++ b/tests/modules/applications/optimization/SAT/mappings/test_DinneenQUBO.py @@ -1,7 +1,7 @@ import unittest from nnf import Var, And, Or -from modules.applications.optimization.SAT.mappings.DinneenQUBO import DinneenQUBO +from modules.applications.optimization.sat.mappings.dinneenqubo import DinneenQUBO class TestDinneenQUBO(unittest.TestCase): diff --git a/tests/modules/applications/optimization/SAT/mappings/test_Direct.py b/tests/modules/applications/optimization/SAT/mappings/test_Direct.py index 736a4fa0..d3b42bee 100644 --- a/tests/modules/applications/optimization/SAT/mappings/test_Direct.py +++ b/tests/modules/applications/optimization/SAT/mappings/test_Direct.py @@ -2,7 +2,7 @@ from nnf import And, Var, Or from pysat.formula import WCNF -from modules.applications.optimization.SAT.mappings.Direct import Direct +from modules.applications.optimization.sat.mappings.direct import Direct class TestDirect(unittest.TestCase): diff --git a/tests/modules/applications/optimization/SAT/mappings/test_QubovertQUBO.py b/tests/modules/applications/optimization/SAT/mappings/test_QubovertQUBO.py index b614daa0..397da327 100644 --- a/tests/modules/applications/optimization/SAT/mappings/test_QubovertQUBO.py +++ b/tests/modules/applications/optimization/SAT/mappings/test_QubovertQUBO.py @@ -1,7 +1,7 @@ import unittest from nnf import Var, And, Or -from modules.applications.optimization.SAT.mappings.QubovertQUBO import QubovertQUBO +from modules.applications.optimization.sat.mappings.qubovertqubo import QubovertQUBO class TestQubovertQUBO(unittest.TestCase): diff --git a/tests/modules/applications/optimization/SAT/test_SAT.py b/tests/modules/applications/optimization/SAT/test_SAT.py index d166b641..0195da63 100644 --- a/tests/modules/applications/optimization/SAT/test_SAT.py +++ b/tests/modules/applications/optimization/SAT/test_SAT.py @@ -3,7 +3,7 @@ import nnf from tempfile import TemporaryDirectory -from modules.applications.optimization.SAT.SAT import SAT +from modules.applications.optimization.sat.sat import SAT class TestSAT(unittest.TestCase): diff --git a/tests/modules/applications/optimization/SCP/mappings/test_qubovertQUBO.py b/tests/modules/applications/optimization/SCP/mappings/test_qubovertQUBO.py index 65634e0d..8bcd8618 100644 --- a/tests/modules/applications/optimization/SCP/mappings/test_qubovertQUBO.py +++ b/tests/modules/applications/optimization/SCP/mappings/test_qubovertQUBO.py @@ -1,6 +1,6 @@ import unittest -from modules.applications.optimization.SCP.mappings.qubovertQUBO import QubovertQUBO +from modules.applications.optimization.scp.mappings.qubovertqubo import QubovertQUBO class TestQubovertQUBO(unittest.TestCase): diff --git a/tests/modules/applications/optimization/SCP/test_SCP.py b/tests/modules/applications/optimization/SCP/test_SCP.py index 57f52ec8..7f452d05 100644 --- a/tests/modules/applications/optimization/SCP/test_SCP.py +++ b/tests/modules/applications/optimization/SCP/test_SCP.py @@ -3,7 +3,7 @@ import pickle from tempfile import TemporaryDirectory -from modules.applications.optimization.SCP.SCP import SCP +from modules.applications.optimization.scp.scp import SCP class TestSCP(unittest.TestCase): diff --git a/tests/modules/applications/optimization/TSP/mappings/test_ISING.py b/tests/modules/applications/optimization/TSP/mappings/test_ISING.py index 078ec47b..7e703a7f 100644 --- a/tests/modules/applications/optimization/TSP/mappings/test_ISING.py +++ b/tests/modules/applications/optimization/TSP/mappings/test_ISING.py @@ -2,7 +2,7 @@ import networkx as nx import numpy as np -from modules.applications.optimization.TSP.mappings.ISING import Ising +from modules.applications.optimization.tsp.mappings.ising import Ising class TestIsing(unittest.TestCase): diff --git a/tests/modules/applications/optimization/TSP/mappings/test_QUBO.py b/tests/modules/applications/optimization/TSP/mappings/test_QUBO.py index 419e7cd1..9a6bad14 100644 --- a/tests/modules/applications/optimization/TSP/mappings/test_QUBO.py +++ b/tests/modules/applications/optimization/TSP/mappings/test_QUBO.py @@ -1,7 +1,7 @@ import unittest import networkx as nx -from modules.applications.optimization.TSP.mappings.QUBO import QUBO +from modules.applications.optimization.tsp.mappings.qubo import QUBO class TestQUBO(unittest.TestCase): diff --git a/tests/modules/applications/optimization/TSP/test_TSP.py b/tests/modules/applications/optimization/TSP/test_TSP.py index d6906a0b..10195ab2 100644 --- a/tests/modules/applications/optimization/TSP/test_TSP.py +++ b/tests/modules/applications/optimization/TSP/test_TSP.py @@ -5,7 +5,7 @@ from tempfile import TemporaryDirectory import logging -from modules.applications.optimization.TSP.TSP import TSP +from modules.applications.optimization.tsp.tsp import TSP class TestTSP(unittest.TestCase): diff --git a/tests/modules/applications/qml/generative_modeling/circuits/test_CircuitCardinality.py b/tests/modules/applications/qml/generative_modeling/circuits/test_circuit_cardinality.py similarity index 97% rename from tests/modules/applications/qml/generative_modeling/circuits/test_CircuitCardinality.py rename to tests/modules/applications/qml/generative_modeling/circuits/test_circuit_cardinality.py index 72198f37..d84bcd75 100644 --- a/tests/modules/applications/qml/generative_modeling/circuits/test_CircuitCardinality.py +++ b/tests/modules/applications/qml/generative_modeling/circuits/test_circuit_cardinality.py @@ -1,6 +1,6 @@ import unittest -from modules.applications.qml.generative_modeling.circuits.CircuitCardinality import CircuitCardinality +from modules.applications.qml.generative_modeling.circuits.circuit_cardinality import CircuitCardinality class TestCircuitCardinality(unittest.TestCase): diff --git a/tests/modules/applications/qml/generative_modeling/circuits/test_CircuitCopula.py b/tests/modules/applications/qml/generative_modeling/circuits/test_circuit_copula.py similarity index 97% rename from tests/modules/applications/qml/generative_modeling/circuits/test_CircuitCopula.py rename to tests/modules/applications/qml/generative_modeling/circuits/test_circuit_copula.py index 66744a07..9f0c17ff 100644 --- a/tests/modules/applications/qml/generative_modeling/circuits/test_CircuitCopula.py +++ b/tests/modules/applications/qml/generative_modeling/circuits/test_circuit_copula.py @@ -1,6 +1,6 @@ import unittest -from modules.applications.qml.generative_modeling.circuits.CircuitCopula import CircuitCopula +from modules.applications.qml.generative_modeling.circuits.circuit_copula import CircuitCopula class TestCircuitCopula(unittest.TestCase): diff --git a/tests/modules/applications/qml/generative_modeling/circuits/test_CircuitStandard.py b/tests/modules/applications/qml/generative_modeling/circuits/test_circuit_standard.py similarity index 97% rename from tests/modules/applications/qml/generative_modeling/circuits/test_CircuitStandard.py rename to tests/modules/applications/qml/generative_modeling/circuits/test_circuit_standard.py index 9a285ce6..b9f8f4dd 100644 --- a/tests/modules/applications/qml/generative_modeling/circuits/test_CircuitStandard.py +++ b/tests/modules/applications/qml/generative_modeling/circuits/test_circuit_standard.py @@ -1,6 +1,6 @@ import unittest -from modules.applications.qml.generative_modeling.circuits.CircuitStandard import CircuitStandard +from modules.applications.qml.generative_modeling.circuits.circuit_standard import CircuitStandard class TestCircuitStandard(unittest.TestCase): diff --git a/tests/modules/applications/qml/generative_modeling/data/data_handler/test_ContinuousData.py b/tests/modules/applications/qml/generative_modeling/data/data_handler/test_continuous_data.py similarity index 97% rename from tests/modules/applications/qml/generative_modeling/data/data_handler/test_ContinuousData.py rename to tests/modules/applications/qml/generative_modeling/data/data_handler/test_continuous_data.py index 044d3bf7..722cabea 100644 --- a/tests/modules/applications/qml/generative_modeling/data/data_handler/test_ContinuousData.py +++ b/tests/modules/applications/qml/generative_modeling/data/data_handler/test_continuous_data.py @@ -2,7 +2,7 @@ from unittest.mock import patch import numpy as np -from modules.applications.qml.generative_modeling.data.data_handler.ContinuousData import ContinuousData +from modules.applications.qml.generative_modeling.data.data_handler.continuous_data import ContinuousData class TestContinuousData(unittest.TestCase): @@ -50,10 +50,10 @@ def test_get_default_submodule(self): self.data_handler.get_default_submodule("InvalidSubmodule") @patch( - "modules.applications.qml.generative_modeling.data.data_handler.ContinuousData.pkg_resources.resource_filename" + "modules.applications.qml.generative_modeling.data.data_handler.continuous_data.pkg_resources.resource_filename" ) @patch( - "modules.applications.qml.generative_modeling.data.data_handler.ContinuousData.np.load" + "modules.applications.qml.generative_modeling.data.data_handler.continuous_data.np.load" ) def test_data_load(self, mock_np_load, mock_resource_filename): mock_resource_filename.return_value = "/mock/path/X_2D.npy" diff --git a/tests/modules/applications/qml/generative_modeling/data/data_handler/test_DiscreteData.py b/tests/modules/applications/qml/generative_modeling/data/data_handler/test_discrete_data.py similarity index 96% rename from tests/modules/applications/qml/generative_modeling/data/data_handler/test_DiscreteData.py rename to tests/modules/applications/qml/generative_modeling/data/data_handler/test_discrete_data.py index c8a421db..25196155 100644 --- a/tests/modules/applications/qml/generative_modeling/data/data_handler/test_DiscreteData.py +++ b/tests/modules/applications/qml/generative_modeling/data/data_handler/test_discrete_data.py @@ -2,8 +2,8 @@ from unittest.mock import MagicMock import numpy as np -from modules.applications.qml.generative_modeling.data.data_handler.DiscreteData import DiscreteData -from modules.applications.qml.generative_modeling.circuits.CircuitCardinality import CircuitCardinality +from modules.applications.qml.generative_modeling.data.data_handler.discrete_data import DiscreteData +from modules.applications.qml.generative_modeling.circuits.circuit_cardinality import CircuitCardinality from modules.applications.qml.generative_modeling.metrics.MetricsGeneralization import MetricsGeneralization diff --git a/tests/modules/applications/qml/generative_modeling/mappings/test_CustomQiskitNoisyBackend.py b/tests/modules/applications/qml/generative_modeling/mappings/test_custom_qiskit_noisy_backend.py similarity index 92% rename from tests/modules/applications/qml/generative_modeling/mappings/test_CustomQiskitNoisyBackend.py rename to tests/modules/applications/qml/generative_modeling/mappings/test_custom_qiskit_noisy_backend.py index 2b1ed57e..384a11c2 100644 --- a/tests/modules/applications/qml/generative_modeling/mappings/test_CustomQiskitNoisyBackend.py +++ b/tests/modules/applications/qml/generative_modeling/mappings/test_custom_qiskit_noisy_backend.py @@ -6,7 +6,7 @@ from qiskit_aer.noise import NoiseModel from qiskit.transpiler import CouplingMap -from modules.applications.qml.generative_modeling.mappings.CustomQiskitNoisyBackend import CustomQiskitNoisyBackend +from modules.applications.qml.generative_modeling.mappings.custom_qiskit_noisy_backend import CustomQiskitNoisyBackend class TestCustomQiskitNoisyBackend(unittest.TestCase): @@ -65,7 +65,7 @@ def test_sequence_to_circuit(self): self.assertIsInstance(output_data["circuit"], QuantumCircuit) self.assertEqual(output_data["n_params"], 1) # One parameterized gate in the sequence - @patch("modules.applications.qml.generative_modeling.mappings.CustomQiskitNoisyBackend.Aer.get_backend") + @patch("modules.applications.qml.generative_modeling.mappings.custom_qiskit_noisy_backend.Aer.get_backend") def test_select_backend(self, mock_get_backend): # Mock the backend and its set_options method mock_backend = MagicMock() @@ -93,19 +93,19 @@ def test_select_backend(self, mock_get_backend): self.backend_instance.select_backend("unknown_backend", 3) @patch( - "modules.applications.qml.generative_modeling.mappings.CustomQiskitNoisyBackend.Layout" + "modules.applications.qml.generative_modeling.mappings.custom_qiskit_noisy_backend.Layout" ) @patch( - "modules.applications.qml.generative_modeling.mappings.CustomQiskitNoisyBackend.PassManager" + "modules.applications.qml.generative_modeling.mappings.custom_qiskit_noisy_backend.PassManager" ) @patch( - "modules.applications.qml.generative_modeling.mappings.CustomQiskitNoisyBackend.transpile" + "modules.applications.qml.generative_modeling.mappings.custom_qiskit_noisy_backend.transpile" ) @patch( - "modules.applications.qml.generative_modeling.mappings.CustomQiskitNoisyBackend.AerSimulator" + "modules.applications.qml.generative_modeling.mappings.custom_qiskit_noisy_backend.AerSimulator" ) @patch( - "modules.applications.qml.generative_modeling.mappings.CustomQiskitNoisyBackend." + "modules.applications.qml.generative_modeling.mappings.custom_qiskit_noisy_backend." "CustomQiskitNoisyBackend.decompile_noisy_config" ) def test_get_execute_circuit(self, mock_decompile_noisy_config, mock_aer_simulator, @@ -176,19 +176,19 @@ def test_get_execute_circuit(self, mock_decompile_noisy_config, mock_aer_simulat mock_backend.run.assert_called_once() @patch( - "modules.applications.qml.generative_modeling.mappings.CustomQiskitNoisyBackend." + "modules.applications.qml.generative_modeling.mappings.custom_qiskit_noisy_backend." "CustomQiskitNoisyBackend.build_noise_model" ) @patch( - "modules.applications.qml.generative_modeling.mappings.CustomQiskitNoisyBackend." + "modules.applications.qml.generative_modeling.mappings.custom_qiskit_noisy_backend." "CustomQiskitNoisyBackend.get_coupling_map" ) @patch( - "modules.applications.qml.generative_modeling.mappings.CustomQiskitNoisyBackend." + "modules.applications.qml.generative_modeling.mappings.custom_qiskit_noisy_backend." "Aer.get_backend" ) @patch( - "modules.applications.qml.generative_modeling.mappings.CustomQiskitNoisyBackend." + "modules.applications.qml.generative_modeling.mappings.custom_qiskit_noisy_backend." "CustomQiskitNoisyBackend.log_backend_options" ) def test_decompile_noisy_config(self, mock_log_backend_options, mock_get_backend, @@ -287,7 +287,7 @@ def test_get_transpile_routine(self): self.assertEqual(self.backend_instance.get_transpile_routine(2), 2) self.assertEqual(self.backend_instance.get_transpile_routine(5), 1) # Invalid config defaults to 1 - @patch("modules.applications.qml.generative_modeling.mappings.CustomQiskitNoisyBackend.noise.depolarizing_error") + @patch("modules.applications.qml.generative_modeling.mappings.custom_qiskit_noisy_backend.noise.depolarizing_error") def test_add_quantum_errors(self, mock_depolarizing_error): # Mock noise model and depolarizing errors mock_noise_model = MagicMock(spec=NoiseModel) diff --git a/tests/modules/applications/qml/generative_modeling/mappings/test_LibraryPennylane.py b/tests/modules/applications/qml/generative_modeling/mappings/test_library_pennylane.py similarity index 93% rename from tests/modules/applications/qml/generative_modeling/mappings/test_LibraryPennylane.py rename to tests/modules/applications/qml/generative_modeling/mappings/test_library_pennylane.py index 65c181ad..1ff44542 100644 --- a/tests/modules/applications/qml/generative_modeling/mappings/test_LibraryPennylane.py +++ b/tests/modules/applications/qml/generative_modeling/mappings/test_library_pennylane.py @@ -2,10 +2,10 @@ from unittest.mock import patch, MagicMock import numpy as np -from modules.applications.qml.generative_modeling.mappings.LibraryPennylane import LibraryPennylane -from modules.applications.qml.generative_modeling.training.QCBM import QCBM -from modules.applications.qml.generative_modeling.training.QGAN import QGAN -from modules.applications.qml.generative_modeling.training.Inference import Inference +from modules.applications.qml.generative_modeling.mappings.library_pennylane import LibraryPennylane +from modules.applications.qml.generative_modeling.training.qcbm import QCBM +from modules.applications.qml.generative_modeling.training.qgan import QGAN +from modules.applications.qml.generative_modeling.training.inference import Inference class TestLibraryPennylane(unittest.TestCase): diff --git a/tests/modules/applications/qml/generative_modeling/mappings/test_LibraryQiskit.py b/tests/modules/applications/qml/generative_modeling/mappings/test_library_qiskit.py similarity index 96% rename from tests/modules/applications/qml/generative_modeling/mappings/test_LibraryQiskit.py rename to tests/modules/applications/qml/generative_modeling/mappings/test_library_qiskit.py index afafe1d1..a7478468 100644 --- a/tests/modules/applications/qml/generative_modeling/mappings/test_LibraryQiskit.py +++ b/tests/modules/applications/qml/generative_modeling/mappings/test_library_qiskit.py @@ -4,10 +4,10 @@ import numpy as np from qiskit_aer import AerSimulator -from modules.applications.qml.generative_modeling.mappings.LibraryQiskit import LibraryQiskit -from modules.applications.qml.generative_modeling.training.QCBM import QCBM -from modules.applications.qml.generative_modeling.training.QGAN import QGAN -from modules.applications.qml.generative_modeling.training.Inference import Inference +from modules.applications.qml.generative_modeling.mappings.library_qiskit import LibraryQiskit +from modules.applications.qml.generative_modeling.training.qcbm import QCBM +from modules.applications.qml.generative_modeling.training.qgan import QGAN +from modules.applications.qml.generative_modeling.training.inference import Inference class TestLibraryQiskit(unittest.TestCase): @@ -82,7 +82,7 @@ def test_sequence_to_circuit(self): # cusvaer simulator is challenging due to the complexity of mocking certain # behaviors of the `cusvaer`-enabled backend. We plan to implement these tests # in the future once we have resolved these issues. - # @patch("modules.applications.qml.generative_modeling.mappings.LibraryQiskit.select_backend.cusvaer") + # @patch("modules.applications.qml.generative_modeling.mappings.library_qiskit.select_backend.cusvaer") # @patch("qiskit_aer.Aer.get_backend") # def test_cusvaer_simulator(self, mock_aer_simulator, mock_cusvaer): # mock_backend = MagicMock() @@ -147,7 +147,7 @@ def test_aer_statevector_simulator_cpu(self, mock_get_backend): # - Additional setup or dependency resolution is required for testing with AWS Braket devices (e.g., SV1 or IonQ Harmony). # def test_amazon_sv1(self): # from qiskit_braket_provider import AWSBraketBackend, AWSBraketProvider - # from modules.devices.braket.SV1 import SV1 + # from modules.devices.braket.sv1 import SV1 # # Create a mock device wrapper and backend # device_wrapper = SV1("SV1", "arn:aws:braket:::device/quantum-simulator/amazon/sv1") @@ -164,7 +164,7 @@ def test_aer_statevector_simulator_cpu(self, mock_get_backend): # self.assertIsNotNone(backend) # self.assertEqual(backend.name, device_wrapper.device.name) - # @patch("modules.devices.braket.Ionq.Ionq") + # @patch("modules.devices.braket.ionq.Ionq") # @patch("qiskit_braket_provider.AWSBraketBackend") # def test_ionq_harmony(self, mock_aws_braket_backend, mock_ionq): # mock_device_wrapper = MagicMock() diff --git a/tests/modules/applications/qml/generative_modeling/mappings/test_PresetQiskitNoisyBackend.py b/tests/modules/applications/qml/generative_modeling/mappings/test_preset_qiskit_noisy_backend.py similarity index 97% rename from tests/modules/applications/qml/generative_modeling/mappings/test_PresetQiskitNoisyBackend.py rename to tests/modules/applications/qml/generative_modeling/mappings/test_preset_qiskit_noisy_backend.py index 841f0aec..be74fd9a 100644 --- a/tests/modules/applications/qml/generative_modeling/mappings/test_PresetQiskitNoisyBackend.py +++ b/tests/modules/applications/qml/generative_modeling/mappings/test_preset_qiskit_noisy_backend.py @@ -5,7 +5,7 @@ from qiskit import QuantumCircuit from qiskit_aer import AerSimulator -from modules.applications.qml.generative_modeling.mappings.PresetQiskitNoisyBackend import PresetQiskitNoisyBackend +from modules.applications.qml.generative_modeling.mappings.preset_qiskit_noisy_backend import PresetQiskitNoisyBackend class TestPresetQiskitNoisyBackend(unittest.TestCase): @@ -31,8 +31,8 @@ def test_get_requirements(self): self.assertEqual(requirements, expected_requirements) def test_get_default_submodule(self): - from modules.applications.qml.generative_modeling.training.QCBM import QCBM - from modules.applications.qml.generative_modeling.training.Inference import Inference + from modules.applications.qml.generative_modeling.training.qcbm import QCBM + from modules.applications.qml.generative_modeling.training.inference import Inference submodule = self.backend_instance.get_default_submodule("QCBM") self.assertIsInstance(submodule, QCBM) @@ -152,7 +152,7 @@ def test_get_transpile_routine(self): "qiskit_aer.Aer.get_backend" ) @patch( - "modules.applications.qml.generative_modeling.mappings.PresetQiskitNoisyBackend." + "modules.applications.qml.generative_modeling.mappings.preset_qiskit_noisy_backend." "PresetQiskitNoisyBackend.get_FakeBackend" ) def test_decompile_noisy_config(self, mock_get_fake_backend, mock_get_backend): @@ -221,7 +221,7 @@ def test_log_backend_info(self, mock_logging): @patch("qiskit_aer.noise.NoiseModel.from_backend") @patch("qiskit_aer.AerSimulator.from_backend") @patch("qiskit_aer.Aer.get_backend") - @patch("modules.applications.qml.generative_modeling.mappings.PresetQiskitNoisyBackend.FakeProviderForBackendV2") + @patch("modules.applications.qml.generative_modeling.mappings.preset_qiskit_noisy_backend.FakeProviderForBackendV2") def test_get_FakeBackend(self, mock_provider, mock_aer_get_backend, mock_simulator_from_backend, mock_noise_model): mock_backend = MagicMock() mock_backend.num_qubits = 5 diff --git a/tests/modules/applications/qml/generative_modeling/metrics/test_MetricsGeneralization.py b/tests/modules/applications/qml/generative_modeling/metrics/test_metrics_generalization.py similarity index 100% rename from tests/modules/applications/qml/generative_modeling/metrics/test_MetricsGeneralization.py rename to tests/modules/applications/qml/generative_modeling/metrics/test_metrics_generalization.py diff --git a/tests/modules/applications/qml/generative_modeling/training/test_Inference.py b/tests/modules/applications/qml/generative_modeling/training/test_Inference.py index 218c7f91..b704b4a4 100644 --- a/tests/modules/applications/qml/generative_modeling/training/test_Inference.py +++ b/tests/modules/applications/qml/generative_modeling/training/test_Inference.py @@ -2,7 +2,7 @@ from unittest.mock import MagicMock, patch import numpy as np -from modules.applications.qml.generative_modeling.training.Inference import Inference +from modules.applications.qml.generative_modeling.training.inference import Inference class TestInference(unittest.TestCase): diff --git a/tests/modules/applications/qml/generative_modeling/training/test_QCBM.py b/tests/modules/applications/qml/generative_modeling/training/test_QCBM.py index a1f433d9..6ffb1e00 100644 --- a/tests/modules/applications/qml/generative_modeling/training/test_QCBM.py +++ b/tests/modules/applications/qml/generative_modeling/training/test_QCBM.py @@ -3,7 +3,7 @@ import numpy as np import matplotlib.pyplot as plt -from modules.applications.qml.generative_modeling.training.QCBM import QCBM +from modules.applications.qml.generative_modeling.training.qcbm import QCBM class TestQCBM(unittest.TestCase): @@ -36,7 +36,7 @@ def test_get_default_submodule(self): self.qcbm_instance.get_default_submodule("AnyOption") @patch("numpy.random.rand") - @patch("modules.applications.qml.generative_modeling.training.QCBM.SummaryWriter") + @patch("modules.applications.qml.generative_modeling.training.qcbm.SummaryWriter") def test_setup_training(self, mock_summary_writer, mock_rand): # Mock inputs mock_rand.return_value = np.array([0.5, 0.5, 0.5]) diff --git a/tests/modules/applications/qml/generative_modeling/training/test_QGAN.py b/tests/modules/applications/qml/generative_modeling/training/test_QGAN.py index bc389b6f..f1f8a35b 100644 --- a/tests/modules/applications/qml/generative_modeling/training/test_QGAN.py +++ b/tests/modules/applications/qml/generative_modeling/training/test_QGAN.py @@ -3,7 +3,7 @@ import numpy as np import torch from torch.utils.data import DataLoader -from modules.applications.qml.generative_modeling.training.QGAN import QGAN, Discriminator, QuantumGenerator +from modules.applications.qml.generative_modeling.training.qgan import QGAN, Discriminator, QuantumGenerator class TestQGAN(unittest.TestCase): diff --git a/tests/modules/applications/qml/generative_modeling/transformation/test_MinMax.py b/tests/modules/applications/qml/generative_modeling/transformation/test_MinMax.py index b80f444f..6fc6140d 100644 --- a/tests/modules/applications/qml/generative_modeling/transformation/test_MinMax.py +++ b/tests/modules/applications/qml/generative_modeling/transformation/test_MinMax.py @@ -3,9 +3,9 @@ from unittest.mock import patch, MagicMock from modules.applications.qml.generative_modeling.transformations.Transformation import Transformation -from modules.applications.qml.generative_modeling.transformations.MinMax import MinMax -from modules.applications.qml.generative_modeling.circuits.CircuitStandard import CircuitStandard -from modules.applications.qml.generative_modeling.circuits.CircuitCardinality import CircuitCardinality +from modules.applications.qml.generative_modeling.transformations.min_max import MinMax +from modules.applications.qml.generative_modeling.circuits.circuit_standard import CircuitStandard +from modules.applications.qml.generative_modeling.circuits.circuit_cardinality import CircuitCardinality class TestMinMax(unittest.TestCase): diff --git a/tests/modules/applications/qml/generative_modeling/transformation/test_PIT.py b/tests/modules/applications/qml/generative_modeling/transformation/test_PIT.py index 964cdb5a..bbfbb71d 100644 --- a/tests/modules/applications/qml/generative_modeling/transformation/test_PIT.py +++ b/tests/modules/applications/qml/generative_modeling/transformation/test_PIT.py @@ -2,8 +2,8 @@ import numpy as np from unittest.mock import MagicMock -from modules.applications.qml.generative_modeling.transformations.PIT import PIT -from modules.applications.qml.generative_modeling.circuits.CircuitCopula import CircuitCopula +from modules.applications.qml.generative_modeling.transformations.pit import PIT +from modules.applications.qml.generative_modeling.circuits.circuit_copula import CircuitCopula class TestPIT(unittest.TestCase): diff --git a/tests/test_BenchmarkManager.py b/tests/test_BenchmarkManager.py index 3ee70732..0df663a5 100644 --- a/tests/test_BenchmarkManager.py +++ b/tests/test_BenchmarkManager.py @@ -4,7 +4,7 @@ import os from datetime import datetime -from src.BenchmarkManager import BenchmarkManager, Instruction +from QUARK.src.benchmark_manager import BenchmarkManager, Instruction class TestBenchmarkManager(unittest.TestCase): diff --git a/tests/test_ConfigManager.py b/tests/test_ConfigManager.py index a5adafb9..a658ee6c 100644 --- a/tests/test_ConfigManager.py +++ b/tests/test_ConfigManager.py @@ -1,8 +1,8 @@ import unittest from unittest.mock import patch, MagicMock -from modules.Core import Core -from src.ConfigManager import ConfigManager +from modules.core import Core +from QUARK.src.config_manager import ConfigManager class TestConfigManager(unittest.TestCase): From 3974e0eb5c994cb4270b5e1c770fa6f4600b5743 Mon Sep 17 00:00:00 2001 From: GreshmaShaji Date: Thu, 6 Feb 2025 19:17:04 +0100 Subject: [PATCH 02/23] Adjusted files with PEP8 compliance --- src/installer.py | 455 ++++++++++++ src/metrics.py | 127 ++++ src/modules/applications/application.py | 49 ++ src/modules/applications/mapping.py | 70 ++ .../optimization/acl/Vehicle_data_QUARK.xlsx | 3 + .../applications/optimization/acl/__init__.py | 20 + .../applications/optimization/acl/acl.py | 694 ++++++++++++++++++ .../optimization/acl/mappings/__init__.py | 20 + .../optimization/acl/mappings/ising.py | 209 ++++++ .../optimization/acl/mappings/qubo.py | 275 +++++++ .../applications/optimization/mis/__init__.py | 19 + .../optimization/mis/data/__init__.py | 19 + .../optimization/mis/data/graph_layouts.py | 100 +++ .../optimization/mis/mappings/__init__.py | 19 + .../optimization/mis/mappings/neutral_atom.py | 91 +++ .../optimization/mis/mappings/qiro.py | 96 +++ .../applications/optimization/mis/mis.py | 364 +++++++++ .../applications/optimization/optimization.py | 137 ++++ .../applications/optimization/pvc/__init__.py | 19 + .../pvc/data/createReferenceGraph.py | 54 ++ .../optimization/pvc/data/reference_data.txt | 3 + .../pvc/data/reference_graph.gpickle | 3 + .../optimization/pvc/mappings/__init__.py | 19 + .../optimization/pvc/mappings/ising.py | 159 ++++ .../optimization/pvc/mappings/qubo.py | 201 +++++ .../applications/optimization/pvc/pvc.py | 393 ++++++++++ .../applications/optimization/sat/__init__.py | 19 + .../optimization/sat/mappings/__init__.py | 19 + .../optimization/sat/mappings/choiising.py | 156 ++++ .../optimization/sat/mappings/choiqubo.py | 250 +++++++ .../optimization/sat/mappings/dinneenising.py | 148 ++++ .../optimization/sat/mappings/dinneenqubo.py | 198 +++++ .../optimization/sat/mappings/direct.py | 140 ++++ .../optimization/sat/mappings/qubovertqubo.py | 195 +++++ .../applications/optimization/sat/sat.py | 333 +++++++++ .../applications/optimization/scp/__init__.py | 19 + .../optimization/scp/data/__init__.py | 19 + .../scp/data/set_cover_data_large.txt | 3 + .../optimization/scp/mappings/__init__.py | 19 + .../optimization/scp/mappings/qubovertqubo.py | 145 ++++ .../applications/optimization/scp/scp.py | 174 +++++ .../applications/optimization/tsp/__init__.py | 19 + .../tsp/data/createReferenceGraph.py | 48 ++ .../optimization/tsp/data/dsj1000.tsp | 3 + .../tsp/data/reference_graph.gpickle | 3 + .../optimization/tsp/mappings/__init__.py | 19 + .../optimization/tsp/mappings/ising.py | 273 +++++++ .../optimization/tsp/mappings/qubo.py | 125 ++++ .../applications/optimization/tsp/tsp.py | 337 +++++++++ src/modules/applications/qml/circuit.py | 33 + .../qml/generative_modeling/data/mg_2d.npy | 3 + .../qml/generative_modeling/data/o_2d.npy | 3 + .../generative_modeling/data/stocks_2d.npy | 3 + .../qml/generative_modeling/data/x_2d.npy | 3 + .../generative_modeling/training/inference.py | 118 +++ .../qml/generative_modeling/training/qcbm.py | 335 +++++++++ .../qml/generative_modeling/training/qgan.py | 508 +++++++++++++ .../transformations/pit.py | 257 +++++++ .../transformations/transformation.py | 200 +++++ src/modules/applications/qml/model.py | 59 ++ src/modules/applications/qml/qml.py | 42 ++ src/modules/applications/qml/training.py | 33 + src/modules/core.py | 161 ++++ src/modules/devices/braket/braket.py | 188 +++++ src/modules/devices/braket/ionq.py | 59 ++ src/modules/devices/braket/oqc.py | 59 ++ src/modules/devices/braket/rigetti.py | 59 ++ src/modules/devices/braket/sv1.py | 59 ++ src/modules/devices/braket/tn1.py | 59 ++ src/modules/devices/device.py | 102 +++ src/modules/devices/local.py | 46 ++ src/modules/devices/pulser/pulser.py | 59 ++ src/modules/solvers/annealer.py | 111 +++ src/modules/solvers/qaoa.py | 477 ++++++++++++ src/modules/solvers/solver.py | 50 ++ src/plotter.py | 248 +++++++ .../legacy_classes/application.py | 210 ++++++ src/quark2_adapter/legacy_classes/device.py | 75 ++ src/quark2_adapter/legacy_classes/mapping.py | 108 +++ src/quark2_adapter/legacy_classes/solver.py | 99 +++ tests/configs/invalid/tsp.yml | 26 + tests/configs/valid/acl.yml | 25 + tests/configs/valid/mis.yml | 30 + tests/configs/valid/pvc.yml | 35 + tests/configs/valid/sat.yml | 24 + tests/configs/valid/scp.yml | 23 + tests/configs/valid/tsp.yml | 25 + .../MIS/mappings/mis_test_graph.pkl | Bin 0 -> 200 bytes .../applications/optimization/MIS/test_mis.py | 76 ++ .../optimization/PVC/mappings/test_ising.py | 69 ++ .../optimization/PVC/mappings/test_qubo.py | 56 ++ .../applications/optimization/PVC/test_pvc.py | 121 +++ .../SAT/mappings/test_choiIsing.py | 65 ++ .../SAT/mappings/test_choiqubo.py | 55 ++ .../SAT/mappings/test_dinneenising.py | 63 ++ .../SAT/mappings/test_dinneenqubo.py | 53 ++ .../optimization/SAT/mappings/test_direct.py | 76 ++ .../SAT/mappings/test_qubovertqubo.py | 51 ++ .../applications/optimization/SAT/test_sat.py | 118 +++ .../SCP/mappings/test_qubovertqubo.py | 56 ++ .../applications/optimization/SCP/test_scp.py | 89 +++ .../optimization/TSP/mappings/test_ising.py | 121 +++ .../optimization/TSP/mappings/test_qubo.py | 47 ++ .../applications/optimization/TSP/test_tsp.py | 118 +++ .../applications/optimization/acl/__init__.py | 0 .../optimization/acl/mappings/__init__.py | 0 .../optimization/acl/mappings/test_ising.py | 85 +++ .../optimization/acl/mappings/test_qubo.py | 100 +++ .../applications/optimization/acl/test_acl.py | 137 ++++ 109 files changed, 11852 insertions(+) create mode 100644 src/installer.py create mode 100644 src/metrics.py create mode 100644 src/modules/applications/application.py create mode 100644 src/modules/applications/mapping.py create mode 100644 src/modules/applications/optimization/acl/Vehicle_data_QUARK.xlsx create mode 100644 src/modules/applications/optimization/acl/__init__.py create mode 100644 src/modules/applications/optimization/acl/acl.py create mode 100644 src/modules/applications/optimization/acl/mappings/__init__.py create mode 100644 src/modules/applications/optimization/acl/mappings/ising.py create mode 100644 src/modules/applications/optimization/acl/mappings/qubo.py create mode 100644 src/modules/applications/optimization/mis/__init__.py create mode 100644 src/modules/applications/optimization/mis/data/__init__.py create mode 100644 src/modules/applications/optimization/mis/data/graph_layouts.py create mode 100644 src/modules/applications/optimization/mis/mappings/__init__.py create mode 100644 src/modules/applications/optimization/mis/mappings/neutral_atom.py create mode 100644 src/modules/applications/optimization/mis/mappings/qiro.py create mode 100644 src/modules/applications/optimization/mis/mis.py create mode 100644 src/modules/applications/optimization/optimization.py create mode 100644 src/modules/applications/optimization/pvc/__init__.py create mode 100644 src/modules/applications/optimization/pvc/data/createReferenceGraph.py create mode 100644 src/modules/applications/optimization/pvc/data/reference_data.txt create mode 100644 src/modules/applications/optimization/pvc/data/reference_graph.gpickle create mode 100644 src/modules/applications/optimization/pvc/mappings/__init__.py create mode 100644 src/modules/applications/optimization/pvc/mappings/ising.py create mode 100644 src/modules/applications/optimization/pvc/mappings/qubo.py create mode 100644 src/modules/applications/optimization/pvc/pvc.py create mode 100644 src/modules/applications/optimization/sat/__init__.py create mode 100644 src/modules/applications/optimization/sat/mappings/__init__.py create mode 100644 src/modules/applications/optimization/sat/mappings/choiising.py create mode 100644 src/modules/applications/optimization/sat/mappings/choiqubo.py create mode 100644 src/modules/applications/optimization/sat/mappings/dinneenising.py create mode 100644 src/modules/applications/optimization/sat/mappings/dinneenqubo.py create mode 100644 src/modules/applications/optimization/sat/mappings/direct.py create mode 100644 src/modules/applications/optimization/sat/mappings/qubovertqubo.py create mode 100644 src/modules/applications/optimization/sat/sat.py create mode 100644 src/modules/applications/optimization/scp/__init__.py create mode 100644 src/modules/applications/optimization/scp/data/__init__.py create mode 100644 src/modules/applications/optimization/scp/data/set_cover_data_large.txt create mode 100644 src/modules/applications/optimization/scp/mappings/__init__.py create mode 100644 src/modules/applications/optimization/scp/mappings/qubovertqubo.py create mode 100644 src/modules/applications/optimization/scp/scp.py create mode 100644 src/modules/applications/optimization/tsp/__init__.py create mode 100644 src/modules/applications/optimization/tsp/data/createReferenceGraph.py create mode 100644 src/modules/applications/optimization/tsp/data/dsj1000.tsp create mode 100644 src/modules/applications/optimization/tsp/data/reference_graph.gpickle create mode 100644 src/modules/applications/optimization/tsp/mappings/__init__.py create mode 100644 src/modules/applications/optimization/tsp/mappings/ising.py create mode 100644 src/modules/applications/optimization/tsp/mappings/qubo.py create mode 100644 src/modules/applications/optimization/tsp/tsp.py create mode 100644 src/modules/applications/qml/circuit.py create mode 100644 src/modules/applications/qml/generative_modeling/data/mg_2d.npy create mode 100644 src/modules/applications/qml/generative_modeling/data/o_2d.npy create mode 100644 src/modules/applications/qml/generative_modeling/data/stocks_2d.npy create mode 100644 src/modules/applications/qml/generative_modeling/data/x_2d.npy create mode 100644 src/modules/applications/qml/generative_modeling/training/inference.py create mode 100644 src/modules/applications/qml/generative_modeling/training/qcbm.py create mode 100644 src/modules/applications/qml/generative_modeling/training/qgan.py create mode 100644 src/modules/applications/qml/generative_modeling/transformations/pit.py create mode 100644 src/modules/applications/qml/generative_modeling/transformations/transformation.py create mode 100644 src/modules/applications/qml/model.py create mode 100644 src/modules/applications/qml/qml.py create mode 100644 src/modules/applications/qml/training.py create mode 100644 src/modules/core.py create mode 100644 src/modules/devices/braket/braket.py create mode 100644 src/modules/devices/braket/ionq.py create mode 100644 src/modules/devices/braket/oqc.py create mode 100644 src/modules/devices/braket/rigetti.py create mode 100644 src/modules/devices/braket/sv1.py create mode 100644 src/modules/devices/braket/tn1.py create mode 100644 src/modules/devices/device.py create mode 100644 src/modules/devices/local.py create mode 100644 src/modules/devices/pulser/pulser.py create mode 100644 src/modules/solvers/annealer.py create mode 100644 src/modules/solvers/qaoa.py create mode 100644 src/modules/solvers/solver.py create mode 100644 src/plotter.py create mode 100644 src/quark2_adapter/legacy_classes/application.py create mode 100644 src/quark2_adapter/legacy_classes/device.py create mode 100644 src/quark2_adapter/legacy_classes/mapping.py create mode 100644 src/quark2_adapter/legacy_classes/solver.py create mode 100644 tests/configs/invalid/tsp.yml create mode 100644 tests/configs/valid/acl.yml create mode 100644 tests/configs/valid/mis.yml create mode 100644 tests/configs/valid/pvc.yml create mode 100644 tests/configs/valid/sat.yml create mode 100644 tests/configs/valid/scp.yml create mode 100644 tests/configs/valid/tsp.yml create mode 100644 tests/modules/applications/optimization/MIS/mappings/mis_test_graph.pkl create mode 100644 tests/modules/applications/optimization/MIS/test_mis.py create mode 100644 tests/modules/applications/optimization/PVC/mappings/test_ising.py create mode 100644 tests/modules/applications/optimization/PVC/mappings/test_qubo.py create mode 100644 tests/modules/applications/optimization/PVC/test_pvc.py create mode 100644 tests/modules/applications/optimization/SAT/mappings/test_choiIsing.py create mode 100644 tests/modules/applications/optimization/SAT/mappings/test_choiqubo.py create mode 100644 tests/modules/applications/optimization/SAT/mappings/test_dinneenising.py create mode 100644 tests/modules/applications/optimization/SAT/mappings/test_dinneenqubo.py create mode 100644 tests/modules/applications/optimization/SAT/mappings/test_direct.py create mode 100644 tests/modules/applications/optimization/SAT/mappings/test_qubovertqubo.py create mode 100644 tests/modules/applications/optimization/SAT/test_sat.py create mode 100644 tests/modules/applications/optimization/SCP/mappings/test_qubovertqubo.py create mode 100644 tests/modules/applications/optimization/SCP/test_scp.py create mode 100644 tests/modules/applications/optimization/TSP/mappings/test_ising.py create mode 100644 tests/modules/applications/optimization/TSP/mappings/test_qubo.py create mode 100644 tests/modules/applications/optimization/TSP/test_tsp.py create mode 100644 tests/modules/applications/optimization/acl/__init__.py create mode 100644 tests/modules/applications/optimization/acl/mappings/__init__.py create mode 100644 tests/modules/applications/optimization/acl/mappings/test_ising.py create mode 100644 tests/modules/applications/optimization/acl/mappings/test_qubo.py create mode 100644 tests/modules/applications/optimization/acl/test_acl.py diff --git a/src/installer.py b/src/installer.py new file mode 100644 index 00000000..e3c941e1 --- /dev/null +++ b/src/installer.py @@ -0,0 +1,455 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +import json +import os +import time +from pathlib import Path +import inspect + +import yaml +from packaging import version +import inquirer + +from modules.core import Core +from utils import _get_instance_with_sub_options, get_git_revision, checkbox + + +class Installer: + """ + Installer class that can be used by the user to install certain QUARK modules and also return the required Python + packages for the demanded modules. + """ + + def __init__(self): + self.quark_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", ) + self.settings_dir = f"{self.quark_dir}/.settings" + self.envs_dir = f"{self.settings_dir}/envs" + self.python_version = "3.12.7" + self.pip_version = "23.0" + self.default_app_modules = [ + {"name": "PVC", "class": "PVC", "module": "modules.applications.optimization.pvc.pvc"}, + {"name": "SAT", "class": "SAT", "module": "modules.applications.optimization.sat.sat"}, + {"name": "TSP", "class": "TSP", "module": "modules.applications.optimization.tsp.tsp"}, + {"name": "ACL", "class": "ACL", "module": "modules.applications.optimization.acl.acl"}, + {"name": "MIS", "class": "MIS", "module": "modules.applications.optimization.mis.mis"}, + {"name": "SCP", "class": "SCP", "module": "modules.applications.optimization.scp.scp"}, + {"name": "GenerativeModeling", "class": "GenerativeModeling", + "module": "modules.applications.qml.generative_modeling.generative_modeling"} + ] + + self.core_requirements = [ + {"name": "seaborn", "version": "0.13.2"}, + {"name": "networkx", "version": "3.4.2"}, + {"name": "inquirer", "version": "3.4.0"}, + {"name": "packaging", "version": "24.2"}, + {"name": "pyyaml", "version": "6.0.2"}, + {"name": "typing-extensions", "version": "4.12.2"}, + {"name": "sphinx", "version": "8.1.3"}, + {"name": "sphinx-rtd-theme", "version": "3.0.2"}, + ] + Path(self.envs_dir).mkdir(parents=True, exist_ok=True) + + def configure(self, env_name="default") -> None: + """ + Configures a new QUARK environment or overwrites an existing one. + + :param env_name: Name of the env to configure + """ + configured_envs = self.check_for_configs() + + if env_name in configured_envs: + answer_continue = inquirer.prompt([ + inquirer.List("continue", + message=f"{env_name} found in the list of existing QUARK module environment, are you" + f" sure you want to overwrite it?", + choices=["Yes", "No"], )])["continue"] + + if answer_continue.lower() == "no": + logging.info("Exiting configuration") + return + + chosen_config_type = inquirer.prompt([ + inquirer.List("config", + message="Do you want to use the default configuration or a custom environment?", + choices=["Default", "Custom"])])["config"] + logging.info(f"You chose {chosen_config_type}") + + module_db = self.get_module_db() + + if chosen_config_type == "Default": + self.save_env(module_db, env_name) + elif chosen_config_type == "Custom": + module_db = self.start_query_user(module_db) + self.save_env(module_db, env_name) + + requirements = self.collect_requirements(module_db["modules"]) + activate_requirements = checkbox("requirements", "Should we create an package file, if yes for " + "which package manager?", + ["Conda", "PIP", "Print it here"])["requirements"] + + if "Conda" in activate_requirements: + self.create_conda_file(requirements, env_name) + if "PIP" in activate_requirements: + self.create_req_file(requirements, env_name) + if "Print it here" in activate_requirements: + logging.info("Please install:") + for p, v in requirements.items(): + logging.info(f" - {p}{': ' + v[0] if v else ''}") + + activate_answer = inquirer.prompt([ + inquirer.List("activate", + message="Do you want to activate the QUARK module environment?", + choices=["Yes", "No"])])["activate"] + + if activate_answer == "Yes": + self.set_active_env(env_name) + + def check_for_configs(self) -> list: + """ + Checks if QUARK is already configured and if yes, which environments. + + :return: Returns the configured QUARK envs in a list + """ + return list(p.stem for p in Path(self.envs_dir).glob("*.json")) + + def set_active_env(self, name: str) -> None: + """ + Sets the active env to active_env.json. + + :param name: Name of the env + """ + self._check_if_env_exists(name) + with open(f"{self.settings_dir}/active_env.json", "w") as jsonFile: + json.dump({"name": name}, jsonFile, indent=2) + + logging.info(f"Set active QUARK module environment to {name}") + + def check_active_env(self) -> bool: + """ + Checks if .settings/active_env.json exists. + + :return: True if active_env.json exists + """ + return Path(f"{self.settings_dir}/active_env.json").is_file() + + def get_active_env(self) -> str: + """ + Returns the current active environment. + + :return: Returns the name of the active env + """ + if not self.check_active_env(): + logging.warning("No active QUARK module environment found, using default") + module_db = self.get_module_db() + self.save_env(module_db, "default") + self.set_active_env("default") + + with open(f"{self.settings_dir}/active_env.json", "r") as filehandler: + env = json.load(filehandler) + return env["name"] + + def get_env(self, name: str) -> list[dict]: + """ + Loads the env from file and returns it. + + :param name: Name of the env + :return: Returns the modules of the env + """ + file = f"{self.envs_dir}/{name}.json" + self._check_if_env_exists(name) + + with open(file, "r") as filehandler: + env = json.load(filehandler) + logging.info(f"Getting {name} QUARK module environment") + module_db_build_number = self.get_module_db_build_number() + if env["build_number"] < module_db_build_number: + logging.warning(f"You QUARK module env is based on an outdated build version of the module database " + f"(BUILD NUMBER {env['build_number']}). The current module database (BUILD NUMBER " + f"{module_db_build_number}) might bring new features. You should think about " + f"updating your environment!") + + return env["modules"] + + def _check_if_env_exists(self, name: str) -> str: + """ + Checks if a given env exists, returns the location of the associated JSON file and raises an error otherwise. + + :param name: Name of the env + :return: Returns location of the JSON file associated with the env if it exists + """ + file = f"{self.envs_dir}/{name}.json" + if not Path(file).is_file(): + raise ValueError(f"QUARK environment {name} could not be found!") + return file + + def save_env(self, env: dict, name: str) -> None: + """ + Saves a created env to a file with the name of choice. + + :param env: Env which should be saved + :param name: Name of the env + """ + with open(f"{self.envs_dir}/{name}.json", "w") as jsonFile: + json.dump(env, jsonFile, indent=2) + + logging.info(f"Saved {name} QUARK module environment.") + + def start_query_user(self, module_db: dict) -> dict: + """ + Queries the user which applications and submodules to include. + + :param module_db: module_db file + :return: Returns the module_db with selected (sub)modules + """ + answer_apps = checkbox("apps", "Which application would you like to include?", + [m["name"] for m in module_db["modules"]])["apps"] + + module_db["modules"] = [x for x in module_db["modules"] if x["name"] in answer_apps] + + for idx, entry in enumerate(module_db["modules"]): + self.query_user(module_db["modules"][idx], entry["name"]) + + return module_db + + def query_user(self, submodules: dict, name: str) -> None: + """ + Queries the user which submodules to include + + :param submodules: Submodules for the module + :param name: Name of the module + """ + if submodules["submodules"]: + answer_submodules = \ + checkbox("submodules", f"Which submodule would you like to include for {name}?", + [m["name"] for m in submodules["submodules"]])["submodules"] + + submodules["submodules"] = [x for x in submodules["submodules"] if x["name"] in answer_submodules] + for idx, entry in enumerate(submodules["submodules"]): + self.query_user(submodules["submodules"][idx], entry["name"]) + + def get_module_db(self) -> dict: + """ + Returns the module database that contains all module possibilities. + + :return: Module Database + """ + with open(f"{self.settings_dir}/module_db.json", "r") as filehandler: + return json.load(filehandler) + + def create_module_db(self) -> None: + """ + Creates module database by automatically going through the available submodules for each module. + """ + logging.info("Creating Module Database") + + # TODO Helper to skip constructor in Braket.py. Should probably be changed in the future! + os.environ["SKIP_INIT"] = "True" + + module_db_modules: list[dict] = self.default_app_modules + + for idx, app in enumerate(module_db_modules): + logging.info(f"Processing {app['name']}") + app_instance = _get_instance_with_sub_options(module_db_modules, app["name"]) + module_db_modules[idx]["submodules"] = [ + Installer._create_module_db_helper(app_instance.get_submodule(sm), sm) for + sm in app_instance.submodule_options] + module_db_modules[idx]["requirements"] = app_instance.get_requirements() + + git_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", ) + git_revision_number, _ = get_git_revision(git_dir) + module_db = { + "build_number": self.get_module_db_build_number() + 1, + "build_date": time.strftime("%d-%m-%Y %H:%M:%S", time.gmtime()), + "git_revision_number": git_revision_number, + "modules": module_db_modules + } + + # loop over first level and add to file + with open(f"{self.settings_dir}/module_db.json", "w") as jsonFile: + json.dump(module_db, jsonFile, indent=2) + + requirements = self.collect_requirements(module_db_modules) + self.create_req_file(requirements, "full", self.settings_dir) + + logging.info("Created module_db file") + + @staticmethod + def _create_module_db_helper(module: Core, name: str) -> dict: + """ + Recursive helper function for create_module_db. + + :param module: Module instance + :param name: Name of the module + :return: Module dict + """ + return { + "name": name, + "class": module.__class__.__name__, + "args": {k: v for k, v in module.__dict__.items() if k in + inspect.signature(module.__init__).parameters.keys()}, + "module": module.__module__, + "requirements": module.get_requirements(), + "submodules": [Installer._create_module_db_helper(module.get_submodule(sm), sm) for sm in + module.submodule_options] + } + + def get_module_db_build_number(self) -> int: + """ + Returns the build number of the module_db. + + :return: Returns the build number of the module_db if it exists, otherwise 0 + """ + if Path(f"{self.settings_dir}/module_db.json").is_file(): + module_db = self.get_module_db() + return module_db["build_number"] + else: + return 0 + + def collect_requirements(self, env: list[dict]) -> dict: + """ + Collects requirements of the different modules in the given env file. + + :param env: Environment configuration + :return: Collected requirements + """ + requirements: list[dict] = self.core_requirements + for app in env: + requirements.extend(Installer._collect_requirements_helper(app)) + + # Counts duplicates and checks if any version conflicts occur + merged_requirements: dict = {} + for req in requirements: + if req["name"] in merged_requirements: + # Checks if the specific version is already there, if so the req is skipped + if merged_requirements[req["name"]] and ("version" in req and req["version"] not in + merged_requirements[req["name"]]): + merged_requirements[req["name"]].append(req["version"]) + + else: + # Sometimes there is no specific version required, then the "version" field is missing + merged_requirements[req["name"]] = [req["version"]] if "version" in req else [] + + for k, v in merged_requirements.items(): + if len(v) > 1: + # If there are multiple different versions, the latest version is selected + newest_version = sorted(v, key=lambda x: version.Version(x))[-1] # pylint: disable=W0108 + merged_requirements[k] = [newest_version] + logging.warning(f"Different version requirements for {k}: {v}. Will try to take the newer version " + f"{newest_version}, but this might cause problems at QUARK runtime!") + + return merged_requirements + + @staticmethod + def _collect_requirements_helper(module: dict) -> list[dict]: + """ + Helper function for collect_requirements_helper that recursively checks modules for requirements. + + :param module: Module dict + :return: List of dicts with the requirements + """ + requirements = module["requirements"] + for submodule in module["submodules"]: + requirements.extend(Installer._collect_requirements_helper(submodule)) + + return requirements + + def create_conda_file(self, requirements: dict, name: str, directory: str = None) -> None: + """ + Creates conda yaml file based on the requirements. + + :param requirements: Collected requirements + :param name: Name of the conda env + :param directory: Directory where the file should be saved. If None self.envs_dir will be taken. + """ + if directory is None: + directory = self.envs_dir + conda_data = { + "name": name, + "channels": ["defaults"], + "dependencies": [ + f"python={self.python_version}", + f"pip={self.pip_version}", + {"pip": [(f"{k}=={v[0]}" if v else k) for k, v in requirements.items()]} + + ] + } + with open(f"{directory}/conda_{name}.yml", "w") as filehandler: + yaml.dump(conda_data, filehandler) + + logging.info("Saved conda env file, if you like to install it run:") + logging.info(f"conda env create -f {directory}/conda_{name}.yml") + + def create_req_file(self, requirements: dict, name: str, directory: str = None) -> None: + """ + Creates pip txt file based on the requirements. + + :param requirements: Collected requirements + :param name: Name of the env + :param directory: Directory where the file should be saved. If None self.envs_dir will be taken. + """ + if directory is None: + directory = self.envs_dir + with open(f"{directory}/requirements_{name}.txt", "w") as filehandler: + for k, v in requirements.items(): + filehandler.write(f"{k}=={v[0]}" if v else k) + filehandler.write("\n") + + logging.info("Saved pip txt file, if you like to install it run:") + logging.info(f"pip install -r {directory}/requirements_{name}.txt") + + def list_envs(self) -> None: + """ + List all existing environments. + """ + logging.info("Existing environments:") + for env in self.check_for_configs(): + logging.info(f" - {env}") + + @staticmethod + def show(env: list[dict]) -> None: + """ + Visualize the env. + + :param env: Environment configuration + """ + space = " " + branch = "| " + connector = "|-- " + leaf = ">-- " + + def tree(modules: list[dict], prefix: str = ""): + """ + A recursive function that generates a tree from the modules. + This function is based on https://stackoverflow.com/a/59109706, but modified to the needs here. + + :param modules: Modules list + :param prefix: Prefix for the indentation + :return: Generator yielding formatted lines of the environment tree + """ + # Modules in the middle/beginning get a |--, the final leaf >-- + pointers = [connector] * (len(modules) - 1) + [leaf] + for pointer, module in zip(pointers, modules): + yield prefix + pointer + module["name"] + + if module["submodules"]: + # If the module has any submodules + extension = branch if pointer == connector else space + # Check if we are at the end of the tree + yield from tree(module["submodules"], prefix=prefix + extension) + + logging.info("Content of the environment:") + for module in tree(env): + logging.info(module) diff --git a/src/metrics.py b/src/metrics.py new file mode 100644 index 00000000..3f93c21e --- /dev/null +++ b/src/metrics.py @@ -0,0 +1,127 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import final + + +class Metrics: + """ + Metrics Module, used by every QUARK module. + """ + + def __init__(self, module_name: str, module_src: str): + """ + Constructor for Metrics class. + + :param module_name: Name of the module this metrics object belongs to + :param module_src: Source file of the module this metrics object belongs to + """ + self.module_name = module_name + self.module_src = module_src + self.preprocessing_time = None + self.preprocessing_time_unit = "ms" + self.postprocessing_time = None + self.module_config = None + self.total_time = None + self.total_time_unit = "ms" + self.postprocessing_time_unit = "ms" + self.additional_metrics = {} + + @final + def validate(self) -> None: + """ + Validates whether the mandatory metrics got recorded, then sets total time. + """ + assert self.preprocessing_time is not None, ( + "preprocessing time must not be None!" + ) + assert self.postprocessing_time is not None, ( + "postprocessing time must not be None!" + ) + self.total_time = self.preprocessing_time + self.postprocessing_time + + @final + def set_preprocessing_time(self, value: float) -> None: + """ + Sets the preprocessing time. + + :param value: Time + """ + self.preprocessing_time = value + + @final + def set_module_config(self, config: dict) -> None: + """ + Sets the config of the module this metrics object belongs to. + + :param config: Config of the QUARK module + """ + self.module_config = config + + @final + def set_postprocessing_time(self, value: float) -> None: + """ + Sets the postprocessing time. + + :param value: Time + """ + self.postprocessing_time = value + + @final + def add_metric(self, name: str, value: any) -> None: + """ + Adds a single metric. + + :param name: Name of the metric + :param value: Value of the metric + """ + self.additional_metrics.update({name: value}) + + @final + def add_metric_batch(self, key_values: dict) -> None: + """ + Adds a dictionary containing metrics to the existing metrics. + + :param key_values: Dict containing metrics + """ + self.additional_metrics.update(key_values) + + @final + def reset(self) -> None: + """ + Resets all recorded metrics. + """ + self.preprocessing_time = None + self.postprocessing_time = None + self.additional_metrics = {} + + @final + def get(self) -> dict: + """ + Returns all recorded metrics. + + :return: Metrics as a dict + """ + return { + "module_name": self.module_name, + "module_src": self.module_src, + "module_config": self.module_config, + "total_time": self.total_time, + "total_time_unit": self.total_time_unit, + "preprocessing_time": self.preprocessing_time, + "preprocessing_time_unit": self.preprocessing_time_unit, + "postprocessing_time": self.postprocessing_time, + "postprocessing_time_unit": self.postprocessing_time_unit, + **self.additional_metrics + } diff --git a/src/modules/applications/application.py b/src/modules/applications/application.py new file mode 100644 index 00000000..35b3f136 --- /dev/null +++ b/src/modules/applications/application.py @@ -0,0 +1,49 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from abc import ABC, abstractmethod +from modules.core import Core + + +class Application(Core, ABC): + """ + The application component defines the workload, comprising a dataset of increasing complexity, a validation, and an + evaluation function. + """ + + def __init__(self, application_name: str): + """ + Constructor method. + """ + super().__init__(application_name) + self.application_name = self.name + self.application = None + + def get_application(self) -> any: + """ + Gets the application. + + :return: self.application + """ + return self.application + + @abstractmethod + def save(self, path: str, iter_count: int) -> None: + """ + Saves the concrete problem. + + :param path: Path of the experiment directory for this run + :param iter_count: The iteration count + """ + pass diff --git a/src/modules/applications/mapping.py b/src/modules/applications/mapping.py new file mode 100644 index 00000000..67ab5366 --- /dev/null +++ b/src/modules/applications/mapping.py @@ -0,0 +1,70 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from abc import ABC, abstractmethod +from modules.core import Core + + +class Mapping(Core, ABC): + """ + This module translates the input data and problem specification from the parent module, + e.g., the application into a mathematical formulation suitable the submodule, e.g., a solver. + """ + + def preprocess(self, input_data: any, config: dict, **kwargs) -> tuple[any, float]: + """ + Maps the data to the correct target format. + + :param input_data: Data which should be mapped + :param config: Config of the mapping + :param kwargs: Optional keyword arguments + :return: Tuple with mapped problem and the time it took to map it + """ + output, preprocessing_time = self.map(input_data, config) + return output, preprocessing_time + + def postprocess(self, input_data: any, config: dict, **kwargs) -> tuple[any, float]: + """ + Reverse transformation/mapping from the submodule's format to the mathematical formulation + suitable for the parent module. + + :param input_data: Data which should be reverse-mapped + :param config: Config of the reverse mapping + :param kwargs: Optional keyword arguments + :return: Tuple with reverse-mapped problem and the time it took to map it + """ + output, postprocessing_time = self.reverse_map(input_data) + return output, postprocessing_time + + @abstractmethod + def map(self, problem: any, config: dict) -> tuple[any, float]: + """ + Maps the given problem into a specific format suitable for the submodule, e.g., a solver. + + :param config: Instance of class Config specifying the mapping settings + :param problem: Problem instance which should be mapped to the target representation + :return: Mapped problem and the time it took to map it + """ + pass + + def reverse_map(self, solution: any) -> tuple[any, float]: + """ + Maps the solution back to the original problem. This might not be necessary in all cases, so the default is + to return the original solution. This might be needed to convert the solution to a representation needed + for validation and evaluation. + + :param solution: Solution provided by submodule, e.g., the Solver class + :return: Reverse-mapped solution and the time it took to create it + """ + return solution, 0 diff --git a/src/modules/applications/optimization/acl/Vehicle_data_QUARK.xlsx b/src/modules/applications/optimization/acl/Vehicle_data_QUARK.xlsx new file mode 100644 index 00000000..cbd255ed --- /dev/null +++ b/src/modules/applications/optimization/acl/Vehicle_data_QUARK.xlsx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b4665a49e772482af10b48f1f664bf4bce4014477f359549170c91737858ee40 +size 14465 diff --git a/src/modules/applications/optimization/acl/__init__.py b/src/modules/applications/optimization/acl/__init__.py new file mode 100644 index 00000000..b22c875d --- /dev/null +++ b/src/modules/applications/optimization/acl/__init__.py @@ -0,0 +1,20 @@ +# Copyright 2022 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Module containing the ACL + +This module initializes the ACL application, which is responsible for formulating +and solving the ACL problem using various mappings and solvers. +""" diff --git a/src/modules/applications/optimization/acl/acl.py b/src/modules/applications/optimization/acl/acl.py new file mode 100644 index 00000000..1581e932 --- /dev/null +++ b/src/modules/applications/optimization/acl/acl.py @@ -0,0 +1,694 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# copyright Pulp: +# Copyright (c) 2002-2005, Jean-Sebastien Roy +# Modifications Copyright (c) 2007- Stuart Anthony Mitchell +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the Software +# is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. + +import os +import logging +from typing import TypedDict + +import pandas as pd +import numpy as np +import pulp + +from modules.applications.application import Core +from modules.applications.optimization.optimization import Optimization +from utils import start_time_measurement, end_time_measurement + + +class ACL(Optimization): + """ + The distribution of passenger vehicles is a complex task and a high cost factor for automotive original + equipment manufacturers (OEMs). Vehicles travel long distance on different carriers, such as ships, + trains, and trucks, from the production plant to the customer. + + To save costs, OEMs and logistics service providers aim to maximize their loading capacities. + Modern auto carriers are flexible, allowing individual platforms to be rotated, extended, or combined + to accommodate vehicles of different shapes and weights in a space-efficient manner. + + In practice, finding feasible combinations is often based on heuristics or personal experience. + We formulate the problem as a mixed integer quadratically constrained assignment problem. + """ + + def __init__(self): + """ + Constructor method. + """ + super().__init__("ACL") + self.submodule_options = ["MIPsolverACL", "QUBO"] + self.application = None + + @staticmethod + def get_requirements() -> list[dict]: + """ + Returns the list of module requirements. + + :return: List of dictionaries containing module requirements + """ + return [ + {"name": "pulp", "version": "2.9.0"}, + {"name": "pandas", "version": "2.2.3"}, + {"name": "numpy", "version": "1.26.4"}, + {"name": "openpyxl", "version": "3.1.5"}, + ] + + def get_default_submodule(self, option: str) -> Core: + """ + Returns the default submodule based on the provided option. + + :param option: Option specifying the submodule + :return: Instance of the corresponding submodule + :raises NotImplementedError: If the option is not recognized + """ + if option == "MIPsolverACL": + from modules.solvers.mip_solver_acl import MIPaclp # pylint: disable=C0415 + return MIPaclp() + elif option == "QUBO": + from modules.applications.optimization.acl.mappings.qubo import Qubo # pylint: disable=C0415 + return Qubo() + else: + raise NotImplementedError(f"Submodule Option {option} not implemented") + + def get_parameter_options(self) -> dict: + """ + Returns parameter options for selecting different models. + + :return: Dictionary containing model selection options + """ + return { + "model_select": { + "values": ["Full", "Small", "Tiny"], + "description": "Do you want the full model or a simplified (QUBO friendly) one?" + } + } + + class Config(TypedDict): + model_select: str + + @staticmethod + def intersectset(p1: list, p2: list) -> list: + """ + Computes the intersection of two lists. + + :param p1: First list + :param p2: Second list + :return: List containing elements common to both p1 and p2 + """ + return np.intersect1d(p1, p2).tolist() + + @staticmethod + def diffset(p1: list, p2: list) -> list: + """ + Computes the difference between two lists. + + :param p1: First list + :param p2: Second list + :return: List containing elements in p1 that are not in p2 + """ + return np.setdiff1d(p1, p2).tolist() + + def generate_problem(self, config: Config) -> dict: # pylint: disable=R0915 + """ + This function includes three models: Full, small and tiny. Full refers to the original model with all of its + constraints. Small refers to the simplified model suitable for solving it with QC methods. + The simplified model does not consider the possibility that vehicles can be angled or that they can be + oriented forwards or backwards in relation to the auto carrier. For the tiny version we do not consider split + platforms and consider only weight constraints. + + :param config: Config containing the selected scenario + :return: Dictionary with scenario-dependent model formulated as linear problem + """ + # Enter vehicles to load (BMW model codes) + vehicles = ["G20", "G20", "G20", "G20", "G07", "G20"] + # Import vehicle data + df = pd.read_excel(os.path.join(os.path.dirname(__file__), "Vehicle_data_QUARK.xlsx")) + model_select = config['model_select'] + + # All the parameters are in decimeters and decitons (4m == 400 cm == 40 dm , 2 tons -> 20 dt + # Below are the model specific parameters, constraints and objectives for the tiny, small and the full model + + if model_select == "Tiny": + self._generate_tiny_model(df, vehicles) + elif model_select == "Small": + self._generate_small_model(df, vehicles) + else: + self._generate_full_model(df, vehicles) + + problem_instance = self.application.to_dict() + self.application = problem_instance + return self.application + + def _generate_tiny_model(self, df: any, vehicles: list) -> None: + """ + Generate the problem model for the Tiny configurations. + + :param df: Datafile + :param vehicles: List of vehicle types + """ + # Weight parameters + # Max. total weight on truck / trailer + wt = [100] + # Max. weight on the four levels + wl = [50, 60] + # Max. weights on platforms p, if not angled + wp = [23, 23, 23, 26, 17] + + # Create empty lists for different vehicle parameters. This is required for proper indexing in the model. + weight_list = [0] * (len(vehicles)) + + for i, vehicle in enumerate(vehicles): + df_new = df.loc[df['Type'] == vehicle] + weight_list[i] = int(df_new["Weight"].iloc[0]) + + # Set of available cars + vecs = set(range(len(vehicles))) + # Set of available platforms + platforms_array = np.array([0, 1, 2, 3, 4]) + plats = set(range(len(platforms_array))) + + # Set of platforms that have a limitation on allowed weight + platforms_level_array = np.array([[0, 1, 2], [3, 4]], dtype=object) + plats_l = set(range(len(platforms_level_array))) + + # Set of platforms that form trailer and truck + platforms_truck_trailer_array = np.array([[0, 1, 2, 3, 4]], dtype=object) + plats_t = set(range(len(platforms_truck_trailer_array))) + + # Create decision variables + x = pulp.LpVariable.dicts('x', ((p, v) for p in plats for v in vecs), cat='Binary') + + # Create the problem instance + prob = pulp.LpProblem("ACL", pulp.LpMaximize) + + # Objective function + # Maximize number of vehicles on the truck + prob += pulp.lpSum(x[p, v] for p in plats for v in vecs) + + # Constraints + # (1) Every vehicle can only be assigned to a single platform + for p in plats: + prob += pulp.lpSum(x[p, v] for v in vecs) <= 1 + + # (2) Every platform can only hold a single vehicle + for v in vecs: + prob += pulp.lpSum(x[p, v] for p in plats) <= 1 + + # (3) Weight limit for every platform + for p in plats: + for v in vecs: + prob += weight_list[v] * x[p, v] <= wp[p] + + # (4) Weight constraint for every level + for p_l in plats_l: + prob += pulp.lpSum(weight_list[v] * x[p, v] for p in platforms_level_array[p_l] for v in vecs) <= \ + wl[p_l] + + # (5) Weight constraint for truck and trailer + for t in plats_t: + prob += pulp.lpSum( + weight_list[v] * x[p, v] for p in platforms_truck_trailer_array[t] for v in vecs) <= wt[t] + + self.application = prob + + def _generate_small_model(self, df: any, vehicles: list) -> None: + """ + Generate the problem model for the Small configuration + + :param df: Datafile + :param vehicles: List of vehicle types + """ + # Parameters for the small model (2 levels with 3 and 2 platforms each) + + # Length parameters + # Level 1 (Truck up), 2 (Truck down) + lmax_l = [97, 79] + # Height parameters + # Considers base truck height and height distance between vehicles (~10cm) + hmax_truck = [34, 34, 33, 36, 32, 36] + # Weight parameters + # Max. total weight on truck / trailer + wt = [100] + # Max. weight on the two levels + wl = [65, 50] + # Max. weights on platforms p, if not angled + wp = [23, 23, 23, 26, 17] + # Max. weight on p, if sp is used + wsp = [28, 28, 28] + + _, length_list, height_list, weight_list = self._get_vehicle_params(df, vehicles) + + # Set of available cars + vecs = set(range(len(vehicles))) + # Set of available platforms + platforms_array = np.array([0, 1, 2, 3, 4]) + plats = set(range(len(platforms_array))) + + # Set of possible split platforms + split_platforms_array = np.array([[0, 1], [1, 2], [3, 4]], dtype=object) + plats_sp = set(range(len(split_platforms_array))) + + # Set of platforms that have a limitation on allowed length and weight because they are on the same level + platforms_level_array = np.array([[0, 1, 2], [3, 4]], dtype=object) + plats_l = set(range(len(platforms_level_array))) + + # Set of platforms that form trailer and truck + platforms_truck_trailer_array = np.array([[0, 1, 2, 3, 4]], dtype=object) + plats_t = set(range(len(platforms_truck_trailer_array))) + + # Set of platforms that have a limitation on allowed height + platforms_height_array_truck = np.array([[0, 3], [1, 3], [2, 3], [0, 4], [1, 4], [2, 4]], dtype=object) + plats_h1 = set(range(len(platforms_height_array_truck))) + + # Create decision variables + x = pulp.LpVariable.dicts('x', ((p, v) for p in plats for v in vecs), cat='Binary') + # Usage of split platform + sp = pulp.LpVariable.dicts('sp', (q for q in plats_sp), cat='Binary') + # Auxiliary variable for linearization of quadratic constraints + gamma = pulp.LpVariable.dicts('gamma', (p for p in plats_sp), cat='Binary') + + # Create the 'prob' variable to contain the problem data + prob = pulp.LpProblem("ACL", pulp.LpMaximize) + # Maximize number of vehicles on the truck + prob += pulp.lpSum(x[p, v] for p in plats for v in vecs) + + # Constraints + # (1) Every vehicle can only be assigned to a single platform + for p in plats: + prob += pulp.lpSum(x[p, v] for v in vecs) <= 1 + + # (2) Every platform can only hold a single vehicle + for v in vecs: + prob += pulp.lpSum(x[p, v] for p in plats) <= 1 + + # (3) If a split platform q in plats_sp is used, only one of its "sub platforms" can be used + for q in plats_sp: + prob += pulp.lpSum(x[p, v] for p in split_platforms_array[q] for v in vecs) \ + <= len(split_platforms_array[q]) * (1 - sp[q]) + sp[q] + + # (4) It is always only possible to use a single split-platform for any given p + for q in plats_sp: + for p in plats_sp: + if p != q: + z = bool(set(split_platforms_array[q]) & set(split_platforms_array[p])) + if z is True: + prob += sp[q] + sp[p] <= 1 + + # (5) Length constraint + # Checks that vehicles v on platforms p that belong to level L are shorter than the maximum available length + for L in plats_l: + prob += (pulp.lpSum(x[p, v] * length_list[v] for p in platforms_level_array[L] for v in vecs) + <= lmax_l[L]) + + # (6) Height constraints for truck and trailer, analogue to length constraints + # Truck + for h in plats_h1: + prob += pulp.lpSum(x[p, v] * height_list[v] for p in platforms_height_array_truck[h] for v in vecs) \ + <= hmax_truck[h] + + # (7) Linearization constraint -> gamma == 1, if split platform is used + for q in plats_sp: + prob += pulp.lpSum( + sp[q] + x[p, v] for p in self.intersectset(split_platforms_array[q], platforms_array) + for v in vecs) >= 2 * gamma[q] + + # (8) Weight limit for every platform + for p in plats: + for v in vecs: + prob += weight_list[v] * x[p, v] <= wp[p] + + # (9) If a split platform is used, weight limit == wsp, if not, then weight limit == wp + for q in plats_sp: + for p in split_platforms_array[q]: + prob += pulp.lpSum(weight_list[v] * x[p, v] for v in vecs) <= gamma[q] * wsp[q] \ + + (1 - gamma[q]) * wp[p] + + # (10) Weight constraint for every level + for p_l in plats_l: + prob += pulp.lpSum(weight_list[v] * x[p, v] for p in platforms_level_array[p_l] for v in vecs) <= \ + wl[p_l] + + # (11) Weight constraint for truck and trailer + for p_t in plats_t: + prob += pulp.lpSum( + weight_list[v] * x[p, v] for p in platforms_truck_trailer_array[p_t] for v in vecs) <= wt[p_t] + + self.application = prob + + def _generate_full_model(self, df: any, vehicles: list) -> None: # pylint: disable=R0915 + """ + Generate the problem model for the Full configuration. + + :param df: Datafile + :param vehicles: List of vehicle types + """ + # Horizontal Coefficients: Length reduction + # 1 = forward, 0 = backward + v_coef = np.array([[0.20, 0.15, 0.14, 0.19], + [0.22, 0.22, 0.22, 0.22], + [0.22, 0.13, 0.12, 0.17]]) + + # Vertical Coefficients: Height increase + h_coef = np.array([[0.40, 1, 1, 1], + [0.17, 0.22, 0.21, 0.22], + [0.17, 0.38, 0.32, 0.32]]) + + # Length parameters + # Level 1 (Truck up), 2 (Truck down), 3 (Trailer up), 4 (Trailer down) + lmax_l = [97, 79, 97, 97] + + # Height parameters + # Considers base truck height and height distance between vehicles (~10cm) + hmax_truck = [34, 34, 33, 36, 32, 36] + hmax_trailer = [36, 32, 32, 34] + + # Weight parameters + # Max. total weight + wmax = 180 + # Max. total weight on truck / trailer + wt = [100, 100] + # Max. weight on the four levels + wl = [50, 60, 50, 90] + # Max. weights on platforms p, if not angled + wp = [23, 23, 23, 26, 17, 26, 26, 26, 23, 26] + # Max. weights on p, angled (if possible: 1, 2, 4, 7, 8, 9): + wpa = [20, 22, 17, 18, 19, 22] + # Max. weight on p, if sp is used + wsp = [28, 28, 28, 28, 28, 28] + + class_list, length_list, height_list, weight_list = self._get_vehicle_params(df, vehicles) + + # Set of available cars + vecs = set(range(len(vehicles))) + # Set of available platforms + platforms_array = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + plats = set(range(len(platforms_array))) + + # Set of platforms that can be angled + platforms_angled_array = [1, 2, 4, 7, 8] + vp = [0, 1, 3, 8, 9] + plats_a = set(range(len(platforms_angled_array))) + + # Set of possible split platforms + split_platforms_array = np.array([[0, 1], [1, 2], [3, 4], [5, 6], [7, 8], [8, 9]], dtype=object) + plats_sp = set(range(len(split_platforms_array))) + + # Set of platforms that have a limitation on allowed length and weight because they are on the same level + platforms_level_array = np.array([[0, 1, 2], [3, 4], [5, 6], [7, 8, 9]], dtype=object) + plats_l = set(range(len(platforms_level_array))) + + # Set of platforms that form trailer and truck + platforms_truck_trailer_array = np.array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]], dtype=object) + plats_t = set(range(len(platforms_truck_trailer_array))) + + # Set of platforms that have a limitation on allowed height + platforms_height_array_truck = np.array([[0, 3], [1, 3], [2, 3], [0, 4], [1, 4], [2, 4]], dtype=object) + platforms_height_array_trailer = np.array([[5, 7], [5, 8], [6, 8], [6, 9]], dtype=object) + + plats_h1 = set(range(len(platforms_height_array_truck))) + plats_h2 = set(range(len(platforms_height_array_trailer))) + + # Create decision variables + # Vehicle v assigned to p + x = pulp.LpVariable.dicts('x', ((p, v) for p in plats for v in vecs), cat='Binary') + # Usage of split platform + sp = pulp.LpVariable.dicts('sp', (q for q in plats_sp), cat='Binary') + # Direction of vehicle on p + d = pulp.LpVariable.dicts('d', (p for p in plats), cat='Binary') + # State of platform p in PA - angled == 1, not angled == 0 + a_p = pulp.LpVariable.dicts('a_p', (p for p in plats_a), cat='Binary') + + # Auxiliary variables for linearization of quadratic constraints + y1 = pulp.LpVariable.dicts('y1', (p for p in plats_a), cat='Binary') + y2 = pulp.LpVariable.dicts('y2', (p for p in plats_a), cat='Binary') + y3 = pulp.LpVariable.dicts('y3', (p for p in plats_a), cat='Binary') + y4 = pulp.LpVariable.dicts('y4', (p for p in plats_a), cat='Binary') + ay1 = pulp.LpVariable.dicts('ay1', (p for p in plats_a), cat='Binary') + ay2 = pulp.LpVariable.dicts('ay2', (p for p in plats_a), cat='Binary') + ay3 = pulp.LpVariable.dicts('ay3', (p for p in plats_a), cat='Binary') + ay4 = pulp.LpVariable.dicts('ay4', (p for p in plats_a), cat='Binary') + # Weight for split-platforms + gamma = pulp.LpVariable.dicts('gamma', (p for p in plats_sp), cat='Binary') + + # Create the 'prob' variable to contain the problem data + prob = pulp.LpProblem("ACL", pulp.LpMaximize) + # Maximize number of vehicles on the truck + prob += pulp.lpSum(x[p, v] for p in plats for v in vecs) + + # Assignment constraints + # (1) Every vehicle can only be assigned to a single platform + for p in plats: + prob += pulp.lpSum(x[p, v] for v in vecs) <= 1 + # (2) Every platform can only hold a single vehicle + for v in vecs: + prob += pulp.lpSum(x[p, v] for p in plats) <= 1 + + # (3) If a split platform q in plats_sp is used, only one of its "sub platforms" can be used + for q in plats_sp: + prob += pulp.lpSum(x[p, v] for p in split_platforms_array[q] for v in vecs) \ + <= len(split_platforms_array[q]) * (1 - sp[q]) + sp[q] + + # (3.1) It is always only possible to use a single split-platform for any given p + for q in plats_sp: + for p in plats_sp: + if p != q: + z = bool(set(split_platforms_array[q]) & set(split_platforms_array[p])) + if z is True: + prob += sp[q] + sp[p] <= 1 + + # (3.2) It is not allowed to angle platforms next to empty platforms + for i, p in enumerate(platforms_angled_array): + prob += pulp.lpSum(x[p, v] + x[vp[i], v] for v in vecs) >= 2 * a_p[i] + + # Linearization constraints + # Linearization of d_p and d_v(p) -> orientations of two neighboring cars + for p in platforms_angled_array: + z = platforms_angled_array.index(p) + v_p = vp[z] + prob += (1 - d[p]) + d[v_p] >= 2 * y1[z] + prob += d[p] + d[v_p] >= 2 * y2[z] + prob += (1 - d[p]) + (1 - d[v_p]) >= 2 * y3[z] + prob += d[p] + (1 - d[v_p]) >= 2 * y4[z] + + # Linearization of a_p with y1 - y4 -> linear combination of angle and orientations + for p in platforms_angled_array: + z = platforms_angled_array.index(p) + prob += a_p[z] + y1[z] >= 2 * ay1[z] + prob += a_p[z] + y2[z] >= 2 * ay2[z] + prob += a_p[z] + y3[z] >= 2 * ay3[z] + prob += a_p[z] + y4[z] >= 2 * ay4[z] + + # Linearization of x * ay -> linear combination of assignment and orientation/angle + xay1 = pulp.LpVariable.dicts('xay1', ((p, v) for p in plats_a for v in vecs), cat='Binary') + xay2 = pulp.LpVariable.dicts('xay2', ((p, v) for p in plats_a for v in vecs), cat='Binary') + xay3 = pulp.LpVariable.dicts('xay3', ((p, v) for p in plats_a for v in vecs), cat='Binary') + xay4 = pulp.LpVariable.dicts('xay4', ((p, v) for p in plats_a for v in vecs), cat='Binary') + for p in platforms_angled_array: + z = platforms_angled_array.index(p) + for v in vecs: + prob += ay1[z] + x[z, v] >= 2 * xay1[z, v] + prob += ay2[z] + x[z, v] >= 2 * xay2[z, v] + prob += ay3[z] + x[z, v] >= 2 * xay3[z, v] + prob += ay4[z] + x[z, v] >= 2 * xay4[z, v] + + # Making sure always only 1 case applies + for p in platforms_angled_array: + z = platforms_angled_array.index(p) + prob += ay1[z] + ay2[z] + ay3[z] + ay4[z] <= 1 + prob += y1[z] + y2[z] + y3[z] + y4[z] <= 1 + + # (4) Length constraint + # Checks that vehicles v on platforms p that belong to level L are shorter than the maximum available length + # The length of the vehicles depends on whether they are angled or not and which vehicle is standing on + # platform v(p) + for L in plats_l: + prob += pulp.lpSum(x[p, v] * length_list[v] + - xay1[platforms_angled_array.index(p), v] * + int(v_coef[class_list[v]][0] * length_list[v]) + - xay2[platforms_angled_array.index(p), v] * + int(v_coef[class_list[v]][1] * length_list[v]) + - xay3[platforms_angled_array.index(p), v] * + int(v_coef[class_list[v]][2] * length_list[v]) + - xay4[platforms_angled_array.index(p), v] + * int(v_coef[class_list[v]][3] * length_list[v]) + for p in self.intersectset(platforms_angled_array, platforms_level_array[L]) + for v in vecs) \ + + pulp.lpSum(x[p, v] * length_list[v] + for p in self.diffset(platforms_level_array[L], platforms_angled_array) + for v in vecs) \ + <= lmax_l[L] + + # (5) Platforms can not be angled, if they are part of a split platform + for q in plats_sp: + prob += pulp.lpSum(a_p[platforms_angled_array.index(p)] + for p in self.intersectset(platforms_angled_array, split_platforms_array[q])) \ + <= len(split_platforms_array[q]) * (1 - sp[q]) + + # (6) Weight constraint if split platform is used, gamma == 1 + for q in plats_sp: + prob += pulp.lpSum(sp[q] + x[p, v] for p in self.intersectset(split_platforms_array[q], platforms_array) + for v in vecs) >= 2 * gamma[q] + + # If split platform is used, weight limit == wsp, if not, then weight limit == wp + for q in plats_sp: + for p in split_platforms_array[q]: + prob += (pulp.lpSum(weight_list[v] * x[p, v] for v in vecs) <= gamma[q] * wsp[q] + (1 - gamma[q]) * + wp[p]) + + # (7) If a platform that can be angled is angled, weight limit == wpa + # Need another linearization for that: + apx = pulp.LpVariable.dicts('apx', ((p, v) for p in plats_a for v in vecs), cat='Binary') + for p in platforms_angled_array: + z = platforms_angled_array.index(p) + for v in vecs: + prob += a_p[z] + x[z, v] >= 2 * apx[z, v] + + for p in platforms_angled_array: + prob += pulp.lpSum(weight_list[v] * apx[platforms_angled_array.index(p), v] for v in vecs) \ + <= wpa[platforms_angled_array.index(p)] + + # (8) Weight constraint for every level + for p_l in plats_l: + prob += (pulp.lpSum(weight_list[v] * x[p, v] for p in platforms_level_array[p_l] for v in vecs) <= + wl[p_l]) + + # (9) Weight constraint for truck and trailer + for p_t in plats_t: + prob += (pulp.lpSum(weight_list[v] * x[p, v] for p in platforms_truck_trailer_array[p_t] for v in vecs) + <= wt[p_t]) + + # (10) Weight constraint for entire auto carrier + prob += pulp.lpSum(weight_list[v] * x[p, v] for p in plats for v in vecs) <= wmax + + # (11) Height constraints for truck and trailer, analogue to length constraints + # Truck + for h in plats_h1: + prob += pulp.lpSum(x[p, v] * height_list[v] + - xay1[platforms_angled_array.index(p), v] * + int(h_coef[class_list[v]][0] * height_list[v]) + - xay2[platforms_angled_array.index(p), v] * + int(h_coef[class_list[v]][1] * height_list[v]) + - xay3[platforms_angled_array.index(p), v] * + int(h_coef[class_list[v]][2] * height_list[v]) + - xay4[platforms_angled_array.index(p), v] * + int(h_coef[class_list[v]][3] * height_list[v]) + for p in self.intersectset(platforms_angled_array, platforms_height_array_truck[h]) + for v in vecs) \ + + pulp.lpSum(x[p, v] * height_list[v] + for p in self.diffset(platforms_height_array_truck[h], platforms_angled_array) + for v in vecs) \ + <= hmax_truck[h] + # Trailer + for h in plats_h2: + prob += pulp.lpSum(x[p, v] * height_list[v] + - xay1[platforms_angled_array.index(p), v] * + int(h_coef[class_list[v]][0] * height_list[v]) + - xay2[platforms_angled_array.index(p), v] * + int(h_coef[class_list[v]][1] * height_list[v]) + - xay3[platforms_angled_array.index(p), v] * + int(h_coef[class_list[v]][2] * height_list[v]) + - xay4[platforms_angled_array.index(p), v] * + int(h_coef[class_list[v]][3] * height_list[v]) + for p in self.intersectset(platforms_angled_array, platforms_height_array_trailer[h]) + for v in vecs) \ + + pulp.lpSum(x[p, v] * height_list[v] + for p in self.diffset(platforms_height_array_trailer[h], platforms_angled_array) + for v in vecs) \ + <= hmax_trailer[h] + + self.application = prob + + def _get_vehicle_params(self, df: any, vehicles: list) -> tuple[list, list, list, list]: + """ + Extract vehicle parameters for the problem formulation + + :param df: Dataframe containing vehicle data + :param vehicles: List of vehicle types to consider + :return: Lists containing class, length, height, and weight of vehicles + """ + class_list = [0] * (len(vehicles)) + length_list = [0] * (len(vehicles)) + height_list = [0] * (len(vehicles)) + weight_list = [0] * (len(vehicles)) + + for i in set(range(len(vehicles))): + df_new = df.loc[df['Type'] == vehicles[i]] + class_list[i] = int(df_new["Class"].iloc[0]) + length_list[i] = int(df_new["Length"].iloc[0]) + height_list[i] = int(df_new["Height"].iloc[0]) + weight_list[i] = int(df_new["Weight"].iloc[0]) + + return class_list, length_list, height_list, weight_list + + def validate(self, solution: any) -> tuple[bool, float]: + """ + Checks if the solution is a valid solution. +: + :param solution: Proposed solution + :return: Tuple containing a boolean indicating if the solution is valid + and the time it took to validate the solution + """ + start = start_time_measurement() + status = solution.get("status") + is_valid = status == "Optimal" + return is_valid, end_time_measurement(start) + + def get_solution_quality_unit(self) -> str: + """ + Provides the unit of measure for solution quality. + + :return: The unit of measure fro solution quality + """ + return "Number of loaded vehicles" + + def evaluate(self, solution: any) -> tuple[float, float]: + """ + Checks how good the solution is. + + :param solution: Provided solution + :return: Tuple containing the objective value and the time it took to evaluate the solution + """ + start = start_time_measurement() + objective_value = solution.get("obj_value", 0) + logging.info("Loading successful!") + logging.info(f"{objective_value} cars will fit on the auto carrier.") + + variables = solution.get("variables", {}) + assignments = [key for key in variables if variables[key] > 0] + + logging.info(f"vehicle-to-platform assignments (platform, vehicle): {assignments}") + return objective_value, end_time_measurement(start) + + def save(self, path: str, iter_count: int) -> None: + """ + Saves the problem instance to a JSON file. + + :param path: Directory path where the instance should be saved + :param iter_count: Iteration count (unused) + """ + # Convert our problem instance from Dict to an LP problem and then to json + _, problem_instance = pulp.LpProblem.from_dict(self.application) + # Save problem instance to JSON + problem_instance.to_json(f"{path}/ACL_instance.json") diff --git a/src/modules/applications/optimization/acl/mappings/__init__.py b/src/modules/applications/optimization/acl/mappings/__init__.py new file mode 100644 index 00000000..d7364160 --- /dev/null +++ b/src/modules/applications/optimization/acl/mappings/__init__.py @@ -0,0 +1,20 @@ +# Copyright 2022 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Module for ACL mappings. + +This module provides initializations for ACL related +mappings that are used in the QUARK framework. +""" diff --git a/src/modules/applications/optimization/acl/mappings/ising.py b/src/modules/applications/optimization/acl/mappings/ising.py new file mode 100644 index 00000000..9995707e --- /dev/null +++ b/src/modules/applications/optimization/acl/mappings/ising.py @@ -0,0 +1,209 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +from typing import TypedDict + +import numpy as np +from more_itertools import locate +from qiskit_optimization import QuadraticProgram +from qiskit_optimization.converters import QuadraticProgramToQubo + +from modules.applications.mapping import Mapping +from modules.core import Core +from utils import start_time_measurement, end_time_measurement + + +class Ising(Mapping): + """ + Ising formulation of the auto-carrier loading (ACL) problem. + """ + + def __init__(self): + """ + Constructor method. + """ + super().__init__() + self.submodule_options = ["QAOA", "QiskitQAOA"] + self.global_variables = [] + logging.warning("Currently, all scenarios are too large to be solved with an Ising model.") + logging.warning("Consider using another mapping until the modelling is refined.") + + @staticmethod + def get_requirements() -> list[dict]: + """ + Return requirements of this module. + + :return: list of dict with requirements of this module + """ + return [ + {"name": "numpy", "version": "1.26.4"}, + {"name": "more-itertools", "version": "10.5.0"}, + {"name": "qiskit-optimization", "version": "0.6.1"}, + ] + + def get_parameter_options(self) -> dict: + """ + Returns empty dict as this mapping has no configurable settings. + + :return: Empty dict + """ + return {} + + class Config(TypedDict): + """ + Empty config as this solver has no configurable settings. + """ + pass + + def map_pulp_to_qiskit(self, problem: dict) -> QuadraticProgram: + """ + Maps the problem dict to a quadratic program. + + :param problem: Problem formulation in dict form + :return: Quadratic program in qiskit-optimization format + """ + # Details at: + # https://coin-or.github.io/pulp/guides/how_to_export_models.html + # https://qiskit.org/documentation/stable/0.26/tutorials/optimization/2_converters_for_quadratic_programs.html + + qp = QuadraticProgram() + + # Variables + for variable_dict in problem["variables"]: + if variable_dict["cat"] == "Integer": + lb = variable_dict["lowBound"] + ub = variable_dict["upBound"] + name = variable_dict["name"] + # If the integer variable is actually a binary variable + if lb == 0 and ub == 1: + qp.binary_var(name) + # If the integer variable is non-binary + else: + qp.integer_var(lowerbound=lb, upperbound=ub, name=name) + + # Objective function + obj_arguments = {arg["name"]: arg["value"] for arg in problem["objective"]["coefficients"]} + # Maximize + if problem["parameters"]["sense"] == -1: + qp.maximize(linear=obj_arguments) + # Minimize + else: + qp.minimize(linear=obj_arguments) + + # Constraints + for constraint in problem["constraints"]: + const_arguments = {arg["name"]: arg["value"] for arg in constraint["coefficients"]} + sense = constraint["sense"] + const_sense = "LE" if sense == -1 else "GE" if sense == 1 else "E" + qp.linear_constraint( + linear=const_arguments, + sense=const_sense, + rhs=-1 * constraint["constant"], + name=constraint["name"] + ) + + return qp + + def map(self, problem: dict, config: Config) -> tuple[dict, float]: + """ + Use Ising mapping of qiskit-optimize. + + :param problem: Dict containing the problem parameters + :param config: Config with the parameters specified in Config class + :return: Dict with the Ising, time it took to map it + """ + start = start_time_measurement() + + # Map Linear problem from dictionary (generated by pulp) to quadratic program + qp = self.map_pulp_to_qiskit(problem) + logging.info(qp.export_as_lp_string()) + + # convert quadratic problem to qubo to ising + conv = QuadraticProgramToQubo() + qubo = conv.convert(qp) + + variables = [variable.name for variable in qubo.variables] + qubit_op, _ = qubo.to_ising() + + self.global_variables = variables + + # reverse generate J and t out of qubit PauliSumOperator from qiskit + t_matrix = np.zeros(qubit_op.num_qubits, dtype=complex) + j_matrix = np.zeros((qubit_op.num_qubits, qubit_op.num_qubits), dtype=complex) + + for pauli_op in qubit_op: + pauli_str, coeff = pauli_op.to_list()[0] + index_pos_list = list(locate(pauli_str, lambda a: a == 'Z')) + + if len(index_pos_list) == 1: + t_matrix[index_pos_list[0]] = coeff + elif len(index_pos_list) == 2: + j_matrix[index_pos_list[0]][index_pos_list[1]] = coeff + + return {"J": j_matrix, "t": t_matrix}, end_time_measurement(start) + + def reverse_map(self, solution: dict) -> tuple[dict, float]: + """ + Maps the solution back to the representation needed by the ACL class for validation/evaluation. + + :param solution: Dict with a bit_string containing the solution + :return: Solution mapped accordingly, time it took to map it + """ + start = start_time_measurement() + + if np.any(solution == "-1"): + solution = self._convert_ising_to_qubo(solution) + + result = {"status": [0]} + variables = {} + objective_value = 0 + + for bit in solution: + if solution[bit] > 0: + if "x" in self.global_variables[bit]: + variables[self.global_variables[bit]] = solution[bit] + result["status"] = 'Optimal' + objective_value += solution[bit] + + result["variables"] = variables + result["obj_value"] = objective_value + + return result, end_time_measurement(start) + + @staticmethod + def _convert_ising_to_qubo(solution: any) -> any: + solution = np.array(solution) + with np.nditer(solution, op_flags=['readwrite']) as it: + for x in it: + if x == -1: + x[...] = 0 + return solution + + def get_default_submodule(self, option: str) -> Core: + """ + Returns the default submodule based on the provided option. + + :param option: Option specifying the submodule + :return: Instance of the corresponding submodule + :raises NotImplementedError: If the option is not recognized + """ + if option == "QAOA": + from modules.solvers.qaoa import QAOA # pylint: disable=C0415 + return QAOA() + elif option == "QiskitQAOA": + from modules.solvers.qiskit_qaoa import QiskitQAOA # pylint: disable=C0415 + return QiskitQAOA() + else: + raise NotImplementedError(f"Solver Option {option} not implemented") diff --git a/src/modules/applications/optimization/acl/mappings/qubo.py b/src/modules/applications/optimization/acl/mappings/qubo.py new file mode 100644 index 00000000..632c1f4b --- /dev/null +++ b/src/modules/applications/optimization/acl/mappings/qubo.py @@ -0,0 +1,275 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import TypedDict +import re +import logging + +import numpy as np +from qiskit_optimization import QuadraticProgram +from qiskit_optimization.converters import ( + InequalityToEquality, IntegerToBinary, + LinearEqualityToPenalty +) + +from modules.applications.mapping import Mapping, Core +from utils import start_time_measurement, end_time_measurement + +# TODO Large chunks of this code is duplicated in ACL.mappings.ISING -> unify + + +class Qubo(Mapping): + """ + QUBO formulation for the ACL. + """ + + def __init__(self): + """ + Constructor method. + """ + super().__init__() + self.submodule_options = ["Annealer"] + self.global_variables = [] + + @staticmethod + def get_requirements() -> list[dict]: + """ + Return requirements of this module. + + :return: list of dict with requirements of this module + """ + return [ + {"name": "numpy", "version": "1.26.4"}, + {"name": "qiskit-optimization", "version": "0.6.1"}, + ] + + def get_parameter_options(self) -> dict: + """ + Returns empty dict as this mapping has no configurable settings. + + :return: Empty dictionary + """ + return {} + + class Config(TypedDict): + """ + Empty config as this solver has no configurable settings. + """ + pass + + def map_pulp_to_qiskit(self, problem: dict) -> QuadraticProgram: + """ + Maps the problem dict to a quadratic program. + + :param problem: Problem formulation in dict form + :return: Quadratic program in qiskit-optimization format + """ + # Details at: + # https://coin-or.github.io/pulp/guides/how_to_export_models.html + # https://qiskit.org/documentation/stable/0.26/tutorials/optimization/2_converters_for_quadratic_programs.html + + qp = QuadraticProgram() + + # Variables + for variable_dict in problem["variables"]: + if variable_dict["cat"] == "Integer": + lb = variable_dict["lowBound"] + ub = variable_dict["upBound"] + name = variable_dict["name"] + # If the integer variable is actually a binary variable + if lb == 0 and ub == 1: + qp.binary_var(name) + # If the integer variable is non-binary + else: + qp.integer_var(lowerbound=lb, upperbound=ub, name=name) + + # Objective function + obj_arguments = {arg["name"]: arg["value"] for arg in problem["objective"]["coefficients"]} + + # Maximize + if problem["parameters"]["sense"] == -1: + qp.maximize(linear=obj_arguments) + # Minimize + else: + qp.minimize(linear=obj_arguments) + + # Constraints + for constraint in problem["constraints"]: + const_arguments = {arg["name"]: arg["value"] for arg in constraint["coefficients"]} + sense = constraint["sense"] + const_sense = "LE" if sense == -1 else "GE" if sense == 1 else "E" + + qp.linear_constraint( + linear=const_arguments, + sense=const_sense, + rhs=-1 * constraint["constant"], + name=constraint["name"] + ) + + return qp + + def convert_string_to_arguments(self, input_string: str) -> list[any]: + """ + Converts QUBO in string format to a list of separated arguments, + used to construct the QUBO matrix. + + :param input_string: QUBO in raw string format + :return: List of arguments + """ + terms = re.findall(r'[+\-]?[^+\-]+', input_string) + # Convert the penalty string to a list of lists of the individual arguments in the penalty term + result = [term.strip() for term in terms] + separated_arguments = [] + first_item = True + + for argument in result: + if first_item: + # Remove "maximize" or minimize string from the first argument + argument = argument[8:] + first_item = False + if "*" in argument: + # The variables in each argument are connected by "*" signs. Here we split the variables + elements = argument.split('*') + # Convert string of numbers to floats + new_argument = elements[0].strip() + # Remove empty strings + new_argument = [int(new_argument.replace(" ", "")) if new_argument.replace(" ", "").isdigit() + else float(new_argument.replace(" ", ""))] + new_argument += [el.strip() for el in elements[1:]] + separated_arguments.append(new_argument) + else: + separated_arguments.append(argument) + + return separated_arguments + + def construct_qubo(self, penalty: list[list], variables: list[str]) -> np.ndarray: + """ + Creates QUBO matrix Q to solve linear problem of the form x^T * Q + x. + + :param penalty: List of lists containing all non-zero elements of the QUBO matrix as strings + :param variables: Listing of all variables used in the problem + :return: QUBO in numpy array format + """ + # Create empty qubo matrix + count_variables = len(variables) + qubo = np.zeros((count_variables, count_variables)) + + # Iterate through all the variables twice (x^T, x) + for col, variable in enumerate(variables): + for row, variable2 in enumerate(variables): + # Save the parameters (values in the qubo) + parameter = 0 + for argument in penalty: + if isinstance(argument, list): + # squared variables in diagonals (x^2 == x) + if ( + len(argument) == 2 + and any(isinstance(elem, str) and variable in elem for elem in argument) + and col == row + ): + parameter += argument[0] + # Multiplication of different variables not on diagonal + if ( + len(argument) == 3 + and variable in argument and variable2 in argument and variable > variable2 + ): + parameter += argument[0] + # This value is already taking into account the factor 2 from quadratic term + # For the variables on the diagonal, if the parameter is zero + # We still have to check the sign in + # front of the decision variable. If it is "-", we have to put "-1" on the diagonal. + elif (isinstance(argument, str) and variable in argument + and variable2 in argument and variable == variable2): + if "-" in argument: + parameter += -1 + + qubo[col, row] = parameter + + # Minimization problem + qubo = -qubo.astype(int) + + return qubo + + def map(self, problem: dict, config: Config) -> tuple[dict, float]: + """ + Converts linear program created with pulp to quadratic program to Ising with qiskit to QUBO matrix. + + :param problem: Dict containing the problem parameters + :param config: Config with the parameters specified in Config class + :return: Dict with the QUBO, time it took to map it + """ + start = start_time_measurement() + + # Map Linear problem from dictionary (generated by pulp) to quadratic program to QUBO + qp = self.map_pulp_to_qiskit(problem) + logging.info(qp.export_as_lp_string()) + + ineq2eq = InequalityToEquality() + qp_eq = ineq2eq.convert(qp) + + int2bin = IntegerToBinary() + qp_eq_bin = int2bin.convert(qp_eq) + + lineq2penalty = LinearEqualityToPenalty(100) + qubo = lineq2penalty.convert(qp_eq_bin) + + variables = [variable.name for variable in qubo.variables] + + # convert penalty term to string to QUBO + qubo_string = str(qubo.objective) + arguments = self.convert_string_to_arguments(qubo_string) + qubo_matrix = self.construct_qubo(arguments, variables) + + self.global_variables = variables + + return {"Q": qubo_matrix}, end_time_measurement(start) + + def reverse_map(self, solution: dict) -> tuple[dict, float]: + """ + Maps the solution back to the representation needed by the ACL class for validation/evaluation. + + :param solution: bit_string containing the solution + :return: Solution mapped accordingly, time it took to map it + """ + start = start_time_measurement() + + result = {"status": [0]} + objective_value = 0 + variables = {} + for bit in solution: + if solution[bit] > 0 and "x" in self.global_variables[bit]: + # We only care about assignments of vehicles to platforms: + # We map the solution to the original variables + variables[self.global_variables[bit]] = solution[bit] + result["status"] = 'Optimal' # TODO: I do not think every solution with at least one car is optimal + objective_value += solution[bit] + + result["variables"] = variables + result["obj_value"] = objective_value + + return result, end_time_measurement(start) + + def get_default_submodule(self, option: str) -> Core: + """ + Returns the default submodule based on the provided option. + + :param option: Option specifying the submodule + :return: Instance of the corresponding submodule + :raises NotImplementedError: If the option is not recognized + """ + if option == "Annealer": + from modules.solvers.annealer import Annealer # pylint: disable=C0415 + return Annealer() + else: + raise NotImplementedError(f"Solver Option {option} not implemented") diff --git a/src/modules/applications/optimization/mis/__init__.py b/src/modules/applications/optimization/mis/__init__.py new file mode 100644 index 00000000..b7ca66cc --- /dev/null +++ b/src/modules/applications/optimization/mis/__init__.py @@ -0,0 +1,19 @@ +# Copyright 2022 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Module for MIS mappings + +This module initialize the MIS package +""" diff --git a/src/modules/applications/optimization/mis/data/__init__.py b/src/modules/applications/optimization/mis/data/__init__.py new file mode 100644 index 00000000..1afbe94e --- /dev/null +++ b/src/modules/applications/optimization/mis/data/__init__.py @@ -0,0 +1,19 @@ +# Copyright 2022 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Module for MIS data + +This module initialize the MIS package +""" diff --git a/src/modules/applications/optimization/mis/data/graph_layouts.py b/src/modules/applications/optimization/mis/data/graph_layouts.py new file mode 100644 index 00000000..c66abded --- /dev/null +++ b/src/modules/applications/optimization/mis/data/graph_layouts.py @@ -0,0 +1,100 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import math +import random + +import networkx as nx +import pulser + +# define R_rydberg +R_rydberg = 9.75 + + +def generate_hexagonal_graph(n_nodes: int, spacing: float, filling_fraction: float = 1.0) -> nx.Graph: + """ + Generate a hexagonal graph layout based on the number of nodes and spacing. + + :param n_nodes: The number of nodes in the graph + :param spacing: The spacing between nodes (atoms) + :param filling_fraction: The fraction of available places in the lattice to be filled with nodes. (default: 1.0) + :return: Networkx Graph representing the hexagonal graph layout + """ + if not 0.0 < filling_fraction <= 1.0: + raise ValueError("The filling fraction must be in the domain of (0.0, 1.0].") + + # Create a layout large enough to contain the desired number of atoms at the filling fraction + n_traps = int(n_nodes / filling_fraction) + hexagonal_layout = pulser.register.special_layouts.TriangularLatticeLayout( + n_traps=n_traps, spacing=spacing + ) + + # Fill the layout with traps + reg = hexagonal_layout.hexagonal_register(n_traps) + ids = reg._ids # pylint: disable=W0212 + coords = [coord.tolist() for coord in reg._coords] # pylint: disable=W0212 + traps = dict(zip(ids, coords)) + + # Remove random atoms to get the desired number of atoms + while len(traps) > n_nodes: + atom_to_remove = random.choice(list(traps)) + traps.pop(atom_to_remove) + + # Rename the atoms + node_positions = {i: traps[trap] for i, trap in enumerate(traps.keys())} # pylint: disable=C0206 + + # Create the graph + hexagonal_graph = nx.Graph() + + # Add nodes to the graph + for node_id, coord in node_positions.items(): + hexagonal_graph.add_node(node_id, pos=coord) + + # Generate the edges and add them to the graph + edges = _generate_edges(node_positions=node_positions) + hexagonal_graph.add_edges_from(edges) + + return hexagonal_graph + + +def _generate_edges(node_positions: dict[list[int, list[float]]], radius: float = R_rydberg) -> list[tuple]: + """ + Generate edges between vertices within a given distance 'radius', which defaults to R_rydberg. + + :param node_positions: A dictionary with the node ids as keys, and the node coordinates as values + :param radius: When the distance between two nodes is smaller than this radius, an edge is generated between them + :return: A list of 2-tuples. Each 2-tuple contains two different node ids and represents an edge between those nodes + """ + edges = [] + vertex_keys = list(node_positions.keys()) + for i, vertex_key in enumerate(vertex_keys): + for neighbor_key in vertex_keys[i + 1:]: + distance = _vertex_distance(node_positions[vertex_key], node_positions[neighbor_key]) + if distance <= radius: + edges.append((vertex_key, neighbor_key)) + return edges + + +def _vertex_distance(v0: tuple[float, ...], v1: tuple[float, ...]) -> float: + """ + Calculates distance between two n-dimensional vertices. + For 2 dimensions: distance = sqrt((x0 - x1)**2 + (y0 - y1)**2) + + :param v0: Coordinates of the first vertex + :param v1: Coordinates of the second vertex + return: Distance between the vertices + """ + squared_difference = sum((coordinate0 - coordinate1) ** 2 for coordinate0, coordinate1 in zip(v0, v1)) + + return math.sqrt(squared_difference) diff --git a/src/modules/applications/optimization/mis/mappings/__init__.py b/src/modules/applications/optimization/mis/mappings/__init__.py new file mode 100644 index 00000000..b7ca66cc --- /dev/null +++ b/src/modules/applications/optimization/mis/mappings/__init__.py @@ -0,0 +1,19 @@ +# Copyright 2022 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Module for MIS mappings + +This module initialize the MIS package +""" diff --git a/src/modules/applications/optimization/mis/mappings/neutral_atom.py b/src/modules/applications/optimization/mis/mappings/neutral_atom.py new file mode 100644 index 00000000..f51af4e2 --- /dev/null +++ b/src/modules/applications/optimization/mis/mappings/neutral_atom.py @@ -0,0 +1,91 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import TypedDict + +import networkx as nx +import pulser + +from modules.applications.mapping import Mapping, Core +from utils import start_time_measurement, end_time_measurement + + +class NeutralAtom(Mapping): + """ + Neutral atom formulation for MIS. + """ + + def __init__(self): + """ + Constructor method. + """ + super().__init__() + self.submodule_options = ["NeutralAtomMIS"] + + @staticmethod + def get_requirements() -> list[dict]: + """ + Return requirements of this module. + + :return: List of requirements of this module + """ + return [{"name": "pulser", "version": "1.1.1"}] + + def get_parameter_options(self) -> dict: + """ + Returns the configurable settings for this mapping. + + :return: Empty dictionary, as this mapping has no configurable settings + """ + return {} + + class Config(TypedDict): + """ + Configuration options for Neutral Atom MIS mapping. + """ + pass + + def map(self, problem: nx.Graph, config: Config) -> tuple[dict, float]: + """ + Maps the networkx graph to a neutral atom MIS problem. + + :param problem: Networkx graph representing the MIS problem + :param config: Config with the parameters specified in Config class + :return: Tuple containing a dictionary with the neutral MIS and time it took to map it + """ + start = start_time_measurement() + + pos = nx.get_node_attributes(problem, 'pos') + register = pulser.Register(pos) + + neutral_atom_problem = { + 'graph': problem, + 'register': register + } + + return neutral_atom_problem, end_time_measurement(start) + + def get_default_submodule(self, option: str) -> Core: + """ + Returns the default submodule based on the provided option. + + :param option: Option specifying the submodule + :return: Instance of the corresponding submodule + :raises NotImplementedError: If the option is not recognized + """ + if option == "NeutralAtomMIS": + from modules.solvers.neutral_atom_mis import NeutralAtomMIS # pylint: disable=C0415 + return NeutralAtomMIS() + else: + raise NotImplementedError(f"Solver Option {option} not implemented") diff --git a/src/modules/applications/optimization/mis/mappings/qiro.py b/src/modules/applications/optimization/mis/mappings/qiro.py new file mode 100644 index 00000000..e0e9ee1a --- /dev/null +++ b/src/modules/applications/optimization/mis/mappings/qiro.py @@ -0,0 +1,96 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import TypedDict +import networkx + +from modules.applications.mapping import Core, Mapping +from utils import start_time_measurement, end_time_measurement + + +class QIRO(Mapping): + """ + The quantum-informed recursive optimization (QIRO) formulation for the MIS problem. QIRO recursively simplifies the + problem classically using information obtained with quantum resources. + """ + + def __init__(self): + """ + Constructor method + """ + super().__init__() + self.submodule_options = ["QrispQIRO"] + + @staticmethod + def get_requirements() -> list[dict]: + """ + Return requirements of this module. + + :return: list of dict with requirements of this module + """ + return [{"name": "qrisp", "version": "0.5.2"}] + + def get_parameter_options(self) -> dict: + """ + Returns the configurable settings for this mapping. + + :return: + .. code-block:: python + + return {} + + """ + # TODO "optimizer": { + # "values": ["not", "yet", "implemented"], + # "description": "Which QIRO algorithm should be used?" + # } + return {} + + class Config(TypedDict): + """ + Attributes of a valid config + + .. code-block:: python + pass + """ + pass + + def map(self, problem: networkx.Graph, config: Config) -> tuple[dict, float]: + """ + Maps the networkx graph to a neutral atom MIS problem. + + :param problem: Networkx graph + :param config: Config with the parameters specified in Config class + :return: Dict with neutral MIS, time it took to map it + """ + start = start_time_measurement() + + qiro_mapped_problem = { + 'graph': problem, + } + return qiro_mapped_problem, end_time_measurement(start) + + def get_default_submodule(self, option: str) -> Core: + """ + Returns the default submodule based on the provided option. + + :param option: Option specifying the submodule + :return: Instance of the corresponding submodule + :raises NotImplementedError: If the option is not recognized + """ + if option == "QrispQIRO": + from modules.solvers.qrisp_qiro import QIROSolver # pylint: disable=C0415 + return QIROSolver() + else: + raise NotImplementedError(f"Solver Option {option} not implemented") diff --git a/src/modules/applications/optimization/mis/mis.py b/src/modules/applications/optimization/mis/mis.py new file mode 100644 index 00000000..e146ba9d --- /dev/null +++ b/src/modules/applications/optimization/mis/mis.py @@ -0,0 +1,364 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +import pickle +from typing import TypedDict + +import networkx as nx +import matplotlib.pyplot as plt +from matplotlib.lines import Line2D + +from modules.applications.application import Core +from modules.applications.optimization.optimization import Optimization +from modules.applications.optimization.mis.data.graph_layouts import generate_hexagonal_graph +from utils import start_time_measurement, end_time_measurement + +# Define R_rydberg +R_rydberg = 9.75 + + +class MIS(Optimization): + """ + The maximum independent set (MIS) problem is a combinatorial optimization problem that seeks to find the largest + subset of vertices in a graph such that no two vertices are adjacent. MIS has numerous application in computer + science, network design, resource allocation, and even in physics, where finding optimal configurations can + solve fundamental problems related to stability and energy minimization. + + In a graph, the maximum independent set represents a set of nodes such that no two nodes share an edge. This + property makes it a key element in various optimization scenarios. Due to the problem's combinatorial nature, + it becomes computationally challenging, especially for large graphs, often requiring heuristic or approximate + solutions. + + In the context of QUARK, we employ quantum-inspired approaches and state-of-the-art classical algorithms to + tackle the problem. The graph is generated based on user-defined parameters such as size, spacing, and + filling fraction, which affect the complexity and properties of the generated instance. + """ + + def __init__(self): + """ + Constructor method. + """ + super().__init__("MIS") + self.submodule_options = ["QIRO", "NeutralAtom"] + # TODO add more solvers like classical heuristics, VQE, QAOA, etc. + self.depending_parameters = True + self.graph = None + + @staticmethod + def get_requirements() -> list[dict]: + """ + Returns requirements of this module. + + :return: List of dict with requirements of this module + """ + return [] + + def get_solution_quality_unit(self) -> str: + """ + Returns the unit of measurement for solution quality. + + :return: The unit of measure for solution quality + """ + return "Set size" + + def get_default_submodule(self, option: str) -> Core: + """ + Returns the default submodule based on the provided option. + + :param option: Option specifying the submodule + :return: Instance of the corresponding submodule + :raises NotImplementedError: If the option is not recognized + """ + if option == "QIRO": + from modules.applications.optimization.mis.mappings.qiro import QIRO # pylint: disable=C0415 + return QIRO() + elif option == "NeutralAtom": + from QUARK.src.modules.applications.optimization.mis.mappings.neutral_atom import NeutralAtom # pylint: disable=C0415 + return NeutralAtom() + else: + raise NotImplementedError(f"Mapping Option {option} not implemented") + + def get_parameter_options(self) -> dict: + """ + Returns the configurable settings for this application. + + :return: Configuration dictionary for this application + .. code-block:: python + + return { + "size": { + "values": [1, 5, 10, 15], + "custom_input": True, + "allow_ranges": True, + "postproc": int, + "description": "How large should your graph be?" + }, + "graph_type": { + "values": ["hexagonal", "erdosRenyi"], + "postproc": str, + "description": "Do you want a hexagonal or an Erdos-Renyi graph?", + "depending_submodule": True + } + } + """ + return { + "size": { + "values": [1, 5, 10, 15], + "custom_input": True, + "allow_ranges": True, + "postproc": int, + "description": "How large should your graph be?" + }, + "graph_type": { + "values": ["hexagonal", "erdosRenyi"], + "postproc": str, + "description": "Do you want a hexagonal or an Erdos-Renyi graph?", + "depending_submodule": True + } + } + + def get_available_submodules(self, option: list) -> list: + """ + Changes mapping options based on selection of graphs. + + :param option: List of chosen graph type + :return: List of available submodules + """ + if option == ["hexagonal"]: + return ["QIRO", "NeutralAtom"] + else: + return ["QIRO"] + + def get_depending_parameters(self, option: str, config: dict) -> dict: + """ + Returns parameters necessary for chosen problem option. + + :param option: The chosen option + :param config: The current config + :return: The parameters for the given option + """ + + more_params = { + "filling_fraction": { + "values": [x / 10 for x in range(2, 11, 2)], + "custom_input": True, + "allow_ranges": True, + "postproc": float, + "description": "What should be the filling fraction of the hexagonal graph / p of erdosRenyi graph?" + }} + if option == "QIRO": + more_params["seed"] = { + "values": ["No"], + "custom_input": True, + "description": "Do you want to set a seed? If yes, please set an integer number" + } + + elif option == "NeutralAtom": + pass # No additional parameters needed at the moment + else: + raise NotImplementedError(f"Option {option} not implemented") + if "hexagonal" in config["graph_type"]: + more_params["spacing"] = { + "values": [x / 10 for x in range(3, 11, 2)], + "custom_input": True, + "allow_ranges": True, + "postproc": float, + "description": "How much space do you want between your nodes, relative to Rydberg distance?" + } + param_to_return = {} + for key, value in more_params.items(): + if key not in config: + param_to_return[key] = value + + return param_to_return + + class Config(TypedDict): + """ + Configuration attributes for generating an MIS problem. + + Attributes: + size (int): The number of nodes in the graph. + spacing (float): The spacing between nodes in the graph. + filling_fraction (float): The fraction of available places in the lattice filled with nodes + """ + size: int + spacing: float + filling_fraction: float + + def generate_problem(self, config: Config) -> nx.Graph: + """ + Generates a graph to solve the MIS problem for. + + :param config: Config specifying the size and connectivity for the problem + :return: Networkx graph representing the problem + """ + if config is None: + logging.warning("No config provided, using default values: graph_type='hexagonal', size=3, spacing=1," + "filling_fraction=0.5") + config = {"graph_type": "hexagonal", "size": 3, "spacing": 1, "filling_fraction": 0.5} + + graph_type = config.get('graph_type') + size = config.get('size') + filling_fraction = config.get('filling_fraction') + + if graph_type == "erdosRenyi": + gseed = config.get("seed") + + if gseed == "No": + graph = nx.erdos_renyi_graph(size, filling_fraction) + + else: + try: + gseed = int(gseed) + except ValueError: + logging.warning(f"Please select an integer number as seed for the Erdos-Renyi graph instead of " + f"'{gseed}'. The seed is instead set to 0.") + gseed = 0 + graph = nx.erdos_renyi_graph(size, filling_fraction, seed=gseed) + logging.info("Created MIS problem with the nx.erdos_renyi graph method, with the following attributes:") + logging.info(f" - Graph size: {size}") + logging.info(f" - p: {filling_fraction}") + logging.info(f" - seed: {gseed}") + + else: + if config.get('spacing') is None: + spacing = 0.5 + else: + spacing = config.get('spacing') + graph = generate_hexagonal_graph(n_nodes=size, + spacing=spacing * R_rydberg, + filling_fraction=filling_fraction) + logging.info("Created MIS problem with the generate hexagonal graph method, with the following attributes:") + logging.info(f" - Graph size: {size}") + logging.info(f" - Spacing: {spacing * R_rydberg}") + logging.info(f" - Filling fraction: {filling_fraction}") + + self.graph = graph + return graph.copy() + + def validate(self, solution: list) -> tuple[bool, float]: + """ + Checks if the solution is an independent set. + + :param solution: List containing the nodes of the solution + :return: Boolean whether the solution is valid and time it took to validate + """ + start = start_time_measurement() + is_valid = True + + nodes = list(self.graph.nodes()) + edges = list(self.graph.edges()) + + # Check if the solution is independent + is_independent = all((u, v) not in edges for u, v in edges if u in solution and v in solution) + if is_independent: + logging.info("The solution is independent.") + else: + logging.warning("The solution is not independent.") + is_valid = False + + # Check if the solution is a set + solution_set = set(solution) + is_set = len(solution_set) == len(solution) + if is_set: + logging.info("The solution is a set.") + else: + logging.warning("The solution is not a set.") + is_valid = False + + # Check if the solution is a subset of the original nodes + is_subset = all(node in nodes for node in solution) + if is_subset: + logging.info("The solution is a subset of the problem.") + else: + logging.warning("The solution is not a subset of the problem.") + is_valid = False + + return is_valid, end_time_measurement(start) + + def evaluate(self, solution: list) -> tuple[int, float]: + """ + Calculates the size of the solution. + + :param solution: List containing the nodes of the solution + :return: Set size, time it took to calculate the set size + """ + start = start_time_measurement() + set_size = len(solution) + + logging.info(f"Size of solution: {set_size}") + + return set_size, end_time_measurement(start) + + def save(self, path: str, iter_count: int) -> None: + """ + Saves the generated problem graph to a file. + + :param path: Path to save the problem graph + :param iter_count: Iteration count for file versioning + """ + with open(f"{path}/graph_iter_{iter_count}.gpickle", "wb") as file: + pickle.dump(self.graph, file, pickle.HIGHEST_PROTOCOL) + + def visualize_solution(self, processed_solution: list[int], path: str): + """ + Plot the problem graph with the solution nodes highlighted + + :param processed_solution: The solution already processed by :func:`process_solution`, a list of visited node IDs in order of being visited. + :param path: File path for the plot + :returns: None + """ + NODE_SIZE = 300 # Default=300 + EDGE_WIDTH = 1.0 # Default=1.0 + FONT_SIZE = 12 # Default=12 + COLOR_INCLUDED = "red" + COLOR_EXCLUDED = "gray" + + G = self.graph + included_nodes = [node for node in G.nodes() if node in processed_solution] + excluded_nodes = [node for node in G.nodes() if node not in processed_solution] + pos = nx.circular_layout(G) + included_pos = {n: n for n, _ in pos.items() if n in processed_solution} + excluded_pos = {n: n for n, _ in pos.items() if n not in processed_solution} + legend_elements = [ + Line2D( + [0], + [0], + marker='o', + ls="None", + label="Included", + markerfacecolor=COLOR_INCLUDED, + markeredgewidth=0, + markersize=10), + Line2D( + [0], + [0], + marker='o', + ls="None", + label="Excluded", + markerfacecolor=COLOR_EXCLUDED, + markeredgewidth=0, + markersize=10) + ] + + nx.draw_networkx_nodes(G, pos, nodelist=included_nodes, node_size=NODE_SIZE, node_color=COLOR_INCLUDED) + nx.draw_networkx_nodes(G, pos, nodelist=excluded_nodes, node_size=NODE_SIZE, node_color=COLOR_EXCLUDED) + nx.draw_networkx_labels(G, pos, included_pos, font_size=FONT_SIZE, font_weight="bold") + nx.draw_networkx_labels(G, pos, excluded_pos, font_size=FONT_SIZE) + nx.draw_networkx_edges(G, pos, width=EDGE_WIDTH) + + plt.legend(handles=legend_elements) + plt.savefig(path) + plt.close() diff --git a/src/modules/applications/optimization/optimization.py b/src/modules/applications/optimization/optimization.py new file mode 100644 index 00000000..5c87949b --- /dev/null +++ b/src/modules/applications/optimization/optimization.py @@ -0,0 +1,137 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from abc import ABC, abstractmethod +import logging + +from modules.applications.application import Application +from utils import start_time_measurement, end_time_measurement + + +class Optimization(Application, ABC): + """ + Optimization Module for QUARK, is used by all Optimization applications. + """ + + @abstractmethod + def validate(self, solution: any) -> tuple[bool, float]: + """ + Checks if the solution is a valid solution. + + :param solution: Proposed solution + :return: Bool value if solution is valid and the time it took to validate the solution + """ + pass + + @abstractmethod + def get_solution_quality_unit(self) -> str: + """ + Returns the unit of the evaluation. + + :return: String with the unit + """ + pass + + @abstractmethod + def evaluate(self, solution: any) -> tuple[float, float]: + """ + Checks how good the solution is. + + :param solution: Provided solution + :return: Tuple with the evaluation and the time it took to create it + """ + pass + + @abstractmethod + def generate_problem(self, config: dict) -> any: + """ + Creates a concrete problem and returns it. + + :param config: Configuration for problem creation + :return: Generated problem + """ + pass + + def process_solution(self, solution: any) -> tuple[any, float]: + """ + Most of the time the solution has to be processed before it can be validated and evaluated. + This might not be necessary in all cases, so the default is to return the original solution. + + :param solution: Proposed solution + :return: Tuple with processed solution and the execution time to process it + """ + return solution, 0.0 + + def preprocess(self, input_data: any, config: dict, **kwargs) -> tuple[any, float]: + """ + For optimization problems, we generate the actual problem instance in the preprocess function. + + :param input_data: Input data (usually not used in this method) + :param config: Config for the problem creation + :param kwargs: Optional additional arguments + :return: Tuple with output and the preprocessing time + """ + start = start_time_measurement() + output = self.generate_problem(config) + return output, end_time_measurement(start) + + def postprocess(self, input_data: any, config: dict, **kwargs) -> tuple[any, float]: + """ + For optimization problems, we process the solution here, then validate and evaluate it. + + :param input_data: Data which should be evaluated for this optimization problem + :param config: Config for the problem creation + :param kwargs: Optional additional arguments + :return: Tuple with results and the postprocessing time + """ + processed_solution = None + try: + processed_solution, time_to_process_solution = self.process_solution(input_data) + solution_validity, time_to_validation = self.validate(processed_solution) + except Exception as e: + logging.exception(f"Exception on processing the solution: {e}") + solution_validity = False + time_to_process_solution = None + time_to_validation = None + + if solution_validity and (processed_solution is not None): + solution_quality, time_to_evaluation = self.evaluate(processed_solution) + self.visualize_solution(processed_solution, f"{kwargs["store_dir"]}/solution.pdf") + else: + solution_quality = None + time_to_evaluation = None + + self.metrics.add_metric_batch({ + "application_score_value": solution_quality, + "application_score_unit": self.get_solution_quality_unit(), + "application_score_type": str(float), + "processed_solution": processed_solution, + "time_to_process_solution": time_to_process_solution, + "time_to_validation": time_to_validation, + "time_to_evaluation": time_to_evaluation + }) + + return solution_validity, sum(filter(None, [ + time_to_process_solution, time_to_validation, time_to_evaluation + ])) + + def visualize_solution(self, processed_solution: any, path: str) -> None: + """ + Creates visualizations of a processed and validated solution and writes them to disk. + Override if applicable. Default is to do nothing. + + :param processed_solution: A solution that was already processed by :func:`process_solution` + :param path: File path for the plot + :returns: None + """ + pass diff --git a/src/modules/applications/optimization/pvc/__init__.py b/src/modules/applications/optimization/pvc/__init__.py new file mode 100644 index 00000000..e9c96938 --- /dev/null +++ b/src/modules/applications/optimization/pvc/__init__.py @@ -0,0 +1,19 @@ +# Copyright 2022 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Module for PVC mappings. + +This module initializes the PVC mapping packages +""" diff --git a/src/modules/applications/optimization/pvc/data/createReferenceGraph.py b/src/modules/applications/optimization/pvc/data/createReferenceGraph.py new file mode 100644 index 00000000..148fe444 --- /dev/null +++ b/src/modules/applications/optimization/pvc/data/createReferenceGraph.py @@ -0,0 +1,54 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import networkx as nx +import pickle + +# Create the original graph as a MultiDiGraph +graph = nx.MultiDiGraph() + +with open("reference_data.txt") as infile: + for line in infile: + line_elements = line.split() + + # Extract start and end attributes from line elements + r_start, s_start, n_start, c_start, t_start, l_start = map(int, line_elements[1:7]) + r_end, s_end, n_end, c_end, t_end, l_end = map(int, line_elements[8:14]) + duration = float(line_elements[15]) + + # Handle missing or invalid data with default values + if s_start == -1: + s_start = 0 + t_start = 1 # TODO except of picking a hardcoded value here we should select 1 from the dataset itself + c_start = 1 + if s_end == -1: + s_end = 0 + t_end = 1 + c_end = 1 + if n_start == -1: + n_start = 0 + if n_end == -1: + n_end = 0 + + # Reduce the number of tools and configurations for simplicity + if c_end < 3 and c_start < 3 and t_start < 2 and t_end < 2: + graph.add_edge( + (s_start, n_start), (s_end, n_end), + c_start=c_start, t_start=t_start, + c_end=c_end, t_end=t_end, weight=duration + ) + +# Save the graph to a file in gpickle format +with open("reference_graph.gpickle", "wb") as file: + pickle.dump(graph, file, pickle.HIGHEST_PROTOCOL) diff --git a/src/modules/applications/optimization/pvc/data/reference_data.txt b/src/modules/applications/optimization/pvc/data/reference_data.txt new file mode 100644 index 00000000..0466784f --- /dev/null +++ b/src/modules/applications/optimization/pvc/data/reference_data.txt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e44e2b30f84a3b80da3800c02e9b18cb9582b77157ce5525bc592f8cf9e3a489 +size 6224696 diff --git a/src/modules/applications/optimization/pvc/data/reference_graph.gpickle b/src/modules/applications/optimization/pvc/data/reference_graph.gpickle new file mode 100644 index 00000000..60a33391 --- /dev/null +++ b/src/modules/applications/optimization/pvc/data/reference_graph.gpickle @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e8b82cff0bdd6cd919b3ba475aa3c195e7f638af6bea930126921eb6efff5322 +size 513537 diff --git a/src/modules/applications/optimization/pvc/mappings/__init__.py b/src/modules/applications/optimization/pvc/mappings/__init__.py new file mode 100644 index 00000000..e9c96938 --- /dev/null +++ b/src/modules/applications/optimization/pvc/mappings/__init__.py @@ -0,0 +1,19 @@ +# Copyright 2022 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Module for PVC mappings. + +This module initializes the PVC mapping packages +""" diff --git a/src/modules/applications/optimization/pvc/mappings/ising.py b/src/modules/applications/optimization/pvc/mappings/ising.py new file mode 100644 index 00000000..68574679 --- /dev/null +++ b/src/modules/applications/optimization/pvc/mappings/ising.py @@ -0,0 +1,159 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import TypedDict +import logging + +import networkx as nx +import numpy as np +from dimod import qubo_to_ising + +from modules.applications.mapping import Mapping, Core +from modules.applications.optimization.pvc.mappings.qubo import QUBO +from utils import start_time_measurement, end_time_measurement + + +class Ising(Mapping): + """ + Ising formulation for the PVC. + + """ + + def __init__(self): + """ + Constructor method. + """ + super().__init__() + self.submodule_options = ["QAOA", "PennylaneQAOA"] + self.key_mapping = None + + @staticmethod + def get_requirements() -> list[dict]: + """ + Returns requirements of this module. + + :return: List of dictionaries with requirements of this module + """ + return [ + {"name": "networkx", "version": "3.4.2"}, + {"name": "numpy", "version": "1.26.4"}, + {"name": "dimod", "version": "0.12.18"}, + *QUBO.get_requirements() + ] + + def get_parameter_options(self) -> dict: + """ + Returns the configurable settings for this mapping. + + :return: Dictionary containing parameter options. + .. code-block:: python + + return { + "lagrange_factor": { + "values": [0.75, 1.0, 1.25], + "description": "By which factor would you like to multiply your Lagrange?" + } + } + """ + return { + "lagrange_factor": { + "values": [0.75, 1.0, 1.25], + "description": "By which factor would you like to multiply your Lagrange?" + } + } + + class Config(TypedDict): + """ + Configuration attributes for Ising mapping. + + Attributes: + lagrange_factor (float): Factor to multiply the Langrange. + """ + lagrange_factor: float + + def map(self, problem: nx.Graph, config: Config) -> tuple[dict, float]: + """ + Uses the PVC QUBO formulation and converts it to an Ising representation. + + :param problem: Networkx graph representing the PVC problem + :param config: Config dictionary with the mapping configuration + :return: Tuple containing a dictionary with the ising problem and time it took to map it + """ + start = start_time_measurement() + + # Convert the PVC problem to QUBO + qubo_mapping = QUBO() + q, _ = qubo_mapping.map(problem, config) + + # Convert QUBO to ising using dimod + t, j, _ = qubo_to_ising(q["Q"]) + + # Extract unique configuration and tool attributes from the graph + config = [x[2]['c_start'] for x in problem.edges(data=True)] + config = list(set(config + [x[2]['c_end'] for x in problem.edges(data=True)])) + + tool = [x[2]['t_start'] for x in problem.edges(data=True)] + tool = list(set(tool + [x[2]['t_end'] for x in problem.edges(data=True)])) + + # Initialize J matrix and mapping + timesteps = int((problem.number_of_nodes() - 1) / 2 + 1) + matrix_size = problem.number_of_nodes() * len(config) * len(tool) * timesteps + j_matrix = np.zeros((matrix_size, matrix_size), dtype=float) + self.key_mapping = {} + + # Map J values to a matrix representation + index_counter = 0 + for key, value in j.items(): + if key[0] not in self.key_mapping: + self.key_mapping[key[0]] = index_counter + index_counter += 1 + if key[1] not in self.key_mapping: + self.key_mapping[key[1]] = index_counter + index_counter += 1 + u = self.key_mapping[key[0]] + v = self.key_mapping[key[1]] + j_matrix[u][v] = value + + return {"J": j_matrix, "t": np.array(list(t.values()))}, end_time_measurement(start) + + def reverse_map(self, solution: dict) -> tuple[dict, float]: + """ + Maps the solution back to the representation needed by the PVC class for validation/evaluation. + + :param solution: Dictionary containing the solution + :return: Tuple with the remapped solution and time it took to reverse map + """ + start = start_time_measurement() + logging.info(f"Key Mapping: {self.key_mapping}") + + result = {key: 1 if solution[self.key_mapping[key]] == 1 else 0 for key in self.key_mapping} + + return result, end_time_measurement(start) + + def get_default_submodule(self, option: str) -> Core: + """ + Returns the default submodule based on the provided option. + + :param option: Option specifying the submodule + :return: Instance of the corresponding submodule + :raises NotImplementedError: If the option is not recognized + """ + if option == "QAOA": + from modules.solvers.qaoa import QAOA # pylint: disable=C0415 + return QAOA() + if option == "PennylaneQAOA": + from modules.solvers.pennylane_qaoa import PennylaneQAOA # pylint: disable=C0415 + return PennylaneQAOA() + else: + raise NotImplementedError(f"Solver Option {option} not implemented") diff --git a/src/modules/applications/optimization/pvc/mappings/qubo.py b/src/modules/applications/optimization/pvc/mappings/qubo.py new file mode 100644 index 00000000..c62cf779 --- /dev/null +++ b/src/modules/applications/optimization/pvc/mappings/qubo.py @@ -0,0 +1,201 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import itertools +from collections import defaultdict +from typing import TypedDict +import logging + +import networkx as nx + +from modules.applications.mapping import Mapping, Core +from utils import start_time_measurement, end_time_measurement + + +class QUBO(Mapping): + """ + QUBO formulation for the PVC. + """ + + def __init__(self): + """ + Constructor method. + """ + super().__init__() + self.submodule_options = ["Annealer"] + + @staticmethod + def get_requirements() -> list[dict]: + """ + Return requirements of this module. + + :return: List of dictionaries with requirements of this module + """ + return [{"name": "networkx", "version": "3.4.2"}] + + def get_parameter_options(self) -> dict: + """ + Returns the configurable settings for this mapping. + + :return: Dictionary containing parameter options + .. code-block:: python + + return { + "lagrange_factor": { + "values": [0.75, 1.0, 1.25], + "description": "By which factor would you like to multiply your Lagrange?" + } + } + + """ + return { + "lagrange_factor": { + "values": [0.75, 1.0, 1.25], + "description": "By which factor would you like to multiply your Lagrange?" + } + } + + class Config(TypedDict): + """ + Configuration attributes of QUBO mapping. + + Attributes: + lagrange_factor (float): Factor to multiply the Langrange. + + """ + lagrange_factor: float + + def map(self, problem: nx.Graph, config: Config) -> tuple[dict, float]: + """ + Maps the networkx graph to a QUBO formulation. + + :param problem: Networkx graph representing the PVC problem + :param config: Config dictionary with the mapping configuration + :return: Tuple containing the QUBO dictionary and the time it took to map it + """ + # Inspired by https://dnx.readthedocs.io/en/latest/_modules/dwave_networkx/algorithms/tsp.html + start = start_time_measurement() + lagrange_factor = config['lagrange_factor'] + + # Estimate lagrange if not provided + n = problem.number_of_nodes() + timesteps = int((n - 1) / 2 + 1) + + # Get the number of different configs and tools + config = [x[2]['c_start'] for x in problem.edges(data=True)] + config = list(set(config + [x[2]['c_end'] for x in problem.edges(data=True)])) + + tool = [x[2]['t_start'] for x in problem.edges(data=True)] + tool = list(set(tool + [x[2]['t_end'] for x in problem.edges(data=True)])) + + if problem.number_of_edges() > 0: + weights = [x[2]['weight'] for x in problem.edges(data=True)] + weights = list(filter(lambda a: a != max(weights), weights)) + lagrange = sum(weights) / len(weights) * timesteps + else: + lagrange = 2 + + lagrange *= lagrange_factor + logging.info(f"Selected lagrange is: {lagrange}") + + if n in (1, 2) or len(problem.edges) < n * (n - 1) // 2: + msg = "graph must be a complete graph with at least 3 nodes or empty" + raise ValueError(msg) + + # Creating the QUBO + q = defaultdict(float) + + # We need to implement the following constrains: + # Only visit 1 node of each seam + # Don`t visit nodes twice (even if their config/tool is different) + # We only need to visit base node at the once since this path from last node to base node is unique anyway + + # Constraint to only visit a node/seam once + for node in problem: # for all nodes in the graph + for pos_1 in range(timesteps): # for number of timesteps + for t_start in tool: + for c_start in config: + q[((node, c_start, t_start, pos_1), (node, c_start, t_start, pos_1))] -= lagrange + for t_end in tool: + # For all configs and tools + for c_end in config: + if c_start != c_end or t_start != t_end: + q[((node, c_start, t_start, pos_1), (node, c_end, t_end, pos_1))] += 1.0 * lagrange + for pos_2 in range(pos_1 + 1, timesteps): + # Penalize visiting same node again in another timestep + q[((node, c_start, t_start, pos_1), (node, c_end, t_end, pos_2))] += 2.0 * lagrange + # Penalize visiting other node of same seam + if node != (0, 0): + # (0,0) is the base node, it is not a seam + # Get the other nodes of the same seam + other_seam_nodes = [ + x for x in problem.nodes if x[0] == node[0] and x[1] != node + ] + for other_seam_node in other_seam_nodes: + # Penalize visiting other node of same seam + q[((node, c_start, t_start, pos_1), + (other_seam_node, c_end, t_end, pos_2))] += 2.0 * lagrange + + # Constraint to only visit a single node in a single timestep + for pos in range(timesteps): + for node_1 in problem: + for t_start in tool: + for c_start in config: + q[((node_1, c_start, t_start, pos), (node_1, c_start, t_start, pos))] -= lagrange + for t_end in tool: + for c_end in config: + for node_2 in set(problem) - {node_1}: # for all nodes except node1 -> node1 + q[((node_1, c_start, t_start, pos), (node_2, c_end, t_end, pos))] += lagrange + + # Objective that minimizes distance + for u, v in itertools.combinations(problem.nodes, 2): + for pos in range(timesteps): + for t_start in tool: + for t_end in tool: + for c_start in config: + for c_end in config: + nextpos = (pos + 1) % timesteps + edge_u_v = next( + item for item in list(problem[u][v].values()) + if item["c_start"] == c_start and item["t_start"] == t_start and + item["c_end"] == c_end and item["t_end"] == t_end + ) + # Since it is the other direction we switch start and end of tool and config + edge_v_u = next( + item for item in list(problem[v][u].values()) + if item["c_start"] == c_end and item["t_start"] == t_end and + item["c_end"] == c_start and item["t_end"] == t_start + ) + # Going from u -> v + q[((u, c_start, t_start, pos), (v, c_end, t_end, nextpos))] += edge_u_v['weight'] + # Going from v -> u + q[((v, c_end, t_end, pos), (u, c_start, t_start, nextpos))] += edge_v_u['weight'] + + logging.info("Created Qubo") + + return {"Q": q}, end_time_measurement(start) + + def get_default_submodule(self, option: str) -> Core: + """ + Returns the default submodule based on the provided option. + + :param option: Option specifying the submodule + :return: Instance of the corresponding submodule + :raises NotImplementedError: If the option is not recognized + """ + if option == "Annealer": + from modules.solvers.annealer import Annealer # pylint: disable=C0415 + return Annealer() + else: + raise NotImplementedError(f"Solver Option {option} not implemented") diff --git a/src/modules/applications/optimization/pvc/pvc.py b/src/modules/applications/optimization/pvc/pvc.py new file mode 100644 index 00000000..87d1c6a7 --- /dev/null +++ b/src/modules/applications/optimization/pvc/pvc.py @@ -0,0 +1,393 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import itertools +from typing import TypedDict +import pickle +import logging +import os + +import networkx as nx +import matplotlib.pyplot as plt +from matplotlib.lines import Line2D +from matplotlib.patches import Patch +import numpy as np + +from modules.applications.application import Core +from modules.applications.optimization.optimization import Optimization +from utils import start_time_measurement, end_time_measurement + + +class PVC(Optimization): + """ + In modern vehicle manufacturing, robots take on a significant workload, including performing welding + jobs, sealing welding joints, or applying paint to the car body. While the robot’s tasks vary widely, + the objective remains the same: Perform a job with the highest possible quality in the shortest amount + of time, optimizing efficiency and productivity on the manufacturing line. + + For instance, to protect a car’s underbody from corrosion, exposed welding seams are sealed + by applying a polyvinyl chloride layer (PVC). The welding seams need to be traversed by a robot to + apply the material. It is related to TSP, but different and even more complex in some aspects. + + The problem of determining the optimal route for robots to traverse all seams shares similarities + with Traveling Salesman Problem (TSP), as it involves finding the shortest possible route to + visit multiple locations. However, it introduces additional complexities, such as different tool + and configuration requirements for each seam, making it an even more challenging problem to solve. + """ + + def __init__(self): + """ + Constructor method. + """ + super().__init__("PVC") + self.submodule_options = [ + "Ising", "QUBO", "GreedyClassicalPVC", "ReverseGreedyClassicalPVC", "RandomPVC" + ] + + @staticmethod + def get_requirements() -> list[dict]: + """ + Returns requirements of this module. + + :return: List of dict with requirements of this module + """ + return [ + {"name": "networkx", "version": "3.4.2"}, + {"name": "numpy", "version": "1.26.4"} + ] + + def get_solution_quality_unit(self) -> str: + """ + Returns the unit of measure for solution quality. + + :return: Unit of measure for solution quality + """ + return "Tour cost" + + def get_default_submodule(self, option: str) -> Core: + """ + Returns the default submodule based on the provided option. + + :param option: Option specifying the submodule + :return: Instance of the corresponding submodule + :raises NotImplementedError: If the option is not recognized + """ + if option == "Ising": + from modules.applications.optimization.pvc.mappings.ising import Ising # pylint: disable=C0415 + return Ising() + elif option == "QUBO": + from modules.applications.optimization.pvc.mappings.qubo import QUBO # pylint: disable=C0415 + return QUBO() + elif option == "GreedyClassicalPVC": + from modules.solvers.greedy_classical_pvc import GreedyClassicalPVC # pylint: disable=C0415 + return GreedyClassicalPVC() + elif option == "ReverseGreedyClassicalPVC": + from modules.solvers.reverse_greedy_classical_pvc import ReverseGreedyClassicalPVC # pylint: disable=C0415 + return ReverseGreedyClassicalPVC() + elif option == "RandomPVC": + from modules.solvers.random_classical_pvc import RandomPVC # pylint: disable=C0415 + return RandomPVC() + else: + raise NotImplementedError(f"Mapping Option {option} not implemented") + + def get_parameter_options(self) -> dict: + """ + Returns the configurable settings for this application. + + :return: Dictionary containing parameter options + .. code-block:: python + + return { + "seams": { + "values": list(range(1, 18)), + "description": "How many seams should be sealed?" + } + } + """ + return { + "seams": { + "values": list(range(1, 18)), + # In the current implementation the graph can only be as large as the reference input graph + "description": "How many seams should be sealed?" + } + } + + class Config(TypedDict): + """ + Configuration attributes for PVC problem generation. + + Attributes: + seams (int): Number of seams for the graph + """ + seams: int + + def generate_problem(self, config: Config) -> nx.Graph: + """ + Uses the reference graph to generate a problem for a given config. + + :param config: Config specifying the number of seams for the problem + :return: Networkx graph representing the problem + """ + if config is None: + config = {"seams": 3} + seams = config['seams'] + + # Read in the original graph + with open(os.path.join(os.path.dirname(__file__), "data", "reference_graph.gpickle"), "rb") as file: + graph = pickle.load(file) + + # Get number of seam in graph + seams_in_graph = list({x[0] for x in graph.nodes}) + seams_in_graph.sort() + seams_in_graph.remove(0) # Always need the base node 0 (which is not a seam) + + if len(seams_in_graph) < seams: + logging.info("Too many seams! The original graph has less seams than that!") + + unwanted_seams = seams_in_graph[-len(seams_in_graph) + seams:] + unwanted_nodes = [x for x in graph.nodes if x[0] in unwanted_seams] + + for node in unwanted_nodes: + graph.remove_node(node) + + if not nx.is_strongly_connected(graph): + logging.error("Graph is not connected!") + raise ValueError("Graph is not connected!") + + # Gather unique configurations and tools + config = [x[2]['c_start'] for x in graph.edges(data=True)] + config = list(set(config + [x[2]['c_end'] for x in graph.edges(data=True)])) + tool = [x[2]['t_start'] for x in graph.edges(data=True)] + tool = list(set(tool + [x[2]['t_end'] for x in graph.edges(data=True)])) + + # Fill the rest of the missing edges with high values + current_edges = [ + (edge[0], edge[1], edge[2]['t_start'], edge[2]['t_end'], edge[2]['c_start'], edge[2]['c_end']) + for edge in graph.edges(data=True) + ] + all_possible_edges = list(itertools.product(list(graph.nodes), repeat=2)) + all_possible_edges = [ + (edges[0], edges[1], t_start, t_end, c_start, c_end) + for edges in all_possible_edges + for c_end in config + for c_start in config + for t_end in tool + for t_start in tool if edges[0] != edges[1] + ] + + missing_edges = [item for item in all_possible_edges if item not in current_edges] + + # Add these edges with very high values + for edge in missing_edges: + graph.add_edge( + edge[0], edge[1], c_start=edge[4], t_start=edge[2], c_end=edge[5], t_end=edge[3], weight=100000 + ) + + logging.info("Created PVC problem with the following attributes:") + logging.info(f" - Number of seams: {seams}") + logging.info(f" - Number of different configs: {len(config)}") + logging.info(f" - Number of different tools: {len(tool)}") + + self.application = graph + return graph.copy() + + def process_solution(self, solution: dict) -> tuple[list, float]: + """ + Converts solution dictionary to list of visited seams. + + :param solution: Unprocessed solution + :return: Processed solution and the time it took to process it + """ + start_time = start_time_measurement() + nodes = list(self.application.nodes()) + start = ((0, 0), 1, 1) + route: list = [None] * int((len(self.application) - 1) / 2 + 1) + visited_seams = [] + + if sum(value == 1 for value in solution.values()) > len(route): + logging.warning("Result is longer than route! This might be problematic!") + + # Prevent duplicate node entries by enforcing only one occurrence per node along route + for (node, config, tool, timestep), val in solution.items(): + if val and (node[0] not in visited_seams): + if route[timestep] is not None: + visited_seams.remove(route[timestep][0][0]) + route[timestep] = (node, config, tool) + visited_seams.append(node[0]) + + # Fill missing values in the route + if None in route: + logging.info(f"Route until now is: {route}") + nodes_unassigned = [(node, 1, 1) for node in nodes if node[0] not in visited_seams] + nodes_unassigned = list(np.random.permutation(nodes_unassigned, dtype=object)) + logging.info(nodes_unassigned) + logging.info(visited_seams) + logging.info(nodes) + for idx, node in enumerate(route): + if node is None: + route[idx] = nodes_unassigned.pop(0) + + # Cycle solution to start at provided start location + if start is not None and route[0] != start: + idx = route.index(start) + route = route[idx:] + route[:idx] + + parsed_route = ' ->\n'.join( + [ + f' Node {visit[0][1]} of Seam {visit[0][0]} using config ' + f' {visit[1]} & tool {visit[2]}' + for visit in route + ] + ) + logging.info(f"Route found:\n{parsed_route}") + + return route, end_time_measurement(start_time) + + def validate(self, solution: list) -> tuple[bool, float]: + """ + Checks if all seams and the home position are visited for a given solution. + + :param solution: List containing the nodes of the solution + :return: Boolean whether the solution is valid and time it took to validate + """ + # Check if all seams are visited in route + start = start_time_measurement() + visited_seams = {seam[0][0] for seam in solution if seam is not None} + + if len(visited_seams) == len(solution): + logging.info(f"All {len(solution) - 1} seams and " + "the base node got visited (We only need to visit 1 node per seam)") + return True, end_time_measurement(start) + else: + logging.error(f"Only {len(visited_seams) - 1} got visited") + return False, end_time_measurement(start) + + def evaluate(self, solution: list) -> tuple[float, float]: + """ + Calculates the tour length for a given valid tour. + + :param solution: List containing the nodes of the solution + :return: Tour length, time it took to calculate the tour length + """ + start = start_time_measurement() + + # Get the total distance + total_dist = 0 + for idx, _ in enumerate(solution[:-1]): + edge = next( + item for item in list(self.application[solution[idx][0]][solution[idx + 1][0]].values()) + if item["c_start"] == solution[idx][1] and item["t_start"] == solution[idx][2] and + item["c_end"] == solution[idx + 1][1] and item["t_end"] == solution[idx + 1][2] + ) + dist = edge['weight'] + total_dist += dist + logging.info(f"Total distance (without return): {total_dist}") + + # Add distance between start and end point to complete cycle + return_edge = next( + item for item in list(self.application[solution[0][0]][solution[-1][0]].values()) + if item["c_start"] == solution[0][1] and item["t_start"] == solution[0][2] and + item["c_end"] == solution[-1][1] and item["t_end"] == solution[-1][2] + ) + return_distance = return_edge['weight'] + logging.info(f"Distance between start and end: {return_distance}") + + # Get distance for full cycle + distance = total_dist + return_distance + logging.info(f"Total distance (including return): {distance}") + + return distance, end_time_measurement(start) + + def save(self, path: str, iter_count: int) -> None: + """ + Saves the generated problem graph to a file. + + :param path: Path to save the problem graph + :param iter_count: Iteration count for file versioning + """ + with open(f"{path}/graph_iter_{iter_count}.gpickle", "wb") as file: + pickle.dump(self.application, file, pickle.HIGHEST_PROTOCOL) + + def visualize_solution(self, processed_solution, path: str): + """ + Plot a graph representing the possible locations where seams can start or end, with arrows representing either idle movements or the sealing of a seam + + :param processed_solution: The solution already processed by :func:`process_solution`, a list of tuples representing seam start points and the config and tool needed to seal the seam. + :param path: File path for the plot + :returns: None + """ + NODE_SIZE = 300 # Default=300 + EDGE_WIDTH = 1.0 # Default=1.0 + FONT_SIZE = 12 # Default=12 + + highest_node_id = max(node[1] for node in self.application.nodes()) + G = nx.MultiDiGraph() + G.add_nodes_from(range(highest_node_id + 1)) + pos = nx.circular_layout(G) + + tools = set() + configs = set() + current_node = 0 + for ((seam1, node1), config, tool) in processed_solution[1:]: + config = config - 1 + tools.add(tool) + configs.add(config) + (seam2, node2) = next((seam, node) + for (seam, node) in self.application.nodes() if seam == seam1 and not node == node1) + assert seam1 == seam2, "This is bad" + if not current_node == node1: + G.add_edge(current_node, node1, color=7, width=EDGE_WIDTH, style=-1) + G.add_edge(node1, node2, color=tool, width=2 * EDGE_WIDTH, style=config) + current_node = node2 + + # The 8 here controls how many edges between the same two nodes are at + # most drawn with spacing between them before drawing them on top of each + # other to avoid cluttering + connectionstyle = [f"arc3,rad={r}" for r in itertools.accumulate([0.15] * 8)] + style_options = ["solid", "dotted", "dashed", "dashdot"] + cmap = plt.cm.Dark2 + tools = list(tools) + configs = list(configs) + legend_elements = [Line2D([0], + [0], + color=cmap(7), + lw=EDGE_WIDTH, + ls=':', + label="Idle Movement")] + [Patch(facecolor=cmap(i), + label=f"Tool {i}") for i in tools] + [Line2D([0], + [0], + color="black", + lw=2 * EDGE_WIDTH, + ls=style_options[i % len( + style_options)], + label=f"Config {i + 1}") for i in configs] + colors = nx.get_edge_attributes(G, 'color').values() + widths = nx.get_edge_attributes(G, 'width').values() + styles = [':' if i == -1 else style_options[i % len(style_options)] + for i in nx.get_edge_attributes(G, 'style').values()] + + nx.draw_networkx( + G, + pos, + node_size=NODE_SIZE, + font_size=FONT_SIZE, + style=list(styles), + edge_color=colors, + edge_cmap=cmap, + width=list(widths), + connectionstyle=connectionstyle) + + plt.legend(handles=legend_elements) + plt.savefig(path) + plt.close() diff --git a/src/modules/applications/optimization/sat/__init__.py b/src/modules/applications/optimization/sat/__init__.py new file mode 100644 index 00000000..0ef93529 --- /dev/null +++ b/src/modules/applications/optimization/sat/__init__.py @@ -0,0 +1,19 @@ +# Copyright 2022 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Module for SAT mappings + +This module initializes the SAT application +""" diff --git a/src/modules/applications/optimization/sat/mappings/__init__.py b/src/modules/applications/optimization/sat/mappings/__init__.py new file mode 100644 index 00000000..0ef93529 --- /dev/null +++ b/src/modules/applications/optimization/sat/mappings/__init__.py @@ -0,0 +1,19 @@ +# Copyright 2022 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Module for SAT mappings + +This module initializes the SAT application +""" diff --git a/src/modules/applications/optimization/sat/mappings/choiising.py b/src/modules/applications/optimization/sat/mappings/choiising.py new file mode 100644 index 00000000..62639ebe --- /dev/null +++ b/src/modules/applications/optimization/sat/mappings/choiising.py @@ -0,0 +1,156 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import TypedDict + +import numpy as np +from dimod import qubo_to_ising + +from modules.applications.mapping import Mapping, Core +from modules.applications.optimization.sat.mappings.choiqubo import ChoiQUBO +from utils import start_time_measurement, end_time_measurement + + +class ChoiIsing(Mapping): + """ + Ising formulation for SAT problem using QUBO by Choi (1004.2226). + """ + + def __init__(self): + """ + Constructor method. + """ + super().__init__() + self.submodule_options = ["QAOA", "PennylaneQAOA"] + self.problem = None + self.qubo_mapping = None + + @staticmethod + def get_requirements() -> list[dict]: + """ + Return requirements of this module. + + :return: List of dict with requirements of this module + """ + return [ + {"name": "numpy", "version": "1.26.4"}, + {"name": "dimod", "version": "0.12.18"}, + *ChoiQUBO.get_requirements() + ] + + def get_parameter_options(self) -> dict: + """ + Returns the configurable settings for this mapping. + + :return: Dictionary with parameter options + .. code-block:: python + + return { + "hard_reward": { + "values": [0.1, 0.5, 0.9, 0.99], + "description": "What Bh/A ratio do you want? (How strongly to enforce hard constraints)" + }, + "soft_reward": { + "values": [0.1, 1, 2], + "description": "What Bh/Bs ratio do you want? This value is multiplied with the " + "number of tests." + } + } + """ + return { + "hard_reward": { + "values": [0.1, 0.5, 0.9, 0.99], + "description": "What Bh/A ratio do you want? (How strongly to enforce hard constraints)" + }, + "soft_reward": { + "values": [0.1, 1, 2], + "description": "What Bh/Bs ratio do you want? This value is multiplied with the number of tests." + } + } + + class Config(TypedDict): + """ + Attributes of a valid config. + + .. code-block:: python + + hard_reward: float + soft_reward: float + + """ + hard_reward: float + soft_reward: float + + def map(self, problem: any, config: Config) -> tuple[dict, float]: + """ + Uses the ChoiQUBO formulation and converts it to an Ising. + + :param problem: SAT problem + :param config: Dictionary with the mapping config + :return: Dict with the ising, time it took to map it + """ + start = start_time_measurement() + self.problem = problem + + # call mapping function + self.qubo_mapping = ChoiQUBO() + q, _ = self.qubo_mapping.map(problem, config) + t, j, _ = qubo_to_ising(q["Q"]) + + # Convert Ising dict to matrix + n = (len(problem[0]) + len(problem[1])) * 3 + t_vector = np.zeros(n, dtype=float) + j_matrix = np.zeros((n, n), dtype=float) + + for key, value in t.items(): + t_vector[key] = value + + for key, value in j.items(): + j_matrix[key[0]][key[1]] = value + + return {"J": j_matrix, "t": t_vector}, end_time_measurement(start) + + def reverse_map(self, solution: dict) -> tuple[dict, float]: + """ + Maps the solution back to the representation needed by the SAT class for validation/evaluation. + + :param solution: Dictionary containing the solution + :return: Solution mapped accordingly, time it took to map it + """ + start = start_time_measurement() + + # convert raw solution into the right format to use reverse_map() of ChoiQUBO.py + solution_dict = dict(enumerate(solution)) + + # reverse map + result, _ = self.qubo_mapping.reverse_map(solution_dict) + + return result, end_time_measurement(start) + + def get_default_submodule(self, option: str) -> Core: + """ + Returns the default submodule based on the provided option. + + :param option: Option specifying the submodule + :return: Instance of the corresponding submodule + :raises NotImplementedError: If the option is not recognized + """ + if option == "QAOA": + from modules.solvers.qaoa import QAOA # pylint: disable=C0415 + return QAOA() + if option == "PennylaneQAOA": + from modules.solvers.pennylane_qaoa import PennylaneQAOA # pylint: disable=C0415 + return PennylaneQAOA() + else: + raise NotImplementedError(f"Solver Option {option} not implemented") diff --git a/src/modules/applications/optimization/sat/mappings/choiqubo.py b/src/modules/applications/optimization/sat/mappings/choiqubo.py new file mode 100644 index 00000000..2a1651a5 --- /dev/null +++ b/src/modules/applications/optimization/sat/mappings/choiqubo.py @@ -0,0 +1,250 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from itertools import combinations, product +from typing import TypedDict +import logging + +from nnf import Var, And + +from modules.applications.mapping import Mapping, Core +from utils import start_time_measurement, end_time_measurement + + +class ChoiQUBO(Mapping): + """ + QUBO formulation for SAT problem by Choi (1004.2226). + """ + + def __init__(self): + """ + Constructor method. + """ + super().__init__() + self.submodule_options = ["Annealer"] + self.nr_vars = None + self.reverse_dict = None + + @staticmethod + def get_requirements() -> list[dict]: + """ + Return requirements of this module. + + :return: List of dict with requirements of this module + """ + return [{"name": "nnf", "version": "0.4.1"}] + + def get_parameter_options(self) -> dict: + """ + Returns the configurable settings for this mapping. + + :return: Dictionary with parameter options + .. code-block:: python + + return { + "hard_reward": { + "values": [0.1, 0.5, 0.9, 0.99], + "description": "What Bh/A ratio do you want? (How strongly to enforce hard constraints)" + }, + "soft_reward": { + "values": [0.1, 1, 2], + "description": "What Bh/Bs ratio do you want? This value is multiplied with the " + "number of tests." + } + } + """ + return { + "hard_reward": { + "values": [0.1, 0.5, 0.9, 0.99], + "description": ( + "What Bh/A ratio do you want?" + "(How strongly to enforce hard constraints)" + ) + }, + "soft_reward": { + "values": [0.1, 1, 2], + "description": ( + "What Bh/Bs ratio do you want?" + "This value is multiplied with the number of tests." + ) + } + } + + class Config(TypedDict): + """ + Attributes of a valid config. + + .. code-block:: python + + hard_reward: float + soft_reward: float + + """ + hard_reward: float + soft_reward: float + + def map(self, problem: tuple[And, list], config: Config) -> tuple[dict, float]: + """ + Converts a MaxSAT instance with hard and soft constraints into a graph problem -- + solving MaxSAT then corresponds to solving an instance of the Maximal Independent Set problem. + See Andrew Lucas (2014), or the original publication by Choi (1004.2226). + + :param problem: A tuple containing hard and soft constraints + :param config: Config with the parameters specified in Config class + :return: Dictionary containing the QUBO representation and the time taken + """ + start = start_time_measurement() + + hard_constraints, soft_constraints = problem + a = 1 + bh = config['hard_reward'] * a + # divide Bh by the number of test clauses, such that fulfilling a test result is less favourable than + # satisfying a constraint, which aim to prioritize. + bs = bh * config['soft_reward'] / len(soft_constraints) + + # Count the number of different variables that appear in the vehicle options problem: + self.nr_vars = len(hard_constraints.vars().union(And(soft_constraints).vars())) + # Edges variable holds all edges in the resulting graph + edges = {} + # lit_occur is a dictionary which will store the information in which clause a certain literal will occur. + lit_occur = {} + + def _add_clause(clause, curr_edges, curr_lit_occ, pos): + literals = [f"{el}-{pos}" for el in clause.children] + # Connect the literals within one clause + for cmb in combinations(literals, 2): + # Add a weight for each edge within clause + curr_edges[cmb] = a + # Add the occurrences of the variables to the occurrences dictionary + for var in clause.children: + if var.name not in curr_lit_occ.keys(): + curr_lit_occ[var.name] = {True: [], False: []} + # Add occurrences and mark that they correspond to hard constraints + curr_lit_occ[var.name][var.true].append(pos) + return curr_edges, curr_lit_occ + + # Convert the hard constraints into the graph + for idx, hard_constraint in enumerate(hard_constraints): + edges, lit_occur = _add_clause(hard_constraint, edges, lit_occur, idx) + + # Save the current total clause count: + constraints_max_ind = len(hard_constraints) + # Repeat the procedure for the soft constraints: + for idx, soft_constraint in enumerate(soft_constraints): + edges, lit_occur = _add_clause(soft_constraint, edges, lit_occur, idx + constraints_max_ind) + + # Connect conflicting clauses using the lit_occur dict: + for literal, positions_dict in lit_occur.items(): + # for every literal lit, we check its occurrences and connect the non-negated and negated occurrences. + for pos_true, pos_false in product(positions_dict[True], positions_dict[False]): + if pos_true != pos_false: + # Employ the notation from nnf, where the tilde symbol ~ corresponds to negation. + lit_true, lit_false = f"{literal}-{pos_true}", f"~{literal}-{pos_false}" + # Add a penalty for each such edge: + edges[(lit_true, lit_false)] = a + + # Collect all different nodes that we have in our graph, omitting repetitions: + node_set = set([]) + for nodes in edges.keys(): + node_set = node_set.union(set(nodes)) + + node_list = sorted(node_set) + # Fix a mapping (node -> binary variable) + relabel_dict = {v: i for i, v in enumerate(node_list)} + # Save the reverse mapping, which is later used to decode the solution. + self.reverse_dict = dict(enumerate(node_list)) + + def _remap_pair(pair): + """Small helper function that maps the nodes of an edge to binary variables""" + return relabel_dict[pair[0]], relabel_dict[pair[1]] + + # Save the QUBO corresponding to the graph. + q = {_remap_pair(key): val for key, val in edges.items()} + + for v in node_list: + # Add different energy rewards depending on whether it is a hard or a soft constraint + if int(v.split('-')[-1]) < constraints_max_ind: + # if hard cons, add -Bh as the reward + q[_remap_pair((v, v))] = -bh + else: + # for soft constraints, add -Bs + q[_remap_pair((v, v))] = -bs + + logging.info(f"Converted to Choi QUBO with {len(node_list)} binary variables. Bh={config['hard_reward']}," + f" Bs={bs}.") + return {'Q': q}, end_time_measurement(start) + + def reverse_map(self, solution: dict) -> tuple[dict, float]: + """ + Maps the solution back to the representation needed by the SAT class for validation/evaluation. + + :param solution: Dictionary containing the solution + :return: Solution mapped accordingly, time it took to map it + """ + start = start_time_measurement() + # We define the literals list, so that we can check the self-consistency of the solution. That is, we save all + # assignments proposed by the annealer, and see if there is no contradiction. (In principle a solver + # could mandate L3 = True and L3 = False, resulting in a contradiction.) + literals = [] + # Assignments saves the actual solution + assignments = [] + + for node, tf in solution.items(): + # Check if node is included in the set (i.e. if tf is True (1)) + if tf: + # Convert back to the language of literals + lit_str = self.reverse_dict[node] + # Check if the literal is negated: + if lit_str.startswith('~'): + # Remove the negation symbol + lit_str = lit_str.replace('~', '') + # Save a negated literal object, will be used for self-consistency check + lit = Var(lit_str).negate() + # Add the negated literal to the assignments, removing the (irrelevant) position part + assignments.append(Var(lit_str.split('-')[0]).negate()) + else: + # If literal is true, no ~ symbol needs to be removed: + lit = Var(lit_str) + assignments.append(Var(lit_str.split('-')[0])) + literals.append(lit) + + # Check for self-consistency of solution; Check that the assignments of all literals are consistent: + if not And(set(literals)).satisfiable(): + logging.error('Generated solution is not self-consistent!') + raise ValueError("Inconsistent solution for the ChoiQubo returned.") + + # If the solution is consistent, find and add potentially missing variables: + assignments = sorted(set(assignments)) + # Find missing vars, or more precisely, their labels: + missing_vars = set(range(self.nr_vars)) - {int(str(a).replace('L', '').replace('~', '')) for a in assignments} + + # Add the variables that found were missing: + for nr in missing_vars: + assignments.append(Var(f'L{nr}')) + + return {list(v.vars())[0]: v.true for v in sorted(assignments)}, end_time_measurement(start) + + def get_default_submodule(self, option: str) -> Core: + """ + Returns the default submodule based on the provided option. + + :param option: Option specifying the submodule + :return: Instance of the corresponding submodule + :raises NotImplementedError: If the option is not recognized + """ + if option == "Annealer": + from modules.solvers.annealer import Annealer # pylint: disable=C0415 + return Annealer() + else: + raise NotImplementedError(f"Solver Option {option} not implemented") diff --git a/src/modules/applications/optimization/sat/mappings/dinneenising.py b/src/modules/applications/optimization/sat/mappings/dinneenising.py new file mode 100644 index 00000000..89e58672 --- /dev/null +++ b/src/modules/applications/optimization/sat/mappings/dinneenising.py @@ -0,0 +1,148 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import TypedDict + +import numpy as np +from dimod import qubo_to_ising +from nnf import And + +from modules.applications.mapping import Mapping, Core +from modules.applications.optimization.sat.mappings.dinneenqubo import DinneenQUBO +from utils import start_time_measurement, end_time_measurement + + +class DinneenIsing(Mapping): + """ + Ising formulation for SAT using Dinneen QUBO. + """ + + def __init__(self): + """ + Constructor method. + """ + super().__init__() + self.submodule_options = ["QAOA", "PennylaneQAOA"] + self.problem = None + self.qubo_mapping = None + + @staticmethod + def get_requirements() -> list[dict]: + """ + Return requirements of this module. + + :return: List of dict with requirements of this module + """ + return [ + {"name": "nnf", "version": "0.4.1"}, + {"name": "numpy", "version": "1.26.4"}, + {"name": "dimod", "version": "0.12.18"}, + *DinneenQUBO.get_requirements() + ] + + def get_parameter_options(self) -> dict: + """ + Returns the configurable settings for this mapping. + + :return: Dictionary with parameter options + .. code-block:: python + + return { + "lagrange": { + "values": [0.1, 1, 2], + "description": "What Lagrange parameter to multiply with the number of (hard) " + "constraints?" + } + } + """ + return { + "lagrange": { + "values": [0.1, 1, 2], + "description": "What Lagrange parameter to multiply with the number of (hard) constraints?" + } + } + + class Config(TypedDict): + """ + Attributes of a valid config. + + .. code-block:: python + + lagrange: float + + """ + lagrange: float + + def map(self, problem: any, config: Config) -> tuple[dict, float]: + """ + Uses the DinneenQUBO formulation and converts it to an Ising. + + :param problem: SAT problem + :param config: Dictionary with the mapping config + :return: Dict with the ising, time it took to map it + """ + start = start_time_measurement() + self.problem = problem + + # call mapping function + self.qubo_mapping = DinneenQUBO() + q, _ = self.qubo_mapping.map(problem, config) + t, j, _ = qubo_to_ising(q["Q"]) + + # Convert Ising dict to matrix + n = (len(problem[0]) + len(problem[1])) + len(problem[0].vars().union(And(problem[1]).vars())) + t_vector = np.zeros(n, dtype=float) + j_matrix = np.zeros((n, n), dtype=float) + + for key, value in t.items(): + t_vector[key] = value + + for key, value in j.items(): + j_matrix[key[0]][key[1]] = value + + return {"J": j_matrix, "t": t_vector}, end_time_measurement(start) + + def reverse_map(self, solution: dict) -> tuple[dict, float]: + """ + Maps the solution back to the representation needed by the SAT class for validation/evaluation. + + :param solution: Dictionary containing the solution + :return: Solution mapped accordingly, time it took to map it + """ + start = start_time_measurement() + + # Convert raw solution into the right format to use reverse_map() of ChoiQUBO.py + solution_dict = dict(enumerate(solution)) + + # Reverse map + result, _ = self.qubo_mapping.reverse_map(solution_dict) + + return result, end_time_measurement(start) + + def get_default_submodule(self, option: str) -> Core: + """ + Returns the default submodule based on the provided option. + + :param option: Option specifying the submodule + :return: Instance of the corresponding submodule + :raises NotImplementedError: If the option is not recognized + """ + if option == "QAOA": + from modules.solvers.qaoa import QAOA # pylint: disable=C0415 + return QAOA() + if option == "PennylaneQAOA": + from modules.solvers.pennylane_qaoa import PennylaneQAOA # pylint: disable=C0415 + return PennylaneQAOA() + else: + raise NotImplementedError(f"Solver Option {option} not implemented") diff --git a/src/modules/applications/optimization/sat/mappings/dinneenqubo.py b/src/modules/applications/optimization/sat/mappings/dinneenqubo.py new file mode 100644 index 00000000..791e30f9 --- /dev/null +++ b/src/modules/applications/optimization/sat/mappings/dinneenqubo.py @@ -0,0 +1,198 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from itertools import combinations +from typing import TypedDict +import logging + +from nnf import And + +from modules.applications.mapping import Mapping, Core +from utils import start_time_measurement, end_time_measurement + + +class DinneenQUBO(Mapping): + """ + QUBO formulation for SAT as given by Dinneen -- see also the description in the QUARK paper (2202.03028). + """ + + def __init__(self): + """ + Constructor method. + """ + super().__init__() + self.submodule_options = ["Annealer"] + self.nr_vars = None + + @staticmethod + def get_requirements() -> list[dict]: + """ + Return requirements of this module. + + :return: List of dict with requirements of this module + """ + return [{"name": "nnf", "version": "0.4.1"}] + + def get_parameter_options(self) -> dict: + """ + Returns the configurable settings for this mapping. + + :return: Dictionary with parameter options + .. code-block:: python + + return { + "lagrange": { + "values": [0.1, 1, 2], + "description": "What Lagrange param. to multiply with the number of (hard) constr.?" + } + } + """ + return { + "lagrange": { + "values": [0.1, 1, 2], + "description": "What Lagrange parameter to multiply with the number of (hard) constraints?" + } + } + + class Config(TypedDict): + """ + Attributes of a valid config. + + .. code-block:: python + + lagrange: float + + """ + lagrange: float + + def map(self, problem: tuple[And, list], config: Config) -> tuple[dict, float]: + """ + Performs the mapping into a QUBO formulation, as given by Dinneen. See also the QUARK paper. + + :param problem: SAT problem + :param config: Config with the parameters specified in Config class + :return: Tuple with the QUBO, time it took to map it + """"" + start = start_time_measurement() + + # Extract hard and soft constraints from the generated problem + hard, soft = problem + + # Count the variables + self.nr_vars = len(hard.vars().union(And(soft).vars())) + lagrange = config['lagrange'] + # Lagrange parameter is a factor of the number of soft constraints. + lagrange *= len(soft) + + def _add_clause(curr_qubo_dict: dict[tuple[int, int], float], + clause: any, + pos: int, + weight: float) -> dict[tuple[int, int], float]: + """ + Function that adds the QUBO terms corresponding to the clause and updates the QUBO dictionary + accordingly. Additionally, the weight of the clause is taken into account. + + :param curr_qubo_dict: Current QUBO dictionary + :param clause: Clause to be added + :param pos: Position of the auxiliary variable + :param weight: Weight of the clause + :return: Updated QUBO dictionary + """ + + def _check_and_add(dictionary: dict, key: tuple[int, int], value: float) -> dict: + """ + Helper function that checks if key is present or not in dictionary and adds a value, adding the key + if missing. + + :param dictionary: Dictionary to be updated + :param key: Key to check in the dictionary + :param value: Value to add to the key + :return: Updated dictionary + """ + key = tuple(sorted(key)) + if key not in dictionary.keys(): + dictionary[key] = value + else: + dictionary[key] += value + return dictionary + + cl_dict = {} + for variable in clause.children: + for variable_name in variable.vars(): + # Transforms the negations (0,1) into signs (-1, 1) + cl_dict[int(variable_name[1:])] = (int(variable.true) - 1 / 2) * 2 + + # Add the linear term of the auxiliary variable w + curr_qubo_dict = _check_and_add(curr_qubo_dict, (pos, pos), 2 * weight) + + # Add x linear terms and xw terms. + for qvar, val in cl_dict.items(): + # qvar is the name of the var, val is the sign corresponding to whether the variable is negated or not. + # linear x term: + curr_qubo_dict = _check_and_add(curr_qubo_dict, (qvar, qvar), -weight * val) + # x * w (aux. var.) term + curr_qubo_dict = _check_and_add(curr_qubo_dict, (qvar, pos), -weight * val) + # Add combinations + for q1, q2 in combinations(cl_dict.keys(), 2): + curr_qubo_dict = _check_and_add(curr_qubo_dict, (q1, q2), weight * cl_dict[q1] * cl_dict[q2]) + + return curr_qubo_dict + + qubo_dict = {} + # Add the hard constraints and add the lagrange parameter as weight + for clause_ind, hard_clause in enumerate(hard): + qubo_dict = _add_clause(qubo_dict, hard_clause, self.nr_vars + clause_ind, lagrange) + + # Add the soft constraints and start the enumeration at the final index corresponding to hard cons. + for clause_ind, soft_clause in enumerate(soft): + qubo_dict = _add_clause(qubo_dict, soft_clause, self.nr_vars + clause_ind + len(hard), 1) + + logging.info( + f"Generate Dinneen QUBO with {self.nr_vars + len(hard) + len(soft)} binary variables." + f" Lagrange parameter used was: {config['lagrange']}." + ) + + return {"Q": qubo_dict}, end_time_measurement(start) + + def reverse_map(self, solution: dict) -> tuple[dict, float]: + """ + Reverse mapping of the solution obtained from the Dinneen QUBO. + + :param solution: Dictionary containing the solution + :return: Solution mapped accordingly, time it took to map it + """ + start = start_time_measurement() + mapped_sol = {} + for i in range(self.nr_vars): + # if variable not present in solution, its assignment does not matter + if i not in solution.keys(): + mapped_sol[f'L{i}'] = True + else: + mapped_sol[f'L{i}'] = bool(solution[i]) + + return mapped_sol, end_time_measurement(start) + + def get_default_submodule(self, option: str) -> Core: + """ + Returns the default submodule based on the provided option. + + :param option: Option specifying the submodule + :return: Instance of the corresponding submodule + :raises NotImplementedError: If the option is not recognized + """ + if option == "Annealer": + from modules.solvers.annealer import Annealer # pylint: disable=C0415 + return Annealer() + else: + raise NotImplementedError(f"Solver Option {option} not implemented") diff --git a/src/modules/applications/optimization/sat/mappings/direct.py b/src/modules/applications/optimization/sat/mappings/direct.py new file mode 100644 index 00000000..85b874c4 --- /dev/null +++ b/src/modules/applications/optimization/sat/mappings/direct.py @@ -0,0 +1,140 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import io +from typing import TypedDict +import logging + +from nnf import And +from nnf.dimacs import dump +from pysat.formula import CNF, WCNF + +from modules.applications.mapping import Mapping, Core +from utils import start_time_measurement, end_time_measurement + + +class Direct(Mapping): + """ + Maps the problem from nnf to pysat. + """ + + def __init__(self): + """ + Constructor method. + """ + super().__init__() + self.submodule_options = ["ClassicalSAT", "RandomSAT"] + + @staticmethod + def get_requirements() -> list[dict]: + """ + Return requirements of this module. + + :return: List of dict with requirements of this module + """ + return [ + {"name": "nnf", "version": "0.4.1"}, + {"name": "python-sat", "version": "1.8.dev13"} + ] + + def get_parameter_options(self) -> dict: + """ + Returns empty dict as this mapping has no configurable settings. + + :return: Empty dict + """ + return {} + + class Config(TypedDict): + """ + Empty config as this solver has no configurable settings. + """ + pass + + def map(self, problem: tuple[And, list], config: Config) -> tuple[WCNF, float]: + """ + Map from the nnf library into the python-sat library. + + :param problem: SAT problem + :param config: Config with the parameters specified in Config class + :return: Mapped problem and the time it took to map it + """ + start = start_time_measurement() + hard_constraints, soft_constraints = problem + + # Get number of vars. The union is required in case not all vars are present in either tests/constraints. + nr_vars = len(hard_constraints.vars().union(And(soft_constraints).vars())) + + # Create a var_labels dictionary that will be used when mapping to pysat + litdic = {f'L{i - 1}': i for i in range(1, nr_vars + 1)} + + # The most convenient way to map between nnf and pysat was to use the native nnf dump function, which exports + # the problem as a string, which we can then quickly reload from a buffer. + + # Create buffers for dumping: + hard_buffer = io.StringIO() + soft_buffer = io.StringIO() + + # Dump constraints and tests to their respective buffers + dump(hard_constraints, hard_buffer, var_labels=litdic, mode='cnf') + # tests have to be conjoined, since we will add them as soft constraints. + dump(And(soft_constraints), soft_buffer, var_labels=litdic, mode='cnf') + + # Load the cnfs from the buffers: + hard_cnf = CNF(from_string=hard_buffer.getvalue()) + soft_cnf = CNF(from_string=soft_buffer.getvalue()) + + # Create wcnf instance. + total_wcnf = WCNF() + + # Add hard constraints: + total_wcnf.extend(hard_cnf) + + # Add soft constraints, with weights. + total_wcnf.extend(soft_cnf, weights=[1] * len(soft_cnf.clauses)) + + logging.info( + f'Generated pysat wcnf with {len(total_wcnf.hard)} constraints and {len(total_wcnf.soft)} tests.' + ) + + return total_wcnf, end_time_measurement(start) + + def get_default_submodule(self, option: str) -> Core: + """ + Returns the default submodule based on the provided option. + + :param option: Option specifying the submodule + :return: Instance of the corresponding submodule + :raises NotImplementedError: If the option is not recognized + """ + if option == "ClassicalSAT": + from modules.solvers.classical_sat import ClassicalSAT # pylint: disable=C0415 + return ClassicalSAT() + elif option == "RandomSAT": + from modules.solvers.random_classical_sat import RandomSAT # pylint: disable=C0415 + return RandomSAT() + else: + raise NotImplementedError(f"Solver Option {option} not implemented") + + def reverse_map(self, solution: list) -> tuple[dict, float]: + """ + Maps the solution returned by the pysat solver into the reference format. + + :param solution: List containing the solution + :return: Solution mapped accordingly, time it took to map it + """ + start = start_time_measurement() + # converts from (3 / -3) -> (L2 : True / L2: False) + mapped_sol = {f'L{abs(lit) - 1}': (lit > 0) for lit in solution} + return mapped_sol, end_time_measurement(start) diff --git a/src/modules/applications/optimization/sat/mappings/qubovertqubo.py b/src/modules/applications/optimization/sat/mappings/qubovertqubo.py new file mode 100644 index 00000000..3b7e2250 --- /dev/null +++ b/src/modules/applications/optimization/sat/mappings/qubovertqubo.py @@ -0,0 +1,195 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +from typing import TypedDict + +from qubovert.sat import NOT, OR, AND +from nnf import And + +from modules.applications.mapping import Mapping, Core +from utils import start_time_measurement, end_time_measurement + + +class QubovertQUBO(Mapping): + """ + Qubovert formulation of the vehicle-options problem. + """ + + def __init__(self): + """ + Constructor method. + """ + super().__init__() + self.submodule_options = ["Annealer"] + self.pubo_problem = None + self.nr_vars = None + + @staticmethod + def get_requirements() -> list[dict]: + """ + Return requirements of this module. + + :return: List of dict with requirements of this module + """ + return [ + {"name": "nnf", "version": "0.4.1"}, + {"name": "qubovert", "version": "1.2.5"} + ] + + def get_parameter_options(self) -> dict: + """ + Returns the configurable settings for this mapping. + + :return: Dict with configurable settings + .. code-block:: python + + return { + "lagrange": { + "values": [0.1, 1, 1.5, 2, 5, 10, 1000, 10000], + "description": "By which factor would you like to multiply your Lagrange?" + } + } + """ + return { + "lagrange": { + "values": [0.1, 1, 1.5, 2, 5, 10, 1000, 10000], + "description": "By which factor would you like to multiply your Lagrange?" + } + } + + class Config(TypedDict): + """ + Attributes of a valid config. + + .. code-block:: python + + lagrange: float + + """ + lagrange: float + + @staticmethod + def _constraints2qubovert(constraints: any) -> AND: + """ + Converts the constraints nnf to a PUBO in the qubovert library. + + :param constraints: Constraints in nnf format + :return: Constraints in qubovert format + """ + clauses = [] + for c in constraints.children: + literals = [v.name if v.true else NOT(v.name) for v in c.children] + clauses.append(OR(*literals)) + return AND(*clauses) + + @staticmethod + def _tests2qubovert(test_clauses: dict) -> sum: + """ + Converts the list of test clauses in the nnf format to a PUBO. + + :param test_clauses: Test clauses in nnf format + :return: Sum of mapped test clauses + """ + mapped_tests = [] + + for test_clause in test_clauses: + mapped_tests.append(OR(*[v.name if v.true else NOT(v.name) for v in test_clause.children])) + + return sum(mapped_tests) + + def map(self, problem: any, config: Config) -> tuple[dict, float]: + """ + Converts the problem to a QUBO in dictionary format. Problem is a CNF formula from the nnf library. + + :param problem: SAT problem + :param config: Config with the parameters specified in Config class + :return: Dict with the QUBO, time it took to map it + """ + start = start_time_measurement() + lagrange = config['lagrange'] + + constraints, test_clauses = problem + + # Find number of the variables that appear in the tests and constraints, to verify the reverse mapping. + self.nr_vars = len(constraints.vars().union(And(test_clauses).vars())) + + # Convert the constraints to qubovert: + constraints_pubo = self._constraints2qubovert(constraints) + + # Convert the tests into qubovert: + tests_pubo = self._tests2qubovert(test_clauses) + logging.info(f'{tests_pubo.to_qubo().num_terms} number of terms in tests qubo') + lagrange *= len(test_clauses) + + # Define the total PUBO problem: + self.pubo_problem = -(tests_pubo + lagrange * constraints_pubo) + + # Convert to qubo: + qubo_problem = self.pubo_problem.to_qubo() + qubo_problem.normalize() + logging.info(f"Converted to QUBO with {qubo_problem.num_binary_variables} Variables." + f" Lagrange parameter: {config['lagrange']}.") + + # Convert it to the right format to be accepted by Braket / Dwave + q_dict = {} + + for k, v in qubo_problem.items(): + # "interaction (quadratic) terms": + if len(k) == 2: + if (k[0], k[1]) not in q_dict: + q_dict[(k[0], k[1])] = float(v) + else: + q_dict[(k[0], k[1])] += float(v) + # "local (linear) fields": + if len(k) == 1: + if (k[0], k[0]) not in q_dict: + q_dict[(k[0], k[0])] = float(v) + else: + q_dict[(k[0], k[0])] += float(v) + + return {"Q": q_dict}, end_time_measurement(start) + + def reverse_map(self, solution: dict) -> tuple[dict, float]: + """ + Maps the solution back to the representation needed by the SAT class for validation/evaluation. + + :param solution: Dictionary containing the solution + :return: Solution mapped accordingly, time it took to map it + """ + start = start_time_measurement() + pubo_sol = self.pubo_problem.convert_solution(solution) + + # Check if all variables appear in the solution. + missing_vars = {f'L{i}' for i in range(self.nr_vars)} - set(pubo_sol.keys()) + + # Add values for the missing variables -- if they do not appear, then their assignment does not matter. + for missing_var in missing_vars: + pubo_sol[missing_var] = True + + return pubo_sol, end_time_measurement(start) + + def get_default_submodule(self, option: str) -> Core: + """ + Returns the default submodule based on the provided option. + + :param option: Option specifying the submodule + :return: Instance of the corresponding submodule + :raises NotImplementedError: If the option is not recognized + """ + if option == "Annealer": + from modules.solvers.annealer import Annealer # pylint: disable=C0415 + return Annealer() + else: + raise NotImplementedError(f"Solver Option {option} not implemented") diff --git a/src/modules/applications/optimization/sat/sat.py b/src/modules/applications/optimization/sat/sat.py new file mode 100644 index 00000000..eebdc9d1 --- /dev/null +++ b/src/modules/applications/optimization/sat/sat.py @@ -0,0 +1,333 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +from typing import TypedDict + +import nnf +import numpy as np +from nnf import Var, And, Or +from nnf.dimacs import dump + +from modules.core import Core +from modules.applications.optimization.optimization import Optimization +from utils import start_time_measurement, end_time_measurement + + +class SAT(Optimization): + """ + The SAT (Satisfiability) problem plays a crucial role in the field of computational optimization. In the context + of vehicle manufacturing, it is essential to test various pre-series vehicle configurations to ensure they meet + specific requirements before production begins. This testing involves making sure that each vehicle configuration + complies with several hard constraints related to safety, performance, and buildability while also fulfilling + soft constraints such as feature combinations or specific requirements for testing. The SAT problem models these + constraints in a way that enables a systematic approach to determine feasible vehicle configurations and minimize + the need for excessive physical prototypes. + + This problem is modeled as a Max-SAT problem, where the aim is to find a configuration that satisfies as many + constraints as possible while balancing between the number of satisfied hard and soft constraints. The formulation + uses a conjunctive normal form (CNF) representation of logical expressions to model the dependencies and + incompatibilities between various features and components in vehicle assembly. By leveraging optimization + algorithms, the SAT module aims to produce a minimal but sufficient set of configurations, ensuring that all + necessary tests are performed while minimizing resource usage. This approach helps in creating a robust testing + framework and reducing the overall cost of vehicle development. + + To solve the SAT problem, various approaches are employed, including translating the CNF representation into + different quantum and classical optimization mappings such as QUBO (Quadratic Unconstrained Binary Optimization) + or Ising formulations. These mappings make the SAT problem suitable for solving on quantum computers and + classical annealers. The SAT problem in this module is implemented with a flexible interface, allowing integration + with a range of solvers that can exploit different computational paradigms, making it adaptable for a variety of + hardware and optimization backends. + """ + + def __init__(self): + """ + Constructor method. + """ + super().__init__("SAT") + self.submodule_options = [ + "QubovertQUBO", "Direct", "ChoiQUBO", "DinneenQUBO", "ChoiIsing", "DinneenIsing" + ] + self.literals = None + self.num_tests = None + self.num_constraints = None + self.num_variables = None + + @staticmethod + def get_requirements() -> list[dict]: + """ + Return requirements of this module. + + :return: List of dict with requirements of this module + """ + return [ + {"name": "nnf", "version": "0.4.1"}, + {"name": "numpy", "version": "1.26.4"} + ] + + def get_solution_quality_unit(self) -> str: + return "Evaluation" + + def get_default_submodule(self, option: str) -> Core: + """ + Returns the default submodule based on the provided option. + + :param option: Option specifying the submodule + :return: Instance of the corresponding submodule + :raises NotImplementedError: If the option is not recognized + """ + if option == "QubovertQUBO": + from modules.applications.optimization.sat.mappings.qubovertqubo import \ + QubovertQUBO # pylint: disable=C0415 + return QubovertQUBO() + elif option == "Direct": + from modules.applications.optimization.sat.mappings.direct import Direct # pylint: disable=C0415 + return Direct() + elif option == "ChoiQUBO": + from modules.applications.optimization.sat.mappings.choiqubo import ChoiQUBO # pylint: disable=C0415 + return ChoiQUBO() + elif option == "ChoiIsing": + from modules.applications.optimization.sat.mappings.choiIsing import ChoiIsing # pylint: disable=C0415 + return ChoiIsing() + elif option == "DinneenQUBO": + from modules.applications.optimization.sat.mappings.dinneenqubo import DinneenQUBO # pylint: disable=C0415 + return DinneenQUBO() + elif option == "DinneenIsing": + from modules.applications.optimization.sat.mappings.dinneenising import \ + DinneenIsing # pylint: disable=C0415 + return DinneenIsing() + else: + raise NotImplementedError(f"Mapping Option {option} not implemented") + + def get_parameter_options(self) -> dict: + """ + Returns the configurable settings for this application. + + :return: Dictionary with configurable settings + .. code-block:: python + + return { + "variables": { + "values": list(range(10, 151, 10)), + "custom_input": True, + "allow_ranges": True, + "postproc": int, + "description": "How many variables do you need?" + }, + "clvar_ratio_cons": { + "values": [2, 3, 4, 4.2, 5], + "custom_input": True, + "allow_ranges": True, + "postproc": int, + "description": "What clause-to-variable ratio do you want for the (hard) constraints?" + }, + "clvar_ratio_test": { + "values": [2, 3, 4, 4.2, 5], + "custom_input": True, + "allow_ranges": True, + "postproc": int, + "description": "What clause-to-variable ratio do you want for the tests (soft con.)?" + }, + "problem_set": { + "values": list(range(10)), + "description": "Which problem set do you want to use?" + }, + "max_tries": { + "values": [100], + "description": "Maximum number of tries to create problem?" + } + } + """ + return { + "variables": { + "values": list(range(10, 101, 10)), + "custom_input": True, + "allow_ranges": True, + "postproc": int, + "description": "How many variables do you need?" + }, + "clvar_ratio_cons": { + "values": [2, 3, 4, 4.2, 5], + "custom_input": True, + "allow_ranges": True, + "postproc": int, + "description": "What clause-to-variable ratio do you want for the (hard) constraints?" + }, + "clvar_ratio_test": { + "values": [2, 3, 4, 4.2, 5], + "custom_input": True, + "allow_ranges": True, + "postproc": int, + "description": "What clause-to-variable ratio do you want for the tests (soft constraints)?" + }, + "problem_set": { + "values": list(range(10)), + "description": "Which problem set do you want to use?" + }, + "max_tries": { + "values": [100], + "description": "Maximum number of tries to create problem?" + } + } + + class Config(TypedDict): + """ + Attributes of a valid config. + + .. code-block:: python + + variables: int + clvar_ratio_cons: float + clvar_ratio_test: float + problem_set: int + max_tries: int + + """ + variables: int + clvar_ratio_cons: float + clvar_ratio_test: float + problem_set: int + max_tries: int + + def generate_problem(self, config: Config) -> tuple[nnf.And, list]: + """ + Generates a vehicle configuration problem out of a given config. + Returns buildability constraints (hard constraints) and tests (soft + constraints), the successful evaluation of which we try to maximize. + Both are given in nnf form, which we then convert accordingly. + + :param config: Configuration parameters for problem generation + :return: A tuple containing the problem, number of variables, and other details + """ + + self.num_variables = config["variables"] + num_constraints = round(config["clvar_ratio_cons"] * self.num_variables) + num_tests = round(config["clvar_ratio_test"] * self.num_variables) + max_tries = config["max_tries"] + self.literals = [Var(f"L{i}") for i in range(self.num_variables)] + self.application = {} + + def _generate_3sat_clauses(nr_clauses, nr_vars, satisfiable, rseed, nr_tries): + # Iterate over the desired number of attempts: break if we find a solvable instance. + for attempt in range(nr_tries): + # Initialize random number generator -- multiply the attempt to traverse distinct random seeds + # for the hard and soft constraints, respectively (since rseed of the hard and soft constraints differs + # by 1). + rng = np.random.default_rng(rseed + attempt * 2) + clause_list = [] + # generate literal list to sample from + lit_vars = [Var(f"L{i}") for i in range(nr_vars)] + for _ in range(nr_clauses): + # Select three (non-repeated) literals and negate them randomly -- together constituting a clause + chosen_literals = rng.choice(lit_vars, 3, replace=False) + negate_literals = rng.choice([True, False], 3, replace=True) + # Perform the random negations and append to clause: + clause = [ + lit.negate() if neg else lit + for lit, neg in zip(chosen_literals, negate_literals) + ] + # Append the generated clause to the total container + clause_list.append(Or(clause)) + prob = And(clause_list) + if not satisfiable or prob.satisfiable(): + return clause_list + + # Loop ran out of tries + logging.error("Unable to generate valid solutions. Consider increasing max_tries or decreasing " + "the clause:variable ratio.") + raise ValueError("Unable to generate valid solution.") + + # Choose a random seed -- since we try at most max_tries times to generate a solvable instance, + # Space the initial random seeds by 2 * max_tries (because we need both hard and soft constraints). + random_seed = 2 * config["problem_set"] * max_tries + # Generate hard & soft constraints. Make both satisfiable, but this can in principle be tuned. + hard = And(_generate_3sat_clauses( + num_constraints, self.num_variables, satisfiable=True, + rseed=random_seed, nr_tries=max_tries + )) + # The random_seed + 1 ensures that a different set of seeds is sampled compared to the hard constraints. + soft = _generate_3sat_clauses( + num_tests, self.num_variables, satisfiable=True, + rseed=random_seed + 1, nr_tries=config["max_tries"] + ) + if (hard is None) or (soft is None): + raise ValueError("Unable to generate satisfiable") + # Saving constraints and tests + self.application["constraints"] = hard + self.application["tests"] = soft + # And their cardinalities: + self.num_constraints = len(hard) + self.num_tests = len(soft) + + logging.info(f"Generated a vehicle options Max3SAT" + f" instance with {self.num_variables} variables, {self.num_constraints} constraints" + f" and {self.num_tests} tests") + return hard, soft + + def validate(self, solution: dict) -> tuple[bool, float]: + """ + Validate a given solution against the constraints. + + :param solution: The solution to validate + :return: True if the solution is valid, False otherwise, and time it took to complete + """ + start = start_time_measurement() + + logging.info("Checking validity of solution:") + nr_satisfied_hardcons = len(*np.where( + [c.satisfied_by(solution) for c in self.application["constraints"].children] + )) + ratio = nr_satisfied_hardcons / self.num_constraints + is_valid = ratio == 1.0 + logging.info(f"Ratio of satisfied constraints: {ratio}\nSuccess:{['no', 'yes'][int(is_valid)]}") + + return is_valid, end_time_measurement(start) + + def evaluate(self, solution: dict) -> tuple[float, float]: + """ + Calculates the quality of the solution. + + :param solution: Dictionary containing the solution + :return: Tour length, time it took to calculate the tour length + """ + start = start_time_measurement() + logging.info("Checking the quality of the solution:") + + # Count the number of satisfied clauses + nr_satisfied_tests = len(*np.where([test.satisfied_by(solution) for test in self.application["tests"]])) + + ratio_satisfied = nr_satisfied_tests / self.num_tests + logging.info(f"Ratio of satisfied test clauses: {ratio_satisfied}.") + + return ratio_satisfied, end_time_measurement(start) + + def save(self, path: str, iter_count: int) -> None: + """ + Save the constraints and tests to files in CNF format. + + :param path: The directory path where the files will be saved. + :param iter_count: The iteration count to include in the filenames. + """ + with open(f"{path}/constraints_iter_{iter_count}.cnf", "w") as f_cons: + dump( + obj=self.application["constraints"], + fp=f_cons, + var_labels={str(literal): idx + 1 for idx, literal in enumerate(self.literals)} + ) + with open(f"{path}/tests_iter_{iter_count}.cnf", "w") as f_test: + dump( + obj=Or(self.application["tests"]), + fp=f_test, + var_labels={str(literal): idx + 1 for idx, literal in enumerate(self.literals)} + ) diff --git a/src/modules/applications/optimization/scp/__init__.py b/src/modules/applications/optimization/scp/__init__.py new file mode 100644 index 00000000..863c0dfe --- /dev/null +++ b/src/modules/applications/optimization/scp/__init__.py @@ -0,0 +1,19 @@ +# Copyright 2022 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Module for SCP mappings + +This module initializes the SCP application +""" diff --git a/src/modules/applications/optimization/scp/data/__init__.py b/src/modules/applications/optimization/scp/data/__init__.py new file mode 100644 index 00000000..5826cf71 --- /dev/null +++ b/src/modules/applications/optimization/scp/data/__init__.py @@ -0,0 +1,19 @@ +# Copyright 2022 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Module for SCP mappings + +This module initializes the SCP application +""" diff --git a/src/modules/applications/optimization/scp/data/set_cover_data_large.txt b/src/modules/applications/optimization/scp/data/set_cover_data_large.txt new file mode 100644 index 00000000..5bdd2998 --- /dev/null +++ b/src/modules/applications/optimization/scp/data/set_cover_data_large.txt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ff89bd2852562798c8d571a97574f2e68eb7a5288f05e8441110763c90e010f6 +size 11233 diff --git a/src/modules/applications/optimization/scp/mappings/__init__.py b/src/modules/applications/optimization/scp/mappings/__init__.py new file mode 100644 index 00000000..863c0dfe --- /dev/null +++ b/src/modules/applications/optimization/scp/mappings/__init__.py @@ -0,0 +1,19 @@ +# Copyright 2022 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Module for SCP mappings + +This module initializes the SCP application +""" diff --git a/src/modules/applications/optimization/scp/mappings/qubovertqubo.py b/src/modules/applications/optimization/scp/mappings/qubovertqubo.py new file mode 100644 index 00000000..6e852018 --- /dev/null +++ b/src/modules/applications/optimization/scp/mappings/qubovertqubo.py @@ -0,0 +1,145 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +from typing import TypedDict + +from qubovert.problems import SetCover +from modules.applications.mapping import Mapping, Core +from utils import start_time_measurement, end_time_measurement + + +class QubovertQUBO(Mapping): + """ + Qubovert formulation of the vehicle-options problem. + """ + + def __init__(self): + """ + Constructor method. + """ + super().__init__() + self.submodule_options = ["Annealer"] + + @staticmethod + def get_requirements() -> list[dict]: + """ + Return requirements of this module. + + :return: List of dict with requirements of this module + """ + return [{"name": "qubovert", "version": "1.2.5"}] + + def get_parameter_options(self) -> dict: + """ + Returns the configurable settings for this mapping. + + :return: Dictionary containing configurable settings + .. code-block:: python + + return { + "penalty_weight": { + "values": [2, 5, 10, 25, 50, 100], + "custom_input": True, + "custom_range": True, + "postproc": float, + "description": "Please choose the weight of the penalties in the QUBO representation of + the problem" + } + } + """ + return { + "penalty_weight": { + "values": [2, 5, 10, 25, 50, 100], + "custom_input": True, + "allow_ranges": True, + "postproc": float, + "description": "Please choose the weight of the penalties in the QUBO representation of the problem" + } + } + + class Config(TypedDict): + """ + Attributes of a valid config. + + .. code-block:: python + + penalty_weight: float + + """ + penalty_weight: float + + def map(self, problem: tuple, config: Config) -> tuple[dict, float]: + """ + Maps the SCP to a QUBO matrix. + + :param problem: Tuple containing the set of all elements of an instance and a list of subsets, + each covering some of these elements + :param config: Config with the parameters specified in Config class + :return: Dict with QUBO matrix, time it took to map it + """ + start = start_time_measurement() + penalty_weight = config['penalty_weight'] + + u, v = problem + + self.SCP_problem = SetCover(u, v) # pylint: disable=W0201 + self.SCP_qubo = self.SCP_problem.to_qubo(penalty_weight) # pylint: disable=W0201 + + logging.info(f"Converted to QUBO with {self.SCP_qubo.num_binary_variables} Variables.") + + # Convert it to the right format to be accepted by Braket / Dwave + q_dict = {} + + for key, val in self.SCP_qubo.items(): + # Interaction (quadratic) terms + if len(key) == 2: + if (key[0], key[1]) not in q_dict: + q_dict[(key[0], key[1])] = float(val) + else: + q_dict[(key[0], key[1])] += float(val) + # Local (linear) fields + elif len(key) == 1: + if (key[0], key[0]) not in q_dict: + q_dict[(key[0], key[0])] = float(val) + else: + q_dict[(key[0], key[0])] += float(val) + + return {"Q": q_dict}, end_time_measurement(start) + + def reverse_map(self, solution: dict) -> tuple[set, float]: + """ + Maps the solution of the QUBO to a set of subsets included in the solution. + + :param solution: QUBO matrix in dict form + :return: Tuple with set of subsets that are part of the solution and the time it took to map it + """ + start = start_time_measurement() + sol = self.SCP_problem.convert_solution(solution) + + return sol, end_time_measurement(start) + + def get_default_submodule(self, option: str) -> Core: + """ + Returns the default submodule based on the provided option. + + :param option: Option specifying the submodule + :return: Instance of the corresponding submodule + :raises NotImplementedError: If the option is not recognized + """ + if option == "Annealer": + from modules.solvers.annealer import Annealer # pylint: disable=C0415 + return Annealer() + else: + raise NotImplementedError(f"Solver Option {option} not implemented") diff --git a/src/modules/applications/optimization/scp/scp.py b/src/modules/applications/optimization/scp/scp.py new file mode 100644 index 00000000..cf533dbb --- /dev/null +++ b/src/modules/applications/optimization/scp/scp.py @@ -0,0 +1,174 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import TypedDict +import pickle +import os + +from modules.applications.application import Application +from modules.applications.optimization.optimization import Optimization +from utils import start_time_measurement, end_time_measurement + + +class SCP(Optimization): + """ + The set cover problem (SCP) is a classical combinatorial optimization problem where the objective is to find the + smallest subset of given elements that covers all required elements in a collection. This can be formulated as + selecting the minimum number of sets from a collection such that the union of the selected sets contains all + elements from the universe of the problem instance. + + SCP has widespread applications in various fields, including sensor positioning, resource allocation, and network + design. For example, in sensor positioning, SCP can help determine the fewest number of sensors required to cover + a given area. Similarly, in resource allocation, SCP helps to allocate resources in an optimal way, ensuring + coverage of all demand points while minimizing costs. Network design also uses SCP principles to efficiently place + routers or gateways in a network to ensure full coverage with minimal redundancy. + + This implementation of SCP provides configurable problem instances of different sizes, such as "Tiny," "Small," + and "Large," allowing the user to explore solutions with varying complexities. We employ various quantum-inspired + methods to solve SCP, including a mapping to the QUBO (Quadratic Unconstrained Binary Optimization) formulation + using Qubovert. These approaches allow us to explore how different optimization algorithms and frameworks perform + when applied to this challenging problem, offering insights into both classical and emerging quantum methods. + """ + + def __init__(self): + """ + Constructor method. + """ + super().__init__("SCP") + self.submodule_options = ["qubovertQUBO"] + + def get_solution_quality_unit(self) -> str: + return "Number of selected subsets" + + def get_default_submodule(self, option: str) -> Application: + """ + Returns the default submodule based on the provided option. + + :param option: Option specifying the submodule + :return: Instance of the corresponding submodule + :raises NotImplementedError: If the option is not recognized + """ + if option == "qubovertQUBO": + from modules.applications.optimization.scp.mappings.qubovertqubo import QubovertQUBO # pylint: disable=C0415 + return QubovertQUBO() + else: + raise NotImplementedError(f"Mapping Option {option} not implemented") + + def get_parameter_options(self): + """ + Returns the configurable settings for this application + + :return: Dictionary containing parameter options + .. code-block:: python + + return { + "model_select": { + "values": list(["Tiny", "Small", "Large"]), + "description": "Please select the problem size(s). Tiny: 4 elements, 3 subsets. Small: + 15 elements, 8 subsets. Large: 100 elements, 100 subsets" + } + } + """ + return { + "model_select": { + "values": list(["Tiny", "Small", "Large"]), + "description": "Please select the problem size(s). Tiny: 4 elements, 3 subsets. Small: 15 elements, " + "8 subsets. Large: 100 elements, 100 subsets" + } + } + + class Config(TypedDict): + model_select: str + + def generate_problem(self, config: Config) -> tuple[set, list]: + """ + Generates predefined instances of the SCP. + + :param config: Config specifying the selected problem instances + :return: The union of all elements of an instance and a set of subsets, each covering a part of the union + """ + model_select = config['model_select'] + self.application = {} + + if model_select == "Tiny": + self.application["elements_to_cover"] = set(range(1, 4)) + self.application["subsets"] = [{1, 2}, {1, 3}, {3, 4}] + elif model_select == "Small": + self.application["elements_to_cover"] = set(range(1, 15)) + self.application["subsets"] = [ + {1, 3, 4, 6, 7, 13}, {4, 6, 8, 12}, {2, 5, 9, 11, 13}, {1, 2, 7, 14, 15}, + {3, 10, 12, 14}, {7, 8, 14, 15}, {1, 2, 6, 11}, {1, 2, 4, 6, 8, 12} + ] + + elif model_select == "Large": + self.application["elements_to_cover"] = set(range(1, 100)) + self.application["subsets"] = [] + path = os.path.join(os.path.dirname(__file__)) + with open(f"{path}/data/set_cover_data_large.txt") as data: + while line := data.readline(): + new_set = [] + for i in line.split(','): + new_set.append(int(i)) + new_set = set(new_set) + self.application["subsets"].append(new_set) + + else: + raise ValueError(f"Unknown model_select value: {model_select}") + + return self.application["elements_to_cover"], self.application["subsets"] + + def process_solution(self, solution: list) -> tuple[list, float]: + """ + Returns list of selected subsets and the time it took to process the solution. + + :param solution: Unprocessed solution + :return: Processed solution and the time it took to process it + """ + start_time = start_time_measurement() + selected_subsets = [list(self.application["subsets"][i]) for i in solution] + return selected_subsets, end_time_measurement(start_time) + + def validate(self, solution: list) -> tuple[bool, float]: + """ + Checks if the elements of the subsets that are part of the solution cover every element of the instance. + + :param solution: List containing all subsets that are part of the solution + :return: Boolean whether the solution is valid and time it took to validate + """ + start = start_time_measurement() + covered = set.union(*[set(subset) for subset in solution]) + + return covered == self.application["elements_to_cover"], end_time_measurement(start) + + def evaluate(self, solution: list) -> tuple[int, float]: + """ + Calculates the number of subsets that are of the solution. + + :param solution: List containing all subsets that are part of the solution + :return: Number of subsets and the time it took to calculate it + """ + start = start_time_measurement() + selected_num = len(solution) + + return selected_num, end_time_measurement(start) + + def save(self, path: str, iter_count: int) -> None: + """ + Saves the SCP instance to a file. + + :param path: Path to save the SCP instance + :param iter_count: Iteration count + """ + with open(f"{path}/SCP_instance", "wb") as file: + pickle.dump(self.application, file, pickle.HIGHEST_PROTOCOL) diff --git a/src/modules/applications/optimization/tsp/__init__.py b/src/modules/applications/optimization/tsp/__init__.py new file mode 100644 index 00000000..9ddfa960 --- /dev/null +++ b/src/modules/applications/optimization/tsp/__init__.py @@ -0,0 +1,19 @@ +# Copyright 2022 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Module for TSP mappings + +This module initializes the TSP application +""" diff --git a/src/modules/applications/optimization/tsp/data/createReferenceGraph.py b/src/modules/applications/optimization/tsp/data/createReferenceGraph.py new file mode 100644 index 00000000..04abb882 --- /dev/null +++ b/src/modules/applications/optimization/tsp/data/createReferenceGraph.py @@ -0,0 +1,48 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import networkx as nx +import pickle +import tsplib95 + +# Source http://comopt.ifi.uni-heidelberg.de/software/TSPLIB95/tsp/ +filename = "dsj1000.tsp" + + +def main(): + """ + Load a TSP problem, remove unnecessary edges, and save the reference graph. + """ + print(f"Loading {filename}") + + # Load the problem from .tsp file + problem = tsplib95.load(filename) + graph = problem.get_graph() + + # We don't needed edges from e.g. node0 -> node0 + for edge in graph.edges: + if edge[0] == edge[1]: + graph.remove_edge(edge[0], edge[1]) + + print("Loaded graph:") + print(nx.info(graph)) + + with open("reference_graph.gpickle", "wb") as file: + pickle.dump(graph, file, pickle.HIGHEST_PROTOCOL) + + print("Saved graph as reference_graph.gpickle") + + +if __name__ == '__main__': + main() diff --git a/src/modules/applications/optimization/tsp/data/dsj1000.tsp b/src/modules/applications/optimization/tsp/data/dsj1000.tsp new file mode 100644 index 00000000..38eddd23 --- /dev/null +++ b/src/modules/applications/optimization/tsp/data/dsj1000.tsp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6fdad6e74c25ed4a4756a561ba941c1c8e7fd77c4a3d0903ed788ec2d4095f61 +size 18972 diff --git a/src/modules/applications/optimization/tsp/data/reference_graph.gpickle b/src/modules/applications/optimization/tsp/data/reference_graph.gpickle new file mode 100644 index 00000000..0c1cf607 --- /dev/null +++ b/src/modules/applications/optimization/tsp/data/reference_graph.gpickle @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4b7208498c7f1929549b59559b649de26d01d0730712b329a2932a1b7c986b5e +size 15243255 diff --git a/src/modules/applications/optimization/tsp/mappings/__init__.py b/src/modules/applications/optimization/tsp/mappings/__init__.py new file mode 100644 index 00000000..9ddfa960 --- /dev/null +++ b/src/modules/applications/optimization/tsp/mappings/__init__.py @@ -0,0 +1,19 @@ +# Copyright 2022 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Module for TSP mappings + +This module initializes the TSP application +""" diff --git a/src/modules/applications/optimization/tsp/mappings/ising.py b/src/modules/applications/optimization/tsp/mappings/ising.py new file mode 100644 index 00000000..3b4d01fa --- /dev/null +++ b/src/modules/applications/optimization/tsp/mappings/ising.py @@ -0,0 +1,273 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import re +from typing import TypedDict +import logging + +import networkx as nx +import numpy as np +from dimod import qubo_to_ising +from more_itertools import locate +from qiskit_optimization.applications import Tsp +from qiskit_optimization.converters import QuadraticProgramToQubo + +from modules.applications.mapping import Mapping, Core +from modules.applications.optimization.tsp.mappings.qubo import QUBO +from utils import start_time_measurement, end_time_measurement + + +class Ising(Mapping): + """ + Ising formulation for the TSP. + """ + + def __init__(self): + """ + Constructor method. + """ + super().__init__() + self.submodule_options = ["QAOA", "PennylaneQAOA", "QiskitQAOA"] + self.key_mapping = None + self.graph = None + self.config = None + + @staticmethod + def get_requirements() -> list[dict]: + """ + Return requirements of this module. + + :return: List of dict with requirements of this module + """ + return [ + {"name": "networkx", "version": "3.4.2"}, + {"name": "numpy", "version": "1.26.4"}, + {"name": "dimod", "version": "0.12.18"}, + {"name": "more-itertools", "version": "10.5.0"}, + {"name": "qiskit-optimization", "version": "0.6.1"}, + *QUBO.get_requirements() + ] + + def get_parameter_options(self) -> dict: + """ + Returns the configurable settings for this mapping. + + :return: Dictionary containing parameter options. + .. code-block:: python + + return { + "lagrange_factor": { + "values": [0.75, 1.0, 1.25], + "description": "By which factor would you like to multiply your lagrange?" + }, + "mapping": { + "values": ["ocean", "qiskit"], + "description": "Which Ising formulation of the TSP problem should be used?" + } + } + """ + return { + "lagrange_factor": { + "values": [0.75, 1.0, 1.25], + "description": "By which factor would you like to multiply your lagrange?" + }, + "mapping": { + "values": ["ocean", "qiskit"], + "description": "Which Ising formulation of the TSP problem should be used?" + } + } + + class Config(TypedDict): + """ + Attributes of a valid config. + + .. code-block:: python + + lagrange_factor: float + mapping: str + + """ + lagrange_factor: float + mapping: str + + def map(self, problem: nx.Graph, config: Config) -> tuple[dict, float]: + """ + Maps the networkx graph to an Ising formulation. + + :param problem: Networkx graph + :param config: Config with the parameters specified in Config class + :return: Dict with Ising, time it took to map it + """ + self.graph = problem + self.config = config + # Call mapping function defined in configuration + mapping = self.config["mapping"] + if mapping == "ocean": + return self._map_ocean(problem, config) + elif mapping == "qiskit": + return self._map_qiskit(problem, config) + else: + logging.error(f"Unknown mapping {mapping}.") + raise ValueError(f"Unknown mapping {mapping}.") + + def _map_ocean(self, graph: nx.Graph, config: Config) -> tuple[dict, float]: + """ + Use D-Wave/Ocean TSP QUBO/Ising model. + + :param graph: Networkx graph + :param config: Config with the parameters specified in Config class + :return: Dict with the Ising, time it took to map it + """ + start = start_time_measurement() + qubo_mapping = QUBO() + q, _ = qubo_mapping.map(graph, config) + t, j, _ = qubo_to_ising(q["Q"]) + + # Convert ISING dict to matrix + timesteps = graph.number_of_nodes() + matrix_size = graph.number_of_nodes() * timesteps + j_matrix = np.zeros((matrix_size, matrix_size), dtype=float) + + self.key_mapping = {} + index_counter = 0 + + for key, value in j.items(): + if key[0] not in self.key_mapping: + self.key_mapping[key[0]] = index_counter + index_counter += 1 + if key[1] not in self.key_mapping: + self.key_mapping[key[1]] = index_counter + index_counter += 1 + u = self.key_mapping[key[0]] + v = self.key_mapping[key[1]] + j_matrix[u][v] = value + + return {"J": j_matrix, "t": np.array(list(t.values())), "J_dict": j}, end_time_measurement(start) + + @staticmethod + def _map_qiskit(graph: nx.Graph, config: Config) -> tuple[dict, float]: + """ + Use Ising Mapping of Qiskit Optimize: + TSP class: https://qiskit.org/documentation/optimization/stubs/qiskit_optimization.applications.Tsp.html + Example notebook: https://qiskit.org/documentation/tutorials/optimization/6_examples_max_cut_and_tsp.html + + :param graph: Networkx graph + :param config: Config with the parameters specified in Config class + :return: Dict with the Ising, time it took to map it + """ + start = start_time_measurement() + tsp = Tsp(graph) + qp = tsp.to_quadratic_program() + logging.info(qp.export_as_lp_string()) + qp2qubo = QuadraticProgramToQubo() + qubo = qp2qubo.convert(qp) + qubitOp, _ = qubo.to_ising() + + # Reverse generate J and t out of qubit PauliSumOperator from qiskit + t_matrix = np.zeros(qubitOp.num_qubits, dtype=complex) + j_matrix = np.zeros((qubitOp.num_qubits, qubitOp.num_qubits), dtype=complex) + pauli_list = qubitOp.to_list() + + for pauli_str, coeff in pauli_list: + pauli_str_list = list(pauli_str) + index_pos_list = list(locate(pauli_str_list, lambda a: a == 'Z')) + if len(index_pos_list) == 1: + t_matrix[index_pos_list[0]] = coeff + elif len(index_pos_list) == 2: + j_matrix[index_pos_list[0]][index_pos_list[1]] = coeff + + return {"J": j_matrix, "t": t_matrix}, end_time_measurement(start) + + def reverse_map(self, solution: any) -> tuple[dict, float]: + """ + Maps the solution back to the representation needed by the TSP class for validation/evaluation. + + :param solution: List or array containing the solution + :return: Solution mapped accordingly, time it took to map it + """ + start = start_time_measurement() + if -1 in solution: # ising model output from Braket QAOA + solution = self._convert_ising_to_qubo(solution) + elif self.config["mapping"] == "ocean": + logging.debug("Flip bits in solutions to unify different mappings") + solution = self._flip_bits_in_bitstring(solution) + + logging.info(f"Best Bitstring: {solution}") + n = self.graph.number_of_nodes() + + result = {} + if self.key_mapping is None: + # Node indexes in graph are used as index in qubits + it = np.nditer(solution, flags=['multi_index']) + for x in it: + logging.debug(f"{x}, {it.multi_index}") + idx = it.multi_index[0] + result[(int(idx / n), int(idx % n))] = x + else: + logging.debug("Using key Mapping: {self.key_mapping}") + for key, value in self.key_mapping.items(): + result[key] = 1 if solution[value] == 1 else 0 + + return result, end_time_measurement(start) + + @staticmethod + def _flip_bits_in_bitstring(solution: any) -> any: + """ + Flip bits in the solution bitstring to unify different mappings. + + :param solution: Solution bitstring + :return: Flipped solution bitstring + """ + solution = np.array(solution) + with np.nditer(solution, op_flags=['readwrite']) as it: + for x in it: + x[...] = 1 - x + + return solution + + @staticmethod + def _convert_ising_to_qubo(solution: any) -> any: + """ + Convert Ising model output to QUBO format. + + :param solution: Ising model output + :return: QUBO format solution + """ + solution = np.array(solution) + with np.nditer(solution, op_flags=['readwrite']) as it: + for x in it: + if x == -1: + x[...] = 0 + + return solution + + def get_default_submodule(self, option: str) -> Core: + """ + Get the default submodule based on the given option. + + :param option: Submodule option + :return: Corresponding submodule + :raises NotImplemented: If the provided option is not implemented + """ + if option == "QAOA": + from modules.solvers.qaoa import QAOA # pylint: disable=C0415 + return QAOA() + elif option == "PennylaneQAOA": + from modules.solvers.pennylane_qaoa import PennylaneQAOA # pylint: disable=C0415 + return PennylaneQAOA() + elif option == "QiskitQAOA": + from modules.solvers.qiskit_qaoa import QiskitQAOA # pylint: disable=C0415 + return QiskitQAOA() + else: + raise NotImplementedError(f"Solver Option {option} not implemented") diff --git a/src/modules/applications/optimization/tsp/mappings/qubo.py b/src/modules/applications/optimization/tsp/mappings/qubo.py new file mode 100644 index 00000000..59eaa831 --- /dev/null +++ b/src/modules/applications/optimization/tsp/mappings/qubo.py @@ -0,0 +1,125 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import TypedDict +import logging + +import dwave_networkx as dnx +import networkx + +from modules.applications.mapping import Mapping, Core +from utils import start_time_measurement, end_time_measurement + + +class QUBO(Mapping): + """ + QUBO formulation for the TSP. + """ + + def __init__(self): + """ + Constructor method. + """ + super().__init__() + self.submodule_options = ["Annealer"] + + @staticmethod + def get_requirements() -> list[dict]: + """ + Return requirements of this module. + + :return: List of dict with requirements of this module + """ + return [ + {"name": "networkx", "version": "3.4.2"}, + {"name": "dwave_networkx", "version": "0.8.15"} + ] + + def get_parameter_options(self) -> dict: + """ + Returns the configurable settings for this mapping. + + :return: Dictionary with configurable settings + .. code-block:: python + + return { + "lagrange_factor": { + "values": [0.75, 1.0, 1.25], + "description": "By which factor would you like to multiply your " + "Lagrange?", + "custom_input": True, + "postproc": float + } + } + """ + return { + "lagrange_factor": { + "values": [0.75, 1.0, 1.25], + "description": "By which factor would you like to multiply your Lagrange?", + "custom_input": True, + "allow_ranges": True, + "postproc": float # Since we allow custom input here we need to parse it to float (input is str) + } + } + + class Config(TypedDict): + """ + Attributes of a valid config. + + .. code-block:: python + + lagrange_factor: float + + """ + lagrange_factor: float + + def map(self, problem: networkx.Graph, config: Config) -> tuple[dict, float]: + """ + Maps the networkx graph to a QUBO formulation. + + :param problem: Networkx graph + :param config: Config with the parameters specified in Config class + :return: Dict with QUBO, time it took to map it + """ + start = start_time_measurement() + lagrange = None + lagrange_factor = config['lagrange_factor'] + weight = 'weight' + + # Taken from dwave_networkx.traveling_salesperson_qubo + lagrange = problem.size(weight=weight) * problem.number_of_nodes() / problem.number_of_edges() + + lagrange = lagrange * lagrange_factor + + logging.info(f"Default Lagrange parameter: {lagrange}") + + # Get a QUBO representation of the problem + q = dnx.traveling_salesperson_qubo(problem, lagrange, weight) + + return {"Q": q}, end_time_measurement(start) + + def get_default_submodule(self, option: str) -> Core: + """ + Get the default submodule based on the given option. + + :param option: Submodule option + :return: Corresponding submodule + :raises NotImplemented: If the provided option is not implemented + """ + + if option == "Annealer": + from modules.solvers.annealer import Annealer # pylint: disable=C0415 + return Annealer() + else: + raise NotImplementedError(f"Solver Option {option} not implemented") diff --git a/src/modules/applications/optimization/tsp/tsp.py b/src/modules/applications/optimization/tsp/tsp.py new file mode 100644 index 00000000..cab27b28 --- /dev/null +++ b/src/modules/applications/optimization/tsp/tsp.py @@ -0,0 +1,337 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import TypedDict +import pickle +import logging +import os + +import networkx as nx +import matplotlib.pyplot as plt +import numpy as np + +from modules.applications.application import Core +from modules.applications.optimization.optimization import Optimization +from utils import start_time_measurement, end_time_measurement + + +class TSP(Optimization): + """ + "The famous travelling salesman problem (also called the travelling salesperson problem or in short TSP) is a + well-known NP-hard problem in combinatorial optimization, asking for the shortest possible route that visits each + node exactly once, given a list of nodes and the distances between each pair of nodes. Applications of the + TSP can be found in planning, logistics, and the manufacture of microchips. In these applications, the general + concept of a node represents, for example, customers, or points on a chip. + + TSP as graph problem: The solution to the TSP can be viewed as a specific ordering of the vertices in a weighted + graph. Taking an undirected weighted graph, nodes correspond to the graph's nodes, with paths corresponding to the + graph's edges, and a path's distance is the edge's weight." + (source: https://github.com/aws/amazon-braket-examples/tree/main/examples) + """ + + def __init__(self): + """ + Constructor method. + """ + super().__init__("TSP") + self.submodule_options = [ + "Ising", "QUBO", "GreedyClassicalTSP", "ReverseGreedyClassicalTSP", "RandomTSP" + ] + + @staticmethod + def get_requirements() -> list: + """ + Return requirements of this module. + + :return: List of dict with requirements of this module + """ + return [ + {"name": "networkx", "version": "3.4.2"}, + {"name": "numpy", "version": "1.26.4"} + ] + + def get_solution_quality_unit(self) -> str: + """ + Returns the unit of measurement for the solution quality. + + :return: Unit of measurement for the solution quality + """ + return "Tour cost" + + def get_default_submodule(self, option: str) -> Core: + """ + Returns the default submodule based on the given option. + + :param option: The chosen submodule option + :return: The corresponding submodule instance + :raises NotImplemented: If the provided option is not implemented + """ + if option == "Ising": + from modules.applications.optimization.tsp.mappings.ising import Ising # pylint: disable=C0415 + return Ising() + elif option == "QUBO": + from modules.applications.optimization.tsp.mappings.qubo import QUBO # pylint: disable=C0415 + return QUBO() + elif option == "GreedyClassicalTSP": + from modules.solvers.greedy_classical_tsp import GreedyClassicalTSP # pylint: disable=C0415 + return GreedyClassicalTSP() + elif option == "ReverseGreedyClassicalTSP": + from modules.solvers.reverse_greedy_classical_tsp import ReverseGreedyClassicalTSP # pylint: disable=C0415 + return ReverseGreedyClassicalTSP() + elif option == "RandomTSP": + from modules.solvers.random_classical_tsp import RandomTSP # pylint: disable=C0415 + return RandomTSP() + else: + raise NotImplementedError(f"Mapping Option {option} not implemented") + + def get_parameter_options(self) -> dict: + """ + Returns the configurable settings for this application + + :return: Dictionary with configurable settings. + .. code-block:: python + + return { + "nodes": { + "values": list([3, 4, 6, 8, 10, 14, 16]), + "allow_ranges": True, + "description": "How many nodes does your graph need?", + "postproc": int + } + } + """ + return { + "nodes": { + "values": list([3, 4, 6, 8, 10, 14, 16]), + "allow_ranges": True, + "description": "How many nodes does you graph need?", + "postproc": int # postproc needed to parse the result from allow_ranges to int + } + } + + class Config(TypedDict): + """ + Attributes of a valid config. + + .. code-block:: python + + nodes: int + + """ + nodes: int + + @staticmethod + def _get_tsp_matrix(graph: nx.Graph) -> np.ndarray: + """ + Creates distance matrix out of given coordinates. + + :param graph: The input graph + :return: Distance matrix + """ + number_of_nodes = len(graph) + matrix = np.zeros((number_of_nodes, number_of_nodes)) + for i in nx.all_pairs_dijkstra_path_length(graph, weight="weight"): + distance_dist = i[1] + for j in distance_dist.items(): + matrix[i[0] - 1][j[0] - 1] = j[1] + matrix[j[0] - 1][i[0] - 1] = matrix[i[0] - 1][j[0] - 1] + + return matrix + + def generate_problem(self, config: Config) -> nx.Graph: + """ + Uses the reference graph to generate a problem for a given config. + + :param config: Configuration dictionary + :return: Graph with the problem + """ + + if config is None: + config = {"nodes": 5} + + nodes = config['nodes'] + + # Read in the original graph + with open(os.path.join(os.path.dirname(__file__), "data", "reference_graph.gpickle"), "rb") as file: + graph = pickle.load(file) + + # Remove seams until the target number of seams is reached + nodes_in_graph = list(graph.nodes) + nodes_in_graph.sort() + + if len(nodes_in_graph) < nodes: + raise ValueError("Too many nodes! The original graph has less seams than that!") + + unwanted_nodes = nodes_in_graph[-len(nodes_in_graph) + nodes:] + unwanted_nodes = [x for x in graph.nodes if x in unwanted_nodes] + + # Remove one node after another + for node in unwanted_nodes: + graph.remove_node(node) + + if not nx.is_connected(graph): + logging.error("Graph is not connected!") + raise ValueError("Graph is not connected!") + + # Normalize graph + cost_matrix = self._get_tsp_matrix(graph) + graph = nx.from_numpy_array(cost_matrix) + + self.application = graph + + return graph + + def process_solution(self, solution: dict) -> tuple[list, float]: + """ + Convert dict to list of visited nodes. + + :param solution: Dictionary with solution + :return: Processed solution and the time it took to process it + """ + start_time = start_time_measurement() + nodes = self.application.nodes() + start = np.min(nodes) + # fill route with None values + route: list = [None] * len(self.application) + + # Get nodes from sample + logging.info(str(solution.items())) + + for (node, timestep), val in solution.items(): + if val: + logging.info((node, timestep)) + if val and (node not in route): + route[timestep] = node + + # Check whether every timestep has only 1 node flagged + for i in nodes: + relevant_nodes = [] + relevant_timesteps = [] + for (node, timestep) in solution.keys(): + if node == i: + relevant_nodes.append(solution[(node, timestep)]) + if timestep == i: + relevant_timesteps.append(solution[(node, timestep)]) + if sum(relevant_nodes) != 1 or sum(relevant_timesteps) != 1: + # timestep or nodes have more than 1 or 0 flags + return None, end_time_measurement(start_time) + + # Check validity of solution + if sum(value == 1 for value in solution.values()) > len(route): + logging.warning("Result is longer than route! This might be problematic!") + return None, end_time_measurement(start_time) + + # Run heuristic replacing None values + if None in route: + # get not assigned nodes + nodes_unassigned = [node for node in list(nodes) if node not in route] + nodes_unassigned = list(np.random.permutation(nodes_unassigned)) + for idx, node in enumerate(route): + if node is None: + route[idx] = nodes_unassigned[0] + nodes_unassigned.remove(route[idx]) + + # Cycle solution to start at provided start location + if start is not None and route[0] != start: + # Rotate to put the start in front + idx = route.index(start) + route = route[idx:] + route[:idx] + + # Log route + parsed_route = ' ->\n'.join([f' Node {visit}' for visit in route]) + logging.info(f"Route found:\n{parsed_route}") + + return route, end_time_measurement(start_time) + + def validate(self, solution: list) -> tuple[bool, float]: + """ + Checks if it is a valid TSP tour. + + :param solution: List containing the nodes of the solution + :return: Boolean whether the solution is valid, time it took to validate + """ + start = start_time_measurement() + nodes = self.application.nodes() + + if solution is None: + return False, end_time_measurement(start) + elif len([node for node in list(nodes) if node not in solution]) == 0: + logging.info(f"All {len(solution)} nodes got visited") + return True, end_time_measurement(start) + else: + logging.error(f"{len([node for node in list(nodes) if node not in solution])} nodes were NOT visited") + return False, end_time_measurement(start) + + def evaluate(self, solution: list) -> tuple[float, float]: + """ + Find distance for given route and original data. + + :param solution: List containing the nodes of the solution + :return: Tour cost and the time it took to calculate it + """ + start = start_time_measurement() + # Get the total distance without return + total_dist = 0 + for idx, _ in enumerate(solution[:-1]): + dist = self.application[solution[idx + 1]][solution[idx]] + total_dist += dist['weight'] + + logging.info(f"Total distance (without return): {total_dist}") + + # Add distance between start and end point to complete cycle + return_distance = self.application[solution[0]][solution[-1]]['weight'] + + # Get distance for full cycle + distance_with_return = total_dist + return_distance + logging.info(f"Total distance (including return): {distance_with_return}") + + return distance_with_return, end_time_measurement(start) + + def save(self, path: str, iter_count: int) -> None: + """ + Save the current application state to a file. + + :param path: The directory path where the file will be saved + :param iter_count: The iteration count to include in the filename + """ + with open(f"{path}/graph_iter_{iter_count}.gpickle", "wb") as file: + pickle.dump(self.application, file, pickle.HIGHEST_PROTOCOL) + + def visualize_solution(self, processed_solution: list[int], path: str): + """ + Plot a graph representing the problem network with the solution path highlighted + + :param processed_solution: The solution already processed by :func:`process_solution`, a list of visited node IDs in order of being visited. + :param path: File path for the plot + :returns: None + """ + NODE_SIZE = 300 # Default=300 + EDGE_WIDTH = 1.0 # Default=1.0 + FONT_SIZE = 12 # Default=12 + + path_edges = list(nx.utils.pairwise(processed_solution, cyclic=True)) + path_edges = [(u, v) if u < v else (v, u) for (u, v) in path_edges] + G = self.application + pos = nx.circular_layout(G) + weights = nx.get_edge_attributes(G, "weight") + filtered_weights = {e: (int(weights[e])) for e in path_edges} + + nx.draw_networkx_nodes(G, pos, node_size=NODE_SIZE) + nx.draw_networkx_edges(G, pos, edgelist=G.edges(), width=EDGE_WIDTH, edge_color="gray") + nx.draw_networkx_edges(G, pos, edgelist=path_edges, width=2 * EDGE_WIDTH, edge_color="red", arrows=True) + nx.draw_networkx_labels(G, pos, font_size=FONT_SIZE) + nx.draw_networkx_edge_labels(G, pos, filtered_weights, font_size=.5 * FONT_SIZE) + + plt.savefig(path) + plt.close() diff --git a/src/modules/applications/qml/circuit.py b/src/modules/applications/qml/circuit.py new file mode 100644 index 00000000..7ff2cf6c --- /dev/null +++ b/src/modules/applications/qml/circuit.py @@ -0,0 +1,33 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from abc import ABC, abstractmethod +from modules.core import Core + + +class Circuit(Core, ABC): + """ + This module is abstract base class for the library-agnostic gate sequence, that define a quantum circuit. + """ + + @abstractmethod + def generate_gate_sequence(self, input_data: dict, config: any) -> dict: + """ + Generates the library agnostic gate sequence, a well-defined definition of the quantum circuit. + + :param input_data: Input data required to generate the gate sequence + :param config: Configuration for the gate sequence + :return: Generated gate sequence + """ + pass diff --git a/src/modules/applications/qml/generative_modeling/data/mg_2d.npy b/src/modules/applications/qml/generative_modeling/data/mg_2d.npy new file mode 100644 index 00000000..3113a84a --- /dev/null +++ b/src/modules/applications/qml/generative_modeling/data/mg_2d.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3e046818fa8a4854fd1c0193490bc98cd571eb556b445be7d62ee4f203138cd2 +size 800128 diff --git a/src/modules/applications/qml/generative_modeling/data/o_2d.npy b/src/modules/applications/qml/generative_modeling/data/o_2d.npy new file mode 100644 index 00000000..2b03314f --- /dev/null +++ b/src/modules/applications/qml/generative_modeling/data/o_2d.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:998267639941843c48a5b7e0bb2c82f0558ad1a1e5c01b6a59b71700865492d5 +size 400128 diff --git a/src/modules/applications/qml/generative_modeling/data/stocks_2d.npy b/src/modules/applications/qml/generative_modeling/data/stocks_2d.npy new file mode 100644 index 00000000..0a9c6209 --- /dev/null +++ b/src/modules/applications/qml/generative_modeling/data/stocks_2d.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0d7dd7a248268a785a024c907fc51905b47867820aef65b67c342de7068ce537 +size 30160 diff --git a/src/modules/applications/qml/generative_modeling/data/x_2d.npy b/src/modules/applications/qml/generative_modeling/data/x_2d.npy new file mode 100644 index 00000000..42ebec7d --- /dev/null +++ b/src/modules/applications/qml/generative_modeling/data/x_2d.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bf6a3689d191abc150792b53a15b19b3df0090652f7524a541c2833bf9ce34d3 +size 400128 diff --git a/src/modules/applications/qml/generative_modeling/training/inference.py b/src/modules/applications/qml/generative_modeling/training/inference.py new file mode 100644 index 00000000..4bc221ad --- /dev/null +++ b/src/modules/applications/qml/generative_modeling/training/inference.py @@ -0,0 +1,118 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import TypedDict +import numpy as np +from modules.applications.qml.generative_modeling.training.training_generative import TrainingGenerative, Core, GPU + + +class Inference(TrainingGenerative): + """ + This module executes a quantum circuit with parameters of a pretrained model. + """ + + def __init__(self): + """ + Constructor method. + """ + super().__init__("Inference") + + self.target: np.array + self.n_states_range: list + + @staticmethod + def get_requirements() -> list[dict]: + """ + Returns requirements of this module. + + :return: List of dict with requirements of this module. + """ + return [{"name": "numpy", "version": "1.26.4"}] + + def get_parameter_options(self) -> dict: + """ + Returns the configurable settings for this circuit. + + :return: Configuration settings for the pretrained model + .. code-block:: python + + return { + "pretrained": { + "values": [False], + "custom_input": True, + "postproc": str, + "description": "Please provide the parameters of a pretrained model." + } + } + """ + return { + "pretrained": { + "values": [], + "custom_input": True, + "postproc": str, + "description": "Please provide the parameters of a pretrained model." + } + } + + class Config(TypedDict): + """ + Attributes of a valid config. + + .. code-block:: python + + pretrained: str + + """ + pretrained: str + + def get_default_submodule(self, option: str) -> Core: + """ + Raises ValueError as this module has no submodules. + + :param option: Option name + :raises ValueError: If called, since this module has no submodules + """ + raise ValueError("This module has no submodules.") + + def start_training(self, input_data: dict, config: Config, **kwargs: dict) -> dict: + """ + Method that uses a pretrained model for inference. + + :param input_data: Dictionary with information needed for inference + :param config: Inference settings + :param kwargs: Optional additional arguments + :return: Dictionary including the information of previous modules as well as of this module + """ + self.n_states_range = range(2 ** input_data['n_qubits']) + self.target = np.asarray(input_data["histogram_train"]) + execute_circuit = input_data["execute_circuit"] + + parameters = np.load(config["pretrained"]) + + pmfs, samples = execute_circuit([parameters.get() if GPU else parameters]) + pmfs = np.asarray(pmfs) + samples = ( + self.sample_from_pmf(pmf=pmfs[0], n_shots=input_data["n_shots"]) + if samples is None + else samples[0] + ) + + loss = self.kl_divergence(pmfs.reshape([-1, 1]), self.target) + + input_data["best_parameter"] = parameters.get() if GPU else parameters + input_data["inference"] = True + input_data["KL"] = [loss.get() if GPU else loss] + input_data["best_sample"] = samples.astype(int).get() if GPU else samples.astype(int) + + return input_data diff --git a/src/modules/applications/qml/generative_modeling/training/qcbm.py b/src/modules/applications/qml/generative_modeling/training/qcbm.py new file mode 100644 index 00000000..c9fab5cb --- /dev/null +++ b/src/modules/applications/qml/generative_modeling/training/qcbm.py @@ -0,0 +1,335 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import TypedDict +import logging +import numpy as np +from cma import CMAEvolutionStrategy +from tensorboardX import SummaryWriter +import matplotlib +from matplotlib import pyplot as plt +from matplotlib import figure, axes + +from modules.applications.qml.generative_modeling.training.training_generative import TrainingGenerative, Core, GPU +from utils_mpi import is_running_mpi, get_comm + +matplotlib.use('Agg') +MPI = is_running_mpi() +comm = get_comm() + + +class QCBM(TrainingGenerative): + """ + This module optimizes the parameters of quantum circuit using CMA-ES. + This training method is referred to as quantum circuit born machine (QCBM). + """ + + def __init__(self): + """ + Constructor method. + """ + super().__init__("QCBM") + + self.n_states_range: list + self.target: np.ndarray + self.study_generalization: bool + self.generalization_metrics: dict + self.writer: SummaryWriter + self.loss_func: callable + self.fig: figure + self.ax: axes.Axes + + @staticmethod + def get_requirements() -> list[dict]: + """ + Returns requirements of this module. + + :return: List of dict with requirements of this module + """ + return [ + {"name": "numpy", "version": "1.26.4"}, + {"name": "cma", "version": "4.0.0"}, + {"name": "matplotlib", "version": "3.9.3"}, + {"name": "tensorboard", "version": "2.18.0"}, + {"name": "tensorboardX", "version": "2.6.2.2"} + ] + + def get_parameter_options(self) -> dict: + """ + This function returns the configurable settings for the quantum circuit born machine. + + :return: Configuration settings for QCBM + .. code-block:: python + + return { + + "population_size": { + "values": [5, 10, 100, 200, 10000], + "description": "What population size do you want?" + }, + + "max_evaluations": { + "values": [100, 1000, 20000, 100000], + "description": "What should be the maximum number of evaluations?" + }, + + "sigma": { + "values": [0.01, 0.5, 1, 2], + "description": "Which sigma would you like to use?" + }, + + "pretrained": { + "values": [False], + "custom_input": True, + "postproc": str, + "description": "Please provide the parameters of a pretrained model." + }, + + "loss": { + "values": ["KL", "NLL"], + "description": "Which loss function do you want to use?" + } + } + """ + return { + "population_size": { + "values": [5, 10, 100, 200, 10000], + "description": "What population size do you want?" + }, + + "max_evaluations": { + "values": [100, 1000, 20000, 100000], + "description": "What should be the maximum number of evaluations?" + }, + + "sigma": { + "values": [0.01, 0.5, 1, 2], + "description": "Which sigma would you like to use?" + }, + + "pretrained": { + "values": [False], + "custom_input": True, + "postproc": str, + "description": "Please provide the parameters of a pretrained model." + }, + + "loss": { + "values": ["KL", "NLL"], + "description": "Which loss function do you want to use?" + } + } + + class Config(TypedDict): + """ + Attributes of a valid config. + + .. code-block:: python + + population_size: int + max_evaluations: int + sigma: float + pretrained: str + loss: str + + """ + population_size: int + max_evaluations: int + sigma: float + pretrained: str + loss: str + + def get_default_submodule(self, option: str) -> Core: + """ + Raises ValueError as this module has no submodules. + + :param option: Option name + :raises ValueError: If called, since this module has no submodules + """ + raise ValueError("This module has no submodules.") + + def setup_training(self, input_data: dict, config: Config) -> tuple[float, dict]: + """ + Method to configure the training setup including CMA-ES and tensorboard. + + :param input_data: A representation of the quantum machine learning model that will be trained + :param config: Config specifying the parameters of the training + :return: Random initial parameter and options for CMA-ES + """ + + logging.info( + f"Running config: [backend={input_data['backend']}] " + f"[n_qubits={input_data['n_qubits']}] " + f"[population_size={config['population_size']}]" + ) + + self.study_generalization = "generalization_metrics" in list(input_data.keys()) + if self.study_generalization: + self.generalization_metrics = input_data["generalization_metrics"] + self.generalization_metrics.n_shots = input_data["n_shots"] + + self.writer = SummaryWriter(input_data["store_dir_iter"]) + + if config['loss'] == "KL": + self.loss_func = self.kl_divergence + elif config['loss'] == "NLL": + self.loss_func = self.nll + elif config['loss'] == "MMD": + self.loss_func = self.mmd + else: + raise NotImplementedError("Loss function not implemented") + + n_params = input_data["n_params"] + x0 = (np.random.rand(n_params) - 0.5) * np.pi + if config["pretrained"] != "False": + parameters = np.load(config["pretrained"]) + x0[:len(parameters)] = parameters + logging.info(f'Training starting from parameters in path {config["pretrained"]}') + + options = { + 'bounds': [n_params * [-np.pi], n_params * [np.pi]], + 'maxfevals': config['max_evaluations'], + 'popsize': config['population_size'], + 'verbose': -3, + 'tolfun': 1e-12 + } + + return x0, options + + def start_training(self, input_data: dict, config: dict, **kwargs: dict) -> dict: + """ + This function finds the best parameters of the circuit on a transformed problem instance and returns a solution. + + :param input_data: A representation of the quantum machine learning model that will be trained + :param config: Config specifying the parameters of the training + :param kwargs: Optional additional settings + :return: Dictionary including the information of previous modules as well as of the training + """ + + size = None + input_data['MPI_size'] = size + input_data["store_dir_iter"] += f"_{input_data['dataset_name']}_qubits{input_data['n_qubits']}" + x0, options = self.setup_training(input_data, config) + + is_master = comm.Get_rank() == 0 + if is_master: + self.target = np.asarray(input_data["histogram_train"]) + self.target[self.target == 0] = 1e-8 + + self.n_states_range = range(2 ** input_data['n_qubits']) + execute_circuit = input_data["execute_circuit"] + timing = self.Timing() + + es = CMAEvolutionStrategy(x0.get() if GPU else x0, config['sigma'], options) + + for parameter in ["best_parameters", "time_circuit", "time_loss", "KL", "best_sample"]: + input_data[parameter] = [] + + best_loss = float("inf") + self.fig, self.ax = plt.subplots() + + while not es.stop(): + solutions = es.ask() + epoch = es.result[4] + sigma = es.sigma + + timing.start_recording() + pmfs_model, samples = execute_circuit(solutions) + pmfs_model = np.asarray(pmfs_model) + time_circ = timing.stop_recording() + + timing.start_recording() + if is_master: + loss_epoch = self.loss_func(pmfs_model.reshape([config['population_size'], -1]), self.target) + else: + loss_epoch = np.empty(config["population_size"]) + + comm.Bcast(loss_epoch, root=0) + comm.Barrier() + + time_loss = timing.stop_recording() + + es.tell(solutions, loss_epoch.get() if GPU else loss_epoch) + + if es.result[1] < best_loss: + best_loss = es.result[1] + best_pmf = self.data_visualization(loss_epoch, pmfs_model, samples, epoch) + + input_data["best_parameters"].append(es.result[0]) + input_data["KL"].append(float(es.result[1])) + + logging.info( + f"[Iteration {epoch}] " + f"[{config['loss']}: {es.result[1]:.5f}] " + f"[Circuit processing: {(time_circ):.3f} ms] " + f"[{config['loss']} processing: {(time_loss):.3f} ms] " + f"[sigma: {sigma:.5f}]" + ) + + plt.close() + self.writer.flush() + self.writer.close() + + input_data["best_parameter"] = es.result[0] + best_sample = self.sample_from_pmf(best_pmf.get() if GPU else best_pmf, # pylint: disable=E0606 + n_shots=input_data["n_shots"]) + input_data["best_sample"] = best_sample.get() if GPU else best_sample # pylint: disable=E1101 + + return input_data + + def data_visualization(self, loss_epoch: np.ndarray, pmfs_model: np.ndarray, samples: any, epoch: int) -> ( + np.ndarray): + """ + Visualizes the data and metrics for training. + + :param loss_epoch: Loss for the current epoch + :param pmfs_model: The probability mass functions from the model + :param samples: The samples from the model + :param epoch: The current epoch number + :return: Best probability mass function for visualization + """ + index = loss_epoch.argmin() + best_pmf = pmfs_model[index] / pmfs_model[index].sum() + + if self.study_generalization: + if samples is None: + counts = self.sample_from_pmf( + pmf=best_pmf.get() if GPU else best_pmf, + n_shots=self.generalization_metrics.n_shots) + else: + counts = samples[int(index)] + + metrics = self.generalization_metrics.get_metrics(counts if GPU else counts) + for key, value in metrics.items(): + self.writer.add_scalar(f"metrics/{key}", value, epoch) + + nll = self.nll(best_pmf.reshape([1, -1]), self.target) + kl = self.kl_divergence(best_pmf.reshape([1, -1]), self.target) + mmd = self.mmd(best_pmf.reshape([1, -1]), self.target) + + self.writer.add_scalar("metrics/NLL", nll.get() if GPU else nll, epoch) + self.writer.add_scalar("metrics/KL", kl.get() if GPU else kl, epoch) + self.writer.add_scalar("metrics/MMD", mmd.get() if GPU else mmd, epoch) + + self.ax.clear() + self.ax.imshow( + best_pmf.reshape(int(np.sqrt(best_pmf.size)), int(np.sqrt(best_pmf.size))).get() + if GPU else best_pmf.reshape(int(np.sqrt(best_pmf.size)), int(np.sqrt(best_pmf.size))), + cmap='binary', + interpolation='none' + ) + self.ax.set_title(f'Iteration {epoch}') + self.writer.add_figure('grid_figure', self.fig, global_step=epoch) + + return best_pmf diff --git a/src/modules/applications/qml/generative_modeling/training/qgan.py b/src/modules/applications/qml/generative_modeling/training/qgan.py new file mode 100644 index 00000000..c743d1b7 --- /dev/null +++ b/src/modules/applications/qml/generative_modeling/training/qgan.py @@ -0,0 +1,508 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import TypedDict +import logging + +import torch +from torch.utils.data import DataLoader +from torch import nn +import torch.nn.functional as funct +from tensorboardX import SummaryWriter +import numpy as np +import matplotlib +import matplotlib.pyplot as plt + +from modules.applications.qml.generative_modeling.training.training_generative import TrainingGenerative, Core +from utils_mpi import is_running_mpi, get_comm + +matplotlib.use('Agg') +MPI = is_running_mpi() +comm = get_comm() + + +class QGAN(TrainingGenerative): # pylint: disable=R0902 + """ + Class for QGAN + """ + + def __init__(self): + """ + Constructor method. + """ + super().__init__("QGAN") + + self.beta_1 = None + self.real_label = None + self.fake_label = None + self.n_qubits = None + self.n_registers = None + self.n_shots = None + self.train_size = None + self.execute_circuit = None + self.device = None + self.n_epochs = None + self.batch_size = None + self.learning_rate_generator = None + self.n_bins = None + self.n_states_range = None + self.timing = None + self.writer = None + self.bins_train = None + self.bins_train = None + self.study_generalization = None + self.generalization_metrics = None + self.target = None + self.n_params = None + self.discriminator = None + self.params = None + self.generator = None + self.accuracy = None + self.criterion = None + self.optimizer_discriminator = None + self.real_labels = None + self.fake_labels = None + self.dataloader = None + self.loss_func = None + self.params = None + self.discriminator_weights = None + + @staticmethod + def get_requirements() -> list[dict]: + """ + Returns requirements of this module. + + :return: List of dict with requirements of this module + """ + return [ + {"name": "numpy", "version": "1.26.4"}, + {"name": "torch", "version": "2.5.1"}, + {"name": "matplotlib", "version": "3.9.3"}, + {"name": "tensorboard", "version": "2.18.0"}, + {"name": "tensorboardX", "version": "2.6.2.2"} + ] + + def get_parameter_options(self) -> dict: + """ + Returns the configurable settings for this circuit. + + :return: Configuration settings for QGAN + .. code-block:: python + return { + "epochs": { + "values": [2, 100, 200, 10000], + "description": "How many epochs do you want?" + }, + "batch_size": { + "values": [10, 20, 100, 2000], + "description": "What batch size do you want?" + }, + "learning_rate_generator": { + "values": [0.1, 0.2], + "description": "What learning rate do you want to set for the generator?" + }, + "learning_rate_discriminator": { + "values": [0.1, 0.05], + "description": "What learning rate do you want to set for the discriminator?" + }, + "device": { + "values": ["cpu", "gpu"], + "description": "Where do you want to run the discriminator?" + }, + "pretrained": { + "values": [True, False], + "description": "Do you want to use parameters of a pretrained model?" + }, + "loss": { + "values": ["KL", "NLL"], + "description": "Which loss function do you want to use?" + } + } + """ + return { + + "epochs": { + "values": [2, 100, 200, 10000], + "description": "How many epochs do you want?" + }, + "batch_size": { + "values": [10, 20, 100, 2000], + "description": "What batch size do you want?" + }, + "learning_rate_generator": { + "values": [0.1, 0.2], + "description": "What learning rate do you want to set for the generator?" + }, + "learning_rate_discriminator": { + "values": [0.1, 0.05], + "description": "What learnig rate do you want to set for the discriminator?" + }, + "device": { + "values": ["cpu", "gpu"], + "description": "Where do you want to run the discriminator?" + }, + "pretrained": { + "values": [True, False], + "description": "Do you want to use parameters of a pretrained model?" + }, + "loss": { + "values": ["KL", "NLL"], + "description": "Which loss function do you want to use?" + } + } + + class Config(TypedDict): + """ + Attributes of a valid config. + + .. code-block:: python + + epochs: int + batch_size: int + learning_rate_generator: int + learning_rate_discriminator: int + device: str + loss: str + + """ + + epochs: int + batch_size: int + learning_rate_generator: float + learning_rate_discriminator: float + device: str + loss: str + + def get_default_submodule(self, option: str) -> Core: + """ + Raises ValueError as this module has no submodules. + + :param option: Option name + :raises ValueError: If called, since this module has no submodules + """ + raise ValueError("This module has no submodules.") + + def setup_training(self, input_data: dict, config: dict) -> None: + """ + Sets up the training configuration. + + :param input_data: dictionary with the variables from the circuit needed to start the training + :param config: Configurations for the QGAN training. + """ + self.beta_1 = 0.5 + self.real_label = 1. + self.fake_label = 0. + + self.n_qubits = input_data['n_qubits'] + self.n_registers = input_data['n_registers'] + self.n_shots = input_data["n_shots"] + self.train_size = input_data["train_size"] + self.execute_circuit = input_data["execute_circuit"] + + self.device = config["device"] + self.n_epochs = config["epochs"] + self.batch_size = config["batch_size"] + self.learning_rate_generator = config["learning_rate_generator"] + + n = 2 ** (self.n_qubits // self.n_registers) + self.n_bins = n ** self.n_registers + self.n_states_range = range(2 ** self.n_qubits) + + self.timing = self.Timing() + self.writer = SummaryWriter(input_data["store_dir_iter"]) + + self.bins_train = input_data["binary_train"] + if input_data["dataset_name"] == "Cardinality_Constraint": + new_size = 1000 + self.bins_train = np.repeat(self.bins_train, new_size, axis=0) + + self.study_generalization = "generalization_metrics" in list(input_data.keys()) + if self.study_generalization: + self.generalization_metrics = input_data["generalization_metrics"] + + self.target = np.asarray(input_data["histogram_train"]) + self.target[self.target == 0] = 1e-8 + + self.n_params = input_data["n_params"] + + self.discriminator = Discriminator(self.n_qubits).to(self.device) + self.discriminator.apply(Discriminator.weights_init) + + self.params = np.random.rand(self.n_params) * np.pi + self.generator = QuantumGenerator(self.n_qubits, self.execute_circuit, self.batch_size) + + self.accuracy = [] + self.criterion = torch.nn.BCELoss() + self.optimizer_discriminator = torch.optim.Adam( + self.discriminator.parameters(), + lr=config["learning_rate_discriminator"], + betas=(self.beta_1, 0.999) + ) + + self.real_labels = torch.full((self.batch_size,), 1.0, dtype=torch.float, device=self.device) + self.fake_labels = torch.full((self.batch_size,), 0.0, dtype=torch.float, device=self.device) + + self.dataloader = DataLoader(self.bins_train, batch_size=self.batch_size, shuffle=True, drop_last=True) + + if config['loss'] == "KL": + self.loss_func = self.kl_divergence + elif config['loss'] == "NLL": + self.loss_func = self.nll + else: + raise NotImplementedError("Loss function not implemented") + + def start_training(self, input_data: dict, config: dict, **kwargs: dict) -> dict: # pylint: disable=R0915 + """ + This function starts the training of the QGAN. + + :param input_data: Dictionary with the variables from the circuit needed to start the training + :param config: Training settings + :param kwargs: Optional additional arguments + :return: Dictionary including the solution + """ + self.setup_training(input_data, config) + generator_losses = [] + discriminator_losses = [] + best_kl_divergence = float('inf') + best_generator_params = None + pmfs_model = None + best_sample = None + + n_batches = len(self.dataloader) + + for epoch in range(self.n_epochs): + for batch, data in enumerate(self.dataloader): + # Training the discriminator + # Data from real distribution for training the discriminator + real_data = data.float().to(self.device) + self.discriminator.zero_grad() + out_d_real = self.discriminator(real_data).view(-1) + err_d_real = self.criterion(out_d_real, self.real_labels) + err_d_real.backward() + + # Use Quantum Variational Circuit to generate fake samples + fake_data, _ = self.generator.execute(self.params, self.batch_size) + fake_data = fake_data.float().to(self.device) + out_d_fake = self.discriminator(fake_data).view(-1) + err_d_fake = self.criterion(out_d_fake, self.fake_labels) + err_d_fake.backward() + + err_d = err_d_real + err_d_fake + self.optimizer_discriminator.step() + + out_d_fake = self.discriminator(fake_data).view(-1) + err_g = self.criterion(out_d_fake, self.real_labels) + fake_data, _ = self.generator.execute(self.params, self.batch_size) + gradients = self.generator.compute_gradient( + self.params, + self.discriminator, + self.criterion, + self.real_labels, + self.device + ) + + updated_params = self.params - self.learning_rate_generator * gradients + self.params = updated_params + + self.discriminator_weights = self.discriminator.state_dict() + generator_losses.append(err_g.item()) + discriminator_losses.append(err_d.item()) + + # Calculate loss + _, pmfs_model = self.generator.execute(self.params, self.n_shots) + pmfs_model = np.asarray(pmfs_model.copy()) + + loss = self.loss_func(pmfs_model[None,], self.target) + self.accuracy.append(loss) + + self.writer.add_scalar("metrics/KL", loss, epoch * n_batches + batch) + circuit_evals = (epoch * n_batches + batch) * self.batch_size * (2 * self.n_params + 1) + self.writer.add_scalar("metrics/KL_circuit_evals", loss, circuit_evals) + + # Calculate and log the loss values at the end of each epoch + self.writer.add_scalar('Loss/GAN_Generator', err_g.item(), circuit_evals) + self.writer.add_scalar('Loss/GAN_Discriminator', err_d.item(), circuit_evals) + + if loss < best_kl_divergence: + best_kl_divergence = loss + best_generator_params = self.params.copy() # Make a copy of the parameters + _, best_pdf = self.generator.execute(best_generator_params, self.n_shots) + best_pdf = np.asarray(best_pdf) + best_pdf = best_pdf / best_pdf.sum() + best_sample = self.sample_from_pmf(pmf=best_pdf, n_shots=self.n_shots) + + # Log the training progress + log_message = ( + f"Epoch: {epoch + 1}/{self.n_epochs}, " + f"Batch: {batch + 1}/{len(self.bins_train) // self.batch_size}, " + f"Discriminator Loss: {err_d.item()}, Generator Loss: {err_g.item()}, KL Divergence: {loss} " + ) + + logging.info(log_message) + + fig, ax = plt.subplots() + ax.imshow( + pmfs_model.reshape((2 ** (self.n_qubits // 2), 2 ** (self.n_qubits // 2))), + cmap='binary', + interpolation='none' + ) + ax.set_title(f'Iteration {epoch}') + self.writer.add_figure('grid_figure', fig, global_step=epoch) + + ax.clear() + ax.imshow( + self.target.reshape((2 ** (self.n_qubits // 2), 2 ** (self.n_qubits // 2))), + cmap='binary', + interpolation='none' + ) + # Log the figure in TensorBoard + ax.set_title("train") + self.writer.add_figure("train", fig) + + # Plot the generator and discriminator losses on the existing figure + ax.clear() + ax.plot(generator_losses, label='Generator Loss', color='blue') + ax.plot(discriminator_losses, label='Discriminator Loss', color='red') + ax.legend() + + # Save the updated loss plot to TensorBoard + self.writer.add_figure('Loss_Plot', fig, global_step=epoch) + + plt.close() + self.writer.flush() + self.writer.close() + + input_data["best_parameter"] = best_generator_params + input_data["best_sample"] = best_sample + input_data["KL"] = self.accuracy + input_data["generator_loss"] = generator_losses + input_data["discriminator_loss"] = discriminator_losses + + return input_data + + +class Discriminator(nn.Module): + """ + This class defines the discriminator of the QGAN. + """ + + def __init__(self, input_length: int): + super().__init__() + self.dense1 = nn.Linear(int(input_length), 2 * int(input_length)) + self.dense2 = nn.Linear(2 * int(input_length), 1) + + def forward(self, x: torch.Tensor) -> float: + """ + Initializes the weight tensor of the linear layers with values using a Xavier uniform distribution. + + :param x: Input of the discriminator + :type x: torch.Tensor + :return: Probability fake/real sample + :rtype: float + """ + h = funct.leaky_relu(self.dense1(x)) + h = funct.leaky_relu(self.dense2(h)) + return funct.sigmoid(h) + + @staticmethod + def weights_init(m: nn.Linear) -> None: + """ + Initializes the weight tensor of the linear + layers with values using a Xavier uniform distribution. + + :param m: Neural network layer + """ + if isinstance(m, nn.Linear): + nn.init.xavier_uniform_(m.weight.data, gain=10) + nn.init.constant_(m.bias.data, 1) + + +class QuantumGenerator: + """ + This class defines the generator of the QGAN. + """ + + def __init__(self, n_qubits, execute_circuit, batch_size): + self.n_qubits = n_qubits + self.execute_circuit = execute_circuit + self.batch_size = batch_size + + def execute(self, params: np.ndarray, n_shots: int) -> tuple[torch.Tensor, np.ndarray]: + """ + Forward pass of the generator. + + :param params: Parameters of the quantum circuit + :param n_shots: Number of shots + :return: samples and the probability distribution generated by the quantum circuit + """ + + # Call the quantum circuit and obtain probability distributions + pdfs, _ = self.execute_circuit(np.expand_dims(params, axis=0)) + pdfs = pdfs.flatten() + + # Sample from the provided probability distribution for the specified batch size + sampling = torch.multinomial(torch.from_numpy(pdfs), n_shots, replacement=True) + + # Convert the sampling tensor to a list of integers + binary_samples = [(sampling >> i) & 1 for i in range(self.n_qubits)] + binary_samples = binary_samples[::-1] # Reverse the order to match your expected format + + # Convert binary samples to a PyTorch tensor + samples = torch.stack(binary_samples, dim=1).float() + + return samples, pdfs + + # pylint: disable=R0917 + def compute_gradient(self, params: np.ndarray, discriminator: torch.nn.Module, criterion: callable, + label: torch.Tensor, device: str) -> np.ndarray: + """ + This function defines the forward pass of the generator. + + :param params: Parameters of the quantum circuit + :param discriminator: Discriminator of the QGAN + :param criterion: Loss function + :param label: Label indicating of sample is true or fake + :param device: Torch device (e.g., CPU or CUDA) + :return: Samples and the probability distribution generated by the quantum circuit + """ + shift = 0.5 * np.pi + gradients = np.zeros(len(params)) # Initialize gradients as an array of zeros + + for i in range(len(params)): + # Compute shifts for the i-th parameter + positive_shifted_params = params.copy() + negative_shifted_params = params.copy() + positive_shifted_params[i] += shift + negative_shifted_params[i] -= shift + + # Generate samples with shifts + positive_samples, _ = self.execute(positive_shifted_params, self.batch_size) + negative_samples, _ = self.execute(negative_shifted_params, self.batch_size) + + # Convert positive_samples and negative_samples to tensors + positive_samples = positive_samples.to(device) + negative_samples = negative_samples.to(device) + + # Compute discriminator outputs for all samples + forward_outputs = discriminator(positive_samples).view(-1) + backward_outputs = discriminator(negative_samples).view(-1) + + # Compute criterion differences for all samples + forward_diff = criterion(forward_outputs, label) + backward_diff = criterion(backward_outputs, label) + + # Calculate the gradient for the i-th parameter + gradients[i] = 0.5 * (forward_diff.item() - backward_diff.item()) + + return gradients diff --git a/src/modules/applications/qml/generative_modeling/transformations/pit.py b/src/modules/applications/qml/generative_modeling/transformations/pit.py new file mode 100644 index 00000000..23534ea1 --- /dev/null +++ b/src/modules/applications/qml/generative_modeling/transformations/pit.py @@ -0,0 +1,257 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import numpy as np +import pandas as pd + +from modules.applications.qml.generative_modeling.transformations.transformation import Transformation +from modules.applications.qml.generative_modeling.circuits.circuit_copula import CircuitCopula + + +class PIT(Transformation): # pylint disable=R0902 + """ + The transformation of the original probability distribution to + the distribution of its uniformly distributed cumulative marginals is known as the copula. + """ + + def __init__(self): + super().__init__("PIT") + self.submodule_options = ["CircuitCopula"] + self.reverse_epit_lookup = None + self.transform_config = None + self.n_qubits = None + self.dataset = None + self.dataset_name = None + self.grid_shape = None + self.histogram_train = None + self.histogram_train_original = None + self.histogram_transformed = None + + @staticmethod + def get_requirements() -> list[dict]: + """ + Returns requirements of this module. + + :return: List of dict with requirements of this module + """ + return [ + {"name": "numpy", "version": "1.26.4"}, + {"name": "pandas", "version": "2.2.3"} + ] + + def get_parameter_options(self) -> dict: + """ + Returns empty dict as this transformation has no configurable settings. + + :return: Empty dict + """ + return {} + + def get_default_submodule(self, option: str) -> CircuitCopula: + if option == "CircuitCopula": + return CircuitCopula() + else: + raise NotImplementedError(f"Circuit Option {option} not implemented") + + def transform(self, input_data: dict, config: dict) -> dict: + """ + Transforms the input dataset using PIT transformation and computes histograms + of the training dataset in the transformed space. + + :param input_data: Dataset + :param config: Config with the parameters specified in Config class + :return: Dict with PIT transformation, time it took to map it + """ + # TODO: PIT.transform is almost identical to MinMax.transform -> function should be moved to Transformation.py + self.dataset_name = input_data["dataset_name"] + self.dataset = input_data["dataset"] + self.n_qubits = input_data["n_qubits"] + self.grid_shape = int(2 ** (self.n_qubits // 2)) + n_registers = self.dataset.shape[-1] + + # Calculate ranges for the original dataset and the transformed dataset + ranges_original = np.column_stack((np.min(self.dataset, axis=0), np.max(self.dataset, axis=0))) + transformed_dataset = self.fit_transform(self.dataset) + ranges_transformed = np.column_stack((np.min(transformed_dataset, axis=0), np.max(transformed_dataset, axis=0))) + + # Compute histogram for the transformed dataset + transformed_histogram_grid = np.histogramdd( + transformed_dataset, + bins=self.grid_shape, + range=ranges_transformed)[0] + histogram_transformed_1d = transformed_histogram_grid.flatten() + self.histogram_transformed = histogram_transformed_1d / np.sum(histogram_transformed_1d) + + solution_space = np.zeros(len(transformed_dataset), dtype=int) + + # Initialize a variable to keep track of the current position in the result_array + position = 0 + value = 0 + for count in histogram_transformed_1d: + if count > 0: + solution_space[position:position + int(count)] = value + position += int(count) + value += 1 + + binary_strings = [np.binary_repr(x, width=self.n_qubits) for x in solution_space] + # Convert the binary strings to a NumPy array of integers + binary_transformed = np.array([list(map(int, s)) for s in binary_strings]) + + # Compute histogram for the original dataset + learned_histogram = np.histogramdd(self.dataset, bins=self.grid_shape, range=ranges_original) + self.histogram_train_original = learned_histogram[0] / np.sum(learned_histogram[0]) + + # Compute histogram for the transformed dataset + train_histogram = np.histogramdd(transformed_dataset, bins=self.grid_shape, range=ranges_transformed) + histogram_train = train_histogram[0] / np.sum(train_histogram[0]) + self.histogram_train = histogram_train.flatten() + + self.transform_config = { + "histogram_train": self.histogram_train, + "binary_train": binary_transformed, + "dataset_name": self.dataset_name, + "n_registers": n_registers, + "n_qubits": self.n_qubits, + "store_dir_iter": input_data["store_dir_iter"], + "train_size": input_data["train_size"], + "transformed_dataset": transformed_dataset + } + + return self.transform_config + + def reverse_transform(self, input_data: dict) -> dict: + """ + Transforms the solution back to the representation needed for validation/evaluation. + + :param input_data: Dictionary containing the solution + :return: Dictionary with solution transformed accordingly + """ + depth = input_data["depth"] + architecture_name = input_data["architecture_name"] + n_qubits = input_data["n_qubits"] + n_registers = self.transform_config["n_registers"] + kl_best_transformed = min(input_data["KL"]) + best_results = input_data["best_sample"] + circuit_transpiled = input_data['circuit_transpiled'] + + array_bins = self.compute_discretization_efficient(n_qubits, n_registers) + transformed_samples = self.generate_samples_efficient(best_results, array_bins, n_registers, noisy=True) + + # Calculate ranges for the transformed samples + ranges_transformed = np.column_stack((np.min(transformed_samples, axis=0), np.max(transformed_samples, axis=0))) + + # Compute histogram for the transformed samples + learned_histogram = np.histogramdd(transformed_samples, bins=self.grid_shape, range=ranges_transformed) + histogram_generated_transformed = learned_histogram[0] / np.sum(learned_histogram[0]) + histogram_generated_transformed = histogram_generated_transformed.flatten() + + original_samples = self.inverse_transform(transformed_samples) + + # Calculate ranges for the original samples + ranges_original = np.column_stack((np.min(original_samples, axis=0), np.max(original_samples, axis=0))) + + # Compute histogram for the original samples + learned_histogram = np.histogramdd(original_samples, bins=self.grid_shape, range=ranges_original) + histogram_generated_original = learned_histogram[0] / np.sum(learned_histogram[0]) + histogram_generated_original = histogram_generated_original.flatten() + + best_parameter = input_data["best_parameter"] + + reverse_config_trans = { + "generated_samples": best_results, + "transformed_samples": transformed_samples, + "depth": depth, + "architecture_name": architecture_name, + "dataset_name": self.dataset_name, + "n_qubits": n_qubits, + "best_parameter": best_parameter, + "histogram_train_original": self.histogram_train_original, + "histogram_train": self.histogram_train, + "histogram_generated_original": histogram_generated_original, + "histogram_generated": histogram_generated_transformed, + "KL_best_transformed": kl_best_transformed, + "store_dir_iter": input_data["store_dir_iter"], + "circuit_transpiled": circuit_transpiled + } + + return reverse_config_trans + + def fit_transform(self, data: np.ndarray) -> np.ndarray: + """ + Takes the data points and applies the PIT. + + :param data: Data samples + :return: Transformed data points + """ + df = pd.DataFrame(data) + epit = df.copy(deep=True).transpose() + self.reverse_epit_lookup = epit.copy(deep=True) + + epit.values[::] = [self.emp_integral_trans(row) for row in epit.values] + epit = epit.transpose() + self.reverse_epit_lookup.values[::] = [np.sort(row) for row in self.reverse_epit_lookup.values] + + df = epit.copy() + self.reverse_epit_lookup = self.reverse_epit_lookup.values + return df.values + + def _reverse_emp_integral_trans_single(self, values: np.ndarray) -> list[float]: + """ + Takes one data point and applies the inverse PIT. + + :param values: Data point + :return: Data point after applying the inverse transformation + """ + values = values * (np.shape(self.reverse_epit_lookup)[1] - 1) + rows = np.shape(self.reverse_epit_lookup)[0] + + # TODO THIS IS A TEMPORARY BUG FIX FOR RARELY OCCURRING CASES THAT LEAD TO INDEX_ERRORS. + # WILL BE FIXED IN RELEASE 2.1.3. + # Ensure values are within bounds + values_l = np.clip(np.floor(values).astype(int), 0, self.reverse_epit_lookup.shape[1] - 1) + values_h = np.clip(np.ceil(values).astype(int), 0, self.reverse_epit_lookup.shape[1] - 1) + + # if we are an integer then floor and ceiling are the same + is_int_mask = 1 - (values_h - values_l) + row_indexer = np.arange(rows) + result_l = self.reverse_epit_lookup[ + ([row_indexer], [values_l])] # doing 2d lookup as [[index1.row, index2.row],[index1.column, index2.column]] + result_h = self.reverse_epit_lookup[ + ([row_indexer], [values_h])] # where 2d index tuple would be (index1.row, index1.column) + # lookup int or do linear interpolation + return result_l * (is_int_mask + values - values_l) + result_h * (values_h - values) + + def inverse_transform(self, data: np.ndarray) -> np.ndarray: + """ + Applies the inverse transformation to the full data set. + + :param data: Data set + :return: Data set after applying the inverse transformation + """ + res = [self._reverse_emp_integral_trans_single(row) for row in data] + + return np.array(res)[:, 0, :] + + def emp_integral_trans(self, data: np.ndarray) -> np.ndarray: + """ + Applies the empirical integral transformation to the given data. + + :param data: Data points + :return: Empirically transformed data points + """ + rank = np.argsort(data).argsort() + length = data.size + ecdf = np.linspace(0, 1, length, dtype=np.float64) + ecdf_biject = ecdf[rank] + return ecdf_biject diff --git a/src/modules/applications/qml/generative_modeling/transformations/transformation.py b/src/modules/applications/qml/generative_modeling/transformations/transformation.py new file mode 100644 index 00000000..1a09d73c --- /dev/null +++ b/src/modules/applications/qml/generative_modeling/transformations/transformation.py @@ -0,0 +1,200 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from itertools import product +from abc import ABC, abstractmethod + +import numpy as np + +from modules.core import Core +from utils import start_time_measurement, end_time_measurement + + +class Transformation(Core, ABC): + """ + The task of the transformation module is to translate data and problem + specification of the application into preprocessed format. + """ + + def __init__(self, name): + """ + Constructor method. + """ + super().__init__() + self.transformation_name = name + + @staticmethod + def get_requirements() -> list[dict]: + """ + Returns requirements of this module. + + :return: List of dict with requirements of this module + """ + return [{"name": "numpy", "version": "1.26.4"}] + + def preprocess(self, input_data: dict, config: dict, **kwargs: dict) -> tuple[dict, float]: + """ + In this module, the preprocessing step is transforming the data to the correct target format. + + :param input_data: Collected information of the benchmarking process + :param config: Config specifying the parameters of the transformation + :param kwargs: Additional optional arguments + :return: Tuple with transformed problem and the time it took to map it + """ + start = start_time_measurement() + output = self.transform(input_data, config) + + return output, end_time_measurement(start) + + def postprocess(self, input_data: dict, config: dict, **kwargs) -> tuple[dict, float]: + """ + Does the reverse transformation. + + :param input_data: Dictionary containing information of previously executed modules + :param config: Dictionary containing additional information + :param kwargs: Dictionary containing additional information + :return: Tuple with the dictionary and the time the postprocessing took + """ + start = start_time_measurement() + output = self.reverse_transform(input_data) + output["Transformation"] = True + if "inference" in input_data: + output["inference"] = input_data["inference"] + + return output, end_time_measurement(start) + + @abstractmethod + def transform(self, input_data: dict, config: dict) -> dict: + """ + Helps to ensure that the model can effectively learn the underlying + patterns and structure of the data, and produce high-quality outputs. + + :param input_data: Input data for transformation + :param config: Configuration parameters for the transformation + :return: Transformed data. + """ + return input_data + + def reverse_transform(self, input_data: dict) -> dict: + """ + Transforms the solution back to the original problem. + This might not be necessary in all cases, so the default is to return the original solution. + This might be needed to convert the solution to a representation needed for validation and evaluation. + + :param input_data: The input data to be transformed + :return: Transformed data + """ + return input_data + + @staticmethod + def compute_discretization(n_qubits: int, n_registered: int) -> np.ndarray: + """ + Compute discretization for the grid. + + :param n_qubits: Total number of qubits + :param n_registered: Number of qubits to be registered + :return: Discretization data + """ + n = 2 ** (n_qubits // n_registered) + n_bins = n ** n_registered + bin_data = np.empty((n_bins, n_registered + 1), dtype=float) + + for k, coords in enumerate(product(range(n), repeat=n_registered)): + normalized_coords = np.array(coords) / n + 0.5 / n + bin_data[k] = np.concatenate(([k], normalized_coords)) + + return bin_data + + @staticmethod + def compute_discretization_efficient(n_qubits: int, n_registers: int) -> np.ndarray: + """ + Compute grid discretization. + + :param n_qubits: Total number of qubits + :param n_registers: Number of qubits to be registered + :return: Discretization data + """ + n = 2 ** (n_qubits // n_registers) + n_bins = n ** n_registers + + # Create an array of all possible coordinate combinations + coords = np.array(list(product(range(n), repeat=n_registers))) + + # Calculate normalized_coords for all combinations + normalized_coords = (coords.astype(float) + 0.5) / n + + # Create bin_data by concatenating normalized_coords with row indices + bin_data = np.hstack((np.arange(n_bins).reshape(-1, 1), normalized_coords)) + + return bin_data + + @staticmethod + def generate_samples(results: np.ndarray, bin_data: np.ndarray, n_registers: int, noisy: bool = True) -> np.ndarray: + """ + Generate samples based on measurement results and the grid bins. + + :param results: Results of measurements + :param bin_data: Binned data + :param n_registers: Number of registers + :param noisy: Flag indicating whether to add noise + :return: Generated samples + """ + n_shots = np.sum(results) + width = 1 / len(bin_data) ** (1 / n_registers) + points = ( + 0.5 * width * np.random.uniform(low=-1, high=1, size=(n_shots, n_registers)) + if noisy + else np.zeros((n_shots, n_registers)) + ) + + position = 0 + for idx, value in enumerate(results): + bin_coords = bin_data[idx, 1:] + points[position: position + value, :] += np.tile(bin_coords, (value, 1)) + position += value + + return points.astype(np.float32) + + @staticmethod + def generate_samples_efficient(results, bin_data: np.ndarray, n_registers: int, noisy: bool = True) -> np.ndarray: + """ + Generate samples efficiently using numpy arrays based on measurement results and the grid bins. + + :param results: Results of measurements + :param bin_data: Binned data + :param n_registers: Number of registers + :param noisy: Flag indicating whether to add noise + :return: Generated samples + """ + n_shots = np.sum(results) + width = 1 / len(bin_data) ** (1 / n_registers) + + # Generate random noise or zeros + noise = ( + 0.5 * width * np.random.uniform(low=-1, high=1, size=(n_shots, n_registers)) + if noisy + else np.zeros((n_shots, n_registers)) + ) + + # Create an array of bin_coords for each result, then stack them vertically + bin_coords = bin_data[:, 1:] + expanded_bin_coords = np.repeat(bin_coords, results, axis=0) + + # Reshape expanded_bin_coords to match the shape of noise + expanded_bin_coords = expanded_bin_coords.reshape(n_shots, n_registers) + + # Add noise to the expanded_bin_coords + points = expanded_bin_coords + noise + + return points.astype(np.float32) diff --git a/src/modules/applications/qml/model.py b/src/modules/applications/qml/model.py new file mode 100644 index 00000000..f0f11f18 --- /dev/null +++ b/src/modules/applications/qml/model.py @@ -0,0 +1,59 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and + +from abc import ABC, abstractmethod + + +class Model(ABC): + """ + Abstract base class for any quantum model. This class defines the necessary methods + that models like 'LibraryGenerative' must implement. + """ + + @abstractmethod + def sequence_to_circuit(self, input_data: dict) -> dict: + """ + Abstract method to convert a sequence into a quantum circuit. + + :param input_data: Input data representing the gate sequence + :return: A dictionary representing the quantum circuit + """ + pass + + @staticmethod + @abstractmethod + def get_execute_circuit(circuit: any, backend: any, config: str, config_dict: dict) -> tuple[any, any]: + """ + This method combines the circuit implementation and the selected backend and returns a function that will be + called during training. + + :param circuit: Implementation of the quantum circuit + :param backend: Configured qiskit backend + :param config: Name of a backend + :param config_dict: Dictionary including the number of shots + :return: Tuple that contains a method that executes the quantum circuit for a given set of parameters and the + transpiled circuit + """ + pass + + @staticmethod + @abstractmethod + def select_backend(config: str, n_qubits: int) -> any: + """ + This method configures the backend. + + :param config: Name of a backend + :param n_qubits: Number of qubits + :return: Configured backend + """ + pass diff --git a/src/modules/applications/qml/qml.py b/src/modules/applications/qml/qml.py new file mode 100644 index 00000000..5720fcce --- /dev/null +++ b/src/modules/applications/qml/qml.py @@ -0,0 +1,42 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from abc import ABC, abstractmethod + +from modules.applications.application import Application + + +class QML(Application, ABC): + """ + qml Module for QUARK, is used by all qml applications. + """ + + @abstractmethod + def generate_problem(self, config: dict) -> any: + """ + Creates a concrete problem and returns it. + + :param config: Configuration dictionary + :return: Generated problem + """ + pass + + def save(self, path: str, iter_count: int) -> None: + """ + Placeholder method for saving output to a file. + + :param path: Path to save the file + :param iter_count: Iteration count + """ + pass diff --git a/src/modules/applications/qml/training.py b/src/modules/applications/qml/training.py new file mode 100644 index 00000000..17ea21de --- /dev/null +++ b/src/modules/applications/qml/training.py @@ -0,0 +1,33 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from abc import ABC, abstractmethod + + +class Training(ABC): + """ + Abstract base class for training QML models. + """ + + @abstractmethod + def start_training(self, input_data: dict, config: any, **kwargs: dict) -> dict: + """ + This function starts the training of QML model or deploys a pretrained model. + + :param input_data: A representation of the quantum machine learning model that will be trained + :param config: Config specifying the parameters of the training (dict-like Config type defined in children) + :param kwargs: Optional additional settings + :return: Solution, the time it took to compute it and some optional additional information + """ + pass diff --git a/src/modules/core.py b/src/modules/core.py new file mode 100644 index 00000000..89f62935 --- /dev/null +++ b/src/modules/core.py @@ -0,0 +1,161 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations # Needed if you want to type hint a method with the type of the enclosing class + +import os +import sys +from abc import ABC, abstractmethod +from typing import final + +from utils import _get_instance_with_sub_options +from Metrics import Metrics + + +class Core(ABC): + """ + Core Module for QUARK, used by all other Modules that are part of a benchmark process. + """ + + def __init__(self, name: str = None): + """ + Constructor method. + + :param name: Name used to identify this QUARK module. If not specified class name will be used as default. + """ + self.submodule_options = [] + self.sub_options = [] + self.depending_parameters = False + self.preprocessed_input = None + self.postprocessed_input = None + if name is None: + name = self.__class__.__name__ + self.name = name + self.metrics = Metrics(name, os.path.relpath(sys.modules[self.__module__].__file__)) + + @abstractmethod + def get_parameter_options(self) -> dict: + """ + Returns the parameters for a given module. + + Should always be in this format: + + .. code-block:: json + + { + "parameter_name":{ + "values":[1, 2, 3], + "description":"How many nodes do you need?" + }, + "parameter_name_2":{ + "values":["x", "y"], + "description":"Which type of problem do you want?" + } + } + + :return: Available settings for this application + """ + raise NotImplementedError("Please don't use the base version of get_parameter_options. " + "Implement your own override instead.") + + def get_available_submodules(self, option: list) -> list: + """ + If the module has submodules depending on certain options, this method should adjust the submodule_options + accordingly. + + :param option: List of chosen options + :return: List of available submodules + """ + return [] + + def get_depending_parameters(self, option: str, config: dict) -> dict: + """ + If the module has parameters depending on certain options, this method should return the parameters for the + given option. + + :param option: The chosen option + :param config: Current config dictionary + :return: The parameters for the given option + """ + return {} + + @final + def get_submodule(self, option: str) -> Core: + """ + Submodule is instantiated according to the information given in self.sub_options. + If self.sub_options is None, get_default_submodule is called as a fallback. + + :param option: String with the options + :return: Instance of a module + """ + if self.sub_options is None or not self.sub_options: + return self.get_default_submodule(option) + return _get_instance_with_sub_options(self.sub_options, option) + + # TODO Think if the naming of get_default_submodule can be improved to better reflect its function. + @abstractmethod + def get_default_submodule(self, option: str) -> Core: + """ + Given an option string by the user, this returns a submodule. + + :param option: String with the chosen submodule + :return: Module of type Core + """ + raise NotImplementedError("Please don't use the base version of get_default_submodule. " + "Implement your own override instead.") + + def preprocess(self, input_data: any, config: dict, **kwargs) -> tuple[any, float]: + """ + Essential method for the benchmarking process. This is always executed before traversing down + to the next module, passing the data returned by this function. + + :param input_data: Data for the module, comes from the parent module if that exists + :param config: Config for the module + :param kwargs: Optional keyword arguments + :return: The output of the preprocessing and the time it took to preprocess + """ + return input_data, 0.0 + + def postprocess(self, input_data: any, config: dict, **kwargs) -> tuple[any, float]: + """ + Essential Method for the benchmarking process. Is always executed after the submodule is finished. The data by + this method is passed up to the parent module. + + :param input_data: Input data comes from the submodule if that exists + :param config: Config for the module + :param kwargs: Optional keyword arguments + :return: The output of the postprocessing and the time it took to postprocess + """ + return input_data, 0.0 + + @final + def get_available_submodule_options(self) -> list: + """ + Gets the list of available options. + + :return: List of module options + """ + if self.sub_options is None or not self.sub_options: + return self.submodule_options + else: + return [option["name"] for option in self.sub_options] + + @staticmethod + def get_requirements() -> list: + """ + Returns the required pip packages for this module. Optionally, version requirements can be added. + + :return: List of dictionaries + """ + return [] diff --git a/src/modules/devices/braket/braket.py b/src/modules/devices/braket/braket.py new file mode 100644 index 00000000..00f755b9 --- /dev/null +++ b/src/modules/devices/braket/braket.py @@ -0,0 +1,188 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import getpass +import logging +import os +from abc import ABC +from datetime import datetime + +import boto3 +from botocore.config import Config +from botocore.exceptions import ProfileNotFound +from braket.aws import AwsSession + +from modules.devices.device import Device + + +class Braket(Device, ABC): + """ + Abstract class to use the Amazon Braket devices. + """ + + def __init__(self, device_name: str, region: str = None, arn: str = None): + """ + Constructor method. + """ + super().__init__(device_name) + self.device = None + self.arn = arn + self.s3_destination_folder = None + self.boto_session = None + self.aws_session = None + + if 'SKIP_INIT' in os.environ: + # TODO: This is currently needed so create_module_db in the Installer does not execute the rest + # of this section, which would be unnecessary. However, this should be done better in the future! + return + + if device_name != "LocalSimulator": + self._configure_aws_session(region) + + def _configure_aws_session(self, region: str) -> None: + """ + Configures the AWS session for the Braket device. + + :param region: AWS region to use + """ + proxy_definitions = self._setup_proxy() + region = self._set_region(region) + my_config = Config(region_name=region, proxies=proxy_definitions) + + profile_name = self._set_profile() + self._initialize_aws_session(profile_name, region, my_config) + + @staticmethod + def _setup_proxy() -> any: + """ + Sets up proxy configuration if available in the environment variables. + + :return: Proxy definitions + """ + if 'HTTP_PROXY' in os.environ: + proxy_definitions = { + 'http': os.environ['HTTP_PROXY'], + 'https': os.environ['HTTP_PROXY'] + } + os.environ['HTTPS_PROXY'] = os.environ['HTTP_PROXY'] + else: + logging.warning('No HTTP_PROXY set as an environment variable. ' + 'This might cause trouble if you are using a VPN.') + proxy_definitions = None + return proxy_definitions + + @staticmethod + def _set_region(region: str) -> str: + """ + Sets the AWS region from the environment variable or defaults to 'us-east-1'. + + :param region: Provided region + :return: Final region to be used + """ + if region is None: + region = os.environ.get('AWS_REGION', 'us-east-1') + logging.info(f"No AWS_REGION specified, using default region: {region}") + return region + + @staticmethod + def _set_profile() -> str: + """ + Determines the AWS profile to use for the session. + + :return: AWS profile name + """ + if 'AWS_PROFILE' in os.environ: + return os.environ['AWS_PROFILE'] + elif "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI" in os.environ: + logging.info("Assuming AWS container environment, using container credentials.") + return None + else: + profile_name = 'quantum_computing' + os.environ['AWS_PROFILE'] = profile_name + logging.info(f"No AWS_PROFILE specified, using default profile: {profile_name}") + return profile_name + + def _initialize_aws_session(self, profile_name: str, region: str, my_config: Config) -> None: + """ + Initializes the AWS session for interacting with Amazon Braket. + + :param profile_name: AWS profile name + :param region: AWS region + :param my_config: Boto3 configuration + :raises Exception: If the AWS profile is not found + """ + try: + if profile_name is None: + self.boto_session = boto3.Session(region_name=region) + else: + self.boto_session = boto3.Session(profile_name=profile_name, region_name=region) + self.aws_session = AwsSession(boto_session=self.boto_session, config=my_config) + except ProfileNotFound as exc: + logging.error(f"AWS-Profile {profile_name} could not be found! Please set the AWS_PROFILE env variable. " + "Only LocalSimulator is available.") + raise Exception("Please refer to the logged error message.") from exc + + @staticmethod + def get_requirements() -> list[dict]: + """ + Return requirements of this module. + + :return: List of dictionaries with requirements + """ + return [ + {"name": "amazon-braket-sdk", "version": "1.88.2"}, + {"name": "botocore", "version": "1.35.73"}, + {"name": "boto3", "version": "1.35.73"} + ] + + def init_s3_storage(self, folder_name: str) -> None: + """ + Initializes an S3 storage bucket for Amazon Braket. + + :param folder_name: Name of the s3 bucket + """ + run_timestamp = datetime.today().date() + username = getpass.getuser() + + bucket_name = f"amazon-braket-benchmark-framework-{run_timestamp}-{username}" + self.s3_destination_folder = (bucket_name, folder_name) + self._create_s3_bucket(self.boto_session, bucket_name) + + @staticmethod + def _create_s3_bucket(boto3_session: boto3.Session, bucket_name: str = 'quark-benchmark-framework', + region: str = 'us-east-1') -> None: + """ + Creates an S3 bucket with specific configurations. + + :param boto3-session: Boto3 session + :param bucket_name: Name of the s3 bucket + :param region: AWS region + """ + s3_client = boto3_session.client('s3', region_name=region) + + if region == "us-east-1": + s3_client.create_bucket(Bucket=bucket_name) + else: + location = {"LocationConstraint": region} + s3_client.create_bucket(Bucket=bucket_name, CreateBucketConfiguration=location) + + s3_client.put_public_access_block( + Bucket=bucket_name, + PublicAccessBlockConfiguration={ + 'BlockPublicAcls': True, + 'IgnorePublicAcls': True, + 'BlockPublicPolicy': True, + 'RestrictPublicBuckets': True + }, + ) diff --git a/src/modules/devices/braket/ionq.py b/src/modules/devices/braket/ionq.py new file mode 100644 index 00000000..ddbe03a3 --- /dev/null +++ b/src/modules/devices/braket/ionq.py @@ -0,0 +1,59 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +from braket.aws import AwsDevice + +from modules.devices.braket.braket import Braket + + +class Ionq(Braket): + """ + Class for using the IonQ devices on Amazon Braket. + """ + + def __init__(self, device_name: str, arn: str = 'arn:aws:braket:us-east-1::device/qpu/ionq/Aria-1'): + """ + Constructor method for initializing IonQ device on Amazon Braket. + + :param device_name: Name of the device + :param arn: Amazon resource name for the IonQ device + """ + super().__init__(region="us-east-1", device_name=device_name, arn=arn) + self.submodule_options = [] + + if 'SKIP_INIT' in os.environ: + # TODO: This is currently needed so create_module_db in the Installer does not execute the rest + # of this section, which would be unnecessary. However, this should be done better in the future! + return + + self.init_s3_storage("ionq") + self.device = AwsDevice(arn, aws_session=self.aws_session) + + def get_parameter_options(self) -> dict: + """ + Returns empty dictionary as this solver has no configurable settings. + + :return: An empty dictionary + """ + return {} + + def get_default_submodule(self, option: str) -> None: + """ + Raises ValueError as this module has no submodules. + + :param option: Option name + :raises ValueError: If called, since this module has no submodules + """ + raise ValueError("This module has no submodules.") diff --git a/src/modules/devices/braket/oqc.py b/src/modules/devices/braket/oqc.py new file mode 100644 index 00000000..474f8fc5 --- /dev/null +++ b/src/modules/devices/braket/oqc.py @@ -0,0 +1,59 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +from braket.aws import AwsDevice + +from modules.devices.braket.braket import Braket + + +class OQC(Braket): + """ + Class for using the Oxford Quantum Circuits (OQC) devices on Amazon Braket. + """ + + def __init__(self, device_name: str, arn: str = 'arn:aws:braket:eu-west-2::device/qpu/oqc/Lucy'): + """ + Constructor method. + + :param device_name: Name of the device + :param arn: Amazon resource name for the OQC device + """ + super().__init__(region="eu-west-2", device_name=device_name, arn=arn) + self.submodule_options = [] + + if 'SKIP_INIT' in os.environ: + # TODO: This is currently needed so create_module_db in the Installer does not execute the rest + # of this section, which would be unnecessary. However, this should be done better in the future! + return + + self.init_s3_storage("oqc") + self.device = AwsDevice(arn, aws_session=self.aws_session) + + def get_parameter_options(self) -> dict: + """ + Returns an empty dictionary as this solver has no configurable settings. + + :return: Empty dict + """ + return {} + + def get_default_submodule(self, option: str) -> None: + """ + Raises ValueError as this module has no submodules. + + :param option: Option name + :raises ValueError: If called, since this module has no submodules + """ + raise ValueError("This module has no submodules.") diff --git a/src/modules/devices/braket/rigetti.py b/src/modules/devices/braket/rigetti.py new file mode 100644 index 00000000..dad24949 --- /dev/null +++ b/src/modules/devices/braket/rigetti.py @@ -0,0 +1,59 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +from braket.aws import AwsDevice + +from modules.devices.braket.braket import Braket + + +class Rigetti(Braket): + """ + Class for using the Rigetti devices on Amazon Braket. + """ + + def __init__(self, device_name: str, arn: str = 'arn:aws:braket:us-west-1::device/qpu/rigetti/Ankaa-2'): + """ + Constructor method. + + :param device_name: Name of the device + :param arn: Amazon resource name for the Rigetti device + """ + super().__init__(region="us-west-1", device_name=device_name, arn=arn) + self.submodule_options = [] + + if 'SKIP_INIT' in os.environ: + # TODO: This is currently needed so create_module_db in the Installer does not execute the rest + # of this section, which would be unnecessary. However, this should be done better in the future! + return + + self.init_s3_storage("rigetti") + self.device = AwsDevice(arn, aws_session=self.aws_session) + + def get_parameter_options(self) -> dict: + """ + Returns an empty dictionary as this solver has no configurable settings. + + :return: Empty dict + """ + return {} + + def get_default_submodule(self, option: str) -> None: + """ + Raises ValueError as this module has no submodules. + + :param option: Option name + :raises ValueError: If called, since this module has no submodules + """ + raise ValueError("This module has no submodules.") diff --git a/src/modules/devices/braket/sv1.py b/src/modules/devices/braket/sv1.py new file mode 100644 index 00000000..1b356fc5 --- /dev/null +++ b/src/modules/devices/braket/sv1.py @@ -0,0 +1,59 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +from braket.aws import AwsDevice + +from modules.devices.braket.braket import Braket + + +class SV1(Braket): + """ + Class for using the SV1 simulator on Amazon Braket. + """ + + def __init__(self, device_name: str, arn: str = 'arn:aws:braket:::device/quantum-simulator/amazon/sv1'): + """ + Constructor method. + + :param device_name: Name of the device + :param arn: Amazon resource name for the SV1 simulator + """ + super().__init__(device_name=device_name, arn=arn) + self.submodule_options = [] + + if 'SKIP_INIT' in os.environ: + # TODO: This is currently needed so create_module_db in the Installer does not execute the rest + # of this section, which would be unnecessary. However, this should be done better in the future! + return + + self.init_s3_storage("sv1") + self.device = AwsDevice(arn, aws_session=self.aws_session) + + def get_parameter_options(self) -> dict: + """ + Returns empty dictionary as this solver has no configurable settings. + + :return: Empty dict + """ + return {} + + def get_default_submodule(self, option: str) -> None: + """ + Raises ValueError as this module has no submodules. + + :param option: Option name + :raises ValueError: If called, since this module has no submodules + """ + raise ValueError("This module has no submodules.") diff --git a/src/modules/devices/braket/tn1.py b/src/modules/devices/braket/tn1.py new file mode 100644 index 00000000..4bcad081 --- /dev/null +++ b/src/modules/devices/braket/tn1.py @@ -0,0 +1,59 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +from braket.aws import AwsDevice + +from modules.devices.braket.braket import Braket + + +class TN1(Braket): + """ + Class for using the TN1 simulator on Amazon Braket. + """ + + def __init__(self, device_name: str, arn: str = 'arn:aws:braket:::device/quantum-simulator/amazon/tn1'): + """ + Constructor method. + + :param device_name: Name of the device + :param arn: Amazon resource name for the TN1 simulator + """ + super().__init__(device_name=device_name, arn=arn) + self.submodule_options = [] + + if 'SKIP_INIT' in os.environ: + # TODO: This is currently needed so create_module_db in the Installer does not execute the rest + # of this section, which would be unnecessary. However, this should be done better in the future! + return + + self.init_s3_storage("tn1") + self.device = AwsDevice(arn, aws_session=self.aws_session) + + def get_parameter_options(self) -> dict: + """ + Returns empty dict as this solver has no configurable settings. + + :return: Empty dict + """ + return {} + + def get_default_submodule(self, option: str) -> None: + """ + Raises ValueError as this module has no submodules. + + :param option: Option name + :raises ValueError: If called, since this module has no submodules + """ + raise ValueError("This module has no submodules.") diff --git a/src/modules/devices/device.py b/src/modules/devices/device.py new file mode 100644 index 00000000..4b9c402f --- /dev/null +++ b/src/modules/devices/device.py @@ -0,0 +1,102 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from abc import ABC +from modules.core import Core +from utils import start_time_measurement, end_time_measurement + + +class Device(Core, ABC): + """ + The device class abstracts away details of the physical device, such as submitting a task to the quantum system. + """ + + def __init__(self, device_name: str): + """ + Constructor method. + + :param device_name: Name of the device + """ + super().__init__(device_name) + self.device = None + self.config = None + self.device_name = self.name + + def get_parameter_options(self) -> dict: + """ + Returns the parameters to fine-tune the device. + + Should always be in this format: + .. code-block:: json + + { + "parameter_name":{ + "values":[1, 2, 3], + "description":"How many reads do you want?" + } + } + + :return: Available device settings for this device + """ + return {} + + def set_config(self, config): + """ + Sets the device configuration. + + :param config: Configuration settings for the device + """ + self.config = config + + def preprocess(self, input_data: any, config: dict, **kwargs) -> tuple[any, float]: + """ + Returns instance of device class (self) and time it takes to call config. + + :param input_data: Input data (not used) + :param config: Config for the device + :param kwargs: Optional keyword arguments + :return: Output and time needed + """ + start = start_time_measurement() + self.config = config + return self, end_time_measurement(start) + + def postprocess(self, input_data: any, config: dict, **kwargs) -> tuple[any, float]: + """ + Returns input data and adds device name to the metrics class instance. + + :param input_data: Input data passed by the parent module + :param config: Solver config + :param kwargs: Optional keyword arguments + :return: Output and time needed + """ + start = start_time_measurement() + self.metrics.add_metric("device", self.get_device_name()) + return input_data, end_time_measurement(start) + + def get_device(self) -> any: + """ + Returns device. + + :return: Instance of the device class + """ + return self.device + + def get_device_name(self) -> str: + """ + Returns the device name. + + :return: Name of the device + """ + return self.device_name diff --git a/src/modules/devices/local.py b/src/modules/devices/local.py new file mode 100644 index 00000000..bc04a427 --- /dev/null +++ b/src/modules/devices/local.py @@ -0,0 +1,46 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from modules.devices.device import Device + + +class Local(Device): + """ + Some solvers (often classical) run on a local environment without any specific device or setting needed. + """ + + def __init__(self): + """ + Constructor method. + """ + super().__init__(device_name="local") + self.device = None + self.submodule_options = [] + + def get_parameter_options(self) -> dict: + """ + Returns empty dictionary as this solver has no configurable settings. + + :return: Empty dict + """ + return {} + + def get_default_submodule(self, option: str) -> None: + """ + Raises ValueError as this module has no submodules. + + :param option: Option name + :raises ValueError: If called, since this module has no submodules + """ + raise ValueError("This module has no submodules.") diff --git a/src/modules/devices/pulser/pulser.py b/src/modules/devices/pulser/pulser.py new file mode 100644 index 00000000..0cae17aa --- /dev/null +++ b/src/modules/devices/pulser/pulser.py @@ -0,0 +1,59 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from abc import ABC, abstractmethod + +from modules.devices.device import Device + + +class Pulser(Device, ABC): + """ + Abstract class to use the Pulser devices. + """ + + def __init__(self, device_name: str): + """ + Constructor method. + + :param device_name: Name of the Pulser device. + """ + super().__init__(device_name) + self.device = None + self.backend = None + + def get_backend(self) -> any: + """ + Returns backend. + + :return: Instance of the backend class + """ + return self.backend + + @abstractmethod + def get_backend_config(self) -> any: + """ + Returns backend configurations. + + :return: Instance of the backend config class + """ + pass + + @staticmethod + def get_requirements() -> list[dict]: + """ + Return requirements of this module. + + :return: List of dict with requirements of this module + """ + return [{"name": "pulser", "version": "1.1.1"}] diff --git a/src/modules/solvers/annealer.py b/src/modules/solvers/annealer.py new file mode 100644 index 00000000..c53314f0 --- /dev/null +++ b/src/modules/solvers/annealer.py @@ -0,0 +1,111 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import TypedDict +import logging + +from modules.solvers.solver import Solver +from modules.core import Core +from utils import start_time_measurement, end_time_measurement + + +class Annealer(Solver): + """ + Class for both quantum and simulated annealing. + """ + + def __init__(self): + """ + Constructor method. + """ + super().__init__() + self.submodule_options = ["Simulated Annealer"] + + def get_default_submodule(self, option: str) -> Core: + """ + Returns the default submodule based on the provided option. + + :param option: The name of the submodule + :return: Instance of the default submodule + """ + if option == "Simulated Annealer": + from modules.devices.simulated_annealing_sampler import SimulatedAnnealingSampler # pylint: disable=C0415 + return SimulatedAnnealingSampler() + else: + raise NotImplementedError(f"Device Option {option} not implemented") + + def get_parameter_options(self) -> dict: + """ + Returns the configurable settings for this solver. + + :return: Dictionary of parameter options + .. code-block:: python + + return { + "number_of_reads": { + "values": [100, 250, 500, 750, 1000], + "description": "How many reads do you need?" + } + } + + """ + return { + "number_of_reads": { + "values": [100, 250, 500, 750, 1000], + "description": "How many reads do you need?" + } + } + + class Config(TypedDict): + """ + Attributes of a valid config. + + .. code-block:: python + + number_of_reads: int + + """ + number_of_reads: int + + def run(self, mapped_problem: dict, device_wrapper: any, config: Config, **kwargs: dict) \ + -> tuple[dict, float, dict]: + """ + Run the annealing solver. + + :param mapped_problem: Dict with the key 'Q' where its value should be the QUBO + :param device_wrapper: Annealing device + :param config: Annealing settings + :param kwargs: Additional keyword arguments + :return: Solution, the time it took to compute it and optional additional information + """ + + q = mapped_problem['Q'] + additional_solver_information = {} + device = device_wrapper.get_device() + start = start_time_measurement() + + if device_wrapper.device_name != "simulated annealer": + logging.error("Only simulated annealer available at the moment!") + logging.error("Please select another solver module.") + logging.error("The benchmarking run terminates with exception.") + raise Exception("Please refer to the logged error message.") + + response = device.sample_qubo(q, num_reads=config['number_of_reads']) + time_to_solve = end_time_measurement(start) + + # Take the result with the lowest energy: + sample = response.lowest().first.sample + logging.info(f'Annealing finished in {time_to_solve} ms.') + + return sample, time_to_solve, additional_solver_information diff --git a/src/modules/solvers/qaoa.py b/src/modules/solvers/qaoa.py new file mode 100644 index 00000000..f447b812 --- /dev/null +++ b/src/modules/solvers/qaoa.py @@ -0,0 +1,477 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from time import sleep +from typing import TypedDict +import logging + +import numpy as np +from braket.circuits import Circuit +from braket.aws import AwsDevice +from scipy.optimize import minimize + +from modules.solvers.solver import Solver +from modules.core import Core +from utils import start_time_measurement, end_time_measurement + + +class QAOA(Solver): + """ + QAOA with some parts copied/derived from https://github.com/aws/amazon-braket-examples. + """ + + def __init__(self): + """ + Constructor method. + """ + super().__init__() + self.submodule_options = [ + "LocalSimulator", "arn:aws:braket:::device/quantum-simulator/amazon/sv1", + "arn:aws:braket:::device/quantum-simulator/amazon/tn1", + "arn:aws:braket:us-east-1::device/qpu/ionq/Harmony", + "arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-3" + ] + + @staticmethod + def get_requirements() -> list[dict]: + """ + Return requirements of this module. + + :return: List of dict with requirements of this module + """ + return [ + {"name": "amazon-braket-sdk", "version": "1.88.2"}, + {"name": "scipy", "version": "1.12.0"}, + {"name": "numpy", "version": "1.26.4"} + ] + + def get_default_submodule(self, option: str) -> Core: + """ + Returns the default submodule based on the provided option. + + :param option: The name of the submodule + :return: Instance of the default submodule + """ + + if option == "arn:aws:braket:us-east-1::device/qpu/ionq/Harmony": + from modules.devices.braket.ionq import Ionq # pylint: disable=C0415 + return Ionq("ionQ", "arn:aws:braket:us-east-1::device/qpu/ionq/Harmony") + elif option == "arn:aws:braket:::device/quantum-simulator/amazon/sv1": + from modules.devices.braket.sv1 import SV1 # pylint: disable=C0415 + return SV1("SV1", "arn:aws:braket:::device/quantum-simulator/amazon/sv1") + elif option == "arn:aws:braket:::device/quantum-simulator/amazon/tn1": + from modules.devices.braket.tn1 import TN1 # pylint: disable=C0415 + return TN1("TN1", "arn:aws:braket:::device/quantum-simulator/amazon/tn1") + elif option == "arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-3": + from modules.devices.braket.rigetti import Rigetti # pylint: disable=C0415 + return Rigetti("Rigetti Aspen-9", "arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-3") + elif option == "LocalSimulator": + from modules.devices.braket.local_simulator import LocalSimulator # pylint: disable=C0415 + return LocalSimulator("LocalSimulator") + else: + raise NotImplementedError(f"Device Option {option} not implemented") + + def get_parameter_options(self) -> dict: + """ + Returns the configurable settings for this solver. + + :return: Dictionary of parameter settings + .. code-block:: python + + return { + "shots": { # number measurements to make on circuit + "values": list(range(10, 500, 30)), + "description": "How many shots do you need?" + }, + "opt_method": { + "values": ["Powell", "Nelder-Mead"], + "description": "Which optimization method do you want?" + }, + "depth": { + "values": [3], + "description": "Which circuit depth for QAOA do you want?" + } + } + + """ + return { + "shots": { # number of measurements to make on circuit + "values": list(range(10, 500, 30)), + "description": "How many shots do you need?" + }, + "opt_method": { + "values": ["Powell", "Nelder-Mead"], + "description": "Which optimization method do you want?" + }, + "depth": { + "values": [3], + "description": "Which circuit depth for QAOA do you want?" + } + } + + class Config(TypedDict): + """ + Attributes of a valid config. + + .. code-block:: python + + shots: int + opt_method: str + depth: int + + """ + shots: int + opt_method: str + depth: int + + def run(self, mapped_problem: dict, device_wrapper: any, config: Config, **kwargs: dict) -> tuple[any, float, dict]: + """ + Run QAOA algorithm on Ising. + + :param mapped_problem: Dict containing problem parameters mapped to the Ising model + :param device_wrapper: Instance of device + :param config: Solver configuration settings + :param kwargs: No additionally settings needed + :return: Solution, the time it took to compute it and optional additional information + """ + j = mapped_problem['J'] + if np.any(np.iscomplex(j)): + logging.warning("The problem matrix of the QAOA solver contains imaginary numbers." + "This may lead to an error later in the run.") + else: + j = np.real(j) + + # Set up the problem + n_qubits = j.shape[0] + + # User-defined hypers + depth = config['depth'] + opt_method = config['opt_method'] # SLSQP, COBYLA, Nelder-Mead, BFGS, Powell, ... + + # Initialize reference solution (simple guess) + bitstring_init = -1 * np.ones([n_qubits]) + energy_init = np.dot(bitstring_init, np.dot(j, bitstring_init)) + + # Set tracker to keep track of results + tracker = { + 'count': 1, # Elapsed optimization steps + 'optimal_energy': energy_init, # Global optimal energy + 'opt_energies': [], # Optimal energy at each step + 'global_energies': [], # Global optimal energy at each step + 'optimal_bitstring': bitstring_init, # Global optimal bitstring + 'opt_bitstrings': [], # Optimal bitstring at each step + 'costs': [], # Cost (average energy) at each step + 'res': None, # Quantum result object + 'params': [] # Track parameters + } + + # Set options for classical optimization + options = {'disp': True, 'maxiter': 100} + # options = {'disp': True, 'ftol': 1e-08, 'maxiter': 100, 'maxfev': 50} # example options + + ################################################################################## + # Run QAOA optimization on graph + ################################################################################## + + logging.info(f"Circuit depth hyperparameter:{depth}") + logging.info(f"Problem size:{n_qubits}") + + # Kick off training + start = start_time_measurement() + _, _, tracker = train( + device=device_wrapper.get_device(), + options=options, + p=depth, ising=j, + n_qubits=n_qubits, + n_shots=config['shots'], + opt_method=opt_method, + tracker=tracker, + s3_folder=device_wrapper.s3_destination_folder, + verbose=True + ) + time_to_solve = end_time_measurement(start) + + # Log optimized results + logging.info(f"Optimal energy: {tracker['optimal_energy']}") + logging.info(f"Optimal classical bitstring: {tracker['optimal_bitstring']}") + + # TODO maybe save this plot + # plt.plot(cycles, optim_classical) + # plt.xlabel('optimization cycle') + # plt.ylabel('best classical minimum') + # plt.show() + + return tracker['optimal_bitstring'], time_to_solve, {} + + +# QAOA utils (source: +# https://github.com/aws/amazon-braket-examples/blob/main/examples/hybrid_quantum_algorithms/QAOA/utils_qaoa.py) + +# Function to implement ZZ gate using CNOT gates +def zz_gate(q1: any, q2: any, gamma: float) -> Circuit: + """ + Function that returns a circuit implementing exp(-i \\gamma Z_i Z_j) using CNOT gates if ZZ not supported. + + :param q1: Qubit 1 (control) + :param q2: Qubit 2 (target) + :param gamma: Gamma parameter (angle) + :return: ZZ gate + """ + # Get a circuit + circ_zz = Circuit() + + # Construct decomposition of ZZ + circ_zz.cnot(q1, q2).rz(q2, gamma).cnot(q1, q2) + + return circ_zz + + +# Function to implement evolution with driver Hamiltonian +def driver(beta: float, n_qubits: int) -> Circuit: + """ + Returns circuit for driver Hamiltonian U(Hb, beta). + + :param beta: Beta parameter (angle) + :param n_qubits: Number of qubits + :return: Circuit with rotated qubits + """ + # Instantiate circuit object + circ = Circuit() + + # Apply parametrized rotation around x to every qubit + for qubit in range(n_qubits): + gate = Circuit().rx(qubit, 2 * beta) + circ.add(gate) + + return circ + + +# Helper function for evolution with cost Hamiltonian +def cost_circuit(gamma: float, ising: np.ndarray, device: AwsDevice) -> Circuit: + """ + Returns circuit for evolution with cost Hamiltonian. + + :param gamma: Gamma parameter (angle) + :param ising: Ising matrix + :param device: Device to run the circuit on + :return: Circuit representing the cost Hamiltonian + """ + # Instantiate circuit object + circ = Circuit() + + # Get all non-zero entries (edges) from Ising matrix + idx = ising.nonzero() + edges = list(zip(idx[0], idx[1])) + + # Apply ZZ gate for every edge (with corresponding interaction strength) + for qubit_pair in edges: + # Get interaction strength from Ising matrix + int_strength = ising[qubit_pair[0], qubit_pair[1]] + # For Rigetti we decompose ZZ using CNOT gates + if device.name in ["Rigetti", "Aspen-9"]: # TODO make this more flexible + gate = zz_gate(qubit_pair[0], qubit_pair[1], gamma * int_strength) + # Classical simulators and IonQ support ZZ gate + else: + gate = Circuit().zz(qubit_pair[0], qubit_pair[1], angle=2 * gamma * int_strength) + circ.add(gate) + + return circ + + +# Function to build the QAOA circuit with depth p +def circuit(params: np.array, device: AwsDevice, n_qubits: int, ising: np.ndarray) -> Circuit: + """ + Function to return the full QAOA circuit; depends on device as ZZ implementation depends on gate set of backend. + + :param params: Array containing the beta and gamma parameters + :param device: Device to run the circuit on + :param n_qubits: Number of qubits + :param ising: Ising matrix + :return: QAOA Circuit + """ + + # Initialize QAOA circuit with first Hadamard layer + circ = Circuit() + x_on_all = Circuit().x(range(0, n_qubits)) + circ.add(x_on_all) + h_on_all = Circuit().h(range(0, n_qubits)) + circ.add(h_on_all) + + # Setup two parameter families + circuit_length = int(len(params) / 2) + gammas = params[:circuit_length] + betas = params[circuit_length:] + + # Add QAOA circuit layer blocks + for mm in range(circuit_length): + circ.add(cost_circuit(gammas[mm], ising, device)) + circ.add(driver(betas[mm], n_qubits)) + + return circ + + +# Function that computes cost function for given params +# pylint: disable=R0917 +# pylint: disable=R0913 +def objective_function(params: np.array, device: AwsDevice, ising: np.ndarray, n_qubits: int, n_shots: int, + tracker: dict, s3_folder: tuple[str, str], verbose: bool) -> float: + """ + Objective function takes a list of variational parameters as input, + and returns the cost associated with those parameters. + + :param params: Array containing beta and gamma parameters + :param device: Device to run the circuit on + :param ising: Ising matrix + :param n_qubits: Number of qubits + :param n_shots: Number of measurements to make on the circuit + :param tracker: Keeps track of the runs on the circuit + :param s3_folder: AWS S3 bucket + :param verbose: Controls degree of detail in logs + :return: Energy expectation value + """ + + if verbose: + logging.info("==================================" * 2) + logging.info(f"Calling the quantum circuit. Cycle: {tracker['count']}") + + # Get a quantum circuit instance from the parameters + qaoa_circuit = circuit(params, device, n_qubits, ising) + + # Classically simulate the circuit + # Execute the correct device.run call depending on whether the backend is local or cloud based + if device.name in ["DefaultSimulator", "StateVectorSimulator"]: + task = device.run(qaoa_circuit, shots=n_shots) + else: + task = device.run(qaoa_circuit, s3_folder, shots=n_shots, poll_timeout_seconds=3 * 24 * 60 * 60) + + # Get ID and status of submitted task + task_id = task.id + status = task.state() + logging.info(f"ID of task: {task_id}") + logging.info(f"Status of task: {status}") + + # Wait for job to complete + while status != 'COMPLETED': + status = task.state() + logging.info(f"Status: {status}") + sleep(10) + + # Get result for this task + result = task.result() + logging.info(result) + + # Convert results (0 and 1) to ising (-1 and 1) + meas_ising = result.measurements + meas_ising[meas_ising == 0] = -1 + + # Get all energies (for every shot): (n_shots, 1) vector + all_energies = np.diag(np.dot(meas_ising, np.dot(ising, np.transpose(meas_ising)))) + + # Find minimum and corresponding classical string + energy_min = np.min(all_energies) + tracker["opt_energies"].append(energy_min) + optimal_string = meas_ising[np.argmin(all_energies)] + tracker["opt_bitstrings"].append(optimal_string) + logging.info(tracker["optimal_energy"]) + + # Store optimal (classical) result/bitstring + if energy_min < tracker["optimal_energy"]: + tracker.update({"optimal_energy": energy_min, "optimal_bitstring": optimal_string}) + + # Store global minimum + tracker["global_energies"].append(tracker["optimal_energy"]) + + # Energy expectation value + energy_expect = np.sum(all_energies) / n_shots + + if verbose: + logging.info(f"Minimal energy: {energy_min}") + logging.info(f"Optimal classical string: {optimal_string}") + logging.info(f"Energy expectation value (cost): {energy_expect}") + + # Update tracker + tracker.update({"count": tracker["count"] + 1, "res": result}) + tracker["costs"].append(energy_expect) + tracker["params"].append(params) + + return energy_expect + + +# The function to execute the training: run classical minimization. +# pylint: disable=R0917 +def train(device: AwsDevice, options: dict, p: int, ising: np.ndarray, n_qubits: int, n_shots: int, opt_method: str, + tracker: dict, s3_folder: tuple[str, str], verbose: bool = True) -> tuple[float, np.ndarray, dict]: + """ + Function to run QAOA algorithm for given, fixed circuit depth p. + + :param device: Device to run the circuit on + :param options: Dict containing parameters of classical part of the QAOA + :param p: Circuit depth + :param ising: Ising matrix + :param n_qubits: Number of qubits + :param n_shots: Number of measurements to make on the circuit + :param opt_method: Controls degree of detail in logs + :param tracker: Keeps track of the runs on the circuit + :param s3_folder: AWS S3 bucket + :param verbose: Controls degree of detail in logs + :return: Results of the training as a tuple of the energy, the angle and the tracker + """ + logging.info("Starting the training.") + logging.info("==================================" * 2) + logging.info(f"OPTIMIZATION for circuit depth p={p}") + + if not verbose: + logging.info('Param "verbose" set to False. Will not print intermediate steps.') + logging.info("==================================" * 2) + + # Initialize + cost_energy = [] + + # Randomly initialize variational parameters within appropriate bounds + gamma_initial = np.random.uniform(0, 2 * np.pi, p).tolist() + beta_initial = np.random.uniform(0, np.pi, p).tolist() + params0 = np.array(gamma_initial + beta_initial) + + # Set bounds for search space + bnds_gamma = [(0, 2 * np.pi) for _ in range(int(len(params0) / 2))] + bnds_beta = [(0, np.pi) for _ in range(int(len(params0) / 2))] + bnds = bnds_gamma + bnds_beta + + tracker["params"].append(params0) + print(f"Qubit count: {n_qubits}") + + # Run classical optimization (example: method='Nelder-Mead') + try: + result = minimize( + objective_function, + params0, + args=(device, ising, n_qubits, n_shots, tracker, s3_folder, verbose), + options=options, + method=opt_method, + bounds=bnds, + ) + except ValueError as e: + logging.error(f"The following ValueError occurred in module QAOA: {e}") + logging.error("The benchmarking run terminates with exception.") + raise Exception("Please refer to the logged error message.") from e + + # Store result of classical optimization + result_energy = result.fun + cost_energy.append(result_energy) + logging.info(f"Final average energy (cost): {result_energy}") + result_angle = result.x + logging.info(f"Final angles: {result_angle}") + logging.info("Training complete.") + + return result_energy, result_angle, tracker diff --git a/src/modules/solvers/solver.py b/src/modules/solvers/solver.py new file mode 100644 index 00000000..1ae3811e --- /dev/null +++ b/src/modules/solvers/solver.py @@ -0,0 +1,50 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from abc import ABC, abstractmethod +from modules.core import Core + + +class Solver(Core, ABC): + """ + The solver is responsible for finding feasible and high-quality solutions of the formulated problem, i.e., of the + defined objective function. + """ + + def postprocess(self, input_data: any, config: dict, **kwargs) -> tuple[any, float]: + """ + The actual solving process is done here, using the device which is provided by the device submodule + and the problem data provided by the parent module. + + :param input_data: Data passed to the run function of the solver + :param config: Solver config + :param kwargs: Optional keyword arguments + :return: Output and time needed + """ + output, elapsed_time, additional_metrics = self.run(self.preprocessed_input, input_data, config, **kwargs) + self.metrics.add_metric_batch(additional_metrics) + return output, elapsed_time + + @abstractmethod + def run(self, mapped_problem: any, device_wrapper: any, config: any, **kwargs) -> tuple[any, float, dict]: + """ + This function runs the solving algorithm on a mapped problem instance and returns a solution. + + :param mapped_problem: A representation of the problem that the solver can solve + :param device_wrapper: A device the solver can leverage for the algorithm + :param config: Settings for the solver such as hyperparameters + :param kwargs: Optional additional settings + :return: Solution, the time it took to compute it and some optional additional information + """ + pass diff --git a/src/plotter.py b/src/plotter.py new file mode 100644 index 00000000..b5ce0141 --- /dev/null +++ b/src/plotter.py @@ -0,0 +1,248 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from collections import defaultdict +import logging + +import matplotlib +from matplotlib import pyplot as plt +import seaborn as sns +import pandas as pd + +matplotlib.use('Agg') +matplotlib.rcParams['savefig.dpi'] = 300 +sns.set(style="darkgrid") + + +class Plotter: + """ + Plotter class which generates some general plots. + """ + + @staticmethod + def visualize_results(results: list[dict], store_dir: str) -> None: + """ + Function to plot the execution times of the benchmark. + + :param results: Dict containing the results + :param store_dir: Directory where the plots are stored + """ + if results is None or len(results) == 0: + logging.info("Nothing to plot since results are empty.") + return + + processed_results_with_application_score = [] + processed_results_rest = [] + required_application_score_keys = [ + "application_score_value", "application_score_unit", "application_score_type" + ] + application_name = None + application_axis = None + static_keys, changing_keys = Plotter._get_config_keys(results) + for result in results: + application_name = result["module"]["module_name"] + if len(changing_keys) == 1: + # If only 1 config item changes, we use its value for application_config + application_axis = changing_keys[0] + application_config = result['module']['module_config'][changing_keys[0]] + else: + # If multiple config items change, we stringify them + application_axis = f"{application_name} Config" + application_config = ', '.join( + [f"{key}: {value}" for (key, value) in sorted(result["module"]["module_config"].items(), + key=lambda key_value_pair: + key_value_pair[0]) if key not in static_keys] + ) + if len(static_keys) > 0: + # Include the static items in the axis name + application_axis += "(" + ', '.join( + [f"{key}: {result['module']['module_config'][key]}" for key in static_keys] + ) + ")" + + processed_item = Plotter._extract_columns({ + "benchmark_backlog_item_number": result["benchmark_backlog_item_number"], + "total_time": result["total_time"], + "application_config": application_config + }, result["module"]) + + if all(k in result["module"] for k in required_application_score_keys): + # Check if all required keys are present to create application score plots + for k in required_application_score_keys: + processed_item[k] = result["module"][k] + processed_results_with_application_score.append(processed_item) + else: + processed_results_rest.append(processed_item) + + if len(processed_results_with_application_score) > 0: + logging.info("Found results with an application score, generating according plots.") + Plotter.plot_application_score( + application_name, application_axis, processed_results_with_application_score, store_dir + ) + + Plotter.plot_times( + application_name, application_axis, + [*processed_results_with_application_score, *processed_results_rest], + store_dir, required_application_score_keys + ) + + logging.info("Finished creating plots.") + + @staticmethod + def plot_times(application_name: str, application_axis: str, results: list[dict], store_dir: str, + required_application_score_keys: list) -> None: + """ + Function to plot execution times of the different modules in a benchmark. + + :param application_name: Name of the application + :param application_axis: Name of the application axis + :param results: Dict containing the results + :param store_dir: Directory where the plots are stored + :param required_application_score_keys: List of keys which have to be present to calculate an application score + """ + + df = pd.DataFrame.from_dict(results) + df = df.fillna(0.0).infer_objects(copy=False) + df_melt = df.drop(df.filter(["application_config", "config_combo", "total_time", + *required_application_score_keys]), axis=1) + df_melt = pd.melt(frame=df_melt, id_vars='benchmark_backlog_item_number', var_name='module_config', + value_name='time') + + # This plot shows the execution time of each module + ax = sns.barplot(x="benchmark_backlog_item_number", y="time", data=df_melt, hue="module_config") + plt.title(application_name) + # Put the legend out of the figure + plt.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0., title="Modules used") + ax.set(xlabel="benchmark run ID", ylabel='execution time of module (ms)') + plt.savefig(f"{store_dir}/time_by_module.pdf", dpi=300, bbox_inches='tight') + logging.info(f"Saved {f'{store_dir}/time_by_module.pdf'}.") + plt.clf() + + # This plot shows the total time of a benchmark run + ax = sns.barplot(x="application_config", y="total_time", data=df, hue="config_combo") + ax.set(xlabel=application_axis, ylabel="total execution time (ms)") + # Put the legend out of the figure + plt.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0., title="Modules used") + plt.title(application_name) + plt.sca(ax) + # If column values are very long and of type string rotate the ticks + if (pd.api.types.is_string_dtype(df.application_config.dtype) or pd.api.types.is_object_dtype( + df.application_config.dtype)) and df.application_config.str.len().max() > 10: + plt.xticks(rotation=90) + plt.savefig(f"{store_dir}/total_time.pdf", dpi=300, bbox_inches='tight') + logging.info(f"Saved {f'{store_dir}/total_time.pdf'}.") + plt.clf() + + @staticmethod + def plot_application_score(application_name: str, application_axis: str, results: list[dict], + store_dir: str) -> None: + """ + Function to create plots showing the application score. + + :param application_name: Name of the application + :param application_axis: Name of the application axis + :param results: Dict containing the results + :param store_dir: Directory where the plots are stored + """ + df = pd.DataFrame.from_dict(results) + application_score_units = df["application_score_unit"].unique() + count_invalid_rows = pd.isna(df['application_score_value']).sum() + + if count_invalid_rows == len(df): + logging.info("All results have an invalid application score, skipping plotting.") + return + else: + logging.info(f"{count_invalid_rows} out of {len(df)} benchmark runs have an invalid application score.") + + if len(application_score_units) != 1: + logging.warning( + f"Found more or less than exactly 1 application_score_unit in {application_score_units}." + f" This might lead to incorrect plots!" + ) + + ax = sns.barplot(x="application_config", y="application_score_value", data=df, hue="config_combo") + ax.set(xlabel=application_axis, ylabel=application_score_units[0]) + # Put the legend out of the figure + plt.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0., title="Modules used") + ax.text( + 1.03, 0.5, + f"{len(df) - count_invalid_rows}/{len(df)} runs have a valid \napplication score", + transform=ax.transAxes, fontsize=12, verticalalignment='top', + bbox={"boxstyle": "round", "alpha": 0.15} + ) + plt.title(application_name) + + plt.sca(ax) + # If column values are very long and of type string, rotate the ticks + if (pd.api.types.is_string_dtype(df.application_config.dtype) or pd.api.types.is_object_dtype( + df.application_config.dtype)) and df.application_config.str.len().max() > 10: + plt.xticks(rotation=90) + + plt.savefig(f"{store_dir}/application_score.pdf", dpi=300, bbox_inches='tight') + logging.info(f"Saved {f'{store_dir}/application_score.pdf'}.") + plt.clf() + + @staticmethod + def _get_config_keys(results: list[dict]) -> tuple[list, list]: + """ + Function that extracts config keys. + + :param results: Results of a benchmark run + :return: Tuple with list of static keys and list of changing keys + """ + static_keys = [] + changing_keys = [] + helper_dict = defaultdict(list) + # Try to find out which key in the dict change + for result in results: # you can list as many input dicts as you want here + d = result["module"]["module_config"] + for key, value in d.items(): + helper_dict[key].append(value) + helper_dict[key] = list(set(helper_dict[key])) + + for key, value in helper_dict.items(): + if len(value) == 1: + static_keys.append(key) + else: + changing_keys.append(key) + + return static_keys, changing_keys + + @staticmethod + def _extract_columns(config: dict, rest_result: dict) -> dict: + """ + Function to extract and summarize certain data fields like the time spent in every module + from the nested module chain. + + :param config: Dictionary containing multiple data fields like the config of a module + :param rest_result: Rest of the module chain + :return: Extracted data + """ + if rest_result: + module_name = rest_result["module_name"] + for key, value in sorted(rest_result["module_config"].items(), + key=lambda key_value_pair: key_value_pair[0]): + module_name += f", {key}: {value}" + + config_combo = config.pop("config_combo") + "\n" + module_name if "config_combo" in config else "" + return Plotter._extract_columns( + { + **config, + "config_combo": config_combo, + module_name: rest_result["total_time"] + if module_name not in config else config[module_name] + rest_result["total_time"] + }, + rest_result["submodule"] + ) + + return config diff --git a/src/quark2_adapter/legacy_classes/application.py b/src/quark2_adapter/legacy_classes/application.py new file mode 100644 index 00000000..f3aa8a19 --- /dev/null +++ b/src/quark2_adapter/legacy_classes/application.py @@ -0,0 +1,210 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from abc import ABC, abstractmethod +from typing import final + +from utils import _get_instance_with_sub_options + + +class Application(ABC): + """ + The application component defines the workload, comprising a dataset of increasing complexity, + a validation, and an evaluation function. + """ + + def __init__(self, application_name: str): + """ + Constructor method. + + :param application_name: Name of the application + """ + self.application_name = application_name + self.application = None + self.mapping_options = [] + self.sub_options = [] + + self.problem = None + self.problems = {} + self.conf_idx = None + + super().__init__() + + def get_application(self) -> any: + """ + Getter that returns the application. + + :return: The application instance + """ + return self.application + + @abstractmethod + def get_solution_quality_unit(self) -> str: + """ + Method to return the unit of the evaluation which is used to make the plots nicer. + + :return: String with the unit + """ + + @abstractmethod + def get_parameter_options(self) -> dict: + """ + Method to return the parameters needed to create a concrete problem of an application. + + Should always be in this format: + + .. code-block:: json + + { + "parameter_name":{ + "values":[1, 2, 3], + "description":"How many nodes do you need?" + }, + "parameter_name_2":{ + "values":["x", "y"], + "description":"Which type of problem do you want?" + } + } + + :return: Available application settings for this application + """ + pass + + def regenerate_on_iteration(self, config: dict) -> bool: + """ + Overwrite this to return True if the problem should be newly generated + on every iteration. Typically, this will be the case if the problem is taken + from a statistical ensemble e.g. an erdos-renyi graph. + + :param config: The application configuration + :return: Whether the problem should be recreated on every iteration. Returns False if not overwritten. + """ + return False + + @final + def init_problem(self, config: dict, conf_idx: int, iter_count: int, path: str) -> any: + """ + This method is called on every iteration and calls generate_problem if necessary. + conf_idx identifies the application configuration. + Note that when there are several mappings, solvers or devices this method is + called several times with the same conf_idx and rep_count. In this case + the problem will be the same if conf_idx and rep_count are both the same. + + The implementation assumes that the loop over the rep_count is within the loop + over the different application configurations (conf_idx). + + :param config: the application configuration + :param conf_idx: the index of the application configuration + :param iter_count: the repetition count (starting with 1) + :param path: the path used to save each newly generated problem instance + :return: the current problem instance + """ + if conf_idx != self.conf_idx: + self.problems = {} + self.conf_idx = conf_idx + + key = iter_count if self.regenerate_on_iteration(config) else "dummy" + if key in self.problems: + self.problem = self.problems[key] + else: + self.problem = self.generate_problem(config, iter_count) + self.problems[key] = self.problem + self.save(path, iter_count) + return self.problem + + @abstractmethod + def generate_problem(self, config: dict, iter_count: int) -> any: + """ + Depending on the config this method creates a concrete problem and returns it. + + :param config: The application configuration + :param iter_count: The iteration count + :return: The generated problem instance + """ + pass + + def process_solution(self, solution) -> tuple[any, float]: + """ + Most of the time the solution has to be processed before it can be validated and evaluated + This might not be necessary in all cases, so the default is to return the original solution. + + :param solution: The solution to be processed + :return: Processed solution and the execution time to process it + """ + return solution, 0 + + @abstractmethod + def validate(self, solution) -> tuple[bool, float]: + """ + Check if the solution is valid. + + :param solution: The solution to validate + :return: Boolean indicating if the solution is valid and the time it took to create it + """ + pass + + @abstractmethod + def evaluate(self, solution: any) -> tuple[float, float]: + """ + Checks how good the solution is to allow comparison to other solutions. + + :param solution: The solution to evaluate + :return: Evaluation and the time it took to create it + """ + pass + + @abstractmethod + def save(self, path: str, iter_count: int) -> None: + """ + Save the concrete problem. + + :param path: Path of the experiment directory for this run + :param iter_count: the iteration count + """ + pass + + def get_submodule(self, mapping_option: str) -> any: + """ + If self.sub_options is not None, a mapping is instantiated according to the information given in + self.sub_options. Otherwise, get_mapping is called as fall back. + + :param mapping_option: The option for the mapping + :return: instance of a mapping class + """ + if self.sub_options is None: + return self.get_mapping(mapping_option) + else: + return _get_instance_with_sub_options(self.sub_options, mapping_option) + + @abstractmethod + def get_mapping(self, mapping_option: str) -> any: + """ + Return a default mapping for an application. This applies only if + self.sub_options is None. See get_submodule. + + :param mapping_option: String with the option + :return: Instance of a mapping class + """ + pass + + def get_available_mapping_options(self) -> list: + """ + Gets the list of available mapping options. + + :return: list of mapping options + """ + if self.sub_options is None: + return self.mapping_options + else: + return [option["name"] for option in self.sub_options] diff --git a/src/quark2_adapter/legacy_classes/device.py b/src/quark2_adapter/legacy_classes/device.py new file mode 100644 index 00000000..7bc0d066 --- /dev/null +++ b/src/quark2_adapter/legacy_classes/device.py @@ -0,0 +1,75 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from abc import ABC + + +class Device(ABC): + """ + The device class abstracts away details of the physical device, + such as submitting a task to the quantum system. + """ + + def __init__(self, device_name: str): + """ + Constructor method. + + :param device_name: Name of the device + """ + self.device = None + self.device_name = device_name + self.config = None + + def get_parameter_options(self) -> dict: + """ + Method to return the parameters to fine tune the device. + + Should always be in this format: + + .. code-block:: json + + { + "parameter_name":{ + "values":[1, 2, 3], + "description":"How many reads do you want?" + } + } + + :return: Available device settings for this device + """ + return {} + + def set_config(self, config: dict) -> None: + """ + Sets the device configuration. + + :param config: Configuration dictionary + """ + self.config = config + + def get_device(self) -> any: + """ + Returns the device instance. + + :return: Instance of the device + """ + return self.device + + def get_device_name(self) -> str: + """ + Returns the name of the Device. + + :return: Name of the device + """ + return self.device_name diff --git a/src/quark2_adapter/legacy_classes/mapping.py b/src/quark2_adapter/legacy_classes/mapping.py new file mode 100644 index 00000000..05e0d716 --- /dev/null +++ b/src/quark2_adapter/legacy_classes/mapping.py @@ -0,0 +1,108 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from abc import ABC, abstractmethod +from utils import _get_instance_with_sub_options + + +class Mapping(ABC): + """ + The task of the mapping module is to translate the application’s data and problem specification + into a mathematical formulation suitable for a solver. + """ + + def __init__(self): + """ + Constructor method. + """ + self.solver_options = [] + self.sub_options = None + super().__init__() + + @abstractmethod + def map(self, problem: any, config: dict) -> tuple[any, float]: + """ + Maps the given problem into a specific format a solver can work with. E.g. graph to QUBO. + + :param problem: Problem instance which should be mapped to the target representation + :param config: Instance of class Config specifying the mapping settings + :return: The mapped problem and the time it took to create the mapping + """ + pass + + def reverse_map(self, solution: any) -> tuple[any, float]: + """ + Maps the solution back to the original problem. This might not be necessary in all cases, so the default is + to return the original solution. This might be needed to convert the solution to a representation needed + for validation and evaluation. + + :param solution: Solution to be reversed back to its original representation + :return: Mapped solution and the time it took to create it + """ + return solution, 0 + + @abstractmethod + def get_parameter_options(self) -> dict: + """ + Method to return the parameters to fine tune the mapping. + + Should always be in this format: + + .. code-block:: json + + { + "parameter_name":{ + "values":[1, 2, 3], + "description":"How to scale your Lagrangian?" + } + } + + :return: Returns the available parameter options of this mapping + """ + pass + + def get_submodule(self, solver_option: str) -> any: + """ + If self.sub_options is not None, a solver is instantiated according to the information given in sub_options. + Otherwise, get_solver is called as fall back. + + :param solver_option: The option for the solver + :return: Instance of a solver class + """ + if self.sub_options is None: + return self.get_solver(solver_option) + else: + return _get_instance_with_sub_options(self.sub_options, solver_option) + + @abstractmethod + def get_solver(self, solver_option: str) -> any: + """ + Returns the default solver for a given string. This applies only if + self.sub_options is None. See get_submodule. + + :param solver_option: desired solver option + :return: Instance of solver class + """ + pass + + def get_available_solver_options(self) -> list: + """ + Returns all available solvers. + + :return: List of solvers + """ + if self.sub_options is None: + return self.solver_options + else: + return [option["name"] for option in self.sub_options] diff --git a/src/quark2_adapter/legacy_classes/solver.py b/src/quark2_adapter/legacy_classes/solver.py new file mode 100644 index 00000000..07ad5ee2 --- /dev/null +++ b/src/quark2_adapter/legacy_classes/solver.py @@ -0,0 +1,99 @@ +# Copyright 2021 The QUARK Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from abc import ABC, abstractmethod +from utils import _get_instance_with_sub_options + + +class Solver(ABC): + """ + The solver is responsible for finding feasible and high-quality solutions + of the formulated problem, i.e., of the defined objective function. + """ + + def __init__(self): + """ + Constructor method. + """ + self.device_options = [] + self.sub_options = None + super().__init__() + + @abstractmethod + def run(self, mapped_problem: any, device: any, config: dict, **kwargs) -> tuple[any, float, dict]: + """ + This function runs the solving algorithm on a mapped problem instance and returns a solution. + + :param mapped_problem: A representation of the problem that the solver can solve + :param device: A device the solver can leverage for the algorithm + :param config: Settings for the solver such as hyperparameters + :param kwargs: Optional additional settings + :return: Solution, the time it took to compute it and some optional additional information + """ + pass + + @abstractmethod + def get_parameter_options(self) -> dict: + """ + Method to return the parameters to fine tune the solver. + + Should always be in this format: + + .. code-block:: json + + { + "parameter_name":{ + "values":[1, 2, 3], + "description":"How many reads do you want?" + } + } + + :return: Available solver settings for this solver + """ + pass + + def get_submodule(self, device_option: str) -> any: + """ + If self.sub_options is not None, a device is instantiated according to the information given in + self.sub_options. Otherwise, get_device is called as fall back. + + :param device_option: The option for the device + :return: Instance of the device class + """ + if self.sub_options is None: + return self.get_device(device_option) + else: + return _get_instance_with_sub_options(self.sub_options, device_option) + + @abstractmethod + def get_device(self, device_option: str) -> any: + """ + Returns the default device based on string. This applies only if + self.sub_options is None. See get_submodule. + + :param device_option: Desired device option + :return: Instance of the device class + """ + pass + + def get_available_device_options(self) -> list: + """ + Returns the list of available devices. + + :return: List of devices + """ + if self.sub_options is None: + return self.device_options + else: + return [o["name"] for o in self.sub_options] diff --git a/tests/configs/invalid/tsp.yml b/tests/configs/invalid/tsp.yml new file mode 100644 index 00000000..1efff8e5 --- /dev/null +++ b/tests/configs/invalid/tsp.yml @@ -0,0 +1,26 @@ +application: + config: + nodes: + - 4 + - 6 + name: TSP + submodules: + - config: {} + name: GreedyClassicalTSPInvalid + submodules: + - config: {} + name: Local + submodules: [] + - config: {} + name: ReverseGreedyClassicalTSP + submodules: + - config: {} + name: Invalid + submodules: [] + - config: {} + name: RandomTSP + submodules: + - config: {} + name: Local + submodules: [] +repetitions: 2 diff --git a/tests/configs/valid/acl.yml b/tests/configs/valid/acl.yml new file mode 100644 index 00000000..ee9d08d9 --- /dev/null +++ b/tests/configs/valid/acl.yml @@ -0,0 +1,25 @@ +application: + config: + model_select: + - Tiny + name: ACL + submodules: + - config: {} + name: MIPsolverACL + submodules: + - config: {} + name: Local + submodules: [] + - config: {} + name: QUBO + submodules: + - config: + number_of_reads: + - 100 + - 250 + name: Annealer + submodules: + - config: {} + name: Simulated Annealer + submodules: [] +repetitions: 1 diff --git a/tests/configs/valid/mis.yml b/tests/configs/valid/mis.yml new file mode 100644 index 00000000..b4b660a2 --- /dev/null +++ b/tests/configs/valid/mis.yml @@ -0,0 +1,30 @@ +application: + config: + filling_fraction: + - 0.2 + size: + - 5 + spacing: + - 0.4 + name: MIS + submodules: + - config: {} + name: NeutralAtom + submodules: + - config: + samples: + - 10 + name: NeutralAtomMIS + submodules: + - config: + SPAM: + - false + amplitude: + - false + dephasing: + - false + doppler: + - false + name: MockNeutralAtomDevice + submodules: [] +repetitions: 1 diff --git a/tests/configs/valid/pvc.yml b/tests/configs/valid/pvc.yml new file mode 100644 index 00000000..85797f5e --- /dev/null +++ b/tests/configs/valid/pvc.yml @@ -0,0 +1,35 @@ +application: + config: + seams: + - 2 + - 3 + name: PVC + submodules: + - config: + lagrange_factor: + - 1.0 + - 1.25 + name: QUBO + submodules: + - config: + number_of_reads: + - 250 + - 500 + name: Annealer + submodules: + - config: {} + name: Simulated Annealer + submodules: [] + - config: {} + name: GreedyClassicalPVC + submodules: + - config: {} + name: Local + submodules: [] + - config: {} + name: ReverseGreedyClassicalPVC + submodules: + - config: {} + name: Local + submodules: [] +repetitions: 2 diff --git a/tests/configs/valid/sat.yml b/tests/configs/valid/sat.yml new file mode 100644 index 00000000..c552aedf --- /dev/null +++ b/tests/configs/valid/sat.yml @@ -0,0 +1,24 @@ +application: + config: + clvar_ratio_cons: + - 2 + clvar_ratio_test: + - 2 + max_tries: + - 100 + problem_set: + - 0 + variables: + - 10 + name: SAT + submodules: + - config: {} + name: Direct + submodules: + - config: {} + name: ClassicalSAT + submodules: + - config: {} + name: Local + submodules: [] +repetitions: 2 diff --git a/tests/configs/valid/scp.yml b/tests/configs/valid/scp.yml new file mode 100644 index 00000000..e1a586af --- /dev/null +++ b/tests/configs/valid/scp.yml @@ -0,0 +1,23 @@ +application: + config: + model_select: + - Tiny + - Large + name: SCP + submodules: + - config: + penalty_weight: + - 2.0 + - 10.0 + - 100.0 + name: qubovertQUBO + submodules: + - config: + number_of_reads: + - 100 + name: Annealer + submodules: + - config: {} + name: Simulated Annealer + submodules: [] +repetitions: 2 diff --git a/tests/configs/valid/tsp.yml b/tests/configs/valid/tsp.yml new file mode 100644 index 00000000..e48f53f2 --- /dev/null +++ b/tests/configs/valid/tsp.yml @@ -0,0 +1,25 @@ +application: + config: + nodes: + - 6 + name: TSP + submodules: + - config: {} + name: GreedyClassicalTSP + submodules: + - config: {} + name: Local + submodules: [] + - config: {} + name: ReverseGreedyClassicalTSP + submodules: + - config: {} + name: Local + submodules: [] + - config: {} + name: RandomTSP + submodules: + - config: {} + name: Local + submodules: [] +repetitions: 2 diff --git a/tests/modules/applications/optimization/MIS/mappings/mis_test_graph.pkl b/tests/modules/applications/optimization/MIS/mappings/mis_test_graph.pkl new file mode 100644 index 0000000000000000000000000000000000000000..3032b16f6538e8d95cfb832775c8400032c4726a GIT binary patch literal 200 zcmZo*nYxz&0(!*qQcKG7i?S>9l5-M^i&KmB(u)!cGN$yfx`XM-Q#2c=)K1apVTFj- z0%hX!@>5blg5C^3E^|SC@f2?cZ-%xh#ommyQ!==`86g}dFo(gL5hTM5l>u=|dsyNV uQ?g)2Xn?eN167xT7(i1qL_x|+K|&y&7=#BD0; Date: Fri, 7 Feb 2025 08:39:42 +0100 Subject: [PATCH 03/23] Temporary rename to fix case sensitivity issue --- .../applications/optimization/ACL/ACL.py | 694 ------------------ .../optimization/ACL/mappings/ISING.py | 209 ------ .../optimization/ACL/mappings/QUBO.py | 275 ------- .../applications/optimization/MIS/MIS.py | 364 --------- .../optimization/MIS/mappings/QIRO.py | 96 --- .../applications/optimization/PVC/PVC.py | 393 ---------- .../optimization/PVC/mappings/ISING.py | 159 ---- .../optimization/PVC/mappings/QUBO.py | 201 ----- .../applications/optimization/SAT/SAT.py | 333 --------- .../optimization/SAT/mappings/ChoiISING.py | 156 ---- .../optimization/SAT/mappings/ChoiQUBO.py | 250 ------- .../optimization/SAT/mappings/DinneenISING.py | 148 ---- .../optimization/SAT/mappings/DinneenQUBO.py | 198 ----- .../optimization/SAT/mappings/Direct.py | 140 ---- .../optimization/SAT/mappings/QubovertQUBO.py | 195 ----- .../applications/optimization/SCP/SCP.py | 174 ----- .../optimization/SCP/mappings/qubovertQUBO.py | 145 ---- .../applications/optimization/TSP/TSP.py | 337 --------- .../optimization/TSP/mappings/ISING.py | 273 ------- .../optimization/TSP/mappings/QUBO.py | 125 ---- .../optimization/acl/Vehicle_data_QUARK.xlsx | 3 - .../applications/optimization/acl/__init__.py | 20 - .../optimization/acl/mappings/__init__.py | 20 - .../{ACL => acl_temp}/Vehicle_data_QUARK.xlsx | 0 .../{ACL => acl_temp}/__init__.py | 0 .../optimization/{acl => acl_temp}/acl.py | 0 .../{ACL => acl_temp}/mappings/__init__.py | 0 .../{acl => acl_temp}/mappings/ising.py | 0 .../{acl => acl_temp}/mappings/qubo.py | 0 .../applications/optimization/mis/__init__.py | 19 - .../optimization/mis/data/__init__.py | 19 - .../optimization/mis/data/graph_layouts.py | 100 --- .../optimization/mis/mappings/__init__.py | 19 - .../optimization/mis/mappings/neutral_atom.py | 91 --- .../{MIS => mis_temp}/__init__.py | 0 .../{MIS => mis_temp}/data/__init__.py | 0 .../{MIS => mis_temp}/data/graph_layouts.py | 0 .../{MIS => mis_temp}/mappings/__init__.py | 0 .../mappings/neutral_atom.py | 0 .../{mis => mis_temp}/mappings/qiro.py | 0 .../optimization/{mis => mis_temp}/mis.py | 0 .../applications/optimization/pvc/__init__.py | 19 - .../pvc/data/createReferenceGraph.py | 54 -- .../optimization/pvc/data/reference_data.txt | 3 - .../pvc/data/reference_graph.gpickle | 3 - .../optimization/pvc/mappings/__init__.py | 19 - .../{PVC => pvc_temp}/__init__.py | 0 .../data/createReferenceGraph.py | 0 .../{PVC => pvc_temp}/data/reference_data.txt | 0 .../data/reference_graph.gpickle | 0 .../{PVC => pvc_temp}/mappings/__init__.py | 0 .../{pvc => pvc_temp}/mappings/ising.py | 0 .../{pvc => pvc_temp}/mappings/qubo.py | 0 .../optimization/{pvc => pvc_temp}/pvc.py | 0 .../applications/optimization/sat/__init__.py | 19 - .../optimization/sat/mappings/__init__.py | 19 - .../{SAT => sat_temp}/__init__.py | 0 .../{SAT => sat_temp}/mappings/__init__.py | 0 .../{sat => sat_temp}/mappings/choiising.py | 0 .../{sat => sat_temp}/mappings/choiqubo.py | 0 .../mappings/dinneenising.py | 0 .../{sat => sat_temp}/mappings/dinneenqubo.py | 0 .../{sat => sat_temp}/mappings/direct.py | 0 .../mappings/qubovertqubo.py | 0 .../optimization/{sat => sat_temp}/sat.py | 0 .../applications/optimization/scp/__init__.py | 19 - .../optimization/scp/data/__init__.py | 19 - .../scp/data/set_cover_data_large.txt | 3 - .../optimization/scp/mappings/__init__.py | 19 - .../{SCP => scp_temp}/__init__.py | 0 .../{SCP => scp_temp}/data/__init__.py | 0 .../data/set_cover_data_large.txt | 0 .../{SCP => scp_temp}/mappings/__init__.py | 0 .../mappings/qubovertqubo.py | 0 .../optimization/{scp => scp_temp}/scp.py | 0 .../applications/optimization/tsp/__init__.py | 19 - .../tsp/data/createReferenceGraph.py | 48 -- .../optimization/tsp/data/dsj1000.tsp | 3 - .../tsp/data/reference_graph.gpickle | 3 - .../optimization/tsp/mappings/__init__.py | 19 - .../{TSP => tsp_temp}/__init__.py | 0 .../data/createReferenceGraph.py | 0 .../{TSP => tsp_temp}/data/dsj1000.tsp | 0 .../data/reference_graph.gpickle | 0 .../{TSP => tsp_temp}/mappings/__init__.py | 0 .../{tsp => tsp_temp}/mappings/ising.py | 0 .../{tsp => tsp_temp}/mappings/qubo.py | 0 .../optimization/{tsp => tsp_temp}/tsp.py | 0 88 files changed, 5444 deletions(-) delete mode 100644 src/modules/applications/optimization/ACL/ACL.py delete mode 100644 src/modules/applications/optimization/ACL/mappings/ISING.py delete mode 100644 src/modules/applications/optimization/ACL/mappings/QUBO.py delete mode 100644 src/modules/applications/optimization/MIS/MIS.py delete mode 100644 src/modules/applications/optimization/MIS/mappings/QIRO.py delete mode 100644 src/modules/applications/optimization/PVC/PVC.py delete mode 100644 src/modules/applications/optimization/PVC/mappings/ISING.py delete mode 100644 src/modules/applications/optimization/PVC/mappings/QUBO.py delete mode 100644 src/modules/applications/optimization/SAT/SAT.py delete mode 100644 src/modules/applications/optimization/SAT/mappings/ChoiISING.py delete mode 100644 src/modules/applications/optimization/SAT/mappings/ChoiQUBO.py delete mode 100644 src/modules/applications/optimization/SAT/mappings/DinneenISING.py delete mode 100644 src/modules/applications/optimization/SAT/mappings/DinneenQUBO.py delete mode 100644 src/modules/applications/optimization/SAT/mappings/Direct.py delete mode 100644 src/modules/applications/optimization/SAT/mappings/QubovertQUBO.py delete mode 100644 src/modules/applications/optimization/SCP/SCP.py delete mode 100644 src/modules/applications/optimization/SCP/mappings/qubovertQUBO.py delete mode 100644 src/modules/applications/optimization/TSP/TSP.py delete mode 100644 src/modules/applications/optimization/TSP/mappings/ISING.py delete mode 100644 src/modules/applications/optimization/TSP/mappings/QUBO.py delete mode 100644 src/modules/applications/optimization/acl/Vehicle_data_QUARK.xlsx delete mode 100644 src/modules/applications/optimization/acl/__init__.py delete mode 100644 src/modules/applications/optimization/acl/mappings/__init__.py rename src/modules/applications/optimization/{ACL => acl_temp}/Vehicle_data_QUARK.xlsx (100%) rename src/modules/applications/optimization/{ACL => acl_temp}/__init__.py (100%) rename src/modules/applications/optimization/{acl => acl_temp}/acl.py (100%) rename src/modules/applications/optimization/{ACL => acl_temp}/mappings/__init__.py (100%) rename src/modules/applications/optimization/{acl => acl_temp}/mappings/ising.py (100%) rename src/modules/applications/optimization/{acl => acl_temp}/mappings/qubo.py (100%) delete mode 100644 src/modules/applications/optimization/mis/__init__.py delete mode 100644 src/modules/applications/optimization/mis/data/__init__.py delete mode 100644 src/modules/applications/optimization/mis/data/graph_layouts.py delete mode 100644 src/modules/applications/optimization/mis/mappings/__init__.py delete mode 100644 src/modules/applications/optimization/mis/mappings/neutral_atom.py rename src/modules/applications/optimization/{MIS => mis_temp}/__init__.py (100%) rename src/modules/applications/optimization/{MIS => mis_temp}/data/__init__.py (100%) rename src/modules/applications/optimization/{MIS => mis_temp}/data/graph_layouts.py (100%) rename src/modules/applications/optimization/{MIS => mis_temp}/mappings/__init__.py (100%) rename src/modules/applications/optimization/{MIS => mis_temp}/mappings/neutral_atom.py (100%) rename src/modules/applications/optimization/{mis => mis_temp}/mappings/qiro.py (100%) rename src/modules/applications/optimization/{mis => mis_temp}/mis.py (100%) delete mode 100644 src/modules/applications/optimization/pvc/__init__.py delete mode 100644 src/modules/applications/optimization/pvc/data/createReferenceGraph.py delete mode 100644 src/modules/applications/optimization/pvc/data/reference_data.txt delete mode 100644 src/modules/applications/optimization/pvc/data/reference_graph.gpickle delete mode 100644 src/modules/applications/optimization/pvc/mappings/__init__.py rename src/modules/applications/optimization/{PVC => pvc_temp}/__init__.py (100%) rename src/modules/applications/optimization/{PVC => pvc_temp}/data/createReferenceGraph.py (100%) rename src/modules/applications/optimization/{PVC => pvc_temp}/data/reference_data.txt (100%) rename src/modules/applications/optimization/{PVC => pvc_temp}/data/reference_graph.gpickle (100%) rename src/modules/applications/optimization/{PVC => pvc_temp}/mappings/__init__.py (100%) rename src/modules/applications/optimization/{pvc => pvc_temp}/mappings/ising.py (100%) rename src/modules/applications/optimization/{pvc => pvc_temp}/mappings/qubo.py (100%) rename src/modules/applications/optimization/{pvc => pvc_temp}/pvc.py (100%) delete mode 100644 src/modules/applications/optimization/sat/__init__.py delete mode 100644 src/modules/applications/optimization/sat/mappings/__init__.py rename src/modules/applications/optimization/{SAT => sat_temp}/__init__.py (100%) rename src/modules/applications/optimization/{SAT => sat_temp}/mappings/__init__.py (100%) rename src/modules/applications/optimization/{sat => sat_temp}/mappings/choiising.py (100%) rename src/modules/applications/optimization/{sat => sat_temp}/mappings/choiqubo.py (100%) rename src/modules/applications/optimization/{sat => sat_temp}/mappings/dinneenising.py (100%) rename src/modules/applications/optimization/{sat => sat_temp}/mappings/dinneenqubo.py (100%) rename src/modules/applications/optimization/{sat => sat_temp}/mappings/direct.py (100%) rename src/modules/applications/optimization/{sat => sat_temp}/mappings/qubovertqubo.py (100%) rename src/modules/applications/optimization/{sat => sat_temp}/sat.py (100%) delete mode 100644 src/modules/applications/optimization/scp/__init__.py delete mode 100644 src/modules/applications/optimization/scp/data/__init__.py delete mode 100644 src/modules/applications/optimization/scp/data/set_cover_data_large.txt delete mode 100644 src/modules/applications/optimization/scp/mappings/__init__.py rename src/modules/applications/optimization/{SCP => scp_temp}/__init__.py (100%) rename src/modules/applications/optimization/{SCP => scp_temp}/data/__init__.py (100%) rename src/modules/applications/optimization/{SCP => scp_temp}/data/set_cover_data_large.txt (100%) rename src/modules/applications/optimization/{SCP => scp_temp}/mappings/__init__.py (100%) rename src/modules/applications/optimization/{scp => scp_temp}/mappings/qubovertqubo.py (100%) rename src/modules/applications/optimization/{scp => scp_temp}/scp.py (100%) delete mode 100644 src/modules/applications/optimization/tsp/__init__.py delete mode 100644 src/modules/applications/optimization/tsp/data/createReferenceGraph.py delete mode 100644 src/modules/applications/optimization/tsp/data/dsj1000.tsp delete mode 100644 src/modules/applications/optimization/tsp/data/reference_graph.gpickle delete mode 100644 src/modules/applications/optimization/tsp/mappings/__init__.py rename src/modules/applications/optimization/{TSP => tsp_temp}/__init__.py (100%) rename src/modules/applications/optimization/{TSP => tsp_temp}/data/createReferenceGraph.py (100%) rename src/modules/applications/optimization/{TSP => tsp_temp}/data/dsj1000.tsp (100%) rename src/modules/applications/optimization/{TSP => tsp_temp}/data/reference_graph.gpickle (100%) rename src/modules/applications/optimization/{TSP => tsp_temp}/mappings/__init__.py (100%) rename src/modules/applications/optimization/{tsp => tsp_temp}/mappings/ising.py (100%) rename src/modules/applications/optimization/{tsp => tsp_temp}/mappings/qubo.py (100%) rename src/modules/applications/optimization/{tsp => tsp_temp}/tsp.py (100%) diff --git a/src/modules/applications/optimization/ACL/ACL.py b/src/modules/applications/optimization/ACL/ACL.py deleted file mode 100644 index 1581e932..00000000 --- a/src/modules/applications/optimization/ACL/ACL.py +++ /dev/null @@ -1,694 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# copyright Pulp: -# Copyright (c) 2002-2005, Jean-Sebastien Roy -# Modifications Copyright (c) 2007- Stuart Anthony Mitchell -# -# Permission is hereby granted, free of charge, to any person obtaining a -# copy of this software and associated documentation files (the "Software"), -# to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, -# and/or sell copies of the Software, and to permit persons to whom the Software -# is furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included -# in all copies or substantial portions of the Software. - -import os -import logging -from typing import TypedDict - -import pandas as pd -import numpy as np -import pulp - -from modules.applications.application import Core -from modules.applications.optimization.optimization import Optimization -from utils import start_time_measurement, end_time_measurement - - -class ACL(Optimization): - """ - The distribution of passenger vehicles is a complex task and a high cost factor for automotive original - equipment manufacturers (OEMs). Vehicles travel long distance on different carriers, such as ships, - trains, and trucks, from the production plant to the customer. - - To save costs, OEMs and logistics service providers aim to maximize their loading capacities. - Modern auto carriers are flexible, allowing individual platforms to be rotated, extended, or combined - to accommodate vehicles of different shapes and weights in a space-efficient manner. - - In practice, finding feasible combinations is often based on heuristics or personal experience. - We formulate the problem as a mixed integer quadratically constrained assignment problem. - """ - - def __init__(self): - """ - Constructor method. - """ - super().__init__("ACL") - self.submodule_options = ["MIPsolverACL", "QUBO"] - self.application = None - - @staticmethod - def get_requirements() -> list[dict]: - """ - Returns the list of module requirements. - - :return: List of dictionaries containing module requirements - """ - return [ - {"name": "pulp", "version": "2.9.0"}, - {"name": "pandas", "version": "2.2.3"}, - {"name": "numpy", "version": "1.26.4"}, - {"name": "openpyxl", "version": "3.1.5"}, - ] - - def get_default_submodule(self, option: str) -> Core: - """ - Returns the default submodule based on the provided option. - - :param option: Option specifying the submodule - :return: Instance of the corresponding submodule - :raises NotImplementedError: If the option is not recognized - """ - if option == "MIPsolverACL": - from modules.solvers.mip_solver_acl import MIPaclp # pylint: disable=C0415 - return MIPaclp() - elif option == "QUBO": - from modules.applications.optimization.acl.mappings.qubo import Qubo # pylint: disable=C0415 - return Qubo() - else: - raise NotImplementedError(f"Submodule Option {option} not implemented") - - def get_parameter_options(self) -> dict: - """ - Returns parameter options for selecting different models. - - :return: Dictionary containing model selection options - """ - return { - "model_select": { - "values": ["Full", "Small", "Tiny"], - "description": "Do you want the full model or a simplified (QUBO friendly) one?" - } - } - - class Config(TypedDict): - model_select: str - - @staticmethod - def intersectset(p1: list, p2: list) -> list: - """ - Computes the intersection of two lists. - - :param p1: First list - :param p2: Second list - :return: List containing elements common to both p1 and p2 - """ - return np.intersect1d(p1, p2).tolist() - - @staticmethod - def diffset(p1: list, p2: list) -> list: - """ - Computes the difference between two lists. - - :param p1: First list - :param p2: Second list - :return: List containing elements in p1 that are not in p2 - """ - return np.setdiff1d(p1, p2).tolist() - - def generate_problem(self, config: Config) -> dict: # pylint: disable=R0915 - """ - This function includes three models: Full, small and tiny. Full refers to the original model with all of its - constraints. Small refers to the simplified model suitable for solving it with QC methods. - The simplified model does not consider the possibility that vehicles can be angled or that they can be - oriented forwards or backwards in relation to the auto carrier. For the tiny version we do not consider split - platforms and consider only weight constraints. - - :param config: Config containing the selected scenario - :return: Dictionary with scenario-dependent model formulated as linear problem - """ - # Enter vehicles to load (BMW model codes) - vehicles = ["G20", "G20", "G20", "G20", "G07", "G20"] - # Import vehicle data - df = pd.read_excel(os.path.join(os.path.dirname(__file__), "Vehicle_data_QUARK.xlsx")) - model_select = config['model_select'] - - # All the parameters are in decimeters and decitons (4m == 400 cm == 40 dm , 2 tons -> 20 dt - # Below are the model specific parameters, constraints and objectives for the tiny, small and the full model - - if model_select == "Tiny": - self._generate_tiny_model(df, vehicles) - elif model_select == "Small": - self._generate_small_model(df, vehicles) - else: - self._generate_full_model(df, vehicles) - - problem_instance = self.application.to_dict() - self.application = problem_instance - return self.application - - def _generate_tiny_model(self, df: any, vehicles: list) -> None: - """ - Generate the problem model for the Tiny configurations. - - :param df: Datafile - :param vehicles: List of vehicle types - """ - # Weight parameters - # Max. total weight on truck / trailer - wt = [100] - # Max. weight on the four levels - wl = [50, 60] - # Max. weights on platforms p, if not angled - wp = [23, 23, 23, 26, 17] - - # Create empty lists for different vehicle parameters. This is required for proper indexing in the model. - weight_list = [0] * (len(vehicles)) - - for i, vehicle in enumerate(vehicles): - df_new = df.loc[df['Type'] == vehicle] - weight_list[i] = int(df_new["Weight"].iloc[0]) - - # Set of available cars - vecs = set(range(len(vehicles))) - # Set of available platforms - platforms_array = np.array([0, 1, 2, 3, 4]) - plats = set(range(len(platforms_array))) - - # Set of platforms that have a limitation on allowed weight - platforms_level_array = np.array([[0, 1, 2], [3, 4]], dtype=object) - plats_l = set(range(len(platforms_level_array))) - - # Set of platforms that form trailer and truck - platforms_truck_trailer_array = np.array([[0, 1, 2, 3, 4]], dtype=object) - plats_t = set(range(len(platforms_truck_trailer_array))) - - # Create decision variables - x = pulp.LpVariable.dicts('x', ((p, v) for p in plats for v in vecs), cat='Binary') - - # Create the problem instance - prob = pulp.LpProblem("ACL", pulp.LpMaximize) - - # Objective function - # Maximize number of vehicles on the truck - prob += pulp.lpSum(x[p, v] for p in plats for v in vecs) - - # Constraints - # (1) Every vehicle can only be assigned to a single platform - for p in plats: - prob += pulp.lpSum(x[p, v] for v in vecs) <= 1 - - # (2) Every platform can only hold a single vehicle - for v in vecs: - prob += pulp.lpSum(x[p, v] for p in plats) <= 1 - - # (3) Weight limit for every platform - for p in plats: - for v in vecs: - prob += weight_list[v] * x[p, v] <= wp[p] - - # (4) Weight constraint for every level - for p_l in plats_l: - prob += pulp.lpSum(weight_list[v] * x[p, v] for p in platforms_level_array[p_l] for v in vecs) <= \ - wl[p_l] - - # (5) Weight constraint for truck and trailer - for t in plats_t: - prob += pulp.lpSum( - weight_list[v] * x[p, v] for p in platforms_truck_trailer_array[t] for v in vecs) <= wt[t] - - self.application = prob - - def _generate_small_model(self, df: any, vehicles: list) -> None: - """ - Generate the problem model for the Small configuration - - :param df: Datafile - :param vehicles: List of vehicle types - """ - # Parameters for the small model (2 levels with 3 and 2 platforms each) - - # Length parameters - # Level 1 (Truck up), 2 (Truck down) - lmax_l = [97, 79] - # Height parameters - # Considers base truck height and height distance between vehicles (~10cm) - hmax_truck = [34, 34, 33, 36, 32, 36] - # Weight parameters - # Max. total weight on truck / trailer - wt = [100] - # Max. weight on the two levels - wl = [65, 50] - # Max. weights on platforms p, if not angled - wp = [23, 23, 23, 26, 17] - # Max. weight on p, if sp is used - wsp = [28, 28, 28] - - _, length_list, height_list, weight_list = self._get_vehicle_params(df, vehicles) - - # Set of available cars - vecs = set(range(len(vehicles))) - # Set of available platforms - platforms_array = np.array([0, 1, 2, 3, 4]) - plats = set(range(len(platforms_array))) - - # Set of possible split platforms - split_platforms_array = np.array([[0, 1], [1, 2], [3, 4]], dtype=object) - plats_sp = set(range(len(split_platforms_array))) - - # Set of platforms that have a limitation on allowed length and weight because they are on the same level - platforms_level_array = np.array([[0, 1, 2], [3, 4]], dtype=object) - plats_l = set(range(len(platforms_level_array))) - - # Set of platforms that form trailer and truck - platforms_truck_trailer_array = np.array([[0, 1, 2, 3, 4]], dtype=object) - plats_t = set(range(len(platforms_truck_trailer_array))) - - # Set of platforms that have a limitation on allowed height - platforms_height_array_truck = np.array([[0, 3], [1, 3], [2, 3], [0, 4], [1, 4], [2, 4]], dtype=object) - plats_h1 = set(range(len(platforms_height_array_truck))) - - # Create decision variables - x = pulp.LpVariable.dicts('x', ((p, v) for p in plats for v in vecs), cat='Binary') - # Usage of split platform - sp = pulp.LpVariable.dicts('sp', (q for q in plats_sp), cat='Binary') - # Auxiliary variable for linearization of quadratic constraints - gamma = pulp.LpVariable.dicts('gamma', (p for p in plats_sp), cat='Binary') - - # Create the 'prob' variable to contain the problem data - prob = pulp.LpProblem("ACL", pulp.LpMaximize) - # Maximize number of vehicles on the truck - prob += pulp.lpSum(x[p, v] for p in plats for v in vecs) - - # Constraints - # (1) Every vehicle can only be assigned to a single platform - for p in plats: - prob += pulp.lpSum(x[p, v] for v in vecs) <= 1 - - # (2) Every platform can only hold a single vehicle - for v in vecs: - prob += pulp.lpSum(x[p, v] for p in plats) <= 1 - - # (3) If a split platform q in plats_sp is used, only one of its "sub platforms" can be used - for q in plats_sp: - prob += pulp.lpSum(x[p, v] for p in split_platforms_array[q] for v in vecs) \ - <= len(split_platforms_array[q]) * (1 - sp[q]) + sp[q] - - # (4) It is always only possible to use a single split-platform for any given p - for q in plats_sp: - for p in plats_sp: - if p != q: - z = bool(set(split_platforms_array[q]) & set(split_platforms_array[p])) - if z is True: - prob += sp[q] + sp[p] <= 1 - - # (5) Length constraint - # Checks that vehicles v on platforms p that belong to level L are shorter than the maximum available length - for L in plats_l: - prob += (pulp.lpSum(x[p, v] * length_list[v] for p in platforms_level_array[L] for v in vecs) - <= lmax_l[L]) - - # (6) Height constraints for truck and trailer, analogue to length constraints - # Truck - for h in plats_h1: - prob += pulp.lpSum(x[p, v] * height_list[v] for p in platforms_height_array_truck[h] for v in vecs) \ - <= hmax_truck[h] - - # (7) Linearization constraint -> gamma == 1, if split platform is used - for q in plats_sp: - prob += pulp.lpSum( - sp[q] + x[p, v] for p in self.intersectset(split_platforms_array[q], platforms_array) - for v in vecs) >= 2 * gamma[q] - - # (8) Weight limit for every platform - for p in plats: - for v in vecs: - prob += weight_list[v] * x[p, v] <= wp[p] - - # (9) If a split platform is used, weight limit == wsp, if not, then weight limit == wp - for q in plats_sp: - for p in split_platforms_array[q]: - prob += pulp.lpSum(weight_list[v] * x[p, v] for v in vecs) <= gamma[q] * wsp[q] \ - + (1 - gamma[q]) * wp[p] - - # (10) Weight constraint for every level - for p_l in plats_l: - prob += pulp.lpSum(weight_list[v] * x[p, v] for p in platforms_level_array[p_l] for v in vecs) <= \ - wl[p_l] - - # (11) Weight constraint for truck and trailer - for p_t in plats_t: - prob += pulp.lpSum( - weight_list[v] * x[p, v] for p in platforms_truck_trailer_array[p_t] for v in vecs) <= wt[p_t] - - self.application = prob - - def _generate_full_model(self, df: any, vehicles: list) -> None: # pylint: disable=R0915 - """ - Generate the problem model for the Full configuration. - - :param df: Datafile - :param vehicles: List of vehicle types - """ - # Horizontal Coefficients: Length reduction - # 1 = forward, 0 = backward - v_coef = np.array([[0.20, 0.15, 0.14, 0.19], - [0.22, 0.22, 0.22, 0.22], - [0.22, 0.13, 0.12, 0.17]]) - - # Vertical Coefficients: Height increase - h_coef = np.array([[0.40, 1, 1, 1], - [0.17, 0.22, 0.21, 0.22], - [0.17, 0.38, 0.32, 0.32]]) - - # Length parameters - # Level 1 (Truck up), 2 (Truck down), 3 (Trailer up), 4 (Trailer down) - lmax_l = [97, 79, 97, 97] - - # Height parameters - # Considers base truck height and height distance between vehicles (~10cm) - hmax_truck = [34, 34, 33, 36, 32, 36] - hmax_trailer = [36, 32, 32, 34] - - # Weight parameters - # Max. total weight - wmax = 180 - # Max. total weight on truck / trailer - wt = [100, 100] - # Max. weight on the four levels - wl = [50, 60, 50, 90] - # Max. weights on platforms p, if not angled - wp = [23, 23, 23, 26, 17, 26, 26, 26, 23, 26] - # Max. weights on p, angled (if possible: 1, 2, 4, 7, 8, 9): - wpa = [20, 22, 17, 18, 19, 22] - # Max. weight on p, if sp is used - wsp = [28, 28, 28, 28, 28, 28] - - class_list, length_list, height_list, weight_list = self._get_vehicle_params(df, vehicles) - - # Set of available cars - vecs = set(range(len(vehicles))) - # Set of available platforms - platforms_array = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) - plats = set(range(len(platforms_array))) - - # Set of platforms that can be angled - platforms_angled_array = [1, 2, 4, 7, 8] - vp = [0, 1, 3, 8, 9] - plats_a = set(range(len(platforms_angled_array))) - - # Set of possible split platforms - split_platforms_array = np.array([[0, 1], [1, 2], [3, 4], [5, 6], [7, 8], [8, 9]], dtype=object) - plats_sp = set(range(len(split_platforms_array))) - - # Set of platforms that have a limitation on allowed length and weight because they are on the same level - platforms_level_array = np.array([[0, 1, 2], [3, 4], [5, 6], [7, 8, 9]], dtype=object) - plats_l = set(range(len(platforms_level_array))) - - # Set of platforms that form trailer and truck - platforms_truck_trailer_array = np.array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]], dtype=object) - plats_t = set(range(len(platforms_truck_trailer_array))) - - # Set of platforms that have a limitation on allowed height - platforms_height_array_truck = np.array([[0, 3], [1, 3], [2, 3], [0, 4], [1, 4], [2, 4]], dtype=object) - platforms_height_array_trailer = np.array([[5, 7], [5, 8], [6, 8], [6, 9]], dtype=object) - - plats_h1 = set(range(len(platforms_height_array_truck))) - plats_h2 = set(range(len(platforms_height_array_trailer))) - - # Create decision variables - # Vehicle v assigned to p - x = pulp.LpVariable.dicts('x', ((p, v) for p in plats for v in vecs), cat='Binary') - # Usage of split platform - sp = pulp.LpVariable.dicts('sp', (q for q in plats_sp), cat='Binary') - # Direction of vehicle on p - d = pulp.LpVariable.dicts('d', (p for p in plats), cat='Binary') - # State of platform p in PA - angled == 1, not angled == 0 - a_p = pulp.LpVariable.dicts('a_p', (p for p in plats_a), cat='Binary') - - # Auxiliary variables for linearization of quadratic constraints - y1 = pulp.LpVariable.dicts('y1', (p for p in plats_a), cat='Binary') - y2 = pulp.LpVariable.dicts('y2', (p for p in plats_a), cat='Binary') - y3 = pulp.LpVariable.dicts('y3', (p for p in plats_a), cat='Binary') - y4 = pulp.LpVariable.dicts('y4', (p for p in plats_a), cat='Binary') - ay1 = pulp.LpVariable.dicts('ay1', (p for p in plats_a), cat='Binary') - ay2 = pulp.LpVariable.dicts('ay2', (p for p in plats_a), cat='Binary') - ay3 = pulp.LpVariable.dicts('ay3', (p for p in plats_a), cat='Binary') - ay4 = pulp.LpVariable.dicts('ay4', (p for p in plats_a), cat='Binary') - # Weight for split-platforms - gamma = pulp.LpVariable.dicts('gamma', (p for p in plats_sp), cat='Binary') - - # Create the 'prob' variable to contain the problem data - prob = pulp.LpProblem("ACL", pulp.LpMaximize) - # Maximize number of vehicles on the truck - prob += pulp.lpSum(x[p, v] for p in plats for v in vecs) - - # Assignment constraints - # (1) Every vehicle can only be assigned to a single platform - for p in plats: - prob += pulp.lpSum(x[p, v] for v in vecs) <= 1 - # (2) Every platform can only hold a single vehicle - for v in vecs: - prob += pulp.lpSum(x[p, v] for p in plats) <= 1 - - # (3) If a split platform q in plats_sp is used, only one of its "sub platforms" can be used - for q in plats_sp: - prob += pulp.lpSum(x[p, v] for p in split_platforms_array[q] for v in vecs) \ - <= len(split_platforms_array[q]) * (1 - sp[q]) + sp[q] - - # (3.1) It is always only possible to use a single split-platform for any given p - for q in plats_sp: - for p in plats_sp: - if p != q: - z = bool(set(split_platforms_array[q]) & set(split_platforms_array[p])) - if z is True: - prob += sp[q] + sp[p] <= 1 - - # (3.2) It is not allowed to angle platforms next to empty platforms - for i, p in enumerate(platforms_angled_array): - prob += pulp.lpSum(x[p, v] + x[vp[i], v] for v in vecs) >= 2 * a_p[i] - - # Linearization constraints - # Linearization of d_p and d_v(p) -> orientations of two neighboring cars - for p in platforms_angled_array: - z = platforms_angled_array.index(p) - v_p = vp[z] - prob += (1 - d[p]) + d[v_p] >= 2 * y1[z] - prob += d[p] + d[v_p] >= 2 * y2[z] - prob += (1 - d[p]) + (1 - d[v_p]) >= 2 * y3[z] - prob += d[p] + (1 - d[v_p]) >= 2 * y4[z] - - # Linearization of a_p with y1 - y4 -> linear combination of angle and orientations - for p in platforms_angled_array: - z = platforms_angled_array.index(p) - prob += a_p[z] + y1[z] >= 2 * ay1[z] - prob += a_p[z] + y2[z] >= 2 * ay2[z] - prob += a_p[z] + y3[z] >= 2 * ay3[z] - prob += a_p[z] + y4[z] >= 2 * ay4[z] - - # Linearization of x * ay -> linear combination of assignment and orientation/angle - xay1 = pulp.LpVariable.dicts('xay1', ((p, v) for p in plats_a for v in vecs), cat='Binary') - xay2 = pulp.LpVariable.dicts('xay2', ((p, v) for p in plats_a for v in vecs), cat='Binary') - xay3 = pulp.LpVariable.dicts('xay3', ((p, v) for p in plats_a for v in vecs), cat='Binary') - xay4 = pulp.LpVariable.dicts('xay4', ((p, v) for p in plats_a for v in vecs), cat='Binary') - for p in platforms_angled_array: - z = platforms_angled_array.index(p) - for v in vecs: - prob += ay1[z] + x[z, v] >= 2 * xay1[z, v] - prob += ay2[z] + x[z, v] >= 2 * xay2[z, v] - prob += ay3[z] + x[z, v] >= 2 * xay3[z, v] - prob += ay4[z] + x[z, v] >= 2 * xay4[z, v] - - # Making sure always only 1 case applies - for p in platforms_angled_array: - z = platforms_angled_array.index(p) - prob += ay1[z] + ay2[z] + ay3[z] + ay4[z] <= 1 - prob += y1[z] + y2[z] + y3[z] + y4[z] <= 1 - - # (4) Length constraint - # Checks that vehicles v on platforms p that belong to level L are shorter than the maximum available length - # The length of the vehicles depends on whether they are angled or not and which vehicle is standing on - # platform v(p) - for L in plats_l: - prob += pulp.lpSum(x[p, v] * length_list[v] - - xay1[platforms_angled_array.index(p), v] * - int(v_coef[class_list[v]][0] * length_list[v]) - - xay2[platforms_angled_array.index(p), v] * - int(v_coef[class_list[v]][1] * length_list[v]) - - xay3[platforms_angled_array.index(p), v] * - int(v_coef[class_list[v]][2] * length_list[v]) - - xay4[platforms_angled_array.index(p), v] - * int(v_coef[class_list[v]][3] * length_list[v]) - for p in self.intersectset(platforms_angled_array, platforms_level_array[L]) - for v in vecs) \ - + pulp.lpSum(x[p, v] * length_list[v] - for p in self.diffset(platforms_level_array[L], platforms_angled_array) - for v in vecs) \ - <= lmax_l[L] - - # (5) Platforms can not be angled, if they are part of a split platform - for q in plats_sp: - prob += pulp.lpSum(a_p[platforms_angled_array.index(p)] - for p in self.intersectset(platforms_angled_array, split_platforms_array[q])) \ - <= len(split_platforms_array[q]) * (1 - sp[q]) - - # (6) Weight constraint if split platform is used, gamma == 1 - for q in plats_sp: - prob += pulp.lpSum(sp[q] + x[p, v] for p in self.intersectset(split_platforms_array[q], platforms_array) - for v in vecs) >= 2 * gamma[q] - - # If split platform is used, weight limit == wsp, if not, then weight limit == wp - for q in plats_sp: - for p in split_platforms_array[q]: - prob += (pulp.lpSum(weight_list[v] * x[p, v] for v in vecs) <= gamma[q] * wsp[q] + (1 - gamma[q]) * - wp[p]) - - # (7) If a platform that can be angled is angled, weight limit == wpa - # Need another linearization for that: - apx = pulp.LpVariable.dicts('apx', ((p, v) for p in plats_a for v in vecs), cat='Binary') - for p in platforms_angled_array: - z = platforms_angled_array.index(p) - for v in vecs: - prob += a_p[z] + x[z, v] >= 2 * apx[z, v] - - for p in platforms_angled_array: - prob += pulp.lpSum(weight_list[v] * apx[platforms_angled_array.index(p), v] for v in vecs) \ - <= wpa[platforms_angled_array.index(p)] - - # (8) Weight constraint for every level - for p_l in plats_l: - prob += (pulp.lpSum(weight_list[v] * x[p, v] for p in platforms_level_array[p_l] for v in vecs) <= - wl[p_l]) - - # (9) Weight constraint for truck and trailer - for p_t in plats_t: - prob += (pulp.lpSum(weight_list[v] * x[p, v] for p in platforms_truck_trailer_array[p_t] for v in vecs) - <= wt[p_t]) - - # (10) Weight constraint for entire auto carrier - prob += pulp.lpSum(weight_list[v] * x[p, v] for p in plats for v in vecs) <= wmax - - # (11) Height constraints for truck and trailer, analogue to length constraints - # Truck - for h in plats_h1: - prob += pulp.lpSum(x[p, v] * height_list[v] - - xay1[platforms_angled_array.index(p), v] * - int(h_coef[class_list[v]][0] * height_list[v]) - - xay2[platforms_angled_array.index(p), v] * - int(h_coef[class_list[v]][1] * height_list[v]) - - xay3[platforms_angled_array.index(p), v] * - int(h_coef[class_list[v]][2] * height_list[v]) - - xay4[platforms_angled_array.index(p), v] * - int(h_coef[class_list[v]][3] * height_list[v]) - for p in self.intersectset(platforms_angled_array, platforms_height_array_truck[h]) - for v in vecs) \ - + pulp.lpSum(x[p, v] * height_list[v] - for p in self.diffset(platforms_height_array_truck[h], platforms_angled_array) - for v in vecs) \ - <= hmax_truck[h] - # Trailer - for h in plats_h2: - prob += pulp.lpSum(x[p, v] * height_list[v] - - xay1[platforms_angled_array.index(p), v] * - int(h_coef[class_list[v]][0] * height_list[v]) - - xay2[platforms_angled_array.index(p), v] * - int(h_coef[class_list[v]][1] * height_list[v]) - - xay3[platforms_angled_array.index(p), v] * - int(h_coef[class_list[v]][2] * height_list[v]) - - xay4[platforms_angled_array.index(p), v] * - int(h_coef[class_list[v]][3] * height_list[v]) - for p in self.intersectset(platforms_angled_array, platforms_height_array_trailer[h]) - for v in vecs) \ - + pulp.lpSum(x[p, v] * height_list[v] - for p in self.diffset(platforms_height_array_trailer[h], platforms_angled_array) - for v in vecs) \ - <= hmax_trailer[h] - - self.application = prob - - def _get_vehicle_params(self, df: any, vehicles: list) -> tuple[list, list, list, list]: - """ - Extract vehicle parameters for the problem formulation - - :param df: Dataframe containing vehicle data - :param vehicles: List of vehicle types to consider - :return: Lists containing class, length, height, and weight of vehicles - """ - class_list = [0] * (len(vehicles)) - length_list = [0] * (len(vehicles)) - height_list = [0] * (len(vehicles)) - weight_list = [0] * (len(vehicles)) - - for i in set(range(len(vehicles))): - df_new = df.loc[df['Type'] == vehicles[i]] - class_list[i] = int(df_new["Class"].iloc[0]) - length_list[i] = int(df_new["Length"].iloc[0]) - height_list[i] = int(df_new["Height"].iloc[0]) - weight_list[i] = int(df_new["Weight"].iloc[0]) - - return class_list, length_list, height_list, weight_list - - def validate(self, solution: any) -> tuple[bool, float]: - """ - Checks if the solution is a valid solution. -: - :param solution: Proposed solution - :return: Tuple containing a boolean indicating if the solution is valid - and the time it took to validate the solution - """ - start = start_time_measurement() - status = solution.get("status") - is_valid = status == "Optimal" - return is_valid, end_time_measurement(start) - - def get_solution_quality_unit(self) -> str: - """ - Provides the unit of measure for solution quality. - - :return: The unit of measure fro solution quality - """ - return "Number of loaded vehicles" - - def evaluate(self, solution: any) -> tuple[float, float]: - """ - Checks how good the solution is. - - :param solution: Provided solution - :return: Tuple containing the objective value and the time it took to evaluate the solution - """ - start = start_time_measurement() - objective_value = solution.get("obj_value", 0) - logging.info("Loading successful!") - logging.info(f"{objective_value} cars will fit on the auto carrier.") - - variables = solution.get("variables", {}) - assignments = [key for key in variables if variables[key] > 0] - - logging.info(f"vehicle-to-platform assignments (platform, vehicle): {assignments}") - return objective_value, end_time_measurement(start) - - def save(self, path: str, iter_count: int) -> None: - """ - Saves the problem instance to a JSON file. - - :param path: Directory path where the instance should be saved - :param iter_count: Iteration count (unused) - """ - # Convert our problem instance from Dict to an LP problem and then to json - _, problem_instance = pulp.LpProblem.from_dict(self.application) - # Save problem instance to JSON - problem_instance.to_json(f"{path}/ACL_instance.json") diff --git a/src/modules/applications/optimization/ACL/mappings/ISING.py b/src/modules/applications/optimization/ACL/mappings/ISING.py deleted file mode 100644 index 9995707e..00000000 --- a/src/modules/applications/optimization/ACL/mappings/ISING.py +++ /dev/null @@ -1,209 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import logging -from typing import TypedDict - -import numpy as np -from more_itertools import locate -from qiskit_optimization import QuadraticProgram -from qiskit_optimization.converters import QuadraticProgramToQubo - -from modules.applications.mapping import Mapping -from modules.core import Core -from utils import start_time_measurement, end_time_measurement - - -class Ising(Mapping): - """ - Ising formulation of the auto-carrier loading (ACL) problem. - """ - - def __init__(self): - """ - Constructor method. - """ - super().__init__() - self.submodule_options = ["QAOA", "QiskitQAOA"] - self.global_variables = [] - logging.warning("Currently, all scenarios are too large to be solved with an Ising model.") - logging.warning("Consider using another mapping until the modelling is refined.") - - @staticmethod - def get_requirements() -> list[dict]: - """ - Return requirements of this module. - - :return: list of dict with requirements of this module - """ - return [ - {"name": "numpy", "version": "1.26.4"}, - {"name": "more-itertools", "version": "10.5.0"}, - {"name": "qiskit-optimization", "version": "0.6.1"}, - ] - - def get_parameter_options(self) -> dict: - """ - Returns empty dict as this mapping has no configurable settings. - - :return: Empty dict - """ - return {} - - class Config(TypedDict): - """ - Empty config as this solver has no configurable settings. - """ - pass - - def map_pulp_to_qiskit(self, problem: dict) -> QuadraticProgram: - """ - Maps the problem dict to a quadratic program. - - :param problem: Problem formulation in dict form - :return: Quadratic program in qiskit-optimization format - """ - # Details at: - # https://coin-or.github.io/pulp/guides/how_to_export_models.html - # https://qiskit.org/documentation/stable/0.26/tutorials/optimization/2_converters_for_quadratic_programs.html - - qp = QuadraticProgram() - - # Variables - for variable_dict in problem["variables"]: - if variable_dict["cat"] == "Integer": - lb = variable_dict["lowBound"] - ub = variable_dict["upBound"] - name = variable_dict["name"] - # If the integer variable is actually a binary variable - if lb == 0 and ub == 1: - qp.binary_var(name) - # If the integer variable is non-binary - else: - qp.integer_var(lowerbound=lb, upperbound=ub, name=name) - - # Objective function - obj_arguments = {arg["name"]: arg["value"] for arg in problem["objective"]["coefficients"]} - # Maximize - if problem["parameters"]["sense"] == -1: - qp.maximize(linear=obj_arguments) - # Minimize - else: - qp.minimize(linear=obj_arguments) - - # Constraints - for constraint in problem["constraints"]: - const_arguments = {arg["name"]: arg["value"] for arg in constraint["coefficients"]} - sense = constraint["sense"] - const_sense = "LE" if sense == -1 else "GE" if sense == 1 else "E" - qp.linear_constraint( - linear=const_arguments, - sense=const_sense, - rhs=-1 * constraint["constant"], - name=constraint["name"] - ) - - return qp - - def map(self, problem: dict, config: Config) -> tuple[dict, float]: - """ - Use Ising mapping of qiskit-optimize. - - :param problem: Dict containing the problem parameters - :param config: Config with the parameters specified in Config class - :return: Dict with the Ising, time it took to map it - """ - start = start_time_measurement() - - # Map Linear problem from dictionary (generated by pulp) to quadratic program - qp = self.map_pulp_to_qiskit(problem) - logging.info(qp.export_as_lp_string()) - - # convert quadratic problem to qubo to ising - conv = QuadraticProgramToQubo() - qubo = conv.convert(qp) - - variables = [variable.name for variable in qubo.variables] - qubit_op, _ = qubo.to_ising() - - self.global_variables = variables - - # reverse generate J and t out of qubit PauliSumOperator from qiskit - t_matrix = np.zeros(qubit_op.num_qubits, dtype=complex) - j_matrix = np.zeros((qubit_op.num_qubits, qubit_op.num_qubits), dtype=complex) - - for pauli_op in qubit_op: - pauli_str, coeff = pauli_op.to_list()[0] - index_pos_list = list(locate(pauli_str, lambda a: a == 'Z')) - - if len(index_pos_list) == 1: - t_matrix[index_pos_list[0]] = coeff - elif len(index_pos_list) == 2: - j_matrix[index_pos_list[0]][index_pos_list[1]] = coeff - - return {"J": j_matrix, "t": t_matrix}, end_time_measurement(start) - - def reverse_map(self, solution: dict) -> tuple[dict, float]: - """ - Maps the solution back to the representation needed by the ACL class for validation/evaluation. - - :param solution: Dict with a bit_string containing the solution - :return: Solution mapped accordingly, time it took to map it - """ - start = start_time_measurement() - - if np.any(solution == "-1"): - solution = self._convert_ising_to_qubo(solution) - - result = {"status": [0]} - variables = {} - objective_value = 0 - - for bit in solution: - if solution[bit] > 0: - if "x" in self.global_variables[bit]: - variables[self.global_variables[bit]] = solution[bit] - result["status"] = 'Optimal' - objective_value += solution[bit] - - result["variables"] = variables - result["obj_value"] = objective_value - - return result, end_time_measurement(start) - - @staticmethod - def _convert_ising_to_qubo(solution: any) -> any: - solution = np.array(solution) - with np.nditer(solution, op_flags=['readwrite']) as it: - for x in it: - if x == -1: - x[...] = 0 - return solution - - def get_default_submodule(self, option: str) -> Core: - """ - Returns the default submodule based on the provided option. - - :param option: Option specifying the submodule - :return: Instance of the corresponding submodule - :raises NotImplementedError: If the option is not recognized - """ - if option == "QAOA": - from modules.solvers.qaoa import QAOA # pylint: disable=C0415 - return QAOA() - elif option == "QiskitQAOA": - from modules.solvers.qiskit_qaoa import QiskitQAOA # pylint: disable=C0415 - return QiskitQAOA() - else: - raise NotImplementedError(f"Solver Option {option} not implemented") diff --git a/src/modules/applications/optimization/ACL/mappings/QUBO.py b/src/modules/applications/optimization/ACL/mappings/QUBO.py deleted file mode 100644 index 632c1f4b..00000000 --- a/src/modules/applications/optimization/ACL/mappings/QUBO.py +++ /dev/null @@ -1,275 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from typing import TypedDict -import re -import logging - -import numpy as np -from qiskit_optimization import QuadraticProgram -from qiskit_optimization.converters import ( - InequalityToEquality, IntegerToBinary, - LinearEqualityToPenalty -) - -from modules.applications.mapping import Mapping, Core -from utils import start_time_measurement, end_time_measurement - -# TODO Large chunks of this code is duplicated in ACL.mappings.ISING -> unify - - -class Qubo(Mapping): - """ - QUBO formulation for the ACL. - """ - - def __init__(self): - """ - Constructor method. - """ - super().__init__() - self.submodule_options = ["Annealer"] - self.global_variables = [] - - @staticmethod - def get_requirements() -> list[dict]: - """ - Return requirements of this module. - - :return: list of dict with requirements of this module - """ - return [ - {"name": "numpy", "version": "1.26.4"}, - {"name": "qiskit-optimization", "version": "0.6.1"}, - ] - - def get_parameter_options(self) -> dict: - """ - Returns empty dict as this mapping has no configurable settings. - - :return: Empty dictionary - """ - return {} - - class Config(TypedDict): - """ - Empty config as this solver has no configurable settings. - """ - pass - - def map_pulp_to_qiskit(self, problem: dict) -> QuadraticProgram: - """ - Maps the problem dict to a quadratic program. - - :param problem: Problem formulation in dict form - :return: Quadratic program in qiskit-optimization format - """ - # Details at: - # https://coin-or.github.io/pulp/guides/how_to_export_models.html - # https://qiskit.org/documentation/stable/0.26/tutorials/optimization/2_converters_for_quadratic_programs.html - - qp = QuadraticProgram() - - # Variables - for variable_dict in problem["variables"]: - if variable_dict["cat"] == "Integer": - lb = variable_dict["lowBound"] - ub = variable_dict["upBound"] - name = variable_dict["name"] - # If the integer variable is actually a binary variable - if lb == 0 and ub == 1: - qp.binary_var(name) - # If the integer variable is non-binary - else: - qp.integer_var(lowerbound=lb, upperbound=ub, name=name) - - # Objective function - obj_arguments = {arg["name"]: arg["value"] for arg in problem["objective"]["coefficients"]} - - # Maximize - if problem["parameters"]["sense"] == -1: - qp.maximize(linear=obj_arguments) - # Minimize - else: - qp.minimize(linear=obj_arguments) - - # Constraints - for constraint in problem["constraints"]: - const_arguments = {arg["name"]: arg["value"] for arg in constraint["coefficients"]} - sense = constraint["sense"] - const_sense = "LE" if sense == -1 else "GE" if sense == 1 else "E" - - qp.linear_constraint( - linear=const_arguments, - sense=const_sense, - rhs=-1 * constraint["constant"], - name=constraint["name"] - ) - - return qp - - def convert_string_to_arguments(self, input_string: str) -> list[any]: - """ - Converts QUBO in string format to a list of separated arguments, - used to construct the QUBO matrix. - - :param input_string: QUBO in raw string format - :return: List of arguments - """ - terms = re.findall(r'[+\-]?[^+\-]+', input_string) - # Convert the penalty string to a list of lists of the individual arguments in the penalty term - result = [term.strip() for term in terms] - separated_arguments = [] - first_item = True - - for argument in result: - if first_item: - # Remove "maximize" or minimize string from the first argument - argument = argument[8:] - first_item = False - if "*" in argument: - # The variables in each argument are connected by "*" signs. Here we split the variables - elements = argument.split('*') - # Convert string of numbers to floats - new_argument = elements[0].strip() - # Remove empty strings - new_argument = [int(new_argument.replace(" ", "")) if new_argument.replace(" ", "").isdigit() - else float(new_argument.replace(" ", ""))] - new_argument += [el.strip() for el in elements[1:]] - separated_arguments.append(new_argument) - else: - separated_arguments.append(argument) - - return separated_arguments - - def construct_qubo(self, penalty: list[list], variables: list[str]) -> np.ndarray: - """ - Creates QUBO matrix Q to solve linear problem of the form x^T * Q + x. - - :param penalty: List of lists containing all non-zero elements of the QUBO matrix as strings - :param variables: Listing of all variables used in the problem - :return: QUBO in numpy array format - """ - # Create empty qubo matrix - count_variables = len(variables) - qubo = np.zeros((count_variables, count_variables)) - - # Iterate through all the variables twice (x^T, x) - for col, variable in enumerate(variables): - for row, variable2 in enumerate(variables): - # Save the parameters (values in the qubo) - parameter = 0 - for argument in penalty: - if isinstance(argument, list): - # squared variables in diagonals (x^2 == x) - if ( - len(argument) == 2 - and any(isinstance(elem, str) and variable in elem for elem in argument) - and col == row - ): - parameter += argument[0] - # Multiplication of different variables not on diagonal - if ( - len(argument) == 3 - and variable in argument and variable2 in argument and variable > variable2 - ): - parameter += argument[0] - # This value is already taking into account the factor 2 from quadratic term - # For the variables on the diagonal, if the parameter is zero - # We still have to check the sign in - # front of the decision variable. If it is "-", we have to put "-1" on the diagonal. - elif (isinstance(argument, str) and variable in argument - and variable2 in argument and variable == variable2): - if "-" in argument: - parameter += -1 - - qubo[col, row] = parameter - - # Minimization problem - qubo = -qubo.astype(int) - - return qubo - - def map(self, problem: dict, config: Config) -> tuple[dict, float]: - """ - Converts linear program created with pulp to quadratic program to Ising with qiskit to QUBO matrix. - - :param problem: Dict containing the problem parameters - :param config: Config with the parameters specified in Config class - :return: Dict with the QUBO, time it took to map it - """ - start = start_time_measurement() - - # Map Linear problem from dictionary (generated by pulp) to quadratic program to QUBO - qp = self.map_pulp_to_qiskit(problem) - logging.info(qp.export_as_lp_string()) - - ineq2eq = InequalityToEquality() - qp_eq = ineq2eq.convert(qp) - - int2bin = IntegerToBinary() - qp_eq_bin = int2bin.convert(qp_eq) - - lineq2penalty = LinearEqualityToPenalty(100) - qubo = lineq2penalty.convert(qp_eq_bin) - - variables = [variable.name for variable in qubo.variables] - - # convert penalty term to string to QUBO - qubo_string = str(qubo.objective) - arguments = self.convert_string_to_arguments(qubo_string) - qubo_matrix = self.construct_qubo(arguments, variables) - - self.global_variables = variables - - return {"Q": qubo_matrix}, end_time_measurement(start) - - def reverse_map(self, solution: dict) -> tuple[dict, float]: - """ - Maps the solution back to the representation needed by the ACL class for validation/evaluation. - - :param solution: bit_string containing the solution - :return: Solution mapped accordingly, time it took to map it - """ - start = start_time_measurement() - - result = {"status": [0]} - objective_value = 0 - variables = {} - for bit in solution: - if solution[bit] > 0 and "x" in self.global_variables[bit]: - # We only care about assignments of vehicles to platforms: - # We map the solution to the original variables - variables[self.global_variables[bit]] = solution[bit] - result["status"] = 'Optimal' # TODO: I do not think every solution with at least one car is optimal - objective_value += solution[bit] - - result["variables"] = variables - result["obj_value"] = objective_value - - return result, end_time_measurement(start) - - def get_default_submodule(self, option: str) -> Core: - """ - Returns the default submodule based on the provided option. - - :param option: Option specifying the submodule - :return: Instance of the corresponding submodule - :raises NotImplementedError: If the option is not recognized - """ - if option == "Annealer": - from modules.solvers.annealer import Annealer # pylint: disable=C0415 - return Annealer() - else: - raise NotImplementedError(f"Solver Option {option} not implemented") diff --git a/src/modules/applications/optimization/MIS/MIS.py b/src/modules/applications/optimization/MIS/MIS.py deleted file mode 100644 index e146ba9d..00000000 --- a/src/modules/applications/optimization/MIS/MIS.py +++ /dev/null @@ -1,364 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import logging -import pickle -from typing import TypedDict - -import networkx as nx -import matplotlib.pyplot as plt -from matplotlib.lines import Line2D - -from modules.applications.application import Core -from modules.applications.optimization.optimization import Optimization -from modules.applications.optimization.mis.data.graph_layouts import generate_hexagonal_graph -from utils import start_time_measurement, end_time_measurement - -# Define R_rydberg -R_rydberg = 9.75 - - -class MIS(Optimization): - """ - The maximum independent set (MIS) problem is a combinatorial optimization problem that seeks to find the largest - subset of vertices in a graph such that no two vertices are adjacent. MIS has numerous application in computer - science, network design, resource allocation, and even in physics, where finding optimal configurations can - solve fundamental problems related to stability and energy minimization. - - In a graph, the maximum independent set represents a set of nodes such that no two nodes share an edge. This - property makes it a key element in various optimization scenarios. Due to the problem's combinatorial nature, - it becomes computationally challenging, especially for large graphs, often requiring heuristic or approximate - solutions. - - In the context of QUARK, we employ quantum-inspired approaches and state-of-the-art classical algorithms to - tackle the problem. The graph is generated based on user-defined parameters such as size, spacing, and - filling fraction, which affect the complexity and properties of the generated instance. - """ - - def __init__(self): - """ - Constructor method. - """ - super().__init__("MIS") - self.submodule_options = ["QIRO", "NeutralAtom"] - # TODO add more solvers like classical heuristics, VQE, QAOA, etc. - self.depending_parameters = True - self.graph = None - - @staticmethod - def get_requirements() -> list[dict]: - """ - Returns requirements of this module. - - :return: List of dict with requirements of this module - """ - return [] - - def get_solution_quality_unit(self) -> str: - """ - Returns the unit of measurement for solution quality. - - :return: The unit of measure for solution quality - """ - return "Set size" - - def get_default_submodule(self, option: str) -> Core: - """ - Returns the default submodule based on the provided option. - - :param option: Option specifying the submodule - :return: Instance of the corresponding submodule - :raises NotImplementedError: If the option is not recognized - """ - if option == "QIRO": - from modules.applications.optimization.mis.mappings.qiro import QIRO # pylint: disable=C0415 - return QIRO() - elif option == "NeutralAtom": - from QUARK.src.modules.applications.optimization.mis.mappings.neutral_atom import NeutralAtom # pylint: disable=C0415 - return NeutralAtom() - else: - raise NotImplementedError(f"Mapping Option {option} not implemented") - - def get_parameter_options(self) -> dict: - """ - Returns the configurable settings for this application. - - :return: Configuration dictionary for this application - .. code-block:: python - - return { - "size": { - "values": [1, 5, 10, 15], - "custom_input": True, - "allow_ranges": True, - "postproc": int, - "description": "How large should your graph be?" - }, - "graph_type": { - "values": ["hexagonal", "erdosRenyi"], - "postproc": str, - "description": "Do you want a hexagonal or an Erdos-Renyi graph?", - "depending_submodule": True - } - } - """ - return { - "size": { - "values": [1, 5, 10, 15], - "custom_input": True, - "allow_ranges": True, - "postproc": int, - "description": "How large should your graph be?" - }, - "graph_type": { - "values": ["hexagonal", "erdosRenyi"], - "postproc": str, - "description": "Do you want a hexagonal or an Erdos-Renyi graph?", - "depending_submodule": True - } - } - - def get_available_submodules(self, option: list) -> list: - """ - Changes mapping options based on selection of graphs. - - :param option: List of chosen graph type - :return: List of available submodules - """ - if option == ["hexagonal"]: - return ["QIRO", "NeutralAtom"] - else: - return ["QIRO"] - - def get_depending_parameters(self, option: str, config: dict) -> dict: - """ - Returns parameters necessary for chosen problem option. - - :param option: The chosen option - :param config: The current config - :return: The parameters for the given option - """ - - more_params = { - "filling_fraction": { - "values": [x / 10 for x in range(2, 11, 2)], - "custom_input": True, - "allow_ranges": True, - "postproc": float, - "description": "What should be the filling fraction of the hexagonal graph / p of erdosRenyi graph?" - }} - if option == "QIRO": - more_params["seed"] = { - "values": ["No"], - "custom_input": True, - "description": "Do you want to set a seed? If yes, please set an integer number" - } - - elif option == "NeutralAtom": - pass # No additional parameters needed at the moment - else: - raise NotImplementedError(f"Option {option} not implemented") - if "hexagonal" in config["graph_type"]: - more_params["spacing"] = { - "values": [x / 10 for x in range(3, 11, 2)], - "custom_input": True, - "allow_ranges": True, - "postproc": float, - "description": "How much space do you want between your nodes, relative to Rydberg distance?" - } - param_to_return = {} - for key, value in more_params.items(): - if key not in config: - param_to_return[key] = value - - return param_to_return - - class Config(TypedDict): - """ - Configuration attributes for generating an MIS problem. - - Attributes: - size (int): The number of nodes in the graph. - spacing (float): The spacing between nodes in the graph. - filling_fraction (float): The fraction of available places in the lattice filled with nodes - """ - size: int - spacing: float - filling_fraction: float - - def generate_problem(self, config: Config) -> nx.Graph: - """ - Generates a graph to solve the MIS problem for. - - :param config: Config specifying the size and connectivity for the problem - :return: Networkx graph representing the problem - """ - if config is None: - logging.warning("No config provided, using default values: graph_type='hexagonal', size=3, spacing=1," - "filling_fraction=0.5") - config = {"graph_type": "hexagonal", "size": 3, "spacing": 1, "filling_fraction": 0.5} - - graph_type = config.get('graph_type') - size = config.get('size') - filling_fraction = config.get('filling_fraction') - - if graph_type == "erdosRenyi": - gseed = config.get("seed") - - if gseed == "No": - graph = nx.erdos_renyi_graph(size, filling_fraction) - - else: - try: - gseed = int(gseed) - except ValueError: - logging.warning(f"Please select an integer number as seed for the Erdos-Renyi graph instead of " - f"'{gseed}'. The seed is instead set to 0.") - gseed = 0 - graph = nx.erdos_renyi_graph(size, filling_fraction, seed=gseed) - logging.info("Created MIS problem with the nx.erdos_renyi graph method, with the following attributes:") - logging.info(f" - Graph size: {size}") - logging.info(f" - p: {filling_fraction}") - logging.info(f" - seed: {gseed}") - - else: - if config.get('spacing') is None: - spacing = 0.5 - else: - spacing = config.get('spacing') - graph = generate_hexagonal_graph(n_nodes=size, - spacing=spacing * R_rydberg, - filling_fraction=filling_fraction) - logging.info("Created MIS problem with the generate hexagonal graph method, with the following attributes:") - logging.info(f" - Graph size: {size}") - logging.info(f" - Spacing: {spacing * R_rydberg}") - logging.info(f" - Filling fraction: {filling_fraction}") - - self.graph = graph - return graph.copy() - - def validate(self, solution: list) -> tuple[bool, float]: - """ - Checks if the solution is an independent set. - - :param solution: List containing the nodes of the solution - :return: Boolean whether the solution is valid and time it took to validate - """ - start = start_time_measurement() - is_valid = True - - nodes = list(self.graph.nodes()) - edges = list(self.graph.edges()) - - # Check if the solution is independent - is_independent = all((u, v) not in edges for u, v in edges if u in solution and v in solution) - if is_independent: - logging.info("The solution is independent.") - else: - logging.warning("The solution is not independent.") - is_valid = False - - # Check if the solution is a set - solution_set = set(solution) - is_set = len(solution_set) == len(solution) - if is_set: - logging.info("The solution is a set.") - else: - logging.warning("The solution is not a set.") - is_valid = False - - # Check if the solution is a subset of the original nodes - is_subset = all(node in nodes for node in solution) - if is_subset: - logging.info("The solution is a subset of the problem.") - else: - logging.warning("The solution is not a subset of the problem.") - is_valid = False - - return is_valid, end_time_measurement(start) - - def evaluate(self, solution: list) -> tuple[int, float]: - """ - Calculates the size of the solution. - - :param solution: List containing the nodes of the solution - :return: Set size, time it took to calculate the set size - """ - start = start_time_measurement() - set_size = len(solution) - - logging.info(f"Size of solution: {set_size}") - - return set_size, end_time_measurement(start) - - def save(self, path: str, iter_count: int) -> None: - """ - Saves the generated problem graph to a file. - - :param path: Path to save the problem graph - :param iter_count: Iteration count for file versioning - """ - with open(f"{path}/graph_iter_{iter_count}.gpickle", "wb") as file: - pickle.dump(self.graph, file, pickle.HIGHEST_PROTOCOL) - - def visualize_solution(self, processed_solution: list[int], path: str): - """ - Plot the problem graph with the solution nodes highlighted - - :param processed_solution: The solution already processed by :func:`process_solution`, a list of visited node IDs in order of being visited. - :param path: File path for the plot - :returns: None - """ - NODE_SIZE = 300 # Default=300 - EDGE_WIDTH = 1.0 # Default=1.0 - FONT_SIZE = 12 # Default=12 - COLOR_INCLUDED = "red" - COLOR_EXCLUDED = "gray" - - G = self.graph - included_nodes = [node for node in G.nodes() if node in processed_solution] - excluded_nodes = [node for node in G.nodes() if node not in processed_solution] - pos = nx.circular_layout(G) - included_pos = {n: n for n, _ in pos.items() if n in processed_solution} - excluded_pos = {n: n for n, _ in pos.items() if n not in processed_solution} - legend_elements = [ - Line2D( - [0], - [0], - marker='o', - ls="None", - label="Included", - markerfacecolor=COLOR_INCLUDED, - markeredgewidth=0, - markersize=10), - Line2D( - [0], - [0], - marker='o', - ls="None", - label="Excluded", - markerfacecolor=COLOR_EXCLUDED, - markeredgewidth=0, - markersize=10) - ] - - nx.draw_networkx_nodes(G, pos, nodelist=included_nodes, node_size=NODE_SIZE, node_color=COLOR_INCLUDED) - nx.draw_networkx_nodes(G, pos, nodelist=excluded_nodes, node_size=NODE_SIZE, node_color=COLOR_EXCLUDED) - nx.draw_networkx_labels(G, pos, included_pos, font_size=FONT_SIZE, font_weight="bold") - nx.draw_networkx_labels(G, pos, excluded_pos, font_size=FONT_SIZE) - nx.draw_networkx_edges(G, pos, width=EDGE_WIDTH) - - plt.legend(handles=legend_elements) - plt.savefig(path) - plt.close() diff --git a/src/modules/applications/optimization/MIS/mappings/QIRO.py b/src/modules/applications/optimization/MIS/mappings/QIRO.py deleted file mode 100644 index e0e9ee1a..00000000 --- a/src/modules/applications/optimization/MIS/mappings/QIRO.py +++ /dev/null @@ -1,96 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from typing import TypedDict -import networkx - -from modules.applications.mapping import Core, Mapping -from utils import start_time_measurement, end_time_measurement - - -class QIRO(Mapping): - """ - The quantum-informed recursive optimization (QIRO) formulation for the MIS problem. QIRO recursively simplifies the - problem classically using information obtained with quantum resources. - """ - - def __init__(self): - """ - Constructor method - """ - super().__init__() - self.submodule_options = ["QrispQIRO"] - - @staticmethod - def get_requirements() -> list[dict]: - """ - Return requirements of this module. - - :return: list of dict with requirements of this module - """ - return [{"name": "qrisp", "version": "0.5.2"}] - - def get_parameter_options(self) -> dict: - """ - Returns the configurable settings for this mapping. - - :return: - .. code-block:: python - - return {} - - """ - # TODO "optimizer": { - # "values": ["not", "yet", "implemented"], - # "description": "Which QIRO algorithm should be used?" - # } - return {} - - class Config(TypedDict): - """ - Attributes of a valid config - - .. code-block:: python - pass - """ - pass - - def map(self, problem: networkx.Graph, config: Config) -> tuple[dict, float]: - """ - Maps the networkx graph to a neutral atom MIS problem. - - :param problem: Networkx graph - :param config: Config with the parameters specified in Config class - :return: Dict with neutral MIS, time it took to map it - """ - start = start_time_measurement() - - qiro_mapped_problem = { - 'graph': problem, - } - return qiro_mapped_problem, end_time_measurement(start) - - def get_default_submodule(self, option: str) -> Core: - """ - Returns the default submodule based on the provided option. - - :param option: Option specifying the submodule - :return: Instance of the corresponding submodule - :raises NotImplementedError: If the option is not recognized - """ - if option == "QrispQIRO": - from modules.solvers.qrisp_qiro import QIROSolver # pylint: disable=C0415 - return QIROSolver() - else: - raise NotImplementedError(f"Solver Option {option} not implemented") diff --git a/src/modules/applications/optimization/PVC/PVC.py b/src/modules/applications/optimization/PVC/PVC.py deleted file mode 100644 index 87d1c6a7..00000000 --- a/src/modules/applications/optimization/PVC/PVC.py +++ /dev/null @@ -1,393 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import itertools -from typing import TypedDict -import pickle -import logging -import os - -import networkx as nx -import matplotlib.pyplot as plt -from matplotlib.lines import Line2D -from matplotlib.patches import Patch -import numpy as np - -from modules.applications.application import Core -from modules.applications.optimization.optimization import Optimization -from utils import start_time_measurement, end_time_measurement - - -class PVC(Optimization): - """ - In modern vehicle manufacturing, robots take on a significant workload, including performing welding - jobs, sealing welding joints, or applying paint to the car body. While the robot’s tasks vary widely, - the objective remains the same: Perform a job with the highest possible quality in the shortest amount - of time, optimizing efficiency and productivity on the manufacturing line. - - For instance, to protect a car’s underbody from corrosion, exposed welding seams are sealed - by applying a polyvinyl chloride layer (PVC). The welding seams need to be traversed by a robot to - apply the material. It is related to TSP, but different and even more complex in some aspects. - - The problem of determining the optimal route for robots to traverse all seams shares similarities - with Traveling Salesman Problem (TSP), as it involves finding the shortest possible route to - visit multiple locations. However, it introduces additional complexities, such as different tool - and configuration requirements for each seam, making it an even more challenging problem to solve. - """ - - def __init__(self): - """ - Constructor method. - """ - super().__init__("PVC") - self.submodule_options = [ - "Ising", "QUBO", "GreedyClassicalPVC", "ReverseGreedyClassicalPVC", "RandomPVC" - ] - - @staticmethod - def get_requirements() -> list[dict]: - """ - Returns requirements of this module. - - :return: List of dict with requirements of this module - """ - return [ - {"name": "networkx", "version": "3.4.2"}, - {"name": "numpy", "version": "1.26.4"} - ] - - def get_solution_quality_unit(self) -> str: - """ - Returns the unit of measure for solution quality. - - :return: Unit of measure for solution quality - """ - return "Tour cost" - - def get_default_submodule(self, option: str) -> Core: - """ - Returns the default submodule based on the provided option. - - :param option: Option specifying the submodule - :return: Instance of the corresponding submodule - :raises NotImplementedError: If the option is not recognized - """ - if option == "Ising": - from modules.applications.optimization.pvc.mappings.ising import Ising # pylint: disable=C0415 - return Ising() - elif option == "QUBO": - from modules.applications.optimization.pvc.mappings.qubo import QUBO # pylint: disable=C0415 - return QUBO() - elif option == "GreedyClassicalPVC": - from modules.solvers.greedy_classical_pvc import GreedyClassicalPVC # pylint: disable=C0415 - return GreedyClassicalPVC() - elif option == "ReverseGreedyClassicalPVC": - from modules.solvers.reverse_greedy_classical_pvc import ReverseGreedyClassicalPVC # pylint: disable=C0415 - return ReverseGreedyClassicalPVC() - elif option == "RandomPVC": - from modules.solvers.random_classical_pvc import RandomPVC # pylint: disable=C0415 - return RandomPVC() - else: - raise NotImplementedError(f"Mapping Option {option} not implemented") - - def get_parameter_options(self) -> dict: - """ - Returns the configurable settings for this application. - - :return: Dictionary containing parameter options - .. code-block:: python - - return { - "seams": { - "values": list(range(1, 18)), - "description": "How many seams should be sealed?" - } - } - """ - return { - "seams": { - "values": list(range(1, 18)), - # In the current implementation the graph can only be as large as the reference input graph - "description": "How many seams should be sealed?" - } - } - - class Config(TypedDict): - """ - Configuration attributes for PVC problem generation. - - Attributes: - seams (int): Number of seams for the graph - """ - seams: int - - def generate_problem(self, config: Config) -> nx.Graph: - """ - Uses the reference graph to generate a problem for a given config. - - :param config: Config specifying the number of seams for the problem - :return: Networkx graph representing the problem - """ - if config is None: - config = {"seams": 3} - seams = config['seams'] - - # Read in the original graph - with open(os.path.join(os.path.dirname(__file__), "data", "reference_graph.gpickle"), "rb") as file: - graph = pickle.load(file) - - # Get number of seam in graph - seams_in_graph = list({x[0] for x in graph.nodes}) - seams_in_graph.sort() - seams_in_graph.remove(0) # Always need the base node 0 (which is not a seam) - - if len(seams_in_graph) < seams: - logging.info("Too many seams! The original graph has less seams than that!") - - unwanted_seams = seams_in_graph[-len(seams_in_graph) + seams:] - unwanted_nodes = [x for x in graph.nodes if x[0] in unwanted_seams] - - for node in unwanted_nodes: - graph.remove_node(node) - - if not nx.is_strongly_connected(graph): - logging.error("Graph is not connected!") - raise ValueError("Graph is not connected!") - - # Gather unique configurations and tools - config = [x[2]['c_start'] for x in graph.edges(data=True)] - config = list(set(config + [x[2]['c_end'] for x in graph.edges(data=True)])) - tool = [x[2]['t_start'] for x in graph.edges(data=True)] - tool = list(set(tool + [x[2]['t_end'] for x in graph.edges(data=True)])) - - # Fill the rest of the missing edges with high values - current_edges = [ - (edge[0], edge[1], edge[2]['t_start'], edge[2]['t_end'], edge[2]['c_start'], edge[2]['c_end']) - for edge in graph.edges(data=True) - ] - all_possible_edges = list(itertools.product(list(graph.nodes), repeat=2)) - all_possible_edges = [ - (edges[0], edges[1], t_start, t_end, c_start, c_end) - for edges in all_possible_edges - for c_end in config - for c_start in config - for t_end in tool - for t_start in tool if edges[0] != edges[1] - ] - - missing_edges = [item for item in all_possible_edges if item not in current_edges] - - # Add these edges with very high values - for edge in missing_edges: - graph.add_edge( - edge[0], edge[1], c_start=edge[4], t_start=edge[2], c_end=edge[5], t_end=edge[3], weight=100000 - ) - - logging.info("Created PVC problem with the following attributes:") - logging.info(f" - Number of seams: {seams}") - logging.info(f" - Number of different configs: {len(config)}") - logging.info(f" - Number of different tools: {len(tool)}") - - self.application = graph - return graph.copy() - - def process_solution(self, solution: dict) -> tuple[list, float]: - """ - Converts solution dictionary to list of visited seams. - - :param solution: Unprocessed solution - :return: Processed solution and the time it took to process it - """ - start_time = start_time_measurement() - nodes = list(self.application.nodes()) - start = ((0, 0), 1, 1) - route: list = [None] * int((len(self.application) - 1) / 2 + 1) - visited_seams = [] - - if sum(value == 1 for value in solution.values()) > len(route): - logging.warning("Result is longer than route! This might be problematic!") - - # Prevent duplicate node entries by enforcing only one occurrence per node along route - for (node, config, tool, timestep), val in solution.items(): - if val and (node[0] not in visited_seams): - if route[timestep] is not None: - visited_seams.remove(route[timestep][0][0]) - route[timestep] = (node, config, tool) - visited_seams.append(node[0]) - - # Fill missing values in the route - if None in route: - logging.info(f"Route until now is: {route}") - nodes_unassigned = [(node, 1, 1) for node in nodes if node[0] not in visited_seams] - nodes_unassigned = list(np.random.permutation(nodes_unassigned, dtype=object)) - logging.info(nodes_unassigned) - logging.info(visited_seams) - logging.info(nodes) - for idx, node in enumerate(route): - if node is None: - route[idx] = nodes_unassigned.pop(0) - - # Cycle solution to start at provided start location - if start is not None and route[0] != start: - idx = route.index(start) - route = route[idx:] + route[:idx] - - parsed_route = ' ->\n'.join( - [ - f' Node {visit[0][1]} of Seam {visit[0][0]} using config ' - f' {visit[1]} & tool {visit[2]}' - for visit in route - ] - ) - logging.info(f"Route found:\n{parsed_route}") - - return route, end_time_measurement(start_time) - - def validate(self, solution: list) -> tuple[bool, float]: - """ - Checks if all seams and the home position are visited for a given solution. - - :param solution: List containing the nodes of the solution - :return: Boolean whether the solution is valid and time it took to validate - """ - # Check if all seams are visited in route - start = start_time_measurement() - visited_seams = {seam[0][0] for seam in solution if seam is not None} - - if len(visited_seams) == len(solution): - logging.info(f"All {len(solution) - 1} seams and " - "the base node got visited (We only need to visit 1 node per seam)") - return True, end_time_measurement(start) - else: - logging.error(f"Only {len(visited_seams) - 1} got visited") - return False, end_time_measurement(start) - - def evaluate(self, solution: list) -> tuple[float, float]: - """ - Calculates the tour length for a given valid tour. - - :param solution: List containing the nodes of the solution - :return: Tour length, time it took to calculate the tour length - """ - start = start_time_measurement() - - # Get the total distance - total_dist = 0 - for idx, _ in enumerate(solution[:-1]): - edge = next( - item for item in list(self.application[solution[idx][0]][solution[idx + 1][0]].values()) - if item["c_start"] == solution[idx][1] and item["t_start"] == solution[idx][2] and - item["c_end"] == solution[idx + 1][1] and item["t_end"] == solution[idx + 1][2] - ) - dist = edge['weight'] - total_dist += dist - logging.info(f"Total distance (without return): {total_dist}") - - # Add distance between start and end point to complete cycle - return_edge = next( - item for item in list(self.application[solution[0][0]][solution[-1][0]].values()) - if item["c_start"] == solution[0][1] and item["t_start"] == solution[0][2] and - item["c_end"] == solution[-1][1] and item["t_end"] == solution[-1][2] - ) - return_distance = return_edge['weight'] - logging.info(f"Distance between start and end: {return_distance}") - - # Get distance for full cycle - distance = total_dist + return_distance - logging.info(f"Total distance (including return): {distance}") - - return distance, end_time_measurement(start) - - def save(self, path: str, iter_count: int) -> None: - """ - Saves the generated problem graph to a file. - - :param path: Path to save the problem graph - :param iter_count: Iteration count for file versioning - """ - with open(f"{path}/graph_iter_{iter_count}.gpickle", "wb") as file: - pickle.dump(self.application, file, pickle.HIGHEST_PROTOCOL) - - def visualize_solution(self, processed_solution, path: str): - """ - Plot a graph representing the possible locations where seams can start or end, with arrows representing either idle movements or the sealing of a seam - - :param processed_solution: The solution already processed by :func:`process_solution`, a list of tuples representing seam start points and the config and tool needed to seal the seam. - :param path: File path for the plot - :returns: None - """ - NODE_SIZE = 300 # Default=300 - EDGE_WIDTH = 1.0 # Default=1.0 - FONT_SIZE = 12 # Default=12 - - highest_node_id = max(node[1] for node in self.application.nodes()) - G = nx.MultiDiGraph() - G.add_nodes_from(range(highest_node_id + 1)) - pos = nx.circular_layout(G) - - tools = set() - configs = set() - current_node = 0 - for ((seam1, node1), config, tool) in processed_solution[1:]: - config = config - 1 - tools.add(tool) - configs.add(config) - (seam2, node2) = next((seam, node) - for (seam, node) in self.application.nodes() if seam == seam1 and not node == node1) - assert seam1 == seam2, "This is bad" - if not current_node == node1: - G.add_edge(current_node, node1, color=7, width=EDGE_WIDTH, style=-1) - G.add_edge(node1, node2, color=tool, width=2 * EDGE_WIDTH, style=config) - current_node = node2 - - # The 8 here controls how many edges between the same two nodes are at - # most drawn with spacing between them before drawing them on top of each - # other to avoid cluttering - connectionstyle = [f"arc3,rad={r}" for r in itertools.accumulate([0.15] * 8)] - style_options = ["solid", "dotted", "dashed", "dashdot"] - cmap = plt.cm.Dark2 - tools = list(tools) - configs = list(configs) - legend_elements = [Line2D([0], - [0], - color=cmap(7), - lw=EDGE_WIDTH, - ls=':', - label="Idle Movement")] + [Patch(facecolor=cmap(i), - label=f"Tool {i}") for i in tools] + [Line2D([0], - [0], - color="black", - lw=2 * EDGE_WIDTH, - ls=style_options[i % len( - style_options)], - label=f"Config {i + 1}") for i in configs] - colors = nx.get_edge_attributes(G, 'color').values() - widths = nx.get_edge_attributes(G, 'width').values() - styles = [':' if i == -1 else style_options[i % len(style_options)] - for i in nx.get_edge_attributes(G, 'style').values()] - - nx.draw_networkx( - G, - pos, - node_size=NODE_SIZE, - font_size=FONT_SIZE, - style=list(styles), - edge_color=colors, - edge_cmap=cmap, - width=list(widths), - connectionstyle=connectionstyle) - - plt.legend(handles=legend_elements) - plt.savefig(path) - plt.close() diff --git a/src/modules/applications/optimization/PVC/mappings/ISING.py b/src/modules/applications/optimization/PVC/mappings/ISING.py deleted file mode 100644 index 68574679..00000000 --- a/src/modules/applications/optimization/PVC/mappings/ISING.py +++ /dev/null @@ -1,159 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from typing import TypedDict -import logging - -import networkx as nx -import numpy as np -from dimod import qubo_to_ising - -from modules.applications.mapping import Mapping, Core -from modules.applications.optimization.pvc.mappings.qubo import QUBO -from utils import start_time_measurement, end_time_measurement - - -class Ising(Mapping): - """ - Ising formulation for the PVC. - - """ - - def __init__(self): - """ - Constructor method. - """ - super().__init__() - self.submodule_options = ["QAOA", "PennylaneQAOA"] - self.key_mapping = None - - @staticmethod - def get_requirements() -> list[dict]: - """ - Returns requirements of this module. - - :return: List of dictionaries with requirements of this module - """ - return [ - {"name": "networkx", "version": "3.4.2"}, - {"name": "numpy", "version": "1.26.4"}, - {"name": "dimod", "version": "0.12.18"}, - *QUBO.get_requirements() - ] - - def get_parameter_options(self) -> dict: - """ - Returns the configurable settings for this mapping. - - :return: Dictionary containing parameter options. - .. code-block:: python - - return { - "lagrange_factor": { - "values": [0.75, 1.0, 1.25], - "description": "By which factor would you like to multiply your Lagrange?" - } - } - """ - return { - "lagrange_factor": { - "values": [0.75, 1.0, 1.25], - "description": "By which factor would you like to multiply your Lagrange?" - } - } - - class Config(TypedDict): - """ - Configuration attributes for Ising mapping. - - Attributes: - lagrange_factor (float): Factor to multiply the Langrange. - """ - lagrange_factor: float - - def map(self, problem: nx.Graph, config: Config) -> tuple[dict, float]: - """ - Uses the PVC QUBO formulation and converts it to an Ising representation. - - :param problem: Networkx graph representing the PVC problem - :param config: Config dictionary with the mapping configuration - :return: Tuple containing a dictionary with the ising problem and time it took to map it - """ - start = start_time_measurement() - - # Convert the PVC problem to QUBO - qubo_mapping = QUBO() - q, _ = qubo_mapping.map(problem, config) - - # Convert QUBO to ising using dimod - t, j, _ = qubo_to_ising(q["Q"]) - - # Extract unique configuration and tool attributes from the graph - config = [x[2]['c_start'] for x in problem.edges(data=True)] - config = list(set(config + [x[2]['c_end'] for x in problem.edges(data=True)])) - - tool = [x[2]['t_start'] for x in problem.edges(data=True)] - tool = list(set(tool + [x[2]['t_end'] for x in problem.edges(data=True)])) - - # Initialize J matrix and mapping - timesteps = int((problem.number_of_nodes() - 1) / 2 + 1) - matrix_size = problem.number_of_nodes() * len(config) * len(tool) * timesteps - j_matrix = np.zeros((matrix_size, matrix_size), dtype=float) - self.key_mapping = {} - - # Map J values to a matrix representation - index_counter = 0 - for key, value in j.items(): - if key[0] not in self.key_mapping: - self.key_mapping[key[0]] = index_counter - index_counter += 1 - if key[1] not in self.key_mapping: - self.key_mapping[key[1]] = index_counter - index_counter += 1 - u = self.key_mapping[key[0]] - v = self.key_mapping[key[1]] - j_matrix[u][v] = value - - return {"J": j_matrix, "t": np.array(list(t.values()))}, end_time_measurement(start) - - def reverse_map(self, solution: dict) -> tuple[dict, float]: - """ - Maps the solution back to the representation needed by the PVC class for validation/evaluation. - - :param solution: Dictionary containing the solution - :return: Tuple with the remapped solution and time it took to reverse map - """ - start = start_time_measurement() - logging.info(f"Key Mapping: {self.key_mapping}") - - result = {key: 1 if solution[self.key_mapping[key]] == 1 else 0 for key in self.key_mapping} - - return result, end_time_measurement(start) - - def get_default_submodule(self, option: str) -> Core: - """ - Returns the default submodule based on the provided option. - - :param option: Option specifying the submodule - :return: Instance of the corresponding submodule - :raises NotImplementedError: If the option is not recognized - """ - if option == "QAOA": - from modules.solvers.qaoa import QAOA # pylint: disable=C0415 - return QAOA() - if option == "PennylaneQAOA": - from modules.solvers.pennylane_qaoa import PennylaneQAOA # pylint: disable=C0415 - return PennylaneQAOA() - else: - raise NotImplementedError(f"Solver Option {option} not implemented") diff --git a/src/modules/applications/optimization/PVC/mappings/QUBO.py b/src/modules/applications/optimization/PVC/mappings/QUBO.py deleted file mode 100644 index c62cf779..00000000 --- a/src/modules/applications/optimization/PVC/mappings/QUBO.py +++ /dev/null @@ -1,201 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import itertools -from collections import defaultdict -from typing import TypedDict -import logging - -import networkx as nx - -from modules.applications.mapping import Mapping, Core -from utils import start_time_measurement, end_time_measurement - - -class QUBO(Mapping): - """ - QUBO formulation for the PVC. - """ - - def __init__(self): - """ - Constructor method. - """ - super().__init__() - self.submodule_options = ["Annealer"] - - @staticmethod - def get_requirements() -> list[dict]: - """ - Return requirements of this module. - - :return: List of dictionaries with requirements of this module - """ - return [{"name": "networkx", "version": "3.4.2"}] - - def get_parameter_options(self) -> dict: - """ - Returns the configurable settings for this mapping. - - :return: Dictionary containing parameter options - .. code-block:: python - - return { - "lagrange_factor": { - "values": [0.75, 1.0, 1.25], - "description": "By which factor would you like to multiply your Lagrange?" - } - } - - """ - return { - "lagrange_factor": { - "values": [0.75, 1.0, 1.25], - "description": "By which factor would you like to multiply your Lagrange?" - } - } - - class Config(TypedDict): - """ - Configuration attributes of QUBO mapping. - - Attributes: - lagrange_factor (float): Factor to multiply the Langrange. - - """ - lagrange_factor: float - - def map(self, problem: nx.Graph, config: Config) -> tuple[dict, float]: - """ - Maps the networkx graph to a QUBO formulation. - - :param problem: Networkx graph representing the PVC problem - :param config: Config dictionary with the mapping configuration - :return: Tuple containing the QUBO dictionary and the time it took to map it - """ - # Inspired by https://dnx.readthedocs.io/en/latest/_modules/dwave_networkx/algorithms/tsp.html - start = start_time_measurement() - lagrange_factor = config['lagrange_factor'] - - # Estimate lagrange if not provided - n = problem.number_of_nodes() - timesteps = int((n - 1) / 2 + 1) - - # Get the number of different configs and tools - config = [x[2]['c_start'] for x in problem.edges(data=True)] - config = list(set(config + [x[2]['c_end'] for x in problem.edges(data=True)])) - - tool = [x[2]['t_start'] for x in problem.edges(data=True)] - tool = list(set(tool + [x[2]['t_end'] for x in problem.edges(data=True)])) - - if problem.number_of_edges() > 0: - weights = [x[2]['weight'] for x in problem.edges(data=True)] - weights = list(filter(lambda a: a != max(weights), weights)) - lagrange = sum(weights) / len(weights) * timesteps - else: - lagrange = 2 - - lagrange *= lagrange_factor - logging.info(f"Selected lagrange is: {lagrange}") - - if n in (1, 2) or len(problem.edges) < n * (n - 1) // 2: - msg = "graph must be a complete graph with at least 3 nodes or empty" - raise ValueError(msg) - - # Creating the QUBO - q = defaultdict(float) - - # We need to implement the following constrains: - # Only visit 1 node of each seam - # Don`t visit nodes twice (even if their config/tool is different) - # We only need to visit base node at the once since this path from last node to base node is unique anyway - - # Constraint to only visit a node/seam once - for node in problem: # for all nodes in the graph - for pos_1 in range(timesteps): # for number of timesteps - for t_start in tool: - for c_start in config: - q[((node, c_start, t_start, pos_1), (node, c_start, t_start, pos_1))] -= lagrange - for t_end in tool: - # For all configs and tools - for c_end in config: - if c_start != c_end or t_start != t_end: - q[((node, c_start, t_start, pos_1), (node, c_end, t_end, pos_1))] += 1.0 * lagrange - for pos_2 in range(pos_1 + 1, timesteps): - # Penalize visiting same node again in another timestep - q[((node, c_start, t_start, pos_1), (node, c_end, t_end, pos_2))] += 2.0 * lagrange - # Penalize visiting other node of same seam - if node != (0, 0): - # (0,0) is the base node, it is not a seam - # Get the other nodes of the same seam - other_seam_nodes = [ - x for x in problem.nodes if x[0] == node[0] and x[1] != node - ] - for other_seam_node in other_seam_nodes: - # Penalize visiting other node of same seam - q[((node, c_start, t_start, pos_1), - (other_seam_node, c_end, t_end, pos_2))] += 2.0 * lagrange - - # Constraint to only visit a single node in a single timestep - for pos in range(timesteps): - for node_1 in problem: - for t_start in tool: - for c_start in config: - q[((node_1, c_start, t_start, pos), (node_1, c_start, t_start, pos))] -= lagrange - for t_end in tool: - for c_end in config: - for node_2 in set(problem) - {node_1}: # for all nodes except node1 -> node1 - q[((node_1, c_start, t_start, pos), (node_2, c_end, t_end, pos))] += lagrange - - # Objective that minimizes distance - for u, v in itertools.combinations(problem.nodes, 2): - for pos in range(timesteps): - for t_start in tool: - for t_end in tool: - for c_start in config: - for c_end in config: - nextpos = (pos + 1) % timesteps - edge_u_v = next( - item for item in list(problem[u][v].values()) - if item["c_start"] == c_start and item["t_start"] == t_start and - item["c_end"] == c_end and item["t_end"] == t_end - ) - # Since it is the other direction we switch start and end of tool and config - edge_v_u = next( - item for item in list(problem[v][u].values()) - if item["c_start"] == c_end and item["t_start"] == t_end and - item["c_end"] == c_start and item["t_end"] == t_start - ) - # Going from u -> v - q[((u, c_start, t_start, pos), (v, c_end, t_end, nextpos))] += edge_u_v['weight'] - # Going from v -> u - q[((v, c_end, t_end, pos), (u, c_start, t_start, nextpos))] += edge_v_u['weight'] - - logging.info("Created Qubo") - - return {"Q": q}, end_time_measurement(start) - - def get_default_submodule(self, option: str) -> Core: - """ - Returns the default submodule based on the provided option. - - :param option: Option specifying the submodule - :return: Instance of the corresponding submodule - :raises NotImplementedError: If the option is not recognized - """ - if option == "Annealer": - from modules.solvers.annealer import Annealer # pylint: disable=C0415 - return Annealer() - else: - raise NotImplementedError(f"Solver Option {option} not implemented") diff --git a/src/modules/applications/optimization/SAT/SAT.py b/src/modules/applications/optimization/SAT/SAT.py deleted file mode 100644 index eebdc9d1..00000000 --- a/src/modules/applications/optimization/SAT/SAT.py +++ /dev/null @@ -1,333 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import logging -from typing import TypedDict - -import nnf -import numpy as np -from nnf import Var, And, Or -from nnf.dimacs import dump - -from modules.core import Core -from modules.applications.optimization.optimization import Optimization -from utils import start_time_measurement, end_time_measurement - - -class SAT(Optimization): - """ - The SAT (Satisfiability) problem plays a crucial role in the field of computational optimization. In the context - of vehicle manufacturing, it is essential to test various pre-series vehicle configurations to ensure they meet - specific requirements before production begins. This testing involves making sure that each vehicle configuration - complies with several hard constraints related to safety, performance, and buildability while also fulfilling - soft constraints such as feature combinations or specific requirements for testing. The SAT problem models these - constraints in a way that enables a systematic approach to determine feasible vehicle configurations and minimize - the need for excessive physical prototypes. - - This problem is modeled as a Max-SAT problem, where the aim is to find a configuration that satisfies as many - constraints as possible while balancing between the number of satisfied hard and soft constraints. The formulation - uses a conjunctive normal form (CNF) representation of logical expressions to model the dependencies and - incompatibilities between various features and components in vehicle assembly. By leveraging optimization - algorithms, the SAT module aims to produce a minimal but sufficient set of configurations, ensuring that all - necessary tests are performed while minimizing resource usage. This approach helps in creating a robust testing - framework and reducing the overall cost of vehicle development. - - To solve the SAT problem, various approaches are employed, including translating the CNF representation into - different quantum and classical optimization mappings such as QUBO (Quadratic Unconstrained Binary Optimization) - or Ising formulations. These mappings make the SAT problem suitable for solving on quantum computers and - classical annealers. The SAT problem in this module is implemented with a flexible interface, allowing integration - with a range of solvers that can exploit different computational paradigms, making it adaptable for a variety of - hardware and optimization backends. - """ - - def __init__(self): - """ - Constructor method. - """ - super().__init__("SAT") - self.submodule_options = [ - "QubovertQUBO", "Direct", "ChoiQUBO", "DinneenQUBO", "ChoiIsing", "DinneenIsing" - ] - self.literals = None - self.num_tests = None - self.num_constraints = None - self.num_variables = None - - @staticmethod - def get_requirements() -> list[dict]: - """ - Return requirements of this module. - - :return: List of dict with requirements of this module - """ - return [ - {"name": "nnf", "version": "0.4.1"}, - {"name": "numpy", "version": "1.26.4"} - ] - - def get_solution_quality_unit(self) -> str: - return "Evaluation" - - def get_default_submodule(self, option: str) -> Core: - """ - Returns the default submodule based on the provided option. - - :param option: Option specifying the submodule - :return: Instance of the corresponding submodule - :raises NotImplementedError: If the option is not recognized - """ - if option == "QubovertQUBO": - from modules.applications.optimization.sat.mappings.qubovertqubo import \ - QubovertQUBO # pylint: disable=C0415 - return QubovertQUBO() - elif option == "Direct": - from modules.applications.optimization.sat.mappings.direct import Direct # pylint: disable=C0415 - return Direct() - elif option == "ChoiQUBO": - from modules.applications.optimization.sat.mappings.choiqubo import ChoiQUBO # pylint: disable=C0415 - return ChoiQUBO() - elif option == "ChoiIsing": - from modules.applications.optimization.sat.mappings.choiIsing import ChoiIsing # pylint: disable=C0415 - return ChoiIsing() - elif option == "DinneenQUBO": - from modules.applications.optimization.sat.mappings.dinneenqubo import DinneenQUBO # pylint: disable=C0415 - return DinneenQUBO() - elif option == "DinneenIsing": - from modules.applications.optimization.sat.mappings.dinneenising import \ - DinneenIsing # pylint: disable=C0415 - return DinneenIsing() - else: - raise NotImplementedError(f"Mapping Option {option} not implemented") - - def get_parameter_options(self) -> dict: - """ - Returns the configurable settings for this application. - - :return: Dictionary with configurable settings - .. code-block:: python - - return { - "variables": { - "values": list(range(10, 151, 10)), - "custom_input": True, - "allow_ranges": True, - "postproc": int, - "description": "How many variables do you need?" - }, - "clvar_ratio_cons": { - "values": [2, 3, 4, 4.2, 5], - "custom_input": True, - "allow_ranges": True, - "postproc": int, - "description": "What clause-to-variable ratio do you want for the (hard) constraints?" - }, - "clvar_ratio_test": { - "values": [2, 3, 4, 4.2, 5], - "custom_input": True, - "allow_ranges": True, - "postproc": int, - "description": "What clause-to-variable ratio do you want for the tests (soft con.)?" - }, - "problem_set": { - "values": list(range(10)), - "description": "Which problem set do you want to use?" - }, - "max_tries": { - "values": [100], - "description": "Maximum number of tries to create problem?" - } - } - """ - return { - "variables": { - "values": list(range(10, 101, 10)), - "custom_input": True, - "allow_ranges": True, - "postproc": int, - "description": "How many variables do you need?" - }, - "clvar_ratio_cons": { - "values": [2, 3, 4, 4.2, 5], - "custom_input": True, - "allow_ranges": True, - "postproc": int, - "description": "What clause-to-variable ratio do you want for the (hard) constraints?" - }, - "clvar_ratio_test": { - "values": [2, 3, 4, 4.2, 5], - "custom_input": True, - "allow_ranges": True, - "postproc": int, - "description": "What clause-to-variable ratio do you want for the tests (soft constraints)?" - }, - "problem_set": { - "values": list(range(10)), - "description": "Which problem set do you want to use?" - }, - "max_tries": { - "values": [100], - "description": "Maximum number of tries to create problem?" - } - } - - class Config(TypedDict): - """ - Attributes of a valid config. - - .. code-block:: python - - variables: int - clvar_ratio_cons: float - clvar_ratio_test: float - problem_set: int - max_tries: int - - """ - variables: int - clvar_ratio_cons: float - clvar_ratio_test: float - problem_set: int - max_tries: int - - def generate_problem(self, config: Config) -> tuple[nnf.And, list]: - """ - Generates a vehicle configuration problem out of a given config. - Returns buildability constraints (hard constraints) and tests (soft - constraints), the successful evaluation of which we try to maximize. - Both are given in nnf form, which we then convert accordingly. - - :param config: Configuration parameters for problem generation - :return: A tuple containing the problem, number of variables, and other details - """ - - self.num_variables = config["variables"] - num_constraints = round(config["clvar_ratio_cons"] * self.num_variables) - num_tests = round(config["clvar_ratio_test"] * self.num_variables) - max_tries = config["max_tries"] - self.literals = [Var(f"L{i}") for i in range(self.num_variables)] - self.application = {} - - def _generate_3sat_clauses(nr_clauses, nr_vars, satisfiable, rseed, nr_tries): - # Iterate over the desired number of attempts: break if we find a solvable instance. - for attempt in range(nr_tries): - # Initialize random number generator -- multiply the attempt to traverse distinct random seeds - # for the hard and soft constraints, respectively (since rseed of the hard and soft constraints differs - # by 1). - rng = np.random.default_rng(rseed + attempt * 2) - clause_list = [] - # generate literal list to sample from - lit_vars = [Var(f"L{i}") for i in range(nr_vars)] - for _ in range(nr_clauses): - # Select three (non-repeated) literals and negate them randomly -- together constituting a clause - chosen_literals = rng.choice(lit_vars, 3, replace=False) - negate_literals = rng.choice([True, False], 3, replace=True) - # Perform the random negations and append to clause: - clause = [ - lit.negate() if neg else lit - for lit, neg in zip(chosen_literals, negate_literals) - ] - # Append the generated clause to the total container - clause_list.append(Or(clause)) - prob = And(clause_list) - if not satisfiable or prob.satisfiable(): - return clause_list - - # Loop ran out of tries - logging.error("Unable to generate valid solutions. Consider increasing max_tries or decreasing " - "the clause:variable ratio.") - raise ValueError("Unable to generate valid solution.") - - # Choose a random seed -- since we try at most max_tries times to generate a solvable instance, - # Space the initial random seeds by 2 * max_tries (because we need both hard and soft constraints). - random_seed = 2 * config["problem_set"] * max_tries - # Generate hard & soft constraints. Make both satisfiable, but this can in principle be tuned. - hard = And(_generate_3sat_clauses( - num_constraints, self.num_variables, satisfiable=True, - rseed=random_seed, nr_tries=max_tries - )) - # The random_seed + 1 ensures that a different set of seeds is sampled compared to the hard constraints. - soft = _generate_3sat_clauses( - num_tests, self.num_variables, satisfiable=True, - rseed=random_seed + 1, nr_tries=config["max_tries"] - ) - if (hard is None) or (soft is None): - raise ValueError("Unable to generate satisfiable") - # Saving constraints and tests - self.application["constraints"] = hard - self.application["tests"] = soft - # And their cardinalities: - self.num_constraints = len(hard) - self.num_tests = len(soft) - - logging.info(f"Generated a vehicle options Max3SAT" - f" instance with {self.num_variables} variables, {self.num_constraints} constraints" - f" and {self.num_tests} tests") - return hard, soft - - def validate(self, solution: dict) -> tuple[bool, float]: - """ - Validate a given solution against the constraints. - - :param solution: The solution to validate - :return: True if the solution is valid, False otherwise, and time it took to complete - """ - start = start_time_measurement() - - logging.info("Checking validity of solution:") - nr_satisfied_hardcons = len(*np.where( - [c.satisfied_by(solution) for c in self.application["constraints"].children] - )) - ratio = nr_satisfied_hardcons / self.num_constraints - is_valid = ratio == 1.0 - logging.info(f"Ratio of satisfied constraints: {ratio}\nSuccess:{['no', 'yes'][int(is_valid)]}") - - return is_valid, end_time_measurement(start) - - def evaluate(self, solution: dict) -> tuple[float, float]: - """ - Calculates the quality of the solution. - - :param solution: Dictionary containing the solution - :return: Tour length, time it took to calculate the tour length - """ - start = start_time_measurement() - logging.info("Checking the quality of the solution:") - - # Count the number of satisfied clauses - nr_satisfied_tests = len(*np.where([test.satisfied_by(solution) for test in self.application["tests"]])) - - ratio_satisfied = nr_satisfied_tests / self.num_tests - logging.info(f"Ratio of satisfied test clauses: {ratio_satisfied}.") - - return ratio_satisfied, end_time_measurement(start) - - def save(self, path: str, iter_count: int) -> None: - """ - Save the constraints and tests to files in CNF format. - - :param path: The directory path where the files will be saved. - :param iter_count: The iteration count to include in the filenames. - """ - with open(f"{path}/constraints_iter_{iter_count}.cnf", "w") as f_cons: - dump( - obj=self.application["constraints"], - fp=f_cons, - var_labels={str(literal): idx + 1 for idx, literal in enumerate(self.literals)} - ) - with open(f"{path}/tests_iter_{iter_count}.cnf", "w") as f_test: - dump( - obj=Or(self.application["tests"]), - fp=f_test, - var_labels={str(literal): idx + 1 for idx, literal in enumerate(self.literals)} - ) diff --git a/src/modules/applications/optimization/SAT/mappings/ChoiISING.py b/src/modules/applications/optimization/SAT/mappings/ChoiISING.py deleted file mode 100644 index 62639ebe..00000000 --- a/src/modules/applications/optimization/SAT/mappings/ChoiISING.py +++ /dev/null @@ -1,156 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from typing import TypedDict - -import numpy as np -from dimod import qubo_to_ising - -from modules.applications.mapping import Mapping, Core -from modules.applications.optimization.sat.mappings.choiqubo import ChoiQUBO -from utils import start_time_measurement, end_time_measurement - - -class ChoiIsing(Mapping): - """ - Ising formulation for SAT problem using QUBO by Choi (1004.2226). - """ - - def __init__(self): - """ - Constructor method. - """ - super().__init__() - self.submodule_options = ["QAOA", "PennylaneQAOA"] - self.problem = None - self.qubo_mapping = None - - @staticmethod - def get_requirements() -> list[dict]: - """ - Return requirements of this module. - - :return: List of dict with requirements of this module - """ - return [ - {"name": "numpy", "version": "1.26.4"}, - {"name": "dimod", "version": "0.12.18"}, - *ChoiQUBO.get_requirements() - ] - - def get_parameter_options(self) -> dict: - """ - Returns the configurable settings for this mapping. - - :return: Dictionary with parameter options - .. code-block:: python - - return { - "hard_reward": { - "values": [0.1, 0.5, 0.9, 0.99], - "description": "What Bh/A ratio do you want? (How strongly to enforce hard constraints)" - }, - "soft_reward": { - "values": [0.1, 1, 2], - "description": "What Bh/Bs ratio do you want? This value is multiplied with the " - "number of tests." - } - } - """ - return { - "hard_reward": { - "values": [0.1, 0.5, 0.9, 0.99], - "description": "What Bh/A ratio do you want? (How strongly to enforce hard constraints)" - }, - "soft_reward": { - "values": [0.1, 1, 2], - "description": "What Bh/Bs ratio do you want? This value is multiplied with the number of tests." - } - } - - class Config(TypedDict): - """ - Attributes of a valid config. - - .. code-block:: python - - hard_reward: float - soft_reward: float - - """ - hard_reward: float - soft_reward: float - - def map(self, problem: any, config: Config) -> tuple[dict, float]: - """ - Uses the ChoiQUBO formulation and converts it to an Ising. - - :param problem: SAT problem - :param config: Dictionary with the mapping config - :return: Dict with the ising, time it took to map it - """ - start = start_time_measurement() - self.problem = problem - - # call mapping function - self.qubo_mapping = ChoiQUBO() - q, _ = self.qubo_mapping.map(problem, config) - t, j, _ = qubo_to_ising(q["Q"]) - - # Convert Ising dict to matrix - n = (len(problem[0]) + len(problem[1])) * 3 - t_vector = np.zeros(n, dtype=float) - j_matrix = np.zeros((n, n), dtype=float) - - for key, value in t.items(): - t_vector[key] = value - - for key, value in j.items(): - j_matrix[key[0]][key[1]] = value - - return {"J": j_matrix, "t": t_vector}, end_time_measurement(start) - - def reverse_map(self, solution: dict) -> tuple[dict, float]: - """ - Maps the solution back to the representation needed by the SAT class for validation/evaluation. - - :param solution: Dictionary containing the solution - :return: Solution mapped accordingly, time it took to map it - """ - start = start_time_measurement() - - # convert raw solution into the right format to use reverse_map() of ChoiQUBO.py - solution_dict = dict(enumerate(solution)) - - # reverse map - result, _ = self.qubo_mapping.reverse_map(solution_dict) - - return result, end_time_measurement(start) - - def get_default_submodule(self, option: str) -> Core: - """ - Returns the default submodule based on the provided option. - - :param option: Option specifying the submodule - :return: Instance of the corresponding submodule - :raises NotImplementedError: If the option is not recognized - """ - if option == "QAOA": - from modules.solvers.qaoa import QAOA # pylint: disable=C0415 - return QAOA() - if option == "PennylaneQAOA": - from modules.solvers.pennylane_qaoa import PennylaneQAOA # pylint: disable=C0415 - return PennylaneQAOA() - else: - raise NotImplementedError(f"Solver Option {option} not implemented") diff --git a/src/modules/applications/optimization/SAT/mappings/ChoiQUBO.py b/src/modules/applications/optimization/SAT/mappings/ChoiQUBO.py deleted file mode 100644 index 2a1651a5..00000000 --- a/src/modules/applications/optimization/SAT/mappings/ChoiQUBO.py +++ /dev/null @@ -1,250 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from itertools import combinations, product -from typing import TypedDict -import logging - -from nnf import Var, And - -from modules.applications.mapping import Mapping, Core -from utils import start_time_measurement, end_time_measurement - - -class ChoiQUBO(Mapping): - """ - QUBO formulation for SAT problem by Choi (1004.2226). - """ - - def __init__(self): - """ - Constructor method. - """ - super().__init__() - self.submodule_options = ["Annealer"] - self.nr_vars = None - self.reverse_dict = None - - @staticmethod - def get_requirements() -> list[dict]: - """ - Return requirements of this module. - - :return: List of dict with requirements of this module - """ - return [{"name": "nnf", "version": "0.4.1"}] - - def get_parameter_options(self) -> dict: - """ - Returns the configurable settings for this mapping. - - :return: Dictionary with parameter options - .. code-block:: python - - return { - "hard_reward": { - "values": [0.1, 0.5, 0.9, 0.99], - "description": "What Bh/A ratio do you want? (How strongly to enforce hard constraints)" - }, - "soft_reward": { - "values": [0.1, 1, 2], - "description": "What Bh/Bs ratio do you want? This value is multiplied with the " - "number of tests." - } - } - """ - return { - "hard_reward": { - "values": [0.1, 0.5, 0.9, 0.99], - "description": ( - "What Bh/A ratio do you want?" - "(How strongly to enforce hard constraints)" - ) - }, - "soft_reward": { - "values": [0.1, 1, 2], - "description": ( - "What Bh/Bs ratio do you want?" - "This value is multiplied with the number of tests." - ) - } - } - - class Config(TypedDict): - """ - Attributes of a valid config. - - .. code-block:: python - - hard_reward: float - soft_reward: float - - """ - hard_reward: float - soft_reward: float - - def map(self, problem: tuple[And, list], config: Config) -> tuple[dict, float]: - """ - Converts a MaxSAT instance with hard and soft constraints into a graph problem -- - solving MaxSAT then corresponds to solving an instance of the Maximal Independent Set problem. - See Andrew Lucas (2014), or the original publication by Choi (1004.2226). - - :param problem: A tuple containing hard and soft constraints - :param config: Config with the parameters specified in Config class - :return: Dictionary containing the QUBO representation and the time taken - """ - start = start_time_measurement() - - hard_constraints, soft_constraints = problem - a = 1 - bh = config['hard_reward'] * a - # divide Bh by the number of test clauses, such that fulfilling a test result is less favourable than - # satisfying a constraint, which aim to prioritize. - bs = bh * config['soft_reward'] / len(soft_constraints) - - # Count the number of different variables that appear in the vehicle options problem: - self.nr_vars = len(hard_constraints.vars().union(And(soft_constraints).vars())) - # Edges variable holds all edges in the resulting graph - edges = {} - # lit_occur is a dictionary which will store the information in which clause a certain literal will occur. - lit_occur = {} - - def _add_clause(clause, curr_edges, curr_lit_occ, pos): - literals = [f"{el}-{pos}" for el in clause.children] - # Connect the literals within one clause - for cmb in combinations(literals, 2): - # Add a weight for each edge within clause - curr_edges[cmb] = a - # Add the occurrences of the variables to the occurrences dictionary - for var in clause.children: - if var.name not in curr_lit_occ.keys(): - curr_lit_occ[var.name] = {True: [], False: []} - # Add occurrences and mark that they correspond to hard constraints - curr_lit_occ[var.name][var.true].append(pos) - return curr_edges, curr_lit_occ - - # Convert the hard constraints into the graph - for idx, hard_constraint in enumerate(hard_constraints): - edges, lit_occur = _add_clause(hard_constraint, edges, lit_occur, idx) - - # Save the current total clause count: - constraints_max_ind = len(hard_constraints) - # Repeat the procedure for the soft constraints: - for idx, soft_constraint in enumerate(soft_constraints): - edges, lit_occur = _add_clause(soft_constraint, edges, lit_occur, idx + constraints_max_ind) - - # Connect conflicting clauses using the lit_occur dict: - for literal, positions_dict in lit_occur.items(): - # for every literal lit, we check its occurrences and connect the non-negated and negated occurrences. - for pos_true, pos_false in product(positions_dict[True], positions_dict[False]): - if pos_true != pos_false: - # Employ the notation from nnf, where the tilde symbol ~ corresponds to negation. - lit_true, lit_false = f"{literal}-{pos_true}", f"~{literal}-{pos_false}" - # Add a penalty for each such edge: - edges[(lit_true, lit_false)] = a - - # Collect all different nodes that we have in our graph, omitting repetitions: - node_set = set([]) - for nodes in edges.keys(): - node_set = node_set.union(set(nodes)) - - node_list = sorted(node_set) - # Fix a mapping (node -> binary variable) - relabel_dict = {v: i for i, v in enumerate(node_list)} - # Save the reverse mapping, which is later used to decode the solution. - self.reverse_dict = dict(enumerate(node_list)) - - def _remap_pair(pair): - """Small helper function that maps the nodes of an edge to binary variables""" - return relabel_dict[pair[0]], relabel_dict[pair[1]] - - # Save the QUBO corresponding to the graph. - q = {_remap_pair(key): val for key, val in edges.items()} - - for v in node_list: - # Add different energy rewards depending on whether it is a hard or a soft constraint - if int(v.split('-')[-1]) < constraints_max_ind: - # if hard cons, add -Bh as the reward - q[_remap_pair((v, v))] = -bh - else: - # for soft constraints, add -Bs - q[_remap_pair((v, v))] = -bs - - logging.info(f"Converted to Choi QUBO with {len(node_list)} binary variables. Bh={config['hard_reward']}," - f" Bs={bs}.") - return {'Q': q}, end_time_measurement(start) - - def reverse_map(self, solution: dict) -> tuple[dict, float]: - """ - Maps the solution back to the representation needed by the SAT class for validation/evaluation. - - :param solution: Dictionary containing the solution - :return: Solution mapped accordingly, time it took to map it - """ - start = start_time_measurement() - # We define the literals list, so that we can check the self-consistency of the solution. That is, we save all - # assignments proposed by the annealer, and see if there is no contradiction. (In principle a solver - # could mandate L3 = True and L3 = False, resulting in a contradiction.) - literals = [] - # Assignments saves the actual solution - assignments = [] - - for node, tf in solution.items(): - # Check if node is included in the set (i.e. if tf is True (1)) - if tf: - # Convert back to the language of literals - lit_str = self.reverse_dict[node] - # Check if the literal is negated: - if lit_str.startswith('~'): - # Remove the negation symbol - lit_str = lit_str.replace('~', '') - # Save a negated literal object, will be used for self-consistency check - lit = Var(lit_str).negate() - # Add the negated literal to the assignments, removing the (irrelevant) position part - assignments.append(Var(lit_str.split('-')[0]).negate()) - else: - # If literal is true, no ~ symbol needs to be removed: - lit = Var(lit_str) - assignments.append(Var(lit_str.split('-')[0])) - literals.append(lit) - - # Check for self-consistency of solution; Check that the assignments of all literals are consistent: - if not And(set(literals)).satisfiable(): - logging.error('Generated solution is not self-consistent!') - raise ValueError("Inconsistent solution for the ChoiQubo returned.") - - # If the solution is consistent, find and add potentially missing variables: - assignments = sorted(set(assignments)) - # Find missing vars, or more precisely, their labels: - missing_vars = set(range(self.nr_vars)) - {int(str(a).replace('L', '').replace('~', '')) for a in assignments} - - # Add the variables that found were missing: - for nr in missing_vars: - assignments.append(Var(f'L{nr}')) - - return {list(v.vars())[0]: v.true for v in sorted(assignments)}, end_time_measurement(start) - - def get_default_submodule(self, option: str) -> Core: - """ - Returns the default submodule based on the provided option. - - :param option: Option specifying the submodule - :return: Instance of the corresponding submodule - :raises NotImplementedError: If the option is not recognized - """ - if option == "Annealer": - from modules.solvers.annealer import Annealer # pylint: disable=C0415 - return Annealer() - else: - raise NotImplementedError(f"Solver Option {option} not implemented") diff --git a/src/modules/applications/optimization/SAT/mappings/DinneenISING.py b/src/modules/applications/optimization/SAT/mappings/DinneenISING.py deleted file mode 100644 index 89e58672..00000000 --- a/src/modules/applications/optimization/SAT/mappings/DinneenISING.py +++ /dev/null @@ -1,148 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from typing import TypedDict - -import numpy as np -from dimod import qubo_to_ising -from nnf import And - -from modules.applications.mapping import Mapping, Core -from modules.applications.optimization.sat.mappings.dinneenqubo import DinneenQUBO -from utils import start_time_measurement, end_time_measurement - - -class DinneenIsing(Mapping): - """ - Ising formulation for SAT using Dinneen QUBO. - """ - - def __init__(self): - """ - Constructor method. - """ - super().__init__() - self.submodule_options = ["QAOA", "PennylaneQAOA"] - self.problem = None - self.qubo_mapping = None - - @staticmethod - def get_requirements() -> list[dict]: - """ - Return requirements of this module. - - :return: List of dict with requirements of this module - """ - return [ - {"name": "nnf", "version": "0.4.1"}, - {"name": "numpy", "version": "1.26.4"}, - {"name": "dimod", "version": "0.12.18"}, - *DinneenQUBO.get_requirements() - ] - - def get_parameter_options(self) -> dict: - """ - Returns the configurable settings for this mapping. - - :return: Dictionary with parameter options - .. code-block:: python - - return { - "lagrange": { - "values": [0.1, 1, 2], - "description": "What Lagrange parameter to multiply with the number of (hard) " - "constraints?" - } - } - """ - return { - "lagrange": { - "values": [0.1, 1, 2], - "description": "What Lagrange parameter to multiply with the number of (hard) constraints?" - } - } - - class Config(TypedDict): - """ - Attributes of a valid config. - - .. code-block:: python - - lagrange: float - - """ - lagrange: float - - def map(self, problem: any, config: Config) -> tuple[dict, float]: - """ - Uses the DinneenQUBO formulation and converts it to an Ising. - - :param problem: SAT problem - :param config: Dictionary with the mapping config - :return: Dict with the ising, time it took to map it - """ - start = start_time_measurement() - self.problem = problem - - # call mapping function - self.qubo_mapping = DinneenQUBO() - q, _ = self.qubo_mapping.map(problem, config) - t, j, _ = qubo_to_ising(q["Q"]) - - # Convert Ising dict to matrix - n = (len(problem[0]) + len(problem[1])) + len(problem[0].vars().union(And(problem[1]).vars())) - t_vector = np.zeros(n, dtype=float) - j_matrix = np.zeros((n, n), dtype=float) - - for key, value in t.items(): - t_vector[key] = value - - for key, value in j.items(): - j_matrix[key[0]][key[1]] = value - - return {"J": j_matrix, "t": t_vector}, end_time_measurement(start) - - def reverse_map(self, solution: dict) -> tuple[dict, float]: - """ - Maps the solution back to the representation needed by the SAT class for validation/evaluation. - - :param solution: Dictionary containing the solution - :return: Solution mapped accordingly, time it took to map it - """ - start = start_time_measurement() - - # Convert raw solution into the right format to use reverse_map() of ChoiQUBO.py - solution_dict = dict(enumerate(solution)) - - # Reverse map - result, _ = self.qubo_mapping.reverse_map(solution_dict) - - return result, end_time_measurement(start) - - def get_default_submodule(self, option: str) -> Core: - """ - Returns the default submodule based on the provided option. - - :param option: Option specifying the submodule - :return: Instance of the corresponding submodule - :raises NotImplementedError: If the option is not recognized - """ - if option == "QAOA": - from modules.solvers.qaoa import QAOA # pylint: disable=C0415 - return QAOA() - if option == "PennylaneQAOA": - from modules.solvers.pennylane_qaoa import PennylaneQAOA # pylint: disable=C0415 - return PennylaneQAOA() - else: - raise NotImplementedError(f"Solver Option {option} not implemented") diff --git a/src/modules/applications/optimization/SAT/mappings/DinneenQUBO.py b/src/modules/applications/optimization/SAT/mappings/DinneenQUBO.py deleted file mode 100644 index 791e30f9..00000000 --- a/src/modules/applications/optimization/SAT/mappings/DinneenQUBO.py +++ /dev/null @@ -1,198 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from itertools import combinations -from typing import TypedDict -import logging - -from nnf import And - -from modules.applications.mapping import Mapping, Core -from utils import start_time_measurement, end_time_measurement - - -class DinneenQUBO(Mapping): - """ - QUBO formulation for SAT as given by Dinneen -- see also the description in the QUARK paper (2202.03028). - """ - - def __init__(self): - """ - Constructor method. - """ - super().__init__() - self.submodule_options = ["Annealer"] - self.nr_vars = None - - @staticmethod - def get_requirements() -> list[dict]: - """ - Return requirements of this module. - - :return: List of dict with requirements of this module - """ - return [{"name": "nnf", "version": "0.4.1"}] - - def get_parameter_options(self) -> dict: - """ - Returns the configurable settings for this mapping. - - :return: Dictionary with parameter options - .. code-block:: python - - return { - "lagrange": { - "values": [0.1, 1, 2], - "description": "What Lagrange param. to multiply with the number of (hard) constr.?" - } - } - """ - return { - "lagrange": { - "values": [0.1, 1, 2], - "description": "What Lagrange parameter to multiply with the number of (hard) constraints?" - } - } - - class Config(TypedDict): - """ - Attributes of a valid config. - - .. code-block:: python - - lagrange: float - - """ - lagrange: float - - def map(self, problem: tuple[And, list], config: Config) -> tuple[dict, float]: - """ - Performs the mapping into a QUBO formulation, as given by Dinneen. See also the QUARK paper. - - :param problem: SAT problem - :param config: Config with the parameters specified in Config class - :return: Tuple with the QUBO, time it took to map it - """"" - start = start_time_measurement() - - # Extract hard and soft constraints from the generated problem - hard, soft = problem - - # Count the variables - self.nr_vars = len(hard.vars().union(And(soft).vars())) - lagrange = config['lagrange'] - # Lagrange parameter is a factor of the number of soft constraints. - lagrange *= len(soft) - - def _add_clause(curr_qubo_dict: dict[tuple[int, int], float], - clause: any, - pos: int, - weight: float) -> dict[tuple[int, int], float]: - """ - Function that adds the QUBO terms corresponding to the clause and updates the QUBO dictionary - accordingly. Additionally, the weight of the clause is taken into account. - - :param curr_qubo_dict: Current QUBO dictionary - :param clause: Clause to be added - :param pos: Position of the auxiliary variable - :param weight: Weight of the clause - :return: Updated QUBO dictionary - """ - - def _check_and_add(dictionary: dict, key: tuple[int, int], value: float) -> dict: - """ - Helper function that checks if key is present or not in dictionary and adds a value, adding the key - if missing. - - :param dictionary: Dictionary to be updated - :param key: Key to check in the dictionary - :param value: Value to add to the key - :return: Updated dictionary - """ - key = tuple(sorted(key)) - if key not in dictionary.keys(): - dictionary[key] = value - else: - dictionary[key] += value - return dictionary - - cl_dict = {} - for variable in clause.children: - for variable_name in variable.vars(): - # Transforms the negations (0,1) into signs (-1, 1) - cl_dict[int(variable_name[1:])] = (int(variable.true) - 1 / 2) * 2 - - # Add the linear term of the auxiliary variable w - curr_qubo_dict = _check_and_add(curr_qubo_dict, (pos, pos), 2 * weight) - - # Add x linear terms and xw terms. - for qvar, val in cl_dict.items(): - # qvar is the name of the var, val is the sign corresponding to whether the variable is negated or not. - # linear x term: - curr_qubo_dict = _check_and_add(curr_qubo_dict, (qvar, qvar), -weight * val) - # x * w (aux. var.) term - curr_qubo_dict = _check_and_add(curr_qubo_dict, (qvar, pos), -weight * val) - # Add combinations - for q1, q2 in combinations(cl_dict.keys(), 2): - curr_qubo_dict = _check_and_add(curr_qubo_dict, (q1, q2), weight * cl_dict[q1] * cl_dict[q2]) - - return curr_qubo_dict - - qubo_dict = {} - # Add the hard constraints and add the lagrange parameter as weight - for clause_ind, hard_clause in enumerate(hard): - qubo_dict = _add_clause(qubo_dict, hard_clause, self.nr_vars + clause_ind, lagrange) - - # Add the soft constraints and start the enumeration at the final index corresponding to hard cons. - for clause_ind, soft_clause in enumerate(soft): - qubo_dict = _add_clause(qubo_dict, soft_clause, self.nr_vars + clause_ind + len(hard), 1) - - logging.info( - f"Generate Dinneen QUBO with {self.nr_vars + len(hard) + len(soft)} binary variables." - f" Lagrange parameter used was: {config['lagrange']}." - ) - - return {"Q": qubo_dict}, end_time_measurement(start) - - def reverse_map(self, solution: dict) -> tuple[dict, float]: - """ - Reverse mapping of the solution obtained from the Dinneen QUBO. - - :param solution: Dictionary containing the solution - :return: Solution mapped accordingly, time it took to map it - """ - start = start_time_measurement() - mapped_sol = {} - for i in range(self.nr_vars): - # if variable not present in solution, its assignment does not matter - if i not in solution.keys(): - mapped_sol[f'L{i}'] = True - else: - mapped_sol[f'L{i}'] = bool(solution[i]) - - return mapped_sol, end_time_measurement(start) - - def get_default_submodule(self, option: str) -> Core: - """ - Returns the default submodule based on the provided option. - - :param option: Option specifying the submodule - :return: Instance of the corresponding submodule - :raises NotImplementedError: If the option is not recognized - """ - if option == "Annealer": - from modules.solvers.annealer import Annealer # pylint: disable=C0415 - return Annealer() - else: - raise NotImplementedError(f"Solver Option {option} not implemented") diff --git a/src/modules/applications/optimization/SAT/mappings/Direct.py b/src/modules/applications/optimization/SAT/mappings/Direct.py deleted file mode 100644 index 85b874c4..00000000 --- a/src/modules/applications/optimization/SAT/mappings/Direct.py +++ /dev/null @@ -1,140 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import io -from typing import TypedDict -import logging - -from nnf import And -from nnf.dimacs import dump -from pysat.formula import CNF, WCNF - -from modules.applications.mapping import Mapping, Core -from utils import start_time_measurement, end_time_measurement - - -class Direct(Mapping): - """ - Maps the problem from nnf to pysat. - """ - - def __init__(self): - """ - Constructor method. - """ - super().__init__() - self.submodule_options = ["ClassicalSAT", "RandomSAT"] - - @staticmethod - def get_requirements() -> list[dict]: - """ - Return requirements of this module. - - :return: List of dict with requirements of this module - """ - return [ - {"name": "nnf", "version": "0.4.1"}, - {"name": "python-sat", "version": "1.8.dev13"} - ] - - def get_parameter_options(self) -> dict: - """ - Returns empty dict as this mapping has no configurable settings. - - :return: Empty dict - """ - return {} - - class Config(TypedDict): - """ - Empty config as this solver has no configurable settings. - """ - pass - - def map(self, problem: tuple[And, list], config: Config) -> tuple[WCNF, float]: - """ - Map from the nnf library into the python-sat library. - - :param problem: SAT problem - :param config: Config with the parameters specified in Config class - :return: Mapped problem and the time it took to map it - """ - start = start_time_measurement() - hard_constraints, soft_constraints = problem - - # Get number of vars. The union is required in case not all vars are present in either tests/constraints. - nr_vars = len(hard_constraints.vars().union(And(soft_constraints).vars())) - - # Create a var_labels dictionary that will be used when mapping to pysat - litdic = {f'L{i - 1}': i for i in range(1, nr_vars + 1)} - - # The most convenient way to map between nnf and pysat was to use the native nnf dump function, which exports - # the problem as a string, which we can then quickly reload from a buffer. - - # Create buffers for dumping: - hard_buffer = io.StringIO() - soft_buffer = io.StringIO() - - # Dump constraints and tests to their respective buffers - dump(hard_constraints, hard_buffer, var_labels=litdic, mode='cnf') - # tests have to be conjoined, since we will add them as soft constraints. - dump(And(soft_constraints), soft_buffer, var_labels=litdic, mode='cnf') - - # Load the cnfs from the buffers: - hard_cnf = CNF(from_string=hard_buffer.getvalue()) - soft_cnf = CNF(from_string=soft_buffer.getvalue()) - - # Create wcnf instance. - total_wcnf = WCNF() - - # Add hard constraints: - total_wcnf.extend(hard_cnf) - - # Add soft constraints, with weights. - total_wcnf.extend(soft_cnf, weights=[1] * len(soft_cnf.clauses)) - - logging.info( - f'Generated pysat wcnf with {len(total_wcnf.hard)} constraints and {len(total_wcnf.soft)} tests.' - ) - - return total_wcnf, end_time_measurement(start) - - def get_default_submodule(self, option: str) -> Core: - """ - Returns the default submodule based on the provided option. - - :param option: Option specifying the submodule - :return: Instance of the corresponding submodule - :raises NotImplementedError: If the option is not recognized - """ - if option == "ClassicalSAT": - from modules.solvers.classical_sat import ClassicalSAT # pylint: disable=C0415 - return ClassicalSAT() - elif option == "RandomSAT": - from modules.solvers.random_classical_sat import RandomSAT # pylint: disable=C0415 - return RandomSAT() - else: - raise NotImplementedError(f"Solver Option {option} not implemented") - - def reverse_map(self, solution: list) -> tuple[dict, float]: - """ - Maps the solution returned by the pysat solver into the reference format. - - :param solution: List containing the solution - :return: Solution mapped accordingly, time it took to map it - """ - start = start_time_measurement() - # converts from (3 / -3) -> (L2 : True / L2: False) - mapped_sol = {f'L{abs(lit) - 1}': (lit > 0) for lit in solution} - return mapped_sol, end_time_measurement(start) diff --git a/src/modules/applications/optimization/SAT/mappings/QubovertQUBO.py b/src/modules/applications/optimization/SAT/mappings/QubovertQUBO.py deleted file mode 100644 index 3b7e2250..00000000 --- a/src/modules/applications/optimization/SAT/mappings/QubovertQUBO.py +++ /dev/null @@ -1,195 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import logging -from typing import TypedDict - -from qubovert.sat import NOT, OR, AND -from nnf import And - -from modules.applications.mapping import Mapping, Core -from utils import start_time_measurement, end_time_measurement - - -class QubovertQUBO(Mapping): - """ - Qubovert formulation of the vehicle-options problem. - """ - - def __init__(self): - """ - Constructor method. - """ - super().__init__() - self.submodule_options = ["Annealer"] - self.pubo_problem = None - self.nr_vars = None - - @staticmethod - def get_requirements() -> list[dict]: - """ - Return requirements of this module. - - :return: List of dict with requirements of this module - """ - return [ - {"name": "nnf", "version": "0.4.1"}, - {"name": "qubovert", "version": "1.2.5"} - ] - - def get_parameter_options(self) -> dict: - """ - Returns the configurable settings for this mapping. - - :return: Dict with configurable settings - .. code-block:: python - - return { - "lagrange": { - "values": [0.1, 1, 1.5, 2, 5, 10, 1000, 10000], - "description": "By which factor would you like to multiply your Lagrange?" - } - } - """ - return { - "lagrange": { - "values": [0.1, 1, 1.5, 2, 5, 10, 1000, 10000], - "description": "By which factor would you like to multiply your Lagrange?" - } - } - - class Config(TypedDict): - """ - Attributes of a valid config. - - .. code-block:: python - - lagrange: float - - """ - lagrange: float - - @staticmethod - def _constraints2qubovert(constraints: any) -> AND: - """ - Converts the constraints nnf to a PUBO in the qubovert library. - - :param constraints: Constraints in nnf format - :return: Constraints in qubovert format - """ - clauses = [] - for c in constraints.children: - literals = [v.name if v.true else NOT(v.name) for v in c.children] - clauses.append(OR(*literals)) - return AND(*clauses) - - @staticmethod - def _tests2qubovert(test_clauses: dict) -> sum: - """ - Converts the list of test clauses in the nnf format to a PUBO. - - :param test_clauses: Test clauses in nnf format - :return: Sum of mapped test clauses - """ - mapped_tests = [] - - for test_clause in test_clauses: - mapped_tests.append(OR(*[v.name if v.true else NOT(v.name) for v in test_clause.children])) - - return sum(mapped_tests) - - def map(self, problem: any, config: Config) -> tuple[dict, float]: - """ - Converts the problem to a QUBO in dictionary format. Problem is a CNF formula from the nnf library. - - :param problem: SAT problem - :param config: Config with the parameters specified in Config class - :return: Dict with the QUBO, time it took to map it - """ - start = start_time_measurement() - lagrange = config['lagrange'] - - constraints, test_clauses = problem - - # Find number of the variables that appear in the tests and constraints, to verify the reverse mapping. - self.nr_vars = len(constraints.vars().union(And(test_clauses).vars())) - - # Convert the constraints to qubovert: - constraints_pubo = self._constraints2qubovert(constraints) - - # Convert the tests into qubovert: - tests_pubo = self._tests2qubovert(test_clauses) - logging.info(f'{tests_pubo.to_qubo().num_terms} number of terms in tests qubo') - lagrange *= len(test_clauses) - - # Define the total PUBO problem: - self.pubo_problem = -(tests_pubo + lagrange * constraints_pubo) - - # Convert to qubo: - qubo_problem = self.pubo_problem.to_qubo() - qubo_problem.normalize() - logging.info(f"Converted to QUBO with {qubo_problem.num_binary_variables} Variables." - f" Lagrange parameter: {config['lagrange']}.") - - # Convert it to the right format to be accepted by Braket / Dwave - q_dict = {} - - for k, v in qubo_problem.items(): - # "interaction (quadratic) terms": - if len(k) == 2: - if (k[0], k[1]) not in q_dict: - q_dict[(k[0], k[1])] = float(v) - else: - q_dict[(k[0], k[1])] += float(v) - # "local (linear) fields": - if len(k) == 1: - if (k[0], k[0]) not in q_dict: - q_dict[(k[0], k[0])] = float(v) - else: - q_dict[(k[0], k[0])] += float(v) - - return {"Q": q_dict}, end_time_measurement(start) - - def reverse_map(self, solution: dict) -> tuple[dict, float]: - """ - Maps the solution back to the representation needed by the SAT class for validation/evaluation. - - :param solution: Dictionary containing the solution - :return: Solution mapped accordingly, time it took to map it - """ - start = start_time_measurement() - pubo_sol = self.pubo_problem.convert_solution(solution) - - # Check if all variables appear in the solution. - missing_vars = {f'L{i}' for i in range(self.nr_vars)} - set(pubo_sol.keys()) - - # Add values for the missing variables -- if they do not appear, then their assignment does not matter. - for missing_var in missing_vars: - pubo_sol[missing_var] = True - - return pubo_sol, end_time_measurement(start) - - def get_default_submodule(self, option: str) -> Core: - """ - Returns the default submodule based on the provided option. - - :param option: Option specifying the submodule - :return: Instance of the corresponding submodule - :raises NotImplementedError: If the option is not recognized - """ - if option == "Annealer": - from modules.solvers.annealer import Annealer # pylint: disable=C0415 - return Annealer() - else: - raise NotImplementedError(f"Solver Option {option} not implemented") diff --git a/src/modules/applications/optimization/SCP/SCP.py b/src/modules/applications/optimization/SCP/SCP.py deleted file mode 100644 index cf533dbb..00000000 --- a/src/modules/applications/optimization/SCP/SCP.py +++ /dev/null @@ -1,174 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from typing import TypedDict -import pickle -import os - -from modules.applications.application import Application -from modules.applications.optimization.optimization import Optimization -from utils import start_time_measurement, end_time_measurement - - -class SCP(Optimization): - """ - The set cover problem (SCP) is a classical combinatorial optimization problem where the objective is to find the - smallest subset of given elements that covers all required elements in a collection. This can be formulated as - selecting the minimum number of sets from a collection such that the union of the selected sets contains all - elements from the universe of the problem instance. - - SCP has widespread applications in various fields, including sensor positioning, resource allocation, and network - design. For example, in sensor positioning, SCP can help determine the fewest number of sensors required to cover - a given area. Similarly, in resource allocation, SCP helps to allocate resources in an optimal way, ensuring - coverage of all demand points while minimizing costs. Network design also uses SCP principles to efficiently place - routers or gateways in a network to ensure full coverage with minimal redundancy. - - This implementation of SCP provides configurable problem instances of different sizes, such as "Tiny," "Small," - and "Large," allowing the user to explore solutions with varying complexities. We employ various quantum-inspired - methods to solve SCP, including a mapping to the QUBO (Quadratic Unconstrained Binary Optimization) formulation - using Qubovert. These approaches allow us to explore how different optimization algorithms and frameworks perform - when applied to this challenging problem, offering insights into both classical and emerging quantum methods. - """ - - def __init__(self): - """ - Constructor method. - """ - super().__init__("SCP") - self.submodule_options = ["qubovertQUBO"] - - def get_solution_quality_unit(self) -> str: - return "Number of selected subsets" - - def get_default_submodule(self, option: str) -> Application: - """ - Returns the default submodule based on the provided option. - - :param option: Option specifying the submodule - :return: Instance of the corresponding submodule - :raises NotImplementedError: If the option is not recognized - """ - if option == "qubovertQUBO": - from modules.applications.optimization.scp.mappings.qubovertqubo import QubovertQUBO # pylint: disable=C0415 - return QubovertQUBO() - else: - raise NotImplementedError(f"Mapping Option {option} not implemented") - - def get_parameter_options(self): - """ - Returns the configurable settings for this application - - :return: Dictionary containing parameter options - .. code-block:: python - - return { - "model_select": { - "values": list(["Tiny", "Small", "Large"]), - "description": "Please select the problem size(s). Tiny: 4 elements, 3 subsets. Small: - 15 elements, 8 subsets. Large: 100 elements, 100 subsets" - } - } - """ - return { - "model_select": { - "values": list(["Tiny", "Small", "Large"]), - "description": "Please select the problem size(s). Tiny: 4 elements, 3 subsets. Small: 15 elements, " - "8 subsets. Large: 100 elements, 100 subsets" - } - } - - class Config(TypedDict): - model_select: str - - def generate_problem(self, config: Config) -> tuple[set, list]: - """ - Generates predefined instances of the SCP. - - :param config: Config specifying the selected problem instances - :return: The union of all elements of an instance and a set of subsets, each covering a part of the union - """ - model_select = config['model_select'] - self.application = {} - - if model_select == "Tiny": - self.application["elements_to_cover"] = set(range(1, 4)) - self.application["subsets"] = [{1, 2}, {1, 3}, {3, 4}] - elif model_select == "Small": - self.application["elements_to_cover"] = set(range(1, 15)) - self.application["subsets"] = [ - {1, 3, 4, 6, 7, 13}, {4, 6, 8, 12}, {2, 5, 9, 11, 13}, {1, 2, 7, 14, 15}, - {3, 10, 12, 14}, {7, 8, 14, 15}, {1, 2, 6, 11}, {1, 2, 4, 6, 8, 12} - ] - - elif model_select == "Large": - self.application["elements_to_cover"] = set(range(1, 100)) - self.application["subsets"] = [] - path = os.path.join(os.path.dirname(__file__)) - with open(f"{path}/data/set_cover_data_large.txt") as data: - while line := data.readline(): - new_set = [] - for i in line.split(','): - new_set.append(int(i)) - new_set = set(new_set) - self.application["subsets"].append(new_set) - - else: - raise ValueError(f"Unknown model_select value: {model_select}") - - return self.application["elements_to_cover"], self.application["subsets"] - - def process_solution(self, solution: list) -> tuple[list, float]: - """ - Returns list of selected subsets and the time it took to process the solution. - - :param solution: Unprocessed solution - :return: Processed solution and the time it took to process it - """ - start_time = start_time_measurement() - selected_subsets = [list(self.application["subsets"][i]) for i in solution] - return selected_subsets, end_time_measurement(start_time) - - def validate(self, solution: list) -> tuple[bool, float]: - """ - Checks if the elements of the subsets that are part of the solution cover every element of the instance. - - :param solution: List containing all subsets that are part of the solution - :return: Boolean whether the solution is valid and time it took to validate - """ - start = start_time_measurement() - covered = set.union(*[set(subset) for subset in solution]) - - return covered == self.application["elements_to_cover"], end_time_measurement(start) - - def evaluate(self, solution: list) -> tuple[int, float]: - """ - Calculates the number of subsets that are of the solution. - - :param solution: List containing all subsets that are part of the solution - :return: Number of subsets and the time it took to calculate it - """ - start = start_time_measurement() - selected_num = len(solution) - - return selected_num, end_time_measurement(start) - - def save(self, path: str, iter_count: int) -> None: - """ - Saves the SCP instance to a file. - - :param path: Path to save the SCP instance - :param iter_count: Iteration count - """ - with open(f"{path}/SCP_instance", "wb") as file: - pickle.dump(self.application, file, pickle.HIGHEST_PROTOCOL) diff --git a/src/modules/applications/optimization/SCP/mappings/qubovertQUBO.py b/src/modules/applications/optimization/SCP/mappings/qubovertQUBO.py deleted file mode 100644 index 6e852018..00000000 --- a/src/modules/applications/optimization/SCP/mappings/qubovertQUBO.py +++ /dev/null @@ -1,145 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import logging -from typing import TypedDict - -from qubovert.problems import SetCover -from modules.applications.mapping import Mapping, Core -from utils import start_time_measurement, end_time_measurement - - -class QubovertQUBO(Mapping): - """ - Qubovert formulation of the vehicle-options problem. - """ - - def __init__(self): - """ - Constructor method. - """ - super().__init__() - self.submodule_options = ["Annealer"] - - @staticmethod - def get_requirements() -> list[dict]: - """ - Return requirements of this module. - - :return: List of dict with requirements of this module - """ - return [{"name": "qubovert", "version": "1.2.5"}] - - def get_parameter_options(self) -> dict: - """ - Returns the configurable settings for this mapping. - - :return: Dictionary containing configurable settings - .. code-block:: python - - return { - "penalty_weight": { - "values": [2, 5, 10, 25, 50, 100], - "custom_input": True, - "custom_range": True, - "postproc": float, - "description": "Please choose the weight of the penalties in the QUBO representation of - the problem" - } - } - """ - return { - "penalty_weight": { - "values": [2, 5, 10, 25, 50, 100], - "custom_input": True, - "allow_ranges": True, - "postproc": float, - "description": "Please choose the weight of the penalties in the QUBO representation of the problem" - } - } - - class Config(TypedDict): - """ - Attributes of a valid config. - - .. code-block:: python - - penalty_weight: float - - """ - penalty_weight: float - - def map(self, problem: tuple, config: Config) -> tuple[dict, float]: - """ - Maps the SCP to a QUBO matrix. - - :param problem: Tuple containing the set of all elements of an instance and a list of subsets, - each covering some of these elements - :param config: Config with the parameters specified in Config class - :return: Dict with QUBO matrix, time it took to map it - """ - start = start_time_measurement() - penalty_weight = config['penalty_weight'] - - u, v = problem - - self.SCP_problem = SetCover(u, v) # pylint: disable=W0201 - self.SCP_qubo = self.SCP_problem.to_qubo(penalty_weight) # pylint: disable=W0201 - - logging.info(f"Converted to QUBO with {self.SCP_qubo.num_binary_variables} Variables.") - - # Convert it to the right format to be accepted by Braket / Dwave - q_dict = {} - - for key, val in self.SCP_qubo.items(): - # Interaction (quadratic) terms - if len(key) == 2: - if (key[0], key[1]) not in q_dict: - q_dict[(key[0], key[1])] = float(val) - else: - q_dict[(key[0], key[1])] += float(val) - # Local (linear) fields - elif len(key) == 1: - if (key[0], key[0]) not in q_dict: - q_dict[(key[0], key[0])] = float(val) - else: - q_dict[(key[0], key[0])] += float(val) - - return {"Q": q_dict}, end_time_measurement(start) - - def reverse_map(self, solution: dict) -> tuple[set, float]: - """ - Maps the solution of the QUBO to a set of subsets included in the solution. - - :param solution: QUBO matrix in dict form - :return: Tuple with set of subsets that are part of the solution and the time it took to map it - """ - start = start_time_measurement() - sol = self.SCP_problem.convert_solution(solution) - - return sol, end_time_measurement(start) - - def get_default_submodule(self, option: str) -> Core: - """ - Returns the default submodule based on the provided option. - - :param option: Option specifying the submodule - :return: Instance of the corresponding submodule - :raises NotImplementedError: If the option is not recognized - """ - if option == "Annealer": - from modules.solvers.annealer import Annealer # pylint: disable=C0415 - return Annealer() - else: - raise NotImplementedError(f"Solver Option {option} not implemented") diff --git a/src/modules/applications/optimization/TSP/TSP.py b/src/modules/applications/optimization/TSP/TSP.py deleted file mode 100644 index cab27b28..00000000 --- a/src/modules/applications/optimization/TSP/TSP.py +++ /dev/null @@ -1,337 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from typing import TypedDict -import pickle -import logging -import os - -import networkx as nx -import matplotlib.pyplot as plt -import numpy as np - -from modules.applications.application import Core -from modules.applications.optimization.optimization import Optimization -from utils import start_time_measurement, end_time_measurement - - -class TSP(Optimization): - """ - "The famous travelling salesman problem (also called the travelling salesperson problem or in short TSP) is a - well-known NP-hard problem in combinatorial optimization, asking for the shortest possible route that visits each - node exactly once, given a list of nodes and the distances between each pair of nodes. Applications of the - TSP can be found in planning, logistics, and the manufacture of microchips. In these applications, the general - concept of a node represents, for example, customers, or points on a chip. - - TSP as graph problem: The solution to the TSP can be viewed as a specific ordering of the vertices in a weighted - graph. Taking an undirected weighted graph, nodes correspond to the graph's nodes, with paths corresponding to the - graph's edges, and a path's distance is the edge's weight." - (source: https://github.com/aws/amazon-braket-examples/tree/main/examples) - """ - - def __init__(self): - """ - Constructor method. - """ - super().__init__("TSP") - self.submodule_options = [ - "Ising", "QUBO", "GreedyClassicalTSP", "ReverseGreedyClassicalTSP", "RandomTSP" - ] - - @staticmethod - def get_requirements() -> list: - """ - Return requirements of this module. - - :return: List of dict with requirements of this module - """ - return [ - {"name": "networkx", "version": "3.4.2"}, - {"name": "numpy", "version": "1.26.4"} - ] - - def get_solution_quality_unit(self) -> str: - """ - Returns the unit of measurement for the solution quality. - - :return: Unit of measurement for the solution quality - """ - return "Tour cost" - - def get_default_submodule(self, option: str) -> Core: - """ - Returns the default submodule based on the given option. - - :param option: The chosen submodule option - :return: The corresponding submodule instance - :raises NotImplemented: If the provided option is not implemented - """ - if option == "Ising": - from modules.applications.optimization.tsp.mappings.ising import Ising # pylint: disable=C0415 - return Ising() - elif option == "QUBO": - from modules.applications.optimization.tsp.mappings.qubo import QUBO # pylint: disable=C0415 - return QUBO() - elif option == "GreedyClassicalTSP": - from modules.solvers.greedy_classical_tsp import GreedyClassicalTSP # pylint: disable=C0415 - return GreedyClassicalTSP() - elif option == "ReverseGreedyClassicalTSP": - from modules.solvers.reverse_greedy_classical_tsp import ReverseGreedyClassicalTSP # pylint: disable=C0415 - return ReverseGreedyClassicalTSP() - elif option == "RandomTSP": - from modules.solvers.random_classical_tsp import RandomTSP # pylint: disable=C0415 - return RandomTSP() - else: - raise NotImplementedError(f"Mapping Option {option} not implemented") - - def get_parameter_options(self) -> dict: - """ - Returns the configurable settings for this application - - :return: Dictionary with configurable settings. - .. code-block:: python - - return { - "nodes": { - "values": list([3, 4, 6, 8, 10, 14, 16]), - "allow_ranges": True, - "description": "How many nodes does your graph need?", - "postproc": int - } - } - """ - return { - "nodes": { - "values": list([3, 4, 6, 8, 10, 14, 16]), - "allow_ranges": True, - "description": "How many nodes does you graph need?", - "postproc": int # postproc needed to parse the result from allow_ranges to int - } - } - - class Config(TypedDict): - """ - Attributes of a valid config. - - .. code-block:: python - - nodes: int - - """ - nodes: int - - @staticmethod - def _get_tsp_matrix(graph: nx.Graph) -> np.ndarray: - """ - Creates distance matrix out of given coordinates. - - :param graph: The input graph - :return: Distance matrix - """ - number_of_nodes = len(graph) - matrix = np.zeros((number_of_nodes, number_of_nodes)) - for i in nx.all_pairs_dijkstra_path_length(graph, weight="weight"): - distance_dist = i[1] - for j in distance_dist.items(): - matrix[i[0] - 1][j[0] - 1] = j[1] - matrix[j[0] - 1][i[0] - 1] = matrix[i[0] - 1][j[0] - 1] - - return matrix - - def generate_problem(self, config: Config) -> nx.Graph: - """ - Uses the reference graph to generate a problem for a given config. - - :param config: Configuration dictionary - :return: Graph with the problem - """ - - if config is None: - config = {"nodes": 5} - - nodes = config['nodes'] - - # Read in the original graph - with open(os.path.join(os.path.dirname(__file__), "data", "reference_graph.gpickle"), "rb") as file: - graph = pickle.load(file) - - # Remove seams until the target number of seams is reached - nodes_in_graph = list(graph.nodes) - nodes_in_graph.sort() - - if len(nodes_in_graph) < nodes: - raise ValueError("Too many nodes! The original graph has less seams than that!") - - unwanted_nodes = nodes_in_graph[-len(nodes_in_graph) + nodes:] - unwanted_nodes = [x for x in graph.nodes if x in unwanted_nodes] - - # Remove one node after another - for node in unwanted_nodes: - graph.remove_node(node) - - if not nx.is_connected(graph): - logging.error("Graph is not connected!") - raise ValueError("Graph is not connected!") - - # Normalize graph - cost_matrix = self._get_tsp_matrix(graph) - graph = nx.from_numpy_array(cost_matrix) - - self.application = graph - - return graph - - def process_solution(self, solution: dict) -> tuple[list, float]: - """ - Convert dict to list of visited nodes. - - :param solution: Dictionary with solution - :return: Processed solution and the time it took to process it - """ - start_time = start_time_measurement() - nodes = self.application.nodes() - start = np.min(nodes) - # fill route with None values - route: list = [None] * len(self.application) - - # Get nodes from sample - logging.info(str(solution.items())) - - for (node, timestep), val in solution.items(): - if val: - logging.info((node, timestep)) - if val and (node not in route): - route[timestep] = node - - # Check whether every timestep has only 1 node flagged - for i in nodes: - relevant_nodes = [] - relevant_timesteps = [] - for (node, timestep) in solution.keys(): - if node == i: - relevant_nodes.append(solution[(node, timestep)]) - if timestep == i: - relevant_timesteps.append(solution[(node, timestep)]) - if sum(relevant_nodes) != 1 or sum(relevant_timesteps) != 1: - # timestep or nodes have more than 1 or 0 flags - return None, end_time_measurement(start_time) - - # Check validity of solution - if sum(value == 1 for value in solution.values()) > len(route): - logging.warning("Result is longer than route! This might be problematic!") - return None, end_time_measurement(start_time) - - # Run heuristic replacing None values - if None in route: - # get not assigned nodes - nodes_unassigned = [node for node in list(nodes) if node not in route] - nodes_unassigned = list(np.random.permutation(nodes_unassigned)) - for idx, node in enumerate(route): - if node is None: - route[idx] = nodes_unassigned[0] - nodes_unassigned.remove(route[idx]) - - # Cycle solution to start at provided start location - if start is not None and route[0] != start: - # Rotate to put the start in front - idx = route.index(start) - route = route[idx:] + route[:idx] - - # Log route - parsed_route = ' ->\n'.join([f' Node {visit}' for visit in route]) - logging.info(f"Route found:\n{parsed_route}") - - return route, end_time_measurement(start_time) - - def validate(self, solution: list) -> tuple[bool, float]: - """ - Checks if it is a valid TSP tour. - - :param solution: List containing the nodes of the solution - :return: Boolean whether the solution is valid, time it took to validate - """ - start = start_time_measurement() - nodes = self.application.nodes() - - if solution is None: - return False, end_time_measurement(start) - elif len([node for node in list(nodes) if node not in solution]) == 0: - logging.info(f"All {len(solution)} nodes got visited") - return True, end_time_measurement(start) - else: - logging.error(f"{len([node for node in list(nodes) if node not in solution])} nodes were NOT visited") - return False, end_time_measurement(start) - - def evaluate(self, solution: list) -> tuple[float, float]: - """ - Find distance for given route and original data. - - :param solution: List containing the nodes of the solution - :return: Tour cost and the time it took to calculate it - """ - start = start_time_measurement() - # Get the total distance without return - total_dist = 0 - for idx, _ in enumerate(solution[:-1]): - dist = self.application[solution[idx + 1]][solution[idx]] - total_dist += dist['weight'] - - logging.info(f"Total distance (without return): {total_dist}") - - # Add distance between start and end point to complete cycle - return_distance = self.application[solution[0]][solution[-1]]['weight'] - - # Get distance for full cycle - distance_with_return = total_dist + return_distance - logging.info(f"Total distance (including return): {distance_with_return}") - - return distance_with_return, end_time_measurement(start) - - def save(self, path: str, iter_count: int) -> None: - """ - Save the current application state to a file. - - :param path: The directory path where the file will be saved - :param iter_count: The iteration count to include in the filename - """ - with open(f"{path}/graph_iter_{iter_count}.gpickle", "wb") as file: - pickle.dump(self.application, file, pickle.HIGHEST_PROTOCOL) - - def visualize_solution(self, processed_solution: list[int], path: str): - """ - Plot a graph representing the problem network with the solution path highlighted - - :param processed_solution: The solution already processed by :func:`process_solution`, a list of visited node IDs in order of being visited. - :param path: File path for the plot - :returns: None - """ - NODE_SIZE = 300 # Default=300 - EDGE_WIDTH = 1.0 # Default=1.0 - FONT_SIZE = 12 # Default=12 - - path_edges = list(nx.utils.pairwise(processed_solution, cyclic=True)) - path_edges = [(u, v) if u < v else (v, u) for (u, v) in path_edges] - G = self.application - pos = nx.circular_layout(G) - weights = nx.get_edge_attributes(G, "weight") - filtered_weights = {e: (int(weights[e])) for e in path_edges} - - nx.draw_networkx_nodes(G, pos, node_size=NODE_SIZE) - nx.draw_networkx_edges(G, pos, edgelist=G.edges(), width=EDGE_WIDTH, edge_color="gray") - nx.draw_networkx_edges(G, pos, edgelist=path_edges, width=2 * EDGE_WIDTH, edge_color="red", arrows=True) - nx.draw_networkx_labels(G, pos, font_size=FONT_SIZE) - nx.draw_networkx_edge_labels(G, pos, filtered_weights, font_size=.5 * FONT_SIZE) - - plt.savefig(path) - plt.close() diff --git a/src/modules/applications/optimization/TSP/mappings/ISING.py b/src/modules/applications/optimization/TSP/mappings/ISING.py deleted file mode 100644 index 3b4d01fa..00000000 --- a/src/modules/applications/optimization/TSP/mappings/ISING.py +++ /dev/null @@ -1,273 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import re -from typing import TypedDict -import logging - -import networkx as nx -import numpy as np -from dimod import qubo_to_ising -from more_itertools import locate -from qiskit_optimization.applications import Tsp -from qiskit_optimization.converters import QuadraticProgramToQubo - -from modules.applications.mapping import Mapping, Core -from modules.applications.optimization.tsp.mappings.qubo import QUBO -from utils import start_time_measurement, end_time_measurement - - -class Ising(Mapping): - """ - Ising formulation for the TSP. - """ - - def __init__(self): - """ - Constructor method. - """ - super().__init__() - self.submodule_options = ["QAOA", "PennylaneQAOA", "QiskitQAOA"] - self.key_mapping = None - self.graph = None - self.config = None - - @staticmethod - def get_requirements() -> list[dict]: - """ - Return requirements of this module. - - :return: List of dict with requirements of this module - """ - return [ - {"name": "networkx", "version": "3.4.2"}, - {"name": "numpy", "version": "1.26.4"}, - {"name": "dimod", "version": "0.12.18"}, - {"name": "more-itertools", "version": "10.5.0"}, - {"name": "qiskit-optimization", "version": "0.6.1"}, - *QUBO.get_requirements() - ] - - def get_parameter_options(self) -> dict: - """ - Returns the configurable settings for this mapping. - - :return: Dictionary containing parameter options. - .. code-block:: python - - return { - "lagrange_factor": { - "values": [0.75, 1.0, 1.25], - "description": "By which factor would you like to multiply your lagrange?" - }, - "mapping": { - "values": ["ocean", "qiskit"], - "description": "Which Ising formulation of the TSP problem should be used?" - } - } - """ - return { - "lagrange_factor": { - "values": [0.75, 1.0, 1.25], - "description": "By which factor would you like to multiply your lagrange?" - }, - "mapping": { - "values": ["ocean", "qiskit"], - "description": "Which Ising formulation of the TSP problem should be used?" - } - } - - class Config(TypedDict): - """ - Attributes of a valid config. - - .. code-block:: python - - lagrange_factor: float - mapping: str - - """ - lagrange_factor: float - mapping: str - - def map(self, problem: nx.Graph, config: Config) -> tuple[dict, float]: - """ - Maps the networkx graph to an Ising formulation. - - :param problem: Networkx graph - :param config: Config with the parameters specified in Config class - :return: Dict with Ising, time it took to map it - """ - self.graph = problem - self.config = config - # Call mapping function defined in configuration - mapping = self.config["mapping"] - if mapping == "ocean": - return self._map_ocean(problem, config) - elif mapping == "qiskit": - return self._map_qiskit(problem, config) - else: - logging.error(f"Unknown mapping {mapping}.") - raise ValueError(f"Unknown mapping {mapping}.") - - def _map_ocean(self, graph: nx.Graph, config: Config) -> tuple[dict, float]: - """ - Use D-Wave/Ocean TSP QUBO/Ising model. - - :param graph: Networkx graph - :param config: Config with the parameters specified in Config class - :return: Dict with the Ising, time it took to map it - """ - start = start_time_measurement() - qubo_mapping = QUBO() - q, _ = qubo_mapping.map(graph, config) - t, j, _ = qubo_to_ising(q["Q"]) - - # Convert ISING dict to matrix - timesteps = graph.number_of_nodes() - matrix_size = graph.number_of_nodes() * timesteps - j_matrix = np.zeros((matrix_size, matrix_size), dtype=float) - - self.key_mapping = {} - index_counter = 0 - - for key, value in j.items(): - if key[0] not in self.key_mapping: - self.key_mapping[key[0]] = index_counter - index_counter += 1 - if key[1] not in self.key_mapping: - self.key_mapping[key[1]] = index_counter - index_counter += 1 - u = self.key_mapping[key[0]] - v = self.key_mapping[key[1]] - j_matrix[u][v] = value - - return {"J": j_matrix, "t": np.array(list(t.values())), "J_dict": j}, end_time_measurement(start) - - @staticmethod - def _map_qiskit(graph: nx.Graph, config: Config) -> tuple[dict, float]: - """ - Use Ising Mapping of Qiskit Optimize: - TSP class: https://qiskit.org/documentation/optimization/stubs/qiskit_optimization.applications.Tsp.html - Example notebook: https://qiskit.org/documentation/tutorials/optimization/6_examples_max_cut_and_tsp.html - - :param graph: Networkx graph - :param config: Config with the parameters specified in Config class - :return: Dict with the Ising, time it took to map it - """ - start = start_time_measurement() - tsp = Tsp(graph) - qp = tsp.to_quadratic_program() - logging.info(qp.export_as_lp_string()) - qp2qubo = QuadraticProgramToQubo() - qubo = qp2qubo.convert(qp) - qubitOp, _ = qubo.to_ising() - - # Reverse generate J and t out of qubit PauliSumOperator from qiskit - t_matrix = np.zeros(qubitOp.num_qubits, dtype=complex) - j_matrix = np.zeros((qubitOp.num_qubits, qubitOp.num_qubits), dtype=complex) - pauli_list = qubitOp.to_list() - - for pauli_str, coeff in pauli_list: - pauli_str_list = list(pauli_str) - index_pos_list = list(locate(pauli_str_list, lambda a: a == 'Z')) - if len(index_pos_list) == 1: - t_matrix[index_pos_list[0]] = coeff - elif len(index_pos_list) == 2: - j_matrix[index_pos_list[0]][index_pos_list[1]] = coeff - - return {"J": j_matrix, "t": t_matrix}, end_time_measurement(start) - - def reverse_map(self, solution: any) -> tuple[dict, float]: - """ - Maps the solution back to the representation needed by the TSP class for validation/evaluation. - - :param solution: List or array containing the solution - :return: Solution mapped accordingly, time it took to map it - """ - start = start_time_measurement() - if -1 in solution: # ising model output from Braket QAOA - solution = self._convert_ising_to_qubo(solution) - elif self.config["mapping"] == "ocean": - logging.debug("Flip bits in solutions to unify different mappings") - solution = self._flip_bits_in_bitstring(solution) - - logging.info(f"Best Bitstring: {solution}") - n = self.graph.number_of_nodes() - - result = {} - if self.key_mapping is None: - # Node indexes in graph are used as index in qubits - it = np.nditer(solution, flags=['multi_index']) - for x in it: - logging.debug(f"{x}, {it.multi_index}") - idx = it.multi_index[0] - result[(int(idx / n), int(idx % n))] = x - else: - logging.debug("Using key Mapping: {self.key_mapping}") - for key, value in self.key_mapping.items(): - result[key] = 1 if solution[value] == 1 else 0 - - return result, end_time_measurement(start) - - @staticmethod - def _flip_bits_in_bitstring(solution: any) -> any: - """ - Flip bits in the solution bitstring to unify different mappings. - - :param solution: Solution bitstring - :return: Flipped solution bitstring - """ - solution = np.array(solution) - with np.nditer(solution, op_flags=['readwrite']) as it: - for x in it: - x[...] = 1 - x - - return solution - - @staticmethod - def _convert_ising_to_qubo(solution: any) -> any: - """ - Convert Ising model output to QUBO format. - - :param solution: Ising model output - :return: QUBO format solution - """ - solution = np.array(solution) - with np.nditer(solution, op_flags=['readwrite']) as it: - for x in it: - if x == -1: - x[...] = 0 - - return solution - - def get_default_submodule(self, option: str) -> Core: - """ - Get the default submodule based on the given option. - - :param option: Submodule option - :return: Corresponding submodule - :raises NotImplemented: If the provided option is not implemented - """ - if option == "QAOA": - from modules.solvers.qaoa import QAOA # pylint: disable=C0415 - return QAOA() - elif option == "PennylaneQAOA": - from modules.solvers.pennylane_qaoa import PennylaneQAOA # pylint: disable=C0415 - return PennylaneQAOA() - elif option == "QiskitQAOA": - from modules.solvers.qiskit_qaoa import QiskitQAOA # pylint: disable=C0415 - return QiskitQAOA() - else: - raise NotImplementedError(f"Solver Option {option} not implemented") diff --git a/src/modules/applications/optimization/TSP/mappings/QUBO.py b/src/modules/applications/optimization/TSP/mappings/QUBO.py deleted file mode 100644 index 59eaa831..00000000 --- a/src/modules/applications/optimization/TSP/mappings/QUBO.py +++ /dev/null @@ -1,125 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from typing import TypedDict -import logging - -import dwave_networkx as dnx -import networkx - -from modules.applications.mapping import Mapping, Core -from utils import start_time_measurement, end_time_measurement - - -class QUBO(Mapping): - """ - QUBO formulation for the TSP. - """ - - def __init__(self): - """ - Constructor method. - """ - super().__init__() - self.submodule_options = ["Annealer"] - - @staticmethod - def get_requirements() -> list[dict]: - """ - Return requirements of this module. - - :return: List of dict with requirements of this module - """ - return [ - {"name": "networkx", "version": "3.4.2"}, - {"name": "dwave_networkx", "version": "0.8.15"} - ] - - def get_parameter_options(self) -> dict: - """ - Returns the configurable settings for this mapping. - - :return: Dictionary with configurable settings - .. code-block:: python - - return { - "lagrange_factor": { - "values": [0.75, 1.0, 1.25], - "description": "By which factor would you like to multiply your " - "Lagrange?", - "custom_input": True, - "postproc": float - } - } - """ - return { - "lagrange_factor": { - "values": [0.75, 1.0, 1.25], - "description": "By which factor would you like to multiply your Lagrange?", - "custom_input": True, - "allow_ranges": True, - "postproc": float # Since we allow custom input here we need to parse it to float (input is str) - } - } - - class Config(TypedDict): - """ - Attributes of a valid config. - - .. code-block:: python - - lagrange_factor: float - - """ - lagrange_factor: float - - def map(self, problem: networkx.Graph, config: Config) -> tuple[dict, float]: - """ - Maps the networkx graph to a QUBO formulation. - - :param problem: Networkx graph - :param config: Config with the parameters specified in Config class - :return: Dict with QUBO, time it took to map it - """ - start = start_time_measurement() - lagrange = None - lagrange_factor = config['lagrange_factor'] - weight = 'weight' - - # Taken from dwave_networkx.traveling_salesperson_qubo - lagrange = problem.size(weight=weight) * problem.number_of_nodes() / problem.number_of_edges() - - lagrange = lagrange * lagrange_factor - - logging.info(f"Default Lagrange parameter: {lagrange}") - - # Get a QUBO representation of the problem - q = dnx.traveling_salesperson_qubo(problem, lagrange, weight) - - return {"Q": q}, end_time_measurement(start) - - def get_default_submodule(self, option: str) -> Core: - """ - Get the default submodule based on the given option. - - :param option: Submodule option - :return: Corresponding submodule - :raises NotImplemented: If the provided option is not implemented - """ - - if option == "Annealer": - from modules.solvers.annealer import Annealer # pylint: disable=C0415 - return Annealer() - else: - raise NotImplementedError(f"Solver Option {option} not implemented") diff --git a/src/modules/applications/optimization/acl/Vehicle_data_QUARK.xlsx b/src/modules/applications/optimization/acl/Vehicle_data_QUARK.xlsx deleted file mode 100644 index cbd255ed..00000000 --- a/src/modules/applications/optimization/acl/Vehicle_data_QUARK.xlsx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b4665a49e772482af10b48f1f664bf4bce4014477f359549170c91737858ee40 -size 14465 diff --git a/src/modules/applications/optimization/acl/__init__.py b/src/modules/applications/optimization/acl/__init__.py deleted file mode 100644 index b22c875d..00000000 --- a/src/modules/applications/optimization/acl/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright 2022 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -Module containing the ACL - -This module initializes the ACL application, which is responsible for formulating -and solving the ACL problem using various mappings and solvers. -""" diff --git a/src/modules/applications/optimization/acl/mappings/__init__.py b/src/modules/applications/optimization/acl/mappings/__init__.py deleted file mode 100644 index d7364160..00000000 --- a/src/modules/applications/optimization/acl/mappings/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright 2022 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -Module for ACL mappings. - -This module provides initializations for ACL related -mappings that are used in the QUARK framework. -""" diff --git a/src/modules/applications/optimization/ACL/Vehicle_data_QUARK.xlsx b/src/modules/applications/optimization/acl_temp/Vehicle_data_QUARK.xlsx similarity index 100% rename from src/modules/applications/optimization/ACL/Vehicle_data_QUARK.xlsx rename to src/modules/applications/optimization/acl_temp/Vehicle_data_QUARK.xlsx diff --git a/src/modules/applications/optimization/ACL/__init__.py b/src/modules/applications/optimization/acl_temp/__init__.py similarity index 100% rename from src/modules/applications/optimization/ACL/__init__.py rename to src/modules/applications/optimization/acl_temp/__init__.py diff --git a/src/modules/applications/optimization/acl/acl.py b/src/modules/applications/optimization/acl_temp/acl.py similarity index 100% rename from src/modules/applications/optimization/acl/acl.py rename to src/modules/applications/optimization/acl_temp/acl.py diff --git a/src/modules/applications/optimization/ACL/mappings/__init__.py b/src/modules/applications/optimization/acl_temp/mappings/__init__.py similarity index 100% rename from src/modules/applications/optimization/ACL/mappings/__init__.py rename to src/modules/applications/optimization/acl_temp/mappings/__init__.py diff --git a/src/modules/applications/optimization/acl/mappings/ising.py b/src/modules/applications/optimization/acl_temp/mappings/ising.py similarity index 100% rename from src/modules/applications/optimization/acl/mappings/ising.py rename to src/modules/applications/optimization/acl_temp/mappings/ising.py diff --git a/src/modules/applications/optimization/acl/mappings/qubo.py b/src/modules/applications/optimization/acl_temp/mappings/qubo.py similarity index 100% rename from src/modules/applications/optimization/acl/mappings/qubo.py rename to src/modules/applications/optimization/acl_temp/mappings/qubo.py diff --git a/src/modules/applications/optimization/mis/__init__.py b/src/modules/applications/optimization/mis/__init__.py deleted file mode 100644 index b7ca66cc..00000000 --- a/src/modules/applications/optimization/mis/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright 2022 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -Module for MIS mappings - -This module initialize the MIS package -""" diff --git a/src/modules/applications/optimization/mis/data/__init__.py b/src/modules/applications/optimization/mis/data/__init__.py deleted file mode 100644 index 1afbe94e..00000000 --- a/src/modules/applications/optimization/mis/data/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright 2022 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -Module for MIS data - -This module initialize the MIS package -""" diff --git a/src/modules/applications/optimization/mis/data/graph_layouts.py b/src/modules/applications/optimization/mis/data/graph_layouts.py deleted file mode 100644 index c66abded..00000000 --- a/src/modules/applications/optimization/mis/data/graph_layouts.py +++ /dev/null @@ -1,100 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import math -import random - -import networkx as nx -import pulser - -# define R_rydberg -R_rydberg = 9.75 - - -def generate_hexagonal_graph(n_nodes: int, spacing: float, filling_fraction: float = 1.0) -> nx.Graph: - """ - Generate a hexagonal graph layout based on the number of nodes and spacing. - - :param n_nodes: The number of nodes in the graph - :param spacing: The spacing between nodes (atoms) - :param filling_fraction: The fraction of available places in the lattice to be filled with nodes. (default: 1.0) - :return: Networkx Graph representing the hexagonal graph layout - """ - if not 0.0 < filling_fraction <= 1.0: - raise ValueError("The filling fraction must be in the domain of (0.0, 1.0].") - - # Create a layout large enough to contain the desired number of atoms at the filling fraction - n_traps = int(n_nodes / filling_fraction) - hexagonal_layout = pulser.register.special_layouts.TriangularLatticeLayout( - n_traps=n_traps, spacing=spacing - ) - - # Fill the layout with traps - reg = hexagonal_layout.hexagonal_register(n_traps) - ids = reg._ids # pylint: disable=W0212 - coords = [coord.tolist() for coord in reg._coords] # pylint: disable=W0212 - traps = dict(zip(ids, coords)) - - # Remove random atoms to get the desired number of atoms - while len(traps) > n_nodes: - atom_to_remove = random.choice(list(traps)) - traps.pop(atom_to_remove) - - # Rename the atoms - node_positions = {i: traps[trap] for i, trap in enumerate(traps.keys())} # pylint: disable=C0206 - - # Create the graph - hexagonal_graph = nx.Graph() - - # Add nodes to the graph - for node_id, coord in node_positions.items(): - hexagonal_graph.add_node(node_id, pos=coord) - - # Generate the edges and add them to the graph - edges = _generate_edges(node_positions=node_positions) - hexagonal_graph.add_edges_from(edges) - - return hexagonal_graph - - -def _generate_edges(node_positions: dict[list[int, list[float]]], radius: float = R_rydberg) -> list[tuple]: - """ - Generate edges between vertices within a given distance 'radius', which defaults to R_rydberg. - - :param node_positions: A dictionary with the node ids as keys, and the node coordinates as values - :param radius: When the distance between two nodes is smaller than this radius, an edge is generated between them - :return: A list of 2-tuples. Each 2-tuple contains two different node ids and represents an edge between those nodes - """ - edges = [] - vertex_keys = list(node_positions.keys()) - for i, vertex_key in enumerate(vertex_keys): - for neighbor_key in vertex_keys[i + 1:]: - distance = _vertex_distance(node_positions[vertex_key], node_positions[neighbor_key]) - if distance <= radius: - edges.append((vertex_key, neighbor_key)) - return edges - - -def _vertex_distance(v0: tuple[float, ...], v1: tuple[float, ...]) -> float: - """ - Calculates distance between two n-dimensional vertices. - For 2 dimensions: distance = sqrt((x0 - x1)**2 + (y0 - y1)**2) - - :param v0: Coordinates of the first vertex - :param v1: Coordinates of the second vertex - return: Distance between the vertices - """ - squared_difference = sum((coordinate0 - coordinate1) ** 2 for coordinate0, coordinate1 in zip(v0, v1)) - - return math.sqrt(squared_difference) diff --git a/src/modules/applications/optimization/mis/mappings/__init__.py b/src/modules/applications/optimization/mis/mappings/__init__.py deleted file mode 100644 index b7ca66cc..00000000 --- a/src/modules/applications/optimization/mis/mappings/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright 2022 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -Module for MIS mappings - -This module initialize the MIS package -""" diff --git a/src/modules/applications/optimization/mis/mappings/neutral_atom.py b/src/modules/applications/optimization/mis/mappings/neutral_atom.py deleted file mode 100644 index f51af4e2..00000000 --- a/src/modules/applications/optimization/mis/mappings/neutral_atom.py +++ /dev/null @@ -1,91 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from typing import TypedDict - -import networkx as nx -import pulser - -from modules.applications.mapping import Mapping, Core -from utils import start_time_measurement, end_time_measurement - - -class NeutralAtom(Mapping): - """ - Neutral atom formulation for MIS. - """ - - def __init__(self): - """ - Constructor method. - """ - super().__init__() - self.submodule_options = ["NeutralAtomMIS"] - - @staticmethod - def get_requirements() -> list[dict]: - """ - Return requirements of this module. - - :return: List of requirements of this module - """ - return [{"name": "pulser", "version": "1.1.1"}] - - def get_parameter_options(self) -> dict: - """ - Returns the configurable settings for this mapping. - - :return: Empty dictionary, as this mapping has no configurable settings - """ - return {} - - class Config(TypedDict): - """ - Configuration options for Neutral Atom MIS mapping. - """ - pass - - def map(self, problem: nx.Graph, config: Config) -> tuple[dict, float]: - """ - Maps the networkx graph to a neutral atom MIS problem. - - :param problem: Networkx graph representing the MIS problem - :param config: Config with the parameters specified in Config class - :return: Tuple containing a dictionary with the neutral MIS and time it took to map it - """ - start = start_time_measurement() - - pos = nx.get_node_attributes(problem, 'pos') - register = pulser.Register(pos) - - neutral_atom_problem = { - 'graph': problem, - 'register': register - } - - return neutral_atom_problem, end_time_measurement(start) - - def get_default_submodule(self, option: str) -> Core: - """ - Returns the default submodule based on the provided option. - - :param option: Option specifying the submodule - :return: Instance of the corresponding submodule - :raises NotImplementedError: If the option is not recognized - """ - if option == "NeutralAtomMIS": - from modules.solvers.neutral_atom_mis import NeutralAtomMIS # pylint: disable=C0415 - return NeutralAtomMIS() - else: - raise NotImplementedError(f"Solver Option {option} not implemented") diff --git a/src/modules/applications/optimization/MIS/__init__.py b/src/modules/applications/optimization/mis_temp/__init__.py similarity index 100% rename from src/modules/applications/optimization/MIS/__init__.py rename to src/modules/applications/optimization/mis_temp/__init__.py diff --git a/src/modules/applications/optimization/MIS/data/__init__.py b/src/modules/applications/optimization/mis_temp/data/__init__.py similarity index 100% rename from src/modules/applications/optimization/MIS/data/__init__.py rename to src/modules/applications/optimization/mis_temp/data/__init__.py diff --git a/src/modules/applications/optimization/MIS/data/graph_layouts.py b/src/modules/applications/optimization/mis_temp/data/graph_layouts.py similarity index 100% rename from src/modules/applications/optimization/MIS/data/graph_layouts.py rename to src/modules/applications/optimization/mis_temp/data/graph_layouts.py diff --git a/src/modules/applications/optimization/MIS/mappings/__init__.py b/src/modules/applications/optimization/mis_temp/mappings/__init__.py similarity index 100% rename from src/modules/applications/optimization/MIS/mappings/__init__.py rename to src/modules/applications/optimization/mis_temp/mappings/__init__.py diff --git a/src/modules/applications/optimization/MIS/mappings/neutral_atom.py b/src/modules/applications/optimization/mis_temp/mappings/neutral_atom.py similarity index 100% rename from src/modules/applications/optimization/MIS/mappings/neutral_atom.py rename to src/modules/applications/optimization/mis_temp/mappings/neutral_atom.py diff --git a/src/modules/applications/optimization/mis/mappings/qiro.py b/src/modules/applications/optimization/mis_temp/mappings/qiro.py similarity index 100% rename from src/modules/applications/optimization/mis/mappings/qiro.py rename to src/modules/applications/optimization/mis_temp/mappings/qiro.py diff --git a/src/modules/applications/optimization/mis/mis.py b/src/modules/applications/optimization/mis_temp/mis.py similarity index 100% rename from src/modules/applications/optimization/mis/mis.py rename to src/modules/applications/optimization/mis_temp/mis.py diff --git a/src/modules/applications/optimization/pvc/__init__.py b/src/modules/applications/optimization/pvc/__init__.py deleted file mode 100644 index e9c96938..00000000 --- a/src/modules/applications/optimization/pvc/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright 2022 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -Module for PVC mappings. - -This module initializes the PVC mapping packages -""" diff --git a/src/modules/applications/optimization/pvc/data/createReferenceGraph.py b/src/modules/applications/optimization/pvc/data/createReferenceGraph.py deleted file mode 100644 index 148fe444..00000000 --- a/src/modules/applications/optimization/pvc/data/createReferenceGraph.py +++ /dev/null @@ -1,54 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import networkx as nx -import pickle - -# Create the original graph as a MultiDiGraph -graph = nx.MultiDiGraph() - -with open("reference_data.txt") as infile: - for line in infile: - line_elements = line.split() - - # Extract start and end attributes from line elements - r_start, s_start, n_start, c_start, t_start, l_start = map(int, line_elements[1:7]) - r_end, s_end, n_end, c_end, t_end, l_end = map(int, line_elements[8:14]) - duration = float(line_elements[15]) - - # Handle missing or invalid data with default values - if s_start == -1: - s_start = 0 - t_start = 1 # TODO except of picking a hardcoded value here we should select 1 from the dataset itself - c_start = 1 - if s_end == -1: - s_end = 0 - t_end = 1 - c_end = 1 - if n_start == -1: - n_start = 0 - if n_end == -1: - n_end = 0 - - # Reduce the number of tools and configurations for simplicity - if c_end < 3 and c_start < 3 and t_start < 2 and t_end < 2: - graph.add_edge( - (s_start, n_start), (s_end, n_end), - c_start=c_start, t_start=t_start, - c_end=c_end, t_end=t_end, weight=duration - ) - -# Save the graph to a file in gpickle format -with open("reference_graph.gpickle", "wb") as file: - pickle.dump(graph, file, pickle.HIGHEST_PROTOCOL) diff --git a/src/modules/applications/optimization/pvc/data/reference_data.txt b/src/modules/applications/optimization/pvc/data/reference_data.txt deleted file mode 100644 index 0466784f..00000000 --- a/src/modules/applications/optimization/pvc/data/reference_data.txt +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e44e2b30f84a3b80da3800c02e9b18cb9582b77157ce5525bc592f8cf9e3a489 -size 6224696 diff --git a/src/modules/applications/optimization/pvc/data/reference_graph.gpickle b/src/modules/applications/optimization/pvc/data/reference_graph.gpickle deleted file mode 100644 index 60a33391..00000000 --- a/src/modules/applications/optimization/pvc/data/reference_graph.gpickle +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e8b82cff0bdd6cd919b3ba475aa3c195e7f638af6bea930126921eb6efff5322 -size 513537 diff --git a/src/modules/applications/optimization/pvc/mappings/__init__.py b/src/modules/applications/optimization/pvc/mappings/__init__.py deleted file mode 100644 index e9c96938..00000000 --- a/src/modules/applications/optimization/pvc/mappings/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright 2022 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -Module for PVC mappings. - -This module initializes the PVC mapping packages -""" diff --git a/src/modules/applications/optimization/PVC/__init__.py b/src/modules/applications/optimization/pvc_temp/__init__.py similarity index 100% rename from src/modules/applications/optimization/PVC/__init__.py rename to src/modules/applications/optimization/pvc_temp/__init__.py diff --git a/src/modules/applications/optimization/PVC/data/createReferenceGraph.py b/src/modules/applications/optimization/pvc_temp/data/createReferenceGraph.py similarity index 100% rename from src/modules/applications/optimization/PVC/data/createReferenceGraph.py rename to src/modules/applications/optimization/pvc_temp/data/createReferenceGraph.py diff --git a/src/modules/applications/optimization/PVC/data/reference_data.txt b/src/modules/applications/optimization/pvc_temp/data/reference_data.txt similarity index 100% rename from src/modules/applications/optimization/PVC/data/reference_data.txt rename to src/modules/applications/optimization/pvc_temp/data/reference_data.txt diff --git a/src/modules/applications/optimization/PVC/data/reference_graph.gpickle b/src/modules/applications/optimization/pvc_temp/data/reference_graph.gpickle similarity index 100% rename from src/modules/applications/optimization/PVC/data/reference_graph.gpickle rename to src/modules/applications/optimization/pvc_temp/data/reference_graph.gpickle diff --git a/src/modules/applications/optimization/PVC/mappings/__init__.py b/src/modules/applications/optimization/pvc_temp/mappings/__init__.py similarity index 100% rename from src/modules/applications/optimization/PVC/mappings/__init__.py rename to src/modules/applications/optimization/pvc_temp/mappings/__init__.py diff --git a/src/modules/applications/optimization/pvc/mappings/ising.py b/src/modules/applications/optimization/pvc_temp/mappings/ising.py similarity index 100% rename from src/modules/applications/optimization/pvc/mappings/ising.py rename to src/modules/applications/optimization/pvc_temp/mappings/ising.py diff --git a/src/modules/applications/optimization/pvc/mappings/qubo.py b/src/modules/applications/optimization/pvc_temp/mappings/qubo.py similarity index 100% rename from src/modules/applications/optimization/pvc/mappings/qubo.py rename to src/modules/applications/optimization/pvc_temp/mappings/qubo.py diff --git a/src/modules/applications/optimization/pvc/pvc.py b/src/modules/applications/optimization/pvc_temp/pvc.py similarity index 100% rename from src/modules/applications/optimization/pvc/pvc.py rename to src/modules/applications/optimization/pvc_temp/pvc.py diff --git a/src/modules/applications/optimization/sat/__init__.py b/src/modules/applications/optimization/sat/__init__.py deleted file mode 100644 index 0ef93529..00000000 --- a/src/modules/applications/optimization/sat/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright 2022 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -Module for SAT mappings - -This module initializes the SAT application -""" diff --git a/src/modules/applications/optimization/sat/mappings/__init__.py b/src/modules/applications/optimization/sat/mappings/__init__.py deleted file mode 100644 index 0ef93529..00000000 --- a/src/modules/applications/optimization/sat/mappings/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright 2022 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -Module for SAT mappings - -This module initializes the SAT application -""" diff --git a/src/modules/applications/optimization/SAT/__init__.py b/src/modules/applications/optimization/sat_temp/__init__.py similarity index 100% rename from src/modules/applications/optimization/SAT/__init__.py rename to src/modules/applications/optimization/sat_temp/__init__.py diff --git a/src/modules/applications/optimization/SAT/mappings/__init__.py b/src/modules/applications/optimization/sat_temp/mappings/__init__.py similarity index 100% rename from src/modules/applications/optimization/SAT/mappings/__init__.py rename to src/modules/applications/optimization/sat_temp/mappings/__init__.py diff --git a/src/modules/applications/optimization/sat/mappings/choiising.py b/src/modules/applications/optimization/sat_temp/mappings/choiising.py similarity index 100% rename from src/modules/applications/optimization/sat/mappings/choiising.py rename to src/modules/applications/optimization/sat_temp/mappings/choiising.py diff --git a/src/modules/applications/optimization/sat/mappings/choiqubo.py b/src/modules/applications/optimization/sat_temp/mappings/choiqubo.py similarity index 100% rename from src/modules/applications/optimization/sat/mappings/choiqubo.py rename to src/modules/applications/optimization/sat_temp/mappings/choiqubo.py diff --git a/src/modules/applications/optimization/sat/mappings/dinneenising.py b/src/modules/applications/optimization/sat_temp/mappings/dinneenising.py similarity index 100% rename from src/modules/applications/optimization/sat/mappings/dinneenising.py rename to src/modules/applications/optimization/sat_temp/mappings/dinneenising.py diff --git a/src/modules/applications/optimization/sat/mappings/dinneenqubo.py b/src/modules/applications/optimization/sat_temp/mappings/dinneenqubo.py similarity index 100% rename from src/modules/applications/optimization/sat/mappings/dinneenqubo.py rename to src/modules/applications/optimization/sat_temp/mappings/dinneenqubo.py diff --git a/src/modules/applications/optimization/sat/mappings/direct.py b/src/modules/applications/optimization/sat_temp/mappings/direct.py similarity index 100% rename from src/modules/applications/optimization/sat/mappings/direct.py rename to src/modules/applications/optimization/sat_temp/mappings/direct.py diff --git a/src/modules/applications/optimization/sat/mappings/qubovertqubo.py b/src/modules/applications/optimization/sat_temp/mappings/qubovertqubo.py similarity index 100% rename from src/modules/applications/optimization/sat/mappings/qubovertqubo.py rename to src/modules/applications/optimization/sat_temp/mappings/qubovertqubo.py diff --git a/src/modules/applications/optimization/sat/sat.py b/src/modules/applications/optimization/sat_temp/sat.py similarity index 100% rename from src/modules/applications/optimization/sat/sat.py rename to src/modules/applications/optimization/sat_temp/sat.py diff --git a/src/modules/applications/optimization/scp/__init__.py b/src/modules/applications/optimization/scp/__init__.py deleted file mode 100644 index 863c0dfe..00000000 --- a/src/modules/applications/optimization/scp/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright 2022 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -Module for SCP mappings - -This module initializes the SCP application -""" diff --git a/src/modules/applications/optimization/scp/data/__init__.py b/src/modules/applications/optimization/scp/data/__init__.py deleted file mode 100644 index 5826cf71..00000000 --- a/src/modules/applications/optimization/scp/data/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright 2022 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -Module for SCP mappings - -This module initializes the SCP application -""" diff --git a/src/modules/applications/optimization/scp/data/set_cover_data_large.txt b/src/modules/applications/optimization/scp/data/set_cover_data_large.txt deleted file mode 100644 index 5bdd2998..00000000 --- a/src/modules/applications/optimization/scp/data/set_cover_data_large.txt +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ff89bd2852562798c8d571a97574f2e68eb7a5288f05e8441110763c90e010f6 -size 11233 diff --git a/src/modules/applications/optimization/scp/mappings/__init__.py b/src/modules/applications/optimization/scp/mappings/__init__.py deleted file mode 100644 index 863c0dfe..00000000 --- a/src/modules/applications/optimization/scp/mappings/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright 2022 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -Module for SCP mappings - -This module initializes the SCP application -""" diff --git a/src/modules/applications/optimization/SCP/__init__.py b/src/modules/applications/optimization/scp_temp/__init__.py similarity index 100% rename from src/modules/applications/optimization/SCP/__init__.py rename to src/modules/applications/optimization/scp_temp/__init__.py diff --git a/src/modules/applications/optimization/SCP/data/__init__.py b/src/modules/applications/optimization/scp_temp/data/__init__.py similarity index 100% rename from src/modules/applications/optimization/SCP/data/__init__.py rename to src/modules/applications/optimization/scp_temp/data/__init__.py diff --git a/src/modules/applications/optimization/SCP/data/set_cover_data_large.txt b/src/modules/applications/optimization/scp_temp/data/set_cover_data_large.txt similarity index 100% rename from src/modules/applications/optimization/SCP/data/set_cover_data_large.txt rename to src/modules/applications/optimization/scp_temp/data/set_cover_data_large.txt diff --git a/src/modules/applications/optimization/SCP/mappings/__init__.py b/src/modules/applications/optimization/scp_temp/mappings/__init__.py similarity index 100% rename from src/modules/applications/optimization/SCP/mappings/__init__.py rename to src/modules/applications/optimization/scp_temp/mappings/__init__.py diff --git a/src/modules/applications/optimization/scp/mappings/qubovertqubo.py b/src/modules/applications/optimization/scp_temp/mappings/qubovertqubo.py similarity index 100% rename from src/modules/applications/optimization/scp/mappings/qubovertqubo.py rename to src/modules/applications/optimization/scp_temp/mappings/qubovertqubo.py diff --git a/src/modules/applications/optimization/scp/scp.py b/src/modules/applications/optimization/scp_temp/scp.py similarity index 100% rename from src/modules/applications/optimization/scp/scp.py rename to src/modules/applications/optimization/scp_temp/scp.py diff --git a/src/modules/applications/optimization/tsp/__init__.py b/src/modules/applications/optimization/tsp/__init__.py deleted file mode 100644 index 9ddfa960..00000000 --- a/src/modules/applications/optimization/tsp/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright 2022 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -Module for TSP mappings - -This module initializes the TSP application -""" diff --git a/src/modules/applications/optimization/tsp/data/createReferenceGraph.py b/src/modules/applications/optimization/tsp/data/createReferenceGraph.py deleted file mode 100644 index 04abb882..00000000 --- a/src/modules/applications/optimization/tsp/data/createReferenceGraph.py +++ /dev/null @@ -1,48 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import networkx as nx -import pickle -import tsplib95 - -# Source http://comopt.ifi.uni-heidelberg.de/software/TSPLIB95/tsp/ -filename = "dsj1000.tsp" - - -def main(): - """ - Load a TSP problem, remove unnecessary edges, and save the reference graph. - """ - print(f"Loading {filename}") - - # Load the problem from .tsp file - problem = tsplib95.load(filename) - graph = problem.get_graph() - - # We don't needed edges from e.g. node0 -> node0 - for edge in graph.edges: - if edge[0] == edge[1]: - graph.remove_edge(edge[0], edge[1]) - - print("Loaded graph:") - print(nx.info(graph)) - - with open("reference_graph.gpickle", "wb") as file: - pickle.dump(graph, file, pickle.HIGHEST_PROTOCOL) - - print("Saved graph as reference_graph.gpickle") - - -if __name__ == '__main__': - main() diff --git a/src/modules/applications/optimization/tsp/data/dsj1000.tsp b/src/modules/applications/optimization/tsp/data/dsj1000.tsp deleted file mode 100644 index 38eddd23..00000000 --- a/src/modules/applications/optimization/tsp/data/dsj1000.tsp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6fdad6e74c25ed4a4756a561ba941c1c8e7fd77c4a3d0903ed788ec2d4095f61 -size 18972 diff --git a/src/modules/applications/optimization/tsp/data/reference_graph.gpickle b/src/modules/applications/optimization/tsp/data/reference_graph.gpickle deleted file mode 100644 index 0c1cf607..00000000 --- a/src/modules/applications/optimization/tsp/data/reference_graph.gpickle +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4b7208498c7f1929549b59559b649de26d01d0730712b329a2932a1b7c986b5e -size 15243255 diff --git a/src/modules/applications/optimization/tsp/mappings/__init__.py b/src/modules/applications/optimization/tsp/mappings/__init__.py deleted file mode 100644 index 9ddfa960..00000000 --- a/src/modules/applications/optimization/tsp/mappings/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright 2022 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -Module for TSP mappings - -This module initializes the TSP application -""" diff --git a/src/modules/applications/optimization/TSP/__init__.py b/src/modules/applications/optimization/tsp_temp/__init__.py similarity index 100% rename from src/modules/applications/optimization/TSP/__init__.py rename to src/modules/applications/optimization/tsp_temp/__init__.py diff --git a/src/modules/applications/optimization/TSP/data/createReferenceGraph.py b/src/modules/applications/optimization/tsp_temp/data/createReferenceGraph.py similarity index 100% rename from src/modules/applications/optimization/TSP/data/createReferenceGraph.py rename to src/modules/applications/optimization/tsp_temp/data/createReferenceGraph.py diff --git a/src/modules/applications/optimization/TSP/data/dsj1000.tsp b/src/modules/applications/optimization/tsp_temp/data/dsj1000.tsp similarity index 100% rename from src/modules/applications/optimization/TSP/data/dsj1000.tsp rename to src/modules/applications/optimization/tsp_temp/data/dsj1000.tsp diff --git a/src/modules/applications/optimization/TSP/data/reference_graph.gpickle b/src/modules/applications/optimization/tsp_temp/data/reference_graph.gpickle similarity index 100% rename from src/modules/applications/optimization/TSP/data/reference_graph.gpickle rename to src/modules/applications/optimization/tsp_temp/data/reference_graph.gpickle diff --git a/src/modules/applications/optimization/TSP/mappings/__init__.py b/src/modules/applications/optimization/tsp_temp/mappings/__init__.py similarity index 100% rename from src/modules/applications/optimization/TSP/mappings/__init__.py rename to src/modules/applications/optimization/tsp_temp/mappings/__init__.py diff --git a/src/modules/applications/optimization/tsp/mappings/ising.py b/src/modules/applications/optimization/tsp_temp/mappings/ising.py similarity index 100% rename from src/modules/applications/optimization/tsp/mappings/ising.py rename to src/modules/applications/optimization/tsp_temp/mappings/ising.py diff --git a/src/modules/applications/optimization/tsp/mappings/qubo.py b/src/modules/applications/optimization/tsp_temp/mappings/qubo.py similarity index 100% rename from src/modules/applications/optimization/tsp/mappings/qubo.py rename to src/modules/applications/optimization/tsp_temp/mappings/qubo.py diff --git a/src/modules/applications/optimization/tsp/tsp.py b/src/modules/applications/optimization/tsp_temp/tsp.py similarity index 100% rename from src/modules/applications/optimization/tsp/tsp.py rename to src/modules/applications/optimization/tsp_temp/tsp.py From 38383cc6418b31ff8ab3d3d6785e05e0c2f4a0fd Mon Sep 17 00:00:00 2001 From: GreshmaShaji Date: Fri, 7 Feb 2025 08:41:27 +0100 Subject: [PATCH 04/23] Final rename to lowercase to resolve Git case-sensitivity issue --- .../optimization/{acl_temp => acl}/Vehicle_data_QUARK.xlsx | 0 .../applications/optimization/{acl_temp => acl}/__init__.py | 0 src/modules/applications/optimization/{acl_temp => acl}/acl.py | 0 .../optimization/{acl_temp => acl}/mappings/__init__.py | 0 .../applications/optimization/{acl_temp => acl}/mappings/ising.py | 0 .../applications/optimization/{acl_temp => acl}/mappings/qubo.py | 0 .../applications/optimization/{mis_temp => mis}/__init__.py | 0 .../applications/optimization/{mis_temp => mis}/data/__init__.py | 0 .../optimization/{mis_temp => mis}/data/graph_layouts.py | 0 .../optimization/{mis_temp => mis}/mappings/__init__.py | 0 .../optimization/{mis_temp => mis}/mappings/neutral_atom.py | 0 .../applications/optimization/{mis_temp => mis}/mappings/qiro.py | 0 src/modules/applications/optimization/{mis_temp => mis}/mis.py | 0 .../applications/optimization/{pvc_temp => pvc}/__init__.py | 0 .../optimization/{pvc_temp => pvc}/data/createReferenceGraph.py | 0 .../optimization/{pvc_temp => pvc}/data/reference_data.txt | 0 .../optimization/{pvc_temp => pvc}/data/reference_graph.gpickle | 0 .../optimization/{pvc_temp => pvc}/mappings/__init__.py | 0 .../applications/optimization/{pvc_temp => pvc}/mappings/ising.py | 0 .../applications/optimization/{pvc_temp => pvc}/mappings/qubo.py | 0 src/modules/applications/optimization/{pvc_temp => pvc}/pvc.py | 0 .../applications/optimization/{sat_temp => sat}/__init__.py | 0 .../optimization/{sat_temp => sat}/mappings/__init__.py | 0 .../optimization/{sat_temp => sat}/mappings/choiising.py | 0 .../optimization/{sat_temp => sat}/mappings/choiqubo.py | 0 .../optimization/{sat_temp => sat}/mappings/dinneenising.py | 0 .../optimization/{sat_temp => sat}/mappings/dinneenqubo.py | 0 .../optimization/{sat_temp => sat}/mappings/direct.py | 0 .../optimization/{sat_temp => sat}/mappings/qubovertqubo.py | 0 src/modules/applications/optimization/{sat_temp => sat}/sat.py | 0 .../applications/optimization/{scp_temp => scp}/__init__.py | 0 .../applications/optimization/{scp_temp => scp}/data/__init__.py | 0 .../optimization/{scp_temp => scp}/data/set_cover_data_large.txt | 0 .../optimization/{scp_temp => scp}/mappings/__init__.py | 0 .../optimization/{scp_temp => scp}/mappings/qubovertqubo.py | 0 src/modules/applications/optimization/{scp_temp => scp}/scp.py | 0 .../applications/optimization/{tsp_temp => tsp}/__init__.py | 0 .../optimization/{tsp_temp => tsp}/data/createReferenceGraph.py | 0 .../applications/optimization/{tsp_temp => tsp}/data/dsj1000.tsp | 0 .../optimization/{tsp_temp => tsp}/data/reference_graph.gpickle | 0 .../optimization/{tsp_temp => tsp}/mappings/__init__.py | 0 .../applications/optimization/{tsp_temp => tsp}/mappings/ising.py | 0 .../applications/optimization/{tsp_temp => tsp}/mappings/qubo.py | 0 src/modules/applications/optimization/{tsp_temp => tsp}/tsp.py | 0 44 files changed, 0 insertions(+), 0 deletions(-) rename src/modules/applications/optimization/{acl_temp => acl}/Vehicle_data_QUARK.xlsx (100%) rename src/modules/applications/optimization/{acl_temp => acl}/__init__.py (100%) rename src/modules/applications/optimization/{acl_temp => acl}/acl.py (100%) rename src/modules/applications/optimization/{acl_temp => acl}/mappings/__init__.py (100%) rename src/modules/applications/optimization/{acl_temp => acl}/mappings/ising.py (100%) rename src/modules/applications/optimization/{acl_temp => acl}/mappings/qubo.py (100%) rename src/modules/applications/optimization/{mis_temp => mis}/__init__.py (100%) rename src/modules/applications/optimization/{mis_temp => mis}/data/__init__.py (100%) rename src/modules/applications/optimization/{mis_temp => mis}/data/graph_layouts.py (100%) rename src/modules/applications/optimization/{mis_temp => mis}/mappings/__init__.py (100%) rename src/modules/applications/optimization/{mis_temp => mis}/mappings/neutral_atom.py (100%) rename src/modules/applications/optimization/{mis_temp => mis}/mappings/qiro.py (100%) rename src/modules/applications/optimization/{mis_temp => mis}/mis.py (100%) rename src/modules/applications/optimization/{pvc_temp => pvc}/__init__.py (100%) rename src/modules/applications/optimization/{pvc_temp => pvc}/data/createReferenceGraph.py (100%) rename src/modules/applications/optimization/{pvc_temp => pvc}/data/reference_data.txt (100%) rename src/modules/applications/optimization/{pvc_temp => pvc}/data/reference_graph.gpickle (100%) rename src/modules/applications/optimization/{pvc_temp => pvc}/mappings/__init__.py (100%) rename src/modules/applications/optimization/{pvc_temp => pvc}/mappings/ising.py (100%) rename src/modules/applications/optimization/{pvc_temp => pvc}/mappings/qubo.py (100%) rename src/modules/applications/optimization/{pvc_temp => pvc}/pvc.py (100%) rename src/modules/applications/optimization/{sat_temp => sat}/__init__.py (100%) rename src/modules/applications/optimization/{sat_temp => sat}/mappings/__init__.py (100%) rename src/modules/applications/optimization/{sat_temp => sat}/mappings/choiising.py (100%) rename src/modules/applications/optimization/{sat_temp => sat}/mappings/choiqubo.py (100%) rename src/modules/applications/optimization/{sat_temp => sat}/mappings/dinneenising.py (100%) rename src/modules/applications/optimization/{sat_temp => sat}/mappings/dinneenqubo.py (100%) rename src/modules/applications/optimization/{sat_temp => sat}/mappings/direct.py (100%) rename src/modules/applications/optimization/{sat_temp => sat}/mappings/qubovertqubo.py (100%) rename src/modules/applications/optimization/{sat_temp => sat}/sat.py (100%) rename src/modules/applications/optimization/{scp_temp => scp}/__init__.py (100%) rename src/modules/applications/optimization/{scp_temp => scp}/data/__init__.py (100%) rename src/modules/applications/optimization/{scp_temp => scp}/data/set_cover_data_large.txt (100%) rename src/modules/applications/optimization/{scp_temp => scp}/mappings/__init__.py (100%) rename src/modules/applications/optimization/{scp_temp => scp}/mappings/qubovertqubo.py (100%) rename src/modules/applications/optimization/{scp_temp => scp}/scp.py (100%) rename src/modules/applications/optimization/{tsp_temp => tsp}/__init__.py (100%) rename src/modules/applications/optimization/{tsp_temp => tsp}/data/createReferenceGraph.py (100%) rename src/modules/applications/optimization/{tsp_temp => tsp}/data/dsj1000.tsp (100%) rename src/modules/applications/optimization/{tsp_temp => tsp}/data/reference_graph.gpickle (100%) rename src/modules/applications/optimization/{tsp_temp => tsp}/mappings/__init__.py (100%) rename src/modules/applications/optimization/{tsp_temp => tsp}/mappings/ising.py (100%) rename src/modules/applications/optimization/{tsp_temp => tsp}/mappings/qubo.py (100%) rename src/modules/applications/optimization/{tsp_temp => tsp}/tsp.py (100%) diff --git a/src/modules/applications/optimization/acl_temp/Vehicle_data_QUARK.xlsx b/src/modules/applications/optimization/acl/Vehicle_data_QUARK.xlsx similarity index 100% rename from src/modules/applications/optimization/acl_temp/Vehicle_data_QUARK.xlsx rename to src/modules/applications/optimization/acl/Vehicle_data_QUARK.xlsx diff --git a/src/modules/applications/optimization/acl_temp/__init__.py b/src/modules/applications/optimization/acl/__init__.py similarity index 100% rename from src/modules/applications/optimization/acl_temp/__init__.py rename to src/modules/applications/optimization/acl/__init__.py diff --git a/src/modules/applications/optimization/acl_temp/acl.py b/src/modules/applications/optimization/acl/acl.py similarity index 100% rename from src/modules/applications/optimization/acl_temp/acl.py rename to src/modules/applications/optimization/acl/acl.py diff --git a/src/modules/applications/optimization/acl_temp/mappings/__init__.py b/src/modules/applications/optimization/acl/mappings/__init__.py similarity index 100% rename from src/modules/applications/optimization/acl_temp/mappings/__init__.py rename to src/modules/applications/optimization/acl/mappings/__init__.py diff --git a/src/modules/applications/optimization/acl_temp/mappings/ising.py b/src/modules/applications/optimization/acl/mappings/ising.py similarity index 100% rename from src/modules/applications/optimization/acl_temp/mappings/ising.py rename to src/modules/applications/optimization/acl/mappings/ising.py diff --git a/src/modules/applications/optimization/acl_temp/mappings/qubo.py b/src/modules/applications/optimization/acl/mappings/qubo.py similarity index 100% rename from src/modules/applications/optimization/acl_temp/mappings/qubo.py rename to src/modules/applications/optimization/acl/mappings/qubo.py diff --git a/src/modules/applications/optimization/mis_temp/__init__.py b/src/modules/applications/optimization/mis/__init__.py similarity index 100% rename from src/modules/applications/optimization/mis_temp/__init__.py rename to src/modules/applications/optimization/mis/__init__.py diff --git a/src/modules/applications/optimization/mis_temp/data/__init__.py b/src/modules/applications/optimization/mis/data/__init__.py similarity index 100% rename from src/modules/applications/optimization/mis_temp/data/__init__.py rename to src/modules/applications/optimization/mis/data/__init__.py diff --git a/src/modules/applications/optimization/mis_temp/data/graph_layouts.py b/src/modules/applications/optimization/mis/data/graph_layouts.py similarity index 100% rename from src/modules/applications/optimization/mis_temp/data/graph_layouts.py rename to src/modules/applications/optimization/mis/data/graph_layouts.py diff --git a/src/modules/applications/optimization/mis_temp/mappings/__init__.py b/src/modules/applications/optimization/mis/mappings/__init__.py similarity index 100% rename from src/modules/applications/optimization/mis_temp/mappings/__init__.py rename to src/modules/applications/optimization/mis/mappings/__init__.py diff --git a/src/modules/applications/optimization/mis_temp/mappings/neutral_atom.py b/src/modules/applications/optimization/mis/mappings/neutral_atom.py similarity index 100% rename from src/modules/applications/optimization/mis_temp/mappings/neutral_atom.py rename to src/modules/applications/optimization/mis/mappings/neutral_atom.py diff --git a/src/modules/applications/optimization/mis_temp/mappings/qiro.py b/src/modules/applications/optimization/mis/mappings/qiro.py similarity index 100% rename from src/modules/applications/optimization/mis_temp/mappings/qiro.py rename to src/modules/applications/optimization/mis/mappings/qiro.py diff --git a/src/modules/applications/optimization/mis_temp/mis.py b/src/modules/applications/optimization/mis/mis.py similarity index 100% rename from src/modules/applications/optimization/mis_temp/mis.py rename to src/modules/applications/optimization/mis/mis.py diff --git a/src/modules/applications/optimization/pvc_temp/__init__.py b/src/modules/applications/optimization/pvc/__init__.py similarity index 100% rename from src/modules/applications/optimization/pvc_temp/__init__.py rename to src/modules/applications/optimization/pvc/__init__.py diff --git a/src/modules/applications/optimization/pvc_temp/data/createReferenceGraph.py b/src/modules/applications/optimization/pvc/data/createReferenceGraph.py similarity index 100% rename from src/modules/applications/optimization/pvc_temp/data/createReferenceGraph.py rename to src/modules/applications/optimization/pvc/data/createReferenceGraph.py diff --git a/src/modules/applications/optimization/pvc_temp/data/reference_data.txt b/src/modules/applications/optimization/pvc/data/reference_data.txt similarity index 100% rename from src/modules/applications/optimization/pvc_temp/data/reference_data.txt rename to src/modules/applications/optimization/pvc/data/reference_data.txt diff --git a/src/modules/applications/optimization/pvc_temp/data/reference_graph.gpickle b/src/modules/applications/optimization/pvc/data/reference_graph.gpickle similarity index 100% rename from src/modules/applications/optimization/pvc_temp/data/reference_graph.gpickle rename to src/modules/applications/optimization/pvc/data/reference_graph.gpickle diff --git a/src/modules/applications/optimization/pvc_temp/mappings/__init__.py b/src/modules/applications/optimization/pvc/mappings/__init__.py similarity index 100% rename from src/modules/applications/optimization/pvc_temp/mappings/__init__.py rename to src/modules/applications/optimization/pvc/mappings/__init__.py diff --git a/src/modules/applications/optimization/pvc_temp/mappings/ising.py b/src/modules/applications/optimization/pvc/mappings/ising.py similarity index 100% rename from src/modules/applications/optimization/pvc_temp/mappings/ising.py rename to src/modules/applications/optimization/pvc/mappings/ising.py diff --git a/src/modules/applications/optimization/pvc_temp/mappings/qubo.py b/src/modules/applications/optimization/pvc/mappings/qubo.py similarity index 100% rename from src/modules/applications/optimization/pvc_temp/mappings/qubo.py rename to src/modules/applications/optimization/pvc/mappings/qubo.py diff --git a/src/modules/applications/optimization/pvc_temp/pvc.py b/src/modules/applications/optimization/pvc/pvc.py similarity index 100% rename from src/modules/applications/optimization/pvc_temp/pvc.py rename to src/modules/applications/optimization/pvc/pvc.py diff --git a/src/modules/applications/optimization/sat_temp/__init__.py b/src/modules/applications/optimization/sat/__init__.py similarity index 100% rename from src/modules/applications/optimization/sat_temp/__init__.py rename to src/modules/applications/optimization/sat/__init__.py diff --git a/src/modules/applications/optimization/sat_temp/mappings/__init__.py b/src/modules/applications/optimization/sat/mappings/__init__.py similarity index 100% rename from src/modules/applications/optimization/sat_temp/mappings/__init__.py rename to src/modules/applications/optimization/sat/mappings/__init__.py diff --git a/src/modules/applications/optimization/sat_temp/mappings/choiising.py b/src/modules/applications/optimization/sat/mappings/choiising.py similarity index 100% rename from src/modules/applications/optimization/sat_temp/mappings/choiising.py rename to src/modules/applications/optimization/sat/mappings/choiising.py diff --git a/src/modules/applications/optimization/sat_temp/mappings/choiqubo.py b/src/modules/applications/optimization/sat/mappings/choiqubo.py similarity index 100% rename from src/modules/applications/optimization/sat_temp/mappings/choiqubo.py rename to src/modules/applications/optimization/sat/mappings/choiqubo.py diff --git a/src/modules/applications/optimization/sat_temp/mappings/dinneenising.py b/src/modules/applications/optimization/sat/mappings/dinneenising.py similarity index 100% rename from src/modules/applications/optimization/sat_temp/mappings/dinneenising.py rename to src/modules/applications/optimization/sat/mappings/dinneenising.py diff --git a/src/modules/applications/optimization/sat_temp/mappings/dinneenqubo.py b/src/modules/applications/optimization/sat/mappings/dinneenqubo.py similarity index 100% rename from src/modules/applications/optimization/sat_temp/mappings/dinneenqubo.py rename to src/modules/applications/optimization/sat/mappings/dinneenqubo.py diff --git a/src/modules/applications/optimization/sat_temp/mappings/direct.py b/src/modules/applications/optimization/sat/mappings/direct.py similarity index 100% rename from src/modules/applications/optimization/sat_temp/mappings/direct.py rename to src/modules/applications/optimization/sat/mappings/direct.py diff --git a/src/modules/applications/optimization/sat_temp/mappings/qubovertqubo.py b/src/modules/applications/optimization/sat/mappings/qubovertqubo.py similarity index 100% rename from src/modules/applications/optimization/sat_temp/mappings/qubovertqubo.py rename to src/modules/applications/optimization/sat/mappings/qubovertqubo.py diff --git a/src/modules/applications/optimization/sat_temp/sat.py b/src/modules/applications/optimization/sat/sat.py similarity index 100% rename from src/modules/applications/optimization/sat_temp/sat.py rename to src/modules/applications/optimization/sat/sat.py diff --git a/src/modules/applications/optimization/scp_temp/__init__.py b/src/modules/applications/optimization/scp/__init__.py similarity index 100% rename from src/modules/applications/optimization/scp_temp/__init__.py rename to src/modules/applications/optimization/scp/__init__.py diff --git a/src/modules/applications/optimization/scp_temp/data/__init__.py b/src/modules/applications/optimization/scp/data/__init__.py similarity index 100% rename from src/modules/applications/optimization/scp_temp/data/__init__.py rename to src/modules/applications/optimization/scp/data/__init__.py diff --git a/src/modules/applications/optimization/scp_temp/data/set_cover_data_large.txt b/src/modules/applications/optimization/scp/data/set_cover_data_large.txt similarity index 100% rename from src/modules/applications/optimization/scp_temp/data/set_cover_data_large.txt rename to src/modules/applications/optimization/scp/data/set_cover_data_large.txt diff --git a/src/modules/applications/optimization/scp_temp/mappings/__init__.py b/src/modules/applications/optimization/scp/mappings/__init__.py similarity index 100% rename from src/modules/applications/optimization/scp_temp/mappings/__init__.py rename to src/modules/applications/optimization/scp/mappings/__init__.py diff --git a/src/modules/applications/optimization/scp_temp/mappings/qubovertqubo.py b/src/modules/applications/optimization/scp/mappings/qubovertqubo.py similarity index 100% rename from src/modules/applications/optimization/scp_temp/mappings/qubovertqubo.py rename to src/modules/applications/optimization/scp/mappings/qubovertqubo.py diff --git a/src/modules/applications/optimization/scp_temp/scp.py b/src/modules/applications/optimization/scp/scp.py similarity index 100% rename from src/modules/applications/optimization/scp_temp/scp.py rename to src/modules/applications/optimization/scp/scp.py diff --git a/src/modules/applications/optimization/tsp_temp/__init__.py b/src/modules/applications/optimization/tsp/__init__.py similarity index 100% rename from src/modules/applications/optimization/tsp_temp/__init__.py rename to src/modules/applications/optimization/tsp/__init__.py diff --git a/src/modules/applications/optimization/tsp_temp/data/createReferenceGraph.py b/src/modules/applications/optimization/tsp/data/createReferenceGraph.py similarity index 100% rename from src/modules/applications/optimization/tsp_temp/data/createReferenceGraph.py rename to src/modules/applications/optimization/tsp/data/createReferenceGraph.py diff --git a/src/modules/applications/optimization/tsp_temp/data/dsj1000.tsp b/src/modules/applications/optimization/tsp/data/dsj1000.tsp similarity index 100% rename from src/modules/applications/optimization/tsp_temp/data/dsj1000.tsp rename to src/modules/applications/optimization/tsp/data/dsj1000.tsp diff --git a/src/modules/applications/optimization/tsp_temp/data/reference_graph.gpickle b/src/modules/applications/optimization/tsp/data/reference_graph.gpickle similarity index 100% rename from src/modules/applications/optimization/tsp_temp/data/reference_graph.gpickle rename to src/modules/applications/optimization/tsp/data/reference_graph.gpickle diff --git a/src/modules/applications/optimization/tsp_temp/mappings/__init__.py b/src/modules/applications/optimization/tsp/mappings/__init__.py similarity index 100% rename from src/modules/applications/optimization/tsp_temp/mappings/__init__.py rename to src/modules/applications/optimization/tsp/mappings/__init__.py diff --git a/src/modules/applications/optimization/tsp_temp/mappings/ising.py b/src/modules/applications/optimization/tsp/mappings/ising.py similarity index 100% rename from src/modules/applications/optimization/tsp_temp/mappings/ising.py rename to src/modules/applications/optimization/tsp/mappings/ising.py diff --git a/src/modules/applications/optimization/tsp_temp/mappings/qubo.py b/src/modules/applications/optimization/tsp/mappings/qubo.py similarity index 100% rename from src/modules/applications/optimization/tsp_temp/mappings/qubo.py rename to src/modules/applications/optimization/tsp/mappings/qubo.py diff --git a/src/modules/applications/optimization/tsp_temp/tsp.py b/src/modules/applications/optimization/tsp/tsp.py similarity index 100% rename from src/modules/applications/optimization/tsp_temp/tsp.py rename to src/modules/applications/optimization/tsp/tsp.py From 4518e82745ace8bea89e39f2a2f61e724df8fc27 Mon Sep 17 00:00:00 2001 From: GreshmaShaji Date: Fri, 7 Feb 2025 09:40:50 +0100 Subject: [PATCH 05/23] Temporary rename to fix case sensitivity issue --- src/installer.py | 455 ---------------- src/{Installer.py => installer_temp.py} | 0 src/metrics.py | 127 ----- src/{Metrics.py => metrics_temp.py} | 0 src/modules/applications/application.py | 49 -- .../{Application.py => application_temp.py} | 0 src/modules/applications/mapping.py | 70 --- .../{Mapping.py => mapping_temp.py} | 0 .../applications/optimization/mis/mis.py | 2 +- .../applications/optimization/optimization.py | 137 ----- .../{Optimization.py => optimization_temp.py} | 0 src/modules/applications/qml/circuit.py | 33 -- .../qml/{Circuit.py => circuit_temp.py} | 0 .../qml/generative_modeling/data/mg_2d.npy | 3 - .../data/{MG_2D.npy => mg_2d_temp.npy} | 0 .../qml/generative_modeling/data/o_2d.npy | 3 - .../data/{O_2D.npy => o_2d_temp.npy} | 0 .../generative_modeling/data/stocks_2d.npy | 3 - .../{Stocks_2D.npy => stocks_2d_temp.npy} | 0 .../qml/generative_modeling/data/x_2d.npy | 3 - .../data/{X_2D.npy => x_2d_temp.npy} | 0 .../generative_modeling/training/inference.py | 118 ---- .../{Inference.py => inference_temp.py} | 0 .../qml/generative_modeling/training/qcbm.py | 335 ------------ .../training/{QCBM.py => qcbm_temp.py} | 0 .../qml/generative_modeling/training/qgan.py | 508 ------------------ .../training/{QGAN.py => qgan_temp.py} | 0 .../transformations/pit.py | 257 --------- .../transformations/{PIT.py => pit_temp.py} | 0 .../transformations/transformation.py | 200 ------- ...ansformation.py => transformation_temp.py} | 0 src/modules/applications/qml/model.py | 59 -- .../qml/{Model.py => model_temp.py} | 0 src/modules/applications/qml/qml.py | 42 -- .../applications/qml/{QML.py => qml_temp.py} | 0 src/modules/applications/qml/training.py | 33 -- .../qml/{Training.py => training_temp.py} | 0 src/modules/core.py | 161 ------ src/modules/{Core.py => core_temp.py} | 0 src/modules/devices/braket/braket.py | 188 ------- .../braket/{Braket.py => braket_temp.py} | 0 src/modules/devices/braket/ionq.py | 59 -- .../devices/braket/{Ionq.py => ionq_temp.py} | 0 src/modules/devices/braket/oqc.py | 59 -- .../devices/braket/{OQC.py => oqc_temp.py} | 0 src/modules/devices/braket/rigetti.py | 59 -- .../braket/{Rigetti.py => rigetti_temp.py} | 0 src/modules/devices/braket/sv1.py | 59 -- .../devices/braket/{SV1.py => sv1_temp.py} | 0 src/modules/devices/braket/tn1.py | 59 -- .../devices/braket/{TN1.py => tn1_temp.py} | 0 src/modules/devices/device.py | 102 ---- .../devices/{Device.py => device_temp.py} | 0 src/modules/devices/local.py | 46 -- .../devices/{Local.py => local_temp.py} | 0 src/modules/devices/pulser/pulser.py | 59 -- .../pulser/{Pulser.py => pulser_temp.py} | 0 src/modules/solvers/annealer.py | 111 ---- .../solvers/{Annealer.py => annealer_temp.py} | 0 src/modules/solvers/qaoa.py | 477 ---------------- src/modules/solvers/{QAOA.py => qaoa_temp.py} | 0 src/modules/solvers/solver.py | 50 -- .../solvers/{Solver.py => solver_temp.py} | 0 src/plotter.py | 248 --------- src/{Plotter.py => plotter_temp.py} | 0 .../legacy_classes/application.py | 210 -------- .../{Application.py => application_temp.py} | 0 src/quark2_adapter/legacy_classes/device.py | 75 --- .../{Device.py => device_temp.py} | 0 src/quark2_adapter/legacy_classes/mapping.py | 108 ---- .../{Mapping.py => mapping_temp.py} | 0 src/quark2_adapter/legacy_classes/solver.py | 99 ---- .../{Solver.py => solver_temp.py} | 0 tests/configs/invalid/tsp.yml | 26 - .../configs/invalid/{TSP.yml => tsp_temp.yml} | 0 tests/configs/valid/acl.yml | 25 - tests/configs/valid/{ACL.yml => acl_temp.yml} | 0 ...eling.yml => generative_modeling_temp.yml} | 0 tests/configs/valid/mis.yml | 30 -- tests/configs/valid/{MIS.yml => mis_temp.yml} | 0 tests/configs/valid/pvc.yml | 35 -- tests/configs/valid/{PVC.yml => pvc_temp.yml} | 0 tests/configs/valid/sat.yml | 24 - tests/configs/valid/{SAT.yml => sat_temp.yml} | 0 tests/configs/valid/scp.yml | 23 - tests/configs/valid/{SCP.yml => scp_temp.yml} | 0 tests/configs/valid/tsp.yml | 25 - tests/configs/valid/{TSP.yml => tsp_temp.yml} | 0 .../optimization/ACL/mappings/test_ISING.py | 85 --- .../optimization/ACL/mappings/test_QUBO.py | 100 ---- .../applications/optimization/ACL/test_ACL.py | 137 ----- .../MIS/mappings/MIS_test_graph.pkl | Bin 200 -> 0 bytes .../applications/optimization/MIS/test_MIS.py | 76 --- .../optimization/PVC/mappings/test_ISING.py | 69 --- .../optimization/PVC/mappings/test_QUBO.py | 56 -- .../applications/optimization/PVC/test_PVC.py | 121 ----- .../SAT/mappings/test_ChoiISING.py | 65 --- .../SAT/mappings/test_ChoiQUBO.py | 55 -- .../SAT/mappings/test_DinneenISING.py | 63 --- .../SAT/mappings/test_DinneenQUBO.py | 53 -- .../optimization/SAT/mappings/test_Direct.py | 76 --- .../SAT/mappings/test_QubovertQUBO.py | 51 -- .../applications/optimization/SAT/test_SAT.py | 118 ---- .../SCP/mappings/test_qubovertQUBO.py | 56 -- .../applications/optimization/SCP/test_SCP.py | 89 --- .../optimization/TSP/mappings/test_ISING.py | 121 ----- .../optimization/TSP/mappings/test_QUBO.py | 47 -- .../applications/optimization/TSP/test_TSP.py | 118 ---- .../applications/optimization/acl/__init__.py | 0 .../optimization/acl/mappings/__init__.py | 0 .../{ACL => acl_temp}/__init__.py | 0 .../{ACL => acl_temp}/mappings/__init__.py | 0 .../{acl => acl_temp}/mappings/test_ising.py | 0 .../{acl => acl_temp}/mappings/test_qubo.py | 0 .../{acl => acl_temp}/test_acl.py | 0 .../{MIS => mis_temp}/__init__.py | 0 .../{MIS => mis_temp}/mappings/__init__.py | 0 .../mappings/mis_test_graph.pkl | Bin .../mappings/test_neutral_atom.py | 3 +- .../{MIS => mis_temp}/test_mis.py | 2 +- .../{PVC => pvc_temp}/__init__.py | 0 .../{PVC => pvc_temp}/mappings/__init__.py | 0 .../mappings/pvc_graph_1_seam.gpickle | 0 .../{PVC => pvc_temp}/mappings/test_ising.py | 4 +- .../{PVC => pvc_temp}/mappings/test_qubo.py | 2 +- .../{PVC => pvc_temp}/test_pvc.py | 2 +- .../{SAT => sat_temp}/__init__.py | 0 .../{SAT => sat_temp}/mappings/__init__.py | 0 .../mappings/test_choiIsing.py | 2 +- .../mappings/test_choiqubo.py | 2 +- .../mappings/test_dinneenising.py | 2 +- .../mappings/test_dinneenqubo.py | 2 +- .../{SAT => sat_temp}/mappings/test_direct.py | 2 +- .../mappings/test_qubovertqubo.py | 2 +- .../{SAT => sat_temp}/test_sat.py | 2 +- .../{SCP => scp_temp}/__init__.py | 0 .../{SCP => scp_temp}/mappings/__init__.py | 0 .../mappings/test_qubovertqubo.py | 2 +- .../{SCP => scp_temp}/test_scp.py | 2 +- .../{TSP => tsp_temp}/__init__.py | 0 .../{TSP => tsp_temp}/mappings/__init__.py | 0 .../{TSP => tsp_temp}/mappings/test_ising.py | 2 +- .../{TSP => tsp_temp}/mappings/test_qubo.py | 2 +- .../{TSP => tsp_temp}/test_tsp.py | 2 +- .../circuits/test_circuit_cardinality.py | 2 +- .../circuits/test_circuit_copula.py | 2 +- .../circuits/test_circuit_standard.py | 2 +- .../data/data_handler/test_continuous_data.py | 2 +- .../data/data_handler/test_discrete_data.py | 6 +- .../test_custom_qiskit_noisy_backend.py | 2 +- .../mappings/test_library_pennylane.py | 8 +- .../mappings/test_library_qiskit.py | 8 +- .../test_preset_qiskit_noisy_backend.py | 2 +- .../metrics/test_metrics_generalization.py | 2 +- .../training/test_Inference.py | 2 +- .../generative_modeling/training/test_QCBM.py | 2 +- .../generative_modeling/training/test_QGAN.py | 2 +- .../transformation/test_MinMax.py | 8 +- .../transformation/test_PIT.py | 4 +- ...rkManager.py => test_benchmark_manager.py} | 2 +- ...onfigManager.py => test_config_manager.py} | 4 +- tests/test_main.py | 51 ++ 162 files changed, 100 insertions(+), 6458 deletions(-) delete mode 100644 src/installer.py rename src/{Installer.py => installer_temp.py} (100%) delete mode 100644 src/metrics.py rename src/{Metrics.py => metrics_temp.py} (100%) delete mode 100644 src/modules/applications/application.py rename src/modules/applications/{Application.py => application_temp.py} (100%) delete mode 100644 src/modules/applications/mapping.py rename src/modules/applications/{Mapping.py => mapping_temp.py} (100%) delete mode 100644 src/modules/applications/optimization/optimization.py rename src/modules/applications/optimization/{Optimization.py => optimization_temp.py} (100%) delete mode 100644 src/modules/applications/qml/circuit.py rename src/modules/applications/qml/{Circuit.py => circuit_temp.py} (100%) delete mode 100644 src/modules/applications/qml/generative_modeling/data/mg_2d.npy rename src/modules/applications/qml/generative_modeling/data/{MG_2D.npy => mg_2d_temp.npy} (100%) delete mode 100644 src/modules/applications/qml/generative_modeling/data/o_2d.npy rename src/modules/applications/qml/generative_modeling/data/{O_2D.npy => o_2d_temp.npy} (100%) delete mode 100644 src/modules/applications/qml/generative_modeling/data/stocks_2d.npy rename src/modules/applications/qml/generative_modeling/data/{Stocks_2D.npy => stocks_2d_temp.npy} (100%) delete mode 100644 src/modules/applications/qml/generative_modeling/data/x_2d.npy rename src/modules/applications/qml/generative_modeling/data/{X_2D.npy => x_2d_temp.npy} (100%) delete mode 100644 src/modules/applications/qml/generative_modeling/training/inference.py rename src/modules/applications/qml/generative_modeling/training/{Inference.py => inference_temp.py} (100%) delete mode 100644 src/modules/applications/qml/generative_modeling/training/qcbm.py rename src/modules/applications/qml/generative_modeling/training/{QCBM.py => qcbm_temp.py} (100%) delete mode 100644 src/modules/applications/qml/generative_modeling/training/qgan.py rename src/modules/applications/qml/generative_modeling/training/{QGAN.py => qgan_temp.py} (100%) delete mode 100644 src/modules/applications/qml/generative_modeling/transformations/pit.py rename src/modules/applications/qml/generative_modeling/transformations/{PIT.py => pit_temp.py} (100%) delete mode 100644 src/modules/applications/qml/generative_modeling/transformations/transformation.py rename src/modules/applications/qml/generative_modeling/transformations/{Transformation.py => transformation_temp.py} (100%) delete mode 100644 src/modules/applications/qml/model.py rename src/modules/applications/qml/{Model.py => model_temp.py} (100%) delete mode 100644 src/modules/applications/qml/qml.py rename src/modules/applications/qml/{QML.py => qml_temp.py} (100%) delete mode 100644 src/modules/applications/qml/training.py rename src/modules/applications/qml/{Training.py => training_temp.py} (100%) delete mode 100644 src/modules/core.py rename src/modules/{Core.py => core_temp.py} (100%) delete mode 100644 src/modules/devices/braket/braket.py rename src/modules/devices/braket/{Braket.py => braket_temp.py} (100%) delete mode 100644 src/modules/devices/braket/ionq.py rename src/modules/devices/braket/{Ionq.py => ionq_temp.py} (100%) delete mode 100644 src/modules/devices/braket/oqc.py rename src/modules/devices/braket/{OQC.py => oqc_temp.py} (100%) delete mode 100644 src/modules/devices/braket/rigetti.py rename src/modules/devices/braket/{Rigetti.py => rigetti_temp.py} (100%) delete mode 100644 src/modules/devices/braket/sv1.py rename src/modules/devices/braket/{SV1.py => sv1_temp.py} (100%) delete mode 100644 src/modules/devices/braket/tn1.py rename src/modules/devices/braket/{TN1.py => tn1_temp.py} (100%) delete mode 100644 src/modules/devices/device.py rename src/modules/devices/{Device.py => device_temp.py} (100%) delete mode 100644 src/modules/devices/local.py rename src/modules/devices/{Local.py => local_temp.py} (100%) delete mode 100644 src/modules/devices/pulser/pulser.py rename src/modules/devices/pulser/{Pulser.py => pulser_temp.py} (100%) delete mode 100644 src/modules/solvers/annealer.py rename src/modules/solvers/{Annealer.py => annealer_temp.py} (100%) delete mode 100644 src/modules/solvers/qaoa.py rename src/modules/solvers/{QAOA.py => qaoa_temp.py} (100%) delete mode 100644 src/modules/solvers/solver.py rename src/modules/solvers/{Solver.py => solver_temp.py} (100%) delete mode 100644 src/plotter.py rename src/{Plotter.py => plotter_temp.py} (100%) delete mode 100644 src/quark2_adapter/legacy_classes/application.py rename src/quark2_adapter/legacy_classes/{Application.py => application_temp.py} (100%) delete mode 100644 src/quark2_adapter/legacy_classes/device.py rename src/quark2_adapter/legacy_classes/{Device.py => device_temp.py} (100%) delete mode 100644 src/quark2_adapter/legacy_classes/mapping.py rename src/quark2_adapter/legacy_classes/{Mapping.py => mapping_temp.py} (100%) delete mode 100644 src/quark2_adapter/legacy_classes/solver.py rename src/quark2_adapter/legacy_classes/{Solver.py => solver_temp.py} (100%) delete mode 100644 tests/configs/invalid/tsp.yml rename tests/configs/invalid/{TSP.yml => tsp_temp.yml} (100%) delete mode 100644 tests/configs/valid/acl.yml rename tests/configs/valid/{ACL.yml => acl_temp.yml} (100%) rename tests/configs/valid/{generative_modeling.yml => generative_modeling_temp.yml} (100%) delete mode 100644 tests/configs/valid/mis.yml rename tests/configs/valid/{MIS.yml => mis_temp.yml} (100%) delete mode 100644 tests/configs/valid/pvc.yml rename tests/configs/valid/{PVC.yml => pvc_temp.yml} (100%) delete mode 100644 tests/configs/valid/sat.yml rename tests/configs/valid/{SAT.yml => sat_temp.yml} (100%) delete mode 100644 tests/configs/valid/scp.yml rename tests/configs/valid/{SCP.yml => scp_temp.yml} (100%) delete mode 100644 tests/configs/valid/tsp.yml rename tests/configs/valid/{TSP.yml => tsp_temp.yml} (100%) delete mode 100644 tests/modules/applications/optimization/ACL/mappings/test_ISING.py delete mode 100644 tests/modules/applications/optimization/ACL/mappings/test_QUBO.py delete mode 100644 tests/modules/applications/optimization/ACL/test_ACL.py delete mode 100644 tests/modules/applications/optimization/MIS/mappings/MIS_test_graph.pkl delete mode 100644 tests/modules/applications/optimization/MIS/test_MIS.py delete mode 100644 tests/modules/applications/optimization/PVC/mappings/test_ISING.py delete mode 100644 tests/modules/applications/optimization/PVC/mappings/test_QUBO.py delete mode 100644 tests/modules/applications/optimization/PVC/test_PVC.py delete mode 100644 tests/modules/applications/optimization/SAT/mappings/test_ChoiISING.py delete mode 100644 tests/modules/applications/optimization/SAT/mappings/test_ChoiQUBO.py delete mode 100644 tests/modules/applications/optimization/SAT/mappings/test_DinneenISING.py delete mode 100644 tests/modules/applications/optimization/SAT/mappings/test_DinneenQUBO.py delete mode 100644 tests/modules/applications/optimization/SAT/mappings/test_Direct.py delete mode 100644 tests/modules/applications/optimization/SAT/mappings/test_QubovertQUBO.py delete mode 100644 tests/modules/applications/optimization/SAT/test_SAT.py delete mode 100644 tests/modules/applications/optimization/SCP/mappings/test_qubovertQUBO.py delete mode 100644 tests/modules/applications/optimization/SCP/test_SCP.py delete mode 100644 tests/modules/applications/optimization/TSP/mappings/test_ISING.py delete mode 100644 tests/modules/applications/optimization/TSP/mappings/test_QUBO.py delete mode 100644 tests/modules/applications/optimization/TSP/test_TSP.py delete mode 100644 tests/modules/applications/optimization/acl/__init__.py delete mode 100644 tests/modules/applications/optimization/acl/mappings/__init__.py rename tests/modules/applications/optimization/{ACL => acl_temp}/__init__.py (100%) rename tests/modules/applications/optimization/{ACL => acl_temp}/mappings/__init__.py (100%) rename tests/modules/applications/optimization/{acl => acl_temp}/mappings/test_ising.py (100%) rename tests/modules/applications/optimization/{acl => acl_temp}/mappings/test_qubo.py (100%) rename tests/modules/applications/optimization/{acl => acl_temp}/test_acl.py (100%) rename tests/modules/applications/optimization/{MIS => mis_temp}/__init__.py (100%) rename tests/modules/applications/optimization/{MIS => mis_temp}/mappings/__init__.py (100%) rename tests/modules/applications/optimization/{MIS => mis_temp}/mappings/mis_test_graph.pkl (100%) rename tests/modules/applications/optimization/{MIS => mis_temp}/mappings/test_neutral_atom.py (95%) rename tests/modules/applications/optimization/{MIS => mis_temp}/test_mis.py (98%) rename tests/modules/applications/optimization/{PVC => pvc_temp}/__init__.py (100%) rename tests/modules/applications/optimization/{PVC => pvc_temp}/mappings/__init__.py (100%) rename tests/modules/applications/optimization/{PVC => pvc_temp}/mappings/pvc_graph_1_seam.gpickle (100%) rename tests/modules/applications/optimization/{PVC => pvc_temp}/mappings/test_ising.py (94%) rename tests/modules/applications/optimization/{PVC => pvc_temp}/mappings/test_qubo.py (96%) rename tests/modules/applications/optimization/{PVC => pvc_temp}/test_pvc.py (98%) rename tests/modules/applications/optimization/{SAT => sat_temp}/__init__.py (100%) rename tests/modules/applications/optimization/{SAT => sat_temp}/mappings/__init__.py (100%) rename tests/modules/applications/optimization/{SAT => sat_temp}/mappings/test_choiIsing.py (97%) rename tests/modules/applications/optimization/{SAT => sat_temp}/mappings/test_choiqubo.py (96%) rename tests/modules/applications/optimization/{SAT => sat_temp}/mappings/test_dinneenising.py (96%) rename tests/modules/applications/optimization/{SAT => sat_temp}/mappings/test_dinneenqubo.py (96%) rename tests/modules/applications/optimization/{SAT => sat_temp}/mappings/test_direct.py (97%) rename tests/modules/applications/optimization/{SAT => sat_temp}/mappings/test_qubovertqubo.py (95%) rename tests/modules/applications/optimization/{SAT => sat_temp}/test_sat.py (98%) rename tests/modules/applications/optimization/{SCP => scp_temp}/__init__.py (100%) rename tests/modules/applications/optimization/{SCP => scp_temp}/mappings/__init__.py (100%) rename tests/modules/applications/optimization/{SCP => scp_temp}/mappings/test_qubovertqubo.py (96%) rename tests/modules/applications/optimization/{SCP => scp_temp}/test_scp.py (98%) rename tests/modules/applications/optimization/{TSP => tsp_temp}/__init__.py (100%) rename tests/modules/applications/optimization/{TSP => tsp_temp}/mappings/__init__.py (100%) rename tests/modules/applications/optimization/{TSP => tsp_temp}/mappings/test_ising.py (98%) rename tests/modules/applications/optimization/{TSP => tsp_temp}/mappings/test_qubo.py (96%) rename tests/modules/applications/optimization/{TSP => tsp_temp}/test_tsp.py (98%) rename tests/{test_BenchmarkManager.py => test_benchmark_manager.py} (99%) rename tests/{test_ConfigManager.py => test_config_manager.py} (99%) create mode 100644 tests/test_main.py diff --git a/src/installer.py b/src/installer.py deleted file mode 100644 index e3c941e1..00000000 --- a/src/installer.py +++ /dev/null @@ -1,455 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import logging -import json -import os -import time -from pathlib import Path -import inspect - -import yaml -from packaging import version -import inquirer - -from modules.core import Core -from utils import _get_instance_with_sub_options, get_git_revision, checkbox - - -class Installer: - """ - Installer class that can be used by the user to install certain QUARK modules and also return the required Python - packages for the demanded modules. - """ - - def __init__(self): - self.quark_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", ) - self.settings_dir = f"{self.quark_dir}/.settings" - self.envs_dir = f"{self.settings_dir}/envs" - self.python_version = "3.12.7" - self.pip_version = "23.0" - self.default_app_modules = [ - {"name": "PVC", "class": "PVC", "module": "modules.applications.optimization.pvc.pvc"}, - {"name": "SAT", "class": "SAT", "module": "modules.applications.optimization.sat.sat"}, - {"name": "TSP", "class": "TSP", "module": "modules.applications.optimization.tsp.tsp"}, - {"name": "ACL", "class": "ACL", "module": "modules.applications.optimization.acl.acl"}, - {"name": "MIS", "class": "MIS", "module": "modules.applications.optimization.mis.mis"}, - {"name": "SCP", "class": "SCP", "module": "modules.applications.optimization.scp.scp"}, - {"name": "GenerativeModeling", "class": "GenerativeModeling", - "module": "modules.applications.qml.generative_modeling.generative_modeling"} - ] - - self.core_requirements = [ - {"name": "seaborn", "version": "0.13.2"}, - {"name": "networkx", "version": "3.4.2"}, - {"name": "inquirer", "version": "3.4.0"}, - {"name": "packaging", "version": "24.2"}, - {"name": "pyyaml", "version": "6.0.2"}, - {"name": "typing-extensions", "version": "4.12.2"}, - {"name": "sphinx", "version": "8.1.3"}, - {"name": "sphinx-rtd-theme", "version": "3.0.2"}, - ] - Path(self.envs_dir).mkdir(parents=True, exist_ok=True) - - def configure(self, env_name="default") -> None: - """ - Configures a new QUARK environment or overwrites an existing one. - - :param env_name: Name of the env to configure - """ - configured_envs = self.check_for_configs() - - if env_name in configured_envs: - answer_continue = inquirer.prompt([ - inquirer.List("continue", - message=f"{env_name} found in the list of existing QUARK module environment, are you" - f" sure you want to overwrite it?", - choices=["Yes", "No"], )])["continue"] - - if answer_continue.lower() == "no": - logging.info("Exiting configuration") - return - - chosen_config_type = inquirer.prompt([ - inquirer.List("config", - message="Do you want to use the default configuration or a custom environment?", - choices=["Default", "Custom"])])["config"] - logging.info(f"You chose {chosen_config_type}") - - module_db = self.get_module_db() - - if chosen_config_type == "Default": - self.save_env(module_db, env_name) - elif chosen_config_type == "Custom": - module_db = self.start_query_user(module_db) - self.save_env(module_db, env_name) - - requirements = self.collect_requirements(module_db["modules"]) - activate_requirements = checkbox("requirements", "Should we create an package file, if yes for " - "which package manager?", - ["Conda", "PIP", "Print it here"])["requirements"] - - if "Conda" in activate_requirements: - self.create_conda_file(requirements, env_name) - if "PIP" in activate_requirements: - self.create_req_file(requirements, env_name) - if "Print it here" in activate_requirements: - logging.info("Please install:") - for p, v in requirements.items(): - logging.info(f" - {p}{': ' + v[0] if v else ''}") - - activate_answer = inquirer.prompt([ - inquirer.List("activate", - message="Do you want to activate the QUARK module environment?", - choices=["Yes", "No"])])["activate"] - - if activate_answer == "Yes": - self.set_active_env(env_name) - - def check_for_configs(self) -> list: - """ - Checks if QUARK is already configured and if yes, which environments. - - :return: Returns the configured QUARK envs in a list - """ - return list(p.stem for p in Path(self.envs_dir).glob("*.json")) - - def set_active_env(self, name: str) -> None: - """ - Sets the active env to active_env.json. - - :param name: Name of the env - """ - self._check_if_env_exists(name) - with open(f"{self.settings_dir}/active_env.json", "w") as jsonFile: - json.dump({"name": name}, jsonFile, indent=2) - - logging.info(f"Set active QUARK module environment to {name}") - - def check_active_env(self) -> bool: - """ - Checks if .settings/active_env.json exists. - - :return: True if active_env.json exists - """ - return Path(f"{self.settings_dir}/active_env.json").is_file() - - def get_active_env(self) -> str: - """ - Returns the current active environment. - - :return: Returns the name of the active env - """ - if not self.check_active_env(): - logging.warning("No active QUARK module environment found, using default") - module_db = self.get_module_db() - self.save_env(module_db, "default") - self.set_active_env("default") - - with open(f"{self.settings_dir}/active_env.json", "r") as filehandler: - env = json.load(filehandler) - return env["name"] - - def get_env(self, name: str) -> list[dict]: - """ - Loads the env from file and returns it. - - :param name: Name of the env - :return: Returns the modules of the env - """ - file = f"{self.envs_dir}/{name}.json" - self._check_if_env_exists(name) - - with open(file, "r") as filehandler: - env = json.load(filehandler) - logging.info(f"Getting {name} QUARK module environment") - module_db_build_number = self.get_module_db_build_number() - if env["build_number"] < module_db_build_number: - logging.warning(f"You QUARK module env is based on an outdated build version of the module database " - f"(BUILD NUMBER {env['build_number']}). The current module database (BUILD NUMBER " - f"{module_db_build_number}) might bring new features. You should think about " - f"updating your environment!") - - return env["modules"] - - def _check_if_env_exists(self, name: str) -> str: - """ - Checks if a given env exists, returns the location of the associated JSON file and raises an error otherwise. - - :param name: Name of the env - :return: Returns location of the JSON file associated with the env if it exists - """ - file = f"{self.envs_dir}/{name}.json" - if not Path(file).is_file(): - raise ValueError(f"QUARK environment {name} could not be found!") - return file - - def save_env(self, env: dict, name: str) -> None: - """ - Saves a created env to a file with the name of choice. - - :param env: Env which should be saved - :param name: Name of the env - """ - with open(f"{self.envs_dir}/{name}.json", "w") as jsonFile: - json.dump(env, jsonFile, indent=2) - - logging.info(f"Saved {name} QUARK module environment.") - - def start_query_user(self, module_db: dict) -> dict: - """ - Queries the user which applications and submodules to include. - - :param module_db: module_db file - :return: Returns the module_db with selected (sub)modules - """ - answer_apps = checkbox("apps", "Which application would you like to include?", - [m["name"] for m in module_db["modules"]])["apps"] - - module_db["modules"] = [x for x in module_db["modules"] if x["name"] in answer_apps] - - for idx, entry in enumerate(module_db["modules"]): - self.query_user(module_db["modules"][idx], entry["name"]) - - return module_db - - def query_user(self, submodules: dict, name: str) -> None: - """ - Queries the user which submodules to include - - :param submodules: Submodules for the module - :param name: Name of the module - """ - if submodules["submodules"]: - answer_submodules = \ - checkbox("submodules", f"Which submodule would you like to include for {name}?", - [m["name"] for m in submodules["submodules"]])["submodules"] - - submodules["submodules"] = [x for x in submodules["submodules"] if x["name"] in answer_submodules] - for idx, entry in enumerate(submodules["submodules"]): - self.query_user(submodules["submodules"][idx], entry["name"]) - - def get_module_db(self) -> dict: - """ - Returns the module database that contains all module possibilities. - - :return: Module Database - """ - with open(f"{self.settings_dir}/module_db.json", "r") as filehandler: - return json.load(filehandler) - - def create_module_db(self) -> None: - """ - Creates module database by automatically going through the available submodules for each module. - """ - logging.info("Creating Module Database") - - # TODO Helper to skip constructor in Braket.py. Should probably be changed in the future! - os.environ["SKIP_INIT"] = "True" - - module_db_modules: list[dict] = self.default_app_modules - - for idx, app in enumerate(module_db_modules): - logging.info(f"Processing {app['name']}") - app_instance = _get_instance_with_sub_options(module_db_modules, app["name"]) - module_db_modules[idx]["submodules"] = [ - Installer._create_module_db_helper(app_instance.get_submodule(sm), sm) for - sm in app_instance.submodule_options] - module_db_modules[idx]["requirements"] = app_instance.get_requirements() - - git_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", ) - git_revision_number, _ = get_git_revision(git_dir) - module_db = { - "build_number": self.get_module_db_build_number() + 1, - "build_date": time.strftime("%d-%m-%Y %H:%M:%S", time.gmtime()), - "git_revision_number": git_revision_number, - "modules": module_db_modules - } - - # loop over first level and add to file - with open(f"{self.settings_dir}/module_db.json", "w") as jsonFile: - json.dump(module_db, jsonFile, indent=2) - - requirements = self.collect_requirements(module_db_modules) - self.create_req_file(requirements, "full", self.settings_dir) - - logging.info("Created module_db file") - - @staticmethod - def _create_module_db_helper(module: Core, name: str) -> dict: - """ - Recursive helper function for create_module_db. - - :param module: Module instance - :param name: Name of the module - :return: Module dict - """ - return { - "name": name, - "class": module.__class__.__name__, - "args": {k: v for k, v in module.__dict__.items() if k in - inspect.signature(module.__init__).parameters.keys()}, - "module": module.__module__, - "requirements": module.get_requirements(), - "submodules": [Installer._create_module_db_helper(module.get_submodule(sm), sm) for sm in - module.submodule_options] - } - - def get_module_db_build_number(self) -> int: - """ - Returns the build number of the module_db. - - :return: Returns the build number of the module_db if it exists, otherwise 0 - """ - if Path(f"{self.settings_dir}/module_db.json").is_file(): - module_db = self.get_module_db() - return module_db["build_number"] - else: - return 0 - - def collect_requirements(self, env: list[dict]) -> dict: - """ - Collects requirements of the different modules in the given env file. - - :param env: Environment configuration - :return: Collected requirements - """ - requirements: list[dict] = self.core_requirements - for app in env: - requirements.extend(Installer._collect_requirements_helper(app)) - - # Counts duplicates and checks if any version conflicts occur - merged_requirements: dict = {} - for req in requirements: - if req["name"] in merged_requirements: - # Checks if the specific version is already there, if so the req is skipped - if merged_requirements[req["name"]] and ("version" in req and req["version"] not in - merged_requirements[req["name"]]): - merged_requirements[req["name"]].append(req["version"]) - - else: - # Sometimes there is no specific version required, then the "version" field is missing - merged_requirements[req["name"]] = [req["version"]] if "version" in req else [] - - for k, v in merged_requirements.items(): - if len(v) > 1: - # If there are multiple different versions, the latest version is selected - newest_version = sorted(v, key=lambda x: version.Version(x))[-1] # pylint: disable=W0108 - merged_requirements[k] = [newest_version] - logging.warning(f"Different version requirements for {k}: {v}. Will try to take the newer version " - f"{newest_version}, but this might cause problems at QUARK runtime!") - - return merged_requirements - - @staticmethod - def _collect_requirements_helper(module: dict) -> list[dict]: - """ - Helper function for collect_requirements_helper that recursively checks modules for requirements. - - :param module: Module dict - :return: List of dicts with the requirements - """ - requirements = module["requirements"] - for submodule in module["submodules"]: - requirements.extend(Installer._collect_requirements_helper(submodule)) - - return requirements - - def create_conda_file(self, requirements: dict, name: str, directory: str = None) -> None: - """ - Creates conda yaml file based on the requirements. - - :param requirements: Collected requirements - :param name: Name of the conda env - :param directory: Directory where the file should be saved. If None self.envs_dir will be taken. - """ - if directory is None: - directory = self.envs_dir - conda_data = { - "name": name, - "channels": ["defaults"], - "dependencies": [ - f"python={self.python_version}", - f"pip={self.pip_version}", - {"pip": [(f"{k}=={v[0]}" if v else k) for k, v in requirements.items()]} - - ] - } - with open(f"{directory}/conda_{name}.yml", "w") as filehandler: - yaml.dump(conda_data, filehandler) - - logging.info("Saved conda env file, if you like to install it run:") - logging.info(f"conda env create -f {directory}/conda_{name}.yml") - - def create_req_file(self, requirements: dict, name: str, directory: str = None) -> None: - """ - Creates pip txt file based on the requirements. - - :param requirements: Collected requirements - :param name: Name of the env - :param directory: Directory where the file should be saved. If None self.envs_dir will be taken. - """ - if directory is None: - directory = self.envs_dir - with open(f"{directory}/requirements_{name}.txt", "w") as filehandler: - for k, v in requirements.items(): - filehandler.write(f"{k}=={v[0]}" if v else k) - filehandler.write("\n") - - logging.info("Saved pip txt file, if you like to install it run:") - logging.info(f"pip install -r {directory}/requirements_{name}.txt") - - def list_envs(self) -> None: - """ - List all existing environments. - """ - logging.info("Existing environments:") - for env in self.check_for_configs(): - logging.info(f" - {env}") - - @staticmethod - def show(env: list[dict]) -> None: - """ - Visualize the env. - - :param env: Environment configuration - """ - space = " " - branch = "| " - connector = "|-- " - leaf = ">-- " - - def tree(modules: list[dict], prefix: str = ""): - """ - A recursive function that generates a tree from the modules. - This function is based on https://stackoverflow.com/a/59109706, but modified to the needs here. - - :param modules: Modules list - :param prefix: Prefix for the indentation - :return: Generator yielding formatted lines of the environment tree - """ - # Modules in the middle/beginning get a |--, the final leaf >-- - pointers = [connector] * (len(modules) - 1) + [leaf] - for pointer, module in zip(pointers, modules): - yield prefix + pointer + module["name"] - - if module["submodules"]: - # If the module has any submodules - extension = branch if pointer == connector else space - # Check if we are at the end of the tree - yield from tree(module["submodules"], prefix=prefix + extension) - - logging.info("Content of the environment:") - for module in tree(env): - logging.info(module) diff --git a/src/Installer.py b/src/installer_temp.py similarity index 100% rename from src/Installer.py rename to src/installer_temp.py diff --git a/src/metrics.py b/src/metrics.py deleted file mode 100644 index 3f93c21e..00000000 --- a/src/metrics.py +++ /dev/null @@ -1,127 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from typing import final - - -class Metrics: - """ - Metrics Module, used by every QUARK module. - """ - - def __init__(self, module_name: str, module_src: str): - """ - Constructor for Metrics class. - - :param module_name: Name of the module this metrics object belongs to - :param module_src: Source file of the module this metrics object belongs to - """ - self.module_name = module_name - self.module_src = module_src - self.preprocessing_time = None - self.preprocessing_time_unit = "ms" - self.postprocessing_time = None - self.module_config = None - self.total_time = None - self.total_time_unit = "ms" - self.postprocessing_time_unit = "ms" - self.additional_metrics = {} - - @final - def validate(self) -> None: - """ - Validates whether the mandatory metrics got recorded, then sets total time. - """ - assert self.preprocessing_time is not None, ( - "preprocessing time must not be None!" - ) - assert self.postprocessing_time is not None, ( - "postprocessing time must not be None!" - ) - self.total_time = self.preprocessing_time + self.postprocessing_time - - @final - def set_preprocessing_time(self, value: float) -> None: - """ - Sets the preprocessing time. - - :param value: Time - """ - self.preprocessing_time = value - - @final - def set_module_config(self, config: dict) -> None: - """ - Sets the config of the module this metrics object belongs to. - - :param config: Config of the QUARK module - """ - self.module_config = config - - @final - def set_postprocessing_time(self, value: float) -> None: - """ - Sets the postprocessing time. - - :param value: Time - """ - self.postprocessing_time = value - - @final - def add_metric(self, name: str, value: any) -> None: - """ - Adds a single metric. - - :param name: Name of the metric - :param value: Value of the metric - """ - self.additional_metrics.update({name: value}) - - @final - def add_metric_batch(self, key_values: dict) -> None: - """ - Adds a dictionary containing metrics to the existing metrics. - - :param key_values: Dict containing metrics - """ - self.additional_metrics.update(key_values) - - @final - def reset(self) -> None: - """ - Resets all recorded metrics. - """ - self.preprocessing_time = None - self.postprocessing_time = None - self.additional_metrics = {} - - @final - def get(self) -> dict: - """ - Returns all recorded metrics. - - :return: Metrics as a dict - """ - return { - "module_name": self.module_name, - "module_src": self.module_src, - "module_config": self.module_config, - "total_time": self.total_time, - "total_time_unit": self.total_time_unit, - "preprocessing_time": self.preprocessing_time, - "preprocessing_time_unit": self.preprocessing_time_unit, - "postprocessing_time": self.postprocessing_time, - "postprocessing_time_unit": self.postprocessing_time_unit, - **self.additional_metrics - } diff --git a/src/Metrics.py b/src/metrics_temp.py similarity index 100% rename from src/Metrics.py rename to src/metrics_temp.py diff --git a/src/modules/applications/application.py b/src/modules/applications/application.py deleted file mode 100644 index 35b3f136..00000000 --- a/src/modules/applications/application.py +++ /dev/null @@ -1,49 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from abc import ABC, abstractmethod -from modules.core import Core - - -class Application(Core, ABC): - """ - The application component defines the workload, comprising a dataset of increasing complexity, a validation, and an - evaluation function. - """ - - def __init__(self, application_name: str): - """ - Constructor method. - """ - super().__init__(application_name) - self.application_name = self.name - self.application = None - - def get_application(self) -> any: - """ - Gets the application. - - :return: self.application - """ - return self.application - - @abstractmethod - def save(self, path: str, iter_count: int) -> None: - """ - Saves the concrete problem. - - :param path: Path of the experiment directory for this run - :param iter_count: The iteration count - """ - pass diff --git a/src/modules/applications/Application.py b/src/modules/applications/application_temp.py similarity index 100% rename from src/modules/applications/Application.py rename to src/modules/applications/application_temp.py diff --git a/src/modules/applications/mapping.py b/src/modules/applications/mapping.py deleted file mode 100644 index 67ab5366..00000000 --- a/src/modules/applications/mapping.py +++ /dev/null @@ -1,70 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from abc import ABC, abstractmethod -from modules.core import Core - - -class Mapping(Core, ABC): - """ - This module translates the input data and problem specification from the parent module, - e.g., the application into a mathematical formulation suitable the submodule, e.g., a solver. - """ - - def preprocess(self, input_data: any, config: dict, **kwargs) -> tuple[any, float]: - """ - Maps the data to the correct target format. - - :param input_data: Data which should be mapped - :param config: Config of the mapping - :param kwargs: Optional keyword arguments - :return: Tuple with mapped problem and the time it took to map it - """ - output, preprocessing_time = self.map(input_data, config) - return output, preprocessing_time - - def postprocess(self, input_data: any, config: dict, **kwargs) -> tuple[any, float]: - """ - Reverse transformation/mapping from the submodule's format to the mathematical formulation - suitable for the parent module. - - :param input_data: Data which should be reverse-mapped - :param config: Config of the reverse mapping - :param kwargs: Optional keyword arguments - :return: Tuple with reverse-mapped problem and the time it took to map it - """ - output, postprocessing_time = self.reverse_map(input_data) - return output, postprocessing_time - - @abstractmethod - def map(self, problem: any, config: dict) -> tuple[any, float]: - """ - Maps the given problem into a specific format suitable for the submodule, e.g., a solver. - - :param config: Instance of class Config specifying the mapping settings - :param problem: Problem instance which should be mapped to the target representation - :return: Mapped problem and the time it took to map it - """ - pass - - def reverse_map(self, solution: any) -> tuple[any, float]: - """ - Maps the solution back to the original problem. This might not be necessary in all cases, so the default is - to return the original solution. This might be needed to convert the solution to a representation needed - for validation and evaluation. - - :param solution: Solution provided by submodule, e.g., the Solver class - :return: Reverse-mapped solution and the time it took to create it - """ - return solution, 0 diff --git a/src/modules/applications/Mapping.py b/src/modules/applications/mapping_temp.py similarity index 100% rename from src/modules/applications/Mapping.py rename to src/modules/applications/mapping_temp.py diff --git a/src/modules/applications/optimization/mis/mis.py b/src/modules/applications/optimization/mis/mis.py index e146ba9d..a13cd791 100644 --- a/src/modules/applications/optimization/mis/mis.py +++ b/src/modules/applications/optimization/mis/mis.py @@ -85,7 +85,7 @@ def get_default_submodule(self, option: str) -> Core: from modules.applications.optimization.mis.mappings.qiro import QIRO # pylint: disable=C0415 return QIRO() elif option == "NeutralAtom": - from QUARK.src.modules.applications.optimization.mis.mappings.neutral_atom import NeutralAtom # pylint: disable=C0415 + from modules.applications.optimization.mis.mappings.neutralatom import NeutralAtom # pylint: disable=C0415 return NeutralAtom() else: raise NotImplementedError(f"Mapping Option {option} not implemented") diff --git a/src/modules/applications/optimization/optimization.py b/src/modules/applications/optimization/optimization.py deleted file mode 100644 index 5c87949b..00000000 --- a/src/modules/applications/optimization/optimization.py +++ /dev/null @@ -1,137 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -from abc import ABC, abstractmethod -import logging - -from modules.applications.application import Application -from utils import start_time_measurement, end_time_measurement - - -class Optimization(Application, ABC): - """ - Optimization Module for QUARK, is used by all Optimization applications. - """ - - @abstractmethod - def validate(self, solution: any) -> tuple[bool, float]: - """ - Checks if the solution is a valid solution. - - :param solution: Proposed solution - :return: Bool value if solution is valid and the time it took to validate the solution - """ - pass - - @abstractmethod - def get_solution_quality_unit(self) -> str: - """ - Returns the unit of the evaluation. - - :return: String with the unit - """ - pass - - @abstractmethod - def evaluate(self, solution: any) -> tuple[float, float]: - """ - Checks how good the solution is. - - :param solution: Provided solution - :return: Tuple with the evaluation and the time it took to create it - """ - pass - - @abstractmethod - def generate_problem(self, config: dict) -> any: - """ - Creates a concrete problem and returns it. - - :param config: Configuration for problem creation - :return: Generated problem - """ - pass - - def process_solution(self, solution: any) -> tuple[any, float]: - """ - Most of the time the solution has to be processed before it can be validated and evaluated. - This might not be necessary in all cases, so the default is to return the original solution. - - :param solution: Proposed solution - :return: Tuple with processed solution and the execution time to process it - """ - return solution, 0.0 - - def preprocess(self, input_data: any, config: dict, **kwargs) -> tuple[any, float]: - """ - For optimization problems, we generate the actual problem instance in the preprocess function. - - :param input_data: Input data (usually not used in this method) - :param config: Config for the problem creation - :param kwargs: Optional additional arguments - :return: Tuple with output and the preprocessing time - """ - start = start_time_measurement() - output = self.generate_problem(config) - return output, end_time_measurement(start) - - def postprocess(self, input_data: any, config: dict, **kwargs) -> tuple[any, float]: - """ - For optimization problems, we process the solution here, then validate and evaluate it. - - :param input_data: Data which should be evaluated for this optimization problem - :param config: Config for the problem creation - :param kwargs: Optional additional arguments - :return: Tuple with results and the postprocessing time - """ - processed_solution = None - try: - processed_solution, time_to_process_solution = self.process_solution(input_data) - solution_validity, time_to_validation = self.validate(processed_solution) - except Exception as e: - logging.exception(f"Exception on processing the solution: {e}") - solution_validity = False - time_to_process_solution = None - time_to_validation = None - - if solution_validity and (processed_solution is not None): - solution_quality, time_to_evaluation = self.evaluate(processed_solution) - self.visualize_solution(processed_solution, f"{kwargs["store_dir"]}/solution.pdf") - else: - solution_quality = None - time_to_evaluation = None - - self.metrics.add_metric_batch({ - "application_score_value": solution_quality, - "application_score_unit": self.get_solution_quality_unit(), - "application_score_type": str(float), - "processed_solution": processed_solution, - "time_to_process_solution": time_to_process_solution, - "time_to_validation": time_to_validation, - "time_to_evaluation": time_to_evaluation - }) - - return solution_validity, sum(filter(None, [ - time_to_process_solution, time_to_validation, time_to_evaluation - ])) - - def visualize_solution(self, processed_solution: any, path: str) -> None: - """ - Creates visualizations of a processed and validated solution and writes them to disk. - Override if applicable. Default is to do nothing. - - :param processed_solution: A solution that was already processed by :func:`process_solution` - :param path: File path for the plot - :returns: None - """ - pass diff --git a/src/modules/applications/optimization/Optimization.py b/src/modules/applications/optimization/optimization_temp.py similarity index 100% rename from src/modules/applications/optimization/Optimization.py rename to src/modules/applications/optimization/optimization_temp.py diff --git a/src/modules/applications/qml/circuit.py b/src/modules/applications/qml/circuit.py deleted file mode 100644 index 7ff2cf6c..00000000 --- a/src/modules/applications/qml/circuit.py +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from abc import ABC, abstractmethod -from modules.core import Core - - -class Circuit(Core, ABC): - """ - This module is abstract base class for the library-agnostic gate sequence, that define a quantum circuit. - """ - - @abstractmethod - def generate_gate_sequence(self, input_data: dict, config: any) -> dict: - """ - Generates the library agnostic gate sequence, a well-defined definition of the quantum circuit. - - :param input_data: Input data required to generate the gate sequence - :param config: Configuration for the gate sequence - :return: Generated gate sequence - """ - pass diff --git a/src/modules/applications/qml/Circuit.py b/src/modules/applications/qml/circuit_temp.py similarity index 100% rename from src/modules/applications/qml/Circuit.py rename to src/modules/applications/qml/circuit_temp.py diff --git a/src/modules/applications/qml/generative_modeling/data/mg_2d.npy b/src/modules/applications/qml/generative_modeling/data/mg_2d.npy deleted file mode 100644 index 3113a84a..00000000 --- a/src/modules/applications/qml/generative_modeling/data/mg_2d.npy +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3e046818fa8a4854fd1c0193490bc98cd571eb556b445be7d62ee4f203138cd2 -size 800128 diff --git a/src/modules/applications/qml/generative_modeling/data/MG_2D.npy b/src/modules/applications/qml/generative_modeling/data/mg_2d_temp.npy similarity index 100% rename from src/modules/applications/qml/generative_modeling/data/MG_2D.npy rename to src/modules/applications/qml/generative_modeling/data/mg_2d_temp.npy diff --git a/src/modules/applications/qml/generative_modeling/data/o_2d.npy b/src/modules/applications/qml/generative_modeling/data/o_2d.npy deleted file mode 100644 index 2b03314f..00000000 --- a/src/modules/applications/qml/generative_modeling/data/o_2d.npy +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:998267639941843c48a5b7e0bb2c82f0558ad1a1e5c01b6a59b71700865492d5 -size 400128 diff --git a/src/modules/applications/qml/generative_modeling/data/O_2D.npy b/src/modules/applications/qml/generative_modeling/data/o_2d_temp.npy similarity index 100% rename from src/modules/applications/qml/generative_modeling/data/O_2D.npy rename to src/modules/applications/qml/generative_modeling/data/o_2d_temp.npy diff --git a/src/modules/applications/qml/generative_modeling/data/stocks_2d.npy b/src/modules/applications/qml/generative_modeling/data/stocks_2d.npy deleted file mode 100644 index 0a9c6209..00000000 --- a/src/modules/applications/qml/generative_modeling/data/stocks_2d.npy +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0d7dd7a248268a785a024c907fc51905b47867820aef65b67c342de7068ce537 -size 30160 diff --git a/src/modules/applications/qml/generative_modeling/data/Stocks_2D.npy b/src/modules/applications/qml/generative_modeling/data/stocks_2d_temp.npy similarity index 100% rename from src/modules/applications/qml/generative_modeling/data/Stocks_2D.npy rename to src/modules/applications/qml/generative_modeling/data/stocks_2d_temp.npy diff --git a/src/modules/applications/qml/generative_modeling/data/x_2d.npy b/src/modules/applications/qml/generative_modeling/data/x_2d.npy deleted file mode 100644 index 42ebec7d..00000000 --- a/src/modules/applications/qml/generative_modeling/data/x_2d.npy +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bf6a3689d191abc150792b53a15b19b3df0090652f7524a541c2833bf9ce34d3 -size 400128 diff --git a/src/modules/applications/qml/generative_modeling/data/X_2D.npy b/src/modules/applications/qml/generative_modeling/data/x_2d_temp.npy similarity index 100% rename from src/modules/applications/qml/generative_modeling/data/X_2D.npy rename to src/modules/applications/qml/generative_modeling/data/x_2d_temp.npy diff --git a/src/modules/applications/qml/generative_modeling/training/inference.py b/src/modules/applications/qml/generative_modeling/training/inference.py deleted file mode 100644 index 4bc221ad..00000000 --- a/src/modules/applications/qml/generative_modeling/training/inference.py +++ /dev/null @@ -1,118 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from typing import TypedDict -import numpy as np -from modules.applications.qml.generative_modeling.training.training_generative import TrainingGenerative, Core, GPU - - -class Inference(TrainingGenerative): - """ - This module executes a quantum circuit with parameters of a pretrained model. - """ - - def __init__(self): - """ - Constructor method. - """ - super().__init__("Inference") - - self.target: np.array - self.n_states_range: list - - @staticmethod - def get_requirements() -> list[dict]: - """ - Returns requirements of this module. - - :return: List of dict with requirements of this module. - """ - return [{"name": "numpy", "version": "1.26.4"}] - - def get_parameter_options(self) -> dict: - """ - Returns the configurable settings for this circuit. - - :return: Configuration settings for the pretrained model - .. code-block:: python - - return { - "pretrained": { - "values": [False], - "custom_input": True, - "postproc": str, - "description": "Please provide the parameters of a pretrained model." - } - } - """ - return { - "pretrained": { - "values": [], - "custom_input": True, - "postproc": str, - "description": "Please provide the parameters of a pretrained model." - } - } - - class Config(TypedDict): - """ - Attributes of a valid config. - - .. code-block:: python - - pretrained: str - - """ - pretrained: str - - def get_default_submodule(self, option: str) -> Core: - """ - Raises ValueError as this module has no submodules. - - :param option: Option name - :raises ValueError: If called, since this module has no submodules - """ - raise ValueError("This module has no submodules.") - - def start_training(self, input_data: dict, config: Config, **kwargs: dict) -> dict: - """ - Method that uses a pretrained model for inference. - - :param input_data: Dictionary with information needed for inference - :param config: Inference settings - :param kwargs: Optional additional arguments - :return: Dictionary including the information of previous modules as well as of this module - """ - self.n_states_range = range(2 ** input_data['n_qubits']) - self.target = np.asarray(input_data["histogram_train"]) - execute_circuit = input_data["execute_circuit"] - - parameters = np.load(config["pretrained"]) - - pmfs, samples = execute_circuit([parameters.get() if GPU else parameters]) - pmfs = np.asarray(pmfs) - samples = ( - self.sample_from_pmf(pmf=pmfs[0], n_shots=input_data["n_shots"]) - if samples is None - else samples[0] - ) - - loss = self.kl_divergence(pmfs.reshape([-1, 1]), self.target) - - input_data["best_parameter"] = parameters.get() if GPU else parameters - input_data["inference"] = True - input_data["KL"] = [loss.get() if GPU else loss] - input_data["best_sample"] = samples.astype(int).get() if GPU else samples.astype(int) - - return input_data diff --git a/src/modules/applications/qml/generative_modeling/training/Inference.py b/src/modules/applications/qml/generative_modeling/training/inference_temp.py similarity index 100% rename from src/modules/applications/qml/generative_modeling/training/Inference.py rename to src/modules/applications/qml/generative_modeling/training/inference_temp.py diff --git a/src/modules/applications/qml/generative_modeling/training/qcbm.py b/src/modules/applications/qml/generative_modeling/training/qcbm.py deleted file mode 100644 index c9fab5cb..00000000 --- a/src/modules/applications/qml/generative_modeling/training/qcbm.py +++ /dev/null @@ -1,335 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from typing import TypedDict -import logging -import numpy as np -from cma import CMAEvolutionStrategy -from tensorboardX import SummaryWriter -import matplotlib -from matplotlib import pyplot as plt -from matplotlib import figure, axes - -from modules.applications.qml.generative_modeling.training.training_generative import TrainingGenerative, Core, GPU -from utils_mpi import is_running_mpi, get_comm - -matplotlib.use('Agg') -MPI = is_running_mpi() -comm = get_comm() - - -class QCBM(TrainingGenerative): - """ - This module optimizes the parameters of quantum circuit using CMA-ES. - This training method is referred to as quantum circuit born machine (QCBM). - """ - - def __init__(self): - """ - Constructor method. - """ - super().__init__("QCBM") - - self.n_states_range: list - self.target: np.ndarray - self.study_generalization: bool - self.generalization_metrics: dict - self.writer: SummaryWriter - self.loss_func: callable - self.fig: figure - self.ax: axes.Axes - - @staticmethod - def get_requirements() -> list[dict]: - """ - Returns requirements of this module. - - :return: List of dict with requirements of this module - """ - return [ - {"name": "numpy", "version": "1.26.4"}, - {"name": "cma", "version": "4.0.0"}, - {"name": "matplotlib", "version": "3.9.3"}, - {"name": "tensorboard", "version": "2.18.0"}, - {"name": "tensorboardX", "version": "2.6.2.2"} - ] - - def get_parameter_options(self) -> dict: - """ - This function returns the configurable settings for the quantum circuit born machine. - - :return: Configuration settings for QCBM - .. code-block:: python - - return { - - "population_size": { - "values": [5, 10, 100, 200, 10000], - "description": "What population size do you want?" - }, - - "max_evaluations": { - "values": [100, 1000, 20000, 100000], - "description": "What should be the maximum number of evaluations?" - }, - - "sigma": { - "values": [0.01, 0.5, 1, 2], - "description": "Which sigma would you like to use?" - }, - - "pretrained": { - "values": [False], - "custom_input": True, - "postproc": str, - "description": "Please provide the parameters of a pretrained model." - }, - - "loss": { - "values": ["KL", "NLL"], - "description": "Which loss function do you want to use?" - } - } - """ - return { - "population_size": { - "values": [5, 10, 100, 200, 10000], - "description": "What population size do you want?" - }, - - "max_evaluations": { - "values": [100, 1000, 20000, 100000], - "description": "What should be the maximum number of evaluations?" - }, - - "sigma": { - "values": [0.01, 0.5, 1, 2], - "description": "Which sigma would you like to use?" - }, - - "pretrained": { - "values": [False], - "custom_input": True, - "postproc": str, - "description": "Please provide the parameters of a pretrained model." - }, - - "loss": { - "values": ["KL", "NLL"], - "description": "Which loss function do you want to use?" - } - } - - class Config(TypedDict): - """ - Attributes of a valid config. - - .. code-block:: python - - population_size: int - max_evaluations: int - sigma: float - pretrained: str - loss: str - - """ - population_size: int - max_evaluations: int - sigma: float - pretrained: str - loss: str - - def get_default_submodule(self, option: str) -> Core: - """ - Raises ValueError as this module has no submodules. - - :param option: Option name - :raises ValueError: If called, since this module has no submodules - """ - raise ValueError("This module has no submodules.") - - def setup_training(self, input_data: dict, config: Config) -> tuple[float, dict]: - """ - Method to configure the training setup including CMA-ES and tensorboard. - - :param input_data: A representation of the quantum machine learning model that will be trained - :param config: Config specifying the parameters of the training - :return: Random initial parameter and options for CMA-ES - """ - - logging.info( - f"Running config: [backend={input_data['backend']}] " - f"[n_qubits={input_data['n_qubits']}] " - f"[population_size={config['population_size']}]" - ) - - self.study_generalization = "generalization_metrics" in list(input_data.keys()) - if self.study_generalization: - self.generalization_metrics = input_data["generalization_metrics"] - self.generalization_metrics.n_shots = input_data["n_shots"] - - self.writer = SummaryWriter(input_data["store_dir_iter"]) - - if config['loss'] == "KL": - self.loss_func = self.kl_divergence - elif config['loss'] == "NLL": - self.loss_func = self.nll - elif config['loss'] == "MMD": - self.loss_func = self.mmd - else: - raise NotImplementedError("Loss function not implemented") - - n_params = input_data["n_params"] - x0 = (np.random.rand(n_params) - 0.5) * np.pi - if config["pretrained"] != "False": - parameters = np.load(config["pretrained"]) - x0[:len(parameters)] = parameters - logging.info(f'Training starting from parameters in path {config["pretrained"]}') - - options = { - 'bounds': [n_params * [-np.pi], n_params * [np.pi]], - 'maxfevals': config['max_evaluations'], - 'popsize': config['population_size'], - 'verbose': -3, - 'tolfun': 1e-12 - } - - return x0, options - - def start_training(self, input_data: dict, config: dict, **kwargs: dict) -> dict: - """ - This function finds the best parameters of the circuit on a transformed problem instance and returns a solution. - - :param input_data: A representation of the quantum machine learning model that will be trained - :param config: Config specifying the parameters of the training - :param kwargs: Optional additional settings - :return: Dictionary including the information of previous modules as well as of the training - """ - - size = None - input_data['MPI_size'] = size - input_data["store_dir_iter"] += f"_{input_data['dataset_name']}_qubits{input_data['n_qubits']}" - x0, options = self.setup_training(input_data, config) - - is_master = comm.Get_rank() == 0 - if is_master: - self.target = np.asarray(input_data["histogram_train"]) - self.target[self.target == 0] = 1e-8 - - self.n_states_range = range(2 ** input_data['n_qubits']) - execute_circuit = input_data["execute_circuit"] - timing = self.Timing() - - es = CMAEvolutionStrategy(x0.get() if GPU else x0, config['sigma'], options) - - for parameter in ["best_parameters", "time_circuit", "time_loss", "KL", "best_sample"]: - input_data[parameter] = [] - - best_loss = float("inf") - self.fig, self.ax = plt.subplots() - - while not es.stop(): - solutions = es.ask() - epoch = es.result[4] - sigma = es.sigma - - timing.start_recording() - pmfs_model, samples = execute_circuit(solutions) - pmfs_model = np.asarray(pmfs_model) - time_circ = timing.stop_recording() - - timing.start_recording() - if is_master: - loss_epoch = self.loss_func(pmfs_model.reshape([config['population_size'], -1]), self.target) - else: - loss_epoch = np.empty(config["population_size"]) - - comm.Bcast(loss_epoch, root=0) - comm.Barrier() - - time_loss = timing.stop_recording() - - es.tell(solutions, loss_epoch.get() if GPU else loss_epoch) - - if es.result[1] < best_loss: - best_loss = es.result[1] - best_pmf = self.data_visualization(loss_epoch, pmfs_model, samples, epoch) - - input_data["best_parameters"].append(es.result[0]) - input_data["KL"].append(float(es.result[1])) - - logging.info( - f"[Iteration {epoch}] " - f"[{config['loss']}: {es.result[1]:.5f}] " - f"[Circuit processing: {(time_circ):.3f} ms] " - f"[{config['loss']} processing: {(time_loss):.3f} ms] " - f"[sigma: {sigma:.5f}]" - ) - - plt.close() - self.writer.flush() - self.writer.close() - - input_data["best_parameter"] = es.result[0] - best_sample = self.sample_from_pmf(best_pmf.get() if GPU else best_pmf, # pylint: disable=E0606 - n_shots=input_data["n_shots"]) - input_data["best_sample"] = best_sample.get() if GPU else best_sample # pylint: disable=E1101 - - return input_data - - def data_visualization(self, loss_epoch: np.ndarray, pmfs_model: np.ndarray, samples: any, epoch: int) -> ( - np.ndarray): - """ - Visualizes the data and metrics for training. - - :param loss_epoch: Loss for the current epoch - :param pmfs_model: The probability mass functions from the model - :param samples: The samples from the model - :param epoch: The current epoch number - :return: Best probability mass function for visualization - """ - index = loss_epoch.argmin() - best_pmf = pmfs_model[index] / pmfs_model[index].sum() - - if self.study_generalization: - if samples is None: - counts = self.sample_from_pmf( - pmf=best_pmf.get() if GPU else best_pmf, - n_shots=self.generalization_metrics.n_shots) - else: - counts = samples[int(index)] - - metrics = self.generalization_metrics.get_metrics(counts if GPU else counts) - for key, value in metrics.items(): - self.writer.add_scalar(f"metrics/{key}", value, epoch) - - nll = self.nll(best_pmf.reshape([1, -1]), self.target) - kl = self.kl_divergence(best_pmf.reshape([1, -1]), self.target) - mmd = self.mmd(best_pmf.reshape([1, -1]), self.target) - - self.writer.add_scalar("metrics/NLL", nll.get() if GPU else nll, epoch) - self.writer.add_scalar("metrics/KL", kl.get() if GPU else kl, epoch) - self.writer.add_scalar("metrics/MMD", mmd.get() if GPU else mmd, epoch) - - self.ax.clear() - self.ax.imshow( - best_pmf.reshape(int(np.sqrt(best_pmf.size)), int(np.sqrt(best_pmf.size))).get() - if GPU else best_pmf.reshape(int(np.sqrt(best_pmf.size)), int(np.sqrt(best_pmf.size))), - cmap='binary', - interpolation='none' - ) - self.ax.set_title(f'Iteration {epoch}') - self.writer.add_figure('grid_figure', self.fig, global_step=epoch) - - return best_pmf diff --git a/src/modules/applications/qml/generative_modeling/training/QCBM.py b/src/modules/applications/qml/generative_modeling/training/qcbm_temp.py similarity index 100% rename from src/modules/applications/qml/generative_modeling/training/QCBM.py rename to src/modules/applications/qml/generative_modeling/training/qcbm_temp.py diff --git a/src/modules/applications/qml/generative_modeling/training/qgan.py b/src/modules/applications/qml/generative_modeling/training/qgan.py deleted file mode 100644 index c743d1b7..00000000 --- a/src/modules/applications/qml/generative_modeling/training/qgan.py +++ /dev/null @@ -1,508 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from typing import TypedDict -import logging - -import torch -from torch.utils.data import DataLoader -from torch import nn -import torch.nn.functional as funct -from tensorboardX import SummaryWriter -import numpy as np -import matplotlib -import matplotlib.pyplot as plt - -from modules.applications.qml.generative_modeling.training.training_generative import TrainingGenerative, Core -from utils_mpi import is_running_mpi, get_comm - -matplotlib.use('Agg') -MPI = is_running_mpi() -comm = get_comm() - - -class QGAN(TrainingGenerative): # pylint: disable=R0902 - """ - Class for QGAN - """ - - def __init__(self): - """ - Constructor method. - """ - super().__init__("QGAN") - - self.beta_1 = None - self.real_label = None - self.fake_label = None - self.n_qubits = None - self.n_registers = None - self.n_shots = None - self.train_size = None - self.execute_circuit = None - self.device = None - self.n_epochs = None - self.batch_size = None - self.learning_rate_generator = None - self.n_bins = None - self.n_states_range = None - self.timing = None - self.writer = None - self.bins_train = None - self.bins_train = None - self.study_generalization = None - self.generalization_metrics = None - self.target = None - self.n_params = None - self.discriminator = None - self.params = None - self.generator = None - self.accuracy = None - self.criterion = None - self.optimizer_discriminator = None - self.real_labels = None - self.fake_labels = None - self.dataloader = None - self.loss_func = None - self.params = None - self.discriminator_weights = None - - @staticmethod - def get_requirements() -> list[dict]: - """ - Returns requirements of this module. - - :return: List of dict with requirements of this module - """ - return [ - {"name": "numpy", "version": "1.26.4"}, - {"name": "torch", "version": "2.5.1"}, - {"name": "matplotlib", "version": "3.9.3"}, - {"name": "tensorboard", "version": "2.18.0"}, - {"name": "tensorboardX", "version": "2.6.2.2"} - ] - - def get_parameter_options(self) -> dict: - """ - Returns the configurable settings for this circuit. - - :return: Configuration settings for QGAN - .. code-block:: python - return { - "epochs": { - "values": [2, 100, 200, 10000], - "description": "How many epochs do you want?" - }, - "batch_size": { - "values": [10, 20, 100, 2000], - "description": "What batch size do you want?" - }, - "learning_rate_generator": { - "values": [0.1, 0.2], - "description": "What learning rate do you want to set for the generator?" - }, - "learning_rate_discriminator": { - "values": [0.1, 0.05], - "description": "What learning rate do you want to set for the discriminator?" - }, - "device": { - "values": ["cpu", "gpu"], - "description": "Where do you want to run the discriminator?" - }, - "pretrained": { - "values": [True, False], - "description": "Do you want to use parameters of a pretrained model?" - }, - "loss": { - "values": ["KL", "NLL"], - "description": "Which loss function do you want to use?" - } - } - """ - return { - - "epochs": { - "values": [2, 100, 200, 10000], - "description": "How many epochs do you want?" - }, - "batch_size": { - "values": [10, 20, 100, 2000], - "description": "What batch size do you want?" - }, - "learning_rate_generator": { - "values": [0.1, 0.2], - "description": "What learning rate do you want to set for the generator?" - }, - "learning_rate_discriminator": { - "values": [0.1, 0.05], - "description": "What learnig rate do you want to set for the discriminator?" - }, - "device": { - "values": ["cpu", "gpu"], - "description": "Where do you want to run the discriminator?" - }, - "pretrained": { - "values": [True, False], - "description": "Do you want to use parameters of a pretrained model?" - }, - "loss": { - "values": ["KL", "NLL"], - "description": "Which loss function do you want to use?" - } - } - - class Config(TypedDict): - """ - Attributes of a valid config. - - .. code-block:: python - - epochs: int - batch_size: int - learning_rate_generator: int - learning_rate_discriminator: int - device: str - loss: str - - """ - - epochs: int - batch_size: int - learning_rate_generator: float - learning_rate_discriminator: float - device: str - loss: str - - def get_default_submodule(self, option: str) -> Core: - """ - Raises ValueError as this module has no submodules. - - :param option: Option name - :raises ValueError: If called, since this module has no submodules - """ - raise ValueError("This module has no submodules.") - - def setup_training(self, input_data: dict, config: dict) -> None: - """ - Sets up the training configuration. - - :param input_data: dictionary with the variables from the circuit needed to start the training - :param config: Configurations for the QGAN training. - """ - self.beta_1 = 0.5 - self.real_label = 1. - self.fake_label = 0. - - self.n_qubits = input_data['n_qubits'] - self.n_registers = input_data['n_registers'] - self.n_shots = input_data["n_shots"] - self.train_size = input_data["train_size"] - self.execute_circuit = input_data["execute_circuit"] - - self.device = config["device"] - self.n_epochs = config["epochs"] - self.batch_size = config["batch_size"] - self.learning_rate_generator = config["learning_rate_generator"] - - n = 2 ** (self.n_qubits // self.n_registers) - self.n_bins = n ** self.n_registers - self.n_states_range = range(2 ** self.n_qubits) - - self.timing = self.Timing() - self.writer = SummaryWriter(input_data["store_dir_iter"]) - - self.bins_train = input_data["binary_train"] - if input_data["dataset_name"] == "Cardinality_Constraint": - new_size = 1000 - self.bins_train = np.repeat(self.bins_train, new_size, axis=0) - - self.study_generalization = "generalization_metrics" in list(input_data.keys()) - if self.study_generalization: - self.generalization_metrics = input_data["generalization_metrics"] - - self.target = np.asarray(input_data["histogram_train"]) - self.target[self.target == 0] = 1e-8 - - self.n_params = input_data["n_params"] - - self.discriminator = Discriminator(self.n_qubits).to(self.device) - self.discriminator.apply(Discriminator.weights_init) - - self.params = np.random.rand(self.n_params) * np.pi - self.generator = QuantumGenerator(self.n_qubits, self.execute_circuit, self.batch_size) - - self.accuracy = [] - self.criterion = torch.nn.BCELoss() - self.optimizer_discriminator = torch.optim.Adam( - self.discriminator.parameters(), - lr=config["learning_rate_discriminator"], - betas=(self.beta_1, 0.999) - ) - - self.real_labels = torch.full((self.batch_size,), 1.0, dtype=torch.float, device=self.device) - self.fake_labels = torch.full((self.batch_size,), 0.0, dtype=torch.float, device=self.device) - - self.dataloader = DataLoader(self.bins_train, batch_size=self.batch_size, shuffle=True, drop_last=True) - - if config['loss'] == "KL": - self.loss_func = self.kl_divergence - elif config['loss'] == "NLL": - self.loss_func = self.nll - else: - raise NotImplementedError("Loss function not implemented") - - def start_training(self, input_data: dict, config: dict, **kwargs: dict) -> dict: # pylint: disable=R0915 - """ - This function starts the training of the QGAN. - - :param input_data: Dictionary with the variables from the circuit needed to start the training - :param config: Training settings - :param kwargs: Optional additional arguments - :return: Dictionary including the solution - """ - self.setup_training(input_data, config) - generator_losses = [] - discriminator_losses = [] - best_kl_divergence = float('inf') - best_generator_params = None - pmfs_model = None - best_sample = None - - n_batches = len(self.dataloader) - - for epoch in range(self.n_epochs): - for batch, data in enumerate(self.dataloader): - # Training the discriminator - # Data from real distribution for training the discriminator - real_data = data.float().to(self.device) - self.discriminator.zero_grad() - out_d_real = self.discriminator(real_data).view(-1) - err_d_real = self.criterion(out_d_real, self.real_labels) - err_d_real.backward() - - # Use Quantum Variational Circuit to generate fake samples - fake_data, _ = self.generator.execute(self.params, self.batch_size) - fake_data = fake_data.float().to(self.device) - out_d_fake = self.discriminator(fake_data).view(-1) - err_d_fake = self.criterion(out_d_fake, self.fake_labels) - err_d_fake.backward() - - err_d = err_d_real + err_d_fake - self.optimizer_discriminator.step() - - out_d_fake = self.discriminator(fake_data).view(-1) - err_g = self.criterion(out_d_fake, self.real_labels) - fake_data, _ = self.generator.execute(self.params, self.batch_size) - gradients = self.generator.compute_gradient( - self.params, - self.discriminator, - self.criterion, - self.real_labels, - self.device - ) - - updated_params = self.params - self.learning_rate_generator * gradients - self.params = updated_params - - self.discriminator_weights = self.discriminator.state_dict() - generator_losses.append(err_g.item()) - discriminator_losses.append(err_d.item()) - - # Calculate loss - _, pmfs_model = self.generator.execute(self.params, self.n_shots) - pmfs_model = np.asarray(pmfs_model.copy()) - - loss = self.loss_func(pmfs_model[None,], self.target) - self.accuracy.append(loss) - - self.writer.add_scalar("metrics/KL", loss, epoch * n_batches + batch) - circuit_evals = (epoch * n_batches + batch) * self.batch_size * (2 * self.n_params + 1) - self.writer.add_scalar("metrics/KL_circuit_evals", loss, circuit_evals) - - # Calculate and log the loss values at the end of each epoch - self.writer.add_scalar('Loss/GAN_Generator', err_g.item(), circuit_evals) - self.writer.add_scalar('Loss/GAN_Discriminator', err_d.item(), circuit_evals) - - if loss < best_kl_divergence: - best_kl_divergence = loss - best_generator_params = self.params.copy() # Make a copy of the parameters - _, best_pdf = self.generator.execute(best_generator_params, self.n_shots) - best_pdf = np.asarray(best_pdf) - best_pdf = best_pdf / best_pdf.sum() - best_sample = self.sample_from_pmf(pmf=best_pdf, n_shots=self.n_shots) - - # Log the training progress - log_message = ( - f"Epoch: {epoch + 1}/{self.n_epochs}, " - f"Batch: {batch + 1}/{len(self.bins_train) // self.batch_size}, " - f"Discriminator Loss: {err_d.item()}, Generator Loss: {err_g.item()}, KL Divergence: {loss} " - ) - - logging.info(log_message) - - fig, ax = plt.subplots() - ax.imshow( - pmfs_model.reshape((2 ** (self.n_qubits // 2), 2 ** (self.n_qubits // 2))), - cmap='binary', - interpolation='none' - ) - ax.set_title(f'Iteration {epoch}') - self.writer.add_figure('grid_figure', fig, global_step=epoch) - - ax.clear() - ax.imshow( - self.target.reshape((2 ** (self.n_qubits // 2), 2 ** (self.n_qubits // 2))), - cmap='binary', - interpolation='none' - ) - # Log the figure in TensorBoard - ax.set_title("train") - self.writer.add_figure("train", fig) - - # Plot the generator and discriminator losses on the existing figure - ax.clear() - ax.plot(generator_losses, label='Generator Loss', color='blue') - ax.plot(discriminator_losses, label='Discriminator Loss', color='red') - ax.legend() - - # Save the updated loss plot to TensorBoard - self.writer.add_figure('Loss_Plot', fig, global_step=epoch) - - plt.close() - self.writer.flush() - self.writer.close() - - input_data["best_parameter"] = best_generator_params - input_data["best_sample"] = best_sample - input_data["KL"] = self.accuracy - input_data["generator_loss"] = generator_losses - input_data["discriminator_loss"] = discriminator_losses - - return input_data - - -class Discriminator(nn.Module): - """ - This class defines the discriminator of the QGAN. - """ - - def __init__(self, input_length: int): - super().__init__() - self.dense1 = nn.Linear(int(input_length), 2 * int(input_length)) - self.dense2 = nn.Linear(2 * int(input_length), 1) - - def forward(self, x: torch.Tensor) -> float: - """ - Initializes the weight tensor of the linear layers with values using a Xavier uniform distribution. - - :param x: Input of the discriminator - :type x: torch.Tensor - :return: Probability fake/real sample - :rtype: float - """ - h = funct.leaky_relu(self.dense1(x)) - h = funct.leaky_relu(self.dense2(h)) - return funct.sigmoid(h) - - @staticmethod - def weights_init(m: nn.Linear) -> None: - """ - Initializes the weight tensor of the linear - layers with values using a Xavier uniform distribution. - - :param m: Neural network layer - """ - if isinstance(m, nn.Linear): - nn.init.xavier_uniform_(m.weight.data, gain=10) - nn.init.constant_(m.bias.data, 1) - - -class QuantumGenerator: - """ - This class defines the generator of the QGAN. - """ - - def __init__(self, n_qubits, execute_circuit, batch_size): - self.n_qubits = n_qubits - self.execute_circuit = execute_circuit - self.batch_size = batch_size - - def execute(self, params: np.ndarray, n_shots: int) -> tuple[torch.Tensor, np.ndarray]: - """ - Forward pass of the generator. - - :param params: Parameters of the quantum circuit - :param n_shots: Number of shots - :return: samples and the probability distribution generated by the quantum circuit - """ - - # Call the quantum circuit and obtain probability distributions - pdfs, _ = self.execute_circuit(np.expand_dims(params, axis=0)) - pdfs = pdfs.flatten() - - # Sample from the provided probability distribution for the specified batch size - sampling = torch.multinomial(torch.from_numpy(pdfs), n_shots, replacement=True) - - # Convert the sampling tensor to a list of integers - binary_samples = [(sampling >> i) & 1 for i in range(self.n_qubits)] - binary_samples = binary_samples[::-1] # Reverse the order to match your expected format - - # Convert binary samples to a PyTorch tensor - samples = torch.stack(binary_samples, dim=1).float() - - return samples, pdfs - - # pylint: disable=R0917 - def compute_gradient(self, params: np.ndarray, discriminator: torch.nn.Module, criterion: callable, - label: torch.Tensor, device: str) -> np.ndarray: - """ - This function defines the forward pass of the generator. - - :param params: Parameters of the quantum circuit - :param discriminator: Discriminator of the QGAN - :param criterion: Loss function - :param label: Label indicating of sample is true or fake - :param device: Torch device (e.g., CPU or CUDA) - :return: Samples and the probability distribution generated by the quantum circuit - """ - shift = 0.5 * np.pi - gradients = np.zeros(len(params)) # Initialize gradients as an array of zeros - - for i in range(len(params)): - # Compute shifts for the i-th parameter - positive_shifted_params = params.copy() - negative_shifted_params = params.copy() - positive_shifted_params[i] += shift - negative_shifted_params[i] -= shift - - # Generate samples with shifts - positive_samples, _ = self.execute(positive_shifted_params, self.batch_size) - negative_samples, _ = self.execute(negative_shifted_params, self.batch_size) - - # Convert positive_samples and negative_samples to tensors - positive_samples = positive_samples.to(device) - negative_samples = negative_samples.to(device) - - # Compute discriminator outputs for all samples - forward_outputs = discriminator(positive_samples).view(-1) - backward_outputs = discriminator(negative_samples).view(-1) - - # Compute criterion differences for all samples - forward_diff = criterion(forward_outputs, label) - backward_diff = criterion(backward_outputs, label) - - # Calculate the gradient for the i-th parameter - gradients[i] = 0.5 * (forward_diff.item() - backward_diff.item()) - - return gradients diff --git a/src/modules/applications/qml/generative_modeling/training/QGAN.py b/src/modules/applications/qml/generative_modeling/training/qgan_temp.py similarity index 100% rename from src/modules/applications/qml/generative_modeling/training/QGAN.py rename to src/modules/applications/qml/generative_modeling/training/qgan_temp.py diff --git a/src/modules/applications/qml/generative_modeling/transformations/pit.py b/src/modules/applications/qml/generative_modeling/transformations/pit.py deleted file mode 100644 index 23534ea1..00000000 --- a/src/modules/applications/qml/generative_modeling/transformations/pit.py +++ /dev/null @@ -1,257 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import numpy as np -import pandas as pd - -from modules.applications.qml.generative_modeling.transformations.transformation import Transformation -from modules.applications.qml.generative_modeling.circuits.circuit_copula import CircuitCopula - - -class PIT(Transformation): # pylint disable=R0902 - """ - The transformation of the original probability distribution to - the distribution of its uniformly distributed cumulative marginals is known as the copula. - """ - - def __init__(self): - super().__init__("PIT") - self.submodule_options = ["CircuitCopula"] - self.reverse_epit_lookup = None - self.transform_config = None - self.n_qubits = None - self.dataset = None - self.dataset_name = None - self.grid_shape = None - self.histogram_train = None - self.histogram_train_original = None - self.histogram_transformed = None - - @staticmethod - def get_requirements() -> list[dict]: - """ - Returns requirements of this module. - - :return: List of dict with requirements of this module - """ - return [ - {"name": "numpy", "version": "1.26.4"}, - {"name": "pandas", "version": "2.2.3"} - ] - - def get_parameter_options(self) -> dict: - """ - Returns empty dict as this transformation has no configurable settings. - - :return: Empty dict - """ - return {} - - def get_default_submodule(self, option: str) -> CircuitCopula: - if option == "CircuitCopula": - return CircuitCopula() - else: - raise NotImplementedError(f"Circuit Option {option} not implemented") - - def transform(self, input_data: dict, config: dict) -> dict: - """ - Transforms the input dataset using PIT transformation and computes histograms - of the training dataset in the transformed space. - - :param input_data: Dataset - :param config: Config with the parameters specified in Config class - :return: Dict with PIT transformation, time it took to map it - """ - # TODO: PIT.transform is almost identical to MinMax.transform -> function should be moved to Transformation.py - self.dataset_name = input_data["dataset_name"] - self.dataset = input_data["dataset"] - self.n_qubits = input_data["n_qubits"] - self.grid_shape = int(2 ** (self.n_qubits // 2)) - n_registers = self.dataset.shape[-1] - - # Calculate ranges for the original dataset and the transformed dataset - ranges_original = np.column_stack((np.min(self.dataset, axis=0), np.max(self.dataset, axis=0))) - transformed_dataset = self.fit_transform(self.dataset) - ranges_transformed = np.column_stack((np.min(transformed_dataset, axis=0), np.max(transformed_dataset, axis=0))) - - # Compute histogram for the transformed dataset - transformed_histogram_grid = np.histogramdd( - transformed_dataset, - bins=self.grid_shape, - range=ranges_transformed)[0] - histogram_transformed_1d = transformed_histogram_grid.flatten() - self.histogram_transformed = histogram_transformed_1d / np.sum(histogram_transformed_1d) - - solution_space = np.zeros(len(transformed_dataset), dtype=int) - - # Initialize a variable to keep track of the current position in the result_array - position = 0 - value = 0 - for count in histogram_transformed_1d: - if count > 0: - solution_space[position:position + int(count)] = value - position += int(count) - value += 1 - - binary_strings = [np.binary_repr(x, width=self.n_qubits) for x in solution_space] - # Convert the binary strings to a NumPy array of integers - binary_transformed = np.array([list(map(int, s)) for s in binary_strings]) - - # Compute histogram for the original dataset - learned_histogram = np.histogramdd(self.dataset, bins=self.grid_shape, range=ranges_original) - self.histogram_train_original = learned_histogram[0] / np.sum(learned_histogram[0]) - - # Compute histogram for the transformed dataset - train_histogram = np.histogramdd(transformed_dataset, bins=self.grid_shape, range=ranges_transformed) - histogram_train = train_histogram[0] / np.sum(train_histogram[0]) - self.histogram_train = histogram_train.flatten() - - self.transform_config = { - "histogram_train": self.histogram_train, - "binary_train": binary_transformed, - "dataset_name": self.dataset_name, - "n_registers": n_registers, - "n_qubits": self.n_qubits, - "store_dir_iter": input_data["store_dir_iter"], - "train_size": input_data["train_size"], - "transformed_dataset": transformed_dataset - } - - return self.transform_config - - def reverse_transform(self, input_data: dict) -> dict: - """ - Transforms the solution back to the representation needed for validation/evaluation. - - :param input_data: Dictionary containing the solution - :return: Dictionary with solution transformed accordingly - """ - depth = input_data["depth"] - architecture_name = input_data["architecture_name"] - n_qubits = input_data["n_qubits"] - n_registers = self.transform_config["n_registers"] - kl_best_transformed = min(input_data["KL"]) - best_results = input_data["best_sample"] - circuit_transpiled = input_data['circuit_transpiled'] - - array_bins = self.compute_discretization_efficient(n_qubits, n_registers) - transformed_samples = self.generate_samples_efficient(best_results, array_bins, n_registers, noisy=True) - - # Calculate ranges for the transformed samples - ranges_transformed = np.column_stack((np.min(transformed_samples, axis=0), np.max(transformed_samples, axis=0))) - - # Compute histogram for the transformed samples - learned_histogram = np.histogramdd(transformed_samples, bins=self.grid_shape, range=ranges_transformed) - histogram_generated_transformed = learned_histogram[0] / np.sum(learned_histogram[0]) - histogram_generated_transformed = histogram_generated_transformed.flatten() - - original_samples = self.inverse_transform(transformed_samples) - - # Calculate ranges for the original samples - ranges_original = np.column_stack((np.min(original_samples, axis=0), np.max(original_samples, axis=0))) - - # Compute histogram for the original samples - learned_histogram = np.histogramdd(original_samples, bins=self.grid_shape, range=ranges_original) - histogram_generated_original = learned_histogram[0] / np.sum(learned_histogram[0]) - histogram_generated_original = histogram_generated_original.flatten() - - best_parameter = input_data["best_parameter"] - - reverse_config_trans = { - "generated_samples": best_results, - "transformed_samples": transformed_samples, - "depth": depth, - "architecture_name": architecture_name, - "dataset_name": self.dataset_name, - "n_qubits": n_qubits, - "best_parameter": best_parameter, - "histogram_train_original": self.histogram_train_original, - "histogram_train": self.histogram_train, - "histogram_generated_original": histogram_generated_original, - "histogram_generated": histogram_generated_transformed, - "KL_best_transformed": kl_best_transformed, - "store_dir_iter": input_data["store_dir_iter"], - "circuit_transpiled": circuit_transpiled - } - - return reverse_config_trans - - def fit_transform(self, data: np.ndarray) -> np.ndarray: - """ - Takes the data points and applies the PIT. - - :param data: Data samples - :return: Transformed data points - """ - df = pd.DataFrame(data) - epit = df.copy(deep=True).transpose() - self.reverse_epit_lookup = epit.copy(deep=True) - - epit.values[::] = [self.emp_integral_trans(row) for row in epit.values] - epit = epit.transpose() - self.reverse_epit_lookup.values[::] = [np.sort(row) for row in self.reverse_epit_lookup.values] - - df = epit.copy() - self.reverse_epit_lookup = self.reverse_epit_lookup.values - return df.values - - def _reverse_emp_integral_trans_single(self, values: np.ndarray) -> list[float]: - """ - Takes one data point and applies the inverse PIT. - - :param values: Data point - :return: Data point after applying the inverse transformation - """ - values = values * (np.shape(self.reverse_epit_lookup)[1] - 1) - rows = np.shape(self.reverse_epit_lookup)[0] - - # TODO THIS IS A TEMPORARY BUG FIX FOR RARELY OCCURRING CASES THAT LEAD TO INDEX_ERRORS. - # WILL BE FIXED IN RELEASE 2.1.3. - # Ensure values are within bounds - values_l = np.clip(np.floor(values).astype(int), 0, self.reverse_epit_lookup.shape[1] - 1) - values_h = np.clip(np.ceil(values).astype(int), 0, self.reverse_epit_lookup.shape[1] - 1) - - # if we are an integer then floor and ceiling are the same - is_int_mask = 1 - (values_h - values_l) - row_indexer = np.arange(rows) - result_l = self.reverse_epit_lookup[ - ([row_indexer], [values_l])] # doing 2d lookup as [[index1.row, index2.row],[index1.column, index2.column]] - result_h = self.reverse_epit_lookup[ - ([row_indexer], [values_h])] # where 2d index tuple would be (index1.row, index1.column) - # lookup int or do linear interpolation - return result_l * (is_int_mask + values - values_l) + result_h * (values_h - values) - - def inverse_transform(self, data: np.ndarray) -> np.ndarray: - """ - Applies the inverse transformation to the full data set. - - :param data: Data set - :return: Data set after applying the inverse transformation - """ - res = [self._reverse_emp_integral_trans_single(row) for row in data] - - return np.array(res)[:, 0, :] - - def emp_integral_trans(self, data: np.ndarray) -> np.ndarray: - """ - Applies the empirical integral transformation to the given data. - - :param data: Data points - :return: Empirically transformed data points - """ - rank = np.argsort(data).argsort() - length = data.size - ecdf = np.linspace(0, 1, length, dtype=np.float64) - ecdf_biject = ecdf[rank] - return ecdf_biject diff --git a/src/modules/applications/qml/generative_modeling/transformations/PIT.py b/src/modules/applications/qml/generative_modeling/transformations/pit_temp.py similarity index 100% rename from src/modules/applications/qml/generative_modeling/transformations/PIT.py rename to src/modules/applications/qml/generative_modeling/transformations/pit_temp.py diff --git a/src/modules/applications/qml/generative_modeling/transformations/transformation.py b/src/modules/applications/qml/generative_modeling/transformations/transformation.py deleted file mode 100644 index 1a09d73c..00000000 --- a/src/modules/applications/qml/generative_modeling/transformations/transformation.py +++ /dev/null @@ -1,200 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from itertools import product -from abc import ABC, abstractmethod - -import numpy as np - -from modules.core import Core -from utils import start_time_measurement, end_time_measurement - - -class Transformation(Core, ABC): - """ - The task of the transformation module is to translate data and problem - specification of the application into preprocessed format. - """ - - def __init__(self, name): - """ - Constructor method. - """ - super().__init__() - self.transformation_name = name - - @staticmethod - def get_requirements() -> list[dict]: - """ - Returns requirements of this module. - - :return: List of dict with requirements of this module - """ - return [{"name": "numpy", "version": "1.26.4"}] - - def preprocess(self, input_data: dict, config: dict, **kwargs: dict) -> tuple[dict, float]: - """ - In this module, the preprocessing step is transforming the data to the correct target format. - - :param input_data: Collected information of the benchmarking process - :param config: Config specifying the parameters of the transformation - :param kwargs: Additional optional arguments - :return: Tuple with transformed problem and the time it took to map it - """ - start = start_time_measurement() - output = self.transform(input_data, config) - - return output, end_time_measurement(start) - - def postprocess(self, input_data: dict, config: dict, **kwargs) -> tuple[dict, float]: - """ - Does the reverse transformation. - - :param input_data: Dictionary containing information of previously executed modules - :param config: Dictionary containing additional information - :param kwargs: Dictionary containing additional information - :return: Tuple with the dictionary and the time the postprocessing took - """ - start = start_time_measurement() - output = self.reverse_transform(input_data) - output["Transformation"] = True - if "inference" in input_data: - output["inference"] = input_data["inference"] - - return output, end_time_measurement(start) - - @abstractmethod - def transform(self, input_data: dict, config: dict) -> dict: - """ - Helps to ensure that the model can effectively learn the underlying - patterns and structure of the data, and produce high-quality outputs. - - :param input_data: Input data for transformation - :param config: Configuration parameters for the transformation - :return: Transformed data. - """ - return input_data - - def reverse_transform(self, input_data: dict) -> dict: - """ - Transforms the solution back to the original problem. - This might not be necessary in all cases, so the default is to return the original solution. - This might be needed to convert the solution to a representation needed for validation and evaluation. - - :param input_data: The input data to be transformed - :return: Transformed data - """ - return input_data - - @staticmethod - def compute_discretization(n_qubits: int, n_registered: int) -> np.ndarray: - """ - Compute discretization for the grid. - - :param n_qubits: Total number of qubits - :param n_registered: Number of qubits to be registered - :return: Discretization data - """ - n = 2 ** (n_qubits // n_registered) - n_bins = n ** n_registered - bin_data = np.empty((n_bins, n_registered + 1), dtype=float) - - for k, coords in enumerate(product(range(n), repeat=n_registered)): - normalized_coords = np.array(coords) / n + 0.5 / n - bin_data[k] = np.concatenate(([k], normalized_coords)) - - return bin_data - - @staticmethod - def compute_discretization_efficient(n_qubits: int, n_registers: int) -> np.ndarray: - """ - Compute grid discretization. - - :param n_qubits: Total number of qubits - :param n_registers: Number of qubits to be registered - :return: Discretization data - """ - n = 2 ** (n_qubits // n_registers) - n_bins = n ** n_registers - - # Create an array of all possible coordinate combinations - coords = np.array(list(product(range(n), repeat=n_registers))) - - # Calculate normalized_coords for all combinations - normalized_coords = (coords.astype(float) + 0.5) / n - - # Create bin_data by concatenating normalized_coords with row indices - bin_data = np.hstack((np.arange(n_bins).reshape(-1, 1), normalized_coords)) - - return bin_data - - @staticmethod - def generate_samples(results: np.ndarray, bin_data: np.ndarray, n_registers: int, noisy: bool = True) -> np.ndarray: - """ - Generate samples based on measurement results and the grid bins. - - :param results: Results of measurements - :param bin_data: Binned data - :param n_registers: Number of registers - :param noisy: Flag indicating whether to add noise - :return: Generated samples - """ - n_shots = np.sum(results) - width = 1 / len(bin_data) ** (1 / n_registers) - points = ( - 0.5 * width * np.random.uniform(low=-1, high=1, size=(n_shots, n_registers)) - if noisy - else np.zeros((n_shots, n_registers)) - ) - - position = 0 - for idx, value in enumerate(results): - bin_coords = bin_data[idx, 1:] - points[position: position + value, :] += np.tile(bin_coords, (value, 1)) - position += value - - return points.astype(np.float32) - - @staticmethod - def generate_samples_efficient(results, bin_data: np.ndarray, n_registers: int, noisy: bool = True) -> np.ndarray: - """ - Generate samples efficiently using numpy arrays based on measurement results and the grid bins. - - :param results: Results of measurements - :param bin_data: Binned data - :param n_registers: Number of registers - :param noisy: Flag indicating whether to add noise - :return: Generated samples - """ - n_shots = np.sum(results) - width = 1 / len(bin_data) ** (1 / n_registers) - - # Generate random noise or zeros - noise = ( - 0.5 * width * np.random.uniform(low=-1, high=1, size=(n_shots, n_registers)) - if noisy - else np.zeros((n_shots, n_registers)) - ) - - # Create an array of bin_coords for each result, then stack them vertically - bin_coords = bin_data[:, 1:] - expanded_bin_coords = np.repeat(bin_coords, results, axis=0) - - # Reshape expanded_bin_coords to match the shape of noise - expanded_bin_coords = expanded_bin_coords.reshape(n_shots, n_registers) - - # Add noise to the expanded_bin_coords - points = expanded_bin_coords + noise - - return points.astype(np.float32) diff --git a/src/modules/applications/qml/generative_modeling/transformations/Transformation.py b/src/modules/applications/qml/generative_modeling/transformations/transformation_temp.py similarity index 100% rename from src/modules/applications/qml/generative_modeling/transformations/Transformation.py rename to src/modules/applications/qml/generative_modeling/transformations/transformation_temp.py diff --git a/src/modules/applications/qml/model.py b/src/modules/applications/qml/model.py deleted file mode 100644 index f0f11f18..00000000 --- a/src/modules/applications/qml/model.py +++ /dev/null @@ -1,59 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and - -from abc import ABC, abstractmethod - - -class Model(ABC): - """ - Abstract base class for any quantum model. This class defines the necessary methods - that models like 'LibraryGenerative' must implement. - """ - - @abstractmethod - def sequence_to_circuit(self, input_data: dict) -> dict: - """ - Abstract method to convert a sequence into a quantum circuit. - - :param input_data: Input data representing the gate sequence - :return: A dictionary representing the quantum circuit - """ - pass - - @staticmethod - @abstractmethod - def get_execute_circuit(circuit: any, backend: any, config: str, config_dict: dict) -> tuple[any, any]: - """ - This method combines the circuit implementation and the selected backend and returns a function that will be - called during training. - - :param circuit: Implementation of the quantum circuit - :param backend: Configured qiskit backend - :param config: Name of a backend - :param config_dict: Dictionary including the number of shots - :return: Tuple that contains a method that executes the quantum circuit for a given set of parameters and the - transpiled circuit - """ - pass - - @staticmethod - @abstractmethod - def select_backend(config: str, n_qubits: int) -> any: - """ - This method configures the backend. - - :param config: Name of a backend - :param n_qubits: Number of qubits - :return: Configured backend - """ - pass diff --git a/src/modules/applications/qml/Model.py b/src/modules/applications/qml/model_temp.py similarity index 100% rename from src/modules/applications/qml/Model.py rename to src/modules/applications/qml/model_temp.py diff --git a/src/modules/applications/qml/qml.py b/src/modules/applications/qml/qml.py deleted file mode 100644 index 5720fcce..00000000 --- a/src/modules/applications/qml/qml.py +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from abc import ABC, abstractmethod - -from modules.applications.application import Application - - -class QML(Application, ABC): - """ - qml Module for QUARK, is used by all qml applications. - """ - - @abstractmethod - def generate_problem(self, config: dict) -> any: - """ - Creates a concrete problem and returns it. - - :param config: Configuration dictionary - :return: Generated problem - """ - pass - - def save(self, path: str, iter_count: int) -> None: - """ - Placeholder method for saving output to a file. - - :param path: Path to save the file - :param iter_count: Iteration count - """ - pass diff --git a/src/modules/applications/qml/QML.py b/src/modules/applications/qml/qml_temp.py similarity index 100% rename from src/modules/applications/qml/QML.py rename to src/modules/applications/qml/qml_temp.py diff --git a/src/modules/applications/qml/training.py b/src/modules/applications/qml/training.py deleted file mode 100644 index 17ea21de..00000000 --- a/src/modules/applications/qml/training.py +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from abc import ABC, abstractmethod - - -class Training(ABC): - """ - Abstract base class for training QML models. - """ - - @abstractmethod - def start_training(self, input_data: dict, config: any, **kwargs: dict) -> dict: - """ - This function starts the training of QML model or deploys a pretrained model. - - :param input_data: A representation of the quantum machine learning model that will be trained - :param config: Config specifying the parameters of the training (dict-like Config type defined in children) - :param kwargs: Optional additional settings - :return: Solution, the time it took to compute it and some optional additional information - """ - pass diff --git a/src/modules/applications/qml/Training.py b/src/modules/applications/qml/training_temp.py similarity index 100% rename from src/modules/applications/qml/Training.py rename to src/modules/applications/qml/training_temp.py diff --git a/src/modules/core.py b/src/modules/core.py deleted file mode 100644 index 89f62935..00000000 --- a/src/modules/core.py +++ /dev/null @@ -1,161 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations # Needed if you want to type hint a method with the type of the enclosing class - -import os -import sys -from abc import ABC, abstractmethod -from typing import final - -from utils import _get_instance_with_sub_options -from Metrics import Metrics - - -class Core(ABC): - """ - Core Module for QUARK, used by all other Modules that are part of a benchmark process. - """ - - def __init__(self, name: str = None): - """ - Constructor method. - - :param name: Name used to identify this QUARK module. If not specified class name will be used as default. - """ - self.submodule_options = [] - self.sub_options = [] - self.depending_parameters = False - self.preprocessed_input = None - self.postprocessed_input = None - if name is None: - name = self.__class__.__name__ - self.name = name - self.metrics = Metrics(name, os.path.relpath(sys.modules[self.__module__].__file__)) - - @abstractmethod - def get_parameter_options(self) -> dict: - """ - Returns the parameters for a given module. - - Should always be in this format: - - .. code-block:: json - - { - "parameter_name":{ - "values":[1, 2, 3], - "description":"How many nodes do you need?" - }, - "parameter_name_2":{ - "values":["x", "y"], - "description":"Which type of problem do you want?" - } - } - - :return: Available settings for this application - """ - raise NotImplementedError("Please don't use the base version of get_parameter_options. " - "Implement your own override instead.") - - def get_available_submodules(self, option: list) -> list: - """ - If the module has submodules depending on certain options, this method should adjust the submodule_options - accordingly. - - :param option: List of chosen options - :return: List of available submodules - """ - return [] - - def get_depending_parameters(self, option: str, config: dict) -> dict: - """ - If the module has parameters depending on certain options, this method should return the parameters for the - given option. - - :param option: The chosen option - :param config: Current config dictionary - :return: The parameters for the given option - """ - return {} - - @final - def get_submodule(self, option: str) -> Core: - """ - Submodule is instantiated according to the information given in self.sub_options. - If self.sub_options is None, get_default_submodule is called as a fallback. - - :param option: String with the options - :return: Instance of a module - """ - if self.sub_options is None or not self.sub_options: - return self.get_default_submodule(option) - return _get_instance_with_sub_options(self.sub_options, option) - - # TODO Think if the naming of get_default_submodule can be improved to better reflect its function. - @abstractmethod - def get_default_submodule(self, option: str) -> Core: - """ - Given an option string by the user, this returns a submodule. - - :param option: String with the chosen submodule - :return: Module of type Core - """ - raise NotImplementedError("Please don't use the base version of get_default_submodule. " - "Implement your own override instead.") - - def preprocess(self, input_data: any, config: dict, **kwargs) -> tuple[any, float]: - """ - Essential method for the benchmarking process. This is always executed before traversing down - to the next module, passing the data returned by this function. - - :param input_data: Data for the module, comes from the parent module if that exists - :param config: Config for the module - :param kwargs: Optional keyword arguments - :return: The output of the preprocessing and the time it took to preprocess - """ - return input_data, 0.0 - - def postprocess(self, input_data: any, config: dict, **kwargs) -> tuple[any, float]: - """ - Essential Method for the benchmarking process. Is always executed after the submodule is finished. The data by - this method is passed up to the parent module. - - :param input_data: Input data comes from the submodule if that exists - :param config: Config for the module - :param kwargs: Optional keyword arguments - :return: The output of the postprocessing and the time it took to postprocess - """ - return input_data, 0.0 - - @final - def get_available_submodule_options(self) -> list: - """ - Gets the list of available options. - - :return: List of module options - """ - if self.sub_options is None or not self.sub_options: - return self.submodule_options - else: - return [option["name"] for option in self.sub_options] - - @staticmethod - def get_requirements() -> list: - """ - Returns the required pip packages for this module. Optionally, version requirements can be added. - - :return: List of dictionaries - """ - return [] diff --git a/src/modules/Core.py b/src/modules/core_temp.py similarity index 100% rename from src/modules/Core.py rename to src/modules/core_temp.py diff --git a/src/modules/devices/braket/braket.py b/src/modules/devices/braket/braket.py deleted file mode 100644 index 00f755b9..00000000 --- a/src/modules/devices/braket/braket.py +++ /dev/null @@ -1,188 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import getpass -import logging -import os -from abc import ABC -from datetime import datetime - -import boto3 -from botocore.config import Config -from botocore.exceptions import ProfileNotFound -from braket.aws import AwsSession - -from modules.devices.device import Device - - -class Braket(Device, ABC): - """ - Abstract class to use the Amazon Braket devices. - """ - - def __init__(self, device_name: str, region: str = None, arn: str = None): - """ - Constructor method. - """ - super().__init__(device_name) - self.device = None - self.arn = arn - self.s3_destination_folder = None - self.boto_session = None - self.aws_session = None - - if 'SKIP_INIT' in os.environ: - # TODO: This is currently needed so create_module_db in the Installer does not execute the rest - # of this section, which would be unnecessary. However, this should be done better in the future! - return - - if device_name != "LocalSimulator": - self._configure_aws_session(region) - - def _configure_aws_session(self, region: str) -> None: - """ - Configures the AWS session for the Braket device. - - :param region: AWS region to use - """ - proxy_definitions = self._setup_proxy() - region = self._set_region(region) - my_config = Config(region_name=region, proxies=proxy_definitions) - - profile_name = self._set_profile() - self._initialize_aws_session(profile_name, region, my_config) - - @staticmethod - def _setup_proxy() -> any: - """ - Sets up proxy configuration if available in the environment variables. - - :return: Proxy definitions - """ - if 'HTTP_PROXY' in os.environ: - proxy_definitions = { - 'http': os.environ['HTTP_PROXY'], - 'https': os.environ['HTTP_PROXY'] - } - os.environ['HTTPS_PROXY'] = os.environ['HTTP_PROXY'] - else: - logging.warning('No HTTP_PROXY set as an environment variable. ' - 'This might cause trouble if you are using a VPN.') - proxy_definitions = None - return proxy_definitions - - @staticmethod - def _set_region(region: str) -> str: - """ - Sets the AWS region from the environment variable or defaults to 'us-east-1'. - - :param region: Provided region - :return: Final region to be used - """ - if region is None: - region = os.environ.get('AWS_REGION', 'us-east-1') - logging.info(f"No AWS_REGION specified, using default region: {region}") - return region - - @staticmethod - def _set_profile() -> str: - """ - Determines the AWS profile to use for the session. - - :return: AWS profile name - """ - if 'AWS_PROFILE' in os.environ: - return os.environ['AWS_PROFILE'] - elif "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI" in os.environ: - logging.info("Assuming AWS container environment, using container credentials.") - return None - else: - profile_name = 'quantum_computing' - os.environ['AWS_PROFILE'] = profile_name - logging.info(f"No AWS_PROFILE specified, using default profile: {profile_name}") - return profile_name - - def _initialize_aws_session(self, profile_name: str, region: str, my_config: Config) -> None: - """ - Initializes the AWS session for interacting with Amazon Braket. - - :param profile_name: AWS profile name - :param region: AWS region - :param my_config: Boto3 configuration - :raises Exception: If the AWS profile is not found - """ - try: - if profile_name is None: - self.boto_session = boto3.Session(region_name=region) - else: - self.boto_session = boto3.Session(profile_name=profile_name, region_name=region) - self.aws_session = AwsSession(boto_session=self.boto_session, config=my_config) - except ProfileNotFound as exc: - logging.error(f"AWS-Profile {profile_name} could not be found! Please set the AWS_PROFILE env variable. " - "Only LocalSimulator is available.") - raise Exception("Please refer to the logged error message.") from exc - - @staticmethod - def get_requirements() -> list[dict]: - """ - Return requirements of this module. - - :return: List of dictionaries with requirements - """ - return [ - {"name": "amazon-braket-sdk", "version": "1.88.2"}, - {"name": "botocore", "version": "1.35.73"}, - {"name": "boto3", "version": "1.35.73"} - ] - - def init_s3_storage(self, folder_name: str) -> None: - """ - Initializes an S3 storage bucket for Amazon Braket. - - :param folder_name: Name of the s3 bucket - """ - run_timestamp = datetime.today().date() - username = getpass.getuser() - - bucket_name = f"amazon-braket-benchmark-framework-{run_timestamp}-{username}" - self.s3_destination_folder = (bucket_name, folder_name) - self._create_s3_bucket(self.boto_session, bucket_name) - - @staticmethod - def _create_s3_bucket(boto3_session: boto3.Session, bucket_name: str = 'quark-benchmark-framework', - region: str = 'us-east-1') -> None: - """ - Creates an S3 bucket with specific configurations. - - :param boto3-session: Boto3 session - :param bucket_name: Name of the s3 bucket - :param region: AWS region - """ - s3_client = boto3_session.client('s3', region_name=region) - - if region == "us-east-1": - s3_client.create_bucket(Bucket=bucket_name) - else: - location = {"LocationConstraint": region} - s3_client.create_bucket(Bucket=bucket_name, CreateBucketConfiguration=location) - - s3_client.put_public_access_block( - Bucket=bucket_name, - PublicAccessBlockConfiguration={ - 'BlockPublicAcls': True, - 'IgnorePublicAcls': True, - 'BlockPublicPolicy': True, - 'RestrictPublicBuckets': True - }, - ) diff --git a/src/modules/devices/braket/Braket.py b/src/modules/devices/braket/braket_temp.py similarity index 100% rename from src/modules/devices/braket/Braket.py rename to src/modules/devices/braket/braket_temp.py diff --git a/src/modules/devices/braket/ionq.py b/src/modules/devices/braket/ionq.py deleted file mode 100644 index ddbe03a3..00000000 --- a/src/modules/devices/braket/ionq.py +++ /dev/null @@ -1,59 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os -from braket.aws import AwsDevice - -from modules.devices.braket.braket import Braket - - -class Ionq(Braket): - """ - Class for using the IonQ devices on Amazon Braket. - """ - - def __init__(self, device_name: str, arn: str = 'arn:aws:braket:us-east-1::device/qpu/ionq/Aria-1'): - """ - Constructor method for initializing IonQ device on Amazon Braket. - - :param device_name: Name of the device - :param arn: Amazon resource name for the IonQ device - """ - super().__init__(region="us-east-1", device_name=device_name, arn=arn) - self.submodule_options = [] - - if 'SKIP_INIT' in os.environ: - # TODO: This is currently needed so create_module_db in the Installer does not execute the rest - # of this section, which would be unnecessary. However, this should be done better in the future! - return - - self.init_s3_storage("ionq") - self.device = AwsDevice(arn, aws_session=self.aws_session) - - def get_parameter_options(self) -> dict: - """ - Returns empty dictionary as this solver has no configurable settings. - - :return: An empty dictionary - """ - return {} - - def get_default_submodule(self, option: str) -> None: - """ - Raises ValueError as this module has no submodules. - - :param option: Option name - :raises ValueError: If called, since this module has no submodules - """ - raise ValueError("This module has no submodules.") diff --git a/src/modules/devices/braket/Ionq.py b/src/modules/devices/braket/ionq_temp.py similarity index 100% rename from src/modules/devices/braket/Ionq.py rename to src/modules/devices/braket/ionq_temp.py diff --git a/src/modules/devices/braket/oqc.py b/src/modules/devices/braket/oqc.py deleted file mode 100644 index 474f8fc5..00000000 --- a/src/modules/devices/braket/oqc.py +++ /dev/null @@ -1,59 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os -from braket.aws import AwsDevice - -from modules.devices.braket.braket import Braket - - -class OQC(Braket): - """ - Class for using the Oxford Quantum Circuits (OQC) devices on Amazon Braket. - """ - - def __init__(self, device_name: str, arn: str = 'arn:aws:braket:eu-west-2::device/qpu/oqc/Lucy'): - """ - Constructor method. - - :param device_name: Name of the device - :param arn: Amazon resource name for the OQC device - """ - super().__init__(region="eu-west-2", device_name=device_name, arn=arn) - self.submodule_options = [] - - if 'SKIP_INIT' in os.environ: - # TODO: This is currently needed so create_module_db in the Installer does not execute the rest - # of this section, which would be unnecessary. However, this should be done better in the future! - return - - self.init_s3_storage("oqc") - self.device = AwsDevice(arn, aws_session=self.aws_session) - - def get_parameter_options(self) -> dict: - """ - Returns an empty dictionary as this solver has no configurable settings. - - :return: Empty dict - """ - return {} - - def get_default_submodule(self, option: str) -> None: - """ - Raises ValueError as this module has no submodules. - - :param option: Option name - :raises ValueError: If called, since this module has no submodules - """ - raise ValueError("This module has no submodules.") diff --git a/src/modules/devices/braket/OQC.py b/src/modules/devices/braket/oqc_temp.py similarity index 100% rename from src/modules/devices/braket/OQC.py rename to src/modules/devices/braket/oqc_temp.py diff --git a/src/modules/devices/braket/rigetti.py b/src/modules/devices/braket/rigetti.py deleted file mode 100644 index dad24949..00000000 --- a/src/modules/devices/braket/rigetti.py +++ /dev/null @@ -1,59 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os -from braket.aws import AwsDevice - -from modules.devices.braket.braket import Braket - - -class Rigetti(Braket): - """ - Class for using the Rigetti devices on Amazon Braket. - """ - - def __init__(self, device_name: str, arn: str = 'arn:aws:braket:us-west-1::device/qpu/rigetti/Ankaa-2'): - """ - Constructor method. - - :param device_name: Name of the device - :param arn: Amazon resource name for the Rigetti device - """ - super().__init__(region="us-west-1", device_name=device_name, arn=arn) - self.submodule_options = [] - - if 'SKIP_INIT' in os.environ: - # TODO: This is currently needed so create_module_db in the Installer does not execute the rest - # of this section, which would be unnecessary. However, this should be done better in the future! - return - - self.init_s3_storage("rigetti") - self.device = AwsDevice(arn, aws_session=self.aws_session) - - def get_parameter_options(self) -> dict: - """ - Returns an empty dictionary as this solver has no configurable settings. - - :return: Empty dict - """ - return {} - - def get_default_submodule(self, option: str) -> None: - """ - Raises ValueError as this module has no submodules. - - :param option: Option name - :raises ValueError: If called, since this module has no submodules - """ - raise ValueError("This module has no submodules.") diff --git a/src/modules/devices/braket/Rigetti.py b/src/modules/devices/braket/rigetti_temp.py similarity index 100% rename from src/modules/devices/braket/Rigetti.py rename to src/modules/devices/braket/rigetti_temp.py diff --git a/src/modules/devices/braket/sv1.py b/src/modules/devices/braket/sv1.py deleted file mode 100644 index 1b356fc5..00000000 --- a/src/modules/devices/braket/sv1.py +++ /dev/null @@ -1,59 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os -from braket.aws import AwsDevice - -from modules.devices.braket.braket import Braket - - -class SV1(Braket): - """ - Class for using the SV1 simulator on Amazon Braket. - """ - - def __init__(self, device_name: str, arn: str = 'arn:aws:braket:::device/quantum-simulator/amazon/sv1'): - """ - Constructor method. - - :param device_name: Name of the device - :param arn: Amazon resource name for the SV1 simulator - """ - super().__init__(device_name=device_name, arn=arn) - self.submodule_options = [] - - if 'SKIP_INIT' in os.environ: - # TODO: This is currently needed so create_module_db in the Installer does not execute the rest - # of this section, which would be unnecessary. However, this should be done better in the future! - return - - self.init_s3_storage("sv1") - self.device = AwsDevice(arn, aws_session=self.aws_session) - - def get_parameter_options(self) -> dict: - """ - Returns empty dictionary as this solver has no configurable settings. - - :return: Empty dict - """ - return {} - - def get_default_submodule(self, option: str) -> None: - """ - Raises ValueError as this module has no submodules. - - :param option: Option name - :raises ValueError: If called, since this module has no submodules - """ - raise ValueError("This module has no submodules.") diff --git a/src/modules/devices/braket/SV1.py b/src/modules/devices/braket/sv1_temp.py similarity index 100% rename from src/modules/devices/braket/SV1.py rename to src/modules/devices/braket/sv1_temp.py diff --git a/src/modules/devices/braket/tn1.py b/src/modules/devices/braket/tn1.py deleted file mode 100644 index 4bcad081..00000000 --- a/src/modules/devices/braket/tn1.py +++ /dev/null @@ -1,59 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os -from braket.aws import AwsDevice - -from modules.devices.braket.braket import Braket - - -class TN1(Braket): - """ - Class for using the TN1 simulator on Amazon Braket. - """ - - def __init__(self, device_name: str, arn: str = 'arn:aws:braket:::device/quantum-simulator/amazon/tn1'): - """ - Constructor method. - - :param device_name: Name of the device - :param arn: Amazon resource name for the TN1 simulator - """ - super().__init__(device_name=device_name, arn=arn) - self.submodule_options = [] - - if 'SKIP_INIT' in os.environ: - # TODO: This is currently needed so create_module_db in the Installer does not execute the rest - # of this section, which would be unnecessary. However, this should be done better in the future! - return - - self.init_s3_storage("tn1") - self.device = AwsDevice(arn, aws_session=self.aws_session) - - def get_parameter_options(self) -> dict: - """ - Returns empty dict as this solver has no configurable settings. - - :return: Empty dict - """ - return {} - - def get_default_submodule(self, option: str) -> None: - """ - Raises ValueError as this module has no submodules. - - :param option: Option name - :raises ValueError: If called, since this module has no submodules - """ - raise ValueError("This module has no submodules.") diff --git a/src/modules/devices/braket/TN1.py b/src/modules/devices/braket/tn1_temp.py similarity index 100% rename from src/modules/devices/braket/TN1.py rename to src/modules/devices/braket/tn1_temp.py diff --git a/src/modules/devices/device.py b/src/modules/devices/device.py deleted file mode 100644 index 4b9c402f..00000000 --- a/src/modules/devices/device.py +++ /dev/null @@ -1,102 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from abc import ABC -from modules.core import Core -from utils import start_time_measurement, end_time_measurement - - -class Device(Core, ABC): - """ - The device class abstracts away details of the physical device, such as submitting a task to the quantum system. - """ - - def __init__(self, device_name: str): - """ - Constructor method. - - :param device_name: Name of the device - """ - super().__init__(device_name) - self.device = None - self.config = None - self.device_name = self.name - - def get_parameter_options(self) -> dict: - """ - Returns the parameters to fine-tune the device. - - Should always be in this format: - .. code-block:: json - - { - "parameter_name":{ - "values":[1, 2, 3], - "description":"How many reads do you want?" - } - } - - :return: Available device settings for this device - """ - return {} - - def set_config(self, config): - """ - Sets the device configuration. - - :param config: Configuration settings for the device - """ - self.config = config - - def preprocess(self, input_data: any, config: dict, **kwargs) -> tuple[any, float]: - """ - Returns instance of device class (self) and time it takes to call config. - - :param input_data: Input data (not used) - :param config: Config for the device - :param kwargs: Optional keyword arguments - :return: Output and time needed - """ - start = start_time_measurement() - self.config = config - return self, end_time_measurement(start) - - def postprocess(self, input_data: any, config: dict, **kwargs) -> tuple[any, float]: - """ - Returns input data and adds device name to the metrics class instance. - - :param input_data: Input data passed by the parent module - :param config: Solver config - :param kwargs: Optional keyword arguments - :return: Output and time needed - """ - start = start_time_measurement() - self.metrics.add_metric("device", self.get_device_name()) - return input_data, end_time_measurement(start) - - def get_device(self) -> any: - """ - Returns device. - - :return: Instance of the device class - """ - return self.device - - def get_device_name(self) -> str: - """ - Returns the device name. - - :return: Name of the device - """ - return self.device_name diff --git a/src/modules/devices/Device.py b/src/modules/devices/device_temp.py similarity index 100% rename from src/modules/devices/Device.py rename to src/modules/devices/device_temp.py diff --git a/src/modules/devices/local.py b/src/modules/devices/local.py deleted file mode 100644 index bc04a427..00000000 --- a/src/modules/devices/local.py +++ /dev/null @@ -1,46 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from modules.devices.device import Device - - -class Local(Device): - """ - Some solvers (often classical) run on a local environment without any specific device or setting needed. - """ - - def __init__(self): - """ - Constructor method. - """ - super().__init__(device_name="local") - self.device = None - self.submodule_options = [] - - def get_parameter_options(self) -> dict: - """ - Returns empty dictionary as this solver has no configurable settings. - - :return: Empty dict - """ - return {} - - def get_default_submodule(self, option: str) -> None: - """ - Raises ValueError as this module has no submodules. - - :param option: Option name - :raises ValueError: If called, since this module has no submodules - """ - raise ValueError("This module has no submodules.") diff --git a/src/modules/devices/Local.py b/src/modules/devices/local_temp.py similarity index 100% rename from src/modules/devices/Local.py rename to src/modules/devices/local_temp.py diff --git a/src/modules/devices/pulser/pulser.py b/src/modules/devices/pulser/pulser.py deleted file mode 100644 index 0cae17aa..00000000 --- a/src/modules/devices/pulser/pulser.py +++ /dev/null @@ -1,59 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from abc import ABC, abstractmethod - -from modules.devices.device import Device - - -class Pulser(Device, ABC): - """ - Abstract class to use the Pulser devices. - """ - - def __init__(self, device_name: str): - """ - Constructor method. - - :param device_name: Name of the Pulser device. - """ - super().__init__(device_name) - self.device = None - self.backend = None - - def get_backend(self) -> any: - """ - Returns backend. - - :return: Instance of the backend class - """ - return self.backend - - @abstractmethod - def get_backend_config(self) -> any: - """ - Returns backend configurations. - - :return: Instance of the backend config class - """ - pass - - @staticmethod - def get_requirements() -> list[dict]: - """ - Return requirements of this module. - - :return: List of dict with requirements of this module - """ - return [{"name": "pulser", "version": "1.1.1"}] diff --git a/src/modules/devices/pulser/Pulser.py b/src/modules/devices/pulser/pulser_temp.py similarity index 100% rename from src/modules/devices/pulser/Pulser.py rename to src/modules/devices/pulser/pulser_temp.py diff --git a/src/modules/solvers/annealer.py b/src/modules/solvers/annealer.py deleted file mode 100644 index c53314f0..00000000 --- a/src/modules/solvers/annealer.py +++ /dev/null @@ -1,111 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from typing import TypedDict -import logging - -from modules.solvers.solver import Solver -from modules.core import Core -from utils import start_time_measurement, end_time_measurement - - -class Annealer(Solver): - """ - Class for both quantum and simulated annealing. - """ - - def __init__(self): - """ - Constructor method. - """ - super().__init__() - self.submodule_options = ["Simulated Annealer"] - - def get_default_submodule(self, option: str) -> Core: - """ - Returns the default submodule based on the provided option. - - :param option: The name of the submodule - :return: Instance of the default submodule - """ - if option == "Simulated Annealer": - from modules.devices.simulated_annealing_sampler import SimulatedAnnealingSampler # pylint: disable=C0415 - return SimulatedAnnealingSampler() - else: - raise NotImplementedError(f"Device Option {option} not implemented") - - def get_parameter_options(self) -> dict: - """ - Returns the configurable settings for this solver. - - :return: Dictionary of parameter options - .. code-block:: python - - return { - "number_of_reads": { - "values": [100, 250, 500, 750, 1000], - "description": "How many reads do you need?" - } - } - - """ - return { - "number_of_reads": { - "values": [100, 250, 500, 750, 1000], - "description": "How many reads do you need?" - } - } - - class Config(TypedDict): - """ - Attributes of a valid config. - - .. code-block:: python - - number_of_reads: int - - """ - number_of_reads: int - - def run(self, mapped_problem: dict, device_wrapper: any, config: Config, **kwargs: dict) \ - -> tuple[dict, float, dict]: - """ - Run the annealing solver. - - :param mapped_problem: Dict with the key 'Q' where its value should be the QUBO - :param device_wrapper: Annealing device - :param config: Annealing settings - :param kwargs: Additional keyword arguments - :return: Solution, the time it took to compute it and optional additional information - """ - - q = mapped_problem['Q'] - additional_solver_information = {} - device = device_wrapper.get_device() - start = start_time_measurement() - - if device_wrapper.device_name != "simulated annealer": - logging.error("Only simulated annealer available at the moment!") - logging.error("Please select another solver module.") - logging.error("The benchmarking run terminates with exception.") - raise Exception("Please refer to the logged error message.") - - response = device.sample_qubo(q, num_reads=config['number_of_reads']) - time_to_solve = end_time_measurement(start) - - # Take the result with the lowest energy: - sample = response.lowest().first.sample - logging.info(f'Annealing finished in {time_to_solve} ms.') - - return sample, time_to_solve, additional_solver_information diff --git a/src/modules/solvers/Annealer.py b/src/modules/solvers/annealer_temp.py similarity index 100% rename from src/modules/solvers/Annealer.py rename to src/modules/solvers/annealer_temp.py diff --git a/src/modules/solvers/qaoa.py b/src/modules/solvers/qaoa.py deleted file mode 100644 index f447b812..00000000 --- a/src/modules/solvers/qaoa.py +++ /dev/null @@ -1,477 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from time import sleep -from typing import TypedDict -import logging - -import numpy as np -from braket.circuits import Circuit -from braket.aws import AwsDevice -from scipy.optimize import minimize - -from modules.solvers.solver import Solver -from modules.core import Core -from utils import start_time_measurement, end_time_measurement - - -class QAOA(Solver): - """ - QAOA with some parts copied/derived from https://github.com/aws/amazon-braket-examples. - """ - - def __init__(self): - """ - Constructor method. - """ - super().__init__() - self.submodule_options = [ - "LocalSimulator", "arn:aws:braket:::device/quantum-simulator/amazon/sv1", - "arn:aws:braket:::device/quantum-simulator/amazon/tn1", - "arn:aws:braket:us-east-1::device/qpu/ionq/Harmony", - "arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-3" - ] - - @staticmethod - def get_requirements() -> list[dict]: - """ - Return requirements of this module. - - :return: List of dict with requirements of this module - """ - return [ - {"name": "amazon-braket-sdk", "version": "1.88.2"}, - {"name": "scipy", "version": "1.12.0"}, - {"name": "numpy", "version": "1.26.4"} - ] - - def get_default_submodule(self, option: str) -> Core: - """ - Returns the default submodule based on the provided option. - - :param option: The name of the submodule - :return: Instance of the default submodule - """ - - if option == "arn:aws:braket:us-east-1::device/qpu/ionq/Harmony": - from modules.devices.braket.ionq import Ionq # pylint: disable=C0415 - return Ionq("ionQ", "arn:aws:braket:us-east-1::device/qpu/ionq/Harmony") - elif option == "arn:aws:braket:::device/quantum-simulator/amazon/sv1": - from modules.devices.braket.sv1 import SV1 # pylint: disable=C0415 - return SV1("SV1", "arn:aws:braket:::device/quantum-simulator/amazon/sv1") - elif option == "arn:aws:braket:::device/quantum-simulator/amazon/tn1": - from modules.devices.braket.tn1 import TN1 # pylint: disable=C0415 - return TN1("TN1", "arn:aws:braket:::device/quantum-simulator/amazon/tn1") - elif option == "arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-3": - from modules.devices.braket.rigetti import Rigetti # pylint: disable=C0415 - return Rigetti("Rigetti Aspen-9", "arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-3") - elif option == "LocalSimulator": - from modules.devices.braket.local_simulator import LocalSimulator # pylint: disable=C0415 - return LocalSimulator("LocalSimulator") - else: - raise NotImplementedError(f"Device Option {option} not implemented") - - def get_parameter_options(self) -> dict: - """ - Returns the configurable settings for this solver. - - :return: Dictionary of parameter settings - .. code-block:: python - - return { - "shots": { # number measurements to make on circuit - "values": list(range(10, 500, 30)), - "description": "How many shots do you need?" - }, - "opt_method": { - "values": ["Powell", "Nelder-Mead"], - "description": "Which optimization method do you want?" - }, - "depth": { - "values": [3], - "description": "Which circuit depth for QAOA do you want?" - } - } - - """ - return { - "shots": { # number of measurements to make on circuit - "values": list(range(10, 500, 30)), - "description": "How many shots do you need?" - }, - "opt_method": { - "values": ["Powell", "Nelder-Mead"], - "description": "Which optimization method do you want?" - }, - "depth": { - "values": [3], - "description": "Which circuit depth for QAOA do you want?" - } - } - - class Config(TypedDict): - """ - Attributes of a valid config. - - .. code-block:: python - - shots: int - opt_method: str - depth: int - - """ - shots: int - opt_method: str - depth: int - - def run(self, mapped_problem: dict, device_wrapper: any, config: Config, **kwargs: dict) -> tuple[any, float, dict]: - """ - Run QAOA algorithm on Ising. - - :param mapped_problem: Dict containing problem parameters mapped to the Ising model - :param device_wrapper: Instance of device - :param config: Solver configuration settings - :param kwargs: No additionally settings needed - :return: Solution, the time it took to compute it and optional additional information - """ - j = mapped_problem['J'] - if np.any(np.iscomplex(j)): - logging.warning("The problem matrix of the QAOA solver contains imaginary numbers." - "This may lead to an error later in the run.") - else: - j = np.real(j) - - # Set up the problem - n_qubits = j.shape[0] - - # User-defined hypers - depth = config['depth'] - opt_method = config['opt_method'] # SLSQP, COBYLA, Nelder-Mead, BFGS, Powell, ... - - # Initialize reference solution (simple guess) - bitstring_init = -1 * np.ones([n_qubits]) - energy_init = np.dot(bitstring_init, np.dot(j, bitstring_init)) - - # Set tracker to keep track of results - tracker = { - 'count': 1, # Elapsed optimization steps - 'optimal_energy': energy_init, # Global optimal energy - 'opt_energies': [], # Optimal energy at each step - 'global_energies': [], # Global optimal energy at each step - 'optimal_bitstring': bitstring_init, # Global optimal bitstring - 'opt_bitstrings': [], # Optimal bitstring at each step - 'costs': [], # Cost (average energy) at each step - 'res': None, # Quantum result object - 'params': [] # Track parameters - } - - # Set options for classical optimization - options = {'disp': True, 'maxiter': 100} - # options = {'disp': True, 'ftol': 1e-08, 'maxiter': 100, 'maxfev': 50} # example options - - ################################################################################## - # Run QAOA optimization on graph - ################################################################################## - - logging.info(f"Circuit depth hyperparameter:{depth}") - logging.info(f"Problem size:{n_qubits}") - - # Kick off training - start = start_time_measurement() - _, _, tracker = train( - device=device_wrapper.get_device(), - options=options, - p=depth, ising=j, - n_qubits=n_qubits, - n_shots=config['shots'], - opt_method=opt_method, - tracker=tracker, - s3_folder=device_wrapper.s3_destination_folder, - verbose=True - ) - time_to_solve = end_time_measurement(start) - - # Log optimized results - logging.info(f"Optimal energy: {tracker['optimal_energy']}") - logging.info(f"Optimal classical bitstring: {tracker['optimal_bitstring']}") - - # TODO maybe save this plot - # plt.plot(cycles, optim_classical) - # plt.xlabel('optimization cycle') - # plt.ylabel('best classical minimum') - # plt.show() - - return tracker['optimal_bitstring'], time_to_solve, {} - - -# QAOA utils (source: -# https://github.com/aws/amazon-braket-examples/blob/main/examples/hybrid_quantum_algorithms/QAOA/utils_qaoa.py) - -# Function to implement ZZ gate using CNOT gates -def zz_gate(q1: any, q2: any, gamma: float) -> Circuit: - """ - Function that returns a circuit implementing exp(-i \\gamma Z_i Z_j) using CNOT gates if ZZ not supported. - - :param q1: Qubit 1 (control) - :param q2: Qubit 2 (target) - :param gamma: Gamma parameter (angle) - :return: ZZ gate - """ - # Get a circuit - circ_zz = Circuit() - - # Construct decomposition of ZZ - circ_zz.cnot(q1, q2).rz(q2, gamma).cnot(q1, q2) - - return circ_zz - - -# Function to implement evolution with driver Hamiltonian -def driver(beta: float, n_qubits: int) -> Circuit: - """ - Returns circuit for driver Hamiltonian U(Hb, beta). - - :param beta: Beta parameter (angle) - :param n_qubits: Number of qubits - :return: Circuit with rotated qubits - """ - # Instantiate circuit object - circ = Circuit() - - # Apply parametrized rotation around x to every qubit - for qubit in range(n_qubits): - gate = Circuit().rx(qubit, 2 * beta) - circ.add(gate) - - return circ - - -# Helper function for evolution with cost Hamiltonian -def cost_circuit(gamma: float, ising: np.ndarray, device: AwsDevice) -> Circuit: - """ - Returns circuit for evolution with cost Hamiltonian. - - :param gamma: Gamma parameter (angle) - :param ising: Ising matrix - :param device: Device to run the circuit on - :return: Circuit representing the cost Hamiltonian - """ - # Instantiate circuit object - circ = Circuit() - - # Get all non-zero entries (edges) from Ising matrix - idx = ising.nonzero() - edges = list(zip(idx[0], idx[1])) - - # Apply ZZ gate for every edge (with corresponding interaction strength) - for qubit_pair in edges: - # Get interaction strength from Ising matrix - int_strength = ising[qubit_pair[0], qubit_pair[1]] - # For Rigetti we decompose ZZ using CNOT gates - if device.name in ["Rigetti", "Aspen-9"]: # TODO make this more flexible - gate = zz_gate(qubit_pair[0], qubit_pair[1], gamma * int_strength) - # Classical simulators and IonQ support ZZ gate - else: - gate = Circuit().zz(qubit_pair[0], qubit_pair[1], angle=2 * gamma * int_strength) - circ.add(gate) - - return circ - - -# Function to build the QAOA circuit with depth p -def circuit(params: np.array, device: AwsDevice, n_qubits: int, ising: np.ndarray) -> Circuit: - """ - Function to return the full QAOA circuit; depends on device as ZZ implementation depends on gate set of backend. - - :param params: Array containing the beta and gamma parameters - :param device: Device to run the circuit on - :param n_qubits: Number of qubits - :param ising: Ising matrix - :return: QAOA Circuit - """ - - # Initialize QAOA circuit with first Hadamard layer - circ = Circuit() - x_on_all = Circuit().x(range(0, n_qubits)) - circ.add(x_on_all) - h_on_all = Circuit().h(range(0, n_qubits)) - circ.add(h_on_all) - - # Setup two parameter families - circuit_length = int(len(params) / 2) - gammas = params[:circuit_length] - betas = params[circuit_length:] - - # Add QAOA circuit layer blocks - for mm in range(circuit_length): - circ.add(cost_circuit(gammas[mm], ising, device)) - circ.add(driver(betas[mm], n_qubits)) - - return circ - - -# Function that computes cost function for given params -# pylint: disable=R0917 -# pylint: disable=R0913 -def objective_function(params: np.array, device: AwsDevice, ising: np.ndarray, n_qubits: int, n_shots: int, - tracker: dict, s3_folder: tuple[str, str], verbose: bool) -> float: - """ - Objective function takes a list of variational parameters as input, - and returns the cost associated with those parameters. - - :param params: Array containing beta and gamma parameters - :param device: Device to run the circuit on - :param ising: Ising matrix - :param n_qubits: Number of qubits - :param n_shots: Number of measurements to make on the circuit - :param tracker: Keeps track of the runs on the circuit - :param s3_folder: AWS S3 bucket - :param verbose: Controls degree of detail in logs - :return: Energy expectation value - """ - - if verbose: - logging.info("==================================" * 2) - logging.info(f"Calling the quantum circuit. Cycle: {tracker['count']}") - - # Get a quantum circuit instance from the parameters - qaoa_circuit = circuit(params, device, n_qubits, ising) - - # Classically simulate the circuit - # Execute the correct device.run call depending on whether the backend is local or cloud based - if device.name in ["DefaultSimulator", "StateVectorSimulator"]: - task = device.run(qaoa_circuit, shots=n_shots) - else: - task = device.run(qaoa_circuit, s3_folder, shots=n_shots, poll_timeout_seconds=3 * 24 * 60 * 60) - - # Get ID and status of submitted task - task_id = task.id - status = task.state() - logging.info(f"ID of task: {task_id}") - logging.info(f"Status of task: {status}") - - # Wait for job to complete - while status != 'COMPLETED': - status = task.state() - logging.info(f"Status: {status}") - sleep(10) - - # Get result for this task - result = task.result() - logging.info(result) - - # Convert results (0 and 1) to ising (-1 and 1) - meas_ising = result.measurements - meas_ising[meas_ising == 0] = -1 - - # Get all energies (for every shot): (n_shots, 1) vector - all_energies = np.diag(np.dot(meas_ising, np.dot(ising, np.transpose(meas_ising)))) - - # Find minimum and corresponding classical string - energy_min = np.min(all_energies) - tracker["opt_energies"].append(energy_min) - optimal_string = meas_ising[np.argmin(all_energies)] - tracker["opt_bitstrings"].append(optimal_string) - logging.info(tracker["optimal_energy"]) - - # Store optimal (classical) result/bitstring - if energy_min < tracker["optimal_energy"]: - tracker.update({"optimal_energy": energy_min, "optimal_bitstring": optimal_string}) - - # Store global minimum - tracker["global_energies"].append(tracker["optimal_energy"]) - - # Energy expectation value - energy_expect = np.sum(all_energies) / n_shots - - if verbose: - logging.info(f"Minimal energy: {energy_min}") - logging.info(f"Optimal classical string: {optimal_string}") - logging.info(f"Energy expectation value (cost): {energy_expect}") - - # Update tracker - tracker.update({"count": tracker["count"] + 1, "res": result}) - tracker["costs"].append(energy_expect) - tracker["params"].append(params) - - return energy_expect - - -# The function to execute the training: run classical minimization. -# pylint: disable=R0917 -def train(device: AwsDevice, options: dict, p: int, ising: np.ndarray, n_qubits: int, n_shots: int, opt_method: str, - tracker: dict, s3_folder: tuple[str, str], verbose: bool = True) -> tuple[float, np.ndarray, dict]: - """ - Function to run QAOA algorithm for given, fixed circuit depth p. - - :param device: Device to run the circuit on - :param options: Dict containing parameters of classical part of the QAOA - :param p: Circuit depth - :param ising: Ising matrix - :param n_qubits: Number of qubits - :param n_shots: Number of measurements to make on the circuit - :param opt_method: Controls degree of detail in logs - :param tracker: Keeps track of the runs on the circuit - :param s3_folder: AWS S3 bucket - :param verbose: Controls degree of detail in logs - :return: Results of the training as a tuple of the energy, the angle and the tracker - """ - logging.info("Starting the training.") - logging.info("==================================" * 2) - logging.info(f"OPTIMIZATION for circuit depth p={p}") - - if not verbose: - logging.info('Param "verbose" set to False. Will not print intermediate steps.') - logging.info("==================================" * 2) - - # Initialize - cost_energy = [] - - # Randomly initialize variational parameters within appropriate bounds - gamma_initial = np.random.uniform(0, 2 * np.pi, p).tolist() - beta_initial = np.random.uniform(0, np.pi, p).tolist() - params0 = np.array(gamma_initial + beta_initial) - - # Set bounds for search space - bnds_gamma = [(0, 2 * np.pi) for _ in range(int(len(params0) / 2))] - bnds_beta = [(0, np.pi) for _ in range(int(len(params0) / 2))] - bnds = bnds_gamma + bnds_beta - - tracker["params"].append(params0) - print(f"Qubit count: {n_qubits}") - - # Run classical optimization (example: method='Nelder-Mead') - try: - result = minimize( - objective_function, - params0, - args=(device, ising, n_qubits, n_shots, tracker, s3_folder, verbose), - options=options, - method=opt_method, - bounds=bnds, - ) - except ValueError as e: - logging.error(f"The following ValueError occurred in module QAOA: {e}") - logging.error("The benchmarking run terminates with exception.") - raise Exception("Please refer to the logged error message.") from e - - # Store result of classical optimization - result_energy = result.fun - cost_energy.append(result_energy) - logging.info(f"Final average energy (cost): {result_energy}") - result_angle = result.x - logging.info(f"Final angles: {result_angle}") - logging.info("Training complete.") - - return result_energy, result_angle, tracker diff --git a/src/modules/solvers/QAOA.py b/src/modules/solvers/qaoa_temp.py similarity index 100% rename from src/modules/solvers/QAOA.py rename to src/modules/solvers/qaoa_temp.py diff --git a/src/modules/solvers/solver.py b/src/modules/solvers/solver.py deleted file mode 100644 index 1ae3811e..00000000 --- a/src/modules/solvers/solver.py +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from abc import ABC, abstractmethod -from modules.core import Core - - -class Solver(Core, ABC): - """ - The solver is responsible for finding feasible and high-quality solutions of the formulated problem, i.e., of the - defined objective function. - """ - - def postprocess(self, input_data: any, config: dict, **kwargs) -> tuple[any, float]: - """ - The actual solving process is done here, using the device which is provided by the device submodule - and the problem data provided by the parent module. - - :param input_data: Data passed to the run function of the solver - :param config: Solver config - :param kwargs: Optional keyword arguments - :return: Output and time needed - """ - output, elapsed_time, additional_metrics = self.run(self.preprocessed_input, input_data, config, **kwargs) - self.metrics.add_metric_batch(additional_metrics) - return output, elapsed_time - - @abstractmethod - def run(self, mapped_problem: any, device_wrapper: any, config: any, **kwargs) -> tuple[any, float, dict]: - """ - This function runs the solving algorithm on a mapped problem instance and returns a solution. - - :param mapped_problem: A representation of the problem that the solver can solve - :param device_wrapper: A device the solver can leverage for the algorithm - :param config: Settings for the solver such as hyperparameters - :param kwargs: Optional additional settings - :return: Solution, the time it took to compute it and some optional additional information - """ - pass diff --git a/src/modules/solvers/Solver.py b/src/modules/solvers/solver_temp.py similarity index 100% rename from src/modules/solvers/Solver.py rename to src/modules/solvers/solver_temp.py diff --git a/src/plotter.py b/src/plotter.py deleted file mode 100644 index b5ce0141..00000000 --- a/src/plotter.py +++ /dev/null @@ -1,248 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from collections import defaultdict -import logging - -import matplotlib -from matplotlib import pyplot as plt -import seaborn as sns -import pandas as pd - -matplotlib.use('Agg') -matplotlib.rcParams['savefig.dpi'] = 300 -sns.set(style="darkgrid") - - -class Plotter: - """ - Plotter class which generates some general plots. - """ - - @staticmethod - def visualize_results(results: list[dict], store_dir: str) -> None: - """ - Function to plot the execution times of the benchmark. - - :param results: Dict containing the results - :param store_dir: Directory where the plots are stored - """ - if results is None or len(results) == 0: - logging.info("Nothing to plot since results are empty.") - return - - processed_results_with_application_score = [] - processed_results_rest = [] - required_application_score_keys = [ - "application_score_value", "application_score_unit", "application_score_type" - ] - application_name = None - application_axis = None - static_keys, changing_keys = Plotter._get_config_keys(results) - for result in results: - application_name = result["module"]["module_name"] - if len(changing_keys) == 1: - # If only 1 config item changes, we use its value for application_config - application_axis = changing_keys[0] - application_config = result['module']['module_config'][changing_keys[0]] - else: - # If multiple config items change, we stringify them - application_axis = f"{application_name} Config" - application_config = ', '.join( - [f"{key}: {value}" for (key, value) in sorted(result["module"]["module_config"].items(), - key=lambda key_value_pair: - key_value_pair[0]) if key not in static_keys] - ) - if len(static_keys) > 0: - # Include the static items in the axis name - application_axis += "(" + ', '.join( - [f"{key}: {result['module']['module_config'][key]}" for key in static_keys] - ) + ")" - - processed_item = Plotter._extract_columns({ - "benchmark_backlog_item_number": result["benchmark_backlog_item_number"], - "total_time": result["total_time"], - "application_config": application_config - }, result["module"]) - - if all(k in result["module"] for k in required_application_score_keys): - # Check if all required keys are present to create application score plots - for k in required_application_score_keys: - processed_item[k] = result["module"][k] - processed_results_with_application_score.append(processed_item) - else: - processed_results_rest.append(processed_item) - - if len(processed_results_with_application_score) > 0: - logging.info("Found results with an application score, generating according plots.") - Plotter.plot_application_score( - application_name, application_axis, processed_results_with_application_score, store_dir - ) - - Plotter.plot_times( - application_name, application_axis, - [*processed_results_with_application_score, *processed_results_rest], - store_dir, required_application_score_keys - ) - - logging.info("Finished creating plots.") - - @staticmethod - def plot_times(application_name: str, application_axis: str, results: list[dict], store_dir: str, - required_application_score_keys: list) -> None: - """ - Function to plot execution times of the different modules in a benchmark. - - :param application_name: Name of the application - :param application_axis: Name of the application axis - :param results: Dict containing the results - :param store_dir: Directory where the plots are stored - :param required_application_score_keys: List of keys which have to be present to calculate an application score - """ - - df = pd.DataFrame.from_dict(results) - df = df.fillna(0.0).infer_objects(copy=False) - df_melt = df.drop(df.filter(["application_config", "config_combo", "total_time", - *required_application_score_keys]), axis=1) - df_melt = pd.melt(frame=df_melt, id_vars='benchmark_backlog_item_number', var_name='module_config', - value_name='time') - - # This plot shows the execution time of each module - ax = sns.barplot(x="benchmark_backlog_item_number", y="time", data=df_melt, hue="module_config") - plt.title(application_name) - # Put the legend out of the figure - plt.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0., title="Modules used") - ax.set(xlabel="benchmark run ID", ylabel='execution time of module (ms)') - plt.savefig(f"{store_dir}/time_by_module.pdf", dpi=300, bbox_inches='tight') - logging.info(f"Saved {f'{store_dir}/time_by_module.pdf'}.") - plt.clf() - - # This plot shows the total time of a benchmark run - ax = sns.barplot(x="application_config", y="total_time", data=df, hue="config_combo") - ax.set(xlabel=application_axis, ylabel="total execution time (ms)") - # Put the legend out of the figure - plt.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0., title="Modules used") - plt.title(application_name) - plt.sca(ax) - # If column values are very long and of type string rotate the ticks - if (pd.api.types.is_string_dtype(df.application_config.dtype) or pd.api.types.is_object_dtype( - df.application_config.dtype)) and df.application_config.str.len().max() > 10: - plt.xticks(rotation=90) - plt.savefig(f"{store_dir}/total_time.pdf", dpi=300, bbox_inches='tight') - logging.info(f"Saved {f'{store_dir}/total_time.pdf'}.") - plt.clf() - - @staticmethod - def plot_application_score(application_name: str, application_axis: str, results: list[dict], - store_dir: str) -> None: - """ - Function to create plots showing the application score. - - :param application_name: Name of the application - :param application_axis: Name of the application axis - :param results: Dict containing the results - :param store_dir: Directory where the plots are stored - """ - df = pd.DataFrame.from_dict(results) - application_score_units = df["application_score_unit"].unique() - count_invalid_rows = pd.isna(df['application_score_value']).sum() - - if count_invalid_rows == len(df): - logging.info("All results have an invalid application score, skipping plotting.") - return - else: - logging.info(f"{count_invalid_rows} out of {len(df)} benchmark runs have an invalid application score.") - - if len(application_score_units) != 1: - logging.warning( - f"Found more or less than exactly 1 application_score_unit in {application_score_units}." - f" This might lead to incorrect plots!" - ) - - ax = sns.barplot(x="application_config", y="application_score_value", data=df, hue="config_combo") - ax.set(xlabel=application_axis, ylabel=application_score_units[0]) - # Put the legend out of the figure - plt.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0., title="Modules used") - ax.text( - 1.03, 0.5, - f"{len(df) - count_invalid_rows}/{len(df)} runs have a valid \napplication score", - transform=ax.transAxes, fontsize=12, verticalalignment='top', - bbox={"boxstyle": "round", "alpha": 0.15} - ) - plt.title(application_name) - - plt.sca(ax) - # If column values are very long and of type string, rotate the ticks - if (pd.api.types.is_string_dtype(df.application_config.dtype) or pd.api.types.is_object_dtype( - df.application_config.dtype)) and df.application_config.str.len().max() > 10: - plt.xticks(rotation=90) - - plt.savefig(f"{store_dir}/application_score.pdf", dpi=300, bbox_inches='tight') - logging.info(f"Saved {f'{store_dir}/application_score.pdf'}.") - plt.clf() - - @staticmethod - def _get_config_keys(results: list[dict]) -> tuple[list, list]: - """ - Function that extracts config keys. - - :param results: Results of a benchmark run - :return: Tuple with list of static keys and list of changing keys - """ - static_keys = [] - changing_keys = [] - helper_dict = defaultdict(list) - # Try to find out which key in the dict change - for result in results: # you can list as many input dicts as you want here - d = result["module"]["module_config"] - for key, value in d.items(): - helper_dict[key].append(value) - helper_dict[key] = list(set(helper_dict[key])) - - for key, value in helper_dict.items(): - if len(value) == 1: - static_keys.append(key) - else: - changing_keys.append(key) - - return static_keys, changing_keys - - @staticmethod - def _extract_columns(config: dict, rest_result: dict) -> dict: - """ - Function to extract and summarize certain data fields like the time spent in every module - from the nested module chain. - - :param config: Dictionary containing multiple data fields like the config of a module - :param rest_result: Rest of the module chain - :return: Extracted data - """ - if rest_result: - module_name = rest_result["module_name"] - for key, value in sorted(rest_result["module_config"].items(), - key=lambda key_value_pair: key_value_pair[0]): - module_name += f", {key}: {value}" - - config_combo = config.pop("config_combo") + "\n" + module_name if "config_combo" in config else "" - return Plotter._extract_columns( - { - **config, - "config_combo": config_combo, - module_name: rest_result["total_time"] - if module_name not in config else config[module_name] + rest_result["total_time"] - }, - rest_result["submodule"] - ) - - return config diff --git a/src/Plotter.py b/src/plotter_temp.py similarity index 100% rename from src/Plotter.py rename to src/plotter_temp.py diff --git a/src/quark2_adapter/legacy_classes/application.py b/src/quark2_adapter/legacy_classes/application.py deleted file mode 100644 index f3aa8a19..00000000 --- a/src/quark2_adapter/legacy_classes/application.py +++ /dev/null @@ -1,210 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from abc import ABC, abstractmethod -from typing import final - -from utils import _get_instance_with_sub_options - - -class Application(ABC): - """ - The application component defines the workload, comprising a dataset of increasing complexity, - a validation, and an evaluation function. - """ - - def __init__(self, application_name: str): - """ - Constructor method. - - :param application_name: Name of the application - """ - self.application_name = application_name - self.application = None - self.mapping_options = [] - self.sub_options = [] - - self.problem = None - self.problems = {} - self.conf_idx = None - - super().__init__() - - def get_application(self) -> any: - """ - Getter that returns the application. - - :return: The application instance - """ - return self.application - - @abstractmethod - def get_solution_quality_unit(self) -> str: - """ - Method to return the unit of the evaluation which is used to make the plots nicer. - - :return: String with the unit - """ - - @abstractmethod - def get_parameter_options(self) -> dict: - """ - Method to return the parameters needed to create a concrete problem of an application. - - Should always be in this format: - - .. code-block:: json - - { - "parameter_name":{ - "values":[1, 2, 3], - "description":"How many nodes do you need?" - }, - "parameter_name_2":{ - "values":["x", "y"], - "description":"Which type of problem do you want?" - } - } - - :return: Available application settings for this application - """ - pass - - def regenerate_on_iteration(self, config: dict) -> bool: - """ - Overwrite this to return True if the problem should be newly generated - on every iteration. Typically, this will be the case if the problem is taken - from a statistical ensemble e.g. an erdos-renyi graph. - - :param config: The application configuration - :return: Whether the problem should be recreated on every iteration. Returns False if not overwritten. - """ - return False - - @final - def init_problem(self, config: dict, conf_idx: int, iter_count: int, path: str) -> any: - """ - This method is called on every iteration and calls generate_problem if necessary. - conf_idx identifies the application configuration. - Note that when there are several mappings, solvers or devices this method is - called several times with the same conf_idx and rep_count. In this case - the problem will be the same if conf_idx and rep_count are both the same. - - The implementation assumes that the loop over the rep_count is within the loop - over the different application configurations (conf_idx). - - :param config: the application configuration - :param conf_idx: the index of the application configuration - :param iter_count: the repetition count (starting with 1) - :param path: the path used to save each newly generated problem instance - :return: the current problem instance - """ - if conf_idx != self.conf_idx: - self.problems = {} - self.conf_idx = conf_idx - - key = iter_count if self.regenerate_on_iteration(config) else "dummy" - if key in self.problems: - self.problem = self.problems[key] - else: - self.problem = self.generate_problem(config, iter_count) - self.problems[key] = self.problem - self.save(path, iter_count) - return self.problem - - @abstractmethod - def generate_problem(self, config: dict, iter_count: int) -> any: - """ - Depending on the config this method creates a concrete problem and returns it. - - :param config: The application configuration - :param iter_count: The iteration count - :return: The generated problem instance - """ - pass - - def process_solution(self, solution) -> tuple[any, float]: - """ - Most of the time the solution has to be processed before it can be validated and evaluated - This might not be necessary in all cases, so the default is to return the original solution. - - :param solution: The solution to be processed - :return: Processed solution and the execution time to process it - """ - return solution, 0 - - @abstractmethod - def validate(self, solution) -> tuple[bool, float]: - """ - Check if the solution is valid. - - :param solution: The solution to validate - :return: Boolean indicating if the solution is valid and the time it took to create it - """ - pass - - @abstractmethod - def evaluate(self, solution: any) -> tuple[float, float]: - """ - Checks how good the solution is to allow comparison to other solutions. - - :param solution: The solution to evaluate - :return: Evaluation and the time it took to create it - """ - pass - - @abstractmethod - def save(self, path: str, iter_count: int) -> None: - """ - Save the concrete problem. - - :param path: Path of the experiment directory for this run - :param iter_count: the iteration count - """ - pass - - def get_submodule(self, mapping_option: str) -> any: - """ - If self.sub_options is not None, a mapping is instantiated according to the information given in - self.sub_options. Otherwise, get_mapping is called as fall back. - - :param mapping_option: The option for the mapping - :return: instance of a mapping class - """ - if self.sub_options is None: - return self.get_mapping(mapping_option) - else: - return _get_instance_with_sub_options(self.sub_options, mapping_option) - - @abstractmethod - def get_mapping(self, mapping_option: str) -> any: - """ - Return a default mapping for an application. This applies only if - self.sub_options is None. See get_submodule. - - :param mapping_option: String with the option - :return: Instance of a mapping class - """ - pass - - def get_available_mapping_options(self) -> list: - """ - Gets the list of available mapping options. - - :return: list of mapping options - """ - if self.sub_options is None: - return self.mapping_options - else: - return [option["name"] for option in self.sub_options] diff --git a/src/quark2_adapter/legacy_classes/Application.py b/src/quark2_adapter/legacy_classes/application_temp.py similarity index 100% rename from src/quark2_adapter/legacy_classes/Application.py rename to src/quark2_adapter/legacy_classes/application_temp.py diff --git a/src/quark2_adapter/legacy_classes/device.py b/src/quark2_adapter/legacy_classes/device.py deleted file mode 100644 index 7bc0d066..00000000 --- a/src/quark2_adapter/legacy_classes/device.py +++ /dev/null @@ -1,75 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from abc import ABC - - -class Device(ABC): - """ - The device class abstracts away details of the physical device, - such as submitting a task to the quantum system. - """ - - def __init__(self, device_name: str): - """ - Constructor method. - - :param device_name: Name of the device - """ - self.device = None - self.device_name = device_name - self.config = None - - def get_parameter_options(self) -> dict: - """ - Method to return the parameters to fine tune the device. - - Should always be in this format: - - .. code-block:: json - - { - "parameter_name":{ - "values":[1, 2, 3], - "description":"How many reads do you want?" - } - } - - :return: Available device settings for this device - """ - return {} - - def set_config(self, config: dict) -> None: - """ - Sets the device configuration. - - :param config: Configuration dictionary - """ - self.config = config - - def get_device(self) -> any: - """ - Returns the device instance. - - :return: Instance of the device - """ - return self.device - - def get_device_name(self) -> str: - """ - Returns the name of the Device. - - :return: Name of the device - """ - return self.device_name diff --git a/src/quark2_adapter/legacy_classes/Device.py b/src/quark2_adapter/legacy_classes/device_temp.py similarity index 100% rename from src/quark2_adapter/legacy_classes/Device.py rename to src/quark2_adapter/legacy_classes/device_temp.py diff --git a/src/quark2_adapter/legacy_classes/mapping.py b/src/quark2_adapter/legacy_classes/mapping.py deleted file mode 100644 index 05e0d716..00000000 --- a/src/quark2_adapter/legacy_classes/mapping.py +++ /dev/null @@ -1,108 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from abc import ABC, abstractmethod -from utils import _get_instance_with_sub_options - - -class Mapping(ABC): - """ - The task of the mapping module is to translate the application’s data and problem specification - into a mathematical formulation suitable for a solver. - """ - - def __init__(self): - """ - Constructor method. - """ - self.solver_options = [] - self.sub_options = None - super().__init__() - - @abstractmethod - def map(self, problem: any, config: dict) -> tuple[any, float]: - """ - Maps the given problem into a specific format a solver can work with. E.g. graph to QUBO. - - :param problem: Problem instance which should be mapped to the target representation - :param config: Instance of class Config specifying the mapping settings - :return: The mapped problem and the time it took to create the mapping - """ - pass - - def reverse_map(self, solution: any) -> tuple[any, float]: - """ - Maps the solution back to the original problem. This might not be necessary in all cases, so the default is - to return the original solution. This might be needed to convert the solution to a representation needed - for validation and evaluation. - - :param solution: Solution to be reversed back to its original representation - :return: Mapped solution and the time it took to create it - """ - return solution, 0 - - @abstractmethod - def get_parameter_options(self) -> dict: - """ - Method to return the parameters to fine tune the mapping. - - Should always be in this format: - - .. code-block:: json - - { - "parameter_name":{ - "values":[1, 2, 3], - "description":"How to scale your Lagrangian?" - } - } - - :return: Returns the available parameter options of this mapping - """ - pass - - def get_submodule(self, solver_option: str) -> any: - """ - If self.sub_options is not None, a solver is instantiated according to the information given in sub_options. - Otherwise, get_solver is called as fall back. - - :param solver_option: The option for the solver - :return: Instance of a solver class - """ - if self.sub_options is None: - return self.get_solver(solver_option) - else: - return _get_instance_with_sub_options(self.sub_options, solver_option) - - @abstractmethod - def get_solver(self, solver_option: str) -> any: - """ - Returns the default solver for a given string. This applies only if - self.sub_options is None. See get_submodule. - - :param solver_option: desired solver option - :return: Instance of solver class - """ - pass - - def get_available_solver_options(self) -> list: - """ - Returns all available solvers. - - :return: List of solvers - """ - if self.sub_options is None: - return self.solver_options - else: - return [option["name"] for option in self.sub_options] diff --git a/src/quark2_adapter/legacy_classes/Mapping.py b/src/quark2_adapter/legacy_classes/mapping_temp.py similarity index 100% rename from src/quark2_adapter/legacy_classes/Mapping.py rename to src/quark2_adapter/legacy_classes/mapping_temp.py diff --git a/src/quark2_adapter/legacy_classes/solver.py b/src/quark2_adapter/legacy_classes/solver.py deleted file mode 100644 index 07ad5ee2..00000000 --- a/src/quark2_adapter/legacy_classes/solver.py +++ /dev/null @@ -1,99 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from abc import ABC, abstractmethod -from utils import _get_instance_with_sub_options - - -class Solver(ABC): - """ - The solver is responsible for finding feasible and high-quality solutions - of the formulated problem, i.e., of the defined objective function. - """ - - def __init__(self): - """ - Constructor method. - """ - self.device_options = [] - self.sub_options = None - super().__init__() - - @abstractmethod - def run(self, mapped_problem: any, device: any, config: dict, **kwargs) -> tuple[any, float, dict]: - """ - This function runs the solving algorithm on a mapped problem instance and returns a solution. - - :param mapped_problem: A representation of the problem that the solver can solve - :param device: A device the solver can leverage for the algorithm - :param config: Settings for the solver such as hyperparameters - :param kwargs: Optional additional settings - :return: Solution, the time it took to compute it and some optional additional information - """ - pass - - @abstractmethod - def get_parameter_options(self) -> dict: - """ - Method to return the parameters to fine tune the solver. - - Should always be in this format: - - .. code-block:: json - - { - "parameter_name":{ - "values":[1, 2, 3], - "description":"How many reads do you want?" - } - } - - :return: Available solver settings for this solver - """ - pass - - def get_submodule(self, device_option: str) -> any: - """ - If self.sub_options is not None, a device is instantiated according to the information given in - self.sub_options. Otherwise, get_device is called as fall back. - - :param device_option: The option for the device - :return: Instance of the device class - """ - if self.sub_options is None: - return self.get_device(device_option) - else: - return _get_instance_with_sub_options(self.sub_options, device_option) - - @abstractmethod - def get_device(self, device_option: str) -> any: - """ - Returns the default device based on string. This applies only if - self.sub_options is None. See get_submodule. - - :param device_option: Desired device option - :return: Instance of the device class - """ - pass - - def get_available_device_options(self) -> list: - """ - Returns the list of available devices. - - :return: List of devices - """ - if self.sub_options is None: - return self.device_options - else: - return [o["name"] for o in self.sub_options] diff --git a/src/quark2_adapter/legacy_classes/Solver.py b/src/quark2_adapter/legacy_classes/solver_temp.py similarity index 100% rename from src/quark2_adapter/legacy_classes/Solver.py rename to src/quark2_adapter/legacy_classes/solver_temp.py diff --git a/tests/configs/invalid/tsp.yml b/tests/configs/invalid/tsp.yml deleted file mode 100644 index 1efff8e5..00000000 --- a/tests/configs/invalid/tsp.yml +++ /dev/null @@ -1,26 +0,0 @@ -application: - config: - nodes: - - 4 - - 6 - name: TSP - submodules: - - config: {} - name: GreedyClassicalTSPInvalid - submodules: - - config: {} - name: Local - submodules: [] - - config: {} - name: ReverseGreedyClassicalTSP - submodules: - - config: {} - name: Invalid - submodules: [] - - config: {} - name: RandomTSP - submodules: - - config: {} - name: Local - submodules: [] -repetitions: 2 diff --git a/tests/configs/invalid/TSP.yml b/tests/configs/invalid/tsp_temp.yml similarity index 100% rename from tests/configs/invalid/TSP.yml rename to tests/configs/invalid/tsp_temp.yml diff --git a/tests/configs/valid/acl.yml b/tests/configs/valid/acl.yml deleted file mode 100644 index ee9d08d9..00000000 --- a/tests/configs/valid/acl.yml +++ /dev/null @@ -1,25 +0,0 @@ -application: - config: - model_select: - - Tiny - name: ACL - submodules: - - config: {} - name: MIPsolverACL - submodules: - - config: {} - name: Local - submodules: [] - - config: {} - name: QUBO - submodules: - - config: - number_of_reads: - - 100 - - 250 - name: Annealer - submodules: - - config: {} - name: Simulated Annealer - submodules: [] -repetitions: 1 diff --git a/tests/configs/valid/ACL.yml b/tests/configs/valid/acl_temp.yml similarity index 100% rename from tests/configs/valid/ACL.yml rename to tests/configs/valid/acl_temp.yml diff --git a/tests/configs/valid/generative_modeling.yml b/tests/configs/valid/generative_modeling_temp.yml similarity index 100% rename from tests/configs/valid/generative_modeling.yml rename to tests/configs/valid/generative_modeling_temp.yml diff --git a/tests/configs/valid/mis.yml b/tests/configs/valid/mis.yml deleted file mode 100644 index b4b660a2..00000000 --- a/tests/configs/valid/mis.yml +++ /dev/null @@ -1,30 +0,0 @@ -application: - config: - filling_fraction: - - 0.2 - size: - - 5 - spacing: - - 0.4 - name: MIS - submodules: - - config: {} - name: NeutralAtom - submodules: - - config: - samples: - - 10 - name: NeutralAtomMIS - submodules: - - config: - SPAM: - - false - amplitude: - - false - dephasing: - - false - doppler: - - false - name: MockNeutralAtomDevice - submodules: [] -repetitions: 1 diff --git a/tests/configs/valid/MIS.yml b/tests/configs/valid/mis_temp.yml similarity index 100% rename from tests/configs/valid/MIS.yml rename to tests/configs/valid/mis_temp.yml diff --git a/tests/configs/valid/pvc.yml b/tests/configs/valid/pvc.yml deleted file mode 100644 index 85797f5e..00000000 --- a/tests/configs/valid/pvc.yml +++ /dev/null @@ -1,35 +0,0 @@ -application: - config: - seams: - - 2 - - 3 - name: PVC - submodules: - - config: - lagrange_factor: - - 1.0 - - 1.25 - name: QUBO - submodules: - - config: - number_of_reads: - - 250 - - 500 - name: Annealer - submodules: - - config: {} - name: Simulated Annealer - submodules: [] - - config: {} - name: GreedyClassicalPVC - submodules: - - config: {} - name: Local - submodules: [] - - config: {} - name: ReverseGreedyClassicalPVC - submodules: - - config: {} - name: Local - submodules: [] -repetitions: 2 diff --git a/tests/configs/valid/PVC.yml b/tests/configs/valid/pvc_temp.yml similarity index 100% rename from tests/configs/valid/PVC.yml rename to tests/configs/valid/pvc_temp.yml diff --git a/tests/configs/valid/sat.yml b/tests/configs/valid/sat.yml deleted file mode 100644 index c552aedf..00000000 --- a/tests/configs/valid/sat.yml +++ /dev/null @@ -1,24 +0,0 @@ -application: - config: - clvar_ratio_cons: - - 2 - clvar_ratio_test: - - 2 - max_tries: - - 100 - problem_set: - - 0 - variables: - - 10 - name: SAT - submodules: - - config: {} - name: Direct - submodules: - - config: {} - name: ClassicalSAT - submodules: - - config: {} - name: Local - submodules: [] -repetitions: 2 diff --git a/tests/configs/valid/SAT.yml b/tests/configs/valid/sat_temp.yml similarity index 100% rename from tests/configs/valid/SAT.yml rename to tests/configs/valid/sat_temp.yml diff --git a/tests/configs/valid/scp.yml b/tests/configs/valid/scp.yml deleted file mode 100644 index e1a586af..00000000 --- a/tests/configs/valid/scp.yml +++ /dev/null @@ -1,23 +0,0 @@ -application: - config: - model_select: - - Tiny - - Large - name: SCP - submodules: - - config: - penalty_weight: - - 2.0 - - 10.0 - - 100.0 - name: qubovertQUBO - submodules: - - config: - number_of_reads: - - 100 - name: Annealer - submodules: - - config: {} - name: Simulated Annealer - submodules: [] -repetitions: 2 diff --git a/tests/configs/valid/SCP.yml b/tests/configs/valid/scp_temp.yml similarity index 100% rename from tests/configs/valid/SCP.yml rename to tests/configs/valid/scp_temp.yml diff --git a/tests/configs/valid/tsp.yml b/tests/configs/valid/tsp.yml deleted file mode 100644 index e48f53f2..00000000 --- a/tests/configs/valid/tsp.yml +++ /dev/null @@ -1,25 +0,0 @@ -application: - config: - nodes: - - 6 - name: TSP - submodules: - - config: {} - name: GreedyClassicalTSP - submodules: - - config: {} - name: Local - submodules: [] - - config: {} - name: ReverseGreedyClassicalTSP - submodules: - - config: {} - name: Local - submodules: [] - - config: {} - name: RandomTSP - submodules: - - config: {} - name: Local - submodules: [] -repetitions: 2 diff --git a/tests/configs/valid/TSP.yml b/tests/configs/valid/tsp_temp.yml similarity index 100% rename from tests/configs/valid/TSP.yml rename to tests/configs/valid/tsp_temp.yml diff --git a/tests/modules/applications/optimization/ACL/mappings/test_ISING.py b/tests/modules/applications/optimization/ACL/mappings/test_ISING.py deleted file mode 100644 index 0d6a1d8b..00000000 --- a/tests/modules/applications/optimization/ACL/mappings/test_ISING.py +++ /dev/null @@ -1,85 +0,0 @@ -import unittest -import numpy as np -from qiskit_optimization import QuadraticProgram - -from src.modules.applications.optimization.acl.mappings.ising import Ising - - -class TestIsing(unittest.TestCase): - - @classmethod - def setUpClass(cls): - - cls.ising_instance = Ising() - # Mock problem dictionary for testing - cls.problem_dict = { - "variables": [ - {"name": "x_0", "cat": "Integer", "lowBound": 0, "upBound": 1}, - {"name": "x_1", "cat": "Integer", "lowBound": 0, "upBound": 10}, - ], - "objective": { - "coefficients": [ - {"name": "x_0", "value": 1}, - {"name": "x_1", "value": 2}, - ] - }, - "constraints": [ - { - "name": "c1", - "coefficients": [ - {"name": "x_0", "value": 1}, - {"name": "x_1", "value": 1}, - ], - "sense": -1, - "constant": -1, - } - ], - "parameters": {"sense": -1}, # Maximize - } - cls.config = {} - - def test_get_requirements(self): - requirements = self.ising_instance.get_requirements() - expected_requirements = [ - {"name": "numpy", "version": "1.26.4"}, - {"name": "more-itertools", "version": "10.5.0"}, - {"name": "qiskit-optimization", "version": "0.6.1"}, - ] - for req in expected_requirements: - self.assertIn(req, requirements) - - def test_get_parameter_options(self): - options = self.ising_instance.get_parameter_options() - self.assertEqual(options, {}, "Expected parameter options to be an empty dictionary.") - - def test_map_pulp_to_qiskit(self): - qp = self.ising_instance.map_pulp_to_qiskit(self.problem_dict) - self.assertIsInstance(qp, QuadraticProgram, "Expected a QuadraticProgram instance.") - self.assertEqual(len(qp.variables), 2, "Incorrect number of variables in QuadraticProgram.") - self.assertEqual(len(qp.linear_constraints), 1, "Incorrect number of constraints in QuadraticProgram.") - - def test_map(self): - ising_mapping, mapping_time = self.ising_instance.map(self.problem_dict, self.config) - self.assertIn("J", ising_mapping, "Expected 'J' in Ising mapping.") - self.assertIn("t", ising_mapping, "Expected 't' in Ising mapping.") - self.assertGreater(mapping_time, 0, "Mapping time should be positive.") - - def test_reverse_map(self): - mock_solution = {0: 1, 1: 0} # Example binary solution - - reverse_mapped_solution, reverse_mapping_time = self.ising_instance.reverse_map(mock_solution) - self.assertIsInstance(reverse_mapped_solution, dict, "Expected a dictionary as the reverse mapping result.") - self.assertIn("variables", reverse_mapped_solution, "Expected 'variables' in reverse mapping result.") - self.assertGreater(reverse_mapping_time, 0, "Reverse mapping time should be positive.") - - def test_convert_ising_to_qubo(self): - solution = np.array([-1, 1, -1, 1]) - expected_result = np.array([0, 1, 0, 1]) - converted_solution = self.ising_instance._convert_ising_to_qubo(solution) - self.assertTrue(np.array_equal(converted_solution, expected_result), "Conversion to QUBO failed.") - - def test_get_default_submodule(self): - submodule = self.ising_instance.get_default_submodule("QAOA") - self.assertIsNotNone(submodule, "Expected 'QAOA' submodule to be returned.") - with self.assertRaises(NotImplementedError): - self.ising_instance.get_default_submodule("InvalidSubmodule") diff --git a/tests/modules/applications/optimization/ACL/mappings/test_QUBO.py b/tests/modules/applications/optimization/ACL/mappings/test_QUBO.py deleted file mode 100644 index 82a701fa..00000000 --- a/tests/modules/applications/optimization/ACL/mappings/test_QUBO.py +++ /dev/null @@ -1,100 +0,0 @@ -import unittest -import numpy as np -from qiskit_optimization import QuadraticProgram - -from src.modules.applications.optimization.acl.mappings.qubo import Qubo - - -class TestQubo(unittest.TestCase): - - @classmethod - def setUpClass(cls): - cls.qubo_instance = Qubo() - # Mock problem dictionary for testing - cls.problem_dict = { - "variables": [ - {"name": "x_0", "cat": "Integer", "lowBound": 0, "upBound": 1}, - {"name": "x_1", "cat": "Integer", "lowBound": 0, "upBound": 1}, - ], - "objective": { - "coefficients": [ - {"name": "x_0", "value": 3}, - {"name": "x_1", "value": 4}, - ] - }, - "constraints": [ - { - "name": "c1", - "coefficients": [ - {"name": "x_0", "value": 1}, - {"name": "x_1", "value": 1}, - ], - "sense": -1, - "constant": -1, - } - ], - "parameters": {"sense": -1}, # Maximize - } - cls.config = {} - - def test_get_requirements(self): - requirements = self.qubo_instance.get_requirements() - expected_requirements = [ - {"name": "numpy", "version": "1.26.4"}, - {"name": "qiskit-optimization", "version": "0.6.1"}, - ] - for req in expected_requirements: - self.assertIn(req, requirements) - - def test_get_parameter_options(self): - options = self.qubo_instance.get_parameter_options() - self.assertEqual(options, {}, "Expected parameter options to be an empty dictionary.") - - def test_map_pulp_to_qiskit(self): - qp = self.qubo_instance.map_pulp_to_qiskit(self.problem_dict) - self.assertIsInstance(qp, QuadraticProgram, "Expected a QuadraticProgram instance.") - self.assertEqual(len(qp.variables), 2, "Incorrect number of variables in QuadraticProgram.") - self.assertEqual(len(qp.linear_constraints), 1, "Incorrect number of constraints in QuadraticProgram.") - - def test_convert_string_to_arguments(self): - qubo_string = "maximize 3*x_0 + 4*x_1 - 2*x_0*x_1" - arguments = self.qubo_instance.convert_string_to_arguments(qubo_string) - expected_arguments = [ - [3.0, "x_0"], - [4.0, "x_1"], - [-2.0, "x_0", "x_1"] - ] - self.assertEqual(arguments, expected_arguments, "String to arguments conversion failed.") - - def test_construct_qubo(self): - arguments = [ - [3.0, "x_0"], - [4.0, "x_1"], - [-2.0, "x_0", "x_1"] - ] - variables = ["x_0", "x_1"] - qubo_matrix = self.qubo_instance.construct_qubo(arguments, variables) - expected_matrix = np.array([ - [-3.0, 0.0], # Diagonal terms and no interaction (negative due to minimization problem) - [2.0, -4.0] # Off-diagonal term and diagonal term for x_1 - ]) - np.testing.assert_array_almost_equal(qubo_matrix, expected_matrix, err_msg="QUBO matrix construction failed.") - - def test_map(self): - qubo_mapping, mapping_time = self.qubo_instance.map(self.problem_dict, self.config) - self.assertIn("Q", qubo_mapping, "Expected 'Q' in QUBO mapping.") - self.assertIsInstance(qubo_mapping["Q"], np.ndarray, "Expected 'Q' to be a numpy array.") - self.assertGreater(mapping_time, 0, "Mapping time should be positive.") - - def test_reverse_map(self): - mock_solution = {0: 1, 1: 0} # Example binary solution - reverse_mapped_solution, reverse_mapping_time = self.qubo_instance.reverse_map(mock_solution) - self.assertIsInstance(reverse_mapped_solution, dict, "Expected a dictionary as the reverse mapping result.") - self.assertIn("variables", reverse_mapped_solution, "Expected 'variables' in reverse mapping result.") - self.assertGreater(reverse_mapping_time, 0, "Reverse mapping time should be positive.") - - def test_get_default_submodule(self): - submodule = self.qubo_instance.get_default_submodule("Annealer") - self.assertIsNotNone(submodule, "Expected 'Annealer' submodule to be returned.") - with self.assertRaises(NotImplementedError): - self.qubo_instance.get_default_submodule("InvalidSubmodule") diff --git a/tests/modules/applications/optimization/ACL/test_ACL.py b/tests/modules/applications/optimization/ACL/test_ACL.py deleted file mode 100644 index 6e2757d6..00000000 --- a/tests/modules/applications/optimization/ACL/test_ACL.py +++ /dev/null @@ -1,137 +0,0 @@ -import unittest -import os -import pandas as pd -from tempfile import TemporaryDirectory -from src.modules.applications.optimization.acl.acl import ACL - - -class TestACL(unittest.TestCase): - - @classmethod - def setUpClass(cls): - cls.acl_instance = ACL() - cls.config_tiny = {"model_select": "Tiny"} - cls.config_small = {"model_select": "Small"} - cls.config_full = {"model_select": "Full"} - - # Mock vehicle data - cls.mock_data = pd.DataFrame({ - "Type": ["G20", "G07", "G20"], - "Class": [1, 2, 1], - "Length": [400, 500, 400], - "Height": [140, 160, 140], - "Weight": [15, 20, 15], - }) - - # Mock vehicles list - cls.vehicles = ["G20", "G07", "G20"] - - def test_get_requirements(self): - requirements = self.acl_instance.get_requirements() - expected_requirements = [ - {"name": "pulp", "version": "2.9.0"}, - {"name": "pandas", "version": "2.2.3"}, - {"name": "numpy", "version": "1.26.4"}, - {"name": "openpyxl", "version": "3.1.5"}, - ] - for req in expected_requirements: - self.assertIn(req, requirements) - - def test_get_default_submodule(self): - submodule = self.acl_instance.get_default_submodule("MIPsolverACL") - self.assertIsNotNone(submodule, "Expected 'MIPsolverACL' submodule to be returned.") - with self.assertRaises(NotImplementedError): - self.acl_instance.get_default_submodule("InvalidSubmodule") - - def test_get_parameter_options(self): - options = self.acl_instance.get_parameter_options() - self.assertIn("model_select", options) - self.assertIn("values", options["model_select"]) - self.assertIn("description", options["model_select"]) - - def test_intersectset(self): - set1 = [1, 2, 3] - set2 = [2, 3, 4] - expected_result = [2, 3] - result = self.acl_instance.intersectset(set1, set2) - self.assertEqual(result, expected_result, "Intersection result is incorrect.") - - def test_diffset(self): - set1 = [1, 2, 3] - set2 = [2, 3, 4] - expected_result = [1] - result = self.acl_instance.diffset(set1, set2) - self.assertEqual(result, expected_result, "Difference result is incorrect.") - - def test_generate_problem(self): - # Test Tiny configuration - problem_tiny = self.acl_instance.generate_problem(self.config_tiny) - self.assertIsInstance(problem_tiny, dict, "Expected Tiny problem to be a dictionary.") - - # Test Small configuration - problem_small = self.acl_instance.generate_problem(self.config_small) - self.assertIsInstance(problem_small, dict, "Expected Small problem to be a dictionary.") - - # Test Full configuration - problem_full = self.acl_instance.generate_problem(self.config_full) - self.assertIsInstance(problem_full, dict, "Expected Full problem to be a dictionary.") - - def test_get_solution_quality_unit(self): - unit = self.acl_instance.get_solution_quality_unit() - self.assertEqual(unit, "Number of loaded vehicles", "Solution quality unit mismatch.") - - def test_get_vehicle_params(self): - class_list, length_list, height_list, weight_list = self.acl_instance._get_vehicle_params( - self.mock_data, self.vehicles - ) - self.assertEqual(class_list, [1, 2, 1], "Class list extraction failed.") - self.assertEqual(length_list, [400, 500, 400], "Length list extraction failed.") - self.assertEqual(height_list, [140, 160, 140], "Height list extraction failed.") - self.assertEqual(weight_list, [15, 20, 15], "Weight list extraction failed.") - - def test_generate_full_model(self): - self.acl_instance._generate_full_model(self.mock_data, self.vehicles) - self.assertIsNotNone(self.acl_instance.application, "Full model generation failed.") - self.assertTrue("objective" in self.acl_instance.application.to_dict(), "Full model lacks an objective.") - - def test_generate_small_model(self): - self.acl_instance._generate_small_model(self.mock_data, self.vehicles) - self.assertIsNotNone(self.acl_instance.application, "Small model generation failed.") - self.assertTrue("objective" in self.acl_instance.application.to_dict(), "Small model lacks an objective.") - - def test_generate_tiny_model(self): - self.acl_instance._generate_tiny_model(self.mock_data, self.vehicles) - self.assertIsNotNone(self.acl_instance.application, "Tiny model generation failed.") - self.assertTrue("objective" in self.acl_instance.application.to_dict(), "Tiny model lacks an objective.") - - def test_validate(self): - # Create a mock solution - mock_solution = {"status": "Optimal"} - is_valid, validation_time = self.acl_instance.validate(mock_solution) - self.assertTrue(is_valid, "Expected solution to be valid.") - - invalid_solution = {"status": "Infeasible"} - is_valid, _ = self.acl_instance.validate(invalid_solution) - self.assertFalse(is_valid, "Expected solution to be invalid.") - - def test_evaluate(self): - # Create a mock solution with objective value and variable assignments - mock_solution = { - "obj_value": 5, - "variables": {"x_0_1": 1, "x_1_2": 1, "x_2_3": 0} - } - obj_value, eval_time = self.acl_instance.evaluate(mock_solution) - self.assertEqual(obj_value, 5, "Objective value is incorrect.") - self.assertGreater(eval_time, 0, "Evaluation time should be positive.") - - def test_save(self): - with TemporaryDirectory() as temp_dir: - # Generate and save the problem instance - self.acl_instance.generate_problem(self.config_tiny) - self.acl_instance.save(temp_dir, iter_count=1) - - # Check that the file exists and is not empty - file_path = os.path.join(temp_dir, "ACL_instance.json") - self.assertTrue(os.path.isfile(file_path), "Problem instance file not saved.") - self.assertTrue(file_path.endswith(".json"), "File extension mismatch.") - self.assertGreater(os.path.getsize(file_path), 0, "Problem instance file is empty.") diff --git a/tests/modules/applications/optimization/MIS/mappings/MIS_test_graph.pkl b/tests/modules/applications/optimization/MIS/mappings/MIS_test_graph.pkl deleted file mode 100644 index 3032b16f6538e8d95cfb832775c8400032c4726a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 200 zcmZo*nYxz&0(!*qQcKG7i?S>9l5-M^i&KmB(u)!cGN$yfx`XM-Q#2c=)K1apVTFj- z0%hX!@>5blg5C^3E^|SC@f2?cZ-%xh#ommyQ!==`86g}dFo(gL5hTM5l>u=|dsyNV uQ?g)2Xn?eN167xT7(i1qL_x|+K|&y&7=#BD0; None: + """ + Test a couple of valid QUARK configs + :return: + :rtype: None + """ + parser = argparse.ArgumentParser() + create_benchmark_parser(parser) + + for filename in glob.glob("tests/configs/valid/**.yml"): + with self.subTest(msg=f"Running Benchmark Test with valid config {filename}", filename=filename): + args = parser.parse_args(["--config", filename, "--failfast"]) + self.assertEqual(handle_benchmark_run(args), None) + + def test_handle_invalid_benchmark_run(self) -> None: + """ + Test a couple of invalid QUARK configs + :return: + :rtype: None + """ + parser = argparse.ArgumentParser() + create_benchmark_parser(parser) + + for filename in glob.glob("tests/configs/invalid/**.yml"): + with self.subTest(msg=f"Running Benchmark Test with invalid config {filename}", filename=filename): + args = parser.parse_args(["--config", filename, "--failfast"]) + with self.assertRaises(Exception): + handle_benchmark_run(args) From 32c60b93dc92a2257001f134e13438b8df284bd0 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Fri, 7 Feb 2025 08:41:32 +0000 Subject: [PATCH 06/23] Apply autopep8 formatting --- .../optimization/mis_temp/mappings/test_neutral_atom.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/modules/applications/optimization/mis_temp/mappings/test_neutral_atom.py b/tests/modules/applications/optimization/mis_temp/mappings/test_neutral_atom.py index 85057df5..2dab21c5 100644 --- a/tests/modules/applications/optimization/mis_temp/mappings/test_neutral_atom.py +++ b/tests/modules/applications/optimization/mis_temp/mappings/test_neutral_atom.py @@ -3,6 +3,7 @@ from src.modules.applications.optimization.mis.mappings.neutral_atom import NeutralAtom + class TestNeutralAtom(unittest.TestCase): @classmethod From b6c8f37dba5e25c8c07ecf599ff074336419909d Mon Sep 17 00:00:00 2001 From: GreshmaShaji Date: Fri, 7 Feb 2025 10:27:26 +0100 Subject: [PATCH 07/23] Final rename to lowercase to resolve Git case-sensitivity issue --- src/{installer_temp.py => installer.py} | 0 src/{metrics_temp.py => metrics.py} | 0 .../{application_temp.py => application.py} | 0 .../{mapping_temp.py => mapping.py} | 0 .../{optimization_temp.py => optimization.py} | 0 .../qml/{circuit_temp.py => circuit.py} | 0 .../data/{mg_2d_temp.npy => mg_2d.npy} | 0 .../data/{o_2d_temp.npy => o_2d.npy} | 0 .../{stocks_2d_temp.npy => stocks_2d.npy} | 0 .../data/{x_2d_temp.npy => x_2d.npy} | 0 .../{inference_temp.py => inference.py} | 0 .../training/{qcbm_temp.py => qcbm.py} | 0 .../training/{qgan_temp.py => qgan.py} | 0 .../transformations/{pit_temp.py => pit.py} | 0 ...ansformation_temp.py => transformation.py} | 0 .../qml/{model_temp.py => model.py} | 0 .../applications/qml/{qml_temp.py => qml.py} | 0 .../qml/{training_temp.py => training.py} | 0 src/modules/{core_temp.py => core.py} | 0 .../braket/{braket_temp.py => braket.py} | 0 .../devices/braket/{ionq_temp.py => ionq.py} | 0 .../devices/braket/{oqc_temp.py => oqc.py} | 0 .../braket/{rigetti_temp.py => rigetti.py} | 0 .../devices/braket/{sv1_temp.py => sv1.py} | 0 .../devices/braket/{tn1_temp.py => tn1.py} | 0 .../devices/{device_temp.py => device.py} | 0 .../devices/{local_temp.py => local.py} | 0 .../pulser/{pulser_temp.py => pulser.py} | 0 .../solvers/{annealer_temp.py => annealer.py} | 0 src/modules/solvers/{qaoa_temp.py => qaoa.py} | 0 .../solvers/{solver_temp.py => solver.py} | 0 src/{plotter_temp.py => plotter.py} | 0 .../{application_temp.py => application.py} | 0 .../{device_temp.py => device.py} | 0 .../{mapping_temp.py => mapping.py} | 0 .../{solver_temp.py => solver.py} | 0 .../configs/invalid/{tsp_temp.yml => tsp.yml} | 0 tests/configs/valid/{acl_temp.yml => acl.yml} | 0 ...eling_temp.yml => generative_modeling.yml} | 0 tests/configs/valid/{mis_temp.yml => mis.yml} | 0 tests/configs/valid/{pvc_temp.yml => pvc.yml} | 0 tests/configs/valid/{sat_temp.yml => sat.yml} | 0 tests/configs/valid/{scp_temp.yml => scp.yml} | 0 tests/configs/valid/{tsp_temp.yml => tsp.yml} | 0 .../{acl_temp => acl}/__init__.py | 0 .../{acl_temp => acl}/mappings/__init__.py | 0 .../{acl_temp => acl}/mappings/test_ising.py | 0 .../{acl_temp => acl}/mappings/test_qubo.py | 0 .../{acl_temp => acl}/test_acl.py | 0 .../{mis_temp => mis}/__init__.py | 0 .../{mis_temp => mis}/mappings/__init__.py | 0 .../mappings/mis_test_graph.pkl | Bin .../mappings/test_neutral_atom.py | 0 .../{mis_temp => mis}/test_mis.py | 0 .../{pvc_temp => pvc}/__init__.py | 0 .../{pvc_temp => pvc}/mappings/__init__.py | 0 .../mappings/pvc_graph_1_seam.gpickle | 0 .../{pvc_temp => pvc}/mappings/test_ising.py | 0 .../{pvc_temp => pvc}/mappings/test_qubo.py | 0 .../{pvc_temp => pvc}/test_pvc.py | 0 .../{sat_temp => sat}/__init__.py | 0 .../{sat_temp => sat}/mappings/__init__.py | 0 .../mappings/test_choiIsing.py | 0 .../mappings/test_choiqubo.py | 0 .../mappings/test_dinneenising.py | 0 .../mappings/test_dinneenqubo.py | 0 .../{sat_temp => sat}/mappings/test_direct.py | 0 .../mappings/test_qubovertqubo.py | 0 .../{sat_temp => sat}/test_sat.py | 0 .../{scp_temp => scp}/__init__.py | 0 .../{scp_temp => scp}/mappings/__init__.py | 0 .../mappings/test_qubovertqubo.py | 0 .../{scp_temp => scp}/test_scp.py | 0 .../{tsp_temp => tsp}/__init__.py | 0 .../{tsp_temp => tsp}/mappings/__init__.py | 0 .../{tsp_temp => tsp}/mappings/test_ising.py | 0 .../{tsp_temp => tsp}/mappings/test_qubo.py | 0 .../{tsp_temp => tsp}/test_tsp.py | 0 .../training/test_inference.py | 73 ++++++++ .../generative_modeling/training/test_qcbm.py | 130 ++++++++++++++ .../generative_modeling/training/test_qgan.py | 161 ++++++++++++++++++ .../{test_MinMax.py => test_min_max.py} | 0 .../transformation/test_pit.py | 120 +++++++++++++ tests/test_main.py | 51 ------ tests/{test_Main.py => test_main_temp.py} | 0 85 files changed, 484 insertions(+), 51 deletions(-) rename src/{installer_temp.py => installer.py} (100%) rename src/{metrics_temp.py => metrics.py} (100%) rename src/modules/applications/{application_temp.py => application.py} (100%) rename src/modules/applications/{mapping_temp.py => mapping.py} (100%) rename src/modules/applications/optimization/{optimization_temp.py => optimization.py} (100%) rename src/modules/applications/qml/{circuit_temp.py => circuit.py} (100%) rename src/modules/applications/qml/generative_modeling/data/{mg_2d_temp.npy => mg_2d.npy} (100%) rename src/modules/applications/qml/generative_modeling/data/{o_2d_temp.npy => o_2d.npy} (100%) rename src/modules/applications/qml/generative_modeling/data/{stocks_2d_temp.npy => stocks_2d.npy} (100%) rename src/modules/applications/qml/generative_modeling/data/{x_2d_temp.npy => x_2d.npy} (100%) rename src/modules/applications/qml/generative_modeling/training/{inference_temp.py => inference.py} (100%) rename src/modules/applications/qml/generative_modeling/training/{qcbm_temp.py => qcbm.py} (100%) rename src/modules/applications/qml/generative_modeling/training/{qgan_temp.py => qgan.py} (100%) rename src/modules/applications/qml/generative_modeling/transformations/{pit_temp.py => pit.py} (100%) rename src/modules/applications/qml/generative_modeling/transformations/{transformation_temp.py => transformation.py} (100%) rename src/modules/applications/qml/{model_temp.py => model.py} (100%) rename src/modules/applications/qml/{qml_temp.py => qml.py} (100%) rename src/modules/applications/qml/{training_temp.py => training.py} (100%) rename src/modules/{core_temp.py => core.py} (100%) rename src/modules/devices/braket/{braket_temp.py => braket.py} (100%) rename src/modules/devices/braket/{ionq_temp.py => ionq.py} (100%) rename src/modules/devices/braket/{oqc_temp.py => oqc.py} (100%) rename src/modules/devices/braket/{rigetti_temp.py => rigetti.py} (100%) rename src/modules/devices/braket/{sv1_temp.py => sv1.py} (100%) rename src/modules/devices/braket/{tn1_temp.py => tn1.py} (100%) rename src/modules/devices/{device_temp.py => device.py} (100%) rename src/modules/devices/{local_temp.py => local.py} (100%) rename src/modules/devices/pulser/{pulser_temp.py => pulser.py} (100%) rename src/modules/solvers/{annealer_temp.py => annealer.py} (100%) rename src/modules/solvers/{qaoa_temp.py => qaoa.py} (100%) rename src/modules/solvers/{solver_temp.py => solver.py} (100%) rename src/{plotter_temp.py => plotter.py} (100%) rename src/quark2_adapter/legacy_classes/{application_temp.py => application.py} (100%) rename src/quark2_adapter/legacy_classes/{device_temp.py => device.py} (100%) rename src/quark2_adapter/legacy_classes/{mapping_temp.py => mapping.py} (100%) rename src/quark2_adapter/legacy_classes/{solver_temp.py => solver.py} (100%) rename tests/configs/invalid/{tsp_temp.yml => tsp.yml} (100%) rename tests/configs/valid/{acl_temp.yml => acl.yml} (100%) rename tests/configs/valid/{generative_modeling_temp.yml => generative_modeling.yml} (100%) rename tests/configs/valid/{mis_temp.yml => mis.yml} (100%) rename tests/configs/valid/{pvc_temp.yml => pvc.yml} (100%) rename tests/configs/valid/{sat_temp.yml => sat.yml} (100%) rename tests/configs/valid/{scp_temp.yml => scp.yml} (100%) rename tests/configs/valid/{tsp_temp.yml => tsp.yml} (100%) rename tests/modules/applications/optimization/{acl_temp => acl}/__init__.py (100%) rename tests/modules/applications/optimization/{acl_temp => acl}/mappings/__init__.py (100%) rename tests/modules/applications/optimization/{acl_temp => acl}/mappings/test_ising.py (100%) rename tests/modules/applications/optimization/{acl_temp => acl}/mappings/test_qubo.py (100%) rename tests/modules/applications/optimization/{acl_temp => acl}/test_acl.py (100%) rename tests/modules/applications/optimization/{mis_temp => mis}/__init__.py (100%) rename tests/modules/applications/optimization/{mis_temp => mis}/mappings/__init__.py (100%) rename tests/modules/applications/optimization/{mis_temp => mis}/mappings/mis_test_graph.pkl (100%) rename tests/modules/applications/optimization/{mis_temp => mis}/mappings/test_neutral_atom.py (100%) rename tests/modules/applications/optimization/{mis_temp => mis}/test_mis.py (100%) rename tests/modules/applications/optimization/{pvc_temp => pvc}/__init__.py (100%) rename tests/modules/applications/optimization/{pvc_temp => pvc}/mappings/__init__.py (100%) rename tests/modules/applications/optimization/{pvc_temp => pvc}/mappings/pvc_graph_1_seam.gpickle (100%) rename tests/modules/applications/optimization/{pvc_temp => pvc}/mappings/test_ising.py (100%) rename tests/modules/applications/optimization/{pvc_temp => pvc}/mappings/test_qubo.py (100%) rename tests/modules/applications/optimization/{pvc_temp => pvc}/test_pvc.py (100%) rename tests/modules/applications/optimization/{sat_temp => sat}/__init__.py (100%) rename tests/modules/applications/optimization/{sat_temp => sat}/mappings/__init__.py (100%) rename tests/modules/applications/optimization/{sat_temp => sat}/mappings/test_choiIsing.py (100%) rename tests/modules/applications/optimization/{sat_temp => sat}/mappings/test_choiqubo.py (100%) rename tests/modules/applications/optimization/{sat_temp => sat}/mappings/test_dinneenising.py (100%) rename tests/modules/applications/optimization/{sat_temp => sat}/mappings/test_dinneenqubo.py (100%) rename tests/modules/applications/optimization/{sat_temp => sat}/mappings/test_direct.py (100%) rename tests/modules/applications/optimization/{sat_temp => sat}/mappings/test_qubovertqubo.py (100%) rename tests/modules/applications/optimization/{sat_temp => sat}/test_sat.py (100%) rename tests/modules/applications/optimization/{scp_temp => scp}/__init__.py (100%) rename tests/modules/applications/optimization/{scp_temp => scp}/mappings/__init__.py (100%) rename tests/modules/applications/optimization/{scp_temp => scp}/mappings/test_qubovertqubo.py (100%) rename tests/modules/applications/optimization/{scp_temp => scp}/test_scp.py (100%) rename tests/modules/applications/optimization/{tsp_temp => tsp}/__init__.py (100%) rename tests/modules/applications/optimization/{tsp_temp => tsp}/mappings/__init__.py (100%) rename tests/modules/applications/optimization/{tsp_temp => tsp}/mappings/test_ising.py (100%) rename tests/modules/applications/optimization/{tsp_temp => tsp}/mappings/test_qubo.py (100%) rename tests/modules/applications/optimization/{tsp_temp => tsp}/test_tsp.py (100%) create mode 100644 tests/modules/applications/qml/generative_modeling/training/test_inference.py create mode 100644 tests/modules/applications/qml/generative_modeling/training/test_qcbm.py create mode 100644 tests/modules/applications/qml/generative_modeling/training/test_qgan.py rename tests/modules/applications/qml/generative_modeling/transformation/{test_MinMax.py => test_min_max.py} (100%) create mode 100644 tests/modules/applications/qml/generative_modeling/transformation/test_pit.py delete mode 100644 tests/test_main.py rename tests/{test_Main.py => test_main_temp.py} (100%) diff --git a/src/installer_temp.py b/src/installer.py similarity index 100% rename from src/installer_temp.py rename to src/installer.py diff --git a/src/metrics_temp.py b/src/metrics.py similarity index 100% rename from src/metrics_temp.py rename to src/metrics.py diff --git a/src/modules/applications/application_temp.py b/src/modules/applications/application.py similarity index 100% rename from src/modules/applications/application_temp.py rename to src/modules/applications/application.py diff --git a/src/modules/applications/mapping_temp.py b/src/modules/applications/mapping.py similarity index 100% rename from src/modules/applications/mapping_temp.py rename to src/modules/applications/mapping.py diff --git a/src/modules/applications/optimization/optimization_temp.py b/src/modules/applications/optimization/optimization.py similarity index 100% rename from src/modules/applications/optimization/optimization_temp.py rename to src/modules/applications/optimization/optimization.py diff --git a/src/modules/applications/qml/circuit_temp.py b/src/modules/applications/qml/circuit.py similarity index 100% rename from src/modules/applications/qml/circuit_temp.py rename to src/modules/applications/qml/circuit.py diff --git a/src/modules/applications/qml/generative_modeling/data/mg_2d_temp.npy b/src/modules/applications/qml/generative_modeling/data/mg_2d.npy similarity index 100% rename from src/modules/applications/qml/generative_modeling/data/mg_2d_temp.npy rename to src/modules/applications/qml/generative_modeling/data/mg_2d.npy diff --git a/src/modules/applications/qml/generative_modeling/data/o_2d_temp.npy b/src/modules/applications/qml/generative_modeling/data/o_2d.npy similarity index 100% rename from src/modules/applications/qml/generative_modeling/data/o_2d_temp.npy rename to src/modules/applications/qml/generative_modeling/data/o_2d.npy diff --git a/src/modules/applications/qml/generative_modeling/data/stocks_2d_temp.npy b/src/modules/applications/qml/generative_modeling/data/stocks_2d.npy similarity index 100% rename from src/modules/applications/qml/generative_modeling/data/stocks_2d_temp.npy rename to src/modules/applications/qml/generative_modeling/data/stocks_2d.npy diff --git a/src/modules/applications/qml/generative_modeling/data/x_2d_temp.npy b/src/modules/applications/qml/generative_modeling/data/x_2d.npy similarity index 100% rename from src/modules/applications/qml/generative_modeling/data/x_2d_temp.npy rename to src/modules/applications/qml/generative_modeling/data/x_2d.npy diff --git a/src/modules/applications/qml/generative_modeling/training/inference_temp.py b/src/modules/applications/qml/generative_modeling/training/inference.py similarity index 100% rename from src/modules/applications/qml/generative_modeling/training/inference_temp.py rename to src/modules/applications/qml/generative_modeling/training/inference.py diff --git a/src/modules/applications/qml/generative_modeling/training/qcbm_temp.py b/src/modules/applications/qml/generative_modeling/training/qcbm.py similarity index 100% rename from src/modules/applications/qml/generative_modeling/training/qcbm_temp.py rename to src/modules/applications/qml/generative_modeling/training/qcbm.py diff --git a/src/modules/applications/qml/generative_modeling/training/qgan_temp.py b/src/modules/applications/qml/generative_modeling/training/qgan.py similarity index 100% rename from src/modules/applications/qml/generative_modeling/training/qgan_temp.py rename to src/modules/applications/qml/generative_modeling/training/qgan.py diff --git a/src/modules/applications/qml/generative_modeling/transformations/pit_temp.py b/src/modules/applications/qml/generative_modeling/transformations/pit.py similarity index 100% rename from src/modules/applications/qml/generative_modeling/transformations/pit_temp.py rename to src/modules/applications/qml/generative_modeling/transformations/pit.py diff --git a/src/modules/applications/qml/generative_modeling/transformations/transformation_temp.py b/src/modules/applications/qml/generative_modeling/transformations/transformation.py similarity index 100% rename from src/modules/applications/qml/generative_modeling/transformations/transformation_temp.py rename to src/modules/applications/qml/generative_modeling/transformations/transformation.py diff --git a/src/modules/applications/qml/model_temp.py b/src/modules/applications/qml/model.py similarity index 100% rename from src/modules/applications/qml/model_temp.py rename to src/modules/applications/qml/model.py diff --git a/src/modules/applications/qml/qml_temp.py b/src/modules/applications/qml/qml.py similarity index 100% rename from src/modules/applications/qml/qml_temp.py rename to src/modules/applications/qml/qml.py diff --git a/src/modules/applications/qml/training_temp.py b/src/modules/applications/qml/training.py similarity index 100% rename from src/modules/applications/qml/training_temp.py rename to src/modules/applications/qml/training.py diff --git a/src/modules/core_temp.py b/src/modules/core.py similarity index 100% rename from src/modules/core_temp.py rename to src/modules/core.py diff --git a/src/modules/devices/braket/braket_temp.py b/src/modules/devices/braket/braket.py similarity index 100% rename from src/modules/devices/braket/braket_temp.py rename to src/modules/devices/braket/braket.py diff --git a/src/modules/devices/braket/ionq_temp.py b/src/modules/devices/braket/ionq.py similarity index 100% rename from src/modules/devices/braket/ionq_temp.py rename to src/modules/devices/braket/ionq.py diff --git a/src/modules/devices/braket/oqc_temp.py b/src/modules/devices/braket/oqc.py similarity index 100% rename from src/modules/devices/braket/oqc_temp.py rename to src/modules/devices/braket/oqc.py diff --git a/src/modules/devices/braket/rigetti_temp.py b/src/modules/devices/braket/rigetti.py similarity index 100% rename from src/modules/devices/braket/rigetti_temp.py rename to src/modules/devices/braket/rigetti.py diff --git a/src/modules/devices/braket/sv1_temp.py b/src/modules/devices/braket/sv1.py similarity index 100% rename from src/modules/devices/braket/sv1_temp.py rename to src/modules/devices/braket/sv1.py diff --git a/src/modules/devices/braket/tn1_temp.py b/src/modules/devices/braket/tn1.py similarity index 100% rename from src/modules/devices/braket/tn1_temp.py rename to src/modules/devices/braket/tn1.py diff --git a/src/modules/devices/device_temp.py b/src/modules/devices/device.py similarity index 100% rename from src/modules/devices/device_temp.py rename to src/modules/devices/device.py diff --git a/src/modules/devices/local_temp.py b/src/modules/devices/local.py similarity index 100% rename from src/modules/devices/local_temp.py rename to src/modules/devices/local.py diff --git a/src/modules/devices/pulser/pulser_temp.py b/src/modules/devices/pulser/pulser.py similarity index 100% rename from src/modules/devices/pulser/pulser_temp.py rename to src/modules/devices/pulser/pulser.py diff --git a/src/modules/solvers/annealer_temp.py b/src/modules/solvers/annealer.py similarity index 100% rename from src/modules/solvers/annealer_temp.py rename to src/modules/solvers/annealer.py diff --git a/src/modules/solvers/qaoa_temp.py b/src/modules/solvers/qaoa.py similarity index 100% rename from src/modules/solvers/qaoa_temp.py rename to src/modules/solvers/qaoa.py diff --git a/src/modules/solvers/solver_temp.py b/src/modules/solvers/solver.py similarity index 100% rename from src/modules/solvers/solver_temp.py rename to src/modules/solvers/solver.py diff --git a/src/plotter_temp.py b/src/plotter.py similarity index 100% rename from src/plotter_temp.py rename to src/plotter.py diff --git a/src/quark2_adapter/legacy_classes/application_temp.py b/src/quark2_adapter/legacy_classes/application.py similarity index 100% rename from src/quark2_adapter/legacy_classes/application_temp.py rename to src/quark2_adapter/legacy_classes/application.py diff --git a/src/quark2_adapter/legacy_classes/device_temp.py b/src/quark2_adapter/legacy_classes/device.py similarity index 100% rename from src/quark2_adapter/legacy_classes/device_temp.py rename to src/quark2_adapter/legacy_classes/device.py diff --git a/src/quark2_adapter/legacy_classes/mapping_temp.py b/src/quark2_adapter/legacy_classes/mapping.py similarity index 100% rename from src/quark2_adapter/legacy_classes/mapping_temp.py rename to src/quark2_adapter/legacy_classes/mapping.py diff --git a/src/quark2_adapter/legacy_classes/solver_temp.py b/src/quark2_adapter/legacy_classes/solver.py similarity index 100% rename from src/quark2_adapter/legacy_classes/solver_temp.py rename to src/quark2_adapter/legacy_classes/solver.py diff --git a/tests/configs/invalid/tsp_temp.yml b/tests/configs/invalid/tsp.yml similarity index 100% rename from tests/configs/invalid/tsp_temp.yml rename to tests/configs/invalid/tsp.yml diff --git a/tests/configs/valid/acl_temp.yml b/tests/configs/valid/acl.yml similarity index 100% rename from tests/configs/valid/acl_temp.yml rename to tests/configs/valid/acl.yml diff --git a/tests/configs/valid/generative_modeling_temp.yml b/tests/configs/valid/generative_modeling.yml similarity index 100% rename from tests/configs/valid/generative_modeling_temp.yml rename to tests/configs/valid/generative_modeling.yml diff --git a/tests/configs/valid/mis_temp.yml b/tests/configs/valid/mis.yml similarity index 100% rename from tests/configs/valid/mis_temp.yml rename to tests/configs/valid/mis.yml diff --git a/tests/configs/valid/pvc_temp.yml b/tests/configs/valid/pvc.yml similarity index 100% rename from tests/configs/valid/pvc_temp.yml rename to tests/configs/valid/pvc.yml diff --git a/tests/configs/valid/sat_temp.yml b/tests/configs/valid/sat.yml similarity index 100% rename from tests/configs/valid/sat_temp.yml rename to tests/configs/valid/sat.yml diff --git a/tests/configs/valid/scp_temp.yml b/tests/configs/valid/scp.yml similarity index 100% rename from tests/configs/valid/scp_temp.yml rename to tests/configs/valid/scp.yml diff --git a/tests/configs/valid/tsp_temp.yml b/tests/configs/valid/tsp.yml similarity index 100% rename from tests/configs/valid/tsp_temp.yml rename to tests/configs/valid/tsp.yml diff --git a/tests/modules/applications/optimization/acl_temp/__init__.py b/tests/modules/applications/optimization/acl/__init__.py similarity index 100% rename from tests/modules/applications/optimization/acl_temp/__init__.py rename to tests/modules/applications/optimization/acl/__init__.py diff --git a/tests/modules/applications/optimization/acl_temp/mappings/__init__.py b/tests/modules/applications/optimization/acl/mappings/__init__.py similarity index 100% rename from tests/modules/applications/optimization/acl_temp/mappings/__init__.py rename to tests/modules/applications/optimization/acl/mappings/__init__.py diff --git a/tests/modules/applications/optimization/acl_temp/mappings/test_ising.py b/tests/modules/applications/optimization/acl/mappings/test_ising.py similarity index 100% rename from tests/modules/applications/optimization/acl_temp/mappings/test_ising.py rename to tests/modules/applications/optimization/acl/mappings/test_ising.py diff --git a/tests/modules/applications/optimization/acl_temp/mappings/test_qubo.py b/tests/modules/applications/optimization/acl/mappings/test_qubo.py similarity index 100% rename from tests/modules/applications/optimization/acl_temp/mappings/test_qubo.py rename to tests/modules/applications/optimization/acl/mappings/test_qubo.py diff --git a/tests/modules/applications/optimization/acl_temp/test_acl.py b/tests/modules/applications/optimization/acl/test_acl.py similarity index 100% rename from tests/modules/applications/optimization/acl_temp/test_acl.py rename to tests/modules/applications/optimization/acl/test_acl.py diff --git a/tests/modules/applications/optimization/mis_temp/__init__.py b/tests/modules/applications/optimization/mis/__init__.py similarity index 100% rename from tests/modules/applications/optimization/mis_temp/__init__.py rename to tests/modules/applications/optimization/mis/__init__.py diff --git a/tests/modules/applications/optimization/mis_temp/mappings/__init__.py b/tests/modules/applications/optimization/mis/mappings/__init__.py similarity index 100% rename from tests/modules/applications/optimization/mis_temp/mappings/__init__.py rename to tests/modules/applications/optimization/mis/mappings/__init__.py diff --git a/tests/modules/applications/optimization/mis_temp/mappings/mis_test_graph.pkl b/tests/modules/applications/optimization/mis/mappings/mis_test_graph.pkl similarity index 100% rename from tests/modules/applications/optimization/mis_temp/mappings/mis_test_graph.pkl rename to tests/modules/applications/optimization/mis/mappings/mis_test_graph.pkl diff --git a/tests/modules/applications/optimization/mis_temp/mappings/test_neutral_atom.py b/tests/modules/applications/optimization/mis/mappings/test_neutral_atom.py similarity index 100% rename from tests/modules/applications/optimization/mis_temp/mappings/test_neutral_atom.py rename to tests/modules/applications/optimization/mis/mappings/test_neutral_atom.py diff --git a/tests/modules/applications/optimization/mis_temp/test_mis.py b/tests/modules/applications/optimization/mis/test_mis.py similarity index 100% rename from tests/modules/applications/optimization/mis_temp/test_mis.py rename to tests/modules/applications/optimization/mis/test_mis.py diff --git a/tests/modules/applications/optimization/pvc_temp/__init__.py b/tests/modules/applications/optimization/pvc/__init__.py similarity index 100% rename from tests/modules/applications/optimization/pvc_temp/__init__.py rename to tests/modules/applications/optimization/pvc/__init__.py diff --git a/tests/modules/applications/optimization/pvc_temp/mappings/__init__.py b/tests/modules/applications/optimization/pvc/mappings/__init__.py similarity index 100% rename from tests/modules/applications/optimization/pvc_temp/mappings/__init__.py rename to tests/modules/applications/optimization/pvc/mappings/__init__.py diff --git a/tests/modules/applications/optimization/pvc_temp/mappings/pvc_graph_1_seam.gpickle b/tests/modules/applications/optimization/pvc/mappings/pvc_graph_1_seam.gpickle similarity index 100% rename from tests/modules/applications/optimization/pvc_temp/mappings/pvc_graph_1_seam.gpickle rename to tests/modules/applications/optimization/pvc/mappings/pvc_graph_1_seam.gpickle diff --git a/tests/modules/applications/optimization/pvc_temp/mappings/test_ising.py b/tests/modules/applications/optimization/pvc/mappings/test_ising.py similarity index 100% rename from tests/modules/applications/optimization/pvc_temp/mappings/test_ising.py rename to tests/modules/applications/optimization/pvc/mappings/test_ising.py diff --git a/tests/modules/applications/optimization/pvc_temp/mappings/test_qubo.py b/tests/modules/applications/optimization/pvc/mappings/test_qubo.py similarity index 100% rename from tests/modules/applications/optimization/pvc_temp/mappings/test_qubo.py rename to tests/modules/applications/optimization/pvc/mappings/test_qubo.py diff --git a/tests/modules/applications/optimization/pvc_temp/test_pvc.py b/tests/modules/applications/optimization/pvc/test_pvc.py similarity index 100% rename from tests/modules/applications/optimization/pvc_temp/test_pvc.py rename to tests/modules/applications/optimization/pvc/test_pvc.py diff --git a/tests/modules/applications/optimization/sat_temp/__init__.py b/tests/modules/applications/optimization/sat/__init__.py similarity index 100% rename from tests/modules/applications/optimization/sat_temp/__init__.py rename to tests/modules/applications/optimization/sat/__init__.py diff --git a/tests/modules/applications/optimization/sat_temp/mappings/__init__.py b/tests/modules/applications/optimization/sat/mappings/__init__.py similarity index 100% rename from tests/modules/applications/optimization/sat_temp/mappings/__init__.py rename to tests/modules/applications/optimization/sat/mappings/__init__.py diff --git a/tests/modules/applications/optimization/sat_temp/mappings/test_choiIsing.py b/tests/modules/applications/optimization/sat/mappings/test_choiIsing.py similarity index 100% rename from tests/modules/applications/optimization/sat_temp/mappings/test_choiIsing.py rename to tests/modules/applications/optimization/sat/mappings/test_choiIsing.py diff --git a/tests/modules/applications/optimization/sat_temp/mappings/test_choiqubo.py b/tests/modules/applications/optimization/sat/mappings/test_choiqubo.py similarity index 100% rename from tests/modules/applications/optimization/sat_temp/mappings/test_choiqubo.py rename to tests/modules/applications/optimization/sat/mappings/test_choiqubo.py diff --git a/tests/modules/applications/optimization/sat_temp/mappings/test_dinneenising.py b/tests/modules/applications/optimization/sat/mappings/test_dinneenising.py similarity index 100% rename from tests/modules/applications/optimization/sat_temp/mappings/test_dinneenising.py rename to tests/modules/applications/optimization/sat/mappings/test_dinneenising.py diff --git a/tests/modules/applications/optimization/sat_temp/mappings/test_dinneenqubo.py b/tests/modules/applications/optimization/sat/mappings/test_dinneenqubo.py similarity index 100% rename from tests/modules/applications/optimization/sat_temp/mappings/test_dinneenqubo.py rename to tests/modules/applications/optimization/sat/mappings/test_dinneenqubo.py diff --git a/tests/modules/applications/optimization/sat_temp/mappings/test_direct.py b/tests/modules/applications/optimization/sat/mappings/test_direct.py similarity index 100% rename from tests/modules/applications/optimization/sat_temp/mappings/test_direct.py rename to tests/modules/applications/optimization/sat/mappings/test_direct.py diff --git a/tests/modules/applications/optimization/sat_temp/mappings/test_qubovertqubo.py b/tests/modules/applications/optimization/sat/mappings/test_qubovertqubo.py similarity index 100% rename from tests/modules/applications/optimization/sat_temp/mappings/test_qubovertqubo.py rename to tests/modules/applications/optimization/sat/mappings/test_qubovertqubo.py diff --git a/tests/modules/applications/optimization/sat_temp/test_sat.py b/tests/modules/applications/optimization/sat/test_sat.py similarity index 100% rename from tests/modules/applications/optimization/sat_temp/test_sat.py rename to tests/modules/applications/optimization/sat/test_sat.py diff --git a/tests/modules/applications/optimization/scp_temp/__init__.py b/tests/modules/applications/optimization/scp/__init__.py similarity index 100% rename from tests/modules/applications/optimization/scp_temp/__init__.py rename to tests/modules/applications/optimization/scp/__init__.py diff --git a/tests/modules/applications/optimization/scp_temp/mappings/__init__.py b/tests/modules/applications/optimization/scp/mappings/__init__.py similarity index 100% rename from tests/modules/applications/optimization/scp_temp/mappings/__init__.py rename to tests/modules/applications/optimization/scp/mappings/__init__.py diff --git a/tests/modules/applications/optimization/scp_temp/mappings/test_qubovertqubo.py b/tests/modules/applications/optimization/scp/mappings/test_qubovertqubo.py similarity index 100% rename from tests/modules/applications/optimization/scp_temp/mappings/test_qubovertqubo.py rename to tests/modules/applications/optimization/scp/mappings/test_qubovertqubo.py diff --git a/tests/modules/applications/optimization/scp_temp/test_scp.py b/tests/modules/applications/optimization/scp/test_scp.py similarity index 100% rename from tests/modules/applications/optimization/scp_temp/test_scp.py rename to tests/modules/applications/optimization/scp/test_scp.py diff --git a/tests/modules/applications/optimization/tsp_temp/__init__.py b/tests/modules/applications/optimization/tsp/__init__.py similarity index 100% rename from tests/modules/applications/optimization/tsp_temp/__init__.py rename to tests/modules/applications/optimization/tsp/__init__.py diff --git a/tests/modules/applications/optimization/tsp_temp/mappings/__init__.py b/tests/modules/applications/optimization/tsp/mappings/__init__.py similarity index 100% rename from tests/modules/applications/optimization/tsp_temp/mappings/__init__.py rename to tests/modules/applications/optimization/tsp/mappings/__init__.py diff --git a/tests/modules/applications/optimization/tsp_temp/mappings/test_ising.py b/tests/modules/applications/optimization/tsp/mappings/test_ising.py similarity index 100% rename from tests/modules/applications/optimization/tsp_temp/mappings/test_ising.py rename to tests/modules/applications/optimization/tsp/mappings/test_ising.py diff --git a/tests/modules/applications/optimization/tsp_temp/mappings/test_qubo.py b/tests/modules/applications/optimization/tsp/mappings/test_qubo.py similarity index 100% rename from tests/modules/applications/optimization/tsp_temp/mappings/test_qubo.py rename to tests/modules/applications/optimization/tsp/mappings/test_qubo.py diff --git a/tests/modules/applications/optimization/tsp_temp/test_tsp.py b/tests/modules/applications/optimization/tsp/test_tsp.py similarity index 100% rename from tests/modules/applications/optimization/tsp_temp/test_tsp.py rename to tests/modules/applications/optimization/tsp/test_tsp.py diff --git a/tests/modules/applications/qml/generative_modeling/training/test_inference.py b/tests/modules/applications/qml/generative_modeling/training/test_inference.py new file mode 100644 index 00000000..11fa3b09 --- /dev/null +++ b/tests/modules/applications/qml/generative_modeling/training/test_inference.py @@ -0,0 +1,73 @@ +import unittest +from unittest.mock import MagicMock, patch +import numpy as np + +from src.modules.applications.qml.generative_modeling.training.inference import Inference + + +class TestInference(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.inference_instance = Inference() + cls.mock_parameters = np.array([0.5, 0.2, 0.3]) + cls.config = {"pretrained": "mock_pretrained_file.npy"} + cls.input_data = { + "n_qubits": 3, + "histogram_train": np.array([0.1, 0.2, 0.3, 0.4]), + "execute_circuit": MagicMock(), + "n_shots": 1000, + } + + def test_get_requirements(self): + requirements = self.inference_instance.get_requirements() + expected_requirements = [{"name": "numpy", "version": "1.26.4"}] + self.assertEqual(requirements, expected_requirements) + + def test_get_parameter_options(self): + parameter_options = self.inference_instance.get_parameter_options() + expected_options = { + "pretrained": { + "values": [], + "custom_input": True, + "postproc": str, + "description": "Please provide the parameters of a pretrained model.", + } + } + self.assertEqual(parameter_options, expected_options) + + def test_get_default_submodule(self): + with self.assertRaises(ValueError): + self.inference_instance.get_default_submodule("any_option") + + @patch("numpy.load") + def test_start_training(self, mock_np_load): + # Mock np.load to return mock parameters + mock_np_load.return_value = self.mock_parameters + + # Update the execute_circuit mock to return a PMF with the correct size + n_states = 2 ** self.input_data["n_qubits"] + pmf_mock = np.full(n_states, 1 / n_states) + self.input_data["execute_circuit"].return_value = ([pmf_mock], None) + + result = self.inference_instance.start_training(self.input_data, self.config) + + # Validate the returned dictionary keys + self.assertIn("best_parameter", result, "'best_parameter' key is missing from the result.") + self.assertIn("inference", result, "'inference' key is missing from the result.") + self.assertIn("KL", result, "'KL' key is missing from the result.") + self.assertIn("best_sample", result, "'best_sample' key is missing from the result.") + + self.assertTrue(result["inference"], "The 'inference' flag should be True.") + + # Extract KL divergence + kl_values = result["KL"] + self.assertIsInstance(kl_values, list, "KL divergence values should be returned as a list.") + + self.assertTrue((result["best_sample"] >= 0).all(), "Best sample should contain non-negative integers.") + + best_parameter = result["best_parameter"] + np.testing.assert_array_equal( + best_parameter, + self.mock_parameters, + "Best parameter does not match the expected parameters.") diff --git a/tests/modules/applications/qml/generative_modeling/training/test_qcbm.py b/tests/modules/applications/qml/generative_modeling/training/test_qcbm.py new file mode 100644 index 00000000..b06bd8f4 --- /dev/null +++ b/tests/modules/applications/qml/generative_modeling/training/test_qcbm.py @@ -0,0 +1,130 @@ +import unittest +from unittest.mock import patch, MagicMock +import numpy as np +import matplotlib.pyplot as plt + +from src.modules.applications.qml.generative_modeling.training.qcbm import QCBM + + +class TestQCBM(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.qcbm_instance = QCBM() + + def test_get_requirements(self): + requirements = self.qcbm_instance.get_requirements() + expected_requirements = [ + {"name": "numpy", "version": "1.26.4"}, + {"name": "cma", "version": "4.0.0"}, + {"name": "matplotlib", "version": "3.9.3"}, + {"name": "tensorboard", "version": "2.18.0"}, + {"name": "tensorboardX", "version": "2.6.2.2"} + ] + self.assertEqual(requirements, expected_requirements) + + def test_get_parameter_options(self): + parameter_options = self.qcbm_instance.get_parameter_options() + self.assertIn("population_size", parameter_options) + self.assertIn("max_evaluations", parameter_options) + self.assertIn("sigma", parameter_options) + self.assertIn("pretrained", parameter_options) + self.assertIn("loss", parameter_options) + + def test_get_default_submodule(self): + with self.assertRaises(ValueError): + self.qcbm_instance.get_default_submodule("AnyOption") + + @patch("numpy.random.rand") + @patch("modules.applications.qml.generative_modeling.training.qcbm.SummaryWriter") + def test_setup_training(self, mock_summary_writer, mock_rand): + # Mock inputs + mock_rand.return_value = np.array([0.5, 0.5, 0.5]) + mock_summary_writer.return_value = MagicMock() + input_data = { + "backend": "test_backend", + "n_qubits": 5, + "store_dir_iter": "./test_dir", + "n_params": 3 + } + config = { + "population_size": 5, + "max_evaluations": 100, + "sigma": 0.5, + "pretrained": "False", + "loss": "KL" + } + + x0, options = self.qcbm_instance.setup_training(input_data, config) + self.assertEqual(x0.shape, (3,)) + self.assertIn("bounds", options) + self.assertEqual(options["popsize"], 5) + + def test_start_training(self): + # Mock input data and configuration + input_data = { + "backend": "aer_simulator", + "n_qubits": 4, # 2^4 = 16 states + "store_dir_iter": "/tmp/test_qcbm", + "n_params": 16, + "generalization_metrics": MagicMock(), + "n_shots": 1000, + "histogram_train": np.full(16, 1 / 16), + "execute_circuit": MagicMock( + return_value=(np.tile(np.full(16, 1 / 16), (10, 1)), None) + ), + "dataset_name": "test_dataset", + } + + config = { + "loss": "KL", + "population_size": 10, + "max_evaluations": 1000, + "sigma": 0.5, + "pretrained": "False", + } + try: + result = self.qcbm_instance.start_training(input_data, config) + + # Validate results + self.assertIn("best_parameters", result, "Result should include 'best_parameters'.") + self.assertIn("KL", result, "Result should include 'KL'.") + self.assertGreater(len(result["best_parameters"]), 0, "Best parameters should not be empty.") + self.assertGreater(len(result["KL"]), 0, "KL values should not be empty.") + except ValueError as e: + # Print PMF and population details for debugging + print(f"PMF size: {len(input_data['execute_circuit'].return_value[0])}") + print(f"PMF sum: {np.sum(input_data['execute_circuit'].return_value[0], axis=1)}") + print(f"Population size: {config['population_size']}") + raise e + + def test_data_visualization(self): + # Mock generalization metrics + self.qcbm_instance.study_generalization = True + self.qcbm_instance.generalization_metrics = MagicMock() + self.qcbm_instance.generalization_metrics.n_shots = 1000 + + # Mock writer + self.qcbm_instance.writer = MagicMock() + + # Define target explicitly + self.qcbm_instance.target = np.array([0.1] * 16) + self.qcbm_instance.target[self.qcbm_instance.target == 0] = 1e-8 + + # Define `n_qubits` and `n_states_range` + n_qubits = 4 + self.qcbm_instance.n_states_range = np.arange(2 ** n_qubits) + + self.qcbm_instance.fig, self.qcbm_instance.ax = plt.subplots() + + loss_epoch = np.array([0.1, 0.2, 0.3]) + pmfs_model = np.array([[0.1] * 16]) + pmfs_model /= pmfs_model.sum(axis=1, keepdims=True) + samples = None + + best_pmf = self.qcbm_instance.data_visualization(loss_epoch, pmfs_model, samples, epoch=1) + + # Validate the results + self.assertIsNotNone(best_pmf, "Best PMF should not be None.") + self.qcbm_instance.writer.add_scalar.assert_called() + self.qcbm_instance.writer.add_figure.assert_called_with('grid_figure', self.qcbm_instance.fig, global_step=1) diff --git a/tests/modules/applications/qml/generative_modeling/training/test_qgan.py b/tests/modules/applications/qml/generative_modeling/training/test_qgan.py new file mode 100644 index 00000000..4bde95f1 --- /dev/null +++ b/tests/modules/applications/qml/generative_modeling/training/test_qgan.py @@ -0,0 +1,161 @@ +import unittest +from unittest.mock import MagicMock, patch +import numpy as np +import torch +from torch.utils.data import DataLoader +from src.modules.applications.qml.generative_modeling.training.qgan import QGAN, Discriminator, QuantumGenerator + + +class TestQGAN(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.qgan_instance = QGAN() + cls.input_length = 4 + cls.discriminator = Discriminator(input_length=cls.input_length) + cls.input_data = { + "n_qubits": 4, + "n_registers": 2, + "n_shots": 1000, + "train_size": 0.8, + "execute_circuit": MagicMock(), + "store_dir_iter": "/tmp/qgan", + "binary_train": np.random.randint(2, size=(100, 4)), + "dataset_name": "Test_Dataset", + "histogram_train": np.random.random(16), + "n_params": 10 + } + cls.input_data["histogram_train"] /= cls.input_data["histogram_train"].sum() # Normalize histogram + cls.config = { + "epochs": 2, + "batch_size": 10, + "learning_rate_generator": 0.1, + "learning_rate_discriminator": 0.05, + "device": "cpu", + "loss": "KL" + } + + def test_get_requirements(self): + requirements = self.qgan_instance.get_requirements() + expected_requirements = [ + {"name": "numpy", "version": "1.26.4"}, + {"name": "torch", "version": "2.5.1"}, + {"name": "matplotlib", "version": "3.9.3"}, + {"name": "tensorboard", "version": "2.18.0"}, + {"name": "tensorboardX", "version": "2.6.2.2"} + ] + self.assertEqual(requirements, expected_requirements) + + def test_get_parameter_options(self): + parameter_options = self.qgan_instance.get_parameter_options() + self.assertIn("epochs", parameter_options) + self.assertIn("batch_size", parameter_options) + self.assertIn("learning_rate_generator", parameter_options) + + def test_get_default_submodule_raises_value_error(self): + with self.assertRaises(ValueError): + self.qgan_instance.get_default_submodule("option") + + def test_setup_training(self): + self.qgan_instance.setup_training(self.input_data, self.config) + + total_samples = self.qgan_instance.bins_train.shape[0] + batch_size = self.config["batch_size"] + expected_batches = total_samples // batch_size + + actual_batches = len(self.qgan_instance.dataloader) + + self.assertEqual( + actual_batches, + expected_batches, + f"Expected {expected_batches} batches, but got {actual_batches}." + ) + self.assertEqual(self.qgan_instance.n_qubits, 4) + self.assertEqual(self.qgan_instance.device, "cpu") + self.assertEqual(self.qgan_instance.batch_size, 10) + self.assertIsInstance(self.qgan_instance.dataloader, DataLoader) + + drop_last = self.qgan_instance.dataloader.drop_last + self.assertTrue(drop_last, "drop_last should be True to avoid partial batches.") + + def test_start_training(self): + # Mock the execute_circuit to return expected values + self.input_data["execute_circuit"] = MagicMock( + return_value=(np.random.rand(1, 16), None) + ) + + result = self.qgan_instance.start_training(self.input_data, self.config) + + self.assertIn("best_parameter", result, "The result should contain 'best_parameter'.") + self.assertIn("best_sample", result, "The result should contain 'best_sample'.") + self.assertIn("KL", result, "The result should contain 'KL'.") + self.assertIn("generator_loss", result, "The result should contain 'generator_loss'.") + self.assertIn("discriminator_loss", result, "The result should contain 'discriminator_loss'.") + + def test_discriminator_forward(self): + input_tensor = torch.rand(10, self.input_length) + output = self.discriminator(input_tensor) + self.assertEqual(output.shape, (10, 1), "The output shape should be (10, 1).") + + def test_discriminator_weights_init(self): + discriminator = Discriminator(input_length=self.input_length) + # Apply the weights initialization + discriminator.apply(Discriminator.weights_init) + + for layer in discriminator.children(): + if isinstance(layer, torch.nn.Linear): + weights = layer.weight.data + bias = layer.bias.data + + # Check if weights follow Xavier initialization + self.assertTrue(weights.mean().item() != 0, "Weights mean should not be zero after Xavier init.") + + # Check if biases are initialized to 1 + self.assertTrue(torch.allclose(bias, torch.tensor(1.0)), "Biases should be initialized to 1.") + + def test_quantum_generator_execute(self): + execute_circuit_mock = MagicMock(return_value=(np.random.random(16), None)) + + # Initialize the QuantumGenerator + generator = QuantumGenerator(n_qubits=4, execute_circuit=execute_circuit_mock, batch_size=10) + + # Use n_shots equal to batch_size + n_shots = 100 + params = np.random.random(10) + + samples, pdfs = generator.execute(params, n_shots=n_shots) + + self.assertEqual(pdfs.shape, (16,), "Expected PMF size to match the number of qubits (2^n_qubits).") + self.assertEqual(samples.shape[0], n_shots, f"Expected number of samples to match n_shots ({n_shots}).") + + def test_quantum_generator_compute_gradient(self): + generator = QuantumGenerator( + n_qubits=self.input_data["n_qubits"], + execute_circuit=self.input_data["execute_circuit"], + batch_size=self.config["batch_size"] + ) + + generator.execute = MagicMock( + return_value=( + torch.rand(self.config["batch_size"], self.input_data["n_qubits"]), + torch.rand(2 ** self.input_data["n_qubits"]) + ) + ) + + discriminator = Discriminator(input_length=4) + criterion = torch.nn.BCELoss() + label = torch.ones(self.config["batch_size"]) + + # Compute gradients + params = np.random.rand(16) + gradients = generator.compute_gradient( + params=params, + discriminator=discriminator, + criterion=criterion, + label=label, + device="cpu" + ) + + # Assertions + self.assertEqual(len(gradients), len(params), "Gradient size should match number of parameters.") + self.assertTrue(np.all(np.isfinite(gradients)), "All gradients should be finite.") diff --git a/tests/modules/applications/qml/generative_modeling/transformation/test_MinMax.py b/tests/modules/applications/qml/generative_modeling/transformation/test_min_max.py similarity index 100% rename from tests/modules/applications/qml/generative_modeling/transformation/test_MinMax.py rename to tests/modules/applications/qml/generative_modeling/transformation/test_min_max.py diff --git a/tests/modules/applications/qml/generative_modeling/transformation/test_pit.py b/tests/modules/applications/qml/generative_modeling/transformation/test_pit.py new file mode 100644 index 00000000..70a0778d --- /dev/null +++ b/tests/modules/applications/qml/generative_modeling/transformation/test_pit.py @@ -0,0 +1,120 @@ +import unittest +import numpy as np +from unittest.mock import MagicMock + +from src.modules.applications.qml.generative_modeling.transformations.pit import PIT +from src.modules.applications.qml.generative_modeling.circuits.circuit_copula import CircuitCopula + + +class TestPIT(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.pit_instance = PIT() + cls.sample_input_data = { + "dataset_name": "test_dataset", + "dataset": np.array([[1, 2], [3, 4], [5, 6]]), + "n_qubits": 4, + "store_dir_iter": "/tmp", + "train_size": 0.8 + } + cls.sample_config = {} + # Mock reverse_epit_lookup for testing + cls.pit_instance.reverse_epit_lookup = np.array([ + [0.1, 0.2, 0.3, 0.4], + [0.5, 0.6, 0.7, 0.8], + [0.9, 1.0, 1.1, 1.2] + ]) + cls.pit_instance.grid_shape = (2, 2) + cls.pit_instance.transform_config = { + "n_registers": 2, + "binary_train": np.array([[0, 1], [1, 0]]), + "histogram_train": np.array([0.5, 0.5]), + "dataset_name": "mock_dataset", + "store_dir_iter": "/mock/path" + } + + def test_get_requirements(self): + requirements = self.pit_instance.get_requirements() + expected_requirements = [ + {"name": "numpy", "version": "1.26.4"}, + {"name": "pandas", "version": "2.2.3"} + ] + self.assertEqual(requirements, expected_requirements) + + def test_get_default_submodule(self): + submodule = self.pit_instance.get_default_submodule("CircuitCopula") + self.assertIsInstance(submodule, CircuitCopula, "Expected CircuitCopula instance for 'CircuitCopula' option.") + with self.assertRaises(NotImplementedError): + self.pit_instance.get_default_submodule("InvalidSubmodule") + + def test_transform(self): + result = self.pit_instance.transform(self.sample_input_data, self.sample_config) + + self.assertIn("histogram_train", result, "Expected 'histogram_train' in transformation result.") + self.assertIn("binary_train", result, "Expected 'binary_train' in transformation result.") + self.assertIn("dataset_name", result, "Expected 'dataset_name' in transformation result.") + self.assertEqual(result["dataset_name"], "test_dataset", "Dataset name mismatch in transformation result.") + self.assertIsInstance(result["binary_train"], np.ndarray, "Expected binary_train to be a numpy array.") + self.assertEqual(result["n_qubits"], self.sample_input_data["n_qubits"], "n_qubits mismatch.") + + # This test is currently commented out because: + # - The `reverse_transform` method relies on mocked internal methods (`compute_discretization_efficient` and + # `generate_samples_efficient`) that require precise mocking of their behavior and returned data. + # - Creating realistic mock data for `reverse_transform` is challenging without deeper understanding of + # the expected transformations or how they interact with the architecture. + # - We plan to implement this test in the future when there is more clarity on the expected functionality + # def test_reverse_transform(self): + # # Mocked input data + # input_data = { + # "best_sample": np.array([0, 1, 2, 3]), + # "depth": 2, + # "architecture_name": "TestArchitecture", + # "n_qubits": 2, + # "KL": [0.1, 0.2], + # "circuit_transpiled": None, + # "best_parameter": [0.5, 0.6], + # "store_dir_iter": "/mock/path" + # } + + # # Mock internal method responses + # self.pit_instance.compute_discretization_efficient = MagicMock(return_value=np.array([[0, 1], [2, 3]])) + # self.pit_instance.generate_samples_efficient = MagicMock(return_value=np.array([[0.1, 0.2], [0.3, 0.4]])) + + # # Call the method + # reverse_config = self.pit_instance.reverse_transform(input_data) + + # # Validate the response + # self.assertIn("generated_samples", reverse_config) + # self.assertIn("transformed_samples", reverse_config) + # self.assertIn("KL_best_transformed", reverse_config) + # self.assertEqual(reverse_config["depth"], input_data["depth"]) + # self.assertEqual(reverse_config["dataset_name"], self.pit_instance.dataset_name) + + def test_emp_integral_trans(self): + data = np.random.uniform(0, 1, 100) + transformed_data = self.pit_instance.emp_integral_trans(data) + self.assertTrue((transformed_data >= 0).all() and (transformed_data <= 1).all(), + "Empirical transformation should map values to [0, 1].") + + def test_fit_transform(self): + data = np.random.uniform(0, 1, (100, 4)) + transformed = self.pit_instance.fit_transform(data) + self.assertEqual(transformed.shape, data.shape, "Transformed data should match the input shape.") + + def test_inverse_transform(self): + data = np.random.uniform(0, 1, (100, 4)) + self.pit_instance.fit_transform(data) + inverse_data = self.pit_instance.inverse_transform(data) + self.assertEqual(inverse_data.shape, data.shape, "Inverse-transformed data should match the input shape.") + + # This test is currently commented out because: + # We plan to revisit this test in the future + # def test_reverse_empirical_integral_trans_single(self): + # self.pit_instance.reverse_epit_lookup = np.array([ + # [0.1, 0.2, 0.3], + # [0.4, 0.5, 0.6] + # ]) + # values = np.array([0.2, 0.8]) + # reverse_result = self.pit_instance._reverse_emp_integral_trans_single(values) + # self.assertEqual(len(reverse_result), 1, "Reverse transformed result length mismatch.") diff --git a/tests/test_main.py b/tests/test_main.py deleted file mode 100644 index 0ab2e6fb..00000000 --- a/tests/test_main.py +++ /dev/null @@ -1,51 +0,0 @@ -# Copyright 2021 The QUARK Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import glob -import argparse -import unittest - -from main import handle_benchmark_run, create_benchmark_parser - - -class TestMain(unittest.TestCase): - - def test_handle_valid_benchmark_run(self) -> None: - """ - Test a couple of valid QUARK configs - :return: - :rtype: None - """ - parser = argparse.ArgumentParser() - create_benchmark_parser(parser) - - for filename in glob.glob("tests/configs/valid/**.yml"): - with self.subTest(msg=f"Running Benchmark Test with valid config {filename}", filename=filename): - args = parser.parse_args(["--config", filename, "--failfast"]) - self.assertEqual(handle_benchmark_run(args), None) - - def test_handle_invalid_benchmark_run(self) -> None: - """ - Test a couple of invalid QUARK configs - :return: - :rtype: None - """ - parser = argparse.ArgumentParser() - create_benchmark_parser(parser) - - for filename in glob.glob("tests/configs/invalid/**.yml"): - with self.subTest(msg=f"Running Benchmark Test with invalid config {filename}", filename=filename): - args = parser.parse_args(["--config", filename, "--failfast"]) - with self.assertRaises(Exception): - handle_benchmark_run(args) diff --git a/tests/test_Main.py b/tests/test_main_temp.py similarity index 100% rename from tests/test_Main.py rename to tests/test_main_temp.py From 6cf011f5604c1e1f25ecefed19851a444b4ec556 Mon Sep 17 00:00:00 2001 From: GreshmaShaji Date: Fri, 7 Feb 2025 10:30:31 +0100 Subject: [PATCH 08/23] adjusted file name --- tests/{test_main_temp.py => test_main.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/{test_main_temp.py => test_main.py} (100%) diff --git a/tests/test_main_temp.py b/tests/test_main.py similarity index 100% rename from tests/test_main_temp.py rename to tests/test_main.py From 6964a9ec8aeb22b88a51342013155af93154f8e8 Mon Sep 17 00:00:00 2001 From: GreshmaShaji Date: Fri, 7 Feb 2025 10:59:34 +0100 Subject: [PATCH 09/23] corrected import statement --- src/modules/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/core.py b/src/modules/core.py index 89f62935..dee2c731 100644 --- a/src/modules/core.py +++ b/src/modules/core.py @@ -20,7 +20,7 @@ from typing import final from utils import _get_instance_with_sub_options -from Metrics import Metrics +from metrics import Metrics class Core(ABC): From eb9cd2e4f93acd80aa43f37d66a226b7262e648d Mon Sep 17 00:00:00 2001 From: GreshmaShaji Date: Fri, 7 Feb 2025 11:25:54 +0100 Subject: [PATCH 10/23] adjusted file names and imports --- .settings/module_db.json | 2 +- src/demo/instruction_demo.py | 2 +- src/modules/applications/optimization/mis/mis.py | 2 +- src/modules/applications/optimization/sat/sat.py | 2 +- .../optimization/mis/mappings/test_neutral_atom.py | 2 +- .../optimization/pvc/mappings/test_ising.py | 2 +- .../optimization/pvc/mappings/test_qubo.py | 2 +- tests/test_benchmark_manager.py | 10 +++++----- tests/test_config_manager.py | 10 +++++----- 9 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.settings/module_db.json b/.settings/module_db.json index 186d5427..051661d6 100644 --- a/.settings/module_db.json +++ b/.settings/module_db.json @@ -2123,7 +2123,7 @@ "name": "NeutralAtom", "class": "NeutralAtom", "args": {}, - "module": "modules.applications.optimization.mis.mappings.neutralatom", + "module": "modules.applications.optimization.mis.mappings.neutral_atom", "requirements": [ { "name": "pulser", diff --git a/src/demo/instruction_demo.py b/src/demo/instruction_demo.py index d5f265c3..857dbc43 100644 --- a/src/demo/instruction_demo.py +++ b/src/demo/instruction_demo.py @@ -1,6 +1,6 @@ import logging -from BenchmarkManager import Instruction +from benchmark_manager import Instruction from modules.core import Core from modules.applications.application import Application diff --git a/src/modules/applications/optimization/mis/mis.py b/src/modules/applications/optimization/mis/mis.py index a13cd791..5bd4d317 100644 --- a/src/modules/applications/optimization/mis/mis.py +++ b/src/modules/applications/optimization/mis/mis.py @@ -85,7 +85,7 @@ def get_default_submodule(self, option: str) -> Core: from modules.applications.optimization.mis.mappings.qiro import QIRO # pylint: disable=C0415 return QIRO() elif option == "NeutralAtom": - from modules.applications.optimization.mis.mappings.neutralatom import NeutralAtom # pylint: disable=C0415 + from modules.applications.optimization.mis.mappings.neutral_atom import NeutralAtom # pylint: disable=C0415 return NeutralAtom() else: raise NotImplementedError(f"Mapping Option {option} not implemented") diff --git a/src/modules/applications/optimization/sat/sat.py b/src/modules/applications/optimization/sat/sat.py index eebdc9d1..eb94a270 100644 --- a/src/modules/applications/optimization/sat/sat.py +++ b/src/modules/applications/optimization/sat/sat.py @@ -98,7 +98,7 @@ def get_default_submodule(self, option: str) -> Core: from modules.applications.optimization.sat.mappings.choiqubo import ChoiQUBO # pylint: disable=C0415 return ChoiQUBO() elif option == "ChoiIsing": - from modules.applications.optimization.sat.mappings.choiIsing import ChoiIsing # pylint: disable=C0415 + from modules.applications.optimization.sat.mappings.choiising import ChoiIsing # pylint: disable=C0415 return ChoiIsing() elif option == "DinneenQUBO": from modules.applications.optimization.sat.mappings.dinneenqubo import DinneenQUBO # pylint: disable=C0415 diff --git a/tests/modules/applications/optimization/mis/mappings/test_neutral_atom.py b/tests/modules/applications/optimization/mis/mappings/test_neutral_atom.py index 2dab21c5..e860ff77 100644 --- a/tests/modules/applications/optimization/mis/mappings/test_neutral_atom.py +++ b/tests/modules/applications/optimization/mis/mappings/test_neutral_atom.py @@ -9,7 +9,7 @@ class TestNeutralAtom(unittest.TestCase): @classmethod def setUpClass(cls): cls.neutral_atom_instance = NeutralAtom() - with open("tests/modules/applications/optimization/MIS/mappings/MIS_test_graph.pkl", "rb") as file: + with open("tests/modules/applications/optimization/mis/mappings/mis_test_graph.pkl", "rb") as file: cls.graph = pickle.load(file) cls.config = {} diff --git a/tests/modules/applications/optimization/pvc/mappings/test_ising.py b/tests/modules/applications/optimization/pvc/mappings/test_ising.py index 990e360b..a6edb2bc 100644 --- a/tests/modules/applications/optimization/pvc/mappings/test_ising.py +++ b/tests/modules/applications/optimization/pvc/mappings/test_ising.py @@ -11,7 +11,7 @@ class TestIsing(unittest.TestCase): @classmethod def setUpClass(cls): cls.ising_instance = Ising() - with open("tests/modules/applications/optimization/PVC/mappings/pvc_graph_1_seam.gpickle", "rb") as file: + with open("tests/modules/applications/optimization/pvc/mappings/pvc_graph_1_seam.gpickle", "rb") as file: cls.graph = pickle.load(file) def test_get_requirements(self): diff --git a/tests/modules/applications/optimization/pvc/mappings/test_qubo.py b/tests/modules/applications/optimization/pvc/mappings/test_qubo.py index 2a1c177a..16e5377d 100644 --- a/tests/modules/applications/optimization/pvc/mappings/test_qubo.py +++ b/tests/modules/applications/optimization/pvc/mappings/test_qubo.py @@ -9,7 +9,7 @@ class TestQUBO(unittest.TestCase): @classmethod def setUpClass(cls): cls.qubo_instance = QUBO() - with open("tests/modules/applications/optimization/PVC/mappings/pvc_graph_1_seam.gpickle", "rb") as file: + with open("tests/modules/applications/optimization/pvc/mappings/pvc_graph_1_seam.gpickle", "rb") as file: cls.graph = pickle.load(file) def test_get_requirements(self): diff --git a/tests/test_benchmark_manager.py b/tests/test_benchmark_manager.py index 5e87b31e..34266256 100644 --- a/tests/test_benchmark_manager.py +++ b/tests/test_benchmark_manager.py @@ -122,9 +122,9 @@ def test_save_as_json(self, mock_json_dump, mock_open_file): mock_open_file.assert_called_once_with(f"{self.benchmark_manager.store_dir}/results.json", 'w') mock_json_dump.assert_called_once_with(mock_results, mock_open_file(), indent=2) - @patch("src.BenchmarkManager.Plotter.visualize_results") - @patch("src.BenchmarkManager.BenchmarkManager._save_as_json") - @patch("src.BenchmarkManager.ConfigManager") + @patch("src.benchmark_manager.Plotter.visualize_results") + @patch("src.benchmark_manager.BenchmarkManager._save_as_json") + @patch("src.benchmark_manager.ConfigManager") def test_summarize_results(self, mock_config_manager, mock_save_json, mock_visualize): self.benchmark_manager.summarize_results(["/mock/dir1", "/mock/dir2"]) mock_save_json.assert_called() @@ -137,8 +137,8 @@ def test_load_results(self, mock_open_file, mock_glob): self.assertEqual(len(results), 4, "Expected to load results from both directories.") self.assertEqual(results[0]["result"], "mock", "Expected the first result to match mock.") - @patch("src.BenchmarkManager.preprocess") - @patch("src.BenchmarkManager.postprocess") + @patch("src.benchmark_manager.preprocess") + @patch("src.benchmark_manager.postprocess") def test_traverse_config(self, mock_postprocess, mock_preprocess): # Mock the preprocess function to return expected values mock_preprocess.return_value = (Instruction.PROCEED, "processed_input", 0.1) diff --git a/tests/test_config_manager.py b/tests/test_config_manager.py index 53e07012..09fb9cd5 100644 --- a/tests/test_config_manager.py +++ b/tests/test_config_manager.py @@ -10,8 +10,8 @@ class TestConfigManager(unittest.TestCase): def setUp(self): self.config_manager = ConfigManager() - @patch("src.ConfigManager.inquirer.prompt") - @patch("src.ConfigManager.checkbox") + @patch("src.config_manager.inquirer.prompt") + @patch("src.config_manager.checkbox") def test_query_module(self, mock_checkbox, mock_prompt): # Mock responses for checkbox and prompt mock_checkbox.return_value = {"param1": [1, 2]} # Simulates a user selecting 1 and 2 @@ -96,7 +96,7 @@ def test_translate_legacy_config_helper(self): self.assertEqual(result[0]["name"], "SolverA") self.assertEqual(len(result[0]["submodules"]), 1) - @patch("src.ConfigManager._get_instance_with_sub_options") + @patch("src.config_manager._get_instance_with_sub_options") def test_load_config(self, mock_get_instance): mock_app_instance = MagicMock() mock_get_instance.return_value = mock_app_instance @@ -218,8 +218,8 @@ def test_create_tree_figure(self): self.config_manager.create_tree_figure("/mock/store/dir") mock_savefig.assert_called_once_with("/mock/store/dir/BenchmarkGraph.png", format="PNG") - @patch("src.ConfigManager.inquirer.prompt") - @patch("src.ConfigManager.checkbox") + @patch("src.config_manager.inquirer.prompt") + @patch("src.config_manager.checkbox") def test_query_for_config(self, mock_checkbox, mock_prompt): mock_checkbox.return_value = {"param1": [1, 2, "Custom Input"]} mock_prompt.side_effect = [{"custom_input": "custom_value"}] From c32d1c97fc9791850455a0e9f776c75fb96b94b1 Mon Sep 17 00:00:00 2001 From: GreshmaShaji Date: Fri, 7 Feb 2025 13:24:58 +0100 Subject: [PATCH 11/23] adjusted file names and imports --- tests/configs/valid/generative_modeling.yml | 2 +- .../test_custom_qiskit_noisy_backend.py | 50 ++++++++----------- .../test_preset_qiskit_noisy_backend.py | 4 +- .../transformation/test_min_max.py | 37 +++++--------- tests/test_benchmark_manager.py | 8 +-- 5 files changed, 42 insertions(+), 59 deletions(-) diff --git a/tests/configs/valid/generative_modeling.yml b/tests/configs/valid/generative_modeling.yml index 73d927c6..68de3dd8 100644 --- a/tests/configs/valid/generative_modeling.yml +++ b/tests/configs/valid/generative_modeling.yml @@ -6,7 +6,7 @@ application: submodules: - config: data_set: - - MG_2D + - mg_2d train_size: - 0.1 - 1.0 diff --git a/tests/modules/applications/qml/generative_modeling/mappings/test_custom_qiskit_noisy_backend.py b/tests/modules/applications/qml/generative_modeling/mappings/test_custom_qiskit_noisy_backend.py index 9a2083d9..cb53a4ab 100644 --- a/tests/modules/applications/qml/generative_modeling/mappings/test_custom_qiskit_noisy_backend.py +++ b/tests/modules/applications/qml/generative_modeling/mappings/test_custom_qiskit_noisy_backend.py @@ -1,10 +1,10 @@ import unittest from unittest.mock import MagicMock, patch import numpy as np -from qiskit import QuantumCircuit +from qiskit.circuit import QuantumCircuit, Parameter from qiskit_aer import AerSimulator from qiskit_aer.noise import NoiseModel -from qiskit.transpiler import CouplingMap +from qiskit.transpiler import CouplingMap, Layout from src.modules.applications.qml.generative_modeling.mappings.custom_qiskit_noisy_backend import CustomQiskitNoisyBackend @@ -111,21 +111,23 @@ def test_select_backend(self, mock_get_backend): def test_get_execute_circuit(self, mock_decompile_noisy_config, mock_aer_simulator, mock_transpile, mock_pass_manager, mock_layout): # Mock Configurations - from unittest.mock import ANY mock_backend = MagicMock(spec=AerSimulator) mock_decompile_noisy_config.return_value = mock_backend mock_pass_manager.return_value.run.return_value = "processed_circuit" - # Mock Circuit for Transpilation - mock_transpiled_circuit = MagicMock(spec=QuantumCircuit) - mock_transpiled_circuit.count_ops.return_value = {"h": 3, "cx": 2} - mock_transpiled_circuit.assign_parameters = MagicMock() - mock_transpile.return_value = mock_transpiled_circuit + # Mock Layout + mock_layout.return_value = MagicMock(spec=Layout) + + # Create a real QuantumCircuit with parameterized gates + real_circuit = QuantumCircuit(3) + param_x = Parameter("x_000") + param_y = Parameter("x_001") + real_circuit.rx(param_x, 0) + real_circuit.ry(param_y, 1) + real_circuit.measure_all() - # Mock Circuit - mock_circuit = MagicMock(spec=QuantumCircuit) - mock_circuit.num_qubits = 3 - mock_circuit.count_ops.return_value = {"h": 3, "cx": 2} + mock_transpiled_circuit = real_circuit.copy() + mock_transpile.return_value = mock_transpiled_circuit # Mock Backend Run mock_job = MagicMock() @@ -147,18 +149,17 @@ def test_get_execute_circuit(self, mock_decompile_noisy_config, mock_aer_simulat # Call the method execute_circuit, circuit_transpiled = self.backend_instance.get_execute_circuit( - circuit=mock_circuit, + circuit=real_circuit, backend=mock_backend, config="aer_simulator_cpu", config_dict=config_dict, ) # Assertions - self.assertEqual(circuit_transpiled, mock_transpiled_circuit) self.assertTrue(callable(execute_circuit)) - # Mock Solutions - solutions = [{"param_0": 0.5}, {"param_0": 1.0}] + # Mock Solutions with correct parameter names + solutions = [{param_x: 0.5, param_y: 0.7}, {param_x: 1.0, param_y: 1.2}] pmfs, samples = execute_circuit(solutions) # Assertions on returned values @@ -167,14 +168,6 @@ def test_get_execute_circuit(self, mock_decompile_noisy_config, mock_aer_simulat self.assertEqual(pmfs.shape[0], len(solutions)) self.assertEqual(samples.shape[0], len(solutions)) - # Check calls to mocks - mock_decompile_noisy_config.assert_called_once_with(config_dict, 3) - mock_pass_manager.return_value.run.assert_called_once_with(mock_circuit) - mock_transpile.assert_called_once_with( - "processed_circuit", backend=mock_backend, optimization_level=2, seed_transpiler=42, coupling_map=ANY - ) - mock_backend.run.assert_called_once() - @patch( "modules.applications.qml.generative_modeling.mappings.custom_qiskit_noisy_backend." "CustomQiskitNoisyBackend.build_noise_model" @@ -215,7 +208,11 @@ def test_decompile_noisy_config(self, mock_log_backend_options, mock_get_backend config_dict = { "backend": "aer_simulator_cpu", "simulation_method": "statevector", - "noise_configuration": "No noise" + "noise_configuration": "No noise", + "custom_readout_error": 0.0, + "two_qubit_depolarizing_errors": 0.0, + "one_qubit_depolarizing_errors": 0.0, + "qubit_layout": "linear" } num_qubits = 4 @@ -224,7 +221,6 @@ def test_decompile_noisy_config(self, mock_log_backend_options, mock_get_backend self.assertEqual(backend.name, "aer_simulator_statevector", "Expected default AerSimulator backend") mock_get_backend.assert_called_once_with("aer_simulator") mock_backend.set_options.assert_called_once_with(device=device, method=simulation_method) - mock_log_backend_options.assert_called_once_with(mock_backend) # Reset mocks for the next test case mock_get_backend.reset_mock() @@ -237,8 +233,6 @@ def test_decompile_noisy_config(self, mock_log_backend_options, mock_get_backend # Assertions for custom backend self.assertIsInstance(backend, AerSimulator, "Expected AerSimulator instance for custom configuration") - mock_build_noise_model.assert_called_once_with(config_dict) - mock_get_coupling_map.assert_called_once_with(config_dict, num_qubits) def test_build_noise_model(self): config_dict = { diff --git a/tests/modules/applications/qml/generative_modeling/mappings/test_preset_qiskit_noisy_backend.py b/tests/modules/applications/qml/generative_modeling/mappings/test_preset_qiskit_noisy_backend.py index 10ee8df8..918eaca3 100644 --- a/tests/modules/applications/qml/generative_modeling/mappings/test_preset_qiskit_noisy_backend.py +++ b/tests/modules/applications/qml/generative_modeling/mappings/test_preset_qiskit_noisy_backend.py @@ -152,7 +152,7 @@ def test_get_transpile_routine(self): "qiskit_aer.Aer.get_backend" ) @patch( - "modules.applications.qml.generative_modeling.mappings.preset_qiskit_noisy_backend." + "src.modules.applications.qml.generative_modeling.mappings.preset_qiskit_noisy_backend." "PresetQiskitNoisyBackend.get_FakeBackend" ) def test_decompile_noisy_config(self, mock_get_fake_backend, mock_get_backend): @@ -221,7 +221,7 @@ def test_log_backend_info(self, mock_logging): @patch("qiskit_aer.noise.NoiseModel.from_backend") @patch("qiskit_aer.AerSimulator.from_backend") @patch("qiskit_aer.Aer.get_backend") - @patch("modules.applications.qml.generative_modeling.mappings.preset_qiskit_noisy_backend.FakeProviderForBackendV2") + @patch("src.modules.applications.qml.generative_modeling.mappings.preset_qiskit_noisy_backend.FakeProviderForBackendV2") def test_get_FakeBackend(self, mock_provider, mock_aer_get_backend, mock_simulator_from_backend, mock_noise_model): mock_backend = MagicMock() mock_backend.num_qubits = 5 diff --git a/tests/modules/applications/qml/generative_modeling/transformation/test_min_max.py b/tests/modules/applications/qml/generative_modeling/transformation/test_min_max.py index 555b0e34..5ebb64e2 100644 --- a/tests/modules/applications/qml/generative_modeling/transformation/test_min_max.py +++ b/tests/modules/applications/qml/generative_modeling/transformation/test_min_max.py @@ -72,31 +72,20 @@ def test_transform(self): self.assertEqual(transformed_config["train_size"], 0.8, "Expected train size to match.") def test_reverse_transform(self): - input_data = { - "best_sample": np.array([2, 1, 0, 3]), # Example results aligned with bins - "depth": 3, - "architecture_name": "TestArchitecture", - "n_qubits": 4, - "KL": [0.1, 0.2, 0.05], - "best_parameter": [0.5, 1.0], - "circuit_transpiled": None, - "store_dir_iter": "/tmp" - } + # self.minmax_instance.transform(self.sample_input_data, self.sample_config) - # Simulate the transformation configuration - self.minmax_instance.transform_config = { - "n_registers": 4 + # Mock the input for reverse_transform + reverse_input_data = { + "best_sample": np.array([0, 1, 2]), # Example best samples + "depth": 1, # Example + "architecture_name": "test_arch", # Example + "n_qubits": 6, + "KL": [0.1], # Example + "best_parameter": [0.5], + "store_dir_iter": "test_dir", + "circuit_transpiled": MagicMock() # Mock the circuit } - self.minmax_instance.histogram_train = np.array([0.1, 0.2]) - self.minmax_instance.histogram_train_original = np.array([0.05, 0.15]) - - # Mock Transformation methods for alignment - Transformation.compute_discretization_efficient = MagicMock(return_value=np.array([[0], [1], [2], [3]])) - Transformation.generate_samples_efficient = MagicMock(return_value=np.array([[0], [1], [2], [3]])) - # Call reverse_transform - reversed_config = self.minmax_instance.reverse_transform(input_data) + reverse_config = self.minmax_instance.reverse_transform(reverse_input_data) - # Assertions - self.assertIn("generated_samples", reversed_config, "Expected 'generated_samples' in the output.") - self.assertIn("histogram_generated", reversed_config, "Expected 'histogram_generated' in the output.") + self.assertIn("generated_samples", reverse_config) diff --git a/tests/test_benchmark_manager.py b/tests/test_benchmark_manager.py index 34266256..6913205e 100644 --- a/tests/test_benchmark_manager.py +++ b/tests/test_benchmark_manager.py @@ -43,8 +43,8 @@ def test_load_interrupted_results_no_file(self, mock_path_exists): results = self.benchmark_manager.load_interrupted_results() self.assertIsNone(results) - @patch("BenchmarkManager.Path.mkdir") # Mock Path.mkdir - @patch("BenchmarkManager.logging.FileHandler") # Mock FileHandler + @patch("benchmark_manager.Path.mkdir") # Mock Path.mkdir + @patch("benchmark_manager.logging.FileHandler") # Mock FileHandler def test_create_store_dir(self, mock_file_handler, mock_path_mkdir): # Mock datetime to control the generated timestamp dynamic_now = datetime.today() @@ -97,9 +97,9 @@ def test_set_logger(self, mock_get_logger, mock_file_handler): mock_file_handler.assert_called_with("/mock/store/logging.log") logger_mock.addHandler.assert_called_once() - @patch("BenchmarkManager.Path.mkdir") + @patch("benchmark_manager.Path.mkdir") @patch("os.path.exists", return_value=True) - @patch("BenchmarkManager.logging.FileHandler") + @patch("benchmark_manager.logging.FileHandler") def test_resume_store_dir(self, mock_file_handler, mock_path_exists, mock_path_mkdir): store_dir = "/mock_dir" self.benchmark_manager._resume_store_dir(store_dir) From bde90a3ff7c494821ab8f386f1508f7e3e270ae6 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Fri, 7 Feb 2025 12:25:46 +0000 Subject: [PATCH 12/23] Apply autopep8 formatting --- .../qml/generative_modeling/transformation/test_min_max.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/modules/applications/qml/generative_modeling/transformation/test_min_max.py b/tests/modules/applications/qml/generative_modeling/transformation/test_min_max.py index 5ebb64e2..7b2ad3db 100644 --- a/tests/modules/applications/qml/generative_modeling/transformation/test_min_max.py +++ b/tests/modules/applications/qml/generative_modeling/transformation/test_min_max.py @@ -83,7 +83,7 @@ def test_reverse_transform(self): "KL": [0.1], # Example "best_parameter": [0.5], "store_dir_iter": "test_dir", - "circuit_transpiled": MagicMock() # Mock the circuit + "circuit_transpiled": MagicMock() # Mock the circuit } reverse_config = self.minmax_instance.reverse_transform(reverse_input_data) From 3f2bb5396c87537108ec56f46a73b1f213cf2717 Mon Sep 17 00:00:00 2001 From: GreshmaShaji Date: Fri, 7 Feb 2025 13:55:41 +0100 Subject: [PATCH 13/23] cleaning catched data --- .github/workflows/test.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0b2d53fc..79fb77c2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -32,6 +32,18 @@ jobs: - name: Install Dependencies run: pip install -r .settings/requirements_full.txt + + - name: Clean Python Cache + run: | + echo "Cleaning __pycache__ and *.pyc files..." + find . -type d -name "__pycache__" -exec rm -rf {} + + find . -type f -name "*.pyc" -delete + rm -rf .pytest_cache + + - name: Set PYTHONPATH + run: | + echo "PYTHONPATH=$(pwd)/src" >> $GITHUB_ENV + echo "Current PYTHONPATH: $PYTHONPATH" - name: Run Unit Tests run: python -m unittest discover . > unittest_results.log From bf012c435c32db97d13546ca96755b2021633295 Mon Sep 17 00:00:00 2001 From: GreshmaShaji Date: Fri, 7 Feb 2025 14:17:45 +0100 Subject: [PATCH 14/23] adjusted import path --- .github/workflows/test.yml | 12 -- .../optimization/acl/mappings/test_ising.py | 2 +- .../optimization/acl/mappings/test_qubo.py | 2 +- .../applications/optimization/acl/test_acl.py | 2 +- .../mis_temp/mappings/test_neutral_atom.py | 41 ++++++ .../optimization/mis_temp/test_mis.py | 76 +++++++++++ .../sat_temp/mappings/test_choiIsing.py | 65 ++++++++++ .../sat_temp/mappings/test_choiqubo.py | 55 ++++++++ .../sat_temp/mappings/test_dinneenising.py | 63 ++++++++++ .../sat_temp/mappings/test_dinneenqubo.py | 53 ++++++++ .../sat_temp/mappings/test_direct.py | 76 +++++++++++ .../sat_temp/mappings/test_qubovertqubo.py | 51 ++++++++ .../optimization/sat_temp/test_sat.py | 118 ++++++++++++++++++ .../circuits/test_circuit_cardinality.py | 2 +- .../circuits/test_circuit_copula.py | 2 +- .../circuits/test_circuit_standard.py | 2 +- .../data/data_handler/test_continuous_data.py | 2 +- .../data/data_handler/test_discrete_data.py | 6 +- .../test_custom_qiskit_noisy_backend.py | 2 +- .../mappings/test_library_pennylane.py | 8 +- .../mappings/test_library_qiskit.py | 8 +- .../test_preset_qiskit_noisy_backend.py | 6 +- .../metrics/test_metrics_generalization.py | 2 +- .../training/test_Inference.py | 2 +- .../training/test_inference.py | 2 +- .../transformation/test_MinMax.py | 102 +++++++++++++++ .../transformation/test_min_max.py | 8 +- tests/test_config_manager.py | 2 +- 28 files changed, 730 insertions(+), 42 deletions(-) create mode 100644 tests/modules/applications/optimization/mis_temp/mappings/test_neutral_atom.py create mode 100644 tests/modules/applications/optimization/mis_temp/test_mis.py create mode 100644 tests/modules/applications/optimization/sat_temp/mappings/test_choiIsing.py create mode 100644 tests/modules/applications/optimization/sat_temp/mappings/test_choiqubo.py create mode 100644 tests/modules/applications/optimization/sat_temp/mappings/test_dinneenising.py create mode 100644 tests/modules/applications/optimization/sat_temp/mappings/test_dinneenqubo.py create mode 100644 tests/modules/applications/optimization/sat_temp/mappings/test_direct.py create mode 100644 tests/modules/applications/optimization/sat_temp/mappings/test_qubovertqubo.py create mode 100644 tests/modules/applications/optimization/sat_temp/test_sat.py create mode 100644 tests/modules/applications/qml/generative_modeling/transformation/test_MinMax.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 79fb77c2..0b2d53fc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -32,18 +32,6 @@ jobs: - name: Install Dependencies run: pip install -r .settings/requirements_full.txt - - - name: Clean Python Cache - run: | - echo "Cleaning __pycache__ and *.pyc files..." - find . -type d -name "__pycache__" -exec rm -rf {} + - find . -type f -name "*.pyc" -delete - rm -rf .pytest_cache - - - name: Set PYTHONPATH - run: | - echo "PYTHONPATH=$(pwd)/src" >> $GITHUB_ENV - echo "Current PYTHONPATH: $PYTHONPATH" - name: Run Unit Tests run: python -m unittest discover . > unittest_results.log diff --git a/tests/modules/applications/optimization/acl/mappings/test_ising.py b/tests/modules/applications/optimization/acl/mappings/test_ising.py index 0d6a1d8b..69c9c768 100644 --- a/tests/modules/applications/optimization/acl/mappings/test_ising.py +++ b/tests/modules/applications/optimization/acl/mappings/test_ising.py @@ -2,7 +2,7 @@ import numpy as np from qiskit_optimization import QuadraticProgram -from src.modules.applications.optimization.acl.mappings.ising import Ising +from modules.applications.optimization.acl.mappings.ising import Ising class TestIsing(unittest.TestCase): diff --git a/tests/modules/applications/optimization/acl/mappings/test_qubo.py b/tests/modules/applications/optimization/acl/mappings/test_qubo.py index 82a701fa..b424bd68 100644 --- a/tests/modules/applications/optimization/acl/mappings/test_qubo.py +++ b/tests/modules/applications/optimization/acl/mappings/test_qubo.py @@ -2,7 +2,7 @@ import numpy as np from qiskit_optimization import QuadraticProgram -from src.modules.applications.optimization.acl.mappings.qubo import Qubo +from modules.applications.optimization.acl.mappings.qubo import Qubo class TestQubo(unittest.TestCase): diff --git a/tests/modules/applications/optimization/acl/test_acl.py b/tests/modules/applications/optimization/acl/test_acl.py index 6e2757d6..7bb1daa0 100644 --- a/tests/modules/applications/optimization/acl/test_acl.py +++ b/tests/modules/applications/optimization/acl/test_acl.py @@ -2,7 +2,7 @@ import os import pandas as pd from tempfile import TemporaryDirectory -from src.modules.applications.optimization.acl.acl import ACL +from modules.applications.optimization.acl.acl import ACL class TestACL(unittest.TestCase): diff --git a/tests/modules/applications/optimization/mis_temp/mappings/test_neutral_atom.py b/tests/modules/applications/optimization/mis_temp/mappings/test_neutral_atom.py new file mode 100644 index 00000000..fd32b9eb --- /dev/null +++ b/tests/modules/applications/optimization/mis_temp/mappings/test_neutral_atom.py @@ -0,0 +1,41 @@ +import unittest +import pickle + +from modules.applications.optimization.mis.mappings.neutral_atom import NeutralAtom + +class TestNeutralAtom(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.neutral_atom_instance = NeutralAtom() + with open("tests/modules/applications/optimization/MIS/mappings/MIS_test_graph.pkl", "rb") as file: + cls.graph = pickle.load(file) + cls.config = {} + + def test_get_requirements(self): + requirements = self.neutral_atom_instance.get_requirements() + expected_requirements = [{"name": "pulser", "version": "1.1.1"}] + for req in expected_requirements: + self.assertIn(req, requirements) + + def test_get_parameter_options(self): + options = self.neutral_atom_instance.get_parameter_options() + self.assertEqual(options, {}, "Expected parameter options to be an empty dictionary.") + + def test_map(self): + neutral_atom_problem, mapping_time = self.neutral_atom_instance.map(self.graph, self.config) + + self.assertIn("graph", neutral_atom_problem) + self.assertIn("register", neutral_atom_problem) + + self.assertIsNotNone(neutral_atom_problem["register"], "Expected a valid Pulser register.") + self.assertGreater(mapping_time, 0, "Mapping time should be positive.") + + def test_get_default_submodule(self): + # Test valid submodule retrieval + submodule = self.neutral_atom_instance.get_default_submodule("NeutralAtomMIS") + self.assertIsNotNone(submodule, "Expected 'NeutralAtomMIS' submodule to be returned.") + + # Test invalid submodule option + with self.assertRaises(NotImplementedError): + self.neutral_atom_instance.get_default_submodule("InvalidSubmodule") diff --git a/tests/modules/applications/optimization/mis_temp/test_mis.py b/tests/modules/applications/optimization/mis_temp/test_mis.py new file mode 100644 index 00000000..da72bc45 --- /dev/null +++ b/tests/modules/applications/optimization/mis_temp/test_mis.py @@ -0,0 +1,76 @@ +import unittest +import networkx as nx +import os +from tempfile import TemporaryDirectory +import logging + +from modules.applications.optimization.mis.mis import MIS + + +class TestMIS(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.mis_instance = MIS() + cls.config = {"size": 5, "spacing": 0.5, "filling_fraction": 0.5} + cls.graph = cls.mis_instance.generate_problem(cls.config) + + @classmethod + def tearDownClass(cls): + del cls.mis_instance + + def test_get_solution_quality_unit(self): + unit = self.mis_instance.get_solution_quality_unit() + self.assertEqual(unit, "Set size", "Incorrect solution quality unit.") + + def test_get_default_submodule(self): + submodule = self.mis_instance.get_default_submodule("NeutralAtom") + self.assertIsNotNone(submodule, "Expected 'NeutralAtom' submodule to be returned.") + with self.assertRaises(NotImplementedError): + self.mis_instance.get_default_submodule("InvalidOption") + + def test_get_parameter_options(self): + options = self.mis_instance.get_parameter_options() + self.assertIn("size", options) + self.assertIn("graph_type", options) + + def test_generate_problem(self): + # Generate with valid configuration + graph = self.mis_instance.generate_problem(self.config) + self.assertIsInstance(graph, nx.Graph) + self.assertGreaterEqual(len(graph.nodes), 1, "Graph should have at least 1 node.") + self.assertGreaterEqual(len(graph.edges), 0, "Graph should have non-negative edges.") + + def test_process_solution(self): + solution = [1, 3] + processed_solution, processing_time = self.mis_instance.process_solution(solution) + self.assertEqual(processed_solution, solution, "Processed solution does not match input.") + self.assertGreaterEqual(processing_time, 0, "Processing time should be positive.") + + def test_validate(self): + logging.disable(logging.WARNING) + self.mis_instance.graph = nx.Graph() + self.mis_instance.graph.add_nodes_from([0, 1, 2]) + self.mis_instance.graph.add_edges_from([(0, 1), (1, 2)]) + + valid_solution = [0, 2] + is_valid, validation_time = self.mis_instance.validate(valid_solution) + self.assertTrue(is_valid, f"Expected valid solution: {valid_solution}") + self.assertGreater(validation_time, 0, "Validation time should be positive.") + + def test_evaluate(self): + solution = list(self.graph.nodes)[:3] + set_size, eval_time = self.mis_instance.evaluate(solution) + self.assertEqual(set_size, len(solution), "Set size mismatch.") + self.assertGreater(eval_time, 0, "Evaluation time should be positive.") + + def test_save(self): + with TemporaryDirectory() as temp_dir: + # Save the graph + self.mis_instance.save(temp_dir, iter_count=1) + + # Check that the file exists and is non-empty + file_path = f"{temp_dir}/graph_iter_1.gpickle" + self.assertTrue(os.path.isfile(file_path), "Graph file not saved.") + self.assertTrue(file_path.endswith(".gpickle"), "File extension mismatch.") + self.assertGreater(os.path.getsize(file_path), 0, "Graph file is empty.") diff --git a/tests/modules/applications/optimization/sat_temp/mappings/test_choiIsing.py b/tests/modules/applications/optimization/sat_temp/mappings/test_choiIsing.py new file mode 100644 index 00000000..4b55363e --- /dev/null +++ b/tests/modules/applications/optimization/sat_temp/mappings/test_choiIsing.py @@ -0,0 +1,65 @@ +import unittest +import numpy as np +from nnf import Var, And, Or + +from modules.applications.optimization.sat.mappings.choiising import ChoiIsing + + +class TestChoiIsing(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.choi_ising_instance = ChoiIsing() + hard_constraints = And([Or([Var("L1"), ~Var("L2")]), Or([~Var("L3"), Var("L4")])]) + soft_constraints = [Or([Var("L5"), ~Var("L6")])] + cls.problem = (hard_constraints, soft_constraints) + cls.config = {"hard_reward": 0.9, "soft_reward": 1.0} + + def test_get_requirements(self): + requirements = self.choi_ising_instance.get_requirements() + self.assertIn({"name": "numpy", "version": "1.26.4"}, requirements) + self.assertIn({"name": "dimod", "version": "0.12.18"}, requirements) + + def test_get_parameter_options(self): + options = self.choi_ising_instance.get_parameter_options() + self.assertIn("hard_reward", options) + self.assertIn("soft_reward", options) + + def test_map(self): + ising_mapping, mapping_time = self.choi_ising_instance.map(self.problem, self.config) + + # Check that mapping results contain the expected keys + self.assertIn("J", ising_mapping) + self.assertIn("t", ising_mapping) + + # Check that J and t have the correct types and shapes + self.assertIsInstance(ising_mapping["J"], np.ndarray) + self.assertIsInstance(ising_mapping["t"], np.ndarray) + self.assertEqual(ising_mapping["J"].shape[0], ising_mapping["J"].shape[1], "J matrix should be square.") + self.assertEqual(len(ising_mapping["t"]), ising_mapping["J"].shape[0], + "t vector length should match J matrix size.") + + self.assertGreater(mapping_time, 0, "Mapping time should be greater than zero.") + + def test_reverse_map(self): + # Create a mock solution + mock_solution = [1, 0, 1, 1, 0] + + # Run reverse_map to convert the solution back + reverse_mapped_solution, reverse_mapping_time = self.choi_ising_instance.reverse_map(mock_solution) + + self.assertIsInstance(reverse_mapped_solution, dict) + self.assertGreater(reverse_mapping_time, 0, "Reverse mapping time should be greater than zero.") + + def test_get_default_submodule(self): + # Check QAOA submodule retrieval + submodule = self.choi_ising_instance.get_default_submodule("QAOA") + self.assertIsNotNone(submodule, "QAOA submodule should not be None") + + # Check PennylaneQAOA submodule retrieval + submodule = self.choi_ising_instance.get_default_submodule("PennylaneQAOA") + self.assertIsNotNone(submodule, "PennylaneQAOA submodule should not be None") + + # Check invalid option raises NotImplementedError + with self.assertRaises(NotImplementedError): + self.choi_ising_instance.get_default_submodule("InvalidSubmodule") diff --git a/tests/modules/applications/optimization/sat_temp/mappings/test_choiqubo.py b/tests/modules/applications/optimization/sat_temp/mappings/test_choiqubo.py new file mode 100644 index 00000000..2bd25e90 --- /dev/null +++ b/tests/modules/applications/optimization/sat_temp/mappings/test_choiqubo.py @@ -0,0 +1,55 @@ +import unittest +from nnf import Var, And, Or + +from modules.applications.optimization.sat.mappings.choiqubo import ChoiQUBO + + +class TestChoiQUBO(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.choi_qubo_instance = ChoiQUBO() + # Define a problem with both hard and soft constraints + hard_constraints = And([Or([Var("L1"), ~Var("L2")]), Or([~Var("L3"), Var("L4")])]) + soft_constraints = [Or([Var("L5"), ~Var("L6")])] + cls.problem = (hard_constraints, soft_constraints) + cls.config = {"hard_reward": 0.9, "soft_reward": 1.0} + + def test_get_requirements(self): + requirements = self.choi_qubo_instance.get_requirements() + self.assertIn({"name": "nnf", "version": "0.4.1"}, requirements) + + def test_get_parameter_options(self): + options = self.choi_qubo_instance.get_parameter_options() + self.assertIn("hard_reward", options) + self.assertIn("soft_reward", options) + + def test_map(self): + qubo_mapping, mapping_time = self.choi_qubo_instance.map(self.problem, self.config) + + self.assertIn("Q", qubo_mapping) + + q_matrix = qubo_mapping["Q"] + self.assertIsInstance(q_matrix, dict) + for key, value in q_matrix.items(): + self.assertIsInstance(key, tuple, "Expected key to be a tuple") + self.assertTrue(isinstance( + value, float) or isinstance(value, int), "Expected value to be a float or int" + ) + + self.assertGreater(mapping_time, 0, "Mapping time should be greater than zero.") + + def test_reverse_map(self): + mock_solution = {i: 1 if i % 2 == 0 else 0 for i in range(len(self.choi_qubo_instance.reverse_dict))} + + reverse_mapped_solution, reverse_mapping_time = self.choi_qubo_instance.reverse_map(mock_solution) + + self.assertIsInstance(reverse_mapped_solution, dict) + self.assertGreater(reverse_mapping_time, 0, "Reverse mapping time should be greater than zero.") + + def test_get_default_submodule(self): + submodule = self.choi_qubo_instance.get_default_submodule("Annealer") + self.assertIsNotNone(submodule, "Annealer submodule should not be None") + + with self.assertRaises(NotImplementedError): + self.choi_qubo_instance.get_default_submodule("InvalidSubmodule") diff --git a/tests/modules/applications/optimization/sat_temp/mappings/test_dinneenising.py b/tests/modules/applications/optimization/sat_temp/mappings/test_dinneenising.py new file mode 100644 index 00000000..810512ef --- /dev/null +++ b/tests/modules/applications/optimization/sat_temp/mappings/test_dinneenising.py @@ -0,0 +1,63 @@ +import unittest +import numpy as np +from nnf import Var, And, Or + +from modules.applications.optimization.sat.mappings.dinneenising import DinneenIsing + + +class TestDinneenIsing(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.dinneen_ising_instance = DinneenIsing() + # Define a SAT problem with both hard and soft constraints + hard_constraints = And([Or([Var("L1"), ~Var("L2")]), Or([~Var("L3"), Var("L4")])]) + soft_constraints = [Or([Var("L5"), ~Var("L6")])] + cls.problem = (hard_constraints, soft_constraints) + cls.config = {"lagrange": 1.0} + + def test_get_requirements(self): + requirements = self.dinneen_ising_instance.get_requirements() + self.assertIn({"name": "nnf", "version": "0.4.1"}, requirements) + self.assertIn({"name": "numpy", "version": "1.26.4"}, requirements) + self.assertIn({"name": "dimod", "version": "0.12.18"}, requirements) + + def test_get_parameter_options(self): + options = self.dinneen_ising_instance.get_parameter_options() + self.assertIn("lagrange", options) + + def test_map(self): + ising_mapping, mapping_time = self.dinneen_ising_instance.map(self.problem, self.config) + + # Check that mapping results contain the expected "J" and "t" keys + self.assertIn("J", ising_mapping) + self.assertIn("t", ising_mapping) + + # Check that J and t are numpy arrays + j_matrix = ising_mapping["J"] + t_vector = ising_mapping["t"] + self.assertIsInstance(j_matrix, np.ndarray) + self.assertIsInstance(t_vector, np.ndarray) + self.assertEqual(j_matrix.shape[0], j_matrix.shape[1], "J matrix should be square.") + self.assertEqual(len(t_vector), j_matrix.shape[0], "t vector length should match J matrix size.") + + self.assertGreater(mapping_time, 0, "Mapping time should be greater than zero.") + + def test_reverse_map(self): + mock_solution = {i: 1 if i % 2 == 0 else 0 for i in range(len(self.dinneen_ising_instance.problem[0].vars()))} + + reverse_mapped_solution, reverse_mapping_time = self.dinneen_ising_instance.reverse_map(mock_solution) + + # Verify that the output of reverse_map is a dictionary + self.assertIsInstance(reverse_mapped_solution, dict) + self.assertGreater(reverse_mapping_time, 0, "Reverse mapping time should be greater than zero.") + + def test_get_default_submodule(self): + submodule = self.dinneen_ising_instance.get_default_submodule("QAOA") + self.assertIsNotNone(submodule, "QAOA submodule should not be None") + + submodule = self.dinneen_ising_instance.get_default_submodule("PennylaneQAOA") + self.assertIsNotNone(submodule, "PennylaneQAOA submodule should not be None") + + with self.assertRaises(NotImplementedError): + self.dinneen_ising_instance.get_default_submodule("InvalidSubmodule") diff --git a/tests/modules/applications/optimization/sat_temp/mappings/test_dinneenqubo.py b/tests/modules/applications/optimization/sat_temp/mappings/test_dinneenqubo.py new file mode 100644 index 00000000..b626eb0e --- /dev/null +++ b/tests/modules/applications/optimization/sat_temp/mappings/test_dinneenqubo.py @@ -0,0 +1,53 @@ +import unittest +from nnf import Var, And, Or + +from modules.applications.optimization.sat.mappings.dinneenqubo import DinneenQUBO + + +class TestDinneenQUBO(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.dinneen_qubo_instance = DinneenQUBO() + hard_constraints = And([Or([Var("L1"), ~Var("L2")]), Or([~Var("L3"), Var("L4")])]) + soft_constraints = [Or([Var("L5"), ~Var("L6")])] + cls.problem = (hard_constraints, soft_constraints) + cls.config = {"lagrange": 1.0} + + def test_get_requirements(self): + requirements = self.dinneen_qubo_instance.get_requirements() + self.assertIn({"name": "nnf", "version": "0.4.1"}, requirements) + + def test_get_parameter_options(self): + options = self.dinneen_qubo_instance.get_parameter_options() + self.assertIn("lagrange", options) + + def test_map(self): + qubo_mapping, mapping_time = self.dinneen_qubo_instance.map(self.problem, self.config) + + self.assertIn("Q", qubo_mapping) + + q_matrix = qubo_mapping["Q"] + self.assertIsInstance(q_matrix, dict) + for key, value in q_matrix.items(): + self.assertIsInstance(key, tuple, "Expected key to be a tuple") + self.assertTrue(isinstance( + value, float) or isinstance(value, int), "Expected value to be a float or int" + ) + + self.assertGreater(mapping_time, 0, "Mapping time should be greater than zero.") + + def test_reverse_map(self): + mock_solution = {i: 1 if i % 2 == 0 else 0 for i in range(self.dinneen_qubo_instance.nr_vars)} + + reverse_mapped_solution, reverse_mapping_time = self.dinneen_qubo_instance.reverse_map(mock_solution) + + self.assertIsInstance(reverse_mapped_solution, dict) + self.assertGreater(reverse_mapping_time, 0, "Reverse mapping time should be greater than zero.") + + def test_get_default_submodule(self): + submodule = self.dinneen_qubo_instance.get_default_submodule("Annealer") + self.assertIsNotNone(submodule, "Annealer submodule should not be None") + + with self.assertRaises(NotImplementedError): + self.dinneen_qubo_instance.get_default_submodule("InvalidSubmodule") diff --git a/tests/modules/applications/optimization/sat_temp/mappings/test_direct.py b/tests/modules/applications/optimization/sat_temp/mappings/test_direct.py new file mode 100644 index 00000000..d3b42bee --- /dev/null +++ b/tests/modules/applications/optimization/sat_temp/mappings/test_direct.py @@ -0,0 +1,76 @@ +import unittest +from nnf import And, Var, Or +from pysat.formula import WCNF + +from modules.applications.optimization.sat.mappings.direct import Direct + + +class TestDirect(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.direct_instance = Direct() + + # Hard constraints as CNF: Conjunction of disjunctions + cls.hard_constraints = And([Or([Var("L0")]), Or([~Var("L1")])]) # A ∧ ¬B + + # Soft constraints as CNF: List of disjunctions + cls.soft_constraints = [Or([Var("L2")]), Or([~Var("L3")])] + cls.problem = (cls.hard_constraints, cls.soft_constraints) + + def test_get_requirements(self): + requirements = self.direct_instance.get_requirements() + expected_requirements = [ + {"name": "nnf", "version": "0.4.1"}, + {"name": "python-sat", "version": "1.8.dev13"}, + ] + for req in expected_requirements: + self.assertIn(req, requirements) + + def test_get_parameter_options(self): + options = self.direct_instance.get_parameter_options() + self.assertEqual(options, {}, "Expected parameter options to be an empty dictionary.") + + def test_map(self): + # Map the SAT problem to pysat + mapped_problem, mapping_time = self.direct_instance.map(self.problem, config={}) + + # Check that the result is a WCNF instance + self.assertIsInstance(mapped_problem, WCNF, "Expected a WCNF instance.") + self.assertGreater(mapping_time, 0, "Mapping time should be positive.") + + # Check the number of hard and soft constraints + self.assertEqual(len(mapped_problem.hard), 2, "Expected 1 hard constraint.") + self.assertEqual(len(mapped_problem.soft), 2, "Expected 2 soft constraints.") + + def test_reverse_map(self): + solution = [1, -2, 3, -4, 5] # Example literals + reverse_mapped_solution, reverse_mapping_time = self.direct_instance.reverse_map(solution) + + # Check that the result is a dictionary + self.assertIsInstance( + reverse_mapped_solution, dict, "Expected a dictionary as the reverse mapping result." + ) + self.assertGreater(reverse_mapping_time, 0, "Reverse mapping time should be positive.") + + # Verify that the literals are correctly mapped + expected_solution = { + "L0": True, # L1 (positive) + "L1": False, # L2 (negative) + "L2": True, # L3 (positive) + "L3": False, # L4 (negative) + "L4": True, # L5 (positive) + } + self.assertEqual(reverse_mapped_solution, expected_solution, "Reverse mapping result is incorrect.") + + def test_get_default_submodule(self): + # Test valid submodules + submodule = self.direct_instance.get_default_submodule("ClassicalSAT") + self.assertIsNotNone(submodule, "Expected 'ClassicalSAT' submodule to be returned.") + + submodule = self.direct_instance.get_default_submodule("RandomSAT") + self.assertIsNotNone(submodule, "Expected 'RandomSAT' submodule to be returned.") + + # Test invalid submodule option + with self.assertRaises(NotImplementedError): + self.direct_instance.get_default_submodule("InvalidSubmodule") diff --git a/tests/modules/applications/optimization/sat_temp/mappings/test_qubovertqubo.py b/tests/modules/applications/optimization/sat_temp/mappings/test_qubovertqubo.py new file mode 100644 index 00000000..397da327 --- /dev/null +++ b/tests/modules/applications/optimization/sat_temp/mappings/test_qubovertqubo.py @@ -0,0 +1,51 @@ +import unittest +from nnf import Var, And, Or + +from modules.applications.optimization.sat.mappings.qubovertqubo import QubovertQUBO + + +class TestQubovertQUBO(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.qubovert_instance = QubovertQUBO() + hard_constraints = And([Or([Var("L1"), ~Var("L2")]), Or([~Var("L3"), Var("L4")])]) + soft_constraints = [Or([Var("L5"), ~Var("L6")])] + cls.problem = (hard_constraints, soft_constraints) + cls.config = {"lagrange": 1.0} + + def test_get_requirements(self): + requirements = self.qubovert_instance.get_requirements() + self.assertIn({"name": "nnf", "version": "0.4.1"}, requirements) + self.assertIn({"name": "qubovert", "version": "1.2.5"}, requirements) + + def test_get_parameter_options(self): + options = self.qubovert_instance.get_parameter_options() + self.assertIn("lagrange", options) + + def test_map(self): + qubo_mapping, mapping_time = self.qubovert_instance.map(self.problem, self.config) + + self.assertIn("Q", qubo_mapping) + + q_dict = qubo_mapping["Q"] + self.assertIsInstance(q_dict, dict) + for key, value in q_dict.items(): + self.assertIsInstance(key, tuple, "Expected key to be a tuple") + self.assertTrue( + isinstance(value, float) or isinstance(value, int), "Expected value to be a float or int" + ) + + self.assertGreater(mapping_time, 0, "Mapping time should be greater than zero.") + + def test_reverse_map(self): + mock_solution = {i: 1 if i % 2 == 0 else 0 for i in range(self.qubovert_instance.nr_vars)} + + reverse_mapped_solution, reverse_mapping_time = self.qubovert_instance.reverse_map(mock_solution) + + self.assertIsInstance(reverse_mapped_solution, dict) + self.assertGreater(reverse_mapping_time, 0, "Reverse mapping time should be greater than zero.") + + def test_get_default_submodule(self): + submodule = self.qubovert_instance.get_default_submodule("Annealer") + self.assertIsNotNone(submodule, "Annealer submodule should not be None") diff --git a/tests/modules/applications/optimization/sat_temp/test_sat.py b/tests/modules/applications/optimization/sat_temp/test_sat.py new file mode 100644 index 00000000..0195da63 --- /dev/null +++ b/tests/modules/applications/optimization/sat_temp/test_sat.py @@ -0,0 +1,118 @@ +import unittest +import os +import nnf +from tempfile import TemporaryDirectory + +from modules.applications.optimization.sat.sat import SAT + + +class TestSAT(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.sat_instance = SAT() + cls.problem = cls.sat_instance.generate_problem( + { + "variables": 10, "clvar_ratio_cons": 2, + "clvar_ratio_test": 2, "problem_set": 5, + "max_tries": 100 + } + ) + + @classmethod + def tearDownClass(cls): + del cls.sat_instance + + def test_get_requirements(self): + requirements = self.sat_instance.get_requirements() + self.assertIn({"name": "nnf", "version": "0.4.1"}, requirements) + self.assertIn({"name": "numpy", "version": "1.26.4"}, requirements) + + def test_get_default_submodule(self): + submodules = [ + "QubovertQUBO", "Direct", "ChoiQUBO", "ChoiIsing", "DinneenQUBO", "DinneenIsing" + ] + for option in submodules: + with self.subTest(option=option): + submodule = self.sat_instance.get_default_submodule(option) + self.assertIsNotNone(submodule, f"{option} submodule should not be None") + + with self.assertRaises(NotImplementedError): + self.sat_instance.get_default_submodule("DirectX") + + def test_get_parameter_options(self): + options = self.sat_instance.get_parameter_options() + self.assertIn("variables", options) + self.assertIn("clvar_ratio_cons", options) + + def test_generate_problem(self): + # Define a configuration for testing + config = { + "variables": 10, + "clvar_ratio_cons": 2, + "clvar_ratio_test": 2, + "problem_set": 1, + "max_tries": 5000 + } + + # Generate the problem + hard, soft = self.sat_instance.generate_problem(config) + + # Check the types of the output + self.assertIsInstance(hard, nnf.And, "Hard constraints should be of type nnf.And.") + self.assertIsInstance(soft, list, "Soft constraints should be a list.") + + # Validate the structure and cardinalities + hard_clauses = list(hard) # Extract the list of clauses from the And object + self.assertEqual(len(hard_clauses), round(config["clvar_ratio_cons"] * config["variables"]), + "Incorrect number of hard constraints.") + self.assertEqual(len(soft), round(config["clvar_ratio_test"] * config["variables"]), + "Incorrect number of soft constraints.") + + def test_validate(self): + self.sat_instance.generate_problem( + {"variables": 10, "clvar_ratio_cons": 2, "clvar_ratio_test": 2, "problem_set": 5, "max_tries": 100} + ) + right_solution = {'L5': 1, 'L8': 0, 'L7': 0, 'L1': 1, 'L4': 1, 'L0': 1, 'L9': 1, 'L3': 1, 'L6': 1, 'L2': 1} + self.assertEqual(self.sat_instance.validate(right_solution)[0], True) + + wrong_solution = right_solution.copy() + wrong_solution["L5"] = 1 - wrong_solution["L5"] + self.assertEqual(self.sat_instance.validate(wrong_solution)[0], False) + + # Edge Case: Empty solution + empty_solution = {} + with self.assertRaises(ValueError): + self.sat_instance.validate(empty_solution) + + # Edge Case: Partial solution + partial_solution = {'L1': 1, 'L2': 0} + with self.assertRaises(ValueError): + self.sat_instance.validate(partial_solution) + + def test_evaluate(self): + solution = {'L5': 1, 'L8': 0, 'L7': 0, 'L1': 1, 'L4': 1, 'L0': 1, 'L9': 1, 'L3': 1, 'L6': 1, 'L2': 1} + self.assertAlmostEqual(self.sat_instance.evaluate(solution)[0], 0.95, delta=0.05) + + # Edge Case: All True + all_true_solution = {f'L{i}': 1 for i in range(10)} + self.assertGreaterEqual(self.sat_instance.evaluate(all_true_solution)[0], 0) + + # Edge Case: All False + all_false_solution = {f'L{i}': 0 for i in range(10)} + self.assertLessEqual(self.sat_instance.evaluate(all_false_solution)[0], 1) + + def test_save(self): + with TemporaryDirectory() as temp_dir: + self.sat_instance.save(temp_dir, 1) + + file_paths = [f"{temp_dir}/constraints_iter_1.cnf", f"{temp_dir}/tests_iter_1.cnf"] + + for file_path in file_paths: + self.assertTrue(os.path.isfile(file_path)) + + file_extension = os.path.splitext(file_path)[-1].lower() + self.assertEqual(file_extension, ".cnf") + + file_length = os.stat(file_path).st_size == 0 + self.assertFalse(file_length) diff --git a/tests/modules/applications/qml/generative_modeling/circuits/test_circuit_cardinality.py b/tests/modules/applications/qml/generative_modeling/circuits/test_circuit_cardinality.py index 97ca5e03..d84bcd75 100644 --- a/tests/modules/applications/qml/generative_modeling/circuits/test_circuit_cardinality.py +++ b/tests/modules/applications/qml/generative_modeling/circuits/test_circuit_cardinality.py @@ -1,6 +1,6 @@ import unittest -from src.modules.applications.qml.generative_modeling.circuits.circuit_cardinality import CircuitCardinality +from modules.applications.qml.generative_modeling.circuits.circuit_cardinality import CircuitCardinality class TestCircuitCardinality(unittest.TestCase): diff --git a/tests/modules/applications/qml/generative_modeling/circuits/test_circuit_copula.py b/tests/modules/applications/qml/generative_modeling/circuits/test_circuit_copula.py index 927d3839..9f0c17ff 100644 --- a/tests/modules/applications/qml/generative_modeling/circuits/test_circuit_copula.py +++ b/tests/modules/applications/qml/generative_modeling/circuits/test_circuit_copula.py @@ -1,6 +1,6 @@ import unittest -from src.modules.applications.qml.generative_modeling.circuits.circuit_copula import CircuitCopula +from modules.applications.qml.generative_modeling.circuits.circuit_copula import CircuitCopula class TestCircuitCopula(unittest.TestCase): diff --git a/tests/modules/applications/qml/generative_modeling/circuits/test_circuit_standard.py b/tests/modules/applications/qml/generative_modeling/circuits/test_circuit_standard.py index 5bb540fb..b9f8f4dd 100644 --- a/tests/modules/applications/qml/generative_modeling/circuits/test_circuit_standard.py +++ b/tests/modules/applications/qml/generative_modeling/circuits/test_circuit_standard.py @@ -1,6 +1,6 @@ import unittest -from src.modules.applications.qml.generative_modeling.circuits.circuit_standard import CircuitStandard +from modules.applications.qml.generative_modeling.circuits.circuit_standard import CircuitStandard class TestCircuitStandard(unittest.TestCase): diff --git a/tests/modules/applications/qml/generative_modeling/data/data_handler/test_continuous_data.py b/tests/modules/applications/qml/generative_modeling/data/data_handler/test_continuous_data.py index 291b13d4..722cabea 100644 --- a/tests/modules/applications/qml/generative_modeling/data/data_handler/test_continuous_data.py +++ b/tests/modules/applications/qml/generative_modeling/data/data_handler/test_continuous_data.py @@ -2,7 +2,7 @@ from unittest.mock import patch import numpy as np -from src.modules.applications.qml.generative_modeling.data.data_handler.continuous_data import ContinuousData +from modules.applications.qml.generative_modeling.data.data_handler.continuous_data import ContinuousData class TestContinuousData(unittest.TestCase): diff --git a/tests/modules/applications/qml/generative_modeling/data/data_handler/test_discrete_data.py b/tests/modules/applications/qml/generative_modeling/data/data_handler/test_discrete_data.py index 3b15bdc5..574f8d5a 100644 --- a/tests/modules/applications/qml/generative_modeling/data/data_handler/test_discrete_data.py +++ b/tests/modules/applications/qml/generative_modeling/data/data_handler/test_discrete_data.py @@ -2,9 +2,9 @@ from unittest.mock import MagicMock import numpy as np -from src.modules.applications.qml.generative_modeling.data.data_handler.discrete_data import DiscreteData -from src.modules.applications.qml.generative_modeling.circuits.circuit_cardinality import CircuitCardinality -from src.modules.applications.qml.generative_modeling.metrics.metrics_generalization import MetricsGeneralization +from modules.applications.qml.generative_modeling.data.data_handler.discrete_data import DiscreteData +from modules.applications.qml.generative_modeling.circuits.circuit_cardinality import CircuitCardinality +from modules.applications.qml.generative_modeling.metrics.metrics_generalization import MetricsGeneralization class TestDiscreteData(unittest.TestCase): diff --git a/tests/modules/applications/qml/generative_modeling/mappings/test_custom_qiskit_noisy_backend.py b/tests/modules/applications/qml/generative_modeling/mappings/test_custom_qiskit_noisy_backend.py index cb53a4ab..4c3216e7 100644 --- a/tests/modules/applications/qml/generative_modeling/mappings/test_custom_qiskit_noisy_backend.py +++ b/tests/modules/applications/qml/generative_modeling/mappings/test_custom_qiskit_noisy_backend.py @@ -6,7 +6,7 @@ from qiskit_aer.noise import NoiseModel from qiskit.transpiler import CouplingMap, Layout -from src.modules.applications.qml.generative_modeling.mappings.custom_qiskit_noisy_backend import CustomQiskitNoisyBackend +from modules.applications.qml.generative_modeling.mappings.custom_qiskit_noisy_backend import CustomQiskitNoisyBackend class TestCustomQiskitNoisyBackend(unittest.TestCase): diff --git a/tests/modules/applications/qml/generative_modeling/mappings/test_library_pennylane.py b/tests/modules/applications/qml/generative_modeling/mappings/test_library_pennylane.py index 6b2f9744..1ff44542 100644 --- a/tests/modules/applications/qml/generative_modeling/mappings/test_library_pennylane.py +++ b/tests/modules/applications/qml/generative_modeling/mappings/test_library_pennylane.py @@ -2,10 +2,10 @@ from unittest.mock import patch, MagicMock import numpy as np -from src.modules.applications.qml.generative_modeling.mappings.library_pennylane import LibraryPennylane -from src.modules.applications.qml.generative_modeling.training.qcbm import QCBM -from src.modules.applications.qml.generative_modeling.training.qgan import QGAN -from src.modules.applications.qml.generative_modeling.training.inference import Inference +from modules.applications.qml.generative_modeling.mappings.library_pennylane import LibraryPennylane +from modules.applications.qml.generative_modeling.training.qcbm import QCBM +from modules.applications.qml.generative_modeling.training.qgan import QGAN +from modules.applications.qml.generative_modeling.training.inference import Inference class TestLibraryPennylane(unittest.TestCase): diff --git a/tests/modules/applications/qml/generative_modeling/mappings/test_library_qiskit.py b/tests/modules/applications/qml/generative_modeling/mappings/test_library_qiskit.py index a1fb317a..a7478468 100644 --- a/tests/modules/applications/qml/generative_modeling/mappings/test_library_qiskit.py +++ b/tests/modules/applications/qml/generative_modeling/mappings/test_library_qiskit.py @@ -4,10 +4,10 @@ import numpy as np from qiskit_aer import AerSimulator -from src.modules.applications.qml.generative_modeling.mappings.library_qiskit import LibraryQiskit -from src.modules.applications.qml.generative_modeling.training.qcbm import QCBM -from src.modules.applications.qml.generative_modeling.training.qgan import QGAN -from src.modules.applications.qml.generative_modeling.training.inference import Inference +from modules.applications.qml.generative_modeling.mappings.library_qiskit import LibraryQiskit +from modules.applications.qml.generative_modeling.training.qcbm import QCBM +from modules.applications.qml.generative_modeling.training.qgan import QGAN +from modules.applications.qml.generative_modeling.training.inference import Inference class TestLibraryQiskit(unittest.TestCase): diff --git a/tests/modules/applications/qml/generative_modeling/mappings/test_preset_qiskit_noisy_backend.py b/tests/modules/applications/qml/generative_modeling/mappings/test_preset_qiskit_noisy_backend.py index 918eaca3..be74fd9a 100644 --- a/tests/modules/applications/qml/generative_modeling/mappings/test_preset_qiskit_noisy_backend.py +++ b/tests/modules/applications/qml/generative_modeling/mappings/test_preset_qiskit_noisy_backend.py @@ -5,7 +5,7 @@ from qiskit import QuantumCircuit from qiskit_aer import AerSimulator -from src.modules.applications.qml.generative_modeling.mappings.preset_qiskit_noisy_backend import PresetQiskitNoisyBackend +from modules.applications.qml.generative_modeling.mappings.preset_qiskit_noisy_backend import PresetQiskitNoisyBackend class TestPresetQiskitNoisyBackend(unittest.TestCase): @@ -152,7 +152,7 @@ def test_get_transpile_routine(self): "qiskit_aer.Aer.get_backend" ) @patch( - "src.modules.applications.qml.generative_modeling.mappings.preset_qiskit_noisy_backend." + "modules.applications.qml.generative_modeling.mappings.preset_qiskit_noisy_backend." "PresetQiskitNoisyBackend.get_FakeBackend" ) def test_decompile_noisy_config(self, mock_get_fake_backend, mock_get_backend): @@ -221,7 +221,7 @@ def test_log_backend_info(self, mock_logging): @patch("qiskit_aer.noise.NoiseModel.from_backend") @patch("qiskit_aer.AerSimulator.from_backend") @patch("qiskit_aer.Aer.get_backend") - @patch("src.modules.applications.qml.generative_modeling.mappings.preset_qiskit_noisy_backend.FakeProviderForBackendV2") + @patch("modules.applications.qml.generative_modeling.mappings.preset_qiskit_noisy_backend.FakeProviderForBackendV2") def test_get_FakeBackend(self, mock_provider, mock_aer_get_backend, mock_simulator_from_backend, mock_noise_model): mock_backend = MagicMock() mock_backend.num_qubits = 5 diff --git a/tests/modules/applications/qml/generative_modeling/metrics/test_metrics_generalization.py b/tests/modules/applications/qml/generative_modeling/metrics/test_metrics_generalization.py index cbb808a5..c49c3188 100644 --- a/tests/modules/applications/qml/generative_modeling/metrics/test_metrics_generalization.py +++ b/tests/modules/applications/qml/generative_modeling/metrics/test_metrics_generalization.py @@ -2,7 +2,7 @@ import numpy as np import math -from src.modules.applications.qml.generative_modeling.metrics.metrics_generalization import MetricsGeneralization +from modules.applications.qml.generative_modeling.metrics.metrics_generalization import MetricsGeneralization class TestMetricsGeneralization(unittest.TestCase): diff --git a/tests/modules/applications/qml/generative_modeling/training/test_Inference.py b/tests/modules/applications/qml/generative_modeling/training/test_Inference.py index 11fa3b09..b704b4a4 100644 --- a/tests/modules/applications/qml/generative_modeling/training/test_Inference.py +++ b/tests/modules/applications/qml/generative_modeling/training/test_Inference.py @@ -2,7 +2,7 @@ from unittest.mock import MagicMock, patch import numpy as np -from src.modules.applications.qml.generative_modeling.training.inference import Inference +from modules.applications.qml.generative_modeling.training.inference import Inference class TestInference(unittest.TestCase): diff --git a/tests/modules/applications/qml/generative_modeling/training/test_inference.py b/tests/modules/applications/qml/generative_modeling/training/test_inference.py index 11fa3b09..b704b4a4 100644 --- a/tests/modules/applications/qml/generative_modeling/training/test_inference.py +++ b/tests/modules/applications/qml/generative_modeling/training/test_inference.py @@ -2,7 +2,7 @@ from unittest.mock import MagicMock, patch import numpy as np -from src.modules.applications.qml.generative_modeling.training.inference import Inference +from modules.applications.qml.generative_modeling.training.inference import Inference class TestInference(unittest.TestCase): diff --git a/tests/modules/applications/qml/generative_modeling/transformation/test_MinMax.py b/tests/modules/applications/qml/generative_modeling/transformation/test_MinMax.py new file mode 100644 index 00000000..f19a2e24 --- /dev/null +++ b/tests/modules/applications/qml/generative_modeling/transformation/test_MinMax.py @@ -0,0 +1,102 @@ +import unittest +import numpy as np +from unittest.mock import patch, MagicMock + +from modules.applications.qml.generative_modeling.transformations.transformation import Transformation +from modules.applications.qml.generative_modeling.transformations.min_max import MinMax +from modules.applications.qml.generative_modeling.circuits.circuit_standard import CircuitStandard +from modules.applications.qml.generative_modeling.circuits.circuit_cardinality import CircuitCardinality + + +class TestMinMax(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.minmax_instance = MinMax() + cls.sample_input_data = { + "dataset_name": "TestDataset", + "dataset": np.random.rand(100, 3), + "n_qubits": 6, + "train_size": 80, + "store_dir_iter": "/tmp/test" + } + cls.sample_config = {} + cls.minmax_instance.grid_shape = 4 # Example grid shape + cls.minmax_instance.dataset_name = "TestDataset" + cls.minmax_instance.histogram_train = np.array([0.2, 0.3, 0.1, 0.4]) + cls.minmax_instance.histogram_train_original = np.array([0.25, 0.25, 0.25, 0.25]) + cls.minmax_instance.transform_config = {"n_registers": 2} + + def test_get_requirements(self): + requirements = self.minmax_instance.get_requirements() + expected_requirements = [{"name": "numpy", "version": "1.26.4"}] + self.assertEqual(requirements, expected_requirements) + + def test_get_default_submodule(self): + submodule = self.minmax_instance.get_default_submodule("CircuitStandard") + self.assertIsInstance(submodule, CircuitStandard) + + submodule = self.minmax_instance.get_default_submodule("CircuitCardinality") + self.assertIsInstance(submodule, CircuitCardinality) + + with self.assertRaises(NotImplementedError): + self.minmax_instance.get_default_submodule("InvalidOption") + + def test_fit_transform(self): + data = np.array([[1, 2], [3, 4]]) + normalized = self.minmax_instance.fit_transform(data) + self.assertTrue((normalized >= 0).all() and (normalized <= 1).all(), "Normalized data should be in [0, 1].") + + def test_inverse_transform(self): + data = np.array([[0.5, 0.5], [0, 0]]) + original = self.minmax_instance.inverse_transform(data) + self.assertTrue((original >= data.min()).all() and (original <= data.max()).all(), + "Reconstructed data should match the original range.") + + def test_transform(self): + input_data = { + "dataset_name": "test_dataset", + "dataset": np.array([[1, 2], [3, 4], [5, 6]]), + "n_qubits": 4, + "train_size": 0.8, + "store_dir_iter": "/tmp" + } + config = {} + + transformed_config = self.minmax_instance.transform(input_data, config) + + self.assertIn("histogram_train", transformed_config, "Expected histogram_train in the transformed config.") + self.assertIn("binary_train", transformed_config, "Expected binary_train in the transformed config.") + self.assertEqual(transformed_config["dataset_name"], "test_dataset", "Dataset name should match.") + self.assertEqual(transformed_config["n_qubits"], 4, "Expected number of qubits to match.") + self.assertEqual(transformed_config["train_size"], 0.8, "Expected train size to match.") + + def test_reverse_transform(self): + input_data = { + "best_sample": np.array([2, 1, 0, 3]), # Example results aligned with bins + "depth": 3, + "architecture_name": "TestArchitecture", + "n_qubits": 4, + "KL": [0.1, 0.2, 0.05], + "best_parameter": [0.5, 1.0], + "circuit_transpiled": None, + "store_dir_iter": "/tmp" + } + + # Simulate the transformation configuration + self.minmax_instance.transform_config = { + "n_registers": 4 + } + self.minmax_instance.histogram_train = np.array([0.1, 0.2]) + self.minmax_instance.histogram_train_original = np.array([0.05, 0.15]) + + # Mock Transformation methods for alignment + Transformation.compute_discretization_efficient = MagicMock(return_value=np.array([[0], [1], [2], [3]])) + Transformation.generate_samples_efficient = MagicMock(return_value=np.array([[0], [1], [2], [3]])) + + # Call reverse_transform + reversed_config = self.minmax_instance.reverse_transform(input_data) + + # Assertions + self.assertIn("generated_samples", reversed_config, "Expected 'generated_samples' in the output.") + self.assertIn("histogram_generated", reversed_config, "Expected 'histogram_generated' in the output.") diff --git a/tests/modules/applications/qml/generative_modeling/transformation/test_min_max.py b/tests/modules/applications/qml/generative_modeling/transformation/test_min_max.py index 7b2ad3db..64b36e65 100644 --- a/tests/modules/applications/qml/generative_modeling/transformation/test_min_max.py +++ b/tests/modules/applications/qml/generative_modeling/transformation/test_min_max.py @@ -2,10 +2,10 @@ import numpy as np from unittest.mock import patch, MagicMock -from src.modules.applications.qml.generative_modeling.transformations.transformation import Transformation -from src.modules.applications.qml.generative_modeling.transformations.min_max import MinMax -from src.modules.applications.qml.generative_modeling.circuits.circuit_standard import CircuitStandard -from src.modules.applications.qml.generative_modeling.circuits.circuit_cardinality import CircuitCardinality +from modules.applications.qml.generative_modeling.transformations.transformation import Transformation +from modules.applications.qml.generative_modeling.transformations.min_max import MinMax +from modules.applications.qml.generative_modeling.circuits.circuit_standard import CircuitStandard +from modules.applications.qml.generative_modeling.circuits.circuit_cardinality import CircuitCardinality class TestMinMax(unittest.TestCase): diff --git a/tests/test_config_manager.py b/tests/test_config_manager.py index 09fb9cd5..d6e4cf3c 100644 --- a/tests/test_config_manager.py +++ b/tests/test_config_manager.py @@ -1,7 +1,7 @@ import unittest from unittest.mock import patch, MagicMock -from src.modules.core import Core +from modules.core import Core from src.config_manager import ConfigManager From afcaf02cfe4aae9b56810cf729abe5b45f9e9dd4 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Fri, 7 Feb 2025 13:18:17 +0000 Subject: [PATCH 15/23] Apply autopep8 formatting --- .../optimization/mis_temp/mappings/test_neutral_atom.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/modules/applications/optimization/mis_temp/mappings/test_neutral_atom.py b/tests/modules/applications/optimization/mis_temp/mappings/test_neutral_atom.py index fd32b9eb..15b7b3f2 100644 --- a/tests/modules/applications/optimization/mis_temp/mappings/test_neutral_atom.py +++ b/tests/modules/applications/optimization/mis_temp/mappings/test_neutral_atom.py @@ -3,6 +3,7 @@ from modules.applications.optimization.mis.mappings.neutral_atom import NeutralAtom + class TestNeutralAtom(unittest.TestCase): @classmethod From e233b12dc703e00aa3bff3ddae35828ff1cbe5f1 Mon Sep 17 00:00:00 2001 From: GreshmaShaji Date: Fri, 7 Feb 2025 14:31:18 +0100 Subject: [PATCH 16/23] adjusted import path --- .../transformation/test_MinMax.py | 102 --------------- .../{test_min_max.py => test_min_max_temp.py} | 0 .../transformation/test_pit.py | 120 ------------------ .../{test_PIT.py => test_pit_temp.py} | 0 4 files changed, 222 deletions(-) delete mode 100644 tests/modules/applications/qml/generative_modeling/transformation/test_MinMax.py rename tests/modules/applications/qml/generative_modeling/transformation/{test_min_max.py => test_min_max_temp.py} (100%) delete mode 100644 tests/modules/applications/qml/generative_modeling/transformation/test_pit.py rename tests/modules/applications/qml/generative_modeling/transformation/{test_PIT.py => test_pit_temp.py} (100%) diff --git a/tests/modules/applications/qml/generative_modeling/transformation/test_MinMax.py b/tests/modules/applications/qml/generative_modeling/transformation/test_MinMax.py deleted file mode 100644 index f19a2e24..00000000 --- a/tests/modules/applications/qml/generative_modeling/transformation/test_MinMax.py +++ /dev/null @@ -1,102 +0,0 @@ -import unittest -import numpy as np -from unittest.mock import patch, MagicMock - -from modules.applications.qml.generative_modeling.transformations.transformation import Transformation -from modules.applications.qml.generative_modeling.transformations.min_max import MinMax -from modules.applications.qml.generative_modeling.circuits.circuit_standard import CircuitStandard -from modules.applications.qml.generative_modeling.circuits.circuit_cardinality import CircuitCardinality - - -class TestMinMax(unittest.TestCase): - - @classmethod - def setUpClass(cls): - cls.minmax_instance = MinMax() - cls.sample_input_data = { - "dataset_name": "TestDataset", - "dataset": np.random.rand(100, 3), - "n_qubits": 6, - "train_size": 80, - "store_dir_iter": "/tmp/test" - } - cls.sample_config = {} - cls.minmax_instance.grid_shape = 4 # Example grid shape - cls.minmax_instance.dataset_name = "TestDataset" - cls.minmax_instance.histogram_train = np.array([0.2, 0.3, 0.1, 0.4]) - cls.minmax_instance.histogram_train_original = np.array([0.25, 0.25, 0.25, 0.25]) - cls.minmax_instance.transform_config = {"n_registers": 2} - - def test_get_requirements(self): - requirements = self.minmax_instance.get_requirements() - expected_requirements = [{"name": "numpy", "version": "1.26.4"}] - self.assertEqual(requirements, expected_requirements) - - def test_get_default_submodule(self): - submodule = self.minmax_instance.get_default_submodule("CircuitStandard") - self.assertIsInstance(submodule, CircuitStandard) - - submodule = self.minmax_instance.get_default_submodule("CircuitCardinality") - self.assertIsInstance(submodule, CircuitCardinality) - - with self.assertRaises(NotImplementedError): - self.minmax_instance.get_default_submodule("InvalidOption") - - def test_fit_transform(self): - data = np.array([[1, 2], [3, 4]]) - normalized = self.minmax_instance.fit_transform(data) - self.assertTrue((normalized >= 0).all() and (normalized <= 1).all(), "Normalized data should be in [0, 1].") - - def test_inverse_transform(self): - data = np.array([[0.5, 0.5], [0, 0]]) - original = self.minmax_instance.inverse_transform(data) - self.assertTrue((original >= data.min()).all() and (original <= data.max()).all(), - "Reconstructed data should match the original range.") - - def test_transform(self): - input_data = { - "dataset_name": "test_dataset", - "dataset": np.array([[1, 2], [3, 4], [5, 6]]), - "n_qubits": 4, - "train_size": 0.8, - "store_dir_iter": "/tmp" - } - config = {} - - transformed_config = self.minmax_instance.transform(input_data, config) - - self.assertIn("histogram_train", transformed_config, "Expected histogram_train in the transformed config.") - self.assertIn("binary_train", transformed_config, "Expected binary_train in the transformed config.") - self.assertEqual(transformed_config["dataset_name"], "test_dataset", "Dataset name should match.") - self.assertEqual(transformed_config["n_qubits"], 4, "Expected number of qubits to match.") - self.assertEqual(transformed_config["train_size"], 0.8, "Expected train size to match.") - - def test_reverse_transform(self): - input_data = { - "best_sample": np.array([2, 1, 0, 3]), # Example results aligned with bins - "depth": 3, - "architecture_name": "TestArchitecture", - "n_qubits": 4, - "KL": [0.1, 0.2, 0.05], - "best_parameter": [0.5, 1.0], - "circuit_transpiled": None, - "store_dir_iter": "/tmp" - } - - # Simulate the transformation configuration - self.minmax_instance.transform_config = { - "n_registers": 4 - } - self.minmax_instance.histogram_train = np.array([0.1, 0.2]) - self.minmax_instance.histogram_train_original = np.array([0.05, 0.15]) - - # Mock Transformation methods for alignment - Transformation.compute_discretization_efficient = MagicMock(return_value=np.array([[0], [1], [2], [3]])) - Transformation.generate_samples_efficient = MagicMock(return_value=np.array([[0], [1], [2], [3]])) - - # Call reverse_transform - reversed_config = self.minmax_instance.reverse_transform(input_data) - - # Assertions - self.assertIn("generated_samples", reversed_config, "Expected 'generated_samples' in the output.") - self.assertIn("histogram_generated", reversed_config, "Expected 'histogram_generated' in the output.") diff --git a/tests/modules/applications/qml/generative_modeling/transformation/test_min_max.py b/tests/modules/applications/qml/generative_modeling/transformation/test_min_max_temp.py similarity index 100% rename from tests/modules/applications/qml/generative_modeling/transformation/test_min_max.py rename to tests/modules/applications/qml/generative_modeling/transformation/test_min_max_temp.py diff --git a/tests/modules/applications/qml/generative_modeling/transformation/test_pit.py b/tests/modules/applications/qml/generative_modeling/transformation/test_pit.py deleted file mode 100644 index 70a0778d..00000000 --- a/tests/modules/applications/qml/generative_modeling/transformation/test_pit.py +++ /dev/null @@ -1,120 +0,0 @@ -import unittest -import numpy as np -from unittest.mock import MagicMock - -from src.modules.applications.qml.generative_modeling.transformations.pit import PIT -from src.modules.applications.qml.generative_modeling.circuits.circuit_copula import CircuitCopula - - -class TestPIT(unittest.TestCase): - - @classmethod - def setUpClass(cls): - cls.pit_instance = PIT() - cls.sample_input_data = { - "dataset_name": "test_dataset", - "dataset": np.array([[1, 2], [3, 4], [5, 6]]), - "n_qubits": 4, - "store_dir_iter": "/tmp", - "train_size": 0.8 - } - cls.sample_config = {} - # Mock reverse_epit_lookup for testing - cls.pit_instance.reverse_epit_lookup = np.array([ - [0.1, 0.2, 0.3, 0.4], - [0.5, 0.6, 0.7, 0.8], - [0.9, 1.0, 1.1, 1.2] - ]) - cls.pit_instance.grid_shape = (2, 2) - cls.pit_instance.transform_config = { - "n_registers": 2, - "binary_train": np.array([[0, 1], [1, 0]]), - "histogram_train": np.array([0.5, 0.5]), - "dataset_name": "mock_dataset", - "store_dir_iter": "/mock/path" - } - - def test_get_requirements(self): - requirements = self.pit_instance.get_requirements() - expected_requirements = [ - {"name": "numpy", "version": "1.26.4"}, - {"name": "pandas", "version": "2.2.3"} - ] - self.assertEqual(requirements, expected_requirements) - - def test_get_default_submodule(self): - submodule = self.pit_instance.get_default_submodule("CircuitCopula") - self.assertIsInstance(submodule, CircuitCopula, "Expected CircuitCopula instance for 'CircuitCopula' option.") - with self.assertRaises(NotImplementedError): - self.pit_instance.get_default_submodule("InvalidSubmodule") - - def test_transform(self): - result = self.pit_instance.transform(self.sample_input_data, self.sample_config) - - self.assertIn("histogram_train", result, "Expected 'histogram_train' in transformation result.") - self.assertIn("binary_train", result, "Expected 'binary_train' in transformation result.") - self.assertIn("dataset_name", result, "Expected 'dataset_name' in transformation result.") - self.assertEqual(result["dataset_name"], "test_dataset", "Dataset name mismatch in transformation result.") - self.assertIsInstance(result["binary_train"], np.ndarray, "Expected binary_train to be a numpy array.") - self.assertEqual(result["n_qubits"], self.sample_input_data["n_qubits"], "n_qubits mismatch.") - - # This test is currently commented out because: - # - The `reverse_transform` method relies on mocked internal methods (`compute_discretization_efficient` and - # `generate_samples_efficient`) that require precise mocking of their behavior and returned data. - # - Creating realistic mock data for `reverse_transform` is challenging without deeper understanding of - # the expected transformations or how they interact with the architecture. - # - We plan to implement this test in the future when there is more clarity on the expected functionality - # def test_reverse_transform(self): - # # Mocked input data - # input_data = { - # "best_sample": np.array([0, 1, 2, 3]), - # "depth": 2, - # "architecture_name": "TestArchitecture", - # "n_qubits": 2, - # "KL": [0.1, 0.2], - # "circuit_transpiled": None, - # "best_parameter": [0.5, 0.6], - # "store_dir_iter": "/mock/path" - # } - - # # Mock internal method responses - # self.pit_instance.compute_discretization_efficient = MagicMock(return_value=np.array([[0, 1], [2, 3]])) - # self.pit_instance.generate_samples_efficient = MagicMock(return_value=np.array([[0.1, 0.2], [0.3, 0.4]])) - - # # Call the method - # reverse_config = self.pit_instance.reverse_transform(input_data) - - # # Validate the response - # self.assertIn("generated_samples", reverse_config) - # self.assertIn("transformed_samples", reverse_config) - # self.assertIn("KL_best_transformed", reverse_config) - # self.assertEqual(reverse_config["depth"], input_data["depth"]) - # self.assertEqual(reverse_config["dataset_name"], self.pit_instance.dataset_name) - - def test_emp_integral_trans(self): - data = np.random.uniform(0, 1, 100) - transformed_data = self.pit_instance.emp_integral_trans(data) - self.assertTrue((transformed_data >= 0).all() and (transformed_data <= 1).all(), - "Empirical transformation should map values to [0, 1].") - - def test_fit_transform(self): - data = np.random.uniform(0, 1, (100, 4)) - transformed = self.pit_instance.fit_transform(data) - self.assertEqual(transformed.shape, data.shape, "Transformed data should match the input shape.") - - def test_inverse_transform(self): - data = np.random.uniform(0, 1, (100, 4)) - self.pit_instance.fit_transform(data) - inverse_data = self.pit_instance.inverse_transform(data) - self.assertEqual(inverse_data.shape, data.shape, "Inverse-transformed data should match the input shape.") - - # This test is currently commented out because: - # We plan to revisit this test in the future - # def test_reverse_empirical_integral_trans_single(self): - # self.pit_instance.reverse_epit_lookup = np.array([ - # [0.1, 0.2, 0.3], - # [0.4, 0.5, 0.6] - # ]) - # values = np.array([0.2, 0.8]) - # reverse_result = self.pit_instance._reverse_emp_integral_trans_single(values) - # self.assertEqual(len(reverse_result), 1, "Reverse transformed result length mismatch.") diff --git a/tests/modules/applications/qml/generative_modeling/transformation/test_PIT.py b/tests/modules/applications/qml/generative_modeling/transformation/test_pit_temp.py similarity index 100% rename from tests/modules/applications/qml/generative_modeling/transformation/test_PIT.py rename to tests/modules/applications/qml/generative_modeling/transformation/test_pit_temp.py From 16d12ec03078df385ca1cba4820ae75c0f831ace Mon Sep 17 00:00:00 2001 From: GreshmaShaji Date: Fri, 7 Feb 2025 14:32:36 +0100 Subject: [PATCH 17/23] adjusted import path --- .../transformation/{test_min_max_temp.py => test_min_max.py} | 0 .../transformation/{test_pit_temp.py => test_pit.py} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename tests/modules/applications/qml/generative_modeling/transformation/{test_min_max_temp.py => test_min_max.py} (100%) rename tests/modules/applications/qml/generative_modeling/transformation/{test_pit_temp.py => test_pit.py} (100%) diff --git a/tests/modules/applications/qml/generative_modeling/transformation/test_min_max_temp.py b/tests/modules/applications/qml/generative_modeling/transformation/test_min_max.py similarity index 100% rename from tests/modules/applications/qml/generative_modeling/transformation/test_min_max_temp.py rename to tests/modules/applications/qml/generative_modeling/transformation/test_min_max.py diff --git a/tests/modules/applications/qml/generative_modeling/transformation/test_pit_temp.py b/tests/modules/applications/qml/generative_modeling/transformation/test_pit.py similarity index 100% rename from tests/modules/applications/qml/generative_modeling/transformation/test_pit_temp.py rename to tests/modules/applications/qml/generative_modeling/transformation/test_pit.py From 000fd5ae4d07f05f03e65633f8882290428ce77e Mon Sep 17 00:00:00 2001 From: GreshmaShaji Date: Fri, 7 Feb 2025 14:49:11 +0100 Subject: [PATCH 18/23] adjusted import path --- .../transformation/test_min_max.py | 12 ++++++------ .../generative_modeling/transformation/test_pit.py | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/modules/applications/qml/generative_modeling/transformation/test_min_max.py b/tests/modules/applications/qml/generative_modeling/transformation/test_min_max.py index 64b36e65..04d65b8e 100644 --- a/tests/modules/applications/qml/generative_modeling/transformation/test_min_max.py +++ b/tests/modules/applications/qml/generative_modeling/transformation/test_min_max.py @@ -73,17 +73,17 @@ def test_transform(self): def test_reverse_transform(self): # self.minmax_instance.transform(self.sample_input_data, self.sample_config) - + best_sample = np.random.randint(0, 64, size=(64,)) # Mock the input for reverse_transform reverse_input_data = { - "best_sample": np.array([0, 1, 2]), # Example best samples - "depth": 1, # Example - "architecture_name": "test_arch", # Example + "best_sample": best_sample, + "depth": 1, + "architecture_name": "test_arch", "n_qubits": 6, - "KL": [0.1], # Example + "KL": [0.1], "best_parameter": [0.5], "store_dir_iter": "test_dir", - "circuit_transpiled": MagicMock() # Mock the circuit + "circuit_transpiled": None } reverse_config = self.minmax_instance.reverse_transform(reverse_input_data) diff --git a/tests/modules/applications/qml/generative_modeling/transformation/test_pit.py b/tests/modules/applications/qml/generative_modeling/transformation/test_pit.py index 70a0778d..bbfbb71d 100644 --- a/tests/modules/applications/qml/generative_modeling/transformation/test_pit.py +++ b/tests/modules/applications/qml/generative_modeling/transformation/test_pit.py @@ -2,8 +2,8 @@ import numpy as np from unittest.mock import MagicMock -from src.modules.applications.qml.generative_modeling.transformations.pit import PIT -from src.modules.applications.qml.generative_modeling.circuits.circuit_copula import CircuitCopula +from modules.applications.qml.generative_modeling.transformations.pit import PIT +from modules.applications.qml.generative_modeling.circuits.circuit_copula import CircuitCopula class TestPIT(unittest.TestCase): From f7e78ad0753eb1db47c3b2b5d073e358efb064dc Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Fri, 7 Feb 2025 13:49:56 +0000 Subject: [PATCH 19/23] Apply autopep8 formatting --- .../qml/generative_modeling/transformation/test_min_max.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/modules/applications/qml/generative_modeling/transformation/test_min_max.py b/tests/modules/applications/qml/generative_modeling/transformation/test_min_max.py index 04d65b8e..6f279e9d 100644 --- a/tests/modules/applications/qml/generative_modeling/transformation/test_min_max.py +++ b/tests/modules/applications/qml/generative_modeling/transformation/test_min_max.py @@ -78,7 +78,7 @@ def test_reverse_transform(self): reverse_input_data = { "best_sample": best_sample, "depth": 1, - "architecture_name": "test_arch", + "architecture_name": "test_arch", "n_qubits": 6, "KL": [0.1], "best_parameter": [0.5], From 262a3d9ddd5e5b169d127d6cf066416668b69479 Mon Sep 17 00:00:00 2001 From: GreshmaShaji Date: Fri, 7 Feb 2025 15:09:55 +0100 Subject: [PATCH 20/23] adjusted import path --- .../mis/mappings/test_neutral_atom.py | 42 ------- .../applications/optimization/mis/test_mis.py | 76 ----------- .../{mis => mis_temp}/__init__.py | 0 .../{mis => mis_temp}/mappings/__init__.py | 0 .../mappings/mis_test_graph.pkl | Bin .../mis_temp/mappings/test_neutral_atom.py | 4 +- .../optimization/mis_temp/test_mis.py | 2 +- .../sat/mappings/test_choiIsing.py | 65 ---------- .../sat/mappings/test_choiqubo.py | 55 -------- .../sat/mappings/test_dinneenising.py | 63 ---------- .../sat/mappings/test_dinneenqubo.py | 53 -------- .../optimization/sat/mappings/test_direct.py | 76 ----------- .../sat/mappings/test_qubovertqubo.py | 51 -------- .../applications/optimization/sat/test_sat.py | 118 ------------------ .../{sat => sat_temp}/__init__.py | 0 .../{sat => sat_temp}/mappings/__init__.py | 0 .../sat_temp/mappings/test_choiIsing.py | 2 +- .../sat_temp/mappings/test_choiqubo.py | 2 +- .../sat_temp/mappings/test_dinneenising.py | 2 +- .../sat_temp/mappings/test_dinneenqubo.py | 2 +- .../sat_temp/mappings/test_direct.py | 2 +- .../sat_temp/mappings/test_qubovertqubo.py | 2 +- .../optimization/sat_temp/test_sat.py | 2 +- 23 files changed, 10 insertions(+), 609 deletions(-) delete mode 100644 tests/modules/applications/optimization/mis/mappings/test_neutral_atom.py delete mode 100644 tests/modules/applications/optimization/mis/test_mis.py rename tests/modules/applications/optimization/{mis => mis_temp}/__init__.py (100%) rename tests/modules/applications/optimization/{mis => mis_temp}/mappings/__init__.py (100%) rename tests/modules/applications/optimization/{mis => mis_temp}/mappings/mis_test_graph.pkl (100%) delete mode 100644 tests/modules/applications/optimization/sat/mappings/test_choiIsing.py delete mode 100644 tests/modules/applications/optimization/sat/mappings/test_choiqubo.py delete mode 100644 tests/modules/applications/optimization/sat/mappings/test_dinneenising.py delete mode 100644 tests/modules/applications/optimization/sat/mappings/test_dinneenqubo.py delete mode 100644 tests/modules/applications/optimization/sat/mappings/test_direct.py delete mode 100644 tests/modules/applications/optimization/sat/mappings/test_qubovertqubo.py delete mode 100644 tests/modules/applications/optimization/sat/test_sat.py rename tests/modules/applications/optimization/{sat => sat_temp}/__init__.py (100%) rename tests/modules/applications/optimization/{sat => sat_temp}/mappings/__init__.py (100%) diff --git a/tests/modules/applications/optimization/mis/mappings/test_neutral_atom.py b/tests/modules/applications/optimization/mis/mappings/test_neutral_atom.py deleted file mode 100644 index e860ff77..00000000 --- a/tests/modules/applications/optimization/mis/mappings/test_neutral_atom.py +++ /dev/null @@ -1,42 +0,0 @@ -import unittest -import pickle - -from src.modules.applications.optimization.mis.mappings.neutral_atom import NeutralAtom - - -class TestNeutralAtom(unittest.TestCase): - - @classmethod - def setUpClass(cls): - cls.neutral_atom_instance = NeutralAtom() - with open("tests/modules/applications/optimization/mis/mappings/mis_test_graph.pkl", "rb") as file: - cls.graph = pickle.load(file) - cls.config = {} - - def test_get_requirements(self): - requirements = self.neutral_atom_instance.get_requirements() - expected_requirements = [{"name": "pulser", "version": "1.1.1"}] - for req in expected_requirements: - self.assertIn(req, requirements) - - def test_get_parameter_options(self): - options = self.neutral_atom_instance.get_parameter_options() - self.assertEqual(options, {}, "Expected parameter options to be an empty dictionary.") - - def test_map(self): - neutral_atom_problem, mapping_time = self.neutral_atom_instance.map(self.graph, self.config) - - self.assertIn("graph", neutral_atom_problem) - self.assertIn("register", neutral_atom_problem) - - self.assertIsNotNone(neutral_atom_problem["register"], "Expected a valid Pulser register.") - self.assertGreater(mapping_time, 0, "Mapping time should be positive.") - - def test_get_default_submodule(self): - # Test valid submodule retrieval - submodule = self.neutral_atom_instance.get_default_submodule("NeutralAtomMIS") - self.assertIsNotNone(submodule, "Expected 'NeutralAtomMIS' submodule to be returned.") - - # Test invalid submodule option - with self.assertRaises(NotImplementedError): - self.neutral_atom_instance.get_default_submodule("InvalidSubmodule") diff --git a/tests/modules/applications/optimization/mis/test_mis.py b/tests/modules/applications/optimization/mis/test_mis.py deleted file mode 100644 index e7f2e0bf..00000000 --- a/tests/modules/applications/optimization/mis/test_mis.py +++ /dev/null @@ -1,76 +0,0 @@ -import unittest -import networkx as nx -import os -from tempfile import TemporaryDirectory -import logging - -from src.modules.applications.optimization.mis.mis import MIS - - -class TestMIS(unittest.TestCase): - - @classmethod - def setUpClass(cls): - cls.mis_instance = MIS() - cls.config = {"size": 5, "spacing": 0.5, "filling_fraction": 0.5} - cls.graph = cls.mis_instance.generate_problem(cls.config) - - @classmethod - def tearDownClass(cls): - del cls.mis_instance - - def test_get_solution_quality_unit(self): - unit = self.mis_instance.get_solution_quality_unit() - self.assertEqual(unit, "Set size", "Incorrect solution quality unit.") - - def test_get_default_submodule(self): - submodule = self.mis_instance.get_default_submodule("NeutralAtom") - self.assertIsNotNone(submodule, "Expected 'NeutralAtom' submodule to be returned.") - with self.assertRaises(NotImplementedError): - self.mis_instance.get_default_submodule("InvalidOption") - - def test_get_parameter_options(self): - options = self.mis_instance.get_parameter_options() - self.assertIn("size", options) - self.assertIn("graph_type", options) - - def test_generate_problem(self): - # Generate with valid configuration - graph = self.mis_instance.generate_problem(self.config) - self.assertIsInstance(graph, nx.Graph) - self.assertGreaterEqual(len(graph.nodes), 1, "Graph should have at least 1 node.") - self.assertGreaterEqual(len(graph.edges), 0, "Graph should have non-negative edges.") - - def test_process_solution(self): - solution = [1, 3] - processed_solution, processing_time = self.mis_instance.process_solution(solution) - self.assertEqual(processed_solution, solution, "Processed solution does not match input.") - self.assertGreaterEqual(processing_time, 0, "Processing time should be positive.") - - def test_validate(self): - logging.disable(logging.WARNING) - self.mis_instance.graph = nx.Graph() - self.mis_instance.graph.add_nodes_from([0, 1, 2]) - self.mis_instance.graph.add_edges_from([(0, 1), (1, 2)]) - - valid_solution = [0, 2] - is_valid, validation_time = self.mis_instance.validate(valid_solution) - self.assertTrue(is_valid, f"Expected valid solution: {valid_solution}") - self.assertGreater(validation_time, 0, "Validation time should be positive.") - - def test_evaluate(self): - solution = list(self.graph.nodes)[:3] - set_size, eval_time = self.mis_instance.evaluate(solution) - self.assertEqual(set_size, len(solution), "Set size mismatch.") - self.assertGreater(eval_time, 0, "Evaluation time should be positive.") - - def test_save(self): - with TemporaryDirectory() as temp_dir: - # Save the graph - self.mis_instance.save(temp_dir, iter_count=1) - - # Check that the file exists and is non-empty - file_path = f"{temp_dir}/graph_iter_1.gpickle" - self.assertTrue(os.path.isfile(file_path), "Graph file not saved.") - self.assertTrue(file_path.endswith(".gpickle"), "File extension mismatch.") - self.assertGreater(os.path.getsize(file_path), 0, "Graph file is empty.") diff --git a/tests/modules/applications/optimization/mis/__init__.py b/tests/modules/applications/optimization/mis_temp/__init__.py similarity index 100% rename from tests/modules/applications/optimization/mis/__init__.py rename to tests/modules/applications/optimization/mis_temp/__init__.py diff --git a/tests/modules/applications/optimization/mis/mappings/__init__.py b/tests/modules/applications/optimization/mis_temp/mappings/__init__.py similarity index 100% rename from tests/modules/applications/optimization/mis/mappings/__init__.py rename to tests/modules/applications/optimization/mis_temp/mappings/__init__.py diff --git a/tests/modules/applications/optimization/mis/mappings/mis_test_graph.pkl b/tests/modules/applications/optimization/mis_temp/mappings/mis_test_graph.pkl similarity index 100% rename from tests/modules/applications/optimization/mis/mappings/mis_test_graph.pkl rename to tests/modules/applications/optimization/mis_temp/mappings/mis_test_graph.pkl diff --git a/tests/modules/applications/optimization/mis_temp/mappings/test_neutral_atom.py b/tests/modules/applications/optimization/mis_temp/mappings/test_neutral_atom.py index 15b7b3f2..e860ff77 100644 --- a/tests/modules/applications/optimization/mis_temp/mappings/test_neutral_atom.py +++ b/tests/modules/applications/optimization/mis_temp/mappings/test_neutral_atom.py @@ -1,7 +1,7 @@ import unittest import pickle -from modules.applications.optimization.mis.mappings.neutral_atom import NeutralAtom +from src.modules.applications.optimization.mis.mappings.neutral_atom import NeutralAtom class TestNeutralAtom(unittest.TestCase): @@ -9,7 +9,7 @@ class TestNeutralAtom(unittest.TestCase): @classmethod def setUpClass(cls): cls.neutral_atom_instance = NeutralAtom() - with open("tests/modules/applications/optimization/MIS/mappings/MIS_test_graph.pkl", "rb") as file: + with open("tests/modules/applications/optimization/mis/mappings/mis_test_graph.pkl", "rb") as file: cls.graph = pickle.load(file) cls.config = {} diff --git a/tests/modules/applications/optimization/mis_temp/test_mis.py b/tests/modules/applications/optimization/mis_temp/test_mis.py index da72bc45..e7f2e0bf 100644 --- a/tests/modules/applications/optimization/mis_temp/test_mis.py +++ b/tests/modules/applications/optimization/mis_temp/test_mis.py @@ -4,7 +4,7 @@ from tempfile import TemporaryDirectory import logging -from modules.applications.optimization.mis.mis import MIS +from src.modules.applications.optimization.mis.mis import MIS class TestMIS(unittest.TestCase): diff --git a/tests/modules/applications/optimization/sat/mappings/test_choiIsing.py b/tests/modules/applications/optimization/sat/mappings/test_choiIsing.py deleted file mode 100644 index 5533f47e..00000000 --- a/tests/modules/applications/optimization/sat/mappings/test_choiIsing.py +++ /dev/null @@ -1,65 +0,0 @@ -import unittest -import numpy as np -from nnf import Var, And, Or - -from src.modules.applications.optimization.sat.mappings.choiising import ChoiIsing - - -class TestChoiIsing(unittest.TestCase): - - @classmethod - def setUpClass(cls): - cls.choi_ising_instance = ChoiIsing() - hard_constraints = And([Or([Var("L1"), ~Var("L2")]), Or([~Var("L3"), Var("L4")])]) - soft_constraints = [Or([Var("L5"), ~Var("L6")])] - cls.problem = (hard_constraints, soft_constraints) - cls.config = {"hard_reward": 0.9, "soft_reward": 1.0} - - def test_get_requirements(self): - requirements = self.choi_ising_instance.get_requirements() - self.assertIn({"name": "numpy", "version": "1.26.4"}, requirements) - self.assertIn({"name": "dimod", "version": "0.12.18"}, requirements) - - def test_get_parameter_options(self): - options = self.choi_ising_instance.get_parameter_options() - self.assertIn("hard_reward", options) - self.assertIn("soft_reward", options) - - def test_map(self): - ising_mapping, mapping_time = self.choi_ising_instance.map(self.problem, self.config) - - # Check that mapping results contain the expected keys - self.assertIn("J", ising_mapping) - self.assertIn("t", ising_mapping) - - # Check that J and t have the correct types and shapes - self.assertIsInstance(ising_mapping["J"], np.ndarray) - self.assertIsInstance(ising_mapping["t"], np.ndarray) - self.assertEqual(ising_mapping["J"].shape[0], ising_mapping["J"].shape[1], "J matrix should be square.") - self.assertEqual(len(ising_mapping["t"]), ising_mapping["J"].shape[0], - "t vector length should match J matrix size.") - - self.assertGreater(mapping_time, 0, "Mapping time should be greater than zero.") - - def test_reverse_map(self): - # Create a mock solution - mock_solution = [1, 0, 1, 1, 0] - - # Run reverse_map to convert the solution back - reverse_mapped_solution, reverse_mapping_time = self.choi_ising_instance.reverse_map(mock_solution) - - self.assertIsInstance(reverse_mapped_solution, dict) - self.assertGreater(reverse_mapping_time, 0, "Reverse mapping time should be greater than zero.") - - def test_get_default_submodule(self): - # Check QAOA submodule retrieval - submodule = self.choi_ising_instance.get_default_submodule("QAOA") - self.assertIsNotNone(submodule, "QAOA submodule should not be None") - - # Check PennylaneQAOA submodule retrieval - submodule = self.choi_ising_instance.get_default_submodule("PennylaneQAOA") - self.assertIsNotNone(submodule, "PennylaneQAOA submodule should not be None") - - # Check invalid option raises NotImplementedError - with self.assertRaises(NotImplementedError): - self.choi_ising_instance.get_default_submodule("InvalidSubmodule") diff --git a/tests/modules/applications/optimization/sat/mappings/test_choiqubo.py b/tests/modules/applications/optimization/sat/mappings/test_choiqubo.py deleted file mode 100644 index ff28fa56..00000000 --- a/tests/modules/applications/optimization/sat/mappings/test_choiqubo.py +++ /dev/null @@ -1,55 +0,0 @@ -import unittest -from nnf import Var, And, Or - -from src.modules.applications.optimization.sat.mappings.choiqubo import ChoiQUBO - - -class TestChoiQUBO(unittest.TestCase): - - @classmethod - def setUpClass(cls): - cls.choi_qubo_instance = ChoiQUBO() - # Define a problem with both hard and soft constraints - hard_constraints = And([Or([Var("L1"), ~Var("L2")]), Or([~Var("L3"), Var("L4")])]) - soft_constraints = [Or([Var("L5"), ~Var("L6")])] - cls.problem = (hard_constraints, soft_constraints) - cls.config = {"hard_reward": 0.9, "soft_reward": 1.0} - - def test_get_requirements(self): - requirements = self.choi_qubo_instance.get_requirements() - self.assertIn({"name": "nnf", "version": "0.4.1"}, requirements) - - def test_get_parameter_options(self): - options = self.choi_qubo_instance.get_parameter_options() - self.assertIn("hard_reward", options) - self.assertIn("soft_reward", options) - - def test_map(self): - qubo_mapping, mapping_time = self.choi_qubo_instance.map(self.problem, self.config) - - self.assertIn("Q", qubo_mapping) - - q_matrix = qubo_mapping["Q"] - self.assertIsInstance(q_matrix, dict) - for key, value in q_matrix.items(): - self.assertIsInstance(key, tuple, "Expected key to be a tuple") - self.assertTrue(isinstance( - value, float) or isinstance(value, int), "Expected value to be a float or int" - ) - - self.assertGreater(mapping_time, 0, "Mapping time should be greater than zero.") - - def test_reverse_map(self): - mock_solution = {i: 1 if i % 2 == 0 else 0 for i in range(len(self.choi_qubo_instance.reverse_dict))} - - reverse_mapped_solution, reverse_mapping_time = self.choi_qubo_instance.reverse_map(mock_solution) - - self.assertIsInstance(reverse_mapped_solution, dict) - self.assertGreater(reverse_mapping_time, 0, "Reverse mapping time should be greater than zero.") - - def test_get_default_submodule(self): - submodule = self.choi_qubo_instance.get_default_submodule("Annealer") - self.assertIsNotNone(submodule, "Annealer submodule should not be None") - - with self.assertRaises(NotImplementedError): - self.choi_qubo_instance.get_default_submodule("InvalidSubmodule") diff --git a/tests/modules/applications/optimization/sat/mappings/test_dinneenising.py b/tests/modules/applications/optimization/sat/mappings/test_dinneenising.py deleted file mode 100644 index d6c5d332..00000000 --- a/tests/modules/applications/optimization/sat/mappings/test_dinneenising.py +++ /dev/null @@ -1,63 +0,0 @@ -import unittest -import numpy as np -from nnf import Var, And, Or - -from src.modules.applications.optimization.sat.mappings.dinneenising import DinneenIsing - - -class TestDinneenIsing(unittest.TestCase): - - @classmethod - def setUpClass(cls): - cls.dinneen_ising_instance = DinneenIsing() - # Define a SAT problem with both hard and soft constraints - hard_constraints = And([Or([Var("L1"), ~Var("L2")]), Or([~Var("L3"), Var("L4")])]) - soft_constraints = [Or([Var("L5"), ~Var("L6")])] - cls.problem = (hard_constraints, soft_constraints) - cls.config = {"lagrange": 1.0} - - def test_get_requirements(self): - requirements = self.dinneen_ising_instance.get_requirements() - self.assertIn({"name": "nnf", "version": "0.4.1"}, requirements) - self.assertIn({"name": "numpy", "version": "1.26.4"}, requirements) - self.assertIn({"name": "dimod", "version": "0.12.18"}, requirements) - - def test_get_parameter_options(self): - options = self.dinneen_ising_instance.get_parameter_options() - self.assertIn("lagrange", options) - - def test_map(self): - ising_mapping, mapping_time = self.dinneen_ising_instance.map(self.problem, self.config) - - # Check that mapping results contain the expected "J" and "t" keys - self.assertIn("J", ising_mapping) - self.assertIn("t", ising_mapping) - - # Check that J and t are numpy arrays - j_matrix = ising_mapping["J"] - t_vector = ising_mapping["t"] - self.assertIsInstance(j_matrix, np.ndarray) - self.assertIsInstance(t_vector, np.ndarray) - self.assertEqual(j_matrix.shape[0], j_matrix.shape[1], "J matrix should be square.") - self.assertEqual(len(t_vector), j_matrix.shape[0], "t vector length should match J matrix size.") - - self.assertGreater(mapping_time, 0, "Mapping time should be greater than zero.") - - def test_reverse_map(self): - mock_solution = {i: 1 if i % 2 == 0 else 0 for i in range(len(self.dinneen_ising_instance.problem[0].vars()))} - - reverse_mapped_solution, reverse_mapping_time = self.dinneen_ising_instance.reverse_map(mock_solution) - - # Verify that the output of reverse_map is a dictionary - self.assertIsInstance(reverse_mapped_solution, dict) - self.assertGreater(reverse_mapping_time, 0, "Reverse mapping time should be greater than zero.") - - def test_get_default_submodule(self): - submodule = self.dinneen_ising_instance.get_default_submodule("QAOA") - self.assertIsNotNone(submodule, "QAOA submodule should not be None") - - submodule = self.dinneen_ising_instance.get_default_submodule("PennylaneQAOA") - self.assertIsNotNone(submodule, "PennylaneQAOA submodule should not be None") - - with self.assertRaises(NotImplementedError): - self.dinneen_ising_instance.get_default_submodule("InvalidSubmodule") diff --git a/tests/modules/applications/optimization/sat/mappings/test_dinneenqubo.py b/tests/modules/applications/optimization/sat/mappings/test_dinneenqubo.py deleted file mode 100644 index 9ca83ae5..00000000 --- a/tests/modules/applications/optimization/sat/mappings/test_dinneenqubo.py +++ /dev/null @@ -1,53 +0,0 @@ -import unittest -from nnf import Var, And, Or - -from src.modules.applications.optimization.sat.mappings.dinneenqubo import DinneenQUBO - - -class TestDinneenQUBO(unittest.TestCase): - - @classmethod - def setUpClass(cls): - cls.dinneen_qubo_instance = DinneenQUBO() - hard_constraints = And([Or([Var("L1"), ~Var("L2")]), Or([~Var("L3"), Var("L4")])]) - soft_constraints = [Or([Var("L5"), ~Var("L6")])] - cls.problem = (hard_constraints, soft_constraints) - cls.config = {"lagrange": 1.0} - - def test_get_requirements(self): - requirements = self.dinneen_qubo_instance.get_requirements() - self.assertIn({"name": "nnf", "version": "0.4.1"}, requirements) - - def test_get_parameter_options(self): - options = self.dinneen_qubo_instance.get_parameter_options() - self.assertIn("lagrange", options) - - def test_map(self): - qubo_mapping, mapping_time = self.dinneen_qubo_instance.map(self.problem, self.config) - - self.assertIn("Q", qubo_mapping) - - q_matrix = qubo_mapping["Q"] - self.assertIsInstance(q_matrix, dict) - for key, value in q_matrix.items(): - self.assertIsInstance(key, tuple, "Expected key to be a tuple") - self.assertTrue(isinstance( - value, float) or isinstance(value, int), "Expected value to be a float or int" - ) - - self.assertGreater(mapping_time, 0, "Mapping time should be greater than zero.") - - def test_reverse_map(self): - mock_solution = {i: 1 if i % 2 == 0 else 0 for i in range(self.dinneen_qubo_instance.nr_vars)} - - reverse_mapped_solution, reverse_mapping_time = self.dinneen_qubo_instance.reverse_map(mock_solution) - - self.assertIsInstance(reverse_mapped_solution, dict) - self.assertGreater(reverse_mapping_time, 0, "Reverse mapping time should be greater than zero.") - - def test_get_default_submodule(self): - submodule = self.dinneen_qubo_instance.get_default_submodule("Annealer") - self.assertIsNotNone(submodule, "Annealer submodule should not be None") - - with self.assertRaises(NotImplementedError): - self.dinneen_qubo_instance.get_default_submodule("InvalidSubmodule") diff --git a/tests/modules/applications/optimization/sat/mappings/test_direct.py b/tests/modules/applications/optimization/sat/mappings/test_direct.py deleted file mode 100644 index da31ab4b..00000000 --- a/tests/modules/applications/optimization/sat/mappings/test_direct.py +++ /dev/null @@ -1,76 +0,0 @@ -import unittest -from nnf import And, Var, Or -from pysat.formula import WCNF - -from src.modules.applications.optimization.sat.mappings.direct import Direct - - -class TestDirect(unittest.TestCase): - - @classmethod - def setUpClass(cls): - cls.direct_instance = Direct() - - # Hard constraints as CNF: Conjunction of disjunctions - cls.hard_constraints = And([Or([Var("L0")]), Or([~Var("L1")])]) # A ∧ ¬B - - # Soft constraints as CNF: List of disjunctions - cls.soft_constraints = [Or([Var("L2")]), Or([~Var("L3")])] - cls.problem = (cls.hard_constraints, cls.soft_constraints) - - def test_get_requirements(self): - requirements = self.direct_instance.get_requirements() - expected_requirements = [ - {"name": "nnf", "version": "0.4.1"}, - {"name": "python-sat", "version": "1.8.dev13"}, - ] - for req in expected_requirements: - self.assertIn(req, requirements) - - def test_get_parameter_options(self): - options = self.direct_instance.get_parameter_options() - self.assertEqual(options, {}, "Expected parameter options to be an empty dictionary.") - - def test_map(self): - # Map the SAT problem to pysat - mapped_problem, mapping_time = self.direct_instance.map(self.problem, config={}) - - # Check that the result is a WCNF instance - self.assertIsInstance(mapped_problem, WCNF, "Expected a WCNF instance.") - self.assertGreater(mapping_time, 0, "Mapping time should be positive.") - - # Check the number of hard and soft constraints - self.assertEqual(len(mapped_problem.hard), 2, "Expected 1 hard constraint.") - self.assertEqual(len(mapped_problem.soft), 2, "Expected 2 soft constraints.") - - def test_reverse_map(self): - solution = [1, -2, 3, -4, 5] # Example literals - reverse_mapped_solution, reverse_mapping_time = self.direct_instance.reverse_map(solution) - - # Check that the result is a dictionary - self.assertIsInstance( - reverse_mapped_solution, dict, "Expected a dictionary as the reverse mapping result." - ) - self.assertGreater(reverse_mapping_time, 0, "Reverse mapping time should be positive.") - - # Verify that the literals are correctly mapped - expected_solution = { - "L0": True, # L1 (positive) - "L1": False, # L2 (negative) - "L2": True, # L3 (positive) - "L3": False, # L4 (negative) - "L4": True, # L5 (positive) - } - self.assertEqual(reverse_mapped_solution, expected_solution, "Reverse mapping result is incorrect.") - - def test_get_default_submodule(self): - # Test valid submodules - submodule = self.direct_instance.get_default_submodule("ClassicalSAT") - self.assertIsNotNone(submodule, "Expected 'ClassicalSAT' submodule to be returned.") - - submodule = self.direct_instance.get_default_submodule("RandomSAT") - self.assertIsNotNone(submodule, "Expected 'RandomSAT' submodule to be returned.") - - # Test invalid submodule option - with self.assertRaises(NotImplementedError): - self.direct_instance.get_default_submodule("InvalidSubmodule") diff --git a/tests/modules/applications/optimization/sat/mappings/test_qubovertqubo.py b/tests/modules/applications/optimization/sat/mappings/test_qubovertqubo.py deleted file mode 100644 index df0dcdc6..00000000 --- a/tests/modules/applications/optimization/sat/mappings/test_qubovertqubo.py +++ /dev/null @@ -1,51 +0,0 @@ -import unittest -from nnf import Var, And, Or - -from src.modules.applications.optimization.sat.mappings.qubovertqubo import QubovertQUBO - - -class TestQubovertQUBO(unittest.TestCase): - - @classmethod - def setUpClass(cls): - cls.qubovert_instance = QubovertQUBO() - hard_constraints = And([Or([Var("L1"), ~Var("L2")]), Or([~Var("L3"), Var("L4")])]) - soft_constraints = [Or([Var("L5"), ~Var("L6")])] - cls.problem = (hard_constraints, soft_constraints) - cls.config = {"lagrange": 1.0} - - def test_get_requirements(self): - requirements = self.qubovert_instance.get_requirements() - self.assertIn({"name": "nnf", "version": "0.4.1"}, requirements) - self.assertIn({"name": "qubovert", "version": "1.2.5"}, requirements) - - def test_get_parameter_options(self): - options = self.qubovert_instance.get_parameter_options() - self.assertIn("lagrange", options) - - def test_map(self): - qubo_mapping, mapping_time = self.qubovert_instance.map(self.problem, self.config) - - self.assertIn("Q", qubo_mapping) - - q_dict = qubo_mapping["Q"] - self.assertIsInstance(q_dict, dict) - for key, value in q_dict.items(): - self.assertIsInstance(key, tuple, "Expected key to be a tuple") - self.assertTrue( - isinstance(value, float) or isinstance(value, int), "Expected value to be a float or int" - ) - - self.assertGreater(mapping_time, 0, "Mapping time should be greater than zero.") - - def test_reverse_map(self): - mock_solution = {i: 1 if i % 2 == 0 else 0 for i in range(self.qubovert_instance.nr_vars)} - - reverse_mapped_solution, reverse_mapping_time = self.qubovert_instance.reverse_map(mock_solution) - - self.assertIsInstance(reverse_mapped_solution, dict) - self.assertGreater(reverse_mapping_time, 0, "Reverse mapping time should be greater than zero.") - - def test_get_default_submodule(self): - submodule = self.qubovert_instance.get_default_submodule("Annealer") - self.assertIsNotNone(submodule, "Annealer submodule should not be None") diff --git a/tests/modules/applications/optimization/sat/test_sat.py b/tests/modules/applications/optimization/sat/test_sat.py deleted file mode 100644 index 3e957c2f..00000000 --- a/tests/modules/applications/optimization/sat/test_sat.py +++ /dev/null @@ -1,118 +0,0 @@ -import unittest -import os -import nnf -from tempfile import TemporaryDirectory - -from src.modules.applications.optimization.sat.sat import SAT - - -class TestSAT(unittest.TestCase): - - @classmethod - def setUpClass(cls): - cls.sat_instance = SAT() - cls.problem = cls.sat_instance.generate_problem( - { - "variables": 10, "clvar_ratio_cons": 2, - "clvar_ratio_test": 2, "problem_set": 5, - "max_tries": 100 - } - ) - - @classmethod - def tearDownClass(cls): - del cls.sat_instance - - def test_get_requirements(self): - requirements = self.sat_instance.get_requirements() - self.assertIn({"name": "nnf", "version": "0.4.1"}, requirements) - self.assertIn({"name": "numpy", "version": "1.26.4"}, requirements) - - def test_get_default_submodule(self): - submodules = [ - "QubovertQUBO", "Direct", "ChoiQUBO", "ChoiIsing", "DinneenQUBO", "DinneenIsing" - ] - for option in submodules: - with self.subTest(option=option): - submodule = self.sat_instance.get_default_submodule(option) - self.assertIsNotNone(submodule, f"{option} submodule should not be None") - - with self.assertRaises(NotImplementedError): - self.sat_instance.get_default_submodule("DirectX") - - def test_get_parameter_options(self): - options = self.sat_instance.get_parameter_options() - self.assertIn("variables", options) - self.assertIn("clvar_ratio_cons", options) - - def test_generate_problem(self): - # Define a configuration for testing - config = { - "variables": 10, - "clvar_ratio_cons": 2, - "clvar_ratio_test": 2, - "problem_set": 1, - "max_tries": 5000 - } - - # Generate the problem - hard, soft = self.sat_instance.generate_problem(config) - - # Check the types of the output - self.assertIsInstance(hard, nnf.And, "Hard constraints should be of type nnf.And.") - self.assertIsInstance(soft, list, "Soft constraints should be a list.") - - # Validate the structure and cardinalities - hard_clauses = list(hard) # Extract the list of clauses from the And object - self.assertEqual(len(hard_clauses), round(config["clvar_ratio_cons"] * config["variables"]), - "Incorrect number of hard constraints.") - self.assertEqual(len(soft), round(config["clvar_ratio_test"] * config["variables"]), - "Incorrect number of soft constraints.") - - def test_validate(self): - self.sat_instance.generate_problem( - {"variables": 10, "clvar_ratio_cons": 2, "clvar_ratio_test": 2, "problem_set": 5, "max_tries": 100} - ) - right_solution = {'L5': 1, 'L8': 0, 'L7': 0, 'L1': 1, 'L4': 1, 'L0': 1, 'L9': 1, 'L3': 1, 'L6': 1, 'L2': 1} - self.assertEqual(self.sat_instance.validate(right_solution)[0], True) - - wrong_solution = right_solution.copy() - wrong_solution["L5"] = 1 - wrong_solution["L5"] - self.assertEqual(self.sat_instance.validate(wrong_solution)[0], False) - - # Edge Case: Empty solution - empty_solution = {} - with self.assertRaises(ValueError): - self.sat_instance.validate(empty_solution) - - # Edge Case: Partial solution - partial_solution = {'L1': 1, 'L2': 0} - with self.assertRaises(ValueError): - self.sat_instance.validate(partial_solution) - - def test_evaluate(self): - solution = {'L5': 1, 'L8': 0, 'L7': 0, 'L1': 1, 'L4': 1, 'L0': 1, 'L9': 1, 'L3': 1, 'L6': 1, 'L2': 1} - self.assertAlmostEqual(self.sat_instance.evaluate(solution)[0], 0.95, delta=0.05) - - # Edge Case: All True - all_true_solution = {f'L{i}': 1 for i in range(10)} - self.assertGreaterEqual(self.sat_instance.evaluate(all_true_solution)[0], 0) - - # Edge Case: All False - all_false_solution = {f'L{i}': 0 for i in range(10)} - self.assertLessEqual(self.sat_instance.evaluate(all_false_solution)[0], 1) - - def test_save(self): - with TemporaryDirectory() as temp_dir: - self.sat_instance.save(temp_dir, 1) - - file_paths = [f"{temp_dir}/constraints_iter_1.cnf", f"{temp_dir}/tests_iter_1.cnf"] - - for file_path in file_paths: - self.assertTrue(os.path.isfile(file_path)) - - file_extension = os.path.splitext(file_path)[-1].lower() - self.assertEqual(file_extension, ".cnf") - - file_length = os.stat(file_path).st_size == 0 - self.assertFalse(file_length) diff --git a/tests/modules/applications/optimization/sat/__init__.py b/tests/modules/applications/optimization/sat_temp/__init__.py similarity index 100% rename from tests/modules/applications/optimization/sat/__init__.py rename to tests/modules/applications/optimization/sat_temp/__init__.py diff --git a/tests/modules/applications/optimization/sat/mappings/__init__.py b/tests/modules/applications/optimization/sat_temp/mappings/__init__.py similarity index 100% rename from tests/modules/applications/optimization/sat/mappings/__init__.py rename to tests/modules/applications/optimization/sat_temp/mappings/__init__.py diff --git a/tests/modules/applications/optimization/sat_temp/mappings/test_choiIsing.py b/tests/modules/applications/optimization/sat_temp/mappings/test_choiIsing.py index 4b55363e..5533f47e 100644 --- a/tests/modules/applications/optimization/sat_temp/mappings/test_choiIsing.py +++ b/tests/modules/applications/optimization/sat_temp/mappings/test_choiIsing.py @@ -2,7 +2,7 @@ import numpy as np from nnf import Var, And, Or -from modules.applications.optimization.sat.mappings.choiising import ChoiIsing +from src.modules.applications.optimization.sat.mappings.choiising import ChoiIsing class TestChoiIsing(unittest.TestCase): diff --git a/tests/modules/applications/optimization/sat_temp/mappings/test_choiqubo.py b/tests/modules/applications/optimization/sat_temp/mappings/test_choiqubo.py index 2bd25e90..ff28fa56 100644 --- a/tests/modules/applications/optimization/sat_temp/mappings/test_choiqubo.py +++ b/tests/modules/applications/optimization/sat_temp/mappings/test_choiqubo.py @@ -1,7 +1,7 @@ import unittest from nnf import Var, And, Or -from modules.applications.optimization.sat.mappings.choiqubo import ChoiQUBO +from src.modules.applications.optimization.sat.mappings.choiqubo import ChoiQUBO class TestChoiQUBO(unittest.TestCase): diff --git a/tests/modules/applications/optimization/sat_temp/mappings/test_dinneenising.py b/tests/modules/applications/optimization/sat_temp/mappings/test_dinneenising.py index 810512ef..d6c5d332 100644 --- a/tests/modules/applications/optimization/sat_temp/mappings/test_dinneenising.py +++ b/tests/modules/applications/optimization/sat_temp/mappings/test_dinneenising.py @@ -2,7 +2,7 @@ import numpy as np from nnf import Var, And, Or -from modules.applications.optimization.sat.mappings.dinneenising import DinneenIsing +from src.modules.applications.optimization.sat.mappings.dinneenising import DinneenIsing class TestDinneenIsing(unittest.TestCase): diff --git a/tests/modules/applications/optimization/sat_temp/mappings/test_dinneenqubo.py b/tests/modules/applications/optimization/sat_temp/mappings/test_dinneenqubo.py index b626eb0e..9ca83ae5 100644 --- a/tests/modules/applications/optimization/sat_temp/mappings/test_dinneenqubo.py +++ b/tests/modules/applications/optimization/sat_temp/mappings/test_dinneenqubo.py @@ -1,7 +1,7 @@ import unittest from nnf import Var, And, Or -from modules.applications.optimization.sat.mappings.dinneenqubo import DinneenQUBO +from src.modules.applications.optimization.sat.mappings.dinneenqubo import DinneenQUBO class TestDinneenQUBO(unittest.TestCase): diff --git a/tests/modules/applications/optimization/sat_temp/mappings/test_direct.py b/tests/modules/applications/optimization/sat_temp/mappings/test_direct.py index d3b42bee..da31ab4b 100644 --- a/tests/modules/applications/optimization/sat_temp/mappings/test_direct.py +++ b/tests/modules/applications/optimization/sat_temp/mappings/test_direct.py @@ -2,7 +2,7 @@ from nnf import And, Var, Or from pysat.formula import WCNF -from modules.applications.optimization.sat.mappings.direct import Direct +from src.modules.applications.optimization.sat.mappings.direct import Direct class TestDirect(unittest.TestCase): diff --git a/tests/modules/applications/optimization/sat_temp/mappings/test_qubovertqubo.py b/tests/modules/applications/optimization/sat_temp/mappings/test_qubovertqubo.py index 397da327..df0dcdc6 100644 --- a/tests/modules/applications/optimization/sat_temp/mappings/test_qubovertqubo.py +++ b/tests/modules/applications/optimization/sat_temp/mappings/test_qubovertqubo.py @@ -1,7 +1,7 @@ import unittest from nnf import Var, And, Or -from modules.applications.optimization.sat.mappings.qubovertqubo import QubovertQUBO +from src.modules.applications.optimization.sat.mappings.qubovertqubo import QubovertQUBO class TestQubovertQUBO(unittest.TestCase): diff --git a/tests/modules/applications/optimization/sat_temp/test_sat.py b/tests/modules/applications/optimization/sat_temp/test_sat.py index 0195da63..3e957c2f 100644 --- a/tests/modules/applications/optimization/sat_temp/test_sat.py +++ b/tests/modules/applications/optimization/sat_temp/test_sat.py @@ -3,7 +3,7 @@ import nnf from tempfile import TemporaryDirectory -from modules.applications.optimization.sat.sat import SAT +from src.modules.applications.optimization.sat.sat import SAT class TestSAT(unittest.TestCase): From 2b50ea3ae25d2f68cafbef8298379d680d9ba8cb Mon Sep 17 00:00:00 2001 From: GreshmaShaji Date: Fri, 7 Feb 2025 15:11:06 +0100 Subject: [PATCH 21/23] adjusted import path --- .../optimization/{mis_temp => mis}/__init__.py | 0 .../{mis_temp => mis}/mappings/__init__.py | 0 .../{mis_temp => mis}/mappings/mis_test_graph.pkl | Bin .../{mis_temp => mis}/mappings/test_neutral_atom.py | 2 +- .../optimization/{mis_temp => mis}/test_mis.py | 2 +- .../optimization/{sat_temp => sat}/__init__.py | 0 .../{sat_temp => sat}/mappings/__init__.py | 0 .../{sat_temp => sat}/mappings/test_choiIsing.py | 2 +- .../{sat_temp => sat}/mappings/test_choiqubo.py | 2 +- .../{sat_temp => sat}/mappings/test_dinneenising.py | 2 +- .../{sat_temp => sat}/mappings/test_dinneenqubo.py | 2 +- .../{sat_temp => sat}/mappings/test_direct.py | 2 +- .../{sat_temp => sat}/mappings/test_qubovertqubo.py | 2 +- .../optimization/{sat_temp => sat}/test_sat.py | 2 +- 14 files changed, 9 insertions(+), 9 deletions(-) rename tests/modules/applications/optimization/{mis_temp => mis}/__init__.py (100%) rename tests/modules/applications/optimization/{mis_temp => mis}/mappings/__init__.py (100%) rename tests/modules/applications/optimization/{mis_temp => mis}/mappings/mis_test_graph.pkl (100%) rename tests/modules/applications/optimization/{mis_temp => mis}/mappings/test_neutral_atom.py (95%) rename tests/modules/applications/optimization/{mis_temp => mis}/test_mis.py (98%) rename tests/modules/applications/optimization/{sat_temp => sat}/__init__.py (100%) rename tests/modules/applications/optimization/{sat_temp => sat}/mappings/__init__.py (100%) rename tests/modules/applications/optimization/{sat_temp => sat}/mappings/test_choiIsing.py (97%) rename tests/modules/applications/optimization/{sat_temp => sat}/mappings/test_choiqubo.py (96%) rename tests/modules/applications/optimization/{sat_temp => sat}/mappings/test_dinneenising.py (96%) rename tests/modules/applications/optimization/{sat_temp => sat}/mappings/test_dinneenqubo.py (96%) rename tests/modules/applications/optimization/{sat_temp => sat}/mappings/test_direct.py (97%) rename tests/modules/applications/optimization/{sat_temp => sat}/mappings/test_qubovertqubo.py (95%) rename tests/modules/applications/optimization/{sat_temp => sat}/test_sat.py (98%) diff --git a/tests/modules/applications/optimization/mis_temp/__init__.py b/tests/modules/applications/optimization/mis/__init__.py similarity index 100% rename from tests/modules/applications/optimization/mis_temp/__init__.py rename to tests/modules/applications/optimization/mis/__init__.py diff --git a/tests/modules/applications/optimization/mis_temp/mappings/__init__.py b/tests/modules/applications/optimization/mis/mappings/__init__.py similarity index 100% rename from tests/modules/applications/optimization/mis_temp/mappings/__init__.py rename to tests/modules/applications/optimization/mis/mappings/__init__.py diff --git a/tests/modules/applications/optimization/mis_temp/mappings/mis_test_graph.pkl b/tests/modules/applications/optimization/mis/mappings/mis_test_graph.pkl similarity index 100% rename from tests/modules/applications/optimization/mis_temp/mappings/mis_test_graph.pkl rename to tests/modules/applications/optimization/mis/mappings/mis_test_graph.pkl diff --git a/tests/modules/applications/optimization/mis_temp/mappings/test_neutral_atom.py b/tests/modules/applications/optimization/mis/mappings/test_neutral_atom.py similarity index 95% rename from tests/modules/applications/optimization/mis_temp/mappings/test_neutral_atom.py rename to tests/modules/applications/optimization/mis/mappings/test_neutral_atom.py index e860ff77..93bfc478 100644 --- a/tests/modules/applications/optimization/mis_temp/mappings/test_neutral_atom.py +++ b/tests/modules/applications/optimization/mis/mappings/test_neutral_atom.py @@ -1,7 +1,7 @@ import unittest import pickle -from src.modules.applications.optimization.mis.mappings.neutral_atom import NeutralAtom +from modules.applications.optimization.mis.mappings.neutral_atom import NeutralAtom class TestNeutralAtom(unittest.TestCase): diff --git a/tests/modules/applications/optimization/mis_temp/test_mis.py b/tests/modules/applications/optimization/mis/test_mis.py similarity index 98% rename from tests/modules/applications/optimization/mis_temp/test_mis.py rename to tests/modules/applications/optimization/mis/test_mis.py index e7f2e0bf..da72bc45 100644 --- a/tests/modules/applications/optimization/mis_temp/test_mis.py +++ b/tests/modules/applications/optimization/mis/test_mis.py @@ -4,7 +4,7 @@ from tempfile import TemporaryDirectory import logging -from src.modules.applications.optimization.mis.mis import MIS +from modules.applications.optimization.mis.mis import MIS class TestMIS(unittest.TestCase): diff --git a/tests/modules/applications/optimization/sat_temp/__init__.py b/tests/modules/applications/optimization/sat/__init__.py similarity index 100% rename from tests/modules/applications/optimization/sat_temp/__init__.py rename to tests/modules/applications/optimization/sat/__init__.py diff --git a/tests/modules/applications/optimization/sat_temp/mappings/__init__.py b/tests/modules/applications/optimization/sat/mappings/__init__.py similarity index 100% rename from tests/modules/applications/optimization/sat_temp/mappings/__init__.py rename to tests/modules/applications/optimization/sat/mappings/__init__.py diff --git a/tests/modules/applications/optimization/sat_temp/mappings/test_choiIsing.py b/tests/modules/applications/optimization/sat/mappings/test_choiIsing.py similarity index 97% rename from tests/modules/applications/optimization/sat_temp/mappings/test_choiIsing.py rename to tests/modules/applications/optimization/sat/mappings/test_choiIsing.py index 5533f47e..4b55363e 100644 --- a/tests/modules/applications/optimization/sat_temp/mappings/test_choiIsing.py +++ b/tests/modules/applications/optimization/sat/mappings/test_choiIsing.py @@ -2,7 +2,7 @@ import numpy as np from nnf import Var, And, Or -from src.modules.applications.optimization.sat.mappings.choiising import ChoiIsing +from modules.applications.optimization.sat.mappings.choiising import ChoiIsing class TestChoiIsing(unittest.TestCase): diff --git a/tests/modules/applications/optimization/sat_temp/mappings/test_choiqubo.py b/tests/modules/applications/optimization/sat/mappings/test_choiqubo.py similarity index 96% rename from tests/modules/applications/optimization/sat_temp/mappings/test_choiqubo.py rename to tests/modules/applications/optimization/sat/mappings/test_choiqubo.py index ff28fa56..2bd25e90 100644 --- a/tests/modules/applications/optimization/sat_temp/mappings/test_choiqubo.py +++ b/tests/modules/applications/optimization/sat/mappings/test_choiqubo.py @@ -1,7 +1,7 @@ import unittest from nnf import Var, And, Or -from src.modules.applications.optimization.sat.mappings.choiqubo import ChoiQUBO +from modules.applications.optimization.sat.mappings.choiqubo import ChoiQUBO class TestChoiQUBO(unittest.TestCase): diff --git a/tests/modules/applications/optimization/sat_temp/mappings/test_dinneenising.py b/tests/modules/applications/optimization/sat/mappings/test_dinneenising.py similarity index 96% rename from tests/modules/applications/optimization/sat_temp/mappings/test_dinneenising.py rename to tests/modules/applications/optimization/sat/mappings/test_dinneenising.py index d6c5d332..810512ef 100644 --- a/tests/modules/applications/optimization/sat_temp/mappings/test_dinneenising.py +++ b/tests/modules/applications/optimization/sat/mappings/test_dinneenising.py @@ -2,7 +2,7 @@ import numpy as np from nnf import Var, And, Or -from src.modules.applications.optimization.sat.mappings.dinneenising import DinneenIsing +from modules.applications.optimization.sat.mappings.dinneenising import DinneenIsing class TestDinneenIsing(unittest.TestCase): diff --git a/tests/modules/applications/optimization/sat_temp/mappings/test_dinneenqubo.py b/tests/modules/applications/optimization/sat/mappings/test_dinneenqubo.py similarity index 96% rename from tests/modules/applications/optimization/sat_temp/mappings/test_dinneenqubo.py rename to tests/modules/applications/optimization/sat/mappings/test_dinneenqubo.py index 9ca83ae5..b626eb0e 100644 --- a/tests/modules/applications/optimization/sat_temp/mappings/test_dinneenqubo.py +++ b/tests/modules/applications/optimization/sat/mappings/test_dinneenqubo.py @@ -1,7 +1,7 @@ import unittest from nnf import Var, And, Or -from src.modules.applications.optimization.sat.mappings.dinneenqubo import DinneenQUBO +from modules.applications.optimization.sat.mappings.dinneenqubo import DinneenQUBO class TestDinneenQUBO(unittest.TestCase): diff --git a/tests/modules/applications/optimization/sat_temp/mappings/test_direct.py b/tests/modules/applications/optimization/sat/mappings/test_direct.py similarity index 97% rename from tests/modules/applications/optimization/sat_temp/mappings/test_direct.py rename to tests/modules/applications/optimization/sat/mappings/test_direct.py index da31ab4b..d3b42bee 100644 --- a/tests/modules/applications/optimization/sat_temp/mappings/test_direct.py +++ b/tests/modules/applications/optimization/sat/mappings/test_direct.py @@ -2,7 +2,7 @@ from nnf import And, Var, Or from pysat.formula import WCNF -from src.modules.applications.optimization.sat.mappings.direct import Direct +from modules.applications.optimization.sat.mappings.direct import Direct class TestDirect(unittest.TestCase): diff --git a/tests/modules/applications/optimization/sat_temp/mappings/test_qubovertqubo.py b/tests/modules/applications/optimization/sat/mappings/test_qubovertqubo.py similarity index 95% rename from tests/modules/applications/optimization/sat_temp/mappings/test_qubovertqubo.py rename to tests/modules/applications/optimization/sat/mappings/test_qubovertqubo.py index df0dcdc6..397da327 100644 --- a/tests/modules/applications/optimization/sat_temp/mappings/test_qubovertqubo.py +++ b/tests/modules/applications/optimization/sat/mappings/test_qubovertqubo.py @@ -1,7 +1,7 @@ import unittest from nnf import Var, And, Or -from src.modules.applications.optimization.sat.mappings.qubovertqubo import QubovertQUBO +from modules.applications.optimization.sat.mappings.qubovertqubo import QubovertQUBO class TestQubovertQUBO(unittest.TestCase): diff --git a/tests/modules/applications/optimization/sat_temp/test_sat.py b/tests/modules/applications/optimization/sat/test_sat.py similarity index 98% rename from tests/modules/applications/optimization/sat_temp/test_sat.py rename to tests/modules/applications/optimization/sat/test_sat.py index 3e957c2f..0195da63 100644 --- a/tests/modules/applications/optimization/sat_temp/test_sat.py +++ b/tests/modules/applications/optimization/sat/test_sat.py @@ -3,7 +3,7 @@ import nnf from tempfile import TemporaryDirectory -from src.modules.applications.optimization.sat.sat import SAT +from modules.applications.optimization.sat.sat import SAT class TestSAT(unittest.TestCase): From 30286ac3f118f995562082f414cbdc03e6f7b0be Mon Sep 17 00:00:00 2001 From: GreshmaShaji Date: Fri, 14 Feb 2025 12:33:48 +0100 Subject: [PATCH 22/23] Adjusted import paths --- .../applications/optimization/pvc/mappings/test_ising.py | 4 ++-- .../applications/optimization/pvc/mappings/test_qubo.py | 2 +- tests/modules/applications/optimization/pvc/test_pvc.py | 2 +- .../optimization/scp/mappings/test_qubovertqubo.py | 2 +- tests/modules/applications/optimization/scp/test_scp.py | 2 +- .../applications/optimization/tsp/mappings/test_ising.py | 2 +- .../applications/optimization/tsp/mappings/test_qubo.py | 2 +- tests/modules/applications/optimization/tsp/test_tsp.py | 2 +- .../qml/generative_modeling/training/test_QCBM.py | 2 +- .../qml/generative_modeling/training/test_QGAN.py | 2 +- .../qml/generative_modeling/training/test_qcbm.py | 2 +- .../qml/generative_modeling/training/test_qgan.py | 2 +- 12 files changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/modules/applications/optimization/pvc/mappings/test_ising.py b/tests/modules/applications/optimization/pvc/mappings/test_ising.py index a6edb2bc..addd491b 100644 --- a/tests/modules/applications/optimization/pvc/mappings/test_ising.py +++ b/tests/modules/applications/optimization/pvc/mappings/test_ising.py @@ -2,8 +2,8 @@ import numpy as np import pickle -from src.modules.applications.optimization.pvc.mappings.ising import Ising -from src.modules.applications.optimization.pvc.mappings.qubo import QUBO +from modules.applications.optimization.pvc.mappings.ising import Ising +from modules.applications.optimization.pvc.mappings.qubo import QUBO class TestIsing(unittest.TestCase): diff --git a/tests/modules/applications/optimization/pvc/mappings/test_qubo.py b/tests/modules/applications/optimization/pvc/mappings/test_qubo.py index 16e5377d..d5eb0462 100644 --- a/tests/modules/applications/optimization/pvc/mappings/test_qubo.py +++ b/tests/modules/applications/optimization/pvc/mappings/test_qubo.py @@ -1,7 +1,7 @@ import unittest import pickle -from src.modules.applications.optimization.pvc.mappings.qubo import QUBO +from modules.applications.optimization.pvc.mappings.qubo import QUBO class TestQUBO(unittest.TestCase): diff --git a/tests/modules/applications/optimization/pvc/test_pvc.py b/tests/modules/applications/optimization/pvc/test_pvc.py index ca58fa0f..a4821b32 100644 --- a/tests/modules/applications/optimization/pvc/test_pvc.py +++ b/tests/modules/applications/optimization/pvc/test_pvc.py @@ -3,7 +3,7 @@ from tempfile import TemporaryDirectory from networkx import Graph -from src.modules.applications.optimization.pvc.pvc import PVC +from modules.applications.optimization.pvc.pvc import PVC class TestPVC(unittest.TestCase): diff --git a/tests/modules/applications/optimization/scp/mappings/test_qubovertqubo.py b/tests/modules/applications/optimization/scp/mappings/test_qubovertqubo.py index 9616534b..8bcd8618 100644 --- a/tests/modules/applications/optimization/scp/mappings/test_qubovertqubo.py +++ b/tests/modules/applications/optimization/scp/mappings/test_qubovertqubo.py @@ -1,6 +1,6 @@ import unittest -from src.modules.applications.optimization.scp.mappings.qubovertqubo import QubovertQUBO +from modules.applications.optimization.scp.mappings.qubovertqubo import QubovertQUBO class TestQubovertQUBO(unittest.TestCase): diff --git a/tests/modules/applications/optimization/scp/test_scp.py b/tests/modules/applications/optimization/scp/test_scp.py index cb1cf2fd..7f452d05 100644 --- a/tests/modules/applications/optimization/scp/test_scp.py +++ b/tests/modules/applications/optimization/scp/test_scp.py @@ -3,7 +3,7 @@ import pickle from tempfile import TemporaryDirectory -from src.modules.applications.optimization.scp.scp import SCP +from modules.applications.optimization.scp.scp import SCP class TestSCP(unittest.TestCase): diff --git a/tests/modules/applications/optimization/tsp/mappings/test_ising.py b/tests/modules/applications/optimization/tsp/mappings/test_ising.py index 5e3670fd..7e703a7f 100644 --- a/tests/modules/applications/optimization/tsp/mappings/test_ising.py +++ b/tests/modules/applications/optimization/tsp/mappings/test_ising.py @@ -2,7 +2,7 @@ import networkx as nx import numpy as np -from src.modules.applications.optimization.tsp.mappings.ising import Ising +from modules.applications.optimization.tsp.mappings.ising import Ising class TestIsing(unittest.TestCase): diff --git a/tests/modules/applications/optimization/tsp/mappings/test_qubo.py b/tests/modules/applications/optimization/tsp/mappings/test_qubo.py index bc35692c..9a6bad14 100644 --- a/tests/modules/applications/optimization/tsp/mappings/test_qubo.py +++ b/tests/modules/applications/optimization/tsp/mappings/test_qubo.py @@ -1,7 +1,7 @@ import unittest import networkx as nx -from src.modules.applications.optimization.tsp.mappings.qubo import QUBO +from modules.applications.optimization.tsp.mappings.qubo import QUBO class TestQUBO(unittest.TestCase): diff --git a/tests/modules/applications/optimization/tsp/test_tsp.py b/tests/modules/applications/optimization/tsp/test_tsp.py index e02a2558..10195ab2 100644 --- a/tests/modules/applications/optimization/tsp/test_tsp.py +++ b/tests/modules/applications/optimization/tsp/test_tsp.py @@ -5,7 +5,7 @@ from tempfile import TemporaryDirectory import logging -from src.modules.applications.optimization.tsp.tsp import TSP +from modules.applications.optimization.tsp.tsp import TSP class TestTSP(unittest.TestCase): diff --git a/tests/modules/applications/qml/generative_modeling/training/test_QCBM.py b/tests/modules/applications/qml/generative_modeling/training/test_QCBM.py index b06bd8f4..6ffb1e00 100644 --- a/tests/modules/applications/qml/generative_modeling/training/test_QCBM.py +++ b/tests/modules/applications/qml/generative_modeling/training/test_QCBM.py @@ -3,7 +3,7 @@ import numpy as np import matplotlib.pyplot as plt -from src.modules.applications.qml.generative_modeling.training.qcbm import QCBM +from modules.applications.qml.generative_modeling.training.qcbm import QCBM class TestQCBM(unittest.TestCase): diff --git a/tests/modules/applications/qml/generative_modeling/training/test_QGAN.py b/tests/modules/applications/qml/generative_modeling/training/test_QGAN.py index 4bde95f1..f1f8a35b 100644 --- a/tests/modules/applications/qml/generative_modeling/training/test_QGAN.py +++ b/tests/modules/applications/qml/generative_modeling/training/test_QGAN.py @@ -3,7 +3,7 @@ import numpy as np import torch from torch.utils.data import DataLoader -from src.modules.applications.qml.generative_modeling.training.qgan import QGAN, Discriminator, QuantumGenerator +from modules.applications.qml.generative_modeling.training.qgan import QGAN, Discriminator, QuantumGenerator class TestQGAN(unittest.TestCase): diff --git a/tests/modules/applications/qml/generative_modeling/training/test_qcbm.py b/tests/modules/applications/qml/generative_modeling/training/test_qcbm.py index b06bd8f4..6ffb1e00 100644 --- a/tests/modules/applications/qml/generative_modeling/training/test_qcbm.py +++ b/tests/modules/applications/qml/generative_modeling/training/test_qcbm.py @@ -3,7 +3,7 @@ import numpy as np import matplotlib.pyplot as plt -from src.modules.applications.qml.generative_modeling.training.qcbm import QCBM +from modules.applications.qml.generative_modeling.training.qcbm import QCBM class TestQCBM(unittest.TestCase): diff --git a/tests/modules/applications/qml/generative_modeling/training/test_qgan.py b/tests/modules/applications/qml/generative_modeling/training/test_qgan.py index 4bde95f1..f1f8a35b 100644 --- a/tests/modules/applications/qml/generative_modeling/training/test_qgan.py +++ b/tests/modules/applications/qml/generative_modeling/training/test_qgan.py @@ -3,7 +3,7 @@ import numpy as np import torch from torch.utils.data import DataLoader -from src.modules.applications.qml.generative_modeling.training.qgan import QGAN, Discriminator, QuantumGenerator +from modules.applications.qml.generative_modeling.training.qgan import QGAN, Discriminator, QuantumGenerator class TestQGAN(unittest.TestCase): From e8966f4a122bc28c746d0d51560c27915f633533 Mon Sep 17 00:00:00 2001 From: Marvin Erdmann Date: Fri, 14 Feb 2025 13:34:34 +0100 Subject: [PATCH 23/23] minor adjustment in test file --- tests/test_benchmark_manager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_benchmark_manager.py b/tests/test_benchmark_manager.py index b3380893..d75f0dd7 100644 --- a/tests/test_benchmark_manager.py +++ b/tests/test_benchmark_manager.py @@ -149,8 +149,8 @@ def test_traverse_config(self, mock_postprocess, mock_preprocess): self.assertEqual(output, "postprocessed_output", "Expected processed output to match mock postprocess return.") self.assertIsNotNone(benchmark_record, "Expected a BenchmarkRecord instance.") - @patch("BenchmarkManager.BenchmarkManager._collect_all_results", return_value=[{"key": "value"}]) - @patch("BenchmarkManager.BenchmarkManager._save_as_json") + @patch("benchmark_manager.BenchmarkManager._collect_all_results", return_value=[{"key": "value"}]) + @patch("benchmark_manager.BenchmarkManager._save_as_json") def test_orchestrate_benchmark(self, mock_save_as_json, mock_collect_all_results): mock_config_manager = MagicMock() mock_config_manager.get_config.return_value = {"application": {"name": "test_app"}}