Repository: brian-team/brian2

Issue:
Raise an error for negative duration in `run()` function
Currently, calling `run(duration)` with a negative duration does not raise an error; instead, it simply runs for 0 seconds.

Proposed Fix: 

- Modify `run()` to raise an appropriate error (e.g., ValueError) when a negative duration is passed.
- The case of `run(0*ms)` should remain unchanged, as it is valid and useful in tests.

Discussion:
@mstimberg: I agree with everything 👍 

Solution patch:
diff --git a/brian2/core/network.py b/brian2/core/network.py
index 4f2a4442..34df6345 100644
--- a/brian2/core/network.py
+++ b/brian2/core/network.py
@@ -1102,7 +1102,16 @@ def run(
         -----
         The simulation can be stopped by calling `Network.stop` or the
         global `stop` function.
+
+        Raises
+        ------
+        ValueError
+            Error raised when duration passed in run function is negative.
         """
+        if duration < 0:
+            raise ValueError(
+                f"Function 'run' expected a non-negative duration but got '{duration}'"
+            )
         # This will trigger warnings for objects that have not been included in a network
         gc.collect()
         device = get_device()  # Do not use the ProxyDevice -- slightly faster
diff --git a/brian2/devices/cpp_standalone/device.py b/brian2/devices/cpp_standalone/device.py
index 729385f7..98946b2c 100644
--- a/brian2/devices/cpp_standalone/device.py
+++ b/brian2/devices/cpp_standalone/device.py
@@ -1770,6 +1770,11 @@ def network_run(
         level=0,
         **kwds,
     ):
+        if duration < 0:
+            raise ValueError(
+                f"Function 'run' expected a non-negative duration but got '{duration}'"
+            )
+
         self.networks.add(net)
         if kwds:
             logger.warn(


Test patch:
diff --git a/brian2/tests/test_cpp_standalone.py b/brian2/tests/test_cpp_standalone.py
index 90bf3e26..0bce5af1 100644
--- a/brian2/tests/test_cpp_standalone.py
+++ b/brian2/tests/test_cpp_standalone.py
@@ -1049,6 +1049,15 @@ def brian_function(index):
     assert_allclose(S.rate_copy[:], np.arange(len(G)) * 42 * Hz)
 
 
+@pytest.mark.cpp_standalone
+@pytest.mark.standalone_only
+def test_negative_duration_in_standalone_device():
+    set_device("cpp_standalone")
+    G = NeuronGroup(1, "v:1")
+    with pytest.raises(ValueError):
+        run(-1 * second)
+
+
 if __name__ == "__main__":
     for t in [
         test_cpp_standalone,
diff --git a/brian2/tests/test_network.py b/brian2/tests/test_network.py
index 57351c33..9b623fd3 100644
--- a/brian2/tests/test_network.py
+++ b/brian2/tests/test_network.py
@@ -1791,6 +1791,21 @@ def test_unused_object_warning():
     assert "never_used" in logs[0][2]
 
 
+@pytest.mark.codegen_independent
+def test_negative_duration_in_run():
+    G = NeuronGroup(1, "v:1")
+    with pytest.raises(ValueError):
+        run(-1 * second)
+
+
+@pytest.mark.codegen_independent
+def test_negative_duration_in_net_run():
+    G = NeuronGroup(1, "v:1")
+    net = Network(G)
+    with pytest.raises(ValueError):
+        net.run(-1 * second)
+
+
 if __name__ == "__main__":
     BrianLogger.log_level_warn()
     for t in [