diff --git a/arbor/arbexcept.cpp b/arbor/arbexcept.cpp
index 50ac0968c07222a30514d4cfcaedf4c624263b0f..37cb7a92d7b29c9408dbae1cb5a5c1d7ec05ec64 100644
--- a/arbor/arbexcept.cpp
+++ b/arbor/arbexcept.cpp
@@ -10,6 +10,8 @@ namespace arb {
 
 using arb::util::pprintf;
 
+domain_error::domain_error(const std::string& w): arbor_exception(w) {}
+
 bad_cell_probe::bad_cell_probe(cell_kind kind, cell_gid_type gid):
     arbor_exception(pprintf("recipe::get_grobe() is not supported for cell with gid {} of kind {})", gid, kind)),
     gid(gid),
diff --git a/arbor/include/arbor/arbexcept.hpp b/arbor/include/arbor/arbexcept.hpp
index 4a06571b3fa07b10b292034a424c4920cae358b9..2574c71febadec29959f5ac51b063333edb9159e 100644
--- a/arbor/include/arbor/arbexcept.hpp
+++ b/arbor/include/arbor/arbexcept.hpp
@@ -28,6 +28,13 @@ struct arbor_exception: std::runtime_error {
     {}
 };
 
+// Logic errors
+
+// Argument violates domain constraints, eg ln(-1)
+struct domain_error: arbor_exception {
+    domain_error(const std::string&);
+};
+
 // Recipe errors:
 
 struct bad_cell_probe: arbor_exception {
diff --git a/arbor/simulation.cpp b/arbor/simulation.cpp
index daa22c3ecca6a0294659b119400a3fc17a26865d..300b9947da1111faaeebfbc558a1a504be263f19 100644
--- a/arbor/simulation.cpp
+++ b/arbor/simulation.cpp
@@ -514,6 +514,9 @@ void simulation::reset() {
 }
 
 time_type simulation::run(time_type tfinal, time_type dt) {
+    if (dt <= 0.0) {
+        throw domain_error("Finite time-step must be supplied.");
+    }
     return impl_->run(tfinal, dt);
 }