Repository: pybamm-team/PyBaMM (python)

Issue:
[Bug]: PyBaMM fails to import when user has already imported a library that uses `mp.set_start_method`
### PyBaMM Version

develop

### Python Version

3.11

### Describe the bug

PyBaMM fails to import when importing a library that uses multiprocessing and has already set the start method to something other than fork, as a result of #3975. This should be apparent when reading the documentation for `mp.set_start_method`

Pending a long-term solution, I think that #3975 should at least have `mp.set_start_method` wrapped in a try-except -- most people, most of the time, do not really need the multiprocessing functionality, and I don't think pybamm should force those users to modify their multiprocessing configuration. It can throw a warning saying multiprocessing is not available or whatever.

Other solutions are welcome, of course. 
 

### Steps to Reproduce

module `thing`:
```
import multiprocessing as mp

mp.set_start_method("spawn")

def hello():
    print("hello, world")
```

script:
```
import thing
import pybamm
``` 

### Relevant log output

```shell
❯ python main.py 
Traceback (most recent call last):
  File "/Users/abills/.../main.py", line 2, in <module>
    import pybamm
  File "/Users/abills/.../python3.11/site-packages/pybamm/__init__.py", line 204, in <module>
    from .solvers.base_solver import BaseSolver
  File "/Users/abills/.../python3.11/site-packages/pybamm/solvers/base_solver.py", line 18, in <module>
    mp.set_start_method("fork")
  File "/Users/abills/.../python3.11/multiprocessing/context.py", line 247, in set_start_method
    raise RuntimeError('context has already been set')
RuntimeError: context has already been set
```


Pull request description:
Update multiprocessing context to be Pool specific
# Description

Convert's the global `mp.set_start_method` context to an individual pool context.

Fixes #3997 

## Type of change

Please add a line in the relevant section of [CHANGELOG.md](https://github.com/pybamm-team/PyBaMM/blob/develop/CHANGELOG.md) to document the change (include PR #) - note reverse order of PR #s. If necessary, also add to the list of breaking changes.

- [ ] New feature (non-breaking change which adds functionality)
- [ ] Optimization (back-end change that speeds up the code)
- [X] Bug fix (non-breaking change which fixes an issue)

# Key checklist:

- [X] No style issues: `$ pre-commit run` (or `$ nox -s pre-commit`) (see [CONTRIBUTING.md](https://github.com/pybamm-team/PyBaMM/blob/develop/CONTRIBUTING.md#installing-and-using-pre-commit) for how to set this up to run automatically when committing locally, in just two lines of code)
- [ ] All tests pass: `$ python run-tests.py --all` (or `$ nox -s tests`)
- [ ] The documentation builds: `$ python run-tests.py --doctest` (or `$ nox -s doctests`)

You can run integration tests, unit tests, and doctests together at once, using `$ python run-tests.py --quick` (or `$ nox -s quick`).

## Further checks:

- [ ] Code is commented, particularly in hard-to-understand areas
- [ ] Tests added that prove fix is effective or that feature works


Patch:
diff --git a/pybamm/solvers/base_solver.py b/pybamm/solvers/base_solver.py
index 28dd4bdd9..0e60aca18 100644
--- a/pybamm/solvers/base_solver.py
+++ b/pybamm/solvers/base_solver.py
@@ -13,10 +13,6 @@ import numpy as np
 import pybamm
 from pybamm.expression_tree.binary_operators import _Heaviside
 
-# Set context for parallel processing depending on the platform
-if platform.system() == "Darwin" or platform.system() == "Linux":
-    mp.set_start_method("fork")
-
 
 class BaseSolver:
     """Solve a discretised model.
@@ -70,6 +66,7 @@ class BaseSolver:
         self.algebraic_solver = False
         self._on_extrapolation = "warn"
         self.computed_var_fcns = {}
+        self._mp_context = self.get_platform_context(platform.system())
 
     @property
     def root_method(self):
@@ -917,7 +914,7 @@ class BaseSolver:
                         model_inputs_list,
                     )
                 else:
-                    with mp.Pool(processes=nproc) as p:
+                    with mp.get_context(self._mp_context).Pool(processes=nproc) as p:
                         new_solutions = p.starmap(
                             self._integrate,
                             zip(
@@ -1395,6 +1392,12 @@ class BaseSolver:
                         "outside these bounds."
                     )
 
+    def get_platform_context(self, system_type: str):
+        # Set context for parallel processing depending on the platform
+        if system_type.lower() in ["linux", "darwin"]:
+            return "fork"
+        return "spawn"
+
     @staticmethod
     def _set_up_model_inputs(model, inputs):
         """Set up input parameters"""


Test patch:
diff --git a/tests/unit/test_solvers/test_base_solver.py b/tests/unit/test_solvers/test_base_solver.py
index dcd4b5b85..9a6dec0ea 100644
--- a/tests/unit/test_solvers/test_base_solver.py
+++ b/tests/unit/test_solvers/test_base_solver.py
@@ -348,6 +348,12 @@ class TestBaseSolver(TestCase):
         with self.assertRaisesRegex(RuntimeError, "already been initialised"):
             solver.solve(model2, t_eval=[0, 1])
 
+    def test_multiprocess_context(self):
+        solver = pybamm.BaseSolver()
+        assert solver.get_platform_context("Win") == "spawn"
+        assert solver.get_platform_context("Linux") == "fork"
+        assert solver.get_platform_context("Darwin") == "fork"
+
     @unittest.skipIf(not pybamm.have_idaklu(), "idaklu solver is not installed")
     def test_sensitivities(self):
         def exact_diff_a(y, a, b):