From b9a657eaba0d96d07870e2e6af5c86bfa781748f Mon Sep 17 00:00:00 2001 From: zaibod <92274292+zaibod@users.noreply.github.com> Date: Tue, 15 Apr 2025 00:38:50 +0200 Subject: [PATCH 001/137] basic setup without solvers --- ...uantumMaterialSimulationConfiguration.java | 55 +++++++++++++++++++ .../QuantumMaterialSimulationSolver.java | 12 ++++ .../clusterer/VrpClustererConfiguration.java | 4 +- .../quantum.material.simulation/empty.txt | 0 4 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 src/main/java/edu/kit/provideq/toolbox/quantum/material/simulation/QuantumMaterialSimulationConfiguration.java create mode 100644 src/main/java/edu/kit/provideq/toolbox/quantum/material/simulation/solvers/QuantumMaterialSimulationSolver.java create mode 100644 src/main/resources/edu/kit/provideq/toolbox/quantum.material.simulation/empty.txt diff --git a/src/main/java/edu/kit/provideq/toolbox/quantum/material/simulation/QuantumMaterialSimulationConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/quantum/material/simulation/QuantumMaterialSimulationConfiguration.java new file mode 100644 index 00000000..b90cc8d9 --- /dev/null +++ b/src/main/java/edu/kit/provideq/toolbox/quantum/material/simulation/QuantumMaterialSimulationConfiguration.java @@ -0,0 +1,55 @@ +package edu.kit.provideq.toolbox.quantum.material.simulation; + +import edu.kit.provideq.toolbox.ResourceProvider; +import edu.kit.provideq.toolbox.exception.MissingExampleException; +import edu.kit.provideq.toolbox.meta.Problem; +import edu.kit.provideq.toolbox.meta.ProblemManager; +import edu.kit.provideq.toolbox.meta.ProblemType; +import java.io.IOException; +import java.util.Objects; +import java.util.Set; +import org.springframework.context.annotation.Configuration; + + +/** + * Definition and registration of the Quantum Material Simulation problem. + */ +@Configuration +public class QuantumMaterialSimulationConfiguration { + + /** + * A simulation problem, computing electronic properties of a molecular system. + */ + public static final ProblemType QUANTUM_MATERIAL_SIMULATION = new ProblemType<>( + "quantum-material-simulation", + String.class, + String.class + ); + + ProblemManager getQuantumMaterialSimulationManager( + ResourceProvider resourceProvider + ) { + return new ProblemManager<>( + QUANTUM_MATERIAL_SIMULATION, + Set.of(), + loadExampleProblems(resourceProvider) + ); + } + + private Set> loadExampleProblems( + ResourceProvider resourceProvider) { + try { + var problemInputStream = Objects.requireNonNull( + getClass().getResourceAsStream("empty.txt"), + "example for Quantum Material Simulation is unavailable!" + ); + var problem = new Problem<>(QUANTUM_MATERIAL_SIMULATION); + problem.setInput(resourceProvider.readStream(problemInputStream)); + return Set.of(problem); + } catch (IOException e) { + throw new MissingExampleException(QUANTUM_MATERIAL_SIMULATION, e); + } + } + + +} diff --git a/src/main/java/edu/kit/provideq/toolbox/quantum/material/simulation/solvers/QuantumMaterialSimulationSolver.java b/src/main/java/edu/kit/provideq/toolbox/quantum/material/simulation/solvers/QuantumMaterialSimulationSolver.java new file mode 100644 index 00000000..a9e07125 --- /dev/null +++ b/src/main/java/edu/kit/provideq/toolbox/quantum/material/simulation/solvers/QuantumMaterialSimulationSolver.java @@ -0,0 +1,12 @@ +package edu.kit.provideq.toolbox.quantum.material.simulation.solvers; + +import edu.kit.provideq.toolbox.meta.ProblemSolver; +import edu.kit.provideq.toolbox.meta.ProblemType; +import edu.kit.provideq.toolbox.quantum.material.simulation.QuantumMaterialSimulationConfiguration; + +public abstract class QuantumMaterialSimulationSolver implements ProblemSolver { + @Override + public ProblemType getProblemType() { + return QuantumMaterialSimulationConfiguration.QUANTUM_MATERIAL_SIMULATION; + } +} diff --git a/src/main/java/edu/kit/provideq/toolbox/vrp/clusterer/VrpClustererConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/vrp/clusterer/VrpClustererConfiguration.java index 1d2e244c..93439c0f 100644 --- a/src/main/java/edu/kit/provideq/toolbox/vrp/clusterer/VrpClustererConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/vrp/clusterer/VrpClustererConfiguration.java @@ -19,9 +19,7 @@ public class VrpClustererConfiguration { public static final ProblemType CLUSTER_VRP = new ProblemType<>( "cluster-vrp", String.class, - String.class, - null, - null + String.class ); @Bean diff --git a/src/main/resources/edu/kit/provideq/toolbox/quantum.material.simulation/empty.txt b/src/main/resources/edu/kit/provideq/toolbox/quantum.material.simulation/empty.txt new file mode 100644 index 00000000..e69de29b From bb609f40d3b466a2458b8eac763009b6403d3a24 Mon Sep 17 00:00:00 2001 From: zaibod <92274292+zaibod@users.noreply.github.com> Date: Tue, 15 Apr 2025 12:57:08 +0200 Subject: [PATCH 002/137] refactor: renamed package and classes --- .../MaterialSimulationConfiguration.java} | 18 +++++++++--------- .../solvers/MaterialSimulationSolver.java | 12 ++++++++++++ .../QuantumMaterialSimulationSolver.java | 12 ------------ .../empty.txt | 0 4 files changed, 21 insertions(+), 21 deletions(-) rename src/main/java/edu/kit/provideq/toolbox/{quantum/material/simulation/QuantumMaterialSimulationConfiguration.java => materialsimulation/MaterialSimulationConfiguration.java} (67%) create mode 100644 src/main/java/edu/kit/provideq/toolbox/materialsimulation/solvers/MaterialSimulationSolver.java delete mode 100644 src/main/java/edu/kit/provideq/toolbox/quantum/material/simulation/solvers/QuantumMaterialSimulationSolver.java rename src/main/resources/edu/kit/provideq/toolbox/{quantum.material.simulation => materialsimulation}/empty.txt (100%) diff --git a/src/main/java/edu/kit/provideq/toolbox/quantum/material/simulation/QuantumMaterialSimulationConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/materialsimulation/MaterialSimulationConfiguration.java similarity index 67% rename from src/main/java/edu/kit/provideq/toolbox/quantum/material/simulation/QuantumMaterialSimulationConfiguration.java rename to src/main/java/edu/kit/provideq/toolbox/materialsimulation/MaterialSimulationConfiguration.java index b90cc8d9..ab5d87cc 100644 --- a/src/main/java/edu/kit/provideq/toolbox/quantum/material/simulation/QuantumMaterialSimulationConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/materialsimulation/MaterialSimulationConfiguration.java @@ -1,4 +1,4 @@ -package edu.kit.provideq.toolbox.quantum.material.simulation; +package edu.kit.provideq.toolbox.materialsimulation; import edu.kit.provideq.toolbox.ResourceProvider; import edu.kit.provideq.toolbox.exception.MissingExampleException; @@ -15,22 +15,22 @@ * Definition and registration of the Quantum Material Simulation problem. */ @Configuration -public class QuantumMaterialSimulationConfiguration { +public class MaterialSimulationConfiguration { /** * A simulation problem, computing electronic properties of a molecular system. */ - public static final ProblemType QUANTUM_MATERIAL_SIMULATION = new ProblemType<>( - "quantum-material-simulation", + public static final ProblemType MATERIAL_SIMULATION = new ProblemType<>( + "material-simulation", String.class, String.class ); - ProblemManager getQuantumMaterialSimulationManager( + ProblemManager getMaterialSimulationManager( ResourceProvider resourceProvider ) { return new ProblemManager<>( - QUANTUM_MATERIAL_SIMULATION, + MATERIAL_SIMULATION, Set.of(), loadExampleProblems(resourceProvider) ); @@ -41,13 +41,13 @@ private Set> loadExampleProblems( try { var problemInputStream = Objects.requireNonNull( getClass().getResourceAsStream("empty.txt"), - "example for Quantum Material Simulation is unavailable!" + "example for Material Simulation is unavailable!" ); - var problem = new Problem<>(QUANTUM_MATERIAL_SIMULATION); + var problem = new Problem<>(MATERIAL_SIMULATION); problem.setInput(resourceProvider.readStream(problemInputStream)); return Set.of(problem); } catch (IOException e) { - throw new MissingExampleException(QUANTUM_MATERIAL_SIMULATION, e); + throw new MissingExampleException(MATERIAL_SIMULATION, e); } } diff --git a/src/main/java/edu/kit/provideq/toolbox/materialsimulation/solvers/MaterialSimulationSolver.java b/src/main/java/edu/kit/provideq/toolbox/materialsimulation/solvers/MaterialSimulationSolver.java new file mode 100644 index 00000000..ddb79c4b --- /dev/null +++ b/src/main/java/edu/kit/provideq/toolbox/materialsimulation/solvers/MaterialSimulationSolver.java @@ -0,0 +1,12 @@ +package edu.kit.provideq.toolbox.materialsimulation.solvers; + +import edu.kit.provideq.toolbox.materialsimulation.MaterialSimulationConfiguration; +import edu.kit.provideq.toolbox.meta.ProblemSolver; +import edu.kit.provideq.toolbox.meta.ProblemType; + +public abstract class MaterialSimulationSolver implements ProblemSolver { + @Override + public ProblemType getProblemType() { + return MaterialSimulationConfiguration.MATERIAL_SIMULATION; + } +} diff --git a/src/main/java/edu/kit/provideq/toolbox/quantum/material/simulation/solvers/QuantumMaterialSimulationSolver.java b/src/main/java/edu/kit/provideq/toolbox/quantum/material/simulation/solvers/QuantumMaterialSimulationSolver.java deleted file mode 100644 index a9e07125..00000000 --- a/src/main/java/edu/kit/provideq/toolbox/quantum/material/simulation/solvers/QuantumMaterialSimulationSolver.java +++ /dev/null @@ -1,12 +0,0 @@ -package edu.kit.provideq.toolbox.quantum.material.simulation.solvers; - -import edu.kit.provideq.toolbox.meta.ProblemSolver; -import edu.kit.provideq.toolbox.meta.ProblemType; -import edu.kit.provideq.toolbox.quantum.material.simulation.QuantumMaterialSimulationConfiguration; - -public abstract class QuantumMaterialSimulationSolver implements ProblemSolver { - @Override - public ProblemType getProblemType() { - return QuantumMaterialSimulationConfiguration.QUANTUM_MATERIAL_SIMULATION; - } -} diff --git a/src/main/resources/edu/kit/provideq/toolbox/quantum.material.simulation/empty.txt b/src/main/resources/edu/kit/provideq/toolbox/materialsimulation/empty.txt similarity index 100% rename from src/main/resources/edu/kit/provideq/toolbox/quantum.material.simulation/empty.txt rename to src/main/resources/edu/kit/provideq/toolbox/materialsimulation/empty.txt From e242fc378997f90d5b76a54294be66ed7409d14c Mon Sep 17 00:00:00 2001 From: zaibod <92274292+zaibod@users.noreply.github.com> Date: Thu, 24 Apr 2025 15:35:03 +0200 Subject: [PATCH 003/137] feat: added h2 example --- .../materialsimulation/MaterialSimulationConfiguration.java | 4 ++-- .../edu/kit/provideq/toolbox/materialsimulation/empty.txt | 0 .../edu/kit/provideq/toolbox/materialsimulation/h2.txt | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) delete mode 100644 src/main/resources/edu/kit/provideq/toolbox/materialsimulation/empty.txt create mode 100644 src/main/resources/edu/kit/provideq/toolbox/materialsimulation/h2.txt diff --git a/src/main/java/edu/kit/provideq/toolbox/materialsimulation/MaterialSimulationConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/materialsimulation/MaterialSimulationConfiguration.java index ab5d87cc..deb71466 100644 --- a/src/main/java/edu/kit/provideq/toolbox/materialsimulation/MaterialSimulationConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/materialsimulation/MaterialSimulationConfiguration.java @@ -40,8 +40,8 @@ private Set> loadExampleProblems( ResourceProvider resourceProvider) { try { var problemInputStream = Objects.requireNonNull( - getClass().getResourceAsStream("empty.txt"), - "example for Material Simulation is unavailable!" + getClass().getResourceAsStream("h2.txt"), + "H2 example for Material Simulation is unavailable!" ); var problem = new Problem<>(MATERIAL_SIMULATION); problem.setInput(resourceProvider.readStream(problemInputStream)); diff --git a/src/main/resources/edu/kit/provideq/toolbox/materialsimulation/empty.txt b/src/main/resources/edu/kit/provideq/toolbox/materialsimulation/empty.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/src/main/resources/edu/kit/provideq/toolbox/materialsimulation/h2.txt b/src/main/resources/edu/kit/provideq/toolbox/materialsimulation/h2.txt new file mode 100644 index 00000000..62bd654d --- /dev/null +++ b/src/main/resources/edu/kit/provideq/toolbox/materialsimulation/h2.txt @@ -0,0 +1 @@ +H 0 0 0; H 0 0 0.735 \ No newline at end of file From c0bbefa3e8b1f48284e699d79aae7216e9b020a3 Mon Sep 17 00:00:00 2001 From: zaibod <92274292+zaibod@users.noreply.github.com> Date: Tue, 15 Apr 2025 00:38:50 +0200 Subject: [PATCH 004/137] basic setup without solvers --- ...uantumMaterialSimulationConfiguration.java | 55 +++++++++++++++++++ .../QuantumMaterialSimulationSolver.java | 12 ++++ .../clusterer/VrpClustererConfiguration.java | 4 +- .../quantum.material.simulation/empty.txt | 0 4 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 src/main/java/edu/kit/provideq/toolbox/quantum/material/simulation/QuantumMaterialSimulationConfiguration.java create mode 100644 src/main/java/edu/kit/provideq/toolbox/quantum/material/simulation/solvers/QuantumMaterialSimulationSolver.java create mode 100644 src/main/resources/edu/kit/provideq/toolbox/quantum.material.simulation/empty.txt diff --git a/src/main/java/edu/kit/provideq/toolbox/quantum/material/simulation/QuantumMaterialSimulationConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/quantum/material/simulation/QuantumMaterialSimulationConfiguration.java new file mode 100644 index 00000000..b90cc8d9 --- /dev/null +++ b/src/main/java/edu/kit/provideq/toolbox/quantum/material/simulation/QuantumMaterialSimulationConfiguration.java @@ -0,0 +1,55 @@ +package edu.kit.provideq.toolbox.quantum.material.simulation; + +import edu.kit.provideq.toolbox.ResourceProvider; +import edu.kit.provideq.toolbox.exception.MissingExampleException; +import edu.kit.provideq.toolbox.meta.Problem; +import edu.kit.provideq.toolbox.meta.ProblemManager; +import edu.kit.provideq.toolbox.meta.ProblemType; +import java.io.IOException; +import java.util.Objects; +import java.util.Set; +import org.springframework.context.annotation.Configuration; + + +/** + * Definition and registration of the Quantum Material Simulation problem. + */ +@Configuration +public class QuantumMaterialSimulationConfiguration { + + /** + * A simulation problem, computing electronic properties of a molecular system. + */ + public static final ProblemType QUANTUM_MATERIAL_SIMULATION = new ProblemType<>( + "quantum-material-simulation", + String.class, + String.class + ); + + ProblemManager getQuantumMaterialSimulationManager( + ResourceProvider resourceProvider + ) { + return new ProblemManager<>( + QUANTUM_MATERIAL_SIMULATION, + Set.of(), + loadExampleProblems(resourceProvider) + ); + } + + private Set> loadExampleProblems( + ResourceProvider resourceProvider) { + try { + var problemInputStream = Objects.requireNonNull( + getClass().getResourceAsStream("empty.txt"), + "example for Quantum Material Simulation is unavailable!" + ); + var problem = new Problem<>(QUANTUM_MATERIAL_SIMULATION); + problem.setInput(resourceProvider.readStream(problemInputStream)); + return Set.of(problem); + } catch (IOException e) { + throw new MissingExampleException(QUANTUM_MATERIAL_SIMULATION, e); + } + } + + +} diff --git a/src/main/java/edu/kit/provideq/toolbox/quantum/material/simulation/solvers/QuantumMaterialSimulationSolver.java b/src/main/java/edu/kit/provideq/toolbox/quantum/material/simulation/solvers/QuantumMaterialSimulationSolver.java new file mode 100644 index 00000000..a9e07125 --- /dev/null +++ b/src/main/java/edu/kit/provideq/toolbox/quantum/material/simulation/solvers/QuantumMaterialSimulationSolver.java @@ -0,0 +1,12 @@ +package edu.kit.provideq.toolbox.quantum.material.simulation.solvers; + +import edu.kit.provideq.toolbox.meta.ProblemSolver; +import edu.kit.provideq.toolbox.meta.ProblemType; +import edu.kit.provideq.toolbox.quantum.material.simulation.QuantumMaterialSimulationConfiguration; + +public abstract class QuantumMaterialSimulationSolver implements ProblemSolver { + @Override + public ProblemType getProblemType() { + return QuantumMaterialSimulationConfiguration.QUANTUM_MATERIAL_SIMULATION; + } +} diff --git a/src/main/java/edu/kit/provideq/toolbox/vrp/clusterer/VrpClustererConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/vrp/clusterer/VrpClustererConfiguration.java index 1d2e244c..93439c0f 100644 --- a/src/main/java/edu/kit/provideq/toolbox/vrp/clusterer/VrpClustererConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/vrp/clusterer/VrpClustererConfiguration.java @@ -19,9 +19,7 @@ public class VrpClustererConfiguration { public static final ProblemType CLUSTER_VRP = new ProblemType<>( "cluster-vrp", String.class, - String.class, - null, - null + String.class ); @Bean diff --git a/src/main/resources/edu/kit/provideq/toolbox/quantum.material.simulation/empty.txt b/src/main/resources/edu/kit/provideq/toolbox/quantum.material.simulation/empty.txt new file mode 100644 index 00000000..e69de29b From a924300da5d6a96f78f3864fa7ccf03fb43d5340 Mon Sep 17 00:00:00 2001 From: zaibod <92274292+zaibod@users.noreply.github.com> Date: Tue, 15 Apr 2025 12:57:08 +0200 Subject: [PATCH 005/137] refactor: renamed package and classes --- .../MaterialSimulationConfiguration.java} | 18 +++++++++--------- .../solvers/MaterialSimulationSolver.java | 12 ++++++++++++ .../QuantumMaterialSimulationSolver.java | 12 ------------ .../empty.txt | 0 4 files changed, 21 insertions(+), 21 deletions(-) rename src/main/java/edu/kit/provideq/toolbox/{quantum/material/simulation/QuantumMaterialSimulationConfiguration.java => materialsimulation/MaterialSimulationConfiguration.java} (67%) create mode 100644 src/main/java/edu/kit/provideq/toolbox/materialsimulation/solvers/MaterialSimulationSolver.java delete mode 100644 src/main/java/edu/kit/provideq/toolbox/quantum/material/simulation/solvers/QuantumMaterialSimulationSolver.java rename src/main/resources/edu/kit/provideq/toolbox/{quantum.material.simulation => materialsimulation}/empty.txt (100%) diff --git a/src/main/java/edu/kit/provideq/toolbox/quantum/material/simulation/QuantumMaterialSimulationConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/materialsimulation/MaterialSimulationConfiguration.java similarity index 67% rename from src/main/java/edu/kit/provideq/toolbox/quantum/material/simulation/QuantumMaterialSimulationConfiguration.java rename to src/main/java/edu/kit/provideq/toolbox/materialsimulation/MaterialSimulationConfiguration.java index b90cc8d9..ab5d87cc 100644 --- a/src/main/java/edu/kit/provideq/toolbox/quantum/material/simulation/QuantumMaterialSimulationConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/materialsimulation/MaterialSimulationConfiguration.java @@ -1,4 +1,4 @@ -package edu.kit.provideq.toolbox.quantum.material.simulation; +package edu.kit.provideq.toolbox.materialsimulation; import edu.kit.provideq.toolbox.ResourceProvider; import edu.kit.provideq.toolbox.exception.MissingExampleException; @@ -15,22 +15,22 @@ * Definition and registration of the Quantum Material Simulation problem. */ @Configuration -public class QuantumMaterialSimulationConfiguration { +public class MaterialSimulationConfiguration { /** * A simulation problem, computing electronic properties of a molecular system. */ - public static final ProblemType QUANTUM_MATERIAL_SIMULATION = new ProblemType<>( - "quantum-material-simulation", + public static final ProblemType MATERIAL_SIMULATION = new ProblemType<>( + "material-simulation", String.class, String.class ); - ProblemManager getQuantumMaterialSimulationManager( + ProblemManager getMaterialSimulationManager( ResourceProvider resourceProvider ) { return new ProblemManager<>( - QUANTUM_MATERIAL_SIMULATION, + MATERIAL_SIMULATION, Set.of(), loadExampleProblems(resourceProvider) ); @@ -41,13 +41,13 @@ private Set> loadExampleProblems( try { var problemInputStream = Objects.requireNonNull( getClass().getResourceAsStream("empty.txt"), - "example for Quantum Material Simulation is unavailable!" + "example for Material Simulation is unavailable!" ); - var problem = new Problem<>(QUANTUM_MATERIAL_SIMULATION); + var problem = new Problem<>(MATERIAL_SIMULATION); problem.setInput(resourceProvider.readStream(problemInputStream)); return Set.of(problem); } catch (IOException e) { - throw new MissingExampleException(QUANTUM_MATERIAL_SIMULATION, e); + throw new MissingExampleException(MATERIAL_SIMULATION, e); } } diff --git a/src/main/java/edu/kit/provideq/toolbox/materialsimulation/solvers/MaterialSimulationSolver.java b/src/main/java/edu/kit/provideq/toolbox/materialsimulation/solvers/MaterialSimulationSolver.java new file mode 100644 index 00000000..ddb79c4b --- /dev/null +++ b/src/main/java/edu/kit/provideq/toolbox/materialsimulation/solvers/MaterialSimulationSolver.java @@ -0,0 +1,12 @@ +package edu.kit.provideq.toolbox.materialsimulation.solvers; + +import edu.kit.provideq.toolbox.materialsimulation.MaterialSimulationConfiguration; +import edu.kit.provideq.toolbox.meta.ProblemSolver; +import edu.kit.provideq.toolbox.meta.ProblemType; + +public abstract class MaterialSimulationSolver implements ProblemSolver { + @Override + public ProblemType getProblemType() { + return MaterialSimulationConfiguration.MATERIAL_SIMULATION; + } +} diff --git a/src/main/java/edu/kit/provideq/toolbox/quantum/material/simulation/solvers/QuantumMaterialSimulationSolver.java b/src/main/java/edu/kit/provideq/toolbox/quantum/material/simulation/solvers/QuantumMaterialSimulationSolver.java deleted file mode 100644 index a9e07125..00000000 --- a/src/main/java/edu/kit/provideq/toolbox/quantum/material/simulation/solvers/QuantumMaterialSimulationSolver.java +++ /dev/null @@ -1,12 +0,0 @@ -package edu.kit.provideq.toolbox.quantum.material.simulation.solvers; - -import edu.kit.provideq.toolbox.meta.ProblemSolver; -import edu.kit.provideq.toolbox.meta.ProblemType; -import edu.kit.provideq.toolbox.quantum.material.simulation.QuantumMaterialSimulationConfiguration; - -public abstract class QuantumMaterialSimulationSolver implements ProblemSolver { - @Override - public ProblemType getProblemType() { - return QuantumMaterialSimulationConfiguration.QUANTUM_MATERIAL_SIMULATION; - } -} diff --git a/src/main/resources/edu/kit/provideq/toolbox/quantum.material.simulation/empty.txt b/src/main/resources/edu/kit/provideq/toolbox/materialsimulation/empty.txt similarity index 100% rename from src/main/resources/edu/kit/provideq/toolbox/quantum.material.simulation/empty.txt rename to src/main/resources/edu/kit/provideq/toolbox/materialsimulation/empty.txt From 6db3bee5c6366b197f9df95c209a768bdc86bdf6 Mon Sep 17 00:00:00 2001 From: zaibod <92274292+zaibod@users.noreply.github.com> Date: Thu, 24 Apr 2025 15:35:03 +0200 Subject: [PATCH 006/137] feat: added h2 example --- .../materialsimulation/MaterialSimulationConfiguration.java | 4 ++-- .../edu/kit/provideq/toolbox/materialsimulation/empty.txt | 0 .../edu/kit/provideq/toolbox/materialsimulation/h2.txt | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) delete mode 100644 src/main/resources/edu/kit/provideq/toolbox/materialsimulation/empty.txt create mode 100644 src/main/resources/edu/kit/provideq/toolbox/materialsimulation/h2.txt diff --git a/src/main/java/edu/kit/provideq/toolbox/materialsimulation/MaterialSimulationConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/materialsimulation/MaterialSimulationConfiguration.java index ab5d87cc..deb71466 100644 --- a/src/main/java/edu/kit/provideq/toolbox/materialsimulation/MaterialSimulationConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/materialsimulation/MaterialSimulationConfiguration.java @@ -40,8 +40,8 @@ private Set> loadExampleProblems( ResourceProvider resourceProvider) { try { var problemInputStream = Objects.requireNonNull( - getClass().getResourceAsStream("empty.txt"), - "example for Material Simulation is unavailable!" + getClass().getResourceAsStream("h2.txt"), + "H2 example for Material Simulation is unavailable!" ); var problem = new Problem<>(MATERIAL_SIMULATION); problem.setInput(resourceProvider.readStream(problemInputStream)); diff --git a/src/main/resources/edu/kit/provideq/toolbox/materialsimulation/empty.txt b/src/main/resources/edu/kit/provideq/toolbox/materialsimulation/empty.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/src/main/resources/edu/kit/provideq/toolbox/materialsimulation/h2.txt b/src/main/resources/edu/kit/provideq/toolbox/materialsimulation/h2.txt new file mode 100644 index 00000000..62bd654d --- /dev/null +++ b/src/main/resources/edu/kit/provideq/toolbox/materialsimulation/h2.txt @@ -0,0 +1 @@ +H 0 0 0; H 0 0 0.735 \ No newline at end of file From 7639f1a1faf77d604a5b6144e2e0a688479ad542 Mon Sep 17 00:00:00 2001 From: zaibod <92274292+zaibod@users.noreply.github.com> Date: Thu, 8 May 2025 10:12:19 +0200 Subject: [PATCH 007/137] feat: added java wrapper for python materialsimulation solver --- .../QiskitMaterialSimulationSolver.java | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 src/main/java/edu/kit/provideq/toolbox/materialsimulation/solvers/QiskitMaterialSimulationSolver.java diff --git a/src/main/java/edu/kit/provideq/toolbox/materialsimulation/solvers/QiskitMaterialSimulationSolver.java b/src/main/java/edu/kit/provideq/toolbox/materialsimulation/solvers/QiskitMaterialSimulationSolver.java new file mode 100644 index 00000000..fedba1f2 --- /dev/null +++ b/src/main/java/edu/kit/provideq/toolbox/materialsimulation/solvers/QiskitMaterialSimulationSolver.java @@ -0,0 +1,65 @@ +package edu.kit.provideq.toolbox.materialsimulation.solvers; + +import edu.kit.provideq.toolbox.Solution; +import edu.kit.provideq.toolbox.materialsimulation.MaterialSimulationConfiguration; +import edu.kit.provideq.toolbox.meta.SolvingProperties; +import edu.kit.provideq.toolbox.meta.SubRoutineResolver; +import edu.kit.provideq.toolbox.process.ProcessRunner; +import edu.kit.provideq.toolbox.process.PythonProcessRunner; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Component; +import reactor.core.publisher.Mono; + +/** + * {@link MaterialSimulationConfiguration#MATERIAL_SIMULATION} solver using a Qiskit + * implementation. + */ +@Component +public class QiskitMaterialSimulationSolver extends MaterialSimulationSolver { + private final String scriptPath; + private final String venv; + private final ApplicationContext context; + + @Autowired + public QiskitMaterialSimulationSolver( + @Value("$path.qiskit.materialsimulation") String scriptPath, + @Value("$venv.qiskit.materialsimulation") String venv, + ApplicationContext context) { + this.scriptPath = scriptPath; + this.venv = venv; + this.context = context; + } + + @Override + public String getName() { + return "Qiskit Material Simulation"; + } + + @Override + public String getDescription() { + return "Solves the material simulation problem using Qiskit with VQE."; + } + + @Override + public Mono> solve( + String input, + SubRoutineResolver subRoutineResolver, + SolvingProperties properties + ) { + var solution = new Solution<>(this); + + var processResult = context + .getBean(PythonProcessRunner.class, scriptPath, venv) + .withArguments( + ProcessRunner.INPUT_FILE_PATH, + ProcessRunner.OUTPUT_FILE_PATH + ) + .writeInputFile(input) + .readOutputFile() + .run(getProblemType(), solution.getId()); + + return Mono.just(processResult.applyTo(solution)); + } +} From 130539b3dc13d00de18581c73cd1c438f250b149 Mon Sep 17 00:00:00 2001 From: zaibod <92274292+zaibod@users.noreply.github.com> Date: Thu, 8 May 2025 10:13:12 +0200 Subject: [PATCH 008/137] feat: added qiskit material simulation solver to application properties --- src/main/resources/application.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 86d54fb5..99adad9f 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1 @@ -# default spring profile, correct one will be set during runtime (see ToolboxServerApplication.java) # options: mac, windows, linux spring.profiles.active=linux springdoc.swagger-ui.operationsSorter=alpha springdoc.swagger-ui.tagsSorter=alpha working.directory=jobs examples.directory=examples springdoc.swagger-ui.path=/ # Solvers name.solvers=solvers # Non OS-specific solvers: (typically GAMS and Python) name.gams=gams path.gams=${name.solvers}/${name.gams} name.gams.max-cut=max-cut path.gams.max-cut=${path.gams}/${name.gams.max-cut}/maxcut.gms name.gams.sat=sat path.gams.sat=${path.gams}/${name.gams.sat}/sat.gms name.qiskit=qiskit path.qiskit=${name.solvers}/${name.qiskit} name.qiskit.knapsack=knapsack path.qiskit.knapsack=${path.qiskit}/${name.qiskit.knapsack}/knapsack_qiskit.py venv.qiskit.knapsack=${name.solvers}_${name.qiskit}_${name.qiskit.knapsack} name.qiskit.max-cut=max-cut path.qiskit.max-cut=${path.qiskit}/${name.qiskit.max-cut}/maxCut_qiskit.py venv.qiskit.max-cut=${name.solvers}_${name.qiskit}_${name.qiskit.max-cut} name.qiskit.qubo=qubo path.qiskit.qubo=${path.qiskit}/${name.qiskit.qubo}/qubo_qiskit.py venv.qiskit.qubo=${name.solvers}_${name.qiskit}_${name.qiskit.qubo} name.cirq=cirq path.cirq=${name.solvers}/${name.cirq} name.cirq.max-cut=max-cut path.cirq.max-cut=${path.cirq}/${name.cirq.max-cut}/max_cut_cirq.py venv.cirq.max-cut=${name.solvers}_${name.cirq}_${name.cirq.max-cut} name.qrisp=qrisp path.qrisp=${name.solvers}/${name.qrisp} name.qrisp.vrp=vrp path.qrisp.vrp=${path.qrisp}/${name.qrisp.vrp}/grover.py venv.qrisp.vrp=${name.solvers}_${name.qrisp}_${name.qrisp.vrp} name.qrisp.qubo=qubo path.qrisp.qubo=${path.qrisp}/${name.qrisp.qubo}/qaoa.py venv.qrisp.qubo=${name.solvers}_${name.qrisp}_${name.qrisp.qubo} name.qrisp.sat=sat path.qrisp.sat.grover=${path.qrisp}/${name.qrisp.sat}/grover.py path.qrisp.sat.exact=${path.qrisp}/${name.qrisp.sat}/exact_grover.py venv.qrisp.sat=${name.solvers}_${name.qrisp}_${name.qrisp.sat} name.dwave=dwave path.dwave=${name.solvers}/${name.dwave} name.dwave.qubo=qubo path.dwave.qubo=${path.dwave}/${name.dwave.qubo}/main.py venv.dwave.qubo=${name.solvers}_${name.dwave}_${name.dwave.qubo} # Non OS-specific custom solvers: (solvers that are not part of a framework) name.custom=custom path.custom=${name.solvers}/${name.custom} name.custom.hs-knapsack=hs-knapsack path.custom.hs-knapsack=${path.custom}/${name.custom.hs-knapsack}/knapsack.py venv.custom.hs-knapsack=${name.solvers}_${name.custom}_${name.custom.hs-knapsack} name.custom.lkh=lkh path.custom.lkh=${path.custom}/${name.custom.lkh}/vrp_lkh.py venv.custom.lkh=${name.solvers}_${name.custom}_${name.custom.lkh} name.custom.berger-vrp=berger-vrp name.custom.sharp-sat-bruteforce=sharp-sat-bruteforce path.custom.sharp-sat-bruteforce=${path.custom}/${name.custom.sharp-sat-bruteforce}/exact-solution-counter.py venv.custom.sharp-sat-bruteforce=${name.solvers}_${name.custom}_${name.custom.sharp-sat-bruteforce} name.custom.sharp-sat-ganak=sharp-sat-ganak venv.custom.sharp-sat-ganak=${name.solvers}_${name.custom}_${name.custom.sharp-sat-ganak} # Demonstrators name.demonstrators=demonstrators name.demonstrators.cplex=cplex path.demonstrators.cplex=${name.demonstrators}/${name.demonstrators.cplex} name.demonstrators.cplex.mip=mip-solver path.demonstrators.cplex.mip=${path.demonstrators.cplex}/${name.demonstrators.cplex.mip}/mip-solver.py venv.demonstrators.cplex.mip=${name.demonstrators}_${name.demonstrators.cplex}_${name.demonstrators.cplex.mip} \ No newline at end of file +# default spring profile, correct one will be set during runtime (see ToolboxServerApplication.java) # options: mac, windows, linux spring.profiles.active=linux springdoc.swagger-ui.operationsSorter=alpha springdoc.swagger-ui.tagsSorter=alpha working.directory=jobs examples.directory=examples springdoc.swagger-ui.path=/ # Solvers name.solvers=solvers # Non OS-specific solvers: (typically GAMS and Python) name.gams=gams path.gams=${name.solvers}/${name.gams} name.gams.max-cut=max-cut path.gams.max-cut=${path.gams}/${name.gams.max-cut}/maxcut.gms name.gams.sat=sat path.gams.sat=${path.gams}/${name.gams.sat}/sat.gms name.qiskit=qiskit path.qiskit=${name.solvers}/${name.qiskit} name.qiskit.knapsack=knapsack path.qiskit.knapsack=${path.qiskit}/${name.qiskit.knapsack}/knapsack_qiskit.py venv.qiskit.knapsack=${name.solvers}_${name.qiskit}_${name.qiskit.knapsack} name.qiskit.materialsimulation=materialsimulation path.qiskit.materialsimulation=${path.qiskit}/${name.qiskit.materialsimulation}/material_simulation_qiskit.py venv.qiskit.materialsimulation=${name.solvers}_${name.qiskit}_${name.qiskit.materialsimulation} name.qiskit.max-cut=max-cut path.qiskit.max-cut=${path.qiskit}/${name.qiskit.max-cut}/maxCut_qiskit.py venv.qiskit.max-cut=${name.solvers}_${name.qiskit}_${name.qiskit.max-cut} name.qiskit.qubo=qubo path.qiskit.qubo=${path.qiskit}/${name.qiskit.qubo}/qubo_qiskit.py venv.qiskit.qubo=${name.solvers}_${name.qiskit}_${name.qiskit.qubo} name.cirq=cirq path.cirq=${name.solvers}/${name.cirq} name.cirq.max-cut=max-cut path.cirq.max-cut=${path.cirq}/${name.cirq.max-cut}/max_cut_cirq.py venv.cirq.max-cut=${name.solvers}_${name.cirq}_${name.cirq.max-cut} name.qrisp=qrisp path.qrisp=${name.solvers}/${name.qrisp} name.qrisp.vrp=vrp path.qrisp.vrp=${path.qrisp}/${name.qrisp.vrp}/grover.py venv.qrisp.vrp=${name.solvers}_${name.qrisp}_${name.qrisp.vrp} name.qrisp.qubo=qubo path.qrisp.qubo=${path.qrisp}/${name.qrisp.qubo}/qaoa.py venv.qrisp.qubo=${name.solvers}_${name.qrisp}_${name.qrisp.qubo} name.qrisp.sat=sat path.qrisp.sat.grover=${path.qrisp}/${name.qrisp.sat}/grover.py path.qrisp.sat.exact=${path.qrisp}/${name.qrisp.sat}/exact_grover.py venv.qrisp.sat=${name.solvers}_${name.qrisp}_${name.qrisp.sat} name.dwave=dwave path.dwave=${name.solvers}/${name.dwave} name.dwave.qubo=qubo path.dwave.qubo=${path.dwave}/${name.dwave.qubo}/main.py venv.dwave.qubo=${name.solvers}_${name.dwave}_${name.dwave.qubo} # Non OS-specific custom solvers: (solvers that are not part of a framework) name.custom=custom path.custom=${name.solvers}/${name.custom} name.custom.hs-knapsack=hs-knapsack path.custom.hs-knapsack=${path.custom}/${name.custom.hs-knapsack}/knapsack.py venv.custom.hs-knapsack=${name.solvers}_${name.custom}_${name.custom.hs-knapsack} name.custom.lkh=lkh path.custom.lkh=${path.custom}/${name.custom.lkh}/vrp_lkh.py venv.custom.lkh=${name.solvers}_${name.custom}_${name.custom.lkh} name.custom.berger-vrp=berger-vrp name.custom.sharp-sat-bruteforce=sharp-sat-bruteforce path.custom.sharp-sat-bruteforce=${path.custom}/${name.custom.sharp-sat-bruteforce}/exact-solution-counter.py venv.custom.sharp-sat-bruteforce=${name.solvers}_${name.custom}_${name.custom.sharp-sat-bruteforce} name.custom.sharp-sat-ganak=sharp-sat-ganak venv.custom.sharp-sat-ganak=${name.solvers}_${name.custom}_${name.custom.sharp-sat-ganak} # Demonstrators name.demonstrators=demonstrators name.demonstrators.cplex=cplex path.demonstrators.cplex=${name.demonstrators}/${name.demonstrators.cplex} name.demonstrators.cplex.mip=mip-solver path.demonstrators.cplex.mip=${path.demonstrators.cplex}/${name.demonstrators.cplex.mip}/mip-solver.py venv.demonstrators.cplex.mip=${name.demonstrators}_${name.demonstrators.cplex}_${name.demonstrators.cplex.mip} \ No newline at end of file From 2cc4b70bbc511b637e813d700e163f68d814b11c Mon Sep 17 00:00:00 2001 From: zaibod <92274292+zaibod@users.noreply.github.com> Date: Thu, 8 May 2025 10:15:41 +0200 Subject: [PATCH 009/137] fix: renamed for consistency and added annotation --- .../materialsimulation/MaterialSimulationConfiguration.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/edu/kit/provideq/toolbox/materialsimulation/MaterialSimulationConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/materialsimulation/MaterialSimulationConfiguration.java index deb71466..f5e642ba 100644 --- a/src/main/java/edu/kit/provideq/toolbox/materialsimulation/MaterialSimulationConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/materialsimulation/MaterialSimulationConfiguration.java @@ -8,6 +8,7 @@ import java.io.IOException; import java.util.Objects; import java.util.Set; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -21,11 +22,12 @@ public class MaterialSimulationConfiguration { * A simulation problem, computing electronic properties of a molecular system. */ public static final ProblemType MATERIAL_SIMULATION = new ProblemType<>( - "material-simulation", + "materialsimulation", String.class, String.class ); + @Bean ProblemManager getMaterialSimulationManager( ResourceProvider resourceProvider ) { From 3ece97c17920fb5feac2af2cc7d033c98120681e Mon Sep 17 00:00:00 2001 From: zaibod <92274292+zaibod@users.noreply.github.com> Date: Thu, 8 May 2025 10:16:02 +0200 Subject: [PATCH 010/137] feat: added qiskit solver to material simulation config --- .../materialsimulation/MaterialSimulationConfiguration.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/edu/kit/provideq/toolbox/materialsimulation/MaterialSimulationConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/materialsimulation/MaterialSimulationConfiguration.java index f5e642ba..aea9dfd5 100644 --- a/src/main/java/edu/kit/provideq/toolbox/materialsimulation/MaterialSimulationConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/materialsimulation/MaterialSimulationConfiguration.java @@ -2,6 +2,7 @@ import edu.kit.provideq.toolbox.ResourceProvider; import edu.kit.provideq.toolbox.exception.MissingExampleException; +import edu.kit.provideq.toolbox.materialsimulation.solvers.QiskitMaterialSimulationSolver; import edu.kit.provideq.toolbox.meta.Problem; import edu.kit.provideq.toolbox.meta.ProblemManager; import edu.kit.provideq.toolbox.meta.ProblemType; @@ -29,11 +30,12 @@ public class MaterialSimulationConfiguration { @Bean ProblemManager getMaterialSimulationManager( + QiskitMaterialSimulationSolver qiskitSolver, ResourceProvider resourceProvider ) { return new ProblemManager<>( MATERIAL_SIMULATION, - Set.of(), + Set.of(qiskitSolver), loadExampleProblems(resourceProvider) ); } From 51c63c98b3f8c8e5e303813146d45beafbce011a Mon Sep 17 00:00:00 2001 From: zaibod Date: Thu, 8 May 2025 14:25:59 +0200 Subject: [PATCH 011/137] feat: added qiskit material simulation solver --- .../material_simulation_qiskit.py | 63 +++++++++++++++++++ .../materialsimulation/requirements.txt | 1 + 2 files changed, 64 insertions(+) create mode 100644 solvers/qiskit/materialsimulation/material_simulation_qiskit.py create mode 100644 solvers/qiskit/materialsimulation/requirements.txt diff --git a/solvers/qiskit/materialsimulation/material_simulation_qiskit.py b/solvers/qiskit/materialsimulation/material_simulation_qiskit.py new file mode 100644 index 00000000..1b7932f1 --- /dev/null +++ b/solvers/qiskit/materialsimulation/material_simulation_qiskit.py @@ -0,0 +1,63 @@ +import argparse +from qiskit_nature.units import DistanceUnit +from qiskit_nature.second_q.drivers import PySCFDriver +from qiskit_nature.second_q.mappers import JordanWignerMapper +from qiskit_algorithms import VQE +from qiskit_algorithms.optimizers import SLSQP +from qiskit.primitives import Estimator +from qiskit_nature.second_q.algorithms import GroundStateEigensolver, QEOM, EvaluationRule +from qiskit_nature.second_q.circuit.library import HartreeFock, UCCSD + +parser = argparse.ArgumentParser( + prog="Qiskit Material Simulation", + description="A CLI Program to run Qiskit a Material Simulation" +) + + +def get_problem(molecule): + driver = PySCFDriver( + atom=molecule, + basis="sto3g", + charge=0, + spin=0, + unit=DistanceUnit.ANGSTROM, + ) + return driver.run() + + +def get_ansatz(problem, mapper): + ansatz = UCCSD( + problem.num_spatial_orbitals, + problem.num_particles, + mapper, + initial_state=HartreeFock( + problem.num_orbitals, + problem.num_particles, + mapper + ) + ) + return ansatz + +parser.add_argument("input_file") +parser.add_argument("--output-file") +args = parser.parse_args() + +es_problem = get_problem(args.input_file) +mapper = JordanWignerMapper() +ansatz = get_ansatz(es_problem, mapper) + +estimator = Estimator() +# This first part sets the ground state solver +# see more about this part in the ground state calculation tutorial +solver = VQE(estimator, ansatz, SLSQP()) +solver.initial_point = [0.0] * ansatz.num_parameters +gse = GroundStateEigensolver(mapper, solver) + +# The qEOM algorithm is simply instantiated with the chosen ground state solver and Estimator primitive +qeom_excited_states_solver = QEOM(gse, estimator, "sd", EvaluationRule.ALL) + +# The qEOM algorithm is then run on the problem +qeom_result = qeom_excited_states_solver.solve(es_problem) + +with open(args.output_file, 'w') as f: + f.write(str(qeom_result)) \ No newline at end of file diff --git a/solvers/qiskit/materialsimulation/requirements.txt b/solvers/qiskit/materialsimulation/requirements.txt new file mode 100644 index 00000000..bbbbb99c --- /dev/null +++ b/solvers/qiskit/materialsimulation/requirements.txt @@ -0,0 +1 @@ +qiskit-nature == 0.7.2 From c1df6958e4f20627b851317ee7d1c4f9cd267b8c Mon Sep 17 00:00:00 2001 From: zaibod <92274292+zaibod@users.noreply.github.com> Date: Mon, 12 May 2025 11:59:55 +0200 Subject: [PATCH 012/137] fix: fixed app prop passing --- .../solvers/QiskitMaterialSimulationSolver.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/edu/kit/provideq/toolbox/materialsimulation/solvers/QiskitMaterialSimulationSolver.java b/src/main/java/edu/kit/provideq/toolbox/materialsimulation/solvers/QiskitMaterialSimulationSolver.java index fedba1f2..2f779cbb 100644 --- a/src/main/java/edu/kit/provideq/toolbox/materialsimulation/solvers/QiskitMaterialSimulationSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/materialsimulation/solvers/QiskitMaterialSimulationSolver.java @@ -24,8 +24,8 @@ public class QiskitMaterialSimulationSolver extends MaterialSimulationSolver { @Autowired public QiskitMaterialSimulationSolver( - @Value("$path.qiskit.materialsimulation") String scriptPath, - @Value("$venv.qiskit.materialsimulation") String venv, + @Value("${path.qiskit.materialsimulation}") String scriptPath, + @Value("${venv.qiskit.materialsimulation}") String venv, ApplicationContext context) { this.scriptPath = scriptPath; this.venv = venv; From f5da10cd9bae95ab24321bfa975b2b12acdd4400 Mon Sep 17 00:00:00 2001 From: zaibod <92274292+zaibod@users.noreply.github.com> Date: Tue, 13 May 2025 10:49:24 +0200 Subject: [PATCH 013/137] fix: change argument type to ensure corrrect order --- .../qiskit/materialsimulation/material_simulation_qiskit.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/solvers/qiskit/materialsimulation/material_simulation_qiskit.py b/solvers/qiskit/materialsimulation/material_simulation_qiskit.py index 1b7932f1..703bf0c2 100644 --- a/solvers/qiskit/materialsimulation/material_simulation_qiskit.py +++ b/solvers/qiskit/materialsimulation/material_simulation_qiskit.py @@ -38,8 +38,9 @@ def get_ansatz(problem, mapper): ) return ansatz + parser.add_argument("input_file") -parser.add_argument("--output-file") +parser.add_argument("output-file") args = parser.parse_args() es_problem = get_problem(args.input_file) From 864e4458250301dca92b0696fcc15d193b009e0e Mon Sep 17 00:00:00 2001 From: zaibod <92274292+zaibod@users.noreply.github.com> Date: Tue, 13 May 2025 10:50:02 +0200 Subject: [PATCH 014/137] fix: add correct dependencies --- solvers/qiskit/materialsimulation/requirements.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/solvers/qiskit/materialsimulation/requirements.txt b/solvers/qiskit/materialsimulation/requirements.txt index bbbbb99c..f3f4f165 100644 --- a/solvers/qiskit/materialsimulation/requirements.txt +++ b/solvers/qiskit/materialsimulation/requirements.txt @@ -1 +1,3 @@ +qiskit == 1.4.3 qiskit-nature == 0.7.2 +pyscf \ No newline at end of file From 28bf7bb138a83cab7549d4e1b0e061f3390f607e Mon Sep 17 00:00:00 2001 From: zaibod <92274292+zaibod@users.noreply.github.com> Date: Tue, 13 May 2025 12:06:48 +0200 Subject: [PATCH 015/137] fix: fixed python script bugs --- .../materialsimulation/material_simulation_qiskit.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/solvers/qiskit/materialsimulation/material_simulation_qiskit.py b/solvers/qiskit/materialsimulation/material_simulation_qiskit.py index 703bf0c2..bfe1b614 100644 --- a/solvers/qiskit/materialsimulation/material_simulation_qiskit.py +++ b/solvers/qiskit/materialsimulation/material_simulation_qiskit.py @@ -31,7 +31,7 @@ def get_ansatz(problem, mapper): problem.num_particles, mapper, initial_state=HartreeFock( - problem.num_orbitals, + problem.num_spatial_orbitals, problem.num_particles, mapper ) @@ -43,13 +43,15 @@ def get_ansatz(problem, mapper): parser.add_argument("output-file") args = parser.parse_args() -es_problem = get_problem(args.input_file) +with open(args.input_file, 'r') as input_file: + input_molecule = input_file.read().strip() + +es_problem = get_problem(input_molecule) mapper = JordanWignerMapper() ansatz = get_ansatz(es_problem, mapper) estimator = Estimator() # This first part sets the ground state solver -# see more about this part in the ground state calculation tutorial solver = VQE(estimator, ansatz, SLSQP()) solver.initial_point = [0.0] * ansatz.num_parameters gse = GroundStateEigensolver(mapper, solver) From b5cacfd5487be6dcfc5e61a5f508d344a246c71f Mon Sep 17 00:00:00 2001 From: zaibod <92274292+zaibod@users.noreply.github.com> Date: Thu, 15 May 2025 12:36:45 +0200 Subject: [PATCH 016/137] fix: fixed wrong argument name --- solvers/qiskit/materialsimulation/material_simulation_qiskit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/solvers/qiskit/materialsimulation/material_simulation_qiskit.py b/solvers/qiskit/materialsimulation/material_simulation_qiskit.py index bfe1b614..ee1c482b 100644 --- a/solvers/qiskit/materialsimulation/material_simulation_qiskit.py +++ b/solvers/qiskit/materialsimulation/material_simulation_qiskit.py @@ -40,7 +40,7 @@ def get_ansatz(problem, mapper): parser.add_argument("input_file") -parser.add_argument("output-file") +parser.add_argument("output_file") args = parser.parse_args() with open(args.input_file, 'r') as input_file: From c1a1826a0212226410ee773aeddda337fe444295 Mon Sep 17 00:00:00 2001 From: zaibod <92274292+zaibod@users.noreply.github.com> Date: Thu, 15 May 2025 15:08:07 +0200 Subject: [PATCH 017/137] feat: added material simulation test --- .../api/MaterialSimulationSolversTest.java | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 src/test/java/edu/kit/provideq/toolbox/api/MaterialSimulationSolversTest.java diff --git a/src/test/java/edu/kit/provideq/toolbox/api/MaterialSimulationSolversTest.java b/src/test/java/edu/kit/provideq/toolbox/api/MaterialSimulationSolversTest.java new file mode 100644 index 00000000..c685198a --- /dev/null +++ b/src/test/java/edu/kit/provideq/toolbox/api/MaterialSimulationSolversTest.java @@ -0,0 +1,54 @@ +package edu.kit.provideq.toolbox.api; + +import static edu.kit.provideq.toolbox.materialsimulation.MaterialSimulationConfiguration.MATERIAL_SIMULATION; + +import edu.kit.provideq.toolbox.materialsimulation.solvers.QiskitMaterialSimulationSolver; +import edu.kit.provideq.toolbox.meta.ProblemManagerProvider; +import edu.kit.provideq.toolbox.meta.ProblemSolver; +import java.time.Duration; +import java.util.stream.Stream; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.web.reactive.server.WebTestClient; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@SpringBootTest +@AutoConfigureMockMvc +class MaterialSimulationSolversTest { + @Autowired + private WebTestClient client; + + @Autowired + private ProblemManagerProvider problemManagerProvider; + + @Autowired + private QiskitMaterialSimulationSolver qiskitMaterialSimulationSolver; + + @BeforeEach + void beforeEach() { + this.client = this.client.mutate() + .responseTimeout(Duration.ofSeconds(20)) + .build(); + } + + @SuppressWarnings("OptionalGetWithoutIsPresent") + Stream provideArguments() { + var problemManager = problemManagerProvider.findProblemManagerForType(MATERIAL_SIMULATION).get(); + + return ApiTestHelper.getAllArgumentCombinations(problemManager) + .map(list -> Arguments.of(list.get(0), list.get(1))); + } + + @ParameterizedTest + @MethodSource("provideArguments") + void testMaterialSimulationSolver(ProblemSolver solver, String input) { + var problem = ApiTestHelper.createProblem(client, solver, input, MATERIAL_SIMULATION); + ApiTestHelper.testSolution(problem); + } +} From a5a3aca32a9cb564a4834b00f411fb7c3df90058 Mon Sep 17 00:00:00 2001 From: zaibod <92274292+zaibod@users.noreply.github.com> Date: Thu, 15 May 2025 15:17:40 +0200 Subject: [PATCH 018/137] chore: fixed overly long line --- .../toolbox/api/MaterialSimulationSolversTest.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/test/java/edu/kit/provideq/toolbox/api/MaterialSimulationSolversTest.java b/src/test/java/edu/kit/provideq/toolbox/api/MaterialSimulationSolversTest.java index c685198a..5f6b7f68 100644 --- a/src/test/java/edu/kit/provideq/toolbox/api/MaterialSimulationSolversTest.java +++ b/src/test/java/edu/kit/provideq/toolbox/api/MaterialSimulationSolversTest.java @@ -5,8 +5,10 @@ import edu.kit.provideq.toolbox.materialsimulation.solvers.QiskitMaterialSimulationSolver; import edu.kit.provideq.toolbox.meta.ProblemManagerProvider; import edu.kit.provideq.toolbox.meta.ProblemSolver; + import java.time.Duration; import java.util.stream.Stream; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.params.ParameterizedTest; @@ -27,9 +29,6 @@ class MaterialSimulationSolversTest { @Autowired private ProblemManagerProvider problemManagerProvider; - @Autowired - private QiskitMaterialSimulationSolver qiskitMaterialSimulationSolver; - @BeforeEach void beforeEach() { this.client = this.client.mutate() @@ -39,7 +38,9 @@ void beforeEach() { @SuppressWarnings("OptionalGetWithoutIsPresent") Stream provideArguments() { - var problemManager = problemManagerProvider.findProblemManagerForType(MATERIAL_SIMULATION).get(); + var problemManager = problemManagerProvider + .findProblemManagerForType(MATERIAL_SIMULATION) + .get(); return ApiTestHelper.getAllArgumentCombinations(problemManager) .map(list -> Arguments.of(list.get(0), list.get(1))); From f8fa0199d8a8d27fbe5fa7a27e8e3ec554676e53 Mon Sep 17 00:00:00 2001 From: zaibod <92274292+zaibod@users.noreply.github.com> Date: Thu, 15 May 2025 15:46:43 +0200 Subject: [PATCH 019/137] chore: fixed checkstyle --- .../provideq/toolbox/api/MaterialSimulationSolversTest.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/test/java/edu/kit/provideq/toolbox/api/MaterialSimulationSolversTest.java b/src/test/java/edu/kit/provideq/toolbox/api/MaterialSimulationSolversTest.java index 5f6b7f68..0a121df2 100644 --- a/src/test/java/edu/kit/provideq/toolbox/api/MaterialSimulationSolversTest.java +++ b/src/test/java/edu/kit/provideq/toolbox/api/MaterialSimulationSolversTest.java @@ -2,13 +2,10 @@ import static edu.kit.provideq.toolbox.materialsimulation.MaterialSimulationConfiguration.MATERIAL_SIMULATION; -import edu.kit.provideq.toolbox.materialsimulation.solvers.QiskitMaterialSimulationSolver; import edu.kit.provideq.toolbox.meta.ProblemManagerProvider; import edu.kit.provideq.toolbox.meta.ProblemSolver; - import java.time.Duration; import java.util.stream.Stream; - import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.params.ParameterizedTest; From fff9af930e515e75f486c2c5b9580a5926c8a7cc Mon Sep 17 00:00:00 2001 From: zaibod <92274292+zaibod@users.noreply.github.com> Date: Tue, 20 May 2025 14:53:34 +0200 Subject: [PATCH 020/137] chore: fixed dependency version --- solvers/qiskit/materialsimulation/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/solvers/qiskit/materialsimulation/requirements.txt b/solvers/qiskit/materialsimulation/requirements.txt index f3f4f165..f67cfe6b 100644 --- a/solvers/qiskit/materialsimulation/requirements.txt +++ b/solvers/qiskit/materialsimulation/requirements.txt @@ -1,3 +1,3 @@ qiskit == 1.4.3 qiskit-nature == 0.7.2 -pyscf \ No newline at end of file +pyscf == 2.9.0 \ No newline at end of file From 9e989ad26acc56d0f3bb3a678fc037fccdf7be22 Mon Sep 17 00:00:00 2001 From: zaibod <92274292+zaibod@users.noreply.github.com> Date: Tue, 27 May 2025 10:51:25 +0200 Subject: [PATCH 021/137] feat: added molecule charge and spin as parameters to script --- .../materialsimulation/material_simulation_qiskit.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/solvers/qiskit/materialsimulation/material_simulation_qiskit.py b/solvers/qiskit/materialsimulation/material_simulation_qiskit.py index ee1c482b..b06a5704 100644 --- a/solvers/qiskit/materialsimulation/material_simulation_qiskit.py +++ b/solvers/qiskit/materialsimulation/material_simulation_qiskit.py @@ -14,12 +14,12 @@ ) -def get_problem(molecule): +def get_problem(molecule, charge, spin): driver = PySCFDriver( atom=molecule, basis="sto3g", - charge=0, - spin=0, + charge=charge, + spin=spin, unit=DistanceUnit.ANGSTROM, ) return driver.run() @@ -41,12 +41,14 @@ def get_ansatz(problem, mapper): parser.add_argument("input_file") parser.add_argument("output_file") +parser.add_argument("--charge", type=int, default=0, help="Charge of the molecule") +parser.add_argument("--spin", type=int, default=0, help="Spin of the molecule") args = parser.parse_args() with open(args.input_file, 'r') as input_file: input_molecule = input_file.read().strip() -es_problem = get_problem(input_molecule) +es_problem = get_problem(input_molecule, args.charge, args.spin) mapper = JordanWignerMapper() ansatz = get_ansatz(es_problem, mapper) From 60fdfde883b6c1c6a4163fad9911c499e23d5913 Mon Sep 17 00:00:00 2001 From: zaibod <92274292+zaibod@users.noreply.github.com> Date: Tue, 27 May 2025 10:51:46 +0200 Subject: [PATCH 022/137] feat: added molecule charge and spin as solver settings --- .../QiskitMaterialSimulationSolver.java | 49 ++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/src/main/java/edu/kit/provideq/toolbox/materialsimulation/solvers/QiskitMaterialSimulationSolver.java b/src/main/java/edu/kit/provideq/toolbox/materialsimulation/solvers/QiskitMaterialSimulationSolver.java index 2f779cbb..986a516a 100644 --- a/src/main/java/edu/kit/provideq/toolbox/materialsimulation/solvers/QiskitMaterialSimulationSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/materialsimulation/solvers/QiskitMaterialSimulationSolver.java @@ -4,14 +4,18 @@ import edu.kit.provideq.toolbox.materialsimulation.MaterialSimulationConfiguration; import edu.kit.provideq.toolbox.meta.SolvingProperties; import edu.kit.provideq.toolbox.meta.SubRoutineResolver; +import edu.kit.provideq.toolbox.meta.setting.SolverSetting; +import edu.kit.provideq.toolbox.meta.setting.basic.IntegerSetting; import edu.kit.provideq.toolbox.process.ProcessRunner; import edu.kit.provideq.toolbox.process.PythonProcessRunner; +import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Component; import reactor.core.publisher.Mono; + /** * {@link MaterialSimulationConfiguration#MATERIAL_SIMULATION} solver using a Qiskit * implementation. @@ -22,6 +26,12 @@ public class QiskitMaterialSimulationSolver extends MaterialSimulationSolver { private final String venv; private final ApplicationContext context; + private static final String SETTING_CHARGE = "Charge"; + private static final int DEFAULT_CHARGE = 0; + + private static final String SETTING_SPIN = "Spin"; + private static final int DEFAULT_SPIN = 0; + @Autowired public QiskitMaterialSimulationSolver( @Value("${path.qiskit.materialsimulation}") String scriptPath, @@ -42,6 +52,26 @@ public String getDescription() { return "Solves the material simulation problem using Qiskit with VQE."; } + @Override + public List getSolverSettings() { + return List.of( + new IntegerSetting( + SETTING_CHARGE, + "The charge of the molecule. The parameter to define " + + "the total number of electrons in the system.", + DEFAULT_CHARGE, + 0, Integer.MAX_VALUE + ), + new IntegerSetting( + SETTING_SPIN, + "The spin of the molecule. Equals the number of unpaired electrons 2S," + + " i.e. the difference between the number of alpha and beta electrons.", + DEFAULT_SPIN, + 0, Integer.MAX_VALUE + ) + ); + } + @Override public Mono> solve( String input, @@ -50,11 +80,28 @@ public Mono> solve( ) { var solution = new Solution<>(this); + int charge = properties.getSetting(SETTING_CHARGE) + .map(IntegerSetting::getValue) + .orElse(DEFAULT_CHARGE); + + int spin = properties.getSetting(SETTING_SPIN) + .map(IntegerSetting::getValue) + .orElse(DEFAULT_SPIN); + + if (charge < 0) { + throw new IllegalArgumentException("Charge must be non-negative"); + } + if (spin < 0) { + throw new IllegalArgumentException("Spin must be non-negative"); + } + var processResult = context .getBean(PythonProcessRunner.class, scriptPath, venv) .withArguments( ProcessRunner.INPUT_FILE_PATH, - ProcessRunner.OUTPUT_FILE_PATH + ProcessRunner.OUTPUT_FILE_PATH, + "--charge", String.valueOf(charge), + "--spin", String.valueOf(spin) ) .writeInputFile(input) .readOutputFile() From d1ddd163219fee8537ae2850fe92f21984b04f11 Mon Sep 17 00:00:00 2001 From: Domenik Eichhorn Date: Tue, 15 Jul 2025 14:15:26 +0200 Subject: [PATCH 023/137] add: citation file --- CITATION.bib | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 CITATION.bib diff --git a/CITATION.bib b/CITATION.bib new file mode 100644 index 00000000..71f536aa --- /dev/null +++ b/CITATION.bib @@ -0,0 +1,6 @@ +@article{eichhorn2025provideq, + title = {ProvideQ: A Quantum Optimization Toolbox}, + author = {Eichhorn, Domenik and Poser, Nick and Schweikart, Maximilian and Schaefer, Ina}, + journal = {arXiv preprint arXiv:2507.07649}, + year = {2025} +} \ No newline at end of file From 7ff0c17c493a78e583d37828f3286c48edc08e53 Mon Sep 17 00:00:00 2001 From: zaibod <92274292+zaibod@users.noreply.github.com> Date: Mon, 13 Oct 2025 16:28:25 +0200 Subject: [PATCH 024/137] Update copyright year in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1c896dfc..2d35e07b 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,6 @@ To use this, enable GitHub Actions and configure the following secrets in the Gi 3. Set `DOKKU_DEPLOYMENT_KEY` to the private key generated in step i. ## License -Copyright (c) 2022 - 2023 ProvideQ +Copyright (c) 2022 - 2025 ProvideQ This project is available under the [MIT License](./LICENSE). From 55cee69b914af4237cea027a323c0ce4b1d8bae0 Mon Sep 17 00:00:00 2001 From: zaibod <92274292+zaibod@users.noreply.github.com> Date: Wed, 29 Oct 2025 18:40:38 +0100 Subject: [PATCH 025/137] added debug echos to track error Add debug messages around GAMS setup script execution --- .github/workflows/ci-cd-develop.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci-cd-develop.yml b/.github/workflows/ci-cd-develop.yml index da4d67d9..c4725f37 100644 --- a/.github/workflows/ci-cd-develop.yml +++ b/.github/workflows/ci-cd-develop.yml @@ -25,7 +25,9 @@ jobs: env: GAMS_LICENSE: ${{ secrets.GAMS_LICENSE }} run: | + echo "DEBUG: before source" source ./scripts/setup-gams.sh + echo "DEBUG: after source echo "/opt/conda/bin" >> "$GITHUB_PATH" echo "$GAMS_PATH" >> "$GITHUB_PATH" echo "GMSPYTHONLIB=/opt/conda/envs/gams/lib/libpython3.10.so" >> "$GITHUB_ENV" From a93b9194b4fe7a8f0259565f858f322f34acb0bd Mon Sep 17 00:00:00 2001 From: zaibod <92274292+zaibod@users.noreply.github.com> Date: Tue, 18 Nov 2025 16:24:06 +0100 Subject: [PATCH 026/137] debug: add debug echos to gams setup Added echo statements to provide feedback during the GAMS installation process. --- scripts/setup-gams.sh | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/scripts/setup-gams.sh b/scripts/setup-gams.sh index 8ce91fca..0793b2b2 100755 --- a/scripts/setup-gams.sh +++ b/scripts/setup-gams.sh @@ -13,6 +13,8 @@ set -e apt-get update apt-get install curl --yes +echo "updated and installed curl" + # === Install GAMS === # GAMS Installation script is based on the official installation guide # (https://www.gams.com/latest/docs/UG_UNIX_INSTALL.html) and adapts some lines from @@ -24,24 +26,29 @@ GAMS_VERSION_HOTFIX=0 # download the self-extracting archive to /opt/gams/gams.exe and run/extract it curl --show-error --output /opt/gams/gams.exe --create-dirs "https://d37drm4t2jghv5.cloudfront.net/distributions/${GAMS_VERSION_RELEASE_MAJOR}.${GAMS_VERSION_HOTFIX}/linux/linux_x64_64_sfx.exe" +echo "downloaded gams" cd /opt/gams chmod +x gams.exe sync ./gams.exe -q # -q = quietly, see https://linux.die.net/man/1/unzipsfx +echo "installed gams" rm -rf gams.exe +echo "removed gams installer" # Install GAMS license GAMS_PATH=/opt/gams/gams${GAMS_VERSION_RELEASE_MAJOR}_linux_x64_64_sfx echo "${GAMS_LICENSE}" | base64 --decode > "$GAMS_PATH/gamslice.txt" +echo "installed gams license"\ # Add GAMS to PATH ln -s "$GAMS_PATH/gams" /usr/local/bin/gams echo "export PATH=\$PATH:$GAMS_PATH" >> ~/.bashrc +echo "added gams to path" # Run GAMS installer cd "$GAMS_PATH" &&\ ./gamsinst -a - +echo "ran gams installer" # === Install conda === From 0917fe0fdd4667d05ba6a47085c9a0bfbc5ad43d Mon Sep 17 00:00:00 2001 From: zaibod <92274292+zaibod@users.noreply.github.com> Date: Tue, 18 Nov 2025 16:28:16 +0100 Subject: [PATCH 027/137] debug: removed "quiet" parameter from gams installer Updated echo statements to include debug information. --- scripts/setup-gams.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/setup-gams.sh b/scripts/setup-gams.sh index 0793b2b2..e62f077f 100755 --- a/scripts/setup-gams.sh +++ b/scripts/setup-gams.sh @@ -13,7 +13,7 @@ set -e apt-get update apt-get install curl --yes -echo "updated and installed curl" +echo "--- DEBUG: updated and installed curl---" # === Install GAMS === # GAMS Installation script is based on the official installation guide @@ -26,19 +26,19 @@ GAMS_VERSION_HOTFIX=0 # download the self-extracting archive to /opt/gams/gams.exe and run/extract it curl --show-error --output /opt/gams/gams.exe --create-dirs "https://d37drm4t2jghv5.cloudfront.net/distributions/${GAMS_VERSION_RELEASE_MAJOR}.${GAMS_VERSION_HOTFIX}/linux/linux_x64_64_sfx.exe" -echo "downloaded gams" +echo "--- DEBUG: downloaded gams---" cd /opt/gams chmod +x gams.exe sync -./gams.exe -q # -q = quietly, see https://linux.die.net/man/1/unzipsfx -echo "installed gams" +./gams.exe # -q # -q = quietly, see https://linux.die.net/man/1/unzipsfx +echo "--- DEBUG: installed gams---" rm -rf gams.exe -echo "removed gams installer" +echo "--- DEBUG: removed gams installer---" # Install GAMS license GAMS_PATH=/opt/gams/gams${GAMS_VERSION_RELEASE_MAJOR}_linux_x64_64_sfx echo "${GAMS_LICENSE}" | base64 --decode > "$GAMS_PATH/gamslice.txt" -echo "installed gams license"\ +echo "--- DEBUG: installed gams license---" # Add GAMS to PATH ln -s "$GAMS_PATH/gams" /usr/local/bin/gams From 93a7b8de749dd7e2c42ce1b5f1ae726c289a28ac Mon Sep 17 00:00:00 2001 From: zaibod <92274292+zaibod@users.noreply.github.com> Date: Wed, 19 Nov 2025 14:30:42 +0100 Subject: [PATCH 028/137] feat: update GAMS version to 49.7 in setup script to replace unavailable version --- scripts/setup-gams.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/setup-gams.sh b/scripts/setup-gams.sh index e62f077f..38ed9b11 100755 --- a/scripts/setup-gams.sh +++ b/scripts/setup-gams.sh @@ -21,7 +21,7 @@ echo "--- DEBUG: updated and installed curl---" # iisaa/gams-docker (https://github.com/iiasa/gams-docker/blob/master/Dockerfile, GPL-3.0 licensed) # Configure the GAMS version here -GAMS_VERSION_RELEASE_MAJOR=49.3 +GAMS_VERSION_RELEASE_MAJOR=49.7 GAMS_VERSION_HOTFIX=0 # download the self-extracting archive to /opt/gams/gams.exe and run/extract it From 9aa40f9c31621d7445ef6ca2f0c5d125733bfeac Mon Sep 17 00:00:00 2001 From: zaibod <92274292+zaibod@users.noreply.github.com> Date: Wed, 19 Nov 2025 14:40:46 +0100 Subject: [PATCH 029/137] debug: re-enabled quiet flag for GAMS installation, more debug echos --- scripts/setup-gams.sh | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/scripts/setup-gams.sh b/scripts/setup-gams.sh index 38ed9b11..3001dbe4 100755 --- a/scripts/setup-gams.sh +++ b/scripts/setup-gams.sh @@ -30,7 +30,7 @@ echo "--- DEBUG: downloaded gams---" cd /opt/gams chmod +x gams.exe sync -./gams.exe # -q # -q = quietly, see https://linux.die.net/man/1/unzipsfx +./gams.exe -q # -q = quietly, see https://linux.die.net/man/1/unzipsfx echo "--- DEBUG: installed gams---" rm -rf gams.exe echo "--- DEBUG: removed gams installer---" @@ -43,12 +43,12 @@ echo "--- DEBUG: installed gams license---" # Add GAMS to PATH ln -s "$GAMS_PATH/gams" /usr/local/bin/gams echo "export PATH=\$PATH:$GAMS_PATH" >> ~/.bashrc -echo "added gams to path" +echo "--- DEBUG: added gams to path---" # Run GAMS installer cd "$GAMS_PATH" &&\ ./gamsinst -a -echo "ran gams installer" +echo "--- DEBUG: ran gams installer---" # === Install conda === @@ -78,9 +78,15 @@ conda create --name gams python=3.10 --yes # (https://askubuntu.com/a/1464306) source /opt/conda/bin/activate gams +echo "---DEBUG: activated conda env---" + # install GAMS links for python pip install gamsapi[core,connect] --find-links /opt/gams/gams${GAMS_VERSION_RELEASE_MAJOR}_linux_x64_64_sfx/api/python/bdist pip install networkx +echo "---DEBUG: installed python libs---" + # make GAMS use our python version echo "export GMSPYTHONLIB=/opt/conda/envs/gams/lib/libpython3.10.so" >> ~/.bashrc + +echo "---DEBUG: successfully set GAMS python version---" From c6ce48363aa2833e1510b62ecc02d5ece70f2ae8 Mon Sep 17 00:00:00 2001 From: zaibod <92274292+zaibod@users.noreply.github.com> Date: Wed, 19 Nov 2025 14:43:32 +0100 Subject: [PATCH 030/137] debug: fix debug echo statement in CI/CD workflow --- .github/workflows/ci-cd-develop.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-cd-develop.yml b/.github/workflows/ci-cd-develop.yml index c4725f37..b02eadd9 100644 --- a/.github/workflows/ci-cd-develop.yml +++ b/.github/workflows/ci-cd-develop.yml @@ -27,7 +27,7 @@ jobs: run: | echo "DEBUG: before source" source ./scripts/setup-gams.sh - echo "DEBUG: after source + echo "DEBUG: after source" echo "/opt/conda/bin" >> "$GITHUB_PATH" echo "$GAMS_PATH" >> "$GITHUB_PATH" echo "GMSPYTHONLIB=/opt/conda/envs/gams/lib/libpython3.10.so" >> "$GITHUB_ENV" From b9d3e9205cbd1023a46b70d055f5616e7a55c49e Mon Sep 17 00:00:00 2001 From: zaibod <92274292+zaibod@users.noreply.github.com> Date: Wed, 19 Nov 2025 14:49:31 +0100 Subject: [PATCH 031/137] refactor: clean up debug messages in setup-gams.sh Removed debug echo statements for cleaner output during installation. --- scripts/setup-gams.sh | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/scripts/setup-gams.sh b/scripts/setup-gams.sh index 3001dbe4..64309e47 100755 --- a/scripts/setup-gams.sh +++ b/scripts/setup-gams.sh @@ -13,8 +13,6 @@ set -e apt-get update apt-get install curl --yes -echo "--- DEBUG: updated and installed curl---" - # === Install GAMS === # GAMS Installation script is based on the official installation guide # (https://www.gams.com/latest/docs/UG_UNIX_INSTALL.html) and adapts some lines from @@ -26,29 +24,24 @@ GAMS_VERSION_HOTFIX=0 # download the self-extracting archive to /opt/gams/gams.exe and run/extract it curl --show-error --output /opt/gams/gams.exe --create-dirs "https://d37drm4t2jghv5.cloudfront.net/distributions/${GAMS_VERSION_RELEASE_MAJOR}.${GAMS_VERSION_HOTFIX}/linux/linux_x64_64_sfx.exe" -echo "--- DEBUG: downloaded gams---" cd /opt/gams chmod +x gams.exe sync ./gams.exe -q # -q = quietly, see https://linux.die.net/man/1/unzipsfx -echo "--- DEBUG: installed gams---" rm -rf gams.exe -echo "--- DEBUG: removed gams installer---" # Install GAMS license GAMS_PATH=/opt/gams/gams${GAMS_VERSION_RELEASE_MAJOR}_linux_x64_64_sfx echo "${GAMS_LICENSE}" | base64 --decode > "$GAMS_PATH/gamslice.txt" -echo "--- DEBUG: installed gams license---" # Add GAMS to PATH ln -s "$GAMS_PATH/gams" /usr/local/bin/gams echo "export PATH=\$PATH:$GAMS_PATH" >> ~/.bashrc -echo "--- DEBUG: added gams to path---" # Run GAMS installer cd "$GAMS_PATH" &&\ ./gamsinst -a -echo "--- DEBUG: ran gams installer---" + # === Install conda === @@ -78,15 +71,9 @@ conda create --name gams python=3.10 --yes # (https://askubuntu.com/a/1464306) source /opt/conda/bin/activate gams -echo "---DEBUG: activated conda env---" - # install GAMS links for python pip install gamsapi[core,connect] --find-links /opt/gams/gams${GAMS_VERSION_RELEASE_MAJOR}_linux_x64_64_sfx/api/python/bdist pip install networkx -echo "---DEBUG: installed python libs---" - # make GAMS use our python version echo "export GMSPYTHONLIB=/opt/conda/envs/gams/lib/libpython3.10.so" >> ~/.bashrc - -echo "---DEBUG: successfully set GAMS python version---" From 58c4e1ac57b2f2c269116c4f5b73e6d8e6be444b Mon Sep 17 00:00:00 2001 From: zaibod <92274292+zaibod@users.noreply.github.com> Date: Wed, 19 Nov 2025 14:51:09 +0100 Subject: [PATCH 032/137] fix: remove debug echo statements from CI/CD script --- .github/workflows/ci-cd-develop.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/ci-cd-develop.yml b/.github/workflows/ci-cd-develop.yml index b02eadd9..da4d67d9 100644 --- a/.github/workflows/ci-cd-develop.yml +++ b/.github/workflows/ci-cd-develop.yml @@ -25,9 +25,7 @@ jobs: env: GAMS_LICENSE: ${{ secrets.GAMS_LICENSE }} run: | - echo "DEBUG: before source" source ./scripts/setup-gams.sh - echo "DEBUG: after source" echo "/opt/conda/bin" >> "$GITHUB_PATH" echo "$GAMS_PATH" >> "$GITHUB_PATH" echo "GMSPYTHONLIB=/opt/conda/envs/gams/lib/libpython3.10.so" >> "$GITHUB_ENV" From 8745e0edd06fd979af5d9d56d8051a4f102c29bc Mon Sep 17 00:00:00 2001 From: zaibod <92274292+zaibod@users.noreply.github.com> Date: Thu, 20 Nov 2025 10:40:54 +0100 Subject: [PATCH 033/137] fix: upgraded qiskit versions --- solvers/qiskit/knapsack/knapsack_qiskit.py | 7 ++++--- solvers/qiskit/knapsack/requirements.txt | 4 ++-- solvers/qiskit/max-cut/maxCut_qiskit.py | 8 ++++---- solvers/qiskit/max-cut/requirements.txt | 4 ++-- solvers/qiskit/qubo/qubo_qiskit.py | 6 +++--- solvers/qiskit/qubo/requirements.txt | 4 ++-- 6 files changed, 17 insertions(+), 16 deletions(-) diff --git a/solvers/qiskit/knapsack/knapsack_qiskit.py b/solvers/qiskit/knapsack/knapsack_qiskit.py index ae6416cd..55720378 100644 --- a/solvers/qiskit/knapsack/knapsack_qiskit.py +++ b/solvers/qiskit/knapsack/knapsack_qiskit.py @@ -2,12 +2,13 @@ import sys # Qiskit -from qiskit.primitives import Sampler +from qiskit.primitives import StatevectorSampler +from qiskit_optimization.minimum_eigensolvers import QAOA from qiskit_algorithms.optimizers import COBYLA from qiskit_optimization.algorithms import MinimumEigenOptimizer from qiskit_optimization.applications import Knapsack from qiskit_optimization.converters import QuadraticProgramToQubo -from qiskit_algorithms import QAOA + arg_count = len(sys.argv) - 1 if arg_count != 2: @@ -47,7 +48,7 @@ # TODO: Sampler() has to be replaces with StatevectorSampler() in newer versions. # (currently not yet supported by qiskit-optimization) # TODO: add a dedicated mixer -qaoa_mes = QAOA(sampler=Sampler(), optimizer=COBYLA()) +qaoa_mes = QAOA(sampler=StatevectorSampler(), optimizer=COBYLA()) qaoa = MinimumEigenOptimizer(qaoa_mes) qaoa_result = qaoa.solve(qubo) diff --git a/solvers/qiskit/knapsack/requirements.txt b/solvers/qiskit/knapsack/requirements.txt index 3f34f649..e42d620d 100644 --- a/solvers/qiskit/knapsack/requirements.txt +++ b/solvers/qiskit/knapsack/requirements.txt @@ -1,4 +1,4 @@ # This file describes the python package requirements for the Qiskit Knapsack solver -qiskit == 1.4.2 -qiskit-optimization == 0.6.1 +qiskit == 2.2.3 +qiskit-optimization == 0.7.0 diff --git a/solvers/qiskit/max-cut/maxCut_qiskit.py b/solvers/qiskit/max-cut/maxCut_qiskit.py index 2cf67fc4..84529daf 100644 --- a/solvers/qiskit/max-cut/maxCut_qiskit.py +++ b/solvers/qiskit/max-cut/maxCut_qiskit.py @@ -16,10 +16,10 @@ # Qiskit from qiskit.circuit.library import TwoLocal -from qiskit.primitives import Sampler +from qiskit.primitives import StatevectorSampler from qiskit_optimization.applications import Maxcut -from qiskit_algorithms import SamplingVQE -from qiskit_algorithms.optimizers import SPSA +from qiskit_optimization.minimum_eigensolvers import SamplingVQE +from qiskit_optimization.optimizers import SPSA #if len(sys.argv) != 3: @@ -86,7 +86,7 @@ # construct VQE optimizer=SPSA(maxiter=300) ry = TwoLocal(qubitOp.num_qubits, "ry", "cz", reps=5, entanglement="linear") -vqe = SamplingVQE(sampler=Sampler(), ansatz=ry, optimizer=optimizer) +vqe = SamplingVQE(sampler=StatevectorSampler(), ansatz=ry, optimizer=optimizer) # run VQE result = vqe.compute_minimum_eigenvalue(qubitOp) diff --git a/solvers/qiskit/max-cut/requirements.txt b/solvers/qiskit/max-cut/requirements.txt index 7823972c..d32d0f32 100644 --- a/solvers/qiskit/max-cut/requirements.txt +++ b/solvers/qiskit/max-cut/requirements.txt @@ -1,6 +1,6 @@ # This file describes the python package requirements for the Qiskit Max-Cut solver pygmlparser == 1.0.1 -qiskit == 1.4.2 -qiskit-optimization == 0.6.1 +qiskit == 2.2.3 +qiskit-optimization == 0.7.0 numpy networkx diff --git a/solvers/qiskit/qubo/qubo_qiskit.py b/solvers/qiskit/qubo/qubo_qiskit.py index d7b3469d..c1c2dd30 100644 --- a/solvers/qiskit/qubo/qubo_qiskit.py +++ b/solvers/qiskit/qubo/qubo_qiskit.py @@ -1,8 +1,8 @@ import sys from qp_converter import QpConverter -from qiskit.primitives import Sampler -from qiskit_algorithms import QAOA +from qiskit.primitives import StatevectorSampler +from qiskit_optimization.minimum_eigensolvers import QAOA from qiskit_algorithms.optimizers import COBYLA from qiskit_optimization.algorithms import MinimumEigenOptimizer @@ -19,7 +19,7 @@ # TODO: Sampler() has to be replaces with StatevectorSampler() in newer versions. # (currently not yet supported by qiskit-optimization) # TODO: add a dedicated mixer -qaoa_mes = QAOA(sampler=Sampler(), optimizer=COBYLA()) +qaoa_mes = QAOA(sampler=StatevectorSampler(), optimizer=COBYLA()) qaoa = MinimumEigenOptimizer(qaoa_mes) qaoa_result = qaoa.solve(qubo) diff --git a/solvers/qiskit/qubo/requirements.txt b/solvers/qiskit/qubo/requirements.txt index 04bd3e61..4bb8653e 100644 --- a/solvers/qiskit/qubo/requirements.txt +++ b/solvers/qiskit/qubo/requirements.txt @@ -1,3 +1,3 @@ # This file describes the python package requirements for the Qiskit QUBO solver -qiskit == 1.4.2 -qiskit-optimization == 0.6.1 +qiskit == 2.2.3 +qiskit-optimization == 0.7.0 From b209a306f35f1fbf418017c78564391bd03b16e6 Mon Sep 17 00:00:00 2001 From: zaibod <92274292+zaibod@users.noreply.github.com> Date: Thu, 20 Nov 2025 15:51:45 +0100 Subject: [PATCH 034/137] fix: add overlooked sympy dependency for qrisp --- solvers/qrisp/qubo/requirements.txt | 3 ++- solvers/qrisp/sat/requirements.txt | 3 ++- solvers/qrisp/vrp/requirements.txt | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/solvers/qrisp/qubo/requirements.txt b/solvers/qrisp/qubo/requirements.txt index 10cb2e71..84e9ce00 100644 --- a/solvers/qrisp/qubo/requirements.txt +++ b/solvers/qrisp/qubo/requirements.txt @@ -1,4 +1,5 @@ # This file describes the python package requirements for the Qrisp QUBO solver qrisp == 0.4.4 numpy == 1.26.4 -gurobipy == 11.0.1 # for lp file reading \ No newline at end of file +gurobipy == 11.0.1 # for lp file reading +sympy == 1.12.1 # needed but not specified in qrisp requirements \ No newline at end of file diff --git a/solvers/qrisp/sat/requirements.txt b/solvers/qrisp/sat/requirements.txt index 30f9c9c3..b6c8cce0 100644 --- a/solvers/qrisp/sat/requirements.txt +++ b/solvers/qrisp/sat/requirements.txt @@ -1,3 +1,4 @@ # This file describes the python package requirements for the Qrisp SAT solver qrisp == 0.4.4 -numpy == 1.26.4 \ No newline at end of file +numpy == 1.26.4 +sympy == 1.12.1 # needed but not specified in qrisp requirements \ No newline at end of file diff --git a/solvers/qrisp/vrp/requirements.txt b/solvers/qrisp/vrp/requirements.txt index c7737696..c6f0918b 100644 --- a/solvers/qrisp/vrp/requirements.txt +++ b/solvers/qrisp/vrp/requirements.txt @@ -1,4 +1,5 @@ # This file describes the python package requirements for the Qrisp VRP solver qrisp == 0.4.4 numpy == 1.26.4 -tsplib95 == 0.7.1 # for tsplib file parsing \ No newline at end of file +tsplib95 == 0.7.1 # for tsplib file parsing +sympy == 1.12.1 # needed but not specified in qrisp requirements \ No newline at end of file From c40c0e60bef4e8e2cd8fb562332a2dce50da5cdf Mon Sep 17 00:00:00 2001 From: zaibod <92274292+zaibod@users.noreply.github.com> Date: Thu, 20 Nov 2025 15:52:45 +0100 Subject: [PATCH 035/137] fix: update Qiskit optimizer import paths --- solvers/qiskit/knapsack/knapsack_qiskit.py | 2 +- .../qiskit/materialsimulation/material_simulation_qiskit.py | 4 ++-- solvers/qiskit/qubo/qubo_qiskit.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/solvers/qiskit/knapsack/knapsack_qiskit.py b/solvers/qiskit/knapsack/knapsack_qiskit.py index 55720378..7c27712f 100644 --- a/solvers/qiskit/knapsack/knapsack_qiskit.py +++ b/solvers/qiskit/knapsack/knapsack_qiskit.py @@ -4,7 +4,7 @@ # Qiskit from qiskit.primitives import StatevectorSampler from qiskit_optimization.minimum_eigensolvers import QAOA -from qiskit_algorithms.optimizers import COBYLA +from qiskit_optimization.optimizers import COBYLA from qiskit_optimization.algorithms import MinimumEigenOptimizer from qiskit_optimization.applications import Knapsack from qiskit_optimization.converters import QuadraticProgramToQubo diff --git a/solvers/qiskit/materialsimulation/material_simulation_qiskit.py b/solvers/qiskit/materialsimulation/material_simulation_qiskit.py index b06a5704..a565f55b 100644 --- a/solvers/qiskit/materialsimulation/material_simulation_qiskit.py +++ b/solvers/qiskit/materialsimulation/material_simulation_qiskit.py @@ -2,8 +2,8 @@ from qiskit_nature.units import DistanceUnit from qiskit_nature.second_q.drivers import PySCFDriver from qiskit_nature.second_q.mappers import JordanWignerMapper -from qiskit_algorithms import VQE -from qiskit_algorithms.optimizers import SLSQP +from qiskit_optimization.minimum_eigensolvers import VQE +from qiskit_optimization.optimizers import SLSQP from qiskit.primitives import Estimator from qiskit_nature.second_q.algorithms import GroundStateEigensolver, QEOM, EvaluationRule from qiskit_nature.second_q.circuit.library import HartreeFock, UCCSD diff --git a/solvers/qiskit/qubo/qubo_qiskit.py b/solvers/qiskit/qubo/qubo_qiskit.py index c1c2dd30..d63e20a0 100644 --- a/solvers/qiskit/qubo/qubo_qiskit.py +++ b/solvers/qiskit/qubo/qubo_qiskit.py @@ -3,7 +3,7 @@ from qiskit.primitives import StatevectorSampler from qiskit_optimization.minimum_eigensolvers import QAOA -from qiskit_algorithms.optimizers import COBYLA +from qiskit_optimization.optimizers import COBYLA from qiskit_optimization.algorithms import MinimumEigenOptimizer if len(sys.argv) != 3: From b0ad49b6bd9726383f8337d7a4d72d058569736d Mon Sep 17 00:00:00 2001 From: zaibod <92274292+zaibod@users.noreply.github.com> Date: Thu, 27 Nov 2025 14:47:40 +0100 Subject: [PATCH 036/137] fix: removed explicit qiskit import --- solvers/qiskit/materialsimulation/requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/solvers/qiskit/materialsimulation/requirements.txt b/solvers/qiskit/materialsimulation/requirements.txt index f67cfe6b..e94ab45b 100644 --- a/solvers/qiskit/materialsimulation/requirements.txt +++ b/solvers/qiskit/materialsimulation/requirements.txt @@ -1,3 +1,2 @@ -qiskit == 1.4.3 qiskit-nature == 0.7.2 pyscf == 2.9.0 \ No newline at end of file From a07ff7bec3658efd5c69be6f7c51496dbd59c50f Mon Sep 17 00:00:00 2001 From: zaibod <92274292+zaibod@users.noreply.github.com> Date: Thu, 27 Nov 2025 15:12:52 +0100 Subject: [PATCH 037/137] fix: readded downgraded qiskit version to prevent usage of a cached higher version of qiskit --- solvers/qiskit/materialsimulation/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/solvers/qiskit/materialsimulation/requirements.txt b/solvers/qiskit/materialsimulation/requirements.txt index e94ab45b..e690a3d4 100644 --- a/solvers/qiskit/materialsimulation/requirements.txt +++ b/solvers/qiskit/materialsimulation/requirements.txt @@ -1,2 +1,3 @@ +qiskit == 0.44 qiskit-nature == 0.7.2 pyscf == 2.9.0 \ No newline at end of file From 9077c87a3dcc9f90facb33a7e70f87f8a1632f41 Mon Sep 17 00:00:00 2001 From: zaibod Date: Wed, 3 Dec 2025 18:31:40 +0100 Subject: [PATCH 038/137] fix: fixed material simulation dependency versions --- .../qiskit/materialsimulation/material_simulation_qiskit.py | 4 ++-- solvers/qiskit/materialsimulation/requirements.txt | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/solvers/qiskit/materialsimulation/material_simulation_qiskit.py b/solvers/qiskit/materialsimulation/material_simulation_qiskit.py index a565f55b..b06a5704 100644 --- a/solvers/qiskit/materialsimulation/material_simulation_qiskit.py +++ b/solvers/qiskit/materialsimulation/material_simulation_qiskit.py @@ -2,8 +2,8 @@ from qiskit_nature.units import DistanceUnit from qiskit_nature.second_q.drivers import PySCFDriver from qiskit_nature.second_q.mappers import JordanWignerMapper -from qiskit_optimization.minimum_eigensolvers import VQE -from qiskit_optimization.optimizers import SLSQP +from qiskit_algorithms import VQE +from qiskit_algorithms.optimizers import SLSQP from qiskit.primitives import Estimator from qiskit_nature.second_q.algorithms import GroundStateEigensolver, QEOM, EvaluationRule from qiskit_nature.second_q.circuit.library import HartreeFock, UCCSD diff --git a/solvers/qiskit/materialsimulation/requirements.txt b/solvers/qiskit/materialsimulation/requirements.txt index e690a3d4..3b430447 100644 --- a/solvers/qiskit/materialsimulation/requirements.txt +++ b/solvers/qiskit/materialsimulation/requirements.txt @@ -1,3 +1,4 @@ -qiskit == 0.44 +qiskit == 1.0.2 +qiskit-algorithms == 0.3.1 qiskit-nature == 0.7.2 pyscf == 2.9.0 \ No newline at end of file From 80bb8d9b72dd2d07655177f697d6454187e44314 Mon Sep 17 00:00:00 2001 From: zaibod <92274292+zaibod@users.noreply.github.com> Date: Wed, 14 Jan 2026 15:00:25 +0100 Subject: [PATCH 039/137] fix: used newer version of gurobipy the license included in the old version was no longer updated and expired --- solvers/qrisp/qubo/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/solvers/qrisp/qubo/requirements.txt b/solvers/qrisp/qubo/requirements.txt index 84e9ce00..c3de9c57 100644 --- a/solvers/qrisp/qubo/requirements.txt +++ b/solvers/qrisp/qubo/requirements.txt @@ -1,5 +1,5 @@ # This file describes the python package requirements for the Qrisp QUBO solver qrisp == 0.4.4 numpy == 1.26.4 -gurobipy == 11.0.1 # for lp file reading +gurobipy == 13 # for lp file reading sympy == 1.12.1 # needed but not specified in qrisp requirements \ No newline at end of file From 95cf3e7ee38d5fe0cb60e4d53c9fa2bf29664204 Mon Sep 17 00:00:00 2001 From: Elscrux <34755314+Elscrux@users.noreply.github.com> Date: Thu, 15 Jan 2026 13:28:12 +0100 Subject: [PATCH 040/137] Update copyright year to 2026 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2d35e07b..70448e05 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,6 @@ To use this, enable GitHub Actions and configure the following secrets in the Gi 3. Set `DOKKU_DEPLOYMENT_KEY` to the private key generated in step i. ## License -Copyright (c) 2022 - 2025 ProvideQ +Copyright (c) 2022 - 2026 ProvideQ This project is available under the [MIT License](./LICENSE). From 597ce0c28b1c42e372e102c68a08f41ec0d65b4c Mon Sep 17 00:00:00 2001 From: Elscrux Date: Tue, 27 May 2025 14:40:26 +0200 Subject: [PATCH 041/137] feat: Add description to ProblemType --- .../demonstrators/DemonstratorConfiguration.java | 2 ++ .../anomaly/dead/DeadFeatureConfiguration.java | 1 + .../voidmodel/VoidModelConfiguration.java | 1 + .../toolbox/knapsack/KnapsackConfiguration.java | 13 ++++++++----- .../toolbox/maxcut/MaxCutConfiguration.java | 2 ++ .../kit/provideq/toolbox/meta/ProblemType.java | 16 +++++++++++++++- .../provideq/toolbox/qubo/QuboConfiguration.java | 3 +++ .../provideq/toolbox/sat/SatConfiguration.java | 2 ++ .../toolbox/sharpsat/SharpSatConfiguration.java | 2 ++ .../provideq/toolbox/tsp/TspConfiguration.java | 2 ++ .../provideq/toolbox/vrp/VrpConfiguration.java | 4 +++- .../vrp/clusterer/VrpClustererConfiguration.java | 1 + 12 files changed, 42 insertions(+), 7 deletions(-) diff --git a/src/main/java/edu/kit/provideq/toolbox/demonstrators/DemonstratorConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/demonstrators/DemonstratorConfiguration.java index 5767fe52..d7c341bc 100644 --- a/src/main/java/edu/kit/provideq/toolbox/demonstrators/DemonstratorConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/demonstrators/DemonstratorConfiguration.java @@ -15,6 +15,8 @@ public class DemonstratorConfiguration { */ public static final ProblemType DEMONSTRATOR = new ProblemType<>( "demonstrator", + "A demonstrator is a broad group of processes that demonstrate " + + "certain aspects of quantum computing.", String.class, String.class ); diff --git a/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/dead/DeadFeatureConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/dead/DeadFeatureConfiguration.java index 80d21ab7..170af1c7 100644 --- a/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/dead/DeadFeatureConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/dead/DeadFeatureConfiguration.java @@ -25,6 +25,7 @@ public class DeadFeatureConfiguration { */ public static final ProblemType FEATURE_MODEL_ANOMALY_DEAD = new ProblemType<>( "feature-model-anomaly-dead", + "A searching problem: For a given feature model, check if the model contains dead features.", String.class, String.class ); diff --git a/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/voidmodel/VoidModelConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/voidmodel/VoidModelConfiguration.java index 7061de70..9a520fd5 100644 --- a/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/voidmodel/VoidModelConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/voidmodel/VoidModelConfiguration.java @@ -25,6 +25,7 @@ public class VoidModelConfiguration { */ public static final ProblemType FEATURE_MODEL_ANOMALY_VOID = new ProblemType<>( "feature-model-anomaly-void", + "A searching problem: For a given feature model, check if the model is void.", String.class, String.class ); diff --git a/src/main/java/edu/kit/provideq/toolbox/knapsack/KnapsackConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/knapsack/KnapsackConfiguration.java index c88d2418..4c896094 100644 --- a/src/main/java/edu/kit/provideq/toolbox/knapsack/KnapsackConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/knapsack/KnapsackConfiguration.java @@ -69,11 +69,14 @@ public class KnapsackConfiguration { */ public static final ProblemType KNAPSACK = new ProblemType<>( "knapsack", - String.class, - String.class, - estimator, - "^(\\d+)" - ); + "An optimization problem: For given items each with a weight and value, " + + "determine which items are part of a collection where the total weight is " + + "less than or equal to a given limit and the sum of values is as large as possible.", + String.class, + String.class, + estimator, + "^(\\d+)" + ); @Bean ProblemManager getKnapsackManager( diff --git a/src/main/java/edu/kit/provideq/toolbox/maxcut/MaxCutConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/maxcut/MaxCutConfiguration.java index 17e14867..f2d078f8 100644 --- a/src/main/java/edu/kit/provideq/toolbox/maxcut/MaxCutConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/maxcut/MaxCutConfiguration.java @@ -26,6 +26,8 @@ public class MaxCutConfiguration { */ public static final ProblemType MAX_CUT = new ProblemType<>( "max-cut", + "An optimization problem: For a given graph, find the optimal separation of vertices " + + "that maximises the cut crossing edge weight sum.", String.class, String.class ); diff --git a/src/main/java/edu/kit/provideq/toolbox/meta/ProblemType.java b/src/main/java/edu/kit/provideq/toolbox/meta/ProblemType.java index 874aa32d..eec47cfa 100644 --- a/src/main/java/edu/kit/provideq/toolbox/meta/ProblemType.java +++ b/src/main/java/edu/kit/provideq/toolbox/meta/ProblemType.java @@ -9,6 +9,7 @@ */ public class ProblemType { private final String id; + private final String description; private final Class inputClass; private final Class resultClass; private final Function estimator; @@ -18,6 +19,7 @@ public class ProblemType { * Defines a new problem type. * * @param id a unique string identifier for this type of problem. + * @param description a description of the problem type. * @param inputClass the Java class object corresponding to the {@link InputT} type parameter. * @param resultClass the Java class object corresponding to the {@link ResultT} type parameter. * @param estimator the bound estimator for this problem type. @@ -25,12 +27,14 @@ public class ProblemType { */ public ProblemType( String id, + String description, Class inputClass, Class resultClass, Function estimator, String solutionPattern ) { this.id = id; + this.description = description; this.inputClass = inputClass; this.resultClass = resultClass; this.estimator = estimator; @@ -41,15 +45,17 @@ public ProblemType( * Defines a new problem type without estimation support. * * @param id a unique string identifier for this type of problem. + * @param description a description of the problem type. * @param inputClass the Java class object corresponding to the {@link InputT} type parameter. * @param resultClass the Java class object corresponding to the {@link ResultT} type parameter. */ public ProblemType( String id, + String description, Class inputClass, Class resultClass ) { - this(id, inputClass, resultClass, null, null); + this(id, description, inputClass, resultClass, null, null); } /** @@ -59,6 +65,13 @@ public String getId() { return id; } + /** + * Returns a description of this problem type. + */ + public String getDescription() { + return description; + } + /** * Returns the Java class corresponding to the {@link InputT} type parameter. */ @@ -91,6 +104,7 @@ public String toString() { + ", inputClass=%s".formatted(inputClass) + ", resultClass=%s".formatted(resultClass) + ", estimator?=%s".formatted(estimator != null) + + ", description='%s'".formatted(description) + '}'; } } diff --git a/src/main/java/edu/kit/provideq/toolbox/qubo/QuboConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/qubo/QuboConfiguration.java index 30f5ba6f..d0825b80 100644 --- a/src/main/java/edu/kit/provideq/toolbox/qubo/QuboConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/qubo/QuboConfiguration.java @@ -29,6 +29,9 @@ public class QuboConfiguration { */ public static final ProblemType QUBO = new ProblemType<>( "qubo", + "QUBO (Quadratic Unconstrained Binary Optimization) A combinatorial optimization problem. " + + "For a given quadratic term with binary decision variables, find the " + + "minimal variable assignment of the term.", String.class, String.class ); diff --git a/src/main/java/edu/kit/provideq/toolbox/sat/SatConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/sat/SatConfiguration.java index b6f12a4c..107a6221 100644 --- a/src/main/java/edu/kit/provideq/toolbox/sat/SatConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/sat/SatConfiguration.java @@ -26,6 +26,8 @@ public class SatConfiguration { */ public static final ProblemType SAT = new ProblemType<>( "sat", + "A satisfiability problem: For a given boolean formula, check " + + "if there is an interpretation that satisfies the formula.", String.class, DimacsCnfSolution.class ); diff --git a/src/main/java/edu/kit/provideq/toolbox/sharpsat/SharpSatConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/sharpsat/SharpSatConfiguration.java index 0138c421..4a83da1a 100644 --- a/src/main/java/edu/kit/provideq/toolbox/sharpsat/SharpSatConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/sharpsat/SharpSatConfiguration.java @@ -25,6 +25,8 @@ public class SharpSatConfiguration { */ public static final ProblemType SHARPSAT = new ProblemType<>( "sharpsat", + "A satisfiability counting problem: For a given boolean formula, count number of " + + "interpretations that satisfies the formula. NP-Hard in nature.", String.class, Integer.class ); diff --git a/src/main/java/edu/kit/provideq/toolbox/tsp/TspConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/tsp/TspConfiguration.java index ab772850..bc53c242 100644 --- a/src/main/java/edu/kit/provideq/toolbox/tsp/TspConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/tsp/TspConfiguration.java @@ -24,6 +24,8 @@ public class TspConfiguration { */ public static final ProblemType TSP = new ProblemType<>( "tsp", + "A Traveling Sales Person Problem. Optimization Problem with the goal of find " + + "an optimal route between a given set of connected cities.", String.class, String.class ); diff --git a/src/main/java/edu/kit/provideq/toolbox/vrp/VrpConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/vrp/VrpConfiguration.java index dc5a000a..cd04b294 100644 --- a/src/main/java/edu/kit/provideq/toolbox/vrp/VrpConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/vrp/VrpConfiguration.java @@ -21,10 +21,12 @@ public class VrpConfiguration { /** * A Capacitated Vehicle Routing Problem * Optimization Problem with the goal to find a minimal route - * for a given set of trucks and cities with demand. + * for a given set of trucks and cities with demand. */ public static final ProblemType VRP = new ProblemType<>( "vrp", + "A Capacitated Vehicle Routing Problem Optimization Problem with the goal " + + "to find a minimal route for a given set of trucks and cities with demand.", String.class, String.class ); diff --git a/src/main/java/edu/kit/provideq/toolbox/vrp/clusterer/VrpClustererConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/vrp/clusterer/VrpClustererConfiguration.java index 93439c0f..581c52b3 100644 --- a/src/main/java/edu/kit/provideq/toolbox/vrp/clusterer/VrpClustererConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/vrp/clusterer/VrpClustererConfiguration.java @@ -18,6 +18,7 @@ public class VrpClustererConfiguration { */ public static final ProblemType CLUSTER_VRP = new ProblemType<>( "cluster-vrp", + "A Configuration for Vehicle Routing Problem Clusterer.", String.class, String.class ); From 85e25be2a1bb8dcdbb1423700f7f7b6c64aad1a2 Mon Sep 17 00:00:00 2001 From: Elscrux Date: Tue, 27 May 2025 14:41:09 +0200 Subject: [PATCH 042/137] feat: Create GET /problems endpoint --- .../api/ProblemRouteDocumentation.java | 41 ++++++++++++++++++- .../provideq/toolbox/api/ProblemRouter.java | 38 ++++++++++++++--- .../provideq/toolbox/api/ProblemTypeDto.java | 9 ++++ 3 files changed, 81 insertions(+), 7 deletions(-) create mode 100644 src/main/java/edu/kit/provideq/toolbox/api/ProblemTypeDto.java diff --git a/src/main/java/edu/kit/provideq/toolbox/api/ProblemRouteDocumentation.java b/src/main/java/edu/kit/provideq/toolbox/api/ProblemRouteDocumentation.java index 84668549..14287be6 100644 --- a/src/main/java/edu/kit/provideq/toolbox/api/ProblemRouteDocumentation.java +++ b/src/main/java/edu/kit/provideq/toolbox/api/ProblemRouteDocumentation.java @@ -15,6 +15,7 @@ import edu.kit.provideq.toolbox.exception.MissingExampleException; import edu.kit.provideq.toolbox.meta.Problem; import edu.kit.provideq.toolbox.meta.ProblemManager; +import edu.kit.provideq.toolbox.meta.ProblemManagerProvider; import edu.kit.provideq.toolbox.meta.ProblemType; import io.swagger.v3.oas.annotations.enums.ParameterIn; import org.springdoc.core.fn.builders.operation.Builder; @@ -31,6 +32,15 @@ private ProblemRouteDocumentation() { throw new UnsupportedOperationException("This class should not be instantiated."); } + public static void configureListTypesDocs(ProblemManagerProvider problemManagerProvider, + Builder ops) { + ops + .operationId(getOperationId("listType")) + .description("Responds with a list of all problem types that are supported by the server.") + .tag("_") + .response(buildProblemTypeListResponse(problemManagerProvider)); + } + static void configureCreateDocs(ProblemManager manager, Builder ops) { var type = manager.getType(); ops @@ -116,6 +126,32 @@ private static org.springdoc.core.fn.builders.content.Builder .mediaType(APPLICATION_JSON_VALUE); } + private static org.springdoc.core.fn.builders.apiresponse.Builder buildProblemTypeListResponse( + ProblemManagerProvider problemManagerProvider) { + var problemTypes = problemManagerProvider.getProblemManagers().stream() + .map(ProblemManager::getType) + .map(ProblemTypeDto::fromProblemType) + .toList(); + + String problemTypeDtosJson; + try { + problemTypeDtosJson = new ObjectMapper().writeValueAsString(problemTypes); + } catch (JsonProcessingException e) { + throw new JsonParseException(e); + } + + return responseBuilder() + .responseCode(String.valueOf(HttpStatus.OK.value())) + .content(contentBuilder() + .array(arraySchemaBuilder() + .schema(schemaBuilder().implementation(ProblemTypeDto.class)) + ) + .example(exampleOjectBuilder() + .value(problemTypeDtosJson) + ) + ); + } + private static org.springdoc.core.fn.builders.apiresponse.Builder buildProblemListResponse( ProblemManager manager) { var problemDtos = manager.getExampleInstances().stream() @@ -141,7 +177,6 @@ private static org.springdoc.core.fn.builders.apiresponse.Builder buildProblemLi ); } - private static org.springdoc.core.fn.builders.apiresponse.Builder buildProblemResponse( ProblemManager problemManager) { var contentBuilder = contentBuilder() @@ -178,6 +213,10 @@ private static org.springdoc.core.fn.builders.exampleobject.Builder buildExample .value(problemDtoJson); } + private static String getOperationId(String operationName) { + return "/%s/%s".formatted(ENDPOINT_NAME, operationName); + } + private static String getOperationId(ProblemType type, String operationName) { return "/%s/%s/%s".formatted(ENDPOINT_NAME, type.getId(), operationName); } diff --git a/src/main/java/edu/kit/provideq/toolbox/api/ProblemRouter.java b/src/main/java/edu/kit/provideq/toolbox/api/ProblemRouter.java index 1c2551ac..5623dd6b 100644 --- a/src/main/java/edu/kit/provideq/toolbox/api/ProblemRouter.java +++ b/src/main/java/edu/kit/provideq/toolbox/api/ProblemRouter.java @@ -32,6 +32,7 @@ @Configuration @EnableWebFlux public class ProblemRouter { + private static final String PROBLEMS_BASE_PATH = "/problems"; public static final String PROBLEM_ID_PARAM_NAME = "problemId"; private ProblemManagerProvider managerProvider; private Validator validator; @@ -40,11 +41,26 @@ public class ProblemRouter { RouterFunction getProblemRoutes() { var managers = this.managerProvider.getProblemManagers(); return Streams.concat( - managers.stream().map(this::defineCreateRoute), - managers.stream().map(this::defineReadRoute), - managers.stream().map(this::defineListRoute), - managers.stream().map(this::defineUpdateRoute) - ).reduce(RouterFunction::and).orElseThrow(); + managers.stream().map(this::defineCreateRoute), + managers.stream().map(this::defineReadRoute), + managers.stream().map(this::defineListRoute), + managers.stream().map(this::defineUpdateRoute) + ) + .reduce(RouterFunction::and) + .orElseThrow() + .and(defineListTypesRoute()); + } + + /** + * Create operation: GET /problems. + */ + private RouterFunction defineListTypesRoute() { + return route().POST( + PROBLEMS_BASE_PATH, + accept(APPLICATION_JSON), + req -> handleListTypes(), + ops -> ProblemRouteDocumentation.configureListTypesDocs(managerProvider, ops) + ).build(); } /** @@ -95,6 +111,16 @@ private RouterFunction defineUpdateRoute(ProblemManager ma ).build(); } + private Mono handleListTypes() { + var problemTypes = managerProvider.getProblemManagers().stream() + .map(ProblemManager::getType) + .map(ProblemTypeDto::fromProblemType) + .toList(); + + return ok().body(Mono.just(problemTypes), new ParameterizedTypeReference<>() { + }); + } + private Mono handleCreate( ProblemManager manager, ServerRequest req @@ -197,7 +223,7 @@ private void validate(ProblemDto problem) { } private String getPathWithoutId(ProblemType type) { - return "/problems/%s".formatted(type.getId()); + return "%s/%s".formatted(PROBLEMS_BASE_PATH, type.getId()); } private String getPathWithId(ProblemType type) { diff --git a/src/main/java/edu/kit/provideq/toolbox/api/ProblemTypeDto.java b/src/main/java/edu/kit/provideq/toolbox/api/ProblemTypeDto.java new file mode 100644 index 00000000..e6240354 --- /dev/null +++ b/src/main/java/edu/kit/provideq/toolbox/api/ProblemTypeDto.java @@ -0,0 +1,9 @@ +package edu.kit.provideq.toolbox.api; + +import edu.kit.provideq.toolbox.meta.ProblemType; + +public record ProblemTypeDto(String id, String description) { + public static ProblemTypeDto fromProblemType(final ProblemType problemType) { + return new ProblemTypeDto(problemType.getId(), problemType.getDescription()); + } +} From 6575907b57bc95e25f7794ae2c794a6d4a97cda5 Mon Sep 17 00:00:00 2001 From: Elscrux Date: Tue, 27 May 2025 15:05:17 +0200 Subject: [PATCH 043/137] feat: Change ProblemSolver id to simple class name We're expecting that no classes are the same --- src/main/java/edu/kit/provideq/toolbox/meta/ProblemSolver.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/edu/kit/provideq/toolbox/meta/ProblemSolver.java b/src/main/java/edu/kit/provideq/toolbox/meta/ProblemSolver.java index d4b0798d..5fe7e803 100644 --- a/src/main/java/edu/kit/provideq/toolbox/meta/ProblemSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/meta/ProblemSolver.java @@ -19,7 +19,7 @@ public interface ProblemSolver { * @return id of the solver */ default String getId() { - return getClass().getName(); + return getClass().getSimpleName(); } /** From e9e51ed356ab3ea9dff17047c14fc75f3dd771e3 Mon Sep 17 00:00:00 2001 From: Elscrux Date: Tue, 27 May 2025 15:05:38 +0200 Subject: [PATCH 044/137] feat: Add validator to ensure that no ProblemSolver class names are the same --- .../toolbox/SolverClassNameValidator.java | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 src/main/java/edu/kit/provideq/toolbox/SolverClassNameValidator.java diff --git a/src/main/java/edu/kit/provideq/toolbox/SolverClassNameValidator.java b/src/main/java/edu/kit/provideq/toolbox/SolverClassNameValidator.java new file mode 100644 index 00000000..4ba3ef9e --- /dev/null +++ b/src/main/java/edu/kit/provideq/toolbox/SolverClassNameValidator.java @@ -0,0 +1,39 @@ +package edu.kit.provideq.toolbox; + +import edu.kit.provideq.toolbox.meta.ProblemManager; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.stereotype.Component; + +@Component +public class SolverClassNameValidator implements ApplicationListener { + @Override + public void onApplicationEvent(ContextRefreshedEvent event) { + List solverClassNames = event.getApplicationContext() + .getBeansOfType(ProblemManager.class) + .values() + .stream() + .map(manager -> (ProblemManager) manager) + .flatMap(manager -> manager.getSolvers().stream()) + .map(solver -> solver.getClass().getSimpleName()) + .toList(); + + var duplicates = solverClassNames + .stream() + .collect(Collectors.groupingBy(name -> name, Collectors.counting())) + .entrySet() + .stream() + .filter(entry -> entry.getValue() > 1) + .map(Map.Entry::getKey) + .toList(); + + if (!duplicates.isEmpty()) { + throw new IllegalStateException( + "Duplicate solver class names found: " + String.join(", ", duplicates) + ); + } + } +} From 60632b33953a85adde8d88d0f05060479cfb4a2d Mon Sep 17 00:00:00 2001 From: Elscrux Date: Tue, 27 May 2025 16:49:29 +0200 Subject: [PATCH 045/137] refactor: change problem type ids to UpperCamelCase --- .../featuremodel/anomaly/dead/DeadFeatureConfiguration.java | 2 +- .../featuremodel/anomaly/voidmodel/VoidModelConfiguration.java | 2 +- .../kit/provideq/toolbox/knapsack/KnapsackConfiguration.java | 2 +- .../edu/kit/provideq/toolbox/maxcut/MaxCutConfiguration.java | 2 +- .../java/edu/kit/provideq/toolbox/qubo/QuboConfiguration.java | 2 +- .../java/edu/kit/provideq/toolbox/sat/SatConfiguration.java | 2 +- .../kit/provideq/toolbox/sharpsat/SharpSatConfiguration.java | 2 +- .../java/edu/kit/provideq/toolbox/tsp/TspConfiguration.java | 2 +- .../java/edu/kit/provideq/toolbox/vrp/VrpConfiguration.java | 2 +- .../toolbox/vrp/clusterer/VrpClustererConfiguration.java | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/dead/DeadFeatureConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/dead/DeadFeatureConfiguration.java index 170af1c7..3541f013 100644 --- a/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/dead/DeadFeatureConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/dead/DeadFeatureConfiguration.java @@ -24,7 +24,7 @@ public class DeadFeatureConfiguration { * "Explaining Anomalies in Feature Models", Kowal et al., 2016 */ public static final ProblemType FEATURE_MODEL_ANOMALY_DEAD = new ProblemType<>( - "feature-model-anomaly-dead", + "FeatureModelAnomalyDead", "A searching problem: For a given feature model, check if the model contains dead features.", String.class, String.class diff --git a/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/voidmodel/VoidModelConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/voidmodel/VoidModelConfiguration.java index 9a520fd5..be383dcf 100644 --- a/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/voidmodel/VoidModelConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/voidmodel/VoidModelConfiguration.java @@ -24,7 +24,7 @@ public class VoidModelConfiguration { * "Explaining Anomalies in Feature Models", Kowal et al., 2016 */ public static final ProblemType FEATURE_MODEL_ANOMALY_VOID = new ProblemType<>( - "feature-model-anomaly-void", + "FeatureModelAnomalyVoid", "A searching problem: For a given feature model, check if the model is void.", String.class, String.class diff --git a/src/main/java/edu/kit/provideq/toolbox/knapsack/KnapsackConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/knapsack/KnapsackConfiguration.java index 4c896094..c78dcd46 100644 --- a/src/main/java/edu/kit/provideq/toolbox/knapsack/KnapsackConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/knapsack/KnapsackConfiguration.java @@ -68,7 +68,7 @@ public class KnapsackConfiguration { * and the sum of values is as large as possible. */ public static final ProblemType KNAPSACK = new ProblemType<>( - "knapsack", + "Knapsack", "An optimization problem: For given items each with a weight and value, " + "determine which items are part of a collection where the total weight is " + "less than or equal to a given limit and the sum of values is as large as possible.", diff --git a/src/main/java/edu/kit/provideq/toolbox/maxcut/MaxCutConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/maxcut/MaxCutConfiguration.java index f2d078f8..2476c3aa 100644 --- a/src/main/java/edu/kit/provideq/toolbox/maxcut/MaxCutConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/maxcut/MaxCutConfiguration.java @@ -25,7 +25,7 @@ public class MaxCutConfiguration { * weight sum. */ public static final ProblemType MAX_CUT = new ProblemType<>( - "max-cut", + "MaxCut", "An optimization problem: For a given graph, find the optimal separation of vertices " + "that maximises the cut crossing edge weight sum.", String.class, diff --git a/src/main/java/edu/kit/provideq/toolbox/qubo/QuboConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/qubo/QuboConfiguration.java index d0825b80..72fb2631 100644 --- a/src/main/java/edu/kit/provideq/toolbox/qubo/QuboConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/qubo/QuboConfiguration.java @@ -28,7 +28,7 @@ public class QuboConfiguration { * find the minimal variable assignment of the term. */ public static final ProblemType QUBO = new ProblemType<>( - "qubo", + "QUBO", "QUBO (Quadratic Unconstrained Binary Optimization) A combinatorial optimization problem. " + "For a given quadratic term with binary decision variables, find the " + "minimal variable assignment of the term.", diff --git a/src/main/java/edu/kit/provideq/toolbox/sat/SatConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/sat/SatConfiguration.java index 107a6221..0e2d6dd9 100644 --- a/src/main/java/edu/kit/provideq/toolbox/sat/SatConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/sat/SatConfiguration.java @@ -25,7 +25,7 @@ public class SatConfiguration { * For a given boolean formula, check if there is an interpretation that satisfies the formula. */ public static final ProblemType SAT = new ProblemType<>( - "sat", + "SAT", "A satisfiability problem: For a given boolean formula, check " + "if there is an interpretation that satisfies the formula.", String.class, diff --git a/src/main/java/edu/kit/provideq/toolbox/sharpsat/SharpSatConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/sharpsat/SharpSatConfiguration.java index 4a83da1a..f2d0d726 100644 --- a/src/main/java/edu/kit/provideq/toolbox/sharpsat/SharpSatConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/sharpsat/SharpSatConfiguration.java @@ -24,7 +24,7 @@ public class SharpSatConfiguration { * NP-Hard in nature. */ public static final ProblemType SHARPSAT = new ProblemType<>( - "sharpsat", + "SharpSAT", "A satisfiability counting problem: For a given boolean formula, count number of " + "interpretations that satisfies the formula. NP-Hard in nature.", String.class, diff --git a/src/main/java/edu/kit/provideq/toolbox/tsp/TspConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/tsp/TspConfiguration.java index bc53c242..2640c54a 100644 --- a/src/main/java/edu/kit/provideq/toolbox/tsp/TspConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/tsp/TspConfiguration.java @@ -23,7 +23,7 @@ public class TspConfiguration { * between a given set of connected cities. */ public static final ProblemType TSP = new ProblemType<>( - "tsp", + "TSP", "A Traveling Sales Person Problem. Optimization Problem with the goal of find " + "an optimal route between a given set of connected cities.", String.class, diff --git a/src/main/java/edu/kit/provideq/toolbox/vrp/VrpConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/vrp/VrpConfiguration.java index cd04b294..87ebd983 100644 --- a/src/main/java/edu/kit/provideq/toolbox/vrp/VrpConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/vrp/VrpConfiguration.java @@ -24,7 +24,7 @@ public class VrpConfiguration { * for a given set of trucks and cities with demand. */ public static final ProblemType VRP = new ProblemType<>( - "vrp", + "VRP", "A Capacitated Vehicle Routing Problem Optimization Problem with the goal " + "to find a minimal route for a given set of trucks and cities with demand.", String.class, diff --git a/src/main/java/edu/kit/provideq/toolbox/vrp/clusterer/VrpClustererConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/vrp/clusterer/VrpClustererConfiguration.java index 581c52b3..58f999aa 100644 --- a/src/main/java/edu/kit/provideq/toolbox/vrp/clusterer/VrpClustererConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/vrp/clusterer/VrpClustererConfiguration.java @@ -17,7 +17,7 @@ public class VrpClustererConfiguration { * A Configuration for Vehicle Routing Problem Clusterer. */ public static final ProblemType CLUSTER_VRP = new ProblemType<>( - "cluster-vrp", + "ClusterVRP", "A Configuration for Vehicle Routing Problem Clusterer.", String.class, String.class From 853e2ba7bf8bb08a27e73a6001de00bd11c8d1ae Mon Sep 17 00:00:00 2001 From: Elscrux Date: Tue, 27 May 2025 16:50:23 +0200 Subject: [PATCH 046/137] feat: filter demonstrator problem type in /problems endpoint --- .../edu/kit/provideq/toolbox/api/ProblemRouteDocumentation.java | 2 ++ src/main/java/edu/kit/provideq/toolbox/api/ProblemRouter.java | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/main/java/edu/kit/provideq/toolbox/api/ProblemRouteDocumentation.java b/src/main/java/edu/kit/provideq/toolbox/api/ProblemRouteDocumentation.java index 14287be6..f3255fb8 100644 --- a/src/main/java/edu/kit/provideq/toolbox/api/ProblemRouteDocumentation.java +++ b/src/main/java/edu/kit/provideq/toolbox/api/ProblemRouteDocumentation.java @@ -1,6 +1,7 @@ package edu.kit.provideq.toolbox.api; import static edu.kit.provideq.toolbox.api.ProblemRouter.PROBLEM_ID_PARAM_NAME; +import static edu.kit.provideq.toolbox.demonstrators.DemonstratorConfiguration.DEMONSTRATOR; import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder; import static org.springdoc.core.fn.builders.arrayschema.Builder.arraySchemaBuilder; import static org.springdoc.core.fn.builders.content.Builder.contentBuilder; @@ -129,6 +130,7 @@ private static org.springdoc.core.fn.builders.content.Builder private static org.springdoc.core.fn.builders.apiresponse.Builder buildProblemTypeListResponse( ProblemManagerProvider problemManagerProvider) { var problemTypes = problemManagerProvider.getProblemManagers().stream() + .filter(p -> !p.getType().equals(DEMONSTRATOR)) .map(ProblemManager::getType) .map(ProblemTypeDto::fromProblemType) .toList(); diff --git a/src/main/java/edu/kit/provideq/toolbox/api/ProblemRouter.java b/src/main/java/edu/kit/provideq/toolbox/api/ProblemRouter.java index 5623dd6b..83de02d3 100644 --- a/src/main/java/edu/kit/provideq/toolbox/api/ProblemRouter.java +++ b/src/main/java/edu/kit/provideq/toolbox/api/ProblemRouter.java @@ -1,5 +1,6 @@ package edu.kit.provideq.toolbox.api; +import static edu.kit.provideq.toolbox.demonstrators.DemonstratorConfiguration.DEMONSTRATOR; import static org.springdoc.webflux.core.fn.SpringdocRouteBuilder.route; import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.web.reactive.function.server.RequestPredicates.accept; @@ -113,6 +114,7 @@ private RouterFunction defineUpdateRoute(ProblemManager ma private Mono handleListTypes() { var problemTypes = managerProvider.getProblemManagers().stream() + .filter(p -> !p.getType().equals(DEMONSTRATOR)) .map(ProblemManager::getType) .map(ProblemTypeDto::fromProblemType) .toList(); From 97f2634775cafdaf6786fd5d25c9e1f7a1599e08 Mon Sep 17 00:00:00 2001 From: Elscrux Date: Thu, 5 Jun 2025 14:27:24 +0200 Subject: [PATCH 047/137] fix: Make /problems GET endpoint --- src/main/java/edu/kit/provideq/toolbox/api/ProblemRouter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/edu/kit/provideq/toolbox/api/ProblemRouter.java b/src/main/java/edu/kit/provideq/toolbox/api/ProblemRouter.java index 83de02d3..6e10f8e6 100644 --- a/src/main/java/edu/kit/provideq/toolbox/api/ProblemRouter.java +++ b/src/main/java/edu/kit/provideq/toolbox/api/ProblemRouter.java @@ -56,7 +56,7 @@ RouterFunction getProblemRoutes() { * Create operation: GET /problems. */ private RouterFunction defineListTypesRoute() { - return route().POST( + return route().GET( PROBLEMS_BASE_PATH, accept(APPLICATION_JSON), req -> handleListTypes(), From 0d31141da47cf49ce1b66954cedfceaf0e87d62a Mon Sep 17 00:00:00 2001 From: Elscrux Date: Tue, 1 Jul 2025 13:06:23 +0200 Subject: [PATCH 048/137] feat: add flag for subroutine definitions to define if the subroutine is called multiple times or not --- .../toolbox/api/SubRoutineDefinitionDto.java | 16 +++++++++++++--- .../anomaly/dead/SatBasedDeadFeatureSolver.java | 3 ++- .../voidmodel/SatBasedVoidFeatureSolver.java | 3 ++- .../toolbox/meta/SubRoutineDefinition.java | 3 ++- .../sat/solvers/QrispExactGroverSolver.java | 3 ++- .../toolbox/tsp/solvers/QuboTspSolver.java | 7 ++++++- .../toolbox/vrp/clusterer/KmeansClusterer.java | 3 ++- .../toolbox/vrp/clusterer/TwoPhaseClusterer.java | 3 ++- .../vrp/solvers/ClusterAndSolveVrpSolver.java | 12 ++---------- 9 files changed, 33 insertions(+), 20 deletions(-) diff --git a/src/main/java/edu/kit/provideq/toolbox/api/SubRoutineDefinitionDto.java b/src/main/java/edu/kit/provideq/toolbox/api/SubRoutineDefinitionDto.java index 56cfdf5f..d8546ff1 100644 --- a/src/main/java/edu/kit/provideq/toolbox/api/SubRoutineDefinitionDto.java +++ b/src/main/java/edu/kit/provideq/toolbox/api/SubRoutineDefinitionDto.java @@ -8,6 +8,7 @@ public final class SubRoutineDefinitionDto { private final String typeId; private final String description; + private final boolean isCalledOnlyOnce; /** * Internal constructor, used for de-serialization. @@ -16,6 +17,7 @@ public final class SubRoutineDefinitionDto { private SubRoutineDefinitionDto() { this.typeId = null; this.description = null; + this.isCalledOnlyOnce = true; } /** @@ -23,10 +25,12 @@ private SubRoutineDefinitionDto() { */ private SubRoutineDefinitionDto( String typeId, - String description + String description, + boolean isCalledOnlyOnce ) { this.typeId = typeId; this.description = description; + this.isCalledOnlyOnce = isCalledOnlyOnce; } /** @@ -37,8 +41,9 @@ public static SubRoutineDefinitionDto fromSubRoutineDefinition( ) { var typeId = definition.type().getId(); var description = definition.description(); + var isCalledOnlyOnce = definition.isCalledOnlyOnce(); - return new SubRoutineDefinitionDto(typeId, description); + return new SubRoutineDefinitionDto(typeId, description, isCalledOnlyOnce); } public String getTypeId() { @@ -49,10 +54,15 @@ public String getDescription() { return description; } + public boolean getIsCalledOnlyOnce() { + return isCalledOnlyOnce; + } + @Override public String toString() { return "SubRoutineDefinitionDto[" + "typeId=" + typeId + ", " - + "description=" + description + ']'; + + "description=" + description + ", " + + "isCalledOnlyOnce=" + isCalledOnlyOnce + ']'; } } diff --git a/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/dead/SatBasedDeadFeatureSolver.java b/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/dead/SatBasedDeadFeatureSolver.java index 7178598e..03408b50 100644 --- a/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/dead/SatBasedDeadFeatureSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/dead/SatBasedDeadFeatureSolver.java @@ -30,7 +30,8 @@ public class SatBasedDeadFeatureSolver implements ProblemSolver private static final SubRoutineDefinition SAT_SUBROUTINE = new SubRoutineDefinition<>( SatConfiguration.SAT, - "Called per feature to determine if it is dead" + "Called per feature to determine if it is dead", + false ); @Override diff --git a/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/voidmodel/SatBasedVoidFeatureSolver.java b/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/voidmodel/SatBasedVoidFeatureSolver.java index 7e644983..2e4fbc8c 100644 --- a/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/voidmodel/SatBasedVoidFeatureSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/voidmodel/SatBasedVoidFeatureSolver.java @@ -23,7 +23,8 @@ public class SatBasedVoidFeatureSolver implements ProblemSolver private static final SubRoutineDefinition SAT_SUBROUTINE = new SubRoutineDefinition<>( SatConfiguration.SAT, - "Used to determine if there is any valid configurations of the Feature Model" + "Used to determine if there is any valid configurations of the Feature Model", + true ); @Override diff --git a/src/main/java/edu/kit/provideq/toolbox/meta/SubRoutineDefinition.java b/src/main/java/edu/kit/provideq/toolbox/meta/SubRoutineDefinition.java index 49e91713..6b4dd283 100644 --- a/src/main/java/edu/kit/provideq/toolbox/meta/SubRoutineDefinition.java +++ b/src/main/java/edu/kit/provideq/toolbox/meta/SubRoutineDefinition.java @@ -11,6 +11,7 @@ */ public record SubRoutineDefinition( ProblemType type, - String description + String description, + boolean isCalledOnlyOnce ) { } diff --git a/src/main/java/edu/kit/provideq/toolbox/sat/solvers/QrispExactGroverSolver.java b/src/main/java/edu/kit/provideq/toolbox/sat/solvers/QrispExactGroverSolver.java index c894d0ff..03202439 100644 --- a/src/main/java/edu/kit/provideq/toolbox/sat/solvers/QrispExactGroverSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/sat/solvers/QrispExactGroverSolver.java @@ -28,7 +28,8 @@ public class QrispExactGroverSolver extends SatSolver { private static final SubRoutineDefinition SHARPSAT_SUBROUTINE = new SubRoutineDefinition<>( SharpSatConfiguration.SHARPSAT, - "Count the number of solutions using a SharpSAT solver" + "Count the number of solutions using a SharpSAT solver", + true ); private final String scriptPath; diff --git a/src/main/java/edu/kit/provideq/toolbox/tsp/solvers/QuboTspSolver.java b/src/main/java/edu/kit/provideq/toolbox/tsp/solvers/QuboTspSolver.java index 6f993a41..548f851c 100644 --- a/src/main/java/edu/kit/provideq/toolbox/tsp/solvers/QuboTspSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/tsp/solvers/QuboTspSolver.java @@ -26,7 +26,12 @@ @Component public class QuboTspSolver extends TspSolver { private static final SubRoutineDefinition QUBO_SUBROUTINE = - new SubRoutineDefinition<>(QuboConfiguration.QUBO, "How should the QUBO be solved?"); + new SubRoutineDefinition<>( + QuboConfiguration.QUBO, + "How should the QUBO be solved?", + true + ); + private final ApplicationContext context; private final String binaryPath; private ResourceProvider resourceProvider; diff --git a/src/main/java/edu/kit/provideq/toolbox/vrp/clusterer/KmeansClusterer.java b/src/main/java/edu/kit/provideq/toolbox/vrp/clusterer/KmeansClusterer.java index 0d64661d..feb944f8 100644 --- a/src/main/java/edu/kit/provideq/toolbox/vrp/clusterer/KmeansClusterer.java +++ b/src/main/java/edu/kit/provideq/toolbox/vrp/clusterer/KmeansClusterer.java @@ -36,7 +36,8 @@ public KmeansClusterer( protected static final SubRoutineDefinition VRP_SUBROUTINE = new SubRoutineDefinition<>( VrpConfiguration.VRP, - "Solve a VRP problem" + "Solve a VRP problem", + false ); @Override diff --git a/src/main/java/edu/kit/provideq/toolbox/vrp/clusterer/TwoPhaseClusterer.java b/src/main/java/edu/kit/provideq/toolbox/vrp/clusterer/TwoPhaseClusterer.java index 7f5aac5b..86581db2 100644 --- a/src/main/java/edu/kit/provideq/toolbox/vrp/clusterer/TwoPhaseClusterer.java +++ b/src/main/java/edu/kit/provideq/toolbox/vrp/clusterer/TwoPhaseClusterer.java @@ -21,7 +21,8 @@ public class TwoPhaseClusterer extends VrpClusterer { protected static final SubRoutineDefinition TSP_SUBROUTINE = new SubRoutineDefinition<>( TspConfiguration.TSP, - "Solve a TSP problem" + "Solve a TSP problem", + false ); @Autowired diff --git a/src/main/java/edu/kit/provideq/toolbox/vrp/solvers/ClusterAndSolveVrpSolver.java b/src/main/java/edu/kit/provideq/toolbox/vrp/solvers/ClusterAndSolveVrpSolver.java index 237279e0..9b35fdb2 100644 --- a/src/main/java/edu/kit/provideq/toolbox/vrp/solvers/ClusterAndSolveVrpSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/vrp/solvers/ClusterAndSolveVrpSolver.java @@ -6,8 +6,6 @@ import edu.kit.provideq.toolbox.meta.SubRoutineResolver; import edu.kit.provideq.toolbox.vrp.clusterer.VrpClustererConfiguration; import java.util.List; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Component; import reactor.core.publisher.Mono; @@ -16,15 +14,9 @@ public class ClusterAndSolveVrpSolver extends VrpSolver { private static final SubRoutineDefinition CLUSTER_SUBROUTINE = new SubRoutineDefinition<>( VrpClustererConfiguration.CLUSTER_VRP, - "Creates a cluster of multiple vehicle routing problems" + "Creates a cluster of multiple vehicle routing problems", + true ); - private final ApplicationContext context; - - @Autowired - public ClusterAndSolveVrpSolver( - ApplicationContext context) { - this.context = context; - } @Override public String getName() { From 200c6d1888cb7cffef8ccb3695b83d99c0c50384 Mon Sep 17 00:00:00 2001 From: Elscrux Date: Tue, 1 Jul 2025 13:28:50 +0200 Subject: [PATCH 049/137] refactor: change /problems to /problem-types and add /problem-types/ProblemType --- .../api/ProblemRouteDocumentation.java | 42 ----- .../provideq/toolbox/api/ProblemRouter.java | 27 +-- .../toolbox/api/ProblemTypeRouter.java | 162 ++++++++++++++++++ 3 files changed, 163 insertions(+), 68 deletions(-) create mode 100644 src/main/java/edu/kit/provideq/toolbox/api/ProblemTypeRouter.java diff --git a/src/main/java/edu/kit/provideq/toolbox/api/ProblemRouteDocumentation.java b/src/main/java/edu/kit/provideq/toolbox/api/ProblemRouteDocumentation.java index f3255fb8..799d4de9 100644 --- a/src/main/java/edu/kit/provideq/toolbox/api/ProblemRouteDocumentation.java +++ b/src/main/java/edu/kit/provideq/toolbox/api/ProblemRouteDocumentation.java @@ -1,7 +1,6 @@ package edu.kit.provideq.toolbox.api; import static edu.kit.provideq.toolbox.api.ProblemRouter.PROBLEM_ID_PARAM_NAME; -import static edu.kit.provideq.toolbox.demonstrators.DemonstratorConfiguration.DEMONSTRATOR; import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder; import static org.springdoc.core.fn.builders.arrayschema.Builder.arraySchemaBuilder; import static org.springdoc.core.fn.builders.content.Builder.contentBuilder; @@ -16,7 +15,6 @@ import edu.kit.provideq.toolbox.exception.MissingExampleException; import edu.kit.provideq.toolbox.meta.Problem; import edu.kit.provideq.toolbox.meta.ProblemManager; -import edu.kit.provideq.toolbox.meta.ProblemManagerProvider; import edu.kit.provideq.toolbox.meta.ProblemType; import io.swagger.v3.oas.annotations.enums.ParameterIn; import org.springdoc.core.fn.builders.operation.Builder; @@ -33,15 +31,6 @@ private ProblemRouteDocumentation() { throw new UnsupportedOperationException("This class should not be instantiated."); } - public static void configureListTypesDocs(ProblemManagerProvider problemManagerProvider, - Builder ops) { - ops - .operationId(getOperationId("listType")) - .description("Responds with a list of all problem types that are supported by the server.") - .tag("_") - .response(buildProblemTypeListResponse(problemManagerProvider)); - } - static void configureCreateDocs(ProblemManager manager, Builder ops) { var type = manager.getType(); ops @@ -127,33 +116,6 @@ private static org.springdoc.core.fn.builders.content.Builder .mediaType(APPLICATION_JSON_VALUE); } - private static org.springdoc.core.fn.builders.apiresponse.Builder buildProblemTypeListResponse( - ProblemManagerProvider problemManagerProvider) { - var problemTypes = problemManagerProvider.getProblemManagers().stream() - .filter(p -> !p.getType().equals(DEMONSTRATOR)) - .map(ProblemManager::getType) - .map(ProblemTypeDto::fromProblemType) - .toList(); - - String problemTypeDtosJson; - try { - problemTypeDtosJson = new ObjectMapper().writeValueAsString(problemTypes); - } catch (JsonProcessingException e) { - throw new JsonParseException(e); - } - - return responseBuilder() - .responseCode(String.valueOf(HttpStatus.OK.value())) - .content(contentBuilder() - .array(arraySchemaBuilder() - .schema(schemaBuilder().implementation(ProblemTypeDto.class)) - ) - .example(exampleOjectBuilder() - .value(problemTypeDtosJson) - ) - ); - } - private static org.springdoc.core.fn.builders.apiresponse.Builder buildProblemListResponse( ProblemManager manager) { var problemDtos = manager.getExampleInstances().stream() @@ -215,10 +177,6 @@ private static org.springdoc.core.fn.builders.exampleobject.Builder buildExample .value(problemDtoJson); } - private static String getOperationId(String operationName) { - return "/%s/%s".formatted(ENDPOINT_NAME, operationName); - } - private static String getOperationId(ProblemType type, String operationName) { return "/%s/%s/%s".formatted(ENDPOINT_NAME, type.getId(), operationName); } diff --git a/src/main/java/edu/kit/provideq/toolbox/api/ProblemRouter.java b/src/main/java/edu/kit/provideq/toolbox/api/ProblemRouter.java index 6e10f8e6..97cf3e8b 100644 --- a/src/main/java/edu/kit/provideq/toolbox/api/ProblemRouter.java +++ b/src/main/java/edu/kit/provideq/toolbox/api/ProblemRouter.java @@ -1,6 +1,5 @@ package edu.kit.provideq.toolbox.api; -import static edu.kit.provideq.toolbox.demonstrators.DemonstratorConfiguration.DEMONSTRATOR; import static org.springdoc.webflux.core.fn.SpringdocRouteBuilder.route; import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.web.reactive.function.server.RequestPredicates.accept; @@ -48,20 +47,7 @@ RouterFunction getProblemRoutes() { managers.stream().map(this::defineUpdateRoute) ) .reduce(RouterFunction::and) - .orElseThrow() - .and(defineListTypesRoute()); - } - - /** - * Create operation: GET /problems. - */ - private RouterFunction defineListTypesRoute() { - return route().GET( - PROBLEMS_BASE_PATH, - accept(APPLICATION_JSON), - req -> handleListTypes(), - ops -> ProblemRouteDocumentation.configureListTypesDocs(managerProvider, ops) - ).build(); + .orElseThrow(); } /** @@ -112,17 +98,6 @@ private RouterFunction defineUpdateRoute(ProblemManager ma ).build(); } - private Mono handleListTypes() { - var problemTypes = managerProvider.getProblemManagers().stream() - .filter(p -> !p.getType().equals(DEMONSTRATOR)) - .map(ProblemManager::getType) - .map(ProblemTypeDto::fromProblemType) - .toList(); - - return ok().body(Mono.just(problemTypes), new ParameterizedTypeReference<>() { - }); - } - private Mono handleCreate( ProblemManager manager, ServerRequest req diff --git a/src/main/java/edu/kit/provideq/toolbox/api/ProblemTypeRouter.java b/src/main/java/edu/kit/provideq/toolbox/api/ProblemTypeRouter.java new file mode 100644 index 00000000..3315e312 --- /dev/null +++ b/src/main/java/edu/kit/provideq/toolbox/api/ProblemTypeRouter.java @@ -0,0 +1,162 @@ +package edu.kit.provideq.toolbox.api; + +import static edu.kit.provideq.toolbox.demonstrators.DemonstratorConfiguration.DEMONSTRATOR; +import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder; +import static org.springdoc.core.fn.builders.arrayschema.Builder.arraySchemaBuilder; +import static org.springdoc.core.fn.builders.content.Builder.contentBuilder; +import static org.springdoc.core.fn.builders.exampleobject.Builder.exampleOjectBuilder; +import static org.springdoc.core.fn.builders.schema.Builder.schemaBuilder; +import static org.springdoc.webflux.core.fn.SpringdocRouteBuilder.route; +import static org.springframework.http.MediaType.APPLICATION_JSON; +import static org.springframework.web.reactive.function.server.RequestPredicates.accept; +import static org.springframework.web.reactive.function.server.ServerResponse.ok; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.Streams; +import edu.kit.provideq.toolbox.meta.ProblemManager; +import edu.kit.provideq.toolbox.meta.ProblemManagerProvider; +import edu.kit.provideq.toolbox.meta.ProblemType; +import org.springdoc.core.fn.builders.operation.Builder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.json.JsonParseException; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpStatus; +import org.springframework.web.reactive.config.EnableWebFlux; +import org.springframework.web.reactive.function.server.RouterFunction; +import org.springframework.web.reactive.function.server.ServerResponse; +import reactor.core.publisher.Mono; + +/** + * This router handles requests regarding {@link ProblemType} instances. + */ +@Configuration +@EnableWebFlux +public class ProblemTypeRouter { + private static final String PROBLEM_TYPES_BASE_PATH = "/problem-types"; + private ProblemManagerProvider managerProvider; + + @Bean + RouterFunction getProblemTypeRoutes() { + var managers = this.managerProvider.getProblemManagers(); + return Streams.concat( + managers.stream().map(this::defineReadRoute) + ) + .reduce(RouterFunction::and) + .orElseThrow() + .and(defineListTypesRoute()); + } + + /** + * Create operation: GET /problem-types. + */ + private RouterFunction defineListTypesRoute() { + return route().GET( + PROBLEM_TYPES_BASE_PATH, + accept(APPLICATION_JSON), + req -> handleListTypes(), + ops -> ops + .operationId("%s/%s".formatted(PROBLEM_TYPES_BASE_PATH, "listType")) + .description( + "Responds with a list of all problem types that are supported by the server.") + .tag("_") + .response(buildProblemTypeListResponse()) + ).build(); + } + + private Mono handleListTypes() { + var problemTypes = managerProvider.getProblemManagers().stream() + .filter(p -> !p.getType().equals(DEMONSTRATOR)) + .map(ProblemManager::getType) + .map(ProblemTypeDto::fromProblemType) + .toList(); + + return ok().body(Mono.just(problemTypes), new ParameterizedTypeReference<>() { + }); + } + + private org.springdoc.core.fn.builders.apiresponse.Builder buildProblemTypeListResponse() { + var problemTypes = managerProvider.getProblemManagers().stream() + .filter(p -> !p.getType().equals(DEMONSTRATOR)) + .map(ProblemManager::getType) + .map(ProblemTypeDto::fromProblemType) + .toList(); + + String problemTypeDtosJson; + try { + problemTypeDtosJson = new ObjectMapper().writeValueAsString(problemTypes); + } catch (JsonProcessingException e) { + throw new JsonParseException(e); + } + + return responseBuilder() + .responseCode(String.valueOf(HttpStatus.OK.value())) + .content(contentBuilder() + .array(arraySchemaBuilder() + .schema(schemaBuilder().implementation(ProblemTypeDto.class)) + ) + .example(exampleOjectBuilder() + .value(problemTypeDtosJson) + ) + ); + } + + /** + * List operation: GET /problem-types/TYPE. + */ + private RouterFunction defineReadRoute(ProblemManager manager) { + return route().GET( + "%s/%s".formatted(PROBLEM_TYPES_BASE_PATH, manager.getType().getId()), + accept(APPLICATION_JSON), + req -> handleList(manager), + ops -> configureReadDocs(manager, ops) + ).build(); + } + + private Mono handleList( + ProblemManager manager + ) { + var problemTypeDto = ProblemTypeDto.fromProblemType(manager.getType()); + return ok().body(Mono.just(problemTypeDto), new ParameterizedTypeReference<>() { + }); + } + + public static void configureReadDocs(ProblemManager manager, Builder ops) { + var type = manager.getType(); + ops + .operationId("%s/%s/%s".formatted(PROBLEM_TYPES_BASE_PATH, type.getId(), "read")) + .description("This endpoint can be used to get / read the problem type '" + + type.getId() + "'.") + .tag(type.getId()) + .response(buildProblemResponse(manager)); + } + + private static org.springdoc.core.fn.builders.apiresponse.Builder buildProblemResponse( + ProblemManager problemManager) { + var problemTypeDto = ProblemTypeDto.fromProblemType(problemManager.getType()); + + String problemTypeDtoJson; + try { + problemTypeDtoJson = new ObjectMapper().writeValueAsString(problemTypeDto); + } catch (JsonProcessingException e) { + throw new JsonParseException(e); + } + + return responseBuilder() + .responseCode(String.valueOf(HttpStatus.OK.value())) + .content(contentBuilder() + .schema(schemaBuilder() + .implementation(ProblemTypeDto.class)) + .example(exampleOjectBuilder() + .value(problemTypeDtoJson) + ) + ); + } + + @Autowired + void setManagerProvider(ProblemManagerProvider managerProvider) { + this.managerProvider = managerProvider; + } +} From d3afd259f7154b243749c149787bbc56b922b6f6 Mon Sep 17 00:00:00 2001 From: Elscrux Date: Tue, 1 Jul 2025 14:57:42 +0200 Subject: [PATCH 050/137] feat: add problem attributes --- .../toolbox/api/ProblemAttributesRouter.java | 177 ++++++++++++++++++ .../provideq/toolbox/api/ProblemTypeDto.java | 8 +- .../knapsack/KnapsackConfiguration.java | 10 +- .../provideq/toolbox/meta/ProblemType.java | 37 +++- .../toolbox/sat/SatConfiguration.java | 26 ++- .../sharpsat/SharpSatConfiguration.java | 4 +- 6 files changed, 255 insertions(+), 7 deletions(-) create mode 100644 src/main/java/edu/kit/provideq/toolbox/api/ProblemAttributesRouter.java diff --git a/src/main/java/edu/kit/provideq/toolbox/api/ProblemAttributesRouter.java b/src/main/java/edu/kit/provideq/toolbox/api/ProblemAttributesRouter.java new file mode 100644 index 00000000..789976c7 --- /dev/null +++ b/src/main/java/edu/kit/provideq/toolbox/api/ProblemAttributesRouter.java @@ -0,0 +1,177 @@ +package edu.kit.provideq.toolbox.api; + +import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder; +import static org.springdoc.core.fn.builders.content.Builder.contentBuilder; +import static org.springdoc.core.fn.builders.parameter.Builder.parameterBuilder; +import static org.springdoc.core.fn.builders.schema.Builder.schemaBuilder; +import static org.springdoc.webflux.core.fn.SpringdocRouteBuilder.route; +import static org.springframework.http.MediaType.APPLICATION_JSON; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +import static org.springframework.web.reactive.function.server.RequestPredicates.accept; +import static org.springframework.web.reactive.function.server.ServerResponse.ok; + +import com.google.common.collect.Streams; +import edu.kit.provideq.toolbox.meta.Problem; +import edu.kit.provideq.toolbox.meta.ProblemManager; +import edu.kit.provideq.toolbox.meta.ProblemManagerProvider; +import edu.kit.provideq.toolbox.meta.ProblemType; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.stream.Collectors; +import org.springdoc.core.fn.builders.operation.Builder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpStatus; +import org.springframework.web.reactive.config.EnableWebFlux; +import org.springframework.web.reactive.function.server.RouterFunction; +import org.springframework.web.reactive.function.server.ServerRequest; +import org.springframework.web.reactive.function.server.ServerResponse; +import org.springframework.web.server.ResponseStatusException; +import reactor.core.publisher.Mono; + +/** + * This router handles requests regarding {@link Problem} instance attributes. + */ +@Configuration +@EnableWebFlux +public class ProblemAttributesRouter { + public static final String PROBLEM_ID_PARAM_NAME = "problemId"; + public static final String ATTRIBUTE_ID_PARAM_NAME = "attributeId"; + private ProblemManagerProvider managerProvider; + + @Bean + RouterFunction getAttributeRoutes() { + var managers = managerProvider.getProblemManagers(); + return Streams.concat( + managers.stream().map(this::defineGetRoute), + managers.stream().map(this::defineListRoute) + ).reduce(RouterFunction::and) + .orElse(null); + } + + /** + * Estimate Operation: GET /problems/TYPE/{problemId}/attributes/{attributeId}. + */ + private RouterFunction defineGetRoute(ProblemManager manager) { + return route().GET( + getAttributeRouteForProblemType(manager.getType()), + accept(APPLICATION_JSON), + req -> handleGet(manager, req), + ops -> handleGetDocumentation(manager, ops) + ).build(); + } + + private Mono handleGet( + ProblemManager manager, + ServerRequest req + ) { + var problemId = req.pathVariable(PROBLEM_ID_PARAM_NAME); + var attributeId = req.pathVariable(ATTRIBUTE_ID_PARAM_NAME); + var problem = RouterUtility.findProblemOrThrow(manager, problemId); + + try { + var attributeGetter = manager.getType().getAttributes().get(attributeId); + String attribute = attributeGetter.apply(problem.getInput().orElseThrow()); + return ok().body(Mono.just(attribute), new ParameterizedTypeReference<>() { + }); + } catch (IllegalStateException | NoSuchElementException e) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage()); + } + } + + private void handleGetDocumentation( + ProblemManager manager, + Builder ops + ) { + ProblemType problemType = manager.getType(); + ops + .operationId(getAttributeRouteForProblemType(problemType)) + .tag(problemType.getId()) + .description("Calculates the attribute for the given problem id and attribute id.") + .parameter(parameterBuilder().in(ParameterIn.PATH).name(PROBLEM_ID_PARAM_NAME)) + .parameter(parameterBuilder().in(ParameterIn.PATH).name(ATTRIBUTE_ID_PARAM_NAME)) + .response(responseBuilder() + .responseCode(String.valueOf(HttpStatus.OK.value())) + .content(getOkResponseContent()) + ); + } + + /** + * Estimate Operation: GET /problems/TYPE/{problemId}/attributes. + */ + private RouterFunction defineListRoute(ProblemManager manager) { + return route().GET( + getAttributesRouteForProblemType(manager.getType()), + accept(APPLICATION_JSON), + req -> handleList(manager, req), + ops -> handleListDocumentation(manager, ops) + ).build(); + } + + private Mono handleList( + ProblemManager manager, + ServerRequest req + ) { + var problemId = req.pathVariable(PROBLEM_ID_PARAM_NAME); + var problem = RouterUtility.findProblemOrThrow(manager, problemId); + + try { + var attributeValues = manager.getType().getAttributes() + .entrySet().stream() + .collect( + Collectors.toMap( + Map.Entry::getKey, + entry -> entry.getValue().apply(problem.getInput().orElseThrow()) + ) + ); + return ok().body(Mono.just(attributeValues), new ParameterizedTypeReference<>() { + }); + } catch (IllegalStateException | NoSuchElementException e) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage()); + } + } + + private void handleListDocumentation( + ProblemManager manager, + Builder ops + ) { + ProblemType problemType = manager.getType(); + ops + .operationId(getAttributesRouteForProblemType(problemType)) + .tag(problemType.getId()) + .description("Calculates all attributes for the given problem id.") + .parameter(parameterBuilder().in(ParameterIn.PATH).name(PROBLEM_ID_PARAM_NAME)) + .response(responseBuilder() + .responseCode(String.valueOf(HttpStatus.OK.value())) + .content(getOkResponseContent()) + ); + } + + private static org.springdoc.core.fn.builders.content.Builder getOkResponseContent() { + return contentBuilder() + .mediaType(APPLICATION_JSON_VALUE) + .schema(schemaBuilder().implementation(String.class)); + } + + private String getAttributeRouteForProblemType(ProblemType type) { + return "/problems/%s/{%s}/attributes/{%s}".formatted( + type.getId(), + PROBLEM_ID_PARAM_NAME, + ATTRIBUTE_ID_PARAM_NAME); + } + + private String getAttributesRouteForProblemType(ProblemType type) { + return "/problems/%s/{%s}/attributes".formatted( + type.getId(), + PROBLEM_ID_PARAM_NAME); + } + + @Autowired + void setManagerProvider(ProblemManagerProvider managerProvider) { + this.managerProvider = managerProvider; + } + +} diff --git a/src/main/java/edu/kit/provideq/toolbox/api/ProblemTypeDto.java b/src/main/java/edu/kit/provideq/toolbox/api/ProblemTypeDto.java index e6240354..5123d764 100644 --- a/src/main/java/edu/kit/provideq/toolbox/api/ProblemTypeDto.java +++ b/src/main/java/edu/kit/provideq/toolbox/api/ProblemTypeDto.java @@ -1,9 +1,13 @@ package edu.kit.provideq.toolbox.api; import edu.kit.provideq.toolbox.meta.ProblemType; +import java.util.List; -public record ProblemTypeDto(String id, String description) { +public record ProblemTypeDto(String id, String description, List attributes) { public static ProblemTypeDto fromProblemType(final ProblemType problemType) { - return new ProblemTypeDto(problemType.getId(), problemType.getDescription()); + return new ProblemTypeDto( + problemType.getId(), + problemType.getDescription(), + problemType.getAttributes().keySet().stream().toList()); } } diff --git a/src/main/java/edu/kit/provideq/toolbox/knapsack/KnapsackConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/knapsack/KnapsackConfiguration.java index c78dcd46..e0320cf3 100644 --- a/src/main/java/edu/kit/provideq/toolbox/knapsack/KnapsackConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/knapsack/KnapsackConfiguration.java @@ -75,7 +75,15 @@ public class KnapsackConfiguration { String.class, String.class, estimator, - "^(\\d+)" + "^(\\d+)", + Map.ofEntries( + Map.entry("item_count", problem -> problem.lines() + .findFirst() + .orElse("")), + Map.entry("capacity", p2 -> p2.lines() + .reduce((a, b) -> b) + .orElse("")) + ) ); @Bean diff --git a/src/main/java/edu/kit/provideq/toolbox/meta/ProblemType.java b/src/main/java/edu/kit/provideq/toolbox/meta/ProblemType.java index eec47cfa..6d2507f1 100644 --- a/src/main/java/edu/kit/provideq/toolbox/meta/ProblemType.java +++ b/src/main/java/edu/kit/provideq/toolbox/meta/ProblemType.java @@ -1,6 +1,7 @@ package edu.kit.provideq.toolbox.meta; import edu.kit.provideq.toolbox.Bound; +import java.util.Map; import java.util.Optional; import java.util.function.Function; @@ -14,6 +15,7 @@ public class ProblemType { private final Class resultClass; private final Function estimator; private final String solutionPattern; + private final Map> attributes; /** * Defines a new problem type. @@ -31,7 +33,8 @@ public ProblemType( Class inputClass, Class resultClass, Function estimator, - String solutionPattern + String solutionPattern, + Map> attributes ) { this.id = id; this.description = description; @@ -39,6 +42,28 @@ public ProblemType( this.resultClass = resultClass; this.estimator = estimator; this.solutionPattern = solutionPattern; + this.attributes = attributes; + } + + /** + * Defines a new problem type without estimation support. + * + * @param id a unique string identifier for this type of problem. + * @param description a description of the problem type. + * @param inputClass the Java class object corresponding to the {@link InputT} type parameter. + * @param resultClass the Java class object corresponding to the {@link ResultT} type parameter. + * @param attributes a map of attributes for this problem type, + * where the key is the attribute name and the value is + * a function to get the attribute value for a given problem instance + */ + public ProblemType( + String id, + String description, + Class inputClass, + Class resultClass, + Map> attributes + ) { + this(id, description, inputClass, resultClass, null, null, attributes); } /** @@ -55,7 +80,7 @@ public ProblemType( Class inputClass, Class resultClass ) { - this(id, description, inputClass, resultClass, null, null); + this(id, description, inputClass, resultClass, null, null, Map.of()); } /** @@ -97,6 +122,13 @@ public String getSolutionPattern() { return solutionPattern; } + /** + * Returns a map of attributes for this problem type. + */ + public Map> getAttributes() { + return Map.copyOf(attributes); + } + @Override public String toString() { return "ProblemType{" @@ -105,6 +137,7 @@ public String toString() { + ", resultClass=%s".formatted(resultClass) + ", estimator?=%s".formatted(estimator != null) + ", description='%s'".formatted(description) + + ", attributes=%s".formatted(attributes) + '}'; } } diff --git a/src/main/java/edu/kit/provideq/toolbox/sat/SatConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/sat/SatConfiguration.java index 0e2d6dd9..1ea9ed78 100644 --- a/src/main/java/edu/kit/provideq/toolbox/sat/SatConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/sat/SatConfiguration.java @@ -1,7 +1,9 @@ package edu.kit.provideq.toolbox.sat; import edu.kit.provideq.toolbox.ResourceProvider; +import edu.kit.provideq.toolbox.exception.ConversionException; import edu.kit.provideq.toolbox.exception.MissingExampleException; +import edu.kit.provideq.toolbox.format.cnf.dimacs.DimacsCnf; import edu.kit.provideq.toolbox.format.cnf.dimacs.DimacsCnfSolution; import edu.kit.provideq.toolbox.meta.Problem; import edu.kit.provideq.toolbox.meta.ProblemManager; @@ -10,8 +12,10 @@ import edu.kit.provideq.toolbox.sat.solvers.QrispExactGroverSolver; import edu.kit.provideq.toolbox.sat.solvers.QrispGroverSolver; import java.io.IOException; +import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.function.Function; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -29,9 +33,29 @@ public class SatConfiguration { "A satisfiability problem: For a given boolean formula, check " + "if there is an interpretation that satisfies the formula.", String.class, - DimacsCnfSolution.class + DimacsCnfSolution.class, + getAttributes() ); + public static Map> getAttributes() { + return Map.ofEntries( + Map.entry("variable_count", problem -> { + try { + return String.valueOf(DimacsCnf.fromString(problem).getVariables().size()); + } catch (ConversionException e) { + return ""; + } + }), + Map.entry("or_clause_count", problem -> { + try { + return String.valueOf(DimacsCnf.fromString(problem).getOrClauses().size()); + } catch (ConversionException e) { + return ""; + } + }) + ); + } + @Bean ProblemManager getSatManager( GamsSatSolver gamsSolver, diff --git a/src/main/java/edu/kit/provideq/toolbox/sharpsat/SharpSatConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/sharpsat/SharpSatConfiguration.java index f2d0d726..67c66b27 100644 --- a/src/main/java/edu/kit/provideq/toolbox/sharpsat/SharpSatConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/sharpsat/SharpSatConfiguration.java @@ -5,6 +5,7 @@ import edu.kit.provideq.toolbox.meta.Problem; import edu.kit.provideq.toolbox.meta.ProblemManager; import edu.kit.provideq.toolbox.meta.ProblemType; +import edu.kit.provideq.toolbox.sat.SatConfiguration; import edu.kit.provideq.toolbox.sharpsat.solvers.GanakSolver; import edu.kit.provideq.toolbox.sharpsat.solvers.PythonBruteForceSolver; import java.io.IOException; @@ -28,7 +29,8 @@ public class SharpSatConfiguration { "A satisfiability counting problem: For a given boolean formula, count number of " + "interpretations that satisfies the formula. NP-Hard in nature.", String.class, - Integer.class + Integer.class, + SatConfiguration.getAttributes() ); @Bean From a8feabd04af41240bddab3936465853bf8b7c330 Mon Sep 17 00:00:00 2001 From: Elscrux Date: Tue, 1 Jul 2025 14:58:58 +0200 Subject: [PATCH 051/137] fix: add missing description for material simulation --- .../materialsimulation/MaterialSimulationConfiguration.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/edu/kit/provideq/toolbox/materialsimulation/MaterialSimulationConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/materialsimulation/MaterialSimulationConfiguration.java index aea9dfd5..a2c12f6f 100644 --- a/src/main/java/edu/kit/provideq/toolbox/materialsimulation/MaterialSimulationConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/materialsimulation/MaterialSimulationConfiguration.java @@ -24,6 +24,7 @@ public class MaterialSimulationConfiguration { */ public static final ProblemType MATERIAL_SIMULATION = new ProblemType<>( "materialsimulation", + "A simulation problem, computing electronic properties of a molecular system.", String.class, String.class ); From b317ffc00649e95a5f247e309351224b52e4883c Mon Sep 17 00:00:00 2001 From: Elscrux Date: Tue, 15 Jul 2025 11:41:27 +0200 Subject: [PATCH 052/137] refactor: rename sat attribute or_clause_count to clause_count --- .../java/edu/kit/provideq/toolbox/sat/SatConfiguration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/edu/kit/provideq/toolbox/sat/SatConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/sat/SatConfiguration.java index 1ea9ed78..b0794ae2 100644 --- a/src/main/java/edu/kit/provideq/toolbox/sat/SatConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/sat/SatConfiguration.java @@ -46,7 +46,7 @@ public static Map> getAttributes() { return ""; } }), - Map.entry("or_clause_count", problem -> { + Map.entry("clause_count", problem -> { try { return String.valueOf(DimacsCnf.fromString(problem).getOrClauses().size()); } catch (ConversionException e) { From 0bdf0c1d7b9c44ff58627ad2e712d7b11a8e2aff Mon Sep 17 00:00:00 2001 From: Elscrux Date: Fri, 7 Nov 2025 17:45:31 +0100 Subject: [PATCH 053/137] refactor: use PascalCase for problem type --- .../materialsimulation/MaterialSimulationConfiguration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/edu/kit/provideq/toolbox/materialsimulation/MaterialSimulationConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/materialsimulation/MaterialSimulationConfiguration.java index a2c12f6f..9dd32af3 100644 --- a/src/main/java/edu/kit/provideq/toolbox/materialsimulation/MaterialSimulationConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/materialsimulation/MaterialSimulationConfiguration.java @@ -23,7 +23,7 @@ public class MaterialSimulationConfiguration { * A simulation problem, computing electronic properties of a molecular system. */ public static final ProblemType MATERIAL_SIMULATION = new ProblemType<>( - "materialsimulation", + "MaterialSimulation", "A simulation problem, computing electronic properties of a molecular system.", String.class, String.class From 484b58329703dc2e275c036321f27bce00e0736e Mon Sep 17 00:00:00 2001 From: Elscrux Date: Fri, 7 Nov 2025 17:45:51 +0100 Subject: [PATCH 054/137] feat: add dimension and capacity attributes to vrp problems --- .../provideq/toolbox/vrp/VrpConfiguration.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/main/java/edu/kit/provideq/toolbox/vrp/VrpConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/vrp/VrpConfiguration.java index 87ebd983..6ff30144 100644 --- a/src/main/java/edu/kit/provideq/toolbox/vrp/VrpConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/vrp/VrpConfiguration.java @@ -10,6 +10,7 @@ import edu.kit.provideq.toolbox.vrp.solvers.QrispVrpSolver; import java.io.IOException; import java.util.HashSet; +import java.util.Map; import java.util.Objects; import java.util.Set; import org.springframework.context.annotation.Bean; @@ -28,7 +29,19 @@ public class VrpConfiguration { "A Capacitated Vehicle Routing Problem Optimization Problem with the goal " + "to find a minimal route for a given set of trucks and cities with demand.", String.class, - String.class + String.class, + Map.ofEntries( + Map.entry("dimension", problem -> problem.lines() + .filter(line -> line.startsWith("DIMENSION")) + .findFirst() + .map(line -> line.split(":")[1].trim()) + .orElse("")), + Map.entry("capacity", problem -> problem.lines() + .filter(line -> line.startsWith("CAPACITY")) + .findFirst() + .map(line -> line.split(":")[1].trim()) + .orElse("")) + ) ); @Bean From 8c8305535639c90e9513a5eb049e62a5575f83e6 Mon Sep 17 00:00:00 2001 From: zaibod Date: Mon, 17 Feb 2025 09:30:16 +0100 Subject: [PATCH 055/137] refactor: problem bound structure --- .../provideq/toolbox/api/ComparisonDto.java | 41 +++++++++++++++++-- .../toolbox/api/EstimationRouter.java | 22 ++++------ .../kit/provideq/toolbox/api/ProblemDto.java | 10 ++--- .../kit/provideq/toolbox/meta/Problem.java | 40 ++++++++++++++++-- 4 files changed, 86 insertions(+), 27 deletions(-) diff --git a/src/main/java/edu/kit/provideq/toolbox/api/ComparisonDto.java b/src/main/java/edu/kit/provideq/toolbox/api/ComparisonDto.java index ad2d4ed2..4488a45a 100644 --- a/src/main/java/edu/kit/provideq/toolbox/api/ComparisonDto.java +++ b/src/main/java/edu/kit/provideq/toolbox/api/ComparisonDto.java @@ -3,9 +3,44 @@ import edu.kit.provideq.toolbox.BoundWithInfo; import edu.kit.provideq.toolbox.Solution; -public record ComparisonDto(float comparison, BoundWithInfo bound, Solution solution) { +public class ComparisonDto { + private BoundDto bound; + private float comparison; + + public ComparisonDto(float comparison, BoundWithInfo bound) { + this.comparison = comparison; + this.bound = new BoundDto(bound); + } + + public ComparisonDto() { + this.comparison = -1; + this.bound = null; + } + + @Override public String toString() { - return "Comparison{comparison=%f, bound=%s, solution=%s}" - .formatted(comparison, bound, solution); + return "Comparison{bound=%s, comparison=%f}" + .formatted(bound, comparison); + } + + public BoundDto getBound() { + return bound; } + + public void setBound(BoundDto bound) { + this.bound = bound; + } + + public void setBound(BoundWithInfo bound) { + this.bound = new BoundDto(bound); + } + + public void setComparison(float comparison) { + this.comparison = comparison; + } + + public float getComparison() { + return comparison; + } + } diff --git a/src/main/java/edu/kit/provideq/toolbox/api/EstimationRouter.java b/src/main/java/edu/kit/provideq/toolbox/api/EstimationRouter.java index 71fd772f..fdf6bc09 100644 --- a/src/main/java/edu/kit/provideq/toolbox/api/EstimationRouter.java +++ b/src/main/java/edu/kit/provideq/toolbox/api/EstimationRouter.java @@ -90,7 +90,7 @@ private Mono handleGet( Mono bound; try { problem.estimateBound(); - bound = Mono.just(new BoundDto(problem.getBound().orElseThrow())); + bound = Mono.just(problem.getBound().orElseThrow()); } catch (IllegalStateException | NoSuchElementException e) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage()); } @@ -114,22 +114,14 @@ private Mono handleCompare( throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Bound not estimated yet!"); } - float bound = problem.getBound().get().bound().value(); - var pattern = Pattern.compile(manager.getType().getSolutionPattern()); - var solutionMatcher = pattern.matcher(problem.getSolution().get().getSolutionData().toString()); - float solutionValue; - if (solutionMatcher.find()) { - solutionValue = Float.parseFloat(solutionMatcher.group(1)); - } else { - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Could not parse solution value!"); + try { + problem.compareBound(); + } catch (IllegalStateException | NoSuchElementException e) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage()); } - float comparison = problem.getBound().get().bound().boundType().compare(bound, solutionValue); - ComparisonDto comparisonDto = new ComparisonDto( - comparison, - problem.getBound().get(), - problem.getSolution().get() - ); + var comparisonDto = problem.getBoundWithComparison(); + return ok().body(Mono.just(comparisonDto), new ParameterizedTypeReference<>() { }); } diff --git a/src/main/java/edu/kit/provideq/toolbox/api/ProblemDto.java b/src/main/java/edu/kit/provideq/toolbox/api/ProblemDto.java index 954e2f0c..23de41e3 100644 --- a/src/main/java/edu/kit/provideq/toolbox/api/ProblemDto.java +++ b/src/main/java/edu/kit/provideq/toolbox/api/ProblemDto.java @@ -17,8 +17,8 @@ public class ProblemDto { private String typeId; private InputT input; private Solution solution; - private BoundWithInfo bound; private ProblemState state; + private ComparisonDto boundWithComparison; private String solverId; private List solverSettings; private List subProblems; @@ -40,7 +40,7 @@ public static ProblemDto fromProblem( dto.typeId = problem.getType().getId(); dto.input = problem.getInput().orElse(null); dto.solution = problem.getSolution().orElse(null); - dto.bound = problem.getBound().orElse(null); + dto.boundWithComparison = problem.getBoundWithComparison().orElse(null); dto.state = problem.getState(); dto.solverId = problem.getSolver() .map(ProblemSolver::getId) @@ -70,8 +70,8 @@ public Solution getSolution() { return solution; } - public BoundWithInfo getBound() { - return bound; + public ComparisonDto getBoundWithComparison() { + return boundWithComparison; } public ProblemState getState() { @@ -107,7 +107,7 @@ public String toString() { + ", solverId=" + solverId + ", input=" + input + ", solution=" + solution - + ", value=" + bound + + ", boundWithComparison=" + boundWithComparison + ", solverSettings=" + solverSettings + ", subProblems=" + subProblems + '}'; diff --git a/src/main/java/edu/kit/provideq/toolbox/meta/Problem.java b/src/main/java/edu/kit/provideq/toolbox/meta/Problem.java index 2b8791ae..1ea2171d 100644 --- a/src/main/java/edu/kit/provideq/toolbox/meta/Problem.java +++ b/src/main/java/edu/kit/provideq/toolbox/meta/Problem.java @@ -2,6 +2,8 @@ import edu.kit.provideq.toolbox.BoundWithInfo; import edu.kit.provideq.toolbox.Solution; +import edu.kit.provideq.toolbox.api.BoundDto; +import edu.kit.provideq.toolbox.api.ComparisonDto; import edu.kit.provideq.toolbox.meta.setting.SolverSetting; import java.util.Collections; import java.util.HashSet; @@ -10,6 +12,7 @@ import java.util.Set; import java.util.UUID; import java.util.function.Consumer; +import java.util.regex.Pattern; import java.util.stream.Collectors; import reactor.core.publisher.Mono; @@ -30,7 +33,7 @@ public class Problem { private InputT input; private Solution solution; - private BoundWithInfo bound; + private final ComparisonDto boundWithComparison = new ComparisonDto(); private ProblemState state; private ProblemSolver solver; private List solverSettings; @@ -101,7 +104,32 @@ public void estimateBound() { long finish = System.currentTimeMillis(); var executionTime = finish - start; - this.bound = new BoundWithInfo(estimatedBound, executionTime); + this.boundWithComparison.setBound(new BoundWithInfo(estimatedBound, executionTime)); + } + + public void compareBound() { + if (this.solution == null) { + throw new IllegalStateException("Cannot compare bound without solution!"); + } + if (this.boundWithComparison.getBound() == null) { + throw new IllegalStateException("Cannot compare bound without bound!"); + } + + var bound = this.boundWithComparison.getBound(); + var solution = this.solution.getSolutionData(); + + var pattern = Pattern.compile(this.type.getSolutionPattern()); + var solutionMatcher = pattern.matcher(solution.toString()); + float solutionValue; + if (solutionMatcher.find()) { + solutionValue = Float.parseFloat(solutionMatcher.group(1)); + } else { + throw new IllegalStateException("Solution does not match the expected pattern!"); + } + + var comparison = bound.boundType().compare(bound.bound(), solutionValue); + + this.boundWithComparison.setComparison(comparison); } public UUID getId() { @@ -231,7 +259,11 @@ public String toString() { + '}'; } - public Optional getBound() { - return Optional.ofNullable(bound); + public Optional getBound() { + return Optional.ofNullable(boundWithComparison.getBound()); + } + + public Optional getBoundWithComparison() { + return Optional.of(boundWithComparison); } } From 33c850cfce020feeb212f7099c4e3f8eb0ddb891 Mon Sep 17 00:00:00 2001 From: zaibod Date: Mon, 30 Jun 2025 18:08:13 +0200 Subject: [PATCH 056/137] refactor: add hasBound method to ComparisonDto Introduced a hasBound() method in ComparisonDto to check for the presence of a bound. Updated Problem to use hasBound() instead of directly checking for null --- src/main/java/edu/kit/provideq/toolbox/api/ComparisonDto.java | 4 ++++ src/main/java/edu/kit/provideq/toolbox/meta/Problem.java | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/edu/kit/provideq/toolbox/api/ComparisonDto.java b/src/main/java/edu/kit/provideq/toolbox/api/ComparisonDto.java index 4488a45a..539e29bf 100644 --- a/src/main/java/edu/kit/provideq/toolbox/api/ComparisonDto.java +++ b/src/main/java/edu/kit/provideq/toolbox/api/ComparisonDto.java @@ -35,6 +35,10 @@ public void setBound(BoundWithInfo bound) { this.bound = new BoundDto(bound); } + public boolean hasBound() { + return bound != null; + } + public void setComparison(float comparison) { this.comparison = comparison; } diff --git a/src/main/java/edu/kit/provideq/toolbox/meta/Problem.java b/src/main/java/edu/kit/provideq/toolbox/meta/Problem.java index 1ea2171d..9adabb29 100644 --- a/src/main/java/edu/kit/provideq/toolbox/meta/Problem.java +++ b/src/main/java/edu/kit/provideq/toolbox/meta/Problem.java @@ -111,7 +111,7 @@ public void compareBound() { if (this.solution == null) { throw new IllegalStateException("Cannot compare bound without solution!"); } - if (this.boundWithComparison.getBound() == null) { + if (!this.boundWithComparison.hasBound()) { throw new IllegalStateException("Cannot compare bound without bound!"); } From 8dfced59da41f89e48eae8326dcf9553b8cc282c Mon Sep 17 00:00:00 2001 From: zaibod <92274292+zaibod@users.noreply.github.com> Date: Mon, 30 Jun 2025 18:05:39 +0200 Subject: [PATCH 057/137] refactor: added docs and small refactors --- .../provideq/toolbox/api/ComparisonDto.java | 20 +++++++++++- .../kit/provideq/toolbox/meta/Problem.java | 31 +++++++++++++------ 2 files changed, 41 insertions(+), 10 deletions(-) diff --git a/src/main/java/edu/kit/provideq/toolbox/api/ComparisonDto.java b/src/main/java/edu/kit/provideq/toolbox/api/ComparisonDto.java index 539e29bf..44c6ff5c 100644 --- a/src/main/java/edu/kit/provideq/toolbox/api/ComparisonDto.java +++ b/src/main/java/edu/kit/provideq/toolbox/api/ComparisonDto.java @@ -1,12 +1,20 @@ package edu.kit.provideq.toolbox.api; import edu.kit.provideq.toolbox.BoundWithInfo; -import edu.kit.provideq.toolbox.Solution; +/** + * A DTO for a comparison between a bound and a solution. + */ public class ComparisonDto { private BoundDto bound; private float comparison; + /** + * Creates a new comparison DTO out of a comparison value and a bound. + * + * @param comparison the comparison value, e.g., the ratio of the solution to the bound + * @param bound a bound + */ public ComparisonDto(float comparison, BoundWithInfo bound) { this.comparison = comparison; this.bound = new BoundDto(bound); @@ -23,6 +31,11 @@ public ComparisonDto() { .formatted(bound, comparison); } + /** + * Gets the bound of the comparison as a BoundDto. + * + * @return the bound of the comparison, e.g., the best known solution or the optimal solution + */ public BoundDto getBound() { return bound; } @@ -43,6 +56,11 @@ public void setComparison(float comparison) { this.comparison = comparison; } + /** + * Gets the comparison value of the comparison. + * + * @return the comparison value, e.g., the ratio of the solution to the bound. + */ public float getComparison() { return comparison; } diff --git a/src/main/java/edu/kit/provideq/toolbox/meta/Problem.java b/src/main/java/edu/kit/provideq/toolbox/meta/Problem.java index 9adabb29..9f239f9d 100644 --- a/src/main/java/edu/kit/provideq/toolbox/meta/Problem.java +++ b/src/main/java/edu/kit/provideq/toolbox/meta/Problem.java @@ -87,6 +87,10 @@ public Mono> solve() { }); } + /** + * Estimates a bound for the problem's solution. Uses the estimator provided by the problem type. + * Also sets the execution time of the estimation in the boundWithComparison object. + */ public void estimateBound() { if (this.input == null) { throw new IllegalStateException("Cannot estimate value without input!"); @@ -96,6 +100,7 @@ public void estimateBound() { if (optionalEstimator.isEmpty()) { throw new IllegalStateException("Cannot estimate value without an estimator!"); } + var estimator = optionalEstimator.get(); long start = System.currentTimeMillis(); @@ -107,6 +112,9 @@ public void estimateBound() { this.boundWithComparison.setBound(new BoundWithInfo(estimatedBound, executionTime)); } + /** + * Compares the current solution with the bound according to the bound type. + */ public void compareBound() { if (this.solution == null) { throw new IllegalStateException("Cannot compare bound without solution!"); @@ -116,16 +124,9 @@ public void compareBound() { } var bound = this.boundWithComparison.getBound(); - var solution = this.solution.getSolutionData(); + var solutionData = this.solution.getSolutionData(); - var pattern = Pattern.compile(this.type.getSolutionPattern()); - var solutionMatcher = pattern.matcher(solution.toString()); - float solutionValue; - if (solutionMatcher.find()) { - solutionValue = Float.parseFloat(solutionMatcher.group(1)); - } else { - throw new IllegalStateException("Solution does not match the expected pattern!"); - } + float solutionValue = getSolutionValue(solutionData); var comparison = bound.boundType().compare(bound.bound(), solutionValue); @@ -266,4 +267,16 @@ public Optional getBound() { public Optional getBoundWithComparison() { return Optional.of(boundWithComparison); } + + private float getSolutionValue(ResultT solutionData) { + var pattern = Pattern.compile(this.type.getSolutionPattern()); + var solutionMatcher = pattern.matcher(solutionData.toString()); + float solutionValue; + if (solutionMatcher.find()) { + solutionValue = Float.parseFloat(solutionMatcher.group(1)); + } else { + throw new IllegalStateException("Solution does not match the expected pattern!"); + } + return solutionValue; + } } From 477e59a4d9d7725b6e1b538468a4709150eb06aa Mon Sep 17 00:00:00 2001 From: Elscrux Date: Thu, 29 Jan 2026 14:51:47 +0100 Subject: [PATCH 058/137] refactor: rename ComparisonDto to BoundComparisonDto --- .../api/{ComparisonDto.java => BoundComparisonDto.java} | 6 +++--- .../java/edu/kit/provideq/toolbox/api/EstimationRouter.java | 3 +-- src/main/java/edu/kit/provideq/toolbox/api/ProblemDto.java | 5 ++--- src/main/java/edu/kit/provideq/toolbox/meta/Problem.java | 6 +++--- 4 files changed, 9 insertions(+), 11 deletions(-) rename src/main/java/edu/kit/provideq/toolbox/api/{ComparisonDto.java => BoundComparisonDto.java} (91%) diff --git a/src/main/java/edu/kit/provideq/toolbox/api/ComparisonDto.java b/src/main/java/edu/kit/provideq/toolbox/api/BoundComparisonDto.java similarity index 91% rename from src/main/java/edu/kit/provideq/toolbox/api/ComparisonDto.java rename to src/main/java/edu/kit/provideq/toolbox/api/BoundComparisonDto.java index 44c6ff5c..23e12e6c 100644 --- a/src/main/java/edu/kit/provideq/toolbox/api/ComparisonDto.java +++ b/src/main/java/edu/kit/provideq/toolbox/api/BoundComparisonDto.java @@ -5,7 +5,7 @@ /** * A DTO for a comparison between a bound and a solution. */ -public class ComparisonDto { +public class BoundComparisonDto { private BoundDto bound; private float comparison; @@ -15,12 +15,12 @@ public class ComparisonDto { * @param comparison the comparison value, e.g., the ratio of the solution to the bound * @param bound a bound */ - public ComparisonDto(float comparison, BoundWithInfo bound) { + public BoundComparisonDto(float comparison, BoundWithInfo bound) { this.comparison = comparison; this.bound = new BoundDto(bound); } - public ComparisonDto() { + public BoundComparisonDto() { this.comparison = -1; this.bound = null; } diff --git a/src/main/java/edu/kit/provideq/toolbox/api/EstimationRouter.java b/src/main/java/edu/kit/provideq/toolbox/api/EstimationRouter.java index fdf6bc09..f0ab059c 100644 --- a/src/main/java/edu/kit/provideq/toolbox/api/EstimationRouter.java +++ b/src/main/java/edu/kit/provideq/toolbox/api/EstimationRouter.java @@ -18,7 +18,6 @@ import io.swagger.v3.oas.annotations.enums.ParameterIn; import java.util.NoSuchElementException; import java.util.UUID; -import java.util.regex.Pattern; import org.springdoc.core.fn.builders.operation.Builder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; @@ -168,7 +167,7 @@ private static org.springdoc.core.fn.builders.content.Builder getOkResponseConte private static org.springdoc.core.fn.builders.content.Builder comparisonOkResponseContent() { return contentBuilder() .mediaType(APPLICATION_JSON_VALUE) - .schema(schemaBuilder().implementation(ComparisonDto.class)); + .schema(schemaBuilder().implementation(BoundComparisonDto.class)); } private Problem findProblemOrThrow( diff --git a/src/main/java/edu/kit/provideq/toolbox/api/ProblemDto.java b/src/main/java/edu/kit/provideq/toolbox/api/ProblemDto.java index 23de41e3..239787c3 100644 --- a/src/main/java/edu/kit/provideq/toolbox/api/ProblemDto.java +++ b/src/main/java/edu/kit/provideq/toolbox/api/ProblemDto.java @@ -1,6 +1,5 @@ package edu.kit.provideq.toolbox.api; -import edu.kit.provideq.toolbox.BoundWithInfo; import edu.kit.provideq.toolbox.Solution; import edu.kit.provideq.toolbox.meta.Problem; import edu.kit.provideq.toolbox.meta.ProblemSolver; @@ -18,7 +17,7 @@ public class ProblemDto { private InputT input; private Solution solution; private ProblemState state; - private ComparisonDto boundWithComparison; + private BoundComparisonDto boundWithComparison; private String solverId; private List solverSettings; private List subProblems; @@ -70,7 +69,7 @@ public Solution getSolution() { return solution; } - public ComparisonDto getBoundWithComparison() { + public BoundComparisonDto getBoundWithComparison() { return boundWithComparison; } diff --git a/src/main/java/edu/kit/provideq/toolbox/meta/Problem.java b/src/main/java/edu/kit/provideq/toolbox/meta/Problem.java index 9f239f9d..27d64bba 100644 --- a/src/main/java/edu/kit/provideq/toolbox/meta/Problem.java +++ b/src/main/java/edu/kit/provideq/toolbox/meta/Problem.java @@ -2,8 +2,8 @@ import edu.kit.provideq.toolbox.BoundWithInfo; import edu.kit.provideq.toolbox.Solution; +import edu.kit.provideq.toolbox.api.BoundComparisonDto; import edu.kit.provideq.toolbox.api.BoundDto; -import edu.kit.provideq.toolbox.api.ComparisonDto; import edu.kit.provideq.toolbox.meta.setting.SolverSetting; import java.util.Collections; import java.util.HashSet; @@ -33,7 +33,7 @@ public class Problem { private InputT input; private Solution solution; - private final ComparisonDto boundWithComparison = new ComparisonDto(); + private final BoundComparisonDto boundWithComparison = new BoundComparisonDto(); private ProblemState state; private ProblemSolver solver; private List solverSettings; @@ -264,7 +264,7 @@ public Optional getBound() { return Optional.ofNullable(boundWithComparison.getBound()); } - public Optional getBoundWithComparison() { + public Optional getBoundWithComparison() { return Optional.of(boundWithComparison); } From e398fea9c5a7975a28c36bb56f54c8c898678a64 Mon Sep 17 00:00:00 2001 From: Elscrux Date: Thu, 29 Jan 2026 15:25:05 +0100 Subject: [PATCH 059/137] refactor: make boundWithComparison non-final --- .../toolbox/api/BoundComparisonDto.java | 18 ++++++++++++++---- .../edu/kit/provideq/toolbox/meta/Problem.java | 13 +++++++++---- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/main/java/edu/kit/provideq/toolbox/api/BoundComparisonDto.java b/src/main/java/edu/kit/provideq/toolbox/api/BoundComparisonDto.java index 23e12e6c..10db5a1d 100644 --- a/src/main/java/edu/kit/provideq/toolbox/api/BoundComparisonDto.java +++ b/src/main/java/edu/kit/provideq/toolbox/api/BoundComparisonDto.java @@ -20,6 +20,16 @@ public BoundComparisonDto(float comparison, BoundWithInfo bound) { this.bound = new BoundDto(bound); } + /** + * Creates a new comparison DTO out of a bound and with an uninitialized comparison value. + * + * @param bound a bound + */ + public BoundComparisonDto(BoundWithInfo bound) { + this.comparison = -1; + this.bound = new BoundDto(bound); + } + public BoundComparisonDto() { this.comparison = -1; this.bound = null; @@ -52,10 +62,6 @@ public boolean hasBound() { return bound != null; } - public void setComparison(float comparison) { - this.comparison = comparison; - } - /** * Gets the comparison value of the comparison. * @@ -65,4 +71,8 @@ public float getComparison() { return comparison; } + public void setComparison(float comparison) { + this.comparison = comparison; + } + } diff --git a/src/main/java/edu/kit/provideq/toolbox/meta/Problem.java b/src/main/java/edu/kit/provideq/toolbox/meta/Problem.java index 27d64bba..c135e6b4 100644 --- a/src/main/java/edu/kit/provideq/toolbox/meta/Problem.java +++ b/src/main/java/edu/kit/provideq/toolbox/meta/Problem.java @@ -33,7 +33,7 @@ public class Problem { private InputT input; private Solution solution; - private final BoundComparisonDto boundWithComparison = new BoundComparisonDto(); + private BoundComparisonDto boundWithComparison; private ProblemState state; private ProblemSolver solver; private List solverSettings; @@ -109,7 +109,8 @@ public void estimateBound() { long finish = System.currentTimeMillis(); var executionTime = finish - start; - this.boundWithComparison.setBound(new BoundWithInfo(estimatedBound, executionTime)); + this.boundWithComparison = + new BoundComparisonDto(new BoundWithInfo(estimatedBound, executionTime)); } /** @@ -119,7 +120,7 @@ public void compareBound() { if (this.solution == null) { throw new IllegalStateException("Cannot compare bound without solution!"); } - if (!this.boundWithComparison.hasBound()) { + if (this.boundWithComparison == null || !this.boundWithComparison.hasBound()) { throw new IllegalStateException("Cannot compare bound without bound!"); } @@ -261,11 +262,15 @@ public String toString() { } public Optional getBound() { + if (boundWithComparison == null) { + return Optional.empty(); + } + return Optional.ofNullable(boundWithComparison.getBound()); } public Optional getBoundWithComparison() { - return Optional.of(boundWithComparison); + return Optional.ofNullable(boundWithComparison); } private float getSolutionValue(ResultT solutionData) { From 0143a8d0427609711195dd22cd1ef82410816919 Mon Sep 17 00:00:00 2001 From: Elscrux Date: Thu, 29 Jan 2026 15:27:49 +0100 Subject: [PATCH 060/137] fix: duplicate setBound method confusing spring which one to choose --- .../edu/kit/provideq/toolbox/api/BoundComparisonDto.java | 5 ----- src/main/java/edu/kit/provideq/toolbox/api/BoundDto.java | 4 ++++ 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/java/edu/kit/provideq/toolbox/api/BoundComparisonDto.java b/src/main/java/edu/kit/provideq/toolbox/api/BoundComparisonDto.java index 10db5a1d..b6bbc33f 100644 --- a/src/main/java/edu/kit/provideq/toolbox/api/BoundComparisonDto.java +++ b/src/main/java/edu/kit/provideq/toolbox/api/BoundComparisonDto.java @@ -35,7 +35,6 @@ public BoundComparisonDto() { this.bound = null; } - @Override public String toString() { return "Comparison{bound=%s, comparison=%f}" .formatted(bound, comparison); @@ -50,10 +49,6 @@ public BoundDto getBound() { return bound; } - public void setBound(BoundDto bound) { - this.bound = bound; - } - public void setBound(BoundWithInfo bound) { this.bound = new BoundDto(bound); } diff --git a/src/main/java/edu/kit/provideq/toolbox/api/BoundDto.java b/src/main/java/edu/kit/provideq/toolbox/api/BoundDto.java index e23356fa..cc90a5d2 100644 --- a/src/main/java/edu/kit/provideq/toolbox/api/BoundDto.java +++ b/src/main/java/edu/kit/provideq/toolbox/api/BoundDto.java @@ -10,4 +10,8 @@ public BoundDto(BoundWithInfo boundWithInfo) { boundWithInfo.bound().boundType(), boundWithInfo.executionTime()); } + + public BoundDto() { + this(-1, BoundType.LOWER, -1); + } } From 66e3c50ddb435f7b7f736a89263387dc12bc067b Mon Sep 17 00:00:00 2001 From: Elscrux Date: Thu, 29 Jan 2026 15:33:32 +0100 Subject: [PATCH 061/137] docs: add solutionPattern javadocs --- .../edu/kit/provideq/toolbox/meta/ProblemType.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/main/java/edu/kit/provideq/toolbox/meta/ProblemType.java b/src/main/java/edu/kit/provideq/toolbox/meta/ProblemType.java index 874aa32d..0e7c98af 100644 --- a/src/main/java/edu/kit/provideq/toolbox/meta/ProblemType.java +++ b/src/main/java/edu/kit/provideq/toolbox/meta/ProblemType.java @@ -17,11 +17,14 @@ public class ProblemType { /** * Defines a new problem type. * - * @param id a unique string identifier for this type of problem. - * @param inputClass the Java class object corresponding to the {@link InputT} type parameter. - * @param resultClass the Java class object corresponding to the {@link ResultT} type parameter. - * @param estimator the bound estimator for this problem type. - * null if estimation is not supported. + * @param id a unique string identifier for this type of problem. + * @param inputClass the Java class object matching the {@link InputT} type parameter. + * @param resultClass the Java class object matching the {@link ResultT} type parameter. + * @param estimator the bound estimator for this problem type, + * null if estimation is not supported. + * @param solutionPattern a regex pattern describing the format of valid solutions + * needs to contain one group to match a float number describing + * the problem quality if estimation is supported, null otherwise. */ public ProblemType( String id, From 9d91a1a08cc15745659744e23d31b5d05e8e002d Mon Sep 17 00:00:00 2001 From: Elscrux Date: Thu, 5 Feb 2026 12:50:14 +0100 Subject: [PATCH 062/137] docs: add docs for isCalledOnlyOnce --- .../provideq/toolbox/meta/SubRoutineDefinition.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/edu/kit/provideq/toolbox/meta/SubRoutineDefinition.java b/src/main/java/edu/kit/provideq/toolbox/meta/SubRoutineDefinition.java index 6b4dd283..1143d983 100644 --- a/src/main/java/edu/kit/provideq/toolbox/meta/SubRoutineDefinition.java +++ b/src/main/java/edu/kit/provideq/toolbox/meta/SubRoutineDefinition.java @@ -4,10 +4,12 @@ * A sub-routine definition describes which problem type needs to be solved by a sub-routine and why * it needs to be solved. * - * @param type {@link ProblemType} that needs to be solved - * by this sub-routine. - * @param description description of the sub-routine call to provide information where and why it is - * needed. + * @param type {@link ProblemType} that needs to be solved + * by this sub-routine. + * @param description description of the sub-routine call to provide information where and why it is + * needed. + * @param isCalledOnlyOnce whether this sub-routine is called only once during the solution + * or if it may be called multiple times.a */ public record SubRoutineDefinition( ProblemType type, From debf4cffeb6237e5067c2f6e66a4778bb1fee1de Mon Sep 17 00:00:00 2001 From: Elscrux Date: Thu, 5 Feb 2026 13:21:49 +0100 Subject: [PATCH 063/137] docs: add SolverClassNameValidator docs --- .../edu/kit/provideq/toolbox/SolverClassNameValidator.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/edu/kit/provideq/toolbox/SolverClassNameValidator.java b/src/main/java/edu/kit/provideq/toolbox/SolverClassNameValidator.java index 4ba3ef9e..9de2f134 100644 --- a/src/main/java/edu/kit/provideq/toolbox/SolverClassNameValidator.java +++ b/src/main/java/edu/kit/provideq/toolbox/SolverClassNameValidator.java @@ -8,6 +8,9 @@ import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.stereotype.Component; +/** + * Validates that all solver class names are unique upon application startup. + */ @Component public class SolverClassNameValidator implements ApplicationListener { @Override From ec564693f491ef3734ab1168b8e922cb4ab3b705 Mon Sep 17 00:00:00 2001 From: Elscrux Date: Thu, 5 Feb 2026 14:07:45 +0100 Subject: [PATCH 064/137] chore: remove too long line --- .../edu/kit/provideq/toolbox/meta/SubRoutineDefinition.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/edu/kit/provideq/toolbox/meta/SubRoutineDefinition.java b/src/main/java/edu/kit/provideq/toolbox/meta/SubRoutineDefinition.java index 1143d983..15a5474d 100644 --- a/src/main/java/edu/kit/provideq/toolbox/meta/SubRoutineDefinition.java +++ b/src/main/java/edu/kit/provideq/toolbox/meta/SubRoutineDefinition.java @@ -6,8 +6,8 @@ * * @param type {@link ProblemType} that needs to be solved * by this sub-routine. - * @param description description of the sub-routine call to provide information where and why it is - * needed. + * @param description description of the sub-routine call to provide information where and + * why it is needed. * @param isCalledOnlyOnce whether this sub-routine is called only once during the solution * or if it may be called multiple times.a */ From 9d5f0d769a84facc4413268ad2afdde42b376326 Mon Sep 17 00:00:00 2001 From: Elscrux Date: Tue, 22 Apr 2025 11:55:40 +0200 Subject: [PATCH 065/137] chore: adjust to new application.properties layout --- src/main/resources/application.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 99adad9f..01cdfc62 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1 @@ -# default spring profile, correct one will be set during runtime (see ToolboxServerApplication.java) # options: mac, windows, linux spring.profiles.active=linux springdoc.swagger-ui.operationsSorter=alpha springdoc.swagger-ui.tagsSorter=alpha working.directory=jobs examples.directory=examples springdoc.swagger-ui.path=/ # Solvers name.solvers=solvers # Non OS-specific solvers: (typically GAMS and Python) name.gams=gams path.gams=${name.solvers}/${name.gams} name.gams.max-cut=max-cut path.gams.max-cut=${path.gams}/${name.gams.max-cut}/maxcut.gms name.gams.sat=sat path.gams.sat=${path.gams}/${name.gams.sat}/sat.gms name.qiskit=qiskit path.qiskit=${name.solvers}/${name.qiskit} name.qiskit.knapsack=knapsack path.qiskit.knapsack=${path.qiskit}/${name.qiskit.knapsack}/knapsack_qiskit.py venv.qiskit.knapsack=${name.solvers}_${name.qiskit}_${name.qiskit.knapsack} name.qiskit.materialsimulation=materialsimulation path.qiskit.materialsimulation=${path.qiskit}/${name.qiskit.materialsimulation}/material_simulation_qiskit.py venv.qiskit.materialsimulation=${name.solvers}_${name.qiskit}_${name.qiskit.materialsimulation} name.qiskit.max-cut=max-cut path.qiskit.max-cut=${path.qiskit}/${name.qiskit.max-cut}/maxCut_qiskit.py venv.qiskit.max-cut=${name.solvers}_${name.qiskit}_${name.qiskit.max-cut} name.qiskit.qubo=qubo path.qiskit.qubo=${path.qiskit}/${name.qiskit.qubo}/qubo_qiskit.py venv.qiskit.qubo=${name.solvers}_${name.qiskit}_${name.qiskit.qubo} name.cirq=cirq path.cirq=${name.solvers}/${name.cirq} name.cirq.max-cut=max-cut path.cirq.max-cut=${path.cirq}/${name.cirq.max-cut}/max_cut_cirq.py venv.cirq.max-cut=${name.solvers}_${name.cirq}_${name.cirq.max-cut} name.qrisp=qrisp path.qrisp=${name.solvers}/${name.qrisp} name.qrisp.vrp=vrp path.qrisp.vrp=${path.qrisp}/${name.qrisp.vrp}/grover.py venv.qrisp.vrp=${name.solvers}_${name.qrisp}_${name.qrisp.vrp} name.qrisp.qubo=qubo path.qrisp.qubo=${path.qrisp}/${name.qrisp.qubo}/qaoa.py venv.qrisp.qubo=${name.solvers}_${name.qrisp}_${name.qrisp.qubo} name.qrisp.sat=sat path.qrisp.sat.grover=${path.qrisp}/${name.qrisp.sat}/grover.py path.qrisp.sat.exact=${path.qrisp}/${name.qrisp.sat}/exact_grover.py venv.qrisp.sat=${name.solvers}_${name.qrisp}_${name.qrisp.sat} name.dwave=dwave path.dwave=${name.solvers}/${name.dwave} name.dwave.qubo=qubo path.dwave.qubo=${path.dwave}/${name.dwave.qubo}/main.py venv.dwave.qubo=${name.solvers}_${name.dwave}_${name.dwave.qubo} # Non OS-specific custom solvers: (solvers that are not part of a framework) name.custom=custom path.custom=${name.solvers}/${name.custom} name.custom.hs-knapsack=hs-knapsack path.custom.hs-knapsack=${path.custom}/${name.custom.hs-knapsack}/knapsack.py venv.custom.hs-knapsack=${name.solvers}_${name.custom}_${name.custom.hs-knapsack} name.custom.lkh=lkh path.custom.lkh=${path.custom}/${name.custom.lkh}/vrp_lkh.py venv.custom.lkh=${name.solvers}_${name.custom}_${name.custom.lkh} name.custom.berger-vrp=berger-vrp name.custom.sharp-sat-bruteforce=sharp-sat-bruteforce path.custom.sharp-sat-bruteforce=${path.custom}/${name.custom.sharp-sat-bruteforce}/exact-solution-counter.py venv.custom.sharp-sat-bruteforce=${name.solvers}_${name.custom}_${name.custom.sharp-sat-bruteforce} name.custom.sharp-sat-ganak=sharp-sat-ganak venv.custom.sharp-sat-ganak=${name.solvers}_${name.custom}_${name.custom.sharp-sat-ganak} # Demonstrators name.demonstrators=demonstrators name.demonstrators.cplex=cplex path.demonstrators.cplex=${name.demonstrators}/${name.demonstrators.cplex} name.demonstrators.cplex.mip=mip-solver path.demonstrators.cplex.mip=${path.demonstrators.cplex}/${name.demonstrators.cplex.mip}/mip-solver.py venv.demonstrators.cplex.mip=${name.demonstrators}_${name.demonstrators.cplex}_${name.demonstrators.cplex.mip} \ No newline at end of file +# default spring profile, correct one will be set during runtime (see ToolboxServerApplication.java) # options: mac, windows, linux spring.profiles.active=linux springdoc.swagger-ui.operationsSorter=alpha springdoc.swagger-ui.tagsSorter=alpha working.directory=jobs examples.directory=examples springdoc.swagger-ui.path=/ # Solvers name.solvers=solvers # Non OS-specific solvers: (typically GAMS and Python) name.gams=gams path.gams=${name.solvers}/${name.gams} name.gams.max-cut=max-cut path.gams.max-cut=${path.gams}/${name.gams.max-cut}/maxcut.gms name.gams.sat=sat path.gams.sat=${path.gams}/${name.gams.sat}/sat.gms name.qiskit=qiskit path.qiskit=${name.solvers}/${name.qiskit} name.qiskit.knapsack=knapsack path.qiskit.knapsack=${path.qiskit}/${name.qiskit.knapsack}/knapsack_qiskit.py venv.qiskit.knapsack=${name.solvers}_${name.qiskit}_${name.qiskit.knapsack} name.qiskit.materialsimulation=materialsimulation path.qiskit.materialsimulation=${path.qiskit}/${name.qiskit.materialsimulation}/material_simulation_qiskit.py venv.qiskit.materialsimulation=${name.solvers}_${name.qiskit}_${name.qiskit.materialsimulation} name.qiskit.max-cut=max-cut path.qiskit.max-cut=${path.qiskit}/${name.qiskit.max-cut}/maxCut_qiskit.py venv.qiskit.max-cut=${name.solvers}_${name.qiskit}_${name.qiskit.max-cut} name.qiskit.qubo=qubo path.qiskit.qubo=${path.qiskit}/${name.qiskit.qubo}/qubo_qiskit.py venv.qiskit.qubo=${name.solvers}_${name.qiskit}_${name.qiskit.qubo} name.cirq=cirq path.cirq=${name.solvers}/${name.cirq} name.cirq.max-cut=max-cut path.cirq.max-cut=${path.cirq}/${name.cirq.max-cut}/max_cut_cirq.py venv.cirq.max-cut=${name.solvers}_${name.cirq}_${name.cirq.max-cut} name.qrisp=qrisp path.qrisp=${name.solvers}/${name.qrisp} name.qrisp.vrp=vrp path.qrisp.vrp=${path.qrisp}/${name.qrisp.vrp}/grover.py venv.qrisp.vrp=${name.solvers}_${name.qrisp}_${name.qrisp.vrp} name.qrisp.qubo=qubo path.qrisp.qubo=${path.qrisp}/${name.qrisp.qubo}/qaoa.py venv.qrisp.qubo=${name.solvers}_${name.qrisp}_${name.qrisp.qubo} name.qrisp.sat=sat path.qrisp.sat.grover=${path.qrisp}/${name.qrisp.sat}/grover.py path.qrisp.sat.exact=${path.qrisp}/${name.qrisp.sat}/exact_grover.py venv.qrisp.sat=${name.solvers}_${name.qrisp}_${name.qrisp.sat} name.dwave=dwave path.dwave=${name.solvers}/${name.dwave} name.dwave.qubo=qubo path.dwave.qubo=${path.dwave}/${name.dwave.qubo}/main.py venv.dwave.qubo=${name.solvers}_${name.dwave}_${name.dwave.qubo} # Non OS-specific custom solvers: (solvers that are not part of a framework) name.custom=custom path.custom=${name.solvers}/${name.custom} name.custom.hs-knapsack=hs-knapsack path.custom.hs-knapsack=${path.custom}/${name.custom.hs-knapsack}/knapsack.py venv.custom.hs-knapsack=${name.solvers}_${name.custom}_${name.custom.hs-knapsack} name.custom.lkh=lkh path.custom.lkh=${path.custom}/${name.custom.lkh}/vrp_lkh.py venv.custom.lkh=${name.solvers}_${name.custom}_${name.custom.lkh} name.custom.berger-vrp=berger-vrp name.custom.sharp-sat-bruteforce=sharp-sat-bruteforce path.custom.sharp-sat-bruteforce=${path.custom}/${name.custom.sharp-sat-bruteforce}/exact-solution-counter.py venv.custom.sharp-sat-bruteforce=${name.solvers}_${name.custom}_${name.custom.sharp-sat-bruteforce} name.custom.sharp-sat-ganak=sharp-sat-ganak venv.custom.sharp-sat-ganak=${name.solvers}_${name.custom}_${name.custom.sharp-sat-ganak} # Demonstrators name.demonstrators=demonstrators name.demonstrators.cplex=cplex path.demonstrators.cplex=${name.demonstrators}/${name.demonstrators.cplex} name.demonstrators.cplex.mip=mip-solver path.demonstrators.cplex.mip=${path.demonstrators.cplex}/${name.demonstrators.cplex.mip}/mip-solver.py venv.demonstrators.cplex.mip=${name.demonstrators}_${name.demonstrators.cplex}_${name.demonstrators.cplex.mip} name.demonstrators.qiskit=qiskit path.demonstrators.qiskit=${name.demonstrators}/${name.demonstrators.qiskit} name.demonstrators.qiskit.molecule-energy=molecule-energy path.demonstrators.qiskit.molecule-energy=${path.demonstrators.qiskit}/${name.demonstrators.qiskit.molecule-energy}/molecule-energy.py venv.demonstrators.qiskit.molecule-energy=${name.demonstrators}_${name.demonstrators.qiskit}_${name.demonstrators.qiskit.molecule-energy} \ No newline at end of file From 0ed292fd742e42e6fab5b23473e6b35b0cff302f Mon Sep 17 00:00:00 2001 From: Elscrux Date: Mon, 10 Feb 2025 13:07:08 +0100 Subject: [PATCH 066/137] feat: Add molecule energy simulator --- demonstrators/qiskit/hydrogen/hydrogen.py | 123 ++++++++++++++++++ demonstrators/qiskit/requirements.txt | 14 ++ .../DemonstratorConfiguration.java | 5 +- .../MoleculeEnergySimulator.java | 76 +++++++++++ 4 files changed, 216 insertions(+), 2 deletions(-) create mode 100644 demonstrators/qiskit/hydrogen/hydrogen.py create mode 100644 demonstrators/qiskit/requirements.txt create mode 100644 src/main/java/edu/kit/provideq/toolbox/demonstrators/MoleculeEnergySimulator.java diff --git a/demonstrators/qiskit/hydrogen/hydrogen.py b/demonstrators/qiskit/hydrogen/hydrogen.py new file mode 100644 index 00000000..d3d6a8b7 --- /dev/null +++ b/demonstrators/qiskit/hydrogen/hydrogen.py @@ -0,0 +1,123 @@ +from qiskit_nature.second_q.drivers import PySCFDriver +from qiskit_nature.second_q.mappers import ParityMapper +from qiskit_algorithms.optimizers import L_BFGS_B +from qiskit.primitives import Estimator +from qiskit_nature.second_q.circuit.library import HartreeFock, UCCSD +from qiskit_algorithms import VQE +from qiskit_nature.second_q.algorithms import GroundStateEigensolver +import sys + +if len(sys.argv) != 2: + raise TypeError('This script expects exactly 1 arguments: the molecule') + +molecule = sys.argv[1] + +driver = PySCFDriver(atom=molecule) +problem = driver.run() + +mapper = ParityMapper(num_particles=problem.num_particles) + +optimizer = L_BFGS_B() + +estimator = Estimator() + +ansatz = UCCSD( + problem.num_spatial_orbitals, + problem.num_particles, + mapper, + initial_state=HartreeFock( + problem.num_spatial_orbitals, + problem.num_particles, + mapper, + ), +) + +vqe = VQE(estimator, ansatz, optimizer) +vqe.initial_point = [0] * ansatz.num_parameters + +algorithm = GroundStateEigensolver(mapper, vqe) + +electronic_structure_result = algorithm.solve(problem) +electronic_structure_result.formatting_precision = 6 +print(electronic_structure_result) # we're going to calculate the total ground state energy + +from qiskit_nature.second_q.drivers import PySCFDriver +from qiskit_nature.second_q.mappers import JordanWignerMapper +from qiskit_algorithms.optimizers import SLSQP +from qiskit.primitives import Estimator + +driver = PySCFDriver(atom='H .0 .0 .0; H .0 .0 0.74279') +problem = driver.run() + +mapper = JordanWignerMapper() + +optimizer = SLSQP(maxiter=10000, ftol=1e-9) + +estimator = Estimator() + +from qiskit import QuantumCircuit +from qiskit.circuit.library import TwoLocal +from qiskit_algorithms import VQE +from qiskit_nature.second_q.algorithms import GroundStateEigensolver +import numpy as np + +var_forms = [['ry', 'rz'], 'ry'] +entanglements = ['full', 'linear'] +entanglement_blocks = ['cx', 'cz', ['cx', 'cz']] +depths = list(range(1, 11)) + +reference_circuit = QuantumCircuit(4) +reference_circuit.x(0) +reference_circuit.x(2) + +results = np.zeros((len(depths), len(entanglements), len(var_forms), len(entanglement_blocks))) + +for i, d in enumerate(depths): + print(f'Depth: {d} done.') + for j, e in enumerate(entanglements): + for k, vf in enumerate(var_forms): + for l, eb in enumerate(entanglement_blocks): + optimizer = SLSQP(maxiter=10000, ftol=1e-9) + variational_form = TwoLocal(4, rotation_blocks=vf, entanglement_blocks=eb,entanglement=e, reps=d) + + ansatz = reference_circuit.compose(variational_form) + + vqe = VQE(estimator, ansatz, optimizer) + vqe.initial_point = [0] * ansatz.num_parameters + + algorithm = GroundStateEigensolver(mapper, vqe) + + electronic_structure_result = algorithm.solve(problem) + + results[i, j, k, l] = electronic_structure_result.total_energies[0] + +import matplotlib.pyplot as plt +from io import StringIO + +fig1, axs1 = plt.subplots(2, 3, sharey=True, sharex=True) +fig2, axs2 = plt.subplots(2, 3, sharey=True, sharex=True) + +fig1.supxlabel('Depth') +fig1.supylabel('Estimated ground state energy') +fig2.supxlabel('Depth') +fig2.supylabel('Estimated ground state energy') + +for j, e in enumerate(entanglements): + for l, eb in enumerate(entanglement_blocks): + axs1[j, l].plot(depths, results[:, j, 0, l]) + axs1[j, l].set_title(f'{e}, ryrz, {eb}') + axs1[j, l].text(0.90, 0.75, f'Min: {np.min(results[:, j, 0, l]):.3f}') + +for j, e in enumerate(entanglements): + for l, eb in enumerate(entanglement_blocks): + axs2[j, l].plot(depths, results[:, j, 1, l]) + axs2[j, l].set_title(f'{e}, ry, {eb}') + axs2[j, l].text(0.90, 0.75, f'Min: {np.min(results[:, j, 1, l]):.3f}') + +def print_fig(fig): + string_io = StringIO() + fig.savefig(string_io, format='svg') + print(string_io.getvalue()) + +print_fig(fig1) +print_fig(fig2) diff --git a/demonstrators/qiskit/requirements.txt b/demonstrators/qiskit/requirements.txt new file mode 100644 index 00000000..17aa4323 --- /dev/null +++ b/demonstrators/qiskit/requirements.txt @@ -0,0 +1,14 @@ +# This file describes the python package requirements for all Qiskit demonstrator scripts +# supported by ProvideQ + +# required for hydrogen solver +qiskit==1.1.0 +qiskit-aer==0.14.2 +qiskit-algorithms==0.3.0 +qiskit-ibm-runtime==0.25.0 +qiskit-machine-learning==0.7.2 +qiskit-nature==0.7.2 +qiskit-nature-pyscf==0.4.0 +qiskit-qasm3-import==0.5.0 +qiskit-transpiler-service==0.4.5 +matplotlib \ No newline at end of file diff --git a/src/main/java/edu/kit/provideq/toolbox/demonstrators/DemonstratorConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/demonstrators/DemonstratorConfiguration.java index d7c341bc..f29878d5 100644 --- a/src/main/java/edu/kit/provideq/toolbox/demonstrators/DemonstratorConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/demonstrators/DemonstratorConfiguration.java @@ -23,10 +23,11 @@ public class DemonstratorConfiguration { @Bean ProblemManager getDemonstratorManager( - CplexMipDemonstrator cplexMipDemonstrator + CplexMipDemonstrator cplexMipDemonstrator, + MoleculeEnergySimulator moleculeEnergySimulator ) { return new ProblemManager<>(DEMONSTRATOR, - Set.of(cplexMipDemonstrator), + Set.of(cplexMipDemonstrator, moleculeEnergySimulator), Set.of(new Problem<>(DEMONSTRATOR))); } } diff --git a/src/main/java/edu/kit/provideq/toolbox/demonstrators/MoleculeEnergySimulator.java b/src/main/java/edu/kit/provideq/toolbox/demonstrators/MoleculeEnergySimulator.java new file mode 100644 index 00000000..a0cc8ca9 --- /dev/null +++ b/src/main/java/edu/kit/provideq/toolbox/demonstrators/MoleculeEnergySimulator.java @@ -0,0 +1,76 @@ +package edu.kit.provideq.toolbox.demonstrators; + +import edu.kit.provideq.toolbox.Solution; +import edu.kit.provideq.toolbox.meta.SolvingProperties; +import edu.kit.provideq.toolbox.meta.SubRoutineResolver; +import edu.kit.provideq.toolbox.meta.setting.SolverSetting; +import edu.kit.provideq.toolbox.meta.setting.basic.TextSetting; +import edu.kit.provideq.toolbox.process.PythonProcessRunner; +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Component; +import reactor.core.publisher.Mono; + +/** + * Demonstrator for the Hydrogen Atom simulation. + * Note that its python dependencies can only be installed on Linux and macOS. + * Based on this Jupyter Notebook + */ +@Component +public class MoleculeEnergySimulator implements Demonstrator { + private final String scriptPath; + private final ApplicationContext context; + + private static final String SETTING_MOLECULE = "Molecule"; + private static final String DEFAULT_MOLECULE = "H .0 .0 .0; H .0 .0 0.74279"; + + @Autowired + public MoleculeEnergySimulator( + @Value("${demonstrators.qiskit.script.hydrogen}") String scriptPath, + ApplicationContext context) { + this.scriptPath = scriptPath; + this.context = context; + } + + @Override + public String getName() { + return "Hydrogen Atom Demonstrator"; + } + + @Override + public String getDescription() { + return ""; + } + + @Override + public List getSolverSettings() { + return List.of( + new TextSetting( + false, + SETTING_MOLECULE, + "The molecule to be simulated, a di-hydrogen molecule by default", + DEFAULT_MOLECULE + ) + ); + } + + @Override + public Mono> solve(String input, SubRoutineResolver subRoutineResolver, + SolvingProperties properties) { + var solution = new Solution<>(this); + + var molecule = properties.getSetting(SETTING_MOLECULE) + .map(TextSetting::getText) + .orElse(DEFAULT_MOLECULE); + + var processResult = context + .getBean(PythonProcessRunner.class, scriptPath) + .withArguments(molecule) + .readOutputString() + .run(getProblemType(), solution.getId()); + + return Mono.just(processResult.applyTo(solution)); + } +} From e6eb5e5e6684a614760db6f7a7e20fea546337a7 Mon Sep 17 00:00:00 2001 From: Elscrux Date: Thu, 13 Feb 2025 14:16:21 +0100 Subject: [PATCH 067/137] refactor: Rename from hydrogen to molecule energy --- .../hydrogen.py => molecule-energy/molecule-energy.py} | 0 demonstrators/qiskit/requirements.txt | 2 +- .../toolbox/demonstrators/MoleculeEnergySimulator.java | 6 +++--- 3 files changed, 4 insertions(+), 4 deletions(-) rename demonstrators/qiskit/{hydrogen/hydrogen.py => molecule-energy/molecule-energy.py} (100%) diff --git a/demonstrators/qiskit/hydrogen/hydrogen.py b/demonstrators/qiskit/molecule-energy/molecule-energy.py similarity index 100% rename from demonstrators/qiskit/hydrogen/hydrogen.py rename to demonstrators/qiskit/molecule-energy/molecule-energy.py diff --git a/demonstrators/qiskit/requirements.txt b/demonstrators/qiskit/requirements.txt index 17aa4323..38de56c5 100644 --- a/demonstrators/qiskit/requirements.txt +++ b/demonstrators/qiskit/requirements.txt @@ -1,7 +1,7 @@ # This file describes the python package requirements for all Qiskit demonstrator scripts # supported by ProvideQ -# required for hydrogen solver +# required for molecule energy solver qiskit==1.1.0 qiskit-aer==0.14.2 qiskit-algorithms==0.3.0 diff --git a/src/main/java/edu/kit/provideq/toolbox/demonstrators/MoleculeEnergySimulator.java b/src/main/java/edu/kit/provideq/toolbox/demonstrators/MoleculeEnergySimulator.java index a0cc8ca9..31a3a01b 100644 --- a/src/main/java/edu/kit/provideq/toolbox/demonstrators/MoleculeEnergySimulator.java +++ b/src/main/java/edu/kit/provideq/toolbox/demonstrators/MoleculeEnergySimulator.java @@ -14,7 +14,7 @@ import reactor.core.publisher.Mono; /** - * Demonstrator for the Hydrogen Atom simulation. + * Demonstrator for the Molecule Energy simulation. * Note that its python dependencies can only be installed on Linux and macOS. * Based on this Jupyter Notebook */ @@ -28,7 +28,7 @@ public class MoleculeEnergySimulator implements Demonstrator { @Autowired public MoleculeEnergySimulator( - @Value("${demonstrators.qiskit.script.hydrogen}") String scriptPath, + @Value("${demonstrators.qiskit.script.molecule-energy}") String scriptPath, ApplicationContext context) { this.scriptPath = scriptPath; this.context = context; @@ -36,7 +36,7 @@ public MoleculeEnergySimulator( @Override public String getName() { - return "Hydrogen Atom Demonstrator"; + return "Molecule Energy Simulator"; } @Override From dcfeaf47b348da4a930c9d676b0be1529e9df2d7 Mon Sep 17 00:00:00 2001 From: Elscrux Date: Thu, 13 Feb 2025 14:17:10 +0100 Subject: [PATCH 068/137] feat: Add note about molecule format --- .../provideq/toolbox/demonstrators/MoleculeEnergySimulator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/edu/kit/provideq/toolbox/demonstrators/MoleculeEnergySimulator.java b/src/main/java/edu/kit/provideq/toolbox/demonstrators/MoleculeEnergySimulator.java index 31a3a01b..0a04d918 100644 --- a/src/main/java/edu/kit/provideq/toolbox/demonstrators/MoleculeEnergySimulator.java +++ b/src/main/java/edu/kit/provideq/toolbox/demonstrators/MoleculeEnergySimulator.java @@ -50,7 +50,7 @@ public List getSolverSettings() { new TextSetting( false, SETTING_MOLECULE, - "The molecule to be simulated, a di-hydrogen molecule by default", + "The molecule to be simulated in XYZ format - a di-hydrogen molecule by default", DEFAULT_MOLECULE ) ); From 471fbaa28d01b4e655158ce192be09d500ea3258 Mon Sep 17 00:00:00 2001 From: Elscrux Date: Thu, 13 Feb 2025 14:17:46 +0100 Subject: [PATCH 069/137] feat: Add molecule energy demonstrator description --- .../provideq/toolbox/demonstrators/MoleculeEnergySimulator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/edu/kit/provideq/toolbox/demonstrators/MoleculeEnergySimulator.java b/src/main/java/edu/kit/provideq/toolbox/demonstrators/MoleculeEnergySimulator.java index 0a04d918..3fd6ce55 100644 --- a/src/main/java/edu/kit/provideq/toolbox/demonstrators/MoleculeEnergySimulator.java +++ b/src/main/java/edu/kit/provideq/toolbox/demonstrators/MoleculeEnergySimulator.java @@ -41,7 +41,7 @@ public String getName() { @Override public String getDescription() { - return ""; + return "Computes the ground state energy for a given molecule using VQE algorithm."; } @Override From 9824832559464e3a0f063856419ba409328e79ec Mon Sep 17 00:00:00 2001 From: Elscrux <34755314+Elscrux@users.noreply.github.com> Date: Thu, 6 Mar 2025 21:39:11 +0100 Subject: [PATCH 070/137] chore: Change arguments to singular argument Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- demonstrators/qiskit/molecule-energy/molecule-energy.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/demonstrators/qiskit/molecule-energy/molecule-energy.py b/demonstrators/qiskit/molecule-energy/molecule-energy.py index d3d6a8b7..ae704254 100644 --- a/demonstrators/qiskit/molecule-energy/molecule-energy.py +++ b/demonstrators/qiskit/molecule-energy/molecule-energy.py @@ -8,8 +8,7 @@ import sys if len(sys.argv) != 2: - raise TypeError('This script expects exactly 1 arguments: the molecule') - + raise TypeError('This script expects exactly 1 argument: the molecule') molecule = sys.argv[1] driver = PySCFDriver(atom=molecule) From add7460fcb2c87ddc1165b98c9bc0f50e5220a0a Mon Sep 17 00:00:00 2001 From: Elscrux Date: Tue, 22 Apr 2025 12:26:25 +0200 Subject: [PATCH 071/137] chore: Use updated application.properties and venv --- .../toolbox/demonstrators/MoleculeEnergySimulator.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/edu/kit/provideq/toolbox/demonstrators/MoleculeEnergySimulator.java b/src/main/java/edu/kit/provideq/toolbox/demonstrators/MoleculeEnergySimulator.java index 3fd6ce55..c36793b0 100644 --- a/src/main/java/edu/kit/provideq/toolbox/demonstrators/MoleculeEnergySimulator.java +++ b/src/main/java/edu/kit/provideq/toolbox/demonstrators/MoleculeEnergySimulator.java @@ -21,6 +21,7 @@ @Component public class MoleculeEnergySimulator implements Demonstrator { private final String scriptPath; + private final String venv; private final ApplicationContext context; private static final String SETTING_MOLECULE = "Molecule"; @@ -28,9 +29,11 @@ public class MoleculeEnergySimulator implements Demonstrator { @Autowired public MoleculeEnergySimulator( - @Value("${demonstrators.qiskit.script.molecule-energy}") String scriptPath, + @Value("${path.demonstrators.qiskit.molecule-energy}") String scriptPath, + @Value("${venv.demonstrators.qiskit.molecule-energy}") String venv, ApplicationContext context) { this.scriptPath = scriptPath; + this.venv = venv; this.context = context; } @@ -66,7 +69,7 @@ public Mono> solve(String input, SubRoutineResolver subRoutineR .orElse(DEFAULT_MOLECULE); var processResult = context - .getBean(PythonProcessRunner.class, scriptPath) + .getBean(PythonProcessRunner.class, scriptPath, venv) .withArguments(molecule) .readOutputString() .run(getProblemType(), solution.getId()); From e82c1390190b4773ee203324cd46744868fd4967 Mon Sep 17 00:00:00 2001 From: Elscrux Date: Tue, 22 Apr 2025 12:27:12 +0200 Subject: [PATCH 072/137] refactor: Move requirements.txt to solver directory to follow the new standard --- .../qiskit/molecule-energy/requirements.txt | 12 ++++++++++++ demonstrators/qiskit/requirements.txt | 14 -------------- 2 files changed, 12 insertions(+), 14 deletions(-) create mode 100644 demonstrators/qiskit/molecule-energy/requirements.txt delete mode 100644 demonstrators/qiskit/requirements.txt diff --git a/demonstrators/qiskit/molecule-energy/requirements.txt b/demonstrators/qiskit/molecule-energy/requirements.txt new file mode 100644 index 00000000..81163f14 --- /dev/null +++ b/demonstrators/qiskit/molecule-energy/requirements.txt @@ -0,0 +1,12 @@ +# This file describes the python package requirements for all Qiskit demonstrator scripts +# supported by ProvideQ + +# required for molecule energy solver +qiskit +qiskit_aer +qiskit_ibm_runtime +matplotlib +pylatexenc +qiskit-transpiler-service +qiskit-nature +qiskit-nature-pyscf \ No newline at end of file diff --git a/demonstrators/qiskit/requirements.txt b/demonstrators/qiskit/requirements.txt deleted file mode 100644 index 38de56c5..00000000 --- a/demonstrators/qiskit/requirements.txt +++ /dev/null @@ -1,14 +0,0 @@ -# This file describes the python package requirements for all Qiskit demonstrator scripts -# supported by ProvideQ - -# required for molecule energy solver -qiskit==1.1.0 -qiskit-aer==0.14.2 -qiskit-algorithms==0.3.0 -qiskit-ibm-runtime==0.25.0 -qiskit-machine-learning==0.7.2 -qiskit-nature==0.7.2 -qiskit-nature-pyscf==0.4.0 -qiskit-qasm3-import==0.5.0 -qiskit-transpiler-service==0.4.5 -matplotlib \ No newline at end of file From 2d0521e89f6472af4f109de04a75113653c8c1e5 Mon Sep 17 00:00:00 2001 From: Elscrux Date: Tue, 29 Apr 2025 10:41:21 +0200 Subject: [PATCH 073/137] chore: Remove unused imports --- demonstrators/qiskit/molecule-energy/molecule-energy.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/demonstrators/qiskit/molecule-energy/molecule-energy.py b/demonstrators/qiskit/molecule-energy/molecule-energy.py index ae704254..c1922b2b 100644 --- a/demonstrators/qiskit/molecule-energy/molecule-energy.py +++ b/demonstrators/qiskit/molecule-energy/molecule-energy.py @@ -40,10 +40,9 @@ electronic_structure_result.formatting_precision = 6 print(electronic_structure_result) # we're going to calculate the total ground state energy -from qiskit_nature.second_q.drivers import PySCFDriver + from qiskit_nature.second_q.mappers import JordanWignerMapper from qiskit_algorithms.optimizers import SLSQP -from qiskit.primitives import Estimator driver = PySCFDriver(atom='H .0 .0 .0; H .0 .0 0.74279') problem = driver.run() @@ -56,8 +55,6 @@ from qiskit import QuantumCircuit from qiskit.circuit.library import TwoLocal -from qiskit_algorithms import VQE -from qiskit_nature.second_q.algorithms import GroundStateEigensolver import numpy as np var_forms = [['ry', 'rz'], 'ry'] From 45c831169e09d580c3005cdb4a4982b1e14a7c8e Mon Sep 17 00:00:00 2001 From: Elscrux Date: Tue, 29 Apr 2025 10:55:59 +0200 Subject: [PATCH 074/137] chore: Change exception type --- demonstrators/qiskit/molecule-energy/molecule-energy.py | 2 +- solvers/custom/hs-knapsack/knapsack.py | 2 +- solvers/qiskit/max-cut/maxCut_qiskit.py | 2 +- solvers/qiskit/qubo/qubo_qiskit.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/demonstrators/qiskit/molecule-energy/molecule-energy.py b/demonstrators/qiskit/molecule-energy/molecule-energy.py index c1922b2b..9c6c4418 100644 --- a/demonstrators/qiskit/molecule-energy/molecule-energy.py +++ b/demonstrators/qiskit/molecule-energy/molecule-energy.py @@ -8,7 +8,7 @@ import sys if len(sys.argv) != 2: - raise TypeError('This script expects exactly 1 argument: the molecule') + raise ValueError('This script expects exactly 1 argument: the molecule') molecule = sys.argv[1] driver = PySCFDriver(atom=molecule) diff --git a/solvers/custom/hs-knapsack/knapsack.py b/solvers/custom/hs-knapsack/knapsack.py index e1863aa8..5d76a53f 100644 --- a/solvers/custom/hs-knapsack/knapsack.py +++ b/solvers/custom/hs-knapsack/knapsack.py @@ -4,7 +4,7 @@ from knapsack01 import HSKnapsack if len(sys.argv) != 3: - raise TypeError('This script expects exactly 2 arguments. Input file (argument 1) and output file (argument 2).') + raise ValueError('This script expects exactly 2 arguments. Input file (argument 1) and output file (argument 2).') input_path = sys.argv[1] output_path = sys.argv[2] diff --git a/solvers/qiskit/max-cut/maxCut_qiskit.py b/solvers/qiskit/max-cut/maxCut_qiskit.py index 84529daf..2fda6cb3 100644 --- a/solvers/qiskit/max-cut/maxCut_qiskit.py +++ b/solvers/qiskit/max-cut/maxCut_qiskit.py @@ -23,7 +23,7 @@ #if len(sys.argv) != 3: -# raise TypeError('This script expects exactly 2 arguments. Input file (argument 1) and output file (argument 2).') +# raise ValueError('This script expects exactly 2 arguments. Input file (argument 1) and output file (argument 2).') input_path = sys.argv[1] output_path = sys.argv[2] diff --git a/solvers/qiskit/qubo/qubo_qiskit.py b/solvers/qiskit/qubo/qubo_qiskit.py index d63e20a0..181a0a9c 100644 --- a/solvers/qiskit/qubo/qubo_qiskit.py +++ b/solvers/qiskit/qubo/qubo_qiskit.py @@ -7,7 +7,7 @@ from qiskit_optimization.algorithms import MinimumEigenOptimizer if len(sys.argv) != 3: - raise TypeError('This script expects exactly 2 arguments. Input file (argument 1) and output file (argument 2).') + raise ValueError('This script expects exactly 2 arguments. Input file (argument 1) and output file (argument 2).') input_path = sys.argv[1] output_path = sys.argv[2] From 24f5340f145e98924e13b6abf8713ff4c116b10a Mon Sep 17 00:00:00 2001 From: Elscrux Date: Tue, 6 May 2025 10:24:35 +0200 Subject: [PATCH 075/137] chore: Move imports up --- .../qiskit/molecule-energy/molecule-energy.py | 32 ++++++++----------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/demonstrators/qiskit/molecule-energy/molecule-energy.py b/demonstrators/qiskit/molecule-energy/molecule-energy.py index 9c6c4418..1ae8bdfd 100644 --- a/demonstrators/qiskit/molecule-energy/molecule-energy.py +++ b/demonstrators/qiskit/molecule-energy/molecule-energy.py @@ -1,10 +1,15 @@ -from qiskit_nature.second_q.drivers import PySCFDriver -from qiskit_nature.second_q.mappers import ParityMapper -from qiskit_algorithms.optimizers import L_BFGS_B -from qiskit.primitives import Estimator -from qiskit_nature.second_q.circuit.library import HartreeFock, UCCSD +from qiskit import QuantumCircuit from qiskit_algorithms import VQE +from qiskit_algorithms.optimizers import L_BFGS_B, SLSQP +from qiskit.circuit.library import TwoLocal from qiskit_nature.second_q.algorithms import GroundStateEigensolver +from qiskit_nature.second_q.circuit.library import HartreeFock, UCCSD +from qiskit_nature.second_q.drivers import PySCFDriver +from qiskit_nature.second_q.mappers import JordanWignerMapper, ParityMapper +from qiskit.primitives import Estimator +import matplotlib.pyplot as plt +from io import StringIO +import numpy as np import sys if len(sys.argv) != 2: @@ -38,11 +43,7 @@ electronic_structure_result = algorithm.solve(problem) electronic_structure_result.formatting_precision = 6 -print(electronic_structure_result) # we're going to calculate the total ground state energy - - -from qiskit_nature.second_q.mappers import JordanWignerMapper -from qiskit_algorithms.optimizers import SLSQP +print(electronic_structure_result) # we're going to calculate the total ground state energy driver = PySCFDriver(atom='H .0 .0 .0; H .0 .0 0.74279') problem = driver.run() @@ -53,10 +54,6 @@ estimator = Estimator() -from qiskit import QuantumCircuit -from qiskit.circuit.library import TwoLocal -import numpy as np - var_forms = [['ry', 'rz'], 'ry'] entanglements = ['full', 'linear'] entanglement_blocks = ['cx', 'cz', ['cx', 'cz']] @@ -74,7 +71,7 @@ for k, vf in enumerate(var_forms): for l, eb in enumerate(entanglement_blocks): optimizer = SLSQP(maxiter=10000, ftol=1e-9) - variational_form = TwoLocal(4, rotation_blocks=vf, entanglement_blocks=eb,entanglement=e, reps=d) + variational_form = TwoLocal(4, rotation_blocks=vf, entanglement_blocks=eb, entanglement=e, reps=d) ansatz = reference_circuit.compose(variational_form) @@ -87,9 +84,6 @@ results[i, j, k, l] = electronic_structure_result.total_energies[0] -import matplotlib.pyplot as plt -from io import StringIO - fig1, axs1 = plt.subplots(2, 3, sharey=True, sharex=True) fig2, axs2 = plt.subplots(2, 3, sharey=True, sharex=True) @@ -110,10 +104,12 @@ axs2[j, l].set_title(f'{e}, ry, {eb}') axs2[j, l].text(0.90, 0.75, f'Min: {np.min(results[:, j, 1, l]):.3f}') + def print_fig(fig): string_io = StringIO() fig.savefig(string_io, format='svg') print(string_io.getvalue()) + print_fig(fig1) print_fig(fig2) From 5666829435ae46dbd0f666cbf70c3713e83978e5 Mon Sep 17 00:00:00 2001 From: Elscrux Date: Fri, 30 May 2025 14:37:36 +0200 Subject: [PATCH 076/137] chore: fixate python requirement versions based on report in https://github.com/qiskit-community/qiskit-community-tutorials/blob/master/chemistry/h2_var_forms.ipynb --- .../qiskit/molecule-energy/requirements.txt | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/demonstrators/qiskit/molecule-energy/requirements.txt b/demonstrators/qiskit/molecule-energy/requirements.txt index 81163f14..b14633c4 100644 --- a/demonstrators/qiskit/molecule-energy/requirements.txt +++ b/demonstrators/qiskit/molecule-energy/requirements.txt @@ -2,11 +2,13 @@ # supported by ProvideQ # required for molecule energy solver -qiskit -qiskit_aer -qiskit_ibm_runtime -matplotlib -pylatexenc -qiskit-transpiler-service -qiskit-nature -qiskit-nature-pyscf \ No newline at end of file +qiskit==1.1.0 +qiskit-aer==0.14.2 +qiskit-algorithms==0.3.0 +qiskit-ibm-runtime==0.25.0 +qiskit-machine-learning==0.7.2 +qiskit-nature==0.7.2 +qiskit-nature-pyscf==0.4.0 +qiskit-qasm3-import==0.5.0 +qiskit-transpiler-service==0.4.5 +matplotlib==3.9.2 \ No newline at end of file From 03df7f6631a6a7551819c0534461e51a3bed5809 Mon Sep 17 00:00:00 2001 From: Elscrux Date: Fri, 30 May 2025 14:39:29 +0200 Subject: [PATCH 077/137] chore: remove redundant assignment of optimizer --- demonstrators/qiskit/molecule-energy/molecule-energy.py | 1 - 1 file changed, 1 deletion(-) diff --git a/demonstrators/qiskit/molecule-energy/molecule-energy.py b/demonstrators/qiskit/molecule-energy/molecule-energy.py index 1ae8bdfd..668e6986 100644 --- a/demonstrators/qiskit/molecule-energy/molecule-energy.py +++ b/demonstrators/qiskit/molecule-energy/molecule-energy.py @@ -70,7 +70,6 @@ for j, e in enumerate(entanglements): for k, vf in enumerate(var_forms): for l, eb in enumerate(entanglement_blocks): - optimizer = SLSQP(maxiter=10000, ftol=1e-9) variational_form = TwoLocal(4, rotation_blocks=vf, entanglement_blocks=eb, entanglement=e, reps=d) ansatz = reference_circuit.compose(variational_form) From 0c41bf5bf87ff7a557aa329b13f6b591d28dc5dc Mon Sep 17 00:00:00 2001 From: Elscrux Date: Fri, 30 May 2025 14:49:23 +0200 Subject: [PATCH 078/137] test: adapt CplexMipDemonstratorTest to general DemonstratorTest --- .../toolbox/api/CplexMipDemonstratorTest.java | 41 --------------- .../toolbox/api/DemonstratorTest.java | 52 +++++++++++++++++++ 2 files changed, 52 insertions(+), 41 deletions(-) delete mode 100644 src/test/java/edu/kit/provideq/toolbox/api/CplexMipDemonstratorTest.java create mode 100644 src/test/java/edu/kit/provideq/toolbox/api/DemonstratorTest.java diff --git a/src/test/java/edu/kit/provideq/toolbox/api/CplexMipDemonstratorTest.java b/src/test/java/edu/kit/provideq/toolbox/api/CplexMipDemonstratorTest.java deleted file mode 100644 index 93039c61..00000000 --- a/src/test/java/edu/kit/provideq/toolbox/api/CplexMipDemonstratorTest.java +++ /dev/null @@ -1,41 +0,0 @@ -package edu.kit.provideq.toolbox.api; - -import static edu.kit.provideq.toolbox.demonstrators.DemonstratorConfiguration.DEMONSTRATOR; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - -import edu.kit.provideq.toolbox.SolutionStatus; -import edu.kit.provideq.toolbox.demonstrators.CplexMipDemonstrator; -import edu.kit.provideq.toolbox.meta.ProblemState; -import java.time.Duration; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.web.reactive.server.WebTestClient; - -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -@SpringBootTest -@AutoConfigureMockMvc -class CplexMipDemonstratorTest { - @Autowired - private WebTestClient client; - - @Autowired - private CplexMipDemonstrator cplexMipDemonstrator; - - @BeforeEach - void beforeEach() { - this.client = this.client.mutate() - .responseTimeout(Duration.ofSeconds(20)) - .build(); - } - - @Test - void testCplexMipDemonstrator() { - var problem = ApiTestHelper.createProblem(client, cplexMipDemonstrator, "", DEMONSTRATOR); - ApiTestHelper.testSolution(problem); - } -} diff --git a/src/test/java/edu/kit/provideq/toolbox/api/DemonstratorTest.java b/src/test/java/edu/kit/provideq/toolbox/api/DemonstratorTest.java new file mode 100644 index 00000000..d2d4e173 --- /dev/null +++ b/src/test/java/edu/kit/provideq/toolbox/api/DemonstratorTest.java @@ -0,0 +1,52 @@ +package edu.kit.provideq.toolbox.api; + +import static edu.kit.provideq.toolbox.demonstrators.DemonstratorConfiguration.DEMONSTRATOR; + +import edu.kit.provideq.toolbox.meta.ProblemManagerProvider; +import edu.kit.provideq.toolbox.meta.ProblemSolver; +import java.time.Duration; +import java.util.stream.Stream; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.web.reactive.server.WebTestClient; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@SpringBootTest +@AutoConfigureMockMvc +class DemonstratorTest { + @Autowired + private WebTestClient client; + + @Autowired + private ProblemManagerProvider problemManagerProvider; + + @BeforeEach + void beforeEach() { + this.client = this.client.mutate() + .responseTimeout(Duration.ofSeconds(20)) + .build(); + } + + @SuppressWarnings("OptionalGetWithoutIsPresent") + Stream provideArguments() { + var problemManager = problemManagerProvider.findProblemManagerForType(DEMONSTRATOR).get(); + + return problemManager + .getSolvers() + .stream() + .map(Arguments::of); + } + + @ParameterizedTest + @MethodSource("provideArguments") + void testDemonstratorsWithEmptyInput(ProblemSolver solver) { + var problem = ApiTestHelper.createProblem(client, solver, "", DEMONSTRATOR); + ApiTestHelper.testSolution(problem); + } +} From 544c31ba97f8c297b25defd4ed44b929de0c308f Mon Sep 17 00:00:00 2001 From: Elscrux Date: Tue, 3 Jun 2025 10:33:15 +0200 Subject: [PATCH 079/137] chore: remove old debug command --- .../java/edu/kit/provideq/toolbox/process/ProcessRunner.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/edu/kit/provideq/toolbox/process/ProcessRunner.java b/src/main/java/edu/kit/provideq/toolbox/process/ProcessRunner.java index 21314223..c31bb7ed 100644 --- a/src/main/java/edu/kit/provideq/toolbox/process/ProcessRunner.java +++ b/src/main/java/edu/kit/provideq/toolbox/process/ProcessRunner.java @@ -294,8 +294,6 @@ protected ProcessRunnerExecutor getExecutor( int processExitCode; try { processBuilder.directory(new File(System.getProperty("user.dir"))); - String command = - processBuilder.command().stream().reduce("", (a, b) -> a + " |break| " + b); Process process = processBuilder.start(); processOutput = resourceProvider.readStream(process.inputReader()); From 5e78b317d031784686c773eef3ae0cce721da41a Mon Sep 17 00:00:00 2001 From: Elscrux Date: Tue, 3 Jun 2025 10:40:12 +0200 Subject: [PATCH 080/137] fix: put molecule argument in quotes --- .../provideq/toolbox/demonstrators/MoleculeEnergySimulator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/edu/kit/provideq/toolbox/demonstrators/MoleculeEnergySimulator.java b/src/main/java/edu/kit/provideq/toolbox/demonstrators/MoleculeEnergySimulator.java index c36793b0..2210bcb2 100644 --- a/src/main/java/edu/kit/provideq/toolbox/demonstrators/MoleculeEnergySimulator.java +++ b/src/main/java/edu/kit/provideq/toolbox/demonstrators/MoleculeEnergySimulator.java @@ -70,7 +70,7 @@ public Mono> solve(String input, SubRoutineResolver subRoutineR var processResult = context .getBean(PythonProcessRunner.class, scriptPath, venv) - .withArguments(molecule) + .withArguments('"' + molecule + '"') .readOutputString() .run(getProblemType(), solution.getId()); From e1248845dc08ea8ed4061d9f7e97130c86c05899 Mon Sep 17 00:00:00 2001 From: Elscrux Date: Tue, 3 Jun 2025 10:41:20 +0200 Subject: [PATCH 081/137] chore: update build file to remove syntax deprecated in gradle v9 --- build.gradle | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/build.gradle b/build.gradle index dcad0d47..3e1b4e9f 100644 --- a/build.gradle +++ b/build.gradle @@ -7,11 +7,15 @@ plugins { group = 'edu.kit.provideq' version = '0.4.1' -sourceCompatibility = '17' + +java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 +} repositories { mavenCentral() - maven { url 'https://jitpack.io' } + maven { url = 'https://jitpack.io' } } dependencies { @@ -35,9 +39,6 @@ tasks.named('test') { useJUnitPlatform() } -// set Java language level -targetCompatibility = JavaVersion.VERSION_17 - tasks.withType(JavaCompile).configureEach { // treat "unchecked" warnings as errors options.compilerArgs << '-Xlint:unchecked' From bd6a8f7ac92cc34711a1c851a76dd05de7c9da6b Mon Sep 17 00:00:00 2001 From: Elscrux Date: Tue, 3 Jun 2025 11:00:46 +0200 Subject: [PATCH 082/137] feat: add detailed report of used arguments --- demonstrators/qiskit/molecule-energy/molecule-energy.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/demonstrators/qiskit/molecule-energy/molecule-energy.py b/demonstrators/qiskit/molecule-energy/molecule-energy.py index 668e6986..e2efe561 100644 --- a/demonstrators/qiskit/molecule-energy/molecule-energy.py +++ b/demonstrators/qiskit/molecule-energy/molecule-energy.py @@ -12,8 +12,9 @@ import numpy as np import sys -if len(sys.argv) != 2: - raise ValueError('This script expects exactly 1 argument: the molecule') +arg_count = len(sys.argv) - 1 +if arg_count != 1: + raise ValueError(f'This script expects exactly 1 argument: the molecule, but got {arg_count}: {sys.argv[1:]}') molecule = sys.argv[1] driver = PySCFDriver(atom=molecule) From 3730ba44f3a94670fdec8ec6d6375a27acb97866 Mon Sep 17 00:00:00 2001 From: Elscrux Date: Tue, 3 Jun 2025 16:29:00 +0200 Subject: [PATCH 083/137] fix: remove prints from script as they also end up in the solution result --- demonstrators/qiskit/molecule-energy/molecule-energy.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/demonstrators/qiskit/molecule-energy/molecule-energy.py b/demonstrators/qiskit/molecule-energy/molecule-energy.py index e2efe561..f006e654 100644 --- a/demonstrators/qiskit/molecule-energy/molecule-energy.py +++ b/demonstrators/qiskit/molecule-energy/molecule-energy.py @@ -44,7 +44,6 @@ electronic_structure_result = algorithm.solve(problem) electronic_structure_result.formatting_precision = 6 -print(electronic_structure_result) # we're going to calculate the total ground state energy driver = PySCFDriver(atom='H .0 .0 .0; H .0 .0 0.74279') problem = driver.run() @@ -67,7 +66,6 @@ results = np.zeros((len(depths), len(entanglements), len(var_forms), len(entanglement_blocks))) for i, d in enumerate(depths): - print(f'Depth: {d} done.') for j, e in enumerate(entanglements): for k, vf in enumerate(var_forms): for l, eb in enumerate(entanglement_blocks): From 2811a1ff0c9e664f7815699fd0ef9ee08baca763 Mon Sep 17 00:00:00 2001 From: Elscrux Date: Tue, 3 Jun 2025 16:46:56 +0200 Subject: [PATCH 084/137] test: increase timeout of timeouted tests --- .../java/edu/kit/provideq/toolbox/api/DemonstratorTest.java | 2 +- .../provideq/toolbox/api/FeatureModelAnomalySolversTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/edu/kit/provideq/toolbox/api/DemonstratorTest.java b/src/test/java/edu/kit/provideq/toolbox/api/DemonstratorTest.java index d2d4e173..535ea044 100644 --- a/src/test/java/edu/kit/provideq/toolbox/api/DemonstratorTest.java +++ b/src/test/java/edu/kit/provideq/toolbox/api/DemonstratorTest.java @@ -29,7 +29,7 @@ class DemonstratorTest { @BeforeEach void beforeEach() { this.client = this.client.mutate() - .responseTimeout(Duration.ofSeconds(20)) + .responseTimeout(Duration.ofSeconds(60)) .build(); } diff --git a/src/test/java/edu/kit/provideq/toolbox/api/FeatureModelAnomalySolversTest.java b/src/test/java/edu/kit/provideq/toolbox/api/FeatureModelAnomalySolversTest.java index 3a493ed1..a9e8e150 100644 --- a/src/test/java/edu/kit/provideq/toolbox/api/FeatureModelAnomalySolversTest.java +++ b/src/test/java/edu/kit/provideq/toolbox/api/FeatureModelAnomalySolversTest.java @@ -37,7 +37,7 @@ class FeatureModelAnomalySolversTest { @BeforeEach void beforeEach() { this.client = this.client.mutate() - .responseTimeout(Duration.ofSeconds(20)) + .responseTimeout(Duration.ofSeconds(60)) .build(); } From adcad8d7477fc6c8a8184824ac6968440c16ec63 Mon Sep 17 00:00:00 2001 From: Elscrux Date: Thu, 5 Feb 2026 14:40:30 +0100 Subject: [PATCH 085/137] feat: update citation --- CITATION.bib | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/CITATION.bib b/CITATION.bib index 71f536aa..49353be2 100644 --- a/CITATION.bib +++ b/CITATION.bib @@ -1,6 +1,8 @@ -@article{eichhorn2025provideq, - title = {ProvideQ: A Quantum Optimization Toolbox}, - author = {Eichhorn, Domenik and Poser, Nick and Schweikart, Maximilian and Schaefer, Ina}, - journal = {arXiv preprint arXiv:2507.07649}, - year = {2025} -} \ No newline at end of file +@inproceedings{eichhorn2025provideq, + title={Provideq: A quantum optimization toolbox}, + author={Eichhorn, Domenik and Poser, Nick and Schweikart, Maximilian and Schaefer, Ina}, + booktitle={2025 IEEE International Conference on Quantum Software (QSW)}, + pages={206--214}, + year={2025}, + organization={IEEE} +} From 7415d1595d1165926e363fe8debb58d21a1b7b50 Mon Sep 17 00:00:00 2001 From: Domenik Eichhorn Date: Fri, 13 Feb 2026 13:29:00 +0100 Subject: [PATCH 086/137] udpate game version --- scripts/setup-gams.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/setup-gams.sh b/scripts/setup-gams.sh index 64309e47..289f8181 100755 --- a/scripts/setup-gams.sh +++ b/scripts/setup-gams.sh @@ -19,7 +19,7 @@ apt-get install curl --yes # iisaa/gams-docker (https://github.com/iiasa/gams-docker/blob/master/Dockerfile, GPL-3.0 licensed) # Configure the GAMS version here -GAMS_VERSION_RELEASE_MAJOR=49.7 +GAMS_VERSION_RELEASE_MAJOR=52.5 GAMS_VERSION_HOTFIX=0 # download the self-extracting archive to /opt/gams/gams.exe and run/extract it From 250cb354a18a085c3dde77ecb541c2c570cc13fe Mon Sep 17 00:00:00 2001 From: Domenik Eichhorn Date: Fri, 13 Feb 2026 13:40:55 +0100 Subject: [PATCH 087/137] added debug prints to testcases --- .../kit/provideq/toolbox/api/ApiTestHelper.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/test/java/edu/kit/provideq/toolbox/api/ApiTestHelper.java b/src/test/java/edu/kit/provideq/toolbox/api/ApiTestHelper.java index 53511624..ac4a42cf 100644 --- a/src/test/java/edu/kit/provideq/toolbox/api/ApiTestHelper.java +++ b/src/test/java/edu/kit/provideq/toolbox/api/ApiTestHelper.java @@ -153,11 +153,17 @@ public static ProblemDto trySolveFor( }) .returnResult() .getResponseBody(); - builder.append("Fetched problem: " + problemDto + "\n"); + if (problemDto == null) { + System.out.println("Testcase with id " + problemId + " will terminate because problemDto is null"); + System.out.println("problemType: " + problemType); + } assertNotNull(problemDto); + builder.append("Fetched problem: " + problemDto + "\n"); + if (problemDto.getState() == ProblemState.SOLVED) { + System.out.println(problemId + " solved successful."); break; } @@ -174,6 +180,11 @@ public static ProblemDto trySolveFor( .getResponseBody(); builder.append("Fetched sub problem: " + subProblemDto + "\n"); + if (subProblemDto == null) { + System.out.println("Subproblem with id " + subProblemId + " will terminate because subProblemDto is null"); + System.out.println("subProblem: " + subProblem); + System.out.println("Log: " + builder); + } assertNotNull(subProblemDto); } } @@ -187,6 +198,8 @@ public static ProblemDto trySolveFor( } if (hasTimeout.get()) { + System.out.print("Stopping testcase due to timeout."); + System.out.println("log: " + builder); throw new IllegalStateException("Problem did not solve in time: " + problemDto); } } From a1774923bbf0831a249f5d83f9285c181af61c19 Mon Sep 17 00:00:00 2001 From: Domenik Eichhorn Date: Fri, 13 Feb 2026 14:00:00 +0100 Subject: [PATCH 088/137] fixed checkstyle error (lines too long) --- .../java/edu/kit/provideq/toolbox/api/ApiTestHelper.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/test/java/edu/kit/provideq/toolbox/api/ApiTestHelper.java b/src/test/java/edu/kit/provideq/toolbox/api/ApiTestHelper.java index ac4a42cf..3f2eb73a 100644 --- a/src/test/java/edu/kit/provideq/toolbox/api/ApiTestHelper.java +++ b/src/test/java/edu/kit/provideq/toolbox/api/ApiTestHelper.java @@ -155,7 +155,8 @@ public static ProblemDto trySolveFor( .getResponseBody(); if (problemDto == null) { - System.out.println("Testcase with id " + problemId + " will terminate because problemDto is null"); + System.out.println("Testcase with id " + problemId + + " will terminate because problemDto is null"); System.out.println("problemType: " + problemType); } assertNotNull(problemDto); @@ -181,7 +182,8 @@ public static ProblemDto trySolveFor( builder.append("Fetched sub problem: " + subProblemDto + "\n"); if (subProblemDto == null) { - System.out.println("Subproblem with id " + subProblemId + " will terminate because subProblemDto is null"); + System.out.println("Subproblem with id " + subProblemId + + " will terminate because subProblemDto is null"); System.out.println("subProblem: " + subProblem); System.out.println("Log: " + builder); } From b4ea97993678ced50f86898af14702dd8bb06f92 Mon Sep 17 00:00:00 2001 From: Elscrux <34755314+Elscrux@users.noreply.github.com> Date: Fri, 13 Feb 2026 15:00:54 +0100 Subject: [PATCH 089/137] fix: replace print with println --- src/test/java/edu/kit/provideq/toolbox/api/ApiTestHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/edu/kit/provideq/toolbox/api/ApiTestHelper.java b/src/test/java/edu/kit/provideq/toolbox/api/ApiTestHelper.java index 3f2eb73a..2d2abb1f 100644 --- a/src/test/java/edu/kit/provideq/toolbox/api/ApiTestHelper.java +++ b/src/test/java/edu/kit/provideq/toolbox/api/ApiTestHelper.java @@ -200,7 +200,7 @@ public static ProblemDto trySolveFor( } if (hasTimeout.get()) { - System.out.print("Stopping testcase due to timeout."); + System.out.println("Stopping testcase due to timeout."); System.out.println("log: " + builder); throw new IllegalStateException("Problem did not solve in time: " + problemDto); } From c25efe75dc544ec2f99666e0446691731b307cbc Mon Sep 17 00:00:00 2001 From: Domenik Eichhorn Date: Thu, 19 Feb 2026 16:18:28 +0100 Subject: [PATCH 090/137] fix: new commands to install compilers that might be needed for solver installment --- scripts/ci-setup-solvers.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/scripts/ci-setup-solvers.sh b/scripts/ci-setup-solvers.sh index 9c72244b..5fdf8452 100755 --- a/scripts/ci-setup-solvers.sh +++ b/scripts/ci-setup-solvers.sh @@ -7,6 +7,13 @@ set -e # get base directory of the repository REPO_DIR=$(dirname "$(dirname "$(readlink -f "$0")")") +RUN apt-get update && apt-get install -y \ + build-essential \ + gcc \ + g++ \ + python3-dev \ + && rm -rf /var/lib/apt/lists/* + # make sure to install to the gams conda env source /opt/conda/bin/activate gams From 3e23e710f27d85d90fd274ffdd4ae0f959287231 Mon Sep 17 00:00:00 2001 From: Domenik Eichhorn Date: Fri, 13 Feb 2026 13:29:00 +0100 Subject: [PATCH 091/137] udpate game version --- scripts/setup-gams.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/setup-gams.sh b/scripts/setup-gams.sh index 64309e47..289f8181 100755 --- a/scripts/setup-gams.sh +++ b/scripts/setup-gams.sh @@ -19,7 +19,7 @@ apt-get install curl --yes # iisaa/gams-docker (https://github.com/iiasa/gams-docker/blob/master/Dockerfile, GPL-3.0 licensed) # Configure the GAMS version here -GAMS_VERSION_RELEASE_MAJOR=49.7 +GAMS_VERSION_RELEASE_MAJOR=52.5 GAMS_VERSION_HOTFIX=0 # download the self-extracting archive to /opt/gams/gams.exe and run/extract it From c139a1a216e6dc56c7984c9f8897c26534724a99 Mon Sep 17 00:00:00 2001 From: Domenik Eichhorn Date: Fri, 13 Feb 2026 13:40:55 +0100 Subject: [PATCH 092/137] added debug prints to testcases --- .../kit/provideq/toolbox/api/ApiTestHelper.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/test/java/edu/kit/provideq/toolbox/api/ApiTestHelper.java b/src/test/java/edu/kit/provideq/toolbox/api/ApiTestHelper.java index 53511624..ac4a42cf 100644 --- a/src/test/java/edu/kit/provideq/toolbox/api/ApiTestHelper.java +++ b/src/test/java/edu/kit/provideq/toolbox/api/ApiTestHelper.java @@ -153,11 +153,17 @@ public static ProblemDto trySolveFor( }) .returnResult() .getResponseBody(); - builder.append("Fetched problem: " + problemDto + "\n"); + if (problemDto == null) { + System.out.println("Testcase with id " + problemId + " will terminate because problemDto is null"); + System.out.println("problemType: " + problemType); + } assertNotNull(problemDto); + builder.append("Fetched problem: " + problemDto + "\n"); + if (problemDto.getState() == ProblemState.SOLVED) { + System.out.println(problemId + " solved successful."); break; } @@ -174,6 +180,11 @@ public static ProblemDto trySolveFor( .getResponseBody(); builder.append("Fetched sub problem: " + subProblemDto + "\n"); + if (subProblemDto == null) { + System.out.println("Subproblem with id " + subProblemId + " will terminate because subProblemDto is null"); + System.out.println("subProblem: " + subProblem); + System.out.println("Log: " + builder); + } assertNotNull(subProblemDto); } } @@ -187,6 +198,8 @@ public static ProblemDto trySolveFor( } if (hasTimeout.get()) { + System.out.print("Stopping testcase due to timeout."); + System.out.println("log: " + builder); throw new IllegalStateException("Problem did not solve in time: " + problemDto); } } From fa901d86e4a381ee4e861a8343c9260171258890 Mon Sep 17 00:00:00 2001 From: Domenik Eichhorn Date: Fri, 13 Feb 2026 14:00:00 +0100 Subject: [PATCH 093/137] fixed checkstyle error (lines too long) --- .../java/edu/kit/provideq/toolbox/api/ApiTestHelper.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/test/java/edu/kit/provideq/toolbox/api/ApiTestHelper.java b/src/test/java/edu/kit/provideq/toolbox/api/ApiTestHelper.java index ac4a42cf..3f2eb73a 100644 --- a/src/test/java/edu/kit/provideq/toolbox/api/ApiTestHelper.java +++ b/src/test/java/edu/kit/provideq/toolbox/api/ApiTestHelper.java @@ -155,7 +155,8 @@ public static ProblemDto trySolveFor( .getResponseBody(); if (problemDto == null) { - System.out.println("Testcase with id " + problemId + " will terminate because problemDto is null"); + System.out.println("Testcase with id " + problemId + + " will terminate because problemDto is null"); System.out.println("problemType: " + problemType); } assertNotNull(problemDto); @@ -181,7 +182,8 @@ public static ProblemDto trySolveFor( builder.append("Fetched sub problem: " + subProblemDto + "\n"); if (subProblemDto == null) { - System.out.println("Subproblem with id " + subProblemId + " will terminate because subProblemDto is null"); + System.out.println("Subproblem with id " + subProblemId + + " will terminate because subProblemDto is null"); System.out.println("subProblem: " + subProblem); System.out.println("Log: " + builder); } From a54c9b94dae77c13d303a6185da4f8d127b0776b Mon Sep 17 00:00:00 2001 From: Elscrux <34755314+Elscrux@users.noreply.github.com> Date: Fri, 13 Feb 2026 15:00:54 +0100 Subject: [PATCH 094/137] fix: replace print with println --- src/test/java/edu/kit/provideq/toolbox/api/ApiTestHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/edu/kit/provideq/toolbox/api/ApiTestHelper.java b/src/test/java/edu/kit/provideq/toolbox/api/ApiTestHelper.java index 3f2eb73a..2d2abb1f 100644 --- a/src/test/java/edu/kit/provideq/toolbox/api/ApiTestHelper.java +++ b/src/test/java/edu/kit/provideq/toolbox/api/ApiTestHelper.java @@ -200,7 +200,7 @@ public static ProblemDto trySolveFor( } if (hasTimeout.get()) { - System.out.print("Stopping testcase due to timeout."); + System.out.println("Stopping testcase due to timeout."); System.out.println("log: " + builder); throw new IllegalStateException("Problem did not solve in time: " + problemDto); } From cb6f53c6a500050a5a40244a1dac6b71d44fac33 Mon Sep 17 00:00:00 2001 From: Domenik Eichhorn Date: Thu, 19 Feb 2026 18:03:03 +0100 Subject: [PATCH 095/137] fix: moved install command to the correct location (in Dockerfile not in script) --- Dockerfile | 8 ++++++++ scripts/ci-setup-solvers.sh | 7 ------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Dockerfile b/Dockerfile index 1bc8bacd..280d686f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -32,6 +32,14 @@ RUN jlink \ FROM debian:bookworm-slim AS runner WORKDIR /app +# Install system build tools, needed to build solver dependencies +RUN apt-get update && apt-get install -y \ + build-essential \ + gcc \ + g++ \ + python3-dev \ + && rm -rf /var/lib/apt/lists/* + COPY scripts scripts # Install GAMS with conda python environment diff --git a/scripts/ci-setup-solvers.sh b/scripts/ci-setup-solvers.sh index 5fdf8452..9c72244b 100755 --- a/scripts/ci-setup-solvers.sh +++ b/scripts/ci-setup-solvers.sh @@ -7,13 +7,6 @@ set -e # get base directory of the repository REPO_DIR=$(dirname "$(dirname "$(readlink -f "$0")")") -RUN apt-get update && apt-get install -y \ - build-essential \ - gcc \ - g++ \ - python3-dev \ - && rm -rf /var/lib/apt/lists/* - # make sure to install to the gams conda env source /opt/conda/bin/activate gams From 3c852e3ff4795826c0dc11ed0dcd13efa8bde1f9 Mon Sep 17 00:00:00 2001 From: Domenik Eichhorn Date: Fri, 20 Feb 2026 10:44:49 +0100 Subject: [PATCH 096/137] fix: added info tag to gradle check command in CI runner for better test output --- .github/workflows/ci-cd-develop.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-cd-develop.yml b/.github/workflows/ci-cd-develop.yml index da4d67d9..d10465fa 100644 --- a/.github/workflows/ci-cd-develop.yml +++ b/.github/workflows/ci-cd-develop.yml @@ -46,7 +46,7 @@ jobs: shell: bash # required for the source command run: | # change to "./gradlew check --info" for more debugging output. source /opt/conda/bin/activate gams - ./gradlew check + ./gradlew check --info - name: Archive test reports if: always() From b64d0dbd52ae14ccf0dab04ed44da478098a050c Mon Sep 17 00:00:00 2001 From: Elscrux Date: Mon, 27 Apr 2026 14:26:32 +0200 Subject: [PATCH 097/137] feat: add dimension attribute to tsp --- .../edu/kit/provideq/toolbox/tsp/TspConfiguration.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/java/edu/kit/provideq/toolbox/tsp/TspConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/tsp/TspConfiguration.java index 2640c54a..6db1aa59 100644 --- a/src/main/java/edu/kit/provideq/toolbox/tsp/TspConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/tsp/TspConfiguration.java @@ -9,6 +9,7 @@ import edu.kit.provideq.toolbox.tsp.solvers.QuboTspSolver; import java.io.IOException; import java.util.HashSet; +import java.util.Map; import java.util.Objects; import java.util.Set; import org.springframework.context.annotation.Bean; @@ -27,7 +28,14 @@ public class TspConfiguration { "A Traveling Sales Person Problem. Optimization Problem with the goal of find " + "an optimal route between a given set of connected cities.", String.class, - String.class + String.class, + Map.ofEntries( + Map.entry("dimension", problem -> problem.lines() + .filter(line -> line.startsWith("DIMENSION")) + .findFirst() + .map(line -> line.split(":")[1].trim()) + .orElse("")) + ) ); @Bean From 4ab5fe0e00b69646fa9e9740e3adb687894948d9 Mon Sep 17 00:00:00 2001 From: Elscrux Date: Tue, 2 Jun 2026 10:55:23 +0200 Subject: [PATCH 098/137] feat: add gams qubo solver --- solvers/gams/qubo/requirements.txt | 2 + solvers/gams/qubo/solver_lp_qubo.py | 107 ++++++++++++++++++ .../toolbox/qubo/QuboConfiguration.java | 5 +- .../toolbox/qubo/solvers/GamsQuboSolver.java | 64 +++++++++++ src/main/resources/application.properties | 2 +- 5 files changed, 178 insertions(+), 2 deletions(-) create mode 100644 solvers/gams/qubo/requirements.txt create mode 100644 solvers/gams/qubo/solver_lp_qubo.py create mode 100644 src/main/java/edu/kit/provideq/toolbox/qubo/solvers/GamsQuboSolver.java diff --git a/solvers/gams/qubo/requirements.txt b/solvers/gams/qubo/requirements.txt new file mode 100644 index 00000000..1c690d5d --- /dev/null +++ b/solvers/gams/qubo/requirements.txt @@ -0,0 +1,2 @@ +# This file describes the python package requirements for the GAMS Qubo solver +gamspy == 1.23.1 \ No newline at end of file diff --git a/solvers/gams/qubo/solver_lp_qubo.py b/solvers/gams/qubo/solver_lp_qubo.py new file mode 100644 index 00000000..24b77d68 --- /dev/null +++ b/solvers/gams/qubo/solver_lp_qubo.py @@ -0,0 +1,107 @@ +import gamspy as gp +import pandas as pd +import json +import re +import sys + +arg_count = len(sys.argv) - 1 +if arg_count != 2: + raise TypeError(f'This script expects exactly 2 arguments but got {arg_count}. Input file (argument 1) and output file (argument 2).') + +input_path = sys.argv[1] +output_path = sys.argv[2] + +def parse_lp_to_df(filepath="unsplittable_model.lp"): + """Parses the LP file to extract the Q matrix coefficients and constant.""" + with open(filepath, "r") as f: + content = f.read() + + # 1. Extract the quadratic block inside [...] + q_block_match = re.search(r"\[(.*?)\]", content, re.DOTALL) + if not q_block_match: + raise ValueError("Could not find the [...] block in the LP file.") + + # Clean up line breaks and excess spaces + q_text = q_block_match.group(1).replace("\n", " ").replace("\r", "") + q_text = re.sub(r"\s+", " ", q_text) + + # 2. Extract coefficients using Regex + # Match squares (e.g., "- 18270 b1^2") + squares = re.findall(r"([+-]?\s*\d+(?:\.\d+)?)\s*b(\d+)\^2", q_text) + # Match products (e.g., "+ 6480 b1 * b2") + products = re.findall(r"([+-]?\s*\d+(?:\.\d+)?)\s*b(\d+)\s*\*\s*b(\d+)", q_text) + + q_records = [] + + # Format diagonal terms (b_i * b_i) + for coef_str, idx in squares: + coef = float(coef_str.replace(" ", "")) + q_records.append({"i": f"b{idx}", "j": f"b{idx}", "value": coef}) + + # Format off-diagonal terms (b_i * b_j) + for coef_str, idx1, idx2 in products: + coef = float(coef_str.replace(" ", "")) + q_records.append({"i": f"b{idx1}", "j": f"b{idx2}", "value": coef}) + + q_df = pd.DataFrame(q_records) + + # 3. Find the constant on the right-hand side + # e1: -x143 + [...] = -590660 + const_match = re.search(r"=\s*([+-]?\d+(?:\.\d+)?)", content) + if const_match: + # Move RHS constant to the objective side + constant = -(float(const_match.group(1).replace(" ", ""))) + else: + constant = 0 + + return q_df, constant + + +q_df, constant = parse_lp_to_df(input_path) + +m = gp.Container() + +# Dynamically find all unique variables present in the parsed LP matrix +unique_vars = set(q_df["i"].unique()).union(set(q_df["j"].unique())) + +# Sort them properly (b1, b2, ... b150) +b_idx = sorted(list(unique_vars), key=lambda x: int(x[1:])) + +i = gp.Set(m, name="i", records=b_idx) +alias_j = gp.Alias(m, name="j", alias_with=i) + +Q = gp.Parameter(m, name="Q", domain=[i, alias_j], records=q_df) + +x = gp.Variable(m, name="x", domain=[i], type="binary") + +# Note: CPLEX LP format implies the [...] block is implicitly multiplied by 0.5. +# We add `0.5 *` here to mirror exactly how the solver reads standard LP files. +obj_expr = 0.5 * gp.Sum((i, alias_j), Q[i, alias_j] * x[i] * x[alias_j]) + constant + +z = gp.Variable(m, name="z", type="free") +eq = gp.Equation(m, name="eq") +eq[...] = z == obj_expr + +qubo_model = gp.Model( + m, name="qubo", equations=[eq], problem="MIQCP", sense="min", objective=z +) + +print("Solving model natively...") +qubo_model.solve(solver="CPLEX", output=sys.stdout, options=gp.Options(time_limit=60)) + +print(f"\n--- Solver Status: {qubo_model.status} ---") +print(f"--- Objective Value: {qubo_model.objective_value} ---") + +solution_dict = {} + +if x.records is not None and not x.records.empty: + for _, row in x.records.iterrows(): + val = row["level"] + if val > 0.5: + var_name = row["i"] + solution_dict[var_name] = float(round(val)) + +with open(output_path, "w") as f: + json.dump(solution_dict, f, indent=4) + +print(f"--- Extracted {len(solution_dict)} active variables to {output_path} ---") \ No newline at end of file diff --git a/src/main/java/edu/kit/provideq/toolbox/qubo/QuboConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/qubo/QuboConfiguration.java index 72fb2631..f95dbd81 100644 --- a/src/main/java/edu/kit/provideq/toolbox/qubo/QuboConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/qubo/QuboConfiguration.java @@ -6,6 +6,7 @@ import edu.kit.provideq.toolbox.meta.ProblemManager; import edu.kit.provideq.toolbox.meta.ProblemType; import edu.kit.provideq.toolbox.qubo.solvers.DwaveQuboSolver; +import edu.kit.provideq.toolbox.qubo.solvers.GamsQuboSolver; import edu.kit.provideq.toolbox.qubo.solvers.KipuQuboSolver; import edu.kit.provideq.toolbox.qubo.solvers.QiskitQuboSolver; import edu.kit.provideq.toolbox.qubo.solvers.QrispQuboSolver; @@ -43,11 +44,13 @@ ProblemManager getQuboManager( QrispQuboSolver qrispSolver, QuantagoniaQuboSolver quantagoniaQuboSolver, KipuQuboSolver kipuQuboSolver, + GamsQuboSolver gamsQuboSolver, ResourceProvider resourceProvider ) { return new ProblemManager<>( QUBO, - Set.of(qiskitSolver, dwaveSolver, qrispSolver, quantagoniaQuboSolver, kipuQuboSolver), + Set.of(qiskitSolver, dwaveSolver, qrispSolver, quantagoniaQuboSolver, kipuQuboSolver, + gamsQuboSolver), loadExampleProblems(resourceProvider) ); } diff --git a/src/main/java/edu/kit/provideq/toolbox/qubo/solvers/GamsQuboSolver.java b/src/main/java/edu/kit/provideq/toolbox/qubo/solvers/GamsQuboSolver.java new file mode 100644 index 00000000..425d6a44 --- /dev/null +++ b/src/main/java/edu/kit/provideq/toolbox/qubo/solvers/GamsQuboSolver.java @@ -0,0 +1,64 @@ +package edu.kit.provideq.toolbox.qubo.solvers; + +import edu.kit.provideq.toolbox.Solution; +import edu.kit.provideq.toolbox.meta.SolvingProperties; +import edu.kit.provideq.toolbox.meta.SubRoutineResolver; +import edu.kit.provideq.toolbox.process.ProcessRunner; +import edu.kit.provideq.toolbox.process.PythonProcessRunner; +import edu.kit.provideq.toolbox.qubo.QuboConfiguration; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Component; +import reactor.core.publisher.Mono; + +/** + * {@link QuboConfiguration#QUBO} solver using a GAMS CPLEX implementation. + */ +@Component +public class GamsQuboSolver extends QuboSolver { + private final String scriptPath; + private final String venv; + private final ApplicationContext context; + + @Autowired + public GamsQuboSolver( + @Value("${path.gams.qubo}") String scriptPath, + @Value("${venv.gams.qubo}") String venv, + ApplicationContext context) { + this.scriptPath = scriptPath; + this.venv = venv; + this.context = context; + } + + @Override + public String getName() { + return "(GAMS) CPLEX Solver for QUBOs"; + } + + @Override + public String getDescription() { + return "A solver for QUBOs using a GAMS CPLEX solver."; + } + + @Override + public Mono> solve( + String input, + SubRoutineResolver subRoutineResolver, + SolvingProperties properties + ) { + var solution = new Solution<>(this); + + var processResult = context + .getBean(PythonProcessRunner.class, scriptPath, venv) + .withArguments( + ProcessRunner.INPUT_FILE_PATH, + ProcessRunner.OUTPUT_FILE_PATH + ) + .writeInputFile(input, "problem.lp") + .readOutputFile("solution.json") + .run(getProblemType(), solution.getId()); + + return Mono.just(processResult.applyTo(solution)); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 01cdfc62..24875c57 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1 @@ -# default spring profile, correct one will be set during runtime (see ToolboxServerApplication.java) # options: mac, windows, linux spring.profiles.active=linux springdoc.swagger-ui.operationsSorter=alpha springdoc.swagger-ui.tagsSorter=alpha working.directory=jobs examples.directory=examples springdoc.swagger-ui.path=/ # Solvers name.solvers=solvers # Non OS-specific solvers: (typically GAMS and Python) name.gams=gams path.gams=${name.solvers}/${name.gams} name.gams.max-cut=max-cut path.gams.max-cut=${path.gams}/${name.gams.max-cut}/maxcut.gms name.gams.sat=sat path.gams.sat=${path.gams}/${name.gams.sat}/sat.gms name.qiskit=qiskit path.qiskit=${name.solvers}/${name.qiskit} name.qiskit.knapsack=knapsack path.qiskit.knapsack=${path.qiskit}/${name.qiskit.knapsack}/knapsack_qiskit.py venv.qiskit.knapsack=${name.solvers}_${name.qiskit}_${name.qiskit.knapsack} name.qiskit.materialsimulation=materialsimulation path.qiskit.materialsimulation=${path.qiskit}/${name.qiskit.materialsimulation}/material_simulation_qiskit.py venv.qiskit.materialsimulation=${name.solvers}_${name.qiskit}_${name.qiskit.materialsimulation} name.qiskit.max-cut=max-cut path.qiskit.max-cut=${path.qiskit}/${name.qiskit.max-cut}/maxCut_qiskit.py venv.qiskit.max-cut=${name.solvers}_${name.qiskit}_${name.qiskit.max-cut} name.qiskit.qubo=qubo path.qiskit.qubo=${path.qiskit}/${name.qiskit.qubo}/qubo_qiskit.py venv.qiskit.qubo=${name.solvers}_${name.qiskit}_${name.qiskit.qubo} name.cirq=cirq path.cirq=${name.solvers}/${name.cirq} name.cirq.max-cut=max-cut path.cirq.max-cut=${path.cirq}/${name.cirq.max-cut}/max_cut_cirq.py venv.cirq.max-cut=${name.solvers}_${name.cirq}_${name.cirq.max-cut} name.qrisp=qrisp path.qrisp=${name.solvers}/${name.qrisp} name.qrisp.vrp=vrp path.qrisp.vrp=${path.qrisp}/${name.qrisp.vrp}/grover.py venv.qrisp.vrp=${name.solvers}_${name.qrisp}_${name.qrisp.vrp} name.qrisp.qubo=qubo path.qrisp.qubo=${path.qrisp}/${name.qrisp.qubo}/qaoa.py venv.qrisp.qubo=${name.solvers}_${name.qrisp}_${name.qrisp.qubo} name.qrisp.sat=sat path.qrisp.sat.grover=${path.qrisp}/${name.qrisp.sat}/grover.py path.qrisp.sat.exact=${path.qrisp}/${name.qrisp.sat}/exact_grover.py venv.qrisp.sat=${name.solvers}_${name.qrisp}_${name.qrisp.sat} name.dwave=dwave path.dwave=${name.solvers}/${name.dwave} name.dwave.qubo=qubo path.dwave.qubo=${path.dwave}/${name.dwave.qubo}/main.py venv.dwave.qubo=${name.solvers}_${name.dwave}_${name.dwave.qubo} # Non OS-specific custom solvers: (solvers that are not part of a framework) name.custom=custom path.custom=${name.solvers}/${name.custom} name.custom.hs-knapsack=hs-knapsack path.custom.hs-knapsack=${path.custom}/${name.custom.hs-knapsack}/knapsack.py venv.custom.hs-knapsack=${name.solvers}_${name.custom}_${name.custom.hs-knapsack} name.custom.lkh=lkh path.custom.lkh=${path.custom}/${name.custom.lkh}/vrp_lkh.py venv.custom.lkh=${name.solvers}_${name.custom}_${name.custom.lkh} name.custom.berger-vrp=berger-vrp name.custom.sharp-sat-bruteforce=sharp-sat-bruteforce path.custom.sharp-sat-bruteforce=${path.custom}/${name.custom.sharp-sat-bruteforce}/exact-solution-counter.py venv.custom.sharp-sat-bruteforce=${name.solvers}_${name.custom}_${name.custom.sharp-sat-bruteforce} name.custom.sharp-sat-ganak=sharp-sat-ganak venv.custom.sharp-sat-ganak=${name.solvers}_${name.custom}_${name.custom.sharp-sat-ganak} # Demonstrators name.demonstrators=demonstrators name.demonstrators.cplex=cplex path.demonstrators.cplex=${name.demonstrators}/${name.demonstrators.cplex} name.demonstrators.cplex.mip=mip-solver path.demonstrators.cplex.mip=${path.demonstrators.cplex}/${name.demonstrators.cplex.mip}/mip-solver.py venv.demonstrators.cplex.mip=${name.demonstrators}_${name.demonstrators.cplex}_${name.demonstrators.cplex.mip} name.demonstrators.qiskit=qiskit path.demonstrators.qiskit=${name.demonstrators}/${name.demonstrators.qiskit} name.demonstrators.qiskit.molecule-energy=molecule-energy path.demonstrators.qiskit.molecule-energy=${path.demonstrators.qiskit}/${name.demonstrators.qiskit.molecule-energy}/molecule-energy.py venv.demonstrators.qiskit.molecule-energy=${name.demonstrators}_${name.demonstrators.qiskit}_${name.demonstrators.qiskit.molecule-energy} \ No newline at end of file +# default spring profile, correct one will be set during runtime (see ToolboxServerApplication.java) # options: mac, windows, linux spring.profiles.active=linux springdoc.swagger-ui.operationsSorter=alpha springdoc.swagger-ui.tagsSorter=alpha working.directory=jobs examples.directory=examples springdoc.swagger-ui.path=/ # Solvers name.solvers=solvers # Non OS-specific solvers: (typically GAMS and Python) name.gams=gams path.gams=${name.solvers}/${name.gams} name.gams.max-cut=max-cut path.gams.max-cut=${path.gams}/${name.gams.max-cut}/maxcut.gms name.gams.sat=sat path.gams.sat=${path.gams}/${name.gams.sat}/sat.gms name.gams.qubo=qubo path.gams.qubo=${path.gams}/${name.gams.qubo}/solver_lp_qubo.py venv.gams.qubo=${name.solvers}_${name.gams}_${name.gams.qubo} name.qiskit=qiskit path.qiskit=${name.solvers}/${name.qiskit} name.qiskit.knapsack=knapsack path.qiskit.knapsack=${path.qiskit}/${name.qiskit.knapsack}/knapsack_qiskit.py venv.qiskit.knapsack=${name.solvers}_${name.qiskit}_${name.qiskit.knapsack} name.qiskit.materialsimulation=materialsimulation path.qiskit.materialsimulation=${path.qiskit}/${name.qiskit.materialsimulation}/material_simulation_qiskit.py venv.qiskit.materialsimulation=${name.solvers}_${name.qiskit}_${name.qiskit.materialsimulation} name.qiskit.max-cut=max-cut path.qiskit.max-cut=${path.qiskit}/${name.qiskit.max-cut}/maxCut_qiskit.py venv.qiskit.max-cut=${name.solvers}_${name.qiskit}_${name.qiskit.max-cut} name.qiskit.qubo=qubo path.qiskit.qubo=${path.qiskit}/${name.qiskit.qubo}/qubo_qiskit.py venv.qiskit.qubo=${name.solvers}_${name.qiskit}_${name.qiskit.qubo} name.cirq=cirq path.cirq=${name.solvers}/${name.cirq} name.cirq.max-cut=max-cut path.cirq.max-cut=${path.cirq}/${name.cirq.max-cut}/max_cut_cirq.py venv.cirq.max-cut=${name.solvers}_${name.cirq}_${name.cirq.max-cut} name.qrisp=qrisp path.qrisp=${name.solvers}/${name.qrisp} name.qrisp.vrp=vrp path.qrisp.vrp=${path.qrisp}/${name.qrisp.vrp}/grover.py venv.qrisp.vrp=${name.solvers}_${name.qrisp}_${name.qrisp.vrp} name.qrisp.qubo=qubo path.qrisp.qubo=${path.qrisp}/${name.qrisp.qubo}/qaoa.py venv.qrisp.qubo=${name.solvers}_${name.qrisp}_${name.qrisp.qubo} name.qrisp.sat=sat path.qrisp.sat.grover=${path.qrisp}/${name.qrisp.sat}/grover.py path.qrisp.sat.exact=${path.qrisp}/${name.qrisp.sat}/exact_grover.py venv.qrisp.sat=${name.solvers}_${name.qrisp}_${name.qrisp.sat} name.dwave=dwave path.dwave=${name.solvers}/${name.dwave} name.dwave.qubo=qubo path.dwave.qubo=${path.dwave}/${name.dwave.qubo}/main.py venv.dwave.qubo=${name.solvers}_${name.dwave}_${name.dwave.qubo} # Non OS-specific custom solvers: (solvers that are not part of a framework) name.custom=custom path.custom=${name.solvers}/${name.custom} name.custom.hs-knapsack=hs-knapsack path.custom.hs-knapsack=${path.custom}/${name.custom.hs-knapsack}/knapsack.py venv.custom.hs-knapsack=${name.solvers}_${name.custom}_${name.custom.hs-knapsack} name.custom.lkh=lkh path.custom.lkh=${path.custom}/${name.custom.lkh}/vrp_lkh.py venv.custom.lkh=${name.solvers}_${name.custom}_${name.custom.lkh} name.custom.berger-vrp=berger-vrp name.custom.sharp-sat-bruteforce=sharp-sat-bruteforce path.custom.sharp-sat-bruteforce=${path.custom}/${name.custom.sharp-sat-bruteforce}/exact-solution-counter.py venv.custom.sharp-sat-bruteforce=${name.solvers}_${name.custom}_${name.custom.sharp-sat-bruteforce} name.custom.sharp-sat-ganak=sharp-sat-ganak venv.custom.sharp-sat-ganak=${name.solvers}_${name.custom}_${name.custom.sharp-sat-ganak} # Demonstrators name.demonstrators=demonstrators name.demonstrators.cplex=cplex path.demonstrators.cplex=${name.demonstrators}/${name.demonstrators.cplex} name.demonstrators.cplex.mip=mip-solver path.demonstrators.cplex.mip=${path.demonstrators.cplex}/${name.demonstrators.cplex.mip}/mip-solver.py venv.demonstrators.cplex.mip=${name.demonstrators}_${name.demonstrators.cplex}_${name.demonstrators.cplex.mip} name.demonstrators.qiskit=qiskit path.demonstrators.qiskit=${name.demonstrators}/${name.demonstrators.qiskit} name.demonstrators.qiskit.molecule-energy=molecule-energy path.demonstrators.qiskit.molecule-energy=${path.demonstrators.qiskit}/${name.demonstrators.qiskit.molecule-energy}/molecule-energy.py venv.demonstrators.qiskit.molecule-energy=${name.demonstrators}_${name.demonstrators.qiskit}_${name.demonstrators.qiskit.molecule-energy} \ No newline at end of file From f36d4b7044e95585f9777ec84a02a07c3370326e Mon Sep 17 00:00:00 2001 From: Elscrux Date: Tue, 2 Jun 2026 20:08:18 +0200 Subject: [PATCH 099/137] feat: change python3 to python in venv setup script --- scripts/install-python-dependencies.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/install-python-dependencies.py b/scripts/install-python-dependencies.py index 215af013..d6d9b7c0 100644 --- a/scripts/install-python-dependencies.py +++ b/scripts/install-python-dependencies.py @@ -25,7 +25,7 @@ print(f"Setting up virtual environment '{venv_name}' for {solver_dir}...") try: venv_path = os.path.join('venv', venv_name) - subprocess.run(['python3', '-m', 'venv', venv_path], check=True) + subprocess.run(['python', '-m', 'venv', venv_path], check=True) if platform.system() == 'Windows': pip_executable = os.path.join(venv_path, 'Scripts', 'pip.exe') else: From 9353eec9955727300079131e2bf3c0a4328bfcf4 Mon Sep 17 00:00:00 2001 From: Elscrux Date: Tue, 16 Jun 2026 00:18:40 +0200 Subject: [PATCH 100/137] feat: update gams qubo solver to new version --- solvers/gams/qubo/solver_lp_qubo.py | 70 +++++++++++++++++++---------- 1 file changed, 47 insertions(+), 23 deletions(-) diff --git a/solvers/gams/qubo/solver_lp_qubo.py b/solvers/gams/qubo/solver_lp_qubo.py index 24b77d68..0bd8a382 100644 --- a/solvers/gams/qubo/solver_lp_qubo.py +++ b/solvers/gams/qubo/solver_lp_qubo.py @@ -25,34 +25,59 @@ def parse_lp_to_df(filepath="unsplittable_model.lp"): q_text = q_block_match.group(1).replace("\n", " ").replace("\r", "") q_text = re.sub(r"\s+", " ", q_text) - # 2. Extract coefficients using Regex - # Match squares (e.g., "- 18270 b1^2") - squares = re.findall(r"([+-]?\s*\d+(?:\.\d+)?)\s*b(\d+)\^2", q_text) - # Match products (e.g., "+ 6480 b1 * b2") - products = re.findall(r"([+-]?\s*\d+(?:\.\d+)?)\s*b(\d+)\s*\*\s*b(\d+)", q_text) + # 2. Extract coefficients using prefix-agnostic Regex + # Matches squares (e.g., "- 9600 x1 ^2") allowing spaces around the caret + squares = re.findall(r"([+-]?\s*\d+(?:\.\d+)?)\s*([a-zA-Z]\d+)\s*\^\s*2", q_text) + # Matches products (e.g., "+ 9600 x1 * x2") + products = re.findall( + r"([+-]?\s*\d+(?:\.\d+)?)\s*([a-zA-Z]\d+)\s*\*\s*([a-zA-Z]\d+)", q_text + ) q_records = [] - # Format diagonal terms (b_i * b_i) - for coef_str, idx in squares: + # Format diagonal terms (x_i * x_i) + for coef_str, var_name in squares: coef = float(coef_str.replace(" ", "")) - q_records.append({"i": f"b{idx}", "j": f"b{idx}", "value": coef}) + q_records.append({"i": var_name, "j": var_name, "value": coef}) - # Format off-diagonal terms (b_i * b_j) - for coef_str, idx1, idx2 in products: + # Format off-diagonal terms (x_i * x_j) + for coef_str, var1, var2 in products: coef = float(coef_str.replace(" ", "")) - q_records.append({"i": f"b{idx1}", "j": f"b{idx2}", "value": coef}) + q_records.append({"i": var1, "j": var2, "value": coef}) + + if not q_records: + raise ValueError( + "No quadratic terms matched. Please verify the LP file structure." + ) q_df = pd.DataFrame(q_records) - # 3. Find the constant on the right-hand side - # e1: -x143 + [...] = -590660 - const_match = re.search(r"=\s*([+-]?\d+(?:\.\d+)?)", content) - if const_match: - # Move RHS constant to the objective side - constant = -(float(const_match.group(1).replace(" ", ""))) - else: - constant = 0 + # 3. Handle the constant from fixed boundary variables in the objective function + constant = 0.0 + bounds_match = re.search(r"Bounds(.*?)(?:Binaries|End)", content, re.DOTALL) + if bounds_match: + bounds_text = bounds_match.group(1) + # Parse fixed boundaries like: x47 = 42150 + fixed_vars = re.findall( + r"([a-zA-Z]\d+)\s*=\s*([+-]?\d+(?:\.\d+)?)", bounds_text + ) + fixed_map = {var: float(val) for var, val in fixed_vars} + + # Locate the linear parts of the objective function before the quadratic bracket block + obj_match = re.search(r"Minimize\s+\w+:(.*?)\[", content, re.DOTALL) + if obj_match: + obj_text = obj_match.group(1).replace("\n", " ").replace("\r", "") + obj_text = re.sub(r"\s+", " ", obj_text) + linear_terms = re.findall( + r"([+-]?)\s*(\d+(?:\.\d+)?)?\s*([a-zA-Z]\d+)", obj_text + ) + + for sign, coef_str, var in linear_terms: + if var in fixed_map: + coef = float(coef_str) if coef_str else 1.0 + if sign == "-": + coef = -coef + constant += coef * fixed_map[var] return q_df, constant @@ -64,8 +89,8 @@ def parse_lp_to_df(filepath="unsplittable_model.lp"): # Dynamically find all unique variables present in the parsed LP matrix unique_vars = set(q_df["i"].unique()).union(set(q_df["j"].unique())) -# Sort them properly (b1, b2, ... b150) -b_idx = sorted(list(unique_vars), key=lambda x: int(x[1:])) +# Sort variables numerically regardless of character prefix (e.g., x1, x2, ... x46) +b_idx = sorted(list(unique_vars), key=lambda x: int("".join(filter(str.isdigit, x)))) i = gp.Set(m, name="i", records=b_idx) alias_j = gp.Alias(m, name="j", alias_with=i) @@ -74,8 +99,7 @@ def parse_lp_to_df(filepath="unsplittable_model.lp"): x = gp.Variable(m, name="x", domain=[i], type="binary") -# Note: CPLEX LP format implies the [...] block is implicitly multiplied by 0.5. -# We add `0.5 *` here to mirror exactly how the solver reads standard LP files. +# Standard CPLEX LP format implicitly scales the quadratic block inside [...] by 0.5. obj_expr = 0.5 * gp.Sum((i, alias_j), Q[i, alias_j] * x[i] * x[alias_j]) + constant z = gp.Variable(m, name="z", type="free") From 80c27a4264543327b38d4e498089ad98e7ef2c93 Mon Sep 17 00:00:00 2001 From: Elscrux Date: Tue, 16 Jun 2026 00:40:05 +0200 Subject: [PATCH 101/137] feat: add new gurobi-based lp parser --- solvers/_utility/parse_qubo_from_lp.py | 34 ++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 solvers/_utility/parse_qubo_from_lp.py diff --git a/solvers/_utility/parse_qubo_from_lp.py b/solvers/_utility/parse_qubo_from_lp.py new file mode 100644 index 00000000..dbd5258c --- /dev/null +++ b/solvers/_utility/parse_qubo_from_lp.py @@ -0,0 +1,34 @@ +def parse_qubo_from_lp(model) -> tuple[list[str], dict[tuple[int, int], float], dict[int, float], float]: + vars = [x.VarName for x in model.getVars() if x.VType == "B"] + + obj = model.getObjective() + + quadratic_terms = { + (vars.index(term[1].VarName), vars.index(term[2].VarName)): term[0] + for term in obj.quadTerms() + } + + linear_terms = { + vars.index(var.VarName): coeff + for coeff, var in obj.linTerms() + if var.VType == "B" + } + + # Detect constant terms from non-binary variables + # A variable is considered a constant if: + # 1. Its LB equals its UB (fixed variable), OR + # 2. Its name can be parsed as an integer + constant_offset = obj.getLinExpr().getConstant() + + # Extract constant contributions + constant_contributions = [ + coeff * lb if lb == ub else ( + coeff * int(var.VarName) if var.VarName.isdigit() else 0 + ) + for coeff, var in obj.getLinExpr().linTerms() + for lb, ub in [(var.getAttr('LB'), var.getAttr('UB'))] + ] + + constant_offset += sum(constant_contributions) + + return vars, quadratic_terms, linear_terms, constant_offset From 2f85eed35cccde72ff11972894829a53b81386a4 Mon Sep 17 00:00:00 2001 From: Elscrux Date: Tue, 16 Jun 2026 00:40:23 +0200 Subject: [PATCH 102/137] refactor: use gurobi lp parser for gams qubo --- solvers/gams/qubo/requirements.txt | 3 +- solvers/gams/qubo/solver_lp_qubo.py | 80 ++++++++--------------------- 2 files changed, 22 insertions(+), 61 deletions(-) diff --git a/solvers/gams/qubo/requirements.txt b/solvers/gams/qubo/requirements.txt index 1c690d5d..9b8fbaef 100644 --- a/solvers/gams/qubo/requirements.txt +++ b/solvers/gams/qubo/requirements.txt @@ -1,2 +1,3 @@ # This file describes the python package requirements for the GAMS Qubo solver -gamspy == 1.23.1 \ No newline at end of file +gamspy == 1.23.1 +gurobipy == 13.0.2 \ No newline at end of file diff --git a/solvers/gams/qubo/solver_lp_qubo.py b/solvers/gams/qubo/solver_lp_qubo.py index 0bd8a382..7c8650b6 100644 --- a/solvers/gams/qubo/solver_lp_qubo.py +++ b/solvers/gams/qubo/solver_lp_qubo.py @@ -1,9 +1,19 @@ import gamspy as gp +import gurobipy import pandas as pd import json +import os import re import sys +# Import parse_qubo_from_lp from sibling _utility directory +# Add the parent (solvers) directory to enable the import +script_dir = os.path.dirname(os.path.abspath(__file__)) +solvers_dir = os.path.join(script_dir, '..', '..') +sys.path.insert(0, solvers_dir) + +from _utility.parse_qubo_from_lp import parse_qubo_from_lp + arg_count = len(sys.argv) - 1 if arg_count != 2: raise TypeError(f'This script expects exactly 2 arguments but got {arg_count}. Input file (argument 1) and output file (argument 2).') @@ -13,73 +23,22 @@ def parse_lp_to_df(filepath="unsplittable_model.lp"): """Parses the LP file to extract the Q matrix coefficients and constant.""" - with open(filepath, "r") as f: - content = f.read() - - # 1. Extract the quadratic block inside [...] - q_block_match = re.search(r"\[(.*?)\]", content, re.DOTALL) - if not q_block_match: - raise ValueError("Could not find the [...] block in the LP file.") - - # Clean up line breaks and excess spaces - q_text = q_block_match.group(1).replace("\n", " ").replace("\r", "") - q_text = re.sub(r"\s+", " ", q_text) - - # 2. Extract coefficients using prefix-agnostic Regex - # Matches squares (e.g., "- 9600 x1 ^2") allowing spaces around the caret - squares = re.findall(r"([+-]?\s*\d+(?:\.\d+)?)\s*([a-zA-Z]\d+)\s*\^\s*2", q_text) - # Matches products (e.g., "+ 9600 x1 * x2") - products = re.findall( - r"([+-]?\s*\d+(?:\.\d+)?)\s*([a-zA-Z]\d+)\s*\*\s*([a-zA-Z]\d+)", q_text - ) q_records = [] - # Format diagonal terms (x_i * x_i) - for coef_str, var_name in squares: - coef = float(coef_str.replace(" ", "")) - q_records.append({"i": var_name, "j": var_name, "value": coef}) - - # Format off-diagonal terms (x_i * x_j) - for coef_str, var1, var2 in products: - coef = float(coef_str.replace(" ", "")) - q_records.append({"i": var1, "j": var2, "value": coef}) + model = gurobipy.read(filepath) + (vars, quadratic_terms, linear_terms, constant_offset) = parse_qubo_from_lp(model) - if not q_records: - raise ValueError( - "No quadratic terms matched. Please verify the LP file structure." - ) + print(quadratic_terms) + for ((var1, var2), coeff) in quadratic_terms.items(): + # restore the original name of the variables + q_records.append({"i": vars[var1], "j": vars[var2], "value": coeff}) q_df = pd.DataFrame(q_records) + print("q_records", q_records) + print("q_df", q_df) - # 3. Handle the constant from fixed boundary variables in the objective function - constant = 0.0 - bounds_match = re.search(r"Bounds(.*?)(?:Binaries|End)", content, re.DOTALL) - if bounds_match: - bounds_text = bounds_match.group(1) - # Parse fixed boundaries like: x47 = 42150 - fixed_vars = re.findall( - r"([a-zA-Z]\d+)\s*=\s*([+-]?\d+(?:\.\d+)?)", bounds_text - ) - fixed_map = {var: float(val) for var, val in fixed_vars} - - # Locate the linear parts of the objective function before the quadratic bracket block - obj_match = re.search(r"Minimize\s+\w+:(.*?)\[", content, re.DOTALL) - if obj_match: - obj_text = obj_match.group(1).replace("\n", " ").replace("\r", "") - obj_text = re.sub(r"\s+", " ", obj_text) - linear_terms = re.findall( - r"([+-]?)\s*(\d+(?:\.\d+)?)?\s*([a-zA-Z]\d+)", obj_text - ) - - for sign, coef_str, var in linear_terms: - if var in fixed_map: - coef = float(coef_str) if coef_str else 1.0 - if sign == "-": - coef = -coef - constant += coef * fixed_map[var] - - return q_df, constant + return q_df, constant_offset q_df, constant = parse_lp_to_df(input_path) @@ -88,6 +47,7 @@ def parse_lp_to_df(filepath="unsplittable_model.lp"): # Dynamically find all unique variables present in the parsed LP matrix unique_vars = set(q_df["i"].unique()).union(set(q_df["j"].unique())) +print("uniqvar", unique_vars) # Sort variables numerically regardless of character prefix (e.g., x1, x2, ... x46) b_idx = sorted(list(unique_vars), key=lambda x: int("".join(filter(str.isdigit, x)))) From f4ff57b98f9d42c3d0bf8a285c70637e733c12b9 Mon Sep 17 00:00:00 2001 From: Elscrux Date: Tue, 16 Jun 2026 00:40:31 +0200 Subject: [PATCH 103/137] refactor: use gurobi lp parser for dwave qubo --- solvers/dwave/qubo/main.py | 35 ++++++++++++++--------------- solvers/dwave/qubo/requirements.txt | 3 ++- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/solvers/dwave/qubo/main.py b/solvers/dwave/qubo/main.py index 050fb3ec..edc32d13 100644 --- a/solvers/dwave/qubo/main.py +++ b/solvers/dwave/qubo/main.py @@ -1,14 +1,22 @@ import argparse import os +import sys from datetime import datetime from typing import Literal +import gurobipy as gp -from dimod import BINARY, BinaryQuadraticModel, binary, constrained, lp +# Import parse_qubo_from_lp from sibling _utility directory +# Add the parent (solvers) directory to enable the import +script_dir = os.path.dirname(os.path.abspath(__file__)) +solvers_dir = os.path.join(script_dir, '..', '..') +sys.path.insert(0, solvers_dir) + +from _utility.parse_qubo_from_lp import parse_qubo_from_lp + +from dimod import BINARY, BinaryQuadraticModel from dimod.serialization import coo -from dwave.cloud import Client from solver import solve_with - def main(): parser = argparse.ArgumentParser( prog="DWave QUBO solver", @@ -32,21 +40,12 @@ def main(): bqm = None if bqm is None: - with open(args.file) as problem: - cqm = lp.load(problem) - converted, _ = constrained.cqm_to_bqm(cqm) - - linear_conv = { - (int(str(x)[1:])): converted.linear[x] for x in converted.linear - } - quad_conv = { - (int(str(x)[1:]), int(str(y)[1:])): converted.quadratic[(x, y)] - for x, y in converted.quadratic - } - - bqm = BinaryQuadraticModel(linear_conv, quad_conv, converted.offset, BINARY) - if len(bqm.quadratic) == 0: - bqm = None + model = gp.read(args.file) + (vars, quadratic_terms, linear_terms, constant_offset) = parse_qubo_from_lp(model) + + bqm = BinaryQuadraticModel(linear_terms, quadratic_terms, constant_offset, BINARY) + if len(bqm.quadratic) == 0: + bqm = None filename = os.path.basename(args.file) diff --git a/solvers/dwave/qubo/requirements.txt b/solvers/dwave/qubo/requirements.txt index 694e1464..dc8328b4 100644 --- a/solvers/dwave/qubo/requirements.txt +++ b/solvers/dwave/qubo/requirements.txt @@ -1,2 +1,3 @@ # This file describes the python package requirements for the Dwave Qubo solver -dwave-ocean-sdk == 6.4.1 +dwave-ocean-sdk == 9.3.0 +gurobipy == 13.0.2 \ No newline at end of file From 4576a6fa5932286f63b2b788c5f426033524c126 Mon Sep 17 00:00:00 2001 From: Elscrux Date: Tue, 16 Jun 2026 00:41:41 +0200 Subject: [PATCH 104/137] feat(install-python-dependencies): add option to filter for the folders to create venvs for Makes it a lot easier to just update one venv in development --- scripts/install-python-dependencies.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/scripts/install-python-dependencies.py b/scripts/install-python-dependencies.py index d6d9b7c0..1f8ad00e 100644 --- a/scripts/install-python-dependencies.py +++ b/scripts/install-python-dependencies.py @@ -1,9 +1,14 @@ import os import subprocess import platform +import sys script_dir = os.path.dirname(os.path.realpath(__file__)) root_dir = os.path.dirname(script_dir) + +# Check if a specific folder argument was provided +folder_filter = sys.argv[1] if len(sys.argv) > 1 else None + base_dirs = [ os.path.join(root_dir, 'demonstrators'), os.path.join(root_dir, 'solvers'), @@ -19,6 +24,10 @@ # Iterate over problem directory (knapsack, tsp, etc.) for solver_name in os.listdir(framework_dir): solver_dir = os.path.join(framework_dir, solver_name) + # If folder filter is specified, only process matching folders + if folder_filter and folder_filter not in solver_dir: + continue + req_file = os.path.join(solver_dir, 'requirements.txt') if os.path.exists(req_file): venv_name = f"{os.path.basename(root)}_{framework_name}_{solver_name}" From beef57c97ea31fbb05643610045e6f7b799ac000 Mon Sep 17 00:00:00 2001 From: Elscrux Date: Tue, 16 Jun 2026 01:10:59 +0200 Subject: [PATCH 105/137] refactor: use gurobi lp parser for qiskit qubo + support for constants --- .../qiskit/qubo/qp_converter/QpConverter.py | 125 ------------------ solvers/qiskit/qubo/qubo_qiskit.py | 31 ++++- solvers/qiskit/qubo/requirements.txt | 1 + 3 files changed, 30 insertions(+), 127 deletions(-) delete mode 100644 solvers/qiskit/qubo/qp_converter/QpConverter.py diff --git a/solvers/qiskit/qubo/qp_converter/QpConverter.py b/solvers/qiskit/qubo/qp_converter/QpConverter.py deleted file mode 100644 index fa1c2f19..00000000 --- a/solvers/qiskit/qubo/qp_converter/QpConverter.py +++ /dev/null @@ -1,125 +0,0 @@ -import re - -from qiskit_optimization import QuadraticProgram - -def parse_lp_file(file_path): - # Read the LP file - with open(file_path, 'r') as file: - lines = file.readlines() - - # Initialize components for parsing - objective_sense = 'minimize' # default - objective = '' - constraints = [] - variable_types = {} - in_objective_section = False - in_constraints_section = False - in_binary_section = False - - # Parsing logic - for line in lines: - line = line.strip() - - # Objective function - if line.lower().startswith('minimize') or line.lower().startswith('maximize'): - in_objective_section = True - objective_sense = line.split()[0].lower() - continue - - if in_objective_section and (line.lower().startswith('subject to') or line.lower().startswith('binary')): - in_objective_section = False - continue - - if in_objective_section: - objective += line - continue - - # Constraints (no constraints in this example, but you may extend it later) - if line.lower().startswith('subject to'): - in_constraints_section = True - continue - elif in_constraints_section and line.lower().startswith('binary'): - in_constraints_section = False - - # Binary variables - if line.lower().startswith('binary'): - in_binary_section = True - continue - - if in_binary_section: - variables = line.split() - for var in variables: - variable_types[var] = 'binary' - - return objective_sense, objective, constraints, variable_types - - -def parse_objective(objective_str): - """ Parse the objective function to extract linear and quadratic coefficients """ - # Remove the 'obj:' prefix - objective_str = re.sub(r'^obj:\s*', '', objective_str) - - # Extract and remove the division factor (e.g., "/ 2", "/ 3", "/ 10") - division_factor_match = re.search(r'/\s*([\d\.]+)', objective_str) - division_factor = float(division_factor_match.group(1)) if division_factor_match else 1.0 - objective_str = re.sub(r'/\s*[\d\.]+', '', objective_str) - - # Remove square brackets - objective_str = objective_str.replace('[', '').replace(']', '') - - # Extract terms using regular expression - term_pattern = re.compile(r'([+-]?\s*\d*\.?\d+(?:e[+-]?\d+)?)\s*\*?\s*([\w\d]+)\s*(?:\*\s*([\w\d]+))?') - terms = term_pattern.findall(objective_str) - linear = {} - quadratic = {} - - # Parse each term - for coeff_str, var1, var2 in terms: - coeff = float(coeff_str.replace(' ', '')) # Remove any spaces in coefficient - - if var2: # Quadratic term - if (var1, var2) in quadratic: - quadratic[(var1, var2)] += coeff - else: - quadratic[(var1, var2)] = coeff - else: # Linear term - if var1 in linear: - linear[var1] += coeff - else: - linear[var1] = coeff - - # Apply division factor - if division_factor != 1.0 and division_factor != 0: - for key in linear: - linear[key] /= division_factor - for key in quadratic: - quadratic[key] /= division_factor - - return linear, quadratic - - -def convert_to_quadratic_program(lp_file_path): - # Parse the LP file - objective_sense, objective, constraints, variable_types = parse_lp_file(lp_file_path) - - # Parse objective components - linear, quadratic = parse_objective(objective) - - # Create an instance of QuadraticProgram - qp = QuadraticProgram() - - # Add variables - for var in variable_types: - if variable_types[var] == 'binary': - qp.binary_var(var) - - # Set the objective sense - if objective_sense == 'minimize': - qp.minimize(linear=linear, quadratic=quadratic) - else: - qp.maximize(linear=linear, quadratic=quadratic) - - # Add constraints (empty in this example) - # You can extend this logic to handle any constraints - - return qp \ No newline at end of file diff --git a/solvers/qiskit/qubo/qubo_qiskit.py b/solvers/qiskit/qubo/qubo_qiskit.py index 181a0a9c..44faf0d6 100644 --- a/solvers/qiskit/qubo/qubo_qiskit.py +++ b/solvers/qiskit/qubo/qubo_qiskit.py @@ -1,5 +1,15 @@ +import os import sys -from qp_converter import QpConverter +import gurobipy as gp +from qiskit_optimization import QuadraticProgram + +# Import parse_qubo_from_lp from sibling _utility directory +# Add the parent (solvers) directory to enable the import +script_dir = os.path.dirname(os.path.abspath(__file__)) +solvers_dir = os.path.join(script_dir, '..', '..') +sys.path.insert(0, solvers_dir) + +from _utility.parse_qubo_from_lp import parse_qubo_from_lp from qiskit.primitives import StatevectorSampler from qiskit_optimization.minimum_eigensolvers import QAOA @@ -12,7 +22,24 @@ input_path = sys.argv[1] output_path = sys.argv[2] -qubo = QpConverter.convert_to_quadratic_program(input_path) +model = gp.read(input_path) +(vars, quadratic_terms, linear_terms, constant_offset) = parse_qubo_from_lp(model) + +qubo = QuadraticProgram() + +# Add variables +for var in vars: + qubo.binary_var(var) + +# Set the objective sense +if model.ModelSense == 1: + qubo.minimize(constant=constant_offset, linear=linear_terms, quadratic=quadratic_terms) +else: + qubo.maximize(constant=constant_offset, linear=linear_terms, quadratic=quadratic_terms) + +# Add constraints (empty in this example) +# You can extend this logic to handle any constraints + # parse LP file: print(qubo.prettyprint()) diff --git a/solvers/qiskit/qubo/requirements.txt b/solvers/qiskit/qubo/requirements.txt index 4bb8653e..c96f4cbd 100644 --- a/solvers/qiskit/qubo/requirements.txt +++ b/solvers/qiskit/qubo/requirements.txt @@ -1,3 +1,4 @@ # This file describes the python package requirements for the Qiskit QUBO solver +gurobipy == 13.0.2 qiskit == 2.2.3 qiskit-optimization == 0.7.0 From 2822bd9691628f363a5537d4a88ec55a27fb278a Mon Sep 17 00:00:00 2001 From: Elscrux Date: Wed, 17 Jun 2026 12:35:58 +0200 Subject: [PATCH 106/137] feat(process-runner): add option to add more than one input file --- .../toolbox/process/ProcessRunner.java | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/main/java/edu/kit/provideq/toolbox/process/ProcessRunner.java b/src/main/java/edu/kit/provideq/toolbox/process/ProcessRunner.java index c31bb7ed..1c792378 100644 --- a/src/main/java/edu/kit/provideq/toolbox/process/ProcessRunner.java +++ b/src/main/java/edu/kit/provideq/toolbox/process/ProcessRunner.java @@ -26,8 +26,21 @@ @Component @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public class ProcessRunner { + /** + * Placeholder for the problem directory path in the process arguments. + */ public static final String PROBLEM_DIRECTORY_PATH = "PROBLEM_DIRECTORY_PATH"; + /** + * Placeholder for the input file path in the process arguments. + */ public static final String INPUT_FILE_PATH = "INPUT_FILE_PATH"; + /** + * Placeholder for another input file path in the process arguments. + */ + public static final String INPUT_FILE_PATH2 = "INPUT_FILE_PATH2"; + /** + * Placeholder for the output file path in the process arguments. + */ public static final String OUTPUT_FILE_PATH = "OUTPUT_FILE_PATH"; /** @@ -98,6 +111,21 @@ public ProcessRunner writeInputFile(String inputData) { * @return ProcessRunner instance for chaining. */ public ProcessRunner writeInputFile(String inputData, String inputFileName) { + return writeInputFile(inputData, inputFileName, INPUT_FILE_PATH); + } + + /** + * Writes the input data to a specific input file. + * + * @param inputData The input data to be written to the input file. + * @param inputFileName The name of the file to write the input data to. + * @param inputFilePathPlaceholder The placeholder to be replaced with the input + * file path in the arguments. + * @return ProcessRunner instance for chaining. + */ + public ProcessRunner writeInputFile(String inputData, + String inputFileName, + String inputFilePathPlaceholder) { // Add at the beginning of the pre-processors list // This ensures that the argument transformers are applied for every argument preProcessors.add(0, (problemType, solutionId) -> { @@ -116,7 +144,7 @@ public ProcessRunner writeInputFile(String inputData, String inputFileName) { // Add support for replacing the input file path in the arguments argumentTransformers.add( - argument -> argument.replace(INPUT_FILE_PATH, normalizedInputFilePath)); + argument -> argument.replace(inputFilePathPlaceholder, normalizedInputFilePath)); return Optional.empty(); }); From deb9a8acc69377794140d2d8e1e89bc6a3c9533a Mon Sep 17 00:00:00 2001 From: Elscrux Date: Wed, 17 Jun 2026 12:36:40 +0200 Subject: [PATCH 107/137] style: allow not to use braces for single line if statements (often used for quick inline returns) --- config/checkstyle/checkstyle.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml index 3a27f5aa..dde68653 100644 --- a/config/checkstyle/checkstyle.xml +++ b/config/checkstyle/checkstyle.xml @@ -74,7 +74,7 @@ + value="LITERAL_DO, LITERAL_ELSE, LITERAL_FOR, LITERAL_WHILE"/> Date: Wed, 17 Jun 2026 12:54:41 +0200 Subject: [PATCH 108/137] feat: add unsplittable mcf with two solvers, one classical, one via qubo --- .gitignore | 5 +- .../unsplittable-mcf/gamspy_qubo/__init__.py | 3 + .../unsplittable-mcf/gamspy_qubo/_utils.py | 248 +++++ .../gamspy_qubo/backend/__init__.py | 6 + .../gamspy_qubo/backend/base.py | 58 ++ .../gamspy_qubo/backend/dwave.py | 51 + .../gamspy_qubo/backend/kipu.py | 94 ++ .../gamspy_qubo/backend/load_json.py | 36 + .../gams/unsplittable-mcf/gamspy_qubo/qubo.py | 890 ++++++++++++++++++ .../unsplittable-mcf/plot_qubo_solution.py | 24 + .../unsplittable-mcf/plot_unsplittable.py | 376 ++++++++ .../gams/unsplittable-mcf/requirements.txt | 3 + .../gams/unsplittable-mcf/solver_classical.py | 29 + .../gams/unsplittable-mcf/solver_lp_export.py | 30 + .../gams/unsplittable-mcf/unsplittable_mcf.py | 700 ++++++++++++++ .../UnsplittableMcfConfiguration.java | 105 +++ .../GamsUnsplittableMcfClassicalSolver.java | 68 ++ .../GamsUnsplittableMcfQuboSolver.java | 122 +++ .../solvers/GamsUnsplittableMcfSolver.java | 15 + src/main/resources/application.properties | 124 ++- .../unsplittable_instance.json | 221 +++++ .../api/UnsplittableMcfSolversTest.java | 73 ++ 22 files changed, 3279 insertions(+), 2 deletions(-) create mode 100644 solvers/gams/unsplittable-mcf/gamspy_qubo/__init__.py create mode 100644 solvers/gams/unsplittable-mcf/gamspy_qubo/_utils.py create mode 100644 solvers/gams/unsplittable-mcf/gamspy_qubo/backend/__init__.py create mode 100644 solvers/gams/unsplittable-mcf/gamspy_qubo/backend/base.py create mode 100644 solvers/gams/unsplittable-mcf/gamspy_qubo/backend/dwave.py create mode 100644 solvers/gams/unsplittable-mcf/gamspy_qubo/backend/kipu.py create mode 100644 solvers/gams/unsplittable-mcf/gamspy_qubo/backend/load_json.py create mode 100644 solvers/gams/unsplittable-mcf/gamspy_qubo/qubo.py create mode 100644 solvers/gams/unsplittable-mcf/plot_qubo_solution.py create mode 100644 solvers/gams/unsplittable-mcf/plot_unsplittable.py create mode 100644 solvers/gams/unsplittable-mcf/requirements.txt create mode 100644 solvers/gams/unsplittable-mcf/solver_classical.py create mode 100644 solvers/gams/unsplittable-mcf/solver_lp_export.py create mode 100644 solvers/gams/unsplittable-mcf/unsplittable_mcf.py create mode 100644 src/main/java/edu/kit/provideq/toolbox/unsplittablemcf/UnsplittableMcfConfiguration.java create mode 100644 src/main/java/edu/kit/provideq/toolbox/unsplittablemcf/solvers/GamsUnsplittableMcfClassicalSolver.java create mode 100644 src/main/java/edu/kit/provideq/toolbox/unsplittablemcf/solvers/GamsUnsplittableMcfQuboSolver.java create mode 100644 src/main/java/edu/kit/provideq/toolbox/unsplittablemcf/solvers/GamsUnsplittableMcfSolver.java create mode 100644 src/main/resources/edu/kit/provideq/toolbox/unsplittablemcf/unsplittable_instance.json create mode 100644 src/test/java/edu/kit/provideq/toolbox/api/UnsplittableMcfSolversTest.java diff --git a/.gitignore b/.gitignore index 5b23f68f..f20019d1 100644 --- a/.gitignore +++ b/.gitignore @@ -52,7 +52,10 @@ __pycache__/ # Software license key for GAMS, used for a docker container with GAMS included gamslice.txt -# Listing files compiled from our GAMS scripts +# Don't include GAMS Data eXchange (GDX) +*.gdx + +# Listing files compiled from our GAMS scripts files, which are generated when running GAMS solvers *.lst *.op2 solvers/gams/**/225a diff --git a/solvers/gams/unsplittable-mcf/gamspy_qubo/__init__.py b/solvers/gams/unsplittable-mcf/gamspy_qubo/__init__.py new file mode 100644 index 00000000..fdf87e23 --- /dev/null +++ b/solvers/gams/unsplittable-mcf/gamspy_qubo/__init__.py @@ -0,0 +1,3 @@ +from gamspy_qubo.qubo import Qubo + +__all__ = ["Qubo"] diff --git a/solvers/gams/unsplittable-mcf/gamspy_qubo/_utils.py b/solvers/gams/unsplittable-mcf/gamspy_qubo/_utils.py new file mode 100644 index 00000000..a32bed38 --- /dev/null +++ b/solvers/gams/unsplittable-mcf/gamspy_qubo/_utils.py @@ -0,0 +1,248 @@ +from __future__ import annotations + +import re + +import numpy as np +import pandas as pd +from gamspy import SolveStatus +from gamspy.exceptions import GamspyException, ValidationError + + +def validate_value(value: int, allowed_values: list[int], param_name: str) -> int: + if value not in allowed_values: + raise ValueError(f"{param_name} must be one of {allowed_values}") + return value + + +def check_row_entries(df: pd.DataFrame) -> pd.DataFrame: + """ + Helper function to filter DataFrame having either 0 or 1 entries in each row. + Args: + df: A Pandas DataFrame. + + Returns: + Filtered DataFrame with rows having either 0 or 1 + """ + row_contains_only_0s_or_1s = df.isin([0, 1]).all(axis=1) + return df[row_contains_only_0s_or_1s] + + +def fetch_quadratic_coeff(raw_df: pd.DataFrame, bin_vars: list) -> np.ndarray: + """ + Helper function to convert the original Q matrix of the problem to a symmetric matrix + + Args: + raw_df: Original problem Q data in a pd.DataFrame + + Returns: + Numpy Q matrix + """ + raw_df["value"] /= 2 + mask = raw_df["j_1"].astype(str) == raw_df["j_2"].astype(str) + filtered_quad = raw_df.loc[mask, :].copy() + raw_df = raw_df.loc[~mask].copy() + diag_quad = filtered_quad.reset_index(drop=True) + quad = raw_df.copy(deep=True) + quad["j_1"], quad["j_2"] = raw_df["j_2"], raw_df["j_1"] + quad = pd.concat([raw_df, quad, diag_quad], axis=0) + quad = quad.pivot(index="j_1", columns="j_2", values="value").fillna(0) + quad = quad.reindex(labels=bin_vars, axis="index") + quad = quad.reindex(labels=bin_vars, axis="columns") + return quad.to_numpy() + + +def var_contribution( + A: pd.DataFrame, vars: dict, cons: list | None = None +) -> np.ndarray: + """ + Helper function to calculate the contribution of given variables + in a constraint or set of constraints + + Args: + A: df of coefficients + vars: contributing variables + cons: participating constraints + + Returns: + np.ndarray of Total contribution of all variables for that constraint + """ + cons = slice(None) if cons is None else cons # type: ignore + coeffs_of_vars_in_constraint = A.loc[cons, vars.keys()].to_numpy() # type: ignore + lb_var_levels = np.array(list(vars.values())).reshape((len(vars), 1)) + if coeffs_of_vars_in_constraint.size > 0: + return coeffs_of_vars_in_constraint @ lb_var_levels + + return np.array([0]) + + +def modify_matrix( + b_vec: np.ndarray, + rhs: float, + slacks: np.ndarray, + A_coeff: pd.DataFrame, + ele: pd.Series, + nslacks: int, +) -> tuple[np.ndarray, pd.DataFrame, int]: + """ + Helper function to update the original "A" matrix of coeffs + + Args: + b_vec: The n*1 vector + rhs : The Right hand side of a constraint + slacks: result of gen_slacks() + A_coeff: "A" matrix + ele: constraint + nslacks: number of slacks + + Returns: + updated b_vec, A_coeff and number of slacks + """ + con_index = ele.i + slack_names = [ + f"slack_{con_index}_{i}" for i in range(nslacks + 1, nslacks + len(slacks) + 1) + ] + new_cols = pd.DataFrame( + 0, index=A_coeff.index, columns=slack_names, dtype=slacks.dtype + ) + new_cols.loc[con_index, slack_names] = slacks + A_coeff = pd.concat([A_coeff, new_cols], axis=1) + nslacks += len(slacks) + + return np.append(b_vec, [rhs]), A_coeff, nslacks + + +def gen_slacks(var_range: float) -> np.ndarray: + """ + Helper function to generate slacks depending on the range of variables or rhs. + + Note: `var_range` cannot be more than 1e4. This hard limit prevents the creation of + excessive auxiliary binary variables and maintains model performance + during the binarization of slacks or integer variables. + + Args: + var_range: upper bound of variable + + Returns: + Numpy array containing slack coefficients + + example: + if var_range=5, then gen_slacks(5) returns [1, 2, 2] + """ + if var_range >= 1e4: + raise ValidationError( + "The Upper bound is greater than or equal to 1e+4, Quitting!" + ) + + power = int(np.log2(var_range)) if var_range > 0 else 0 + bounded_coef = var_range - (2**power - 1) + D_val = [2**i for i in range(power)] + [bounded_coef] + return np.array(D_val) + + +def get_lhs_bounds(ele: pd.DataFrame) -> tuple[float, float]: + """ + Helper function to find the bounds of a constraint + + Args: + ele: The coefficients of the constraint + + Returns: + lower_bound, upper_bound + """ + return float(ele[ele < 0].sum()), float(ele[ele > 0].sum()) + + +def check_convexity(Q: np.ndarray) -> str: + eigenvalues = np.linalg.eigvals(Q) + + if np.all(eigenvalues > 0): + return "Function is strictly convex." + elif np.all(eigenvalues >= 0): + return "Function is convex." + else: + return "Function is not convex." + + +def qubo_to_ising(Q: dict, offset: float = 0.0) -> tuple[dict, dict, float]: + h = {} # type: ignore + J = {} + linear_offset = 0.0 + quadratic_offset = 0.0 + + for (u, v), bias in Q.items(): + if u == v: + if u in h: + h[u] += 0.5 * bias + else: + h[u] = 0.5 * bias + linear_offset += bias + else: + if bias != 0.0: + J[(u, v)] = 0.25 * bias + + if u in h: + h[u] += 0.25 * bias + else: + h[u] = 0.25 * bias + + if v in h: + h[v] += 0.25 * bias + else: + h[v] = 0.25 * bias + + quadratic_offset += bias + + offset += 0.5 * linear_offset + 0.25 * quadratic_offset + + return h, J, offset + + +def qubo_to_maxcut(Q: np.ndarray) -> np.ndarray: + return -1 * np.sum(Q, axis=1) + + +def triu(Q_matrix: np.ndarray): + upper_mask = np.triu(np.ones_like(Q_matrix), k=1) # 1 above diagonal, 0 elsewhere + scaled_upper = Q_matrix * (1 + upper_mask) # Double above diagonal + return np.triu(scaled_upper) + + +def check_classical_solve(solveStatus: SolveStatus | None): + if solveStatus is None: + raise GamspyException("Solver status is None. Solve the model first.") + + if solveStatus in [ + SolveStatus.IterationInterrupt, + SolveStatus.ResourceInterrupt, + SolveStatus.UserInterrupt, + ]: + # Continue mapping incumbent solution if solve_status is one of + # [UserInterrupt, ResourceInterrupt, IterationInterrupt]. + pass + + elif solveStatus != SolveStatus.NormalCompletion: + raise GamspyException( + f"Solver did not yield NormalCompletion. Solver status = {solveStatus}" + ) + + +def parse_examiner_output(text): + if "Primal constraints satisfied" in text: + return {"feasible": True, "message": "Solution is feasible."} + + match = re.search( + r"Primal infeasible.*?Max violation:\s+(.*?):\s+(.*)", text, re.DOTALL + ) + + if match: + constraint_name = match.group(1).strip() + violation_details = match.group(2).split("\n")[0].strip() + return { + "feasible": False, + "message": f"Infeasible: {constraint_name} violates bounds ({violation_details})", + } + + return { + "feasible": False, + "message": "Examiner failed or returned unexpected format.", + } diff --git a/solvers/gams/unsplittable-mcf/gamspy_qubo/backend/__init__.py b/solvers/gams/unsplittable-mcf/gamspy_qubo/backend/__init__.py new file mode 100644 index 00000000..e1e21d7e --- /dev/null +++ b/solvers/gams/unsplittable-mcf/gamspy_qubo/backend/__init__.py @@ -0,0 +1,6 @@ +from gamspy_qubo.backend.base import baseBackend +from gamspy_qubo.backend.dwave import DwaveBackend +from gamspy_qubo.backend.kipu import KipuBackend +from gamspy_qubo.backend.load_json import JsonBackend + +__all__ = ["baseBackend", "DwaveBackend", "KipuBackend", "JsonBackend"] diff --git a/solvers/gams/unsplittable-mcf/gamspy_qubo/backend/base.py b/solvers/gams/unsplittable-mcf/gamspy_qubo/backend/base.py new file mode 100644 index 00000000..ac6894ae --- /dev/null +++ b/solvers/gams/unsplittable-mcf/gamspy_qubo/backend/base.py @@ -0,0 +1,58 @@ +from __future__ import annotations + +import importlib +from abc import ABC, abstractmethod +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + import numpy as np + import pandas as pd + from gamspy import Sense + + +class baseBackend(ABC): + """Abstract base class that provides a structure to different quantum backends.""" + + def __init__( + self, + q_matrix: np.ndarray, + q_variables: pd.Series, + q_constant: float | int, + sense: Sense, + ): + """ + :param q_matrix: QUBO Matrix + :type q_matrix: np.ndarray + :param q_variables: List of variables that are associated with the QUBO + :type q_variables: list[str] + :param q_constant: the offset penalty term + :type q_constant: float | int + :param sense: direction of optimization, i.e, Min. or Max. + :type sense: Sense + """ + self.q_matrix = q_matrix + self.q_variables = q_variables + self.q_constant = q_constant + self.sense = sense + + @abstractmethod + def solve(self, *args, **kwargs) -> pd.DataFrame: + """Solve the problem using quantum backend + + Returns: + A pandas.DataFrame that must have the following structure, + `columns = ["i", "level"]`, where `i` contains variables and + `level` is the corresponding value of the variable after solving. + """ + raise NotImplementedError + + @staticmethod + def check_dependencies(solver_name: str, packages: dict): + for pkg_name, import_path in packages.items(): + try: + importlib.import_module(import_path) + except ImportError: + raise ImportError( + f"The {solver_name} solver requires the '{pkg_name}' package. " + f"Install it with: pip install {pkg_name}" + ) from None diff --git a/solvers/gams/unsplittable-mcf/gamspy_qubo/backend/dwave.py b/solvers/gams/unsplittable-mcf/gamspy_qubo/backend/dwave.py new file mode 100644 index 00000000..ff87665c --- /dev/null +++ b/solvers/gams/unsplittable-mcf/gamspy_qubo/backend/dwave.py @@ -0,0 +1,51 @@ +from __future__ import annotations + +import importlib + +import pandas as pd + +from gamspy_qubo._utils import triu +from gamspy_qubo.backend import baseBackend + + +class DwaveBackend(baseBackend): + """ + DWave Backend based on `dimod.BinaryQuadraticModel` and `SimulatedAnnealingSampler`. + """ + + def __init__(self, q_matrix, q_variables, q_constant, sense): + super().__init__(q_matrix, q_variables, q_constant, sense) + self.check_dependencies( + "DWAVE", {"dwave-ocean-sdk": "dwave.samplers", "dimod": "dimod"} + ) + + def solve(self, *args, **kwargs) -> pd.DataFrame: + """ + Solve the model using Dwave `SimulatedAnnealingSampler` and map the solution back + to original problem. + + Users can provide keyword arguments as expected by `SimulatedAnnealingSample.sample()` + + Note: Follows the general return structure defined in `baseBackend.solve` + """ + print("\n--- Starting D-Wave (Ocean) Solve ---") + multiplier = -1 if self.sense.value == "MAX" else 1 + ut_mat = triu(self.q_matrix) + ut_mat *= multiplier + matrix_dict = {} + rows, cols = ut_mat.nonzero() + for i, j in zip(rows, cols, strict=True): + matrix_dict[(self.q_variables[i], self.q_variables[j])] = ut_mat[i, j] + + dimod = importlib.import_module("dimod") + dwave_sampler = importlib.import_module("dwave.samplers") + bqm = dimod.BinaryQuadraticModel.from_qubo( + matrix_dict, offset=float(self.q_constant) + ) + sampler = dwave_sampler.SimulatedAnnealingSampler() + kwargs.setdefault("num_reads", 100) + response = sampler.sample(bqm, **kwargs) + best_sample = response.first.sample + sol = pd.DataFrame(best_sample.items(), columns=["i", "level"]) + + return sol diff --git a/solvers/gams/unsplittable-mcf/gamspy_qubo/backend/kipu.py b/solvers/gams/unsplittable-mcf/gamspy_qubo/backend/kipu.py new file mode 100644 index 00000000..696cfae1 --- /dev/null +++ b/solvers/gams/unsplittable-mcf/gamspy_qubo/backend/kipu.py @@ -0,0 +1,94 @@ +from __future__ import annotations + +import importlib + +import pandas as pd + +from gamspy_qubo._utils import triu +from gamspy_qubo.backend import baseBackend + + +class KipuBackend(baseBackend): + """ + Kipu Backend based on `Miray Advanced` (simulator) quantum optimizer. + """ + + def __init__(self, q_matrix, q_variables, q_constant, sense): + super().__init__(q_matrix, q_variables, q_constant, sense) + self.check_dependencies("KIPU", {"planqk-service-sdk": "planqk.service.client"}) + + def _miray(self): + pass + + def _illay(self): + pass + + def solve(self, *args, **kwargs) -> pd.DataFrame: + """ + Solve the model using Kipu's `Miray` quantum optimizer and map the solution back + to original problem. + + ### Authorization + To authorize the client, you must provide the following as keyword arguments + (retrieved from your application credentials): + * `access_key_id`: Your unique application access key. + * `secret_access_key`: Your private application secret key. + + ### Configuration (kwargs) + Users can provide additional parameters to tune the solver request. + Supported arguments include: + * `shots` (int): The number of samples to collect. Default is 1000. + * `num_iterations` (int): Number of optimization iterations. Default is 3. + * `num_greedy_passes` (int): Number of greedy refinement passes. Default is 0. + + Note: Follows the general return structure defined in `baseBackend.solve` + """ + assert self.q_matrix.shape[0] <= 20, ( + f"Miray Optimizer only supports 20 qubits. This QUBO has {self.q_matrix.shape[0]} variables." + ) + + print("\n--- Starting Kipu Optimizer ---") + planq_service_client = importlib.import_module("planqk.service.client") + client = planq_service_client.PlanqkServiceClient( + service_endpoint="https://gateway.hub.kipu-quantum.com/kipu-quantum/miray-advanced-quantum-optimizer---simulator/1.0.0", + access_key_id=kwargs.get("access_key_id"), + secret_access_key=kwargs.get("secret_access_key"), + token_endpoint="https://gateway.hub.kipu-quantum.com/token", + ) + + multiplier = -1 if self.sense.value == "MAX" else 1 + ut_mat = triu(self.q_matrix) + ut_mat *= multiplier + problem = {} + rows, cols = ut_mat.nonzero() + for i, j in zip(rows, cols, strict=True): + if i == j: + problem[f"({i},)"] = ut_mat[i, j] # diagonal terms + else: + problem[f"({i}, {j})"] = ut_mat[i, j] # off-diagonal terms + problem["()"] = self.q_constant # constant + + request_defaults = {"shots": 1000, "num_iterations": 3, "num_greedy_passes": 0} + filtered_kwargs = { + key: kwargs.get(key, default_value) + for key, default_value in request_defaults.items() + } + request = { + "problem": problem, + "problem_type": "binary", + **filtered_kwargs, + } + service_exec = client.run(request) # send job + print(f"JOB ID: {service_exec.id} | Waiting...") + + result = service_exec.result() + response = result.result + _sol = { + self.q_variables[int(k)]: v for k, v in response["mapped_solution"].items() + } + sol = pd.DataFrame( + _sol.items(), + columns=["i", "level"], + ) + + return sol diff --git a/solvers/gams/unsplittable-mcf/gamspy_qubo/backend/load_json.py b/solvers/gams/unsplittable-mcf/gamspy_qubo/backend/load_json.py new file mode 100644 index 00000000..fe25c88b --- /dev/null +++ b/solvers/gams/unsplittable-mcf/gamspy_qubo/backend/load_json.py @@ -0,0 +1,36 @@ +import json + +import pandas as pd + +from gamspy_qubo.backend import baseBackend + + +class JsonBackend(baseBackend): + """ + A custom backend that reads a pre-solved QUBO solution from a JSON file. + """ + + def __init__(self, q_matrix, q_variables, q_constant, sense): + super().__init__(q_matrix, q_variables, q_constant, sense) + + def solve(self, json_path="solution.json", *args, **kwargs) -> pd.DataFrame: + with open(json_path, "r") as f: + sol_dict = json.load(f) + + results = [] + # q_variables holds the QUBO binary indices (e.g., 'b1', 'b2') + for var in self.q_variables: + # Match the variable name to the JSON keys, defaulting to 0.0 if not found + json_key = var.replace("b", "x", 1) + val = sol_dict.get(json_key, 0.0) + results.append({"i": var, "level": float(val)}) + + df = pd.DataFrame(results) + + # Provide safe default bounds for the binary variables + df["marginal"] = 0.0 + df["lower"] = 0.0 + df["upper"] = 1.0 + df["scale"] = 1.0 + + return df diff --git a/solvers/gams/unsplittable-mcf/gamspy_qubo/qubo.py b/solvers/gams/unsplittable-mcf/gamspy_qubo/qubo.py new file mode 100644 index 00000000..f1d0064d --- /dev/null +++ b/solvers/gams/unsplittable-mcf/gamspy_qubo/qubo.py @@ -0,0 +1,890 @@ +import logging as log + +import gamspy as gp +import numpy as np +import pandas as pd +from gamspy.exceptions import GamspyException, ValidationError + +from gamspy_qubo import _utils +from gamspy_qubo.backend import DwaveBackend, JsonBackend, KipuBackend + +LOG_LEVEL_DICT = {0: log.WARN, 1: log.INFO, 2: log.DEBUG} +SUPPORTED_QUANTUM_BACKENDS = [ + "dwave", + "kipu", + "json", +] # append this list when adding new quantum backend + + +class Qubo(gp.Model): + """ + QUBO Reformulation for Integer Programs (IPs) modelled in GAMSPy. + + Parameters + ---------- + model: gp.Model + GAMSPy generated IP Model. + name: str | None + Name for the reformulated model. By default it is "QUBO" + penalty: int | None + Set the appropriate penalty to be used to penalize the constraints. By default it is 1 + log_on: int | None + Enable logging information. + Options are {0 = WARN, 1 = INFO, 2 = DEBUG}. + By default it is 0. + + Notes + ----- + - Currently two quantum solvers, ("dwave", "kipu"), are supported. + - The desired quantum solver can be set using the `solver` keyword argument in the `model.solve()` call. (default = "cplex") + - We first use the `CONVERT` solver to generate a standardized version of the original problem. + - One can also pass keyword arguments to the `CONVERT` solver, for. e.g., `options=gp.Options(hold_fixed_variables=True)` + """ + + def __init__( + self, + model: gp.Model, + /, + name: str = "QUBO", + penalty: int = 1, + log_on: int = 0, + **kwargs, + ): + if not isinstance(model, gp.Model): + raise ValidationError("Qubo() only accepts a >gamspy.Model< object.") + + self._og_model: gp.Model = model + self._modelName: str = name + self._sense: gp.Sense = model.sense + self.penalty: int = penalty + _work_dir = model.container.working_directory + self._container: gp.Container = self._run_convert(workdir=_work_dir, **kwargs) + self._q_container = gp.Container(working_directory=_work_dir) + self.Q: np.ndarray = np.array([]) + self.Qconst: float = 0.0 + self._TRANSFORMATION_COMPLETE = False + self._MAPPING_COMPLETE = False + + if ( + log_level := LOG_LEVEL_DICT.get( + _utils.validate_value( + log_on, allowed_values=[0, 1, 2], param_name="log_on" + ), + log.WARN, + ) + ) < log.WARN: + log.basicConfig( + filename=f"{self._og_model.name}_reformulation.log", + filemode="w", + format="%(message)s", + level=log_level, + force=True, + ) + + def __str__(self) -> str: + return ( + f"Model {self._modelName}:\n Problem Type: MIQCP\n Sense:" + f" {self._sense}\n Equations: {self._modelName}_objective" + ) + + def _run_convert(self, workdir, **kwargs) -> gp.Container: + try: + self._og_model.solve( + solver="CONVERT", + solver_options={ + "dumpgdx": f"{self._og_model.name}.gdx", + "GDXQuadratic": 1, + "GDXHessian": 1, + }, + **kwargs, + ) + except Exception as e: + raise GamspyException("Error while running the >CONVERT< operation.") from e + + return gp.Container( + load_from=f"{self._og_model.name}.gdx", + working_directory=workdir, + ) + + def _check_transformation(self): + if not self._TRANSFORMATION_COMPLETE: + raise ValidationError("Run transform first to generate the Q Matrix.") + + def transform( + self, penalty: int | None = None + ) -> tuple[gp.Parameter, gp.Set, gp.Parameter]: + """ + Method to perform the QUBO reformulation on the provided model. + + Arguments: + penalty: different penalty can be provided again to generate a new QUBO. + + Returns: + qd: gp.Parameter: the Q Matrix + qi: gp.Set: binary variables participating in the QUBO + qconst: gp.Parameter: the offset calculated based on the penalty provided. + """ + if penalty is None: + penalty = self.penalty + + self._fixed_vars_flag = False + self._lower_bounded_vars_flag = False + obj_eq_name: pd.DataFrame = self._container["iobj"].records + + if obj_eq_name is None: + raise ValidationError( + "The objective is not defined using a scalar equation. `iobj` in gdx is empty. Quitting." + ) + + obj_var: list = self._container["jobj"].records["j"].to_list() + # fetch the objective variable name + all_vars: pd.DataFrame = self._container[ + "j" + ].records # fetches all variable names + raw_a: pd.DataFrame = self._container["A"].records # A coefficients + eq_data: pd.DataFrame = self._container["e"].records # fetches equation data + + if ( + raw_a[-raw_a["i"].isin(obj_eq_name["i"].tolist())]["value"] + .mod(1) + .sum(axis=0) + > 0 + ): # floating point coeffs in objective function is accepted + raise ValidationError( + "Reformulation with Non-Integer Coefficients not possible. Quitting." + ) + + raw_a = raw_a.pivot(index="i", columns="j", values="value").fillna( + 0 + ) # arranging in a matrix + + if ( + eq_data["lower"].mod(1).sum(axis=0) > 0 + or eq_data["upper"].mod(1).sum(axis=0) > 0 + ): + raise ValidationError( + "Reformulation with Non-Integer RHS not possible. Quitting." + ) + + bin_vars = self._container[ + "jb" + ].records # fetches binary variable names, if any + int_vars = self._container[ + "ji" + ].records # fetches integer variable names, if any + bin_vars = ( + [] if bin_vars is None else bin_vars["j"].to_list() + ) # check if any bin_vars are present + int_vars = ( + [] if int_vars is None else int_vars["j"].to_list() + ) # check if any int_vars are present + self._int_vars_flag = False if len(int_vars) == 0 else True + all_var_vals = ( + self._container["x"].records + ) # get all variable values, viz., [level, marginal, lower, upper, scale] + + if ( + len(all_vars) - len(bin_vars) - len(int_vars) != 1 + ): # Continuous variables are not allowed + raise ValidationError("There are continuous variables. Quitting.") + + self._obj_eq_name = obj_eq_name["i"].to_list() + + check_quad = self._container["ANl"].records + + """ + Check if there are any fixed variables in the gdx, i.e., lb=ub=level of any variable. + If such variables exist, separate them from the list of non-fixed variables and treat them as constants in the objective function. + + We also need to check if the level of variables are set and handle them separately + """ + lower_mask = (all_var_vals["lower"] > 0) & ( + all_var_vals["lower"] != all_var_vals["upper"] + ) + fixed_mask = (all_var_vals["level"] == all_var_vals["lower"]) & ( + all_var_vals["level"] == all_var_vals["upper"] + ) + + self._vars_with_lower_bounds = dict( + all_var_vals[lower_mask][["j", "lower"]].values + ) + fixed_vars = dict(all_var_vals[fixed_mask][["j", "level"]].values) + + fixed_and_lower_bounds = {**self._vars_with_lower_bounds, **fixed_vars} + sum_fixed_obj_var_coeffs = 0.0 + + if check_quad is not None: + rawquad = self._container[ + "Q" + ].records # fetch quadratic terms from the original problem, if any. + + if self._int_vars_flag: + raise ValidationError( + "Quadratic Program with integer variables are not supported." + ) + + if any(check_quad["j"].isin(fixed_and_lower_bounds.keys())): + raise ValidationError( + "Quadratic terms with non-zero variable levels are not supported at the moment." + ) + + log.debug("Coefficient matrix: raw_a\n" + raw_a.to_string()) + log.debug("\nEquation Data: eq_data\n" + eq_data.to_string()) + log.debug("\nVariable Data: all_var_vals\n" + all_var_vals.to_string()) + + if ( + fixed_and_lower_bounds + ): # adjust the rhs of equations when level of variables > 0 + log.info( + f"\nList of variables with lower bounds:\n{self._vars_with_lower_bounds}" + ) + self._lower_bounded_vars_flag = True + contribution = _utils.var_contribution(raw_a, fixed_and_lower_bounds) + eq_data.loc[:, ["lower", "upper"]] -= contribution + if fixed_vars: + fixed_set = set(fixed_vars.keys()) + self._fixed_vars_flag = True + log.info(f"\nList of Fixed Variables:\n{fixed_vars}") + # remove the fixed variables from computation + bin_vars = [var for var in bin_vars if var not in fixed_set] + int_vars = [var for var in int_vars if var not in fixed_set] + sum_fixed_obj_var_coeffs += np.ndarray.item( + _utils.var_contribution(raw_a, fixed_vars, cons=self._obj_eq_name) + ) + raw_a.drop( + labels=list(fixed_vars.keys()), axis=1, inplace=True + ) # dropping columns from the coefficient matrix + self._fixed_var_vals: pd.DataFrame = all_var_vals[ + all_var_vals["j"].isin(fixed_vars) + ].copy(deep=True) + + log.debug( + "\nAfter removing fixed variables and adjusting for non-zero levels: raw_a\n" + + raw_a.to_string() + ) + log.debug( + "\nAfter removing fixed variables and adjusting for non-zero levels: eq_data\n" + + eq_data.to_string() + ) + + """ + Check if there exist a row in coefficient matrix with all zero values. This can happen if all vars in a constraint are fixed. + Such row is irrelevant for QUBO and can be dropped out of the matrix and set of constraints. + """ + redundant_cons = list(raw_a[raw_a.apply(abs).sum(axis=1) == 0].index) + if len(redundant_cons) > 0: + log.info(f"\nDropping these redundant constraint: \n{redundant_cons}") + raw_a.drop(redundant_cons, axis=0, inplace=True) + eq_data.drop( + eq_data[eq_data["i"].isin(redundant_cons)].index, axis=0, inplace=True + ) + + """ + If integer variables exist, convert all integers to binary with '@' as a delimiter of variable names + If Integer variables with lower bound exist, i.e., lb >=1 and lb!=ub, then convert binary variable for that range + If these variables contribute to the objective function, their lower bounds are added as a constant + """ + sum_lower_bound_of_int_vars = 0 + if self._int_vars_flag: + if self._vars_with_lower_bounds: + sum_lower_bound_of_int_vars += np.ndarray.item( + _utils.var_contribution( + raw_a, self._vars_with_lower_bounds, self._obj_eq_name + ) + ) + + int_var_vals = all_var_vals[all_var_vals["j"].isin(int_vars)] + int_to_bin_bounds = { + row["j"]: _utils.gen_slacks(row["upper"] - row["lower"]) + for _, row in int_var_vals.iterrows() + } # generate coeffs for converted binary vars + int_bin_vals = pd.DataFrame(columns=["intName", "binName", "value"]) + for var, bin_bounds in int_to_bin_bounds.items(): + for i in range(len(bin_bounds)): # naming the converted binary vars + new_row = pd.DataFrame( + { + "intName": var, + "binName": f"{var}@_bin{i}", + "value": bin_bounds[i], + }, + index=[0], + ) + int_bin_vals = ( + pd.concat([int_bin_vals, new_row], ignore_index=True) + if not int_bin_vals.empty + else new_row.copy() + ) + + self._binName_list = int_bin_vals[ + "binName" + ].to_list() # list of all converted binary variable names + log.info( + "\nInteger to Binary Mapping: int_bin_vals\n" + int_bin_vals.to_string() + ) + int_bin_vals = int_bin_vals.pivot( + index="intName", columns="binName", values="value" + ).fillna(0) # mapping each binary var to its integer var component + int_bin_vals = int_bin_vals.reindex(labels=int_vars, axis="index") + int_bin_vals = int_bin_vals.reindex( + labels=self._binName_list, axis="columns" + ) + self._int_bin_vals = int_bin_vals + + raw_a_int = raw_a[int_vars] + raw_a_int = raw_a_int.dot( + int_bin_vals + ) # updating the "A" coeff matrix with the new coeffs for converted binary vars + raw_a_rest = raw_a[obj_var + bin_vars] + raw_a = pd.concat( + [raw_a_rest, raw_a_int], axis="columns" + ) # new "A" coeff matrix + log.info("\nInteger to Binary Mapping: raw_a\n" + raw_a.to_string()) + bin_vars += self._binName_list # append the list of original binary variables with the list of converted binary variables + + cons = eq_data[-eq_data["i"].isin(self._obj_eq_name)].reset_index( + drop=True + ) # fetch only the constraints and not the objective equation + nvars = len(bin_vars) + nslacks = 0 + self._obj_var_direction = raw_a[obj_var].loc[self._obj_eq_name].to_numpy() + obj_var_coeff = raw_a[bin_vars].loc[self._obj_eq_name].to_numpy() + if self._obj_var_direction > 0: + obj_var_coeff = -1 * obj_var_coeff + obj = np.zeros((nvars, nvars)) + np.fill_diagonal(obj, obj_var_coeff) + + """ + Pre-processing in case special constraints exist + Remove the constraint from the "A" matrix and include the special penalty directly in the objective + Doing so, reduces the number of slack variables used in the final reformulation + special penalty case 1: sum(x_i | 1 <= i <= n) <= 1 => P*sum(x_i*x_j | i < j) + special penalty case 2: x_i + x_j >= 1 => P*(1 - x_i - x_j + x_i*x_j) + """ + + # Case 1 implementation + special_cons_case_1_label = [ + ele.i for _, ele in cons.iterrows() if ele.upper == 1 and ele.lower != 1 + ] + if special_cons_case_1_label: + case1_cons = raw_a[bin_vars].loc[special_cons_case_1_label] + case1_cons = _utils.check_row_entries(case1_cons.copy()) + case1_cons_index_label = list(case1_cons.index) + case1_penalty = case1_cons.to_numpy() + if case1_penalty.size > 0: + case1_penalty = (case1_penalty.T @ case1_penalty) / 2 + np.fill_diagonal(case1_penalty, np.zeros((1, len(bin_vars)))) + else: # if there are no rows with only 0/1 entries + case1_penalty = np.zeros((nvars, nvars)) + log.debug(f"\nSpecial constraint case 1:\n{special_cons_case_1_label}") + else: + case1_cons_index_label = [] + case1_penalty = np.zeros((nvars, nvars)) + + # Case 2 implementation + special_cons_case_2_label = [ + ele.i for _, ele in cons.iterrows() if ele.lower == 1 and ele.upper != 1 + ] + if special_cons_case_2_label: + case2_cons = raw_a[bin_vars].loc[special_cons_case_2_label] + case2_cons = _utils.check_row_entries(case2_cons.copy()) + case2_cons = case2_cons[case2_cons.sum(axis=1) == 2] + case2_cons_index_label = list(case2_cons.index) + case2_penalty = case2_cons.to_numpy() + if case2_penalty.size > 0: + case2_penalty = (case2_penalty.T @ case2_penalty) / 2 + case2_diag = np.diag_indices_from(case2_penalty) + case2_penalty[case2_diag] *= -2 + else: # if there are no rows with two 1s in them + case2_penalty = np.zeros((nvars, nvars)) + log.debug(f"\nSpecial constraint case 2:\n{special_cons_case_2_label}") + + else: + case2_cons_index_label = [] + case2_penalty = np.zeros((nvars, nvars)) + + final_special_cons = case1_cons_index_label + case2_cons_index_label + final_special_penalty = case1_penalty + case2_penalty + case2_penalty_offset_factor = len(case2_cons_index_label) + + is_max = True if self._sense == gp.Sense.MAX else False + P = -1 * penalty if is_max else penalty # penalty term for classic solvers + obj += P * final_special_penalty + + cons.drop(cons[cons["i"].isin(final_special_cons)].index, axis=0, inplace=True) + raw_a.drop(final_special_cons, axis=0, inplace=True) + + A_coeff = raw_a.loc[cons["i"], bin_vars] + + self._quad_val = np.array([]) + if ( + check_quad is not None + ): # check if quadratic terms are present in the original problem + log.debug("\nRaw Q data from GDX: Q\n" + rawquad.to_string()) + rawquad_obj = rawquad[rawquad["i_0"].isin(self._obj_eq_name)].copy( + deep=True + ) + if ( + len(rawquad_obj.index) != 0 + ): # check if quadratic terms exist in the objective function + rawquad_obj.drop(["i_0"], axis=1, inplace=True) + self._quad_val = _utils.fetch_quadratic_coeff( + raw_df=rawquad_obj, bin_vars=bin_vars + ) + sum_fixed_obj_var_coeffs /= 2 + + rawquad_cons = rawquad[ + -rawquad["i_0"].isin(self._obj_eq_name) + ] # non-linear constraints without objective equation + if len(rawquad_cons.index) != 0: # non-linear constraints exists + raise ValidationError("There are non-linear constraints. Quitting.") + ### Removed the support for quadratic constraints. + + if ( + self._quad_val.size + ): # add the old quadratic terms/matrix to the new objective + log.debug( + "\nUpdate Objective by adding Q: \n" + np.array2string(self._quad_val) + ) + obj += ( + -1 * self._quad_val if self._obj_var_direction > 0 else self._quad_val + ) + log.debug("\nNew Q: \n" + np.array2string(obj)) + + b_vec = np.array([]) + log.info("\nFinal Cons: \n" + cons.to_string()) + for _, ele in cons.iterrows(): + if ele.upper == ele.lower: # equal-to type constraint + rhs = ele.lower + lhs_min_lb, lhs_max_ub = _utils.get_lhs_bounds(A_coeff.loc[ele.i]) + if (rhs - lhs_min_lb) < 0 or (lhs_max_ub - rhs) < 0: + raise ValidationError(f"Constraint is infeasible: {ele.i}") + else: + b_vec = np.append(b_vec, [rhs]) + slacks: ( + list | np.ndarray + ) = [] # do not introduce slacks for equality type constraints + + elif ele.upper == np.inf: # greater than type constraint + rhs = ele.lower + _, lhs_max_ub = _utils.get_lhs_bounds(A_coeff.loc[ele.i]) + slacks_range = lhs_max_ub - rhs + if slacks_range > 0: + slacks = -1 * _utils.gen_slacks(slacks_range) + b_vec, A_coeff, nslacks = _utils.modify_matrix( + b_vec, rhs, slacks, A_coeff, ele, nslacks + ) + elif slacks_range == 0: + b_vec = np.append(b_vec, [rhs]) + slacks = [] + else: + raise ValidationError(f"Constraint is infeasible: {ele.i}") + + else: # less-than type constraint + rhs = ele.upper + lhs_min_lb, _ = _utils.get_lhs_bounds(A_coeff.loc[ele.i]) + slacks_range = rhs - lhs_min_lb + if slacks_range > 0: + slacks = _utils.gen_slacks(slacks_range) + b_vec, A_coeff, nslacks = _utils.modify_matrix( + b_vec, rhs, slacks, A_coeff, ele, nslacks + ) + elif slacks_range == 0: + b_vec = np.append(b_vec, [rhs]) + slacks = [] + else: + raise ValidationError(f"Constraint is infeasible: {ele.i}") + + logging_a_mat = A_coeff.unstack().reset_index() + logging_a_mat = logging_a_mat[logging_a_mat[0] != 0] + log_data = { + "final_coefficient_matrix": f"\n{logging_a_mat.to_string()}", + "final_rhs": b_vec, + "constant_rhs_term": b_vec.T @ b_vec, + "case2_penalty_offset_factor": case2_penalty_offset_factor, + "fixed_variable_contribution": sum_fixed_obj_var_coeffs, + "integer_lower_bound_contribution": sum_lower_bound_of_int_vars, + } + # Single structured log entry + log.debug( + "Final optimization summary:\n" + + "\n".join( + f"{k.replace('_', ' ').title()}: {v}" for k, v in log_data.items() + ) + ) + # A matrix and b_vec are available. Now, for penalization: $(A.X - B)^{2}$ = $(A.X - B)^{T} * (A.X - B)$ + a_mat = A_coeff.to_numpy() + nvars += nslacks # increment the total number of variables by total number of slack variable used + X_diag = np.zeros((nvars, nvars)) + np.fill_diagonal(X_diag, -b_vec.T @ a_mat) + new_x = a_mat.T @ a_mat + 2 * X_diag + + newobj = np.zeros((nvars, nvars)) + newobj[: len(bin_vars), : len(bin_vars)] = ( + obj # define the new objective: Q for the qubo + ) + + self.Qconst = ( + P * b_vec.T @ b_vec + + P * case2_penalty_offset_factor + + sum_fixed_obj_var_coeffs + + sum_lower_bound_of_int_vars + ) + log.debug(f"\nPenalty: {P} | Total Offset: {self.Qconst}\n") + self.Q = newobj + P * new_x + Qdf_df: pd.DataFrame | pd.Series = pd.DataFrame( + self.Q, columns=list(A_coeff.columns), index=list(A_coeff.columns) + ) + Qdf_df = Qdf_df.unstack() + Qdf_df = Qdf_df.reset_index() + Qdf = list(Qdf_df.itertuples(index=False, name=None)) + + qconst = gp.Parameter( + self._q_container, + "qconst", + None, + self.Qconst, + description="Constant term to offset the objective value to original", + ) + if Qdf: + qi = gp.Set( + self._q_container, + "qi", + records=A_coeff.columns, + description="QUBO variables", + ) + qd = gp.Parameter( + self._q_container, + "qd", + [qi, qi], + records=Qdf, + description="Q matrix", + ) + else: + qi = gp.Set( + self._q_container, + "qi", + records=["all_variables_fixed"], + description="QUBO variables", + ) + qd = gp.Parameter( + self._q_container, + "qd", + [qi, qi], + records=[("all_variables_fixed", "all_variables_fixed", 0)], + description="Q matrix", + ) + + self._TRANSFORMATION_COMPLETE = True + return qd, qi, qconst + + def write_gdx(self, gdxName: str | None = None): + if gdxName: + self._q_container.write(gdxName) + else: + self._q_container.write(f"qout_{self._modelName}.gdx") + + def write_qubowl(self) -> None: + """ + Convenient method to produce QUBO in QUBOWL ready file format (.qs) + + Returns: + A QMat_.qs file accepted by QUBOWL. + """ + self._check_transformation() + + non_zero_indices = np.tril_indices_from(self.Q) + non_zero_values = self.Q[non_zero_indices] + with open(f"QMat_{self._modelName}.qs", "w") as fp: + fp.write(f"{self.Q.shape[0]} {len(non_zero_values)} {self.Qconst}\n") + for i, j, value in zip(*non_zero_indices, non_zero_values, strict=True): + if value != 0: + fp.write(f"{i + 1} {j + 1} {value}\n") + + def _model(self) -> None: + try: + qd, qi, qconst = self._q_container.getSymbols(["qd", "qi", "qconst"]) + except Exception as e: + raise GamspyException( + "Something went from while fetching the q symbols." + ) from e + + i = gp.Alias(self._q_container, name="i", alias_with=qi) + + j = gp.Alias(self._q_container, name="j", alias_with=qi) + + x = gp.Variable( + self._q_container, + name="x", + type="binary", + domain=i, + description="QUBO variables", + ) + + qubo_obj = gp.Sum(gp.Domain(i, j), x[i] * qd[i, j] * x[j]) + qconst + super().__init__( + container=self._q_container, + name=self._modelName, + problem="MIQCP", + sense=self._sense, + objective=qubo_obj, + ) + + def solve(self, *args, **kwargs) -> pd.DataFrame | None: + kwargs.setdefault("solver", "cplex") + self._backend = kwargs.get("solver") + optimized_variable_values = None + orig_obj_var = getattr(self._og_model, "_objective_variable", None) + if orig_obj_var is not None: + original_obj_sym = orig_obj_var.name + else: + original_obj_sym = f"{self._og_model.name}_objective_variable" + + if not self._TRANSFORMATION_COMPLETE: + self.transform() + + if f"{self._modelName}_objective" not in self._q_container.data: + self._model() + + if self._backend not in SUPPORTED_QUANTUM_BACKENDS: + try: + solved = super().solve(*args, **kwargs) + _utils.check_classical_solve(solveStatus=super().solve_status) + except Exception as e: + raise GamspyException( + f"Something went wrong while solving using >{self._backend}< backend." + ) from e + optimized_variable_values = self._q_container["x"].records + elif self._backend in SUPPORTED_QUANTUM_BACKENDS: + q_vars: pd.Series = self._q_container["qi"].records["uni"] + _initialize = { + "dwave": DwaveBackend, + "kipu": KipuBackend, + "json": JsonBackend, + } + _backend: DwaveBackend | KipuBackend | JsonBackend = _initialize[ + self._backend + ]( + q_matrix=self.Q, + q_variables=q_vars, + q_constant=self.Qconst, + sense=self._sense, + ) # type: ignore + try: + optimized_variable_values = _backend.solve(*args, **kwargs) + assert isinstance(optimized_variable_values, pd.DataFrame), ( + "backend.solve() must return a `pd.DataFrame`. " + "Refer to the return structue of `baseBackend.solve` for more details." + ) + optimized_variable_values["i"] = pd.Categorical( + optimized_variable_values["i"], + categories=q_vars.cat.categories, + ordered=True, + ) # NOTE: some solvers can shuffle the order of UELs, and + # it is critical for them to be in order for mapping them back + cols = ["marginal", "lower", "upper", "scale"] + optimized_variable_values = optimized_variable_values.reindex( + columns=optimized_variable_values.columns.tolist() + cols + ) + except Exception as e: + raise GamspyException( + f"Something went wrong while solving using >{self._backend}< backend." + ) from e + else: + raise GamspyException(f"Backend {self._backend} not supported.") + + solution = { + "optimized_vals": optimized_variable_values.sort_values("i"), + "obj_fn_sym": original_obj_sym, + } + self._map_solution(solution) + + return solved if self._backend not in SUPPORTED_QUANTUM_BACKENDS else None + + def _map_solution(self, solution) -> None: + """ + This function maps the QUBO solution to the original Problem + """ + all_vars = self._container["j"].records + optimized_vals = solution["optimized_vals"] + original_obj_sym = solution["obj_fn_sym"] + + if self._fixed_vars_flag: + self._fixed_var_vals.rename({"j": "i"}, axis=1, inplace=True) + optimized_vals = pd.concat( + [optimized_vals, self._fixed_var_vals], ignore_index=True + ) + + if self._int_vars_flag: + int_bin_map = self._int_bin_vals.unstack() + int_bin_map = int_bin_map[int_bin_map != 0].reset_index() # type: ignore + + opt_lookup = optimized_vals.set_index("i")["level"] + int_bin_map["final_level"] = int_bin_map[0] * int_bin_map["binName"].map( + opt_lookup + ) + + int_summary = int_bin_map.groupby("intName")["final_level"].sum() + + original_int_vals = self._container["x"].records + original_int_vals = original_int_vals[ + original_int_vals["j"].isin(int_summary.index) + ].copy() + + original_int_vals["level"] = original_int_vals["j"].map(int_summary) + original_int_vals.rename(columns={"j": "i"}, inplace=True) + + cols = ["i", "level", "marginal", "lower", "upper", "scale"] + original_int_vals = original_int_vals[cols] + optimized_vals = optimized_vals[ + ~optimized_vals["i"].isin(self._binName_list) + ] + optimized_vals = pd.concat( + [optimized_vals, original_int_vals], ignore_index=True + ) + + if self._vars_with_lower_bounds: + optimized_vals.loc[ + optimized_vals["i"].isin(self._vars_with_lower_bounds.keys()), + "level", + ] += list(self._vars_with_lower_bounds.values()) + + pattern = r"(?P.+)\((?P.*?)\)" + extracted = all_vars["element_text"].str.extract(pattern) + extracted["domain"] = extracted["domain"].str.replace(r"\'", "", regex=True) + extracted["uni"] = all_vars["uni"] + + merged_vals = optimized_vals.merge( + extracted, left_on="i", right_on="uni", how="inner" + ) + + for var_name, group in merged_vals.groupby("var_sym"): + target_symbol = self._og_model.container[var_name] + domain_names = [ + dom if isinstance(dom, str) else dom.name + for dom in target_symbol.domain + ] + + split_labels = group["domain"].str.split(",", expand=True) + split_labels.columns = domain_names + + attrs = ["level", "marginal", "lower", "upper", "scale"] + new_records = pd.concat( + [split_labels, group[attrs]], + axis=1, + ) + dtype_map = { + **{c: "category" for c in domain_names}, + **{c: "float" for c in attrs}, + } + new_records = new_records.astype(dtype_map) + target_symbol.records = new_records.reset_index(drop=True) + + """ + The code below is required to calculate the contribution of variables towards the objective using the new levels obtained from the QUBO solve. + Since the QUBO solve returns a different level for the objective variable when the optimal solution is not returned, for example, it includes the penalty for every constraint not satisfied. + """ + + obj_var = self._container["jobj"].records["j"].values[0] + rem_syms = all_vars[all_vars["uni"] != obj_var]["uni"].to_list() + mask = optimized_vals["i"].isin(rem_syms) + filtered_vals = optimized_vals[mask] + + x_l = filtered_vals["level"].to_numpy() + orig_syms_w_new_levels = filtered_vals.set_index("i")["level"].to_dict() + + # at the moment `quad` only contains contribution from the objective row. + quad_contribution = x_l.T @ self._quad_val @ x_l if self._quad_val.size else 0 + jacobian: pd.DataFrame = self._container["A"].records # A coefficients + jacobian = jacobian.pivot(index="i", columns="j", values="value").fillna( + 0 + ) # arranging in a matrix + linear_contribution = _utils.var_contribution( + jacobian, orig_syms_w_new_levels, cons=self._obj_eq_name + ).flatten()[0] + + total_objective_contribution = linear_contribution + quad_contribution + total_objective_contribution = ( + -1 * total_objective_contribution + if self._obj_var_direction > 0 + else total_objective_contribution + ) + + obj_var_coeff: gp.Variable = self._q_container[ + f"{self._modelName}_objective_variable" + ] + obj_var_coeff.l = total_objective_contribution + super().__setattr__( + "_objective_value", total_objective_contribution + ) # sets the value for the attribute of QUBO model instance + self._og_model.container[ + original_obj_sym + ].records = obj_var_coeff.records # sets the value for original model instance + self._MAPPING_COMPLETE = True + + @property + def qubo(self): + return self.Q + + @staticmethod + def check_convexity(Q: np.ndarray): + """ + Convenience method to check the convexity of the QUBO using eigenvalues + """ + return _utils.check_convexity(Q) + + @staticmethod + def qubo_to_ising(Q: dict, offset: float = 0.0): + """ + This is the Qubo to Ising Reformulation. Here, the variable X in {-1,1} + + Args: + Q: in a form of dict, {(i,j): val} + offset: offset from the Qubo reformulation + + Returns: + h: the bias vector \ + J: the coupling matrix\ + offset: adjusted offset for the Ising model + """ + return _utils.qubo_to_ising(Q, offset) + + @staticmethod + def qubo_to_maxcut(Q: np.ndarray): + """ + This is the Qubo to Maxcut Reformulation. This can be used for SDP procedures. + + Args: + Q: a n x n symmetric numpy matrix + + Returns: + n x 1 vector associated with the extra variable required in max cut transformation + """ + return _utils.qubo_to_maxcut(Q) + + @staticmethod + def triu(Q: np.ndarray) -> np.ndarray: + """ + Helper function to fetch the upper triangular matrix of a symmetric Q matrix + + Returns: + An upper triangular matrix + """ + return _utils.triu(Q) + + def check_feasibility(self): + import io + + stream = io.StringIO() + self._og_model.solve( + solver="examiner", + solver_options={"examineInitPoint": 1, "objvarAutoAdjust": 1}, + output=stream, + ) + result = _utils.parse_examiner_output(text=stream.getvalue()) + + if not self._MAPPING_COMPLETE: + result["WARNING"] = ( + "Model must be solved before checking feasibility of the QUBO solution." + ) + + return result diff --git a/solvers/gams/unsplittable-mcf/plot_qubo_solution.py b/solvers/gams/unsplittable-mcf/plot_qubo_solution.py new file mode 100644 index 00000000..7f040d8f --- /dev/null +++ b/solvers/gams/unsplittable-mcf/plot_qubo_solution.py @@ -0,0 +1,24 @@ +import os +import sys +from unsplittable_mcf import UnsplittableMCF + +arg_count = len(sys.argv) - 1 +if arg_count != 3: + raise TypeError( + f"This script expects exactly 3 arguments but got {arg_count}. " + "Unsplittable MCF instance file (argument 1), solution file (argument 2), and output HTML file (argument 3)." + ) + +unsplittable_mcf_path = sys.argv[1] +solution_path = sys.argv[2] +output_path = sys.argv[3] + +if os.path.exists(solution_path): + mapper = UnsplittableMCF(instance_file=unsplittable_mcf_path) + mapper.build_equations_and_model() + + mapper.plot_json_solution( + json_path=solution_path, output_html=output_path + ) +else: + print(f"Error: '{solution_path}' not found. Please run the external solver first.") diff --git a/solvers/gams/unsplittable-mcf/plot_unsplittable.py b/solvers/gams/unsplittable-mcf/plot_unsplittable.py new file mode 100644 index 00000000..bc0c584d --- /dev/null +++ b/solvers/gams/unsplittable-mcf/plot_unsplittable.py @@ -0,0 +1,376 @@ +from pyvis.network import Network +import re +import colorsys +import pandas as pd + +def _rgb_to_hex(rgb_color): + r, g, b = [int(c * 255) for c in rgb_color] + return f"#{r:02x}{g:02x}{b:02x}" + + +def generate_distinct_colors(n): + colors = [] + for i in range(n): + hue = i / (n if n > 0 else 1) + rgb = colorsys.hls_to_rgb(hue, 0.5, 0.7) + colors.append(_rgb_to_hex(rgb)) + return colors + + +def _parse_flow_input(flow_input): + """Parses list or DataFrame into a standard list of dicts.""" + flow_records = [] + + if flow_input is None: + return [] + + if isinstance(flow_input, pd.DataFrame): + # Filter for active flows if 'level' column exists + if "level" in flow_input.columns: + active_flows = flow_input[flow_input["level"] > 0.5] + else: + active_flows = flow_input + + for _, row in active_flows.iterrows(): + # Adjust indices based on GAMS domain order (v, t, v, t, o) + flow_records.append( + { + "u": row.iloc[0], + "t_u": row.iloc[1], + "v": row.iloc[2], + "t_v": row.iloc[3], + "order": row.iloc[4], + "value": 1, + } + ) + elif isinstance(flow_input, list): + flow_records = flow_input + + # Normalize time steps + for f in flow_records: + f["t_u"] = int(str(f["t_u"]).replace("t", "")) + f["t_v"] = int(str(f["t_v"]).replace("t", "")) + + return flow_records + + +def _get_node_info(node_name, time_step): + match = re.search(r"v_([a-zA-Z]+)(\d*)", str(node_name)) + if match: + style_key = match.group(1) + node_index = int(match.group(2)) if match.group(2) else 0 + label = f"{style_key}{node_index}\nt{time_step}" + if style_key == "source": + label = "Source" + if style_key == "magic": + label = "Slack" + return style_key, node_index, int(time_step), label + return "unknown", 0, int(time_step), str(node_name) + + +# --- Main Plotting Function (Single Solution) --- +def plot_mcf(filename="mcf_plot.html", flow_input=None, demand_records=None): + """Plots a single solution with detailed demand info and hover quantities.""" + if flow_input is None: + flow_input = [] + if demand_records is None and isinstance(flow_input, pd.DataFrame): + demand_records = pd.DataFrame() + + flow_records = _parse_flow_input(flow_input) + _generate_network(filename, flow_records, demand_records, mode="single") + + +# --- Comparison Function (Classical vs QUBO) --- +def plot_comparison( + filename="mcf_comparison.html", + classical_input=None, + qubo_input=None, + demand_records=None, +): + """Plots a comparison with detailed demand info and hover quantities.""" + if classical_input is None: + classical_input = [] + if qubo_input is None: + qubo_input = [] + + c_records = _parse_flow_input(classical_input) + d_records = _parse_flow_input(qubo_input) + + c_keys = set((r["u"], r["t_u"], r["v"], r["t_v"], r["order"]) for r in c_records) + d_keys = set((r["u"], r["t_u"], r["v"], r["t_v"], r["order"]) for r in d_records) + + combined_flows = [] + + # Matches (Green) + for k in c_keys.intersection(d_keys): + combined_flows.append( + { + "u": k[0], + "t_u": k[1], + "v": k[2], + "t_v": k[3], + "order": k[4], + "color": "green", + "status": "Match", + "width": 4, + } + ) + + # Classical Only (Blue) + for k in c_keys - d_keys: + combined_flows.append( + { + "u": k[0], + "t_u": k[1], + "v": k[2], + "t_v": k[3], + "order": k[4], + "color": "blue", + "status": "Classical Only", + "width": 2, + } + ) + + # QUBO Only (Red) + for k in d_keys - c_keys: + combined_flows.append( + { + "u": k[0], + "t_u": k[1], + "v": k[2], + "t_v": k[3], + "order": k[4], + "color": "red", + "status": "QUBO Only", + "width": 2, + } + ) + + print( + f"Comparison Stats: Matches={len(c_keys.intersection(d_keys))}, Classical_Only={len(c_keys-d_keys)}, QUBO_Only={len(d_keys-c_keys)}" + ) + + _generate_network(filename, combined_flows, demand_records, mode="compare") + + +# --- Internal Graph Generation Logic --- +def _generate_network(filename, flow_records, demand_records, mode="single"): + NODE_STYLES = { + "source": {"label": "Source", "level": 0}, + "fac": {"label": "Factory", "level": 1}, + "sort": {"label": "Sorting", "level": 2}, + "db": {"label": "DieBank", "level": 3}, + "asm": {"label": "Assembly", "level": 4}, + "test": {"label": "Testing", "level": 5}, + "dc": {"label": "Dist. Ctr", "level": 6}, + "magic": {"label": "Slack", "level": 7}, + } + + Y_TIME_SPACING, Y_UNIT_SPACING, X_SPACING = 120, 50, 250 + UNIT_COLORS = ["#ADD8E6", "#87CEEB", "#6495ED", "#4169E1", "#000080"] + + net = Network(height="100vh", width="100%", notebook=True, directed=True) + added_nodes = set() + edge_counts = {} + max_time_step = 0 + + # --- 1. Map Orders to Quantities --- + # We sum up the values for each order to get total flow quantity + order_qty_map = {} + if demand_records is not None and not demand_records.empty: + for _, row in demand_records.iterrows(): + # Robustly get 'o' and 'value' + o_val = row.get("o", row.iloc[0]) + val = row.get("value", row.iloc[-1]) # Value is typically last column + + # Aggregate if multiple entries per order (e.g. multiple products) + current_qty = order_qty_map.get(o_val, 0) + try: + current_qty += float(val) + except (ValueError, TypeError): + pass + order_qty_map[o_val] = current_qty + + # --- 2. Setup Colors --- + order_color_map = {} + if mode == "single": + unique_orders = sorted(list(set(f["order"] for f in flow_records))) + order_colors = generate_distinct_colors(len(unique_orders)) + order_color_map = {o: c for o, c in zip(unique_orders, order_colors)} + + # --- 3. Build Graph --- + for flow in flow_records: + u, tu, v, tv = flow["u"], flow["t_u"], flow["v"], flow["t_v"] + order = flow["order"] + max_time_step = max(max_time_step, tu, tv) + + source_id, target_id = f"{u},{tu}", f"{v},{tv}" + + # Add Nodes + for node_name, time, n_id in [(u, tu, source_id), (v, tv, target_id)]: + if n_id not in added_nodes: + key, idx, t, lbl = _get_node_info(node_name, time) + style = NODE_STYLES.get(key, {"level": 8}) + y_pos = (t * Y_TIME_SPACING) + (idx * Y_UNIT_SPACING) + if key == "source": + y_pos = 50 + + node_col = ( + UNIT_COLORS[idx % len(UNIT_COLORS)] + if key != "source" + else "#FFD700" + ) + if key == "magic": + node_col = "#A9A9A9" + + net.add_node( + n_id, + label=lbl, + level=style["level"], + x=style["level"] * X_SPACING, + y=y_pos, + color=node_col, + shape="circle", + physics=False, + ) + added_nodes.add(n_id) + + # Edge Logic + pair_key = tuple(sorted((source_id, target_id))) + edge_counts[pair_key] = edge_counts.get(pair_key, 0) + 1 + count = edge_counts[pair_key] + + smooth_opts = False + if count > 1: + smooth_opts = { + "type": "curvedCW", + "roundness": 0.2 * (count // 2) * (1 if count % 2 == 0 else -1), + } + + # Get Quantity for Hover + flow_qty = order_qty_map.get(order, "?") + + # Determine Color/Title + if mode == "compare": + color = flow.get("color", "grey") + width = flow.get("width", 2) + title = f"{flow['status']}\nOrder: {order}\nQty: {flow_qty}\n{u} -> {v}" + dashes = True if "magic" in str(u) or "magic" in str(v) else False + else: + color = order_color_map.get(order, "black") + width = 2 + dashes = True if "magic" in str(u) or "magic" in str(v) else False + if dashes: + color = "black" + title = f"Order: {order}\nQty: {flow_qty}\n{u} -> {v}" + + net.add_edge( + source_id, + target_id, + title=title, + color=color, + width=width, + dashes=dashes, + smooth=smooth_opts, + ) + + # --- 4. Visual Elements --- + # Time Guides + for t in range(max_time_step + 1): + y = t * Y_TIME_SPACING + net.add_node( + f"g_{t}_L", x=-100, y=y, label=f"t={t}", shape="text", physics=False + ) + net.add_node( + f"g_{t}_R", x=8 * X_SPACING, y=y, label=" ", shape="text", physics=False + ) + net.add_edge( + f"g_{t}_L", f"g_{t}_R", color="lightgrey", dashes=True, physics=False + ) + + # Legends + lx = (max([s["level"] for s in NODE_STYLES.values()]) + 1) * X_SPACING + ly = 0 + + if mode == "compare": + net.add_node( + "L_Title", + label="-- Comparison --", + x=lx, + y=ly, + shape="text", + font={"size": 20}, + physics=False, + ) + for lbl, col in [ + ("Match", "green"), + ("Classical Only", "blue"), + ("QUBO Only", "red"), + ]: + ly += 50 + net.add_node( + f"L_{lbl}", label=lbl, color=col, x=lx, y=ly, shape="box", physics=False + ) + else: + net.add_node( + "L_Title", + label="-- Orders --", + x=lx, + y=ly, + shape="text", + font={"size": 20}, + physics=False, + ) + for order, color in order_color_map.items(): + ly += 50 + net.add_node( + f"L_{order}", + label=f"Order {order}", + color=color, + x=lx, + y=ly, + shape="box", + physics=False, + ) + + # Demand Info (Printed on Canvas) + if demand_records is not None and not demand_records.empty: + ly += 80 + net.add_node( + "D_Title", + label="-- Demand --", + x=lx, + y=ly, + shape="text", + font={"size": 20}, + physics=False, + ) + ly += 40 + + for i, row in demand_records.iterrows(): + # Robust Extraction + o_val = row.get("o", row.iloc[0] if len(row) > 0 else "?") + p_val = row.get("p", row.iloc[1] if len(row) > 1 else None) + t_val = row.get("t", row.iloc[2] if len(row) > 2 else "?") + v_val = row.get("v", row.iloc[3] if len(row) > 3 else "?") + qty = row.get("value", row.iloc[4] if len(row) > 4 else "?") + + if p_val: + label_text = f"Order {o_val} ({p_val}): {qty}\nat {v_val} by {t_val}" + else: + label_text = f"Order {o_val}: {qty}\nat {v_val} by {t_val}" + + net.add_node( + f"Dem_{i}", + label=label_text, + x=lx, + y=ly, + shape="text", + font={"size": 14, "align": "left"}, + physics=False, + ) + ly += 50 + + net.set_options('{"physics": {"enabled": false}}') + print(f"Generating plot: {filename}") + net.show(filename) \ No newline at end of file diff --git a/solvers/gams/unsplittable-mcf/requirements.txt b/solvers/gams/unsplittable-mcf/requirements.txt new file mode 100644 index 00000000..bfaeb88b --- /dev/null +++ b/solvers/gams/unsplittable-mcf/requirements.txt @@ -0,0 +1,3 @@ +# This file describes the python package requirements for the GAMS unsplittable-mcf solver +gamspy == 1.23.1 +pyvis == 0.3.2 \ No newline at end of file diff --git a/solvers/gams/unsplittable-mcf/solver_classical.py b/solvers/gams/unsplittable-mcf/solver_classical.py new file mode 100644 index 00000000..55457e8f --- /dev/null +++ b/solvers/gams/unsplittable-mcf/solver_classical.py @@ -0,0 +1,29 @@ +""" +Classical CPLEX Solver for Unsplittable Multi Commodity Flow Problem. + +This solver uses GAMSPy to build the MCF model and solves it using CPLEX. +""" + +import sys +from unsplittable_mcf import UnsplittableMCF + +arg_count = len(sys.argv) - 1 +if arg_count != 2: + raise TypeError( + f"This script expects exactly 2 arguments but got {arg_count}. " + "Input file (argument 1) and output file (argument 2)." + ) + +input_path = sys.argv[1] +output_path = sys.argv[2] + +print(f"Loading instance from {input_path}...") + +# Create model from instance file +model = UnsplittableMCF(instance_file=input_path) + +print("Building equations and model...") +model.build_equations_and_model() + +print("\n--- Solving Classical MIP with CPLEX ---") +model.solve_classical(solver="CPLEX", output_html=output_path) diff --git a/solvers/gams/unsplittable-mcf/solver_lp_export.py b/solvers/gams/unsplittable-mcf/solver_lp_export.py new file mode 100644 index 00000000..f6c0ff1d --- /dev/null +++ b/solvers/gams/unsplittable-mcf/solver_lp_export.py @@ -0,0 +1,30 @@ +""" +LP Export for Unsplittable Multi Commodity Flow Problem. + +This solver exports the MCF model to LP format (QUBO reformulation). +""" + +import sys +from unsplittable_mcf import UnsplittableMCF + +arg_count = len(sys.argv) - 1 +if arg_count != 2: + raise TypeError( + f"This script expects exactly 2 arguments but got {arg_count}. " + "Input file (argument 1) and output file (argument 2)." + ) + +input_path = sys.argv[1] +output_path = sys.argv[2] + +print(f"Loading instance from {input_path}...") + +# Create model from instance file +model = UnsplittableMCF(instance_file=input_path) + +print("Building equations and model...") +model.build_equations_and_model() + +# Export to LP format (QUBO reformulation) +print(f"\n--- Exporting to LP format: {output_path} ---") +model.export_lp(output_path) diff --git a/solvers/gams/unsplittable-mcf/unsplittable_mcf.py b/solvers/gams/unsplittable-mcf/unsplittable_mcf.py new file mode 100644 index 00000000..b930660e --- /dev/null +++ b/solvers/gams/unsplittable-mcf/unsplittable_mcf.py @@ -0,0 +1,700 @@ +import json +import logging +import os + +import gamspy as gp +import numpy as np +import pandas as pd + + +class UnsplittableMCF: + DEFAULT_CONFIG = { + "N_PRODUCTS": 1, + "N_TIME_STEPS": 4, + "N_ORDERS": 1, + "N_FACTORIES": 1, + "N_SORT": 1, + "N_DIEBANK": 1, + "N_ASSEMBLY": 1, + "N_TEST": 1, + "N_DISTRIBUTION": 1, + "N_DEMAND": 1, + "MIN_CYCLE_TIME": 0, + "MAX_CYCLE_TIME": 1, + "MIN_CAPACITY": 10, + "MAX_CAPACITY": 15, + "MIN_LOAD_FACTOR": 1, + "MAX_LOAD_FACTOR": 2, + "PENALTY_DELAY": 5, + "PENALTY_SLACK": 40, + "PENALTY_CONSTANT": 50, + } + + def __init__(self, config_file="config.txt", instance_file=None): + self.m = gp.Container() + self.instance_data = {} + + if instance_file and os.path.exists(instance_file): + with open(instance_file, "r") as f: + self.instance_data = json.load(f) + + self.config = self.DEFAULT_CONFIG.copy() + self.config.update(self.instance_data.get("config", {})) + + self._build_base_symbols() + self._populate_from_data() + + elif config_file and os.path.exists(config_file): + self.config = self._read_config(config_file) + self._build_base_symbols() + self._generate_random_data() + + else: + raise FileNotFoundError( + "Must provide either a valid config_file or instance_file." + ) + + self._derive_network_parameters() + + def _read_config(self, filename): + cfg = self.DEFAULT_CONFIG.copy() + + with open(filename, "r") as f: + for line in f: + line = line.strip() + if line and not line.startswith("#") and "=" in line: + key, val = line.split("=", 1) + clean_key = key.strip() + + if clean_key not in self.DEFAULT_CONFIG: + print(f"Warning: Ignoring unknown config key '{clean_key}'") + continue + + try: + cfg[clean_key] = int(val.strip()) + except ValueError: + cfg[clean_key] = val.strip() + return cfg + + def _build_base_symbols(self): + m = self.m + c = self.config + + self.p = gp.Set(m, "p", records=[f"p{i}" for i in range(c["N_PRODUCTS"])]) + self.t = gp.Set(m, "t", records=[f"t{i}" for i in range(c["N_TIME_STEPS"])]) + self.o = gp.Set(m, "o", records=[f"o{i}" for i in range(c["N_ORDERS"])]) + + self.v = gp.Set( + m, + "v", + records=( + ["v_source", "v_magic"] + + [f"v_fac{i}" for i in range(c["N_FACTORIES"])] + + [f"v_sort{i}" for i in range(c["N_SORT"])] + + [f"v_db{i}" for i in range(c["N_DIEBANK"])] + + [f"v_asm{i}" for i in range(c["N_ASSEMBLY"])] + + [f"v_test{i}" for i in range(c["N_TEST"])] + + [f"v_dc{i}" for i in range(c["N_DISTRIBUTION"])] + ), + description="nodes", + ) + + v, t, o, p = self.v, self.t, self.o, self.p + + # Alias + self.v1 = gp.Alias(m, "v1", alias_with=v) + self.v2 = gp.Alias(m, "v2", alias_with=v) + self.t1 = gp.Alias(m, "t1", alias_with=t) + self.t2 = gp.Alias(m, "t2", alias_with=t) + + # simple network nodes + self.v_source = gp.Set(m, "v_source", domain=v, records=["v_source"]) + self.v_magic = gp.Set(m, "v_magic", domain=v, records=["v_magic"]) + + self.v_factory = gp.Set( + m, + "v_factory", + domain=v, + records=[f"v_fac{i}" for i in range(c["N_FACTORIES"])], + ) + self.v_sort = gp.Set( + m, + "v_sort", + domain=v, + records=[f"v_sort{i}" for i in range(c["N_SORT"])], + ) + self.v_diebank = gp.Set( + m, + "v_diebank", + domain=v, + records=[f"v_db{i}" for i in range(c["N_DIEBANK"])], + ) + self.v_assembly = gp.Set( + m, + "v_assembly", + domain=v, + records=[f"v_asm{i}" for i in range(c["N_ASSEMBLY"])], + ) + self.v_test = gp.Set( + m, + "v_test", + domain=v, + records=[f"v_test{i}" for i in range(c["N_TEST"])], + ) + self.v_distribution = gp.Set( + m, + "v_distribution", + domain=v, + records=[f"v_dc{i}" for i in range(c["N_DISTRIBUTION"])], + ) + + # edges + self.e = gp.Set(m, "e", domain=[v, t, v, t], description="directed TEN arcs") + + # edges with corresponding orders + self.eo = gp.Set( + m, + "eo", + domain=[v, t, v, t, o], + description="directed TEN arcs that can be used for order o", + ) + self.eo_delay = gp.Set( + m, + "eo_delay", + domain=[v, t, v, t, o], + description="directed TEN arcs for delayed delivery for order o", + ) + self.eo_slack = gp.Set( + m, + "eo_slack", + domain=[v, t, v, t, o], + description="directed TEN arcs for unsatisfied demand for order o", + ) + + # mappings + self.o_map = gp.Set( + m, + "o_map", + domain=[o, p, t, self.v_distribution], + description="order-product-time-distribution_center mapping", + ) + self.o_map2 = gp.Set( + m, + "o_map2", + domain=[o, t, self.v_distribution], + description="order-time-distribution_center mapping", + ) + self.o_v_distribution = gp.Set( + m, + "o_v_distribution", + domain=[o, self.v_distribution], + description="order to distribution center mapping", + ) + self.o_t = gp.Set( + m, + "o_t", + domain=[o, t], + description="order to time step mapping", + ) + self.o_p = gp.Set( + m, + "o_p", + domain=[o, p], + description="order to product mapping", + ) + + # Parameters + self.demand = gp.Parameter(m, "demand", domain=[o, p, t, v]) + self.demand_o = gp.Parameter( + m, "demand_o", domain=[o, t, v], description="demand of order o" + ) + self.total_demand = gp.Parameter( + m, "total_demand", domain=o, description="total demand of order o" + ) + + self.cycle_time = gp.Parameter( + m, "cycle_time", domain=v, description="cycle time at node v" + ) + self.capacity = gp.Parameter( + m, "capacity", domain=v, description="capacity of node v" + ) + self.load_factor = gp.Parameter( + m, + "load_factor", + domain=[v, p], + description="capacity load factor of product p at node v", + ) + + self.cost_delay = gp.Parameter( + m, + "cost_delay", + domain=[v, t, v, t, o], + description="cost for a delayed order", + ) + self.cost_slack = gp.Parameter( + m, + "cost_slack", + domain=[v, t, v, t, o], + description="cost for unsatisfied orders", + ) + + def _generate_random_data(self): + """Generates ONLY the core base data that will be saved in the JSON.""" + np.random.seed(42) + c = self.config + v, t, o, p, e = self.v, self.t, self.o, self.p, self.e + + ( + v_source, + v_factory, + v_sort, + v_diebank, + v_assembly, + v_test, + v_distribution, + ) = ( + self.v_source, + self.v_factory, + self.v_sort, + self.v_diebank, + self.v_assembly, + self.v_test, + self.v_distribution, + ) + v1, v2, t1, t2 = self.v1, self.v2, self.t1, self.t2 + + p_list = p.records["uni"].tolist() + t_list = t.records["uni"].tolist() + v_dist_list = v_distribution.records["v"].tolist() + + demand_records = [] + for rec_o in o.records["uni"]: + p_choice = np.random.choice(p_list) + t_choice = np.random.choice(t_list) + v_choice = np.random.choice(v_dist_list) + val = np.random.randint(1, 5) * c["N_DEMAND"] + demand_records.append((rec_o, p_choice, t_choice, v_choice, val)) + + self.demand.setRecords(demand_records) + + self.cycle_time[v_factory] = np.random.randint( + c["MIN_CYCLE_TIME"], c["MAX_CYCLE_TIME"] + 1 + ) + self.cycle_time[v_sort] = np.random.randint( + c["MIN_CYCLE_TIME"], c["MAX_CYCLE_TIME"] + 1 + ) + self.cycle_time[v_diebank] = 0 + self.cycle_time[v_assembly] = np.random.randint( + c["MIN_CYCLE_TIME"], c["MAX_CYCLE_TIME"] + 1 + ) + self.cycle_time[v_test] = np.random.randint( + c["MIN_CYCLE_TIME"], c["MAX_CYCLE_TIME"] + 1 + ) + self.cycle_time[v_distribution] = 0 + + self.capacity[v_factory] = np.random.randint( + c["MIN_CAPACITY"], c["MAX_CAPACITY"] + 1 + ) + self.capacity[v_sort] = np.random.randint( + c["MIN_CAPACITY"], c["MAX_CAPACITY"] + 1 + ) + self.capacity[v_diebank] = gp.SpecialValues.POSINF + self.capacity[v_assembly] = np.random.randint( + c["MIN_CAPACITY"], c["MAX_CAPACITY"] + 1 + ) + self.capacity[v_test] = np.random.randint( + c["MIN_CAPACITY"], c["MAX_CAPACITY"] + 1 + ) + self.capacity[v_distribution] = gp.SpecialValues.POSINF + + self.load_factor[v_factory, p] = np.random.randint( + c["MIN_LOAD_FACTOR"], c["MAX_LOAD_FACTOR"] + 1 + ) + self.load_factor[v_sort, p] = np.random.randint( + c["MIN_LOAD_FACTOR"], c["MAX_LOAD_FACTOR"] + 1 + ) + self.load_factor[v_diebank, p] = 0 + self.load_factor[v_assembly, p] = np.random.randint( + c["MIN_LOAD_FACTOR"], c["MAX_LOAD_FACTOR"] + 1 + ) + self.load_factor[v_test, p] = np.random.randint( + c["MIN_LOAD_FACTOR"], c["MAX_LOAD_FACTOR"] + 1 + ) + self.load_factor[v_distribution, p] = 0 + + # graph structure is not really random, but will bes saved in json + e[v_source, t1, v_factory, t2].where[t1.first] = True + e[v_factory, t1, v_sort, t2].where[ + gp.Ord(t1) + self.cycle_time[v_factory] == gp.Ord(t2) + ] = True + e[v_sort, t1, v_diebank, t2].where[ + (gp.Ord(t1) + self.cycle_time[v_sort] == gp.Ord(t2)) + & gp.Sum(e[v, t, v_sort, t1], 1) + ] = True + e[v_diebank, t1, v_diebank, t2].where[ + (gp.Ord(t1) + 1 == gp.Ord(t2)) & gp.Sum(e[v, t, v_diebank, t1], 1) + ] = True + e[v_diebank, t1, v_assembly, t2].where[ + (gp.Ord(t1) + self.cycle_time[v_diebank] == gp.Ord(t2)) + & gp.Sum(e[v, t, v_diebank, t1], 1) + ] = True + e[v_assembly, t1, v_test, t2].where[ + (gp.Ord(t1) + self.cycle_time[v_assembly] == gp.Ord(t2)) + & gp.Sum(e[v, t, v_assembly, t1], 1) + ] = True + e[v_test, t1, v_distribution, t2].where[ + (gp.Ord(t1) + self.cycle_time[v_test] == gp.Ord(t2)) + & gp.Sum(e[v, t, v_test, t1], 1) + ] = True + e[v_distribution, t1, v_distribution, t2].where[ + (gp.Ord(t1) + 1 == gp.Ord(t2)) + ] = True + + self.eo[v1, t1, v2, t2, o].where[e[v1, t1, v2, t2]] = True + + print("Random base data successfully generated.") + + def _populate_from_data(self): + symbols_to_load = [ + "cycle_time", + "capacity", + "load_factor", + "demand", + "eo", + ] + + for sym in symbols_to_load: + if sym in self.instance_data: + records_df = pd.DataFrame(self.instance_data[sym]) + records_df = records_df.replace(1e100, gp.SpecialValues.POSINF) + gams_obj = getattr(self, sym) + gams_obj.setRecords(records_df) + + print("Base data successfully populated from JSON.") + + def _derive_network_parameters(self): + c = self.config + t, o, p = self.t, self.o, self.p + v1, v2, t1, t2 = self.v1, self.v2, self.t1, self.t2 + v_distribution, v_magic = self.v_distribution, self.v_magic + + self.o_map[o, p, t, v_distribution].where[ + self.demand[o, p, t, v_distribution] + ] = True + + self.o_map2[o, t, v_distribution].where[ + gp.Sum(p, self.o_map[o, p, t, v_distribution]) + ] = True + self.o_v_distribution[o, v_distribution].where[ + gp.Sum((p, t), self.o_map[o, p, t, v_distribution]) + ] = True + self.o_t[o, t].where[ + gp.Sum((p, v_distribution), self.o_map[o, p, t, v_distribution]) + ] = True + self.o_p[o, p].where[ + gp.Sum((t, v_distribution), self.o_map[o, p, t, v_distribution]) + ] = True + + self.e[v1, t1, v2, t2].where[gp.Sum(o, self.eo[v1, t1, v2, t2, o])] = True + + self.demand_o[o, t, v_distribution].where[self.o_map2[o, t, v_distribution]] = ( + gp.Sum(p, self.demand[o, p, t, v_distribution]) + ) + self.total_demand[o] = gp.Sum( + (p, t, v_distribution), self.demand[o, p, t, v_distribution] + ) + + self.eo_delay[v_distribution, t1, v_distribution, t2, o].where[ + self.o_t[o, t2] + & self.o_v_distribution[o, v_distribution] + & (gp.Ord(t1) > gp.Ord(t2)) + ] = True + + self.eo_slack[v_magic, t, v_distribution, t, o].where[ + self.o_t[o, t] & self.o_v_distribution[o, v_distribution] + ] = True + + self.cost_delay[v1, t1, v2, t2, o].where[self.eo_delay[v1, t1, v2, t2, o]] = ( + gp.Ord(t1) - gp.Ord(t2) + ) * c["PENALTY_DELAY"] + self.cost_slack[v1, t1, v2, t2, o].where[self.eo_slack[v1, t1, v2, t2, o]] = c[ + "PENALTY_SLACK" + ] + + def build_equations_and_model(self): + m = self.m + v, t, o, p, e = self.v, self.t, self.o, self.p, self.e + o_p = self.o_p + demand_o, total_demand = self.demand_o, self.total_demand + capacity, load_factor = ( + self.capacity, + self.load_factor, + ) + + v_source = self.v_source + eo_delay, eo_slack = ( + self.eo_delay, + self.eo_slack, + ) + cost_delay, cost_slack = self.cost_delay, self.cost_slack + + v1, v2, t1, t2 = self.v1, self.v2, self.t1, self.t2 + + # time expanded network nodes for all node and time steps that are in the graph + self.vt = gp.Set(m, "vt", domain=[v, t]) + self.vt[v, t].where[ + gp.Sum(self.e[v, t, v2, t2], 1) + gp.Sum(self.e[v1, t1, v, t], 1) > 0 + ] = True + + vt = self.vt + + # Variable + self.Y = gp.Variable( + m, + "Y", + type="binary", + domain=[v1, t1, v2, t2, o], + description="binary variable that decides if a arc is used for order o", + ) + Y = self.Y + + # Equations + eq_flow_balance = gp.Equation( + m, + "eq_flow_balance", + domain=[v, t, o], + description="flow conservation (includes demand satisfaction)", + ) + eq_capacity = gp.Equation( + m, + "eq_capacity", + domain=[v, t], + description="capacity constraint to restrict inflow", + ) + + obj = gp.Sum( + eo_delay[v1, t1, v2, t2, o], + Y[eo_delay] * total_demand[o] * cost_delay[eo_delay], + ) + gp.Sum( + eo_slack[v1, t1, v2, t2, o], + Y[eo_slack] * total_demand[o] * cost_slack[eo_slack], + ) + + # all in coming and out going flows need to be equal to the demand at that node + # demand is only not zero for the distribution centers + eq_flow_balance[v1, t1, o].where[vt[v1, t1] & ~v_source[v1]] = demand_o[ + o, t1, v1 + ] == gp.Sum(e[v2, t2, v1, t1], Y[e, o] * total_demand[o]) - gp.Sum( + e[v1, t1, v2, t2], Y[e, o] * total_demand[o] + ) + gp.Sum(eo_delay[v2, t2, v1, t1, o], Y[eo_delay] * total_demand[o]) - gp.Sum( + eo_delay[v1, t1, v2, t2, o], Y[eo_delay] * total_demand[o] + ) + gp.Sum(eo_slack[v2, t2, v1, t1, o], Y[eo_slack] * total_demand[o]) - gp.Sum( + eo_slack[v1, t1, v2, t2, o], Y[eo_slack] * total_demand[o] + ) + + eq_capacity[v1, t1].where[vt[v1, t1] & ~v_source[v1]] = ( + gp.Sum( + gp.Domain(e[v2, t2, v1, t1], o_p[o, p]), + Y[e, o] * total_demand[o] * load_factor[v1, p], + ) + <= capacity[v1] + ) + + self.mcf = gp.Model( + m, + "mcf", + problem="MIP", + equations=m.getEquations(), + sense=gp.Sense.MIN, + objective=obj, + ) + + print("GAMSPy Model successfully built.") + + def save_to_json(self, filename="instance_export.json"): + export_data = {"config": self.config} + + symbols_to_save = [ + "cycle_time", + "capacity", + "load_factor", + "demand", + "eo", + ] + + for sym in symbols_to_save: + gams_obj = getattr(self, sym) + if gams_obj.records is not None: + df = gams_obj.records.copy() + + if isinstance(gams_obj, gp.Set) and "element_text" in df.columns: + df = df.drop(columns=["element_text"]) + + # Map GAMSPy's infinity to 1e100 for the JSON export + df = df.replace([float("inf"), np.inf], 1e100) + + export_data[sym] = df.values.tolist() + + with open(filename, "w") as f: + json.dump(export_data, f, indent=4) + print(f"Instance saved to {filename}") + + def export_lp(self, filename="unsplittable_qubo.lp"): + import os + + from gamspy_qubo import Qubo + + if self.mcf is None: + raise ValueError("Model not built. Call build_model() before exporting.") + + print("\n--- Reformulating MIP to QUBO ---") + penalty = self.config["PENALTY_CONSTANT"] + + mcf_qubo = Qubo(self.mcf, name="unsplittable_qubo", penalty=penalty) + + print(f"--- Exporting QUBO to LP format: {filename} ---") + + abs_lp_path = os.path.abspath(filename) + + # Export the reformulated model using CPLEX. + # We expect a ResourceInterrupt Warning from the logger here, since the time limit is set to 0 + logger = logging.getLogger("MODEL") + logger.disabled = True + mcf_qubo.solve( + solver="CPLEX", + solver_options={ + "writelp": abs_lp_path, + "ResLim ": 0, + }, + options=gp.Options(generate_name_dict=False), + ) + logger.disabled = False + + + if os.path.exists(abs_lp_path): + print( + f"QUBO successfully exported to {abs_lp_path}. Ready for the external solver!" + ) + else: + print(f"Error: LP file was not found at {abs_lp_path}.") + + def solve_classical(self, solver=None, output_html="classical_routing.html"): + """Solves the standard MIP without QUBO reformulation and plots the solution.""" + from plot_unsplittable import plot_mcf + import pandas as pd + + if not hasattr(self, "mcf") or self.mcf is None: + raise ValueError("Model not built. Call build_model() before solving.") + + print("\n--- Solving Classical MIP ---") + self.mcf.solve(solver=solver) + + print("\n--- Classical Solve Results ---") + print(f"Model Status: {self.mcf.status}") + + if self.mcf.objective_value is not None: + print(f"Objective Value: {self.mcf.objective_value}") + + # Extract native solution + classical_flows = ( + self.Y.records.copy() if self.Y.records is not None else pd.DataFrame() + ) + + print("\n--- Generating Classical Plot ---") + plot_mcf( + filename=output_html, + flow_input=classical_flows, + demand_records=self.demand.records, + ) + print(f"Done! Open '{output_html}' in your browser.") + else: + print("No solution found.") + + def compare_and_plot( + self, json_path="solution.json", output_html="unsplittable_comparison.html" + ): + """Solves classically, injects the QUBO JSON solution, and plots the comparison.""" + import pandas as pd + from gamspy_qubo import Qubo + + from plot_unsplittable import plot_comparison + + if not hasattr(self, "mcf") or self.mcf is None: + raise ValueError("Model not built. Call build_model() before comparing.") + + print("\n--- 1. Solving Classical MIP ---") + self.mcf.solve(solver="CPLEX") + print(f"Classical Objective Value: {self.mcf.objective_value}") + + classical_flows = ( + self.Y.records.copy() if self.Y.records is not None else pd.DataFrame() + ) + + print("\n--- 2. Reformulating into QUBO ---") + penalty = self.config["PENALTY_CONSTANT"] + mcf_qubo = Qubo(self.mcf, name="unsplittable_qubo", penalty=penalty) + + print("\n--- 3. Injecting JSON Solution ---") + mcf_qubo.solve(solver="json", json_path=json_path) + + qubo_flows = ( + self.Y.records.copy() if self.Y.records is not None else pd.DataFrame() + ) + + print("\n--- 4. Generating Comparison Plot ---") + plot_comparison( + filename=output_html, + classical_input=classical_flows, + qubo_input=qubo_flows, + demand_records=self.demand.records, + ) + print(f"Done! Open '{output_html}' in your browser.") + + def plot_base_network(self, output_html="base_network.html"): + """Plots all possible permitted arcs in the generated Time-Expanded Network before solving.""" + from plot_unsplittable import plot_mcf + + if not hasattr(self, "eo") or self.eo.records is None: + raise ValueError( + "Network not generated. Initialize the class with data first." + ) + + print("\n--- Generating Base Network Plot ---") + # self.eo.records contains all allowed arcs [v, t, v, t, o] + plot_mcf( + filename=output_html, + flow_input=self.eo.records, + demand_records=self.demand.records, + ) + print(f"Done! Open '{output_html}' in your browser.") + + def plot_json_solution( + self, json_path="solution.json", output_html="json_only_solution.html" + ): + """Loads the QUBO JSON solution and plots only the quantum/external routing.""" + import pandas as pd + from gamspy_qubo import Qubo + + from plot_unsplittable import plot_mcf + + if not hasattr(self, "mcf") or self.mcf is None: + raise ValueError("Model not built. Call build_model() before plotting.") + + print("\n--- Reformulating into QUBO ---") + penalty = self.config["PENALTY_CONSTANT"] + mcf_qubo = Qubo(self.mcf, name="unsplittable_qubo", penalty=penalty) + + print(f"\n--- Injecting JSON Solution from {json_path} ---") + mcf_qubo.solve(solver="json", json_path=json_path) + + qubo_flows = ( + self.Y.records.copy() if self.Y.records is not None else pd.DataFrame() + ) + + print("\n--- Generating JSON-Only Plot ---") + plot_mcf( + filename=output_html, + flow_input=qubo_flows, + demand_records=self.demand.records, + ) + print(f"Done! Open '{output_html}' in your browser.") diff --git a/src/main/java/edu/kit/provideq/toolbox/unsplittablemcf/UnsplittableMcfConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/unsplittablemcf/UnsplittableMcfConfiguration.java new file mode 100644 index 00000000..0ec3b615 --- /dev/null +++ b/src/main/java/edu/kit/provideq/toolbox/unsplittablemcf/UnsplittableMcfConfiguration.java @@ -0,0 +1,105 @@ +package edu.kit.provideq.toolbox.unsplittablemcf; + +import edu.kit.provideq.toolbox.ResourceProvider; +import edu.kit.provideq.toolbox.exception.MissingExampleException; +import edu.kit.provideq.toolbox.meta.Problem; +import edu.kit.provideq.toolbox.meta.ProblemManager; +import edu.kit.provideq.toolbox.meta.ProblemType; +import edu.kit.provideq.toolbox.unsplittablemcf.solvers.GamsUnsplittableMcfClassicalSolver; +import edu.kit.provideq.toolbox.unsplittablemcf.solvers.GamsUnsplittableMcfQuboSolver; +import java.io.IOException; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Definition and registration of the Unsplittable Multi Commodity Flow problem. + */ +@Configuration +public class UnsplittableMcfConfiguration { + + /** + * An optimization problem: + * For a given time-expanded network with products, orders, and capacity constraints, + * find optimal routing paths to fulfill customer orders through a Time-Expanded Network + * without splitting an order's demand. + */ + public static final ProblemType UNSPLITTABLE_MCF = new ProblemType<>( + "UnsplittableMCF", + "An optimization problem: For a given time-expanded network with products, orders, " + + "and capacity constraints, find optimal routing paths to fulfill customer orders " + + "through a Time-Expanded Network without splitting an order's demand", + String.class, + String.class, + Map.ofEntries( + Map.entry("products", problem -> getValue(problem, "N_PRODUCTS")), + Map.entry("time_steps", problem -> getValue(problem, "N_TIME_STEPS")), + Map.entry("orders", problem -> getValue(problem, "N_ORDERS")), + Map.entry("factories", problem -> getValue(problem, "N_FACTORIES")), + Map.entry("distribution_centers", problem -> getValue(problem, "N_DISTRIBUTION")) + ) + ); + + /** + * Helper method to extract configuration values from JSON input. + */ + private static String getValue(String json, String key) { + try { + // Simple JSON parsing without external library dependency + int configStart = json.indexOf("\"config\""); + if (configStart == -1) return ""; + + int keyStart = json.indexOf("\"" + key + "\"", configStart); + if (keyStart == -1) return ""; + + int colonPos = json.indexOf(":", keyStart); + if (colonPos == -1) return ""; + + int valueStart = colonPos + 1; + while (valueStart < json.length() + && (json.charAt(valueStart) == ' ' || json.charAt(valueStart) == '\n')) { + valueStart++; + } + + int valueEnd = valueStart; + while (valueEnd < json.length() + && Character.isDigit(json.charAt(valueEnd))) { + valueEnd++; + } + + return json.substring(valueStart, valueEnd).trim(); + } catch (Exception e) { + return ""; + } + } + + @Bean + ProblemManager getUnsplittableMcfManager( + GamsUnsplittableMcfClassicalSolver classicalSolver, + GamsUnsplittableMcfQuboSolver lpExportSolver, + ResourceProvider resourceProvider + ) { + return new ProblemManager<>( + UNSPLITTABLE_MCF, + Set.of(classicalSolver, lpExportSolver), + loadExampleProblems(resourceProvider) + ); + } + + private Set> loadExampleProblems( + ResourceProvider resourceProvider) { + try { + var problemInputStream = Objects.requireNonNull( + getClass().getResourceAsStream("unsplittable_instance.json"), + "Example instance for UnsplittableMCF is unavailable!" + ); + var problem = new Problem<>(UNSPLITTABLE_MCF); + problem.setInput(resourceProvider.readStream(problemInputStream)); + return Set.of(problem); + } catch (IOException e) { + throw new MissingExampleException(UNSPLITTABLE_MCF, e); + } + } +} diff --git a/src/main/java/edu/kit/provideq/toolbox/unsplittablemcf/solvers/GamsUnsplittableMcfClassicalSolver.java b/src/main/java/edu/kit/provideq/toolbox/unsplittablemcf/solvers/GamsUnsplittableMcfClassicalSolver.java new file mode 100644 index 00000000..6e298aca --- /dev/null +++ b/src/main/java/edu/kit/provideq/toolbox/unsplittablemcf/solvers/GamsUnsplittableMcfClassicalSolver.java @@ -0,0 +1,68 @@ +package edu.kit.provideq.toolbox.unsplittablemcf.solvers; + +import edu.kit.provideq.toolbox.Solution; +import edu.kit.provideq.toolbox.meta.SolvingProperties; +import edu.kit.provideq.toolbox.meta.SubRoutineResolver; +import edu.kit.provideq.toolbox.process.ProcessRunner; +import edu.kit.provideq.toolbox.process.PythonProcessRunner; +import edu.kit.provideq.toolbox.unsplittablemcf.UnsplittableMcfConfiguration; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Component; +import reactor.core.publisher.Mono; + +/** + * Classical CPLEX solver for the {@link UnsplittableMcfConfiguration#UNSPLITTABLE_MCF} problem. + * This solver uses GAMSPy to build the MIP model and solves it using CPLEX. + */ +@Component +public class GamsUnsplittableMcfClassicalSolver extends GamsUnsplittableMcfSolver { + private final String scriptPath; + private final String venv; + private final ApplicationContext context; + + @Autowired + public GamsUnsplittableMcfClassicalSolver( + @Value("${path.gams.unsplittable-mcf-classical}") String scriptPath, + @Value("${venv.gams.unsplittable-mcf}") String venv, + ApplicationContext context) { + this.scriptPath = scriptPath; + this.venv = venv; + this.context = context; + } + + @Override + public String getName() { + return "(GAMS) CPLEX Classical MCF Solver"; + } + + @Override + public String getDescription() { + return "Solves the Unsplittable Multi Commodity Flow problem using GAMSPy with CPLEX. " + + "Builds a time-expanded network model and finds optimal flow routes minimizing " + + "delay and slack penalties."; + } + + @Override + public Mono> solve( + String input, + SubRoutineResolver subRoutineResolver, + SolvingProperties properties + ) { + var solution = new Solution<>(this); + + // Run the classical MCF solver via Python process + var processResult = context + .getBean(PythonProcessRunner.class, scriptPath, venv) + .withArguments( + ProcessRunner.INPUT_FILE_PATH, + ProcessRunner.OUTPUT_FILE_PATH + ) + .writeInputFile(input, "unsplittable-mcf.json") + .readOutputFile("output.html") + .run(getProblemType(), solution.getId()); + + return Mono.just(processResult.applyTo(solution)); + } +} diff --git a/src/main/java/edu/kit/provideq/toolbox/unsplittablemcf/solvers/GamsUnsplittableMcfQuboSolver.java b/src/main/java/edu/kit/provideq/toolbox/unsplittablemcf/solvers/GamsUnsplittableMcfQuboSolver.java new file mode 100644 index 00000000..f1abc5af --- /dev/null +++ b/src/main/java/edu/kit/provideq/toolbox/unsplittablemcf/solvers/GamsUnsplittableMcfQuboSolver.java @@ -0,0 +1,122 @@ +package edu.kit.provideq.toolbox.unsplittablemcf.solvers; + +import edu.kit.provideq.toolbox.Solution; +import edu.kit.provideq.toolbox.meta.SolvingProperties; +import edu.kit.provideq.toolbox.meta.SubRoutineDefinition; +import edu.kit.provideq.toolbox.meta.SubRoutineResolver; +import edu.kit.provideq.toolbox.process.ProcessRunner; +import edu.kit.provideq.toolbox.process.PythonProcessRunner; +import edu.kit.provideq.toolbox.qubo.QuboConfiguration; +import edu.kit.provideq.toolbox.unsplittablemcf.UnsplittableMcfConfiguration; +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Component; +import reactor.core.publisher.Mono; + +/** + * LP Export and QUBO solver for the {@link UnsplittableMcfConfiguration#UNSPLITTABLE_MCF} problem. + * This solver exports the MCF model to LP format (QUBO reformulation) and then solves it using + * the QUBO subroutine from the GAMS solvers. + */ +@Component +public class GamsUnsplittableMcfQuboSolver extends GamsUnsplittableMcfSolver { + private static final SubRoutineDefinition QUBO_SUBROUTINE = + new SubRoutineDefinition<>( + QuboConfiguration.QUBO, + "How should the QUBO be solved?", + true + ); + + private final String exportScriptPath; + private final String plotQuboSolutionScriptPath; + private final String venv; + private final ApplicationContext context; + + @Autowired + public GamsUnsplittableMcfQuboSolver( + @Value("${path.gams.unsplittable-mcf-lp-export}") String exportScriptPath, + @Value("${path.gams.unsplittable-mcf-plot_qubo_solution}") String plotQuboSolutionScriptPath, + @Value("${venv.gams.unsplittable-mcf}") String venv, + ApplicationContext context) { + this.exportScriptPath = exportScriptPath; + this.plotQuboSolutionScriptPath = plotQuboSolutionScriptPath; + this.venv = venv; + this.context = context; + } + + @Override + public String getName() { + return "U-MCF to QUBO Transformation"; + } + + @Override + public String getDescription() { + return "Solves the Unsplittable Multi Commodity Flow problem by transforming it to a " + + "QUBO problem and solving with a QUBO solver."; + } + + @Override + public List> getSubRoutines() { + return List.of(QUBO_SUBROUTINE); + } + + @Override + public Mono> solve( + String input, + SubRoutineResolver subRoutineResolver, + SolvingProperties properties + ) { + var solution = new Solution<>(this); + + // Run the LP exporter + var quboExportResult = context + .getBean(PythonProcessRunner.class, exportScriptPath, venv) + .withArguments( + ProcessRunner.INPUT_FILE_PATH, + ProcessRunner.OUTPUT_FILE_PATH + ) + .writeInputFile(input, "unsplittable-mcf.json") + .readOutputFile() + .run(getProblemType(), solution.getId()); + + if (!quboExportResult.success()) { + solution.setDebugData("LP export failed: " + + quboExportResult.errorOutput().orElse("Unknown error")); + solution.abort(); + return Mono.just(solution); + } + + var qubo = quboExportResult.output(); + if (qubo.isEmpty()) { + solution.setDebugData("LP export did not produce any output."); + solution.abort(); + return Mono.just(solution); + } + + // Call the QUBO subroutine + return subRoutineResolver.runSubRoutine(QUBO_SUBROUTINE, qubo.get()) + .map(quboSolution -> { + var plotQuboResult = context + .getBean(PythonProcessRunner.class, plotQuboSolutionScriptPath, venv) + .withArguments( + ProcessRunner.INPUT_FILE_PATH, + ProcessRunner.INPUT_FILE_PATH2, + ProcessRunner.OUTPUT_FILE_PATH + ) + .writeInputFile( + input, + "unsplittable-mcf.json", + ProcessRunner.INPUT_FILE_PATH) + .writeInputFile( + quboSolution.getSolutionData(), + "qubo-solution.json", + ProcessRunner.INPUT_FILE_PATH2) + .readOutputFile("output.html") + .run(getProblemType(), solution.getId()); + + return plotQuboResult.applyTo(solution); + }); + } +} diff --git a/src/main/java/edu/kit/provideq/toolbox/unsplittablemcf/solvers/GamsUnsplittableMcfSolver.java b/src/main/java/edu/kit/provideq/toolbox/unsplittablemcf/solvers/GamsUnsplittableMcfSolver.java new file mode 100644 index 00000000..cfefaffd --- /dev/null +++ b/src/main/java/edu/kit/provideq/toolbox/unsplittablemcf/solvers/GamsUnsplittableMcfSolver.java @@ -0,0 +1,15 @@ +package edu.kit.provideq.toolbox.unsplittablemcf.solvers; + +import edu.kit.provideq.toolbox.meta.ProblemSolver; +import edu.kit.provideq.toolbox.meta.ProblemType; +import edu.kit.provideq.toolbox.unsplittablemcf.UnsplittableMcfConfiguration; + +/** + * Base class for Unsplittable Multi Commodity Flow solvers. + */ +public abstract class GamsUnsplittableMcfSolver implements ProblemSolver { + @Override + public ProblemType getProblemType() { + return UnsplittableMcfConfiguration.UNSPLITTABLE_MCF; + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 24875c57..d9817304 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,123 @@ -# default spring profile, correct one will be set during runtime (see ToolboxServerApplication.java) # options: mac, windows, linux spring.profiles.active=linux springdoc.swagger-ui.operationsSorter=alpha springdoc.swagger-ui.tagsSorter=alpha working.directory=jobs examples.directory=examples springdoc.swagger-ui.path=/ # Solvers name.solvers=solvers # Non OS-specific solvers: (typically GAMS and Python) name.gams=gams path.gams=${name.solvers}/${name.gams} name.gams.max-cut=max-cut path.gams.max-cut=${path.gams}/${name.gams.max-cut}/maxcut.gms name.gams.sat=sat path.gams.sat=${path.gams}/${name.gams.sat}/sat.gms name.gams.qubo=qubo path.gams.qubo=${path.gams}/${name.gams.qubo}/solver_lp_qubo.py venv.gams.qubo=${name.solvers}_${name.gams}_${name.gams.qubo} name.qiskit=qiskit path.qiskit=${name.solvers}/${name.qiskit} name.qiskit.knapsack=knapsack path.qiskit.knapsack=${path.qiskit}/${name.qiskit.knapsack}/knapsack_qiskit.py venv.qiskit.knapsack=${name.solvers}_${name.qiskit}_${name.qiskit.knapsack} name.qiskit.materialsimulation=materialsimulation path.qiskit.materialsimulation=${path.qiskit}/${name.qiskit.materialsimulation}/material_simulation_qiskit.py venv.qiskit.materialsimulation=${name.solvers}_${name.qiskit}_${name.qiskit.materialsimulation} name.qiskit.max-cut=max-cut path.qiskit.max-cut=${path.qiskit}/${name.qiskit.max-cut}/maxCut_qiskit.py venv.qiskit.max-cut=${name.solvers}_${name.qiskit}_${name.qiskit.max-cut} name.qiskit.qubo=qubo path.qiskit.qubo=${path.qiskit}/${name.qiskit.qubo}/qubo_qiskit.py venv.qiskit.qubo=${name.solvers}_${name.qiskit}_${name.qiskit.qubo} name.cirq=cirq path.cirq=${name.solvers}/${name.cirq} name.cirq.max-cut=max-cut path.cirq.max-cut=${path.cirq}/${name.cirq.max-cut}/max_cut_cirq.py venv.cirq.max-cut=${name.solvers}_${name.cirq}_${name.cirq.max-cut} name.qrisp=qrisp path.qrisp=${name.solvers}/${name.qrisp} name.qrisp.vrp=vrp path.qrisp.vrp=${path.qrisp}/${name.qrisp.vrp}/grover.py venv.qrisp.vrp=${name.solvers}_${name.qrisp}_${name.qrisp.vrp} name.qrisp.qubo=qubo path.qrisp.qubo=${path.qrisp}/${name.qrisp.qubo}/qaoa.py venv.qrisp.qubo=${name.solvers}_${name.qrisp}_${name.qrisp.qubo} name.qrisp.sat=sat path.qrisp.sat.grover=${path.qrisp}/${name.qrisp.sat}/grover.py path.qrisp.sat.exact=${path.qrisp}/${name.qrisp.sat}/exact_grover.py venv.qrisp.sat=${name.solvers}_${name.qrisp}_${name.qrisp.sat} name.dwave=dwave path.dwave=${name.solvers}/${name.dwave} name.dwave.qubo=qubo path.dwave.qubo=${path.dwave}/${name.dwave.qubo}/main.py venv.dwave.qubo=${name.solvers}_${name.dwave}_${name.dwave.qubo} # Non OS-specific custom solvers: (solvers that are not part of a framework) name.custom=custom path.custom=${name.solvers}/${name.custom} name.custom.hs-knapsack=hs-knapsack path.custom.hs-knapsack=${path.custom}/${name.custom.hs-knapsack}/knapsack.py venv.custom.hs-knapsack=${name.solvers}_${name.custom}_${name.custom.hs-knapsack} name.custom.lkh=lkh path.custom.lkh=${path.custom}/${name.custom.lkh}/vrp_lkh.py venv.custom.lkh=${name.solvers}_${name.custom}_${name.custom.lkh} name.custom.berger-vrp=berger-vrp name.custom.sharp-sat-bruteforce=sharp-sat-bruteforce path.custom.sharp-sat-bruteforce=${path.custom}/${name.custom.sharp-sat-bruteforce}/exact-solution-counter.py venv.custom.sharp-sat-bruteforce=${name.solvers}_${name.custom}_${name.custom.sharp-sat-bruteforce} name.custom.sharp-sat-ganak=sharp-sat-ganak venv.custom.sharp-sat-ganak=${name.solvers}_${name.custom}_${name.custom.sharp-sat-ganak} # Demonstrators name.demonstrators=demonstrators name.demonstrators.cplex=cplex path.demonstrators.cplex=${name.demonstrators}/${name.demonstrators.cplex} name.demonstrators.cplex.mip=mip-solver path.demonstrators.cplex.mip=${path.demonstrators.cplex}/${name.demonstrators.cplex.mip}/mip-solver.py venv.demonstrators.cplex.mip=${name.demonstrators}_${name.demonstrators.cplex}_${name.demonstrators.cplex.mip} name.demonstrators.qiskit=qiskit path.demonstrators.qiskit=${name.demonstrators}/${name.demonstrators.qiskit} name.demonstrators.qiskit.molecule-energy=molecule-energy path.demonstrators.qiskit.molecule-energy=${path.demonstrators.qiskit}/${name.demonstrators.qiskit.molecule-energy}/molecule-energy.py venv.demonstrators.qiskit.molecule-energy=${name.demonstrators}_${name.demonstrators.qiskit}_${name.demonstrators.qiskit.molecule-energy} \ No newline at end of file +# default spring profile, correct one will be set during runtime (see ToolboxServerApplication.java) +# options: mac, windows, linux +spring.profiles.active=linux +springdoc.swagger-ui.operationsSorter=alpha +springdoc.swagger-ui.tagsSorter=alpha + +working.directory=jobs +examples.directory=examples +springdoc.swagger-ui.path=/ + +# Solvers +name.solvers=solvers + +# Non OS-specific solvers: (typically GAMS and Python) +name.gams=gams +path.gams=${name.solvers}/${name.gams} + +name.gams.max-cut=max-cut +path.gams.max-cut=${path.gams}/${name.gams.max-cut}/maxcut.gms + +name.gams.sat=sat +path.gams.sat=${path.gams}/${name.gams.sat}/sat.gms + +name.gams.qubo=qubo +path.gams.qubo=${path.gams}/${name.gams.qubo}/solver_lp_qubo.py +venv.gams.qubo=${name.solvers}_${name.gams}_${name.gams.qubo} + +name.gams.unsplittable-mcf=unsplittable-mcf +path.gams.unsplittable-mcf-classical=${path.gams}/${name.gams.unsplittable-mcf}/solver_classical.py +path.gams.unsplittable-mcf-lp-export=${path.gams}/${name.gams.unsplittable-mcf}/solver_lp_export.py +path.gams.unsplittable-mcf-plot_qubo_solution=${path.gams}/${name.gams.unsplittable-mcf}/plot_qubo_solution.py +venv.gams.unsplittable-mcf=${name.solvers}_${name.gams}_${name.gams.unsplittable-mcf} + +name.qiskit=qiskit +path.qiskit=${name.solvers}/${name.qiskit} + +name.qiskit.knapsack=knapsack +path.qiskit.knapsack=${path.qiskit}/${name.qiskit.knapsack}/knapsack_qiskit.py +venv.qiskit.knapsack=${name.solvers}_${name.qiskit}_${name.qiskit.knapsack} + +name.qiskit.materialsimulation=materialsimulation +path.qiskit.materialsimulation=${path.qiskit}/${name.qiskit.materialsimulation}/material_simulation_qiskit.py +venv.qiskit.materialsimulation=${name.solvers}_${name.qiskit}_${name.qiskit.materialsimulation} + +name.qiskit.max-cut=max-cut +path.qiskit.max-cut=${path.qiskit}/${name.qiskit.max-cut}/maxCut_qiskit.py +venv.qiskit.max-cut=${name.solvers}_${name.qiskit}_${name.qiskit.max-cut} + +name.qiskit.qubo=qubo +path.qiskit.qubo=${path.qiskit}/${name.qiskit.qubo}/qubo_qiskit.py +venv.qiskit.qubo=${name.solvers}_${name.qiskit}_${name.qiskit.qubo} + + +name.cirq=cirq +path.cirq=${name.solvers}/${name.cirq} + +name.cirq.max-cut=max-cut +path.cirq.max-cut=${path.cirq}/${name.cirq.max-cut}/max_cut_cirq.py +venv.cirq.max-cut=${name.solvers}_${name.cirq}_${name.cirq.max-cut} + + +name.qrisp=qrisp +path.qrisp=${name.solvers}/${name.qrisp} + +name.qrisp.vrp=vrp +path.qrisp.vrp=${path.qrisp}/${name.qrisp.vrp}/grover.py +venv.qrisp.vrp=${name.solvers}_${name.qrisp}_${name.qrisp.vrp} + +name.qrisp.qubo=qubo +path.qrisp.qubo=${path.qrisp}/${name.qrisp.qubo}/qaoa.py +venv.qrisp.qubo=${name.solvers}_${name.qrisp}_${name.qrisp.qubo} + +name.qrisp.sat=sat +path.qrisp.sat.grover=${path.qrisp}/${name.qrisp.sat}/grover.py +path.qrisp.sat.exact=${path.qrisp}/${name.qrisp.sat}/exact_grover.py +venv.qrisp.sat=${name.solvers}_${name.qrisp}_${name.qrisp.sat} + +name.dwave=dwave +path.dwave=${name.solvers}/${name.dwave} + +name.dwave.qubo=qubo +path.dwave.qubo=${path.dwave}/${name.dwave.qubo}/main.py +venv.dwave.qubo=${name.solvers}_${name.dwave}_${name.dwave.qubo} + + +# Non OS-specific custom solvers: (solvers that are not part of a framework) +name.custom=custom +path.custom=${name.solvers}/${name.custom} + +name.custom.hs-knapsack=hs-knapsack +path.custom.hs-knapsack=${path.custom}/${name.custom.hs-knapsack}/knapsack.py +venv.custom.hs-knapsack=${name.solvers}_${name.custom}_${name.custom.hs-knapsack} + +name.custom.lkh=lkh +path.custom.lkh=${path.custom}/${name.custom.lkh}/vrp_lkh.py +venv.custom.lkh=${name.solvers}_${name.custom}_${name.custom.lkh} + +name.custom.berger-vrp=berger-vrp + +name.custom.sharp-sat-bruteforce=sharp-sat-bruteforce +path.custom.sharp-sat-bruteforce=${path.custom}/${name.custom.sharp-sat-bruteforce}/exact-solution-counter.py +venv.custom.sharp-sat-bruteforce=${name.solvers}_${name.custom}_${name.custom.sharp-sat-bruteforce} + +name.custom.sharp-sat-ganak=sharp-sat-ganak +venv.custom.sharp-sat-ganak=${name.solvers}_${name.custom}_${name.custom.sharp-sat-ganak} + + +# Demonstrators +name.demonstrators=demonstrators + +name.demonstrators.cplex=cplex +path.demonstrators.cplex=${name.demonstrators}/${name.demonstrators.cplex} + +name.demonstrators.cplex.mip=mip-solver +path.demonstrators.cplex.mip=${path.demonstrators.cplex}/${name.demonstrators.cplex.mip}/mip-solver.py +venv.demonstrators.cplex.mip=${name.demonstrators}_${name.demonstrators.cplex}_${name.demonstrators.cplex.mip} + +name.demonstrators.qiskit=qiskit +path.demonstrators.qiskit=${name.demonstrators}/${name.demonstrators.qiskit} + +name.demonstrators.qiskit.molecule-energy=molecule-energy +path.demonstrators.qiskit.molecule-energy=${path.demonstrators.qiskit}/${name.demonstrators.qiskit.molecule-energy}/molecule-energy.py +venv.demonstrators.qiskit.molecule-energy=${name.demonstrators}_${name.demonstrators.qiskit}_${name.demonstrators.qiskit.molecule-energy} diff --git a/src/main/resources/edu/kit/provideq/toolbox/unsplittablemcf/unsplittable_instance.json b/src/main/resources/edu/kit/provideq/toolbox/unsplittablemcf/unsplittable_instance.json new file mode 100644 index 00000000..06e7995b --- /dev/null +++ b/src/main/resources/edu/kit/provideq/toolbox/unsplittablemcf/unsplittable_instance.json @@ -0,0 +1,221 @@ +{ + "config": { + "N_PRODUCTS": 1, + "N_TIME_STEPS": 2, + "N_ORDERS": 2, + "N_FACTORIES": 1, + "N_SORT": 1, + "N_DIEBANK": 1, + "N_ASSEMBLY": 1, + "N_TEST": 1, + "N_DISTRIBUTION": 1, + "N_DEMAND": 1, + "MIN_CYCLE_TIME": 0, + "MAX_CYCLE_TIME": 1, + "MIN_CAPACITY": 10, + "MAX_CAPACITY": 15, + "MIN_LOAD_FACTOR": 1, + "MAX_LOAD_FACTOR": 2, + "PENALTY_DELAY": 5, + "PENALTY_SLACK": 40, + "PENALTY_CONSTANT": 50 + }, + "cycle_time": [ + [ + "v_sort0", + 1.0 + ] + ], + "capacity": [ + [ + "v_fac0", + 11.0 + ], + [ + "v_sort0", + 12.0 + ], + [ + "v_db0", + 1e+100 + ], + [ + "v_asm0", + 12.0 + ], + [ + "v_test0", + 12.0 + ], + [ + "v_dc0", + 1e+100 + ] + ], + "load_factor": [ + [ + "v_fac0", + "p0", + 2.0 + ], + [ + "v_sort0", + "p0", + 1.0 + ], + [ + "v_asm0", + "p0", + 2.0 + ], + [ + "v_test0", + "p0", + 2.0 + ] + ], + "demand": [ + [ + "o0", + "p0", + "t0", + "v_dc0", + 4.0 + ], + [ + "o1", + "p0", + "t0", + "v_dc0", + 3.0 + ] + ], + "eo": [ + [ + "v_source", + "t0", + "v_fac0", + "t0", + "o0" + ], + [ + "v_source", + "t0", + "v_fac0", + "t0", + "o1" + ], + [ + "v_source", + "t0", + "v_fac0", + "t1", + "o0" + ], + [ + "v_source", + "t0", + "v_fac0", + "t1", + "o1" + ], + [ + "v_fac0", + "t0", + "v_sort0", + "t0", + "o0" + ], + [ + "v_fac0", + "t0", + "v_sort0", + "t0", + "o1" + ], + [ + "v_fac0", + "t1", + "v_sort0", + "t1", + "o0" + ], + [ + "v_fac0", + "t1", + "v_sort0", + "t1", + "o1" + ], + [ + "v_sort0", + "t0", + "v_db0", + "t1", + "o0" + ], + [ + "v_sort0", + "t0", + "v_db0", + "t1", + "o1" + ], + [ + "v_db0", + "t1", + "v_asm0", + "t1", + "o0" + ], + [ + "v_db0", + "t1", + "v_asm0", + "t1", + "o1" + ], + [ + "v_asm0", + "t1", + "v_test0", + "t1", + "o0" + ], + [ + "v_asm0", + "t1", + "v_test0", + "t1", + "o1" + ], + [ + "v_test0", + "t1", + "v_dc0", + "t1", + "o0" + ], + [ + "v_test0", + "t1", + "v_dc0", + "t1", + "o1" + ], + [ + "v_dc0", + "t0", + "v_dc0", + "t1", + "o0" + ], + [ + "v_dc0", + "t0", + "v_dc0", + "t1", + "o1" + ] + ] +} \ No newline at end of file diff --git a/src/test/java/edu/kit/provideq/toolbox/api/UnsplittableMcfSolversTest.java b/src/test/java/edu/kit/provideq/toolbox/api/UnsplittableMcfSolversTest.java new file mode 100644 index 00000000..3abab6dd --- /dev/null +++ b/src/test/java/edu/kit/provideq/toolbox/api/UnsplittableMcfSolversTest.java @@ -0,0 +1,73 @@ +package edu.kit.provideq.toolbox.api; + +import static edu.kit.provideq.toolbox.qubo.QuboConfiguration.QUBO; +import static edu.kit.provideq.toolbox.unsplittablemcf.UnsplittableMcfConfiguration.UNSPLITTABLE_MCF; + +import edu.kit.provideq.toolbox.meta.ProblemManagerProvider; +import edu.kit.provideq.toolbox.meta.ProblemSolver; +import edu.kit.provideq.toolbox.qubo.solvers.GamsQuboSolver; +import java.time.Duration; +import java.util.stream.Stream; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.web.reactive.server.WebTestClient; + +/** + * Integration tests for Unsplittable Multi Commodity Flow solvers. + */ +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@SpringBootTest +@AutoConfigureMockMvc +class UnsplittableMcfSolversTest { + @Autowired + private WebTestClient client; + + @Autowired + private ProblemManagerProvider problemManagerProvider; + + @BeforeEach + void beforeEach() { + this.client = this.client.mutate() + .responseTimeout(Duration.ofSeconds(60)) // Longer timeout for MCF solving + .build(); + } + + @SuppressWarnings("OptionalGetWithoutIsPresent") + Stream provideArguments() { + var problemManager = problemManagerProvider.findProblemManagerForType(UNSPLITTABLE_MCF).get(); + var quboProblemManager = problemManagerProvider.findProblemManagerForType(QUBO).get(); + + // TODO: For now just allow the Gams Qubo solver until the Qubo solution format is standardized + var quboSolvers = quboProblemManager.getSolvers().stream() + .filter(GamsQuboSolver.class::isInstance) + .toList(); + + return ApiTestHelper.getAllArgumentCombinations(problemManager, quboSolvers) + .map(list -> Arguments.of(list.get(0), list.get(1), list.get(2))); + } + + @ParameterizedTest + @MethodSource("provideArguments") + void testUnsplittableMcfSolver(ProblemSolver solver, + String input, + ProblemSolver quboSolver) { + var problem = ApiTestHelper.createProblem(client, solver, input, UNSPLITTABLE_MCF); + + // Configure qubo solver for possible subroutines + for (SubProblemReferenceDto subProblem : problem.getSubProblems()) { + var subProblemTypeId = subProblem.getSubRoutine().getTypeId(); + for (String subProblemId : subProblem.getSubProblemIds()) { + ApiTestHelper.setProblemSolver(client, quboSolver, subProblemId, subProblemTypeId); + } + } + + problem = ApiTestHelper.trySolveFor(20, client, problem.getId(), UNSPLITTABLE_MCF); + ApiTestHelper.testSolution(problem); + } +} From 2e35f19a3a92e128685bfa01f052620757b1e883 Mon Sep 17 00:00:00 2001 From: Elscrux Date: Thu, 18 Jun 2026 17:24:55 +0200 Subject: [PATCH 109/137] feat: improve Gams Qubo solver description --- .../edu/kit/provideq/toolbox/qubo/solvers/GamsQuboSolver.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/edu/kit/provideq/toolbox/qubo/solvers/GamsQuboSolver.java b/src/main/java/edu/kit/provideq/toolbox/qubo/solvers/GamsQuboSolver.java index 425d6a44..960195df 100644 --- a/src/main/java/edu/kit/provideq/toolbox/qubo/solvers/GamsQuboSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/qubo/solvers/GamsQuboSolver.java @@ -38,7 +38,8 @@ public String getName() { @Override public String getDescription() { - return "A solver for QUBOs using a GAMS CPLEX solver."; + return "Solves QUBO problems by transforming it into a MIP," + + "which is then solved with CPLEX. Implementation is done in GAMS"; } @Override From 898d0afc8e18f717846a0cb6e869f3c61ba489d9 Mon Sep 17 00:00:00 2001 From: Elscrux Date: Fri, 19 Jun 2026 16:34:51 +0200 Subject: [PATCH 110/137] test: remove D-Wave Solver from test because it doesn't reliably produce correct results anymore which means we can guarantee that this test case succeeds https://github.com/ProvideQ/toolbox-server/pull/153#issuecomment-4752349803 --- src/test/java/edu/kit/provideq/toolbox/api/TspSolverTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/edu/kit/provideq/toolbox/api/TspSolverTest.java b/src/test/java/edu/kit/provideq/toolbox/api/TspSolverTest.java index f608d628..345ebd2f 100644 --- a/src/test/java/edu/kit/provideq/toolbox/api/TspSolverTest.java +++ b/src/test/java/edu/kit/provideq/toolbox/api/TspSolverTest.java @@ -77,7 +77,7 @@ void testLkhTspSolver() { private Stream quboSolvers() { return Stream.of( - Arguments.of(dwaveQuboSolver, "NAME : small sample"), + // Arguments.of(dwaveQuboSolver, "NAME : small sample"), Arguments.of(qrispQuboSolver, "NAME : VerySmallSample") ); } From 040815faa3f80f318f5f116d87c74ebe6f499c1f Mon Sep 17 00:00:00 2001 From: ekulos-code Date: Wed, 29 Jan 2025 15:15:59 +0100 Subject: [PATCH 111/137] Add basic structure for OpenQASM processing --- .../CircuitProcessingConfiguration.java | 39 +++++++++++++ .../solver/CircuitProcessingSolver.java | 12 ++++ .../solver/MoveToExecutionSolver.java | 58 +++++++++++++++++++ .../solver/MoveToMitigationSolver.java | 44 ++++++++++++++ .../solver/MoveToOptimizationSolver.java | 44 ++++++++++++++ .../solver/executor/ExecutionResult.java | 13 +++++ .../solver/executor/ExecutionSolver.java | 36 ++++++++++++ .../executor/ExecutorConfiguration.java | 32 ++++++++++ .../ErrorMitigationConfiguration.java | 34 +++++++++++ .../mitigation/ErrorMitigationSolver.java | 35 +++++++++++ .../OptimizationConfiguration.java | 32 ++++++++++ .../optimization/OptimizationSolver.java | 35 +++++++++++ 12 files changed, 414 insertions(+) create mode 100644 src/main/java/edu/kit/provideq/toolbox/circuit/processing/CircuitProcessingConfiguration.java create mode 100644 src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/CircuitProcessingSolver.java create mode 100644 src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/MoveToExecutionSolver.java create mode 100644 src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/MoveToMitigationSolver.java create mode 100644 src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/MoveToOptimizationSolver.java create mode 100644 src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/executor/ExecutionResult.java create mode 100644 src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/executor/ExecutionSolver.java create mode 100644 src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/executor/ExecutorConfiguration.java create mode 100644 src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/mitigation/ErrorMitigationConfiguration.java create mode 100644 src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/mitigation/ErrorMitigationSolver.java create mode 100644 src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/optimization/OptimizationConfiguration.java create mode 100644 src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/optimization/OptimizationSolver.java diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/CircuitProcessingConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/CircuitProcessingConfiguration.java new file mode 100644 index 00000000..193657ce --- /dev/null +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/CircuitProcessingConfiguration.java @@ -0,0 +1,39 @@ +package edu.kit.provideq.toolbox.circuit.processing; + +import edu.kit.provideq.toolbox.circuit.processing.solver.MoveToExecutionSolver; +import edu.kit.provideq.toolbox.circuit.processing.solver.MoveToMitigationSolver; +import edu.kit.provideq.toolbox.circuit.processing.solver.MoveToOptimizationSolver; +import edu.kit.provideq.toolbox.meta.Problem; +import edu.kit.provideq.toolbox.meta.ProblemManager; +import edu.kit.provideq.toolbox.meta.ProblemType; +import java.util.Set; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class CircuitProcessingConfiguration { + public static final ProblemType CIRCUIT_PROCESSING = new ProblemType<>( + "circuit-processing", + String.class, + String.class, + null + ); + + @Bean + ProblemManager getCircuitProcessingManager( + MoveToExecutionSolver moveToExecutionSolver, + MoveToOptimizationSolver moveToOptimizationSolver, + MoveToMitigationSolver moveToMitigationSolver + ) { + return new ProblemManager<>( + CIRCUIT_PROCESSING, + Set.of( + moveToExecutionSolver, + moveToOptimizationSolver, + moveToMitigationSolver + ), + // TODO: dummy problem + Set.of(new Problem<>(CIRCUIT_PROCESSING)) + ); + } +} diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/CircuitProcessingSolver.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/CircuitProcessingSolver.java new file mode 100644 index 00000000..7c978ad2 --- /dev/null +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/CircuitProcessingSolver.java @@ -0,0 +1,12 @@ +package edu.kit.provideq.toolbox.circuit.processing.solver; + +import edu.kit.provideq.toolbox.circuit.processing.CircuitProcessingConfiguration; +import edu.kit.provideq.toolbox.meta.ProblemSolver; +import edu.kit.provideq.toolbox.meta.ProblemType; + +public abstract class CircuitProcessingSolver implements ProblemSolver { + @Override + public ProblemType getProblemType() { + return CircuitProcessingConfiguration.CIRCUIT_PROCESSING; + } +} diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/MoveToExecutionSolver.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/MoveToExecutionSolver.java new file mode 100644 index 00000000..19d555c8 --- /dev/null +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/MoveToExecutionSolver.java @@ -0,0 +1,58 @@ +package edu.kit.provideq.toolbox.circuit.processing.solver; + +import edu.kit.provideq.toolbox.Solution; +import edu.kit.provideq.toolbox.SolutionStatus; +import edu.kit.provideq.toolbox.circuit.processing.solver.executor.ExecutionResult; +import edu.kit.provideq.toolbox.circuit.processing.solver.executor.ExecutorConfiguration; +import edu.kit.provideq.toolbox.meta.SolvingProperties; +import edu.kit.provideq.toolbox.meta.SubRoutineDefinition; +import edu.kit.provideq.toolbox.meta.SubRoutineResolver; +import org.springframework.stereotype.Component; +import reactor.core.publisher.Mono; + +import java.util.List; + +@Component +public class MoveToExecutionSolver extends CircuitProcessingSolver { + private static final SubRoutineDefinition EXECUTOR_SUBROUTINE = + new SubRoutineDefinition<>( + ExecutorConfiguration.EXECUTOR_CONFIG, + "Creates a execution solver" + ); + + @Override + public String getName() { + return "Execute QASM Code"; + } + + @Override + public String getDescription() { + return "Move QASM input to the executors"; + } + + @Override + public List> getSubRoutines() { + return List.of(EXECUTOR_SUBROUTINE); + } + + @Override + public Mono> solve( + String input, + SubRoutineResolver subRoutineResolver, + SolvingProperties properties + ) { + return subRoutineResolver.runSubRoutine(EXECUTOR_SUBROUTINE, input) + .map(executionResultSolution -> { + Solution solution = new Solution<>(this); + SolutionStatus status = executionResultSolution.getStatus(); + if (status == SolutionStatus.ERROR) { + solution.fail(); + solution.setDebugData("An error occurred while executing the circuit"); + return solution; + } + solution.complete(); + solution.setSolutionData(executionResultSolution.getSolutionData().toString()); + return solution; + }); + } +} diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/MoveToMitigationSolver.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/MoveToMitigationSolver.java new file mode 100644 index 00000000..4c70a353 --- /dev/null +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/MoveToMitigationSolver.java @@ -0,0 +1,44 @@ +package edu.kit.provideq.toolbox.circuit.processing.solver; + +import edu.kit.provideq.toolbox.Solution; +import edu.kit.provideq.toolbox.circuit.processing.solver.mitigation.ErrorMitigationConfiguration; +import edu.kit.provideq.toolbox.meta.SolvingProperties; +import edu.kit.provideq.toolbox.meta.SubRoutineDefinition; +import edu.kit.provideq.toolbox.meta.SubRoutineResolver; +import org.springframework.stereotype.Component; +import reactor.core.publisher.Mono; + +import java.util.List; + +@Component +public class MoveToMitigationSolver extends CircuitProcessingSolver { + private static final SubRoutineDefinition MITIGATOR_SUBROUTINE = + new SubRoutineDefinition<>( + ErrorMitigationConfiguration.MITIGATION_CONFIG, + "Creates a mitigation solver" + ); + + @Override + public String getName() { + return "Mitigate QASM Code Errors"; + } + + @Override + public String getDescription() { + return "Move QASM input to the error mitigators"; + } + + @Override + public List> getSubRoutines() { + return List.of(MITIGATOR_SUBROUTINE); + } + + @Override + public Mono> solve( + String input, + SubRoutineResolver subRoutineResolver, + SolvingProperties properties + ) { + return subRoutineResolver.runSubRoutine(MITIGATOR_SUBROUTINE, input); + } +} diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/MoveToOptimizationSolver.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/MoveToOptimizationSolver.java new file mode 100644 index 00000000..a6d938f6 --- /dev/null +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/MoveToOptimizationSolver.java @@ -0,0 +1,44 @@ +package edu.kit.provideq.toolbox.circuit.processing.solver; + +import edu.kit.provideq.toolbox.Solution; +import edu.kit.provideq.toolbox.circuit.processing.solver.optimization.OptimizationConfiguration; +import edu.kit.provideq.toolbox.meta.SolvingProperties; +import edu.kit.provideq.toolbox.meta.SubRoutineDefinition; +import edu.kit.provideq.toolbox.meta.SubRoutineResolver; +import org.springframework.stereotype.Component; +import reactor.core.publisher.Mono; + +import java.util.List; + +@Component +public class MoveToOptimizationSolver extends CircuitProcessingSolver { + private static final SubRoutineDefinition OPTIMIZER_SUBROUTINE = + new SubRoutineDefinition<>( + OptimizationConfiguration.OPTIMIZATION_CONFIG, + "Creates a optimization solver" + ); + + @Override + public String getName() { + return "Optimize QASM Code"; + } + + @Override + public String getDescription() { + return "Move QASM input to the optimizers"; + } + + @Override + public List> getSubRoutines() { + return List.of(OPTIMIZER_SUBROUTINE); + } + + @Override + public Mono> solve( + String input, + SubRoutineResolver subRoutineResolver, + SolvingProperties properties + ) { + return subRoutineResolver.runSubRoutine(OPTIMIZER_SUBROUTINE, input); + } +} diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/executor/ExecutionResult.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/executor/ExecutionResult.java new file mode 100644 index 00000000..4751c2b7 --- /dev/null +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/executor/ExecutionResult.java @@ -0,0 +1,13 @@ +package edu.kit.provideq.toolbox.circuit.processing.solver.executor; + +import java.util.Optional; + +public record ExecutionResult(Optional resultString, Optional circuit) { + public boolean hasResult() { + return resultString.isPresent(); + } + + public boolean hasCircuit() { + return circuit.isPresent(); + } +} diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/executor/ExecutionSolver.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/executor/ExecutionSolver.java new file mode 100644 index 00000000..9043bdea --- /dev/null +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/executor/ExecutionSolver.java @@ -0,0 +1,36 @@ +package edu.kit.provideq.toolbox.circuit.processing.solver.executor; + +import edu.kit.provideq.toolbox.Solution; +import edu.kit.provideq.toolbox.meta.ProblemSolver; +import edu.kit.provideq.toolbox.meta.ProblemType; +import edu.kit.provideq.toolbox.meta.SolvingProperties; +import edu.kit.provideq.toolbox.meta.SubRoutineResolver; +import org.springframework.stereotype.Component; +import reactor.core.publisher.Mono; + +import java.util.Optional; + +@Component +public class ExecutionSolver implements ProblemSolver { + @Override + public String getName() { + return "Execute OpenQASM circuit"; + } + + @Override + public String getDescription() { + return "Execute an OpenQASM circuit"; + } + + @Override + public Mono> solve(String input, SubRoutineResolver subRoutineResolver, SolvingProperties properties) { + var solution = new Solution<>(this); + solution.setSolutionData(new ExecutionResult(Optional.of(input), Optional.empty())); + return Mono.just(solution); + } + + @Override + public ProblemType getProblemType() { + return ExecutorConfiguration.EXECUTOR_CONFIG; + } +} diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/executor/ExecutorConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/executor/ExecutorConfiguration.java new file mode 100644 index 00000000..d93390f2 --- /dev/null +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/executor/ExecutorConfiguration.java @@ -0,0 +1,32 @@ +package edu.kit.provideq.toolbox.circuit.processing.solver.executor; + +import edu.kit.provideq.toolbox.meta.Problem; +import edu.kit.provideq.toolbox.meta.ProblemManager; +import edu.kit.provideq.toolbox.meta.ProblemType; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.Set; + +@Configuration +public class ExecutorConfiguration { + public static final ProblemType EXECUTOR_CONFIG = new ProblemType<>( + "circuit-processing-executor", + String.class, + ExecutionResult.class, + null + ); + + @Bean + ProblemManager getExecutorProblemManager( + ExecutionSolver executionSolver + ) { + return new ProblemManager<>( + EXECUTOR_CONFIG, + Set.of( + executionSolver + ), + Set.of(new Problem<>(EXECUTOR_CONFIG)) + ); + } +} diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/mitigation/ErrorMitigationConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/mitigation/ErrorMitigationConfiguration.java new file mode 100644 index 00000000..953efc12 --- /dev/null +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/mitigation/ErrorMitigationConfiguration.java @@ -0,0 +1,34 @@ +package edu.kit.provideq.toolbox.circuit.processing.solver.mitigation; + +import edu.kit.provideq.toolbox.circuit.processing.solver.executor.ExecutionResult; +import edu.kit.provideq.toolbox.circuit.processing.solver.executor.ExecutionSolver; +import edu.kit.provideq.toolbox.meta.Problem; +import edu.kit.provideq.toolbox.meta.ProblemManager; +import edu.kit.provideq.toolbox.meta.ProblemType; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.Set; + +@Configuration +public class ErrorMitigationConfiguration { + public static final ProblemType MITIGATION_CONFIG = new ProblemType<>( + "circuit-processing-mitigation", + String.class, + String.class, + null + ); + + @Bean + ProblemManager getMitigationProblemManager( + ErrorMitigationSolver errorMitigationSolver + ) { + return new ProblemManager<>( + MITIGATION_CONFIG, + Set.of( + errorMitigationSolver + ), + Set.of(new Problem<>(MITIGATION_CONFIG)) + ); + } +} diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/mitigation/ErrorMitigationSolver.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/mitigation/ErrorMitigationSolver.java new file mode 100644 index 00000000..5c70686a --- /dev/null +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/mitigation/ErrorMitigationSolver.java @@ -0,0 +1,35 @@ +package edu.kit.provideq.toolbox.circuit.processing.solver.mitigation; + +import edu.kit.provideq.toolbox.Solution; +import edu.kit.provideq.toolbox.meta.ProblemSolver; +import edu.kit.provideq.toolbox.meta.ProblemType; +import edu.kit.provideq.toolbox.meta.SolvingProperties; +import edu.kit.provideq.toolbox.meta.SubRoutineResolver; +import org.springframework.stereotype.Component; +import reactor.core.publisher.Mono; + +@Component +public class ErrorMitigationSolver implements ProblemSolver { + + @Override + public String getName() { + return "Mitigate Errors for OpenQASM"; + } + + @Override + public String getDescription() { + return "Run error mitigation strategies on an OpenQASM circuit"; + } + + @Override + public Mono> solve(String input, SubRoutineResolver subRoutineResolver, SolvingProperties properties) { + var solution = new Solution<>(this); + solution.setSolutionData(input); + return Mono.just(solution); + } + + @Override + public ProblemType getProblemType() { + return ErrorMitigationConfiguration.MITIGATION_CONFIG; + } +} diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/optimization/OptimizationConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/optimization/OptimizationConfiguration.java new file mode 100644 index 00000000..78dfbbfd --- /dev/null +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/optimization/OptimizationConfiguration.java @@ -0,0 +1,32 @@ +package edu.kit.provideq.toolbox.circuit.processing.solver.optimization; + +import edu.kit.provideq.toolbox.meta.Problem; +import edu.kit.provideq.toolbox.meta.ProblemManager; +import edu.kit.provideq.toolbox.meta.ProblemType; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.Set; + +@Configuration +public class OptimizationConfiguration { + public static final ProblemType OPTIMIZATION_CONFIG = new ProblemType<>( + "circuit-processing-optimization", + String.class, + String.class, + null + ); + + @Bean + ProblemManager getOptimizationProblemManager( + OptimizationSolver optimizationSolver + ) { + return new ProblemManager<>( + OPTIMIZATION_CONFIG, + Set.of( + optimizationSolver + ), + Set.of(new Problem<>(OPTIMIZATION_CONFIG)) + ); + } +} diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/optimization/OptimizationSolver.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/optimization/OptimizationSolver.java new file mode 100644 index 00000000..fcf51939 --- /dev/null +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/optimization/OptimizationSolver.java @@ -0,0 +1,35 @@ +package edu.kit.provideq.toolbox.circuit.processing.solver.optimization; + +import edu.kit.provideq.toolbox.Solution; +import edu.kit.provideq.toolbox.meta.ProblemSolver; +import edu.kit.provideq.toolbox.meta.ProblemType; +import edu.kit.provideq.toolbox.meta.SolvingProperties; +import edu.kit.provideq.toolbox.meta.SubRoutineResolver; +import org.springframework.stereotype.Component; +import reactor.core.publisher.Mono; + +@Component +public class OptimizationSolver implements ProblemSolver { + + @Override + public String getName() { + return "Optimize OpenQASM circuit"; + } + + @Override + public String getDescription() { + return "Run optimization algorithms on an OpenQASM circuit"; + } + + @Override + public Mono> solve(String input, SubRoutineResolver subRoutineResolver, SolvingProperties properties) { + var solution = new Solution<>(this); + solution.setSolutionData(input); + return Mono.just(solution); + } + + @Override + public ProblemType getProblemType() { + return OptimizationConfiguration.OPTIMIZATION_CONFIG; + } +} From f9a59e819d98df6d564936316bcc0a0c80d0ddec Mon Sep 17 00:00:00 2001 From: ekulos-code Date: Mon, 10 Mar 2025 12:27:27 +0100 Subject: [PATCH 112/137] Add execution and optimization solvers --- .../decompose_multi_cx_optimizer.py | 19 ++++ .../remove_redundancies_optimizer.py | 19 ++++ .../aer-noisy/aer_noisy_executor.py | 27 +++++ .../default-executor/default_executor.py | 18 +++ .../projectq-executor/projectq_executor.py | 17 +++ .../qulacs-executor/qulacs_executor.py | 18 +++ solvers/circuitprocessing/requirements.txt | 5 + .../CircuitProcessingConfiguration.java | 21 +++- .../solver/CircuitProcessingSolver.java | 7 ++ .../solver/MoveToExecutionSolver.java | 2 +- .../solver/executor/ExecutionSolver.java | 100 +++++++++++++++- .../optimization/OptimizationSolver.java | 107 +++++++++++++++++- 12 files changed, 350 insertions(+), 10 deletions(-) create mode 100644 solvers/circuitoptimizing/decompose-multi-cx/decompose_multi_cx_optimizer.py create mode 100644 solvers/circuitoptimizing/remove-redundancies/remove_redundancies_optimizer.py create mode 100644 solvers/circuitprocessing/aer-noisy/aer_noisy_executor.py create mode 100644 solvers/circuitprocessing/default-executor/default_executor.py create mode 100644 solvers/circuitprocessing/projectq-executor/projectq_executor.py create mode 100644 solvers/circuitprocessing/qulacs-executor/qulacs_executor.py create mode 100644 solvers/circuitprocessing/requirements.txt diff --git a/solvers/circuitoptimizing/decompose-multi-cx/decompose_multi_cx_optimizer.py b/solvers/circuitoptimizing/decompose-multi-cx/decompose_multi_cx_optimizer.py new file mode 100644 index 00000000..3d62f964 --- /dev/null +++ b/solvers/circuitoptimizing/decompose-multi-cx/decompose_multi_cx_optimizer.py @@ -0,0 +1,19 @@ +import sys +from pytket.qasm import circuit_from_qasm_str, circuit_to_qasm_str +from pytket.predicates import CompilationUnit +from pytket.passes import DecomposeMultiQubitsCX + + +input_circuit = sys.argv[1] + +try: + circuit = circuit_from_qasm_str(input_circuit) +except Exception as e: + print("Was not able to convert to OpenQASM: ", e) + sys.exit(1) + +pass1 = DecomposeMultiQubitsCX() +cu = CompilationUnit(circuit) +pass1.apply(cu) + +print(circuit_to_qasm_str(cu.circuit)) \ No newline at end of file diff --git a/solvers/circuitoptimizing/remove-redundancies/remove_redundancies_optimizer.py b/solvers/circuitoptimizing/remove-redundancies/remove_redundancies_optimizer.py new file mode 100644 index 00000000..58b57e68 --- /dev/null +++ b/solvers/circuitoptimizing/remove-redundancies/remove_redundancies_optimizer.py @@ -0,0 +1,19 @@ +import sys +from pytket.qasm import circuit_from_qasm_str, circuit_to_qasm_str +from pytket.predicates import CompilationUnit +from pytket.passes import RemoveRedundancies + + +input_circuit = sys.argv[1] + +try: + circuit = circuit_from_qasm_str(input_circuit) +except Exception as e: + print("Was not able to convert to OpenQASM: ", e) + sys.exit(1) + +pass1 = RemoveRedundancies() +cu = CompilationUnit(circuit) +pass1.apply(cu) + +print(circuit_to_qasm_str(cu.circuit)) \ No newline at end of file diff --git a/solvers/circuitprocessing/aer-noisy/aer_noisy_executor.py b/solvers/circuitprocessing/aer-noisy/aer_noisy_executor.py new file mode 100644 index 00000000..9db4439b --- /dev/null +++ b/solvers/circuitprocessing/aer-noisy/aer_noisy_executor.py @@ -0,0 +1,27 @@ +import sys +from pytket.qasm import circuit_from_qasm_str +from pytket.extensions.qiskit import AerBackend + +from qiskit_aer.noise import NoiseModel +from qiskit_aer.noise.errors import depolarizing_error + +input_circuit = sys.argv[1] +shots = sys.argv[2] + +try: + circuit = circuit_from_qasm_str(input_circuit) +except Exception as e: + print("Was not able to convert to OpenQASM: ", e) + sys.exit(1) + +# https://docs.quantinuum.com/tket/user-guide/manual/manual_noise.html +noise_model = NoiseModel() +noise_model.add_readout_error([[0.9, 0.1],[0.1, 0.9]], [0]) +noise_model.add_readout_error([[0.95, 0.05],[0.05, 0.95]], [1]) +noise_model.add_quantum_error(depolarizing_error(0.1, 2), ["cx"], [0, 1]) + +backend = AerBackend(noise_model) +c = backend.get_compiled_circuit(circuit) +handle = backend.process_circuit(c, n_shots=int(shots)) +counts = backend.get_result(handle).get_counts() +print(counts) \ No newline at end of file diff --git a/solvers/circuitprocessing/default-executor/default_executor.py b/solvers/circuitprocessing/default-executor/default_executor.py new file mode 100644 index 00000000..9ce98997 --- /dev/null +++ b/solvers/circuitprocessing/default-executor/default_executor.py @@ -0,0 +1,18 @@ +import sys +from pytket.qasm import circuit_from_qasm_str +from pytket.extensions.qiskit import AerBackend + +input_circuit = sys.argv[1] +shots = int(sys.argv[2]) + +try: + circuit = circuit_from_qasm_str(input_circuit) +except Exception as e: + print("Was not able to convert to OpenQASM: ", e) + sys.exit(1) + +backend = AerBackend() +c = backend.get_compiled_circuit(circuit) +handle = backend.process_circuit(c, n_shots=shots) +counts = backend.get_result(handle).get_counts() +print(counts) \ No newline at end of file diff --git a/solvers/circuitprocessing/projectq-executor/projectq_executor.py b/solvers/circuitprocessing/projectq-executor/projectq_executor.py new file mode 100644 index 00000000..8600c2bd --- /dev/null +++ b/solvers/circuitprocessing/projectq-executor/projectq_executor.py @@ -0,0 +1,17 @@ +import sys +from pytket.qasm import circuit_from_qasm_str +from pytket.extensions.projectq import ProjectQBackend + +input_circuit = sys.argv[1] +shots = int(sys.argv[2]) + +try: + circuit = circuit_from_qasm_str(input_circuit) +except Exception as e: + print("Was not able to convert to OpenQASM: ", e) + sys.exit(1) + +backend = ProjectQBackend() +handle = backend.process_circuit(circuit, n_shots=shots) +result = backend.get_result(handle) +print(result.get_shots()) \ No newline at end of file diff --git a/solvers/circuitprocessing/qulacs-executor/qulacs_executor.py b/solvers/circuitprocessing/qulacs-executor/qulacs_executor.py new file mode 100644 index 00000000..9652f6b0 --- /dev/null +++ b/solvers/circuitprocessing/qulacs-executor/qulacs_executor.py @@ -0,0 +1,18 @@ +import sys +from pytket.qasm import circuit_from_qasm_str +from pytket.extensions.qulacs import QulacsBackend + +input_circuit = sys.argv[1] +shots = int(sys.argv[2]) + +try: + circuit = circuit_from_qasm_str(input_circuit) +except Exception as e: + print("Was not able to convert to OpenQASM: ", e) + sys.exit(1) + +backend = QulacsBackend() +c = backend.get_compiled_circuit(circuit) +handle = backend.process_circuit(c, n_shots=shots) +counts = backend.get_result(handle).get_counts() +print(counts) \ No newline at end of file diff --git a/solvers/circuitprocessing/requirements.txt b/solvers/circuitprocessing/requirements.txt new file mode 100644 index 00000000..40a709cf --- /dev/null +++ b/solvers/circuitprocessing/requirements.txt @@ -0,0 +1,5 @@ +pytket +pytket-qiskit +pytket-pyquil +pytket-projectq +pytket-qulacs \ No newline at end of file diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/CircuitProcessingConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/CircuitProcessingConfiguration.java index 193657ce..2a72d788 100644 --- a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/CircuitProcessingConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/CircuitProcessingConfiguration.java @@ -25,6 +25,24 @@ ProblemManager getCircuitProcessingManager( MoveToOptimizationSolver moveToOptimizationSolver, MoveToMitigationSolver moveToMitigationSolver ) { + Problem demo = new Problem<>(CIRCUIT_PROCESSING); + demo.setInput(""" + OPENQASM 2.0; + include "qelib1.inc"; + qreg q[2]; + creg c[2]; + h q[0]; + cx q[0],q[1]; + measure q[0] -> c[0]; + measure q[1] -> c[1];"""); + Problem secondDemo = new Problem<>(CIRCUIT_PROCESSING); + secondDemo.setInput(""" + OPENQASM 2.0; + include "qelib1.inc"; + qreg q[3]; + crz(0.5) q[0], q[1]; + t q[2]; + cswap q[2], q[0], q[1];"""); return new ProblemManager<>( CIRCUIT_PROCESSING, Set.of( @@ -32,8 +50,7 @@ ProblemManager getCircuitProcessingManager( moveToOptimizationSolver, moveToMitigationSolver ), - // TODO: dummy problem - Set.of(new Problem<>(CIRCUIT_PROCESSING)) + Set.of(demo, secondDemo) ); } } diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/CircuitProcessingSolver.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/CircuitProcessingSolver.java index 7c978ad2..507ce9c6 100644 --- a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/CircuitProcessingSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/CircuitProcessingSolver.java @@ -3,8 +3,15 @@ import edu.kit.provideq.toolbox.circuit.processing.CircuitProcessingConfiguration; import edu.kit.provideq.toolbox.meta.ProblemSolver; import edu.kit.provideq.toolbox.meta.ProblemType; +import edu.kit.provideq.toolbox.meta.SubRoutineDefinition; public abstract class CircuitProcessingSolver implements ProblemSolver { + public static final SubRoutineDefinition CIRCUIT_PROCESSING_SUBROUTINE = + new SubRoutineDefinition<>( + CircuitProcessingConfiguration.CIRCUIT_PROCESSING, + "Creates a circuit processing solver" + ); + @Override public ProblemType getProblemType() { return CircuitProcessingConfiguration.CIRCUIT_PROCESSING; diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/MoveToExecutionSolver.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/MoveToExecutionSolver.java index 19d555c8..8d919d7c 100644 --- a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/MoveToExecutionSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/MoveToExecutionSolver.java @@ -47,7 +47,7 @@ public Mono> solve( SolutionStatus status = executionResultSolution.getStatus(); if (status == SolutionStatus.ERROR) { solution.fail(); - solution.setDebugData("An error occurred while executing the circuit"); + solution.setDebugData(executionResultSolution.getDebugData()); return solution; } solution.complete(); diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/executor/ExecutionSolver.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/executor/ExecutionSolver.java index 9043bdea..c96df7f1 100644 --- a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/executor/ExecutionSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/executor/ExecutionSolver.java @@ -5,13 +5,38 @@ import edu.kit.provideq.toolbox.meta.ProblemType; import edu.kit.provideq.toolbox.meta.SolvingProperties; import edu.kit.provideq.toolbox.meta.SubRoutineResolver; +import edu.kit.provideq.toolbox.meta.setting.SolverSetting; +import edu.kit.provideq.toolbox.meta.setting.basic.IntegerSetting; +import edu.kit.provideq.toolbox.meta.setting.basic.SelectSetting; +import edu.kit.provideq.toolbox.process.PythonProcessRunner; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Component; import reactor.core.publisher.Mono; +import java.util.List; import java.util.Optional; @Component public class ExecutionSolver implements ProblemSolver { + private static final String SETTING_NUMBER_OF_SHOTS = "Number of shots"; + private static final String SETTING_SELECT_SIMULATOR = "Selected Simulator"; + private static final int DEFAULT_NUMBER_OF_SHOTS = 1024; + private static final QuantumSimulator DEFAULT_SIMULATOR = QuantumSimulator.AER; + + private final String scriptPath; + private final ApplicationContext context; + + @Autowired + public ExecutionSolver( + @Value("${circuitprocessing.base.directory}") String scriptPath, + ApplicationContext context + ) { + this.context = context; + this.scriptPath = scriptPath; + } + @Override public String getName() { return "Execute OpenQASM circuit"; @@ -22,10 +47,51 @@ public String getDescription() { return "Execute an OpenQASM circuit"; } + @Override + public List getSolverSettings() { + return List.of( + new IntegerSetting( + SETTING_NUMBER_OF_SHOTS, + "The number of shots to run", + 1, + 10000, + DEFAULT_NUMBER_OF_SHOTS), + new SelectSetting<>( + SETTING_SELECT_SIMULATOR, + "The simulator to run the code with", + List.of(QuantumSimulator.values()), + QuantumSimulator.AER, + QuantumSimulator::getValue + ) + ); + } + @Override public Mono> solve(String input, SubRoutineResolver subRoutineResolver, SolvingProperties properties) { var solution = new Solution<>(this); - solution.setSolutionData(new ExecutionResult(Optional.of(input), Optional.empty())); + + int shotNumber = properties.getSetting(SETTING_NUMBER_OF_SHOTS) + .map(IntegerSetting::getValue) + .orElse(DEFAULT_NUMBER_OF_SHOTS); + + QuantumSimulator selectedSimulator = properties + .>getSetting(SETTING_SELECT_SIMULATOR) + .map(s -> s.getSelectedOptionT(QuantumSimulator::fromValue)) + .orElse(DEFAULT_SIMULATOR); + + var processResult = context + .getBean(PythonProcessRunner.class, scriptPath + selectedSimulator.getScriptPath()) + .withArguments(input, String.valueOf(shotNumber)) + .readOutputString() + .run(getProblemType(), solution.getId()); + + if (processResult.success()) { + solution.complete(); + solution.setSolutionData(new ExecutionResult(processResult.output(), Optional.of(input))); + return Mono.just(solution); + } + solution.fail(); + processResult.errorOutput().ifPresent(solution::setDebugData); return Mono.just(solution); } @@ -33,4 +99,36 @@ public Mono> solve(String input, SubRoutineResolver su public ProblemType getProblemType() { return ExecutorConfiguration.EXECUTOR_CONFIG; } + + enum QuantumSimulator { + AER("AerBackend", "default-executor/default_executor.py"), + // PROJECTQ("ProjectQBackend", "projectq-executor/projectq_executor.py"), + QULACS("QulacsBackend", "qulacs-executor/qulacs_executor.py"), + AER_NOISY("Aer Noisy Backend (max. 2 qubits)", "aer-noisy/aer_noisy_executor.py"); + + private final String value; + private final String scriptPath; + + QuantumSimulator(String value, String scriptPath) { + this.value = value; + this.scriptPath = scriptPath; + } + + public String getValue() { + return value; + } + + public String getScriptPath() { + return scriptPath; + } + + public static QuantumSimulator fromValue(String value) { + for (QuantumSimulator simulator : values()) { + if (simulator.value.equals(value)) { + return simulator; + } + } + throw new IllegalArgumentException("Unknown value: " + value); + } + } } diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/optimization/OptimizationSolver.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/optimization/OptimizationSolver.java index fcf51939..846fad84 100644 --- a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/optimization/OptimizationSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/optimization/OptimizationSolver.java @@ -1,15 +1,36 @@ package edu.kit.provideq.toolbox.circuit.processing.solver.optimization; import edu.kit.provideq.toolbox.Solution; -import edu.kit.provideq.toolbox.meta.ProblemSolver; -import edu.kit.provideq.toolbox.meta.ProblemType; -import edu.kit.provideq.toolbox.meta.SolvingProperties; -import edu.kit.provideq.toolbox.meta.SubRoutineResolver; +import edu.kit.provideq.toolbox.circuit.processing.solver.CircuitProcessingSolver; +import edu.kit.provideq.toolbox.meta.*; +import edu.kit.provideq.toolbox.meta.setting.SolverSetting; +import edu.kit.provideq.toolbox.meta.setting.basic.SelectSetting; +import edu.kit.provideq.toolbox.process.PythonProcessRunner; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Component; import reactor.core.publisher.Mono; +import java.util.List; + @Component public class OptimizationSolver implements ProblemSolver { + private static final String SETTING_SELECT_OPTIMIZER = "Selected Optimization Pass"; + private static final OptimizationSolver.QuantumOptimizer DEFAULT_OPTIMIZER = + QuantumOptimizer.DECOMPOSE_MULTI_CX; + + private final String scriptPath; + private final ApplicationContext context; + + @Autowired + public OptimizationSolver( + @Value("${circuitoptimizing.base.directory}") String scriptPath, + ApplicationContext context + ) { + this.scriptPath = scriptPath; + this.context = context; + } @Override public String getName() { @@ -22,9 +43,51 @@ public String getDescription() { } @Override - public Mono> solve(String input, SubRoutineResolver subRoutineResolver, SolvingProperties properties) { + public List> getSubRoutines() { + return List.of(CircuitProcessingSolver.CIRCUIT_PROCESSING_SUBROUTINE); + } + + @Override + public List getSolverSettings() { + return List.of( + new SelectSetting<>( + SETTING_SELECT_OPTIMIZER, + "The optimization pass to refactor the code with", + List.of(OptimizationSolver.QuantumOptimizer.values()), + QuantumOptimizer.DECOMPOSE_MULTI_CX, + OptimizationSolver.QuantumOptimizer::getValue + ) + ); + } + + @Override + public Mono> solve( + String input, + SubRoutineResolver subRoutineResolver, + SolvingProperties properties + ) { var solution = new Solution<>(this); - solution.setSolutionData(input); + + OptimizationSolver.QuantumOptimizer selectedOptimizer = properties + .>getSetting(SETTING_SELECT_OPTIMIZER) + .map(s -> s.getSelectedOptionT(OptimizationSolver.QuantumOptimizer::fromValue)) + .orElse(DEFAULT_OPTIMIZER); + + var processResult = context + .getBean(PythonProcessRunner.class, scriptPath + selectedOptimizer.getScriptPath()) + .withArguments(input) + .readOutputString() + .run(getProblemType(), solution.getId()); + + if (processResult.success() && processResult.output().isPresent()) { + solution.complete(); + solution.setSolutionData(processResult.output().get()); + return subRoutineResolver + .runSubRoutine(CircuitProcessingSolver.CIRCUIT_PROCESSING_SUBROUTINE, + processResult.output().get()); + } + solution.fail(); + processResult.errorOutput().ifPresent(solution::setDebugData); return Mono.just(solution); } @@ -32,4 +95,36 @@ public Mono> solve(String input, SubRoutineResolver subRoutineR public ProblemType getProblemType() { return OptimizationConfiguration.OPTIMIZATION_CONFIG; } + + enum QuantumOptimizer { + DECOMPOSE_MULTI_CX("DecomposeMultiQubitsCX", + "decompose-multi-cx/decompose_multi_cx_optimizer.py"), + REMOVE_REDUNDANCIES("RemoveRedundancies", + "remove-redundancies/remove_redundancies_optimizer.py"); + + private final String value; + private final String scriptPath; + + QuantumOptimizer(String value, String scriptPath) { + this.value = value; + this.scriptPath = scriptPath; + } + + public String getValue() { + return value; + } + + public String getScriptPath() { + return scriptPath; + } + + public static OptimizationSolver.QuantumOptimizer fromValue(String value) { + for (OptimizationSolver.QuantumOptimizer simulator : values()) { + if (simulator.value.equals(value)) { + return simulator; + } + } + throw new IllegalArgumentException("Unknown value: " + value); + } + } } From 6785d03539e478976d9003e781671857bd115f81 Mon Sep 17 00:00:00 2001 From: ekulos-code Date: Mon, 10 Mar 2025 13:28:22 +0100 Subject: [PATCH 113/137] Merge dev, account for checkstyle errors --- .../processing/CircuitProcessingConfiguration.java | 3 +-- .../processing/solver/MoveToExecutionSolver.java | 3 +-- .../processing/solver/MoveToMitigationSolver.java | 3 +-- .../processing/solver/MoveToOptimizationSolver.java | 3 +-- .../processing/solver/executor/ExecutionSolver.java | 11 +++++++---- .../solver/executor/ExecutorConfiguration.java | 6 ++---- .../mitigation/ErrorMitigationConfiguration.java | 8 ++------ .../solver/mitigation/ErrorMitigationSolver.java | 6 +++++- .../optimization/OptimizationConfiguration.java | 6 ++---- .../solver/optimization/OptimizationSolver.java | 9 ++++++--- 10 files changed, 28 insertions(+), 30 deletions(-) diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/CircuitProcessingConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/CircuitProcessingConfiguration.java index 2a72d788..c0092195 100644 --- a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/CircuitProcessingConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/CircuitProcessingConfiguration.java @@ -15,8 +15,7 @@ public class CircuitProcessingConfiguration { public static final ProblemType CIRCUIT_PROCESSING = new ProblemType<>( "circuit-processing", String.class, - String.class, - null + String.class ); @Bean diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/MoveToExecutionSolver.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/MoveToExecutionSolver.java index 8d919d7c..8d1d6b60 100644 --- a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/MoveToExecutionSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/MoveToExecutionSolver.java @@ -7,11 +7,10 @@ import edu.kit.provideq.toolbox.meta.SolvingProperties; import edu.kit.provideq.toolbox.meta.SubRoutineDefinition; import edu.kit.provideq.toolbox.meta.SubRoutineResolver; +import java.util.List; import org.springframework.stereotype.Component; import reactor.core.publisher.Mono; -import java.util.List; - @Component public class MoveToExecutionSolver extends CircuitProcessingSolver { private static final SubRoutineDefinition EXECUTOR_SUBROUTINE = diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/MoveToMitigationSolver.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/MoveToMitigationSolver.java index 4c70a353..56e68fcd 100644 --- a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/MoveToMitigationSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/MoveToMitigationSolver.java @@ -5,11 +5,10 @@ import edu.kit.provideq.toolbox.meta.SolvingProperties; import edu.kit.provideq.toolbox.meta.SubRoutineDefinition; import edu.kit.provideq.toolbox.meta.SubRoutineResolver; +import java.util.List; import org.springframework.stereotype.Component; import reactor.core.publisher.Mono; -import java.util.List; - @Component public class MoveToMitigationSolver extends CircuitProcessingSolver { private static final SubRoutineDefinition MITIGATOR_SUBROUTINE = diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/MoveToOptimizationSolver.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/MoveToOptimizationSolver.java index a6d938f6..d213b4ec 100644 --- a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/MoveToOptimizationSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/MoveToOptimizationSolver.java @@ -5,11 +5,10 @@ import edu.kit.provideq.toolbox.meta.SolvingProperties; import edu.kit.provideq.toolbox.meta.SubRoutineDefinition; import edu.kit.provideq.toolbox.meta.SubRoutineResolver; +import java.util.List; import org.springframework.stereotype.Component; import reactor.core.publisher.Mono; -import java.util.List; - @Component public class MoveToOptimizationSolver extends CircuitProcessingSolver { private static final SubRoutineDefinition OPTIMIZER_SUBROUTINE = diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/executor/ExecutionSolver.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/executor/ExecutionSolver.java index c96df7f1..58ed0aeb 100644 --- a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/executor/ExecutionSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/executor/ExecutionSolver.java @@ -9,15 +9,14 @@ import edu.kit.provideq.toolbox.meta.setting.basic.IntegerSetting; import edu.kit.provideq.toolbox.meta.setting.basic.SelectSetting; import edu.kit.provideq.toolbox.process.PythonProcessRunner; +import java.util.List; +import java.util.Optional; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Component; import reactor.core.publisher.Mono; -import java.util.List; -import java.util.Optional; - @Component public class ExecutionSolver implements ProblemSolver { private static final String SETTING_NUMBER_OF_SHOTS = "Number of shots"; @@ -67,7 +66,11 @@ public List getSolverSettings() { } @Override - public Mono> solve(String input, SubRoutineResolver subRoutineResolver, SolvingProperties properties) { + public Mono> solve( + String input, + SubRoutineResolver subRoutineResolver, + SolvingProperties properties + ) { var solution = new Solution<>(this); int shotNumber = properties.getSetting(SETTING_NUMBER_OF_SHOTS) diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/executor/ExecutorConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/executor/ExecutorConfiguration.java index d93390f2..7701bc1a 100644 --- a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/executor/ExecutorConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/executor/ExecutorConfiguration.java @@ -3,18 +3,16 @@ import edu.kit.provideq.toolbox.meta.Problem; import edu.kit.provideq.toolbox.meta.ProblemManager; import edu.kit.provideq.toolbox.meta.ProblemType; +import java.util.Set; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import java.util.Set; - @Configuration public class ExecutorConfiguration { public static final ProblemType EXECUTOR_CONFIG = new ProblemType<>( "circuit-processing-executor", String.class, - ExecutionResult.class, - null + ExecutionResult.class ); @Bean diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/mitigation/ErrorMitigationConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/mitigation/ErrorMitigationConfiguration.java index 953efc12..26afe47f 100644 --- a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/mitigation/ErrorMitigationConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/mitigation/ErrorMitigationConfiguration.java @@ -1,22 +1,18 @@ package edu.kit.provideq.toolbox.circuit.processing.solver.mitigation; -import edu.kit.provideq.toolbox.circuit.processing.solver.executor.ExecutionResult; -import edu.kit.provideq.toolbox.circuit.processing.solver.executor.ExecutionSolver; import edu.kit.provideq.toolbox.meta.Problem; import edu.kit.provideq.toolbox.meta.ProblemManager; import edu.kit.provideq.toolbox.meta.ProblemType; +import java.util.Set; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import java.util.Set; - @Configuration public class ErrorMitigationConfiguration { public static final ProblemType MITIGATION_CONFIG = new ProblemType<>( "circuit-processing-mitigation", String.class, - String.class, - null + String.class ); @Bean diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/mitigation/ErrorMitigationSolver.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/mitigation/ErrorMitigationSolver.java index 5c70686a..5f6c3f87 100644 --- a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/mitigation/ErrorMitigationSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/mitigation/ErrorMitigationSolver.java @@ -22,7 +22,11 @@ public String getDescription() { } @Override - public Mono> solve(String input, SubRoutineResolver subRoutineResolver, SolvingProperties properties) { + public Mono> solve( + String input, + SubRoutineResolver subRoutineResolver, + SolvingProperties properties + ) { var solution = new Solution<>(this); solution.setSolutionData(input); return Mono.just(solution); diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/optimization/OptimizationConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/optimization/OptimizationConfiguration.java index 78dfbbfd..bfbe3f17 100644 --- a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/optimization/OptimizationConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/optimization/OptimizationConfiguration.java @@ -3,18 +3,16 @@ import edu.kit.provideq.toolbox.meta.Problem; import edu.kit.provideq.toolbox.meta.ProblemManager; import edu.kit.provideq.toolbox.meta.ProblemType; +import java.util.Set; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import java.util.Set; - @Configuration public class OptimizationConfiguration { public static final ProblemType OPTIMIZATION_CONFIG = new ProblemType<>( "circuit-processing-optimization", String.class, - String.class, - null + String.class ); @Bean diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/optimization/OptimizationSolver.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/optimization/OptimizationSolver.java index 846fad84..8c883e00 100644 --- a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/optimization/OptimizationSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/optimization/OptimizationSolver.java @@ -2,18 +2,21 @@ import edu.kit.provideq.toolbox.Solution; import edu.kit.provideq.toolbox.circuit.processing.solver.CircuitProcessingSolver; -import edu.kit.provideq.toolbox.meta.*; +import edu.kit.provideq.toolbox.meta.ProblemSolver; +import edu.kit.provideq.toolbox.meta.ProblemType; +import edu.kit.provideq.toolbox.meta.SolvingProperties; +import edu.kit.provideq.toolbox.meta.SubRoutineDefinition; +import edu.kit.provideq.toolbox.meta.SubRoutineResolver; import edu.kit.provideq.toolbox.meta.setting.SolverSetting; import edu.kit.provideq.toolbox.meta.setting.basic.SelectSetting; import edu.kit.provideq.toolbox.process.PythonProcessRunner; +import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Component; import reactor.core.publisher.Mono; -import java.util.List; - @Component public class OptimizationSolver implements ProblemSolver { private static final String SETTING_SELECT_OPTIMIZER = "Selected Optimization Pass"; From d918fb67713d405a5c18beaba556cba715eba80f Mon Sep 17 00:00:00 2001 From: ekulos-code Date: Wed, 19 Mar 2025 17:29:27 +0100 Subject: [PATCH 114/137] Implement suggestions --- .../projectq-executor/projectq_executor.py | 17 ----------------- solvers/circuitprocessing/requirements.txt | 2 -- .../solver/executor/ExecutionSolver.java | 2 +- .../solver/optimization/OptimizationSolver.java | 5 +++-- 4 files changed, 4 insertions(+), 22 deletions(-) delete mode 100644 solvers/circuitprocessing/projectq-executor/projectq_executor.py diff --git a/solvers/circuitprocessing/projectq-executor/projectq_executor.py b/solvers/circuitprocessing/projectq-executor/projectq_executor.py deleted file mode 100644 index 8600c2bd..00000000 --- a/solvers/circuitprocessing/projectq-executor/projectq_executor.py +++ /dev/null @@ -1,17 +0,0 @@ -import sys -from pytket.qasm import circuit_from_qasm_str -from pytket.extensions.projectq import ProjectQBackend - -input_circuit = sys.argv[1] -shots = int(sys.argv[2]) - -try: - circuit = circuit_from_qasm_str(input_circuit) -except Exception as e: - print("Was not able to convert to OpenQASM: ", e) - sys.exit(1) - -backend = ProjectQBackend() -handle = backend.process_circuit(circuit, n_shots=shots) -result = backend.get_result(handle) -print(result.get_shots()) \ No newline at end of file diff --git a/solvers/circuitprocessing/requirements.txt b/solvers/circuitprocessing/requirements.txt index 40a709cf..4fde5da3 100644 --- a/solvers/circuitprocessing/requirements.txt +++ b/solvers/circuitprocessing/requirements.txt @@ -1,5 +1,3 @@ pytket pytket-qiskit -pytket-pyquil -pytket-projectq pytket-qulacs \ No newline at end of file diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/executor/ExecutionSolver.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/executor/ExecutionSolver.java index 58ed0aeb..754967fe 100644 --- a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/executor/ExecutionSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/executor/ExecutionSolver.java @@ -53,7 +53,7 @@ public List getSolverSettings() { SETTING_NUMBER_OF_SHOTS, "The number of shots to run", 1, - 10000, + 1000000, DEFAULT_NUMBER_OF_SHOTS), new SelectSetting<>( SETTING_SELECT_SIMULATOR, diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/optimization/OptimizationSolver.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/optimization/OptimizationSolver.java index 8c883e00..ebd8dced 100644 --- a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/optimization/OptimizationSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/optimization/OptimizationSolver.java @@ -37,12 +37,13 @@ public OptimizationSolver( @Override public String getName() { - return "Optimize OpenQASM circuit"; + return "Apply Tket Optimization Pass"; } @Override public String getDescription() { - return "Run optimization algorithms on an OpenQASM circuit"; + return "Transform the given circuit into an optimized but equivalent circuit using" + + "Tket compilation passes (e.g. removing redundancies)."; } @Override From 3c9275e1cba9df0407f27d097b412223f963870c Mon Sep 17 00:00:00 2001 From: ekulos-code Date: Mon, 10 Mar 2025 12:27:27 +0100 Subject: [PATCH 115/137] Add execution and optimization solvers --- .../projectq-executor/projectq_executor.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 solvers/circuitprocessing/projectq-executor/projectq_executor.py diff --git a/solvers/circuitprocessing/projectq-executor/projectq_executor.py b/solvers/circuitprocessing/projectq-executor/projectq_executor.py new file mode 100644 index 00000000..8600c2bd --- /dev/null +++ b/solvers/circuitprocessing/projectq-executor/projectq_executor.py @@ -0,0 +1,17 @@ +import sys +from pytket.qasm import circuit_from_qasm_str +from pytket.extensions.projectq import ProjectQBackend + +input_circuit = sys.argv[1] +shots = int(sys.argv[2]) + +try: + circuit = circuit_from_qasm_str(input_circuit) +except Exception as e: + print("Was not able to convert to OpenQASM: ", e) + sys.exit(1) + +backend = ProjectQBackend() +handle = backend.process_circuit(circuit, n_shots=shots) +result = backend.get_result(handle) +print(result.get_shots()) \ No newline at end of file From 1be0aa9263b3d11512a378da82fc93623e1677d1 Mon Sep 17 00:00:00 2001 From: ekulos-code Date: Wed, 19 Mar 2025 17:29:27 +0100 Subject: [PATCH 116/137] Implement suggestions --- .../projectq-executor/projectq_executor.py | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 solvers/circuitprocessing/projectq-executor/projectq_executor.py diff --git a/solvers/circuitprocessing/projectq-executor/projectq_executor.py b/solvers/circuitprocessing/projectq-executor/projectq_executor.py deleted file mode 100644 index 8600c2bd..00000000 --- a/solvers/circuitprocessing/projectq-executor/projectq_executor.py +++ /dev/null @@ -1,17 +0,0 @@ -import sys -from pytket.qasm import circuit_from_qasm_str -from pytket.extensions.projectq import ProjectQBackend - -input_circuit = sys.argv[1] -shots = int(sys.argv[2]) - -try: - circuit = circuit_from_qasm_str(input_circuit) -except Exception as e: - print("Was not able to convert to OpenQASM: ", e) - sys.exit(1) - -backend = ProjectQBackend() -handle = backend.process_circuit(circuit, n_shots=shots) -result = backend.get_result(handle) -print(result.get_shots()) \ No newline at end of file From c44c7c2439f53b923ef30395de7fb5fca122dc51 Mon Sep 17 00:00:00 2001 From: ekulos-code Date: Mon, 26 May 2025 13:52:30 +0200 Subject: [PATCH 117/137] Preliminary migration for circuit processing to venvs --- .../aer-noisy/aer_noisy_executor.py | 11 +++++++++-- .../default-executor/default_executor.py | 11 +++++++++-- .../qulacs-executor/qulacs_executor.py | 11 +++++++++-- .../{ => circuitexecution}/requirements.txt | 0 .../decompose_multi_cx_optimizer.py | 10 ++++++++-- .../remove_redundancies_optimizer.py | 7 ++++++- .../circuitoptimizing/requirements.txt | 1 + .../solver/executor/ExecutionSolver.java | 14 +++++++++++--- .../solver/optimization/OptimizationSolver.java | 14 +++++++++++--- 9 files changed, 64 insertions(+), 15 deletions(-) rename solvers/circuitprocessing/{ => circuitexecution}/aer-noisy/aer_noisy_executor.py (82%) rename solvers/circuitprocessing/{ => circuitexecution}/default-executor/default_executor.py (70%) rename solvers/circuitprocessing/{ => circuitexecution}/qulacs-executor/qulacs_executor.py (70%) rename solvers/circuitprocessing/{ => circuitexecution}/requirements.txt (100%) rename solvers/{ => circuitprocessing}/circuitoptimizing/decompose-multi-cx/decompose_multi_cx_optimizer.py (69%) rename solvers/{ => circuitprocessing}/circuitoptimizing/remove-redundancies/remove_redundancies_optimizer.py (75%) create mode 100644 solvers/circuitprocessing/circuitoptimizing/requirements.txt diff --git a/solvers/circuitprocessing/aer-noisy/aer_noisy_executor.py b/solvers/circuitprocessing/circuitexecution/aer-noisy/aer_noisy_executor.py similarity index 82% rename from solvers/circuitprocessing/aer-noisy/aer_noisy_executor.py rename to solvers/circuitprocessing/circuitexecution/aer-noisy/aer_noisy_executor.py index 9db4439b..c63a4388 100644 --- a/solvers/circuitprocessing/aer-noisy/aer_noisy_executor.py +++ b/solvers/circuitprocessing/circuitexecution/aer-noisy/aer_noisy_executor.py @@ -5,8 +5,15 @@ from qiskit_aer.noise import NoiseModel from qiskit_aer.noise.errors import depolarizing_error -input_circuit = sys.argv[1] -shots = sys.argv[2] +input_path = sys.argv[1] +num_runs = sys.argv[2] + +# read input from file +with open(input_path, 'r') as input_file: + text = input_file.read() + +input_circuit = text +shots = num_runs try: circuit = circuit_from_qasm_str(input_circuit) diff --git a/solvers/circuitprocessing/default-executor/default_executor.py b/solvers/circuitprocessing/circuitexecution/default-executor/default_executor.py similarity index 70% rename from solvers/circuitprocessing/default-executor/default_executor.py rename to solvers/circuitprocessing/circuitexecution/default-executor/default_executor.py index 9ce98997..c5a43bde 100644 --- a/solvers/circuitprocessing/default-executor/default_executor.py +++ b/solvers/circuitprocessing/circuitexecution/default-executor/default_executor.py @@ -2,8 +2,15 @@ from pytket.qasm import circuit_from_qasm_str from pytket.extensions.qiskit import AerBackend -input_circuit = sys.argv[1] -shots = int(sys.argv[2]) +input_path = sys.argv[1] +num_runs = sys.argv[2] + +# read input from file +with open(input_path, 'r') as input_file: + text = input_file.read() + +input_circuit = text +shots = num_runs try: circuit = circuit_from_qasm_str(input_circuit) diff --git a/solvers/circuitprocessing/qulacs-executor/qulacs_executor.py b/solvers/circuitprocessing/circuitexecution/qulacs-executor/qulacs_executor.py similarity index 70% rename from solvers/circuitprocessing/qulacs-executor/qulacs_executor.py rename to solvers/circuitprocessing/circuitexecution/qulacs-executor/qulacs_executor.py index 9652f6b0..71963c62 100644 --- a/solvers/circuitprocessing/qulacs-executor/qulacs_executor.py +++ b/solvers/circuitprocessing/circuitexecution/qulacs-executor/qulacs_executor.py @@ -2,8 +2,15 @@ from pytket.qasm import circuit_from_qasm_str from pytket.extensions.qulacs import QulacsBackend -input_circuit = sys.argv[1] -shots = int(sys.argv[2]) +input_path = sys.argv[1] +num_runs = sys.argv[2] + +# read input from file +with open(input_path, 'r') as input_file: + text = input_file.read() + +input_circuit = text +shots = num_runs try: circuit = circuit_from_qasm_str(input_circuit) diff --git a/solvers/circuitprocessing/requirements.txt b/solvers/circuitprocessing/circuitexecution/requirements.txt similarity index 100% rename from solvers/circuitprocessing/requirements.txt rename to solvers/circuitprocessing/circuitexecution/requirements.txt diff --git a/solvers/circuitoptimizing/decompose-multi-cx/decompose_multi_cx_optimizer.py b/solvers/circuitprocessing/circuitoptimizing/decompose-multi-cx/decompose_multi_cx_optimizer.py similarity index 69% rename from solvers/circuitoptimizing/decompose-multi-cx/decompose_multi_cx_optimizer.py rename to solvers/circuitprocessing/circuitoptimizing/decompose-multi-cx/decompose_multi_cx_optimizer.py index 3d62f964..3378a019 100644 --- a/solvers/circuitoptimizing/decompose-multi-cx/decompose_multi_cx_optimizer.py +++ b/solvers/circuitprocessing/circuitoptimizing/decompose-multi-cx/decompose_multi_cx_optimizer.py @@ -3,8 +3,13 @@ from pytket.predicates import CompilationUnit from pytket.passes import DecomposeMultiQubitsCX +input_path = sys.argv[1] -input_circuit = sys.argv[1] +# read input from file +with open(input_path, 'r') as input_file: + text = input_file.read() + +input_circuit = text try: circuit = circuit_from_qasm_str(input_circuit) @@ -16,4 +21,5 @@ cu = CompilationUnit(circuit) pass1.apply(cu) -print(circuit_to_qasm_str(cu.circuit)) \ No newline at end of file +print(circuit_to_qasm_str(cu.circuit)) + diff --git a/solvers/circuitoptimizing/remove-redundancies/remove_redundancies_optimizer.py b/solvers/circuitprocessing/circuitoptimizing/remove-redundancies/remove_redundancies_optimizer.py similarity index 75% rename from solvers/circuitoptimizing/remove-redundancies/remove_redundancies_optimizer.py rename to solvers/circuitprocessing/circuitoptimizing/remove-redundancies/remove_redundancies_optimizer.py index 58b57e68..a238a84a 100644 --- a/solvers/circuitoptimizing/remove-redundancies/remove_redundancies_optimizer.py +++ b/solvers/circuitprocessing/circuitoptimizing/remove-redundancies/remove_redundancies_optimizer.py @@ -3,8 +3,13 @@ from pytket.predicates import CompilationUnit from pytket.passes import RemoveRedundancies +input_path = sys.argv[1] -input_circuit = sys.argv[1] +# read input from file +with open(input_path, 'r') as input_file: + text = input_file.read() + +input_circuit = text try: circuit = circuit_from_qasm_str(input_circuit) diff --git a/solvers/circuitprocessing/circuitoptimizing/requirements.txt b/solvers/circuitprocessing/circuitoptimizing/requirements.txt new file mode 100644 index 00000000..f32bd3e4 --- /dev/null +++ b/solvers/circuitprocessing/circuitoptimizing/requirements.txt @@ -0,0 +1 @@ +pytket \ No newline at end of file diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/executor/ExecutionSolver.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/executor/ExecutionSolver.java index 754967fe..9cca644d 100644 --- a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/executor/ExecutionSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/executor/ExecutionSolver.java @@ -8,6 +8,7 @@ import edu.kit.provideq.toolbox.meta.setting.SolverSetting; import edu.kit.provideq.toolbox.meta.setting.basic.IntegerSetting; import edu.kit.provideq.toolbox.meta.setting.basic.SelectSetting; +import edu.kit.provideq.toolbox.process.ProcessRunner; import edu.kit.provideq.toolbox.process.PythonProcessRunner; import java.util.List; import java.util.Optional; @@ -25,14 +26,17 @@ public class ExecutionSolver implements ProblemSolver { private static final QuantumSimulator DEFAULT_SIMULATOR = QuantumSimulator.AER; private final String scriptPath; + private final String venv; private final ApplicationContext context; @Autowired public ExecutionSolver( - @Value("${circuitprocessing.base.directory}") String scriptPath, + @Value("${path.circuitprocessing.circuitexecution}") String scriptPath, + @Value("${venv.circuitprocessing.circuitexecution}") String venv, ApplicationContext context ) { this.context = context; + this.venv = venv; this.scriptPath = scriptPath; } @@ -83,8 +87,12 @@ public Mono> solve( .orElse(DEFAULT_SIMULATOR); var processResult = context - .getBean(PythonProcessRunner.class, scriptPath + selectedSimulator.getScriptPath()) - .withArguments(input, String.valueOf(shotNumber)) + .getBean(PythonProcessRunner.class, scriptPath + selectedSimulator.getScriptPath(), venv) + .withArguments( + ProcessRunner.INPUT_FILE_PATH, + String.valueOf(shotNumber) + ) + .writeInputFile(input) .readOutputString() .run(getProblemType(), solution.getId()); diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/optimization/OptimizationSolver.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/optimization/OptimizationSolver.java index ebd8dced..be8d1b4d 100644 --- a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/optimization/OptimizationSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/optimization/OptimizationSolver.java @@ -9,6 +9,7 @@ import edu.kit.provideq.toolbox.meta.SubRoutineResolver; import edu.kit.provideq.toolbox.meta.setting.SolverSetting; import edu.kit.provideq.toolbox.meta.setting.basic.SelectSetting; +import edu.kit.provideq.toolbox.process.ProcessRunner; import edu.kit.provideq.toolbox.process.PythonProcessRunner; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; @@ -24,14 +25,17 @@ public class OptimizationSolver implements ProblemSolver { QuantumOptimizer.DECOMPOSE_MULTI_CX; private final String scriptPath; + private final String venv; private final ApplicationContext context; @Autowired public OptimizationSolver( - @Value("${circuitoptimizing.base.directory}") String scriptPath, + @Value("${path.circuitprocessing.circuitoptimization}") String scriptPath, + @Value("${venv.circuitprocessing.circuitoptimization}") String venv, ApplicationContext context ) { this.scriptPath = scriptPath; + this.venv = venv; this.context = context; } @@ -77,9 +81,13 @@ public Mono> solve( .map(s -> s.getSelectedOptionT(OptimizationSolver.QuantumOptimizer::fromValue)) .orElse(DEFAULT_OPTIMIZER); + //String[] inputArray = new String[]{input}; var processResult = context - .getBean(PythonProcessRunner.class, scriptPath + selectedOptimizer.getScriptPath()) - .withArguments(input) + .getBean(PythonProcessRunner.class, scriptPath + selectedOptimizer.getScriptPath(), venv) + .withArguments( + ProcessRunner.INPUT_FILE_PATH + ) + .writeInputFile(input) .readOutputString() .run(getProblemType(), solution.getId()); From d5f5df4df3563a95ab546a0ffecd131682162fbb Mon Sep 17 00:00:00 2001 From: ekulos-code Date: Wed, 22 Apr 2026 05:15:40 +0200 Subject: [PATCH 118/137] Fix issues caused by changes on develop --- .../processing/CircuitProcessingConfiguration.java | 2 ++ .../processing/solver/CircuitProcessingSolver.java | 3 ++- .../circuit/processing/solver/MoveToExecutionSolver.java | 3 ++- .../processing/solver/MoveToMitigationSolver.java | 3 ++- .../processing/solver/MoveToOptimizationSolver.java | 3 ++- .../solver/executor/ExecutorConfiguration.java | 2 ++ .../solver/mitigation/ErrorMitigationConfiguration.java | 2 ++ .../solver/optimization/OptimizationConfiguration.java | 2 ++ src/main/resources/application.properties | 9 +++++++++ 9 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/CircuitProcessingConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/CircuitProcessingConfiguration.java index c0092195..85abdd4b 100644 --- a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/CircuitProcessingConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/CircuitProcessingConfiguration.java @@ -14,6 +14,8 @@ public class CircuitProcessingConfiguration { public static final ProblemType CIRCUIT_PROCESSING = new ProblemType<>( "circuit-processing", + "A quantum circuit processing problem that routes a QASM circuit through optimization, " + + "error mitigation, or execution.", String.class, String.class ); diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/CircuitProcessingSolver.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/CircuitProcessingSolver.java index 507ce9c6..9d00eff2 100644 --- a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/CircuitProcessingSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/CircuitProcessingSolver.java @@ -9,7 +9,8 @@ public abstract class CircuitProcessingSolver implements ProblemSolver CIRCUIT_PROCESSING_SUBROUTINE = new SubRoutineDefinition<>( CircuitProcessingConfiguration.CIRCUIT_PROCESSING, - "Creates a circuit processing solver" + "Creates a circuit processing solver", + true ); @Override diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/MoveToExecutionSolver.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/MoveToExecutionSolver.java index 8d1d6b60..023cd7b8 100644 --- a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/MoveToExecutionSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/MoveToExecutionSolver.java @@ -16,7 +16,8 @@ public class MoveToExecutionSolver extends CircuitProcessingSolver { private static final SubRoutineDefinition EXECUTOR_SUBROUTINE = new SubRoutineDefinition<>( ExecutorConfiguration.EXECUTOR_CONFIG, - "Creates a execution solver" + "Creates a execution solver", + true ); @Override diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/MoveToMitigationSolver.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/MoveToMitigationSolver.java index 56e68fcd..fcfe9892 100644 --- a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/MoveToMitigationSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/MoveToMitigationSolver.java @@ -14,7 +14,8 @@ public class MoveToMitigationSolver extends CircuitProcessingSolver { private static final SubRoutineDefinition MITIGATOR_SUBROUTINE = new SubRoutineDefinition<>( ErrorMitigationConfiguration.MITIGATION_CONFIG, - "Creates a mitigation solver" + "Creates a mitigation solver", + true ); @Override diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/MoveToOptimizationSolver.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/MoveToOptimizationSolver.java index d213b4ec..794e7b68 100644 --- a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/MoveToOptimizationSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/MoveToOptimizationSolver.java @@ -14,7 +14,8 @@ public class MoveToOptimizationSolver extends CircuitProcessingSolver { private static final SubRoutineDefinition OPTIMIZER_SUBROUTINE = new SubRoutineDefinition<>( OptimizationConfiguration.OPTIMIZATION_CONFIG, - "Creates a optimization solver" + "Creates a optimization solver", + true ); @Override diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/executor/ExecutorConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/executor/ExecutorConfiguration.java index 7701bc1a..1501f0b4 100644 --- a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/executor/ExecutorConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/executor/ExecutorConfiguration.java @@ -11,6 +11,8 @@ public class ExecutorConfiguration { public static final ProblemType EXECUTOR_CONFIG = new ProblemType<>( "circuit-processing-executor", + "A quantum circuit execution problem that runs a given QASM circuit on a quantum backend " + + "and returns the measurement results.", String.class, ExecutionResult.class ); diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/mitigation/ErrorMitigationConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/mitigation/ErrorMitigationConfiguration.java index 26afe47f..4fd0952a 100644 --- a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/mitigation/ErrorMitigationConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/mitigation/ErrorMitigationConfiguration.java @@ -11,6 +11,8 @@ public class ErrorMitigationConfiguration { public static final ProblemType MITIGATION_CONFIG = new ProblemType<>( "circuit-processing-mitigation", + "A quantum circuit error mitigation problem that applies error mitigation techniques to " + + "a given QASM circuit.", String.class, String.class ); diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/optimization/OptimizationConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/optimization/OptimizationConfiguration.java index bfbe3f17..32f2fed7 100644 --- a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/optimization/OptimizationConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/optimization/OptimizationConfiguration.java @@ -11,6 +11,8 @@ public class OptimizationConfiguration { public static final ProblemType OPTIMIZATION_CONFIG = new ProblemType<>( "circuit-processing-optimization", + "A quantum circuit optimization problem that reduces gate count or circuit depth of a " + + "given QASM circuit.", String.class, String.class ); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index d9817304..a9f06430 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -121,3 +121,12 @@ path.demonstrators.qiskit=${name.demonstrators}/${name.demonstrators.qiskit} name.demonstrators.qiskit.molecule-energy=molecule-energy path.demonstrators.qiskit.molecule-energy=${path.demonstrators.qiskit}/${name.demonstrators.qiskit.molecule-energy}/molecule-energy.py venv.demonstrators.qiskit.molecule-energy=${name.demonstrators}_${name.demonstrators.qiskit}_${name.demonstrators.qiskit.molecule-energy} + +name.circuitprocessing=circuitprocessing +path.circuitprocessing=${name.solvers}/${name.circuitprocessing} +name.circuitprocessing.circuitexecution=circuitexecution +path.circuitprocessing.circuitexecution=${path.circuitprocessing}/${name.circuitprocessing.circuitexecution}/ +venv.circuitprocessing.circuitexecution=${name.solvers}_${name.circuitprocessing}_${name.circuitprocessing.circuitexecution} +name.circuitprocessing.circuitoptimization=circuitoptimizing +path.circuitprocessing.circuitoptimization=${path.circuitprocessing}/${name.circuitprocessing.circuitoptimization}/ +venv.circuitprocessing.circuitoptimization=${name.solvers}_${name.circuitprocessing}_${name.circuitprocessing.circuitoptimization} From 9bb13230477222eabab106660834f463c740ebce Mon Sep 17 00:00:00 2001 From: ekulos-code Date: Wed, 29 Apr 2026 13:30:44 +0200 Subject: [PATCH 119/137] Add basic testing for circuit processing --- .../default-executor/default_executor.py | 2 +- .../circuitexecution/requirements.txt | 2 +- .../mitigation/ErrorMitigationSolver.java | 1 + .../api/CircuitProcessingSolversTest.java | 101 ++++++++++++++++++ .../api/ErrorMitigationSolverTest.java | 38 +++++++ .../toolbox/api/ExecutionSolverTest.java | 49 +++++++++ .../toolbox/api/OptimizationSolverTest.java | 63 +++++++++++ 7 files changed, 254 insertions(+), 2 deletions(-) create mode 100644 src/test/java/edu/kit/provideq/toolbox/api/CircuitProcessingSolversTest.java create mode 100644 src/test/java/edu/kit/provideq/toolbox/api/ErrorMitigationSolverTest.java create mode 100644 src/test/java/edu/kit/provideq/toolbox/api/ExecutionSolverTest.java create mode 100644 src/test/java/edu/kit/provideq/toolbox/api/OptimizationSolverTest.java diff --git a/solvers/circuitprocessing/circuitexecution/default-executor/default_executor.py b/solvers/circuitprocessing/circuitexecution/default-executor/default_executor.py index c5a43bde..96c6c2e6 100644 --- a/solvers/circuitprocessing/circuitexecution/default-executor/default_executor.py +++ b/solvers/circuitprocessing/circuitexecution/default-executor/default_executor.py @@ -10,7 +10,7 @@ text = input_file.read() input_circuit = text -shots = num_runs +shots = int(num_runs) try: circuit = circuit_from_qasm_str(input_circuit) diff --git a/solvers/circuitprocessing/circuitexecution/requirements.txt b/solvers/circuitprocessing/circuitexecution/requirements.txt index 4fde5da3..46f05119 100644 --- a/solvers/circuitprocessing/circuitexecution/requirements.txt +++ b/solvers/circuitprocessing/circuitexecution/requirements.txt @@ -1,3 +1,3 @@ pytket pytket-qiskit -pytket-qulacs \ No newline at end of file +# pytket-qulacs \ No newline at end of file diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/mitigation/ErrorMitigationSolver.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/mitigation/ErrorMitigationSolver.java index 5f6c3f87..6399409e 100644 --- a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/mitigation/ErrorMitigationSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/mitigation/ErrorMitigationSolver.java @@ -29,6 +29,7 @@ public Mono> solve( ) { var solution = new Solution<>(this); solution.setSolutionData(input); + solution.complete(); return Mono.just(solution); } diff --git a/src/test/java/edu/kit/provideq/toolbox/api/CircuitProcessingSolversTest.java b/src/test/java/edu/kit/provideq/toolbox/api/CircuitProcessingSolversTest.java new file mode 100644 index 00000000..0ea70b1c --- /dev/null +++ b/src/test/java/edu/kit/provideq/toolbox/api/CircuitProcessingSolversTest.java @@ -0,0 +1,101 @@ +package edu.kit.provideq.toolbox.api; + +import static edu.kit.provideq.toolbox.circuit.processing.CircuitProcessingConfiguration.CIRCUIT_PROCESSING; +import static edu.kit.provideq.toolbox.circuit.processing.solver.executor.ExecutorConfiguration.EXECUTOR_CONFIG; +import static edu.kit.provideq.toolbox.circuit.processing.solver.mitigation.ErrorMitigationConfiguration.MITIGATION_CONFIG; +import static edu.kit.provideq.toolbox.circuit.processing.solver.optimization.OptimizationConfiguration.OPTIMIZATION_CONFIG; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import edu.kit.provideq.toolbox.circuit.processing.solver.MoveToExecutionSolver; +import edu.kit.provideq.toolbox.circuit.processing.solver.MoveToMitigationSolver; +import edu.kit.provideq.toolbox.circuit.processing.solver.MoveToOptimizationSolver; +import edu.kit.provideq.toolbox.circuit.processing.solver.executor.ExecutionSolver; +import edu.kit.provideq.toolbox.circuit.processing.solver.mitigation.ErrorMitigationSolver; +import edu.kit.provideq.toolbox.circuit.processing.solver.optimization.OptimizationSolver; +import java.time.Duration; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.web.reactive.server.WebTestClient; + +@SpringBootTest +@AutoConfigureMockMvc +class CircuitProcessingSolversTest { + private static final String CIRCUIT = """ + OPENQASM 2.0; + include "qelib1.inc"; + qreg q[2]; + creg c[2]; + h q[0]; + cx q[0],q[1]; + measure q[0] -> c[0]; + measure q[1] -> c[1];"""; + + @Autowired + private WebTestClient client; + + @Autowired + private MoveToMitigationSolver moveToMitigationSolver; + + @Autowired + private MoveToExecutionSolver moveToExecutionSolver; + + @Autowired + private MoveToOptimizationSolver moveToOptimizationSolver; + + @Autowired + private ErrorMitigationSolver errorMitigationSolver; + + @Autowired + private ExecutionSolver executionSolver; + + @Autowired + private OptimizationSolver optimizationSolver; + + @BeforeEach + void beforeEach() { + this.client = this.client.mutate() + .responseTimeout(Duration.ofSeconds(60)) + .build(); + } + + @Test + void testMoveToMitigationSolver() { + var problemDto = ApiTestHelper.createProblem(client, moveToMitigationSolver, CIRCUIT, CIRCUIT_PROCESSING); + var subProblemId = problemDto.getSubProblems().get(0).getSubProblemIds().get(0); + ApiTestHelper.setProblemSolver(client, errorMitigationSolver, subProblemId, MITIGATION_CONFIG.getId()); + var solvedDto = ApiTestHelper.trySolveFor(60, client, problemDto.getId(), CIRCUIT_PROCESSING); + ApiTestHelper.testSolution(solvedDto); + assertEquals(CIRCUIT, solvedDto.getSolution().getSolutionData()); + } + + @Test + void testMoveToExecutionSolver() { + var problemDto = ApiTestHelper.createProblem(client, moveToExecutionSolver, CIRCUIT, CIRCUIT_PROCESSING); + var subProblemId = problemDto.getSubProblems().get(0).getSubProblemIds().get(0); + ApiTestHelper.setProblemSolver(client, executionSolver, subProblemId, EXECUTOR_CONFIG.getId()); + var solvedDto = ApiTestHelper.trySolveFor(60, client, problemDto.getId(), CIRCUIT_PROCESSING); + ApiTestHelper.testSolution(solvedDto); + assertFalse(solvedDto.getSolution().getSolutionData().isBlank()); + } + + @Test + void testMoveToOptimizationSolver() { + var problemDto = ApiTestHelper.createProblem(client, moveToOptimizationSolver, CIRCUIT, CIRCUIT_PROCESSING); + var optSubProblemId = problemDto.getSubProblems().get(0).getSubProblemIds().get(0); + var optDto = ApiTestHelper.setProblemSolver( + client, optimizationSolver, optSubProblemId, OPTIMIZATION_CONFIG.getId()); + var circuitSubProblemId = optDto.getSubProblems().get(0).getSubProblemIds().get(0); + var mitigationEntryDto = ApiTestHelper.setProblemSolver( + client, moveToMitigationSolver, circuitSubProblemId, CIRCUIT_PROCESSING.getId()); + var mitigationId = mitigationEntryDto.getSubProblems().get(0).getSubProblemIds().get(0); + ApiTestHelper.setProblemSolver(client, errorMitigationSolver, mitigationId, MITIGATION_CONFIG.getId()); + var solvedDto = ApiTestHelper.trySolveFor(120, client, problemDto.getId(), CIRCUIT_PROCESSING); + ApiTestHelper.testSolution(solvedDto); + assertTrue(solvedDto.getSolution().getSolutionData().contains("OPENQASM")); + } +} diff --git a/src/test/java/edu/kit/provideq/toolbox/api/ErrorMitigationSolverTest.java b/src/test/java/edu/kit/provideq/toolbox/api/ErrorMitigationSolverTest.java new file mode 100644 index 00000000..7c5ff690 --- /dev/null +++ b/src/test/java/edu/kit/provideq/toolbox/api/ErrorMitigationSolverTest.java @@ -0,0 +1,38 @@ +package edu.kit.provideq.toolbox.api; + +import static edu.kit.provideq.toolbox.circuit.processing.solver.mitigation.ErrorMitigationConfiguration.MITIGATION_CONFIG; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import edu.kit.provideq.toolbox.circuit.processing.solver.mitigation.ErrorMitigationSolver; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.web.reactive.server.WebTestClient; + +@SpringBootTest +@AutoConfigureMockMvc +class ErrorMitigationSolverTest { + private static final String CIRCUIT = """ + OPENQASM 2.0; + include "qelib1.inc"; + qreg q[2]; + creg c[2]; + h q[0]; + cx q[0],q[1]; + measure q[0] -> c[0]; + measure q[1] -> c[1];"""; + + @Autowired + private WebTestClient client; + + @Autowired + private ErrorMitigationSolver errorMitigationSolver; + + @Test + void testErrorMitigationSolver() { + var problem = ApiTestHelper.createProblem(client, errorMitigationSolver, CIRCUIT, MITIGATION_CONFIG); + ApiTestHelper.testSolution(problem); + assertEquals(CIRCUIT, problem.getSolution().getSolutionData()); + } +} diff --git a/src/test/java/edu/kit/provideq/toolbox/api/ExecutionSolverTest.java b/src/test/java/edu/kit/provideq/toolbox/api/ExecutionSolverTest.java new file mode 100644 index 00000000..acb704c1 --- /dev/null +++ b/src/test/java/edu/kit/provideq/toolbox/api/ExecutionSolverTest.java @@ -0,0 +1,49 @@ +package edu.kit.provideq.toolbox.api; + +import static edu.kit.provideq.toolbox.circuit.processing.solver.executor.ExecutorConfiguration.EXECUTOR_CONFIG; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import edu.kit.provideq.toolbox.Solution; +import edu.kit.provideq.toolbox.circuit.processing.solver.executor.ExecutionSolver; +import java.time.Duration; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.web.reactive.server.WebTestClient; + +@SpringBootTest +@AutoConfigureMockMvc +class ExecutionSolverTest { + private static final String CIRCUIT = """ + OPENQASM 2.0; + include "qelib1.inc"; + qreg q[2]; + creg c[2]; + h q[0]; + cx q[0],q[1]; + measure q[0] -> c[0]; + measure q[1] -> c[1];"""; + + @Autowired + private WebTestClient client; + + @Autowired + private ExecutionSolver executionSolver; + + @BeforeEach + void beforeEach() { + this.client = this.client.mutate() + .responseTimeout(Duration.ofSeconds(60)) + .build(); + } + + @Test + void testExecutionSolver() { + var problem = ApiTestHelper.createProblem(client, executionSolver, CIRCUIT, EXECUTOR_CONFIG); + ApiTestHelper.testSolution(problem); + Solution solution = problem.getSolution(); + assertTrue(solution.getSolutionData().toString().contains("OPENQASM")); + } +} diff --git a/src/test/java/edu/kit/provideq/toolbox/api/OptimizationSolverTest.java b/src/test/java/edu/kit/provideq/toolbox/api/OptimizationSolverTest.java new file mode 100644 index 00000000..5ec157b8 --- /dev/null +++ b/src/test/java/edu/kit/provideq/toolbox/api/OptimizationSolverTest.java @@ -0,0 +1,63 @@ +package edu.kit.provideq.toolbox.api; + +import static edu.kit.provideq.toolbox.circuit.processing.CircuitProcessingConfiguration.CIRCUIT_PROCESSING; +import static edu.kit.provideq.toolbox.circuit.processing.solver.mitigation.ErrorMitigationConfiguration.MITIGATION_CONFIG; +import static edu.kit.provideq.toolbox.circuit.processing.solver.optimization.OptimizationConfiguration.OPTIMIZATION_CONFIG; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import edu.kit.provideq.toolbox.circuit.processing.solver.MoveToMitigationSolver; +import edu.kit.provideq.toolbox.circuit.processing.solver.mitigation.ErrorMitigationSolver; +import edu.kit.provideq.toolbox.circuit.processing.solver.optimization.OptimizationSolver; +import java.time.Duration; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.web.reactive.server.WebTestClient; + +@SpringBootTest +@AutoConfigureMockMvc +class OptimizationSolverTest { + private static final String CIRCUIT = """ + OPENQASM 2.0; + include "qelib1.inc"; + qreg q[2]; + creg c[2]; + h q[0]; + cx q[0],q[1]; + measure q[0] -> c[0]; + measure q[1] -> c[1];"""; + + @Autowired + private WebTestClient client; + + @Autowired + private OptimizationSolver optimizationSolver; + + @Autowired + private MoveToMitigationSolver moveToMitigationSolver; + + @Autowired + private ErrorMitigationSolver errorMitigationSolver; + + @BeforeEach + void beforeEach() { + this.client = this.client.mutate() + .responseTimeout(Duration.ofSeconds(60)) + .build(); + } + + @Test + void testOptimizationSolver() { + var problemDto = ApiTestHelper.createProblem(client, optimizationSolver, CIRCUIT, OPTIMIZATION_CONFIG); + var circuitSubProblemId = problemDto.getSubProblems().get(0).getSubProblemIds().get(0); + var mitigationEntryDto = ApiTestHelper.setProblemSolver( + client, moveToMitigationSolver, circuitSubProblemId, CIRCUIT_PROCESSING.getId()); + var mitigationId = mitigationEntryDto.getSubProblems().get(0).getSubProblemIds().get(0); + ApiTestHelper.setProblemSolver(client, errorMitigationSolver, mitigationId, MITIGATION_CONFIG.getId()); + var solvedDto = ApiTestHelper.trySolveFor(120, client, problemDto.getId(), OPTIMIZATION_CONFIG); + ApiTestHelper.testSolution(solvedDto); + assertTrue(solvedDto.getSolution().getSolutionData().contains("OPENQASM")); + } +} From aad9cc2ee29d13e6be408d646c97afdc4e2f063e Mon Sep 17 00:00:00 2001 From: ekulos-code Date: Thu, 7 May 2026 18:52:05 +0200 Subject: [PATCH 120/137] Add problem fetching from files for tests and configs Merge circuit execution files --- .../aer-noisy/aer_noisy_executor.py | 34 --------------- .../default-executor/default_executor.py | 25 ----------- .../circuitexecution/executor.py | 40 +++++++++++++++++ .../qulacs-executor/qulacs_executor.py | 25 ----------- .../CircuitProcessingConfiguration.java | 43 +++++++++++-------- .../solver/executor/ExecutionSolver.java | 23 +++++----- .../executor/ExecutorConfiguration.java | 23 ++++++++-- .../ErrorMitigationConfiguration.java | 23 ++++++++-- .../OptimizationConfiguration.java | 23 ++++++++-- .../circuit/processing/bell-state.qasm | 8 ++++ .../toolbox/circuit/processing/cswap.qasm | 6 +++ .../api/CircuitProcessingSolversTest.java | 39 +++++++++++------ .../api/ErrorMitigationSolverTest.java | 38 ++++++++++------ .../toolbox/api/ExecutionSolverTest.java | 29 ++++++++----- .../toolbox/api/OptimizationSolverTest.java | 31 ++++++++----- 15 files changed, 236 insertions(+), 174 deletions(-) delete mode 100644 solvers/circuitprocessing/circuitexecution/aer-noisy/aer_noisy_executor.py delete mode 100644 solvers/circuitprocessing/circuitexecution/default-executor/default_executor.py create mode 100644 solvers/circuitprocessing/circuitexecution/executor.py delete mode 100644 solvers/circuitprocessing/circuitexecution/qulacs-executor/qulacs_executor.py create mode 100644 src/main/resources/edu/kit/provideq/toolbox/circuit/processing/bell-state.qasm create mode 100644 src/main/resources/edu/kit/provideq/toolbox/circuit/processing/cswap.qasm diff --git a/solvers/circuitprocessing/circuitexecution/aer-noisy/aer_noisy_executor.py b/solvers/circuitprocessing/circuitexecution/aer-noisy/aer_noisy_executor.py deleted file mode 100644 index c63a4388..00000000 --- a/solvers/circuitprocessing/circuitexecution/aer-noisy/aer_noisy_executor.py +++ /dev/null @@ -1,34 +0,0 @@ -import sys -from pytket.qasm import circuit_from_qasm_str -from pytket.extensions.qiskit import AerBackend - -from qiskit_aer.noise import NoiseModel -from qiskit_aer.noise.errors import depolarizing_error - -input_path = sys.argv[1] -num_runs = sys.argv[2] - -# read input from file -with open(input_path, 'r') as input_file: - text = input_file.read() - -input_circuit = text -shots = num_runs - -try: - circuit = circuit_from_qasm_str(input_circuit) -except Exception as e: - print("Was not able to convert to OpenQASM: ", e) - sys.exit(1) - -# https://docs.quantinuum.com/tket/user-guide/manual/manual_noise.html -noise_model = NoiseModel() -noise_model.add_readout_error([[0.9, 0.1],[0.1, 0.9]], [0]) -noise_model.add_readout_error([[0.95, 0.05],[0.05, 0.95]], [1]) -noise_model.add_quantum_error(depolarizing_error(0.1, 2), ["cx"], [0, 1]) - -backend = AerBackend(noise_model) -c = backend.get_compiled_circuit(circuit) -handle = backend.process_circuit(c, n_shots=int(shots)) -counts = backend.get_result(handle).get_counts() -print(counts) \ No newline at end of file diff --git a/solvers/circuitprocessing/circuitexecution/default-executor/default_executor.py b/solvers/circuitprocessing/circuitexecution/default-executor/default_executor.py deleted file mode 100644 index 96c6c2e6..00000000 --- a/solvers/circuitprocessing/circuitexecution/default-executor/default_executor.py +++ /dev/null @@ -1,25 +0,0 @@ -import sys -from pytket.qasm import circuit_from_qasm_str -from pytket.extensions.qiskit import AerBackend - -input_path = sys.argv[1] -num_runs = sys.argv[2] - -# read input from file -with open(input_path, 'r') as input_file: - text = input_file.read() - -input_circuit = text -shots = int(num_runs) - -try: - circuit = circuit_from_qasm_str(input_circuit) -except Exception as e: - print("Was not able to convert to OpenQASM: ", e) - sys.exit(1) - -backend = AerBackend() -c = backend.get_compiled_circuit(circuit) -handle = backend.process_circuit(c, n_shots=shots) -counts = backend.get_result(handle).get_counts() -print(counts) \ No newline at end of file diff --git a/solvers/circuitprocessing/circuitexecution/executor.py b/solvers/circuitprocessing/circuitexecution/executor.py new file mode 100644 index 00000000..379288f1 --- /dev/null +++ b/solvers/circuitprocessing/circuitexecution/executor.py @@ -0,0 +1,40 @@ +import sys +from pytket.qasm import circuit_from_qasm_str + +input_path = sys.argv[1] +num_runs = int(sys.argv[2]) +backend_name = sys.argv[3] + +with open(input_path, 'r') as input_file: + text = input_file.read() + +try: + circuit = circuit_from_qasm_str(text) +except Exception as e: + print("Was not able to convert to OpenQASM: ", e) + sys.exit(1) + +if backend_name == "aer": + from pytket.extensions.qiskit import AerBackend + backend = AerBackend() +elif backend_name == "qulacs": + from pytket.extensions.qulacs import QulacsBackend + backend = QulacsBackend() +elif backend_name == "aer_noisy": + from pytket.extensions.qiskit import AerBackend + from qiskit_aer.noise import NoiseModel + from qiskit_aer.noise.errors import depolarizing_error + # https://docs.quantinuum.com/tket/user-guide/manual/manual_noise.html + noise_model = NoiseModel() + noise_model.add_readout_error([[0.9, 0.1], [0.1, 0.9]], [0]) + noise_model.add_readout_error([[0.95, 0.05], [0.05, 0.95]], [1]) + noise_model.add_quantum_error(depolarizing_error(0.1, 2), ["cx"], [0, 1]) + backend = AerBackend(noise_model) +else: + print(f"Unknown backend: {backend_name}", file=sys.stderr) + sys.exit(1) + +c = backend.get_compiled_circuit(circuit) +handle = backend.process_circuit(c, n_shots=num_runs) +counts = backend.get_result(handle).get_counts() +print(counts) diff --git a/solvers/circuitprocessing/circuitexecution/qulacs-executor/qulacs_executor.py b/solvers/circuitprocessing/circuitexecution/qulacs-executor/qulacs_executor.py deleted file mode 100644 index 71963c62..00000000 --- a/solvers/circuitprocessing/circuitexecution/qulacs-executor/qulacs_executor.py +++ /dev/null @@ -1,25 +0,0 @@ -import sys -from pytket.qasm import circuit_from_qasm_str -from pytket.extensions.qulacs import QulacsBackend - -input_path = sys.argv[1] -num_runs = sys.argv[2] - -# read input from file -with open(input_path, 'r') as input_file: - text = input_file.read() - -input_circuit = text -shots = num_runs - -try: - circuit = circuit_from_qasm_str(input_circuit) -except Exception as e: - print("Was not able to convert to OpenQASM: ", e) - sys.exit(1) - -backend = QulacsBackend() -c = backend.get_compiled_circuit(circuit) -handle = backend.process_circuit(c, n_shots=shots) -counts = backend.get_result(handle).get_counts() -print(counts) \ No newline at end of file diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/CircuitProcessingConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/CircuitProcessingConfiguration.java index 85abdd4b..1011d7fb 100644 --- a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/CircuitProcessingConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/CircuitProcessingConfiguration.java @@ -1,11 +1,16 @@ package edu.kit.provideq.toolbox.circuit.processing; +import edu.kit.provideq.toolbox.ResourceProvider; import edu.kit.provideq.toolbox.circuit.processing.solver.MoveToExecutionSolver; import edu.kit.provideq.toolbox.circuit.processing.solver.MoveToMitigationSolver; import edu.kit.provideq.toolbox.circuit.processing.solver.MoveToOptimizationSolver; +import edu.kit.provideq.toolbox.exception.MissingExampleException; import edu.kit.provideq.toolbox.meta.Problem; import edu.kit.provideq.toolbox.meta.ProblemManager; import edu.kit.provideq.toolbox.meta.ProblemType; +import java.io.IOException; +import java.util.HashSet; +import java.util.Objects; import java.util.Set; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -22,28 +27,11 @@ public class CircuitProcessingConfiguration { @Bean ProblemManager getCircuitProcessingManager( + ResourceProvider provider, MoveToExecutionSolver moveToExecutionSolver, MoveToOptimizationSolver moveToOptimizationSolver, MoveToMitigationSolver moveToMitigationSolver ) { - Problem demo = new Problem<>(CIRCUIT_PROCESSING); - demo.setInput(""" - OPENQASM 2.0; - include "qelib1.inc"; - qreg q[2]; - creg c[2]; - h q[0]; - cx q[0],q[1]; - measure q[0] -> c[0]; - measure q[1] -> c[1];"""); - Problem secondDemo = new Problem<>(CIRCUIT_PROCESSING); - secondDemo.setInput(""" - OPENQASM 2.0; - include "qelib1.inc"; - qreg q[3]; - crz(0.5) q[0], q[1]; - t q[2]; - cswap q[2], q[0], q[1];"""); return new ProblemManager<>( CIRCUIT_PROCESSING, Set.of( @@ -51,7 +39,24 @@ ProblemManager getCircuitProcessingManager( moveToOptimizationSolver, moveToMitigationSolver ), - Set.of(demo, secondDemo) + loadExampleProblems(provider) ); } + + private Set> loadExampleProblems(ResourceProvider provider) { + try { + String[] problemNames = new String[] {"bell-state.qasm", "cswap.qasm"}; + var problemSet = new HashSet>(); + for (var problemName : problemNames) { + var problemStream = Objects.requireNonNull( + getClass().getResourceAsStream(problemName), "Problem " + problemName + " not found"); + var problem = new Problem<>(CIRCUIT_PROCESSING); + problem.setInput(provider.readStream(problemStream)); + problemSet.add(problem); + } + return problemSet; + } catch (IOException e) { + throw new MissingExampleException(CIRCUIT_PROCESSING, e); + } + } } diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/executor/ExecutionSolver.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/executor/ExecutionSolver.java index 9cca644d..3b216c5e 100644 --- a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/executor/ExecutionSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/executor/ExecutionSolver.java @@ -87,10 +87,11 @@ public Mono> solve( .orElse(DEFAULT_SIMULATOR); var processResult = context - .getBean(PythonProcessRunner.class, scriptPath + selectedSimulator.getScriptPath(), venv) + .getBean(PythonProcessRunner.class, scriptPath + "executor.py", venv) .withArguments( ProcessRunner.INPUT_FILE_PATH, - String.valueOf(shotNumber) + String.valueOf(shotNumber), + selectedSimulator.getBackendKey() ) .writeInputFile(input) .readOutputString() @@ -112,25 +113,25 @@ public ProblemType getProblemType() { } enum QuantumSimulator { - AER("AerBackend", "default-executor/default_executor.py"), - // PROJECTQ("ProjectQBackend", "projectq-executor/projectq_executor.py"), - QULACS("QulacsBackend", "qulacs-executor/qulacs_executor.py"), - AER_NOISY("Aer Noisy Backend (max. 2 qubits)", "aer-noisy/aer_noisy_executor.py"); + AER("AerBackend", "aer"), + // PROJECTQ("ProjectQBackend", "projectq"), + QULACS("QulacsBackend", "qulacs"), + AER_NOISY("Aer Noisy Backend (max. 2 qubits)", "aer_noisy"); private final String value; - private final String scriptPath; + private final String backendKey; - QuantumSimulator(String value, String scriptPath) { + QuantumSimulator(String value, String backendKey) { this.value = value; - this.scriptPath = scriptPath; + this.backendKey = backendKey; } public String getValue() { return value; } - public String getScriptPath() { - return scriptPath; + public String getBackendKey() { + return backendKey; } public static QuantumSimulator fromValue(String value) { diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/executor/ExecutorConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/executor/ExecutorConfiguration.java index 1501f0b4..939122b7 100644 --- a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/executor/ExecutorConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/executor/ExecutorConfiguration.java @@ -1,8 +1,12 @@ package edu.kit.provideq.toolbox.circuit.processing.solver.executor; +import edu.kit.provideq.toolbox.ResourceProvider; +import edu.kit.provideq.toolbox.exception.MissingExampleException; import edu.kit.provideq.toolbox.meta.Problem; import edu.kit.provideq.toolbox.meta.ProblemManager; import edu.kit.provideq.toolbox.meta.ProblemType; +import java.io.IOException; +import java.util.Objects; import java.util.Set; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -19,14 +23,25 @@ public class ExecutorConfiguration { @Bean ProblemManager getExecutorProblemManager( + ResourceProvider provider, ExecutionSolver executionSolver ) { return new ProblemManager<>( EXECUTOR_CONFIG, - Set.of( - executionSolver - ), - Set.of(new Problem<>(EXECUTOR_CONFIG)) + Set.of(executionSolver), + loadExampleProblems(provider) ); } + + private Set> loadExampleProblems(ResourceProvider provider) { + try { + var problemStream = Objects.requireNonNull( + getClass().getResourceAsStream("../../bell-state.qasm"), "Problem bell-state.qasm not found"); + var problem = new Problem<>(EXECUTOR_CONFIG); + problem.setInput(provider.readStream(problemStream)); + return Set.of(problem); + } catch (IOException e) { + throw new MissingExampleException(EXECUTOR_CONFIG, e); + } + } } diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/mitigation/ErrorMitigationConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/mitigation/ErrorMitigationConfiguration.java index 4fd0952a..f9ee685b 100644 --- a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/mitigation/ErrorMitigationConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/mitigation/ErrorMitigationConfiguration.java @@ -1,8 +1,12 @@ package edu.kit.provideq.toolbox.circuit.processing.solver.mitigation; +import edu.kit.provideq.toolbox.ResourceProvider; +import edu.kit.provideq.toolbox.exception.MissingExampleException; import edu.kit.provideq.toolbox.meta.Problem; import edu.kit.provideq.toolbox.meta.ProblemManager; import edu.kit.provideq.toolbox.meta.ProblemType; +import java.io.IOException; +import java.util.Objects; import java.util.Set; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -19,14 +23,25 @@ public class ErrorMitigationConfiguration { @Bean ProblemManager getMitigationProblemManager( + ResourceProvider provider, ErrorMitigationSolver errorMitigationSolver ) { return new ProblemManager<>( MITIGATION_CONFIG, - Set.of( - errorMitigationSolver - ), - Set.of(new Problem<>(MITIGATION_CONFIG)) + Set.of(errorMitigationSolver), + loadExampleProblems(provider) ); } + + private Set> loadExampleProblems(ResourceProvider provider) { + try { + var problemStream = Objects.requireNonNull( + getClass().getResourceAsStream("../../bell-state.qasm"), "Problem bell-state.qasm not found"); + var problem = new Problem<>(MITIGATION_CONFIG); + problem.setInput(provider.readStream(problemStream)); + return Set.of(problem); + } catch (IOException e) { + throw new MissingExampleException(MITIGATION_CONFIG, e); + } + } } diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/optimization/OptimizationConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/optimization/OptimizationConfiguration.java index 32f2fed7..6de1e680 100644 --- a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/optimization/OptimizationConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/optimization/OptimizationConfiguration.java @@ -1,8 +1,12 @@ package edu.kit.provideq.toolbox.circuit.processing.solver.optimization; +import edu.kit.provideq.toolbox.ResourceProvider; +import edu.kit.provideq.toolbox.exception.MissingExampleException; import edu.kit.provideq.toolbox.meta.Problem; import edu.kit.provideq.toolbox.meta.ProblemManager; import edu.kit.provideq.toolbox.meta.ProblemType; +import java.io.IOException; +import java.util.Objects; import java.util.Set; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -19,14 +23,25 @@ public class OptimizationConfiguration { @Bean ProblemManager getOptimizationProblemManager( + ResourceProvider provider, OptimizationSolver optimizationSolver ) { return new ProblemManager<>( OPTIMIZATION_CONFIG, - Set.of( - optimizationSolver - ), - Set.of(new Problem<>(OPTIMIZATION_CONFIG)) + Set.of(optimizationSolver), + loadExampleProblems(provider) ); } + + private Set> loadExampleProblems(ResourceProvider provider) { + try { + var problemStream = Objects.requireNonNull( + getClass().getResourceAsStream("../../bell-state.qasm"), "Problem bell-state.qasm not found"); + var problem = new Problem<>(OPTIMIZATION_CONFIG); + problem.setInput(provider.readStream(problemStream)); + return Set.of(problem); + } catch (IOException e) { + throw new MissingExampleException(OPTIMIZATION_CONFIG, e); + } + } } diff --git a/src/main/resources/edu/kit/provideq/toolbox/circuit/processing/bell-state.qasm b/src/main/resources/edu/kit/provideq/toolbox/circuit/processing/bell-state.qasm new file mode 100644 index 00000000..c3bf3c2b --- /dev/null +++ b/src/main/resources/edu/kit/provideq/toolbox/circuit/processing/bell-state.qasm @@ -0,0 +1,8 @@ +OPENQASM 2.0; +include "qelib1.inc"; +qreg q[2]; +creg c[2]; +h q[0]; +cx q[0],q[1]; +measure q[0] -> c[0]; +measure q[1] -> c[1]; diff --git a/src/main/resources/edu/kit/provideq/toolbox/circuit/processing/cswap.qasm b/src/main/resources/edu/kit/provideq/toolbox/circuit/processing/cswap.qasm new file mode 100644 index 00000000..b2bce4dc --- /dev/null +++ b/src/main/resources/edu/kit/provideq/toolbox/circuit/processing/cswap.qasm @@ -0,0 +1,6 @@ +OPENQASM 2.0; +include "qelib1.inc"; +qreg q[3]; +crz(0.5) q[0], q[1]; +t q[2]; +cswap q[2], q[0], q[1]; diff --git a/src/test/java/edu/kit/provideq/toolbox/api/CircuitProcessingSolversTest.java b/src/test/java/edu/kit/provideq/toolbox/api/CircuitProcessingSolversTest.java index 0ea70b1c..e31e8382 100644 --- a/src/test/java/edu/kit/provideq/toolbox/api/CircuitProcessingSolversTest.java +++ b/src/test/java/edu/kit/provideq/toolbox/api/CircuitProcessingSolversTest.java @@ -14,7 +14,12 @@ import edu.kit.provideq.toolbox.circuit.processing.solver.executor.ExecutionSolver; import edu.kit.provideq.toolbox.circuit.processing.solver.mitigation.ErrorMitigationSolver; import edu.kit.provideq.toolbox.circuit.processing.solver.optimization.OptimizationSolver; +import edu.kit.provideq.toolbox.meta.Problem; +import edu.kit.provideq.toolbox.meta.ProblemManager; +import edu.kit.provideq.toolbox.meta.ProblemManagerProvider; import java.time.Duration; +import java.util.List; +import java.util.Optional; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -25,19 +30,12 @@ @SpringBootTest @AutoConfigureMockMvc class CircuitProcessingSolversTest { - private static final String CIRCUIT = """ - OPENQASM 2.0; - include "qelib1.inc"; - qreg q[2]; - creg c[2]; - h q[0]; - cx q[0],q[1]; - measure q[0] -> c[0]; - measure q[1] -> c[1];"""; - @Autowired private WebTestClient client; + @Autowired + private ProblemManagerProvider problemManagerProvider; + @Autowired private MoveToMitigationSolver moveToMitigationSolver; @@ -56,26 +54,38 @@ class CircuitProcessingSolversTest { @Autowired private OptimizationSolver optimizationSolver; + private ProblemManager problemManager; + private List problems; + @BeforeEach void beforeEach() { this.client = this.client.mutate() .responseTimeout(Duration.ofSeconds(60)) .build(); + problemManager = problemManagerProvider.findProblemManagerForType(CIRCUIT_PROCESSING).get(); + problems = problemManager.getExampleInstances() + .stream() + .map(Problem::getInput) + .filter(Optional::isPresent) + .map(Optional::get) + .toList(); } @Test void testMoveToMitigationSolver() { - var problemDto = ApiTestHelper.createProblem(client, moveToMitigationSolver, CIRCUIT, CIRCUIT_PROCESSING); + var circuit = problems.get(0); + var problemDto = ApiTestHelper.createProblem(client, moveToMitigationSolver, circuit, CIRCUIT_PROCESSING); var subProblemId = problemDto.getSubProblems().get(0).getSubProblemIds().get(0); ApiTestHelper.setProblemSolver(client, errorMitigationSolver, subProblemId, MITIGATION_CONFIG.getId()); var solvedDto = ApiTestHelper.trySolveFor(60, client, problemDto.getId(), CIRCUIT_PROCESSING); ApiTestHelper.testSolution(solvedDto); - assertEquals(CIRCUIT, solvedDto.getSolution().getSolutionData()); + assertEquals(circuit, solvedDto.getSolution().getSolutionData()); } @Test void testMoveToExecutionSolver() { - var problemDto = ApiTestHelper.createProblem(client, moveToExecutionSolver, CIRCUIT, CIRCUIT_PROCESSING); + var circuit = problems.get(0); + var problemDto = ApiTestHelper.createProblem(client, moveToExecutionSolver, circuit, CIRCUIT_PROCESSING); var subProblemId = problemDto.getSubProblems().get(0).getSubProblemIds().get(0); ApiTestHelper.setProblemSolver(client, executionSolver, subProblemId, EXECUTOR_CONFIG.getId()); var solvedDto = ApiTestHelper.trySolveFor(60, client, problemDto.getId(), CIRCUIT_PROCESSING); @@ -85,7 +95,8 @@ void testMoveToExecutionSolver() { @Test void testMoveToOptimizationSolver() { - var problemDto = ApiTestHelper.createProblem(client, moveToOptimizationSolver, CIRCUIT, CIRCUIT_PROCESSING); + var circuit = problems.get(0); + var problemDto = ApiTestHelper.createProblem(client, moveToOptimizationSolver, circuit, CIRCUIT_PROCESSING); var optSubProblemId = problemDto.getSubProblems().get(0).getSubProblemIds().get(0); var optDto = ApiTestHelper.setProblemSolver( client, optimizationSolver, optSubProblemId, OPTIMIZATION_CONFIG.getId()); diff --git a/src/test/java/edu/kit/provideq/toolbox/api/ErrorMitigationSolverTest.java b/src/test/java/edu/kit/provideq/toolbox/api/ErrorMitigationSolverTest.java index 7c5ff690..1815a325 100644 --- a/src/test/java/edu/kit/provideq/toolbox/api/ErrorMitigationSolverTest.java +++ b/src/test/java/edu/kit/provideq/toolbox/api/ErrorMitigationSolverTest.java @@ -4,6 +4,12 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import edu.kit.provideq.toolbox.circuit.processing.solver.mitigation.ErrorMitigationSolver; +import edu.kit.provideq.toolbox.meta.Problem; +import edu.kit.provideq.toolbox.meta.ProblemManager; +import edu.kit.provideq.toolbox.meta.ProblemManagerProvider; +import java.util.List; +import java.util.Optional; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; @@ -13,26 +19,34 @@ @SpringBootTest @AutoConfigureMockMvc class ErrorMitigationSolverTest { - private static final String CIRCUIT = """ - OPENQASM 2.0; - include "qelib1.inc"; - qreg q[2]; - creg c[2]; - h q[0]; - cx q[0],q[1]; - measure q[0] -> c[0]; - measure q[1] -> c[1];"""; - @Autowired private WebTestClient client; + @Autowired + private ProblemManagerProvider problemManagerProvider; + @Autowired private ErrorMitigationSolver errorMitigationSolver; + private ProblemManager problemManager; + private List problems; + + @BeforeEach + void beforeEach() { + problemManager = problemManagerProvider.findProblemManagerForType(MITIGATION_CONFIG).get(); + problems = problemManager.getExampleInstances() + .stream() + .map(Problem::getInput) + .filter(Optional::isPresent) + .map(Optional::get) + .toList(); + } + @Test void testErrorMitigationSolver() { - var problem = ApiTestHelper.createProblem(client, errorMitigationSolver, CIRCUIT, MITIGATION_CONFIG); + var circuit = problems.get(0); + var problem = ApiTestHelper.createProblem(client, errorMitigationSolver, circuit, MITIGATION_CONFIG); ApiTestHelper.testSolution(problem); - assertEquals(CIRCUIT, problem.getSolution().getSolutionData()); + assertEquals(circuit, problem.getSolution().getSolutionData()); } } diff --git a/src/test/java/edu/kit/provideq/toolbox/api/ExecutionSolverTest.java b/src/test/java/edu/kit/provideq/toolbox/api/ExecutionSolverTest.java index acb704c1..11549377 100644 --- a/src/test/java/edu/kit/provideq/toolbox/api/ExecutionSolverTest.java +++ b/src/test/java/edu/kit/provideq/toolbox/api/ExecutionSolverTest.java @@ -5,7 +5,11 @@ import edu.kit.provideq.toolbox.Solution; import edu.kit.provideq.toolbox.circuit.processing.solver.executor.ExecutionSolver; +import edu.kit.provideq.toolbox.meta.Problem; +import edu.kit.provideq.toolbox.meta.ProblemManagerProvider; import java.time.Duration; +import java.util.List; +import java.util.Optional; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -16,32 +20,35 @@ @SpringBootTest @AutoConfigureMockMvc class ExecutionSolverTest { - private static final String CIRCUIT = """ - OPENQASM 2.0; - include "qelib1.inc"; - qreg q[2]; - creg c[2]; - h q[0]; - cx q[0],q[1]; - measure q[0] -> c[0]; - measure q[1] -> c[1];"""; - @Autowired private WebTestClient client; + @Autowired + private ProblemManagerProvider problemManagerProvider; + @Autowired private ExecutionSolver executionSolver; + private List problems; + @BeforeEach void beforeEach() { this.client = this.client.mutate() .responseTimeout(Duration.ofSeconds(60)) .build(); + problems = problemManagerProvider.findProblemManagerForType(EXECUTOR_CONFIG).get() + .getExampleInstances() + .stream() + .map(Problem::getInput) + .filter(Optional::isPresent) + .map(Optional::get) + .toList(); } @Test void testExecutionSolver() { - var problem = ApiTestHelper.createProblem(client, executionSolver, CIRCUIT, EXECUTOR_CONFIG); + var circuit = problems.get(0); + var problem = ApiTestHelper.createProblem(client, executionSolver, circuit, EXECUTOR_CONFIG); ApiTestHelper.testSolution(problem); Solution solution = problem.getSolution(); assertTrue(solution.getSolutionData().toString().contains("OPENQASM")); diff --git a/src/test/java/edu/kit/provideq/toolbox/api/OptimizationSolverTest.java b/src/test/java/edu/kit/provideq/toolbox/api/OptimizationSolverTest.java index 5ec157b8..4945a5c7 100644 --- a/src/test/java/edu/kit/provideq/toolbox/api/OptimizationSolverTest.java +++ b/src/test/java/edu/kit/provideq/toolbox/api/OptimizationSolverTest.java @@ -8,7 +8,12 @@ import edu.kit.provideq.toolbox.circuit.processing.solver.MoveToMitigationSolver; import edu.kit.provideq.toolbox.circuit.processing.solver.mitigation.ErrorMitigationSolver; import edu.kit.provideq.toolbox.circuit.processing.solver.optimization.OptimizationSolver; +import edu.kit.provideq.toolbox.meta.Problem; +import edu.kit.provideq.toolbox.meta.ProblemManager; +import edu.kit.provideq.toolbox.meta.ProblemManagerProvider; import java.time.Duration; +import java.util.List; +import java.util.Optional; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -19,19 +24,12 @@ @SpringBootTest @AutoConfigureMockMvc class OptimizationSolverTest { - private static final String CIRCUIT = """ - OPENQASM 2.0; - include "qelib1.inc"; - qreg q[2]; - creg c[2]; - h q[0]; - cx q[0],q[1]; - measure q[0] -> c[0]; - measure q[1] -> c[1];"""; - @Autowired private WebTestClient client; + @Autowired + private ProblemManagerProvider problemManagerProvider; + @Autowired private OptimizationSolver optimizationSolver; @@ -41,16 +39,27 @@ class OptimizationSolverTest { @Autowired private ErrorMitigationSolver errorMitigationSolver; + private ProblemManager problemManager; + private List problems; + @BeforeEach void beforeEach() { this.client = this.client.mutate() .responseTimeout(Duration.ofSeconds(60)) .build(); + problemManager = problemManagerProvider.findProblemManagerForType(OPTIMIZATION_CONFIG).get(); + problems = problemManager.getExampleInstances() + .stream() + .map(Problem::getInput) + .filter(Optional::isPresent) + .map(Optional::get) + .toList(); } @Test void testOptimizationSolver() { - var problemDto = ApiTestHelper.createProblem(client, optimizationSolver, CIRCUIT, OPTIMIZATION_CONFIG); + var circuit = problems.get(0); + var problemDto = ApiTestHelper.createProblem(client, optimizationSolver, circuit, OPTIMIZATION_CONFIG); var circuitSubProblemId = problemDto.getSubProblems().get(0).getSubProblemIds().get(0); var mitigationEntryDto = ApiTestHelper.setProblemSolver( client, moveToMitigationSolver, circuitSubProblemId, CIRCUIT_PROCESSING.getId()); From daaf816fd24e6c58894cf03d707e372dd3abb3fd Mon Sep 17 00:00:00 2001 From: ekulos-code Date: Fri, 5 Jun 2026 18:47:03 +0200 Subject: [PATCH 121/137] Implement first range of suggestions --- .../circuitexecution/requirements.txt | 4 ++-- .../circuitoptimizing/requirements.txt | 2 +- .../CircuitProcessingConfiguration.java | 6 +++--- .../CircuitProcessingSolver.java | 2 +- .../MoveToExecutionSolver.java | 8 ++++---- .../MoveToMitigationSolver.java | 4 ++-- .../MoveToOptimizationSolver.java | 6 +++--- .../executor/ExecutionResult.java | 2 +- .../executor/ExecutionSolver.java | 4 ++-- .../executor/ExecutorConfiguration.java | 2 +- .../ErrorMitigationConfiguration.java | 2 +- .../mitigation/ErrorMitigationSolver.java | 2 +- .../OptimizationConfiguration.java | 2 +- .../optimization/OptimizationSolver.java | 5 ++--- src/main/resources/application.properties | 2 +- .../api/CircuitProcessingSolversTest.java | 18 +++++++++--------- .../toolbox/api/ErrorMitigationSolverTest.java | 4 ++-- .../toolbox/api/ExecutionSolverTest.java | 4 ++-- .../toolbox/api/OptimizationSolverTest.java | 10 +++++----- 19 files changed, 44 insertions(+), 45 deletions(-) rename src/main/java/edu/kit/provideq/toolbox/circuit/processing/{solver => solvers}/CircuitProcessingSolver.java (92%) rename src/main/java/edu/kit/provideq/toolbox/circuit/processing/{solver => solvers}/MoveToExecutionSolver.java (85%) rename src/main/java/edu/kit/provideq/toolbox/circuit/processing/{solver => solvers}/MoveToMitigationSolver.java (88%) rename src/main/java/edu/kit/provideq/toolbox/circuit/processing/{solver => solvers}/MoveToOptimizationSolver.java (84%) rename src/main/java/edu/kit/provideq/toolbox/circuit/processing/{solver => solvers}/executor/ExecutionResult.java (78%) rename src/main/java/edu/kit/provideq/toolbox/circuit/processing/{solver => solvers}/executor/ExecutionSolver.java (96%) rename src/main/java/edu/kit/provideq/toolbox/circuit/processing/{solver => solvers}/executor/ExecutorConfiguration.java (95%) rename src/main/java/edu/kit/provideq/toolbox/circuit/processing/{solver => solvers}/mitigation/ErrorMitigationConfiguration.java (95%) rename src/main/java/edu/kit/provideq/toolbox/circuit/processing/{solver => solvers}/mitigation/ErrorMitigationSolver.java (93%) rename src/main/java/edu/kit/provideq/toolbox/circuit/processing/{solver => solvers}/optimization/OptimizationConfiguration.java (95%) rename src/main/java/edu/kit/provideq/toolbox/circuit/processing/{solver => solvers}/optimization/OptimizationSolver.java (95%) diff --git a/solvers/circuitprocessing/circuitexecution/requirements.txt b/solvers/circuitprocessing/circuitexecution/requirements.txt index 46f05119..2ef4b7c2 100644 --- a/solvers/circuitprocessing/circuitexecution/requirements.txt +++ b/solvers/circuitprocessing/circuitexecution/requirements.txt @@ -1,3 +1,3 @@ -pytket -pytket-qiskit +pytket == 2.18.0 +pytket-qiskit == 0.77.0 # pytket-qulacs \ No newline at end of file diff --git a/solvers/circuitprocessing/circuitoptimizing/requirements.txt b/solvers/circuitprocessing/circuitoptimizing/requirements.txt index f32bd3e4..30683d5a 100644 --- a/solvers/circuitprocessing/circuitoptimizing/requirements.txt +++ b/solvers/circuitprocessing/circuitoptimizing/requirements.txt @@ -1 +1 @@ -pytket \ No newline at end of file +pytket == 2.18.0 \ No newline at end of file diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/CircuitProcessingConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/CircuitProcessingConfiguration.java index 1011d7fb..4c05f037 100644 --- a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/CircuitProcessingConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/CircuitProcessingConfiguration.java @@ -1,9 +1,9 @@ package edu.kit.provideq.toolbox.circuit.processing; import edu.kit.provideq.toolbox.ResourceProvider; -import edu.kit.provideq.toolbox.circuit.processing.solver.MoveToExecutionSolver; -import edu.kit.provideq.toolbox.circuit.processing.solver.MoveToMitigationSolver; -import edu.kit.provideq.toolbox.circuit.processing.solver.MoveToOptimizationSolver; +import edu.kit.provideq.toolbox.circuit.processing.solvers.MoveToExecutionSolver; +import edu.kit.provideq.toolbox.circuit.processing.solvers.MoveToMitigationSolver; +import edu.kit.provideq.toolbox.circuit.processing.solvers.MoveToOptimizationSolver; import edu.kit.provideq.toolbox.exception.MissingExampleException; import edu.kit.provideq.toolbox.meta.Problem; import edu.kit.provideq.toolbox.meta.ProblemManager; diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/CircuitProcessingSolver.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/CircuitProcessingSolver.java similarity index 92% rename from src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/CircuitProcessingSolver.java rename to src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/CircuitProcessingSolver.java index 9d00eff2..b83fc3d4 100644 --- a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/CircuitProcessingSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/CircuitProcessingSolver.java @@ -1,4 +1,4 @@ -package edu.kit.provideq.toolbox.circuit.processing.solver; +package edu.kit.provideq.toolbox.circuit.processing.solvers; import edu.kit.provideq.toolbox.circuit.processing.CircuitProcessingConfiguration; import edu.kit.provideq.toolbox.meta.ProblemSolver; diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/MoveToExecutionSolver.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/MoveToExecutionSolver.java similarity index 85% rename from src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/MoveToExecutionSolver.java rename to src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/MoveToExecutionSolver.java index 023cd7b8..5a11d092 100644 --- a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/MoveToExecutionSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/MoveToExecutionSolver.java @@ -1,9 +1,9 @@ -package edu.kit.provideq.toolbox.circuit.processing.solver; +package edu.kit.provideq.toolbox.circuit.processing.solvers; import edu.kit.provideq.toolbox.Solution; import edu.kit.provideq.toolbox.SolutionStatus; -import edu.kit.provideq.toolbox.circuit.processing.solver.executor.ExecutionResult; -import edu.kit.provideq.toolbox.circuit.processing.solver.executor.ExecutorConfiguration; +import edu.kit.provideq.toolbox.circuit.processing.solvers.executor.ExecutionResult; +import edu.kit.provideq.toolbox.circuit.processing.solvers.executor.ExecutorConfiguration; import edu.kit.provideq.toolbox.meta.SolvingProperties; import edu.kit.provideq.toolbox.meta.SubRoutineDefinition; import edu.kit.provideq.toolbox.meta.SubRoutineResolver; @@ -16,7 +16,7 @@ public class MoveToExecutionSolver extends CircuitProcessingSolver { private static final SubRoutineDefinition EXECUTOR_SUBROUTINE = new SubRoutineDefinition<>( ExecutorConfiguration.EXECUTOR_CONFIG, - "Creates a execution solver", + "Creates an execution solver", true ); diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/MoveToMitigationSolver.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/MoveToMitigationSolver.java similarity index 88% rename from src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/MoveToMitigationSolver.java rename to src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/MoveToMitigationSolver.java index fcfe9892..6e0133c9 100644 --- a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/MoveToMitigationSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/MoveToMitigationSolver.java @@ -1,7 +1,7 @@ -package edu.kit.provideq.toolbox.circuit.processing.solver; +package edu.kit.provideq.toolbox.circuit.processing.solvers; import edu.kit.provideq.toolbox.Solution; -import edu.kit.provideq.toolbox.circuit.processing.solver.mitigation.ErrorMitigationConfiguration; +import edu.kit.provideq.toolbox.circuit.processing.solvers.mitigation.ErrorMitigationConfiguration; import edu.kit.provideq.toolbox.meta.SolvingProperties; import edu.kit.provideq.toolbox.meta.SubRoutineDefinition; import edu.kit.provideq.toolbox.meta.SubRoutineResolver; diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/MoveToOptimizationSolver.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/MoveToOptimizationSolver.java similarity index 84% rename from src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/MoveToOptimizationSolver.java rename to src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/MoveToOptimizationSolver.java index 794e7b68..235c943d 100644 --- a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/MoveToOptimizationSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/MoveToOptimizationSolver.java @@ -1,7 +1,7 @@ -package edu.kit.provideq.toolbox.circuit.processing.solver; +package edu.kit.provideq.toolbox.circuit.processing.solvers; import edu.kit.provideq.toolbox.Solution; -import edu.kit.provideq.toolbox.circuit.processing.solver.optimization.OptimizationConfiguration; +import edu.kit.provideq.toolbox.circuit.processing.solvers.optimization.OptimizationConfiguration; import edu.kit.provideq.toolbox.meta.SolvingProperties; import edu.kit.provideq.toolbox.meta.SubRoutineDefinition; import edu.kit.provideq.toolbox.meta.SubRoutineResolver; @@ -14,7 +14,7 @@ public class MoveToOptimizationSolver extends CircuitProcessingSolver { private static final SubRoutineDefinition OPTIMIZER_SUBROUTINE = new SubRoutineDefinition<>( OptimizationConfiguration.OPTIMIZATION_CONFIG, - "Creates a optimization solver", + "Creates an optimization solver", true ); diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/executor/ExecutionResult.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/executor/ExecutionResult.java similarity index 78% rename from src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/executor/ExecutionResult.java rename to src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/executor/ExecutionResult.java index 4751c2b7..4e2212b7 100644 --- a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/executor/ExecutionResult.java +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/executor/ExecutionResult.java @@ -1,4 +1,4 @@ -package edu.kit.provideq.toolbox.circuit.processing.solver.executor; +package edu.kit.provideq.toolbox.circuit.processing.solvers.executor; import java.util.Optional; diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/executor/ExecutionSolver.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/executor/ExecutionSolver.java similarity index 96% rename from src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/executor/ExecutionSolver.java rename to src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/executor/ExecutionSolver.java index 3b216c5e..894c776d 100644 --- a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/executor/ExecutionSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/executor/ExecutionSolver.java @@ -1,4 +1,4 @@ -package edu.kit.provideq.toolbox.circuit.processing.solver.executor; +package edu.kit.provideq.toolbox.circuit.processing.solvers.executor; import edu.kit.provideq.toolbox.Solution; import edu.kit.provideq.toolbox.meta.ProblemSolver; @@ -87,7 +87,7 @@ public Mono> solve( .orElse(DEFAULT_SIMULATOR); var processResult = context - .getBean(PythonProcessRunner.class, scriptPath + "executor.py", venv) + .getBean(PythonProcessRunner.class, scriptPath, venv) .withArguments( ProcessRunner.INPUT_FILE_PATH, String.valueOf(shotNumber), diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/executor/ExecutorConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/executor/ExecutorConfiguration.java similarity index 95% rename from src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/executor/ExecutorConfiguration.java rename to src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/executor/ExecutorConfiguration.java index 939122b7..d9283ba2 100644 --- a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/executor/ExecutorConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/executor/ExecutorConfiguration.java @@ -1,4 +1,4 @@ -package edu.kit.provideq.toolbox.circuit.processing.solver.executor; +package edu.kit.provideq.toolbox.circuit.processing.solvers.executor; import edu.kit.provideq.toolbox.ResourceProvider; import edu.kit.provideq.toolbox.exception.MissingExampleException; diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/mitigation/ErrorMitigationConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/mitigation/ErrorMitigationConfiguration.java similarity index 95% rename from src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/mitigation/ErrorMitigationConfiguration.java rename to src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/mitigation/ErrorMitigationConfiguration.java index f9ee685b..cdf489e5 100644 --- a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/mitigation/ErrorMitigationConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/mitigation/ErrorMitigationConfiguration.java @@ -1,4 +1,4 @@ -package edu.kit.provideq.toolbox.circuit.processing.solver.mitigation; +package edu.kit.provideq.toolbox.circuit.processing.solvers.mitigation; import edu.kit.provideq.toolbox.ResourceProvider; import edu.kit.provideq.toolbox.exception.MissingExampleException; diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/mitigation/ErrorMitigationSolver.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/mitigation/ErrorMitigationSolver.java similarity index 93% rename from src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/mitigation/ErrorMitigationSolver.java rename to src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/mitigation/ErrorMitigationSolver.java index 6399409e..29fd5c7f 100644 --- a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/mitigation/ErrorMitigationSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/mitigation/ErrorMitigationSolver.java @@ -1,4 +1,4 @@ -package edu.kit.provideq.toolbox.circuit.processing.solver.mitigation; +package edu.kit.provideq.toolbox.circuit.processing.solvers.mitigation; import edu.kit.provideq.toolbox.Solution; import edu.kit.provideq.toolbox.meta.ProblemSolver; diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/optimization/OptimizationConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/optimization/OptimizationConfiguration.java similarity index 95% rename from src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/optimization/OptimizationConfiguration.java rename to src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/optimization/OptimizationConfiguration.java index 6de1e680..26fba1fd 100644 --- a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/optimization/OptimizationConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/optimization/OptimizationConfiguration.java @@ -1,4 +1,4 @@ -package edu.kit.provideq.toolbox.circuit.processing.solver.optimization; +package edu.kit.provideq.toolbox.circuit.processing.solvers.optimization; import edu.kit.provideq.toolbox.ResourceProvider; import edu.kit.provideq.toolbox.exception.MissingExampleException; diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/optimization/OptimizationSolver.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/optimization/OptimizationSolver.java similarity index 95% rename from src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/optimization/OptimizationSolver.java rename to src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/optimization/OptimizationSolver.java index be8d1b4d..fb382c8c 100644 --- a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solver/optimization/OptimizationSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/optimization/OptimizationSolver.java @@ -1,7 +1,7 @@ -package edu.kit.provideq.toolbox.circuit.processing.solver.optimization; +package edu.kit.provideq.toolbox.circuit.processing.solvers.optimization; import edu.kit.provideq.toolbox.Solution; -import edu.kit.provideq.toolbox.circuit.processing.solver.CircuitProcessingSolver; +import edu.kit.provideq.toolbox.circuit.processing.solvers.CircuitProcessingSolver; import edu.kit.provideq.toolbox.meta.ProblemSolver; import edu.kit.provideq.toolbox.meta.ProblemType; import edu.kit.provideq.toolbox.meta.SolvingProperties; @@ -81,7 +81,6 @@ public Mono> solve( .map(s -> s.getSelectedOptionT(OptimizationSolver.QuantumOptimizer::fromValue)) .orElse(DEFAULT_OPTIMIZER); - //String[] inputArray = new String[]{input}; var processResult = context .getBean(PythonProcessRunner.class, scriptPath + selectedOptimizer.getScriptPath(), venv) .withArguments( diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index a9f06430..07e15a62 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -125,7 +125,7 @@ venv.demonstrators.qiskit.molecule-energy=${name.demonstrators}_${name.demonstra name.circuitprocessing=circuitprocessing path.circuitprocessing=${name.solvers}/${name.circuitprocessing} name.circuitprocessing.circuitexecution=circuitexecution -path.circuitprocessing.circuitexecution=${path.circuitprocessing}/${name.circuitprocessing.circuitexecution}/ +path.circuitprocessing.circuitexecution=${path.circuitprocessing}/${name.circuitprocessing.circuitexecution}/executor.py venv.circuitprocessing.circuitexecution=${name.solvers}_${name.circuitprocessing}_${name.circuitprocessing.circuitexecution} name.circuitprocessing.circuitoptimization=circuitoptimizing path.circuitprocessing.circuitoptimization=${path.circuitprocessing}/${name.circuitprocessing.circuitoptimization}/ diff --git a/src/test/java/edu/kit/provideq/toolbox/api/CircuitProcessingSolversTest.java b/src/test/java/edu/kit/provideq/toolbox/api/CircuitProcessingSolversTest.java index e31e8382..2257eb7b 100644 --- a/src/test/java/edu/kit/provideq/toolbox/api/CircuitProcessingSolversTest.java +++ b/src/test/java/edu/kit/provideq/toolbox/api/CircuitProcessingSolversTest.java @@ -1,19 +1,19 @@ package edu.kit.provideq.toolbox.api; import static edu.kit.provideq.toolbox.circuit.processing.CircuitProcessingConfiguration.CIRCUIT_PROCESSING; -import static edu.kit.provideq.toolbox.circuit.processing.solver.executor.ExecutorConfiguration.EXECUTOR_CONFIG; -import static edu.kit.provideq.toolbox.circuit.processing.solver.mitigation.ErrorMitigationConfiguration.MITIGATION_CONFIG; -import static edu.kit.provideq.toolbox.circuit.processing.solver.optimization.OptimizationConfiguration.OPTIMIZATION_CONFIG; +import static edu.kit.provideq.toolbox.circuit.processing.solvers.executor.ExecutorConfiguration.EXECUTOR_CONFIG; +import static edu.kit.provideq.toolbox.circuit.processing.solvers.mitigation.ErrorMitigationConfiguration.MITIGATION_CONFIG; +import static edu.kit.provideq.toolbox.circuit.processing.solvers.optimization.OptimizationConfiguration.OPTIMIZATION_CONFIG; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import edu.kit.provideq.toolbox.circuit.processing.solver.MoveToExecutionSolver; -import edu.kit.provideq.toolbox.circuit.processing.solver.MoveToMitigationSolver; -import edu.kit.provideq.toolbox.circuit.processing.solver.MoveToOptimizationSolver; -import edu.kit.provideq.toolbox.circuit.processing.solver.executor.ExecutionSolver; -import edu.kit.provideq.toolbox.circuit.processing.solver.mitigation.ErrorMitigationSolver; -import edu.kit.provideq.toolbox.circuit.processing.solver.optimization.OptimizationSolver; +import edu.kit.provideq.toolbox.circuit.processing.solvers.MoveToExecutionSolver; +import edu.kit.provideq.toolbox.circuit.processing.solvers.MoveToMitigationSolver; +import edu.kit.provideq.toolbox.circuit.processing.solvers.MoveToOptimizationSolver; +import edu.kit.provideq.toolbox.circuit.processing.solvers.executor.ExecutionSolver; +import edu.kit.provideq.toolbox.circuit.processing.solvers.mitigation.ErrorMitigationSolver; +import edu.kit.provideq.toolbox.circuit.processing.solvers.optimization.OptimizationSolver; import edu.kit.provideq.toolbox.meta.Problem; import edu.kit.provideq.toolbox.meta.ProblemManager; import edu.kit.provideq.toolbox.meta.ProblemManagerProvider; diff --git a/src/test/java/edu/kit/provideq/toolbox/api/ErrorMitigationSolverTest.java b/src/test/java/edu/kit/provideq/toolbox/api/ErrorMitigationSolverTest.java index 1815a325..3ebd03be 100644 --- a/src/test/java/edu/kit/provideq/toolbox/api/ErrorMitigationSolverTest.java +++ b/src/test/java/edu/kit/provideq/toolbox/api/ErrorMitigationSolverTest.java @@ -1,9 +1,9 @@ package edu.kit.provideq.toolbox.api; -import static edu.kit.provideq.toolbox.circuit.processing.solver.mitigation.ErrorMitigationConfiguration.MITIGATION_CONFIG; +import static edu.kit.provideq.toolbox.circuit.processing.solvers.mitigation.ErrorMitigationConfiguration.MITIGATION_CONFIG; import static org.junit.jupiter.api.Assertions.assertEquals; -import edu.kit.provideq.toolbox.circuit.processing.solver.mitigation.ErrorMitigationSolver; +import edu.kit.provideq.toolbox.circuit.processing.solvers.mitigation.ErrorMitigationSolver; import edu.kit.provideq.toolbox.meta.Problem; import edu.kit.provideq.toolbox.meta.ProblemManager; import edu.kit.provideq.toolbox.meta.ProblemManagerProvider; diff --git a/src/test/java/edu/kit/provideq/toolbox/api/ExecutionSolverTest.java b/src/test/java/edu/kit/provideq/toolbox/api/ExecutionSolverTest.java index 11549377..5e386689 100644 --- a/src/test/java/edu/kit/provideq/toolbox/api/ExecutionSolverTest.java +++ b/src/test/java/edu/kit/provideq/toolbox/api/ExecutionSolverTest.java @@ -1,10 +1,10 @@ package edu.kit.provideq.toolbox.api; -import static edu.kit.provideq.toolbox.circuit.processing.solver.executor.ExecutorConfiguration.EXECUTOR_CONFIG; +import static edu.kit.provideq.toolbox.circuit.processing.solvers.executor.ExecutorConfiguration.EXECUTOR_CONFIG; import static org.junit.jupiter.api.Assertions.assertTrue; import edu.kit.provideq.toolbox.Solution; -import edu.kit.provideq.toolbox.circuit.processing.solver.executor.ExecutionSolver; +import edu.kit.provideq.toolbox.circuit.processing.solvers.executor.ExecutionSolver; import edu.kit.provideq.toolbox.meta.Problem; import edu.kit.provideq.toolbox.meta.ProblemManagerProvider; import java.time.Duration; diff --git a/src/test/java/edu/kit/provideq/toolbox/api/OptimizationSolverTest.java b/src/test/java/edu/kit/provideq/toolbox/api/OptimizationSolverTest.java index 4945a5c7..3c19e172 100644 --- a/src/test/java/edu/kit/provideq/toolbox/api/OptimizationSolverTest.java +++ b/src/test/java/edu/kit/provideq/toolbox/api/OptimizationSolverTest.java @@ -1,13 +1,13 @@ package edu.kit.provideq.toolbox.api; import static edu.kit.provideq.toolbox.circuit.processing.CircuitProcessingConfiguration.CIRCUIT_PROCESSING; -import static edu.kit.provideq.toolbox.circuit.processing.solver.mitigation.ErrorMitigationConfiguration.MITIGATION_CONFIG; -import static edu.kit.provideq.toolbox.circuit.processing.solver.optimization.OptimizationConfiguration.OPTIMIZATION_CONFIG; +import static edu.kit.provideq.toolbox.circuit.processing.solvers.mitigation.ErrorMitigationConfiguration.MITIGATION_CONFIG; +import static edu.kit.provideq.toolbox.circuit.processing.solvers.optimization.OptimizationConfiguration.OPTIMIZATION_CONFIG; import static org.junit.jupiter.api.Assertions.assertTrue; -import edu.kit.provideq.toolbox.circuit.processing.solver.MoveToMitigationSolver; -import edu.kit.provideq.toolbox.circuit.processing.solver.mitigation.ErrorMitigationSolver; -import edu.kit.provideq.toolbox.circuit.processing.solver.optimization.OptimizationSolver; +import edu.kit.provideq.toolbox.circuit.processing.solvers.MoveToMitigationSolver; +import edu.kit.provideq.toolbox.circuit.processing.solvers.mitigation.ErrorMitigationSolver; +import edu.kit.provideq.toolbox.circuit.processing.solvers.optimization.OptimizationSolver; import edu.kit.provideq.toolbox.meta.Problem; import edu.kit.provideq.toolbox.meta.ProblemManager; import edu.kit.provideq.toolbox.meta.ProblemManagerProvider; From 431f503bab9840f7744b2085f134934f295f4996 Mon Sep 17 00:00:00 2001 From: ekulos-code Date: Mon, 15 Jun 2026 20:36:25 +0200 Subject: [PATCH 122/137] Implement second range of suggestions --- .../edu/kit/provideq/toolbox/Solution.java | 20 ++++++++++-- .../solvers/MoveToExecutionSolver.java | 14 +------- .../solvers/executor/ExecutionResult.java | 13 ++++++++ .../optimization/OptimizationSolver.java | 32 ++++++++----------- src/main/resources/application.properties | 4 ++- 5 files changed, 48 insertions(+), 35 deletions(-) diff --git a/src/main/java/edu/kit/provideq/toolbox/Solution.java b/src/main/java/edu/kit/provideq/toolbox/Solution.java index 6b29f8c0..cb55ac3a 100644 --- a/src/main/java/edu/kit/provideq/toolbox/Solution.java +++ b/src/main/java/edu/kit/provideq/toolbox/Solution.java @@ -33,13 +33,13 @@ private Solution(UUID id) { this.id = id; } - public Solution(ProblemSolver problemSolver) { + public Solution(ProblemSolver problemSolver) { this.id = UUID.randomUUID(); this.solverName = problemSolver.getName(); } - public static Solution failed( - ProblemSolver problemSolver, + public static Solution failed( + ProblemSolver problemSolver, String text) { var solution = new Solution(problemSolver); solution.fail(); @@ -47,6 +47,20 @@ public static Solution failed( return solution; } + public static Solution from( + ProblemSolver solver, + Solution source, + Function mapper) { + var result = new Solution(); + result.solverName = source.solverName; + result.status = source.status; + result.metaData = source.metaData; + result.debugData = source.debugData; + result.executionMilliseconds = source.executionMilliseconds; + result.solutionData = source.solutionData == null ? null : mapper.apply(source.solutionData); + return result; + } + public UUID getId() { return this.id; } diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/MoveToExecutionSolver.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/MoveToExecutionSolver.java index 5a11d092..20906aa5 100644 --- a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/MoveToExecutionSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/MoveToExecutionSolver.java @@ -1,7 +1,6 @@ package edu.kit.provideq.toolbox.circuit.processing.solvers; import edu.kit.provideq.toolbox.Solution; -import edu.kit.provideq.toolbox.SolutionStatus; import edu.kit.provideq.toolbox.circuit.processing.solvers.executor.ExecutionResult; import edu.kit.provideq.toolbox.circuit.processing.solvers.executor.ExecutorConfiguration; import edu.kit.provideq.toolbox.meta.SolvingProperties; @@ -42,17 +41,6 @@ public Mono> solve( SolvingProperties properties ) { return subRoutineResolver.runSubRoutine(EXECUTOR_SUBROUTINE, input) - .map(executionResultSolution -> { - Solution solution = new Solution<>(this); - SolutionStatus status = executionResultSolution.getStatus(); - if (status == SolutionStatus.ERROR) { - solution.fail(); - solution.setDebugData(executionResultSolution.getDebugData()); - return solution; - } - solution.complete(); - solution.setSolutionData(executionResultSolution.getSolutionData().toString()); - return solution; - }); + .map(s -> Solution.from(this, s, ExecutionResult::toString)); } } diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/executor/ExecutionResult.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/executor/ExecutionResult.java index 4e2212b7..5936ac07 100644 --- a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/executor/ExecutionResult.java +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/executor/ExecutionResult.java @@ -10,4 +10,17 @@ public boolean hasResult() { public boolean hasCircuit() { return circuit.isPresent(); } + + @Override + public String toString() { + var stringBuilder = new StringBuilder(); + resultString.ifPresent(r -> stringBuilder.append("Result:\n").append(r)); + circuit.ifPresent(c -> { + if (!stringBuilder.isEmpty()) { + stringBuilder.append("\n\n"); + } + stringBuilder.append("Circuit:\n").append(c); + }); + return stringBuilder.toString(); + } } diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/optimization/OptimizationSolver.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/optimization/OptimizationSolver.java index fb382c8c..e0a4d48f 100644 --- a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/optimization/OptimizationSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/optimization/OptimizationSolver.java @@ -12,6 +12,7 @@ import edu.kit.provideq.toolbox.process.ProcessRunner; import edu.kit.provideq.toolbox.process.PythonProcessRunner; import java.util.List; +import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationContext; @@ -24,17 +25,21 @@ public class OptimizationSolver implements ProblemSolver { private static final OptimizationSolver.QuantumOptimizer DEFAULT_OPTIMIZER = QuantumOptimizer.DECOMPOSE_MULTI_CX; - private final String scriptPath; + private final Map optimizerScriptPaths; private final String venv; private final ApplicationContext context; @Autowired public OptimizationSolver( - @Value("${path.circuitprocessing.circuitoptimization}") String scriptPath, + @Value("${path.circuitprocessing.circuitoptimization.decomposemulticx}") String decomposeMultiCxPath, + @Value("${path.circuitprocessing.circuitoptimization.removeredundancies}") String removeRedundanciesPath, @Value("${venv.circuitprocessing.circuitoptimization}") String venv, ApplicationContext context ) { - this.scriptPath = scriptPath; + this.optimizerScriptPaths = Map.of( + QuantumOptimizer.DECOMPOSE_MULTI_CX, decomposeMultiCxPath, + QuantumOptimizer.REMOVE_REDUNDANCIES, removeRedundanciesPath + ); this.venv = venv; this.context = context; } @@ -82,7 +87,7 @@ public Mono> solve( .orElse(DEFAULT_OPTIMIZER); var processResult = context - .getBean(PythonProcessRunner.class, scriptPath + selectedOptimizer.getScriptPath(), venv) + .getBean(PythonProcessRunner.class, optimizerScriptPaths.get(selectedOptimizer), venv) .withArguments( ProcessRunner.INPUT_FILE_PATH ) @@ -91,11 +96,10 @@ public Mono> solve( .run(getProblemType(), solution.getId()); if (processResult.success() && processResult.output().isPresent()) { - solution.complete(); - solution.setSolutionData(processResult.output().get()); return subRoutineResolver .runSubRoutine(CircuitProcessingSolver.CIRCUIT_PROCESSING_SUBROUTINE, - processResult.output().get()); + processResult.output().get()) + .map(subRoutineSolution -> Solution.from(this, subRoutineSolution, s -> s)); } solution.fail(); processResult.errorOutput().ifPresent(solution::setDebugData); @@ -108,27 +112,19 @@ public ProblemType getProblemType() { } enum QuantumOptimizer { - DECOMPOSE_MULTI_CX("DecomposeMultiQubitsCX", - "decompose-multi-cx/decompose_multi_cx_optimizer.py"), - REMOVE_REDUNDANCIES("RemoveRedundancies", - "remove-redundancies/remove_redundancies_optimizer.py"); + DECOMPOSE_MULTI_CX("DecomposeMultiQubitsCX"), + REMOVE_REDUNDANCIES("RemoveRedundancies"); private final String value; - private final String scriptPath; - QuantumOptimizer(String value, String scriptPath) { + QuantumOptimizer(String value) { this.value = value; - this.scriptPath = scriptPath; } public String getValue() { return value; } - public String getScriptPath() { - return scriptPath; - } - public static OptimizationSolver.QuantumOptimizer fromValue(String value) { for (OptimizationSolver.QuantumOptimizer simulator : values()) { if (simulator.value.equals(value)) { diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 07e15a62..0a020977 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -128,5 +128,7 @@ name.circuitprocessing.circuitexecution=circuitexecution path.circuitprocessing.circuitexecution=${path.circuitprocessing}/${name.circuitprocessing.circuitexecution}/executor.py venv.circuitprocessing.circuitexecution=${name.solvers}_${name.circuitprocessing}_${name.circuitprocessing.circuitexecution} name.circuitprocessing.circuitoptimization=circuitoptimizing -path.circuitprocessing.circuitoptimization=${path.circuitprocessing}/${name.circuitprocessing.circuitoptimization}/ +path.circuitprocessing.circuitoptimization=${path.circuitprocessing}/${name.circuitprocessing.circuitoptimization} +path.circuitprocessing.circuitoptimization.decomposemulticx=${path.circuitprocessing.circuitoptimization}/decompose-multi-cx/decompose_multi_cx_optimizer.py +path.circuitprocessing.circuitoptimization.removeredundancies=${path.circuitprocessing.circuitoptimization}/remove-redundancies/remove_redundancies_optimizer.py venv.circuitprocessing.circuitoptimization=${name.solvers}_${name.circuitprocessing}_${name.circuitprocessing.circuitoptimization} From d2d92353bd22bde0b182e6da99d437ed8fa476a0 Mon Sep 17 00:00:00 2001 From: ekulos-code Date: Thu, 25 Jun 2026 04:40:14 +0200 Subject: [PATCH 123/137] Fix checkstyle errors --- .../executor/ExecutorConfiguration.java | 3 ++- .../ErrorMitigationConfiguration.java | 3 ++- .../OptimizationConfiguration.java | 3 ++- .../optimization/OptimizationSolver.java | 6 ++++-- .../api/CircuitProcessingSolversTest.java | 21 ++++++++++++------- .../api/ErrorMitigationSolverTest.java | 3 ++- .../toolbox/api/OptimizationSolverTest.java | 6 ++++-- 7 files changed, 30 insertions(+), 15 deletions(-) diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/executor/ExecutorConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/executor/ExecutorConfiguration.java index d9283ba2..cbd2f88e 100644 --- a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/executor/ExecutorConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/executor/ExecutorConfiguration.java @@ -36,7 +36,8 @@ ProblemManager getExecutorProblemManager( private Set> loadExampleProblems(ResourceProvider provider) { try { var problemStream = Objects.requireNonNull( - getClass().getResourceAsStream("../../bell-state.qasm"), "Problem bell-state.qasm not found"); + getClass().getResourceAsStream("../../bell-state.qasm"), + "Problem bell-state.qasm not found"); var problem = new Problem<>(EXECUTOR_CONFIG); problem.setInput(provider.readStream(problemStream)); return Set.of(problem); diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/mitigation/ErrorMitigationConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/mitigation/ErrorMitigationConfiguration.java index cdf489e5..69156d7d 100644 --- a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/mitigation/ErrorMitigationConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/mitigation/ErrorMitigationConfiguration.java @@ -36,7 +36,8 @@ ProblemManager getMitigationProblemManager( private Set> loadExampleProblems(ResourceProvider provider) { try { var problemStream = Objects.requireNonNull( - getClass().getResourceAsStream("../../bell-state.qasm"), "Problem bell-state.qasm not found"); + getClass().getResourceAsStream("../../bell-state.qasm"), + "Problem bell-state.qasm not found"); var problem = new Problem<>(MITIGATION_CONFIG); problem.setInput(provider.readStream(problemStream)); return Set.of(problem); diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/optimization/OptimizationConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/optimization/OptimizationConfiguration.java index 26fba1fd..bc9aba98 100644 --- a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/optimization/OptimizationConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/optimization/OptimizationConfiguration.java @@ -36,7 +36,8 @@ ProblemManager getOptimizationProblemManager( private Set> loadExampleProblems(ResourceProvider provider) { try { var problemStream = Objects.requireNonNull( - getClass().getResourceAsStream("../../bell-state.qasm"), "Problem bell-state.qasm not found"); + getClass().getResourceAsStream("../../bell-state.qasm"), + "Problem bell-state.qasm not found"); var problem = new Problem<>(OPTIMIZATION_CONFIG); problem.setInput(provider.readStream(problemStream)); return Set.of(problem); diff --git a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/optimization/OptimizationSolver.java b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/optimization/OptimizationSolver.java index e0a4d48f..5106a296 100644 --- a/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/optimization/OptimizationSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/circuit/processing/solvers/optimization/OptimizationSolver.java @@ -31,8 +31,10 @@ public class OptimizationSolver implements ProblemSolver { @Autowired public OptimizationSolver( - @Value("${path.circuitprocessing.circuitoptimization.decomposemulticx}") String decomposeMultiCxPath, - @Value("${path.circuitprocessing.circuitoptimization.removeredundancies}") String removeRedundanciesPath, + @Value("${path.circuitprocessing.circuitoptimization.decomposemulticx}") + String decomposeMultiCxPath, + @Value("${path.circuitprocessing.circuitoptimization.removeredundancies}") + String removeRedundanciesPath, @Value("${venv.circuitprocessing.circuitoptimization}") String venv, ApplicationContext context ) { diff --git a/src/test/java/edu/kit/provideq/toolbox/api/CircuitProcessingSolversTest.java b/src/test/java/edu/kit/provideq/toolbox/api/CircuitProcessingSolversTest.java index 2257eb7b..1b9ae252 100644 --- a/src/test/java/edu/kit/provideq/toolbox/api/CircuitProcessingSolversTest.java +++ b/src/test/java/edu/kit/provideq/toolbox/api/CircuitProcessingSolversTest.java @@ -74,9 +74,12 @@ void beforeEach() { @Test void testMoveToMitigationSolver() { var circuit = problems.get(0); - var problemDto = ApiTestHelper.createProblem(client, moveToMitigationSolver, circuit, CIRCUIT_PROCESSING); - var subProblemId = problemDto.getSubProblems().get(0).getSubProblemIds().get(0); - ApiTestHelper.setProblemSolver(client, errorMitigationSolver, subProblemId, MITIGATION_CONFIG.getId()); + var problemDto = ApiTestHelper.createProblem(client, moveToMitigationSolver, + circuit, CIRCUIT_PROCESSING); + var subProblemId = problemDto.getSubProblems().get(0) + .getSubProblemIds().get(0); + ApiTestHelper.setProblemSolver(client, errorMitigationSolver, subProblemId, + MITIGATION_CONFIG.getId()); var solvedDto = ApiTestHelper.trySolveFor(60, client, problemDto.getId(), CIRCUIT_PROCESSING); ApiTestHelper.testSolution(solvedDto); assertEquals(circuit, solvedDto.getSolution().getSolutionData()); @@ -85,10 +88,12 @@ void testMoveToMitigationSolver() { @Test void testMoveToExecutionSolver() { var circuit = problems.get(0); - var problemDto = ApiTestHelper.createProblem(client, moveToExecutionSolver, circuit, CIRCUIT_PROCESSING); + var problemDto = ApiTestHelper.createProblem(client, moveToExecutionSolver, + circuit, CIRCUIT_PROCESSING); var subProblemId = problemDto.getSubProblems().get(0).getSubProblemIds().get(0); ApiTestHelper.setProblemSolver(client, executionSolver, subProblemId, EXECUTOR_CONFIG.getId()); - var solvedDto = ApiTestHelper.trySolveFor(60, client, problemDto.getId(), CIRCUIT_PROCESSING); + var solvedDto = ApiTestHelper.trySolveFor(60, client, + problemDto.getId(), CIRCUIT_PROCESSING); ApiTestHelper.testSolution(solvedDto); assertFalse(solvedDto.getSolution().getSolutionData().isBlank()); } @@ -96,7 +101,8 @@ void testMoveToExecutionSolver() { @Test void testMoveToOptimizationSolver() { var circuit = problems.get(0); - var problemDto = ApiTestHelper.createProblem(client, moveToOptimizationSolver, circuit, CIRCUIT_PROCESSING); + var problemDto = ApiTestHelper.createProblem(client, moveToOptimizationSolver, + circuit, CIRCUIT_PROCESSING); var optSubProblemId = problemDto.getSubProblems().get(0).getSubProblemIds().get(0); var optDto = ApiTestHelper.setProblemSolver( client, optimizationSolver, optSubProblemId, OPTIMIZATION_CONFIG.getId()); @@ -104,7 +110,8 @@ void testMoveToOptimizationSolver() { var mitigationEntryDto = ApiTestHelper.setProblemSolver( client, moveToMitigationSolver, circuitSubProblemId, CIRCUIT_PROCESSING.getId()); var mitigationId = mitigationEntryDto.getSubProblems().get(0).getSubProblemIds().get(0); - ApiTestHelper.setProblemSolver(client, errorMitigationSolver, mitigationId, MITIGATION_CONFIG.getId()); + ApiTestHelper.setProblemSolver(client, errorMitigationSolver, mitigationId, + MITIGATION_CONFIG.getId()); var solvedDto = ApiTestHelper.trySolveFor(120, client, problemDto.getId(), CIRCUIT_PROCESSING); ApiTestHelper.testSolution(solvedDto); assertTrue(solvedDto.getSolution().getSolutionData().contains("OPENQASM")); diff --git a/src/test/java/edu/kit/provideq/toolbox/api/ErrorMitigationSolverTest.java b/src/test/java/edu/kit/provideq/toolbox/api/ErrorMitigationSolverTest.java index 3ebd03be..28fde2a0 100644 --- a/src/test/java/edu/kit/provideq/toolbox/api/ErrorMitigationSolverTest.java +++ b/src/test/java/edu/kit/provideq/toolbox/api/ErrorMitigationSolverTest.java @@ -45,7 +45,8 @@ void beforeEach() { @Test void testErrorMitigationSolver() { var circuit = problems.get(0); - var problem = ApiTestHelper.createProblem(client, errorMitigationSolver, circuit, MITIGATION_CONFIG); + var problem = ApiTestHelper.createProblem(client, errorMitigationSolver, + circuit, MITIGATION_CONFIG); ApiTestHelper.testSolution(problem); assertEquals(circuit, problem.getSolution().getSolutionData()); } diff --git a/src/test/java/edu/kit/provideq/toolbox/api/OptimizationSolverTest.java b/src/test/java/edu/kit/provideq/toolbox/api/OptimizationSolverTest.java index 3c19e172..1d491bd3 100644 --- a/src/test/java/edu/kit/provideq/toolbox/api/OptimizationSolverTest.java +++ b/src/test/java/edu/kit/provideq/toolbox/api/OptimizationSolverTest.java @@ -59,12 +59,14 @@ void beforeEach() { @Test void testOptimizationSolver() { var circuit = problems.get(0); - var problemDto = ApiTestHelper.createProblem(client, optimizationSolver, circuit, OPTIMIZATION_CONFIG); + var problemDto = ApiTestHelper.createProblem(client, optimizationSolver, + circuit, OPTIMIZATION_CONFIG); var circuitSubProblemId = problemDto.getSubProblems().get(0).getSubProblemIds().get(0); var mitigationEntryDto = ApiTestHelper.setProblemSolver( client, moveToMitigationSolver, circuitSubProblemId, CIRCUIT_PROCESSING.getId()); var mitigationId = mitigationEntryDto.getSubProblems().get(0).getSubProblemIds().get(0); - ApiTestHelper.setProblemSolver(client, errorMitigationSolver, mitigationId, MITIGATION_CONFIG.getId()); + ApiTestHelper.setProblemSolver(client, errorMitigationSolver, mitigationId, + MITIGATION_CONFIG.getId()); var solvedDto = ApiTestHelper.trySolveFor(120, client, problemDto.getId(), OPTIMIZATION_CONFIG); ApiTestHelper.testSolution(solvedDto); assertTrue(solvedDto.getSolution().getSolutionData().contains("OPENQASM")); From fc53a5eb701fb898f37e799d353b533ece27629d Mon Sep 17 00:00:00 2001 From: Elscrux Date: Fri, 26 Jun 2026 15:18:25 +0200 Subject: [PATCH 124/137] feat(qubo): deprecate kipu and quantagonia qubo solver --- .../edu/kit/provideq/toolbox/qubo/QuboConfiguration.java | 7 +------ .../kit/provideq/toolbox/qubo/solvers/KipuQuboSolver.java | 1 + .../toolbox/qubo/solvers/QuantagoniaQuboSolver.java | 1 + 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/main/java/edu/kit/provideq/toolbox/qubo/QuboConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/qubo/QuboConfiguration.java index f95dbd81..ee8bb95f 100644 --- a/src/main/java/edu/kit/provideq/toolbox/qubo/QuboConfiguration.java +++ b/src/main/java/edu/kit/provideq/toolbox/qubo/QuboConfiguration.java @@ -7,10 +7,8 @@ import edu.kit.provideq.toolbox.meta.ProblemType; import edu.kit.provideq.toolbox.qubo.solvers.DwaveQuboSolver; import edu.kit.provideq.toolbox.qubo.solvers.GamsQuboSolver; -import edu.kit.provideq.toolbox.qubo.solvers.KipuQuboSolver; import edu.kit.provideq.toolbox.qubo.solvers.QiskitQuboSolver; import edu.kit.provideq.toolbox.qubo.solvers.QrispQuboSolver; -import edu.kit.provideq.toolbox.qubo.solvers.QuantagoniaQuboSolver; import java.io.IOException; import java.util.Objects; import java.util.Set; @@ -42,15 +40,12 @@ ProblemManager getQuboManager( QiskitQuboSolver qiskitSolver, DwaveQuboSolver dwaveSolver, QrispQuboSolver qrispSolver, - QuantagoniaQuboSolver quantagoniaQuboSolver, - KipuQuboSolver kipuQuboSolver, GamsQuboSolver gamsQuboSolver, ResourceProvider resourceProvider ) { return new ProblemManager<>( QUBO, - Set.of(qiskitSolver, dwaveSolver, qrispSolver, quantagoniaQuboSolver, kipuQuboSolver, - gamsQuboSolver), + Set.of(qiskitSolver, dwaveSolver, qrispSolver, gamsQuboSolver), loadExampleProblems(resourceProvider) ); } diff --git a/src/main/java/edu/kit/provideq/toolbox/qubo/solvers/KipuQuboSolver.java b/src/main/java/edu/kit/provideq/toolbox/qubo/solvers/KipuQuboSolver.java index 060c01f1..270a881c 100644 --- a/src/main/java/edu/kit/provideq/toolbox/qubo/solvers/KipuQuboSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/qubo/solvers/KipuQuboSolver.java @@ -28,6 +28,7 @@ * Kipu Digitized Counterdiabatic Quantum Optimization (DCQO) solver hosted on the PlanQK platform. */ @Component +@Deprecated public class KipuQuboSolver extends QuboSolver { private static final String SETTING_PLANQK_TOKEN = "PlanQK Access Token"; diff --git a/src/main/java/edu/kit/provideq/toolbox/qubo/solvers/QuantagoniaQuboSolver.java b/src/main/java/edu/kit/provideq/toolbox/qubo/solvers/QuantagoniaQuboSolver.java index 5862a833..bb5c1d1c 100644 --- a/src/main/java/edu/kit/provideq/toolbox/qubo/solvers/QuantagoniaQuboSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/qubo/solvers/QuantagoniaQuboSolver.java @@ -27,6 +27,7 @@ * Quantagonia QUBO solver hosted on the PlanQK platform. */ @Component +@Deprecated public class QuantagoniaQuboSolver extends QuboSolver { private static final String SETTING_PLANQK_TOKEN = "PlanQK Access Token"; From 589aa589fb6935b72e448ef3309c43d475e1da0c Mon Sep 17 00:00:00 2001 From: Elscrux Date: Fri, 26 Jun 2026 15:50:48 +0200 Subject: [PATCH 125/137] test(qubo): remove kipu and quantagonia qubo --- .../java/edu/kit/provideq/toolbox/api/QuboSolverTest.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/test/java/edu/kit/provideq/toolbox/api/QuboSolverTest.java b/src/test/java/edu/kit/provideq/toolbox/api/QuboSolverTest.java index 5cdcad08..cb53dff3 100644 --- a/src/test/java/edu/kit/provideq/toolbox/api/QuboSolverTest.java +++ b/src/test/java/edu/kit/provideq/toolbox/api/QuboSolverTest.java @@ -41,11 +41,6 @@ Stream provideArguments() { var problemManager = problemManagerProvider.findProblemManagerForType(QUBO).get(); return ApiTestHelper.getAllArgumentCombinations(problemManager) - .filter(list -> { - Object solver = list.get(0); - return !(solver instanceof KipuQuboSolver) - && !(solver instanceof QuantagoniaQuboSolver); - }) .map(list -> Arguments.of(list.get(0), list.get(1))); } From c7aa6b2bb882510dd36e9809106fe37e818e5128 Mon Sep 17 00:00:00 2001 From: Elscrux Date: Fri, 26 Jun 2026 16:00:26 +0200 Subject: [PATCH 126/137] ci: automatic deployment --- .github/workflows/ci-cd-develop.yml | 20 ++++++++++++-------- .github/workflows/deploy-main.yml | 24 ------------------------ 2 files changed, 12 insertions(+), 32 deletions(-) delete mode 100644 .github/workflows/deploy-main.yml diff --git a/.github/workflows/ci-cd-develop.yml b/.github/workflows/ci-cd-develop.yml index d10465fa..7a1f79a0 100644 --- a/.github/workflows/ci-cd-develop.yml +++ b/.github/workflows/ci-cd-develop.yml @@ -65,16 +65,20 @@ jobs: deploy-staging: runs-on: ubuntu-latest needs: check - if: ${{ github.ref == 'refs/heads/develop' }} + if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop' }} steps: - - name: Clone repo (shallow) + - name: Cloning repo uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Push to dokku - uses: dokku/github-action@master + + - name: Deploy via SSH and Docker Compose + uses: appleboy/ssh-action@v1.2.5 with: - branch: develop - # only extracting the server address to a secret avoids leaking it in the logs - git_remote_url: ssh://dokku@${{ secrets.DOKKU_SERVER_ADDRESS }}/toolbox-backend-staging - ssh_private_key: ${{ secrets.DOKKU_DEPLOYMENT_KEY }} + host: ${{ secrets.DEPLOY_HOST }} + username: ${{ secrets.DEPLOY_USER }} + key: ${{ secrets.DEPLOY_SSH_KEY }} + password: ${{ secrets.DEPLOY_SSH_PASSWORD }} + script_path: | + echo "Deploying ${{ github.event.repository.name }} to ${{ github.ref_name }} environment" + sh /root/ProvideQ/${{ github.event.repository.name }}/deploy.sh ${{ github.ref_name }} \ No newline at end of file diff --git a/.github/workflows/deploy-main.yml b/.github/workflows/deploy-main.yml deleted file mode 100644 index cc65e593..00000000 --- a/.github/workflows/deploy-main.yml +++ /dev/null @@ -1,24 +0,0 @@ -# adapted from https://github.com/dokku/github-action/blob/db5e3b84461e5e73c56d8b0f6a67aab0df25256c/example-workflows/simple.yml -name: 'deploy-main' - -on: - push: - branches: - - main - -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - name: Cloning repo - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Push to dokku - uses: dokku/github-action@master - with: - branch: main - # only extracting the server address to a secret avoids leaking it in the logs - git_remote_url: ssh://dokku@${{ secrets.DOKKU_SERVER_ADDRESS }}/toolbox-backend - ssh_private_key: ${{ secrets.DOKKU_DEPLOYMENT_KEY }} From 9f265b8164fe9bbf3b22a222e9647bdb0da08fba Mon Sep 17 00:00:00 2001 From: Elscrux Date: Fri, 26 Jun 2026 16:27:54 +0200 Subject: [PATCH 127/137] ci: remove ssh password --- .github/workflows/ci-cd-develop.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci-cd-develop.yml b/.github/workflows/ci-cd-develop.yml index 7a1f79a0..b6adc3be 100644 --- a/.github/workflows/ci-cd-develop.yml +++ b/.github/workflows/ci-cd-develop.yml @@ -78,7 +78,6 @@ jobs: host: ${{ secrets.DEPLOY_HOST }} username: ${{ secrets.DEPLOY_USER }} key: ${{ secrets.DEPLOY_SSH_KEY }} - password: ${{ secrets.DEPLOY_SSH_PASSWORD }} script_path: | echo "Deploying ${{ github.event.repository.name }} to ${{ github.ref_name }} environment" sh /root/ProvideQ/${{ github.event.repository.name }}/deploy.sh ${{ github.ref_name }} \ No newline at end of file From 82e8eb95bbc471102a7f89a32988ab36f6490a83 Mon Sep 17 00:00:00 2001 From: Elscrux Date: Fri, 26 Jun 2026 19:23:55 +0200 Subject: [PATCH 128/137] ci: run script instead of trying to run a script file --- .github/workflows/ci-cd-develop.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-cd-develop.yml b/.github/workflows/ci-cd-develop.yml index b6adc3be..6b98530f 100644 --- a/.github/workflows/ci-cd-develop.yml +++ b/.github/workflows/ci-cd-develop.yml @@ -78,6 +78,6 @@ jobs: host: ${{ secrets.DEPLOY_HOST }} username: ${{ secrets.DEPLOY_USER }} key: ${{ secrets.DEPLOY_SSH_KEY }} - script_path: | + script: | echo "Deploying ${{ github.event.repository.name }} to ${{ github.ref_name }} environment" sh /root/ProvideQ/${{ github.event.repository.name }}/deploy.sh ${{ github.ref_name }} \ No newline at end of file From c97072ea4a749cc981efe99f131931ab28cc0fe9 Mon Sep 17 00:00:00 2001 From: Elscrux Date: Fri, 26 Jun 2026 19:24:56 +0200 Subject: [PATCH 129/137] chore: remove unused imports --- src/test/java/edu/kit/provideq/toolbox/api/QuboSolverTest.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/test/java/edu/kit/provideq/toolbox/api/QuboSolverTest.java b/src/test/java/edu/kit/provideq/toolbox/api/QuboSolverTest.java index cb53dff3..4a507f6d 100644 --- a/src/test/java/edu/kit/provideq/toolbox/api/QuboSolverTest.java +++ b/src/test/java/edu/kit/provideq/toolbox/api/QuboSolverTest.java @@ -2,11 +2,8 @@ import static edu.kit.provideq.toolbox.qubo.QuboConfiguration.QUBO; -import edu.kit.provideq.toolbox.SolutionStatus; import edu.kit.provideq.toolbox.meta.ProblemManagerProvider; import edu.kit.provideq.toolbox.meta.ProblemSolver; -import edu.kit.provideq.toolbox.qubo.solvers.KipuQuboSolver; -import edu.kit.provideq.toolbox.qubo.solvers.QuantagoniaQuboSolver; import java.time.Duration; import java.util.stream.Stream; import org.junit.jupiter.api.BeforeEach; From 1d7f9250860ae2b3ab2a5552b574a1fcc9e3bedf Mon Sep 17 00:00:00 2001 From: Elscrux Date: Fri, 26 Jun 2026 19:29:39 +0200 Subject: [PATCH 130/137] docs: document deprecation --- .../edu/kit/provideq/toolbox/qubo/solvers/KipuQuboSolver.java | 4 +++- .../provideq/toolbox/qubo/solvers/QuantagoniaQuboSolver.java | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/edu/kit/provideq/toolbox/qubo/solvers/KipuQuboSolver.java b/src/main/java/edu/kit/provideq/toolbox/qubo/solvers/KipuQuboSolver.java index 270a881c..42a8591e 100644 --- a/src/main/java/edu/kit/provideq/toolbox/qubo/solvers/KipuQuboSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/qubo/solvers/KipuQuboSolver.java @@ -26,9 +26,11 @@ /** * {@link QuboConfiguration#QUBO} solver using the * Kipu Digitized Counterdiabatic Quantum Optimization (DCQO) solver hosted on the PlanQK platform. + * + * @deprecated This solver is not provided on the Kipu platform anymore, use different Qubo solvers. */ @Component -@Deprecated +@Deprecated(forRemoval = true) public class KipuQuboSolver extends QuboSolver { private static final String SETTING_PLANQK_TOKEN = "PlanQK Access Token"; diff --git a/src/main/java/edu/kit/provideq/toolbox/qubo/solvers/QuantagoniaQuboSolver.java b/src/main/java/edu/kit/provideq/toolbox/qubo/solvers/QuantagoniaQuboSolver.java index bb5c1d1c..6a8d44cf 100644 --- a/src/main/java/edu/kit/provideq/toolbox/qubo/solvers/QuantagoniaQuboSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/qubo/solvers/QuantagoniaQuboSolver.java @@ -25,9 +25,11 @@ /** * {@link QuboConfiguration#QUBO} solver using the * Quantagonia QUBO solver hosted on the PlanQK platform. + * + * @deprecated This solver is not provided on the Kipu platform anymore, use different Qubo solvers. */ @Component -@Deprecated +@Deprecated(forRemoval = true) public class QuantagoniaQuboSolver extends QuboSolver { private static final String SETTING_PLANQK_TOKEN = "PlanQK Access Token"; From 2da8dcf728169012ca554fdc0b77051f3541c9c8 Mon Sep 17 00:00:00 2001 From: Elscrux Date: Fri, 26 Jun 2026 19:40:28 +0200 Subject: [PATCH 131/137] test: increase attempted solve time for all solver tests --- .../provideq/toolbox/api/CircuitProcessingSolversTest.java | 4 ++-- .../toolbox/api/FeatureModelAnomalySolversTest.java | 2 +- .../java/edu/kit/provideq/toolbox/api/SatSolversTest.java | 4 ++-- .../java/edu/kit/provideq/toolbox/api/TspSolverTest.java | 2 +- .../provideq/toolbox/api/UnsplittableMcfSolversTest.java | 2 +- .../java/edu/kit/provideq/toolbox/api/VrpSolverTest.java | 6 +++--- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/test/java/edu/kit/provideq/toolbox/api/CircuitProcessingSolversTest.java b/src/test/java/edu/kit/provideq/toolbox/api/CircuitProcessingSolversTest.java index 1b9ae252..98ba83d2 100644 --- a/src/test/java/edu/kit/provideq/toolbox/api/CircuitProcessingSolversTest.java +++ b/src/test/java/edu/kit/provideq/toolbox/api/CircuitProcessingSolversTest.java @@ -80,7 +80,7 @@ void testMoveToMitigationSolver() { .getSubProblemIds().get(0); ApiTestHelper.setProblemSolver(client, errorMitigationSolver, subProblemId, MITIGATION_CONFIG.getId()); - var solvedDto = ApiTestHelper.trySolveFor(60, client, problemDto.getId(), CIRCUIT_PROCESSING); + var solvedDto = ApiTestHelper.trySolveFor(120, client, problemDto.getId(), CIRCUIT_PROCESSING); ApiTestHelper.testSolution(solvedDto); assertEquals(circuit, solvedDto.getSolution().getSolutionData()); } @@ -92,7 +92,7 @@ void testMoveToExecutionSolver() { circuit, CIRCUIT_PROCESSING); var subProblemId = problemDto.getSubProblems().get(0).getSubProblemIds().get(0); ApiTestHelper.setProblemSolver(client, executionSolver, subProblemId, EXECUTOR_CONFIG.getId()); - var solvedDto = ApiTestHelper.trySolveFor(60, client, + var solvedDto = ApiTestHelper.trySolveFor(120, client, problemDto.getId(), CIRCUIT_PROCESSING); ApiTestHelper.testSolution(solvedDto); assertFalse(solvedDto.getSolution().getSolutionData().isBlank()); diff --git a/src/test/java/edu/kit/provideq/toolbox/api/FeatureModelAnomalySolversTest.java b/src/test/java/edu/kit/provideq/toolbox/api/FeatureModelAnomalySolversTest.java index a9e8e150..ef45afc6 100644 --- a/src/test/java/edu/kit/provideq/toolbox/api/FeatureModelAnomalySolversTest.java +++ b/src/test/java/edu/kit/provideq/toolbox/api/FeatureModelAnomalySolversTest.java @@ -87,7 +87,7 @@ void testFeatureModelAnomalySolver( } } - problem = ApiTestHelper.trySolveFor(15, client, problem.getId(), problemType); + problem = ApiTestHelper.trySolveFor(120, client, problem.getId(), problemType); ApiTestHelper.testSolution(problem); } } diff --git a/src/test/java/edu/kit/provideq/toolbox/api/SatSolversTest.java b/src/test/java/edu/kit/provideq/toolbox/api/SatSolversTest.java index 50845c93..4c15925d 100644 --- a/src/test/java/edu/kit/provideq/toolbox/api/SatSolversTest.java +++ b/src/test/java/edu/kit/provideq/toolbox/api/SatSolversTest.java @@ -68,7 +68,7 @@ Stream provideQrispExactGroverArguments() { var satManager = problemManagerProvider.findProblemManagerForType(SAT).get(); var qrispSolvers = satManager.getSolvers().stream() - .filter(s -> s instanceof QrispExactGroverSolver) + .filter(QrispExactGroverSolver.class::isInstance) .toList(); var exampleInputs = satManager.getExampleInstances().stream() @@ -119,7 +119,7 @@ void testQrispExactGroverSolver( ApiTestHelper.testSolution(assignedSubProblem); } } - var solvedProblemDto = ApiTestHelper.trySolveFor(60, client, problemDto.getId(), problemType); + var solvedProblemDto = ApiTestHelper.trySolveFor(120, client, problemDto.getId(), problemType); ApiTestHelper.testSolution(solvedProblemDto); } } diff --git a/src/test/java/edu/kit/provideq/toolbox/api/TspSolverTest.java b/src/test/java/edu/kit/provideq/toolbox/api/TspSolverTest.java index 345ebd2f..8d0dae97 100644 --- a/src/test/java/edu/kit/provideq/toolbox/api/TspSolverTest.java +++ b/src/test/java/edu/kit/provideq/toolbox/api/TspSolverTest.java @@ -105,7 +105,7 @@ void testQuboTspSolver(QuboSolver solver, String problemName) { ); //solve problem: - var solvedProblemDto = ApiTestHelper.trySolveFor(60, client, problemDto.getId(), TSP); + var solvedProblemDto = ApiTestHelper.trySolveFor(120, client, problemDto.getId(), TSP); ApiTestHelper.testSolution(solvedProblemDto); } } diff --git a/src/test/java/edu/kit/provideq/toolbox/api/UnsplittableMcfSolversTest.java b/src/test/java/edu/kit/provideq/toolbox/api/UnsplittableMcfSolversTest.java index 3abab6dd..5ff9ecf8 100644 --- a/src/test/java/edu/kit/provideq/toolbox/api/UnsplittableMcfSolversTest.java +++ b/src/test/java/edu/kit/provideq/toolbox/api/UnsplittableMcfSolversTest.java @@ -67,7 +67,7 @@ void testUnsplittableMcfSolver(ProblemSolver solver, } } - problem = ApiTestHelper.trySolveFor(20, client, problem.getId(), UNSPLITTABLE_MCF); + problem = ApiTestHelper.trySolveFor(120, client, problem.getId(), UNSPLITTABLE_MCF); ApiTestHelper.testSolution(problem); } } diff --git a/src/test/java/edu/kit/provideq/toolbox/api/VrpSolverTest.java b/src/test/java/edu/kit/provideq/toolbox/api/VrpSolverTest.java index d6bea5a0..b3bbb72a 100644 --- a/src/test/java/edu/kit/provideq/toolbox/api/VrpSolverTest.java +++ b/src/test/java/edu/kit/provideq/toolbox/api/VrpSolverTest.java @@ -135,7 +135,7 @@ void testKmeansWithLkhForVrp() { } //solve the problem: - problemDto = ApiTestHelper.trySolveFor(60, client, problemDto.getId(), VRP); + problemDto = ApiTestHelper.trySolveFor(120, client, problemDto.getId(), VRP); ApiTestHelper.testSolution(problemDto); } } @@ -176,7 +176,7 @@ void testTwoPhaseWithLkhForTsp() { } //solve the problem: - var solvedProblemDto = ApiTestHelper.trySolveFor(60, client, problemDto.getId(), VRP); + var solvedProblemDto = ApiTestHelper.trySolveFor(120, client, problemDto.getId(), VRP); ApiTestHelper.testSolution(solvedProblemDto); } } @@ -226,7 +226,7 @@ void testTwoPhaseWithAnnealer() { } //solve the problem: - var solvedProblemDto = ApiTestHelper.trySolveFor(60, client, problemDto.getId(), VRP); + var solvedProblemDto = ApiTestHelper.trySolveFor(120, client, problemDto.getId(), VRP); ApiTestHelper.testSolution(solvedProblemDto); } } From f30627ae9d5e6d4d8b64af1504a410ab0b8c8431 Mon Sep 17 00:00:00 2001 From: Elscrux Date: Fri, 26 Jun 2026 19:47:02 +0200 Subject: [PATCH 132/137] ci: debug --- .github/workflows/ci-cd-develop.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci-cd-develop.yml b/.github/workflows/ci-cd-develop.yml index 6b98530f..2b46ae9e 100644 --- a/.github/workflows/ci-cd-develop.yml +++ b/.github/workflows/ci-cd-develop.yml @@ -78,6 +78,8 @@ jobs: host: ${{ secrets.DEPLOY_HOST }} username: ${{ secrets.DEPLOY_USER }} key: ${{ secrets.DEPLOY_SSH_KEY }} + debug: true script: | + whoami echo "Deploying ${{ github.event.repository.name }} to ${{ github.ref_name }} environment" sh /root/ProvideQ/${{ github.event.repository.name }}/deploy.sh ${{ github.ref_name }} \ No newline at end of file From 918cd23e62053513e9b5b18ca9e91836f60ad6a9 Mon Sep 17 00:00:00 2001 From: Elscrux Date: Fri, 26 Jun 2026 20:02:58 +0200 Subject: [PATCH 133/137] ci: set command timeout to 60 minutes --- .github/workflows/ci-cd-develop.yml | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci-cd-develop.yml b/.github/workflows/ci-cd-develop.yml index 2b46ae9e..187c5f4d 100644 --- a/.github/workflows/ci-cd-develop.yml +++ b/.github/workflows/ci-cd-develop.yml @@ -67,19 +67,13 @@ jobs: needs: check if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop' }} steps: - - name: Cloning repo - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Deploy via SSH and Docker Compose uses: appleboy/ssh-action@v1.2.5 with: host: ${{ secrets.DEPLOY_HOST }} username: ${{ secrets.DEPLOY_USER }} key: ${{ secrets.DEPLOY_SSH_KEY }} - debug: true + command_timeout: 60m script: | - whoami - echo "Deploying ${{ github.event.repository.name }} to ${{ github.ref_name }} environment" + echo "Deploying ${{ github.event.repository.name }} to '${{ github.ref_name }}' environment" sh /root/ProvideQ/${{ github.event.repository.name }}/deploy.sh ${{ github.ref_name }} \ No newline at end of file From e171a862e7f554fac55935c702eeee6bf18241a8 Mon Sep 17 00:00:00 2001 From: Elscrux Date: Fri, 26 Jun 2026 23:11:21 +0200 Subject: [PATCH 134/137] ci: always clone the latest state first --- .github/workflows/ci-cd-develop.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/ci-cd-develop.yml b/.github/workflows/ci-cd-develop.yml index 187c5f4d..321b2dc4 100644 --- a/.github/workflows/ci-cd-develop.yml +++ b/.github/workflows/ci-cd-develop.yml @@ -67,6 +67,11 @@ jobs: needs: check if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop' }} steps: + - name: Cloning repo + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Deploy via SSH and Docker Compose uses: appleboy/ssh-action@v1.2.5 with: From c8ad9123380eadff243e1b950741fbb9f54afb5d Mon Sep 17 00:00:00 2001 From: Elscrux Date: Wed, 15 Jul 2026 00:43:24 +0200 Subject: [PATCH 135/137] ci: rename to simply ci-cd because it's also run for the master --- .github/workflows/{ci-cd-develop.yml => ci-cd.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{ci-cd-develop.yml => ci-cd.yml} (100%) diff --git a/.github/workflows/ci-cd-develop.yml b/.github/workflows/ci-cd.yml similarity index 100% rename from .github/workflows/ci-cd-develop.yml rename to .github/workflows/ci-cd.yml From 59d3fe3693b56e30990e198d648312e43e196b87 Mon Sep 17 00:00:00 2001 From: Elscrux Date: Wed, 15 Jul 2026 00:44:50 +0200 Subject: [PATCH 136/137] docs: update badge --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 70448e05..70742532 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ -[![Build](https://img.shields.io/github/actions/workflow/status/ProvideQ/toolbox-server/deploy-main.yml?style=for-the-badge)](https://github.com/ProvideQ/toolbox-server/actions/workflows/deploy-main.yml) +[![Build](https://img.shields.io/github/actions/workflow/status/ProvideQ/toolbox-server/ci-cd.yml?branch=main&style=for-the-badge +)](https://github.com/ProvideQ/toolbox-server/actions/workflows/ci-cd.yml) [![Release](https://img.shields.io/github/v/release/ProvideQ/toolbox-server?style=for-the-badge)](https://github.com/ProvideQ/toolbox-server/releases/) # ProvideQ Toolbox Server From bcf822bf4ea706eb4e07854f202c7c1945acae72 Mon Sep 17 00:00:00 2001 From: Domenik Eichhorn Date: Wed, 15 Jul 2026 11:08:21 +0200 Subject: [PATCH 137/137] chore: updated version number --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 3e1b4e9f..510fe67d 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ plugins { } group = 'edu.kit.provideq' -version = '0.4.1' +version = '0.5.0' java { sourceCompatibility = JavaVersion.VERSION_17