Repository: 1QB-Information-Technologies/ccvm (language: python)

Issue:
Add GA pipeline for running tests on PRs



Discussion / hints:

Fix patch:
diff --git a/.github/workflows/pr_review.yml b/.github/workflows/pr_review.yml
new file mode 100644
index 00000000..6f443f77
--- /dev/null
+++ b/.github/workflows/pr_review.yml
@@ -0,0 +1,26 @@
+name: Pull Request Workflow
+on:
+  - workflow_dispatch
+  - pull_request
+jobs:
+  run_tests:
+    name: Run tests on pull requests
+    runs-on: ubuntu-latest
+    environment: dev
+
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v2
+        with:
+          fetch-depth: 0
+      - name: Set up Python 3.10
+        uses: actions/setup-python@v2
+        with:
+          python-version: "3.10.12"
+      - name: Install dependencies
+        run: |
+          python -m pip install --upgrade pip
+          pip install -r requirements.txt
+      - name: Run tests
+        run: |
+          pytest
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 44a0df75..c96e666b 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -18,7 +18,7 @@ Contributions are made to this repo via Issues and Pull Requests (PRs). A few ge
 
 ### Issues
 
-Issues should be used to report problems with the library, request a new feature, or to discuss potential changes before a PR is created. 
+Issues should be used to report problems with the library, request a new feature, or to discuss potential changes before a PR is created.
 
 - If you find an issue that addresses the problem you're having, please add your
   own reproduction information to the existing issue rather than creating a new
@@ -57,3 +57,6 @@ Run our unit tests to ensure your changes does not affect other parts of the cod
 
 Run `pytest .` from the root directory.
 
+#### GitHub Actions Testing Pipeline
+
+We use GitHub Actions to automatically run unit tests whenever a pull request is opened or receives new commits. A pull request may not be merged until the pipeline completes without detecting failures.


Test patch:
diff --git a/ccvm_simulators/tests/test_ccvm_solver.py b/ccvm_simulators/tests/test_ccvm_solver.py
index b0e33f7c..95ec6385 100644
--- a/ccvm_simulators/tests/test_ccvm_solver.py
+++ b/ccvm_simulators/tests/test_ccvm_solver.py
@@ -19,9 +19,6 @@ def _validate_parameters(self, parameters):
     def tune(self):
         print("dummy tune function")
 
-    def solve(self):
-        print("dummy solve function")
-
     def _calculate_grads_boxqp(self):
         print("dummy _calculate_grads_boxqp function")
 
@@ -31,6 +28,14 @@ def _change_variables_boxqp(self):
     def _fit_to_constraints_boxqp(self):
         print("dummy _fit_to_constraints_boxqp function")
 
+    def _calculate_drift_boxqp(self):
+        print("dummy _calculate_drift_boxqp function")
+
+    def _solve(self):
+        print("dummy solve function")
+
+    def _solve_adam(self):
+        print("dummy _solve_adam function")
 
 class TestCCVMSolver(TestCase):
     @classmethod
@@ -76,3 +81,24 @@ def test_get_scaling_factor_success(self):
         self.assertTrue(
             torch.eq(self.solver.get_scaling_factor(q_matrix), expected_value)
         )
+
+    def test_method_selector_valid(self):
+        """Test that method_selector set the correct methods when valid inputs are passed"""
+        self.solver._method_selector("boxqp")
+        assert self.solver.calculate_grads == self.solver._calculate_grads_boxqp
+        assert self.solver.change_variables == self.solver._change_variables_boxqp
+        assert (
+            self.solver.fit_to_constraints
+            == self.solver._fit_to_constraints_boxqp
+        )
+
+    def test_method_selector_invalid(self):
+        """Test that method_selector raises a ValueError when an invalid input is passed"""
+        invalid_problem_category = "invalid_problem_category"
+        with self.assertRaises(ValueError) as error:
+            self.solver._method_selector(invalid_problem_category)
+
+        assert (
+            str(error.exception)
+            == f"The given instance is not a valid problem category. Given category: {invalid_problem_category}"
+        )
diff --git a/ccvm_simulators/tests/test_mf_solver.py b/ccvm_simulators/tests/test_mf_solver.py
index 0beb0ad9..5593eb48 100644
--- a/ccvm_simulators/tests/test_mf_solver.py
+++ b/ccvm_simulators/tests/test_mf_solver.py
@@ -60,52 +60,55 @@ def test_set_parameter_key_with_invalid_keys(self):
         with self.assertRaises(ValueError):
             self.mf_solver.parameter_key = invalid_parameters
 
-    def test_method_selector_valid(self):
-        """Test that method_selector set the correct methods when valid inputs are passed"""
-        self.mf_solver._method_selector("boxqp")
-        assert self.mf_solver.calculate_grads == self.mf_solver._calculate_grads_boxqp
-        assert self.mf_solver.change_variables == self.mf_solver._change_variables_boxqp
-        assert (
-            self.mf_solver.fit_to_constraints
-            == self.mf_solver._fit_to_constraints_boxqp
+    def test_calculate_grads_boxqp_valid(self):
+        """Test that calculate_grads returns correct data when valid parameters are passed"""
+        batch_size = 3
+        problem_size = 2
+
+        self.mf_solver.q_matrix = torch.ones(problem_size, problem_size)
+        self.mf_solver.v_vector = torch.ones(problem_size)
+
+        mu_tilde = torch.zeros(batch_size, problem_size)
+        S = self.valid_parameters[problem_size]["S"]
+        fs = self.valid_parameters[problem_size]["feedback_scale"]
+
+        grads_mu = self.mf_solver._calculate_grads_boxqp(
+            mu_tilde=mu_tilde,
+            S=S,
+            fs=fs,
         )
 
-    def test_method_selector_invalid(self):
-        """Test that method_selector raises a ValueError when an invalid input is passed"""
-        invalid_problem_category = "invalid_problem_category"
-        with self.assertRaises(ValueError) as error:
-            self.mf_solver._method_selector(invalid_problem_category)
+        assert grads_mu.shape == torch.Size([batch_size, problem_size])
 
-        assert (
-            str(error.exception)
-            == f"The given problem category is not valid. Given category: {invalid_problem_category}"
+        # Expected values were calculated using the function. This check is just to make
+        # sure that the function does not get changed unexpectedly.
+        expected_grads_mu = torch.tensor(
+            [[-20.0, -20.0], [-20.0, -20.0], [-20.0, -20.0]]
         )
+        assert torch.equal(grads_mu, expected_grads_mu)
 
-    def test_calculate_grads_boxqp_valid(self):
-        """Test that calculate_grads returns correct data when valid parameters are passed"""
+    def test_calculate_drift_boxqp_valid(self):
+        """Test that calculate_drift returns correct data when valid parameters are passed"""
         batch_size = 3
         problem_size = 2
 
+        self.mf_solver.q_matrix = torch.ones(problem_size, problem_size)
+        self.mf_solver.v_vector = torch.ones(problem_size)
+
         mu = torch.zeros(batch_size, problem_size)
         mu_tilde = torch.zeros(batch_size, problem_size)
         sigma = torch.zeros(batch_size, problem_size)
-        q_matrix = torch.ones(problem_size, problem_size)
-        v_vector = torch.ones(problem_size)
         pump = self.valid_parameters[problem_size]["pump"]
-        wiener_increment = torch.zeros(batch_size, problem_size)
         j = self.valid_parameters[problem_size]["j"]
         g = 0.1
         S = self.valid_parameters[problem_size]["S"]
         fs = self.valid_parameters[problem_size]["feedback_scale"]
 
-        grads_mu, grads_sigma = self.mf_solver._calculate_grads_boxqp(
+        grads_mu, grads_sigma = self.mf_solver._calculate_drift_boxqp(
             mu=mu,
             mu_tilde=mu_tilde,
             sigma=sigma,
-            q_matrix=q_matrix,
-            v_vector=v_vector,
             pump=pump,
-            wiener_increment=wiener_increment,
             j=j,
             g=g,
             S=S,
@@ -249,7 +252,7 @@ def test_solve_success_minimal_inputs(self):
         self.mf_solver.change_variables = self.mock_change_variables
         self.mf_solver.calculate_grads = self.mock_calculate_grads
 
-        solution = self.mf_solver.solve(instance)
+        solution = self.mf_solver._solve(instance)
 
         # Check that the solution is correct
         assert solution.problem_size == instance.problem_size
@@ -291,7 +294,7 @@ def test_solve_device_mismatch(self):
         self.mf_solver.device = "cpu"
         self.mf_solver.parameter_key = self.valid_parameters
         with self.assertRaises(ValueError) as error:
-            self.mf_solver.solve(instance)
+            self.mf_solver._solve(instance)
 
         self.assertEqual(
             str(error.exception),