Wire-Cell News

Updates from the Wire-Cell team.

Simulation updates

Despite the lack of news updates, there's been a lot of activity. Hopefully more posts will follow to catch up. For now, some news about WCT simulation.

Release 0.7.0 is focused on the new signal processing technique that exploits compressed sensing techniques (L1 for short, named after the key term in the regularization) and deserves its own post. For here, it was also the first release to include support for WCT simulation. The next big release, 0.8.x is devoted to simulation features, particularly to support multiple anodes (APAs) and two faced anodes.

To track progress toward that release a WireCell GitHub project #1 has been made. In principle, when all the "to dos" are "done" then we will cut 0.8.0. In the rest of this post some of the important changes are described.

Continuous vs Discontinuous

The Ductor and (MultiDuctor, see below) simulation components operate in either a "continuous" or "discontinuous" mode. Each mode has a trade off depending on how the user is providing input depositions.

the Ductor produces frames starting at the configured "start time" and a regular intervals according to the "readout time" so that all time is spanned and until the input depo stream is exhausted.
the Ductor operates as above but uses the time of the first deposition to determine the "start time". A corollary of this is that the user may provide subsequent sets of depos, each starting at a different time.

The following figure illustrates the differences as a timeline going from right to left.


The diamonds represent depos for "signal" interactions. Every other interaction is colored red or blue. The smaller black dots represent depos from some "background" event.

The green boxes represent frames made in "continuous" mode and the yellow in "discontinuous" mode. The "discontinuous" frames assume that the "background" depos far from the "signal" depos were not provided by the user. Of course, the user could indeed include these backgrounds and then they'd produce frames but the user must group them somehow into a set of depos when using "discontinuous" mode.

The trade offs between the two modes:

  • continuous mode sometimes reads out "nothing" (although "nothing" may mean "just noise" if that's how the sim is configured)
  • continuous mode captures all activity.
  • continuous mode will split activity which may be from one depo (or from the depos from one track) when it spans the boundary between two frames. This can be seen in the second blue set.
  • discontinuous mode will "oversample" time if ever the user provides depo sets which are closer in time than one readout. This can be seen in that the second red set is "too close" to the first blue set.

Which mode is best? The continuous mode is more correct and the discontinuous mode may require less CPU. Some guidelines:

  • use continuous mode for simulating full stream running such as for DUNE FD. Framing must still be done but their readout time may be somewhat arbitrary. In any case, consumer of the stream of frames must concatenate them.
  • use discontinuous mode with care as a way to focus CPU usage on a'priori known regions of interest of time. Consider to configure the job for a slightly longer readout window than actually desired and then trim the result to avoid edge effects. Take care to avoid the "oversampling" of time as described above. Take care of what depo provides the start time.

Objectified access to data files

Previously, the "wire geometry" and "field response" files were loaded as needed. This does not scale as each AnodePlane and other parts need to load the same files over and over. Now, each file is loaded once in its own component instance and an AnodePlane is configured to use these components.

The following Jsonnet configuration snippet shows an example (from this test):

local wc = import "wirecell.jsonnet";
local params = import "_params.jsonnet";

local fields_nominal = {
    type: "FieldResponse",
    name: "nominal",
    data: { filename: params.files.fields[0] }
local fields_uvground = {
    type: "FieldResponse",
    name: "uvground",
    data: { filename: params.files.fields[1] }
local fields_vyground = {
    type: "FieldResponse",
    name: "vyground",
    data: { filename: params.files.fields[2] }

local wires = {
    type: "WireSchemaFile",
    data: { filename: params.files.wires }

local anode_nominal = {
    type : "AnodePlane",
    name : "nominal",
    data : params.elec + params.daq {
	ident : 0,              // must match what's in wires
	field_response: wc.tn(fields_nominal),
	wire_schema: wc.tn(wires)
local anode_uvground = anode_nominal {
    name: "uvground",
    data : super.data {
	field_response: wc.tn(fields_uvground),
local anode_vyground = anode_nominal {
    name: "vyground",
    data : super.data {
	field_response: wc.tn(fields_vyground),

Interleave different types of field response function sets

The example configuration above shows multiple AnodePlane instances being configured. This is then used to provide support for signal simulation in the case where some wires are grounded/shorted or otherwise have a different field response function set than nominal.

One can think of multiple different universes being interleaved. For the MicroBooNE example in one universe the anode plane has fully nominal field responses, in another, all U-V wires are shorted/grounded and in the final all V-W wires are shorted/grounded. In the real universe we must mix and match each of these three depending on where a particular deposition lands on the wire planes.

To apply these different, interleaved "universes" the MultiDuctor applies user-configured rules that map a Ductor and its AnodePlane to each energy deposition. Currently the main rule supported is one that locates each depo in "wire space" so that one may assign different field responses to different wires. Extending the example above:

// The guts of this chain can be generated with:
// $ wirecell-util convert-uboone-wire-regions \
//                 microboone-celltree-wires-v2.1.json.bz2 \
//                 MicroBooNE_ShortedWireList_v2.csv \
//                 foo.json
// Copy-paste the plane:0 and plane:2 in uv_ground and vy_ground, respectively
local uboone_ductor_chain = [
	ductor: wc.tn(ductor_uvground),
	rule: "wirebounds",
	args: [ 
	    [ { plane:0, min:296, max:296 } ],
	    [ { plane:0, min:298, max:315 } ],
	    [ { plane:0, min:317, max:317 } ],
	    [ { plane:0, min:319, max:327 } ],
	    [ { plane:0, min:336, max:337 } ],
	    [ { plane:0, min:343, max:345 } ],
	    [ { plane:0, min:348, max:351 } ],
	    [ { plane:0, min:376, max:400 } ],
	    [ { plane:0, min:410, max:445 } ],
	    [ { plane:0, min:447, max:484 } ],
	    [ { plane:0, min:501, max:503 } ],
	    [ { plane:0, min:505, max:520 } ],
	    [ { plane:0, min:522, max:524 } ],
	    [ { plane:0, min:536, max:559 } ],
	    [ { plane:0, min:561, max:592 } ],
	    [ { plane:0, min:595, max:598 } ],
	    [ { plane:0, min:600, max:632 } ],
	    [ { plane:0, min:634, max:652 } ],
	    [ { plane:0, min:654, max:654 } ],
	    [ { plane:0, min:656, max:671 } ],

	ductor: wc.tn(ductor_vyground),
	rule: "wirebounds",
	args: [
	    [ { plane:2, min:2336, max:2399 } ],
	    [ { plane:2, min:2401, max:2414 } ],
	    [ { plane:2, min:2416, max:2463 } ],
    {               // catch all if the above do not match.
	ductor: wc.tn(ductor_nominal),
	rule: "bool",
	args: true,


local multi_ductor = {
    type: "MultiDuctor",
    data : {
	anode: anode.nominal,
	continuous: false,
	chains : [

The MultiDuctor "chain" works under a "first match gets the depo" policy. That is, each element of the chain is interpreted based on the named rule (~"wirebounds"~ here) and if the depo falls in any of the listed wire bounds then the associated IDuctor named with the ductor attribute is used to form waveforms for that depo. That is, MultiDuctor doesn't do any "real" work, but acts as a switchyard to "sub ductors". It marshals each depo to the appropriate "real" ductor and then sums all their resulting frames together.

One caution: the field response functions have some extent in channel (typically +/- 10 wires) and this extent is not considered when a particular "universe" is selected by MultiDuctor. As a consequence near the edge of a rule's selection one universe will "bleed" into another. For example, a depo just inside a shorted wire region will place waveform following the shorted field response functions onto channels whose wires should have nominal field response. This edge case is simply and error of approximation. At some point it may be demonstrated that this error is unacceptable.

Multiple Anodes

The MultiDuctor above is meant to interweave multiple different "universes" each containing an instance of the "same" anode plane. The protoDUNE-SP, DUNE and other detectors have both two faced anode planes and multiple anode planes (APAs in DUNE-speak). To support this the AnodePlane and base Ductor required additional changes.

The IAnodeFace now has the method:

/// Return a bounding box containing the volume to which this
/// face is sensitive.
virtual BoundingBox sensitive() const = 0;

The AnodePlane will populate this BoundingBox if the user specifies a point on the cathode. The result will include also bound the face's wires. If no cathode point is given then the resulting bounding box will be empty (BoundingBox::empty() returns true).

The Ductor then uses this information. If the bounding box of the face's sensitive volume is given then only depositions inside that volume will be used to produce induced waveforms. However, and explained next, all depos will be considered in order for the Ductor to know the "now" time of the simulation.

This change is made in order to support synchronization between multiple parallel ductors which span a detector such as in this illustrative workflow.


Given this, the change can be understood. In order for each APA's "readout" to remain synchronized with those from the other five, some mechanism is needed to provide all with the current time. Passing the same depo to every ductor is a simple solution.

Another change is the new and somewhat oddly named VagabondX drifter. This drifter is configured with any number of pairs of anode/cathode planes defined by their global X-axis intercepts. It can then take any depo in space and properly drift it to the configured anode plane. The savvy user will then configure this so that the anode plane coincides with the "field response" plane so that these two operations are properly stitched together. To the extent that a detector has co-planar cathode and anode planes, the VagabondX can handle drifting without any problem. This provides a simple single-component solution that avoids (for better or worse) the parallelism which is required downstream. Note, any depos that land in dead regions between APAs still have a chance to properly be dropped via the sensitive volume mechanism described above.