diff --git a/lalburst/python/lalburst/snglcoinc.py b/lalburst/python/lalburst/snglcoinc.py
index f41cc04b991ac5fb694398f692a8a71998b45180..c8ebfd9351ba05a5ca67fba1a038f6d2fc5f63b6 100644
--- a/lalburst/python/lalburst/snglcoinc.py
+++ b/lalburst/python/lalburst/snglcoinc.py
@@ -793,213 +793,78 @@ class CoincTables(object):
 #
 
 
-class CoincSynthesizer(object):
-	"""
-	Class to collect the information required to predict the rate at
-	which different instrument combinations will participate in
-	background coincidences, and compute various probabilities and
-	rates related to the problem of doing so.
-	"""
-
-	def __init__(self, eventlists = None, segmentlists = None, delta_t = None, min_instruments = 2, abundance_rel_accuracy = 1e-4):
-		"""
-		eventlists is either a dictionary mapping instrument name
-		to a list of the events (arbitrary objects) seen in that
-		instrument or a dictionary mapping instrument name to a
-		total count of events seen in that instrument (some
-		features will not be available if a dictionary of counts is
-		provided).  segmentlists is a glue.segments.segmentlistdict
-		object describing the observation segments for each of the
-		instruments.  A segment list must be provided for (at
-		least) each instrument in eventlists.  delta_t is a time
-		window in seconds, the light travel time between instrument
-		pairs is added to this internally to set the maximum
-		allowed coincidence window between a pair of instruments.
-		min_instruments sets the minimum number of instruments that
-		must participate in a coincidence (default is 2).
-
-		abundance_rel_accuracy sets the fractional error tolerated
-		in the Monte Carlo integrator used to estimate the relative
-		abundances of the different kinds of coincs.
+class CoincRates(object):
+	def __init__(self, instruments, delta_t, min_instruments = 2, abundance_rel_accuracy = 1e-4):
+		"""
+		Model for coincidences among noise events collected from
+		several antennas.  Independent Poisson processes are
+		assumed.  The coincidence window for each pair of
+		instruments is (delta_t + light travel time between that
+		pair).  A coincidence is assumed to require full N-way
+		coincidence and require at least min_instruments to
+		participate.
+
+		Initial configuration requires some relatively expensive
+		pre-calculation of internal quantities, but once
+		initialized coincidence rates can be computed quickly from
+		observed event rates.  Several other related quantities can
+		be computed.
 
 		Example:
 
-		>>> from glue.segments import *
-		>>> eventlists = {"H1": [0, 1, 2, 3], "L1": [10, 11, 12, 13], "V1": [20, 21, 22, 23]}
-		>>> seglists = segmentlistdict({"H1": segmentlist([segment(0, 30)]), "L1": segmentlist([segment(10, 50)]), "V1": segmentlist([segment(20, 70)])})
-		>>> coinc_synth = CoincSynthesizer(eventlists, seglists, 0.001)
-		>>> coinc_synth.mu
-		{'V1': 0.08, 'H1': 0.13333333333333333, 'L1': 0.1}
-		>>> coinc_synth.tau
-		{frozenset(['V1', 'H1']): 0.028287979933844225, frozenset(['H1', 'L1']): 0.011012846152223924, frozenset(['V1', 'L1']): 0.027448341016726496}
-		>>> coinc_synth.rates	# doctest: +SKIP
-		{frozenset(['V1', 'H1']): 0.0006034769052553435, frozenset(['V1', 'H1', 'L1']): 1.1793108172576082e-06, frozenset(['H1', 'L1']): 0.000293675897392638, frozenset(['V1', 'L1']): 0.00043917345626762395}
-		>>> coinc_synth.P_live
-		{frozenset(['V1', 'H1']): 0.0, frozenset(['V1', 'H1', 'L1']): 0.25, frozenset(['H1', 'L1']): 0.25, frozenset(['V1', 'L1']): 0.5}
-		>>>
-		>>>
-		>>> coinc_synth = CoincSynthesizer(eventlists, seglists, 0.001, min_instruments = 1)
-		>>> coinc_synth.rates	# doctest: +SKIP
-		{frozenset(['V1']): 0.08, frozenset(['H1']): 0.13333333333333333, frozenset(['V1', 'H1']): 0.0006034769052553435, frozenset(['L1']): 0.1, frozenset(['V1', 'L1']): 0.00043917345626762395, frozenset(['V1', 'H1', 'L1']): 1.179508868912594e-06, frozenset(['H1', 'L1']): 0.000293675897392638}
-		"""
-		self.eventlists = eventlists if eventlists is not None else dict.fromkeys(segmentlists, 0) if segmentlists is not None else {}
-		self.segmentlists = segmentlists if segmentlists is not None else segmentsUtils.segments.segmentlistdict()
-		if set(self.eventlists) > set(self.segmentlists):
-			raise ValueError("require a segmentlist for each event list")
+		>>> coincrates = CoincRates(("H1", "L1", "V1"), 0.005, 2)
+		"""
+		self.instruments = frozenset(instruments)
 		self.delta_t = delta_t
-		if min_instruments < 1:
-			raise ValueError("min_instruments must be >= 1")
 		self.min_instruments = min_instruments
-		self.abundance_rel_accuracy = abundance_rel_accuracy
-
-
-	def reset(self):
-		"""
-		Reset all internally-cached data.  This method must be
-		invoked if the .eventlists, .segmentlists or .delta_t
-		attributes (or their contents) are modified.  This class
-		relies heavily on pre-computed quantities that are derived
-		from the input parameters and cached;  invoking this method
-		forces the recalculation of cached data (the next time it's
-		needed).  Until this method is invoked, derived data like
-		coincidence window sizes and mean event rates might reflect
-		the previous state of this class.
-		"""
-		try:
-			del self._P_live
-		except AttributeError:
-			pass
-		try:
-			del self._mu
-		except AttributeError:
-			pass
-		try:
-			del self._tau
-		except AttributeError:
-			pass
-		try:
-			del self._coincidence_rate_factors
-		except AttributeError:
-			pass
-
-
-	@property
-	def all_instrument_combos(self):
-		"""
-		A tuple of all possible instrument combinations (as
-		frozensets).
-		"""
-		all_instruments = tuple(self.eventlists)
-		return tuple(frozenset(instruments) for n in range(self.min_instruments, len(all_instruments) + 1) for instruments in itertools.combinations(all_instruments, n))
-
-
-	@property
-	def P_live(self):
-		"""
-		Dictionary mapping instrument combination (as a frozenset)
-		to fraction of the total time in which the minimum required
-		number of instruments were on during which precisely that
-		combination of instruments (and no other instruments) are
-		on.  E.g., P_live[frozenset(("H1", "L1"))] gives the
-		probability that precisely H1 and L1 are the only
-		instruments operating.
-		"""
-		try:
-			return self._P_live
-		except AttributeError:
-			livetime = float(abs(segmentsUtils.vote(self.segmentlists.values(), self.min_instruments)))
-			all_instruments = set(self.segmentlists)
-			self._P_live = dict((instruments, float(abs(self.segmentlists.intersection(instruments) - self.segmentlists.union(all_instruments - instruments))) / livetime) for instruments in self.all_instrument_combos)
-			# check normalization
-			total = sum(sorted(self._P_live.values()))
-			assert abs(1.0 - total) < 1e-14
-			for key in self._P_live:
-				self._P_live[key] /= total
-			# done
-			return self._P_live
-
-
-	@property
-	def mu(self):
-		"""
-		Dictionary mapping instrument name to mean event rate in
-		Hz.  This is a reference to an internally-cached
-		dictionary.  Modifications will be retained, or an
-		externally supplied dictionary can be assigned to this
-		attribute to override it entirely.
-		"""
-		try:
-			return self._mu
-		except AttributeError:
-			try:
-				# try treating eventlists values as lists
-				# and measure their lengths
-				counts = dict((instrument, len(events)) for instrument, events in self.eventlists.items())
-			except TypeError:
-				# failed.  assume they're scalars giving
-				# the number of events directly
-				counts = self.eventlists
-			self._mu = dict((instrument, count / float(abs(self.segmentlists[instrument]))) for instrument, count in counts.items())
-			return self._mu
-
-
-	@mu.setter
-	def mu(self, val):
-		self._mu = val
-
-
-	@property
-	def tau(self):
-		"""
-		Dictionary mapping pair of instrument names (as a
-		frozenset) to coincidence window in seconds.  This is a
-		reference to an internally-cached dictionary.
-		Modifications will be retained, or an externally supplied
-		dictionary can be assigned to this attribute to override it
-		entirely.
-		"""
-		try:
-			return self._tau
-		except AttributeError:
-			self._tau = dict((frozenset(ab), self.delta_t + light_travel_time(*ab)) for ab in itertools.combinations(tuple(self.eventlists), 2))
-			return self._tau
-
-
-	@tau.setter
-	def tau(self, val):
-		self._tau = val
-		# force re-computation of coincidence rates
-		try:
-			del self._coincidence_rate_factors
-		except AttributeError:
-			pass
-
-
-	@property
-	def coincidence_rate_factors(self):
-		"""
-		For instruments {1, ..., N}, with rates \\mu_{1}, ...,
-		\\mu_{N}, the rate of coincidences is
-
-		\\propto \\prod_{i} \\mu_{i}.
-
-		The proportionality constant depends only on the
-		coincidence windows.  This function computes and returns a
-		dictionary of the proportionality constants keyed by
-		instrument set.
-
-		The return value is a reference to an internally-cached
-		dictionary.  Modifications will be retained until the
-		cached data is regenerated (after .reset() is invoked or
-		the .tau attribute is assigned to).
-		"""
-		try:
-			return self._coincidence_rate_factors
-		except AttributeError:
-			pass
+		if self.min_instruments > len(self.instruments):
+			raise ValueError("require len(instruments) >= min_instruments")
+		if self.delta_t < 0.:
+			raise ValueError("require delta_t >= 0.")
+		if abundance_rel_accuracy <= 0.:
+			raise ValueError("require abundance_rel_accuracy > 0.")
+
+		# dictionary mapping pair of instrument names (as a
+		# frozenset) to coincidence window in seconds = delta_t +
+		# light travel time
+		self.tau = dict((frozenset(ab), self.delta_t + light_travel_time(*ab)) for ab in itertools.combinations(tuple(self.instruments), 2))
+
+		# for instruments {1, ..., N}, with rates \\mu_{1}, ...,
+		# \\mu_{N}, the rate of coincidences is
+		#
+		# 	\\propto \\prod_{i} \\mu_{i}.
+		#
+		# the proportionality constant depends only on the
+		# coincidence windows.  the following computes a dictionary
+		# of these proportionality constants keyed by instrument
+		# set.
 
 		# initialize the proportionality constants
-		self._coincidence_rate_factors = dict.fromkeys(self.all_instrument_combos, 1.0)
+		self.rate_factors = dict.fromkeys(self.all_instrument_combos, 1.0)
+
+		# fast-path for gstlal-inspiral pipeline:  hard-coded
+		# result for H,L,V network, 5 ms coincidence window, 1 or 2
+		# minimum instruments required
+		if self.instruments == set(("H1", "L1", "V1")) and self.delta_t == 0.005:
+			if self.min_instruments == 1:
+				self.rate_factors = {
+					frozenset(["H1"]): 1.0,
+					frozenset(["L1"]): 1.0,
+					frozenset(["V1"]): 1.0,
+					frozenset(["H1", "L1"]): 0.030025692304447849,
+					frozenset(["H1", "V1"]): 0.064575959867688451,
+					frozenset(["L1", "V1"]): 0.062896682033452986,
+					frozenset(["H1", "L1", "V1"]): 0.0016876238239802771
+				}
+				return
+			elif self.min_instruments == 2:
+				self.rate_factors = {
+					frozenset(["H1", "L1"]): 0.030025692304447849,
+					frozenset(["H1", "V1"]): 0.064575959867688451,
+					frozenset(["L1", "V1"]): 0.062896682033452986,
+					frozenset(["H1", "L1", "V1"]): 0.0016876624438056285
+				}
+				return
 
 		for instruments in self.all_instrument_combos:
 		# choose the instrument whose TOA forms the "epoch" of the
@@ -1052,7 +917,7 @@ class CoincSynthesizer(object):
 		# stone-throwing to estimate the ratio of the desired
 		# volume to that volume and multiply by that factor.
 			for instrument in instruments:
-				self._coincidence_rate_factors[key] *= 2. * self.tau[frozenset((anchor, instrument))]
+				self.rate_factors[key] *= 2. * self.tau[frozenset((anchor, instrument))]
 		# compute the ratio of the desired volume to that volume by
 		# stone throwing.
 			if len(instruments) > 1:
@@ -1094,17 +959,16 @@ class CoincSynthesizer(object):
 		# here is much simpler.
 				math_sqrt = math.sqrt
 				random_uniform = random.uniform
-				two_epsilon = 2. * self.abundance_rel_accuracy
+				two_epsilon = 2. * abundance_rel_accuracy
 				n, d = 0, 0
 				while math_sqrt(d) >= two_epsilon * n:
 					dt = tuple(random_uniform(*window) for window in windows)
 					if all(abs(dt[i] - dt[j]) <= maxdt for i, j, maxdt in ijseq):
 						n += 1
 					d += 1
-				self._coincidence_rate_factors[key] *= float(n) / float(d)
+				self.rate_factors[key] *= float(n) / float(d)
 
-		# done
-		return self._coincidence_rate_factors
+		# done computing rate_factors
 
 		# FIXME:  commented-out implementation that requires scipy
 		# >= 0.19.  saving it for later.  NOTE:  untested
@@ -1154,219 +1018,208 @@ class CoincSynthesizer(object):
 		#		# the origin is in the interior
 		#		interior = numpy.zeros((len(instruments),), dtype = "double")
 		#		# compute volume
-		#		self._coincidence_rate_factors[key] *= spatial.ConvexHull(spatial.HalfspaceIntersection(halfspaces, interior).intersections).volume
+		#		self.rate_factors[key] = spatial.ConvexHull(spatial.HalfspaceIntersection(halfspaces, interior).intersections).volume
 		#	else:
 		#		# 1-D case (qhull barfs, but who needs it)
-		#		self._coincidence_rate_factors[key] *= 2. * self.tau[frozenset((anchor, instruments[0]))]
+		#		self.rate_factors[key] = 2. * self.tau[frozenset((anchor, instruments[0]))]
 
 
 	@property
-	def rates(self):
-		"""
-		Dictionary mapping instrument combination (as a frozenset)
-		to mean rate in Hz at which that combination of instruments
-		can be found in a coincidence under the assumption that all
-		instruments are on and able to participate in coincidences.
-		Corrections (e.g., based on the contents of the .P_live
-		attribute) are required if that assumption does not hold.
-		Note the difference between the contents of this dictionary
-		and the rates of various kinds of coincidences.  For
-		example, the rate for frozenset(("H1", "L1")) is the rate,
-		in Hz, at which that combination of instruments
-		participates in coincidences, not the rate of H1,L1
-		doubles.  The return value is not cached.
+	def all_instrument_combos(self):
 		"""
-		# compute \mu_{1} * \mu_{2} ... \mu_{N} * FACTOR where
-		# FACTOR is the previously-computed proportionality
-		# constant from coincidence_rate_factors.
+		A tuple of all possible instrument combinations (as
+		frozensets).
 
-		rates = dict(self.coincidence_rate_factors)
-		for instruments in rates:
-			for instrument in instruments:
-				rates[instruments] *= self.mu[instrument]
+		Example:
 
-		# rates now contains the mean rate at which each
-		# combination of instruments can be found in a coincidence
-		# during the times when at least those instruments are
-		# available to form coincidences.  Note:  the rate, e.g.,
-		# for the combo "H1,L1" is the sum of the rate of "H1,L1"
-		# doubles as well as the rate of "H1,L1,V1" triples and all
-		# other higher-order coincidences in which H1 and L1
-		# participate.
+		>>> coincrates = CoincRates(("H1", "L1", "V1"), 0.005, 1)
+		(frozenset(['V1']), frozenset(['H1']), frozenset(['L1']), frozenset(['V1', 'H1']), frozenset(['V1', 'L1']), frozenset(['H1', 'L1']), frozenset(['V1', 'H1', 'L1']))
+		>>> coincrates = CoincRates(("H1", "L1", "V1"), 0.005, 2)
+		>>> coincrates.all_instrument_combos
+		(frozenset(['V1', 'H1']), frozenset(['V1', 'L1']), frozenset(['H1', 'L1']), frozenset(['V1', 'H1', 'L1']))
+		"""
+		all_instruments = tuple(self.instruments)
+		return tuple(frozenset(instruments) for n in range(self.min_instruments, len(all_instruments) + 1) for instruments in itertools.combinations(all_instruments, n))
 
-		return rates
 
+	def coinc_rates(self, **rates):
+		"""
+		Given the event rates for a collection of instruments,
+		compute the rates at which N-way coincidences occur among
+		them where N >= min_instruments.  The return value is a
+		dictionary whose keys are frozensets of instruments and
+		whose values are the rate of coincidences for that set.
 
-	@property
-	def mean_coinc_rate(self):
-		"""
-		Dictionary mapping instrument combo (as a frozenset) to the
-		mean rate at which coincidences involving precisely that
-		combination of instruments occur, averaged over times when
-		at least the minimum required number of instruments are
-		operating --- the mean rate during times when coincidences
-		are possible, not the mean rate over all time.  The result
-		is not cached.
-		"""
-		rates = self.rates	# don't re-evalute in loop
-		coinc_rate = dict.fromkeys(rates, 0.0)
-		# iterate over probabilities in order for better numerical
-		# accuracy
-		for on_instruments, P_on_instruments in sorted(self.P_live.items(), key = lambda ignored_P: ignored_P[1]):
-			# short cut
-			if not P_on_instruments:
-				continue
+		NOTE:  the computed rates are the rates at which
+		coincidences among at least those instruments occur, not
+		the rate at which coincidences among exactly those
+		instruments occur.  e.g., considering the H1, L1, V1
+		network, for the pair H1, L1 the return value is the sum of
+		the rate at which those two instruments form double
+		coincidences and also the rate at which they participate in
+		H1, L1, V1 triple coincidences.
 
-			# rates for instrument combinations that are
-			# possible given the instruments that are on
-			allowed_rates = dict((participating_instruments, rate) for participating_instruments, rate in rates.items() if participating_instruments <= on_instruments)
+		See also .strict_coinc_rates().
 
-			# subtract from each rate the rate at which that
-			# combination of instruments is found in (allowed)
-			# higher-order coincs.  after this, allowed_rates
-			# maps instrument combo to rate of coincs involving
-			# exactly that combo given the instruments that are
-			# on
-			for key in sorted(allowed_rates, key = lambda x: len(x), reverse = True):
-				allowed_rates[key] -= sum(sorted(rate for otherkey, rate in allowed_rates.items() if key < otherkey))
+		Example:
 
-			for combo, rate in allowed_rates.items():
-				assert rate >= 0.
-				coinc_rate[combo] += P_on_instruments * rate
-		return coinc_rate
+		>>> coincrates = CoincRates(("H1", "L1", "V1"), 0.005, 2)
+		>>> coincrates.coinc_rates(H1 = 0.001, L1 = 0.002, V1 = 0.003)
+		{frozenset(['V1', 'H1']): 1.9372787960306537e-07, frozenset(['V1', 'H1', 'L1']): 1.0125974662833771e-11, frozenset(['H1', 'L1']): 6.00513846088957e-08, frozenset(['V1', 'L1']): 3.77380092200718e-07}
+		>>> coincrates.coinc_rates(H1 = 0.001, L1 = 0.002, V1 = 0.002)
+		{frozenset(['V1', 'H1']): 1.291519197353769e-07, frozenset(['V1', 'H1', 'L1']): 6.750649775222514e-12, frozenset(['H1', 'L1']): 6.00513846088957e-08, frozenset(['V1', 'L1']): 2.5158672813381197e-07}
+		>>> coincrates.coinc_rates(H1 = 0.001, L1 = 0.002, V1 = 0.001)
+		{frozenset(['V1', 'H1']): 6.457595986768845e-08, frozenset(['V1', 'H1', 'L1']): 3.375324887611257e-12, frozenset(['H1', 'L1']): 6.00513846088957e-08, frozenset(['V1', 'L1']): 1.2579336406690598e-07}
+		"""
+		if set(rates) != self.instruments:
+			raise ValueError("require event rates for %s, got rates for %s" % (", ".join(sorted(self.instruments)), ", ".join(sorted(rates))))
+		if any(rate < 0. for rate in rates.values()):
+			# they don't have to be non-negative for this
+			# method to succede, but negative values are
+			# nonsensical and other things will certainly fail.
+			# it's best to catch and report the problem as
+			# early in the code path as it can be identified,
+			# so that when the problem is encountered it's
+			# easier to identify the cause
+			raise ValueError("rates must be >= 0")
+		if max(rates.values()) * max(self.tau.values()) >= 1.:
+			raise ValueError("events per coincidence window must be << 1: rates = %s, max window = %g" % (rates, max(self.tau.values())))
 
+		# compute \mu_{1} * \mu_{2} ... \mu_{N} * FACTOR where
+		# FACTOR is the previously-computed proportionality
+		# constant from self.rate_factors
 
-	@property
-	def P_instrument_combo(self):
-		"""
-		A dictionary mapping instrument combination (as a
-		frozenset) to the probability that a background coincidence
-		involves precisely that combination of instruments.  This
-		is derived from the live times and the mean rates at which
-		the different instrument combinations participate in
-		coincidences.  The result is not cached.
-		"""
-		# convert rates to relative abundances
-		mean_rates = self.mean_coinc_rate	# calculate once
-		total_rate = sum(sorted(mean_rates.values()))
-		P = dict((key, rate / total_rate) for key, rate in mean_rates.items())
-		# make sure normalization is good
-		total = sum(sorted(P.values()))
-		assert abs(1.0 - total) < 1e-14
-		for key in P:
-			P[key] /= total
-		return P
+		coinc_rates = dict(self.rate_factors)
+		for instruments in coinc_rates:
+			for instrument in instruments:
+				coinc_rates[instruments] *= rates[instrument]
+		return coinc_rates
 
 
-	@property
-	def mean_coinc_count(self):
+	def strict_coinc_rates(self, **rates):
 		"""
-		A dictionary mapping instrument combination (as a
-		frozenset) to the total number of coincidences involving
-		precisely that combination of instruments expected from the
-		background.  The result is not cached.
-		"""
-		T = float(abs(segmentsUtils.vote(self.segmentlists.values(), self.min_instruments)))
-		return dict((instruments, rate * T) for instruments, rate in self.mean_coinc_rate.items())
+		Given the event rates for a collection of instruments,
+		compute the rates at which strict N-way coincidences occur
+		among them where N >= min_instruments.  The return value is
+		a dictionary whose keys are frozensets of instruments and
+		whose values are the rate of coincidences for that set.
 
+		NOTE:  the computed rates are the rates at which
+		coincidences occur among exactly those instrument
+		combinations, excluding the rate at which each combination
+		participates in higher-order coincs.  e.g., considering the
+		H1, L1, V1 network, for the pair H1, L1 the return value is
+		the rate at which H1, L1 doubles occur, not including the
+		rate at which the H1, L1 pair participates in H1, L1, V1
+		triples.
 
-	def instrument_combos(self):
-		"""
-		Generator that yields random instrument combinations (as
-		frozensets) in relative abundances that match the expected
-		relative abundances of background coincidences given the
-		live times, mean single-instrument event rates, and
-		coincidence windows.
+		See also .coinc_rates().
 
 		Example:
 
-		>>> from glue.segments import *
-		>>> eventlists = {"H1": [0, 1, 2, 3], "L1": [10, 11, 12, 13], "V1": [20, 21, 22, 23]}
-		>>> seglists = segmentlistdict({"H1": segmentlist([segment(0, 30)]), "L1": segmentlist([segment(10, 50)]), "V1": segmentlist([segment(20, 70)])})
-		>>> coinc_synth = CoincSynthesizer(eventlists, seglists, 0.001)
-		>>> combos = coinc_synth.instrument_combos()
-		>>> combos.next()	# doctest: +SKIP
-		frozenset(['V1', 'L1'])
+		>>> coincrates = CoincRates(("H1", "L1", "V1"), 0.005, 2)
+		>>> coincrates.strict_coinc_rates(H1 = 0.001, L1 = 0.002, V1 = 0.003)
+		{frozenset(['V1', 'H1']): 1.9371775362840254e-07, frozenset(['V1', 'H1', 'L1']): 1.0125974662833771e-11, frozenset(['H1', 'L1']): 6.004125863423287e-08, frozenset(['V1', 'L1']): 3.7736996622605513e-07}
+		>>> coincrates.strict_coinc_rates(H1 = 0.001, L1 = 0.002, V1 = 0.002)
+		{frozenset(['V1', 'H1']): 1.2914516908560167e-07, frozenset(['V1', 'H1', 'L1']): 6.750649775222514e-12, frozenset(['H1', 'L1']): 6.004463395912047e-08, frozenset(['V1', 'L1']): 2.5157997748403677e-07}
+		>>> coincrates.strict_coinc_rates(H1 = 0.001, L1 = 0.002, V1 = 0.001)
+		{frozenset(['V1', 'H1']): 6.457258454280083e-08, frozenset(['V1', 'H1', 'L1']): 3.375324887611257e-12, frozenset(['H1', 'L1']): 6.004800928400808e-08, frozenset(['V1', 'L1']): 1.2578998874201838e-07}
+		"""
+		# initialize from the plain coinc rates
+		strict_coinc_rates = self.coinc_rates(**rates)
+		# iterating over the instrument combos from the combo with
+		# the most instruments to the combo with the least
+		# instruments ...
+		for instruments in sorted(strict_coinc_rates, reverse = True, key = lambda instruments: len(instruments)):
+			# ... subtract from its rate the rate at which
+			# combos containing it occur (which, because we're
+			# moving from biggest combo to smallest combo, have
+			# already had the rates of higher order combos
+			# containing themselves subtracted)
+			for key, rate in strict_coinc_rates.items():
+				if instruments < key:
+					strict_coinc_rates[instruments] -= rate
+		# make sure this didn't produce any negative rates
+		assert all(rate >= 0. for rate in strict_coinc_rates.values()), "encountered negative rate: %s" % strict_coinc_rates
+		return strict_coinc_rates
+
+
+	def marginalized_strict_coinc_counts(self, seglists, **rates):
 		"""
-		#
-		# retrieve sorted tuple of (probability mass, instrument
-		# combo) pairs.  remove instrument combos whose probability
-		# mass is 0.  if no combos remain then we can't form
-		# coincidences
-		#
+		A dictionary mapping instrument combination (as a
+		frozenset) to the total number of coincidences involving
+		precisely that combination of instruments expected from the
+		background.
+		"""
+		if set(seglists) != self.instruments:
+			raise ValueError("require segmentlists for %s, got %s" % (", ".join(sorted(self.instruments)), ", ".join(sorted(seglists))))
 
-		P = tuple(sorted([mass, instruments] for instruments, mass in self.P_instrument_combo.items() if mass != 0))
-		if not P:
-			return
+		# time when exactly a given set of instruments are on
+		livetime = dict((on_instruments, float(abs(seglists.intersection(on_instruments) - seglists.union(self.instruments - on_instruments)))) for on_instruments in self.all_instrument_combos)
 
-		#
-		# replace the probability masses with cummulative probabilities
-		#
+		coinc_count = dict.fromkeys(livetime, 0.0)
+		for on_instruments, T in livetime.items():
+			for coinc_instruments, rate in self.strict_coinc_rates(**dict((instrument, (rate if instrument in on_instruments else 0.)) for instrument, rate in rates.items())).items():
+				coinc_count[coinc_instruments] += T * rate
 
-		for i in range(1, len(P)):
-			P[i][0] += P[i - 1][0]
+		return coinc_count
 
-		#
-		# normalize (should be already, just be certain)
-		#
 
-		assert abs(P[-1][0] - 1.0) < 1e-14
-		for i in range(len(P)):
-			P[i][0] /= P[-1][0]
-		assert P[-1][0] == 1.0
+	def lnP_instruments(self, **rates):
+		"""
+		Given the event rates for a collection of instruments,
+		compute the natural logarithm of the probability that a
+		coincidence is found to involve exactly a given set of
+		instruments.  This is equivalent to the ratios of the
+		values in the dictionary returned by .strict_coinc_rates()
+		to their sum.
 
-		#
-		# generate random instrument combos
-		#
+		Raises ZeroDivisionError if all coincidence rates are 0.
 
-		while 1:	# 1 is immutable, so faster than True
-			yield P[bisect_left(P, [random.uniform(0.0, 1.0)])][1]
+		See also .strict_coinc_rates().
 
+		Example:
 
-	def coincs(self, timefunc, allow_zero_lag = False):
+		>>> coincrates = CoincRates(("H1", "L1", "V1"), 0.005, 2)
+		>>> coincrates.lnP_instruments(H1 = 0.001, L1 = 0.002, V1 = 0.003)
+		{frozenset(['V1', 'H1']): -1.1811240675627561, frozenset(['V1', 'H1', 'L1']): -11.04017769668565, frozenset(['H1', 'L1']): -2.3524943192518166, frozenset(['V1', 'L1']): -0.514300240038396}
 		"""
-		Generator to yield time shifted coincident event tuples
-		without the use of explicit time shift vectors.  This
-		generator can only be used if the eventlists dictionary
-		with which this object was initialized contained lists of
-		event objects and not merely counts of events.
+		strict_coinc_rates = self.strict_coinc_rates(**rates)
+		total_rate = sum(strict_coinc_rates.values())
+		if total_rate == 0.:
+			raise ZeroDivisionError("all rates are 0")
+		P_instruments = dict((instruments, rate / total_rate) for instruments, rate in strict_coinc_rates.items())
+		norm = sum(sorted(P_instruments.values()))
+		# safety check:  result should be nearly exactly normalized
+		assert abs(1.0 - norm) < 1e-14
+		return dict((instruments, (math.log(P / norm) if P else NegInf)) for instruments, P in P_instruments.items())
 
-		timefunc is a function for computing the "time" of an
-		event, its signature should be
 
-			t = timefunc(event)
-
-		This function will be applied to the event objects
-		contained in self.eventlists.
+	def random_instruments(self, **rates):
+		"""
+		Generator that, given the event rates for a collection of
+		instruments, yields a sequence of two-element tuples each
+		containing a randomly-selected frozen set of instruments
+		and the natural logarithm of the ratio of the rate at which
+		that combination of instruments forms coincidences to the
+		rate at which it is being yielded by this generator.
 
-		If allow_zero_lag is False (the default), then only event tuples
-		with no genuine zero-lag coincidences are returned, that is
-		only tuples in which no event pairs would be considered to
-		be coincident without time shifts applied.  Note that
-		single-instrument "coincidences", if allowed, are *not*
-		considered to be zero-lag coincidences.
+		See also .lnP_instruments().
 
 		Example:
 
-		>>> from glue.segments import *
-		>>> eventlists = {"H1": [0, 1, 2, 3], "L1": [10, 11, 12, 13], "V1": [20, 21, 22, 23]}
-		>>> seglists = segmentlistdict({"H1": segmentlist([segment(0, 30)]), "L1": segmentlist([segment(10, 50)]), "V1": segmentlist([segment(20, 70)])})
-		>>> coinc_synth = CoincSynthesizer(eventlists, seglists, 0.001)
-		>>> coincs = coinc_synth.coincs((lambda x: 0), allow_zero_lag = True)
-		>>> # returns a tuple of events
-		>>> coincs.next()	# doctest: +SKIP
-		"""
-		for instruments in self.instrument_combos():
-			# randomly selected events from those instruments
-			instruments = tuple(instruments)
-			events = tuple(random.choice(self.eventlists[instrument]) for instrument in instruments)
-
-			# test for a genuine zero-lag coincidence among them
-			if not allow_zero_lag and any(abs(ta - tb) < self.tau[frozenset((instrumenta, instrumentb))] for (instrumenta, ta), (instrumentb, tb) in itertools.combinations(zip(instruments, (timefunc(event) for event in events)), 2)):
-				continue
-
-			# return acceptable event tuples
-			yield events
+		>>> coincrates = CoincRates(("H1", "L1", "V1"), 0.005, 2)
+		>>> x = iter(coincrates.random_instruments(H1 = 0.001, L1 = 0.002, V1 = 0.003))
+		>>> x.next()	# doctest: +SKIP
+		(frozenset(['H1', 'L1']), -3.738788683913535)
+		"""
+		# guaranteed non-empty
+		lnP_instruments = self.lnP_instruments(**rates)
+		lnN = math.log(len(lnP_instruments))
+		results = tuple((instruments, lnP - lnN) for instruments, lnP in lnP_instruments.items())
+		choice = random.choice
+		while 1:
+			yield choice(results)
 
 
 	def plausible_toas(self, instruments):
@@ -1380,14 +1233,16 @@ class CoincSynthesizer(object):
 
 		Example:
 
-		>>> # minimal initialization for this method
-		>>> coinc_synth = CoincSynthesizer(segmentlists = {"H1": None, "L1": None, "V1": None}, delta_t = 0.005)
-		>>> toas = coinc_synth.plausible_toas(("H1", "L1", "V1"))
-		>>> toas.next()	# doctest: +SKIP
-		{'V1': 0.004209420456924601, 'H1': 0.0, 'L1': -0.006071537909950742}
+		>>> coincrates = CoincRates(("H1", "L1", "V1"), 0.005, 2)
+		>>> x = iter(coincrates.plausible_toas(("H1", "L1")))
+		>>> x.next()	# doctest: +SKIP
+		{'H1': 0.0, 'L1': -0.010229226372297711}
 		"""
-		# this algorithm is documented in slideless_coinc_generator_rates()
 		instruments = tuple(instruments)
+		if set(instruments) > self.instruments:
+			raise ValueError("not configured for %s" % ", ".join(sorted(set(instruments) - self.instruments)))
+		if len(instruments) < self.min_instruments:
+			raise ValueError("require at least %d instruments, got %d" % (self.min_instruments, len(instruments)))
 		anchor, instruments = instruments[0], instruments[1:]
 		anchor_offset = ((anchor, 0.0),)	 # don't build inside loop
 		uniform = random.uniform