--- Final Solver (used for performance test) ---
from typing import Any
import ctypes
import os

# Load the C shared library
_lib_path = os.path.join(os.path.dirname(__file__), 'solver_c.so')
Lib = ctypes.CDLL(_lib_path)

_lib_integrate = Lib.integrate
_lib_integrate.argtypes = [
    ctypes.c_double, ctypes.c_double, ctypes.c_double, ctypes.c_double,
    ctypes.c_double, ctypes.c_double,
    ctypes.c_double, ctypes.c_double, ctypes.c_double, ctypes.c_double,
    ctypes.c_double, ctypes.c_double, ctypes.c_double, ctypes.c_int,
    ctypes.POINTER(ctypes.c_double)
]
_lib_integrate.restype = ctypes.c_int

class Solver:
    def __init__(self):
        # Dummy warm-up call
        out = (ctypes.c_double * 4)()
        _lib_integrate(
            0.89, 0.01, 0.005, 0.095,
            0.0, 1.0,
            0.35, 0.2, 0.1, 0.002,
            1e-8, 1e-5, 0.1, 100,
            out
        )

    def solve(self, problem, **kwargs) -> Any:
        t0 = float(problem["t0"])
        t1 = float(problem["t1"])
        y0 = problem["y0"]
        p = problem["params"]
        beta = float(p["beta"])
        sigma = float(p["sigma"])
        gamma = float(p["gamma"])
        omega = float(p["omega"])

        y0_S = float(y0[0])
        y0_E = float(y0[1])
        y0_I = float(y0[2])
        y0_R = float(y0[3])

        h0 = max(1e-6, (t1 - t0) / 1000.0)
        max_steps = 10000000
        atol = 1e-8
        rtol = 1e-5

        out = (ctypes.c_double * 4)()
        ret = _lib_integrate(
            y0_S, y0_E, y0_I, y0_R,
            t0, t1,
            beta, sigma, gamma, omega,
            atol, rtol, h0, max_steps,
            out
        )
        if ret != 0:
            raise RuntimeError("Integration failed")
        return [out[0], out[1], out[2], out[3]]
--- End Solver ---
Running performance test...
============================= test session starts ==============================
platform linux -- Python 3.12.13, pytest-9.0.2, pluggy-1.6.0
rootdir: /tests
plugins: anyio-4.13.0, jaxtyping-0.3.9
collected 3 items

../tests/test_outputs.py .EE

==================================== ERRORS ====================================
____________________ ERROR at setup of test_solver_validity ____________________

    @pytest.fixture(scope="module")
    def solver_instance() -> Any:
        """Loads the user's 'Solver' class from solver.py."""
        if not SOLVER_PATH.exists():
            pytest.fail(f"Solver file not found at {SOLVER_PATH}")
    
        spec = importlib.util.spec_from_file_location("solver", SOLVER_PATH)
        solver_module = importlib.util.module_from_spec(spec)
>       spec.loader.exec_module(solver_module)

/tests/test_outputs.py:47: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
<frozen importlib._bootstrap_external>:999: in exec_module
    ???
<frozen importlib._bootstrap>:488: in _call_with_frames_removed
    ???
solver.py:7: in <module>
    Lib = ctypes.CDLL(_lib_path)
          ^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <CDLL '/app/solver_c.so', handle 0 at 0x72c5541b3e90>
name = '/app/solver_c.so', mode = 0, handle = None, use_errno = False
use_last_error = False, winmode = None

    def __init__(self, name, mode=DEFAULT_MODE, handle=None,
                 use_errno=False,
                 use_last_error=False,
                 winmode=None):
        if name:
            name = _os.fspath(name)
        self._name = name
        flags = self._func_flags_
        if use_errno:
            flags |= _FUNCFLAG_USE_ERRNO
        if use_last_error:
            flags |= _FUNCFLAG_USE_LASTERROR
        if _sys.platform.startswith("aix"):
            """When the name contains ".a(" and ends with ")",
               e.g., "libFOO.a(libFOO.so)" - this is taken to be an
               archive(member) syntax for dlopen(), and the mode is adjusted.
               Otherwise, name is presented to dlopen() as a file argument.
            """
            if name and name.endswith(")") and ".a(" in name:
                mode |= ( _os.RTLD_MEMBER | _os.RTLD_NOW )
        if _os.name == "nt":
            if winmode is not None:
                mode = winmode
            else:
                import nt
                mode = nt._LOAD_LIBRARY_SEARCH_DEFAULT_DIRS
                if '/' in name or '\\' in name:
                    self._name = nt._getfullpathname(self._name)
                    mode |= nt._LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR
    
        class _FuncPtr(_CFuncPtr):
            _flags_ = flags
            _restype_ = self._func_restype_
        self._FuncPtr = _FuncPtr
    
        if handle is None:
>           self._handle = _dlopen(self._name, mode)
                           ^^^^^^^^^^^^^^^^^^^^^^^^^
E           OSError: /app/solver_c.so: cannot open shared object file: No such file or directory

/usr/local/lib/python3.12/ctypes/__init__.py:379: OSError
____________________ ERROR at setup of test_solver_speedup _____________________

    @pytest.fixture(scope="module")
    def solver_instance() -> Any:
        """Loads the user's 'Solver' class from solver.py."""
        if not SOLVER_PATH.exists():
            pytest.fail(f"Solver file not found at {SOLVER_PATH}")
    
        spec = importlib.util.spec_from_file_location("solver", SOLVER_PATH)
        solver_module = importlib.util.module_from_spec(spec)
>       spec.loader.exec_module(solver_module)

/tests/test_outputs.py:47: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
<frozen importlib._bootstrap_external>:999: in exec_module
    ???
<frozen importlib._bootstrap>:488: in _call_with_frames_removed
    ???
solver.py:7: in <module>
    Lib = ctypes.CDLL(_lib_path)
          ^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <CDLL '/app/solver_c.so', handle 0 at 0x72c5541b3e90>
name = '/app/solver_c.so', mode = 0, handle = None, use_errno = False
use_last_error = False, winmode = None

    def __init__(self, name, mode=DEFAULT_MODE, handle=None,
                 use_errno=False,
                 use_last_error=False,
                 winmode=None):
        if name:
            name = _os.fspath(name)
        self._name = name
        flags = self._func_flags_
        if use_errno:
            flags |= _FUNCFLAG_USE_ERRNO
        if use_last_error:
            flags |= _FUNCFLAG_USE_LASTERROR
        if _sys.platform.startswith("aix"):
            """When the name contains ".a(" and ends with ")",
               e.g., "libFOO.a(libFOO.so)" - this is taken to be an
               archive(member) syntax for dlopen(), and the mode is adjusted.
               Otherwise, name is presented to dlopen() as a file argument.
            """
            if name and name.endswith(")") and ".a(" in name:
                mode |= ( _os.RTLD_MEMBER | _os.RTLD_NOW )
        if _os.name == "nt":
            if winmode is not None:
                mode = winmode
            else:
                import nt
                mode = nt._LOAD_LIBRARY_SEARCH_DEFAULT_DIRS
                if '/' in name or '\\' in name:
                    self._name = nt._getfullpathname(self._name)
                    mode |= nt._LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR
    
        class _FuncPtr(_CFuncPtr):
            _flags_ = flags
            _restype_ = self._func_restype_
        self._FuncPtr = _FuncPtr
    
        if handle is None:
>           self._handle = _dlopen(self._name, mode)
                           ^^^^^^^^^^^^^^^^^^^^^^^^^
E           OSError: /app/solver_c.so: cannot open shared object file: No such file or directory

/usr/local/lib/python3.12/ctypes/__init__.py:379: OSError
==================================== PASSES ====================================
=========================== short test summary info ============================
PASSED ../tests/test_outputs.py::test_solver_exists
ERROR ../tests/test_outputs.py::test_solver_validity - OSError: /app/solver_c...
ERROR ../tests/test_outputs.py::test_solver_speedup - OSError: /app/solver_c....
========================= 1 passed, 2 errors in 0.65s ==========================
