diff --git a/.github/workflows/basic.yml b/.github/workflows/basic.yml
index 6ff2dee426faa7f027b7489b316a444ae7e12aff..dcfcd33ec5e48298cd78dfc66fdfbdbf2272325b 100644
--- a/.github/workflows/basic.yml
+++ b/.github/workflows/basic.yml
@@ -189,6 +189,7 @@ jobs:
             python python/example/single_cell_model.py
             python python/example/single_cell_recipe.py
             python python/example/single_cell_stdp.py
+            python python/example/brunel.py -n 400 -m 100 -e 20 -p 0.1 -w 1.2 -d 1 -g 0.5 -l 5 -t 100 -s 1 -G 50 -S 123
             python python/example/single_cell_swc.py python/example/single_cell_detailed.swc
             python python/example/single_cell_detailed.py python/example/single_cell_detailed.swc
             python python/example/single_cell_detailed_recipe.py python/example/single_cell_detailed.swc
diff --git a/arbor/include/arbor/lif_cell.hpp b/arbor/include/arbor/lif_cell.hpp
index e2af157ab4b84184ae213780a0dacfddb4190ae3..68d694caa9b41f52c4140122f79cb4eb01663302 100644
--- a/arbor/include/arbor/lif_cell.hpp
+++ b/arbor/include/arbor/lif_cell.hpp
@@ -2,7 +2,7 @@
 
 namespace arb {
 
-// Model parameteres of leaky integrate and fire neuron model.
+// Model parameters of leaky integrate and fire neuron model.
 struct lif_cell {
     // Neuronal parameters.
     double tau_m = 10;    // Membrane potential decaying constant [ms].
diff --git a/arbor/include/arbor/schedule.hpp b/arbor/include/arbor/schedule.hpp
index 7442c70e6fcb4290c3d9f32309b7acfa7fdf5ba5..6e851335be0cf50eda91e38bbb50617edac08410 100644
--- a/arbor/include/arbor/schedule.hpp
+++ b/arbor/include/arbor/schedule.hpp
@@ -23,7 +23,7 @@ inline time_event_span as_time_event_span(const std::vector<time_type>& v) {
 }
 
 // A schedule describes a sequence of time values used for sampling. Schedules
-// are queried monotonically in time: if two method calls `events(t0, t1)` 
+// are queried monotonically in time: if two method calls `events(t0, t1)`
 // and `events(t2, t3)` are made without an intervening call to `reset()`,
 // then 0 ≤ _t0_ ≤ _t1_ ≤ _t2_ ≤ _t3_.
 
diff --git a/doc/python/decor.rst b/doc/python/decor.rst
index 93ef1be705d3f3982bd3d301d1d73648afcecd50..099bfebdc2a5ff9e361e417b20d5da7da455598b 100644
--- a/doc/python/decor.rst
+++ b/doc/python/decor.rst
@@ -120,7 +120,8 @@ Cable cell decoration
 
         Apply a mechanism with a region using the name of the mechanism.
         The mechanism will use the parameter values set in the mechanism catalogue.
-        Returns a unique identifier that can be used to query the local indexes (see :gen:`index`) assigned to the placed items on the cable cell.
+        Returns a unique identifier that can be used to query the local indexes (see :gen:`index`)
+        assigned to the placed items on the cable cell.
 
         :param str region: description of the region.
         :param str mechanism: the name of the mechanism.
@@ -128,7 +129,9 @@ Cable cell decoration
     .. method:: place(locations, const arb::mechanism_desc& d)
 
         Place one instance of synapse described by ``mechanism`` to each location in ``locations``.
-        Returns a unique identifier that can be used to query the local indexes (see :gen:`index`) assigned to the placed items on the cable cell.
+        Returns a unique identifier that can be used to query the local indexes (see :gen:`index`) assigned to the
+        placed items on the cable cell. For instance: the ``index`` returned when a synapse mechanism is ``place``d,
+        can be used when creating a :py:class:`arbor.connection`
 
         :param str locations: description of the locset.
         :param str mechanism: the name of the mechanism.
diff --git a/doc/python/recipe.rst b/doc/python/recipe.rst
index 5d74801e497557c48391a7b757d299d48e0bdd75..0a945d5f80f677e321cb236f292a0c29bdf0a472 100644
--- a/doc/python/recipe.rst
+++ b/doc/python/recipe.rst
@@ -194,7 +194,7 @@ Event generator and schedules
         Construct a Poisson schedule.
 
         By default returns a schedule with events starting from :attr:`tstart` = 0 ms,
-        with an expected frequency :attr:`freq` = 10 Hz and :attr:`seed` = 0.
+        with an expected frequency :attr:`freq` = 10 kHz and :attr:`seed` = 0.
 
     .. attribute:: tstart
 
@@ -202,7 +202,7 @@ Event generator and schedules
 
     .. attribute:: freq
 
-        The expected frequency [Hz].
+        The expected frequency [kHz].
 
     .. attribute:: seed
 
@@ -225,7 +225,7 @@ An example of an event generator reads as follows:
         target = arbor.cell_member(0,0)
         seed   = target.gid
         tstart = 1
-        freq   = 5
+        freq   = 0.005
         sched  = arbor.poisson_schedule(tstart, freq, seed)
 
         # construct an event generator with this schedule on target cell and weight 0.1
diff --git a/doc/python/single_cell_model.rst b/doc/python/single_cell_model.rst
index fb030c80d92b84142ccdf055b8b2c5fabc2d38ae..2b395d4b89041fb2dd126c14c4a7221ff13bd5f0 100644
--- a/doc/python/single_cell_model.rst
+++ b/doc/python/single_cell_model.rst
@@ -28,7 +28,7 @@ Single cell model
 
        :param what: Name of the variable to record (currently only 'voltage').
        :param where: :class:`location` at which to sample the variable.
-       :param frequency: The frequency at which to sample [Hz].
+       :param frequency: The frequency at which to sample [kHz].
 
     .. method:: spikes()
 
diff --git a/doc/tutorial/network_ring.rst b/doc/tutorial/network_ring.rst
index b42a45dde51283df6cf9c7cfa51fb1666f73b1b4..a7cbe4be120ec3000958715dc2d411717f31cdd7 100644
--- a/doc/tutorial/network_ring.rst
+++ b/doc/tutorial/network_ring.rst
@@ -190,9 +190,14 @@ This means the timestamps of the generated events will be kept in memory. Be def
 
 In addition to having the timestamps of spikes, we want to extract the voltage as a function of time.
 
-Step **(14)** sets the probes (step **10**) to measure at a certain schedule. This is sometimes described as attaching a :term:`sampler` to a :term:`probe`. :py:func:`arbor.simulation.sample` expects a :term:`probe id` and the desired schedule (here: a recording frequency of 10 kHz). Note that the probe id is a separate index from those of :term:`connection` endpoints; probe ids correspond to the index of the list produced by :py:func:`arbor.recipe.probes` on cell ``gid``.
-
-:py:func:`arbor.simulation.sample` returns a handle to the :term:`samples <sample>` that will be recorded. We store these handles for later use.
+Step **(14)** sets the probes (step **10**) to measure at a certain schedule. This is sometimes described as
+attaching a :term:`sampler` to a :term:`probe`. :py:func:`arbor.simulation.sample` expects a :term:`probe id` and the
+desired schedule (here: a recording frequency of 10 kHz). Note that the probe id is a separate index from those of
+:term:`connection` endpoints; probe ids correspond to the index of the list produced by :py:func:`arbor.recipe.
+probes` on cell ``gid``.
+
+:py:func:`arbor.simulation.sample` returns a handle to the :term:`samples <sample>` that will be recorded. We store
+these handles for later use.
 
 Step **(15)** executes the simulation for a duration of 100 ms.
 
diff --git a/doc/tutorial/single_cell_detailed.rst b/doc/tutorial/single_cell_detailed.rst
index 4934b388ce27141bd54e4555e5f9c19a76aabaab..4655cd1b6ee4a7c846683f08ab65ebd671e15257 100644
--- a/doc/tutorial/single_cell_detailed.rst
+++ b/doc/tutorial/single_cell_detailed.rst
@@ -444,9 +444,9 @@ We can indicate the location we would like to probe using labels from the :class
 .. code-block:: python
 
    # Add voltage probes on the "custom_terminal" locset
-   # which sample the voltage at 50000 Hz
+   # which sample the voltage at 50 kHz
 
-   model.probe('voltage', where='"custom_terminal"',  frequency=50000)
+   model.probe('voltage', where='"custom_terminal"', frequency=50)
 
 The simulation
 ^^^^^^^^^^^^^^
diff --git a/doc/tutorial/single_cell_detailed_recipe.rst b/doc/tutorial/single_cell_detailed_recipe.rst
index 94723c416dc6d29a020045e3098d2f57dae501f9..8f5b7791755a5fe914a3967d39bf26a41f7df975 100644
--- a/doc/tutorial/single_cell_detailed_recipe.rst
+++ b/doc/tutorial/single_cell_detailed_recipe.rst
@@ -338,7 +338,7 @@ to plot the voltage registered by the probe on the "custom_terminal" locset.
    sim.record(arbor.spike_recording.all)
 
    # Instruct the simulation to sample the probe (0, 0)
-   # at a regular schedule with period = 0.02 ms (50000 Hz)
+   # at a regular schedule with period = 0.02 ms (50 kHz)
    probe_id = arbor.cell_member(0,0)
    handle = sim.sample(probe_id, arbor.regular_schedule(0.02))
 
@@ -348,7 +348,7 @@ This variable serves as a global identifier of a probe on a cell, namely the fir
 cell with gid = 0, which is id of the :ref:`only probe <tutorialsinglecellswcrecipe-probe>` we created on
 the only cell in the model.
 
-Next, we instructed the simulation to sample ``probe_id`` at a frequency of 50KHz. That function returns a
+Next, we instructed the simulation to sample ``probe_id`` at a frequency of 50 kHz. That function returns a
 ``handle`` which we will use to extract the results of the sampling after running the simulation.
 
 The execution
diff --git a/doc/tutorial/single_cell_model.rst b/doc/tutorial/single_cell_model.rst
index 4c838c444323b3d57335d915d34c394660240cf1..f9668712e4ff4c5ddf921ed5a5e5d683cc493d07 100644
--- a/doc/tutorial/single_cell_model.rst
+++ b/doc/tutorial/single_cell_model.rst
@@ -112,7 +112,7 @@ an interface for recording outputs and running the simulation.
     m = arbor.single_cell_model(cell)
 
     # (6) Attach voltage probe sampling at 10 kHz (every 0.1 ms).
-    m.probe('voltage', '"midpoint"', frequency=10000)
+    m.probe('voltage', '"midpoint"', frequency=10)
 
     # (7) Run simulation for 30 ms of simulated activity.
     m.run(tfinal=30)
@@ -123,7 +123,7 @@ with our single-compartment cell.
 Step **(6)** adds a :meth:`arbor.single_cell_model.probe`
 used to record variables from the model. Three pieces of information are
 provided: the type of quantity we want probed (voltage), the location where we want to
-probe ('"midpoint"'), and the frequency at which we want to sample (10kHz).
+probe ('"midpoint"'), and the frequency at which we want to sample (10 kHz).
 
 Step **(7)** runs the actual simulation for a duration of 30 ms.
 
diff --git a/example/brunel/brunel.cpp b/example/brunel/brunel.cpp
index 3e595d57605c2cea347d670d2cfe46ecb1c662dc..7f208844d08b79dcbe5346a32245727de5483739 100644
--- a/example/brunel/brunel.cpp
+++ b/example/brunel/brunel.cpp
@@ -57,7 +57,7 @@ struct cl_options {
     uint32_t seed = 42;
 
     // Parameters for spike output.
-    bool spike_file_output = false;
+    std::string spike_file_output = "";
 
     // Turn on/off profiling output for all ranks.
     bool profile_only_zero = false;
@@ -119,18 +119,12 @@ public:
         std::vector<cell_connection> connections;
         // Add incoming excitatory connections.
         for (auto i: sample_subset(gid, 0, ncells_exc_, in_degree_exc_)) {
-            cell_member_type source{cell_gid_type(i), 0};
-            cell_member_type target{gid, 0};
-            cell_connection conn(source, target, weight_exc_, delay_);
-            connections.push_back(conn);
+            connections.push_back({{cell_gid_type(i), 0}, {gid, 0}, weight_exc_, delay_});
         }
 
         // Add incoming inhibitory connections.
         for (auto i: sample_subset(gid, ncells_exc_, ncells_exc_ + ncells_inh_, in_degree_inh_)) {
-            cell_member_type source{cell_gid_type(i), 0};
-            cell_member_type target{gid, 0};
-            cell_connection conn(source, target, weight_inh_, delay_);
-            connections.push_back(conn);
+            connections.push_back({{cell_gid_type(i), 0}, {gid, 0}, weight_inh_, delay_});
         }
         return connections;
     }
@@ -148,16 +142,10 @@ public:
     }
 
     std::vector<event_generator> event_generators(cell_gid_type gid) const override {
-        std::vector<arb::event_generator> gens;
-
         std::mt19937_64 G;
         G.seed(gid + seed_);
-
         time_type t0 = 0;
-        cell_member_type target{gid, 0};
-
-        gens.emplace_back(poisson_generator(target, weight_ext_, t0, lambda_, G));
-        return gens;
+        return {poisson_generator({gid, 0}, weight_ext_, t0, lambda_, G)};
     }
 
     cell_size_type num_sources(cell_gid_type) const override {
@@ -234,8 +222,9 @@ int main(int argc, char** argv) {
         cl_options options = o.value();
 
         std::fstream spike_out;
-        if (options.spike_file_output && root) {
-            spike_out = sup::open_or_throw("./spikes.gdf", std::ios_base::out, false);
+        auto spike_file_output = options.spike_file_output;
+        if (spike_file_output != "" && root) {
+            spike_out = sup::open_or_throw(spike_file_output, std::ios_base::out, false);
         }
 
         meters.checkpoint("setup", context);
@@ -356,7 +345,7 @@ std::optional<cl_options> read_options(int argc, char** argv) {
                      "-w|--weight            [Weight of excitatory connections]\n"
                      "-d|--delay             [Delay of all connections]\n"
                      "-g|--rel-inh-w         [Relative strength of inhibitory synapses with respect to the excitatory ones]\n"
-                     "-l|--lambda            [Expected number of spikes from a single poisson cell per ms]\n"
+                     "-l|--lambda            [Mean firing rate from a single poisson cell (kHz)]\n"
                      "-t|--tfinal            [Length of the simulation period (ms)]\n"
                      "-s|--dt                [Simulation time step (ms)]\n"
                      "-G|--group-size        [Number of cells per cell group]\n"
@@ -382,15 +371,15 @@ std::optional<cl_options> read_options(int argc, char** argv) {
             { opt.tfinal,            "-t", "--tfinal" },
             { opt.dt,                "-s", "--dt" },
             { opt.group_size,        "-G", "--group-size" },
-            { opt.seed,              "-s", "--seed" },
-            { to::set(opt.spike_file_output), to::flag, "-f", "--write-spikes" },
-            { to::set(opt.profile_only_zero), to::flag, "-z", "--profile-rank-zero" },
+            { opt.seed,              "-S", "--seed" },
+            { opt.spike_file_output, "-f", "--write-spikes" },
+            // { to::set(opt.profile_only_zero), to::flag, "-z", "--profile-rank-zero" },
             { to::set(opt.verbose),           to::flag, "-v", "--verbose" },
             { to::action(help),               to::flag, to::exit, "-h", "--help" }
     };
 
     if (!to::run(options, argc, argv+1)) return {};
-    if (argv[1]) throw to::option_error("unrecogonized argument", argv[1]);
+    if (argv[1]) throw to::option_error("unrecognized argument", argv[1]);
 
     if (opt.group_size < 1) {
         throw std::runtime_error("minimum of one cell per group");
@@ -423,5 +412,6 @@ std::ostream& operator<<(std::ostream& o, const cl_options& options) {
     o << "  dt                                                         : " << options.dt << "\n";
     o << "  Group size                                                 : " << options.group_size << "\n";
     o << "  Seed                                                       : " << options.seed << "\n";
+    o << "  Spike file output                                          : " << options.spike_file_output << "\n";
     return o;
 }
diff --git a/example/brunel/readme.md b/example/brunel/readme.md
index 8168b6fe4385e5846b76412819b31fc8c5258b99..e09e5daaaa8474cc5a498f4354d26573c2346de8 100644
--- a/example/brunel/readme.md
+++ b/example/brunel/readme.md
@@ -31,5 +31,5 @@ The parameters that can be passed as command-line arguments are the following:
 For example, we could run the miniapp as follows:
 
 ```
-./brunel -n 400 -m 100 -e 20 -p 0.1 -w 1.2 -d 1 -g 0.5 -l 5 -t 100 -s 1 -G 50 -S 123 -f
+./brunel -n 400 -m 100 -e 20 -p 0.1 -w 1.2 -d 1 -g 0.5 -l 5 -t 100 -s 1 -G 50 -S 123 -f spikes.txt
 ```
diff --git a/python/example/brunel.py b/python/example/brunel.py
new file mode 100755
index 0000000000000000000000000000000000000000..d1081fc347778c3a0fb4140585aae49857d24c71
--- /dev/null
+++ b/python/example/brunel.py
@@ -0,0 +1,156 @@
+#!/usr/bin/env python3
+
+import arbor
+import numpy,argparse
+
+'''
+A Brunel network consists of nexc excitatory LIF neurons and ninh inhibitory LIF neurons.
+Each neuron in the network receives in_degree_prop * nexc excitatory connections
+chosen randomly, in_degree_prop * ninh inhibitory connections and next (external) Poisson connections.
+All the connections have the same delay. The strenght of excitatory and Poisson connections is given by
+parameter weight, whereas the strength of inhibitory connections is rel_inh_strength * weight.
+Poisson neurons all spike independently with expected number of spikes given by parameter poiss_lambda.
+Because of the refractory period, the activity is mostly driven by Poisson neurons and
+recurrent connections have a small effect.
+
+Call with parameters, for example:
+./brunel.py -n 400 -m 100 -e 20 -p 0.1 -w 1.2 -d 1 -g 0.5 -l 5 -t 100 -s 1 -G 50 -S 123 -f spikes.txt
+
+'''
+
+# Samples m unique values in interval [start, end) - gid.
+# We exclude gid because we don't want self-loops.
+def sample_subset(gid, start, end, m):
+    gen = numpy.random.RandomState(gid+42)
+    s = set()
+    while len(s) < m:
+        val = gen.randint(low=start,high=end)
+        if val != gid:
+            s.add(val)
+    return s
+
+class brunel_recipe (arbor.recipe):
+    def __init__(self, nexc, ninh, next, in_degree_prop, weight, delay, rel_inh_strength, poiss_lambda, seed = 42):
+
+        arbor.recipe.__init__(self)
+
+        # Make sure that in_degree_prop in the interval (0, 1]
+        if not 0.0<in_degree_prop<=1.0:
+            print("The proportion of incoming connections should be in the interval (0, 1].")
+            quit()
+
+        self.ncells_exc_ = nexc
+        self.ncells_inh_ = ninh
+        self.delay_ = delay
+        self.seed_ = seed
+
+        # Set up the parameters.
+        self.weight_exc_ = weight
+        self.weight_inh_ = -rel_inh_strength * weight
+        self.weight_ext_ =  weight
+        self.in_degree_exc_ = round(in_degree_prop * nexc)
+        self.in_degree_inh_ = round(in_degree_prop * ninh)
+        # each cell receives next incoming Poisson sources with mean rate poiss_lambda, which is equivalent
+        # to a single Poisson source with mean rate next*poiss_lambda
+        self.lambda_ = next * poiss_lambda
+
+    def num_cells(self):
+        return self.ncells_exc_ + self.ncells_inh_
+
+    def cell_kind(self, gid):
+        return arbor.cell_kind.lif
+
+    def connections_on(self, gid):
+        connections=[]
+        # Add incoming excitatory connections.
+        for i in sample_subset(gid, 0, self.ncells_exc_, self.in_degree_exc_):
+            connections.append(arbor.connection((i,0), (gid,0), self.weight_exc_, self.delay_))
+        # Add incoming inhibitory connections.
+        for i in sample_subset(gid, self.ncells_exc_, self.ncells_exc_ + self.ncells_inh_, self.in_degree_inh_):
+            connections.append(arbor.connection((i,0), (gid,0), self.weight_inh_, self.delay_))
+        return connections
+
+    def cell_description(self, gid):
+        cell = arbor.lif_cell()
+        cell.tau_m = 10
+        cell.V_th = 10
+        cell.C_m = 20
+        cell.E_L = 0
+        cell.V_m = 0
+        cell.V_reset = 0
+        cell.t_ref = 2
+        return cell
+
+    def event_generators(self, gid):
+        t0 = 0
+        sched = arbor.poisson_schedule(t0, self.lambda_, gid + self.seed_)
+        return [arbor.event_generator(arbor.cell_member(gid,0), self.weight_ext_, sched)]
+
+    def num_targets(self, gid):
+        return 1
+
+    def num_sources(self, gid):
+        return 1
+
+if __name__ == "__main__":
+
+    parser = argparse.ArgumentParser(description='Brunel model miniapp.')
+    parser.add_argument('-n', '--n-excitatory', dest='nexc', type=int, default=400, help='Number of cells in the excitatory population')
+    parser.add_argument('-m', '--n-inhibitory', dest='ninh', type=int, default=100, help='Number of cells in the inhibitory population')
+    parser.add_argument('-e', '--n-external', dest='next', type=int, default=40, help='Number of incoming Poisson (external) connections per cell')
+    parser.add_argument('-p', '--in-degree-prop', dest='syn_per_cell_prop', type=float, default=0.05, help='Proportion of the connections received per cell')
+    parser.add_argument('-w', '--weight', dest='weight', type=float, default=1.2, help='Weight of excitatory connections')
+    parser.add_argument('-d', '--delay', dest='delay', type=float, default=0.1, help='Delay of all connections')
+    parser.add_argument('-g', '--rel-inh-w', dest='rel_inh_strength', type=float, default=1, help='Relative strength of inhibitory synapses with respect to the excitatory ones')
+    parser.add_argument('-l', '--lambda', dest='poiss_lambda', type=float, default=1, help='Mean firing rate from a single poisson cell (kHz)')
+    parser.add_argument('-t', '--tfinal', dest='tfinal', type=float, default=100, help='Length of the simulation period (ms)')
+    parser.add_argument('-s', '--dt', dest='dt', type=float, default=1, help='Simulation time step (ms)')
+    parser.add_argument('-G', '--group-size', dest='group_size', type=int, default=10, help='Number of cells per cell group')
+    parser.add_argument('-S', '--seed', dest='seed', type=int, default=42, help='Seed for poisson spike generators')
+    parser.add_argument('-f', '--write-spikes', dest='spike_file_output', type=str, help='Save spikes to file')
+    # parser.add_argument('-z', '--profile-rank-zero', dest='profile_only_zero', action='store_true', help='Only output profile information for rank 0')
+    parser.add_argument('-v', '--verbose', dest='verbose', action='store_true', help='Print more verbose information to stdout')
+
+    opt = parser.parse_args()
+    if opt.verbose:
+        print("Running brunel.py with the following settings:")
+        for k,v in vars(opt).items():
+            print(f"{k} = {v}")
+
+    context = arbor.context()
+    print(context)
+
+    meters = arbor.meter_manager()
+    meters.start(context)
+
+    recipe = brunel_recipe(opt.nexc, opt.ninh, opt.next, opt.syn_per_cell_prop, opt.weight, opt.delay, opt.rel_inh_strength, opt.poiss_lambda, opt.seed)
+
+    meters.checkpoint('recipe-create', context)
+
+    hint = arbor.partition_hint()
+    hint.cpu_group_size = opt.group_size
+    hints = {arbor.cell_kind.lif: hint}
+    decomp = arbor.partition_load_balance(recipe, context, hints)
+    print(decomp)
+
+    meters.checkpoint('load-balance', context)
+
+    sim = arbor.simulation(recipe, decomp, context)
+    sim.record(arbor.spike_recording.all)
+
+    meters.checkpoint('simulation-init', context)
+
+    sim.run(opt.tfinal,opt.dt)
+
+    meters.checkpoint('simulation-run', context)
+
+    # Print profiling information
+    print(f'{arbor.meter_report(meters, context)}')
+
+    # Print spike times
+    print(f"{len(sim.spikes())} spikes generated.")
+
+    if opt.spike_file_output:
+        with open(opt.spike_file_output, 'w') as the_file:
+            for meta, data in sim.spikes():
+                the_file.write(f"{meta[0]} {data:3.3f}\n")
diff --git a/python/schedule.cpp b/python/schedule.cpp
index e4f99106065084bd57f642095c8c88dd7b3dd4f5..36edc380836be239cb7400ba87a34e17e5580a92 100644
--- a/python/schedule.cpp
+++ b/python/schedule.cpp
@@ -26,7 +26,7 @@ std::ostream& operator<<(std::ostream& o, const explicit_schedule_shim& e) {
 
 std::ostream& operator<<(std::ostream& o, const poisson_schedule_shim& p) {
     return o << "<arbor.poisson_schedule: tstart " << p.tstart << " ms"
-             << ", freq " << p.freq << " Hz"
+             << ", freq " << p.freq << " kHz"
              << ", seed " << p.seed << ">";
 };
 
@@ -148,6 +148,12 @@ poisson_schedule_shim::poisson_schedule_shim(
     seed = s;
 }
 
+poisson_schedule_shim::poisson_schedule_shim(arb::time_type f) {
+    set_tstart(0.);
+    set_freq(f);
+    seed = 0;
+}
+
 void poisson_schedule_shim::set_tstart(arb::time_type t) {
     pyarb::assert_throw(is_nonneg()(t), "tstart must be a non-negative number");
     tstart = t;
@@ -167,8 +173,7 @@ arb::time_type poisson_schedule_shim::get_freq() const {
 }
 
 arb::schedule poisson_schedule_shim::schedule() const {
-    // convert frequency to kHz.
-    return arb::poisson_schedule(tstart, freq/1000., rng_type(seed));
+    return arb::poisson_schedule(tstart, freq, rng_type(seed));
 }
 
 std::vector<arb::time_type> poisson_schedule_shim::events(arb::time_type t0, arb::time_type t1) {
@@ -236,15 +241,19 @@ void register_schedules(py::module& m) {
 
     poisson_schedule
         .def(py::init<time_type, time_type, std::mt19937_64::result_type>(),
-            "tstart"_a = 0., "freq"_a = 10., "seed"_a = 0,
+            "tstart"_a = 0., "freq"_a, "seed"_a = 0,
             "Construct a Poisson schedule with arguments:\n"
             "  tstart: The delivery time of the first event in the sequence [ms], 0 by default.\n"
-            "  freq:   The expected frequency [Hz], 10 by default.\n"
+            "  freq:   The expected frequency [kHz].\n"
             "  seed:   The seed for the random number generator, 0 by default.")
+        .def(py::init<time_type>(),
+            "freq"_a,
+            "Construct a Poisson schedule, starting from t = 0, default seed, with:\n"
+            "  freq:   The expected frequency [kHz], 10 by default.\n")
         .def_property("tstart", &poisson_schedule_shim::get_tstart, &poisson_schedule_shim::set_tstart,
             "The delivery time of the first event in the sequence [ms].")
         .def_property("freq", &poisson_schedule_shim::get_freq, &poisson_schedule_shim::set_freq,
-            "The expected frequency [Hz].")
+            "The expected frequency [kHz].")
         .def_readwrite("seed", &poisson_schedule_shim::seed,
             "The seed for the random number generator.")
         .def("events", &poisson_schedule_shim::events,
diff --git a/python/schedule.hpp b/python/schedule.hpp
index 7762d2f9d0b14187c3724fbbc7bc224c883e0cfa..9ee81fdabf2a0c38d194117878c94ac7218a36cb 100644
--- a/python/schedule.hpp
+++ b/python/schedule.hpp
@@ -78,12 +78,12 @@ struct explicit_schedule_shim: schedule_shim_base {
 struct poisson_schedule_shim: schedule_shim_base {
     using rng_type = std::mt19937_64;
 
-    arb::time_type tstart = arb::terminal_time;
-    arb::time_type freq = 10.;  // Hz
-    rng_type::result_type seed = 0;
+    arb::time_type tstart; // ms
+    arb::time_type freq; // kHz
+    rng_type::result_type seed;
 
-    poisson_schedule_shim() = default;
     poisson_schedule_shim(arb::time_type ts, arb::time_type f, rng_type::result_type s);
+    poisson_schedule_shim(arb::time_type f);
 
     void set_tstart(arb::time_type t);
     void set_freq(arb::time_type f);
diff --git a/python/single_cell_model.cpp b/python/single_cell_model.cpp
index fae9d364662b02d6736229adafe6420aff79342b..6fd99c824edb951f7e2f6188f03dfd7cb2033f9c 100644
--- a/python/single_cell_model.cpp
+++ b/python/single_cell_model.cpp
@@ -30,7 +30,7 @@ namespace pyarb {
 // Stores the location and sampling frequency for a probe in a single cell model.
 struct probe_site {
     arb::mlocation site;  // Location of sample on morphology.
-    double frequency;     // Sampling frequency [Hz].
+    double frequency;     // Sampling frequency [kHz].
 };
 
 // Stores a single trace, which can be queried and viewed by the user at the end
@@ -194,7 +194,7 @@ public:
 
             traces_.push_back({"voltage", p.site, {}, {}});
 
-            auto sched = arb::regular_schedule(1000./p.frequency);
+            auto sched = arb::regular_schedule(p.frequency);
 
             // Now attach the sampler at probe site, with sampling schedule sched, writing to voltage
             sim_->add_sampler(arb::one_probe({0,i}), sched, trace_callback(traces_[i]));
@@ -252,7 +252,7 @@ void register_single_cell(pybind11::module& m) {
             "Sample a variable on the cell.\n"
             " what:      Name of the variable to record (currently only 'voltage').\n"
             " where:     Location on cell morphology at which to sample the variable.\n"
-            " frequency: The target frequency at which to sample [Hz].")
+            " frequency: The target frequency at which to sample [kHz].")
         .def("probe",
             [](single_cell_model& m, const char* what, const arb::mlocation& where, double frequency) {
                 m.probe(what, where, frequency);},
@@ -260,7 +260,7 @@ void register_single_cell(pybind11::module& m) {
             "Sample a variable on the cell.\n"
             " what:      Name of the variable to record (currently only 'voltage').\n"
             " where:     Location on cell morphology at which to sample the variable.\n"
-            " frequency: The target frequency at which to sample [Hz].")
+            " frequency: The target frequency at which to sample [kHz].")
         .def_property_readonly("spikes",
             [](const single_cell_model& m) {
                 return m.spike_times();}, "Holds spike times [ms] after a call to run().")
diff --git a/python/test/unit/test_schedules.py b/python/test/unit/test_schedules.py
index 9d0344783b5875382855ebea5e554cb8796294b9..e0c6d9daa2cf585484cb9af2f512b669c22e1336 100644
--- a/python/test/unit/test_schedules.py
+++ b/python/test/unit/test_schedules.py
@@ -109,6 +109,15 @@ class ExplicitSchedule(unittest.TestCase):
             rs.events(1., -1.)
 
 class PoissonSchedule(unittest.TestCase):
+    def test_freq_poisson_schedule(self):
+        ps = arb.poisson_schedule(42.)
+        self.assertEqual(ps.freq, 42.)
+
+    def test_freq_tstart_contor_poisson_schedule(self):
+        ps = arb.poisson_schedule(freq = 5., tstart = 4.3)
+        self.assertEqual(ps.freq, 5.)
+        self.assertEqual(ps.tstart, 4.3)
+
     def test_freq_seed_contor_poisson_schedule(self):
         ps = arb.poisson_schedule(freq = 5., seed = 42)
         self.assertEqual(ps.freq, 5.)
@@ -120,18 +129,9 @@ class PoissonSchedule(unittest.TestCase):
         self.assertEqual(ps.freq, 100.)
         self.assertEqual(ps.seed, 1000)
 
-    def test_set_tstart_freq_seed_poisson_schedule(self):
-        ps = arb.poisson_schedule()
-        ps.tstart = 4.5
-        ps.freq = 5.5
-        ps.seed = 83
-        self.assertAlmostEqual(ps.tstart, 4.5)
-        self.assertAlmostEqual(ps.freq, 5.5)
-        self.assertEqual(ps.seed, 83)
-
     def test_events_poisson_schedule(self):
         expected = [17.4107, 502.074, 506.111, 597.116]
-        ps = arb.poisson_schedule(0., 10., 0)
+        ps = arb.poisson_schedule(0., 0.01, 0)
         for i in range(len(expected)):
             self.assertAlmostEqual(expected[i], ps.events(0., 600.)[i], places = 3)
         expected = [5030.22, 5045.75, 5069.84, 5091.56, 5182.17, 5367.3, 5566.73, 5642.13, 5719.85, 5796, 5808.33]
@@ -139,33 +139,39 @@ class PoissonSchedule(unittest.TestCase):
             self.assertAlmostEqual(expected[i], ps.events(5000., 6000.)[i], places = 2)
 
     def test_exceptions_poisson_schedule(self):
+        with self.assertRaises(TypeError):
+            arb.poisson_schedule()
+        with self.assertRaises(TypeError):
+            arb.poisson_schedule(tstart = 10.)
+        with self.assertRaises(TypeError):
+            arb.poisson_schedule(seed = 1432)
         with self.assertRaisesRegex(RuntimeError,
             "tstart must be a non-negative number"):
-            arb.poisson_schedule(tstart = -10.)
+            arb.poisson_schedule(freq=34., tstart = -10.)
         with self.assertRaises(TypeError):
-            arb.poisson_schedule(tstart = None)
+            arb.poisson_schedule(freq=34., tstart = None)
         with self.assertRaises(TypeError):
-            arb.poisson_schedule(tstart = 'tstart')
+            arb.poisson_schedule(freq=34., tstart = 'tstart')
         with self.assertRaisesRegex(RuntimeError,
             "frequency must be a non-negative number"):
             arb.poisson_schedule(freq = -100.)
         with self.assertRaises(TypeError):
             arb.poisson_schedule(freq = 'freq')
         with self.assertRaises(TypeError):
-            arb.poisson_schedule(seed = -1)
+            arb.poisson_schedule(freq=34., seed = -1)
         with self.assertRaises(TypeError):
-            arb.poisson_schedule(seed = 10.)
+            arb.poisson_schedule(freq=34., seed = 10.)
         with self.assertRaises(TypeError):
-            arb.poisson_schedule(seed = 'seed')
+            arb.poisson_schedule(freq=34., seed = 'seed')
         with self.assertRaises(TypeError):
-            arb.poisson_schedule(seed = None)
+            arb.poisson_schedule(freq=34., seed = None)
         with self.assertRaisesRegex(RuntimeError,
             "t0 must be a non-negative number"):
-            ps = arb.poisson_schedule()
+            ps = arb.poisson_schedule(0,0.01)
             ps.events(-1., 1.)
         with self.assertRaisesRegex(RuntimeError,
             "t1 must be a non-negative number"):
-            ps = arb.poisson_schedule()
+            ps = arb.poisson_schedule(0,0.01)
             ps.events(1., -1.)
 
 def suite():