# Detector simulation
In this notebook we load a track dataset generated by `edep-sim` and we calculate the ADC counts corresponding to each pixel. The result is exported to a HDF5 file.

```python
%load_ext autoreload

%autoreload 2
```

```python
# This is need so you can import larndsim without doing python setup.py install
import os,sys,inspect
currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
parentdir = os.path.dirname(currentdir)
sys.path.insert(0,parentdir)
```

```python
from math import ceil
from time import time

import matplotlib.pyplot as plt
from matplotlib import cm, colors
import mpl_toolkits.mplot3d.art3d as art3d

import numpy as np
import cupy as cp
import eagerpy as ep
#import fire
import h5py

from numba import cuda
from numba.cuda.random import create_xoroshiro128p_states

import matplotlib as mpl
mpl.rcParams['figure.dpi'] = 100

from tqdm.notebook import tqdm
```

```python
from larndsim import consts
import importlib
importlib.reload(consts)
consts.load_detector_properties("../larndsim/detector_properties/module0.yaml",
                                "../larndsim/pixel_layouts/multi_tile_layout-2.2.16.yaml")

from larndsim import quenching, drifting, detsim, pixels_from_track, fee

from numpy.lib import recfunctions as rfn
import torch

def torch_from_structured(tracks):
    tracks_np = rfn.structured_to_unstructured(tracks, copy=True, dtype=np.float32)
    return torch.from_numpy(tracks_np).float()

def structered_from_torch(tracks_torch, dtype):
    return rfn.unstructured_to_structured(tracks_torch.cpu().numpy(), dtype=dtype)
```

    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.pixel_trim_dac using bits (0, 512)
    	<function _list_property at 0x7fc740d17dc0>((<class 'int'>, 0, 31, 64, 8))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.threshold_global using bits (512, 520)
    	<function _basic_property at 0x7fc740d17d30>((<class 'int'>, 0, 255))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.csa_gain using bits (520, 521)
    	<function _compound_property at 0x7fc740d17e50>((['csa_gain', 'csa_bypass_enable', 'bypass_caps_en'], (<class 'int'>, <class 'bool'>), 0, 1))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.csa_bypass_enable using bits (521, 522)
    	<function _compound_property at 0x7fc740d17e50>((['csa_gain', 'csa_bypass_enable', 'bypass_caps_en'], (<class 'int'>, <class 'bool'>), 0, 1))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.bypass_caps_en using bits (522, 523)
    	<function _compound_property at 0x7fc740d17e50>((['csa_gain', 'csa_bypass_enable', 'bypass_caps_en'], (<class 'int'>, <class 'bool'>), 0, 1))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.csa_enable using bits (528, 592)
    	<function _list_property at 0x7fc740d17dc0>(((<class 'int'>, <class 'bool'>), 0, 1, 64, 1))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.ibias_tdac using bits (592, 596)
    	<function _basic_property at 0x7fc740d17d30>((<class 'int'>, 0, 15))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.ibias_comp using bits (600, 604)
    	<function _basic_property at 0x7fc740d17d30>((<class 'int'>, 0, 15))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.ibias_buffer using bits (608, 612)
    	<function _basic_property at 0x7fc740d17d30>((<class 'int'>, 0, 15))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.ibias_csa using bits (616, 620)
    	<function _basic_property at 0x7fc740d17d30>((<class 'int'>, 0, 15))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.ibias_vref_buffer using bits (624, 628)
    	<function _basic_property at 0x7fc740d17d30>((<class 'int'>, 0, 15))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.ibias_vcm_buffer using bits (632, 636)
    	<function _basic_property at 0x7fc740d17d30>((<class 'int'>, 0, 15))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.ibias_tpulse using bits (640, 644)
    	<function _basic_property at 0x7fc740d17d30>((<class 'int'>, 0, 15))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.ref_current_trim using bits (648, 653)
    	<function _compound_property at 0x7fc740d17e50>((['ref_current_trim', 'override_ref', 'ref_kickstart'], <class 'int'>, 0, 31))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.override_ref using bits (653, 654)
    	<function _compound_property at 0x7fc740d17e50>((['ref_current_trim', 'override_ref', 'ref_kickstart'], (<class 'int'>, <class 'bool'>), 0, 1))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.ref_kickstart using bits (654, 655)
    	<function _compound_property at 0x7fc740d17e50>((['ref_current_trim', 'override_ref', 'ref_kickstart'], (<class 'int'>, <class 'bool'>), 0, 1))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.vref_dac using bits (656, 664)
    	<function _basic_property at 0x7fc740d17d30>((<class 'int'>, 0, 255))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.vcm_dac using bits (664, 672)
    	<function _basic_property at 0x7fc740d17d30>((<class 'int'>, 0, 255))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.csa_bypass_select using bits (672, 736)
    	<function _list_property at 0x7fc740d17dc0>(((<class 'int'>, <class 'bool'>), 0, 1, 64, 1))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.csa_monitor_select using bits (736, 800)
    	<function _list_property at 0x7fc740d17dc0>(((<class 'int'>, <class 'bool'>), 0, 1, 64, 1))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.csa_testpulse_enable using bits (800, 864)
    	<function _list_property at 0x7fc740d17dc0>(((<class 'int'>, <class 'bool'>), 0, 1, 64, 1))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.csa_testpulse_dac using bits (864, 872)
    	<function _basic_property at 0x7fc740d17d30>((<class 'int'>, 0, 255))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.current_monitor_bank0 using bits (872, 876)
    	<function _list_property at 0x7fc740d17dc0>(((<class 'int'>, <class 'bool'>), 0, 1, 4, 1))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.current_monitor_bank1 using bits (880, 884)
    	<function _list_property at 0x7fc740d17dc0>(((<class 'int'>, <class 'bool'>), 0, 1, 4, 1))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.current_monitor_bank2 using bits (888, 892)
    	<function _list_property at 0x7fc740d17dc0>(((<class 'int'>, <class 'bool'>), 0, 1, 4, 1))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.current_monitor_bank3 using bits (896, 900)
    	<function _list_property at 0x7fc740d17dc0>(((<class 'int'>, <class 'bool'>), 0, 1, 4, 1))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.voltage_monitor_bank0 using bits (904, 907)
    	<function _list_property at 0x7fc740d17dc0>(((<class 'int'>, <class 'bool'>), 0, 1, 3, 1))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.voltage_monitor_bank1 using bits (912, 915)
    	<function _list_property at 0x7fc740d17dc0>(((<class 'int'>, <class 'bool'>), 0, 1, 3, 1))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.voltage_monitor_bank2 using bits (920, 923)
    	<function _list_property at 0x7fc740d17dc0>(((<class 'int'>, <class 'bool'>), 0, 1, 3, 1))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.voltage_monitor_bank3 using bits (928, 931)
    	<function _list_property at 0x7fc740d17dc0>(((<class 'int'>, <class 'bool'>), 0, 1, 3, 1))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.voltage_monitor_refgen using bits (936, 944)
    	<function _list_property at 0x7fc740d17dc0>(((<class 'int'>, <class 'bool'>), 0, 1, 8, 1))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.digital_monitor_enable using bits (944, 945)
    	<function _compound_property at 0x7fc740d17e50>((['digital_monitor_enable', 'digital_monitor_select'], (<class 'int'>, <class 'bool'>), 0, 1))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.digital_monitor_select using bits (945, 949)
    	<function _compound_property at 0x7fc740d17e50>((['digital_monitor_enable', 'digital_monitor_select'], (<class 'int'>, <class 'bool'>), 0, 10))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.digital_monitor_chan using bits (952, 958)
    	<function _basic_property at 0x7fc740d17d30>((<class 'int'>, 0, 63))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.adc_hold_delay using bits (960, 976)
    	<function _basic_property at 0x7fc740d17d30>((<class 'int'>, 0, 65535))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.chip_id using bits (976, 984)
    	<function _basic_property at 0x7fc740d17d30>((<class 'int'>, 0, 255))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.enable_tx_dynamic_powerdown using bits (984, 985)
    	<function _compound_property at 0x7fc740d17e50>((['enable_tx_dynamic_powerdown', 'load_config_defaults', 'enable_fifo_diagnostics', 'clk_ctrl', 'tx_dynamic_powerdown_cycles'], (<class 'int'>, <class 'bool'>), 0, 1))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.load_config_defaults using bits (985, 986)
    	<function _compound_property at 0x7fc740d17e50>((['enable_tx_dynamic_powerdown', 'load_config_defaults', 'enable_fifo_diagnostics', 'clk_ctrl', 'tx_dynamic_powerdown_cycles'], (<class 'int'>, <class 'bool'>), 0, 1))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.enable_fifo_diagnostics using bits (986, 987)
    	<function _compound_property at 0x7fc740d17e50>((['enable_tx_dynamic_powerdown', 'load_config_defaults', 'enable_fifo_diagnostics', 'clk_ctrl', 'tx_dynamic_powerdown_cycles'], (<class 'int'>, <class 'bool'>), 0, 1))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.clk_ctrl using bits (987, 989)
    	<function _compound_property at 0x7fc740d17e50>((['enable_tx_dynamic_powerdown', 'load_config_defaults', 'enable_fifo_diagnostics', 'clk_ctrl', 'tx_dynamic_powerdown_cycles'], (<class 'int'>, <class 'bool'>), 0, 3))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.tx_dynamic_powerdown_cycles using bits (989, 992)
    	<function _compound_property at 0x7fc740d17e50>((['enable_tx_dynamic_powerdown', 'load_config_defaults', 'enable_fifo_diagnostics', 'clk_ctrl', 'tx_dynamic_powerdown_cycles'], (<class 'int'>, <class 'bool'>), 0, 7))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.enable_piso_upstream using bits (992, 996)
    	<function _list_property at 0x7fc740d17dc0>(((<class 'int'>, <class 'bool'>), 0, 1, 4, 1))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.enable_piso_downstream using bits (1000, 1004)
    	<function _list_property at 0x7fc740d17dc0>(((<class 'int'>, <class 'bool'>), 0, 1, 4, 1))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.enable_posi using bits (1008, 1012)
    	<function _list_property at 0x7fc740d17dc0>(((<class 'int'>, <class 'bool'>), 0, 1, 4, 1))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.test_mode_uart0 using bits (1016, 1018)
    	<function _compound_property at 0x7fc740d17e50>((['test_mode_uart0', 'test_mode_uart1', 'test_mode_uart2', 'test_mode_uart3'], <class 'int'>, 0, 3))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.test_mode_uart1 using bits (1018, 1020)
    	<function _compound_property at 0x7fc740d17e50>((['test_mode_uart0', 'test_mode_uart1', 'test_mode_uart2', 'test_mode_uart3'], <class 'int'>, 0, 3))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.test_mode_uart2 using bits (1020, 1022)
    	<function _compound_property at 0x7fc740d17e50>((['test_mode_uart0', 'test_mode_uart1', 'test_mode_uart2', 'test_mode_uart3'], <class 'int'>, 0, 3))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.test_mode_uart3 using bits (1022, 1024)
    	<function _compound_property at 0x7fc740d17e50>((['test_mode_uart0', 'test_mode_uart1', 'test_mode_uart2', 'test_mode_uart3'], <class 'int'>, 0, 3))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.enable_cross_trigger using bits (1024, 1025)
    	<function _compound_property at 0x7fc740d17e50>((['enable_cross_trigger', 'enable_periodic_reset', 'enable_rolling_periodic_reset', 'enable_periodic_trigger', 'enable_rolling_periodic_trigger', 'enable_periodic_trigger_veto', 'enable_hit_veto'], <class 'int'>, 0, 1))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.enable_periodic_reset using bits (1025, 1026)
    	<function _compound_property at 0x7fc740d17e50>((['enable_cross_trigger', 'enable_periodic_reset', 'enable_rolling_periodic_reset', 'enable_periodic_trigger', 'enable_rolling_periodic_trigger', 'enable_periodic_trigger_veto', 'enable_hit_veto'], <class 'int'>, 0, 1))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.enable_rolling_periodic_reset using bits (1026, 1027)
    	<function _compound_property at 0x7fc740d17e50>((['enable_cross_trigger', 'enable_periodic_reset', 'enable_rolling_periodic_reset', 'enable_periodic_trigger', 'enable_rolling_periodic_trigger', 'enable_periodic_trigger_veto', 'enable_hit_veto'], <class 'int'>, 0, 1))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.enable_periodic_trigger using bits (1027, 1028)
    	<function _compound_property at 0x7fc740d17e50>((['enable_cross_trigger', 'enable_periodic_reset', 'enable_rolling_periodic_reset', 'enable_periodic_trigger', 'enable_rolling_periodic_trigger', 'enable_periodic_trigger_veto', 'enable_hit_veto'], <class 'int'>, 0, 1))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.enable_rolling_periodic_trigger using bits (1028, 1029)
    	<function _compound_property at 0x7fc740d17e50>((['enable_cross_trigger', 'enable_periodic_reset', 'enable_rolling_periodic_reset', 'enable_periodic_trigger', 'enable_rolling_periodic_trigger', 'enable_periodic_trigger_veto', 'enable_hit_veto'], <class 'int'>, 0, 1))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.enable_periodic_trigger_veto using bits (1029, 1030)
    	<function _compound_property at 0x7fc740d17e50>((['enable_cross_trigger', 'enable_periodic_reset', 'enable_rolling_periodic_reset', 'enable_periodic_trigger', 'enable_rolling_periodic_trigger', 'enable_periodic_trigger_veto', 'enable_hit_veto'], <class 'int'>, 0, 1))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.enable_hit_veto using bits (1030, 1031)
    	<function _compound_property at 0x7fc740d17e50>((['enable_cross_trigger', 'enable_periodic_reset', 'enable_rolling_periodic_reset', 'enable_periodic_trigger', 'enable_rolling_periodic_trigger', 'enable_periodic_trigger_veto', 'enable_hit_veto'], <class 'int'>, 0, 1))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.shadow_reset_length using bits (1032, 1040)
    	<function _basic_property at 0x7fc740d17d30>((<class 'int'>, 0, 255))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.adc_burst_length using bits (1040, 1048)
    	<function _basic_property at 0x7fc740d17d30>((<class 'int'>, 0, 255))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.channel_mask using bits (1048, 1112)
    	<function _list_property at 0x7fc740d17dc0>((<class 'int'>, 0, 1, 64, 1))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.external_trigger_mask using bits (1112, 1176)
    	<function _list_property at 0x7fc740d17dc0>((<class 'int'>, 0, 1, 64, 1))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.cross_trigger_mask using bits (1176, 1240)
    	<function _list_property at 0x7fc740d17dc0>((<class 'int'>, 0, 1, 64, 1))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.periodic_trigger_mask using bits (1240, 1304)
    	<function _list_property at 0x7fc740d17dc0>((<class 'int'>, 0, 1, 64, 1))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.periodic_reset_cycles using bits (1304, 1328)
    	<function _basic_property at 0x7fc740d17d30>((<class 'int'>, 0, 16777215))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.periodic_trigger_cycles using bits (1328, 1360)
    	<function _basic_property at 0x7fc740d17d30>((<class 'int'>, 0, 4294967295))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.enable_dynamic_reset using bits (1360, 1361)
    	<function _compound_property at 0x7fc740d17e50>((['enable_dynamic_reset', 'enable_min_delta_adc', 'threshold_polarity', 'reset_length', 'mark_first_packet'], (<class 'int'>, <class 'bool'>), 0, 1))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.enable_min_delta_adc using bits (1361, 1362)
    	<function _compound_property at 0x7fc740d17e50>((['enable_dynamic_reset', 'enable_min_delta_adc', 'threshold_polarity', 'reset_length', 'mark_first_packet'], (<class 'int'>, <class 'bool'>), 0, 1))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.threshold_polarity using bits (1362, 1363)
    	<function _compound_property at 0x7fc740d17e50>((['enable_dynamic_reset', 'enable_min_delta_adc', 'threshold_polarity', 'reset_length', 'mark_first_packet'], (<class 'int'>, <class 'bool'>), 0, 1))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.reset_length using bits (1363, 1366)
    	<function _compound_property at 0x7fc740d17e50>((['enable_dynamic_reset', 'enable_min_delta_adc', 'threshold_polarity', 'reset_length', 'mark_first_packet'], <class 'int'>, 0, 7))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.mark_first_packet using bits (1366, 1367)
    	<function _compound_property at 0x7fc740d17e50>((['enable_dynamic_reset', 'enable_min_delta_adc', 'threshold_polarity', 'reset_length', 'mark_first_packet'], (<class 'int'>, <class 'bool'>), 0, 1))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.reset_threshold using bits (1368, 1376)
    	<function _basic_property at 0x7fc740d17d30>((<class 'int'>, 0, 255))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.min_delta_adc using bits (1376, 1384)
    	<function _basic_property at 0x7fc740d17d30>((<class 'int'>, 0, 255))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.digital_threshold using bits (1384, 1896)
    	<function _list_property at 0x7fc740d17dc0>((<class 'int'>, 0, 255, 64, 8))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.RESERVED using bits (1896, 1912)
    	<function _basic_property at 0x7fc740d17d30>((<class 'int'>, 0, 0))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.tx_slices0 using bits (1912, 1916)
    	<function _compound_property at 0x7fc740d17e50>((['tx_slices0', 'tx_slices1'], <class 'int'>, 0, 15))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.tx_slices1 using bits (1916, 1920)
    	<function _compound_property at 0x7fc740d17e50>((['tx_slices0', 'tx_slices1'], <class 'int'>, 0, 15))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.tx_slices2 using bits (1920, 1924)
    	<function _compound_property at 0x7fc740d17e50>((['tx_slices2', 'tx_slices3'], <class 'int'>, 0, 15))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.tx_slices3 using bits (1924, 1928)
    	<function _compound_property at 0x7fc740d17e50>((['tx_slices2', 'tx_slices3'], <class 'int'>, 0, 15))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.i_tx_diff0 using bits (1928, 1932)
    	<function _compound_property at 0x7fc740d17e50>((['i_tx_diff0', 'i_tx_diff1'], <class 'int'>, 0, 15))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.i_tx_diff1 using bits (1932, 1936)
    	<function _compound_property at 0x7fc740d17e50>((['i_tx_diff0', 'i_tx_diff1'], <class 'int'>, 0, 15))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.i_tx_diff2 using bits (1936, 1940)
    	<function _compound_property at 0x7fc740d17e50>((['i_tx_diff2', 'i_tx_diff3'], <class 'int'>, 0, 15))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.i_tx_diff3 using bits (1940, 1944)
    	<function _compound_property at 0x7fc740d17e50>((['i_tx_diff2', 'i_tx_diff3'], <class 'int'>, 0, 15))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.i_rx0 using bits (1944, 1948)
    	<function _compound_property at 0x7fc740d17e50>((['i_rx0', 'i_rx1'], <class 'int'>, 0, 15))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.i_rx1 using bits (1948, 1952)
    	<function _compound_property at 0x7fc740d17e50>((['i_rx0', 'i_rx1'], <class 'int'>, 0, 15))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.i_rx2 using bits (1952, 1956)
    	<function _compound_property at 0x7fc740d17e50>((['i_rx2', 'i_rx3'], <class 'int'>, 0, 15))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.i_rx3 using bits (1956, 1960)
    	<function _compound_property at 0x7fc740d17e50>((['i_rx2', 'i_rx3'], <class 'int'>, 0, 15))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.i_rx_clk using bits (1960, 1964)
    	<function _compound_property at 0x7fc740d17e50>((['i_rx_clk', 'i_rx_rst'], <class 'int'>, 0, 15))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.i_rx_rst using bits (1964, 1968)
    	<function _compound_property at 0x7fc740d17e50>((['i_rx_clk', 'i_rx_rst'], <class 'int'>, 0, 15))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.i_rx_ext_trig using bits (1968, 1972)
    	<function _basic_property at 0x7fc740d17d30>((<class 'int'>, 0, 15))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.r_term0 using bits (1976, 1981)
    	<function _basic_property at 0x7fc740d17d30>((<class 'int'>, 0, 31))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.r_term1 using bits (1984, 1989)
    	<function _basic_property at 0x7fc740d17d30>((<class 'int'>, 0, 31))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.r_term2 using bits (1992, 1997)
    	<function _basic_property at 0x7fc740d17d30>((<class 'int'>, 0, 31))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.r_term3 using bits (2000, 2005)
    	<function _basic_property at 0x7fc740d17d30>((<class 'int'>, 0, 31))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.r_term_clk using bits (2008, 2013)
    	<function _basic_property at 0x7fc740d17d30>((<class 'int'>, 0, 31))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.r_term_reset using bits (2016, 2021)
    	<function _basic_property at 0x7fc740d17d30>((<class 'int'>, 0, 31))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.r_term_ext_trig using bits (2024, 2029)
    	<function _basic_property at 0x7fc740d17d30>((<class 'int'>, 0, 31))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.v_cm_lvds_tx0 using bits (2032, 2035)
    	<function _compound_property at 0x7fc740d17e50>((['v_cm_lvds_tx0', 'v_cm_lvds_tx0'], <class 'int'>, 0, 7))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.v_cm_lvds_tx1 using bits (2036, 2039)
    	<function _compound_property at 0x7fc740d17e50>((['v_cm_lvds_tx0', 'v_cm_lvds_tx0'], <class 'int'>, 0, 7))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.v_cm_lvds_tx2 using bits (2040, 2043)
    	<function _compound_property at 0x7fc740d17e50>((['v_cm_lvds_tx2', 'v_cm_lvds_tx3'], <class 'int'>, 0, 7))
    Generate <class 'larpix.configuration.configuration_v2b.Configuration_v2b'>.v_cm_lvds_tx3 using bits (2044, 2047)
    	<function _compound_property at 0x7fc740d17e50>((['v_cm_lvds_tx2', 'v_cm_lvds_tx3'], <class 'int'>, 0, 7))

```python
from larndsim.sim_with_grad import sim_with_grad
sim = sim_with_grad()
sim.load_detector_properties("../larndsim/detector_properties/module0.yaml",
                             "../larndsim/pixel_layouts/multi_tile_layout-2.2.16.yaml")
```

    /sdf/home/s/sgaz/conda/envs/ml/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

### Dataset import
First of all we load the `edep-sim` output. For this sample we need to invert $z$ and $y$ axes.

```python
unique_pix = cp.empty((0,2))
pixels_signals = cp.zeros((len(unique_pix), len(consts.time_ticks)*3))
```

```python
with h5py.File('module0_corsika.h5', 'r') as f:
    tracks = np.array(f['segments'])

x_start = np.copy(tracks['x_start'] )
x_end = np.copy(tracks['x_end'])
x = np.copy(tracks['x'])

tracks['x_start'] = np.copy(tracks['z_start'])
tracks['x_end'] = np.copy(tracks['z_end'])
tracks['x'] = np.copy(tracks['z'])

tracks['z_start'] = x_start
tracks['z_end'] = x_end
tracks['z'] = x

selected_tracks = tracks[30:40]
```

### Quenching and drifting
We calculate the number of electrons after recombination (`quenching` module) and the position and number of electrons after drifting (`drifting` module).

```python
TPB = 256
BPG = ceil(selected_tracks.shape[0] / TPB)
import importlib
importlib.reload(drifting)

selected_tracks_torch = torch_from_structured(np.copy(selected_tracks))

quenching.quench[BPG,TPB](selected_tracks, consts.birks)
drifting.drift[BPG,TPB](selected_tracks)
```

```python
selected_tracks_quench = sim.quench(selected_tracks_torch, consts.birks, fields=selected_tracks.dtype.names)

#Truncation makes gradients vanish - do here for comparisons
n_elec_idx = selected_tracks.dtype.names.index('n_electrons')
selected_tracks_quench[:, n_elec_idx] = selected_tracks_quench[:, n_elec_idx].trunc()

selected_tracks_drift = sim.drift(selected_tracks_quench, fields=selected_tracks.dtype.names)
selected_tracks_drift[:, n_elec_idx] = selected_tracks_drift[:, n_elec_idx].trunc()
```

    /sdf/home/s/sgaz/conda/envs/ml/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

```python
(selected_tracks_drift[:, selected_tracks.dtype.names.index('n_electrons')].numpy()
  == selected_tracks['n_electrons']).all()
```

    True

We find the pixels intersected by the projection of the tracks on the anode plane using the Bresenham's algorithm. We also take into account the neighboring pixels, due to the transverse diffusion of the charges.

```python
importlib.reload(pixels_from_track)

# Here we build a map between tracks and event IDs
unique_eventIDs = cp.unique(selected_tracks['eventID'])
event_id_map = cp.searchsorted(unique_eventIDs,cp.asarray(selected_tracks['eventID']))
event_id_map_torch = torch.from_numpy(event_id_map.get())

longest_pix = ceil(max(selected_tracks["dx"])/consts.pixel_pitch)
max_radius = ceil(max(selected_tracks["tran_diff"])*5/consts.pixel_pitch)

MAX_PIXELS = int((longest_pix*4+6)*max_radius*1.5)
MAX_ACTIVE_PIXELS = int(longest_pix*1.5)

active_pixels = cp.full((selected_tracks.shape[0], MAX_ACTIVE_PIXELS, 2), -1, dtype=np.int32)
neighboring_pixels = cp.full((selected_tracks.shape[0], MAX_PIXELS, 2), -1, dtype=np.int32)
n_pixels_list = cp.zeros(shape=(selected_tracks.shape[0]))
TPB = 128
BPG = ceil(selected_tracks.shape[0] / TPB)
pixels_from_track.get_pixels[BPG,TPB](selected_tracks,
                                      active_pixels,
                                      neighboring_pixels,
                                      n_pixels_list,
                                      max_radius+1)
```

```python
active_pixels_torch, neighboring_pixels_torch, n_pixels_list_ep = sim.get_pixels(selected_tracks_drift,
                                                                                fields=selected_tracks.dtype.names)
print(neighboring_pixels_torch.shape)
print(neighboring_pixels.shape)
```

    /sdf/home/s/sgaz/conda/envs/ml/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)
    /sdf/home/s/sgaz/conda/envs/ml/lib/python3.8/site-packages/eagerpy/tensor/base.py:98: UserWarning: __floordiv__ is deprecated, and its behavior will change in a future version of pytorch. It currently rounds toward 0 (like the 'trunc' function NOT 'floor'). This results in incorrect rounding for negative values. To keep the current behavior, use torch.div(a, b, rounding_mode='trunc'), or for actual floor division, use torch.div(a, b, rounding_mode='floor').
      return type(self)(self.raw.__floordiv__(unwrap1(other)))

    torch.Size([10, 87, 2])
    (10, 87, 2)

    /sdf/home/s/sgaz/conda/envs/ml/lib/python3.8/site-packages/torch/functional.py:445: UserWarning: torch.meshgrid: in an upcoming release, it will be required to pass the indexing argument. (Triggered internally at  ../aten/src/ATen/native/TensorShape.cpp:2157.)
      return _VF.meshgrid(tensors, **kwargs)  # type: ignore[attr-defined]

## Bug?
Neighboring pixels is different for one of the track segments. Running _just_ that track segment vs running a set of track segments gives two different results in the cupy setup. The single track segment cupy result matches the eagerpy setup.

```python
np.where((neighboring_pixels_torch.numpy() != neighboring_pixels.get()))
```

    (array([3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
            3]),
     array([ 0,  1,  2,  2,  3,  3,  4,  4,  5,  6,  7,  8,  9,  9, 10, 11, 11,
            12, 12, 13, 13, 14, 14]),
     array([1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0,
            1]))

```python
def cupy_unique_axis0(array):
    # axis is still not supported for cupy.unique, this
    # is a workaround
    if len(array.shape) != 2:
        raise ValueError("Input array must be 2D.")
    sortarr     = array[cp.lexsort(array.T[::-1])]
    mask        = cp.empty(array.shape[0], dtype=cp.bool_)
    mask[0]     = True
    mask[1:]    = cp.any(sortarr[1:] != sortarr[:-1], axis=1)
    return sortarr[mask]

shapes = neighboring_pixels.shape
joined = neighboring_pixels.reshape(shapes[0]*shapes[1],2)
this_unique_pix = cupy_unique_axis0(joined)
this_unique_pix = this_unique_pix[(this_unique_pix[:,0] != -1) & (this_unique_pix[:,1] != -1),:]
```

```python
unique_pix = cp.concatenate((unique_pix, this_unique_pix),axis=0)
```

### Charge distribution calculation
Here we calculate the current induced by each track on the pixels, taking into account longitudinal and transverse diffusion. The track segment is parametrized as:
\begin{align}
x'(r') &=x_s + \frac{\Delta x}{\Delta r}r'\\
y'(r') &=y_s + \frac{\Delta y}{\Delta r}r'\\
z'(r') &=z_s + \frac{\Delta z}{\Delta r}r',
\end{align}
where $\Delta r$ is the segment length. Here we assume $z$ as the drift direction.
The diffused charge distribution is calculated with the following integral:
\begin{equation}
\rho(x,y,z) = \frac{Q}{\sqrt{(2\pi)^3}\sigma_x\sigma_y\sigma_z\Delta r}\exp\left[-\frac{(x-x_s)^2}{2\sigma_x^2}-\frac{(y-y_s)^2}{2\sigma_y^2}-\frac{(z-z_s)^2}{2\sigma_z^2}\right]\int^{r'=\Delta r}_{r'=0}dr'\exp[-(ar'^2+br')],
\end{equation}
where
\begin{align}
a &= \left[\left(\frac{\Delta x}{\Delta r}\right)^2\frac{1}{2\sigma_x^2} + \left(\frac{\Delta y}{\Delta r}\right)^2\frac{1}{2\sigma_y^2} + \left(\frac{\Delta z}{\Delta r}\right)^2\frac{1}{2\sigma_z^2} \right]\\
b &= -\left[\frac{(x-x_s)}{\sigma_x^2}\frac{\Delta x}{\Delta r}+
\frac{(y-y_s)}{\sigma_y^2}\frac{\Delta y}{\Delta r}+
\frac{(z-z_s)}{\sigma_z^2}\frac{\Delta z}{\Delta r}\right].
\end{align}

The simmetry of the transverse diffusion along the track allows to take a slice on the $xy$ plane and solve the integral once at a fixed $z$ coordinate (e.g. at $z_{m} = (z_s+z_e)/2$) and re-use it at other $z$ coordinates away from the endpoints (where $\rho(x,y,z)$ varies along $z$ so must be calculated at each $z$).

```python
importlib.reload(detsim)

max_length = cp.array([0])
track_starts = cp.empty(selected_tracks.shape[0])
threadsperblock = 128
blockspergrid = ceil(selected_tracks.shape[0] / threadsperblock)
detsim.time_intervals[blockspergrid,threadsperblock](track_starts, max_length,  event_id_map, selected_tracks)
```

```python
track_starts_torch, max_length_torch = sim.time_intervals(event_id_map_torch,
                                                          selected_tracks_drift,
                                                          fields=selected_tracks.dtype.names)

print(track_starts_torch, max_length_torch)
print(track_starts, max_length)
```

    /sdf/home/s/sgaz/conda/envs/ml/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

    tensor([158.0174, 159.4631, 165.9619, 174.5850, 177.3604, 181.8703, 177.4428,
            176.2220, 170.8327, 169.0134]) tensor(218)
    [158.  159.5 166.  174.6 177.4 181.9 177.4 176.2 170.8 169. ] [218]

```python
signals = cp.zeros((selected_tracks.shape[0],
                    neighboring_pixels.shape[1],
                    cp.asnumpy(max_length)[0]), dtype=np.float32)
threadsperblock = (1,1,64)

importlib.reload(detsim)

blockspergrid_x = ceil(signals.shape[0] / threadsperblock[0])
blockspergrid_y = ceil(signals.shape[1] / threadsperblock[1])
blockspergrid_z = ceil(signals.shape[2] / threadsperblock[2])
blockspergrid = (blockspergrid_x, blockspergrid_y, blockspergrid_z)
detsim.tracks_current[blockspergrid,threadsperblock](signals,
                                                     neighboring_pixels,
                                                     selected_tracks)
print(signals.shape)
```

    (10, 87, 218)

```python
signals_ep = sim.tracks_current(neighboring_pixels_torch, selected_tracks_drift,
                                max_length_torch,
                                fields=selected_tracks.dtype.names)
print(signals_ep.shape)
```

    /sdf/home/s/sgaz/conda/envs/ml/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)
    /sdf/home/s/sgaz/conda/envs/ml/lib/python3.8/site-packages/eagerpy/tensor/base.py:98: UserWarning: __floordiv__ is deprecated, and its behavior will change in a future version of pytorch. It currently rounds toward 0 (like the 'trunc' function NOT 'floor'). This results in incorrect rounding for negative values. To keep the current behavior, use torch.div(a, b, rounding_mode='trunc'), or for actual floor division, use torch.div(a, b, rounding_mode='floor').
      return type(self)(self.raw.__floordiv__(unwrap1(other)))

    torch.Size([10, 87, 218])

```python
pixel_index_map = cp.full((selected_tracks.shape[0], neighboring_pixels.shape[1]), -1)
try:
    compare = neighboring_pixels[..., np.newaxis, :] == unique_pix
    indices = cp.where(cp.logical_and(compare[..., 0], compare[..., 1]))
except cp.cuda.memory.OutOfMemoryError:
    print("out of memory")
pixel_index_map[indices[0], indices[1]] = indices[2]
```

```python
this_pixels_signals = cp.zeros((len(this_unique_pix), len(consts.time_ticks)*3))
pixels_signals = cp.concatenate((pixels_signals, this_pixels_signals), axis=0)
```

```python
blockspergrid_x = ceil(signals.shape[0] / threadsperblock[0])
blockspergrid_y = ceil(signals.shape[1] / threadsperblock[1])
blockspergrid_z = ceil(signals.shape[2] / threadsperblock[2])
blockspergrid = (blockspergrid_x, blockspergrid_y, blockspergrid_z)
this_pixels_signals = cp.zeros((len(this_unique_pix), len(consts.time_ticks)*3))
pixels_signals = cp.concatenate((pixels_signals, this_pixels_signals), axis=0)
detsim.sum_pixel_signals[blockspergrid,threadsperblock](pixels_signals,
                                                        signals,
                                                        track_starts,
                                                        pixel_index_map)
```

```python
currents = pixels_signals.sum(axis=1)*sim.t_sampling/sim.e_charge
```

    /sdf/home/s/sgaz/conda/envs/ml/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

```python
sum(currents)/sum(selected_tracks['n_electrons'])
```

    array(1.04705509)

```python
unique_pix_torch = torch.empty((0, 2))
pixels_signals_torch = torch.zeros((len(unique_pix_torch), len(sim.time_ticks)*3))

shapes_torch = neighboring_pixels_torch.shape
joined_torch = neighboring_pixels_torch.reshape(shapes_torch[0]*shapes_torch[1], 2)

this_unique_pix_torch = torch.unique(joined_torch, dim=0)
this_unique_pix_torch = this_unique_pix_torch[(this_unique_pix_torch[:,0] != -1) & (this_unique_pix_torch[:,1] != -1),:]
unique_pix_torch = torch.cat((unique_pix_torch, this_unique_pix_torch),dim=0)

this_pixels_signals_torch = torch.zeros((len(this_unique_pix_torch), len(sim.time_ticks)*3))
pixels_signals_torch = torch.cat((pixels_signals_torch, this_pixels_signals_torch), dim=0)

pixel_index_map_torch = torch.full((selected_tracks.shape[0], neighboring_pixels_torch.shape[1]), -1)
compare_torch = (neighboring_pixels_torch[..., np.newaxis, :] == unique_pix_torch)

indices_torch = torch.where(torch.logical_and(compare_torch[..., 0], compare_torch[...,1]))
pixel_index_map_torch[indices_torch[0], indices_torch[1]] = indices_torch[2]
```

```python
pixels_signals_torch = sim.sum_pixel_signals(pixels_signals_torch,
                                             signals_ep,
                                            track_starts_torch,
                                            pixel_index_map_torch)

currents_torch = pixels_signals_torch.sum(dim=1)*sim.t_sampling/sim.e_charge
```

```python
sum(currents_torch)/sum(selected_tracks['n_electrons'])
```

    tensor(1.0068)

```python
track = selected_tracks[0]
pixel_plane = track['pixel_plane']
z_anode = consts.tpc_borders[pixel_plane][2][0]
z = (track['z_start']+track['z_end'])/2
drift_distance = abs(z - z_anode)
drift_start = abs(min(track["z_start"],track["z_end"]) - z_anode)
drift_end = abs(max(track["z_start"],track["z_end"]) - z_anode)
drift_time = drift_distance / consts.vdrift
drift_start_time = drift_start / consts.vdrift
drift_end_time = drift_end / consts.vdrift
```

### 3D event display

```python
%matplotlib notebook

cmap = cm.Spectral_r
norm = colors.Normalize(vmin=0, vmax=256)
m = cm.ScalarMappable(norm=norm, cmap=cmap)

cmap = cm.viridis
norm_curr = colors.LogNorm(vmin=min(currents[currents>0]), vmax=max(currents))
m_curr = cm.ScalarMappable(norm=norm_curr, cmap=cmap)
fig = plt.figure(figsize=(7,7))
ax = fig.add_subplot(111, projection='3d')

for it,t in enumerate(selected_tracks):
    if it == 0:
        ax.plot((t["x_start"], t["x_end"]),
            (t["z_start"], t["z_end"]),
            (t["y_start"], t["y_end"]),
            c='r',
            lw=1,
            alpha=1,
            zorder=10,
            label='Geant4 detector segment')
    else:
        ax.plot((t["x_start"], t["x_end"]),
                (t["z_start"], t["z_end"]),
                (t["y_start"], t["y_end"]),
                c='r',
                lw=1,
                alpha=1,
                zorder=9999)

    ax.plot((t["x_start"], t["x_end"]),
            (consts.tpc_borders[t['pixel_plane']][2][0], consts.tpc_borders[t['pixel_plane']][2][0]),
            (t["y_start"], t["y_end"]),
            c='r',
            lw=1,
            ls=':',
            alpha=1,
            zorder=9999)

for ip, p in enumerate(unique_pix):
    x_rect, y_rect = detsim.get_pixel_coordinates(p.get())
    pixel_plane = int(p[0] // consts.n_pixels[0])
    row = int(pixel_plane // 4)
    column = int(pixel_plane % 4)
    if currents[ip] > 0:
        rect = plt.Rectangle((x_rect, y_rect),
                             consts.pixel_pitch, consts.pixel_pitch,
                             linewidth=0.1, fc=m_curr.to_rgba(currents[ip].get()),
                             edgecolor='gray')
        ax.add_patch(rect)
        art3d.pathpatch_2d_to_3d(rect, z=consts.tpc_borders[pixel_plane][2][0], zdir="y")

anode1 = plt.Rectangle((consts.tpc_borders[0][0][0], consts.tpc_borders[0][1][0]),
                        consts.tpc_borders[0][0][1]-consts.tpc_borders[0][0][0],
                        consts.tpc_borders[0][1][1]-consts.tpc_borders[0][1][0],
                       linewidth=1, fc='none',
                       edgecolor='gray', label=('Pixel' if ip == 5 else ''))
ax.add_patch(anode1)
art3d.pathpatch_2d_to_3d(anode1, z=consts.tpc_borders[0][2][0], zdir="y")

anode2 = plt.Rectangle((consts.tpc_borders[0][0][0], consts.tpc_borders[0][1][0]),
                        consts.tpc_borders[0][0][1]-consts.tpc_borders[0][0][0],
                        consts.tpc_borders[0][1][1]-consts.tpc_borders[0][1][0],
                       linewidth=1, fc='none',
                       edgecolor='gray', label=('Pixel' if ip == 5 else ''))
ax.add_patch(anode2)
art3d.pathpatch_2d_to_3d(anode2, z=consts.tpc_borders[1][2][0], zdir="y")

cathode = plt.Rectangle((consts.tpc_borders[0][0][0], consts.tpc_borders[0][1][0]),
                        consts.tpc_borders[0][0][1]-consts.tpc_borders[0][0][0],
                        consts.tpc_borders[0][1][1]-consts.tpc_borders[0][1][0],
                        linewidth=1, fc='gray', alpha=0.5,
                        edgecolor='gray', label=('Pixel' if ip == 5 else ''))
ax.add_patch(cathode)
art3d.pathpatch_2d_to_3d(cathode, z=0, zdir="y")

ax.plot((consts.tpc_borders[0][0][0],consts.tpc_borders[0][0][0]),(consts.tpc_borders[0][2][0],consts.tpc_borders[1][2][0]),
        (consts.tpc_borders[0][1][0],consts.tpc_borders[0][1][0]), lw=1,color='gray')

ax.plot((consts.tpc_borders[0][0][0],consts.tpc_borders[0][0][0]),(consts.tpc_borders[0][2][0],consts.tpc_borders[1][2][0]),
        (consts.tpc_borders[0][1][1],consts.tpc_borders[0][1][1]), lw=1,color='gray')

ax.plot((consts.tpc_borders[0][0][1],consts.tpc_borders[0][0][1]),(consts.tpc_borders[0][2][0],consts.tpc_borders[1][2][0]),
        (consts.tpc_borders[0][1][0],consts.tpc_borders[0][1][0]), lw=1,color='gray')

ax.plot((consts.tpc_borders[0][0][1],consts.tpc_borders[0][0][1]),(consts.tpc_borders[0][2][0],consts.tpc_borders[1][2][0]),
        (consts.tpc_borders[0][1][1],consts.tpc_borders[0][1][1]), lw=1,color='gray')

# ax.set_ylim(consts.module_borders[pixel_plane][2][0],50)
ax.set_xlim(consts.tpc_borders[0][0][0],consts.tpc_borders[1][0][1])
ax.set_ylim(consts.tpc_borders[0][2][0],consts.tpc_borders[1][2][0])
ax.set_zlim(consts.tpc_borders[0][1][0],consts.tpc_borders[1][1][1])

ax.set_box_aspect((2,2,4))
ax.grid(False)
ax.xaxis.set_major_locator(plt.MaxNLocator(3))
ax.yaxis.set_major_locator(plt.MaxNLocator(3))
# ax.set_axis_off()
ax.w_xaxis.set_pane_color((1.0, 1.0, 1.0, 1.0))
ax.w_yaxis.set_pane_color((1.0, 1.0, 1.0, 1.0))
ax.w_zaxis.set_pane_color((1.0, 1.0, 1.0, 1.0))

ax.view_init(azim=20)
# def rotate(angle):
#     ax.view_init(azim=angle)

# from matplotlib import animation

# rot_animation = animation.FuncAnimation(fig, rotate, frames=np.arange(0,362,2),interval=50)

ax.set_ylabel("z [cm]")
ax.set_xlabel("x [cm]")
ax.set_zlabel("y [cm]")
_ = plt.colorbar(m_curr,fraction=0.035, pad=0.05,label='Induced current integral [# electrons]')

```

    <IPython.core.display.Javascript object>

```python
%matplotlib notebook

cmap = cm.Spectral_r
norm = colors.Normalize(vmin=0, vmax=256)
m = cm.ScalarMappable(norm=norm, cmap=cmap)

cmap = cm.viridis
norm_curr = colors.LogNorm(vmin=min(currents_torch[currents_torch>0]), vmax=max(currents_torch))
m_curr = cm.ScalarMappable(norm=norm_curr, cmap=cmap)
fig = plt.figure(figsize=(7,7))
ax = fig.add_subplot(111, projection='3d')

for it,t in enumerate(selected_tracks):
    if it == 0:
        ax.plot((t["x_start"], t["x_end"]),
            (t["z_start"], t["z_end"]),
            (t["y_start"], t["y_end"]),
            c='r',
            lw=1,
            alpha=1,
            zorder=10,
            label='Geant4 detector segment')
    else:
        ax.plot((t["x_start"], t["x_end"]),
                (t["z_start"], t["z_end"]),
                (t["y_start"], t["y_end"]),
                c='r',
                lw=1,
                alpha=1,
                zorder=9999)

    ax.plot((t["x_start"], t["x_end"]),
            (sim.tpc_borders[t['pixel_plane']][2][0], sim.tpc_borders[t['pixel_plane']][2][0]),
            (t["y_start"], t["y_end"]),
            c='r',
            lw=1,
            ls=':',
            alpha=1,
            zorder=9999)

x_r_all, y_r_all = sim.get_pixel_coordinates(ep.astensor(unique_pix_torch))
for ip, p in enumerate(unique_pix_torch):
    x_rect = x_r_all[ip, 0].numpy()
    y_rect = y_r_all[ip, 0].numpy()
    pixel_plane = int(p[0] // sim.n_pixels[0])
    row = int(pixel_plane // 4)
    column = int(pixel_plane % 4)
    if currents_torch[ip] > 0:
        rect = plt.Rectangle((x_rect, y_rect),
                             sim.pixel_pitch, sim.pixel_pitch,
                             linewidth=0.1, fc=m_curr.to_rgba(currents_torch[ip]),
                             edgecolor='gray')
        ax.add_patch(rect)
        art3d.pathpatch_2d_to_3d(rect, z=sim.tpc_borders[pixel_plane][2][0], zdir="y")

anode1 = plt.Rectangle((sim.tpc_borders[0][0][0], sim.tpc_borders[0][1][0]),
                        sim.tpc_borders[0][0][1]-sim.tpc_borders[0][0][0],
                        sim.tpc_borders[0][1][1]-sim.tpc_borders[0][1][0],
                       linewidth=1, fc='none',
                       edgecolor='gray', label=('Pixel' if ip == 5 else ''))
ax.add_patch(anode1)
art3d.pathpatch_2d_to_3d(anode1, z=sim.tpc_borders[0][2][0], zdir="y")

anode2 = plt.Rectangle((sim.tpc_borders[0][0][0], sim.tpc_borders[0][1][0]),
                        sim.tpc_borders[0][0][1]-sim.tpc_borders[0][0][0],
                        sim.tpc_borders[0][1][1]-sim.tpc_borders[0][1][0],
                       linewidth=1, fc='none',
                       edgecolor='gray', label=('Pixel' if ip == 5 else ''))
ax.add_patch(anode2)
art3d.pathpatch_2d_to_3d(anode2, z=sim.tpc_borders[1][2][0], zdir="y")

cathode = plt.Rectangle((sim.tpc_borders[0][0][0], sim.tpc_borders[0][1][0]),
                        sim.tpc_borders[0][0][1]-sim.tpc_borders[0][0][0],
                        sim.tpc_borders[0][1][1]-sim.tpc_borders[0][1][0],
                        linewidth=1, fc='gray', alpha=0.5,
                        edgecolor='gray', label=('Pixel' if ip == 5 else ''))
ax.add_patch(cathode)
art3d.pathpatch_2d_to_3d(cathode, z=0, zdir="y")

ax.plot((sim.tpc_borders[0][0][0],sim.tpc_borders[0][0][0]),(sim.tpc_borders[0][2][0],sim.tpc_borders[1][2][0]),
        (sim.tpc_borders[0][1][0],sim.tpc_borders[0][1][0]), lw=1,color='gray')

ax.plot((sim.tpc_borders[0][0][0],sim.tpc_borders[0][0][0]),(sim.tpc_borders[0][2][0],sim.tpc_borders[1][2][0]),
        (sim.tpc_borders[0][1][1],sim.tpc_borders[0][1][1]), lw=1,color='gray')

ax.plot((sim.tpc_borders[0][0][1],sim.tpc_borders[0][0][1]),(sim.tpc_borders[0][2][0],sim.tpc_borders[1][2][0]),
        (sim.tpc_borders[0][1][0],sim.tpc_borders[0][1][0]), lw=1,color='gray')

ax.plot((sim.tpc_borders[0][0][1],sim.tpc_borders[0][0][1]),(sim.tpc_borders[0][2][0],sim.tpc_borders[1][2][0]),
        (sim.tpc_borders[0][1][1],sim.tpc_borders[0][1][1]), lw=1,color='gray')

# ax.set_ylim(sim.module_borders[pixel_plane][2][0],50)
ax.set_xlim(sim.tpc_borders[0][0][0],sim.tpc_borders[1][0][1])
ax.set_ylim(sim.tpc_borders[0][2][0],sim.tpc_borders[1][2][0])
ax.set_zlim(sim.tpc_borders[0][1][0],sim.tpc_borders[1][1][1])

ax.set_box_aspect((2,2,4))
ax.grid(False)
ax.xaxis.set_major_locator(plt.MaxNLocator(3))
ax.yaxis.set_major_locator(plt.MaxNLocator(3))
# ax.set_axis_off()
ax.w_xaxis.set_pane_color((1.0, 1.0, 1.0, 1.0))
ax.w_yaxis.set_pane_color((1.0, 1.0, 1.0, 1.0))
ax.w_zaxis.set_pane_color((1.0, 1.0, 1.0, 1.0))

ax.view_init(azim=20)
# def rotate(angle):
#     ax.view_init(azim=angle)

# from matplotlib import animation

# rot_animation = animation.FuncAnimation(fig, rotate, frames=np.arange(0,362,2),interval=50)

ax.set_ylabel("z [cm]")
ax.set_xlabel("x [cm]")
ax.set_zlabel("y [cm]")
_ = plt.colorbar(m_curr,fraction=0.035, pad=0.05,label='Induced current integral [# electrons]')

```

    <IPython.core.display.Javascript object>

    <ipython-input-31-5c4cc6c1954c>:45: UserWarning: __floordiv__ is deprecated, and its behavior will change in a future version of pytorch. It currently rounds toward 0 (like the 'trunc' function NOT 'floor'). This results in incorrect rounding for negative values. To keep the current behavior, use torch.div(a, b, rounding_mode='trunc'), or for actual floor division, use torch.div(a, b, rounding_mode='floor').
      pixel_plane = int(p[0] // sim.n_pixels[0])

```python
%matplotlib inline
plt.plot(np.arange(len(currents.get().flatten())), currents.get().flatten())
plt.plot(np.arange(len(currents_torch.flatten())), currents_torch.flatten())
plt.show()
```

Mostly quite close, cupy (blue) has an extra spike

```python
# rot_animation.save('animation.gif', writer='imagemagick', fps=10, bitrate=10, dpi=100)
```

    /sdf/home/s/sgaz/conda/envs/ml/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

### Electronics response and digitization
Here we simulate the electronics response (the self-triggering cycle) and the signal digitization.

```python
time_ticks = cp.linspace(0, len(unique_eventIDs)*consts.time_interval[1]*3, pixels_signals.shape[1]+1)
integral_list = cp.zeros((pixels_signals.shape[0], fee.MAX_ADC_VALUES))
adc_ticks_list = cp.zeros((pixels_signals.shape[0], fee.MAX_ADC_VALUES))
TPB = 128
BPG = ceil(pixels_signals.shape[0] / TPB)
rng_states = create_xoroshiro128p_states(TPB * BPG, seed=0)
# integrate = cp.zeros((pixels_signals.shape[0], time_ticks.shape[0]))
fee.get_adc_values[BPG,TPB](pixels_signals,
                            time_ticks,
                            integral_list,
                            adc_ticks_list,
                            0,
                            rng_states)#,
adc_list = fee.digitize(integral_list)
```

```python
time_ticks_torch = torch.linspace(0, len(unique_eventIDs)*sim.time_interval[1]*3, pixels_signals_torch.shape[1]+1)

integral_list_torch, adc_ticks_list_torch = sim.get_adc_values(pixels_signals_torch,
                                                               time_ticks_torch,
                                                                0)
adc_list_torch = sim.digitize(integral_list_torch)
```

    /sdf/home/s/sgaz/conda/envs/ml/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

### 2D event display with induced current and ADC counts

```python
cmap = cm.Spectral_r
norm = colors.Normalize(vmin=0, vmax=256)
m = cm.ScalarMappable(norm=norm, cmap=cmap)

cmap = cm.viridis
norm_curr = colors.LogNorm(vmin=min(currents[currents>0]), vmax=max(currents))
m_curr = cm.ScalarMappable(norm=norm_curr, cmap=cmap)

fig,ax = plt.subplots(1,2,figsize=(6,3.8))

importlib.reload(detsim)
for ip, p in enumerate(unique_pix):
    x_rect, y_rect = detsim.get_pixel_coordinates(p.get())
    pixel_plane = int(p[0] // consts.n_pixels[0])
    c = currents[ip].get()
    if c >= 1:
        rect = plt.Rectangle((x_rect, y_rect),
                             consts.pixel_pitch, consts.pixel_pitch,
                             linewidth=0.2, fc=m_curr.to_rgba(c),
                             edgecolor='grey')
        ax[0].add_patch(rect)

    a = adc_list[ip][adc_list[ip]>fee.digitize(0)]
    if len(a):
        rect = plt.Rectangle((x_rect, y_rect),
                             consts.pixel_pitch, consts.pixel_pitch,
                             linewidth=0.2, fc=m.to_rgba(np.sum(a.get())),
                             edgecolor='grey')
        ax[1].add_patch(rect)

for it,t in enumerate(selected_tracks):
    ax[0].plot((t["x_start"], t["x_end"]),
            (t["y_start"], t["y_end"]),
            c='r',
            lw=1.25,
            ls=':',
            alpha=1,
            zorder=10)
    ax[1].plot((t["x_start"], t["x_end"]),
            (t["y_start"], t["y_end"]),
            c='r',
            lw=0,
            ls=':',
            alpha=1,
            zorder=10)
    ax[0].scatter((t["x_start"], t["x_end"]),
                  (t["y_start"], t["y_end"]),
                  c='r', s=1, zorder=99999)
#     ax[1].scatter((t["x_start"], t["x_end"]),
#                   (t["y_start"], t["y_end"]),
#                   c='r', s=1, zorder=99999)

ax[0].set_aspect("equal")
ax[1].set_aspect("equal")
ax[0].set_xlabel("x [cm]")
ax[1].set_xlabel("x [cm]")
ax[0].set_ylabel("y [cm]")
from mpl_toolkits.axes_grid1.axes_divider import make_axes_locatable

divider0 = make_axes_locatable(ax[1])
cax0 = divider0.append_axes("right", size="7%", pad=0.07)
fig.colorbar(m, ax=ax[1], cax=cax0, label='ADC counts sum')

divider1 = make_axes_locatable(ax[0])
cax1 = divider1.append_axes("right", size="7%", pad=0.07)
fig.colorbar(m_curr, ax=ax[0], cax=cax1, label='Induced current integral [# electrons]')

plt.subplots_adjust(hspace=0.5)
fig.savefig("currentadc.pdf")
```

```python
cmap = cm.Spectral_r
norm = colors.Normalize(vmin=0, vmax=256)
m = cm.ScalarMappable(norm=norm, cmap=cmap)

#cmap = cm.viridis
#norm_curr = colors.LogNorm(vmin=min(currents_torch[currents_torch>0]), vmax=max(currents_torch))
#m_curr = cm.ScalarMappable(norm=norm_curr, cmap=cmap)

fig,ax = plt.subplots(1,2,figsize=(6,3.8))

x_r_all, y_r_all = sim.get_pixel_coordinates(ep.astensor(unique_pix_torch))
for ip, p in enumerate(unique_pix_torch):
    x_rect = x_r_all[ip, 0].numpy()
    y_rect = y_r_all[ip, 0].numpy()
    pixel_plane = int(p[0] // sim.n_pixels[0])
    c = currents_torch[ip]
    if c >= 1:
        rect = plt.Rectangle((x_rect, y_rect),
                             sim.pixel_pitch, sim.pixel_pitch,
                             linewidth=0.2, fc=m_curr.to_rgba(c),
                             edgecolor='grey')
        ax[0].add_patch(rect)

    a = adc_list_torch[ip][adc_list_torch[ip]>sim.digitize(torch.tensor([0]))]
    if len(a):
        rect = plt.Rectangle((x_rect, y_rect),
                             sim.pixel_pitch, sim.pixel_pitch,
                             linewidth=0.2, fc=m.to_rgba(a.sum()),
                             edgecolor='grey')
        ax[1].add_patch(rect)

for it,t in enumerate(selected_tracks):
    ax[0].plot((t["x_start"], t["x_end"]),
            (t["y_start"], t["y_end"]),
            c='r',
            lw=1.25,
            ls=':',
            alpha=1,
            zorder=10)
    ax[1].plot((t["x_start"], t["x_end"]),
            (t["y_start"], t["y_end"]),
            c='r',
            lw=0,
            ls=':',
            alpha=1,
            zorder=10)
    ax[0].scatter((t["x_start"], t["x_end"]),
                  (t["y_start"], t["y_end"]),
                  c='r', s=1, zorder=99999)
#     ax[1].scatter((t["x_start"], t["x_end"]),
#                   (t["y_start"], t["y_end"]),
#                   c='r', s=1, zorder=99999)

ax[0].set_aspect("equal")
ax[1].set_aspect("equal")
ax[0].set_xlabel("x [cm]")
ax[1].set_xlabel("x [cm]")
ax[0].set_ylabel("y [cm]")
from mpl_toolkits.axes_grid1.axes_divider import make_axes_locatable

divider0 = make_axes_locatable(ax[1])
cax0 = divider0.append_axes("right", size="7%", pad=0.07)
fig.colorbar(m, ax=ax[1], cax=cax0, label='ADC counts sum')

divider1 = make_axes_locatable(ax[0])
cax1 = divider1.append_axes("right", size="7%", pad=0.07)
fig.colorbar(m_curr, ax=ax[0], cax=cax1, label='Induced current integral [# electrons]')

plt.subplots_adjust(hspace=0.5)
fig.savefig("currentadc_torch.pdf")
```

```python
cmap = cm.seismic
norm = colors.Normalize(vmin=0.5, vmax=1.5)
m = cm.ScalarMappable(norm=norm, cmap=cmap)

cmap = cm.seismic
norm_curr = colors.Normalize(vmin=0.5, vmax=1.5)
m_curr = cm.ScalarMappable(norm=norm_curr, cmap=cmap)

fig,ax = plt.subplots(1,2,figsize=(6,3.8))

x_r_all, y_r_all = sim.get_pixel_coordinates(ep.astensor(unique_pix_torch))
for ip, p in enumerate(unique_pix_torch):
    x_rect = x_r_all[ip, 0].numpy()
    y_rect = y_r_all[ip, 0].numpy()
    pixel_plane = int(p[0] // sim.n_pixels[0])
    c = currents_torch[ip].numpy()
    c_og = currents[ip].get()
    if c >= 1 or c_og >=1:
        rect = plt.Rectangle((x_rect, y_rect),
                             sim.pixel_pitch, sim.pixel_pitch,
                             linewidth=0.2, fc=m_curr.to_rgba(c/c_og),
                             edgecolor='grey')
        ax[0].add_patch(rect)

    a = adc_list_torch[ip][adc_list_torch[ip]>sim.digitize(torch.tensor([0]))].numpy()
    a_og = adc_list[ip][adc_list[ip]>fee.digitize(0)].get()
    if len(a) or len(a_og):
        rect = plt.Rectangle((x_rect, y_rect),
                             sim.pixel_pitch, sim.pixel_pitch,
                             linewidth=0.2, fc=m.to_rgba(a.sum()/a_og.sum()),
                             edgecolor='grey')
        ax[1].add_patch(rect)

for it,t in enumerate(selected_tracks):
    ax[0].plot((t["x_start"], t["x_end"]),
            (t["y_start"], t["y_end"]),
            c='r',
            lw=1.25,
            ls=':',
            alpha=1,
            zorder=10)
    ax[1].plot((t["x_start"], t["x_end"]),
            (t["y_start"], t["y_end"]),
            c='r',
            lw=0,
            ls=':',
            alpha=1,
            zorder=10)
    ax[0].scatter((t["x_start"], t["x_end"]),
                  (t["y_start"], t["y_end"]),
                  c='r', s=1, zorder=99999)
#     ax[1].scatter((t["x_start"], t["x_end"]),
#                   (t["y_start"], t["y_end"]),
#                   c='r', s=1, zorder=99999)

ax[0].set_aspect("equal")
ax[1].set_aspect("equal")
ax[0].set_xlabel("x [cm]")
ax[1].set_xlabel("x [cm]")
ax[0].set_ylabel("y [cm]")
from mpl_toolkits.axes_grid1.axes_divider import make_axes_locatable

divider0 = make_axes_locatable(ax[1])
cax0 = divider0.append_axes("right", size="7%", pad=0.07)
fig.colorbar(m, ax=ax[1], cax=cax0, label='ADC counts sum Ratio (Diff / Original)')

divider1 = make_axes_locatable(ax[0])
cax1 = divider1.append_axes("right", size="7%", pad=0.07)
fig.colorbar(m_curr, ax=ax[0], cax=cax1, label='Induced current integral Ratio (Diff / Original)')

plt.subplots_adjust(hspace=0.5)
fig.savefig("currentadc_ratio.pdf")
```

Ratio is quite close, mod noise + one cluster of pixels (likely due to difference mentioned above)

### Export result
As a last step we backtrack the ADC counts to the Geant4 tracks and we export the result in a HDF5 file.

```python
MAX_TRACKS_PER_PIXEL = 5

# Mapping between unique pixel array and track array index
track_pixel_map = cp.full((unique_pix.shape[0], MAX_TRACKS_PER_PIXEL), -1)
TPB = 32
BPG = ceil(unique_pix.shape[0] / TPB)
detsim.get_track_pixel_map[BPG, TPB](track_pixel_map, unique_pix, neighboring_pixels)

# Here we backtrack the ADC counts to the Geant4 tracks
TPB = 128
BPG = ceil(adc_list.shape[0] / TPB)
backtracked_id = cp.full((adc_list.shape[0], adc_list.shape[1], MAX_TRACKS_PER_PIXEL), -1)

detsim.backtrack_adcs[BPG,TPB](selected_tracks,
                               adc_list,
                               adc_ticks_list,
                               track_pixel_map,
                               event_id_map,
                               unique_eventIDs,
                               backtracked_id,
                               0)
```

```python
!rm test.h5
pc = fee.export_to_hdf5(adc_list.get(),
                        adc_ticks_list.get(),
                        unique_pix.get(),
                        backtracked_id.get(),
                        "test.h5")
```

```python
!rm evd.test.h5
!python /global/homes/s/soleti/larpix/larpix-v2-testing-scripts/event-display/to_evd_file.py --in_filename test.h5  --out_filename evd.test.h5  --geometry_file ../larndsim/pixel_layouts/multi_tile_layout-2.2.16.yaml --event_dt 2000 --dbscan_eps 25 --vd 1.587 --nhit_cut 2
```

```python
evd = h5py.File("evd.test.h5")
```

```python
fig,ax=plt.subplots(1,1)
ax.plot((selected_tracks['z_start']*10,selected_tracks['z_end']*10),(selected_tracks['y_start']*10,selected_tracks['y_end']*10),c='k')
ax.plot((evd['tracks']['start'][:,2],evd['tracks']['end'][:,2]),(evd['tracks']['start'][:,1]-218.236,evd['tracks']['end'][:,1]-218.236),label='Reconstructed track',ls='--',c='r')
ax.legend()
ax.set_xlabel("z [mm]")
ax.set_ylabel("y [mm]")
```

```python

```