Compare commits

..

278 Commits

Author SHA1 Message Date
Vignesh Sitaraman 4e6e101370 modified: TrackRecon.C
new file:   anasen_fem/cfrac_pred_2026-10-28/README.md
	new file:   anasen_fem/cfrac_pred_2026-10-28/band_centre.csv
	new file:   anasen_fem/cfrac_pred_2026-10-28/cfrac_pred_method.md
	new file:   anasen_fem/cfrac_pred_2026-10-28/check_extrapolation.log
	new file:   anasen_fem/cfrac_pred_2026-10-28/collect_slices.log
	new file:   anasen_fem/cfrac_pred_2026-10-28/fit_function.log
	new file:   anasen_fem/cfrac_pred_2026-10-28/function_map.csv
	new file:   anasen_fem/cfrac_pred_2026-10-28/make_cfrac_pred.log
	new file:   anasen_fem/cfrac_pred_2026-10-28/map_function.log
	new file:   anasen_fem/cfrac_pred_2026-10-28/slices_fit.csv
	new file:   anasen_fem/cfrac_pred_2026-10-28/solve_z44.7820/README.md
	new file:   anasen_fem/cfrac_pred_2026-10-28/solve_z44.7820/anode_dot.csv
	new file:   anasen_fem/cfrac_pred_2026-10-28/solve_z44.7820/elmer_phys.log
	new file:   anasen_fem/cfrac_pred_2026-10-28/solve_z44.7820/elmer_weight.log
	new file:   anasen_fem/cfrac_pred_2026-10-28/solve_z44.7820/elmergrid.log
	new file:   anasen_fem/cfrac_pred_2026-10-28/solve_z44.7820/geometry.py
	new file:   anasen_fem/cfrac_pred_2026-10-28/solve_z44.7820/probe_fields.py
	new file:   anasen_fem/cfrac_pred_2026-10-28/solve_z44.7820/scalars.dat
	new file:   anasen_fem/cfrac_pred_2026-10-28/solve_z44.7820/scalars.dat.names
	new file:   anasen_fem/cfrac_pred_2026-10-28/solve_z44.7820/scalars_weight.dat
	new file:   anasen_fem/cfrac_pred_2026-10-28/solve_z44.7820/scalars_weight.dat.names
	new file:   anasen_fem/cfrac_pred_2026-10-28/solve_z44.7820/wires2d.sif
	new file:   anasen_fem/cfrac_pred_2026-10-28/solve_z44.7820/wires2d_weight.sif
	new file:   anasen_fem/cfrac_pred_2026-10-28/solve_z44.7820/wires_gmsh2d_bc.py
2026-11-04 16:49:26 -05:00
Vignesh Sitaraman 8be662d0d1 modified: TrackRecon.C added fine timing correction
modified:   run_17F.sh updating to new format and including some other changes also updated the eloss files for 4%
2026-10-23 10:32:40 -04:00
Vignesh Sitaraman d4856783e7 modified: .gitignore
renamed:    cross_section/examples/paap_ta_runs_hist.toml -> cross_section/examples/paap_ta_runs.toml
2026-10-17 14:37:36 -04:00
Vignesh Sitaraman d63a422a41 modified: TrackRecon.C
new folder:   cross_section, this folder allows for cross section calculations for the ANASEN data, a readme and some example files are provided
	modified:   run_tr.sh
2026-10-17 12:38:35 -04:00
Vignesh Sitaraman 8c231120d8 modified: TrackRecon.C 2026-10-16 16:52:47 -04:00
Vignesh Sitaraman 8043237b89 modified: TrackRecon.C removing double counting from the qqq and a si dither toggle
modified:   eloss_calculations/Eloss.py changed from mass fraction to stoichiometry based on the catima documentation also regeneretated files and pushed
	modified:   run_17F.sh
	modified:   run_27Al.sh changed the Eloss dedx factor based on thenew stoichimetry basis as oppoosed to the mass fraction
	modified:   run_tr.sh
	modified:   scratch/overlay_2d.C restructured the beam E gates for the 2d overlay plots
2026-10-16 06:40:37 -04:00
Vignesh Sitaraman 14b1a89d65 modified: TrackRecon.C
modified:   run_tr.sh
2026-10-01 14:45:55 -04:00
Vignesh Sitaraman 12c4c87ba0 modified: pccal/fit_pc_energy_calibration.C changes for the adc limit and the lower limit to fit for the pc based on each weire separately 2026-09-30 16:43:20 -04:00
Vignesh Sitaraman c9339a1165 modified: Armory/ClassPW.h changed because lowest cluster was getting deleted leading to loss of data
modified:   TrackRecon.C loss/ halucination for pc calib due to ADC ceiling being placed on gainmatched files instead of the raw
	modified:   pc_energy_calibration.dat calibration fie,updated to get the "real" calibrated signal as proposed by the Alpha calibration run
2026-09-30 16:38:54 -04:00
Vignesh Sitaraman ea79180595 modified: TrackRecon.C added alpha eloss/efix plots
modified:   run_27Al.sh
2026-09-28 10:17:37 -04:00
Vignesh Sitaraman 8806af3f6d modified: TrackRecon.C
modified:   anasen_fem/wires2d.sif
	modified:   run_tr.sh
2026-09-25 23:05:24 -04:00
Vignesh Sitaraman 01783858fa modified: Armory/ClassPW.h
modified:   Armory/PCZRecon.h
	modified:   TrackRecon.C
2026-09-24 11:39:50 -04:00
Vignesh Sitaraman 018a92f647 modified: TrackRecon.C proton beam energy correction
modified:   anasen_fem/run.py
	modified:   anasen_fem/scalars.dat.names
	modified:   anasen_fem/scalars_weight.dat.names
2026-09-23 15:17:49 -04:00
Vignesh Sitaraman 3edbfa5bd8 modified: anasen_fem/README.md added the Shockley-Ramo dot calculation capability adapted fom Sudarsan's branch with a new visusalisation scheme to show the +ve and-ve sides on a logscale
modified:   anasen_fem/clean.sh
	new file:   anasen_fem/dotproduct.py
	new file:   anasen_fem/paraview_dotproduct.py
	modified:   anasen_fem/paraview_plotter.py
	modified:   anasen_fem/run.py
	modified:   anasen_fem/scalars.dat.names
	new file:   anasen_fem/scalars_weight.dat
	new file:   anasen_fem/scalars_weight.dat.names
	modified:   anasen_fem/wires2d.sif
	new file:   anasen_fem/wires2d_weight.sif
	modified:   anasen_fem/wires_gmsh2d_bc.py
2026-09-23 12:14:05 -04:00
Vignesh Sitaraman aebb9854ec modified: TrackRecon.C corrected a1c1 factors and diagnostics 2026-09-21 18:11:38 -04:00
Vignesh Sitaraman 19262299ba modified: run_27Al.sh
modified:   run_tr.sh
2026-09-18 17:48:37 -04:00
Vignesh Sitaraman f30e244d27 modified: TrackRecon.C the code was NOT dead 2026-09-18 17:48:06 -04:00
Vignesh Sitaraman 397c410320 Revert " modified: TrackRecon.C removed some dead code, changed cfrac parameters for 27Al and 17F"
This reverts commit cc32682de3.
2026-09-18 17:27:27 -04:00
Vignesh Sitaraman cc32682de3 modified: TrackRecon.C removed some dead code, changed cfrac parameters for 27Al and 17F 2026-09-18 16:57:54 -04:00
Vignesh Sitaraman 7ec86569fb modified: TrackRecon.C aded a2c1 and a2c2 toplogies just to see waht tehy look like
modified:   run_27Al.sh
	modified:   run_tr.sh
	new file:   scratch/RunwiseTrend.C diagnostics to see variationwith run
	modified:   slope_intercept_results_17F.dat
2026-09-14 11:00:08 -04:00
Vignesh Sitaraman bbf00ad3c6 modified: TrackRecon.C added 2.23 gated ex_vs_something spectra as a diagnostic for the 27Al run 2026-09-13 13:05:35 -04:00
Vignesh Sitaraman 7be00bcb3f modified: TrackRecon.C added qqq xy diagnostics for beam 2026-09-10 14:45:55 -04:00
Vignesh Sitaraman d46f456fd9 modified: TrackRecon.C
modified:   eloss_calculations/Eloss.py
	modified:   eloss_calculations/alpha_lookup_50MeV_250torr_4pc.dat redone at 0.89
	modified:   eloss_calculations/aluminum_lookup_80MeV_250torr_4pc.dat
	modified:   eloss_calculations/deutron_lookup_30MeV_250torr_4pc.dat
	modified:   eloss_calculations/fluorine_lookup_70MeV_250torr_4pc.dat
	modified:   eloss_calculations/oxygen_lookup_70MeV_250torr_4pc.dat
	modified:   eloss_calculations/proton_lookup_30MeV_250torr_4pc.dat
	modified:   run_17F.sh
	modified:   run_27Al.sh
2026-09-10 10:47:51 -04:00
Vignesh Sitaraman 97357e383e modified: TrackRecon.C 2026-09-10 08:19:16 -04:00
Vignesh Sitaraman 83114262db modified: TrackRecon.C 2026-09-09 10:49:17 -04:00
Vignesh Sitaraman 9b2c20daca modified: TrackRecon.C
modified:   scratch/make_prettyplots.C
2026-09-08 15:35:43 -04:00
Vignesh Sitaraman 38c8c7e0e0 modified: TrackRecon.C put in timing gates and phi gates uniformly
modified:   .gitignore
	deleted:    Analysis.C
	deleted:    Analyzer.C deleted the older analysis formats
	deleted:    Analyzer.h
	deleted:    Timing_Summary_Matplotlib.png
	deleted:    WedgeChVAnode.jpeg
	modified:   run_tr.sh
	modified:   scratch/make_prettyplots.C added rebinning capability and log z scale option
2026-09-08 14:37:04 -04:00
Vignesh Sitaraman ec43a81030 modified: TrackRecon.C implemented and applied beamtilt parameter calcualtion and application
modified:   run_27Al.sh
	new file:   scratch/FitBeamAxis.C code to figure out beamtilt
2026-09-07 09:57:13 -04:00
Vignesh Sitaraman 7f936f6a53 modified: run_27Al.sh testing beamtilt theory 2026-09-06 16:05:13 -04:00
Vignesh Sitaraman 048b11fce3 modified: .gitignore
new file:   CLAUDE.md
2026-09-06 13:41:38 -04:00
Vignesh Sitaraman d3c4c47e41 modified: TrackRecon.C
modified:   run_tr.sh
2026-09-06 10:43:55 -04:00
Vignesh Sitaraman fde75b8a77 modified: TrackRecon.C
modified:   run_27Al.sh
2026-09-04 14:44:10 -04:00
Vignesh Sitaraman 80b079b81d deleted: Armory/mass20.txt
modified:   TrackRecon.C paap gating
	modified:   run_17F.sh
	modified:   run_27Al.sh
	modified:   run_tr.sh
	modified:   scratch/overlay_2d.C
2026-09-04 10:16:34 -04:00
Vignesh Sitaraman 78add5432a modified: TrackRecon.C added plots for dE_cal vs dT and Ex vs dT
modified:   eloss_calculations/alpha_lookup_50MeV_250torr_3pc.dat for 0.89 dedx
	modified:   eloss_calculations/aluminum_lookup_80MeV_250torr_3pc.dat
	modified:   eloss_calculations/deutron_lookup_30MeV_250torr_3pc.dat
	modified:   eloss_calculations/fluorine_lookup_70MeV_250torr_3pc.dat
	modified:   eloss_calculations/oxygen_lookup_70MeV_250torr_3pc.dat
	modified:   eloss_calculations/proton_lookup_30MeV_250torr_3pc.dat
	modified:   run_27Al.sh collapsed the parallelization a bit
	modified:   scratch/overlay_2d.C added minz, rebinning and box option
2026-09-02 13:29:16 -04:00
Vignesh Sitaraman ffb1c1a960 modified: TrackRecon.C
modified:   scratch/overlay_2d.C
2026-09-02 09:44:36 -04:00
Vignesh Sitaraman 4d9145d532 modified: TrackRecon.C mismatched braces 2026-09-02 09:31:54 -04:00
Vignesh Sitaraman fce1fb6c1d modified: TrackRecon. 2026-09-02 09:24:56 -04:00
Vignesh Sitaraman bf8d4089f1 modified: TrackRecon.C
modified:   run_17F.sh
2026-09-02 08:39:47 -04:00
Vignesh Sitaraman 941f450af1 modified: TrackRecon.C
modified:   scratch/overlay_2d.C
2026-09-01 14:05:46 -04:00
Vignesh Sitaraman 461d60a50c modified: eloss_calculations/Eloss.py trying out with 0.89/0.9 multiplier on all particles
modified:   eloss_calculations/alpha_lookup_50MeV_250torr_3pc.dat
	modified:   eloss_calculations/aluminum_lookup_80MeV_250torr_3pc.dat
	modified:   eloss_calculations/deutron_lookup_30MeV_250torr_3pc.dat
	modified:   eloss_calculations/fluorine_lookup_70MeV_250torr_3pc.dat
	modified:   eloss_calculations/oxygen_lookup_70MeV_250torr_3pc.dat
	modified:   eloss_calculations/proton_lookup_30MeV_250torr_3pc.dat
	new file:   scratch/overlay_2d.C small script to overlay multiple 2d histograms from the same file
    various scripts used to optimise the different parameters in the analysis
	new file:   scratch/CompareElasticLocus.C
	new file:   scratch/PlotDedxScan.C
	new file:   scratch/RunMultiFit.C
	new file:   scratch/ScanDedxScale.C
2026-09-01 11:43:28 -04:00
Vignesh Sitaraman d245fc2679 modified: TrackRecon.C fixed typo 2026-09-01 06:41:04 -04:00
Vignesh Sitaraman dd6b938eb2 new file: MiscEdE.C plotting the radioactive beam IC PID as well as looking for timing coincideences between that and the MCP-Rf time
new file:   run_misc_ede.sh same as above
	modified:   TrackRecon.C added beamE gated plots for the step aladder for 27Al
	modified:   run_17F.sh doing more files now
	modified:   run_27Al.sh scaling change to 0.9 to try it out
	modified:   run_tr.sh
	modified:   scratch/make_prettyplots.C add log scale option to the 2D plots
2026-09-01 06:24:36 -04:00
Vignesh Sitaraman 3dd678221a modified: TrackRecon.C implementation of Ex vs TOF plot not working rn 2026-08-31 14:58:29 -04:00
Vignesh Sitaraman b5e88a6731 modified: TrackRecon.C minor changes 2026-08-31 14:36:54 -04:00
Vignesh Sitaraman d211971904 modified: TrackRecon.C added topology gated spectra as well as calibrated versions of the a2 ratio plots 2026-08-31 09:40:14 -04:00
Vignesh Sitaraman 6619613db5 modified: TrackRecon.C restructuring the p(a,a)p branch to make it more readable need to implement per topo version of plots
modified:   run_27Al.sh
	modified:   run_tr.sh
2026-08-30 14:29:45 -04:00
Vignesh Sitaraman e83775d79f modified: TrackRecon.C added some plots, perppeing for more major changes to the proton branch
modified:   run_17F.sh
	modified:   run_27Al.sh
2026-08-27 10:29:24 -04:00
Vignesh Sitaraman 8cd98c35e1 modified: .gitignore added Ryan's autofit routine to gitignore 2026-08-26 12:11:58 -04:00
Vignesh Sitaraman 3e60105617 modified: TrackRecon.C remoevd the gated proton spectra dn introduced new histos where we "snap" to different exicited state and plto the Ekin_vs_Etrack
modified:   run_27Al.sh dEdx scaling scan
2026-08-21 13:45:18 -04:00
Vignesh Sitaraman c0c49be0c9 modified: TrackRecon.C consolidated the ditehr sigma, the timing cuts and reset the beam xy to (0,0), beamenergy gets set to 0.001 instead of returning for values that are beyond calculated catima range.
Moved some diagnostic plots because they were out of range for the boleans to actually catch them, lifted the si time ditehring so that it gets applied everywhere for smootehr time plots.
	modified:   eloss_calculations/Eloss.py introduced a external scaling factr to allow eloss calculations to be paired with the run scripts to figure out the right dEdx scaling
	modified:   run_27Al.sh
2026-08-18 15:14:18 -04:00
Vignesh Sitaraman 9416638af6 modified: TrackRecon.C changed the vertex by wire to be calibrated dE 2026-08-17 13:53:26 -04:00
Vignesh Sitaraman 0a7dd1bcf6 modified: TrackRecon.C gate changes 2026-08-17 11:37:21 -04:00
Vignesh Sitaraman cf584a6d7a modified: Armory/PCZRecon.h added a2c0
modified:   TrackRecon.C added a2c0, changed a1c1 cfrac parameters based on optimisation for the alpha and source data. Might need to be changed for the reaction though dfo rsome reason the apramteres don't quite makes sense
	modified:   eloss_calculations/Eloss.py added a scaling factor to particles with A>10 based to make the beams go further into the detector based on reaction data
	modified:   pc_energy_calibration.dat halved the anode dE cal to account for the misamtch between calib and rpedict for the source data
2026-08-17 11:05:22 -04:00
Vignesh Sitaraman c83557f30c modified: Armory/PCZRecon.h changed from vertex xy checks which al;way s defaulted to the same version to a proximity check to the wire ( using pitchok)
modified:   TrackRecon.C added diagnostic plots for recon and eloss
2026-08-14 14:22:08 -04:00
Vignesh Sitaraman ed505139b4 modified: pc_energy_calibration.dat corrected calibs for 16 18 20 and 23 2026-08-13 20:54:42 -04:00
Vignesh Sitaraman 9b68c5b720 modified: TrackRecon.C added some more diagnostics plots for the reaciton dataset 2026-08-13 11:11:29 -04:00
Vignesh Sitaraman 98c48af0b1 new file: Armory/PCZRecon.h refactor to lift a1c0 and a1c1 frac into a class PCZRecon which includes the a1c2 slope correction as well. Making the code more readable and easier to maintain.
also changes the model_invert into a function call instead of using the TF1 class
	modified:   TrackRecon.C refactor plus removal of older vesions of pcz and track reconstruction from the old analysis. Changed the ranges for dECalib spectra since their max was way too high.
	modified:   pc_energy_calibration.dat changed the calibration of anode 2-4 to the same as anode1 and 5 to 6 becuase they were causing bifuraction in signal. This might have been happening because the 17F data is poisoning the calibraion. This hypothesis
    is pending validation however.
2026-08-12 15:02:35 -04:00
Vignesh Sitaraman fbd601ec40 modified: run_27Al.sh 2026-08-12 10:30:15 -04:00
Vignesh Sitaraman 21fd1ca4d2 modified: TrackRecon.C correction of cathode multiplicity gate on teh pc events 2026-08-12 10:30:15 -04:00
Vignesh Sitaraman 542e927049 modified: TrackRecon.C correction of cathode multiplicity gate on teh pc events 2026-08-11 14:43:38 -04:00
Vignesh Sitaraman 2784671732 modified: TrackRecon.C PID gate on the suspected proton locus and prudcing the Ex function corresponding to the state. Also implemented a1c0 dithering for the reaction data 2026-08-11 14:19:29 -04:00
Vignesh Sitaraman 774050dfdf modified: TrackRecon.C 2026-08-11 12:58:36 -04:00
Vignesh Sitaraman caeff0d7c5 modified: TrackRecon.C rehaul of the calibration dynamic so that any channels that dont have sufficient sx3 a1c2 stats get calibrated by lining up their centroids with those that do. Still a bit iffy on the bad anode formalism but keeping it in for now.
modified:   pc_energy_calibration.dat
	modified:   pccal/fit_pc_energy_calibration.C
2026-08-10 14:27:52 -04:00
Vignesh Sitaraman 6ff00bedc2 modified: pc_energy_calibration.dat
modified:   run_27Al.sh
2026-08-08 15:00:51 -04:00
Vignesh Sitaraman 8e9d539653 modified: TrackRecon.C 2026-08-08 14:59:52 -04:00
Vignesh Sitaraman 902afaad54 pcgain 2026-08-07 11:02:49 -04:00
Vignesh Sitaraman 07e30a5ddd modified: TrackRecon.C implemented new energy calibration paradigm for the PC and some other minor changes
modified:   pc_energy_calibration.dat
	modified:   pccal/fit_pc_energy_calibration.C
	modified:   run_27Al.sh
2026-08-06 17:04:17 -04:00
Vignesh Sitaraman 00c507e0ac modified: Armory/ClassPW.h
modified:   TrackRecon.C corrected anode cal logic with changes to classpw.h to incorporate the guardwire to cathode wire region for anode charge collection
	modified:   pc_energy_calibration.dat
2026-08-06 13:30:51 -04:00
Vignesh Sitaraman 2d3bcc7c74 modified: TrackRecon.C calibration script in progress needs overhaul
modified:   run_27Al.sh
2026-08-06 10:19:41 -04:00
Vignesh Sitaraman 58d0ef1839 modified: TrackRecon.C changed the calibrationf for anode cathode so that we can get Eloss difference in the active area of the proportional counter. Using only ground staet now as opposed to snapping to the neareast Ex
modified:   run_27Al.sh
	modified:   run_tr.sh
2026-08-05 10:51:19 -04:00
Vignesh Sitaraman 51ece5e69b modified: TrackRecon.C corrected some eval calls and implemeneted a beam E inversion function to get the beam energy from the measured Ex and theta and compare it to the Beam E from Eloss
modified:   run_27Al.sh
2026-08-03 12:07:26 -04:00
Vignesh Sitaraman bd79dda26b modified: MakeVertex.C new version of sudarsan's code
modified:   TrackRecon.C changes to selectivity of detecrtor channels based on sudarsan's code and some changes to event structure
	modified:   run_27Al.sh
2026-08-03 09:45:43 -04:00
Vignesh Sitaraman 14999546b7 modified: Armory/HistPlotter.h
modified:   TrackRecon.C made changes to account for frequent crashes due to the number of histograms. New changes allow for flsuhing of data to histograms every n events
	modified:   run_27Al.sh
2026-07-31 14:05:18 -04:00
Vignesh Sitaraman a292695a4d modified: TrackRecon.C
modified:   run_27Al.sh
	modified:   run_tr.sh
2026-07-30 15:32:53 -04:00
Vignesh Sitaraman 173fb36ea2 modified: TrackRecon.C
modified:   run_27Al.sh
2026-07-29 14:44:53 -04:00
Vignesh Sitaraman 3e9c86d8dc modified: .gitignore
modified:   TrackRecon.C
	new file:   pc_energy_calibration.dat
	modified:   pccal/fit_pc_energy_calibration.C
	modified:   run_27Al.sh
	modified:   run_tr.sh
2026-07-28 11:32:30 -04:00
Vignesh Sitaraman 3b09639c60 modified: TrackRecon.C 2026-07-14 14:56:16 -04:00
Vignesh Sitaraman 2bd4f4eb33 modified: TrackRecon.C
new file:   pccal/fit_pc_energy_calibration.C
2026-07-14 13:45:26 -04:00
Vignesh Sitaraman a3cab973be modified: TrackRecon.C 2026-07-13 14:05:09 -04:00
Vignesh Sitaraman f17259c8cc modified: TrackRecon.C
modified:   run_tr.sh
2026-07-13 13:32:48 -04:00
Vignesh Sitaraman 95f15ed23d modified: TrackRecon.C
modified:   run_tr.sh
2026-07-12 08:52:20 -04:00
Vignesh Sitaraman aa960fd711 modified: eloss_calculations/Eloss.py
modified:   eloss_calculations/alpha_lookup_50MeV_250torr_3pc.dat
	modified:   eloss_calculations/alpha_lookup_50MeV_250torr_4pc.dat
	modified:   eloss_calculations/alpha_lookup_50MeV_350torr_3pc.dat
	modified:   eloss_calculations/alpha_lookup_50MeV_350torr_4pc.dat
	modified:   eloss_calculations/aluminum_lookup_80MeV_250torr_3pc.dat
	modified:   eloss_calculations/aluminum_lookup_80MeV_250torr_4pc.dat
	modified:   eloss_calculations/aluminum_lookup_80MeV_350torr_3pc.dat
	modified:   eloss_calculations/aluminum_lookup_80MeV_350torr_4pc.dat
	modified:   eloss_calculations/deutron_lookup_30MeV_250torr_3pc.dat
	modified:   eloss_calculations/deutron_lookup_30MeV_250torr_4pc.dat
	modified:   eloss_calculations/deutron_lookup_30MeV_350torr_3pc.dat
	modified:   eloss_calculations/deutron_lookup_30MeV_350torr_4pc.dat
	modified:   eloss_calculations/fluorine_lookup_70MeV_250torr_3pc.dat
	modified:   eloss_calculations/fluorine_lookup_70MeV_250torr_4pc.dat
	modified:   eloss_calculations/fluorine_lookup_70MeV_350torr_3pc.dat
	modified:   eloss_calculations/fluorine_lookup_70MeV_350torr_4pc.dat
	modified:   eloss_calculations/oxygen_lookup_70MeV_250torr_3pc.dat
	modified:   eloss_calculations/oxygen_lookup_70MeV_250torr_4pc.dat
	modified:   eloss_calculations/oxygen_lookup_70MeV_350torr_3pc.dat
	modified:   eloss_calculations/oxygen_lookup_70MeV_350torr_4pc.dat
	modified:   eloss_calculations/proton_lookup_30MeV_250torr_3pc.dat
	modified:   eloss_calculations/proton_lookup_30MeV_250torr_4pc.dat
	modified:   eloss_calculations/proton_lookup_30MeV_350torr_3pc.dat
	modified:   eloss_calculations/proton_lookup_30MeV_350torr_4pc.dat
2026-07-10 16:14:34 -04:00
Vignesh Sitaraman 356d0c65b2 modified: TrackRecon.C
modified:   run_tr.sh
2026-07-10 14:57:24 -04:00
Vignesh Sitaraman ba8e8440e8 modified: Armory/ClassPW.h
modified:   TrackRecon.C
	modified:   eloss_calculations/Eloss.py
	modified:   eloss_calculations/alpha_lookup_50MeV_250torr_3pc.dat
	modified:   eloss_calculations/alpha_lookup_50MeV_250torr_4pc.dat
	modified:   eloss_calculations/alpha_lookup_50MeV_350torr_3pc.dat
	modified:   eloss_calculations/alpha_lookup_50MeV_350torr_4pc.dat
	modified:   eloss_calculations/aluminum_lookup_80MeV_250torr_3pc.dat
	modified:   eloss_calculations/aluminum_lookup_80MeV_250torr_4pc.dat
	modified:   eloss_calculations/aluminum_lookup_80MeV_350torr_3pc.dat
	modified:   eloss_calculations/aluminum_lookup_80MeV_350torr_4pc.dat
	new file:   eloss_calculations/deutron_lookup_30MeV_250torr_3pc.dat
	new file:   eloss_calculations/deutron_lookup_30MeV_250torr_4pc.dat
	new file:   eloss_calculations/deutron_lookup_30MeV_350torr_3pc.dat
	new file:   eloss_calculations/deutron_lookup_30MeV_350torr_4pc.dat
	modified:   eloss_calculations/fluorine_lookup_70MeV_250torr_3pc.dat
	modified:   eloss_calculations/fluorine_lookup_70MeV_250torr_4pc.dat
	modified:   eloss_calculations/fluorine_lookup_70MeV_350torr_3pc.dat
	modified:   eloss_calculations/fluorine_lookup_70MeV_350torr_4pc.dat
	modified:   eloss_calculations/oxygen_lookup_70MeV_250torr_3pc.dat
	modified:   eloss_calculations/oxygen_lookup_70MeV_250torr_4pc.dat
	modified:   eloss_calculations/oxygen_lookup_70MeV_350torr_3pc.dat
	modified:   eloss_calculations/oxygen_lookup_70MeV_350torr_4pc.dat
	new file:   eloss_calculations/proton_lookup_30MeV_250torr_3pc.dat
	new file:   eloss_calculations/proton_lookup_30MeV_250torr_4pc.dat
	new file:   eloss_calculations/proton_lookup_30MeV_350torr_3pc.dat
	new file:   eloss_calculations/proton_lookup_30MeV_350torr_4pc.dat
	modified:   run_17F.sh
	modified:   run_27Al.sh
	modified:   run_tr.sh
2026-07-10 14:32:52 -04:00
Vignesh Sitaraman dae9ca32cc modified: MakeVertex.C
modified:   eloss_calculations/Eloss.py
	deleted:    eloss_calculations/alpha_lookup_20MeV_3pc.dat
	deleted:    eloss_calculations/alpha_lookup_20MeV_3pc_350Torr.dat
	deleted:    eloss_calculations/alpha_lookup_20MeV_4pc.dat
	deleted:    eloss_calculations/alpha_lookup_20MeV_4pc_350Torr.dat
	renamed:    eloss_calculations/deutron_lookup_20MeV_3pc.dat -> eloss_calculations/alpha_lookup_50MeV_250torr_3pc.dat
	new file:   eloss_calculations/alpha_lookup_50MeV_250torr_4pc.dat
	new file:   eloss_calculations/alpha_lookup_50MeV_350torr_3pc.dat
	new file:   eloss_calculations/alpha_lookup_50MeV_350torr_4pc.dat
	deleted:    eloss_calculations/alpha_lookup_6.0MeV.dat
	new file:   eloss_calculations/aluminum_lookup_80MeV_250torr_3pc.dat
	renamed:    eloss_calculations/aluminum_lookup_80MeV_4pc.dat -> eloss_calculations/aluminum_lookup_80MeV_250torr_4pc.dat
	new file:   eloss_calculations/aluminum_lookup_80MeV_350torr_3pc.dat
	renamed:    eloss_calculations/aluminum_lookup_80MeV_4pc_350Torr.dat -> eloss_calculations/aluminum_lookup_80MeV_350torr_4pc.dat
	deleted:    eloss_calculations/aluminum_lookup_80MeV_3pc.dat
	deleted:    eloss_calculations/aluminum_lookup_80MeV_3pc_350Torr.dat
	new file:   eloss_calculations/fluorine_lookup_70MeV_250torr_3pc.dat
	renamed:    eloss_calculations/fluorine_lookup_70MeV_4pc.dat -> eloss_calculations/fluorine_lookup_70MeV_250torr_4pc.dat
	new file:   eloss_calculations/fluorine_lookup_70MeV_350torr_3pc.dat
	renamed:    eloss_calculations/fluorine_lookup_70MeV_4pc_350Torr.dat -> eloss_calculations/fluorine_lookup_70MeV_350torr_4pc.dat
	deleted:    eloss_calculations/fluorine_lookup_70MeV_3pc.dat
	deleted:    eloss_calculations/fluorine_lookup_70MeV_3pc_350Torr.dat
	deleted:    eloss_calculations/make_eloss_table.C
	new file:   eloss_calculations/oxygen_lookup_70MeV_250torr_3pc.dat
	renamed:    eloss_calculations/oxygen_lookup_70MeV_4pc.dat -> eloss_calculations/oxygen_lookup_70MeV_250torr_4pc.dat
	new file:   eloss_calculations/oxygen_lookup_70MeV_350torr_3pc.dat
	renamed:    eloss_calculations/oxygen_lookup_70MeV_4pc_350Torr.dat -> eloss_calculations/oxygen_lookup_70MeV_350torr_4pc.dat
	deleted:    eloss_calculations/oxygen_lookup_70MeV_3pc.dat
	deleted:    eloss_calculations/oxygen_lookup_70MeV_3pc_350Torr.dat
	deleted:    eloss_calculations/proton_lookup_20MeV_3pc.dat
	deleted:    eloss_calculations/proton_lookup_20MeV_3pc_350Torr.dat
	deleted:    eloss_calculations/proton_lookup_20MeV_4pc.dat
	deleted:    eloss_calculations/proton_lookup_20MeV_4pc_350Torr.dat
	deleted:    eloss_calculations/proton_lookup_6.0MeV.dat
2026-07-09 12:04:53 -04:00
Vignesh Sitaraman 3cfecd4c29 modified: Armory/ClassPW.h
modified:   TrackRecon.C
	modified:   run_tr.sh
2026-07-06 10:31:52 -04:00
Vignesh Sitaraman 7568431c03 modified: TrackRecon.C 2026-07-01 15:00:52 -04:00
Vignesh Sitaraman 2632044293 modified: TrackRecon.C 2026-06-29 14:33:45 -04:00
Vignesh Sitaraman 370649a7ba modified: TrackRecon.C 2026-06-29 08:55:20 -04:00
Vignesh Sitaraman 48eea6d70b modified: TrackRecon.C consolidated varaibles and implemented a method to eliminate the use of a1c0 for a1c1 calculation and report the cell pitch/ mis values for only the value chosen.
changed ranges for phi plots to -200 to 200. Folders for a1c0 and a1c1 are categorized into detector types for easier access. Using deltaphi mechanism instead of the si.phi-pc.phi formalism
    to better account for wrap around
2026-06-28 18:06:57 -04:00
Vignesh Sitaraman 826139a26d modified: TrackRecon.C changed cathode gains for the 27 Al to have consitent cfrac and other parameters for all runs. This also should allow us to
merge runs from thboth campaigns easier. Corrected error in proton mass in stuff copied over from Sudarsan's branch.
    Changed some variable names to mka ethem more consistent and clearer as to their actuall purpose.
	modified:   run_tr.sh
2026-06-28 15:43:37 -04:00
Vignesh Sitaraman 18b948a335 new file: scratch/fit_a1c0_zcorr.C not sure if this is the right apporach, doing this for now as a stopgap 2026-06-26 13:41:28 -04:00
Vignesh Sitaraman 8c10235be9 modified: .gitignore
modified:   TrackRecon.C
2026-06-26 11:14:14 -04:00
Vignesh Sitaraman 88eba525e1 modified: TrackRecon.C included new branch for True A1C0 events, A1C1 and A1C0 events now sit in different branches than the brenchmark branch, increased time gate 2026-06-26 09:52:20 -04:00
Vignesh Sitaraman aa48a9d82f modified: TrackRecon.C 2026-06-25 16:47:56 -04:00
Vignesh Sitaraman d19f0352af modified: TrackRecon.C 2026-06-25 16:12:23 -04:00
Vignesh Sitaraman 364fce5d0c modified: run_tr.sh 2026-06-25 14:03:35 -04:00
Vignesh Sitaraman d1d265835c modified: Armory/ClassPW.h
modified:   TrackRecon.C
2026-06-25 14:01:54 -04:00
Vignesh Sitaraman d784650f15 modified: Armory/ClassPW.h
new file:   CLAUDE.md
	modified:   TrackRecon.C
	modified:   run_tr.sh
2026-06-25 08:23:09 -04:00
Vignesh Sitaraman 4249b9b848 modified: Armory/ClassPW.h
modified:   TrackRecon.C replacing auto with const auto & to pass value instead of copying
2026-06-24 11:59:15 -04:00
Vignesh Sitaraman 6bad95d228 modified: Armory/ClassPW.h changed the wire phiminisation from a search based function to a phi vertex solution
modified:   TrackRecon.C changed starting aprameters for the cfrac fit, folding the lowereband from 17F into the higher band for cfrtac analysis. Exploring a new offset correction for the SX3s and QQQs based on the A1C0 Hybrid approach (WIP)
    A1C1 events are solved as A1C0 for both adjacent cells and thefinal answer with the lower Vertexperp is chosen.
	modified:   run_tr.sh reordered so that we can look at all alpha source runs as proton runs are still ebing processed
2026-06-24 10:36:23 -04:00
Vignesh Sitaraman c2a8cbefc6 modified: TrackRecon.C 2026-06-21 15:42:04 -04:00
Vignesh Sitaraman 3ba532843d modified: TrackRecon.C included stuff to account for dead wires and thus pseudo 1 wire events, cleaned up code and added comments to make the approaches more understandable
modified:   fit_a1c1_cfrac.C included a reference free method for gain amtching cell
	modified:   slope_intercept_results_17F.dat put in value for the missing wire gains based on other wires with similar profiles. The variation among them is minute but the vairation from one is consitent
2026-06-19 16:28:31 -04:00
Vignesh Sitaraman 4170adb503 modified: TrackRecon.C energy theshold of 5 on wireevent filling
modified:   run_tr.sh
2026-06-18 19:47:21 -04:00
Vignesh Sitaraman 385f5af204 modified: TrackRecon.C corrected vertexrecon_pczfix limits 2026-06-18 17:43:48 -04:00
Vignesh Sitaraman ff21920bae modified: TrackRecon.C consolidated the a1c1 linear functions and fits
new file:   fit_a1c1_cfrac.C
	modified:   run_tr.sh
2026-06-18 17:41:45 -04:00
Vignesh Sitaraman 9e2023a9db modified: TrackRecon.C
modified:   run_tr.sh
2026-06-15 19:27:41 -04:00
Vignesh Sitaraman 819a8937f7 modified: Armory/PC_StepLadder_Correction.h
modified:   TrackRecon.C benchmark a1c1 cfrac seems to work
	modified:   run_17F.sh
	modified:   run_27Al.sh
	modified:   run_tr.sh
2026-06-15 18:14:24 -04:00
Vignesh Sitaraman fcd412dc59 modified: Armory/ClassPW.h fixed typo
modified:   TrackRecon.C found some interesting behaviour in the A1c1 plots going to implement new a1c1 model invert to acount for a/c charege variation next
	modified:   scratch/plot_dither_scan.C
	modified:   scratch/scan_dither_runs.sh
2026-06-14 13:36:58 -04:00
Vignesh Sitaraman 1bbf2ae059 modified: TrackRecon.C
modified:   eloss_calculations/Eloss.py
	new file:   eloss_calculations/deutron_lookup_20MeV_3pc.dat for 27Al(a,d) pathway
	new file:   scratch/plot_dither_scan.C optimising dithering for A1C1 and A1C0 events
	new file:   scratch/scan_dither_runs.sh
	renamed:    scan_slope_runs.sh -> scratch/scan_slope_runs.sh
2026-06-12 10:42:22 -04:00
Vignesh Sitaraman 7861cc496c modified: MakeVertex.C adapted from Sudarsan's pushes
modified:   TrackRecon.C dithering was not inclded ;-;
2026-06-12 09:14:40 -04:00
Vignesh Sitaraman f7248f6a4e modified: Armory/PC_StepLadder_Correction.h
modified:   TrackRecon.C trying to benchmeark A1Cn events against a1c2 and failing due to flaww in logic
	modified:   run_tr.sh
	modified:   scratch/plot_slope_scan.C
2026-06-11 16:51:41 -04:00
Vignesh Sitaraman 20f8ad227c modified: Armory/PC_StepLadder_Correction.h
modified:   TrackRecon.C
	modified:   run_tr.sh
	modified:   scan_slope_runs.sh
	modified:   scratch/plot_slope_scan.C
2026-06-11 09:29:14 -04:00
Vignesh Sitaraman 8dd6526221 modified: TrackRecon.C bookkeeping + introducing CO2 dependent Eloss + diagnostic plots to figure out teh slope correection for the PC model_invert
new file:   eloss_calculations/alpha_lookup_20MeV_3pc_350Torr.dat
	new file:   eloss_calculations/alpha_lookup_20MeV_4pc_350Torr.dat
	new file:   eloss_calculations/aluminum_lookup_80MeV_3pc_350Torr.dat
	new file:   eloss_calculations/aluminum_lookup_80MeV_4pc_350Torr.dat
	new file:   eloss_calculations/fluorine_lookup_70MeV_3pc_350Torr.dat
	new file:   eloss_calculations/fluorine_lookup_70MeV_4pc_350Torr.dat
	new file:   eloss_calculations/oxygen_lookup_70MeV_3pc_350Torr.dat
	new file:   eloss_calculations/oxygen_lookup_70MeV_4pc_350Torr.dat
	new file:   eloss_calculations/proton_lookup_20MeV_3pc_350Torr.dat
	new file:   eloss_calculations/proton_lookup_20MeV_4pc_350Torr.dat
	modified:   run_tr.sh book keeping
	new file:   scan_slope_runs.sh   diagnostic plots to figure out the slope correection for the PC model_invert
	new file:   scratch/plot_slope_scan.C  diagnostic plots to figure out the slope correection for the PC model_invert
2026-06-09 17:37:22 -04:00
Vignesh Sitaraman 948c6e6ea2 renamed: Calibration.C -> CalibrationQQQ.C changed name to make the calibration script explicitly state the kind of detectors QQQ
renamed:    Calibration.h -> CalibrationQQQ.h
	modified:   README.md
2026-06-08 11:48:00 -04:00
Vignesh Sitaraman 5824fa7148 modified: TrackRecon.C made variables to account for variable energy loss in 27Al and 17F runs due to beamline components
modified:   run_17F.sh made all outputs direct to specific folders for clarity and to avoid overwriting results from different datasets
	modified:   run_27Al.sh made output folder format consitent across datasets
	modified:   run_tr.sh made output folder format consitent across datasets
2026-06-08 11:32:39 -04:00
Vignesh Sitaraman 20dc8fef22 modified: TrackRecon.C
modified:   run_tr.sh parralelistaion of all runs and addition of CO2 percent as an environment variable
2026-06-08 10:39:22 -04:00
Vignesh Sitaraman 702399be84 modified: Calibration.C corrected the p energy being used to the 241Am peak
modified:   GainMatchQQQ.C made all output files .dat instead of .txt
	modified:   TrackRecon.C made ome more plots for dissertation
	modified:   qqq_Calib.dat updated with 241Am peak position
	modified:   run_17F.sh
	modified:   run_tr.sh
	modified:   scratch/make_prettyplots.C made change so now we can input multiple runs at once and plots them together
2026-06-07 17:18:44 -04:00
Vignesh Sitaraman 9ad4e9d5d4 modified: scratch/make_prettyplots.C 2026-06-05 19:47:05 -04:00
Vignesh Sitaraman 7891aaebca modified: scratch/scan_rf_timing.C output images instead of gif 2026-06-05 15:41:32 -04:00
Vignesh Sitaraman d8e4b22614 modified: TrackRecon.C seprating rf-mcp time for qqq into inner vs outer rings 2026-06-05 11:06:49 -04:00
Vignesh Sitaraman d5419bab9b new file: anasen_fem/1dpotplot.py 1d potential plotter from A to C from elmer output
# modified:   anasen_fem/wires_gmsh2d_bc.py
	new file:   scratch/scan_rf_timing.C scan RF-MCP timing for runs to look at variation
2026-06-05 11:05:40 -04:00
Vignesh Sitaraman 66fd758554 modified: scratch/make_prettyplots.C set z min 2026-06-05 08:48:42 -04:00
Vignesh Sitaraman 88cf59bce3 modified: anasen_fem/paraview_plotter.py correct path for zephyrus 2026-06-04 15:59:13 -04:00
Vignesh Sitaraman 86933ac2bf modified: TrackRecon.C
modified:   eloss_calculations/Eloss.py changes to accoodate 3%CO2 mix instead of 4%CO2 mix
2026-06-04 15:35:04 -04:00
Vignesh Sitaraman a875b17247 modified: TrackRecon.C
modified:   run_tr.sh
	modified:   scratch/make_prettyplots.C
2026-06-04 14:32:33 -04:00
Vignesh Sitaraman 6104c6f7a9 modified: TrackRecon.C
modified:   run_17F.sh
	renamed:    scratch/make_pretty.C -> scratch/make_prettyplots.C
2026-06-04 08:15:16 -04:00
Vignesh Sitaraman 1fb12d2f46 modified: Armory/HistPlotter.h changes to make histgrams plot in alphabetical order
modified:   TrackRecon.C changes to maek the misc branches work
	modified:   TrackRecon.h changes to maek the misc branches work
	modified:   run_17F.sh
2026-06-03 15:38:00 -04:00
Vignesh Sitaraman 792933de25 modified: TrackRecon.C made to changes to make the old analysis histograms switchable. Having problems with the misc branch addresses for 17F reaction data analysis.
modified:   TrackRecon.h
2026-06-03 14:42:16 -04:00
Vignesh Sitaraman a64d45e081 modified: TrackRecon.C changed the nA analyssis to be a little more efficient
modified:   run_17F.sh
2026-06-03 11:46:07 -04:00
Vignesh Sitaraman 254402b4cc new file: scratch/make_pretty.C make pretty plots for the thesis 2026-06-02 18:35:39 -04:00
Vignesh Sitaraman 787b1ca498 new file: scratch/PhiCorr.C
renamed:    anasen_labels.csv -> scratch/anasen_labels.csv
	renamed:    grid_generate.py -> scratch/grid_generate.py
	renamed:    shadowplay.py -> scratch/shadowplay.py
2026-05-29 13:32:32 -04:00
Vignesh Sitaraman 15773d4606 modified: TrackRecon.C
modified:   run_27Al.sh
	modified:   run_tr.sh
2026-05-26 15:43:21 -04:00
Vignesh Sitaraman 07fe2a5d38 modified: TrackRecon.C qqq.event recon typo corrected 2026-05-26 08:55:35 -04:00
Vignesh Sitaraman 3b70b279e7 modified: run_17F.sh
modified:   run_tr.sh
2026-05-20 15:03:46 -04:00
Vignesh Sitaraman f5f7515d7a modified: TrackRecon.C partly cleaned up analysis code
modified:   TrackRecon.h
	new file:   run_tr.sh
	modified:   scratch/sx3z_vs_phiz/scan_offset.C
	modified:   scratch/sx3z_vs_phiz/scan_offset_fix.C
2026-05-20 14:58:36 -04:00
Vignesh Sitaraman 41b286da5d modified: MakeVertex.C
modified:   anasen_fem/run.py
	modified:   anasen_fem/scalars.dat.names
	modified:   run_sx3.sh
	new file:   slope_intercept_results.dat
2026-05-20 09:36:53 -04:00
Vignesh Sitaraman bb05baf89d modified: Armory/ClassPW.h changes made to explain the andoe and cathode shifts in a more intuitive geometric manner
modified:   MakeVertex.C changed code to make nA plots instead of nA0C to add more stats to n anode analysis
	modified:   anasen_fem/paraview_plotter.py changed the analysis to plot every 32nd field line, dure to indreaed field density from the new wire shift
	modified:   anasen_fem/run.py going from 0 to 174.6 in 17.43 increments to account for the remeasured lenth of the pc
	modified:   anasen_fem/scalars.dat.names
	modified:   anasen_fem/wires_gmsh2d_bc.py length of pc changed, 4 wire shift incorporated, set mesh order to 2 to decrease the meashing density
    pccal folder seems to make 17F analysis work, not sure why still trying to figure it out
	new file:   pccal/anode_gainmatch.C
	new file:   pccal/anode_gm_coeffs.dat
	new file:   pccal/cathode_gainmatch.C
	new file:   pccal/cathode_gm_coeffs.dat
	new file:   pccal/pc_gm_coeffs.dat
	new file:   pccal/slope_intercept_26Al.dat
    scratch folder analysis for pcz vs pczfix analysis from sudarsan's branch
	new file:   scratch/sx3z_vs_phiz/scan_offset.C
	new file:   scratch/sx3z_vs_phiz/scan_offset_fix.C
2026-05-19 17:57:54 -04:00
Vignesh Sitaraman d8593b2f55 modified: MakeVertex.C
modified:   anasen_fem/run.py
	modified:   anasen_fem/scalars.dat.names
	modified:   anasen_fem/wires_gmsh2d_bc.py
2026-05-19 10:28:41 -04:00
Vignesh Sitaraman ce0bedf704 deleted: Armory/ClassPC1An.h delete4d duplicate
modified:   Armory/ClassPW.h changes made for 4 wire shift to the anode and cathode starting positions
	modified:   Armory/PC_StepLadder_Correction.h
	modified:   anasen_fem/paraview_plotter.py corrected path for new pc
	modified:   run_17F.sh
	modified:   run_27Al.sh
	modified:   run_sx3.sh
2026-05-18 17:46:20 -04:00
Vignesh Sitaraman b69dcf39d6 modified: MakeVertex.C
modified:   README.md
	modified:   anasen_fem/run.py
	modified:   anasen_fem/scalars.dat.names
	modified:   anasen_fem/wires_gmsh2d_bc.py
	modified:   run_sx3.sh
2026-05-15 18:01:31 -04:00
Vignesh Sitaraman 17f44bc12e modified: README.md
modified:   run_27Al.sh
2026-05-15 14:57:12 -04:00
Vignesh Sitaraman 3503953b0f modified: MakeVertex.C
new file:   README.md WIP but I think this is a good start
	modified:   run_sx3.sh
2026-05-15 14:45:57 -04:00
Vignesh Sitaraman c142f4b567 deleted: Analyzer1.C
deleted:    Analyzer1.h
	deleted:    GainMatchSX3.C
	deleted:    GainMatchSX3.h
	deleted:    GainMatchSX3Front.C
	deleted:    GainMatchSX3Front.h
	deleted:    GainMatchSX3Front1.C
	deleted:    sx3_BackGains.txt
	deleted:    sx3_GainMatchfront.txt
    Book keeping deleting older unused scripts.
2026-05-15 14:02:00 -04:00
Vignesh Sitaraman 574506acb7 modified: Armory/ClassPW.h
modified:   Armory/PC_StepLadder_Correction.h incorp[orating 4 wire twist]
	modified:   MakeVertex.C
2026-05-15 11:14:52 -04:00
Vignesh Sitaraman 419cbd3c8c modified: MakeVertex.C psuedo excitation function for th 27Al using the single anode wire events, DOES NOT WORK YET needs to be fixed
# modified:   run_27Al.sh
2026-05-12 16:37:23 -04:00
Vignesh Sitaraman 5282ef7e1c Merge branch 'devel_vignesh' of https://fsunuc.physics.fsu.edu/git/rtang/ANASEN_analysis into devel_vignesh 2026-05-12 13:40:08 -04:00
Vignesh Sitaraman 1d7b9dde47 modified: MakeVertex.C
modified:   eloss_calculations/Eloss.py making lookup tables for aluminum, fluorine, and oxygen
	new file:   eloss_calculations/aluminum_lookup_80MeV.dat
	new file:   eloss_calculations/fluorine_lookup_70MeV.dat
	new file:   eloss_calculations/oxygen_lookup_70MeV.dat
2026-05-12 13:39:38 -04:00
Vignesh Sitaraman 1081e4b690 modified: MakeVertex.C
modified:   run_27Al.sh
	modified:   run_sx3.sh
2026-05-11 17:23:43 -04:00
Vignesh Sitaraman 4dd157bcb8 modified: Armory/SX3Geom.h
modified:   MakeVertex.C
	modified:   MakeVertex.h
	modified:   run_sx3.sh
	modified:   sx3cal/27Al/backgains.dat
	new file:   sx3cal/27Al/backgains.dat.Apr2026
	modified:   sx3cal/27Al/frontgains.dat.unity
2026-05-11 14:01:17 -04:00
Vignesh Sitaraman 164a3e2e57 Merge branch 'devel_vignesh' of https://fsunuc.physics.fsu.edu/git/rtang/ANASEN_analysis into devel_vignesh 2026-05-11 13:56:53 -04:00
Vignesh Sitaraman 3a42c2d137 modified: Armory/ClassPW.h
modified:   MakeVertex.C
	modified:   run_sx3.sh
2026-05-11 13:56:44 -04:00
Vignesh Sitaraman 2908da4854 modified: .gitignore
new file:   Armory/EventBuilder.cpp
	modified:   MakeVertex.C
	modified:   run_sx3.sh
2026-05-09 17:14:01 -04:00
Vignesh Sitaraman 9d581fa72d new file: anasen_labels.csv
new file:   detector_geometry.dat
	new file:   eloss_calculations/Eloss.py
	new file:   eloss_calculations/alpha_lookup_20MeV.dat
	new file:   eloss_calculations/alpha_lookup_6.0MeV.dat
	new file:   eloss_calculations/make_eloss_table.C
	new file:   eloss_calculations/proton_lookup_20MeV.dat
	new file:   eloss_calculations/proton_lookup_6.0MeV.dat
	new file:   grid_generate.py
	new file:   shadowplay.py
2026-05-08 16:42:36 -04:00
Vignesh Sitaraman c8923ca870 modified: run_sx3.sh 2026-05-05 16:44:40 -04:00
Vignesh Sitaraman d43edcf2ee modified: MakeVertex.C 2026-05-05 16:44:28 -04:00
Vignesh Sitaraman e25a63cd14 deleted: pccal/anode_gainmatch.C
deleted:    pccal/anode_gm_coeffs.dat
	deleted:    pccal/cathode_gainmatch.C
	deleted:    pccal/cathode_gm_coeffs.dat
	deleted:    pccal/slope_intercept_26Al.dat
	deleted:    scratch/sx3z_vs_phiz/scan_offset.C
	deleted:    scratch/sx3z_vs_phiz/scan_offset_fix.C
	deleted:    scratch/sx3z_vs_phiz/stitch.C
	deleted:    scratch/sx3z_vs_phiz/testmodel.h
	deleted:    scratch/test_eloss.h
	renamed:    pccal/pc_gm_coeffs.dat -> slope_intercept_results_17F.dat
2026-05-05 16:41:11 -04:00
Vignesh Sitaraman 3f55a2a81e new file: Armory/Kinematics.cpp
new file:   Armory/Kinematics.h
	new file:   Armory/PC_StepLadder_Correction.h
	modified:   BatchProcess.sh
	deleted:    ELoss/Eloss.py
	deleted:    ELoss/Eloss_17F
	deleted:    ELoss/Eloss_27Al
	deleted:    ELoss/Eloss_alpha
	deleted:    ELoss/Eloss_p
	deleted:    ELoss/alpha_lookup_20MeV.dat
	deleted:    ELoss/alpha_lookup_6.0MeV.dat
	deleted:    ELoss/proton_lookup_20MeV.dat
	deleted:    ELoss/proton_lookup_6.0MeV.dat
	modified:   MakeVertex.C
2026-05-01 12:01:42 -04:00
Vignesh Sitaraman 15e60a0cc1 Merge branch 'devel_vignesh' of https://fsunuc.physics.fsu.edu/git/rtang/ANASEN_analysis into devel_vignesh 2026-04-27 18:24:06 -04:00
Vignesh Sitaraman 4a7dc22acd modified: Armory/ClassDet.h
modified:   BatchProcess.sh
	modified:   MakeVertex.C
	modified:   ProcessRun.sh
	modified:   run_sx3.sh
2026-04-27 18:23:55 -04:00
Vignesh Sitaraman e41f2e14ec modified: .gitignore 2026-04-27 17:59:42 -04:00
Vignesh Sitaraman 40ec71a216 modified: anasen_fem/garfield_sim.py mad
modified:   anasen_fem/run.py
	modified:   anasen_fem/wires_gmsh2d_bc.py
    made changes to both to resolve inconsistency between the mesh generated by GMSH (3nodes) and expected by garfield (10nodes)
2026-04-27 17:59:42 -04:00
Vignesh Sitaraman dccea0c862 modified: anasen_fem/garfield_sim.py
modified:   anasen_fem/paraview_plotter.py
2026-04-27 14:17:09 -04:00
Vignesh Sitaraman fc09dc9258 Merge branch 'devel_vignesh' of https://fsunuc.physics.fsu.edu/git/rtang/ANASEN_analysis into devel_vignesh 2026-04-27 13:57:54 -04:00
Vignesh Sitaraman 16c49c4f12 modified: Armory/ClassDet.h
modified:   Armory/Mapper.cpp
	modified:   mapping.h
    made changes to allow misc det type to be read in
	modified:   ProcessRun.sh added file paths in diferent pcs
	modified:   anasen_fem/garfield_sim.py
2026-04-27 13:52:27 -04:00
Vignesh Sitaraman c3bb28a1a2 modified: Armory/ClassDet.h
modified:   Armory/Mapper.cpp
    made changes to channel map and Detector class to include misc channels( MCP, RF, IC etc) and fine timing information.
2026-04-25 12:34:05 -04:00
Vignesh Sitaraman 307d5dfc44 new file: anasen_fem/dielectrics.dat
modified:   anasen_fem/garfield_sim.py
2026-04-25 12:26:45 -04:00
Vignesh Sitaraman 1a01f332e6 Merge branch 'devel_vignesh' of https://fsunuc.physics.fsu.edu/git/rtang/ANASEN_analysis into devel_vignesh 2026-04-24 14:36:31 -04:00
Vignesh Sitaraman e3bfcda35c modified: anasen_fem/garfield_sim.py changed mesh input for garfield 2026-04-24 14:34:50 -04:00
Vignesh Sitaraman 67bb9b6fd9 modified: .gitignore
modified:   anasen_fem/paraview_plotter.py
	modified:   anasen_fem/wires2d.sif
	modified:   anasen_fem/wires_gmsh2d_bc.py
2026-04-24 14:31:50 -04:00
Vignesh Sitaraman b936da724a modified: .gitignore
modified:   ELoss/Eloss.py  made changes to generate a ELoss lookup table instead of Range and dE/dx
	new file:   ELoss/alpha_lookup_20MeV.dat
	new file:   ELoss/alpha_lookup_6.0MeV.dat
	new file:   ELoss/proton_lookup_20MeV.dat
	new file:   ELoss/proton_lookup_6.0MeV.dat
2026-04-23 17:21:52 -04:00
Vignesh Sitaraman 8e2cdaae58 modified: ELoss/Eloss.py missed a line while editing the file 2026-04-22 14:48:14 -04:00
Vignesh Sitaraman 50a8e03eef modified: .gitignore
new file:   ELoss/Eloss.py made a Eloss calculator using the pyCatima library
	new file:   ELoss/alpha_loss_250torr.dat
	new file:   ELoss/proton_loss_250torr.dat
	modified:   MakeVertex.C
2026-04-22 14:46:48 -04:00
Vignesh Sitaraman 6e0100253b modified: MakeVertex.C
modified:   anasen_fem/garfield_sim.py
	new file:   run_17F.sh
	new file:   run_27Al.sh
	new file:   scratch/test_eloss.h
2026-04-20 11:37:33 -04:00
Vignesh Sitaraman 4c8e3b7519 modified: MakeVertex.C 2026-04-17 12:47:20 -04:00
Vignesh Sitaraman b801421d3c modified: MakeVertex.C changed to Sudarsan's MakeVertex.C
modified:   TrackRecon.C changed to my MakeVertex.C
	modified:   run_sx3.sh
	new file:   scratch/sx3z_vs_phiz/testmodel.h required to run Sudarsan's version of MakeVertex.C
2026-04-15 13:34:14 -04:00
Vignesh Sitaraman 45ab7f9cee new file: anasen_fem/garfield_sim.py
modified:   anasen_fem/run.py
	modified:   anasen_fem/scalars.dat.names
	modified:   anasen_fem/wires_gmsh2d_bc.py
	new file:   run_sx3.sh
2026-04-14 16:05:06 -04:00
Vignesh Sitaraman 3999fd5b71 modified: .gitignore
modified:   anasen_fem/scalars.dat.names
2026-04-02 14:25:13 -04:00
Vignesh Sitaraman e407b9842e modified: anasen_fem/run.py added line sto archive the simulation files to save space.
modified:   anasen_fem/wires_gmsh2d_bc.py made it refine twice instead of doing a weighed refinement to get a faster runtime. The losses in precision aren't too bad.
2026-04-02 14:11:03 -04:00
Vignesh Sitaraman 00c8a9378e modified: anasen_fem/run.py
modified:   anasen_fem/scalars.dat
	modified:   anasen_fem/scalars.dat.names
	modified:   anasen_fem/wires2d.sif
	modified:   anasen_fem/wires_gmsh2d_bc.py included IC and hot needle to simulations
2026-04-01 17:42:53 -04:00
Vignesh Sitaraman 652b680fb9 modified: anasen_fem/paraview_plotter.py made changes to allow field lines to only originate fom equipotential lines. 2026-03-31 17:24:23 -04:00
Vignesh Sitaraman 74dfc46d8c modified: .gitignore
modified:   anasen_fem/paraview_plotter.py included code to make and save field plots
	modified:   anasen_fem/run.py included changes for parapview plotter and also save all outputs for mesh and elmer in folders
	modified:   anasen_fem/scalars.dat.names
	modified:   anasen_fem/wires_gmsh2d_bc.py
2026-03-31 13:34:12 -04:00
Vignesh Sitaraman 5b34251638 modified: anasen_fem/run.py
modified:   anasen_fem/scalars.dat.names
	modified:   anasen_fem/wires_gmsh2d_bc.py
2026-03-31 10:16:28 -04:00
Vignesh Sitaraman 68efdadc8f modified: sx3cal/EXFit.C
modified:   sx3cal/LRFit.C
2026-03-30 09:52:31 -04:00
Vignesh Sitaraman e0f2fa192c modified: MakeVertex.C
modified:   MatchAndPlotCentroids.C
	modified:   anasen_fem/scalars.dat.names
	deleted:    centroids.txt
	renamed:    slope_intercept_cathode.dat -> slope_intercept_cathode_27Al.dat
	renamed:    slope_intercept_results.dat -> slope_intercept_results_27Al.dat
	renamed:    slope_intercept_results_anode.dat -> slope_intercept_results_anode_27Al.dat
2026-03-25 16:50:59 -04:00
Vignesh Sitaraman 456db0a9d0 modified: .gitignore
modified:   anasen_fem/paraview_plotter.py increase line width and resolution for better visualization
	modified:   anasen_fem/run.py
	modified:   anasen_fem/scalars.dat.names
	modified:   anasen_fem/wires2d.sif
	modified:   anasen_fem/wires_gmsh2d_bc.py
2026-03-23 17:48:26 -04:00
Vignesh Sitaraman ec9d25b048 modified: .gitignore
modified:   Armory/ClassPW.h new geo + crossover moved to PW.h
	modified:   MakeVertex.C added plots
	new file:   anasen_fem/README.md
	new file:   anasen_fem/clean.sh
	new file:   anasen_fem/junk/wires.py
	new file:   anasen_fem/junk/wires2d_test.sif
	new file:   anasen_fem/junk/wires_gmsh.py changed wire radius from Sudarsan's version
	new file:   anasen_fem/junk/wires_gmsh_bc.py
	new file:   anasen_fem/paraview_plotter.py
	new file:   anasen_fem/run.py
	new file:   anasen_fem/scalars.dat
	new file:   anasen_fem/scalars.dat.names
	new file:   anasen_fem/wires2d.sif
	new file:   anasen_fem/wires_gmsh2d_bc.py
2026-03-16 14:22:35 -04:00
Vignesh Sitaraman 65af941b39 modified: Armory/ClassPW.h 2026-03-09 14:24:44 -04:00
Vignesh Sitaraman 9af7e70f26 modified: MakeVertex.C 2026-03-06 16:09:28 -05:00
Vignesh Sitaraman 6e6d281dd3 modified: MakeVertex.C hist scale changed 2026-03-06 16:00:27 -05:00
Vignesh Sitaraman 411ef2d9de modified: MakeVertex.C implemented diagnostics to compare the behaviour of singleton cathodes vs events with higher multiplicty 2026-03-06 15:56:48 -05:00
Vignesh Sitaraman cbd4bf42a7 modified: MakeVertex.C 2026-02-26 12:42:02 -05:00
Vignesh Sitaraman 601caa3881 modified: MakeVertex.C 2026-02-24 21:06:24 -05:00
Vignesh Sitaraman 2938411c35 modified: .gitignore
new file:   ELoss/Eloss_17F
	new file:   ELoss/Eloss_27Al
	new file:   ELoss/Eloss_alpha
	new file:   ELoss/Eloss_p
	modified:   MakeVertex.C
	deleted:    MakeVertex.C:Zone.Identifier
	deleted:    MakeVertex.h:Zone.Identifier
2026-02-24 11:59:01 -05:00
Vignesh Sitaraman 8c2657255c modified: Armory/ClassPW.h
new file:   Armory/SX3Geom.h
	modified:   MakeVertex.C
	renamed:    sx3cal/sx3cal/EXFit.C -> sx3cal/EXFit.C
	renamed:    sx3cal/sx3cal/LRFit.C -> sx3cal/LRFit.C
	renamed:    sx3cal/sx3cal/backgains.dat -> sx3cal/backgains.dat
	renamed:    sx3cal/sx3cal/backgains.dat.unity -> sx3cal/backgains.dat.unity
	renamed:    sx3cal/sx3cal/frontgains.dat -> sx3cal/frontgains.dat
	renamed:    sx3cal/sx3cal/frontgains.dat.unity -> sx3cal/frontgains.dat.unity
	renamed:    sx3cal/sx3cal/rightgains.dat -> sx3cal/rightgains.dat
	renamed:    sx3cal/sx3cal/rightgains.dat.unity -> sx3cal/rightgains.dat.unity
2026-02-19 14:49:49 -05:00
Vignesh Sitaraman 00f8460e36 modified: MakeVertex.C
new file:   sx3cal/sx3cal/EXFit.C
	new file:   sx3cal/sx3cal/LRFit.C
	new file:   sx3cal/sx3cal/backgains.dat
	new file:   sx3cal/sx3cal/backgains.dat.unity
	new file:   sx3cal/sx3cal/frontgains.dat
	new file:   sx3cal/sx3cal/frontgains.dat.unity
	new file:   sx3cal/sx3cal/rightgains.dat
	new file:   sx3cal/sx3cal/rightgains.dat.unity
2026-02-18 16:01:55 -05:00
Vignesh Sitaraman 7260d42d8d modified: Armory/ANASEN_model.C
modified:   Armory/ClassPW.h
	modified:   MakeVertex.C
	modified:   TrackRecon.C
2026-02-18 12:06:54 -05:00
Vignesh Sitaraman 4401ae8eb2 new file: MakeVertex.C
new file:   MakeVertex.C:Zone.Identifier
	new file:   MakeVertex.h
	new file:   MakeVertex.h:Zone.Identifier
	modified:   TrackRecon.C
2026-02-11 15:08:11 -05:00
Vignesh Sitaraman 282aa5ecea modified: .gitignore
new file:   Armory/ClassData.h
	new file:   Armory/Hit.h
	modified:   Armory/Makefile
	new file:   Armory/fsuReader.h
	new file:   Armory/macro.h
	new file:   BatchProcess.sh
	modified:   ProcessRun.sh
	modified:   process_mapped_run.sh
2026-02-10 17:01:27 -05:00
Vignesh Sitaraman 9f949edd00 new file: RunTimeSummary.C
new file:   Timing_Summary_Matplotlib.png
	modified:   TrackRecon.C
	modified:   process_mapped_run.sh
2026-02-09 16:11:07 -05:00
Vignesh Sitaraman 56cc900b61 modified: .gitignore
modified:   TrackRecon.C
	deleted:    batchproces_mapped_run.sh
	modified:   process_mapped_run.sh
2026-02-05 15:20:17 -05:00
Vignesh Sitaraman 17ab8c884a modified: TrackRecon.C
new file:   batchproces_mapped_run.sh
	new file:   process_mapped_run.sh
2026-01-30 10:53:34 -05:00
Vignesh Sitaraman 67199cdb60 modified: QQQ_Calcheck.C
modified:   TrackRecon.C
	new file:   qqq_Calib.dat
	new file:   qqq_GainMatch.dat
	renamed:    slope_intercept_cathode.txt -> slope_intercept_cathode.dat
	renamed:    slope_intercept_results.txt -> slope_intercept_results.dat
	renamed:    slope_intercept_results_anode.txt -> slope_intercept_results_anode.dat
2026-01-28 14:27:42 -05:00
Vignesh Sitaraman ac035370b4 modified: QQQ_Calcheck.C made changes to bring it in line with the new consistent Wedge-Ring mapping
modified:   TrackRecon.C
2026-01-25 14:15:45 -05:00
Vignesh Sitaraman c4b543bdeb modified: ProcessRun.sh
modified:   TrackRecon.C
	modified:   mapping.h corrrrected QQQ0 rings
2026-01-25 11:23:27 -05:00
Vignesh Sitaraman 0a8432e4e3 modified: TrackRecon.C phi and theta plots for PC and QQQ included 2026-01-23 17:55:55 -05:00
Vignesh Sitaraman 0883ebdb6e modified: Armory/Makefile
deleted:    Armory/README.md
	modified:   Calibration.C Sudarsan pointed out that the gain match and calibration stages have the ring and wedge swapped, so I fixed that.
	modified:   ProcessRun.sh changes for running on laptop
	modified:   TrackRecon.C same inconsistency as in Calibration.C fixed
	deleted:    makeplots.C not used anymore
	modified:   mapping.h corrected teh mapping for the QQQs poptentially, need to confirm
	modified:   mapping_old.txt
2026-01-22 15:07:10 -05:00
Vignesh Sitaraman 9c20c4abfe modified: TrackRecon.C QQQ wedge channels flipped but the energy gains have not, thus QQQ energy needs to be recalibrated. 2026-01-21 11:56:32 -05:00
Vignesh Sitaraman 13bfafe9c4 modified: Armory/ClassPW.h
modified:   TrackRecon.C
2026-01-18 15:41:54 -05:00
Vignesh Sitaraman 22e32c7ebc Refactor plotting logic in TrackRecon::Process for clarity and added neighbour checks for anode and cathode hits 2026-01-16 16:36:25 -05:00
Vignesh Sitaraman 4599ad2a38 modified: TrackRecon.C 2026-01-13 17:55:27 -05:00
Vignesh Sitaraman c1ffaa8340 modified: TrackRecon.C 2026-01-13 17:51:01 -05:00
Vignesh Sitaraman 19286055ea modified: TrackRecon.C inlcuded timing plots for the PC 2026-01-13 12:04:14 -05:00
Vignesh Sitaraman 82c2127b4d modified: TrackRecon.C included test cases for 1,2,&3 cathode events. Also stopped sorting the cathode anmd anodehits arrays. 2026-01-09 15:49:32 -05:00
Vignesh Sitaraman d81e35d5e4 modified: Analyzer.C
modified:   TrackRecon.C Reconstruction only for the QQQ tracks
	new file:   TrackRecon.h
2025-12-19 11:56:10 -05:00
Vignesh Sitaraman 97880940be modified: .gitignore
modified:   .vscode/settings.json
	modified:   Analyzer.C
	modified:   Calibration.C
	modified:   GainMatchQQQ.C
	modified:   QQQ_Calcheck.C
2025-12-16 15:41:21 -05:00
Vignesh Sitaraman aee3a2467d modified: Calibration.C
modified:   GainMatchQQQ.C
	modified:   QQQ_Calcheck.C
	modified:   makeplots.C
2025-12-01 13:49:23 -05:00
Vignesh Sitaraman c32215e293 modified: .vscode/settings.json
modified:   Calibration.C
	modified:   GainMatchQQQ.C
	new file:   QQQ_Calcheck.C
	new file:   QQQ_Calcheck.h
	new file:   makeplots.C
2025-11-26 11:32:16 -05:00
Vignesh Sitaraman 535afcb704 modified: Calibration.C
modified:   GainMatchQQQ.C
2025-11-17 18:33:11 -05:00
Vignesh Sitaraman 0773b9e6cc modified: .gitignore
modified:   .vscode/settings.json
	renamed:    HistPlotter.h -> Armory/HistPlotter.h
	new file:   Armory/LICENSE
	new file:   Armory/README.md
	modified:   Calibration.C
	modified:   GainMatchQQQ.C
	modified:   GainMatchSX3.C
	modified:   sx3_GainMatchfront.txt
2025-11-17 09:33:08 -05:00
Vignesh Sitaraman 7582731de4 new file: sx3_BackGains.txt
new file:   sx3_GainMatchfront.txt
2025-10-29 17:47:40 -04:00
Vignesh Sitaraman 49de3b64a8 modified: Analyzer.C
modified:   GainMatchSX3.C
	modified:   GainMatchSX3Front.C
	new file:   HistPlotter.h
2025-10-29 17:42:54 -04:00
Vignesh Sitaraman 61473ca14e modified: Calibration.C use both front and back gains
modified:   GainMatchSX3.C changed structure a bit
	modified:   GainMatchSX3Front.C removed some redundant code that I was trying out
2025-10-06 15:11:31 -04:00
Vignesh Sitaraman ecd755e09c modified: Calibration.C looking at 1d hists for sx3 backs 2025-09-30 15:45:16 -04:00
Vignesh Sitaraman 265ebd3372 modified: Calibration.C
modified:   GainMatchQQQ.C
	modified:   GainMatchSX3.C
	modified:   GainMatchSX3Front.C trying out not doing back gainmatching to see if that improves fits.
2025-09-30 15:17:00 -04:00
Vignesh Sitaraman afef56df12 modified: Analyzer.C
modified:   GainMatchSX3.C
2025-09-22 13:31:22 -04:00
Vignesh Sitaraman 579f4e4f6c modified: .vscode/settings.json
modified:   GainMatchSX3.C to make the calib a 2 factor calib insteade of inlcuding the fronts
	modified:   GainMatchSX3Front.C chcanged the readout for the  new back calib
2025-09-18 13:28:02 -04:00
Vignesh Sitaraman d59b22ff78 modified: Calibration.C 2025-09-17 13:42:12 -04:00
Vignesh Sitaraman 49610e9c2f modified: Calibration.C 2025-09-17 13:22:11 -04:00
Vignesh Sitaraman fa4b1dd2f5 new file: Calibration.C
renamed:    GainMatch.h -> Calibration.h
	renamed:    GainMatch.C -> GainMatchQQQ.C
	new file:   GainMatchQQQ.h
2025-09-04 14:56:55 -04:00
Vignesh Sitaraman 877f765357 modified: GainMatchSX3Front.C 2025-08-20 17:09:01 -07:00
Vignesh Sitaraman 06fbc1afd9 modified: GainMatchSX3.C
modified:   GainMatchSX3Front.C
2025-08-20 15:25:40 -07:00
Vignesh Sitaraman b44ffd7fdf modified: .vscode/c_cpp_properties.json made changes to include the laptop
modified:   GainMatchSX3.C included flags to allow interactive mode and verbose fit
	modified:   GainMatchSX3Front.C included flags to allow interactive mode and verbose fit
2025-07-31 12:07:07 -04:00
Vignesh Sitaraman 3d0d176f5a modified: GainMatchSX3.C
modified:   GainMatchSX3Front.C
    changes made to the GainMatchSX3.C and GainMatchSX3Front.C files to include reduced Chisquared and fixed uncertainties for the gain matching process.
2025-07-24 16:12:46 -04:00
Vignesh Sitaraman 4fc05ea338 new file: GainMatchSX3Front.C using a 2 step fit for the same method, going to implement a inverse fit now wherein the fronts are fit first and then the sum is fit against the backs.
new file:   GainMatchSX3Front.h
	new file:   GainMatchSX3Front1.C
2025-07-21 11:19:27 -04:00
Vignesh Sitaraman dd2ec66db1 modified: GainMatchSX3.C multidimfit now runs but does not work as expected,
the fit curve is empty.
2025-07-09 15:30:12 -04:00
Vignesh Sitaraman a8d4e8f0f6 modified: GainMatchSX3.C multidimfit WIP 2025-07-08 13:53:00 -04:00
Vignesh Sitaraman 2864036ec8 fix: correct include path formatting in c_cpp_properties.json 2025-06-10 11:31:08 -04:00
Vignesh Sitaraman 050cb425d5 modified: GainMatchSX3.C 2025-06-10 11:24:01 -04:00
Vignesh Sitaraman fb355a3cc4 modified: GainMatchSX3.C
changes made to GainMatchSX3  when I found a bug in the way the sx3 condition was being checked
2025-06-10 11:18:08 -04:00
Vignesh Sitaraman 5b43d60b30 modified: GainMatchSX3.C changed terminate to accomodate for events which don't have a back but have more than 3 SX3hits 2025-05-27 13:29:01 -04:00
Vignesh Sitaraman e86ab5ed4d modified: GainMatchSX3.C gainmatching of backs fixed. fronts in progress 2025-05-19 13:16:22 -04:00
Vignesh Sitaraman 9cadfdd191 modified: GainMatchSX3.C 2025-05-13 13:32:22 -04:00
Vignesh Sitaraman d4582d80ff modified: GainMatch.C
new file:   GainMatchSX3.C
	new file:   GainMatchSX3.h
2025-05-12 17:21:25 -04:00
Vignesh Sitaraman 18870ed82b modified: GainMatch.C 2025-04-25 16:18:58 -04:00
Vignesh Sitaraman 5c1c5348f4 modified: GainMatch.C QQQs now show up as gain matched. 2025-04-25 15:41:10 -04:00
Vignesh Sitaraman 65ab69ebe6 modified: GainMatch.C 2025-04-24 10:48:14 -04:00
Vignesh Sitaraman 1df7470ca1 modified: .vscode/settings.json 2025-04-10 09:51:36 -04:00
Vignesh Sitaraman a7a765c059 modified: Analyzer.C
new file:   GainMatch.C
	new file:   GainMatch.h
2025-04-10 09:50:54 -04:00
Vignesh Sitaraman 39e8f41ab1 modified: Analyzer.C implemented basic trackreconstruction
modified:   Armory/ANASEN_model.C changed qqq radii
	modified:   Armory/ClassPW.h implemented basic trackreconstruction
2025-02-27 10:34:41 -05:00
Vignesh Sitaraman 9225620426 modified: Analyzer.C
modified:   Armory/ClassPW.h edited to account for the 4 wire offset instead of 3 in cathodes. This has been crossreferenced using the alpha source data in QQQ coinincidence to confirm
    the position of the source.
2025-02-21 15:40:52 -05:00
Vignesh Sitaraman 2225b3a942 modified: .gitignore 2025-02-17 17:06:33 -05:00
Vignesh Sitaraman 6cfb38b564 modified: Analyzer.C 2025-02-17 17:03:58 -05:00
Vignesh Sitaraman cb72c14ca4 modified: Analyzer.C modfied the algorithm for selection of valid cathodes, the section is now based on geometry
modified:   Armory/ClassPW.h the rotation of the cathodes was reversed
2025-02-17 16:59:08 -05:00
Vignesh Sitaraman 5995081396 modified: Analyzer.C
modified:   Armory/ClassPW.h
    adjusted the Cathode geometry to  incorporate the offsett of 3 wires
2025-02-05 14:59:02 -05:00
Vignesh Sitaraman d623e0cd17 modified: Analyzer.C
made changes to correct for geomtry and eliminating redundant variables to a make the  code more readable
	modified:   Armory/ClassPW.h
    made changes to correct for the fact that the physical geometry of the detector is not the same as the geometry in the simulation, it is reversed and has an offset of 3
2025-02-05 08:23:23 -05:00
Vignesh Sitaraman 8d7322cf5a modified: Analyzer.C 2025-02-03 10:32:07 -05:00
Vignesh Sitaraman 699b0f8701 modified: Analyzer.C
HPZProjection implemented, testing seems to result in a plot with 0s. Need to debug
2025-01-31 09:25:37 -05:00
Vignesh Sitaraman 02213caaee modified: Analyzer.C
implementation of function to fgiure out the cqreelated cathodes in events
2025-01-28 10:19:14 -05:00
Vignesh Sitaraman 56a6389b4f modified: Analyzer.C
modified the analyser to include gain matching for the anodes and the cathodes
	modified:   FitHistogramsWithTSpectrum_Sequential_Improved.C
	modified:   MatchAndPlotCentroids.C
	modified:   centroids.txt
	modified:   slope_intercept_results.txt
2025-01-27 16:34:39 -05:00
Vignesh Sitaraman b99ad4e4d7 new file: FitHistogramsWithTSpectrum_Sequential_Improved.C
new file:   MatchAndPlotCentroids.C
	new file:   centroids.txt
	new file:   centroids_edited.txt
	new file:   slope_intercept_cathode.txt
	new file:   slope_intercept_results.txt
	new file:   slope_intercept_results_anode.txt
2025-01-27 15:11:27 -05:00
Vignesh Sitaraman a5dfa2ecd3 modified: .vscode/settings.json
modified:   Analyzer.C
	modified:   Analyzer.h
	modified:   ProcessRun.sh
2025-01-27 10:55:22 -05:00
Vignesh Sitaraman 26e943adc8 modified: .vscode/settings.json
modified:   Analyzer.C
2025-01-27 09:51:25 -05:00
vs19g 42e093b104 modified: .vscode/c_cpp_properties.json
modified:   .vscode/settings.json
	modified:   Analyzer.C
	new file:   Armory/#ClassPW.h#
	modified:   Armory/ClassDet.h
	modified:   Armory/ClassPW.h
	modified:   Armory/Mapper.cpp
2025-01-17 11:18:07 -05:00
dirac 3129339647 modified: Analyzer.C
modified:   ProcessRun.sh
	modified:   mapping.h
2024-11-05 08:48:21 -05:00
dirac 7a70340b18 modified: Analyzer.C
modified:   mapping.h
2024-10-30 09:28:01 -04:00
dirac a10081ea81 modified: .vscode/settings.json
modified:   Analyzer.C
	modified:   Analyzer.h
	modified:   Armory/ClassDet.h
	modified:   Armory/ClassPW.h
	modified:   Armory/Mapper.cpp
	modified:   PCGainMatch.C
	modified:   ProcessRun.sh
	modified:   mapping.h
2024-10-25 15:02:59 -04:00
vs19g 4cb8a2c48c new file: Analyzer1.C
new file:   Analyzer1.h
2024-10-04 12:43:24 -04:00
vs19g fe6dbee171 Refactor PCGainMatch.C and Analyzer.C for improved efficiency and readability 2024-10-01 14:13:53 -04:00
vs19g 7805481ead Refactor PCGainMatch.C and Analyzer.C for improved efficiency and readability 2024-09-16 10:25:13 -04:00
vs19g 511b4aa808 modified: PCGainMatch.C 2024-09-06 15:06:32 -04:00
vs19g 43233ceb02 modified: PCGainMatch.C 2024-09-06 14:32:22 -04:00
vs19g 68fc36a8f6 modified: PCGainMatch.C 2024-09-03 16:41:17 -04:00
vs19g a6e754b958 modified: Analyzer.C
new file:   PCGainMatch.C
	new file:   PCGainMatch.h
2024-08-28 10:49:36 -04:00
vs19g 48ede97992 modified: Analyzer.C implemented cuts and histograms for the 2D histograms 2024-08-26 11:41:06 -04:00
vs19g f0a393abe2 modified: Analyzer.C 2024-08-26 10:34:13 -04:00
vs19g 4ba9c73b98 modified: Analyzer.C 2024-08-26 10:33:29 -04:00
vs19g 238ec8961e modified: Analyzer.C 2024-08-26 10:33:29 -04:00
211 changed files with 579607 additions and 5887 deletions

34
.gitignore vendored
View File

@ -1,13 +1,41 @@
EventBuilder*
*.d
*.so
*.pcm
*.root
*.exe
*.txt
*.err
*.seq
*.png
*.gif
*.msh
*.vtk
Mapper
AnasenMS
data/
data_proton/
root_data/
Sudarshan/
wires2d/
myenv/
Analyzer_C_ACLiC_dict0713aaa966_dictContent.h
.gitignore
Analyzer_C_ACLiC_dict5411fecd5c_dictUmbrella.h
gainmatch.C
gainmatch.h
MakePlotsQQQ.C
MakePlotsQQQ.h
MakePlotsSX3.C
MakePlotsSX3.h
Armory/CorrelateQQQ.h
QQQStage2.C
anasen_fem/scalars.dat.names
anasen_fem/He96_CO2_4_260Torr.gas
anasen_fem/heavy_ion_track.csv
Armory/EventBuilder
EventBuilder
anasen_fem/anode_to_cathode_1d_17.43.csv
pc_calib_raw/
AutoFit.C
scratch/images/WedgeChVAnode.jpeg
cross_section/out/

View File

@ -59,7 +59,20 @@
"includePath": [
"${workspaceFolder}/**",
"/usr/include/x86_64-linux-gnu/qt6/**",
"/usr/local/cern/root/include/**",
"/usr/local/cern/root/include/**"
],
"defines": [],
"compilerPath": "/usr/bin/gcc",
"cStandard": "c17",
"cppStandard": "gnu++17",
"intelliSenseMode": "linux-gcc-x64"
},
{
"name": "VigneshROG",
"includePath": [
"/home/vsitaraman/**",
"${workspaceFolder}/**",
"/home/vsitaraman/root/include/**"
],
"defines": [],
"compilerPath": "/usr/bin/gcc",

29
.vscode/settings.json vendored
View File

@ -100,7 +100,32 @@
"PCPulser_All_new.C": "cpp",
"PosCal_2.C": "cpp",
"AutoFit.C": "cpp",
"Fitting.C": "cpp"
"Fitting.C": "cpp",
"PCGainMatch.C": "cpp",
"Analyzer1.C": "cpp",
"FitHistogramsWithTSpectrum_Sequential_Improved.C": "cpp",
"PlotAndFitCentroids.C": "cpp",
"MatchAndPlotCentroids.C": "cpp",
"GainMatch.C": "cpp",
"GainMatchSX3.C": "cpp",
"RelBack_Fix_new.C": "cpp",
"SiRelativeGains_Step1_new.C": "cpp",
"charconv": "cpp",
"format": "cpp",
"GainMatchSX3Front.C": "cpp",
"GainMatchSX3Front1.C": "cpp",
"Calibration.C": "cpp",
"GainMatchQQQ.C": "cpp",
"UTF-8gainmatch.C": "cpp",
"MakePlotsQQQ.C": "cpp",
"MakePlotsSX3.C": "cpp",
"QQQ_Calibcheck.C": "cpp",
"QQQ_Calcheck.C": "cpp",
"makeplots.C": "cpp",
"GlobalMinimizeQQQ.C": "cpp",
"QQQStage2.C": "cpp",
"inspect.C": "cpp"
},
"github-enterprise.uri": "https://fsunuc.physics.fsu.edu"
"github-enterprise.uri": "https://fsunuc.physics.fsu.edu",
"C_Cpp.default.compilerPath": "/usr/bin/gcc"
}

View File

@ -1,20 +0,0 @@
#include "TChain.h"
#include <iostream>
void Analysis(int start, int end) {
// Create a TChain
TChain *chain = new TChain("tree");
for(int i = start; i < end+1; i++) {
chain->Add(Form("data/root_data/Run_%03d_mapped.root", i));
}
// Process the chain using Analyzer.C+
chain->Process("Analyzer.C+");
}
// Define a macro with the same name as the script
void Analysis() {
Analysis(150, 194); // Adjust the range if needed
}

View File

@ -1,493 +0,0 @@
#define Analyzer_cxx
#include "Analyzer.h"
#include <TH2.h>
#include <TStyle.h>
#include <TCanvas.h>
#include <TMath.h>
#include <utility>
#include <algorithm>
#include "Armory/ClassSX3.h"
#include "Armory/ClassPW.h"
#include "TVector3.h"
TH2F *hsx3IndexVE;
TH2F *hqqqIndexVE;
TH2F *hpcIndexVE;
TH2F *hsx3Coin;
TH2F *hqqqCoin;
TH2F *hpcCoin;
TH2F *hqqqPolar;
TH2F *hsx3VpcIndex;
TH2F *hqqqVpcIndex;
TH2F *hqqqVpcE;
TH2F *hsx3VpcE;
TH2F *hanVScatsum;
int padID = 0;
SX3 sx3_contr;
PW pw_contr;
PW pwinstance;
TVector3 hitPos;
bool HitNonZero;
TH1F *hZProj;
void Analyzer::Begin(TTree * /*tree*/)
{
TString option = GetOption();
hsx3IndexVE = new TH2F("hsx3IndexVE", "SX3 index vs Energy; sx3 index ; Energy", 24 * 12, 0, 24 * 12, 400, 0, 5000);
hsx3IndexVE->SetNdivisions(-612, "x");
hqqqIndexVE = new TH2F("hqqqIndexVE", "QQQ index vs Energy; QQQ index ; Energy", 4 * 2 * 16, 0, 4 * 2 * 16, 400, 0, 5000);
hqqqIndexVE->SetNdivisions(-1204, "x");
hpcIndexVE = new TH2F("hpcIndexVE", "PC index vs Energy; PC index ; Energy", 2 * 24, 0, 2 * 24, 400, 0, 4000);
hpcIndexVE->SetNdivisions(-1204, "x");
hsx3Coin = new TH2F("hsx3Coin", "SX3 Coincident", 24 * 12, 0, 24 * 12, 24 * 12, 0, 24 * 12);
hqqqCoin = new TH2F("hqqqCoin", "QQQ Coincident", 4 * 2 * 16, 0, 4 * 2 * 16, 4 * 2 * 16, 0, 4 * 2 * 16);
hpcCoin = new TH2F("hpcCoin", "PC Coincident", 2 * 24, 0, 2 * 24, 2 * 24, 0, 2 * 24);
hqqqPolar = new TH2F("hqqqPolar", "QQQ Polar ID", 16 * 4, -TMath::Pi(), TMath::Pi(), 16, 10, 50);
hsx3VpcIndex = new TH2F("hsx3Vpcindex", "sx3 vs pc; sx3 index; pc index", 24 * 12, 0, 24 * 12, 48, 0, 48);
hsx3VpcIndex->SetNdivisions(-612, "x");
hsx3VpcIndex->SetNdivisions(-12, "y");
hqqqVpcIndex = new TH2F("hqqqVpcindex", "qqq vs pc; qqq index; pc index", 4 * 2 * 16, 0, 4 * 2 * 16, 48, 0, 48);
hqqqVpcIndex->SetNdivisions(-612, "x");
hqqqVpcIndex->SetNdivisions(-12, "y");
hqqqVpcE = new TH2F("hqqqVpcEnergy", "qqq vs pc; qqq energy; pc energy", 400, 0, 5000, 800, 0, 16000);
hqqqVpcE->SetNdivisions(-612, "x");
hqqqVpcE->SetNdivisions(-12, "y");
hsx3VpcE = new TH2F("hsx3VpcEnergy", "sx3 vs pc; sx3 energy; pc energy", 400, 0, 5000, 800, 0, 16000);
hsx3VpcE->SetNdivisions(-612, "x");
hsx3VpcE->SetNdivisions(-12, "y");
hZProj = new TH1F("hZProj", "Z Projection", 200, -600, 600);
hanVScatsum = new TH2F("hanVScatsum", "Anode vs Cathode Sum; Anode E; Cathode E", 400, 0, 10000, 400, 0, 16000);
sx3_contr.ConstructGeo();
pw_contr.ConstructGeo();
}
Bool_t Analyzer::Process(Long64_t entry)
{
// if ( entry > 100 ) return kTRUE;
hitPos.Clear();
HitNonZero = false;
// if( entry > 1) return kTRUE;
// printf("################### ev : %llu \n", entry);
b_sx3Multi->GetEntry(entry);
b_sx3ID->GetEntry(entry);
b_sx3Ch->GetEntry(entry);
b_sx3E->GetEntry(entry);
b_sx3T->GetEntry(entry);
b_qqqMulti->GetEntry(entry);
b_qqqID->GetEntry(entry);
b_qqqCh->GetEntry(entry);
b_qqqE->GetEntry(entry);
b_qqqT->GetEntry(entry);
b_pcMulti->GetEntry(entry);
b_pcID->GetEntry(entry);
b_pcCh->GetEntry(entry);
b_pcE->GetEntry(entry);
b_pcT->GetEntry(entry);
sx3.CalIndex();
qqq.CalIndex();
pc.CalIndex();
// sx3.Print();
// ########################################################### Raw data
// //======================= SX3
std::vector<std::pair<int, int>> ID; // first = id, 2nd = index
for (int i = 0; i < sx3.multi; i++)
{
ID.push_back(std::pair<int, int>(sx3.id[i], i));
hsx3IndexVE->Fill(sx3.index[i], sx3.e[i]);
for (int j = i + 1; j < sx3.multi; j++)
{
hsx3Coin->Fill(sx3.index[i], sx3.index[j]);
}
for (int j = 0; j < pc.multi; j++)
{
hsx3VpcIndex->Fill(sx3.index[i], pc.index[j]);
// if( sx3.ch[index] > 8 ){
// hsx3VpcE->Fill( sx3.e[i], pc.e[j] );
// }
}
}
if (ID.size() > 0)
{
std::sort(ID.begin(), ID.end(), [](const std::pair<int, int> &a, const std::pair<int, int> &b)
{ return a.first < b.first; });
// printf("##############################\n");
// for( size_t i = 0; i < ID.size(); i++) printf("%zu | %d %d \n", i, ID[i].first, ID[i].second );
std::vector<std::pair<int, int>> sx3ID;
sx3ID.push_back(ID[0]);
bool found = false;
for (size_t i = 1; i < ID.size(); i++)
{
if (ID[i].first == sx3ID.back().first)
{
sx3ID.push_back(ID[i]);
if (sx3ID.size() >= 3)
{
found = true;
}
}
else
{
if (!found)
{
sx3ID.clear();
sx3ID.push_back(ID[i]);
}
}
}
// printf("---------- sx3ID Multi : %zu \n", sx3ID.size());
if (found)
{
int sx3ChUp, sx3ChDn, sx3ChBk;
float sx3EUp, sx3EDn;
// printf("------ sx3 ID : %d, multi: %zu\n", sx3ID[0].first, sx3ID.size());
for (size_t i = 0; i < sx3ID.size(); i++)
{
int index = sx3ID[i].second;
// printf(" %zu | index %d | ch : %d, energy : %d \n", i, index, sx3.ch[index], sx3.e[index]);
if (sx3.ch[index] < 8)
{
if (sx3.ch[index] % 2 == 0)
{
sx3ChDn = sx3.ch[index];
sx3EDn = sx3.e[index];
}
else
{
sx3ChUp = sx3.ch[index];
sx3EUp = sx3.e[index];
}
}
else
{
sx3ChBk = sx3.ch[index];
}
for (int j = 0; j < pc.multi; j++)
{
// hsx3VpcIndex->Fill( sx3.index[i], pc.index[j] );
if (sx3.ch[index] > 8)
{
hsx3VpcE->Fill(sx3.e[i], pc.e[j]);
// hpcIndexVE->Fill( pc.index[i], pc.e[i] );
}
}
}
sx3_contr.CalSX3Pos(sx3ID[0].first, sx3ChUp, sx3ChDn, sx3ChBk, sx3EUp, sx3EDn);
hitPos = sx3_contr.GetHitPos();
HitNonZero = true;
// hitPos.Print();
}
}
// //======================= QQQ
for (int i = 0; i < qqq.multi; i++)
{
// for( int j = 0; j < pc.multi; j++){
// if(pc.index[j]==4){
hqqqIndexVE->Fill(qqq.index[i], qqq.e[i]);
// }
// }
for (int j = 0; j < qqq.multi; j++)
{
if (j == i)
continue;
hqqqCoin->Fill(qqq.index[i], qqq.index[j]);
}
for (int j = i + 1; j < qqq.multi; j++)
{
for (int k = 0; k < pc.multi; k++)
{
if (pc.index[k] < 24 && pc.e[k] > 50)
{
hqqqVpcE->Fill(qqq.e[i], pc.e[k]);
// hpcIndexVE->Fill( pc.index[i], pc.e[i] );
hqqqVpcIndex->Fill(qqq.index[i], pc.index[j]);
}
// }
}
// if( qqq.used[i] == true ) continue;
// if( qqq.id[i] == qqq.id[j] && (16 - qqq.ch[i]) * (16 - qqq.ch[j]) < 0 ){ // must be same detector and wedge and ring
if (qqq.id[i] == qqq.id[j])
{ // must be same detector
int chWedge = -1;
int chRing = -1;
if (qqq.ch[i] < qqq.ch[j])
{
chRing = qqq.ch[j] - 16;
chWedge = qqq.ch[i];
}
else
{
chRing = qqq.ch[i];
chWedge = qqq.ch[j] - 16;
}
// printf(" ID : %d , chWedge : %d, chRing : %d \n", qqq.id[i], chWedge, chRing);
double theta = -TMath::Pi() / 2 + 2 * TMath::Pi() / 16 / 4. * (qqq.id[i] * 16 + chWedge + 0.5);
double rho = 10. + 40. / 16. * (chRing + 0.5);
// if(qqq.e[i]>50){
hqqqPolar->Fill(theta, rho);
// }
// qqq.used[i] = true;
// qqq.used[j] = true;
if (!HitNonZero)
{
double x = rho * TMath::Cos(theta);
double y = rho * TMath::Sin(theta);
hitPos.SetXYZ(x, y, 23 + 75 + 30);
HitNonZero = true;
}
}
}
}
// //======================= PC
ID.clear();
int counter = 0;
std::vector<std::pair<int, double>> E;
E.clear();
for (int i = 0; i < pc.multi; i++)
{
if (pc.e[i] > 100)
ID.push_back(std::pair<int, int>(pc.id[i], i));
if (pc.e[i] > 100)
E.push_back(std::pair<int, double>(pc.index[i], pc.e[i]));
hpcIndexVE->Fill(pc.index[i], pc.e[i]);
for (int j = i + 1; j < pc.multi; j++)
{
hpcCoin->Fill(pc.index[i], pc.index[j]);
}
}
// for( size_t i = 0; i < E.size(); i++) printf("%zu | %d %d \n", i, E[i].first, E[i].second );
pwinstance.ConstructGeo();
Coord Crossover[24][24];
TVector3 a, c, diff;
double a2, ac, c2, adiff, cdiff, denom, alpha, beta;
int index = 0;
for (int i = 0; i < pwinstance.An.size(); i++)
{
a = pwinstance.An[i].first - pwinstance.An[i].second;
for (int j = 0; j < pwinstance.Ca.size(); j++)
{
c = pwinstance.Ca[j].first - pwinstance.Ca[j].second;
diff = pwinstance.An[i].first - pwinstance.Ca[j].first;
a2 = a.Dot(a);
ac = a.Dot(c);
c2 = c.Dot(c);
adiff = a.Dot(diff);
cdiff = c.Dot(diff);
denom = a2 * c2 - ac * ac;
alpha = (ac * cdiff - c2 * adiff) / denom;
beta = (a2 * cdiff - ac * adiff) / denom;
Crossover[i][j].x = pwinstance.An[i].first.X() + alpha * a.X();
Crossover[i][j].y = pwinstance.An[i].first.Y() + alpha * a.Y();
Crossover[i][j].z = pwinstance.An[i].first.Z() + alpha * a.Z();
if (i == 23)
{
if (abs(i - j) < 7 || abs(i - j) > 17)
{
if (alpha < 0 && alpha > -1)
{
printf("Anode and cathode indices and coord : %d %d %f %f %f %f\n", i, j, pwinstance.Ca[j].first.X(), pwinstance.Ca[j].first.Y(), pwinstance.Ca[j].first.Z(), alpha);
printf("Crossover wires, points and alpha are : %f %f %f %f \n", Crossover[i][j].x, Crossover[i][j].y, Crossover[i][j].z, alpha);
}
}
}
}
}
if (E.size() >= 3)
{
int aID = 0;
int cID = 0;
float aE = 0;
float cE = 0;
// if( ID[0].first < 1 ) {
// aID = pc.ch[ID[0].second];
// cID = pc.ch[ID[1].second];
// }else{
// cID = pc.ch[ID[0].second];
// aID = pc.ch[ID[1].second];
// }
// printf("anode= %d, cathode = %d\n", aID, cID);
for (int k = 0; k < qqq.multi; k++)
{
if (qqq.index[k] == 75 && pc.index[k] == 2 && pc.e[k] > 100)
{
int multi_an = 0;
for (int l = 0; l < E.size(); l++)
{
if (E[l].first < 24)
{
multi_an++;
}
}
if (multi_an >= 1)
{
for (int l = 0; l < E.size(); l++)
{
if (E[l].first < 24 && E[l].first != 19 && E[l].first != 12)
{
aE = E[l].second;
}
else if (E[l].first > 24)
{
cE = E[l].second;
}
}
}
}
}
hanVScatsum->Fill(aE, cE);
if (ID[0].first < 1)
{
aID = pc.ch[ID[0].second];
cID = pc.ch[ID[1].second];
}
else
{
cID = pc.ch[ID[0].second];
aID = pc.ch[ID[1].second];
}
if (HitNonZero)
{
pw_contr.CalTrack(hitPos, aID, cID);
hZProj->Fill(pw_contr.GetZ0());
}
}
// ########################################################### Track constrcution
// ############################## DO THE KINEMATICS
return kTRUE;
}
void Analyzer::Terminate()
{
gStyle->SetOptStat("neiou");
TCanvas *canvas = new TCanvas("cANASEN", "ANASEN", 2000, 2000);
canvas->Divide(3, 3);
// hsx3VpcIndex->Draw("colz");
//=============================================== pad-1
padID++;
canvas->cd(padID);
canvas->cd(padID)->SetGrid(1);
hsx3IndexVE->Draw("colz");
//=============================================== pad-2
padID++;
canvas->cd(padID);
canvas->cd(padID)->SetGrid(1);
hqqqIndexVE->Draw("colz");
//=============================================== pad-3
padID++;
canvas->cd(padID);
canvas->cd(padID)->SetGrid(1);
hpcIndexVE->Draw("colz");
//=============================================== pad-4
padID++;
canvas->cd(padID);
canvas->cd(padID)->SetGrid(1);
hsx3Coin->Draw("colz");
//=============================================== pad-5
padID++;
canvas->cd(padID);
canvas->cd(padID)->SetGrid(1);
canvas->cd(padID)->SetLogz(true);
hqqqCoin->Draw("colz");
//=============================================== pad-6
padID++;
canvas->cd(padID);
canvas->cd(padID)->SetGrid(1);
hpcCoin->Draw("colz");
//=============================================== pad-7
padID++;
canvas->cd(padID);
canvas->cd(padID)->SetGrid(1);
// hsx3VpcIndex ->Draw("colz");
hsx3VpcE->Draw("colz");
//=============================================== pad-8
padID++;
canvas->cd(padID);
canvas->cd(padID)->SetGrid(1);
// hqqqVpcIndex ->Draw("colz");
hqqqVpcE->Draw("colz");
//=============================================== pad-9
padID++;
// canvas->cd(padID)->DrawFrame(-50, -50, 50, 50);
// hqqqPolar->Draw("same colz pol");
canvas->cd(padID);
canvas->cd(padID)->SetGrid(1);
// hZProj->Draw();
hanVScatsum->Draw("colz");
}

View File

@ -1,283 +0,0 @@
#ifndef ClassPW_h
#define ClassPW_h
#include <cstdio>
#include <TMath.h>
#include <TVector3.h>
struct PWHitInfo{
std::pair<short, short> nearestWire; // anode, cathode
std::pair<double, double> nearestDist; // anode, cathode
std::pair<short, short> nextNearestWire; // anode, cathode
std::pair<double, double> nextNearestDist; // anode, cathode
void Clear(){
nearestWire.first = -1;
nearestWire.second = -1;
nearestDist.first = 999999999;
nearestDist.second = 999999999;
nextNearestWire.first = -1;
nextNearestWire.second = -1;
nextNearestDist.first = 999999999;
nextNearestDist.second = 999999999;
}
};
//!########################################################
class PW{ // proportional wire
public:
PW(){ ClearHitInfo();};
~PW(){};
PWHitInfo GetHitInfo() const {return hitInfo;}
std::pair<short, short> GetNearestID() const {return hitInfo.nearestWire;}
std::pair<double, double> GetNearestDistance() const {return hitInfo.nearestDist;}
std::pair<short, short> Get2ndNearestID() const {return hitInfo.nextNearestWire;}
std::pair<double, double> Get2ndNearestDistance() const {return hitInfo.nextNearestDist;}
TVector3 GetTrackPos() const {return trackPos;}
TVector3 GetTrackVec() const {return trackVec;}
double GetTrackTheta() const {return trackVec.Theta();}
double GetTrackPhi() const {return trackVec.Phi();}
double GetZ0();
int GetNumWire() const {return nWire;}
double GetDeltaAngle() const {return dAngle;}
double GetAnodeLength() const {return anodeLength;}
double GetCathodeLength() const {return cathodeLength;}
TVector3 GetAnodeDn(short id) const {return An[id].first;}
TVector3 GetAnodeUp(short id) const {return An[id].second;}
TVector3 GetCathodeDn(short id) const {return Ca[id].first;}
TVector3 GetCathodeUp(short id) const {return Ca[id].second;}
TVector3 GetAnodneMid(short id) const {return (An[id].first + An[id].second) * 0.5; }
double GetAnodeTheta(short id) const {return (An[id].first - An[id].second).Theta();}
double GetAnodePhi(short id) const {return (An[id].first - An[id].second).Phi();}
TVector3 GetCathodneMid(short id) const {return (Ca[id].first + Ca[id].second) * 0.5; }
double GetCathodeTheta(short id) const {return (Ca[id].first - Ca[id].second).Theta();}
double GetCathodePhi(short id) const {return (Ca[id].first - Ca[id].second).Phi();}
void ClearHitInfo();
void ConstructGeo();
void FindWireID(TVector3 pos, TVector3 direction, bool verbose = false);
void CalTrack(TVector3 sx3Pos, int anodeID, int cathodeID, bool verbose = false);
void CalTrack2(TVector3 sx3Pos, PWHitInfo hitInfo, double sigmaA = 0, double sigmaC = 0, bool verbose = false);
void Print(){
printf(" The nearest | Anode: %2d(%5.2f) Cathode: %2d(%5.2f)\n", hitInfo.nearestWire.first,
hitInfo.nearestDist.first,
hitInfo.nearestWire.second,
hitInfo.nearestDist.second);
printf(" The 2nd nearest | Anode: %2d(%5.2f) Cathode: %2d(%5.2f)\n", hitInfo.nextNearestWire.first,
hitInfo.nextNearestDist.first,
hitInfo.nextNearestWire.second,
hitInfo.nextNearestDist.second);
}
private:
PWHitInfo hitInfo;
TVector3 trackPos;
TVector3 trackVec;
const int nWire = 24;
const int wireShift = 3;
const float zLen = 380; //mm
const float radiusA = 37;
const float radiusC = 43;
double dAngle;
double anodeLength;
double cathodeLength;
std::vector<std::pair<TVector3,TVector3>> An; // the anode wire position vector in space
std::vector<std::pair<TVector3,TVector3>> Ca; // the cathode wire position vector in space
double Distance(TVector3 a1, TVector3 a2, TVector3 b1, TVector3 b2){
TVector3 na = a1 - a2;
TVector3 nb = b1 - b2;
TVector3 nd = (na.Cross(nb)).Unit();
return TMath::Abs(nd.Dot(a1-b2));
}
};
inline void PW::ClearHitInfo(){
hitInfo.Clear();
}
inline void PW::ConstructGeo(){
An.clear();
Ca.clear();
std::pair<TVector3, TVector3> p1; // anode
std::pair<TVector3, TVector3> q1; // cathode
//anode and cathode start at pos-Y axis and count in right-Hand
//anode wire shift is right-hand.
//cathode wire shift is left-hand.
for(int i = 0; i < nWire; i++ ){
// Anode rotate right-hand
p1.first.SetXYZ( radiusA * TMath::Cos( TMath::TwoPi() / nWire * (i) + TMath::PiOver2()),
radiusA * TMath::Sin( TMath::TwoPi() / nWire * (i) + TMath::PiOver2()),
zLen/2);
p1.second.SetXYZ( radiusA * TMath::Cos( TMath::TwoPi() / nWire * (i + wireShift) + TMath::PiOver2()),
radiusA * TMath::Sin( TMath::TwoPi() / nWire * (i + wireShift) + TMath::PiOver2()),
-zLen/2);
An.push_back(p1);
// Cathod rotate left-hand
q1.first.SetXYZ( radiusC * TMath::Cos( TMath::TwoPi() / nWire * (i) + TMath::PiOver2()),
radiusC * TMath::Sin( TMath::TwoPi() / nWire * (i) + TMath::PiOver2()),
zLen/2);
q1.second.SetXYZ( radiusC * TMath::Cos( TMath::TwoPi() / nWire * (i - wireShift) + TMath::PiOver2()),
radiusC * TMath::Sin( TMath::TwoPi() / nWire * (i - wireShift) + TMath::PiOver2()),
-zLen/2);
Ca.push_back(q1);
}
dAngle = wireShift * TMath::TwoPi() / nWire;
anodeLength = TMath::Sqrt( zLen*zLen + TMath::Power(2* radiusA * TMath::Sin(dAngle/2),2) );
cathodeLength = TMath::Sqrt( zLen*zLen + TMath::Power(2* radiusC * TMath::Sin(dAngle/2),2) );
}
inline void PW::FindWireID(TVector3 pos, TVector3 direction, bool verbose ){
hitInfo.Clear();
double phi = direction.Phi();
for( int i = 0; i < nWire; i++){
double disA = 99999999;
double phiS = An[i].first.Phi() - TMath::PiOver4();
double phiL = An[i].second.Phi() + TMath::PiOver4();
// printf("A%2d: %f %f | %f\n", i, phiS * TMath::RadToDeg(), phiL * TMath::RadToDeg(), phi * TMath::RadToDeg());
if( phi > 0 && phiS > phiL ) phiL = phiL + TMath::TwoPi();
if( phi < 0 && phiS > phiL ) phiS = phiS - TMath::TwoPi();
if( phiS < phi && phi < phiL) {
disA = Distance( pos, pos + direction, An[i].first, An[i].second);
if( disA < hitInfo.nearestDist.first ){
hitInfo.nearestDist.first = disA;
hitInfo.nearestWire.first = i;
}
}
double disC = 99999999;
phiS = Ca[i].second.Phi()- TMath::PiOver4();
phiL = Ca[i].first.Phi() + TMath::PiOver4();
// printf("C%2d: %f %f\n", i, phiS * TMath::RadToDeg(), phiL * TMath::RadToDeg());
if( phi > 0 && phiS > phiL ) phiL = phiL + TMath::TwoPi();
if( phi < 0 && phiS > phiL ) phiS = phiS - TMath::TwoPi();
if(phiS < phi && phi < phiL) {
disC = Distance( pos, pos + direction, Ca[i].first, Ca[i].second);
if( disC < hitInfo.nearestDist.second ){
hitInfo.nearestDist.second = disC;
hitInfo.nearestWire.second = i;
}
}
if(verbose) printf(" %2d | %8.2f, %8.2f\n", i, disA, disC);
}
//==== find the 2nd nearest wire
short anode1 = hitInfo.nearestWire.first;
short aaa1 = anode1 - 1; if( aaa1 < 0 ) aaa1 += nWire;
short aaa2 = (anode1 + 1) % nWire;
double haha1 = Distance( pos, pos + direction, An[aaa1].first, An[aaa1].second);
double haha2 = Distance( pos, pos + direction, An[aaa2].first, An[aaa2].second);
if( haha1 < haha2){
hitInfo.nextNearestWire.first = aaa1;
hitInfo.nextNearestDist.first = haha1;
}else{
hitInfo.nextNearestWire.first = aaa2;
hitInfo.nextNearestDist.first = haha2;
}
short cathode1 = hitInfo.nearestWire.second;
short ccc1 = cathode1 - 1; if( ccc1 < 0 ) ccc1 += nWire;
short ccc2 = (cathode1 + 1) % nWire;
haha1 = Distance( pos, pos + direction, Ca[ccc1].first, Ca[ccc1].second);
haha2 = Distance( pos, pos + direction, Ca[ccc2].first, Ca[ccc2].second);
if( haha1 < haha2){
hitInfo.nextNearestWire.second = ccc1;
hitInfo.nextNearestDist.second = haha1;
}else{
hitInfo.nextNearestWire.second = ccc2;
hitInfo.nextNearestDist.second = haha2;
}
if( verbose ) Print();
}
inline void PW::CalTrack(TVector3 sx3Pos, int anodeID, int cathodeID, bool verbose){
trackPos = sx3Pos;
TVector3 n1 = (An[anodeID].first - An[anodeID].second).Cross((sx3Pos - An[anodeID].second)).Unit();
TVector3 n2 = (Ca[cathodeID].first - Ca[cathodeID].second).Cross((sx3Pos - Ca[cathodeID].second)).Unit();
// if the handiness of anode and cathode revered, it should be n2 cross n1
trackVec = (n2.Cross(n1)).Unit();
if( verbose ) printf("Theta, Phi = %f, %f \n", trackVec.Theta() *TMath::RadToDeg(), trackVec.Phi()*TMath::RadToDeg());
}
inline void PW::CalTrack2(TVector3 sx3Pos, PWHitInfo hitInfo, double sigmaA, double sigmaC, bool verbose){
trackPos = sx3Pos;
double p1 = TMath::Abs(hitInfo.nearestDist.first + gRandom->Gaus(0, sigmaA));
double p2 = TMath::Abs(hitInfo.nextNearestDist.first + gRandom->Gaus(0, sigmaA));
double fracA = p1 / (p1 + p2);
short anodeID1 = hitInfo.nearestWire.first;
short anodeID2 = hitInfo.nextNearestWire.first;
TVector3 shiftA1 = (An[anodeID2].first - An[anodeID1].first) * fracA;
TVector3 shiftA2 = (An[anodeID2].second - An[anodeID1].second) * fracA;
double q1 = TMath::Abs(hitInfo.nearestDist.second + gRandom->Gaus(0, sigmaC));
double q2 = TMath::Abs(hitInfo.nextNearestDist.second + gRandom->Gaus(0, sigmaC));
double fracC = q1 / (q1 + q2);
short cathodeID1 = hitInfo.nearestWire.second;
short cathodeID2 = hitInfo.nextNearestWire.second;
TVector3 shiftC1 = (Ca[cathodeID2].first - Ca[cathodeID1].first) * fracC;
TVector3 shiftC2 = (Ca[cathodeID2].second - Ca[cathodeID1].second) * fracC;
TVector3 a1 = An[anodeID1].first + shiftA1;
TVector3 a2 = An[anodeID1].second + shiftA2;
TVector3 c1 = Ca[cathodeID1].first + shiftC1;
TVector3 c2 = Ca[cathodeID1].second + shiftC2;
TVector3 n1 = (a1 - a2).Cross((sx3Pos - a2)).Unit();
TVector3 n2 = (c1 - c2).Cross((sx3Pos - c2)).Unit();
// if the handiness of anode and cathode revered, it should be n2 cross n1
trackVec = (n2.Cross(n1)).Unit();
if( verbose ) printf("Theta, Phi = %f, %f \n", trackVec.Theta() *TMath::RadToDeg(), trackVec.Phi()*TMath::RadToDeg());
}
inline double PW::GetZ0(){
double x = trackPos.X();
double y = trackPos.Y();
double rho = TMath::Sqrt(x*x + y*y);
double theta = trackVec.Theta();
return trackPos.Z() - rho / TMath::Tan(theta);
}
#endif

View File

@ -42,7 +42,7 @@ void ANASEN_model(int anodeID1 = -1, int anodeID2 = -1, int cathodeID1 = -1, int
//--- making ANASEN
const int nWire = 24;
const int wireShift = 3;
const int zLen = 300; //mm
const int zLen = 350; //mm
const int radiusA = 38;
const int radiusC = 43;
@ -103,8 +103,8 @@ void ANASEN_model(int anodeID1 = -1, int anodeID2 = -1, int cathodeID1 = -1, int
new TGeoRotation("rot1", 360/nSX3 * (i + 0.5), 0., 0.)));
}
const int qqqR1 = 10;
const int qqqR2 = 50;
const int qqqR1 = 50;
const int qqqR2 = 100;
TGeoVolume *qqq = geom->MakeTubs("qqq", Al, qqqR1, qqqR2, 0.5, 5, 85);
qqq->SetLineColor(7);
for( int i = 0; i < 4; i++){

1270
Armory/ClassData.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -5,60 +5,67 @@
#define MAXMULTI 1000
class Det{
class Det
{
public:
Det(): multi(0) {Clear(); }
Det() : multi(0) { Clear(); }
unsigned short multi; // max 65535
unsigned short id[MAXMULTI];
unsigned short ch[MAXMULTI];
unsigned short e[MAXMULTI];
unsigned long long t[MAXMULTI];
unsigned short multi; // max 65535
unsigned short id[MAXMULTI];
unsigned short ch[MAXMULTI];
unsigned short e[MAXMULTI];
unsigned long long t[MAXMULTI];
unsigned long long tf[MAXMULTI];
unsigned short sn[MAXMULTI];
unsigned short digiCh[MAXMULTI];
unsigned short sn[MAXMULTI];
unsigned short digiCh[MAXMULTI];
unsigned short index[MAXMULTI]; // id * nCh + ch;
bool used[MAXMULTI];
unsigned short index[MAXMULTI]; // id * nCh + ch;
bool used[MAXMULTI];
void Clear(){
void Clear()
{
multi = 0;
for( int i = 0; i < MAXMULTI; i++){
for (int i = 0; i < MAXMULTI; i++)
{
id[i] = 0;
ch[i] = 0;
e[i] = 0;
t[i] = 0;
index[i] = 0;
sn[i] = 0;
digiCh[i] = 0;
used[i] = false;
e[i] = 0;
t[i] = 0;
tf[i] = 0;
index[i] = 0;
sn[i] = 0;
digiCh[i] = 0;
used[i] = false;
}
}
void Print(){
void Print()
{
printf("=============================== multi : %u\n", multi);
for( int i = 0; i < multi; i++) {
printf(" %3d | %2d-%-2d(%5d) %5u %15llu \n", i, id[i], ch[i], index[i], e[i], t[i]);
for (int i = 0; i < multi; i++)
{
printf(" %3d | %2d-%-2d(%5d) %5u %15llu %15llu \n", i, id[i], ch[i], index[i], e[i], t[i], tf[i]);
}
}
void SetDetDimension(unsigned short maxID, unsigned maxCh){
void SetDetDimension(unsigned short maxID, unsigned maxCh)
{
nID = maxID;
nCh = maxCh;
}
void CalIndex(){
for( int i = 0; i < multi; i++){
index[i] = id[i] * nCh + ch[i] ;
void CalIndex()
{
for (int i = 0; i < multi; i++)
{
index[i] = id[i] * nCh + ch[i];
}
}
private:
unsigned short nID;
unsigned short nCh;
};
#endif
#endif

View File

@ -1,246 +0,0 @@
#ifndef ClassPC_h
#define ClassPC_h
#include <cstdio>
#include <TMath.h>
#include <TVector3.h>
#include <TRandom.h>
struct PCHit_1An{
std::pair<short, short> nearestWire; // anode, cathode
std::pair<double, double> nearestDist; // anode, cathode
short nextNearestWire; // cathode
double nextNearestDist; // cathode
void Clear(){
nearestWire.first = -1;
nearestWire.second = -1;
nearestDist.first = 999999999;
nearestDist.second = 999999999;
nextNearestWire= -1;
nextNearestDist = 999999999;
}
};
//!########################################################
class PC{ // proportional wire
public:
PC(){ ClearHitInfo();};
~PC(){};
PCHit_1An GetHitInfo() const {return hitInfo;}
std::pair<short, short> GetNearestID() const {return hitInfo.nearestWire;}
std::pair<double, double> GetNearestDistance() const {return hitInfo.nearestDist;}
short Get2ndNearestID() const {return hitInfo.nextNearestWire;}
double Get2ndNearestDistance() const {return hitInfo.nextNearestDist;}
TVector3 GetTrackPos() const {return trackPos;}
TVector3 GetTrackVec() const {return trackVec;}
double GetTrackTheta() const {return trackVec.Theta();}
double GetTrackPhi() const {return trackVec.Phi();}
double GetZ0();
int GetNumWire() const {return nWire;}
double GetDeltaAngle() const {return dAngle;}
double GetAnodeLength() const {return anodeLength;}
double GetCathodeLength() const {return cathodeLength;}
TVector3 GetAnodeDn(short id) const {return An[id].first;}
TVector3 GetAnodeUp(short id) const {return An[id].second;}
TVector3 GetCathodeDn(short id) const {return Ca[id].first;}
TVector3 GetCathodeUp(short id) const {return Ca[id].second;}
TVector3 GetAnodneMid(short id) const {return (An[id].first + An[id].second) * 0.5; }
double GetAnodeTheta(short id) const {return (An[id].first - An[id].second).Theta();}
double GetAnodePhi(short id) const {return (An[id].first - An[id].second).Phi();}
TVector3 GetCathodneMid(short id) const {return (Ca[id].first + Ca[id].second) * 0.5; }
double GetCathodeTheta(short id) const {return (Ca[id].first - Ca[id].second).Theta();}
double GetCathodePhi(short id) const {return (Ca[id].first - Ca[id].second).Phi();}
void ClearHitInfo();
void ConstructGeo();
void FindWireID(TVector3 pos, TVector3 direction, bool verbose = false);
void CalTrack3(TVector3 sx3Pos, PCHit_1An hitInfo, double sigmaA = 0, double sigmaC = 0, bool verbose = false);
void Print(){
printf(" The nearest | Anode: %2d(%5.2f) Cathode: %2d(%5.2f)\n", hitInfo.nearestWire.first,
hitInfo.nearestDist.first,
hitInfo.nearestWire.second,
hitInfo.nearestDist.second);
printf(" The 2nd nearest Cathode: %2d(%5.2f)\n", hitInfo.nextNearestWire,
hitInfo.nextNearestDist);
}
private:
// PCHitInfo hitInfo;
PCHit_1An hitInfo;
TVector3 trackPos;
TVector3 trackVec;
const int nWire = 24;
const int wireShift = 3;
const float zLen = 380; //mm
const float radiusA = 37;
const float radiusC = 43;
double dAngle;
double anodeLength;
double cathodeLength;
std::vector<std::pair<TVector3,TVector3>> An; // the anode wire position vector in space
std::vector<std::pair<TVector3,TVector3>> Ca; // the cathode wire position vector in space
double Distance(TVector3 a1, TVector3 a2, TVector3 b1, TVector3 b2){
TVector3 na = a1 - a2;
TVector3 nb = b1 - b2;
TVector3 nd = (na.Cross(nb)).Unit();
return TMath::Abs(nd.Dot(a1-b2));
}
};
inline void PC::ClearHitInfo(){
hitInfo.Clear();
}
inline void PC::ConstructGeo(){
An.clear();
Ca.clear();
std::pair<TVector3, TVector3> p1; // anode
std::pair<TVector3, TVector3> q1; // cathode
//anode and cathode start at pos-Y axis and count in right-Hand
//anode wire shift is right-hand.
//cathode wire shift is left-hand.
for(int i = 0; i < nWire; i++ ){
// Anode rotate right-hand
p1.first.SetXYZ( radiusA * TMath::Cos( TMath::TwoPi() / nWire * (i) + TMath::PiOver2()),
radiusA * TMath::Sin( TMath::TwoPi() / nWire * (i) + TMath::PiOver2()),
zLen/2);
p1.second.SetXYZ( radiusA * TMath::Cos( TMath::TwoPi() / nWire * (i + wireShift) + TMath::PiOver2()),
radiusA * TMath::Sin( TMath::TwoPi() / nWire * (i + wireShift) + TMath::PiOver2()),
-zLen/2);
An.push_back(p1);
// Cathod rotate left-hand
q1.first.SetXYZ( radiusC * TMath::Cos( TMath::TwoPi() / nWire * (i) + TMath::PiOver2()),
radiusC * TMath::Sin( TMath::TwoPi() / nWire * (i) + TMath::PiOver2()),
zLen/2);
q1.second.SetXYZ( radiusC * TMath::Cos( TMath::TwoPi() / nWire * (i - wireShift) + TMath::PiOver2()),
radiusC * TMath::Sin( TMath::TwoPi() / nWire * (i - wireShift) + TMath::PiOver2()),
-zLen/2);
Ca.push_back(q1);
}
dAngle = wireShift * TMath::TwoPi() / nWire;
anodeLength = TMath::Sqrt( zLen*zLen + TMath::Power(2* radiusA * TMath::Sin(dAngle/2),2) );
cathodeLength = TMath::Sqrt( zLen*zLen + TMath::Power(2* radiusC * TMath::Sin(dAngle/2),2) );
}
inline void PC::FindWireID(TVector3 pos, TVector3 direction, bool verbose ){
hitInfo.Clear();
double phi = direction.Phi();
for( int i = 0; i < nWire; i++){
double disA = 99999999;
double phiS = An[i].first.Phi() - TMath::PiOver4();
double phiL = An[i].second.Phi() + TMath::PiOver4();
// printf("A%2d: %f %f | %f\n", i, phiS * TMath::RadToDeg(), phiL * TMath::RadToDeg(), phi * TMath::RadToDeg());
if( phi > 0 && phiS > phiL ) phiL = phiL + TMath::TwoPi();
if( phi < 0 && phiS > phiL ) phiS = phiS - TMath::TwoPi();
if( phiS < phi && phi < phiL) {
disA = Distance( pos, pos + direction, An[i].first, An[i].second);
if( disA < hitInfo.nearestDist.first ){
hitInfo.nearestDist.first = disA;
hitInfo.nearestWire.first = i;
}
}
double disC = 99999999;
phiS = Ca[i].second.Phi()- TMath::PiOver4();
phiL = Ca[i].first.Phi() + TMath::PiOver4();
// printf("C%2d: %f %f\n", i, phiS * TMath::RadToDeg(), phiL * TMath::RadToDeg());
if( phi > 0 && phiS > phiL ) phiL = phiL + TMath::TwoPi();
if( phi < 0 && phiS > phiL ) phiS = phiS - TMath::TwoPi();
if(phiS < phi && phi < phiL) {
disC = Distance( pos, pos + direction, Ca[i].first, Ca[i].second);
if( disC < hitInfo.nearestDist.second ){
hitInfo.nearestDist.second = disC;
hitInfo.nearestWire.second = i;
}
}
if(verbose) printf(" %2d | %8.2f, %8.2f\n", i, disA, disC);
}
short cathode1 = hitInfo.nearestWire.second;
short ccc1 = cathode1 - 1; if( ccc1 < 0 ) ccc1 += nWire;
short ccc2 = (cathode1 + 1) % nWire;
double haha1 = Distance( pos, pos + direction, Ca[ccc1].first, Ca[ccc1].second);
double haha2 = Distance( pos, pos + direction, Ca[ccc2].first, Ca[ccc2].second);
if( haha1 < haha2){
hitInfo.nextNearestWire = ccc1;
hitInfo.nextNearestDist = haha1;
}else{
hitInfo.nextNearestWire = ccc2;
hitInfo.nextNearestDist= haha2;
}
if( verbose ) Print();
}
inline void PC::CalTrack3(TVector3 sx3Pos, PCHit_1An hitInfo, double sigmaA, double sigmaC, bool verbose){
trackPos = sx3Pos;
double p1 = TMath::Abs(hitInfo.nearestDist.first + gRandom->Gaus(0, sigmaA));
short anodeID1 = hitInfo.nearestWire.first;
double q1 = TMath::Abs(hitInfo.nearestDist.second + gRandom->Gaus(0, sigmaC));
double q2 = TMath::Abs(hitInfo.nextNearestDist+ gRandom->Gaus(0, sigmaC));
double fracC = q1 / (q1 + q2);
short cathodeID1 = hitInfo.nearestWire.second;
short cathodeID2 = hitInfo.nextNearestWire;
TVector3 shiftC1 = (Ca[cathodeID2].first - Ca[cathodeID1].first) * fracC;
TVector3 shiftC2 = (Ca[cathodeID2].second - Ca[cathodeID1].second) * fracC;
TVector3 a1 = An[anodeID1].first;
TVector3 c1 = Ca[cathodeID1].first + shiftC1;
TVector3 c2 = Ca[cathodeID1].second + shiftC2;
TVector3 n1 = (sx3Pos - a1).Unit();
TVector3 n2 = (c1 - c2).Cross((sx3Pos - c2)).Unit();
// if the handiness of anode and cathode revered, it should be n2 cross n1
trackVec = (n2.Cross(n1)).Unit();
if( verbose ) printf("Theta, Phi = %f, %f \n", trackVec.Theta() *TMath::RadToDeg(), trackVec.Phi()*TMath::RadToDeg());
}
inline double PC::GetZ0(){
double x = trackPos.X();
double y = trackPos.Y();
double rho = TMath::Sqrt(x*x + y*y);
double theta = trackVec.Theta();
return trackPos.Z() - rho / TMath::Tan(theta);
}
#endif

473
Armory/ClassPW.h Normal file → Executable file
View File

@ -2,6 +2,7 @@
#define ClassPW_h
#include <cstdio>
#include <iostream>
#include <TMath.h>
#include <TVector3.h>
#include <TRandom.h>
@ -61,6 +62,18 @@ public:
double GetTrackPhi() const { return trackVec.Phi(); }
double GetZ0();
Coord Crossover[24][24][2];
inline TVector3 getClosestWirePosAtWirePhi(std::pair<TVector3, TVector3>, double phi);
inline TVector3 getClosestWirePosAtWirePhiOld(std::pair<TVector3, TVector3>, double phi);
inline std::tuple<std::pair<TVector3, TVector3>, double, double, double> GetPseudoWire(const std::vector<std::tuple<int, double, double>> &cluster, std::string type);
inline std::tuple<TVector3, double, double, double, double, double, double, double>
FindCrossoverProperties(const std::vector<std::tuple<int, double, double>> &a_cluster, const std::vector<std::tuple<int, double, double>> &c_cluster);
inline std::vector<std::vector<std::tuple<int, double, double>>>
Make_Clusters(const std::unordered_map<int, std::tuple<int, double, double>> &wireEvents);
int GetNumWire() const { return nWire; }
double GetDeltaAngle() const { return dAngle; }
double GetAnodeLength() const { return anodeLength; }
@ -74,7 +87,7 @@ public:
double GetAnodeTheta(short id) const { return (An[id].first - An[id].second).Theta(); }
double GetAnodePhi(short id) const { return (An[id].first - An[id].second).Phi(); }
TVector3 GetCathodneMid(short id) const { return (Ca[id].first + Ca[id].second) * 0.5; }
TVector3 GetCathodeMid(short id) const { return (Ca[id].first + Ca[id].second) * 0.5; }
double GetCathodeTheta(short id) const { return (Ca[id].first - Ca[id].second).Theta(); }
double GetCathodePhi(short id) const { return (Ca[id].first - Ca[id].second).Phi(); }
@ -82,7 +95,7 @@ public:
void ConstructGeo();
void FindWireID(TVector3 pos, TVector3 direction, bool verbose = false);
void CalTrack(TVector3 sx3Pos, int anodeID, int cathodeID, bool verbose = false);
void CalTrack2(TVector3 sx3Pos, PWHitInfo hitInfo, double sigmaA = 0, double sigmaC = 0, bool verbose = false);
void CalTrack2(TVector3 sx3Pos, TVector3 anodeInt, bool verbose = false);
void Print()
{
@ -104,10 +117,12 @@ private:
TVector3 trackVec;
const int nWire = 24;
const int wireShift = 3;
const float zLen = 380; // mm
const int wireShift = 4;
// const float zLen = 380; // mm
// const float zLen = 348.6; // mm
const float zLen = 174.3 * 2; // mm
const float radiusA = 37;
const float radiusC = 43;
const float radiusC = 42;
double dAngle;
double anodeLength;
@ -139,34 +154,318 @@ inline void PW::ConstructGeo()
std::pair<TVector3, TVector3> p1; // anode
std::pair<TVector3, TVector3> q1; // cathode
// anode and cathode start at pos-Y axis and count in right-Hand
// anode wire shift is right-hand.
// cathode wire shift is left-hand.
double k = TMath::TwoPi() / 24.; // 48 solder thru holes, wires in every other one
double offset_a1 = -6 * k - 4 * k; // -6 to go from 0,0 to 90degree up, and 4 for the wire offset //old version -5 * k - 5 * k;
double offset_c1 = -6 * k + k / 2.0; // correct for a half-turn
// std::cerr << "Here!" << std::endl;
// #include "../scratch/testing.h"
double offset_a2 = offset_a1 + wireShift * k;
double offset_c2 = offset_c1 - wireShift * k;
for (int i = 0; i < nWire; i++)
{
// Anode rotate right-hand
p1.first.SetXYZ(radiusA * TMath::Cos(TMath::TwoPi() / nWire * (i) + TMath::PiOver2()),
radiusA * TMath::Sin(TMath::TwoPi() / nWire * (i) + TMath::PiOver2()),
zLen / 2);
p1.second.SetXYZ(radiusA * TMath::Cos(TMath::TwoPi() / nWire * (i + wireShift) + TMath::PiOver2()),
radiusA * TMath::Sin(TMath::TwoPi() / nWire * (i + wireShift) + TMath::PiOver2()),
-zLen / 2);
An.push_back(p1);
// Anode rotate right-hand coming in towards +z riding with the beam. In this frame, +x is to the right, and +y down
// updated Feb 2026, Sudarsan B. Photographs indicate that anode wires twist right handed, as one moves from -z to +z with the convention above
// wire indices increase leftward as one moves to +z (hence -k factor), but wires themselves twist rightward - as indicated by offset_a2 being more +ve w.r.t offset_a1
//'First' is -z locus, 'second' is +z locus
p1.first.SetXYZ(radiusA * TMath::Cos(-k * i + offset_a1),
radiusA * TMath::Sin(-k * i + offset_a1),
-zLen / 2);
p1.second.SetXYZ(radiusA * TMath::Cos(-k * i + offset_a2),
radiusA * TMath::Sin(-k * i + offset_a2),
+zLen / 2);
// Cathod rotate left-hand
q1.first.SetXYZ(radiusC * TMath::Cos(TMath::TwoPi() / nWire * (i) + TMath::PiOver2()),
radiusC * TMath::Sin(TMath::TwoPi() / nWire * (i) + TMath::PiOver2()),
zLen / 2);
q1.second.SetXYZ(radiusC * TMath::Cos(TMath::TwoPi() / nWire * (i - wireShift) + TMath::PiOver2()),
radiusC * TMath::Sin(TMath::TwoPi() / nWire * (i - wireShift) + TMath::PiOver2()),
-zLen / 2);
// Cathodes twist left-hand as indicated by offset_c2 being more negative than offset_c1, under the same system, while wires increase rightward (hence +k factor)
q1.first.SetXYZ(radiusC * TMath::Cos(k * i + offset_c1),
radiusC * TMath::Sin(k * i + offset_c1),
-zLen / 2);
q1.second.SetXYZ(radiusC * TMath::Cos(k * i + offset_c2),
radiusC * TMath::Sin(k * i + offset_c2),
zLen / 2);
An.push_back(p1);
Ca.push_back(q1);
}
// Calculate Crossover Geometry ONCE
TVector3 a, c, diff;
double a2, ac, c2, adiff, cdiff, denom, alpha;
for (size_t i = 0; i < An.size(); i++)
{
// a = An[i].first - An[i].second;
a = An[i].second - An[i].first;
for (size_t j = 0; j < Ca.size(); j++)
{
c = Ca[j].second - Ca[j].first;
diff = An[i].second - Ca[j].second;
a2 = a.Dot(a);
c2 = c.Dot(c);
ac = a.Dot(c);
adiff = a.Dot(diff);
cdiff = c.Dot(diff);
denom = a2 * c2 - ac * ac;
alpha = (ac * cdiff - c2 * adiff) / denom;
Crossover[i][j][0].x = An[i].second.X() + alpha * a.X();
Crossover[i][j][0].y = An[i].second.Y() + alpha * a.Y();
Crossover[i][j][0].z = An[i].second.Z() + alpha * a.Z();
if (Crossover[i][j][0].z < -190 || Crossover[i][j][0].z > 190)
{
// std::cout << "Weird crossover but ok" << std::endl;
}
if ((i + j) % 24 == 12 || Crossover[i][j][0].z < -190 || Crossover[i][j][0].z > 190)
{
Crossover[i][j][0].z = 9999999;
// std::cout << "Weird crossover" << std::endl;
}
Crossover[i][j][1].x = alpha;
Crossover[i][j][1].y = 0;
}
}
dAngle = wireShift * TMath::TwoPi() / nWire;
anodeLength = TMath::Sqrt(zLen * zLen + TMath::Power(2 * radiusA * TMath::Sin(dAngle / 2), 2));
cathodeLength = TMath::Sqrt(zLen * zLen + TMath::Power(2 * radiusC * TMath::Sin(dAngle / 2), 2));
cathodeLength = TMath::Sqrt(zLen * zLen + TMath::Power(2 * radiusC * TMath::Sin(dAngle / 2), 2)); // chord length subtending an angle alpha is 2rsin(alpha/2)
}
inline TVector3 PW::getClosestWirePosAtWirePhiOld(std::pair<TVector3, TVector3> awire, double sx3phi_radian)
{
// 1. Get wire geometry
TVector3 a1 = awire.first; // Top of the wire
TVector3 a2 = awire.second; // Bottom of the wire
TVector3 wireVec = a2 - a1; // Vector pointing down the wire
// Variables to track our minimums during the scan
double min_delta_phi = 9999.0;
double best_t = -1.0;
TVector3 best_pcz_intersect;
// 2. THE SCAN: Walk down the wire in 1000 tiny steps
// (For a 380mm wire, this is checking every 0.38 mm)
int num_steps = 1000;
for (int i = 0; i <= num_steps; ++i)
{
double t_test = (double)i / num_steps; // Ranges from 0.0 to 1.0
TVector3 test_pt = a1 + t_test * wireVec; // The 3D point at this step
// Calculate absolute Delta Phi between Si hit and this specific point on the wire
if (TMath::IsNaN(sx3phi_radian - test_pt.Phi()))
continue;
double dPhi = TMath::Abs(TVector2::Phi_mpi_pi(sx3phi_radian - test_pt.Phi())); // Phi_mpi_pi just puts the angle in the range -180 to 180
// If this is the smallest Delta Phi we've seen so far, save it!
if (dPhi < min_delta_phi)
{
min_delta_phi = dPhi;
best_t = t_test;
best_pcz_intersect = test_pt;
}
}
return best_pcz_intersect;
}
inline TVector3 PW::getClosestWirePosAtWirePhi(std::pair<TVector3, TVector3> awire, double phi)
{
const TVector3 &a1 = awire.first;
const TVector3 &a2 = awire.second;
const double s = TMath::Sin(phi), c = TMath::Cos(phi);
const double dx = a2.X() - a1.X(), dy = a2.Y() - a1.Y();
const double t = (a1.Y() * c - a1.X() * s) / (dx * s - dy * c);
auto nearerEndpoint = [&]() -> TVector3
{
auto dphi = [&](const TVector3 &p)
{
return TMath::Abs(TVector2::Phi_mpi_pi(phi - p.Phi()));
};
return dphi(a1) <= dphi(a2) ? a1 : a2;
};
if (t < 0.0 || t > 1.0)
return nearerEndpoint();
const TVector3 hit = a1 + t * (a2 - a1);
if (hit.X() * c + hit.Y() * s <= 0.0) // wrong half-plane (anti-phi side)
return nearerEndpoint();
return hit;
}
inline std::vector<std::vector<std::tuple<int, double, double>>>
PW::Make_Clusters(const std::unordered_map<int, std::tuple<int, double, double>> &wireEvents)
{
std::vector<std::vector<std::tuple<int, double, double>>> wireClusters;
std::vector<std::tuple<int, double, double>> wireCluster;
// TODO: Write a macro once, call it twice
int wirecount = 0;
while (wirecount < 24)
{
if (wireEvents.find(wirecount) == wireEvents.end())
{
wirecount++;
continue;
}
wireCluster.clear();
int ctr2 = wirecount;
do
{
wireCluster.emplace_back(wireEvents.at(ctr2));
ctr2 += 1;
if (ctr2 == 24 || ctr2 - wirecount == 7)
break; // loose logic, needs to be looked at.
} while (wireEvents.find(ctr2) != wireEvents.end());
wireClusters.push_back(std::move(wireCluster));
wirecount = ctr2; // we already dealt with wires until the last value of ctr2
}
if (wireClusters.size() > 1)
{
auto &first_cluster = wireClusters.front();
auto &last_cluster = wireClusters.back();
if (std::get<0>(last_cluster.back()) == 23 && std::get<0>(first_cluster.front()) == 0)
{
last_cluster.insert(last_cluster.end(), first_cluster.begin(), first_cluster.end());
wireClusters.erase(wireClusters.begin()); // invalidates the references above; not used after this
}
// TODO: Can also deal with 'gaps' of missing wires similarly. end of one segment and beginning of another segment will be separated by missing wire --> combine the two
// TODO: Also needs some development regarding the time-correlation. Don't put wires in the same cluster if they aren't time coincident
}
return wireClusters;
/*if(aClusters.size()>1 || cClusters.size() > 1) {
std::cout << " ============== " << std::endl;
}
if(aClusters.size()>1 && cClusters.size() >=1) {
std::cout << aClusters.size() << " new anode clusters ----> " << std::endl;
int cc=1;
for(auto ac : aClusters) {
std::cout << " Cluster " << cc << std::endl;
double first_ts = std::get<2>(ac.at(0));
for(auto item : ac) {
std::cout << " \t" << std::get<0>(item) << " " << std::get<1>(item) << " " << std::get<2>(item)-first_ts << std::endl;
}
std::cout << " ------" << std::endl;
cc++;
}
}
if(cClusters.size()>=1 ) {
std::cout << cClusters.size() << " new cathode clusters ----> " << std::endl;
int cc=1;
for(auto ac : cClusters) {
std::cout << " Cluster " << cc << std::endl;
double first_ts = std::get<2>(ac.at(0));
for(auto item : ac) {
std::cout << " \t" << std::get<0>(item) << " " << std::get<1>(item) << " " << std::get<2>(item)-first_ts << std::endl;
}
std::cout << " ------" << std::endl;
cc++;
}
} */
}
inline std::tuple<std::pair<TVector3, TVector3>, double, double, double>
PW::GetPseudoWire(const std::vector<std::tuple<int, double, double>> &cluster, std::string type)
{
std::pair<TVector3, TVector3> avgvec = std::pair(TVector3(0, 0, 0), TVector3(0, 0, 0));
double sumEnergy = 0;
double maxEnergy = 0;
double tsMaxEnergy = 0;
if (type == "ANODE")
{
// if(cluster.size()>1) std::cout << " -------anodes" << std::endl;
for (auto wire : cluster)
{
avgvec.first += std::get<1>(wire) * TVector3(An.at(std::get<0>(wire)).first.X(), An.at(std::get<0>(wire)).first.Y(), 0);
avgvec.second += std::get<1>(wire) * TVector3(An.at(std::get<0>(wire)).second.X(), An.at(std::get<0>(wire)).second.Y(), 0);
sumEnergy += std::get<1>(wire);
if (std::get<1>(wire) > maxEnergy)
{
maxEnergy = std::get<1>(wire);
tsMaxEnergy = std::get<2>(wire);
}
/*if(cluster.size()>1) {
std::cout << "\t\t ch:" << std::get<0>(wire) << " " << std::get<1>(wire) << " " << std::get<2>(wire) << std::endl;
std::cout << "\t\t w1(r,phi,z):" << An.at(std::get<0>(wire)).first.Perp() << " " << An.at(std::get<0>(wire)).first.Phi()*180/M_PI << " " << An.at(std::get<0>(wire)).first.Z() << std::endl;
std::cout << "\t\t w2(r,phi,z):" << An.at(std::get<0>(wire)).second.Perp() << " " << An.at(std::get<0>(wire)).second.Phi()*180/M_PI << " " << An.at(std::get<0>(wire)).second.Z() << std::endl;
}*/
}
avgvec.first = avgvec.first * (1.0 / sumEnergy);
avgvec.second = avgvec.second * (1.0 / sumEnergy);
double phi1 = avgvec.first.Phi();
double phi2 = avgvec.second.Phi();
avgvec.first.SetXYZ(radiusA * TMath::Cos(phi1), radiusA * TMath::Sin(phi1), -zLen / 2);
avgvec.second.SetXYZ(radiusA * TMath::Cos(phi2), radiusA * TMath::Sin(phi2), zLen / 2);
/*if(cluster.size()>1) {
std::cout << "\t\t avg1(r,phi,z):" << avgvec.first.Perp() << " " << avgvec.first.Phi()*180/M_PI << " " << avgvec.first.Z() << std::endl;
std::cout << "\t\t avg2(r,phi,z):" << avgvec.second.Perp() << " " << avgvec.second.Phi()*180/M_PI << " " << avgvec.second.Z() << std::endl;
}*/
}
else if (type == "CATHODE")
{
for (auto wire : cluster)
{
avgvec.first += std::get<1>(wire) * TVector3(Ca.at(std::get<0>(wire)).first.X(), Ca.at(std::get<0>(wire)).first.Y(), 0);
avgvec.second += std::get<1>(wire) * TVector3(Ca.at(std::get<0>(wire)).second.X(), Ca.at(std::get<0>(wire)).second.Y(), 0);
sumEnergy += std::get<1>(wire);
if (std::get<1>(wire) > maxEnergy)
{
maxEnergy = std::get<1>(wire);
tsMaxEnergy = std::get<2>(wire);
}
}
avgvec.first = avgvec.first * (1.0 / sumEnergy);
avgvec.second = avgvec.second * (1.0 / sumEnergy);
double phi1 = avgvec.first.Phi();
double phi2 = avgvec.second.Phi();
avgvec.first.SetXYZ(radiusC * TMath::Cos(phi1), radiusC * TMath::Sin(phi1), -zLen / 2);
avgvec.second.SetXYZ(radiusC * TMath::Cos(phi2), radiusC * TMath::Sin(phi2), zLen / 2);
}
return std::tuple(avgvec, sumEnergy, maxEnergy, tsMaxEnergy);
}
inline std::tuple<TVector3, double, double, double, double, double, double, double> PW::FindCrossoverProperties(const std::vector<std::tuple<int, double, double>> &a_cluster,
const std::vector<std::tuple<int, double, double>> &c_cluster)
{
// std::pair<TVector3, TVector3> apwire = GetPseudoWire(a_cluster,"ANODE",anodeSumE);
// std::pair<TVector3, TVector3> cpwire = GetPseudoWire(c_cluster,"CATHODE",cathodeSumE);
auto [apwire, apSumE, apMaxE, apTSMaxE] = GetPseudoWire(a_cluster, "ANODE");
auto [cpwire, cpSumE, cpMaxE, cpTSMaxE] = GetPseudoWire(c_cluster, "CATHODE");
TVector3 crossover;
crossover.Clear();
TVector3 a, c, diff;
double a2, ac, c2, adiff, cdiff, denom, alpha = 0;
if (apSumE && cpSumE)
{
a = apwire.first - apwire.second;
c = cpwire.first - cpwire.second;
diff = apwire.first - cpwire.first;
a2 = a.Dot(a);
c2 = c.Dot(c);
ac = a.Dot(c);
adiff = a.Dot(diff);
cdiff = c.Dot(diff);
denom = a2 * c2 - ac * ac;
alpha = (ac * cdiff - c2 * adiff) / denom;
crossover = apwire.first + alpha * a;
if (crossover.z() < -190 || crossover.Z() > 190)
{
alpha = 9999999;
apSumE = -1;
cpSumE = -1;
apMaxE = -1;
cpMaxE = -1;
apTSMaxE = -1;
cpTSMaxE = -1;
}
}
// std::cout << apSumE << " " << cpSumE << " " << " " << crossover.Perp() << std::endl;
return std::tuple(crossover, alpha, apSumE, cpSumE, apMaxE, cpMaxE, apTSMaxE, cpTSMaxE);
}
inline void PW::FindWireID(TVector3 pos, TVector3 direction, bool verbose)
@ -278,43 +577,35 @@ inline void PW::CalTrack(TVector3 sx3Pos, int anodeID, int cathodeID, bool verbo
printf("Theta, Phi = %f, %f \n", trackVec.Theta() * TMath::RadToDeg(), trackVec.Phi() * TMath::RadToDeg());
}
inline void PW::CalTrack2(TVector3 sx3Pos, PWHitInfo hitInfo, double sigmaA, double sigmaC, bool verbose)
inline void PW::CalTrack2(TVector3 siPos, TVector3 anodeInt, bool verbose)
{
trackPos = sx3Pos;
double p1 = TMath::Abs(hitInfo.nearestDist.first + gRandom->Gaus(0, sigmaA));
double p2 = TMath::Abs(hitInfo.nextNearestDist.first + gRandom->Gaus(0, sigmaA));
double fracA = p1 / (p1 + p2);
short anodeID1 = hitInfo.nearestWire.first;
short anodeID2 = hitInfo.nextNearestWire.first;
TVector3 shiftA1 = (An[anodeID2].first - An[anodeID1].first) * fracA;
TVector3 shiftA2 = (An[anodeID2].second - An[anodeID1].second) * fracA;
double q1 = TMath::Abs(hitInfo.nearestDist.second + gRandom->Gaus(0, sigmaC));
double q2 = TMath::Abs(hitInfo.nextNearestDist.second + gRandom->Gaus(0, sigmaC));
double fracC = q1 / (q1 + q2);
short cathodeID1 = hitInfo.nearestWire.second;
short cathodeID2 = hitInfo.nextNearestWire.second;
TVector3 shiftC1 = (Ca[cathodeID2].first - Ca[cathodeID1].first) * fracC;
TVector3 shiftC2 = (Ca[cathodeID2].second - Ca[cathodeID1].second) * fracC;
TVector3 a1 = An[anodeID1].first + shiftA1;
TVector3 a2 = An[anodeID1].second + shiftA2;
TVector3 c1 = Ca[cathodeID1].first + shiftC1;
TVector3 c2 = Ca[cathodeID1].second + shiftC2;
TVector3 n1 = (a1 - a2).Cross((sx3Pos - a2)).Unit();
TVector3 n2 = (c1 - c2).Cross((sx3Pos - c2)).Unit();
// if the handiness of anode and cathode revered, it should be n2 cross n1
trackVec = (n2.Cross(n1)).Unit();
double mx, my;
double z;
mx = siPos.X() / (siPos.X() - anodeInt.X());
my = siPos.Y() / (siPos.Y() - anodeInt.Y());
z = siPos.Z() + mx * (anodeInt.Z() - siPos.Z());
// if (mx == my)
{
trackVec = TVector3(0, 0, z);
}
if (verbose)
printf("Theta, Phi = %f, %f \n", trackVec.Theta() * TMath::RadToDeg(), trackVec.Phi() * TMath::RadToDeg());
printf("X slope = %f and Y slope = %f \n", mx, my);
}
/*inline TVector3 PW::CalTrack3(TVector3 siPos, TVector3 anodeInt, bool verbose)
{
TVector3 v = anodeInt-siPos;
double t_minimum = -1.0*(siPos.X()*v.X()+siPos.Y()*v.Y())/(v.X()*v.X()+v.Y()*v.Y());
TVector3 vector_closest_to_z = siPos + t_minimum*v;
return vector_closest_to_z;
if (verbose)
printf("X slope = %f and Y slope = %f \n", mx, my);
}*/
inline double PW::GetZ0()
{
@ -323,7 +614,75 @@ inline double PW::GetZ0()
double rho = TMath::Sqrt(x * x + y * y);
double theta = trackVec.Theta();
return trackPos.Z() - rho / TMath::Tan(theta);
return trackVec.Z();
}
// Each wire family is strung as straight skew chords between two rings, so it sweeps a
// one-sheet hyperboloid (x*x+y*y)/(a*a) - (z*z)/(c*c) = 1 whose waist 'a' is smaller than
// the ring radius. The anode waist was fit to the measured anode crossover points; the
// other families share the flare 'c' and scale their waist with the ring radius.
//
// Radial ordering, inner to outer: guard (ring 32) < anode (ring 37) < cathode (ring 42).
// Charge collection spans guard -> cathode. The anodes in the middle are the readout, the
// cathodes see the induced/mirror charge, and the guard wires are field-shaping, not read out.
const double kPCHyperbC = 301.895;
const double kPCAnodeWaist = 32.0429; // ring radius 37 mm -- readout
const double kPCCathodeWaist = 32.0429 * 42.0 / 37.0; // ring radius 42 mm -- induced charge
const double kPCGuardWaist = 32.0429 * 32.0 / 37.0; // ring radius 32 mm -- not read out
// Intersection of the segment x1 -> x1+dx with the one-sheet hyperboloid of waist a, flare c.
// Returns TVector3(0, 0, 54321) when the segment does not cross the surface.
inline TVector3 pc_hyperboloid_intersect(const TVector3 &x1, const TVector3 &dx, double a, double c)
{
double t2 = 1.0; // value of 't' at the destination point, by definition t=(z(t)-z0)/dz
auto A = pow(dx.Perp(), 2) / (a * a) - pow(dx.Z(), 2) / (c * c);
auto B = 2 * (dx.X() * x1.X() + dx.Y() * x1.Y()) / (a * a) - 2 * (dx.Z() * x1.Z()) / (c * c);
auto C = pow(x1.Perp(), 2) / (a * a) - pow(x1.Z(), 2) / (c * c) - 1.0;
double disc = B * B - 4 * A * C;
if (disc < 0)
return TVector3(0, 0, 54321);
double tsol1 = (-B + TMath::Sqrt(disc)) / (2 * A);
double tsol2 = (-B - TMath::Sqrt(disc)) / (2 * A);
if (tsol1 >= 0 && tsol1 <= t2)
return x1 + tsol1 * dx;
else if (tsol2 >= 0 && tsol2 <= t2)
return x1 + tsol2 * dx;
else
return TVector3(0, 0, 54321);
}
#endif
inline std::tuple<TVector3, TVector3, double> find_PC_PathLength(const TVector3 &x1, const TVector3 &x2)
{
/*
Anode -> cathode segment for a particle moving from x1 to x2.
* Typical arguments here will be x1=r_rhoMin, x2=qqqevent.pos or sx3event.pos
* Returns {cathode_intersect, anode_intersect, gap_in_cm}; gap == 54321 sentinel on failure.
* NOTE: this is the anode->cathode gap, NOT the full charge-collection region.
For the anode energy calibration use find_PC_CollectionPath() instead.
*/
TVector3 dx = x2 - x1; // direction vector
TVector3 anode_intersect = pc_hyperboloid_intersect(x1, dx, kPCAnodeWaist, kPCHyperbC);
TVector3 cathode_intersect = pc_hyperboloid_intersect(x1, dx, kPCCathodeWaist, kPCHyperbC);
if (anode_intersect.Z() != 54321 && cathode_intersect.Z() != 54321)
return std::tuple(cathode_intersect, anode_intersect, (cathode_intersect - anode_intersect).Mag() * 0.1);
else
return std::tuple(TVector3(0, 0, 0), TVector3(0, 0, 0), 54321);
}
inline std::tuple<TVector3, TVector3, double> find_PC_CollectionPath(const TVector3 &x1, const TVector3 &x2)
{
/*
Charge-collection region for a particle moving from x1 to x2: it begins where the track
passes the guard wires and ends at the cathode. This is the segment whose energy deposit
the anodes actually collect, so it is the dE target for the anode energy calibration.
* Returns {guard_intersect, cathode_intersect, thickness_in_cm}; 54321 sentinel on failure.
*/
TVector3 dx = x2 - x1;
TVector3 guard_intersect = pc_hyperboloid_intersect(x1, dx, kPCGuardWaist, kPCHyperbC);
TVector3 cathode_intersect = pc_hyperboloid_intersect(x1, dx, kPCCathodeWaist, kPCHyperbC);
if (guard_intersect.Z() != 54321 && cathode_intersect.Z() != 54321)
return std::tuple(guard_intersect, cathode_intersect, (cathode_intersect - guard_intersect).Mag() * 0.1);
else
return std::tuple(TVector3(0, 0, 0), TVector3(0, 0, 0), 54321);
}
#endif

355
Armory/EventBuilder.cpp Normal file
View File

@ -0,0 +1,355 @@
#include "fsuReader.h"
#include "TROOT.h"
#include "TSystem.h"
#include "TClonesArray.h"
#include "TGraph.h"
#include "TFile.h"
#include "TTree.h"
#include "TMacro.h"
#define MAX_MULTI 2000
struct FileInfo{
std::string fileName;
int fileID;
unsigned long hitCount;
};
//^#############################################################
//^#############################################################
int main(int argc, char **argv) {
printf("=========================================\n");
printf("=== *.fsu Events Builder ===\n");
printf("=========================================\n");
if (argc < 6) {
printf("Incorrect number of arguments:\n");
printf("%s [timeWindow] [withTrace] [verbose] [batchSize] [inFile1] [inFile2] .... \n", argv[0]);
printf(" timeWindow : in ns, -1 = no event building \n");
printf(" withTrace : 0 for no trace, 1 for trace \n");
printf(" verbose : > 0 for debug \n");
printf(" batchSize : the size of hit in a batch \n");
printf(" Output file name is contructed from inFile1 \n");
printf("\n");
printf(" Example: %s 0 0 0 10000 '\\ls -1 *001*.fsu'\n", argv[0]);
printf("\n\n");
return 1;
}
uInt runStartTime = getTime_us();
///============= read input
long timeWindow = atoi(argv[1]);
bool traceOn = atoi(argv[2]);
unsigned int debug = atoi(argv[3]);
unsigned int batchSize = atoi(argv[4]);
int nFile = argc - 5;
TString inFileName[nFile];
for( int i = 0 ; i < nFile ; i++){ inFileName[i] = argv[i+5];}
/// Form outFileName;
TString outFileName = inFileName[0];
int pos = outFileName.Last('/');
pos = outFileName.Index("_", pos+1); // find next "_"
pos = outFileName.Index("_", pos+1); // find next "_"
if( nFile == 1 ) pos = outFileName.Index("_", pos+1); // find next "_", S/N
outFileName.Remove(pos); // remove the rest
outFileName += "_" + std::to_string(timeWindow);
outFileName += ".root";
printf("-------> Out file name : %s \n", outFileName.Data());
printf("========================================= Number of Files : %d \n", nFile);
for( int i = 0; i < nFile; i++) printf("%2d | %s \n", i, inFileName[i].Data());
printf("=========================================\n");
printf(" Time Window = %ld ns = %.1f us\n", timeWindow, timeWindow/1000.);
printf(" Include Trace = %s\n", traceOn ? "Yes" : "No");
printf(" Debug level = %d\n", debug);
printf(" Batch size = %d events/file\n", batchSize);
printf(" Max multiplity = %d hits/event (hard coded)\n", MAX_MULTI);
printf("========================================= Grouping files\n");
std::vector<std::vector<FileInfo>> fileGroupList; // fileName and ID = SN * 1000 + index
std::vector<FileInfo> fileList;
unsigned long long int totalHitCount = 0;
FSUReader * readerA = new FSUReader(inFileName[0].Data(), 1, 1);
readerA->ScanNumBlock(0,0);
FileInfo fileInfo = {inFileName[0].Data(), readerA->GetSN() * 1000 + readerA->GetFileOrder(), readerA->GetHitCount()};
fileList.push_back(fileInfo);
totalHitCount += readerA->GetHitCount();
for( int i = 1; i < nFile; i++){
FSUReader * readerB = new FSUReader(inFileName[i].Data(), 1, 0);
readerB->ScanNumBlock(0,0);
totalHitCount += readerB->GetHitCount();
fileInfo = {inFileName[i].Data(), readerB->GetSN() * 1000 + readerB->GetFileOrder(), readerB->GetHitCount()};
if( readerA->GetSN() == readerB->GetSN() ){
fileList.push_back(fileInfo);
}else{
fileGroupList.push_back(fileList);
fileList.clear();
fileList.push_back(fileInfo);
}
delete readerA;
readerA = readerB;
}
fileGroupList.push_back(fileList);
delete readerA;
printf("======================= total Hit Count : %llu\n", totalHitCount);
for( size_t i = 0; i < fileGroupList.size(); i++){
printf("group ----- %ld \n", i);
//sort by ID
std::sort(fileGroupList[i].begin(), fileGroupList[i].end(), [](const FileInfo & a, const FileInfo & b) {
return a.fileID < b.fileID;
});
for( size_t j = 0; j < fileGroupList[i].size(); j++){
printf("%3ld | %8d | %9lu| %s \n", j, fileGroupList[i][j].fileID, fileGroupList[i][j].hitCount, fileGroupList[i][j].fileName.c_str() );
}
}
// //*====================================== create tree
TFile * outRootFile = new TFile(outFileName, "recreate");
TTree * tree = new TTree("tree", outFileName);
unsigned long long evID = 0;
unsigned int multi = 0;
unsigned short sn[MAX_MULTI] = {0}; /// board SN
unsigned short ch[MAX_MULTI] = {0}; /// chID
unsigned short e[MAX_MULTI] = {0}; /// 15 bit
unsigned short e2[MAX_MULTI] = {0}; /// 15 bit
unsigned long long e_t[MAX_MULTI] = {0}; /// timestamp 47 bit
unsigned short e_f[MAX_MULTI] = {0}; /// fine time 10 bit
unsigned short traceLength[MAX_MULTI];
tree->Branch("evID", &evID, "event_ID/l");
tree->Branch("multi", &multi, "multi/i");
tree->Branch("sn", sn, "sn[multi]/s");
tree->Branch("ch", ch, "ch[multi]/s");
tree->Branch("e", e, "e[multi]/s");
tree->Branch("e2", e2, "e2[multi]/s");
tree->Branch("e_t", e_t, "e_timestamp[multi]/l");
tree->Branch("e_f", e_f, "e_fineTime[multi]/s");
tree->Branch("traceLength", traceLength, "traceLength[multi]/s");
TClonesArray * arrayTrace = nullptr;
TGraph * trace = nullptr;
if( traceOn ) {
arrayTrace = new TClonesArray("TGraph");
tree->Branch("trace", arrayTrace, 2560000);
arrayTrace->BypassStreamer();
}
//*======================================= Open files
printf("========================================= Open files & Build Events.\n");
const short nGroup = fileGroupList.size();
std::vector<Hit> hitList[nGroup];
FSUReader ** reader = new FSUReader * [nGroup];
ulong ID[nGroup];
for( short i = 0; i < nGroup; i++){
std::vector<std::string> fList;
for( size_t j = 0; j < fileGroupList[i].size(); j++){
fList.push_back( fileGroupList[i][j].fileName );
}
reader[i] = new FSUReader(fList, 600, debug);
hitList[i] = reader[i]->ReadBatch(batchSize, debug );
reader[i]->PrintHitListInfo(&hitList[i], "hitList-" + std::to_string(reader[i]->GetSN()));
ID[i] = 0;
if( debug ) {
for( size_t p = 0; p < 10; p ++ ){
if( hitList[i].size() <= p ) break;
hitList[i][p].Print();
}
}
}
unsigned long long tStart = 0;
unsigned long long tEnd = 0;
//find earliest time group;
unsigned long long t0 = -1;
short g0 = 0 ;
for( short i = 0; i < nGroup; i++){
if( hitList[i].size() == 0 ) continue;
if( hitList[i][0].timestamp < t0 ) {
t0 = hitList[i][0].timestamp;
g0 = i;
}
}
tStart = t0;
if( debug ) printf("First timestamp is %llu, group : %u\n", t0, g0);
int nFileFinished = 0;
multi = 0;
evID = 0;
std::vector<Hit> events;
unsigned long long hitProcessed = 0;
do{
//*============= Build events from hitList[i]
if( debug ) printf("################################ ev build %llu \n", evID);
events.clear();
for( short i = 0; i < nGroup; i++){
short ig = (i + g0 ) % nGroup;
if( hitList[ig].size() == 0 ) continue;
//chekc if reached the end of hitList
if( ID[ig] >= hitList[ig].size() ) {
hitList[ig] = reader[ig]->ReadBatch(batchSize, debug + 1);
if( debug ) reader[ig]->PrintHitListInfo( &hitList[ig], "hitList-" + std::to_string(ig));
ID[ig] = 0;
if( hitList[ig].size() == 0 ) continue;
}
if( timeWindow >= 0 ){
do{
if( (long int)(hitList[ig].at(ID[ig]).timestamp - t0) <= timeWindow ){
events.push_back(hitList[ig].at(ID[ig]));
ID[ig] ++;
}else{
break;
}
//check if reached the end of hitList
if( ID[ig] >= hitList[ig].size() ) {
hitList[ig] = reader[ig]->ReadBatch(batchSize, debug);
if( debug ) reader[ig]->PrintHitListInfo( &hitList[ig], "hitList-" + std::to_string(ig));
ID[ig] = 0;
if( hitList[ig].size() == 0 ) break;
}
}while(ID[ig] < hitList[ig].size());
}else{
events.push_back(hitList[ig].at(ID[ig]));
ID[ig] ++;
}
if( timeWindow < 0) break;
}
if( events.size() > 1 ){
std::sort(events.begin(), events.end(), [](const Hit& a, const Hit& b) {
return a.timestamp < b.timestamp;
});
}
tEnd = events.back().timestamp;
hitProcessed += events.size();
if( hitProcessed % 10000 == 0 ) printf("hit Porcessed %llu/%llu hit....%.2f%%\n\033[A\r", hitProcessed, totalHitCount, hitProcessed*100./totalHitCount);
multi = events.size() ;
if( events.size() >= MAX_MULTI ) {
printf("event %lld has size = %d > MAX_MULTI = %d\n", evID, multi, MAX_MULTI);
multi = MAX_MULTI;
}
if( debug ) printf("=================================== filling data | %u \n", multi);
for( size_t p = 0; p < multi ; p ++ ) {
if( debug ) {printf("%4zu | ", p); events[p].Print();}
sn[p] = events[p].sn;
ch[p] = events[p].ch;
e[p] = events[p].energy;
e2[p] = events[p].energy2;
e_t[p] = events[p].timestamp;
e_f[p] = events[p].fineTime;
traceLength[p] = events[p].traceLength;
if( traceOn ){
trace = (TGraph *) arrayTrace->ConstructedAt(multi, "C");
trace->Clear();
for( int hh = 0; hh < traceLength[multi]; hh++){
trace->SetPoint(hh, hh, events[p].trace[hh]);
}
}
}
outRootFile->cd();
tree->Fill();
// tree->Write();
multi = 0;
evID ++;
///===================== find the next first timestamp
t0 = -1;
g0 = -1;
for( int i = 0; i < nGroup; i++) {
if( hitList[i].size() == 0 ) continue;
if( hitList[i][ID[i]].timestamp < t0 ) {
t0 = hitList[i][ID[i]].timestamp;
g0 = i;
}
}
if( debug ) printf("Next First timestamp is %llu, group : %u\n", t0, g0);
//*=============
nFileFinished = 0;
for( int i = 0 ; i < nGroup; i++) {
if( hitList[i].size() == 0 ) {
nFileFinished ++;
continue;
}else{
if( ID[i] >= hitList[i].size( )) {
hitList[i] = reader[i]->ReadBatch(batchSize, debug);
ID[i] = 0;
if( hitList[i].size() == 0 ) nFileFinished ++;
}
}
}
if( debug > 1 ) printf("========== nFileFinished : %d\n", nFileFinished);
}while( nFileFinished < nGroup);
tree->Write();
uInt runEndTime = getTime_us();
double runTime = (runEndTime - runStartTime) * 1e-6;
printf("========================================= finished.\n");
printf(" event building time = %.2f sec = %.2f min\n", runTime, runTime/60.);
printf(" total events built = %llu by event builder (%llu in tree)\n", evID, tree->GetEntriesFast());
double tDuration_sec = (tEnd - tStart) * 1e-9;
printf(" first timestamp = %20llu ns\n", tStart);
printf(" last timestamp = %20llu ns\n", tEnd);
printf(" total data duration = %.2f sec = %.2f min\n", tDuration_sec, tDuration_sec/60.);
printf("==============> saved to %s \n", outFileName.Data());
TMacro info;
info.AddLine(Form("tStart= %20llu ns",tStart));
info.AddLine(Form(" tEnd= %20llu ns",tEnd));
info.Write("info");
outRootFile->Close();
for( int i = 0; i < nGroup; i++) delete reader[i];
delete [] reader;
return 0;
}

491
Armory/HistPlotter.h Normal file
View File

@ -0,0 +1,491 @@
#ifndef HISTPLOTTER_H
#define HISTPLOTTER_H
#include <TCanvas.h>
#include <TROOT.h>
#include <TSystem.h>
#include <TStyle.h>
#include <iostream>
#include <TFile.h>
#include <TMemFile.h>
#include <TH1.h>
#include <TH2.h>
#include <TCutG.h>
#include <signal.h>
#include <cstdlib>
#include <utility>
#include <fstream>
#include <sstream>
#include <map>
#include <unordered_map>
#include <set>
#include <TGraphErrors.h>
class HistPlotter
{
private:
long long barrier_count, barrier_limit; // meant to keep track of how often to call FillN() on histograms
enum
{
TFILE,
TMEMFILE
} filetype;
std::map<std::string, TObject *> oMap; //!< Maps std::string to all TH1, TH2 objects in the class
std::unordered_map<std::string, TObject *> cutsMap; //!< Maps std::string to TCutG objects held by the class
std::set<std::string> folderList; //!< List of all folder names used to nest objects
std::unordered_map<TObject *, std::string> foldersForObjects; //!< Map that returns the folder corresponding to the object whose pointer is specified
TFile *ofile = nullptr; //!< TFile pointer for the output file
TMemFile *omfile = nullptr; //!< TFile pointer for the output memfile
// Caches to permit FillN() calls
std::map<std::string, std::vector<double>> onedimcache;
std::map<std::string, std::pair<std::vector<double>, std::vector<double>>> twodimcache;
inline void FillN_All_Histograms();
public:
HistPlotter(std::string outfile, std::string type);
inline void FlushToDisk(int integral); //!< Writes all objects to file before closing, nesting objects in folders as is found necessary
inline void PrintObjects(); //!< Dump objects to std::cout for inspection
inline void ReadCuts(std::string);
inline TCutG *FindCut(std::string cut)
{
return static_cast<TCutG *>(cutsMap.at(cut));
}
inline void set_barrier_limit(long long limit) { barrier_limit = limit; }
inline void barrier_increment()
{
barrier_count++;
if (barrier_count == barrier_limit)
{
FillN_All_Histograms();
barrier_count = 0;
}
}
inline void force_flush_caches()
{
FillN_All_Histograms();
barrier_count = 0;
}
/*! \fn void FindCut()
\brief
- Searches for a cut by name 'cut' in the internal list of cuts 'cutsMap'. Ugly fails (via unresolved at()) if such a cut isn't found.
\param filename - name of the plainxtext file containing the cut file locations and identifiers
\return Pointer to the TCutG object that matches the name. Very useful to use this as plotter.FindCut("protonbarrelpid")->IsInside(deltaE, E) for instance.
*/
inline void SetNewTitle(std::string name, std::string title)
{
auto result = oMap.find(name); // result is an iterator
if (result == oMap.end())
return; // no warnings, could be changed in future
else
static_cast<TNamed *>(oMap.at(name))->SetTitle(title.c_str()); // set new title
}
// Smart functions that create a new histogram if it doesn't exist.
inline void FillGraph(const std::string &name, float valuex, float valuey, float errx = 0, float erry = 0);
inline void Fill1D(const std::string &name, int nbinsx, float xlow, float xhigh, float value);
inline void Fill2D(const std::string &name, int nbinsx, float xlow, float xhigh, int nbinsy, float ylow, float yhigh, float valuex, float valuey);
inline void Fill1D(const std::string &name, int nbinsx, float xlow, float xhigh, float value, const std::string &folder);
inline void Fill2D(const std::string &name, int nbinsx, float xlow, float xhigh, int nbinsy, float ylow, float yhigh, float valuex, float valuey, const std::string &folder);
// TObject* findObject(std::string key);
};
HistPlotter::HistPlotter(std::string outfile, std::string type = "")
{
/*!
\brief Constructor. Opens a TFile instance with the specified filename
\param outfile : std::string that holds the desired output ROOT filename
\return None
*/
if (type == "" || type == "TFILE")
{
ofile = new TFile(outfile.c_str(), "recreate");
filetype = TFILE;
}
else if (type == "TMEMFILE")
{
omfile = new TMemFile(outfile.c_str(), "recreate");
filetype = TMEMFILE;
}
else
{
std::cout << "Unknown type " << type << " specified for HistPlotter (use \"TFILE\" or \"TMEMFILE\"), using default \"TFILE\" " << std::endl;
ofile = new TFile(outfile.c_str(), "recreate");
filetype = TFILE;
}
barrier_count = 0;
barrier_limit = 1000;
}
void HistPlotter::FillN_All_Histograms()
{
for (auto it = oMap.begin(); it != oMap.end(); it++)
{
// it->first is std::string 'name', it->second is the TObject
if (it->second->InheritsFrom("TH1F"))
{
// FillN(size, array-of-doubles, array-of-weights); //we set array-of-weights to (1,1,1,.. (size)
static_cast<TH1F *>(it->second)->FillN(onedimcache[it->first].size(), // size
onedimcache[it->first].data(), // array
std::vector<double>(onedimcache[it->first].size(), 1.0).data()); // weight of ones
onedimcache[it->first].clear();
}
else if (it->second->InheritsFrom("TH2F"))
{
// FillN(size, array-of-doubles, array-of-weights); //we set array-of-weights to (1,1,1,.. (size))
static_cast<TH2F *>(it->second)->FillN(twodimcache[it->first].first.size(), // size
twodimcache[it->first].first.data(), // x array
twodimcache[it->first].second.data(), // y array
std::vector<double>(twodimcache[it->first].first.size(), 1.0).data()); // weight of ones
twodimcache[it->first].first.clear();
twodimcache[it->first].second.clear();
}
}
std::cout << "." << std::endl;
}
void HistPlotter::FlushToDisk(int min_integral = 0)
{
/*! \fn void FlushToDisk()
\brief Function that can be used at any point to exit smoothly by saving all ROOT objects in memory
to the output file before closing it. Obeys the binding of histograms to separate folders, if so specified.
\return No return -- void
*/
if (filetype == TMEMFILE && omfile)
{
std::cout << "Not flushing a TMemfile .. exiting .." << std::endl;
delete omfile;
return;
}
if (ofile->IsZombie() || !ofile)
{
std::cerr << "Output file is zombie, finishing up without writing to disk!" << std::endl;
return;
}
FillN_All_Histograms();
for (auto it = oMap.begin(); it != oMap.end(); it++)
{
// omap maps: name(first) to object address(second).
// foldersForObjects maps: object address(first) to foldername(second)
auto result = foldersForObjects.find(it->second); // returns <TObject* histogram,std::string foldername> pair if found
if (result != foldersForObjects.end())
{ // we try to create folder if needed and cd to it
ofile->mkdir(result->second.c_str(), "", kTRUE); // args: name, title, returnExistingDirectory
ofile->cd(result->second.c_str());
}
else
{
ofile->cd(); // toplevel for all default histograms. Default setting
}
if (((TH1F *)it->second)->Integral() > min_integral)
it->second->Write();
}
// Create a directory for all cuts, and save all cuts in them
ofile->mkdir("gCUTS", "", kTRUE);
ofile->cd("gCUTS");
for (auto it = cutsMap.begin(); it != cutsMap.end(); it++)
{
(static_cast<TNamed *>(it->second))->SetName(it->first.c_str());
it->second->Write();
}
ofile->Close();
std::cout << "Wrote " << oMap.size() << " histograms to TFile " << std::string(ofile->GetName()) << std::endl;
}
void HistPlotter::FillGraph(const std::string &name, float valuex, float valuey, float errx, float erry)
{
/*! \fn void FillGraph()
\brief
- Creates a TGraphError in memory with name 'name' if it doesn't exist, and fills it with valuex, valuey
- Writes present state to disk and fails with return value -1 if the name clashes with another object that's not of type TGraph*
\param name name of the TGraph
\param valuex The xvalue
\param valuey The yvalue
\param errx The x error
\param erry The y error
\return No return void
*/
auto result = oMap.find(name);
if (result == oMap.end())
{
TGraphErrors *tempG = new TGraphErrors();
tempG->SetName(name.c_str());
oMap.insert(std::make_pair(name, static_cast<TObject *>(tempG)));
}
if (!oMap.at(name)->InheritsFrom("TGraphErrors"))
{
std::cerr << "Object " << name << " refers to something other than a TGraph*, not filling it hence!" << std::endl;
std::cerr << "Abort.." << std::endl;
FlushToDisk();
exit(-1);
}
// static_cast<TGraphErrors*>(oMap.at(name))->AddPointError(valuex,valuey,errx,erry);
}
void HistPlotter::Fill1D(const std::string &name, int nbinsx, float xlow, float xhigh, float value)
{
/*! \fn void Fill1D()
\brief
- Creates a TH1F in memory with name 'name' if it doesn't exist, and fills it with valuex, valuey
- Writes present state to disk and fails with return value -1 if the name clashes with another object that's not of type TH1*
\param name name of the TH1F histogram
\param nbinsx Number of bins in the histogram
\param xlow Lower limit on x-axis
\param xhigh Upper limit on x-axis
\param value The bin corresponding to value in (nbinsx, xlow, xhigh) is incremented by 1
\return No return void
*/
auto result = oMap.find(name); // result is an iterator
if (result == oMap.end())
{
TH1F *temp1D = new TH1F(name.c_str(), name.c_str(), nbinsx, xlow, xhigh);
oMap.insert(std::make_pair(name, static_cast<TObject *>(temp1D)));
onedimcache.insert(std::make_pair(name, std::vector<double>()));
onedimcache[name].reserve(16384);
}
else if (foldersForObjects.find(oMap.at(name)) != foldersForObjects.end())
{ // shouldn't have a folder associated with it
std::cerr << "Object " << name << " already registered at " << foldersForObjects[oMap[name]] << ", choose a different name for the histogram to be stored in toplevel .." << std::endl;
}
// Check if the string 'name' maps to a 1D hist. If there's any other object by this name raise issue
if (!oMap.at(name)->InheritsFrom("TH1F"))
{
std::cerr << "Object " << name << " refers to something other than a TH1*, not filling it hence!" << std::endl;
std::cerr << "Abort.." << std::endl;
FlushToDisk();
exit(-1);
}
onedimcache[name].emplace_back(value);
// static_cast<TH1F*>(oMap.at(name))->Fill(value);
}
void HistPlotter::Fill1D(const std::string &name, int nbinsx, float xlow, float xhigh, float value, const std::string &foldername)
{
/*! \fn void Fill1D()
\brief
- Creates a TH1F in memory with name 'name' if it doesn't exist, and fills it with valuex, valuey
- Writes present state to disk and fails with return value -1 if the name clashes with another object that's not of type TH1*
- Remembers the foldername this particular histogram maps to, if provided. If not, defaults to toplevel.
\param name name of the TH1F histogram
\param nbinsx Number of bins in the histogram
\param xlow Lower limit on x-axis
\param xhigh Upper limit on x-axis
\param value The bin corresponding to value in (nbinsx, xlow, xhigh) is incremented by 1
\param foldername Name of the folder to put this histogram into. Defaults to toplevel if left empty
\return No return -- void
*/
auto result = oMap.find(name); // result is an iterator
if (result == oMap.end())
{
TH1F *temp1D = new TH1F(name.c_str(), name.c_str(), nbinsx, xlow, xhigh);
oMap.insert(std::make_pair(name, static_cast<TObject *>(temp1D)));
onedimcache.insert(std::make_pair(name, std::vector<double>()));
onedimcache[name].reserve(16384);
if (foldername != "")
{
if (folderList.find(foldername) == folderList.end())
{
folderList.insert(foldername);
}
foldersForObjects.insert(std::make_pair(static_cast<TObject *>(temp1D), foldername));
}
}
else
{
// object is present in map, but we enforce unique names
// it must already have a folder attached to it
if (foldersForObjects.find(oMap.at(name)) == foldersForObjects.end())
{
std::cerr << "Object " << name << " already registered at toplevel, choose a different name for the histogram to be stored in " << foldername << " folder .." << std::endl;
}
else if (foldersForObjects[oMap[name]] != foldername)
{
std::cerr << "Object " << name << " already registered at " << foldersForObjects[oMap[name]] << ", choose a different name for the histogram to be stored in " << foldername << " folder .." << std::endl;
}
}
// Check if the string 'name' maps to a 1D hist. If there's any other object by this name raise issue
if (!oMap.at(name)->InheritsFrom("TH1F"))
{
std::cerr << "Object " << name << " refers to something other than a TH1*, not filling it hence!" << std::endl;
std::cerr << "Abort.." << std::endl;
FlushToDisk();
exit(-1);
}
onedimcache[name].emplace_back(value);
// static_cast<TH1F*>(oMap.at(name))->Fill(value);
}
void HistPlotter::Fill2D(const std::string &name, int nbinsx, float xlow, float xhigh, int nbinsy, float ylow, float yhigh, float valuex, float valuey)
{
/*! \fn void Fill2D()
\brief
- Creates a TH2F in memory with name 'name' if it doesn't exist, and fills it with valuex, valuey
- Writes present state to disk and fails with return value -1 if the name clashes with another object that's not of type TH2*
\param name name of the TH1F histogram
\param nbinsx Number of xbins in the histogram
\param xlow Lower limit on x-axis
\param xhigh Upper limit on x-axis
\param nbinsy Number of ybins in the histogram
\param ylow Lower limit on y-axis
\param yhigh Upper limit on y-axis
\param valuex
\param valuey The bin corresponding to (valuex, valuey) in (nbinsx, xlow, xhigh, ybinsx, ylow, yhigh) is incremented by 1
\return No return -- void
*/
auto result = oMap.find(name); // result is an iterator
if (result == oMap.end())
{
TH2F *temp2D = new TH2F(name.c_str(), name.c_str(), nbinsx, xlow, xhigh, nbinsy, ylow, yhigh);
oMap.insert(std::make_pair(name, static_cast<TObject *>(temp2D)));
twodimcache.insert(std::make_pair(name, std::make_pair(std::vector<double>(), std::vector<double>())));
twodimcache[name].first.reserve(16384);
twodimcache[name].second.reserve(16384);
}
else if (foldersForObjects.find(oMap.at(name)) != foldersForObjects.end())
{ // shouldn't have a folder associated with it
std::cerr << "Object " << name << " already registered at " << foldersForObjects[oMap[name]] << ", choose a different name for the histogram to be stored in toplevel .." << std::endl;
}
// Check if the string 'name' maps to a 1D hist. If there's any other object by this name raise issue
if (!oMap.at(name)->InheritsFrom("TH2F"))
{
std::cerr << "Object " << name << " refers to something other than a TH2*, not filling it hence!" << std::endl;
std::cerr << "Abort.." << std::endl;
FlushToDisk();
exit(-1);
}
twodimcache[name].first.emplace_back(valuex);
twodimcache[name].second.emplace_back(valuey);
// static_cast<TH2F*>(oMap.at(name))->Fill(valuex,valuey);
}
void HistPlotter::Fill2D(const std::string &name, int nbinsx, float xlow, float xhigh, int nbinsy, float ylow, float yhigh, float valuex, float valuey, const std::string &foldername)
{
/*! \fn void Fill2D()
\brief
- Creates a TH2F in memory with name 'name' if it doesn't exist, and fills it with valuex, valuey
- Writes present state to disk and fails with return value -1 if the name clashes with another object that's not of type TH2*
- Remembers the foldername this particular histogram maps to, if provided. If not defaults to toplevel
\param name name of the TH1F histogram
\param nbinsx Number of xbins in the histogram
\param xlow Lower limit on x-axis
\param xhigh Upper limit on x-axis
\param nbinsy Number of ybins in the histogram
\param ylow Lower limit on y-axis
\param yhigh Upper limit on y-axis
\param valuex
\param valuey The bin corresponding to (valuex, valuey) in (nbinsx, xlow, xhigh, ybinsx, ylow, yhigh) is incremented by 1
\param foldername Name of the folder to put this histogram into. Defaults to toplevel if left empty
\return No return -- void
*/
auto result = oMap.find(name); // result is an iterator
if (result == oMap.end())
{
TH2F *temp2D = new TH2F(name.c_str(), name.c_str(), nbinsx, xlow, xhigh, nbinsy, ylow, yhigh);
oMap.insert(std::make_pair(name, static_cast<TObject *>(temp2D)));
twodimcache.insert(std::make_pair(name, std::make_pair(std::vector<double>(), std::vector<double>())));
twodimcache[name].first.reserve(16384);
twodimcache[name].second.reserve(16384);
if (foldername != "")
{
if (folderList.find(foldername) == folderList.end())
{
folderList.insert(foldername);
}
foldersForObjects.insert(std::make_pair(static_cast<TObject *>(temp2D), foldername));
}
}
else
{
// object is present in map, but we enforce unique names
// it must already have a folder attached to it
if (foldersForObjects.find(oMap.at(name)) == foldersForObjects.end())
{
std::cerr << "Object " << name << " already registered at toplevel, choose a different name for the histogram to be stored in " << foldername << " folder .." << std::endl;
}
else if (foldersForObjects[oMap.at(name)] != foldername)
{
std::cerr << "Object " << name << " already registered at " << foldersForObjects[oMap[name]] << ", choose a different name for the histogram to be stored in " << foldername << " folder .." << std::endl;
}
}
// Check if the string 'name' maps to a 1D hist. If there's any other object by this name raise issue
if (!oMap.at(name)->InheritsFrom("TH2F"))
{
std::cerr << "Object " << name << " refers to something other than a TH2*, not filling it hence!" << std::endl;
std::cerr << "Abort.." << std::endl;
FlushToDisk();
exit(-1);
}
twodimcache[name].first.emplace_back(valuex);
twodimcache[name].second.emplace_back(valuey);
// static_cast<TH2F*>(oMap.at(name))->Fill(valuex,valuey);
}
void HistPlotter::ReadCuts(std::string filename)
{
/*! \fn void ReadCuts()
\brief Reads a list of cuts from a file. The file must have the format below, two columns
- Column#1 - path to a file that contains a single TCutG object named "CUTG", the default name in ROOT.
- Column#2 - The identifier name you plan to use in the code, like 'protonbarrelpid' or something, that will be searched by FindCut()
\param filename name of the plainxtext file containing the cut file locations and identifiers
\return No return -- void
*/
std::ifstream infile;
infile.open(filename);
std::string cutfilename, cutname;
for (std::string line; std::getline(infile, line);)
{
if (line.size() != 0 && line[0] == '#')
; // don't do anything with '#' lines
else
{
std::stringstream ss(line);
ss >> cutfilename >> cutname;
TFile f(cutfilename.c_str());
if (f.IsZombie())
{
std::cerr << "Cannot open cutfile " << cutfilename << " .. skipping.." << std::endl;
continue;
}
TCutG *cut = (TCutG *)(f.Get("CUTG"));
cutsMap.insert(std::make_pair(cutname, static_cast<TObject *>(cut)));
f.Close();
} // else
} // for loop
infile.close();
}
void HistPlotter::PrintObjects()
{
/*
void PrintObjects()
Prints the contents of the unordered_maps oMap and cutsMap to facilitate debugging
*/
std::cout << "Type | Name " << std::endl;
std::cout << "---- | --------------------- " << std::endl;
for (auto it = oMap.begin(); it != oMap.end(); it++)
{
std::cout << it->second->ClassName() << " | " << it->first << std::endl;
}
for (auto it = cutsMap.begin(); it != cutsMap.end(); it++)
{
std::cout << it->second->ClassName() << " | " << it->first << std::endl;
}
std::cout << "---- | --------------------- " << std::endl;
}
#endif

52
Armory/Hit.h Normal file
View File

@ -0,0 +1,52 @@
#ifndef Hit_H
#define Hit_H
#include <vector>
class Hit{
public:
unsigned short sn;
uint8_t ch;
unsigned short energy;
unsigned short energy2;
unsigned long long timestamp;
unsigned short fineTime;
bool pileUp;
unsigned short traceLength;
std::vector<short> trace;
Hit(){
Clear();
}
void Clear(){
sn = 0;
ch = 0;
energy = 0;
energy2 = 0;
timestamp = 0;
fineTime = 0;
traceLength = 0;
pileUp = false;
trace.clear();
}
void Print(){
printf("(%5d, %2d) %6d %16llu, %6d, %d, %5ld\n", sn, ch, energy, timestamp, fineTime, pileUp, trace.size());
}
void PrintTrace(){
for( unsigned short i = 0; i < traceLength; i++){
printf("%3u | %6d \n", i, trace[i]);
}
}
// Define operator< for sorting
bool operator<(const Hit& other) const {
return timestamp < other.timestamp;
}
};
#endif

111
Armory/Kinematics.cpp Normal file
View File

@ -0,0 +1,111 @@
#include "Kinematics.h"
//#include "elastcaller.h"
//double Kinematics::getQval(double m1, double m2, double m3, double t1, double t3, double angle3)
double Kinematics::getExc(double t3, double angle3)
/*
\brief Follows convention in Marion, 2013: (1 - beam, 2- target, 3-ejectile, 4-recoil)
m1 is beam, (typically heavy nucleus)
m2 is 'd', (light target)
m3 is 'p', (light ejectile mass)
t1 is beam kinetic energy,
\param t3 lab-kinetic-energy in MeV
\param angle3 lab-angle in deg of detected proton (if d,p) or other charged particle
\return Excitation energy of the heavy-recoil nucleus in MeV
*/
{
double m1 = m_A;
double m2 = m_d;
double m3 = m_p;
double m4 = m_B;
double t1 = E_beam;
//t1 = slowitdown("75Ga",t1,"1(12C)2(2H)",1.2);
m1 *= u_MeV;
m2 *= u_MeV;
m3 *= u_MeV;
m4 *= u_MeV;
double e1 = m1 + t1;
double e3 = m3 + t3;
ET = t1 + m1 + m2;
double p1 = TMath::Sqrt(t1*t1 + 2*m1*t1);
double p3 = TMath::Sqrt(t3*t3 + 2*m3*t3);
double cosTheta = TMath::Cos(angle3*TMath::Pi()/180.);
// return m1+m2-m3-TMath::Sqrt(m1*m1 + m2*m2 + m3*m3 + 2.*m2*e1 - 2.*e3*(e1+m2)+ 2.*p1*p3*cosTheta);
double Q = m1+m2-m3-TMath::Sqrt(m1*m1 + m2*m2 + m3*m3 + 2.*m2*e1 - 2.*e3*(e1+m2)+ 2.*p1*p3*cosTheta);
Qx = Q;
T4 = ET - e3 - (m1+m2-m3-Q);
//T4 = slowitdown("75Ga",T4,"1(12C)2(2H)",1.2);
P4 = TMath::Sqrt(T4*T4 + 2*m4*T4);
//this angle will not be affected by eloss
theta4 = (180./M_PI)*TMath::ASin((p3/P4)*TMath::Sin(angle3*M_PI/180.));
// T4-=16.5; //eloss in about 1.4 mg CD2
T4-=15.5; //eloss in 1.35 mg CD2 //TODO: actually degrade recoil/ejectiles in target
// T4-=14.1; //eloss in about 1.2 mg CD2
// T4-=31.7; //eloss in 2.7 mg CD2
//recalculate everything other than angle with lowered Kinetic energy
P4 = TMath::Sqrt(T4*T4 + 2*m4*T4);
gamma4 = T4/m4+1.;
beta4 = TMath::Sqrt(1. - 1./(gamma4*gamma4));
//beta4 = TMath::Sqrt((P4*P4)/(P4*P4 + m4*m4));
theta4 = (180./M_PI)*TMath::ASin((p3/P4)*TMath::Sin(angle3*M_PI/180.));
return Q0 - Q;//Q0 = Q + Exc
}
double Kinematics::getBeta4(double t3, double angle3)
/*
\brief Follows convention in Marion, 2013: (1 - beam, 2- target, 3-ejectile, 4-recoil)
m1 is beam, (typically heavy nucleus)
m2 is 'd', (light target)
m3 is 'p', (light ejectile mass)
t1 is beam kinetic energy,
\param t3 lab-kinetic-energy in MeV
\param angle3 lab-angle in deg of detected proton (if d,p) or other charged particle
\return doppler-shift beta (=v/c) of the heavy-recoil nucleus, calls getExc() to fill the value
*/
{
getExc(t3, angle3);
return beta4;
}
double Kinematics::getBrho(double t3, double angle3, double charge_state) {
/*
\brief Follows convention in Marion, 2013: (1 - beam, 2- target, 3-ejectile, 4-recoil)
m1 is beam, (typically heavy nucleus)
m2 is 'd', (light target)
m3 is 'p', (light ejectile mass)
t1 is beam kinetic energy,
\param t3 lab-kinetic-energy in MeV
\param angle3 lab-angle in deg of detected proton (if d,p) or other charged particle
\param charge_state charge state of the intended nucleus, in units of elementary charge (= +1 for H+, +2 for He2+ etc).
\return b-rho value, generated from P4*3.3359e-3/charge_state where P4 is 4momentum of heavy-recoil calculated from orruba kinematics
*/
getExc(t3,angle3);
return P4*3.3359e-3/charge_state;
}
double Kinematics::getTheta4(double t3, double angle3) {
/*
\brief Follows convention in Marion, 2013: (1 - beam, 2- target, 3-ejectile, 4-recoil)
m1 is beam, (typically heavy nucleus)
m2 is 'd', (light target)
m3 is 'p', (light ejectile mass)
t1 is beam kinetic energy,
\param t3 lab-kinetic-energy in MeV
\param angle3 lab-angle in deg of detected proton (if d,p) or other charged particle
\param charge_state charge state of the intended nucleus, in units of elementary charge (= +1 for H+, +2 for He2+ etc).
\return lab theta value of heavy recoil in degrees
*/
getExc(t3,angle3);
return theta4;
}

207
Armory/Kinematics.h Normal file
View File

@ -0,0 +1,207 @@
#ifndef KINEMATICS_H
#define KINEMATICS_H
#include <TMath.h>
#include <iostream>
#include <fstream>
#include <string>
#include <TVector3.h>
const double u_MeV = 931.49410372; //u in MeV
class Kinematics {
public:
/*
A(d,p)B is used as template, with A being beam, and p being ejectile.
Always, make m3 the thing you detect, and m1 the beam
*/
double m_A, m_d, m_p, m_B;
double E_beam;
/**
* @{ \name List of funny impossible default values
*/
/**
* \brief Default values used for all the physics values
*/
double Q0=-9999, Qx=-9999;
double P4=-9999, E4=-9999, T4=-9999; //heavy recoil momentum, totE, KE
double P3=-9999, E3=-9999, T3=-9999; //light recoil
double ET=-9999;
double gamma4=-9999, beta4=-9999, theta4=-9999; //theta=heavy-recoil lab angle
double brho=-9999;
/**
* @}
*/
Kinematics(double m1, double m2, double m3, double m4, double ebeam) {
/*
A(d,p)B is used as template, with A being beam, and p being ejectile.
Always, make m3 the thing you detect, m2 the target, and m1 the beam
ebeam is in MeV/u, all others are in amu
*/
m_A = m1;
m_d = m2;
m_p = m3, m_B = m4, E_beam = ebeam*m_A;
Q0 = (- m_B - m_p + m_d + m_A)*u_MeV;
}
Kinematics() {}
void setValues(double m1, double m2, double m3, double m4, double ebeam) {
/*
Can be used to 'live update' say the beam energy in the case of active target detectors.
*/
m_A = m1;
m_d = m2;
m_p = m3, m_B = m4, E_beam = ebeam*m_A;
Q0 = (- m_B - m_p + m_d + m_A)*u_MeV;
//std::cout << "Q0 MeV: " << Q0 << std::endl;
}
void setEBeam(double Ebeam) {E_beam = Ebeam;}
double getBeta4(double t3, double angle3);
double getTheta4(double t3, double angle3);
double getBrho(double t3, double angle3, double charge_state);
double getExc(double t3, double angle3); //t3 is proton energy detected in ORRUBA, angle3 is proton angle in degrees
double getBeta4_fromvec(double t3, const TVector3 &pos, const TVector3 &origin) {
TVector3 local = pos - origin; //position w.r.t origin
float angle = local.Theta()*180./M_PI;
return getBeta4(t3, angle);
}
double getExc_fromvec(double t3, const TVector3 &pos, const TVector3 &origin) {
TVector3 local = pos - origin; //position w.r.t origin
float angle = local.Theta()*180./M_PI;
return getExc(t3, angle);
}
void setValuesFromFile(const std::string& filename) {
(void) filename;
/*std::ifstream in;
in.open(filename);
if(!in) {
std::cerr<< "File not open at " << filename << std::endl;
return;
}
for(std::string line; std::getline(in, line); ) {
if(line.size()!=0 && line[0]=='#')
; //don't do anything with '#' lines
else {
std::stringstream ss(line);
ss>>m_A>>m_d>>m_p>>m_B>>E_beam;
}
}
in.close();*/
}
};
//double Kinematics::getQval(double m1, double m2, double m3, double t1, double t3, double angle3)
double Kinematics::getExc(double t3, double angle3)
/*
\brief Follows convention in Marion, 2013: (1 - beam, 2- target, 3-ejectile, 4-recoil)
m1 is beam, (typically heavy nucleus)
m2 is 'd', (light target)
m3 is 'p', (light ejectile mass)
t1 is beam kinetic energy,
All calculations are done here, and other wrapper functions written make derived quantities from stuff calculated here.
\param t3 lab-kinetic-energy in MeV
\param angle3 lab-angle in deg of detected proton (if d,p) or other charged particle
\return Excitation energy of the heavy-recoil nucleus in MeV
*/
{
double m1 = m_A;
double m2 = m_d;
double m3 = m_p;
double m4 = m_B;
double t1 = E_beam;
m1 *= u_MeV;
m2 *= u_MeV;
m3 *= u_MeV;
m4 *= u_MeV;
double e1 = m1 + t1;
double e3 = m3 + t3;
ET = t1 + m1 + m2;
double p1 = TMath::Sqrt(t1*t1 + 2*m1*t1);
double p3 = TMath::Sqrt(t3*t3 + 2*m3*t3);
double cosTheta = TMath::Cos(angle3*TMath::Pi()/180.);
// return m1+m2-m3-TMath::Sqrt(m1*m1 + m2*m2 + m3*m3 + 2.*m2*e1 - 2.*e3*(e1+m2)+ 2.*p1*p3*cosTheta);
double Q = m1+m2-m3-TMath::Sqrt(m1*m1 + m2*m2 + m3*m3 + 2.*m2*e1 - 2.*e3*(e1+m2)+ 2.*p1*p3*cosTheta);
Qx = Q;
//Recoil properties just in case it's useful
T4 = ET - e3 - (m1+m2-m3-Q);
P4 = TMath::Sqrt(T4*T4 + 2*m4*T4);
//this angle will not be affected by eloss
theta4 = (180./M_PI)*TMath::ASin((p3/P4)*TMath::Sin(angle3*M_PI/180.));
//recalculate everything other than angle with lowered Kinetic energy
P4 = TMath::Sqrt(T4*T4 + 2*m4*T4);
gamma4 = T4/m4+1.;
beta4 = TMath::Sqrt(1. - 1./(gamma4*gamma4));
theta4 = (180./M_PI)*TMath::ASin((p3/P4)*TMath::Sin(angle3*M_PI/180.));
return Q0 - Q;//Q0 = Q + Exc
}
double Kinematics::getBeta4(double t3, double angle3)
/*
\brief Follows convention in Marion, 2013: (1 - beam, 2- target, 3-ejectile, 4-recoil)
m1 is beam, (typically heavy nucleus)
m2 is 'd', (light target)
m3 is 'p', (light ejectile mass)
t1 is beam kinetic energy,
\param t3 lab-kinetic-energy in MeV
\param angle3 lab-angle in deg of detected proton (if d,p) or other charged particle
\return doppler-shift beta (=v/c) of the heavy-recoil nucleus, calls getExc() to fill the value
*/
{
getExc(t3, angle3);
return beta4;
}
double Kinematics::getBrho(double t3, double angle3, double charge_state) {
/*
\brief Follows convention in Marion, 2013: (1 - beam, 2- target, 3-ejectile, 4-recoil)
m1 is beam, (typically heavy nucleus)
m2 is 'd', (light target)
m3 is 'p', (light ejectile mass)
t1 is beam kinetic energy,
\param t3 lab-kinetic-energy in MeV
\param angle3 lab-angle in deg of detected proton (if d,p) or other charged particle
\param charge_state charge state of the intended nucleus, in units of elementary charge (= +1 for H+, +2 for He2+ etc).
\return b-rho value, generated from P4*3.3359e-3/charge_state where P4 is 4momentum of heavy-recoil calculated from orruba kinematics
*/
getExc(t3,angle3);
return P4*3.3359e-3/charge_state;
}
double Kinematics::getTheta4(double t3, double angle3) {
/*
\brief Follows convention in Marion, 2013: (1 - beam, 2- target, 3-ejectile, 4-recoil)
m1 is beam, (typically heavy nucleus)
m2 is 'd', (light target)
m3 is 'p', (light ejectile mass)
t1 is beam kinetic energy,
\param t3 lab-kinetic-energy in MeV
\param angle3 lab-angle in deg of detected proton (if d,p) or other charged particle
\param charge_state charge state of the intended nucleus, in units of elementary charge (= +1 for H+, +2 for He2+ etc).
\return lab theta value of heavy recoil in degrees
*/
getExc(t3,angle3);
return theta4;
}
#endif

674
Armory/LICENSE Normal file
View File

@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.

View File

@ -10,7 +10,7 @@ COPTS = -fPIC -DLINUX -g -O0 -Wall -std=c++17 -lpthread
ROOTLIBS = `root-config --cflags --glibs`
ALL = Mapper AnasenMS
ALL = Mapper EventBuilder#AnasenMS
#########################################################################
@ -23,6 +23,10 @@ Mapper : Mapper.cpp ../mapping.h ClassDet.h
@echo "--------- making Mapper"
$(CC) $(COPTS) -o Mapper Mapper.cpp $(ROOTLIBS)
AnasenMS : constant.h Isotope.h ClassTransfer.h ClassSX3.h ClassPW.h ClassAnasen.h anasenMS.cpp
@echo "--------- making ANASEN Monte Carlo"
$(CC) $(COPTS) -o AnasenMS anasenMS.cpp $(ROOTLIBS)
# AnasenMS : constant.h Isotope.h ClassTransfer.h ClassSX3.h ClassPW.h ClassAnasen.h anasenMS.cpp
# @echo "--------- making ANASEN Monte Carlo"
# $(CC) $(COPTS) -o AnasenMS anasenMS.cpp $(ROOTLIBS)
EventBuilder : EventBuilder.cpp ClassData.h fsuReader.h Hit.h
@echo "--------- making EventBuilder"
$(CC) $(COPTS) -o EventBuilder EventBuilder.cpp $(ROOTLIBS)

View File

@ -11,12 +11,14 @@
#include "ClassDet.h"
//===============================
int main(int argc, char **argv){
int main(int argc, char **argv)
{
printf("=========================================\n");
printf("=== Mapper ===\n");
printf("=========================================\n");
if (argc != 2) {
printf("=========================================\n");
if (argc != 2)
{
printf("Incorrect number of arguments:\n");
printf("%s [inFile]\n", argv[0]);
printf("\n\n");
@ -28,155 +30,188 @@ int main(int argc, char **argv){
PrintMapping();
TFile * inFile = new TFile(inFileName.c_str(), "READ");
TTree * tree = (TTree*) inFile->Get("tree");
TFile *inFile = new TFile(inFileName.c_str(), "READ");
TTree *tree = (TTree *)inFile->Get("tree");
unsigned long long totnumEntry = tree->GetEntries();
ULong64_t evID;
UInt_t multi;
UShort_t sn[MAXMULTI];
UShort_t ch[MAXMULTI];
UShort_t e[MAXMULTI];
UShort_t e2[MAXMULTI];
ULong64_t e_t[MAXMULTI];
UShort_t e_f[MAXMULTI];
ULong64_t evID;
UInt_t multi;
UShort_t sn[MAXMULTI];
UShort_t ch[MAXMULTI];
UShort_t e[MAXMULTI];
UShort_t e2[MAXMULTI];
ULong64_t e_t[MAXMULTI];
UShort_t e_f[MAXMULTI];
tree->SetBranchAddress("evID", &evID);
tree->SetBranchAddress("evID", &evID);
tree->SetBranchAddress("multi", &multi);
tree->SetBranchAddress("sn", sn);
tree->SetBranchAddress("ch", ch);
tree->SetBranchAddress("e", e);
tree->SetBranchAddress("e2", e2);
tree->SetBranchAddress("e_t", e_t);
tree->SetBranchAddress("e_f", e_f);
tree->SetBranchAddress("sn", sn);
tree->SetBranchAddress("ch", ch);
tree->SetBranchAddress("e", e);
tree->SetBranchAddress("e2", e2);
tree->SetBranchAddress("e_t", e_t);
tree->SetBranchAddress("e_f", e_f);
///================== new tree
TString outFileName = inFileName;
TString runStr = outFileName;
TString runStr = outFileName;
int pos = outFileName.Last('/');
pos = outFileName.Index("_", pos+1); // find next "_"
runStr.Remove(0, pos+1);
pos = outFileName.Index("_", pos + 1); // find next "_"
runStr.Remove(0, pos + 1);
runStr.Remove(3);
pos = outFileName.Index("_", pos+1); // find next "_"
outFileName.Remove(pos); // remove the rest
pos = outFileName.Index("_", pos + 1); // find next "_"
outFileName.Remove(pos); // remove the rest
outFileName += "_mapped.root";
ULong_t eventID;
UInt_t run = runStr.Atoi();
Det sx3;
Det qqq;
Det pc ;
Det pc;
Det misc;
printf(" Raw root file : %s\n", inFileName.c_str());
printf(" Run : %03d\n", run);
printf(" total Entry : %lld \n", totnumEntry);
printf(" Out file name : %s \n", outFileName.Data());
TFile * saveFile = new TFile( outFileName,"RECREATE");
TTree * newTree = new TTree("tree","tree");
TFile *saveFile = new TFile(outFileName, "RECREATE");
TTree *newTree = new TTree("tree", "tree");
newTree->Branch("evID", &eventID,"eventID/l");
newTree->Branch("run", &run,"run/i");
newTree->Branch("evID", &eventID, "eventID/l");
newTree->Branch("run", &run, "run/i");
newTree->Branch("sx3Multi", &sx3.multi, "sx3Multi/s");
newTree->Branch("sx3ID", &sx3.id, "sx3ID[sx3Multi]/s");
newTree->Branch("sx3Ch", &sx3.ch, "sx3Ch[sx3Multi]/s");
newTree->Branch("sx3E", &sx3.e, "sx3Energy[sx3Multi]/s");
newTree->Branch("sx3T", &sx3.t, "sx3Time[sx3Multi]/l");
newTree->Branch("sx3ID", &sx3.id, "sx3ID[sx3Multi]/s");
newTree->Branch("sx3Ch", &sx3.ch, "sx3Ch[sx3Multi]/s");
newTree->Branch("sx3E", &sx3.e, "sx3Energy[sx3Multi]/s");
newTree->Branch("sx3T", &sx3.t, "sx3Time[sx3Multi]/l");
newTree->Branch("qqqMulti", &qqq.multi, "qqqMulti/s");
newTree->Branch("qqqID", &qqq.id, "qqqID[qqqMulti]/s");
newTree->Branch("qqqCh", &qqq.ch, "qqqCh[qqqMulti]/s");
newTree->Branch("qqqE", &qqq.e, "qqqEnergy[qqqMulti]/s");
newTree->Branch("qqqT", &qqq.t, "qqqTime[qqqMulti]/l");
newTree->Branch("qqqSN", &qqq.sn, "qqqSN[qqqMulti]/s");
newTree->Branch("qqqID", &qqq.id, "qqqID[qqqMulti]/s");
newTree->Branch("qqqCh", &qqq.ch, "qqqCh[qqqMulti]/s");
newTree->Branch("qqqE", &qqq.e, "qqqEnergy[qqqMulti]/s");
newTree->Branch("qqqT", &qqq.t, "qqqTime[qqqMulti]/l");
newTree->Branch("qqqSN", &qqq.sn, "qqqSN[qqqMulti]/s");
newTree->Branch("pcMulti", &pc.multi, "pcMulti/s");
newTree->Branch("pcID", &pc.id, "pcID[pcMulti]/s");
newTree->Branch("pcCh", &pc.ch, "pcCh[pcMulti]/s");
newTree->Branch("pcE", &pc.e, "pcEnergy[pcMulti]/s");
newTree->Branch("pcT", &pc.t, "pcTime[pcMulti]/l");
newTree->Branch("pcID", &pc.id, "pcID[pcMulti]/s");
newTree->Branch("pcCh", &pc.ch, "pcCh[pcMulti]/s");
newTree->Branch("pcE", &pc.e, "pcEnergy[pcMulti]/s");
newTree->Branch("pcT", &pc.t, "pcTime[pcMulti]/l");
newTree->Branch("miscMulti", &misc.multi, "miscMulti/s");
newTree->Branch("miscID", &misc.id, "miscID[miscMulti]/s");
newTree->Branch("miscCh", &misc.ch, "miscCh[miscMulti]/s");
newTree->Branch("miscE", &misc.e, "miscEnergy[miscMulti]/s");
newTree->Branch("miscT", &misc.t, "miscTime[miscMulti]/l");
newTree->Branch("miscF", &misc.tf, "miscFineTime[miscMulti]/l");
///================== looping old tree and apply mapping
//clock
// TBenchmark clock;
// Bool_t shown;
// clock
// TBenchmark clock;
// Bool_t shown;
for( unsigned long long ev = 0; ev < totnumEntry; ev++){
for (unsigned long long ev = 0; ev < totnumEntry; ev++)
{
tree->GetEntry(ev);
eventID = evID;
sx3.multi = 0;
qqq.multi = 0;
pc.multi = 0;
misc.multi = 0;
sx3.Clear();
sx3.Clear();
qqq.Clear();
pc.Clear();
misc.Clear();
pc.Clear();
misc.Clear();
for( unsigned int i = 0; i < multi; i++){
for (unsigned int i = 0; i < multi; i++)
{
// printf("%10u/%10u| %5d, %2u, %6u, %14llu\n", i, multi, sn[i], ch[i], e[i], e_t[i] );
//globalCh = digi-ID * nCh(digi-iD) + ch
// globalCh = digi-ID * nCh(digi-iD) + ch
int globalCh = -1;
for( int j = 0; j < nBd; j++){
if( board.at(j) == sn[i]){
globalCh = (sn[i] > 1000 ? j * 64 : 7*64 + (j-7) * 16) + ch[i]; //& = number V1740
for (int j = 0; j < nBd; j++)
{
if (board.at(j) == sn[i])
{
globalCh = (sn[i] > 1000 ? j * 64 : 7 * 64 + (j - 7) * 16) + ch[i]; //& = number V1740
break;
}
}
if( globalCh == -1) printf("ev %llu\n", ev);
if (globalCh == -1)
printf("ev %llu\n", ev);
unsigned short ID = mapping[globalCh];
//=================================== sx3
if( ID < 10000 ) {
if (ID < 10000)
{
sx3.id[sx3.multi] = ID / 100;
sx3.ch[sx3.multi] = ID % 100;
sx3.e[sx3.multi] = e[i];
sx3.t[sx3.multi] = e_t[i];
sx3.multi ++;
sx3.multi++;
}
//=================================== qqq
if( 10000 <= ID && ID < 20000 ) {
if (10000 <= ID && ID < 20000)
{
qqq.id[qqq.multi] = (ID - 10000) / 100;
qqq.ch[qqq.multi] = (ID - 10000) % 100;
qqq.e[qqq.multi] = e[i];
qqq.t[qqq.multi] = e_t[i];
qqq.sn[qqq.multi] = sn[i];
qqq.multi ++;
qqq.multi++;
}
//=================================== pc
if( 20000 <= ID && ID < 30000 ) {
if (20000 <= ID && ID < 30000)
{
pc.id[pc.multi] = (ID - 20000) / 100;
pc.ch[pc.multi] = (ID - 20000) % 100;
pc.e[pc.multi] = e[i];
pc.t[pc.multi] = e_t[i];
pc.multi ++;
pc.multi++;
}
//=================================== misc
if (30000 <= ID && ID < 40000)
{
misc.id[misc.multi] = (ID - 30000) / 100;
misc.ch[misc.multi] = (ID - 30000) % 100;
misc.e[misc.multi] = e[i];
misc.t[misc.multi] = e_t[i];
misc.tf[misc.multi] = e_f[i];
// if( ID == 30002 || ID == 30004 ) printf("sn : %d ch: %2d | gID %3d | ID %6d | e_f : %d\n", sn[i], ch[i], globalCh, ID, e_f[i]);
misc.multi++;
}
}
saveFile->cd(); //set focus on this file
newTree->Fill();
if( eventID % 100 == 0 ) printf("%6lu/%6llu [%2d%%]\n\033[A\r", eventID, totnumEntry, TMath::Nint((eventID+1)*100./totnumEntry));
saveFile->cd(); // set focus on this file
newTree->Fill();
if (eventID % 100 == 0)
printf("%6lu/%6llu [%2d%%]\n\033[A\r", eventID, totnumEntry, TMath::Nint((eventID + 1) * 100. / totnumEntry));
}
inFile->Close();
saveFile->cd(); //set focus on this file
newTree->Write();
saveFile->cd(); // set focus on this file
newTree->Write();
UInt_t eventNumber = newTree->GetEntries();
saveFile->Close();
printf("-------------- done, %u\n", eventNumber);
return 0;
return 0;
}

331
Armory/PCZRecon.h Normal file
View File

@ -0,0 +1,331 @@
#ifndef PCZRecon_h
#define PCZRecon_h
// PC Z-position reconstruction, one section per anode/cathode topology:
// A1C0 (single anode wire only), A2C0 (two-wire anode cluster, no cathode --
// same math as A1C0, see that section), A1C1 (anode + single cathode, charge
// division), A1C2 (anode + two cathodes, "step ladder" correction). Each topology gets one
// well-defined entry point instead of the math being split across files by
// historical accident (A1C0/A1C1 used to live in TrackRecon.C itself; A1C2's
// underlying model lives in the separately-shared PC_StepLadder_Correction.h
// -- see the A1C2 section below for why that one isn't just moved in).
//
// This header holds the reconstruction MATH only. The per-dataset tuning
// constants it reads (cfrac fit parameters, dead-wire lists, Z calibration,
// beam-axis origin) are still owned and set by TrackRecon.C's Begin() --
// this header just declares them `extern` so the same single translation
// unit (TrackRecon.C is compiled as one .C file via ACLiC) can see them.
// Moving the constants themselves out is a separate, riskier change and is
// deliberately NOT done here.
//
// Relocated verbatim from TrackRecon.C (no logic changes): A1C1CellSol,
// A1C1Sol, solve_cell, a1c1_solve, SideChoice, a1c1_pick_side, a1c1_zcorr,
// a1c0_hybrid_pcz (split into a1c0_wirePos + a1c0_hybrid_pcz).
//
// New in this header (see call-site migration notes where each is used):
// a1c1_solve_pick, a1c1_cfrac_pcz, a1c2_zfix.
#include <TVector3.h>
#include <TRandom3.h>
#include <TMath.h>
#include "ClassPW.h"
// --- Per-dataset tuning constants, defined and set in TrackRecon.C ---
extern PW pwinstance;
extern const double a1c1_zg[8];
extern double a1c1_cfmin_cell[7];
extern double a1c1_k_cell[7];
extern double a1c1_cfmin2_cell[7];
extern double a1c1_k2_cell[7];
extern double a1c1_cfrac_split;
extern double a1c1_lowband_rfactor;
extern double a1c1_missing_fmax;
extern double a1c1_z_scale_qqq;
extern double a1c1_z_off_qqq;
extern double a1c1_z_scale_sx3;
extern double a1c1_z_off_sx3;
bool a1c1_missing_neighbor(int awire, int cwire); // TrackRecon.C: dead-wire-adjacency check
TVector3 beamVertex(const TVector3 &si, const TVector3 &dir);
double beamPerp(const TVector3 &p);
// ---------------------------------------------------------------------
// A1C0: single-wire (anode only) position reconstruction
// ---------------------------------------------------------------------
// Anode-wire Z, corrected by the a1c1-derived scale+offset (a1c1_zcorr),
// same reference frame the a1c1 solve below reports in.
inline double a1c1_zcorr(double z_a1c0, bool isQQQ)
{
double scale = isQQQ ? a1c1_z_scale_qqq : a1c1_z_scale_sx3;
double off = isQQQ ? a1c1_z_off_qqq : a1c1_z_off_sx3;
return z_a1c0 * (1.0 - scale) - off;
}
// The raw (undithered) A1C0 wire position: nearest-wire XY at the given
// track phi, Z corrected into the same frame a1c1 uses. This is what
// _rawZ_a1c0 / phi cuts / _dPhi_a1c0-style diagnostics should read --
// anywhere you want the true discretized wire position, not a smoothed one.
inline TVector3 a1c0_wirePos(const std::pair<TVector3, TVector3> &apwire, double phi, bool isQQQ)
{
TVector3 pc = pwinstance.getClosestWirePosAtWirePhi(apwire, phi);
pc.SetZ(a1c1_zcorr(pc.Z(), isQQQ));
return pc;
}
// a1c0_wirePos, then Gaussian-dithered in Z to hide wire-pitch quantization.
// sigma is the caller's choice (dither_sigma or dither_sigma_c0/2.0 etc in
// TrackRecon.C) -- this function doesn't know which convention is "correct"
// for a given call site, only how to apply whichever sigma it's given.
inline TVector3 a1c0_hybrid_pcz(const std::pair<TVector3, TVector3> &apwire, double phi,
bool isQQQ, double sigma, TRandom3 &rand)
{
TVector3 pc = a1c0_wirePos(apwire, phi, isQQQ);
pc.SetZ(rand.Gaus(pc.Z(), sigma));
return pc;
}
// ---------------------------------------------------------------------
// A2C0: two-wire anode-cluster (charge-shared), no-cathode position
// reconstruction
// ---------------------------------------------------------------------
//
// Same math as A1C0 above -- pseudowire + phi-minimization, no dither --
// just handed a genuine 2-wire energy-weighted pseudowire (GetPseudoWire
// over a 2-wire anode cluster) instead of a single real wire. a1c0_wirePos
// already treats its `apwire` argument as an opaque pseudowire pair
// regardless of how many physical wires went into it, so this is a
// documented, named entry point rather than new math: call sites can say
// what topology they mean instead of reusing a1c0_wirePos silently and
// trusting a comment to explain why.
//
// Deliberately no dithered twin (no a2c0_hybrid_pcz): A2C0 is meant to feed
// the reaction-analysis plots at its raw, undithered resolution, not stand
// in for a1c0_hybrid_pcz's benchmark-truth-comparison role. If a "genuine
// A2C0" BenchMark validation block is ever wanted (mirroring the existing
// aClusters.size()==1 && cClusters.size()==0 A1C0 block in TrackRecon.C),
// add one there rather than adding dithering here.
inline TVector3 a2c0_wirePos(const std::pair<TVector3, TVector3> &apwire, double phi, bool isQQQ)
{
return a1c0_wirePos(apwire, phi, isQQQ);
}
// ---------------------------------------------------------------------
// A1C1: single-anode + single-cathode charge-division position reconstruction
// ---------------------------------------------------------------------
struct A1C1CellSol
{
int cell = -1;
double pcz = -99999;
double f = 0.0;
double pitch = 0.0;
bool inband = false;
bool pitchok = false;
};
struct A1C1Sol
{
int band;
double cfrac_used;
double pcz_lo;
double pcz_hi;
A1C1CellSol hi;
A1C1CellSol lo;
};
inline A1C1CellSol solve_cell(int cell, int wf, double zf, double cfrac,
const double *cfmin, const double *kk, bool dead_neighbor)
{
A1C1CellSol s;
s.cell = cell;
s.pcz = zf; // safe sentinel: fired-wire position so edge-wire defaults don't read as z=0
if (cell < 0 || cell > 6)
return s;
double zc = 0.5 * (a1c1_zg[cell] + a1c1_zg[cell + 1]); // cell centre
double half = 0.5 * (a1c1_zg[cell] - a1c1_zg[cell + 1]); // half-cell width
double pitch = a1c1_zg[cell] - a1c1_zg[cell + 1]; // full wire spacing
if (half <= 0.0 || kk[cell] <= 0.0)
return s;
s.pitch = pitch;
// f = 0 -> cell centre, f = 1 -> fired wire. Outside [0,1] = outside the band.
s.f = (cfrac - cfmin[cell]) / kk[cell];
// sign maps increasing f toward the fired cathode wire.
double sgn = (a1c1_zg[wf] >= zc) ? +1.0 : -1.0;
s.pcz = zc + sgn * s.f * half;
double fmax = dead_neighbor ? a1c1_missing_fmax : 1.0;
s.inband = (s.f >= 0.0 && s.f <= fmax);
// Reconstructed position should remain within one cell pitch of the fired wire.
s.pitchok = (TMath::Abs(s.pcz - zf) <= pitch);
return s;
}
inline A1C1Sol a1c1_solve(double cfrac, double zf, int cwire = -1, double anodeE = -1, int awire = -1)
{
A1C1Sol s{0, cfrac, zf, zf, {}, {}};
const double *cfmin = a1c1_cfmin_cell;
const double *kk = a1c1_k_cell;
if (a1c1_cfrac_split > 0.0 && cfrac >= 0.0 && cfrac < a1c1_cfrac_split)
{
s.band = 1;
if (a1c1_lowband_rfactor > 0.0 && cfrac > 0.0 && cfrac < 1.0)
{
double r = cfrac / (1.0 - cfrac);
r *= a1c1_lowband_rfactor;
cfrac = r / (1.0 + r);
}
else
{
cfmin = a1c1_cfmin2_cell;
kk = a1c1_k2_cell;
}
}
s.cfrac_used = cfrac;
int wf = 0;
for (int i = 1; i < 8; ++i)
if (TMath::Abs(a1c1_zg[i] - zf) < TMath::Abs(a1c1_zg[wf] - zf))
wf = i;
bool dead_neighbor = a1c1_missing_neighbor(awire, cwire); // same for both cells; hoist to avoid double scan
s.hi = solve_cell(wf - 1, wf, zf, cfrac, cfmin, kk, dead_neighbor); // cell above (higher z)
s.lo = solve_cell(wf, wf, zf, cfrac, cfmin, kk, dead_neighbor); // cell below (lower z)
s.pcz_hi = s.hi.pcz;
s.pcz_lo = s.lo.pcz;
return s;
}
// Which of the two candidate cells the beam-axis test selects.
enum class SideChoice
{
High, // the cell ABOVE the fired wire (pcz_hi)
Low // the cell BELOW the fired wire (pcz_lo)
};
// Picks between the two candidate cells (lo/hi) a1c1_solve returns.
//
// This used to compare beamVertex(si, ...)'s beamPerp for each candidate, but
// that check is a no-op: both candidates share the same (cx, cy), so
// beamVertex's t-parameter -- computed purely from dir.X()/dir.Y() -- comes out
// identical for both, and therefore so does the resulting vertex X/Y and
// beamPerp. The two disambiguating branches below were consequently dead code;
// the function always fell through to returning Low regardless of the actual
// physics.
//
// inband/pitchok DO differ between the two cells (each depends on cfrac's
// position within that cell's own band), so they're used here for status
// instead. The final tie-break prefers whichever candidate sits closer to the
// fired wire itself, rather than farther into the next cell over.
inline SideChoice a1c1_pick_side(const A1C1CellSol &lo, const A1C1CellSol &hi, double zf, int &status)
{
bool okl = lo.inband && lo.pitchok;
bool okh = hi.inband && hi.pitchok;
status = (okl || okh) ? ((okl && okh) ? 1 : 0) : 2;
if (okl && !okh)
return SideChoice::Low;
if (okh && !okl)
return SideChoice::High;
double dl = TMath::Abs(lo.pcz - zf);
double dh = TMath::Abs(hi.pcz - zf);
return (dl <= dh) ? SideChoice::Low : SideChoice::High;
}
// a1c1_solve() + a1c1_pick_side() together, with the picked cell already
// resolved. Every call site that needs more than just the final pcz (side
// status, cell index, f, inband, pitchok -- e.g. for benchmark/diagnostic
// histograms) was previously re-deriving this same 4-line pattern by hand;
// this is that pattern, named once.
struct A1C1PickedSol
{
A1C1Sol sol;
SideChoice side = SideChoice::Low;
int side_status = -1;
const A1C1CellSol &best() const { return (side == SideChoice::High) ? sol.hi : sol.lo; }
};
inline A1C1PickedSol a1c1_solve_pick(double cfrac, double zf, const TVector3 &si, double cx, double cy,
int cwire = -1, double anodeE = -1, int awire = -1)
{
// si/cx/cy are no longer used by the pick itself (see a1c1_pick_side above)
// but are kept in the signature so the ~10 existing call sites throughout
// TrackRecon.C don't need to change.
A1C1PickedSol out;
out.sol = a1c1_solve(cfrac, zf, cwire, anodeE, awire);
out.side = a1c1_pick_side(out.sol.lo, out.sol.hi, zf, out.side_status);
return out;
}
// Full pipeline: raw anode/cathode energies -> cfrac -> solve -> pick side ->
// picked Z, plus whether the pick landed in-band. This is what most call
// sites actually want (they don't need the intermediate A1C1Sol/side_status
// unless they're doing benchmark diagnostics -- for that, call
// a1c1_solve_pick directly instead).
//
// Takes primitives rather than TrackRecon.C's `Event` type so this header
// has no dependency on TrackRecon.C's class definitions; see the thin
// `Event`-taking overload kept in TrackRecon.C next to the `Event` class
// for the short call-site spelling existing code uses.
inline double a1c1_cfrac_pcz(double pcz_raw, double energyAnode, double energyCathode,
double cx, double cy, int cathodeCh, int anodeCh,
const TVector3 &si, bool &inband)
{
inband = false;
double ac = energyAnode + energyCathode;
double cfrac = (ac > 0.0) ? energyCathode / ac : -1.0;
if (cfrac < 0.0)
return pcz_raw;
A1C1PickedSol picked = a1c1_solve_pick(cfrac, pcz_raw, si, cx, cy, cathodeCh, energyAnode, anodeCh);
const A1C1CellSol &best = picked.best();
inband = (best.inband && picked.side_status != 2);
return best.pcz;
}
// ---------------------------------------------------------------------
// A1C2: two-cathode "step ladder" Z reconstruction
// ---------------------------------------------------------------------
//
// Same step-ladder, pivot-about-cell-midpoint model as Armory/
// PC_StepLadder_Correction.h's model_invert. That file is shared with
// MakeVertex.C (a separate, parallel branch of development, out of scope
// here) and a few scratch macros, so rather than include it or edit it,
// this is a standalone copy -- keeping every TrackRecon.C reconstruction
// path (A1C0/A1C1/A1C2) self-contained in this one header rather than
// reaching into a file with unrelated consumers. If the underlying model
// ever changes, both copies need the same edit; there are exactly two.
//
// Rewritten as a plain scalar function rather than kept in model_invert's
// original `double f(double *y, double *p)` shape -- that signature only
// existed to match TF1's raw-function-pointer constructor. TrackRecon.C
// used to go through a `TF1 pcfix_func` purely to get a callable out of it,
// calling only pcfix_func.Eval(z) at every site (npar `p` was never used,
// and nothing called .Draw()/.Integral()/anything else TF1-specific) --
// so the TF1 wrapper bought nothing. a1c2_zfix has the same scalar
// in/scalar out shape as a1c0_wirePos/a1c1_solve above instead.
inline double a1c2_zfix(double z)
{
double result = z;
double slope = 0.52;
double z_grid[8] = {148.2677, 102.1111, 59.8527, 19.7248, -19.7248, -59.8527, -102.1111, -148.2677};
for (int i = 0; i < 7; i++)
{
if (z <= z_grid[i] && z > z_grid[i + 1])
{
double zavg = (z_grid[i] + z_grid[i + 1]) * 0.5; // midpoint about which we pivot
result = (z - zavg) / slope + zavg;
break;
}
}
return result;
}
#endif

View File

@ -0,0 +1,82 @@
#include <TF1.h>
double model_invert(double *y, double *p)
{
double result = y[0];
double slope = 0.52;
double z_grid[8] = {147.998, 101.946, 59.7634, 19.6965, -19.6965, -59.7634, -101.946, -147.998};
for (int i = 0; i < 7; i++)
{
if (y[0] <= z_grid[i] && y[0] > z_grid[i + 1])
{
double zavg = (z_grid[i] + z_grid[i + 1]) * 0.5; // midpoint about which we pivot
result = (y[0] - zavg) / slope + zavg;
break;
}
}
return result;
}
// ---- Parabola variant (now takes optional slope; out-of-band -> Si, no clamp-snap) ----
inline double model_invert_a1c1(double cfrac, double z_si, double z_pivot,
double C, const double* c0_table, bool& ok,
double slope = 1.0)
{
ok = false;
static const double z_grid[8] =
{147.998, 101.946, 59.7634, 19.6965, -19.6965, -59.7634, -101.946, -147.998};
const double cfrac_hi = 0.90, cfrac_lo = 0.12;
if (cfrac > cfrac_hi || cfrac < cfrac_lo) return z_si;
const double sgn = (z_si >= z_pivot) ? +1.0 : -1.0;
int gnb = -1; double z_nb = (sgn > 0) ? +1.0e30 : -1.0e30;
for (int i = 0; i < 8; ++i) {
if (sgn > 0 && z_grid[i] > z_pivot + 1e-6 && z_grid[i] < z_nb) { z_nb = z_grid[i]; gnb = i; }
if (sgn < 0 && z_grid[i] < z_pivot - 1e-6 && z_grid[i] > z_nb) { z_nb = z_grid[i]; gnb = i; }
}
if (gnb < 0) return z_si;
const double W = TMath::Abs(z_nb - z_pivot);
if (W <= 0.0) return z_si;
int cell = (sgn > 0) ? gnb : gnb - 1;
if (cell < 0) cell = 0; if (cell > 6) cell = 6;
double s2 = (c0_table[cell] - cfrac) / C;
if (s2 < 0.0 || s2 > 1.0) return z_si; // out-of-band -> Si (no boundary snap)
double s = TMath::Sqrt(s2);
double z_rec = z_pivot + sgn * s * W;
if (slope > 0.0 && slope != 1.0) {
const double zc = 0.5 * (z_grid[cell] + z_grid[cell + 1]);
z_rec = (z_rec - zc) / slope + zc;
}
if (TMath::Abs(z_rec - z_si) > W) return z_si;
ok = true;
return z_rec;
}
// ---- Linear centre-fold variant ----
inline double model_invert_a1c1_linear(double cfrac, double z_si, double z_pivot,
double k, const double* cfmin_table, bool& ok)
{
ok = false;
static const double z_grid[8] =
{147.998, 101.946, 59.7634, 19.6965, -19.6965, -59.7634, -101.946, -147.998};
const double cfrac_hi = 0.90, cfrac_lo = 0.12;
if (cfrac > cfrac_hi || cfrac < cfrac_lo) return z_si;
int cell = -1;
for (int i = 0; i < 7; ++i)
if (z_si <= z_grid[i] && z_si > z_grid[i + 1]) { cell = i; break; }
if (cell < 0) return z_si;
const double zc = 0.5 * (z_grid[cell] + z_grid[cell + 1]);
const double half = 0.5 * (z_grid[cell] - z_grid[cell + 1]);
if (half <= 0.0 || k <= 0.0) return z_si;
double f = (cfrac - cfmin_table[cell]) / k;
if (f < 0.0) f = 0.0;
if (f > 1.0) return z_si;
const double d = f * half;
const double sgn = (z_pivot >= zc) ? +1.0 : -1.0;
const double z_rec = zc + sgn * d;
if (TMath::Abs(z_rec - z_si) > half) return z_si;
ok = true;
return z_rec;
}

190
Armory/SX3Geom.h Executable file
View File

@ -0,0 +1,190 @@
#ifndef SX3Geom_h
#define SX3Geom_h
#include <vector>
const double DEFAULT_NULL=-987654321.;
class sx3_geometry_scalefactors {
public:
//If sx3 has L, R being the left and right extremities, we choose add, stretch here such that
// x_mm = (x_raw+add)*stretch; so add=abs(L), stretch=75/(abs(L)+R)
float add[4];
float stretch[4];
};
class qqq5_finegains {
public:
std::array<std::pair<float,float>,32> front;
//front.at(30).first = slope at clkpos 0, ring 30 for E front layer
//front.at(30).second = intercept for the same as above
std::array<std::pair<float,float>,4> back;
};
class sx3_fbgains {
public:
//Order of indices is [pad][strip]
float padoffsets[4][4];
float padgains[4][4];
float stripLoffsets[4][4];
float stripLgains[4][4];
float stripRoffsets[4][4];
float stripRgains[4][4];
};
std::array<sx3_fbgains,24> sx3_xtalk_gains; //every sx3 needs to be gainmatched as a frontL-back, frontR-back pair (pad strip pair)
std::array<sx3_geometry_scalefactors,24> sx3gs;
class sx3 {
public:
//TODO: Convert to std::array
//Holds all information in an event, including ped subtraction+scaling. back[2].at(0) will have the largest energy seen in ch2, if any
std::vector<float> back[4];
std::vector<float> frontL[4];
std::vector<float> frontR[4];
double ts = DEFAULT_NULL;
//Easy lookup of final calibrated event. Only filled for valid cases, assumed for now to be 1L, 1R, 1B
float frontX=DEFAULT_NULL;
float frontXmm=DEFAULT_NULL;
float frontE=DEFAULT_NULL;
float backE=DEFAULT_NULL;
int stripF=DEFAULT_NULL;
int stripB=DEFAULT_NULL;
float frontEL=DEFAULT_NULL;
float frontER=DEFAULT_NULL;
float phi=DEFAULT_NULL; //
std::set<int> valid_front_chans;
std::set<int> valid_back_chans;
std::set<int> unmatched_front_chans; //every front channel is unmatched and invalid at first. when it gets matched, it gets removed and sent to valid
bool foundevent=false;
bool valid=false;//valid will be set to false in all cases where we have ambiguity
int flags=-1;//flags settable to different types of values to indicate different invalid situations
void fillevent(const std::string& position, const int subchannel, const float value); //make 'const' what functions don't need to change, helps with performance
void validate(const sx3_fbgains&, const sx3_geometry_scalefactors&);
void validate();
};
void sx3::fillevent(const std::string& positionstring, const int subchannel, const float value) {
assert(subchannel>=0 && subchannel<4);
foundevent=1;
if(positionstring=="FRONT_L") {
frontL[subchannel].push_back(value);
unmatched_front_chans.insert(subchannel);
} else if(positionstring=="FRONT_R") {
frontR[subchannel].push_back(value);
unmatched_front_chans.insert(subchannel);
} else if(positionstring=="BACK") {
back[subchannel].push_back(value);
valid_back_chans.insert(subchannel);
} else {
std::cout << "Unknown string "+positionstring+" encountered in sx3::fillevent \n" << std::endl;
}
if(frontL[subchannel].size()!=0 && frontR[subchannel].size()!=0 ) {
unmatched_front_chans.erase(subchannel);
valid_front_chans.insert(subchannel); //std::set, so no duplication will happen
}
}
//void sx3::validate(const sx3_fbgains& fbgains, const sx3_geometry_scalefactors& sx3gs) {
void sx3::validate() {
if(valid_front_chans.size()!=0 && valid_back_chans.size()!=0) {
valid=true;
float maxFE=0;
float maxBE=0;
//float zpos=0;
int bchan=-1;
int fchan=-1;
/* for(auto cc: valid_front_chans) {
std::cout << "fc" << cc << std::endl;// " " << frontL[cc].at(0) << " " << frontR[cc].at(0) << std::endl;
}
for(auto cc: valid_back_chans) {
std::cout << "bc" << cc << std::endl; //" " << back[cc].at(0) << std::endl;
}
*/
for(auto chan: valid_front_chans) {
if(frontL[chan].size()>1) {
printf("\nmultihit sx3 at Lsubchan:%d, ts:%1.13g\n",chan,ts);
for(const auto& e: frontL[chan]) printf("e: %f\t",e);
std::sort(frontL[chan].begin(), frontL[chan].end(), std::greater<float>());
flags += (-1000);
}
if(frontR[chan].size()>1) {
printf("\nmultihit sx3 at Rsubchan:%d, ts:%1.13g\n",chan,ts);
for(const auto& e: frontR[chan]) printf("e: %f\t",e);
std::sort(frontR[chan].begin(), frontR[chan].end(), std::greater<float>());
flags += (-2000);
}
//assign position using max L+R value
/*printf("chan:%d sizeL: %d sizeR: %d\n",chan, frontL[chan].size(), frontR[chan].size()); fflush(stdout);
printf("foo\n");
std::cout << "\nL:" << std::endl;
for(auto thing: frontL[chan]) std::cout << thing << " " << std::flush;
std::cout << "\nR:" << std::endl;
for(auto thing: frontR[chan]) std::cout << thing << " " << std::flush;*/
if(frontL[chan].at(0) + frontR[chan].at(0)>= maxFE) {
maxFE = frontL[chan].at(0) + frontR[chan].at(0);
//zpos = (frontL[chan].at(0)-frontR[chan].at(0))/maxFE;
fchan = chan;
}
}
for(auto chan: valid_back_chans) {
if(back[chan].size()>1) {
printf("\nmultihit sx3 at Bsubchan:%d, ts:%1.13g\n",chan,ts);
for(const auto& e: back[chan]) printf("e: %f\t",e);
std::sort(back[chan].begin(), back[chan].end(), std::greater<float>());
flags += (-3000);
}
if(back[chan].size() ==0 ) {
printf("foo\n");
//continue;
}
if(back[chan].at(0) >= maxBE) {
maxBE = back[chan].at(0);
bchan = chan;
}
}
/*
Cross-talk corrections are important when evaluating 'energy' signals from strips/pads.
They can cause unexpected behavior when used universally for all EL, ER cases, so we split scenarios in two.
- Positions along each strip (frontX) *are not* corrected for crosstalk.
- Total F and B energies (frontE, backE) *are*.
Sudarsan B, 31 Oct 2024
*/
if(fchan==-1 || bchan==-1) {
std::cout << "how" << std::endl;
std::cout << "fc " << std::flush; for(auto fc : valid_front_chans) std::cout << fc << " (" << frontL[fc].at(0) << "," << frontR[fc].at(0)<< ") "; std::cout << std::endl;
std::cout << "bc " << std::flush; for(auto bc : valid_back_chans) std::cout << bc << " " << back[bc].at(0) << std::flush; std::cout << std::endl;
}
float Eleft = frontL[fchan].at(0);
float Eright = frontR[fchan].at(0);
frontEL = Eleft;
frontER = Eright;
frontX = (Eleft-Eright)/(Eleft+Eright);
//frontXmm = (frontX+sx3gs.add[fchan])*sx3gs.stretch[fchan]; //convert to mm
//frontE = Eleft*fbgains.stripLgains[bchan][fchan] + fbgains.stripLoffsets[bchan][fchan]
// + Eright*fbgains.stripRgains[bchan][fchan] + fbgains.stripRoffsets[bchan][fchan];
//backE = back[bchan].at(0)*fbgains.padgains[bchan][fchan]+fbgains.padoffsets[bchan][fchan];
frontE = Eleft+Eright;
backE = maxBE;
stripF=fchan;
stripB=bchan;
flags = 0;
} else if(valid_front_chans.size()!=0 && valid_back_chans.size()==0) {
flags = -10;
} else if(valid_front_chans.size()==0 && valid_back_chans.size()!=0) {
flags = -20;
}
}
typedef sx3 sx3det;
#endif

795
Armory/fsuReader.h Normal file
View File

@ -0,0 +1,795 @@
#include "ClassData.h"
#include "Hit.h"
#include <algorithm>
#include <filesystem>
// #include "AggSeparator.h"
class FSUReader{
public:
FSUReader();
FSUReader(std::string fileName, uInt dataSize = 100, int verbose = 1);
FSUReader(std::vector<std::string> fileList, uInt dataSize = 100, int verbose = 1);
~FSUReader();
void OpenFile(std::string fileName, uInt dataSize, int verbose = 1);
bool IsOpen() const{return inFile == nullptr ? false : true;}
bool IsEndOfFile() const {
// printf("%s : %d | %ld |%ld\n", __func__, feof(inFile), ftell(inFile), inFileSize);
if(fileList.empty() ) {
if( (uLong )ftell(inFile) >= inFileSize){
return true;
}else{
return false;
}
}else{
if( fileID + 1 == (int) fileList.size() && ((uLong)ftell(inFile) >= inFileSize) ) {
return true;
}else{
return false;
}
}
}
void ScanNumBlock(int verbose = 1, uShort saveData = 0); // saveData = 0 (no save), 1 (no trace), 2 (with trace);
int ReadNextBlock(bool traceON = false, int verbose = 0, uShort saveData = 0); // saveData = 0 (no save), 1 (no trace), 2 (with trace);
int ReadBlock(unsigned int ID, int verbose = 0);
unsigned int GetFilePos() const {return filePos;}
unsigned long GetTotNumBlock() const{ return totNumBlock;}
std::vector<unsigned int> GetBlockTimestamp() const {return blockTimeStamp;}
Data * GetData() const{return data;}
std::string GetFileName() const{return fileName;}
int GetDPPType() const{return DPPType;}
int GetSN() const{return sn;}
int GetTick2ns() const{return tick2ns;}
int GetNumCh() const{return numCh;}
int GetFileOrder() const{return order;}
int GetChMask() const{return chMask;}
unsigned long GetFileByteSize() const {return inFileSize;}
void ClearHitList() { hit.clear();}
ulong GetHitListLength() const {return hit.size();}
std::vector<Hit> GetHitVector() const {return hit;}
void SortHit(int verbose = false);
Hit GetHit(int id) const {
if( id < 0 ) id = hit.size() + id;
return hit[id];
}
void ClearHitCount() {hitCount = 0;}
ulong GetHitCount() const{return hitCount;}
std::vector<Hit> ReadBatch(unsigned int batchSize = 1000000, bool verbose = false); // output the sorted Hit
// std::string SaveHit(std::vector<Hit> hitList, bool isAppend = false);
// std::string SaveHit2NewFile(std::string saveFolder = "./", std::string indexStr = "");
// void SortAndSaveTS(unsigned int batchSize = 1000000, bool verbose = false);
// off_t GetTSFileSize() const {return tsFileSize;}
//TODO
//void SplitFile(unsigned long hitSizePreFile);
void PrintHit(ulong numHit = -1, ulong startIndex = 0) {
for( ulong i = startIndex; i < std::min(numHit, hitCount); i++){
printf("%10zu ", i); hit[i].Print();
}
}
static void PrintHitListInfo(std::vector<Hit> * hitList, std::string name){
size_t n = hitList->size();
size_t s = sizeof(Hit);
printf("============== %s, size : %zu | %.2f MByte\n", name.c_str(), n, n*s/1024./1024.);
if( n > 0 ){
printf("t0 : %15llu ns\n", hitList->front().timestamp);
printf("t1 : %15llu ns\n", hitList->back().timestamp);
printf("dt : %15.3f ms\n", (hitList->back().timestamp - hitList->front().timestamp)/1e6);
}
}
void PrintHitListInfo(){
size_t n = hit.size();
size_t s = sizeof(Hit);
printf("============== reader.hit, size : %zu | %.2f MByte\n", n, n*s/1024./1024.);
if( n > 0 ){
printf("t0 : %15llu ns\n", hit.at(0).timestamp);
printf("t1 : %15llu ns\n", hit.back().timestamp);
printf("dt : %15.3f ms\n", (hit.back().timestamp - hit.front().timestamp)/1e6);
}
}
//void SaveAsCAENCoMPASSFormat();
private:
FILE * inFile;
Data * data;
std::string fileName;
std::vector<std::string> fileList;
short fileID;
unsigned long inFileSize;
unsigned int filePos;
unsigned long totNumBlock;
unsigned int blockID;
bool isDualBlock;
uShort sn;
uShort DPPType;
uShort tick2ns;
uShort order;
uShort chMask;
uShort numCh;
std::vector<unsigned int> blockPos;
std::vector<unsigned int > blockTimeStamp;
unsigned long hitCount;
std::vector<Hit> hit;
unsigned int word[1]; /// 4 byte
size_t dummy;
char * buffer;
off_t tsFileSize;
};
inline FSUReader::~FSUReader(){
delete data;
if( inFile ) fclose(inFile);
}
inline FSUReader::FSUReader(){
inFile = nullptr;
data = nullptr;
blockPos.clear();
blockTimeStamp.clear();
hit.clear();
fileList.clear();
fileID = -1;
}
inline FSUReader::FSUReader(std::string fileName, uInt dataSize, int verbose){
inFile = nullptr;
data = nullptr;
blockPos.clear();
blockTimeStamp.clear();
hit.clear();
fileList.clear();
fileID = -1;
OpenFile(fileName, dataSize, verbose);
}
inline FSUReader::FSUReader(std::vector<std::string> fileList, uInt dataSize, int verbose){
inFile = nullptr;
data = nullptr;
blockPos.clear();
blockTimeStamp.clear();
hit.clear();
//The files are the same DPPType and sn
this->fileList = fileList;
fileID = 0;
OpenFile(fileList[fileID], dataSize, verbose);
}
inline void FSUReader::OpenFile(std::string fileName, uInt dataSize, int verbose){
/// File format must be YYY...Y_runXXX_AAA_BBB_TT_CCC.fsu
/// YYY...Y = prefix
/// XXX = runID, 3 digits
/// AAA = board Serial Number, 3 digits
/// BBB = DPPtype, 3 digits
/// TT = tick2ns, any digits
/// CCC = over size index, 3 digits
if( inFile != nullptr ) fclose(inFile);
inFile = fopen(fileName.c_str(), "r");
if( inFile == NULL ){
printf("FSUReader::Cannot open file : %s \n", fileName.c_str());
this->fileName = "";
return;
}
this->fileName = fileName;
fseek(inFile, 0L, SEEK_END);
inFileSize = ftell(inFile);
if(verbose) printf("%s | file size : %ld Byte = %.2f MB\n", fileName.c_str() , inFileSize, inFileSize/1024./1024.);
fseek(inFile, 0L, SEEK_SET);
filePos = 0;
if( fileID > 0 ) return;
totNumBlock = 0;
blockID = 0;
blockPos.clear();
blockTimeStamp.clear();
hitCount = 0;
hit.clear();
//check is the file is *.fsu or *.fsu.X
size_t found = fileName.find_last_of('.');
std::string ext = fileName.substr(found + 1);
if( ext.find("fsu") != std::string::npos ) {
if(verbose > 1) printf("It is an raw data *.fsu format\n");
isDualBlock = false;
chMask = -1;
}else{
chMask = atoi(ext.c_str());
isDualBlock = true;
if(verbose > 1) printf("It is a splitted dual block data *.fsu.X format, dual channel mask : %d \n", chMask);
}
std::string fileNameNoExt;
found = fileName.find_last_of(".fsu");
size_t found2 = fileName.find_last_of('/');
if( found2 == std::string::npos ){
fileNameNoExt = fileName.substr(0, found-4);
}else{
fileNameNoExt = fileName.substr(found2+1, found-4);
}
// Split the string by underscores
std::istringstream iss(fileNameNoExt);
std::vector<std::string> tokens;
std::string token;
while (std::getline(iss, token, '_')) { tokens.push_back(token); }
sn = atoi(tokens[2].c_str());
tick2ns = atoi(tokens[4].c_str());
order = atoi(tokens[5].c_str());
DPPType = 0;
if( fileName.find("PHA") != std::string::npos ) DPPType = DPPTypeCode::DPP_PHA_CODE;
if( fileName.find("PSD") != std::string::npos ) DPPType = DPPTypeCode::DPP_PSD_CODE;
if( fileName.find("QDC") != std::string::npos ) DPPType = DPPTypeCode::DPP_QDC_CODE;
if( DPPType == 0 ){
fclose(inFile);
inFile = nullptr;
printf("Cannot find DPPType in the filename. Abort.");
return ;
}
numCh = (DPPType == DPPTypeCode::DPP_QDC_CODE ? 64 : 16);
data = new Data(numCh, dataSize);
data->tick2ns = tick2ns;
data->boardSN = sn;
data->DPPType = DPPType;
}
inline int FSUReader::ReadNextBlock(bool traceON, int verbose, uShort saveData){
if( inFile == NULL ) return -1;
if( feof(inFile) || filePos >= inFileSize) {
if( fileID >= 0 && fileID + 1 < (short) fileList.size() ){
printf("-------------- next file\n");
fileID ++;
OpenFile(fileList[fileID], data->GetDataSize(), 1 );
}else{
return -1;
}
}
dummy = fread(word, 4, 1, inFile);
fseek(inFile, -4, SEEK_CUR);
if( dummy != 1) {
printf("fread error, should read 4 bytes, but read %ld x 4 byte, file pos: %ld / %ld byte\n",
dummy, ftell(inFile), inFileSize);
return -10;
}
short header = ((word[0] >> 28 ) & 0xF);
Hit temp;
if( header == 0xA ) { ///normal header
unsigned int aggSize = (word[0] & 0x0FFFFFFF) * 4; ///byte
if( aggSize > inFileSize - ftell(inFile)) aggSize = inFileSize - ftell(inFile);
buffer = new char[aggSize];
dummy = fread(buffer, aggSize, 1, inFile);
filePos = ftell(inFile);
if( dummy != 1) {
printf("fread error, should read %d bytes, but read %ld x %d byte, file pos: %ld / %ld byte \n",
aggSize, dummy, aggSize, ftell(inFile), inFileSize);
return -30;
}
data->DecodeBuffer(buffer, aggSize, !traceON, verbose); // data will own the buffer
//printf(" word Index = %u | filePos : %u | ", data->GetWordIndex(), filePos);
}else if( (header & 0xF ) == 0x8 ) { /// dual channel header
unsigned int dualSize = (word[0] & 0x7FFFFFFF) * 4; ///byte
buffer = new char[dualSize];
dummy = fread(buffer, dualSize, 1, inFile);
filePos = ftell(inFile);
data->buffer = buffer;
data->DecodeDualBlock(buffer, dualSize, DPPType, chMask, !traceON, verbose);
}else{
printf("incorrect header.\n trminate.");
return -20;
}
unsigned int eventCout = 0;
for( int ch = 0; ch < data->GetNChannel(); ch++){
if( data->NumEventsDecoded[ch] == 0 ) continue;
hitCount += data->NumEventsDecoded[ch];
eventCout += data->NumEventsDecoded[ch];
if( saveData ){
int start = data->GetDataIndex(ch) - data->NumEventsDecoded[ch] + 1;
if( start < 0 ) start = start + data->GetDataSize();
for( int i = start; i < start + data->NumEventsDecoded[ch]; i++ ){
int k = i % data->GetDataSize();
temp.sn = sn;
temp.ch = ch;
temp.energy = data->GetEnergy(ch, k);
temp.energy2 = data->GetEnergy2(ch, k);
temp.timestamp = data->GetTimestamp(ch, k);
temp.fineTime = data->GetFineTime(ch, k);
temp.pileUp = data->GetPileUp(ch, k);
if( saveData > 1 ) {
temp.traceLength = data->Waveform1[ch][k].size();
temp.trace = data->Waveform1[ch][k];
}else{
temp.traceLength = 0;
if( temp.trace.size() > 0 ) temp.trace.clear();
}
hit.push_back(temp);
}
}
}
data->ClearTriggerRate();
data->ClearNumEventsDecoded();
data->ClearBuffer(); // this will clear the buffer.
return 0;
}
inline int FSUReader::ReadBlock(unsigned int ID, int verbose){
if( totNumBlock == 0 )return -1;
if( ID >= totNumBlock )return -1;
data->ClearData();
fseek( inFile, 0L, SEEK_SET);
if( verbose ) printf("Block index: %u, File Pos: %u byte\n", ID, blockPos[ID]);
fseek(inFile, blockPos[ID], SEEK_CUR);
filePos = blockPos[ID];
blockID = ID;
return ReadNextBlock(false, verbose, false);
}
inline void FSUReader::SortHit(int verbose){
if( verbose) printf("\nQuick Sort hit array according to time...");
std::sort(hit.begin(), hit.end(), [](const Hit& a, const Hit& b) {
return a.timestamp < b.timestamp;
});
if( verbose) printf(".......done.\n");
}
inline void FSUReader::ScanNumBlock(int verbose, uShort saveData){
if( inFile == nullptr ) return;
if( feof(inFile) ) return;
blockID = 0;
blockPos.push_back(0);
data->ClearData();
rewind(inFile);
filePos = 0;
bool isTraceOn = saveData < 2 ? false : true;
while( ReadNextBlock(isTraceOn, verbose - 1, saveData) == 0 ){
blockPos.push_back(filePos);
blockTimeStamp.push_back(data->aggTime);
blockID ++;
if(verbose && blockID % 10000 == 0) printf("%u, %.2f%% %u/%lu\n\033[A\r", blockID, filePos*100./inFileSize, filePos, inFileSize);
}
totNumBlock = blockID;
if(verbose) {
printf("\nScan complete: number of data Block : %lu\n", totNumBlock);
printf( " number of hit : %lu", hitCount);
if( hitCount > 1e6 ) printf(" = %.3f million", hitCount/1e6);
printf("\n");
if( saveData )printf( " size of the hit array : %lu\n", hit.size());
if( saveData ){
size_t sizeT = sizeof(hit[0]) * hit.size();
printf("size of hit array : %lu byte = %.2f kByte, = %.2f MByte\n", sizeT, sizeT/1024., sizeT/1024./1024.);
}
}
if( fileList.size() > 0 ){
fileID = 0;
OpenFile(fileList[fileID], data->GetDataSize(), 0);
}
rewind(inFile);
blockID = 0;
filePos = 0;
//check is the hitCount == hit.size();
if( saveData ){
if( hitCount != hit.size()){
printf("!!!!!! the Data::dataSize is not big enough. !!!!!!!!!!!!!!!\n");
}else{
SortHit(verbose+1);
}
}
}
inline std::vector<Hit> FSUReader::ReadBatch(unsigned int batchSize, bool verbose){
// printf("%s sn:%d. filePos : %lu\n", __func__, sn, ftell(inFile));
std::vector<Hit> hitList_A;
if( IsEndOfFile() ) {
hitList_A = hit;
hit.clear();
return hitList_A;
}
if( hit.size() == 0 ){
int res = 0;
do{
res = ReadNextBlock(true, 0, 3);
}while ( hit.size() < batchSize && res == 0);
SortHit();
uLong t0_B = hit.at(0).timestamp;
uLong t1_B = hit.back().timestamp;
if( verbose ) {
printf(" hit in memeory : %7zu | %u | %lu \n", hit.size(), filePos, inFileSize);
printf("t0 : %15lu ns\n", t0_B);
printf("t1 : %15lu ns\n", t1_B);
printf("dt : %15.3f ms\n", (t1_B - t0_B)/1e6);
}
hitList_A = hit;
hit.clear();
}else{
hitList_A = hit;
hit.clear();
}
if( IsEndOfFile() ) return hitList_A; // when file finished for 1st batch read
int res = 0;
do{
res = ReadNextBlock(true, 0, 3);
}while ( hit.size() < batchSize && res == 0);
SortHit();
uLong t0_B = hit.at(0).timestamp;
uLong t1_B = hit.back().timestamp;
if( verbose ) {
printf(" hit in memeory : %7zu | %u | %lu \n", hit.size(), filePos, inFileSize);
printf("t0 : %15lu\n", t0_B);
printf("t1 : %15lu\n", t1_B);
printf("dt : %15.3f ms\n", (t1_B - t0_B)/1e6);
}
uLong t0_A = hitList_A.at(0).timestamp;
uLong t1_A = hitList_A.back().timestamp;
ulong ID_A = 0;
ulong ID_B = 0;
if( t0_A >= t0_B) {
printf("\033[0;31m!!!!!!!!!!!!!!!!! %s | Need to increase the batch size. \033[0m\n", __func__);
return std::vector<Hit> ();
}
if( t1_A > t0_B) { // need to sort between two hitList
if( verbose ) {
printf("############# need to sort \n");
printf("=========== sume of A + B : %zu \n", hitList_A.size() + hit.size());
}
std::vector<Hit> hitTemp;
// find the hit that is >= t0_B, save them to hitTemp
for( size_t j = 0; j < hitList_A.size() ; j++){
if( hitList_A[j].timestamp < t0_B ) continue;;
if( ID_A == 0 ) ID_A = j;
hitTemp.push_back(hitList_A[j]);
}
// remove hitList_A element that is >= t0_B
hitList_A.erase(hitList_A.begin() + ID_A, hitList_A.end() );
// find the hit that is <= t1_A, save them to hitTemp
for( size_t j = 0; j < hit.size(); j++){
if( hit[j].timestamp > t1_A ) {
break;
}
hitTemp.push_back(hit[j]);
ID_B = j + 1;
}
// remove hit elements that is <= t1_A
hit.erase(hit.begin(), hit.begin() + ID_B );
// sort hitTemp
std::sort(hitTemp.begin(), hitTemp.end(), [](const Hit& a, const Hit& b) {
return a.timestamp < b.timestamp;
});
if( verbose ) {
printf("----------------- ID_A : %lu, Drop\n", ID_A);
printf("----------------- ID_B : %lu, Drop\n", ID_B);
PrintHitListInfo(&hitList_A, "hitList_A");
PrintHitListInfo(&hitTemp, "hitTemp");
PrintHitListInfo();
printf("=========== sume of A + B + Temp : %zu \n", hitList_A.size() + hit.size() + hitTemp.size());
printf("----------------- refill hitList_A \n");
}
for( size_t j = 0; j < hitTemp.size(); j++){
hitList_A.push_back(hitTemp[j]);
}
hitTemp.clear();
if( verbose ) {
PrintHitListInfo(&hitList_A, "hitList_A");
PrintHitListInfo();
printf("=========== sume of A + B : %zu \n", hitList_A.size() + hit.size());
}
}
return hitList_A;
}
/*
inline void FSUReader::SortAndSaveTS(unsigned int batchSize, bool verbose){
int count = 0;
std::vector<Hit> hitList_A ;
do{
if( verbose ) printf("***************************************************\n");
int res = 0;
do{
res = ReadNextBlock(true, 0, 3);
}while ( hit.size() < batchSize && res == 0);
SortHit();
uLong t0_B = hit.at(0).timestamp;
uLong t1_B = hit.back().timestamp;
if( verbose ) {
printf(" hit in memeory : %7zu | %u | %lu \n", hit.size(), filePos, inFileSize);
printf("t0 : %15lu\n", t0_B);
printf("t1 : %15lu\n", t1_B);
}
if( count == 0 ) {
hitList_A = hit; // copy hit
}else{
uLong t0_A = hitList_A.at(0).timestamp;
uLong t1_A = hitList_A.back().timestamp;
ulong ID_A = 0;
ulong ID_B = 0;
if( t0_A > t0_B) {
printf("Need to increase the batch size. \n");
return;
}
if( t1_A > t0_B) { // need to sort between two hitList
if( verbose ) {
printf("############# need to sort \n");
printf("=========== sume of A + B : %zu \n", hitList_A.size() + hit.size());
}
std::vector<Hit> hitTemp;
for( size_t j = 0; j < hitList_A.size() ; j++){
if( hitList_A[j].timestamp < t0_B ) continue;
if( ID_A == 0 ) ID_A = j;
hitTemp.push_back(hitList_A[j]);
}
hitList_A.erase(hitList_A.begin() + ID_A, hitList_A.end() );
if( verbose ) {
printf("----------------- ID_A : %lu, Drop\n", ID_A);
PrintHitListInfo(hitList_A, "hitList_A");
}
for( size_t j = 0; j < hit.size(); j++){
if( hit[j].timestamp > t1_A ) {
ID_B = j;
break;
}
hitTemp.push_back(hit[j]);
}
std::sort(hitTemp.begin(), hitTemp.end(), [](const Hit& a, const Hit& b) {
return a.timestamp < b.timestamp;
});
hit.erase(hit.begin(), hit.begin() + ID_B );
if( verbose ) {
PrintHitListInfo(hitTemp, "hitTemp");
printf("----------------- ID_B : %lu, Drop\n", ID_B);
PrintHitListInfo(hit, "hit");
printf("=========== sume of A + B + Temp : %zu \n", hitList_A.size() + hit.size() + hitTemp.size());
printf("----------------- refill hitList_A \n");
}
ulong ID_Temp = 0;
for( size_t j = 0; j < hitTemp.size(); j++){
hitList_A.push_back(hitTemp[j]);
if( hitList_A.size() >= batchSize ) {
ID_Temp = j+1;
break;
}
}
hitTemp.erase(hitTemp.begin(), hitTemp.begin() + ID_Temp );
for( size_t j = 0 ; j < hit.size(); j ++){
hitTemp.push_back(hit[j]);
}
SaveHit(hitList_A, count <= 1 ? false : true);
if( verbose ) {
PrintHitListInfo(hitList_A, "hitList_A");
PrintHitListInfo(hitTemp, "hitTemp");
printf("----------------- replace hitList_A by hitTemp \n");
}
hitList_A.clear();
hitList_A = hitTemp;
hit.clear();
if( verbose ) {
PrintHitListInfo(hitList_A, "hitList_A");
printf("===========================================\n");
}
}else{ // save hitList_A, replace hitList_A
SaveHit(hitList_A, count <= 1? false : true);
hitList_A.clear();
hitList_A = hit;
if( verbose ) PrintHitListInfo(hitList_A, "hitList_A");
}
}
ClearHitList();
count ++;
}while(filePos < inFileSize);
SaveHit(hitList_A, count <= 1 ? false : true);
printf("================= finished.\n");
}
*/
/*
inline std::string FSUReader::SaveHit(std::vector<Hit> hitList, bool isAppend){
std::string outFileName;
if( fileList.empty() ) {
outFileName = fileName + ".ts" ;
}else{
outFileName = fileList[0] + ".ts" ;
}
uint64_t hitSize = hitList.size();
FILE * outFile ;
if( isAppend ) {
outFile = fopen(outFileName.c_str(), "rb+"); //read/write bineary
rewind(outFile);
fseek( outFile, 4, SEEK_CUR);
uint64_t org_hitSize;
fread(&org_hitSize, 8, 1, outFile);
rewind(outFile);
fseek( outFile, 4, SEEK_CUR);
org_hitSize += hitSize;
fwrite(&org_hitSize, 8, 1, outFile);
fseek(outFile, 0, SEEK_END);
}else{
outFile = fopen(outFileName.c_str(), "wb"); //overwrite binary
uint32_t header = 0xAA000000;
header += sn;
fwrite( &header, 4, 1, outFile );
fwrite( &hitSize, 8, 1, outFile);
}
for( ulong i = 0; i < hitSize; i++){
if( i% 10000 == 0 ) printf("Saving %lu/%lu Hit (%.2f%%)\n\033[A\r", i, hitSize, i*100./hitSize);
uint16_t flag = hitList[i].ch + (hitList[i].pileUp << 8) ;
if( DPPType == DPPTypeCode::DPP_PSD_CODE ) flag += ( 1 << 15);
if( hitList[i].traceLength > 0 ) flag += (1 << 14);
// fwrite( &(hit[i].ch), 1, 1, outFile);
fwrite( &flag, 2, 1, outFile);
fwrite( &(hitList[i].energy), 2, 1, outFile);
if( DPPType == DPPTypeCode::DPP_PSD_CODE ) fwrite( &(hitList[i].energy2), 2, 1, outFile);
fwrite( &(hitList[i].timestamp), 6, 1, outFile);
fwrite( &(hitList[i].fineTime), 2, 1, outFile);
if( hitList[i].traceLength > 0 ) fwrite( &(hitList[i].traceLength), 2, 1, outFile);
for( uShort j = 0; j < hitList[i].traceLength; j++){
fwrite( &(hitList[i].trace[j]), 2, 1, outFile);
}
}
off_t tsFileSize = ftello(outFile); // unsigned int = Max ~4GB
fclose(outFile);
printf("Saved to %s, size: ", outFileName.c_str());
if( tsFileSize < 1024 ) {
printf(" %ld Byte", tsFileSize);
}else if( tsFileSize < 1024*1024 ) {
printf(" %.2f kB", tsFileSize/1024.);
}else if( tsFileSize < 1024*1024*1024){
printf(" %.2f MB", tsFileSize/1024./1024.);
}else{
printf(" %.2f GB", tsFileSize/1024./1024./1024.);
}
printf("\n");
return outFileName;
}
*/

56
Armory/macro.h Normal file
View File

@ -0,0 +1,56 @@
#ifndef MACRO_H
#define MACRO_H
#define MaxNPorts 4 //for optical link
#define MaxNBoards 4 //for both optical link and usb
#define MaxNDigitizer MaxNPorts * MaxNBoards
#define MaxRegChannel 16
#define MaxNChannels 64
#define MaxRecordLength 0x3fff * 8
#define MaxSaveFileSize 1024 * 1024 * 1024 * 2
#define MaxDisplayTraceTimeLength 20000 //ns
#define ScopeUpdateMiliSec 200 // msec
#define MaxNumberOfTrace 5 // in an event
#define SETTINGSIZE 2048
#define DAQLockFile "DAQLock.dat"
#define PIDFile "pid.dat"
#include <sys/time.h> /** struct timeval, select() */
inline unsigned int getTime_us(){
unsigned int time_us;
struct timeval t1;
struct timezone tz;
gettimeofday(&t1, &tz);
time_us = (t1.tv_sec) * 1000 * 1000 + t1.tv_usec;
return time_us;
}
#include <chrono>
inline unsigned long long getTime_ns(){
std::chrono::high_resolution_clock::time_point currentTime = std::chrono::high_resolution_clock::now();
std::chrono::nanoseconds nanoseconds = std::chrono::duration_cast<std::chrono::nanoseconds>(currentTime.time_since_epoch());
return nanoseconds.count();
}
typedef unsigned short uShort;
typedef unsigned int uInt;
typedef unsigned long uLong;
typedef unsigned long long ullong;
#define DebugMode 0 //process check, when 1, print out all function call
// if DebugMode is 1, define DebugPrint() to be printf(), else, DebugPrint() define nothing
#if DebugMode
#define DebugPrint(fmt, ...) printf(fmt "::%s\n",##__VA_ARGS__, __func__);
#else
#define DebugPrint(fmt, ...)
#endif
#endif

File diff suppressed because it is too large Load Diff

6
BatchProcess.sh Normal file
View File

@ -0,0 +1,6 @@
#!/bin/bash
#parallel -j 6 echo ./ProcessRun.sh {1} 2000 0 ::: {020..400}
parallel --jobs 1 --results log/log_{}.txt --memfree 1G --ctag -j 1 ./ProcessRun.sh {1} 2000 0 ::: {109..400} # for 17F
# parallel --results log/log_{}.txt --ctag -j 6 ./ProcessRun.sh {1} 4000 0 ::: {5..21}

45
CLAUDE.md Normal file
View File

@ -0,0 +1,45 @@
# ANASEN Analysis — working notes for Claude
## Agent behavior
- ALWAYS output a brief, step-by-step plan before modifying files or running commands.
- Commit only when explicitly asked. Do not push to remote on your own initiative; pushing is allowed only when the session's instructions name a branch to push to (e.g. a `claude/*` working branch), and never to `devel_vignesh` or any other shared branch.
- **Targeted edits only.** Never regenerate a whole file to make a local change.
- **Never read `MakeVertex.C`** (4,003 lines, 240 KB). Same rule as `TrackRecon.C` below if you must touch it: grep, then Read with offset/limit.
- **Never read `TrackRecon.C` end to end** (4,268 lines, 218 KB; ~60k tokens, and it stays in the prefix for the rest of the session). Grep for the symbol, then Read with offset/limit around the hit. If a question genuinely needs the whole file, say so first. Do not explore the codebase open-endedly.
- **Do not spawn subagents** for work that can be done inline — each starts cold and re-derives context already loaded. Worth it only for wide parallel searches where the conclusion is all that's needed.
- **You cannot verify your own work here.** No test suite, no CI, no data in the repo; validation is visual, via ROOT `TBrowser`, on a machine that has the data. Say explicitly when a change is unverified — never report a physics change as working because it compiled. The dangerous bugs are silent: a flipped sign in a kinematics term, an inverted spline direction, a wrong argument order, an off-by-one in a wire mask. None of them throw. The run completes, the plot renders, and the spectrum is wrong.
- **Review once per coherent change, not per edit.** Scale depth to blast radius: scratch macros, shell, and docs are shallow; `TrackRecon.C`, `MakeVertex.C`, `Armory/`, or any calibration fit is deep and should tolerate uncertain findings — silent failure is what's being hunted. Run `/security-review` only for file I/O or shell interpolation in the `run_*.sh` drivers; it is not a physics tool.
## Build & execution
- ROOT macros (`.C`) compile via ACLiC inside ROOT. Do not link into binaries.
- Standalone binaries (`EventBuilder`, `Mapper`): `cd Armory && make`
- Pre-compile for parallel runs: `root -q -l -b -e '.L TrackRecon.C++O'`
- Batch runs: `./run_17F.sh`, `./run_27Al.sh`, `./run_tr.sh`
- `run_tr.sh` gates its stages with `if [[ 1 -eq 0 ]]` / `if [[ 1 -eq 1 ]]`. Flip the literal to enable a block.
- Data is NOT in the repo. Mapped ROOT files live at `../ANASEN_analysis/data/${DATASET}_Data/${PREFIX}${run}_mapped.root`.
## Runtime configuration
All config is `getenv`-driven; the run scripts `export` it. Defaults are in `TrackRecon.C::Begin()`.
Physics / results-affecting:
`DATASET` `reactiondata` `CO2percent` `pressure_in_torr` `CATHODE_GAIN` `PC_ENERGY_CALIBRATION`
`source_vertex` `CUTLIST` `DITHER_SIGMA` `RNG_SEED` `BEAM_AXIS_X` `BEAM_AXIS_Y`
`timecut_low` `timecut_high` `DISABLE_BAD_ANODE_WIRES` `A1C1_LOWBAND_RFACTOR`
`A1C1_Z_SCALE_QQQ` `A1C1_Z_SCALE_SX3` `A1C1_Z_OFF_QQQ` `A1C1_Z_OFF_SX3`
Plumbing: `OUT_DIR` `RUN_NUMBER` `FLUSH_BARRIER` `MAX_RSS_MB` `MEMCHECK_STRIDE`
**`DEDX_SCALE` is read by `eloss_calculations/Eloss.py`, NOT by `TrackRecon.C`.**
It multiplies `catima.dedx` when the lookup tables are generated. Changing it invalidates every `.dat` table — re-run `Eloss.py` (the run scripts do this automatically before processing). Tables are keyed `eloss_calculations/<species>_lookup_<E>MeV_<P>torr_<CO2>pc.dat` (e.g. `alpha_lookup_50MeV_250torr_3pc.dat`).
## Footguns
- **`HistPlotter` keys `oMap` by histogram NAME only.** The folder argument does not namespace. Two `Fill` calls with the same name and the *same* folder merge silently — no warning, no error, wrong plot. A same-name/different-folder (or toplevel-vs-folder) clash does print to `stderr` and then merges anyway, but the run scripts send stdout+stderr to `/dev/null`, so in a batch run it is invisible either way. Always make names unique.
- **Si–PC coincidence sits at negative dt.** The band is centred well below zero, not near it. The one gate of record is `siPcCoincident()` in `TrackRecon.C` (`kSiPcDtMin = -500`, `kSiPcDtMax = 150` ns); anything else is a plotting range, not a cut. Do not "correct" a timing gate toward zero.
- **RNG**: in `TrackRecon.C`, one shared `anasenRandom` (`TRandom3`, seed 4357, `RNG_SEED` to override), aliased by reference wherever a local `rnd`/`rand` appears — do not introduce fresh `TRandom3(0)` instances; it breaks reproducibility. `MakeVertex.C` predates this and still has ~12 independent default-seeded `TRandom3` objects; do not assume its output is reproducible against `TrackRecon.C`.
- **Pass By Reference in Event Loops:** Always iterate via `const auto&` over cluster/hit vectors to prevent expensive struct copies in multi-million event runs.
## Invariants to check on any physics change
Units (MeV vs keV, cm vs mm); sign conventions on `z` and beam direction; eloss spline domain and extrapolation; energy conservation in the kinematics; dead-wire / neighbour masking; RNG seeding. When reviewing, include the `.dat` and `run_*.sh` parameter deltas — the most consequential recent changes have been constants, not code.
## Docs
`README.md` has the pipeline documentation and full file references.

270
CalibrationQQQ.C Normal file
View File

@ -0,0 +1,270 @@
#define CalibrationQQQ_cxx
#include <TH2.h>
#include <TF1.h>
#include <TStyle.h>
#include <TCanvas.h>
#include <TMath.h>
#include <TCutG.h>
#include <fstream>
#include <utility>
#include <algorithm>
#include "Armory/HistPlotter.h"
#include "TVector3.h"
#include "Calibration.h"
TH2F *hQQQFVB;
HistPlotter *plotter;
int padID = 0;
TCutG *cut;
std::map<std::tuple<int, int, int>, std::vector<std::pair<double, double>>> dataPoints;
bool qqqEcut = false;
// Gain Arrays
const int MAX_QQQ = 4;
const int MAX_RING = 16;
const int MAX_WEDGE = 16;
double qqqwGain[MAX_QQQ][MAX_RING][MAX_WEDGE] = {{{0}}};
// double qqqrGain[MAX_QQQ][MAX_RING][MAX_WEDGE] = {{{0}}};
bool qqqwGainValid[MAX_QQQ][MAX_RING][MAX_WEDGE] = {{{false}}};
// bool qqqrGainValid[MAX_QQQ][MAX_RING][MAX_WEDGE] = {{{false}}};
void CalibrationQQQ::Begin(TTree * /*tree*/)
{
plotter = new HistPlotter("Calib.root", "TFILE");
// ----------------------- Load QQQ Gains
{
std::string filename = "qqq_GainMatch.dat";
std::ifstream infile(filename);
if (!infile.is_open())
{
std::cerr << "Error opening " << filename << "!" << std::endl;
}
else
{
int det, ring, wedge;
double gainw, gainr;
while (infile >> det >> ring >> wedge >> gainw >> gainr)
{
qqqwGain[det][ring][wedge] = gainw;
// qqqrGain[det][ring][wedge] = gainr;
qqqwGainValid[det][ring][wedge] = (gainw > 0);
// qqqrGainValid[det][ring][wedge] = (gainr > 0);
}
infile.close();
std::cout << "Loaded QQQ gains from " << filename << std::endl;
}
}
for (int det = 0; det < MAX_QQQ; det++)
{
for (int ring = 0; ring < MAX_RING; ring++)
{
for (int wedge = 0; wedge < MAX_WEDGE; wedge++)
{
TString hname = Form("hCal_qqq%d_ring%d_wedge%d", det, ring, wedge);
TString htitle = Form("QQQ det%d ring%d wedge%d; Energy (arb); Counts", det, ring, wedge);
// hQQQSpectra[det][ring][wedge] = new TH1F(hname, htitle, 4000, 0, 16000);
}
}
}
}
Bool_t CalibrationQQQ::Process(Long64_t entry)
{
b_qqqMulti->GetEntry(entry);
b_qqqID->GetEntry(entry);
b_qqqCh->GetEntry(entry);
b_qqqE->GetEntry(entry);
b_qqqT->GetEntry(entry);
qqq.CalIndex();
for (int i = 0; i < qqq.multi; i++)
{
for (int j = i + 1; j < qqq.multi; j++)
{
if (qqq.e[i] > 100)
qqqEcut = true;
if (qqq.id[i] == qqq.id[j])
{
int chWedge = -1;
int chRing = -1;
float eWedgeRaw = 0.0;
float eWedge = 0.0;
float eRingRaw = 0.0;
float eRing = 0.0;
if (qqq.ch[i] < 16 && qqq.ch[j] >= 16 && /*qqqrGainValid[qqq.id[i]][qqq.ch[i]][qqq.ch[j] - 16] &&*/ qqqwGainValid[qqq.id[i]][qqq.ch[i]][qqq.ch[j] - 16])
{
chWedge = qqq.ch[i];
eWedgeRaw = qqq.e[i];
eWedge = qqq.e[i] * qqqwGain[qqq.id[i]][qqq.ch[i]][qqq.ch[j] - 16];
// printf("Wedge E: %.2f Gain: %.4f \n", eWedge, qqqGain[qqq.id[i]][qqq.ch[i]][qqq.ch[j] - 16]);
chRing = qqq.ch[j] - 16;
eRingRaw = qqq.e[j];
eRing = qqq.e[j];// * qqqrGain[qqq.id[j]][qqq.ch[j]][qqq.ch[i] - 16];
}
else if (qqq.ch[j] < 16 && qqq.ch[i] >= 16 && /*qqqrGainValid[qqq.id[j]][qqq.ch[j]][qqq.ch[i] - 16] &&*/ qqqwGainValid[qqq.id[j]][qqq.ch[j]][qqq.ch[i] - 16])
{
chWedge = qqq.ch[j];
eWedge = qqq.e[j] * qqqwGain[qqq.id[j]][qqq.ch[j]][qqq.ch[i] - 16];
eWedgeRaw = qqq.e[j];
chRing = qqq.ch[i] - 16;
eRing = qqq.e[i];// * qqqrGain[qqq.id[i]][qqq.ch[i]][qqq.ch[j] - 16];
eRingRaw = qqq.e[i];
}
else
continue;
// hQQQFVB->Fill(eWedge, eRing);
plotter->Fill2D(Form("hRaw_qqq%d_ring%d_wedge%d", qqq.id[i], chRing, chWedge), 400, 0, 16000, 400, 0, 16000, eWedgeRaw, eRingRaw, "ERaw");
plotter->Fill2D(Form("hGM_qqq%d_ring%d_wedge%d", qqq.id[i], chRing, chWedge), 400, 0, 16000, 400, 0, 16000, eWedge, eRing, "EGM");
plotter->Fill2D("hRawQQQ", 4000, 0, 16000, 4000, 0, 16000, eWedgeRaw, eRingRaw);
plotter->Fill2D("hGMQQQ", 4000, 0, 16000, 4000, 0, 16000, eWedge, eRing);
TString histName = Form("hQQQFVB_id%d_r%d_w%d", qqq.id[i], chRing, chWedge);
// TH2F *hist2d = (TH2F *)gDirectory->Get(histName);
// if (!hist2d)
// {
// hist2d = new TH2F(histName, Form("QQQ Det%d R%d W%d;Wedge E;Ring E", qqq.id[i], chRing, chWedge), 400, 0, 16000, 400, 0, 16000);
// }
// hist2d->Fill(eWedge, eRing);
// if (cut && cut->IsInside(eWedge, eRing))
const double MIN_ADC = 1500.0;
const double MAX_ADC = 3000.0;
// if (eWedge >= MIN_ADC && eWedge <= MAX_ADC &&
// eRing >= MIN_ADC && eRing <= MAX_ADC)
double ratio = (eWedge > 0.0) ? (eRing / eWedge) : 0.0;
double maxslope = 1.5;
bool validPoint = false;
if (ratio < maxslope && ratio > 1. / maxslope)
{
// Accumulate data for gain matching
dataPoints[{qqq.id[i], chRing, chWedge}].emplace_back(eWedge, eRing);
}
}
}
}
return kTRUE;
}
void CalibrationQQQ::Terminate()
{
const double AM241_PEAK = 5485.56;
const double P_PEAK = 7000; // keV
double calibArray[MAX_QQQ][MAX_RING][MAX_WEDGE] = {{{0}}};
bool calibValid[MAX_QQQ][MAX_RING][MAX_WEDGE] = {{{false}}};
std::ofstream outFile("qqq_Calib.dat");
if (!outFile.is_open())
{
std::cerr << "Error opening qqq_Calib.dat!" << std::endl;
return;
}
//----------------------------------------------------------------------
// 1. Create per–channel 1D spectra in ADC from stored gain-matched data
//----------------------------------------------------------------------
std::map<std::tuple<int, int, int>, TH1F *> spectra;
for (auto &kv : dataPoints)
{
int det, ring, wedge;
std::tie(det, ring, wedge) = kv.first;
TString hname = Form("hSpec_d%d_r%d_w%d", det, ring, wedge);
TH1F *h = new TH1F(hname, hname, 4000, 0, 16000);
for (auto &p : kv.second)
{
double eWedge = p.first; // already gain-matched ADC
double eRing = p.second;
// Use ring ADC for calibration (cleaner alpha peak)
h->Fill(eRing);
}
spectra[kv.first] = h;
}
//----------------------------------------------------------------------
// 2. Fit Am-241 peak and extract keV/ADC calibration slope
//----------------------------------------------------------------------
for (auto &kv : spectra)
{
int det, ring, wedge;
std::tie(det, ring, wedge) = kv.first;
TH1F *h = kv.second;
if (!h || h->GetEntries() < 50)
continue;
int binMax = h->GetMaximumBin();
double adcPeak = h->GetXaxis()->GetBinCenter(binMax);
if (adcPeak <= 0)
continue;
double slope_keV = AM241_PEAK / adcPeak; // keV per ADC
// double slope_keV = P_PEAK / adcPeak; // keV per ADC
calibArray[det][ring][wedge] = slope_keV;
calibValid[det][ring][wedge] = true;
outFile << det << " " << wedge << " " << ring << " "
<< slope_keV << "\n";
// printf("QQQ DET=%d R=%d W=%d ADCpeak=%.1f slope_keV=%.6f\n",det, ring, wedge, adcPeak, slope_keV);
}
outFile.close();
std::cout << "Wrote QQQ calibration file qqq_Calib.dat\n";
//----------------------------------------------------------------------
// 3. Build fully calibrated 2D combined histogram
//----------------------------------------------------------------------
TH2F *hCal = new TH2F("hCal",
"All QQQ Calibrated;Wedge Energy (keV);Ring Energy (keV)",
800, 0, 7000,
800, 0, 7000);
for (auto &kv : dataPoints)
{
int det, ring, wedge;
std::tie(det, ring, wedge) = kv.first;
if (!calibValid[det][ring][wedge])
continue;
double slope = calibArray[det][ring][wedge];
for (auto &p : kv.second)
{
double eWGM = p.first; // gain matched ADC
double eRGM = p.second;
double eWkeV = eWGM * slope / 1000;
double eRkeV = eRGM * slope / 1000;
hCal->Fill(eWkeV, eRkeV);
plotter->Fill2D("hCalQQQ", 4000, 0, 10, 4000, 0, 10, eWkeV, eRkeV);
plotter->Fill2D(Form("hRCal_qqq%d", det), 16, 0, 15, 400, 0, 24, ring, eRkeV, "RingCal");
plotter->Fill2D(Form("hWCal_qqq%d", det), 16, 0, 15, 400, 0, 24, wedge, eWkeV, "WedgeCal");
}
}
plotter->FlushToDisk();
std::cout << "Calibrated 2D QQQ histogram saved.\n";
}

View File

@ -1,114 +1,114 @@
#ifndef gainmatch_h
#define gainmatch_h
#include <TROOT.h>
#include <TChain.h>
#include <TFile.h>
#include <TSelector.h>
#include "Armory/ClassDet.h"
class gainmatch : public TSelector {
public :
TTree *fChain; //!pointer to the analyzed TTree or TChain
// Fixed size dimensions of array or collections stored in the TTree if any.
// Declaration of leaf types
Det sx3;
Det qqq;
Det pc ;
ULong64_t evID;
UInt_t run;
// List of branches
TBranch *b_eventID; //!
TBranch *b_run; //!
TBranch *b_sx3Multi; //!
TBranch *b_sx3ID; //!
TBranch *b_sx3Ch; //!
TBranch *b_sx3E; //!
TBranch *b_sx3T; //!
TBranch *b_qqqMulti; //!
TBranch *b_qqqID; //!
TBranch *b_qqqCh; //!
TBranch *b_qqqE; //!
TBranch *b_qqqT; //!
TBranch *b_pcMulti; //!
TBranch *b_pcID; //!
TBranch *b_pcCh; //!
TBranch *b_pcE; //!
TBranch *b_pcT; //!
gainmatch(TTree * /*tree*/ =0) : fChain(0) { }
virtual ~gainmatch() { }
virtual Int_t Version() const { return 2; }
virtual void Begin(TTree *tree);
virtual void SlaveBegin(TTree *tree);
virtual void Init(TTree *tree);
virtual Bool_t Notify();
virtual Bool_t Process(Long64_t entry);
virtual Int_t GetEntry(Long64_t entry, Int_t getall = 0) { return fChain ? fChain->GetTree()->GetEntry(entry, getall) : 0; }
virtual void SetOption(const char *option) { fOption = option; }
virtual void SetObject(TObject *obj) { fObject = obj; }
virtual void SetInputList(TList *input) { fInput = input; }
virtual TList *GetOutputList() const { return fOutput; }
virtual void SlaveTerminate();
virtual void Terminate();
ClassDef(gainmatch,0);
};
#endif
#ifdef gainmatch_cxx
void gainmatch::Init(TTree *tree){
// Set branch addresses and branch pointers
if (!tree) return;
fChain = tree;
fChain->SetMakeClass(1);
fChain->SetBranchAddress("evID", &evID, &b_eventID);
fChain->SetBranchAddress("run", &run, &b_run);
sx3.SetDetDimension(24,12);
qqq.SetDetDimension(4,32);
pc.SetDetDimension(2,24);
fChain->SetBranchAddress("sx3Multi", &sx3.multi, &b_sx3Multi);
fChain->SetBranchAddress("sx3ID", &sx3.id, &b_sx3ID);
fChain->SetBranchAddress("sx3Ch", &sx3.ch, &b_sx3Ch);
fChain->SetBranchAddress("sx3E", &sx3.e, &b_sx3E);
fChain->SetBranchAddress("sx3T", &sx3.t, &b_sx3T);
fChain->SetBranchAddress("qqqMulti", &qqq.multi, &b_qqqMulti);
fChain->SetBranchAddress("qqqID", &qqq.id, &b_qqqID);
fChain->SetBranchAddress("qqqCh", &qqq.ch, &b_qqqCh);
fChain->SetBranchAddress("qqqE", &qqq.e, &b_qqqE);
fChain->SetBranchAddress("qqqT", &qqq.t, &b_qqqT);
fChain->SetBranchAddress("pcMulti", &pc.multi, &b_pcMulti);
fChain->SetBranchAddress("pcID", &pc.id, &b_pcID);
fChain->SetBranchAddress("pcCh", &pc.ch, &b_pcCh);
fChain->SetBranchAddress("pcE", &pc.e, &b_pcE);
fChain->SetBranchAddress("pcT", &pc.t, &b_pcT);
}
Bool_t gainmatch::Notify(){
return kTRUE;
}
void gainmatch::SlaveBegin(TTree * /*tree*/){
TString option = GetOption();
}
void gainmatch::SlaveTerminate(){
}
#endif // #ifdef gainmatch_cxx
#ifndef CalibrationQQQ_h
#define CalibrationQQQ_h
#include <TROOT.h>
#include <TChain.h>
#include <TFile.h>
#include <TSelector.h>
#include "Armory/ClassDet.h"
class CalibrationQQQ : public TSelector {
public :
TTree *fChain; //!pointer to the analyzed TTree or TChain
// Fixed size dimensions of array or collections stored in the TTree if any.
// Declaration of leaf types
Det sx3;
Det qqq;
Det pc ;
ULong64_t evID;
UInt_t run;
// List of branches
TBranch *b_eventID; //!
TBranch *b_run; //!
TBranch *b_sx3Multi; //!
TBranch *b_sx3ID; //!
TBranch *b_sx3Ch; //!
TBranch *b_sx3E; //!
TBranch *b_sx3T; //!
TBranch *b_qqqMulti; //!
TBranch *b_qqqID; //!
TBranch *b_qqqCh; //!
TBranch *b_qqqE; //!
TBranch *b_qqqT; //!
TBranch *b_pcMulti; //!
TBranch *b_pcID; //!
TBranch *b_pcCh; //!
TBranch *b_pcE; //!
TBranch *b_pcT; //!
CalibrationQQQ(TTree * /*tree*/ =0) : fChain(0) { }
virtual ~CalibrationQQQ() { }
virtual Int_t Version() const { return 2; }
virtual void Begin(TTree *tree);
virtual void SlaveBegin(TTree *tree);
virtual void Init(TTree *tree);
virtual Bool_t Notify();
virtual Bool_t Process(Long64_t entry);
virtual Int_t GetEntry(Long64_t entry, Int_t getall = 0) { return fChain ? fChain->GetTree()->GetEntry(entry, getall) : 0; }
virtual void SetOption(const char *option) { fOption = option; }
virtual void SetObject(TObject *obj) { fObject = obj; }
virtual void SetInputList(TList *input) { fInput = input; }
virtual TList *GetOutputList() const { return fOutput; }
virtual void SlaveTerminate();
virtual void Terminate();
ClassDef(CalibrationQQQ,0);
};
#endif
#ifdef CalibrationQQQ_cxx
void CalibrationQQQ::Init(TTree *tree){
// Set branch addresses and branch pointers
if (!tree) return;
fChain = tree;
fChain->SetMakeClass(1);
fChain->SetBranchAddress("evID", &evID, &b_eventID);
fChain->SetBranchAddress("run", &run, &b_run);
sx3.SetDetDimension(24,12);
qqq.SetDetDimension(4,32);
pc.SetDetDimension(2,24);
fChain->SetBranchAddress("sx3Multi", &sx3.multi, &b_sx3Multi);
fChain->SetBranchAddress("sx3ID", &sx3.id, &b_sx3ID);
fChain->SetBranchAddress("sx3Ch", &sx3.ch, &b_sx3Ch);
fChain->SetBranchAddress("sx3E", &sx3.e, &b_sx3E);
fChain->SetBranchAddress("sx3T", &sx3.t, &b_sx3T);
fChain->SetBranchAddress("qqqMulti", &qqq.multi, &b_qqqMulti);
fChain->SetBranchAddress("qqqID", &qqq.id, &b_qqqID);
fChain->SetBranchAddress("qqqCh", &qqq.ch, &b_qqqCh);
fChain->SetBranchAddress("qqqE", &qqq.e, &b_qqqE);
fChain->SetBranchAddress("qqqT", &qqq.t, &b_qqqT);
fChain->SetBranchAddress("pcMulti", &pc.multi, &b_pcMulti);
fChain->SetBranchAddress("pcID", &pc.id, &b_pcID);
fChain->SetBranchAddress("pcCh", &pc.ch, &b_pcCh);
fChain->SetBranchAddress("pcE", &pc.e, &b_pcE);
fChain->SetBranchAddress("pcT", &pc.t, &b_pcT);
}
Bool_t CalibrationQQQ::Notify(){
return kTRUE;
}
void CalibrationQQQ::SlaveBegin(TTree * /*tree*/){
TString option = GetOption();
}
void CalibrationQQQ::SlaveTerminate(){
}
#endif // #ifdef CalibrationQQQ_cxx

View File

@ -0,0 +1,124 @@
#include <TFile.h>
#include <TH1.h>
#include <TSpectrum.h>
#include <TF1.h>
#include <TCanvas.h>
#include <vector>
#include <iostream>
#include <algorithm>
#include <fstream>
#include <TText.h>
void FitHistogramsWithTSpectrum_Sequential_Improved() {
TFile *inputFile = new TFile("Histograms_anodes.root", "READ");
if (!inputFile || inputFile->IsZombie()) {
std::cerr << "Error opening the input file!" << std::endl;
return;
}
TCanvas *c1 = new TCanvas("c1", "Histogram Viewer", 800, 600);
// Open the output ASCII file to save the centroids
std::ofstream outFile("centroids.txt");
if (!outFile.is_open()) {
std::cerr << "Error opening output file!" << std::endl;
return;
}
outFile << "HistogramIndex\tPeakNumber\tCentroid\tAmplitude\tSigma" << std::endl;
for (int i = 0; i < 24; ++i) {
TH1 *histogram = dynamic_cast<TH1*>(inputFile->Get(Form("hCathode_%d", i)));
if (!histogram) {
std::cerr << "Failed to retrieve histogram_" << i << " from the file." << std::endl;
continue;
}
// Set range for peak search
double minX = 700;
double maxX = 25000;
histogram->GetXaxis()->SetRangeUser(minX, maxX);
// Draw the histogram
c1->cd();
histogram->Draw();
// Peak search using TSpectrum
const int maxPeaks = 5;
TSpectrum spectrumFinder(maxPeaks);
int nFound = spectrumFinder.Search(histogram, 2, "", 0.01);
if (nFound <= 0) {
std::cerr << "No peaks found for histogram " << i << std::endl;
continue;
}
Double_t *xPositions = spectrumFinder.GetPositionX();
Double_t *yPositions = spectrumFinder.GetPositionY();
std::vector<std::pair<Double_t, Double_t>> peaks;
// Collect and sort peaks by X position
for (int j = 0; j < nFound; ++j) {
peaks.emplace_back(xPositions[j], yPositions[j]);
}
std::sort(peaks.begin(), peaks.end());
// Fit each peak with a Gaussian
for (int j = 0; j < peaks.size(); ++j) {
Double_t peakX = peaks[j].first;
Double_t peakY = peaks[j].second;
Double_t initialAmplitude = peakY; // Better initial guess
Double_t initialCentroid = peakX; // Centroid based on peak position
Double_t initialSigma = 60.0;
// Define Gaussian with initial parameters
TF1 *gaussFit = new TF1(Form("gauss_%d", j), "gaus", peakX - 200, peakX + 200);
//gaussFit->SetParameters(peakY, peakX, 25.0); // Initial guesses for amplitude, mean, sigma
gaussFit->SetParameters(initialAmplitude, initialCentroid, initialSigma);
// Perform fit
int fitStatus = histogram->Fit(gaussFit, "RQ+");
if (fitStatus != 0) {
std::cerr << "Fit failed for peak " << j + 1 << " in histogram " << i << std::endl;
delete gaussFit;
continue;
}
// Retrieve fit parameters
double amplitude = gaussFit->GetParameter(0);
double centroid = gaussFit->GetParameter(1);
double sigma = gaussFit->GetParameter(2);
double amplitudeError = gaussFit->GetParError(0);
double centroidError = gaussFit->GetParError(1);
double sigmaError = gaussFit->GetParError(2);
// Chi-squared value
double chi2 = gaussFit->GetChisquare();
int ndf = gaussFit->GetNDF();
outFile << i << "\t" << j + 1 << "\t" << centroid << std::endl;
gaussFit->SetLineColor(kRed);
gaussFit->Draw("SAME");
TText *text = new TText();
text->SetNDC();
text->SetTextSize(0.03);
text->SetTextColor(kRed);
//text->DrawText(0.15, 0.8 - j * 0.05, Form("Peak %d: Amp=%.2f, Mean=%.2f, Sigma=%.2f", j + 1, amplitude, centroid, sigma));
text->DrawText(0.15, 0.8 - j * 0.05,
Form("Peak %d: Amp=%.2f±%.2f, Mean=%.2f±%.2f, Sigma=%.2f±%.2f, Chi2/NDF=%.2f",
j + 1, amplitude, amplitudeError, centroid, centroidError, sigma, sigmaError, chi2 / ndf));
// Save results
// Clean up
delete gaussFit;
}
// Update canvas for visualization
c1->Update();
std::cout << "Press Enter to view the next histogram..." << std::endl;
c1->WaitPrimitive(); // Wait until Enter is pressed in the ROOT console
}
// Close resources
inputFile->Close();
outFile.close();
delete c1;
}

309
GainMatchQQQ.C Normal file
View File

@ -0,0 +1,309 @@
#define GainMatchQQQ_cxx
#include "GainMatchQQQ.h"
#include <TH2.h>
#include <TF1.h>
#include <TStyle.h>
#include <TCanvas.h>
#include <TMath.h>
#include <TCutG.h>
#include <fstream>
#include <utility>
#include <algorithm>
#include <cmath>
#include <numeric>
#include "Armory/HistPlotter.h"
#include "TVector3.h"
#include "TGraphErrors.h"
#include "TF1.h"
#include <cmath>
TH2F *hQQQFVB;
HistPlotter *plotter;
int padID = 0;
TCutG *cut;
std::map<std::tuple<int, int, int>, std::vector<std::pair<double, double>>> dataPoints;
void GainMatchQQQ::Begin(TTree * /*tree*/)
{
plotter = new HistPlotter("GainQQQ.root", "TFILE");
TString option = GetOption();
hQQQFVB = new TH2F("hQQQFVB", "QQQ Front vs Back; Front E; Back E", 800, 0, 16000, 800, 0, 16000);
// Load the TCutG object
TFile *cutFile = TFile::Open("qqqcorr.root");
if (!cutFile || cutFile->IsZombie())
{
std::cerr << "Error: Could not open qqqcorr.root" << std::endl;
return;
}
cut = dynamic_cast<TCutG *>(cutFile->Get("qqqcorr"));
if (!cut)
{
std::cerr << "Error: Could not find TCutG named 'qqqcorr' in qqqcorr.root" << std::endl;
return;
}
cut->SetName("qqqcorr"); // Ensure the cut has the correct name
}
Bool_t GainMatchQQQ::Process(Long64_t entry)
{
int ringMults[16] = {0};
int wedgeMults[16] = {0};
std::vector<std::tuple<int, int, int, double, double>> events;
b_qqqMulti->GetEntry(entry);
b_qqqID->GetEntry(entry);
b_qqqCh->GetEntry(entry);
b_qqqE->GetEntry(entry);
b_qqqT->GetEntry(entry);
qqq.CalIndex();
for (int i = 0; i < qqq.multi; i++)
{
for (int j = i + 1; j < qqq.multi; j++)
{
if (qqq.id[i] == qqq.id[j])
{
int chWedge = -1;
int chRing = -1;
float eWedge = 0.0;
float eRing = 0.0;
if (qqq.ch[i] < 16 && qqq.ch[j] >= 16)
{
chWedge = qqq.ch[i];
eWedge = qqq.e[i];
chRing = qqq.ch[j] - 16;
eRing = qqq.e[j];
}
else if (qqq.ch[j] < 16 && qqq.ch[i] >= 16)
{
chWedge = qqq.ch[j];
eWedge = qqq.e[j];
chRing = qqq.ch[i] - 16;
eRing = qqq.e[i];
}
else
continue;
ringMults[chRing]++;
wedgeMults[chWedge]++;
hQQQFVB->Fill(eWedge, eRing);
events.emplace_back(qqq.id[i], chRing, chWedge, eRing, eWedge);
plotter->Fill2D(Form("hRaw_qqq%d_ring%d_wedge%d", qqq.id[i], chRing, chWedge), 800, 0, 3000, 800, 0, 3000, eWedge, eRing, "hRawQQQ");
// double ratio = (eWedge > 0.0) ? (eRing / eWedge) : 0.0;
// double maxslope = 1.5;
// bool validPoint = false;
// if (ratio < maxslope && ratio > 1. / maxslope)
// {
// // Accumulate data for gain matching
// dataPoints[{qqq.id[i], chRing, chWedge}].emplace_back(eWedge, eRing);
// plotter->Fill2D("hAll_in", 4000, 0, 16000, 4000, 0, 16000, eWedge, eRing);
// validPoint = true;
// }
// if (!validPoint)
// {
// plotter->Fill2D("hAll_out", 4000, 0, 16000, 4000, 0, 16000, eWedge, eRing);
// }
}
}
}
for (auto tuple : events)
{
auto [id, chr, chw, er, ew] = tuple;
if (ringMults[chr] > 1 || wedgeMults[chw] > 1)
continue; // ignore multiplicity > 1 events
double ratio = (ew > 0.0) ? (er / ew) : 0.0;
double maxslope = 1.5;
bool validPoint = false;
if (ratio < maxslope && ratio > 1. / maxslope)
{
// Accumulate data for gain matching
dataPoints[{id, chr, chw}].emplace_back(ew, er);
plotter->Fill2D("hAll_in", 4000, 0, 16000, 4000, 0, 16000, ew, er);
validPoint = true;
}
if (!validPoint)
{
plotter->Fill2D("hAll_out", 4000, 0, 16000, 4000, 0, 16000, ew, er);
}
}
return kTRUE;
}
void GainMatchQQQ::Terminate()
{
const int MAX_DET = 4;
const int MAX_RING = 16;
const int MAX_WEDGE = 16;
// We store gains locally just for the "corrected" plot,
// but the file will output Slopes for the global minimizer.
double gainW[MAX_DET][MAX_RING][MAX_WEDGE] = {{{0}}};
double gainR[MAX_DET][MAX_RING][MAX_WEDGE] = {{{0}}};
bool gainValid[MAX_DET][MAX_RING][MAX_WEDGE] = {{{false}}};
// Output file for the Minimizer
std::ofstream outFile("qqq_GainMatch.dat");
// Benchmark/Debug file
std::ofstream benchFile("benchmark_diff.dat");
benchFile << "ID Wedge Ring Chi2NDF Slope SlopeErr" << std::endl;
if (!outFile.is_open()) { std::cerr << "Error opening output file!" << std::endl; return; }
const int MIN_POINTS = 50;
const int MAX_ITER = 3; // Outlier rejection passes
const double CLIP_SIGMA = 2.5; // Sigma threshold for outliers
for (const auto &kv : dataPoints)
{
auto key = kv.first;
auto [id, ring, wedge] = key;
const auto &pts = kv.second;
if (pts.size() < (size_t)MIN_POINTS) continue;
std::vector<std::pair<double, double>> current_pts = pts;
double finalSlope = 0.0;
double finalSlopeErr = 0.0;
bool converged = false;
// --- Iterative Fitting ---
for (int iter = 0; iter < MAX_ITER; ++iter)
{
if (current_pts.size() < (size_t)MIN_POINTS) break;
std::vector<double> x, y, ex, ey;
for (const auto &p : current_pts)
{
x.push_back(p.first); // Wedge E
y.push_back(p.second); // Ring E
ex.push_back(std::sqrt(std::abs(p.first))); // Error in X (Poisson)
ey.push_back(std::sqrt(std::abs(p.second))); // Error in Y (Poisson)
// Sanity check to avoid 0 error
if(ex.back() < 1.0) ex.back() = 1.0;
if(ey.back() < 1.0) ey.back() = 1.0;
}
// 2. Create Graph
TGraphErrors *gr = new TGraphErrors(current_pts.size(), x.data(), y.data(), ex.data(), ey.data());
// 3. Fit Linear Function through Origin
TF1 *f1= new TF1("calibFit", "[0]*x", 0, 16000);
f1->SetParameter(0, 1.0);
// "Q"=Quiet, "N"=NoDraw, "S"=ResultPtr
// We do NOT use "W" (Ignore weights), we want to use the errors we set.
int fitStatus = gr->Fit(f1, "QNS");
if (fitStatus != 0) {
delete gr; delete f1;
break;
}
finalSlope = f1->GetParameter(0);
double chi2 = f1->GetChisquare();
double ndf = f1->GetNDF();
// Get the statistical error on the slope
double rawErr = f1->GetParError(0);
// SCALING ERROR:
// If Chi2/NDF > 1, the data scatters more than Poisson stats predict.
// // We inflate the error by sqrt(Chi2/NDF) to be conservative for the Minimizer.
// double redChi2 = (ndf > 0) ? (chi2 / ndf) : 1.0;
// double inflation = (redChi2 > 1.0) ? std::sqrt(redChi2) : 1.0;
// finalSlopeErr = rawErr * inflation;
// 4. Outlier Rejection
if (iter == MAX_ITER - 1) {
converged = true;
delete gr; delete f1;
break;
}
// Calculate Residuals
std::vector<double> residuals;
double sumSqResid = 0.0;
for(size_t k=0; k<current_pts.size(); ++k) {
double val = f1->Eval(current_pts[k].first);
double res = current_pts[k].second - val;
residuals.push_back(res);
sumSqResid += res*res;
}
// double sigma = std::sqrt(sumSqResid / current_pts.size());
// // Filter
// std::vector<std::pair<double, double>> next_pts;
// for(size_t k=0; k<current_pts.size(); ++k) {
// if(std::abs(residuals[k]) < CLIP_SIGMA * sigma) {
// next_pts.push_back(current_pts[k]);
// }
// }
// if (next_pts.size() == current_pts.size()) {
// converged = true;
// delete gr; delete f1;
// break;
// }
// current_pts = next_pts;
// delete gr; delete f1;
}
if (!converged || finalSlope <= 0) continue;
// --- Store/Output ---
// 1. Save locally for the verification plot (hAll)
// Approximate local gain for plotting purposes only
double gW_local = std::sqrt(finalSlope);
double gR_local = 1.0 / gW_local;
gainW[id][ring][wedge] = gW_local;
gainR[id][ring][wedge] = gR_local;
gainValid[id][ring][wedge] = true;
// 2. Write to File for Minimizer
// Format: ID Wedge Ring Slope Error
outFile << id << " " << wedge << " " << ring << " " << finalSlope << " " << finalSlopeErr << std::endl;
// 3. Benchmark Info
benchFile << id << " " << wedge << " " << ring << " "
<< finalSlope << " " << finalSlopeErr << std::endl;
}
outFile.close();
benchFile.close();
std::cout << "Gain matching with Errors complete." << std::endl;
// Plotting the corrected data (Visual check using local approx gains)
for (auto &kv : dataPoints)
{
int id, ring, wedge;
std::tie(id, ring, wedge) = kv.first;
if (!gainValid[id][ring][wedge]) continue;
auto &pts = kv.second;
for (auto &pr : pts)
{
double corrWedge = pr.first * gainW[id][ring][wedge];
double corrRing = pr.second * gainR[id][ring][wedge];
plotter->Fill2D("hAll", 4000, 0, 16000, 4000, 0, 16000, corrWedge, corrRing);
}
}
plotter->FlushToDisk();
}

114
GainMatchQQQ.h Normal file
View File

@ -0,0 +1,114 @@
#ifndef GainMatchQQQ_h
#define GainMatchQQQ_h
#include <TROOT.h>
#include <TChain.h>
#include <TFile.h>
#include <TSelector.h>
#include "Armory/ClassDet.h"
class GainMatchQQQ : public TSelector {
public :
TTree *fChain; //!pointer to the analyzed TTree or TChain
// Fixed size dimensions of array or collections stored in the TTree if any.
// Declaration of leaf types
Det sx3;
Det qqq;
Det pc ;
ULong64_t evID;
UInt_t run;
// List of branches
TBranch *b_eventID; //!
TBranch *b_run; //!
TBranch *b_sx3Multi; //!
TBranch *b_sx3ID; //!
TBranch *b_sx3Ch; //!
TBranch *b_sx3E; //!
TBranch *b_sx3T; //!
TBranch *b_qqqMulti; //!
TBranch *b_qqqID; //!
TBranch *b_qqqCh; //!
TBranch *b_qqqE; //!
TBranch *b_qqqT; //!
TBranch *b_pcMulti; //!
TBranch *b_pcID; //!
TBranch *b_pcCh; //!
TBranch *b_pcE; //!
TBranch *b_pcT; //!
GainMatchQQQ(TTree * /*tree*/ =0) : fChain(0) { }
virtual ~GainMatchQQQ() { }
virtual Int_t Version() const { return 2; }
virtual void Begin(TTree *tree);
virtual void SlaveBegin(TTree *tree);
virtual void Init(TTree *tree);
virtual Bool_t Notify();
virtual Bool_t Process(Long64_t entry);
virtual Int_t GetEntry(Long64_t entry, Int_t getall = 0) { return fChain ? fChain->GetTree()->GetEntry(entry, getall) : 0; }
virtual void SetOption(const char *option) { fOption = option; }
virtual void SetObject(TObject *obj) { fObject = obj; }
virtual void SetInputList(TList *input) { fInput = input; }
virtual TList *GetOutputList() const { return fOutput; }
virtual void SlaveTerminate();
virtual void Terminate();
ClassDef(GainMatchQQQ,0);
};
#endif
#ifdef GainMatchQQQ_cxx
void GainMatchQQQ::Init(TTree *tree){
// Set branch addresses and branch pointers
if (!tree) return;
fChain = tree;
fChain->SetMakeClass(1);
fChain->SetBranchAddress("evID", &evID, &b_eventID);
fChain->SetBranchAddress("run", &run, &b_run);
sx3.SetDetDimension(24,12);
qqq.SetDetDimension(4,32);
pc.SetDetDimension(2,24);
fChain->SetBranchAddress("sx3Multi", &sx3.multi, &b_sx3Multi);
fChain->SetBranchAddress("sx3ID", &sx3.id, &b_sx3ID);
fChain->SetBranchAddress("sx3Ch", &sx3.ch, &b_sx3Ch);
fChain->SetBranchAddress("sx3E", &sx3.e, &b_sx3E);
fChain->SetBranchAddress("sx3T", &sx3.t, &b_sx3T);
fChain->SetBranchAddress("qqqMulti", &qqq.multi, &b_qqqMulti);
fChain->SetBranchAddress("qqqID", &qqq.id, &b_qqqID);
fChain->SetBranchAddress("qqqCh", &qqq.ch, &b_qqqCh);
fChain->SetBranchAddress("qqqE", &qqq.e, &b_qqqE);
fChain->SetBranchAddress("qqqT", &qqq.t, &b_qqqT);
fChain->SetBranchAddress("pcMulti", &pc.multi, &b_pcMulti);
fChain->SetBranchAddress("pcID", &pc.id, &b_pcID);
fChain->SetBranchAddress("pcCh", &pc.ch, &b_pcCh);
fChain->SetBranchAddress("pcE", &pc.e, &b_pcE);
fChain->SetBranchAddress("pcT", &pc.t, &b_pcT);
}
Bool_t GainMatchQQQ::Notify(){
return kTRUE;
}
void GainMatchQQQ::SlaveBegin(TTree * /*tree*/){
TString option = GetOption();
}
void GainMatchQQQ::SlaveTerminate(){
}
#endif // #ifdef GainMatchQQQ_cxx

4003
MakeVertex.C Executable file

File diff suppressed because it is too large Load Diff

133
MakeVertex.h Normal file
View File

@ -0,0 +1,133 @@
#ifndef MakeVertex_h
#define MakeVertex_h
#include <TROOT.h>
#include <TChain.h>
#include <TApplication.h>
#include <TFile.h>
#include <TSelector.h>
#include <iomanip>
#include <vector> // Required for vectors
#include <utility> // Required for std::pair
#include "Armory/ClassDet.h"
#include "Armory/ClassPW.h" // YOU ADDED THIS (Correct! Defines Coord)
class MakeVertex : public TSelector {
public :
TTree *fChain; //!pointer to the analyzed TTree or TChain
// Declaration of leaf types
Det sx3;
Det qqq;
Det pc ;
Det misc;
ULong64_t evID;
UInt_t run;
// List of branches
TBranch *b_eventID; //!
TBranch *b_run; //!
TBranch *b_sx3Multi; //!
TBranch *b_sx3ID; //!
TBranch *b_sx3Ch; //!
TBranch *b_sx3E; //!
TBranch *b_sx3T; //!
TBranch *b_qqqMulti; //!
TBranch *b_qqqID; //!
TBranch *b_qqqCh; //!
TBranch *b_qqqE; //!
TBranch *b_qqqT; //!
TBranch *b_pcMulti; //!
TBranch *b_pcID; //!
TBranch *b_pcCh; //!
TBranch *b_pcE; //!
TBranch *b_pcT; //!
TBranch *b_miscMulti; //!
TBranch *b_miscID; //!
TBranch *b_miscCh; //!
TBranch *b_miscE; //!
TBranch *b_miscT; //!
TBranch *b_miscTf; //!
// 1. Geometry Cache
Coord Crossover[24][24][2];
// 2. Persistent Vectors (REQUIRED for the optimized .cxx to work)
std::vector<std::pair<int, double>> anodeHits;
std::vector<std::pair<int, double>> cathodeHits;
std::vector<std::pair<int, double>> corrcatMax;
std::vector<std::pair<int, double>> corranoMax;
std::vector<double> cathodeTimes;
std::vector<double> anodeTimes;
MakeVertex(TTree * /*tree*/ =0) : fChain(0) { }
virtual ~MakeVertex() { }
virtual Int_t Version() const { return 2; }
virtual void Begin(TTree *tree);
virtual void SlaveBegin(TTree *tree);
virtual void Init(TTree *tree);
virtual Bool_t Notify();
virtual Bool_t Process(Long64_t entry);
virtual Int_t GetEntry(Long64_t entry, Int_t getall = 0) { return fChain ? fChain->GetTree()->GetEntry(entry, getall) : 0; }
virtual void SetOption(const char *option) { fOption = option; }
virtual void SetObject(TObject *obj) { fObject = obj; }
virtual void SetInputList(TList *input) { fInput = input; }
virtual TList *GetOutputList() const { return fOutput; }
virtual void SlaveTerminate();
virtual void Terminate();
ClassDef(MakeVertex,0);
};
#endif
#ifdef MakeVertex_cxx
void MakeVertex::Init(TTree *tree){
if (!tree) return;
fChain = tree;
fChain->SetMakeClass(1);
fChain->SetBranchAddress("evID", &evID, &b_eventID);
fChain->SetBranchAddress("run", &run, &b_run);
sx3.SetDetDimension(24,12);
qqq.SetDetDimension(4,32);
pc.SetDetDimension(2,24);
fChain->SetBranchAddress("sx3Multi", &sx3.multi, &b_sx3Multi);
fChain->SetBranchAddress("sx3ID", &sx3.id, &b_sx3ID);
fChain->SetBranchAddress("sx3Ch", &sx3.ch, &b_sx3Ch);
fChain->SetBranchAddress("sx3E", &sx3.e, &b_sx3E);
fChain->SetBranchAddress("sx3T", &sx3.t, &b_sx3T);
fChain->SetBranchAddress("qqqMulti", &qqq.multi, &b_qqqMulti);
fChain->SetBranchAddress("qqqID", &qqq.id, &b_qqqID);
fChain->SetBranchAddress("qqqCh", &qqq.ch, &b_qqqCh);
fChain->SetBranchAddress("qqqE", &qqq.e, &b_qqqE);
fChain->SetBranchAddress("qqqT", &qqq.t, &b_qqqT);
fChain->SetBranchAddress("pcMulti", &pc.multi, &b_pcMulti);
fChain->SetBranchAddress("pcID", &pc.id, &b_pcID);
fChain->SetBranchAddress("pcCh", &pc.ch, &b_pcCh);
fChain->SetBranchAddress("pcE", &pc.e, &b_pcE);
fChain->SetBranchAddress("pcT", &pc.t, &b_pcT);
fChain->SetBranchAddress("miscMulti", &misc.multi, &b_miscMulti);
fChain->SetBranchAddress("miscID", &misc.id, &b_miscID);
fChain->SetBranchAddress("miscCh", &misc.ch, &b_miscCh);
fChain->SetBranchAddress("miscE", &misc.e, &b_miscE);
fChain->SetBranchAddress("miscT", &misc.t, &b_miscT);
fChain->SetBranchAddress("miscf", &misc.tf, &b_miscTf);
}
Bool_t MakeVertex::Notify(){
return kTRUE;
}
void MakeVertex::SlaveBegin(TTree * /*tree*/){
// TString option = GetOption();
}
void MakeVertex::SlaveTerminate(){
}
#endif // #ifdef MakeVertex_cxx

139
MatchAndPlotCentroids.C Normal file
View File

@ -0,0 +1,139 @@
#include <fstream>
#include <sstream>
#include <vector>
#include <map>
#include <iostream>
#include <TGraph.h>
#include <TF1.h>
#include <TCanvas.h>
#include <TH1.h>
void MatchAndPlotCentroids() {
// Open the centroid data file
std::ifstream inputFile("centroids_27Al.txt");
// std::ifstream inputFile("centroids_17F.txt");
if (!inputFile.is_open()) {
std::cerr << "Error: Could not open Centroids.txt" << std::endl;
return;
}
// Data structure to store centroids by histogram and peak number
std::map<int, std::map<int, double>> centroidData;
// Read data from the file
std::string line;
while (std::getline(inputFile, line)) {
std::istringstream iss(line);
int histogramIndex, peakNumber;
double centroid;
if (iss >> histogramIndex >> peakNumber >> centroid) {
centroidData[histogramIndex][peakNumber] = centroid;
}
}
inputFile.close();
// Ensure histogram 24 exists and has data
if (centroidData.find(1) == centroidData.end()) {
std::cerr << "Error: Histogram 0 not found in the data!" << std::endl;
return;
}
// Reference centroids from histogram 24
const auto& referenceCentroids = centroidData[1];
std::ofstream outputFile("slope_intercept_results.dat");
if (!outputFile.is_open()) {
std::cerr << "Error: Could not open the output file for writing!" << std::endl;
return;
}
outputFile << "Histogram Number\tSlope\tIntercept\n";
// Loop through histograms 25 to 47
// 1. Create a SINGLE canvas BEFORE the loop
TCanvas *c1 = new TCanvas("c1", "Centroid Fit Viewer", 800, 600);
for (int targetHist = 0; targetHist <= 23; targetHist++) {
// Ensure the target histogram exists and matches in peak numbers
if (centroidData.find(targetHist) == centroidData.end() || centroidData[targetHist].size() != referenceCentroids.size()) {
//4th cnetroid data point for 19 was generated using the 3 datqa points for the slope of wires 0 and 19
std::cout << "Skipping Histogram " << targetHist << " due to mismatched or missing data." << std::endl;
continue;
}
// Prepare x and y values for TGraph
std::vector<double> xValues, yValues;
for (const auto& [peakNumber, refCentroid] : referenceCentroids) {
if (centroidData[targetHist].find(peakNumber) != centroidData[targetHist].end()) {
yValues.push_back(refCentroid);
xValues.push_back(centroidData[targetHist][peakNumber]);
} else {
std::cerr << "Warning: Peak " << peakNumber << " missing in histogram " << targetHist << std::endl;
}
}
if (xValues.size() < 3) {
std::cout << "Skipping Histogram " << targetHist << " as it has less than 3 matching centroids." << std::endl;
continue;
}
// Create a TGraph
TGraph *graph = new TGraph(xValues.size(), &xValues[0], &yValues[0]);
graph->SetTitle(Form("Centroid of Histogram %d vs 1", targetHist));
graph->GetYaxis()->SetTitle("Centroid of Histogram 1");
graph->GetXaxis()->SetTitle(Form("Centroid of Histogram %d", targetHist));
graph->SetMarkerStyle(20);
graph->SetMarkerSize(1.0);
graph->SetMarkerColor(kBlue);
graph->Draw("AP");
// Draw the graph
graph->Draw("AP");
double minX = *std::min_element(xValues.begin(), xValues.end());
double maxX = *std::max_element(xValues.begin(), xValues.end());
// Fit the data with a linear function
TF1 *fitLine = new TF1("fitLine", "pol1", minX, maxX); // Adjust range as needed
fitLine->SetLineColor(kRed); // Set the line color to distinguish it
fitLine->SetLineWidth(2); // Thicker line for visibility
graph->Fit(fitLine, "M");
fitLine->Draw("same");
fitLine->SetParLimits(0, -10, 10); // Limit intercept between -10 and 10
fitLine->SetParLimits(1, 0, 2);
// Extract slope and intercept
double slope = fitLine->GetParameter(1);
double intercept = fitLine->GetParameter(0);
outputFile << targetHist << "\t" << slope << "\t" << intercept << "\n";
std::cout << "Histogram 24 vs " << targetHist << ": Slope = " << slope << ", Intercept = " << intercept << std::endl;
std::vector<double> residuals;
for (size_t i = 0; i < xValues.size(); ++i) {
double fittedY = fitLine->Eval(xValues[i]); // Evaluate fitted function at x
double residual = yValues[i] - fittedY; // Residual = observed - fitted
residuals.push_back(residual);
}
// Create a graph for the residuals
/*TGraph *residualGraph = new TGraph(residuals.size(), &xValues[0], &residuals[0]);
residualGraph->SetTitle(Form("Residuals for Histogram 24 vs %d", targetHist));
residualGraph->GetYaxis()->SetTitle("Residuals");
residualGraph->GetXaxis()->SetTitle(Form("Centroid of Histogram %d", targetHist));
residualGraph->SetMarkerStyle(20);
residualGraph->SetMarkerSize(1.0);
residualGraph->SetMarkerColor(kGreen);
// Draw the residuals plot below the original plot (can be on a new canvas if preferred)
TCanvas *c2 = new TCanvas(Form("c_residuals_24_vs_%d", targetHist), Form("Residuals for Centroid 24 vs %d", targetHist), 800, 400);
residualGraph->Draw("AP");*/
c1->Update();
//c2->Update();
std::cout << "Press Enter to continue..." << std::endl;
//std::cin.get();
c1->WaitPrimitive();
//c2->WaitPrimitive();
//std::cin.get();
//std::cin.get();
}
outputFile.close();
std::cout << "Results written to slope_intercept_results.txt" << std::endl;
}

359
MiscEdE.C Normal file
View File

@ -0,0 +1,359 @@
#include "TFile.h"
#include "TTree.h"
#include "TChain.h"
#include "TH1D.h"
#include "TH2D.h"
#include "TCutG.h"
#include "TKey.h"
#include "TString.h"
#include "TSystem.h"
#include <map>
#include <vector>
#include <utility>
#include <cstdlib>
namespace
{
constexpr double kAdcMax = 16384; // 14-bit-ish ADC range for axes
struct HitData
{
double e;
unsigned long long t;
};
}
void MiscEdE(const char *f0,
const char *f1 = "", const char *f2 = "", const char *f3 = "",
const char *f4 = "", const char *f5 = "", const char *f6 = "",
const char *f7 = "", const char *f8 = "", const char *f9 = "")
{
std::vector<TString> inputs;
for (const char *f : {f0, f1, f2, f3, f4, f5, f6, f7, f8, f9})
if (f && f[0])
inputs.emplace_back(f);
if (inputs.empty())
{
printf("MiscEdE: no input files given.\n");
return;
}
TChain *tree = new TChain("tree");
for (auto &in : inputs)
{
if (gSystem->AccessPathName(in))
{
printf("MiscEdE: WARNING input not found, skipping: %s\n", in.Data());
continue;
}
tree->Add(in);
}
if (tree->GetNtrees() == 0)
{
printf("MiscEdE: no readable inputs.\n");
return;
}
// ---------------------------------------------------------
// Bash Environment Variable Extraction
// ---------------------------------------------------------
int runNumber = -1;
if (gSystem->Getenv("CURRENT_RUN")) {
runNumber = std::atoi(gSystem->Getenv("CURRENT_RUN"));
}
printf("MiscEdE: Run Number loaded from Bash: %d\n", runNumber);
TString cutDir = "~/ANASEN_analysis"; // Fallback if not run via bash
if (gSystem->Getenv("CUT_DIR")) {
cutDir = gSystem->Getenv("CUT_DIR");
}
// ---------------------------------------------------------
// Run-Dependent Channel Routing
// ---------------------------------------------------------
// Defaults (Run <= 180)
int snLolli = 405, chLolli = 9;
int snSi = 405, chSi = 11;
int snRF = 405, chRF = 15;
int snMCP = 405, chMCP = 14;
if (runNumber > 180 && runNumber <= 282) {
chRF = 10;
}
else if (runNumber > 282 && runNumber <= 322) {
snRF = 89; chRF = 0;
snMCP = 89; chMCP = 1;
}
else if (runNumber > 322) {
snRF = 89; chRF = 0;
snMCP = 89; chMCP = 2;
}
// Composite Unique IDs (UID = Board * 100 + Channel) to prevent collisions
int uidLollipop = snLolli * 100 + chLolli;
int uidSiMon = snSi * 100 + chSi;
int uidRF = snRF * 100 + chRF;
int uidMCP = snMCP * 100 + chMCP;
printf(" -> Routed Lollipop : Board %d, Ch %d (UID: %d)\n", snLolli, chLolli, uidLollipop);
printf(" -> Routed Si Monitor: Board %d, Ch %d (UID: %d)\n", snSi, chSi, uidSiMon);
printf(" -> Routed RF : Board %d, Ch %d (UID: %d)\n", snRF, chRF, uidRF);
printf(" -> Routed MCP : Board %d, Ch %d (UID: %d)\n", snMCP, chMCP, uidMCP);
// ---------------------------------------------------------
// Exact schema matching EventBuilder.cpp
// ---------------------------------------------------------
const int MAX_MULTI = 2000;
unsigned int multi = 0;
unsigned short sn[MAX_MULTI];
unsigned short ch[MAX_MULTI];
unsigned short e[MAX_MULTI];
unsigned long long e_t[MAX_MULTI]; // Timestamp branch is "e_t"
tree->SetBranchAddress("multi", &multi);
tree->SetBranchAddress("sn", sn);
tree->SetBranchAddress("ch", ch);
tree->SetBranchAddress("e", e);
tree->SetBranchAddress("e_t", e_t);
TString stem = gSystem->BaseName(inputs[0].Data());
if (stem.EndsWith(".root"))
stem.Remove(stem.Length() - 5);
TString outName = "MiscEdE_" + stem + ".root";
TFile *out = new TFile(outName, "recreate");
// ---------------------------------------------------------
// Load the 17F and 16O TCutGs from file
// ---------------------------------------------------------
TCutG *cut17F = nullptr;
TString path17F = gSystem->ExpandPathName(Form("%s/17FCut.root", cutDir.Data()));
if (!gSystem->AccessPathName(path17F))
{
TFile *fCut = TFile::Open(path17F);
if (fCut && !fCut->IsZombie())
{
for (auto keyObj : *fCut->GetListOfKeys())
{
TKey *key = (TKey *)keyObj;
if (TString(key->GetClassName()) == "TCutG")
{
cut17F = (TCutG *)key->ReadObj()->Clone("cut17F");
break;
}
}
fCut->Close();
}
}
TCutG *cut16O = nullptr;
TString path16O = gSystem->ExpandPathName(Form("%s/16OCut.root", cutDir.Data()));
if (!gSystem->AccessPathName(path16O))
{
TFile *fCut = TFile::Open(path16O);
if (fCut && !fCut->IsZombie())
{
for (auto keyObj : *fCut->GetListOfKeys())
{
TKey *key = (TKey *)keyObj;
if (TString(key->GetClassName()) == "TCutG")
{
cut16O = (TCutG *)key->ReadObj()->Clone("cut16O");
break;
}
}
fCut->Close();
}
}
out->cd();
// ---------------------------------------------------------
// Prepare Histograms
// ---------------------------------------------------------
std::map<int, TH2D*> hEvsChMap;
auto getEvsCh = [&](int boardSN) -> TH2D* {
if (hEvsChMap.count(boardSN)) return hEvsChMap[boardSN];
TH2D *h = new TH2D(Form("h2_E_vs_ch_bd%d", boardSN), Form("Board %d: E vs channel;board channel;E [ADC]", boardSN), 16, -0.5, 15.5, 800, 0, kAdcMax);
hEvsChMap[boardSN] = h;
return h;
};
TH2D *h2_EvsdT_LolliSi = new TH2D("EvsdT_Lollipop_Si", "Energy Si vs dT (Lollipop - Si);dT [ticks];Si E [ADC]", 500, 0, 2000, 400, 0, kAdcMax);
TH2D *h2_TOF_SiRF = new TH2D("TOF_Si_RF", "Time of Flight: Energy Si vs (T_Si - T_RF);T_Si - T_RF [ticks];Si E [ADC]", 500, 0, 2000, 400, 0, kAdcMax);
// Dynamic Pairwise E-dE mapped by UID
std::map<std::pair<int, int>, TH2D *> hEdE;
auto getEdE = [&](int uidA, int uidB) -> TH2D * {
if (uidA > uidB) std::swap(uidA, uidB);
auto key = std::make_pair(uidA, uidB);
auto it = hEdE.find(key);
if (it != hEdE.end()) return it->second;
TString name = Form("h2_ede_bd%d_ch%d_vs_bd%d_ch%d", uidA/100, uidA%100, uidB/100, uidB%100);
TString title = Form("E-dE: Bd %d Ch %d (x) vs Bd %d Ch %d (y);X E [ADC];Y E [ADC]", uidA/100, uidA%100, uidB/100, uidB%100);
TH2D *h = new TH2D(name, title, 400, 0, kAdcMax, 400, 0, kAdcMax);
hEdE[key] = h;
return h;
};
// 17F Gated Plots
TH2D *hEdE_17F = nullptr;
TH2D *hEdE_inv = nullptr;
TH2D *h2_EvsdT_LolliSi_17F = nullptr;
TH2D *h2_TOF_SiRF_17F = nullptr;
TH1D *h1_dT_17F = nullptr;
if (cut17F)
{
hEdE_17F = new TH2D("EdE_lollipopIC_vs_SiMonitor_17FCut", "lollipop IC vs Si monitor (17F Gated);Si monitor E [ADC];lollipop IC E [ADC]", 400, 0, kAdcMax, 400, 0, kAdcMax);
hEdE_inv = new TH2D("EdE_lollipopIC_vs_SiMonitor_17FCut_Inverse", "lollipop IC vs Si monitor (Inverse 17F Gated);Si monitor E [ADC];lollipop IC E [ADC]", 400, 0, kAdcMax, 400, 0, kAdcMax);
h2_EvsdT_LolliSi_17F = new TH2D("EvsdT_Lollipop_Si_17FCut", "Energy Si vs dT (17F Gated);dT (Lollipop - Si) [ticks];Si E [ADC]", 500, 0, 2000, 400, 0, kAdcMax);
h2_TOF_SiRF_17F = new TH2D("TOF_Si_RF_17FCut", "Si TOF vs Energy (17F Gated);T_Si - T_RF [ticks];Si E [ADC]", 500, 0, 2000, 400, 0, kAdcMax);
h1_dT_17F = new TH1D("dT_Lollipop_Si_17FCut", "dT Lollipop-Si (17F Gated);T_Lollipop - T_Si [ticks]", 500, 0, 2000);
}
// 16O Gated Plots
TH2D *hEdE_16O = nullptr;
TH2D *h2_EvsdT_LolliSi_16O = nullptr;
TH2D *h2_TOF_SiRF_16O = nullptr;
TH1D *h1_dT_16O = nullptr;
if (cut16O)
{
hEdE_16O = new TH2D("EdE_lollipopIC_vs_SiMonitor_16OCut", "lollipop IC vs Si monitor (16O Gated);Si monitor E [ADC];lollipop IC E [ADC]", 400, 0, kAdcMax, 400, 0, kAdcMax);
h2_EvsdT_LolliSi_16O = new TH2D("EvsdT_Lollipop_Si_16OCut", "Energy Si vs dT (16O Gated);dT (Lollipop - Si) [ticks];Si E [ADC]", 500, 0, 2000, 400, 0, kAdcMax);
h2_TOF_SiRF_16O = new TH2D("TOF_Si_RF_16OCut", "Si TOF vs Energy (16O Gated);T_Si - T_RF [ticks];Si E [ADC]", 500, 0, 2000, 800, 0, kAdcMax);
h1_dT_16O = new TH1D("dT_Lollipop_Si_16OCut", "dT Lollipop-Si (16O Gated);T_Lollipop - T_Si [ticks]", 500, 0, 2000);
}
Long64_t nEnt = tree->GetEntries();
printf("MiscEdE: %lld entries across %d file(s) -> %s\n", nEnt, tree->GetNtrees(), outName.Data());
Long64_t used = 0;
for (Long64_t i = 0; i < nEnt; ++i)
{
tree->GetEntry(i);
// We map hits by UID instead of standard channel to prevent overlaps between Bd405 and Bd89
std::map<int, HitData> hit;
for (unsigned int j = 0; j < multi && j < (unsigned)MAX_MULTI; ++j)
{
// Only care about MISC boards 405 and 89
if (sn[j] != 405 && sn[j] != 89)
continue;
int c = ch[j];
int uid = (sn[j] * 100) + ch[j];
double en = e[j];
unsigned long long ts = e_t[j];
auto it = hit.find(uid);
if (it == hit.end() || en > it->second.e)
hit[uid] = {en, ts};
getEvsCh(sn[j])->Fill(c, en);
}
if (hit.empty())
continue;
++used;
// ---------------------------------------------------------
// Generic Dynamic Pairwise E-dE Logic across all UIDs
// ---------------------------------------------------------
for (auto a = hit.begin(); a != hit.end(); ++a)
{
for (auto b = std::next(a); b != hit.end(); ++b)
{
if (a->second.e > 500 && b->second.e > 500)
{
getEdE(a->first, b->first)->Fill(a->second.e, b->second.e);
}
}
}
// ---------------------------------------------------------
// Core Timing and PID Gating Logic (Lollipop-Si specific)
// ---------------------------------------------------------
if (hit.count(uidLollipop) && hit.count(uidSiMon))
{
double e_lol = hit[uidLollipop].e;
double e_si = hit[uidSiMon].e;
// Calculate delta T safely across potential CAEN rollovers
double dt_lol_si = static_cast<double>(hit[uidLollipop].t) - static_cast<double>(hit[uidSiMon].t);
if (e_lol > 500 && e_si > 500)
{
h2_EvsdT_LolliSi->Fill(dt_lol_si, e_si);
bool is17F = cut17F && cut17F->IsInside(e_si, e_lol);
bool is16O = cut16O && cut16O->IsInside(e_si, e_lol);
if (is17F)
{
hEdE_17F->Fill(e_si, e_lol);
h2_EvsdT_LolliSi_17F->Fill(dt_lol_si, e_si);
h1_dT_17F->Fill(dt_lol_si);
}
else if (cut17F)
{
hEdE_inv->Fill(e_si, e_lol);
}
if (is16O)
{
hEdE_16O->Fill(e_si, e_lol);
h2_EvsdT_LolliSi_16O->Fill(dt_lol_si, e_si);
h1_dT_16O->Fill(dt_lol_si);
}
// Generate absolute TOF if the RF channel fired
if (hit.count(uidRF))
{
double tof = static_cast<double>(hit[uidSiMon].t) - static_cast<double>(hit[uidRF].t);
h2_TOF_SiRF->Fill(tof, e_si);
if (is17F)
h2_TOF_SiRF_17F->Fill(tof, e_si);
if (is16O)
h2_TOF_SiRF_16O->Fill(tof, e_si);
}
}
}
}
// ---------------------------------------------------------
// Purge Sparse E-dE Histograms (< 500 counts)
// ---------------------------------------------------------
for (auto it = hEdE.begin(); it != hEdE.end();)
{
if (it->second->GetEntries() < 500)
{
delete it->second; // Free memory and remove from ROOT file
it = hEdE.erase(it);
}
else
{
++it;
}
}
// Convenience alias: Transpose generic plot to correctly place stopping E on X
if (hEdE.count({uidLollipop, uidSiMon}))
{
TH2D *src = hEdE[{uidLollipop, uidSiMon}];
TH2D *alias = new TH2D("EdE_lollipopIC_vs_SiMonitor_Ungated",
Form("lollipop IC vs Si monitor (Ungated);Bd %d Ch %d (Si) E [ADC];Bd %d Ch %d (Lolli) E [ADC]", snSi, chSi, snLolli, chLolli),
src->GetNbinsY(), src->GetYaxis()->GetXmin(), src->GetYaxis()->GetXmax(),
src->GetNbinsX(), src->GetXaxis()->GetXmin(), src->GetXaxis()->GetXmax());
for (int ix = 1; ix <= src->GetNbinsX(); ++ix)
for (int iy = 1; iy <= src->GetNbinsY(); ++iy)
alias->SetBinContent(iy, ix, src->GetBinContent(ix, iy)); // (x,y) -> (y,x)
}
out->Write();
out->Close();
printf("MiscEdE: Processed %lld valid MISC events. Wrote %s\n", used, outName.Data());
}

454
PCGainMatch.C Normal file
View File

@ -0,0 +1,454 @@
#define PCGainMatch_cxx
#include "PCGainMatch.h"
#include <TH2.h>
#include <TStyle.h>
#include <TCanvas.h>
#include <TMath.h>
#include <TCutG.h>
#include <utility>
#include <algorithm>
#include "Armory/ClassSX3.h"
#include "Armory/ClassPW.h"
#include "TVector3.h"
TH2F * hsx3IndexVE;
TH2F * hqqqIndexVE;
TH2F * hpcIndexVE;
TH2F * hsx3Coin;
TH2F * hqqqCoin;
TH2F * hpcCoin;
TH2F * hqqqPolar;
TH2F * hsx3VpcIndex;
TH2F * hqqqVpcIndex;
TH2F * hqqqVpcE;
TH2F * hsx3VpcE;
TH2F * hanVScatsum;
TH2F * hanVScatsum_a[24];
TH2F * hanVScatsum_hcut;
TH2F * hanVScatsum_lcut;
TH2F * hAnodeHits;
TH1F * hAnodeMultiplicity;
int padID = 0;
SX3 sx3_contr;
PW pw_contr;
TVector3 hitPos;
bool HitNonZero;
TH1F * hZProj;
TCutG *AnCatSum_high;
TCutG *AnCatSum_low;
TCutG *PCCoinc_cut1;
TCutG *PCCoinc_cut2;
bool inCuth;
bool inCutl;
bool inPCCut;
void PCGainMatch::Begin(TTree * /*tree*/){
TString option = GetOption();
hsx3IndexVE = new TH2F("hsx3IndexVE", "SX3 index vs Energy; sx3 index ; Energy", 24*12, 0, 24*12, 400, 0, 5000); hsx3IndexVE->SetNdivisions( -612, "x");
hqqqIndexVE = new TH2F("hqqqIndexVE", "QQQ index vs Energy; QQQ index ; Energy", 4*2*16, 0, 4*2*16, 400, 0, 5000); hqqqIndexVE->SetNdivisions( -1204, "x");
hpcIndexVE = new TH2F("hpcIndexVE", "PC index vs Energy; PC index ; Energy", 2*24, 0, 2*24, 800, 0, 16000); hpcIndexVE->SetNdivisions( -1204, "x");
hsx3Coin = new TH2F("hsx3Coin", "SX3 Coincident", 24*12, 0, 24*12, 24*12, 0, 24*12);
hqqqCoin = new TH2F("hqqqCoin", "QQQ Coincident", 4*2*16, 0, 4*2*16, 4*2*16, 0, 4*2*16);
hpcCoin = new TH2F("hpcCoin", "PC Coincident", 2*24, 0, 2*24, 2*24, 0, 2*24);
hqqqPolar = new TH2F("hqqqPolar", "QQQ Polar ID", 16*4, -TMath::Pi(), TMath::Pi(),16, 10, 50);
hsx3VpcIndex = new TH2F("hsx3Vpcindex", "sx3 vs pc; sx3 index; pc index", 24*12, 0, 24*12, 48, 0, 48);
hsx3VpcIndex->SetNdivisions( -612, "x");
hsx3VpcIndex->SetNdivisions( -12, "y");
hqqqVpcIndex = new TH2F("hqqqVpcindex", "qqq vs pc; qqq index; pc index", 4*2*16, 0, 4*2*16, 48, 0, 48);
hqqqVpcIndex->SetNdivisions( -612, "x");
hqqqVpcIndex->SetNdivisions( -12, "y");
hqqqVpcE = new TH2F("hqqqVpcEnergy", "qqq vs pc; qqq energy; pc energy", 400, 0, 5000, 400, 0, 5000);
hqqqVpcE->SetNdivisions( -612, "x");
hqqqVpcE->SetNdivisions( -12, "y");
hsx3VpcE = new TH2F("hsx3VpcEnergy", "sx3 vs pc; sx3 energy; pc energy", 400, 0, 5000, 400, 0, 5000);
hsx3VpcE->SetNdivisions( -612, "x");
hsx3VpcE->SetNdivisions( -12, "y");
hZProj = new TH1F("hZProj", "Nos of anodes", 20, 0, 19);
hAnodeHits = new TH2F("hAnodeHits", "Anode vs Anode Energy, Anode ID; Anode E", 24,0 , 23, 400, 0 , 20000);
hAnodeMultiplicity = new TH1F("hAnodeMultiplicity", "Number of Anodes/Event", 40, 0, 40);
hanVScatsum = new TH2F("hanVScatsum", "Anode vs Cathode Sum; Anode E; Cathode E", 400,0 , 10000, 800, 0 , 16000);
for (int i = 0; i < 24; i++) {
TString histName = Form("hAnodeVsCathode_%d", i);
TString histTitle = Form("Anode %d vs Cathode Sum; Anode E; Cathode Sum E", i);
hanVScatsum_a[i] = new TH2F(histName, histTitle, 400, 0, 10000, 400, 0, 16000);
}
hanVScatsum_lcut = new TH2F("hanVScatsum_LCUT", "Anode vs Cathode Sum; Anode E; Cathode E", 400,0 , 16000, 400, 0 , 16000);
hanVScatsum_hcut = new TH2F("hanVScatsum_HCUT", "Anode vs Cathode Sum; Anode E; Cathode E", 400,0 , 16000, 400, 0 , 16000);
sx3_contr.ConstructGeo();
pw_contr.ConstructGeo();
// TFile *f1 = new TFile("AnCatSum_high.root");
// TFile *f2 = new TFile("AnCatSum_low.root");
// TFile *f3 = new TFile("PCCoinc_cut1.root");
// TFile *f4 = new TFile("PCCoinc_cut2.root");
// AnCatSum_high= (TCutG*)f1->Get("AnCatSum_high");
// AnCatSum_low= (TCutG*)f2->Get("AnCatSum_low");
// PCCoinc_cut1= (TCutG*)f3->Get("PCCoinc_cut1");
// PCCoinc_cut2= (TCutG*)f4->Get("PCCoinc_cut2");
}
Bool_t PCGainMatch::Process(Long64_t entry){
// if (entry % 1000000 == 0) {
// std::cout << "Processing entry: " << entry << std::endl;
// }
// if ( entry > 100 ) return kTRUE;
hitPos.Clear();
HitNonZero = false;
// if( entry > 1) return kTRUE;
// printf("################### ev : %llu \n", entry);
b_sx3Multi->GetEntry(entry);
b_sx3ID->GetEntry(entry);
b_sx3Ch->GetEntry(entry);
b_sx3E->GetEntry(entry);
b_sx3T->GetEntry(entry);
b_qqqMulti->GetEntry(entry);
b_qqqID->GetEntry(entry);
b_qqqCh->GetEntry(entry);
b_qqqE->GetEntry(entry);
b_qqqT->GetEntry(entry);
b_pcMulti->GetEntry(entry);
b_pcID->GetEntry(entry);
b_pcCh->GetEntry(entry);
b_pcE->GetEntry(entry);
b_pcT->GetEntry(entry);
sx3.CalIndex();
qqq.CalIndex();
pc.CalIndex();
// sx3.Print();
//########################################################### Raw data
// //======================= PC
std::vector<std::pair<int, double>> anodeHits={};
std::vector<std::pair<int, double>> cathodeHits={};
int aID = 0;
int cID = 0;
float aE = 0;
float cE = 0;
// Define the excluded SX3 and QQQ channels
// std::unordered_set<int> excludeSX3 = {34, 35, 36, 37, 61, 62, 67, 73, 74, 75, 76, 77, 78, 79, 80, 93, 97, 100, 103, 108, 109, 110, 111, 112};
// std::unordered_set<int> excludeQQQ = {0, 17, 109, 110, 111, 112, 113, 119, 127, 128};
// inCuth=false;
// inCutl=false;
// inPCCut=false;
for( int i = 0; i < pc.multi; i ++){
if(pc.e[i]>50 && pc.multi<7){
float aESum = 0;
float cESum = 0;
if (pc.index[i] < 24 ) {
anodeHits.push_back(std::pair<int, double>(pc.index[i], pc.e[i]));
} else if (pc.index[i] >= 24) {
cathodeHits.push_back(std::pair<int, double>(pc.index[i], pc.e[i]));
}
for(int j=i+1;j<pc.multi;j++){
// if(PCCoinc_cut1->IsInside(pc.index[i], pc.index[j]) || PCCoinc_cut2->IsInside(pc.index[i], pc.index[j])){
// // hpcCoin->Fill(pc.index[i], pc.index[j]);
// inPCCut = true;
// }
hpcCoin->Fill(pc.index[i], pc.index[j]);
}
if (anodeHits.size()==1 && cathodeHits.size() >= 1) {
for (const auto& anode : anodeHits) {
// for(int l=0; l<sx3.multi; l++){
// if (sx3.index[l]==80){
aID = anode.first;
aE = anode.second;
aESum += aE;
// printf("aID : %d, aE : %f\n", aID, aE);
}
// printf("aID : %d, aE : %f, cE : %f\n", aID, aE, cE);
for (const auto& cathode : cathodeHits) {
cID = cathode.first;
cE = cathode.second;
// if(cE>cEMax){
// cEMax = cE;
// cIDMax = cID;
// }
// if(cE>cEnextMax && cE<cEMax){
// cEnextMax = cE;
// cIDnextMax = cID;
// }
cESum += cE;
}
// }
// inCuth = false;
// inCutl = false;
// inPCCut = false;
// for(int j=i+1;j<pc.multi;j++){
// if(PCCoinc_cut1->IsInside(pc.index[i], pc.index[j]) || PCCoinc_cut2->IsInside(pc.index[i], pc.index[j])){
// // hpcCoin->Fill(pc.index[i], pc.index[j]);
// inPCCut = true;
// }
// hpcCoin->Fill(pc.index[i], pc.index[j]);
// }
// Check if the accumulated energies are within the defined ranges
// if (AnCatSum_high && AnCatSum_high->IsInside(aESum, cESum)) {
// inCuth = true;
// }
// if (AnCatSum_low && AnCatSum_low->IsInside(aESum, cESum)) {
// inCutl = true;
// }
// Fill histograms based on the cut conditions
// if (inCuth && inPCCut) {
// hanVScatsum_hcut->Fill(aESum, cESum);
// }
// if (inCutl && inPCCut) {
// hanVScatsum_lcut->Fill(aESum, cESum);
// }
// for(auto anode : anodeHits){
// float aE = anode.second;
// aESum += aE;
// if(inPCCut){
hanVScatsum->Fill(aESum, cESum);
// }
if (aID < 24 && aE > 50) {
hanVScatsum_a[aID]->Fill(aE, cESum);
}
// }
// Fill histograms for the `pc` data
hpcIndexVE->Fill(pc.index[i], pc.e[i]);
// if(inPCCut){
hAnodeMultiplicity->Fill(anodeHits.size());
// }
}
}
}
// //======================= SX3
std::vector<std::pair<int, int>> ID; // first = id, 2nd = index
for( int i = 0; i < sx3.multi; i ++){
if(sx3.e[i]>50){
ID.push_back(std::pair<int, int>(sx3.id[i], i));
hsx3IndexVE->Fill( sx3.index[i], sx3.e[i] );
for( int j = i+1; j < sx3.multi; j++){
hsx3Coin->Fill( sx3.index[i], sx3.index[j]);
}
for( int j = 0; j < pc.multi; j++){
hsx3VpcIndex->Fill( sx3.index[i], pc.index[j] );
// if( sx3.ch[index] > 8 ){
// hsx3VpcE->Fill( sx3.e[i], pc.e[j] );
// }
}
}
}
if( ID.size() > 0 ){
std::sort(ID.begin(), ID.end(), [](const std::pair<int, int> & a, const std::pair<int, int> & b) {
return a.first < b.first;
} );
// printf("##############################\n");
// for( size_t i = 0; i < ID.size(); i++) printf("%zu | %d %d \n", i, ID[i].first, ID[i].second );
std::vector<std::pair<int, int>> sx3ID;
sx3ID.push_back(ID[0]);
bool found = false;
for( size_t i = 1; i < ID.size(); i++){
if( ID[i].first == sx3ID.back().first) {
sx3ID.push_back(ID[i]);
if( sx3ID.size() >= 3) {
found = true;
}
}else{
if( !found ){
sx3ID.clear();
sx3ID.push_back(ID[i]);
}
}
}
// printf("---------- sx3ID Multi : %zu \n", sx3ID.size());
if( found ){
int sx3ChUp, sx3ChDn, sx3ChBk;
float sx3EUp, sx3EDn;
// printf("------ sx3 ID : %d, multi: %zu\n", sx3ID[0].first, sx3ID.size());
for( size_t i = 0; i < sx3ID.size(); i++ ){
int index = sx3ID[i].second;
// printf(" %zu | index %d | ch : %d, energy : %d \n", i, index, sx3.ch[index], sx3.e[index]);
if( sx3.ch[index] < 8 ){
if( sx3.ch[index] % 2 == 0) {
sx3ChDn = sx3.ch[index];
sx3EDn = sx3.e[index];
}else{
sx3ChUp = sx3.ch[index];
sx3EUp = sx3.e[index];
}
}else{
sx3ChBk = sx3.ch[index];
}
for( int j = 0; j < pc.multi; j++){
// hsx3VpcIndex->Fill( sx3.index[i], pc.index[j] );
if( sx3.ch[index] > 8 && pc.index[j]<24 && pc.e[j]>50 ){
hsx3VpcE->Fill( sx3.e[i], pc.e[j] );
// hpcIndexVE->Fill( pc.index[i], pc.e[i] );
}
}
}
sx3_contr.CalSX3Pos(sx3ID[0].first, sx3ChUp, sx3ChDn, sx3ChBk, sx3EUp, sx3EDn);
hitPos = sx3_contr.GetHitPos();
HitNonZero = true;
// hitPos.Print();
}
}
// //======================= QQQ
for( int i = 0; i < qqq.multi; i ++){
// for( int j = 0; j < pc.multi; j++){
if(qqq.e[i]>50 ){
hqqqIndexVE->Fill( qqq.index[i], qqq.e[i] );
for( int j = 0; j < qqq.multi; j++){
if ( j == i ) continue;
hqqqCoin->Fill( qqq.index[i], qqq.index[j]);
}
for( int j = i + 1; j < qqq.multi; j++){
for( int k = 0; k < pc.multi; k++){
// if(qqq.e[i>50]){
hqqqVpcE->Fill( qqq.e[i], pc.e[k] );
hqqqVpcIndex->Fill( qqq.index[i], pc.index[j] );
}
// }
}
}
// }
}
// hanVScatsum->Fill(aE,cE);
if( HitNonZero){
pw_contr.CalTrack( hitPos, aID, cID);
hZProj->Fill(pw_contr.GetZ0());
}
//########################################################### Track constrcution
//############################## DO THE KINEMATICS
return kTRUE;
}
void PCGainMatch::Terminate(){
gStyle->SetOptStat("neiou");
TCanvas * canvas = new TCanvas("cANASEN", "ANASEN", 2000, 2000);
canvas->Divide(3,3);
//hsx3VpcIndex->Draw("colz");
//=============================================== pad-1
padID ++; canvas->cd(padID); canvas->cd(padID)->SetGrid(1);
hsx3IndexVE->Draw("colz");
//=============================================== pad-2
padID ++; canvas->cd(padID); canvas->cd(padID)->SetGrid(1);
hqqqIndexVE->Draw("colz");
//=============================================== pad-3
padID ++; canvas->cd(padID); canvas->cd(padID)->SetGrid(1);
hpcIndexVE->Draw("colz");
//=============================================== pad-4
padID ++; canvas->cd(padID); canvas->cd(padID)->SetGrid(1);
hsx3Coin->Draw("colz");
//=============================================== pad-5
padID ++; canvas->cd(padID); canvas->cd(padID)->SetGrid(1);
canvas->cd(padID)->SetLogz(true);
hqqqCoin->Draw("colz");
//=============================================== pad-6
padID ++; canvas->cd(padID); canvas->cd(padID)->SetGrid(1);
hpcCoin->Draw("colz");
//=============================================== pad-7
padID ++; canvas->cd(padID); canvas->cd(padID)->SetGrid(1);
// hsx3VpcIndex ->Draw("colz");
hsx3VpcE->Draw("colz") ;
//=============================================== pad-8
padID ++; canvas->cd(padID); canvas->cd(padID)->SetGrid(1);
// hqqqVpcIndex ->Draw("colz");
hqqqVpcE ->Draw("colz");
//=============================================== pad-9
padID ++;
// canvas->cd(padID)->DrawFrame(-50, -50, 50, 50);
// hqqqPolar->Draw("same colz pol");
canvas->cd(padID); canvas->cd(padID)->SetGrid(1);
hanVScatsum->Draw("colz");
// hAnodeHits->Draw("colz");
// hAnodeMultiplicity->Draw();
}

View File

@ -1,5 +1,5 @@
#ifndef Analyzer_h
#define Analyzer_h
#ifndef PCGainMatch_h
#define PCGainMatch_h
#include <TROOT.h>
#include <TChain.h>
@ -8,7 +8,7 @@
#include "Armory/ClassDet.h"
class Analyzer : public TSelector {
class PCGainMatch : public TSelector {
public :
TTree *fChain; //!pointer to the analyzed TTree or TChain
@ -41,8 +41,8 @@ public :
TBranch *b_pcE; //!
TBranch *b_pcT; //!
Analyzer(TTree * /*tree*/ =0) : fChain(0) { }
virtual ~Analyzer() { }
PCGainMatch(TTree * /*tree*/ =0) : fChain(0) { }
virtual ~PCGainMatch() { }
virtual Int_t Version() const { return 2; }
virtual void Begin(TTree *tree);
virtual void SlaveBegin(TTree *tree);
@ -57,13 +57,13 @@ public :
virtual void SlaveTerminate();
virtual void Terminate();
ClassDef(Analyzer,0);
ClassDef(PCGainMatch,0);
};
#endif
#ifdef Analyzer_cxx
void Analyzer::Init(TTree *tree){
#ifdef PCGainMatch_cxx
void PCGainMatch::Init(TTree *tree){
// Set branch addresses and branch pointers
if (!tree) return;
@ -95,18 +95,18 @@ void Analyzer::Init(TTree *tree){
}
Bool_t Analyzer::Notify(){
Bool_t PCGainMatch::Notify(){
return kTRUE;
}
void Analyzer::SlaveBegin(TTree * /*tree*/){
void PCGainMatch::SlaveBegin(TTree * /*tree*/){
TString option = GetOption();
}
void Analyzer::SlaveTerminate(){
void PCGainMatch::SlaveTerminate(){
}

View File

@ -1,29 +1,38 @@
#!/bin/bash
if [ "$#" -ne 2 ]; then
echo "Usage: $0 runID timeWindow_ns"
if [ "$#" -ne 3 ]; then
echo "Usage: $0 runID timeWindow_ns option"
echo "option: 0 - process raw data, 1 - process mapped data"
echo "Exiting..."
exit 1
fi
runID=$1
runID=$(printf "%03d" $1)
timeWindow=$2
rawFolder=/home/tandem/Desktop/analysis/data
rootFolder=../root_data
option=$3
# rawFolder=/home/tandem/data1/2024_09_17Fap/data
# rawFolder=/mnt/d/17F
# rootFolder=/mnt/d/Remapped_files/17F_data/root_data
# rawFolder=/mnt/data1/vs19g/2024_06_27Al
# rootFolder=/home/vs19g/Desktop/27Al_Data/root_data
rawFolder=/mnt/data1/vs19g/2024_09_17Fap/data
rootFolder=/home/vs19g/Desktop/17F_Data/root_data
if [ $option -eq 0 ]; then
rsync -auh --info=progress2 splitpole@128.186.111.223:/media/nvmeData/2024_09_17Fap/*.fsu /home/tandem/data1/2024_09_17Fap/data
# rsync -auh --info=progress2 splitpole@128.186.111.223:/media/nvmeData/2024_09_17Fap/*.fsu /home/tandem/data1/2024_09_17Fap/data
fileList=`\ls -1 ${rawFolder}/*Run_${runID}_*.fsu`
fileList=`\ls -1 ${rawFolder}/*Run*_${runID}_*.fsu`
./EventBuilder ${timeWindow} 0 0 100000000 ${fileList}
time ./EventBuilder ${timeWindow} 0 0 100000000 ${fileList}
outFile=${rawFolder}/*${runID}*${timeWindow}.root
mv -vf ${outFile} ${rootFolder}/.
./Mapper ${rootFolder}/*${runID}*${timeWindow}.root
time ./Mapper ${rootFolder}/*${runID}*${timeWindow}.root
fi
root "processRun.C(\"${rootFolder}/Run_${runID}_mapped.root\")"
# root "processRun.C(\"${rootFolder}/Run_${runID}_mapped.root\")"

167
QQQ_Calcheck.C Normal file
View File

@ -0,0 +1,167 @@
#define QQQ_Calcheck_cxx
#include <TH2.h>
#include <TF1.h>
#include <TStyle.h>
#include <TCanvas.h>
#include <TMath.h>
#include <TCutG.h>
#include <fstream>
#include <utility>
#include <algorithm>
#include "Armory/HistPlotter.h"
#include "TVector3.h"
#include "QQQ_Calcheck.h"
TH2F *hQQQFVB;
HistPlotter *plotter;
int padID = 0;
TCutG *cut;
std::map<std::tuple<int, int, int>, std::vector<std::pair<double, double>>> dataPoints;
bool qqqEcut = false;
// Gain Arrays
const int MAX_QQQ = 4;
const int MAX_RING = 16;
const int MAX_WEDGE = 16;
double qqqwGain[MAX_QQQ][MAX_RING][MAX_WEDGE] = {{{0}}};
double qqqrGain[MAX_QQQ][MAX_RING][MAX_WEDGE] = {{{0}}};
bool qqqwGainValid[MAX_QQQ][MAX_RING][MAX_WEDGE] = {{{false}}};
bool qqqrGainValid[MAX_QQQ][MAX_RING][MAX_WEDGE] = {{{false}}};
double qqqCalib[MAX_QQQ][MAX_RING][MAX_WEDGE] = {{{0}}};
bool qqqCalibValid[MAX_QQQ][MAX_RING][MAX_WEDGE] = {{{false}}};
void QQQ_Calcheck::Begin(TTree * /*tree*/)
{
plotter = new HistPlotter("Cal_checkQQQ.root", "TFILE");
// ----------------------- Load QQQ Gains
{
std::string filename = "qqq_GainMatch.dat";
std::ifstream infile(filename);
if (!infile.is_open())
{
std::cerr << "Error opening " << filename << "!" << std::endl;
}
else
{
int det, ring, wedge;
double gainw,gainr;
while (infile >> det >> ring >> wedge >> gainw>> gainr)
{
qqqwGain[det][ring][wedge] = gainw;
// qqqrGain[det][ring][wedge] = gainr;
qqqwGainValid[det][ring][wedge] = (gainw > 0);
// qqqrGainValid[det][ring][wedge] = (gainr > 0);
}
infile.close();
std::cout << "Loaded QQQ gains from " << filename << std::endl;
}
}
// ----------------------- Load QQQ Calibrations
{
std::string filename = "qqq_Calib.dat";
std::ifstream infile(filename);
if (!infile.is_open())
{
std::cerr << "Error opening " << filename << "!" << std::endl;
}
else
{
int det, ring, wedge;
double slope;
while (infile >> det >> ring >> wedge >> slope)
{
qqqCalib[det][ring][wedge] = slope;
qqqCalibValid[det][ring][wedge] = (slope > 0);
}
infile.close();
std::cout << "Loaded QQQ calibrations from " << filename << std::endl;
}
}
}
Bool_t QQQ_Calcheck::Process(Long64_t entry)
{
b_qqqMulti->GetEntry(entry);
b_qqqID->GetEntry(entry);
b_qqqCh->GetEntry(entry);
b_qqqE->GetEntry(entry);
b_qqqT->GetEntry(entry);
qqq.CalIndex();
for (int i = 0; i < qqq.multi; i++)
{
for (int j = i + 1; j < qqq.multi; j++)
{
if (qqq.e[i] > 100)
qqqEcut = true;
if (qqq.id[i] == qqq.id[j])
{
int chWedge = -1;
int chRing = -1;
float eWedgeRaw = 0.0;
float eWedge = 0.0;
float eWedgeMeV = 0.0;
float eRingRaw = 0.0;
float eRing = 0.0;
float eRingMeV = 0.0;
// plug in gains
if (qqq.ch[i] < 16 && qqq.ch[j] >= 16 && /*qqqrGainValid[qqq.id[i]][qqq.ch[i]][qqq.ch[j] - 16] &&*/ qqqwGainValid[qqq.id[i]][qqq.ch[i]][qqq.ch[j] - 16])
{
chWedge = qqq.ch[i];
eWedgeRaw = qqq.e[i];
eWedge = qqq.e[i] * qqqwGain[qqq.id[i]][qqq.ch[i]][qqq.ch[j] - 16];
// printf("Wedge E: %.2f Gain: %.4f \n", eWedge, qqqGain[qqq.id[i]][qqq.ch[i]][qqq.ch[j] - 16]);
chRing = qqq.ch[j] - 16;
eRingRaw = qqq.e[j];
eRing = qqq.e[j];//* qqqrGain[qqq.id[j]][qqq.ch[j]][qqq.ch[i]-16];
}
else if (qqq.ch[j] < 16 && qqq.ch[i] >= 16/* && qqqrGainValid[qqq.id[j]][qqq.ch[j]][qqq.ch[i] - 16] */&& qqqwGainValid[qqq.id[j]][qqq.ch[j]][qqq.ch[i] - 16])
{
chWedge = qqq.ch[j];
eWedge = qqq.e[j] * qqqwGain[qqq.id[j]][qqq.ch[j]][qqq.ch[i] - 16];
eWedgeRaw = qqq.e[j];
chRing = qqq.ch[i] - 16;
eRing = qqq.e[i];// * qqqrGain[qqq.id[i]][qqq.ch[i]][qqq.ch[j] - 16];
eRingRaw = qqq.e[i];
}
else
continue;
// plug in calibrations
if (qqqCalibValid[qqq.id[i]][chWedge][chRing])
{
eWedgeMeV = eWedge * qqqCalib[qqq.id[i]][chWedge][chRing] / 1000;
eRingMeV = eRing * qqqCalib[qqq.id[i]][chWedge][chRing] / 1000;
}
else
continue;
// hQQQFVB->Fill(eWedge, eRing);
plotter->Fill2D(Form("hRaw_qqq%d_ring%d_wedge%d", qqq.id[i], chRing, chWedge), 400, 0, 8000, 400, 0, 8000, eWedgeRaw, eRingRaw, "ERaw");
plotter->Fill2D(Form("hGM_qqq%d_ring%d_wedge%d", qqq.id[i], chRing, chWedge), 400, 0, 16000, 400, 0, 16000, eWedge, eRing, "EGM");
plotter->Fill2D(Form("hCal_qqq%d_ring%d_wedge%d", qqq.id[i], chRing, chWedge), 400, 0, 10, 400, 0, 10, eWedgeMeV, eRingMeV, "ECal");
if(eWedgeRaw >1500 && eRingRaw>1500 )
plotter->Fill2D(Form("hCal_cut_qqq%d_ring%d_wedge%d", qqq.id[i], chRing, chWedge), 400, 0, 10, 400, 0, 10, eWedgeMeV, eRingMeV, "ECal_cut");
plotter->Fill2D(Form("hRCal_qqq%d", qqq.id[i]), 16, 0, 15, 1000, 0, 30, chRing, eRingMeV, "RingCal");
plotter->Fill2D(Form("hWCal_qqq%d", qqq.id[i]), 16, 0, 15, 1000, 0, 30, chWedge, eWedgeMeV, "WedgeCal");
plotter->Fill2D("hRawQQQ", 4000, 0, 8000, 4000, 0, 8000, eWedgeRaw, eRingRaw);
plotter->Fill2D("hGMQQQ", 4000, 0, 8000, 4000, 0, 8000, eWedge, eRing);
plotter->Fill2D("hCalQQQ", 4000, 0, 10, 4000, 0, 10, eWedgeMeV, eRingMeV);
}
}
}
return kTRUE;
}
void QQQ_Calcheck::Terminate()
{
plotter->FlushToDisk();
std::cout << "Calibration check file for 2D QQQ histogram saved.\n";
}

114
QQQ_Calcheck.h Normal file
View File

@ -0,0 +1,114 @@
#ifndef QQQ_Calcheck_h
#define QQQ_Calcheck_h
#include <TROOT.h>
#include <TChain.h>
#include <TFile.h>
#include <TSelector.h>
#include "Armory/ClassDet.h"
class QQQ_Calcheck : public TSelector {
public :
TTree *fChain; //!pointer to the analyzed TTree or TChain
// Fixed size dimensions of array or collections stored in the TTree if any.
// Declaration of leaf types
Det sx3;
Det qqq;
Det pc ;
ULong64_t evID;
UInt_t run;
// List of branches
TBranch *b_eventID; //!
TBranch *b_run; //!
TBranch *b_sx3Multi; //!
TBranch *b_sx3ID; //!
TBranch *b_sx3Ch; //!
TBranch *b_sx3E; //!
TBranch *b_sx3T; //!
TBranch *b_qqqMulti; //!
TBranch *b_qqqID; //!
TBranch *b_qqqCh; //!
TBranch *b_qqqE; //!
TBranch *b_qqqT; //!
TBranch *b_pcMulti; //!
TBranch *b_pcID; //!
TBranch *b_pcCh; //!
TBranch *b_pcE; //!
TBranch *b_pcT; //!
QQQ_Calcheck(TTree * /*tree*/ =0) : fChain(0) { }
virtual ~QQQ_Calcheck() { }
virtual Int_t Version() const { return 2; }
virtual void Begin(TTree *tree);
virtual void SlaveBegin(TTree *tree);
virtual void Init(TTree *tree);
virtual Bool_t Notify();
virtual Bool_t Process(Long64_t entry);
virtual Int_t GetEntry(Long64_t entry, Int_t getall = 0) { return fChain ? fChain->GetTree()->GetEntry(entry, getall) : 0; }
virtual void SetOption(const char *option) { fOption = option; }
virtual void SetObject(TObject *obj) { fObject = obj; }
virtual void SetInputList(TList *input) { fInput = input; }
virtual TList *GetOutputList() const { return fOutput; }
virtual void SlaveTerminate();
virtual void Terminate();
ClassDef(QQQ_Calcheck,0);
};
#endif
#ifdef QQQ_Calcheck_cxx
void QQQ_Calcheck::Init(TTree *tree){
// Set branch addresses and branch pointers
if (!tree) return;
fChain = tree;
fChain->SetMakeClass(1);
fChain->SetBranchAddress("evID", &evID, &b_eventID);
fChain->SetBranchAddress("run", &run, &b_run);
sx3.SetDetDimension(24,12);
qqq.SetDetDimension(4,32);
pc.SetDetDimension(2,24);
fChain->SetBranchAddress("sx3Multi", &sx3.multi, &b_sx3Multi);
fChain->SetBranchAddress("sx3ID", &sx3.id, &b_sx3ID);
fChain->SetBranchAddress("sx3Ch", &sx3.ch, &b_sx3Ch);
fChain->SetBranchAddress("sx3E", &sx3.e, &b_sx3E);
fChain->SetBranchAddress("sx3T", &sx3.t, &b_sx3T);
fChain->SetBranchAddress("qqqMulti", &qqq.multi, &b_qqqMulti);
fChain->SetBranchAddress("qqqID", &qqq.id, &b_qqqID);
fChain->SetBranchAddress("qqqCh", &qqq.ch, &b_qqqCh);
fChain->SetBranchAddress("qqqE", &qqq.e, &b_qqqE);
fChain->SetBranchAddress("qqqT", &qqq.t, &b_qqqT);
fChain->SetBranchAddress("pcMulti", &pc.multi, &b_pcMulti);
fChain->SetBranchAddress("pcID", &pc.id, &b_pcID);
fChain->SetBranchAddress("pcCh", &pc.ch, &b_pcCh);
fChain->SetBranchAddress("pcE", &pc.e, &b_pcE);
fChain->SetBranchAddress("pcT", &pc.t, &b_pcT);
}
Bool_t QQQ_Calcheck::Notify(){
return kTRUE;
}
void QQQ_Calcheck::SlaveBegin(TTree * /*tree*/){
TString option = GetOption();
}
void QQQ_Calcheck::SlaveTerminate(){
}
#endif // #ifdef QQQ_Calcheck_cxx

238
README.md Normal file
View File

@ -0,0 +1,238 @@
# ANASEN Analysis
Analysis code for the **Array for Nuclear Astrophysics and Structure with Exotic Nuclei (ANASEN)** detector at FSU. Processes raw .fsu data through event building, channel mapping, calibration, and physics-level vertex reconstruction for transfer reaction experiments.
---
## Detector Overview
| Detector | Type | Role |
|----------|------|------|
| **SX3** | Double-sided silicon strip (barrel, ~88 mm radius) | Energy and Z-position of recoils/ejectiles |
| **QQQ** | Annular silicon (forward, z = 100 mm) | Energy and polar angle of forward-going particles |
| **PC** | Helical proportional counter | dE and azimuthal/Z position via 24 anode + 24 cathode wires |
The PC uses 24 twisted anode wires and 24 cathode wires. Wire geometry, crossover positions, pseudo-wire interpolation, and track-plane intersection are all handled by `Armory/ClassPW.h`.
---
## Full Analysis Chain
```
Raw .fsu files (FSU digitizer output)
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 1. EVENT BUILDING │
│ Binary: EventBuilder (Armory/EventBuilder.cpp) │
│ Script: buildEvents.sh or ProcessRun.sh <run> <tw> 0 │
│ Input : *.fsu files │
│ Output: Run_NNN_<timeWindow>ns.root (raw, unmapped TTree) │
│ Hits within a configurable time window are grouped as events. │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 3. PRE-ANALYSIS CHECKS (optional) │
│ Macro: PreAnalysis.C │
│ Plots raw rates and energy spectra per channel from unmapped │
│ data — useful for identifying dead/noisy channels before │
│ mapping. │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 2. CHANNEL MAPPING │
│ Binary: Mapper (Armory/Mapper.cpp) │
│ Script: ProcessRun.sh <run> <tw> 0 (calls Mapper internally) │
│ Config: mapping.h │
│ Input : Eventbuilt ROOT tree │
│ Output: Run_NNN_mapped.root │
│ Translates hardware (digitizer SN, channel) to logical │
│ detector identity (SX3/QQQ/PC, strip/wire number). │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 4. CALIBRATION │
│ │
| SX3 — two-pass procedure: │
│ Pass 1 — Left/Right matching (sx3cal/LRFit.C) │
│ │ Start with unity gains: │
│ │ LRFit.C fits the left/right charge ratio │
│ │ Collate per-detector results into a single rightgains.dat │
| │ │
│ Pass 2 — Back/Front gain matching (sx3cal/EXFit.C) │
│ │ Run on data that is unity-gain sorted but L/R matched │
│ │ EXFit.C : │
│ │ 1) gain-matches the back strips (backgains.dat) │
│ │ 2) corrects dynamic range non-linearity in the fronts │
│ │ (frontgains.dat) │
│ │ Run for every detector, collate into master backgains.dat │
│ │ and frontgains.dat. │
│ │ |
│ ├── GainMatchQQQ.C │
│ │ QQQ ring/wedge gain matching │
│ │ Output: qqq_GainMatch.dat │
│ ├── CalibrationQQQ.C │
│ │ Final absolute energy calibration for all detectors │
│ │ Output: qqq_Calib.dat │
│ ├── PCGainMatch.C │
│ │ PC anode and cathode gain matching │
│ │ Output: slope_intercept_*.dat │
│ ├── FitHistogramsWithTSpectrum_Sequential_Improved.C │
│ │ Automated peak finding on PC wire spectra │
│ │ done separately for anodes and cathodes then combined │
│ │ (TSpectrum-based; writes centroids for gain match) │
│ └──QQQ_Calcheck.C │
│ Verifies QQQ calibration against known sources │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 5. VERTEX RECONSTRUCTION (MakeVertex) │
│ Macro : MakeVertex.C (ROOT TSelector) │
│ Batch : run_sx3.sh (choose dataset, wireflip and offset) │
│ Input : Run_NNN_mapped.root │
│ Output: result_runNNN.root (calibrated TTree │
│ + diagnostic histograms) │
│ Applies energy calibrations, reconstructs PC hit positions │
│ (anode energy + cathode charge division), clusters wires, │
│ and builds SX3/QQQ/PC event objects. │
│ 3D track-reconstruction diagnostics using │
│ PC wire geometry and Si hit positions. │
└─────────────────────────────────────────────────────────────────┘
```
---
## Channel Mapping (`mapping.h` / `mapping_alpha.h`)
The Mapper binary reads the raw TTree (digitizer serial number + channel) and rewrites it with logical detector labels. `mapping.h` is used for standard beam runs; `mapping_alpha.h` for source/alpha calibration runs where the electronics configuration differs. The active mapping is selected by editing the `#include` at the top of `Armory/Mapper.cpp` before building.
`PrintMapping()` (defined in the mapping header) prints the full channel table at runtime for verification.
---
## MakeVertex — Vertex Reconstruction
`MakeVertex.C` is a ROOT `TSelector` that loops over the analyzed event tree and fills diagnostic and physics histograms. It loads energy-loss lookup tables from `eloss_calculations/` and uses `Armory/Kinematics.h` for two-body reaction kinematics.
### Diagnostic Section Toggles
Comment out any `#define` at the top of `MakeVertex.C` to skip that block at compile time:
```cpp
#define DIAG_WIREMULT // anode/cathode cluster multiplicity plots
#define DIAG_1WIRE // raw per-wire dE vs Si (no PC required)
#define DIAG_PC_SX3 // PC-SX3 coincidence analysis
#define DIAG_1A0C_SX3 // 1A0C single-wire vertex with SX3
#define DIAG_1A0C_QQQ // 1A0C single-wire vertex with QQQ
#define DIAG_NA0C_SX3 // nA0C (n>=2) pseudo-wire vertex with SX3
#define DIAG_NA0C_QQQ // nA0C (n>=2) pseudo-wire vertex with QQQ
#define DIAG_PC_QQQ // PC-QQQ coincidence analysis
```
### Histogram Output Folders
| ROOT Folder | Content |
|-------------|---------|
| `wiremult` | Anode/cathode cluster size and multiplicity matrices |
| `1wire` | Raw single-wire dE vs SX3/QQQ energy and phi correlations |
| `hTiming` | PC-Si timing spectra |
| `hPCZSX3` | PC-Z reconstruction with SX3 coincidence |
| `hPCzQQQ` | PC-Z projection with QQQ coincidence |
| `1A0C`,`2A0C`, `3A0C`, … | n anodes (n≥1), 0 cathode: pseudo-wire vertex + excitation spectra |
| `ainterp_noc` | Pseudo-wire interpolation diagnostics (no cathode gate) |
| `phicut` | PC-QQQ with phi coincidence gate |
### Vertex Reconstruction Methods
**nA0C (n ≥ 1 anodes, `DIAG_NA0C_SX3/QQQ`):** All anode clusters are flattened and passed to `GetPseudoWire()` to produce an energy-weighted average wire. `getClosestWirePosAtWirePhi()` then finds the point on that pseudo-wire at the Si phi. Histograms land in a folder named `{n}A0C` (e.g. `2A0C`).
---
## Energy Loss (`eloss_calculations/`)
Lookup tables pre-computed with [pycatima](https://github.com/hrosiak/pycatima) for the isobutane/helium fill gas at 250 Torr. Tables cover protons, alphas, aluminum beam, fluorine, and oxygen. `Eloss.py` regenerates the tables; `make_eloss_table.C` is a ROOT-based alternative generator.
The `cm_to_MeV` / `MeV_to_cm` TGraph pairs loaded in `MakeVertex.C` provide fast range–energy conversion for correcting energy loss in the Si detectors.
---
## Geometry Utilities
| File | Purpose |
|------|---------|
| `grid_generate.py` | Generates `detector_geometry.dat` — calculates SX3, QQQ, and PC wire azimuthal angles from first principles given detector radii and strip positions |
| `shadowplay.py` | Exports ANASEN geometry (wires, SX3, QQQ) as a VTK file + `anasen_labels.csv` for visualisation in ParaView |
| `detector_geometry.dat` | Pre-generated geometry reference (SX3 strip angles, wire positions) |
| `anasen_labels.csv` | Label table for ParaView geometry display |
---
## FEM Simulations (`anasen_fem/`)
Finite-element simulations of the PC electric field using gmsh (meshing), Elmer (FEM solver), and ParaView (visualisation). See `anasen_fem/README.md` for full setup and installation instructions.
```bash
cd anasen_fem
python3 run.py # mesh → FEM solve → field extraction → ParaView output
```
---
## Batch Processing Scripts
| Script | Purpose |
|--------|---------|
| `buildEvents.sh <run> <tw>` | Build events from raw .fsu for a single run |
| `ProcessRun.sh <run> <tw> <opt>` | Full single-run pipeline: event build (opt=0) or analysis (opt=1) |
| `BatchProcess.sh` | Parallel batch processing over a run range via GNU parallel |
| `process_mapped_run.sh <start> <end>` | Run `TrackRecon.C` in parallel over a range of mapped files |
| `run_sx3.sh/run_27Al.sh/run_17F.sh` | Run `VertexRecon.C` in parallel over a range of mapped files and hAdds them for analysis|
---
## Dependencies
- [ROOT](https://root.cern) ≥ 6.x (TSelector, TChain, TVector3, TF1, TGraph, TSpectrum)
- [GNU parallel](https://www.gnu.org/software/parallel/) — for batch scripts
- [pycatima](https://github.com/hrosiak/pycatima) — for regenerating energy-loss tables
- gmsh + Elmer + ParaView — for FEM simulations only
---
## Key Files Reference
| File | Purpose |
|------|---------|
| `mapping.h` / `mapping_alpha.h` | Hardware→logical channel mapping (beam / alpha-source configs) |
| `Armory/EventBuilder.cpp` | Time-window coincidence event builder (compiled binary) |
| `Armory/Mapper.cpp` | Channel remapper: SN+ch → detector ID (compiled binary) |
| `PreAnalysis.C / .h` | Raw rate and energy checks on mapped data |
| `sx3cal/EXFit.C`, `EXFit2.C` | SX3 front/back gain coefficient extraction |
| `sx3cal/LRFit.C` | SX3 left-right position calibration |
| `sx3cal/{17F,27Al}/` | Per-experiment gain coefficient files (frontgains, backgains, rightgains) |
| `GainMatchQQQ.C` | QQQ gain matching |
| `Calibration.C / .h` | Absolute energy calibration |
| `QQQ_Calcheck.C` | QQQ calibration verification |
| `PCGainMatch.C` | PC anode/cathode gain matching |
| `FitHistogramsWithTSpectrum_Sequential_Improved.C` | Automated PC anode peak fitting |
| `Analyzer.C / .h` | Event-level clustering and PC hit reconstruction, (older in need of rehaul)|
| `Analysis.C` | TChain wrapper to run Analyzer over a run range |
| `processRun.C` | Single-file wrapper to run Analyzer on one ROOT file |
| `MakeVertex.C / .h` | Vertex reconstruction and physics histogram production |
| `TrackRecon.C / .h` | Alternate to VertexRecon, to track changes through code, eventaully plan to clean up one for actual data analysis |
| `RunTimeSummary.C` | Run-by-run timing/rate summary across a run range |
| `Armory/ClassPW.h` | PC wire geometry: crossovers, pseudo-wire, track-plane intersection |
| `Armory/Kinematics.h/.cpp` | Two-body reaction kinematics (excitation energy, Q-value) |
| `Armory/HistPlotter.h` | Histogram management with named ROOT folders |
| `Armory/SX3Geom.h` | SX3 detector geometry |
| `Armory/PC_StepLadder_Correction.h` | PC Z-position slope correction |
| `eloss_calculations/Eloss.py` | Energy-loss table generator (pycatima) |
| `grid_generate.py` | Generates `detector_geometry.dat` from first principles |
| `shadowplay.py` | Exports ANASEN geometry to VTK + CSV for ParaView |
| `detector_geometry.dat` | Pre-generated detector geometry reference |
| `anasen_labels.csv` | Label table for ParaView geometry visualisation |
| `anasen_fem/` | FEM electric field simulations for the PC |

140
RunTimeSummary.C Normal file
View File

@ -0,0 +1,140 @@
#include <TFile.h>
#include <TH1.h>
#include <TH2.h>
#include <TString.h>
#include <TSystem.h>
#include <TCanvas.h>
#include <iostream>
void RunTimeSummary(int startRun, int endRun)
{
TString fileDir = "/mnt/d/Remapped_files/17F_data/root_data/";
TString histName = "AnodeQQQ_Time";
TString filePattern = "Run_%03d_mapped_histograms.root";
TString filePatternAlt = "ProtonRun_%d_mapped_histograms.root";
TString filePatternAlt2 = "Source_%d_mapped_histograms.root";
int nBinsTime = 0;
double timeMin = 0, timeMax = 0;
bool foundRef = false;
for (int r = startRun; r <= endRun; r++)
{
TString tempName;
// 1. Try Pattern 1: Run_XXX...
tempName = fileDir + Form(filePattern, r);
if (gSystem->AccessPathName(tempName))
{ // Returns true if MISSING
// 2. Try Pattern 2: ProtonRun_X...
tempName = fileDir + Form(filePatternAlt, r);
if (gSystem->AccessPathName(tempName))
{
// 3. Try Pattern 3: Source_X...
tempName = fileDir + Form(filePatternAlt2, r);
if (gSystem->AccessPathName(tempName))
{
// All 3 patterns failed. Skip this run.
continue;
}
}
}
// If we get here, 'tempName' holds the valid filename that was found
TFile *fTemp = TFile::Open(tempName);
if (!fTemp || fTemp->IsZombie())
{
if (fTemp)
delete fTemp;
continue;
}
TH1F *hRef = (TH1F *)fTemp->Get(histName);
if (hRef)
{
nBinsTime = hRef->GetNbinsX();
timeMin = hRef->GetXaxis()->GetXmin();
timeMax = hRef->GetXaxis()->GetXmax();
foundRef = true;
delete hRef;
fTemp->Close();
delete fTemp;
printf("Reference found in Run %d: %d bins, Range [%.1f, %.1f]\n", r, nBinsTime, timeMin, timeMax);
break;
}
fTemp->Close();
delete fTemp;
}
if (!foundRef)
{
printf("Error: No valid histograms found in the entire range. Exiting.\n");
return;
}
int nRuns = endRun - startRun + 1;
TH2F *hSummary = new TH2F("hSummary",
Form("Timing Summary (Runs %d-%d);Timing;Run Number", startRun, endRun),
nBinsTime, timeMin, timeMax,
nRuns, startRun, endRun + 1);
for (int run = startRun; run <= endRun; run++)
{
TString filename = fileDir + Form(filePattern, run);
if (gSystem->AccessPathName(filename))
continue;
TFile *fin = TFile::Open(filename);
if (!fin || fin->IsZombie())
{
if (fin)
delete fin;
continue;
}
TH1F *hin = (TH1F *)fin->Get(histName);
if (hin)
{
// Determine which ROW (Y-bin) corresponds to this Run
// Note: ROOT bins start at 1.
// If startRun=10 and run=10 -> binY=1.
int binY_Run = run - startRun + 1;
// Loop through the Time bins (X-bins in the 1D hist)
for (int binX_Time = 1; binX_Time <= hin->GetNbinsX(); binX_Time++)
{
double content = hin->GetBinContent(binX_Time);
// Copy content to: (Time, Run)
if (content > 0)
{
hSummary->SetBinContent(binX_Time, binY_Run, content);
}
}
delete hin;
}
fin->Close();
delete fin;
if ((run - startRun) % 10 == 0)
printf("Stitched Run %d...\n", run);
}
TFile *fOut = new TFile("SummaryPlot.root", "RECREATE");
hSummary->Write();
TCanvas *c1 = new TCanvas("c1", "Time Summary Plot", 1000, 800);
hSummary->SetStats(0);
hSummary->Draw("COLZ");
printf("Done! Saved to SummaryPlot.root\n");
}

File diff suppressed because it is too large Load Diff

View File

@ -5,110 +5,137 @@
#include <TChain.h>
#include <TFile.h>
#include <TSelector.h>
#include <vector>
#include <utility>
#include <unordered_map>
#include <tuple>
#include "Armory/ClassDet.h"
#include "Armory/ClassPW.h"
class TrackRecon : public TSelector {
public :
TTree *fChain; //!pointer to the analyzed TTree or TChain
// Fixed size dimensions of array or collections stored in the TTree if any.
class TrackRecon : public TSelector
{
public:
TTree *fChain; //! pointer to the analyzed TTree or TChain
// Declaration of leaf types
Det sx3;
Det qqq;
Det pc ;
Det pc;
Det misc;
ULong64_t evID;
UInt_t run;
ULong64_t evID;
UInt_t run;
// List of branches
TBranch *b_eventID; //!
TBranch *b_run; //!
TBranch *b_sx3Multi; //!
TBranch *b_sx3ID; //!
TBranch *b_sx3Ch; //!
TBranch *b_sx3E; //!
TBranch *b_sx3T; //!
TBranch *b_qqqMulti; //!
TBranch *b_qqqID; //!
TBranch *b_qqqCh; //!
TBranch *b_qqqE; //!
TBranch *b_qqqT; //!
TBranch *b_pcMulti; //!
TBranch *b_pcID; //!
TBranch *b_pcCh; //!
TBranch *b_pcE; //!
TBranch *b_pcT; //!
TBranch *b_eventID; //!
TBranch *b_run; //!
TBranch *b_sx3Multi; //!
TBranch *b_sx3ID; //!
TBranch *b_sx3Ch; //!
TBranch *b_sx3E; //!
TBranch *b_sx3T; //!
TBranch *b_qqqMulti; //!
TBranch *b_qqqID; //!
TBranch *b_qqqCh; //!
TBranch *b_qqqE; //!
TBranch *b_qqqT; //!
TBranch *b_pcMulti; //!
TBranch *b_pcID; //!
TBranch *b_pcCh; //!
TBranch *b_pcE; //!
TBranch *b_pcT; //!
TBranch *b_miscMulti; //!
TBranch *b_miscID; //!
TBranch *b_miscCh; //!
TBranch *b_miscE; //!
TBranch *b_miscT; //!
TBranch *b_miscTf; //!
TrackRecon(TTree * /*tree*/ =0) : fChain(0) { }
virtual ~TrackRecon() { }
virtual Int_t Version() const { return 2; }
virtual void Begin(TTree *tree);
virtual void SlaveBegin(TTree *tree);
virtual void Init(TTree *tree);
virtual Bool_t Notify();
virtual Bool_t Process(Long64_t entry);
virtual Int_t GetEntry(Long64_t entry, Int_t getall = 0) { return fChain ? fChain->GetTree()->GetEntry(entry, getall) : 0; }
virtual void SetOption(const char *option) { fOption = option; }
virtual void SetObject(TObject *obj) { fObject = obj; }
virtual void SetInputList(TList *input) { fInput = input; }
virtual TList *GetOutputList() const { return fOutput; }
virtual void SlaveTerminate();
virtual void Terminate();
// 1. Geometry Cache
Coord Crossover[24][24][2];
ClassDef(TrackRecon,0);
// 2. Persistent Vectors (REQUIRED for the optimized .cxx to work)
std::vector<std::pair<int, double>> anodeHits;
std::vector<std::pair<int, double>> cathodeHits;
std::vector<std::pair<int, double>> corrcatMax;
std::vector<std::pair<int, double>> corranoMax;
std::vector<double> cathodeTimes;
std::vector<double> anodeTimes;
std::unordered_map<int, std::tuple<int, double, double>> aWireEvents, cWireEvents;
TrackRecon(TTree * /*tree*/ = 0) : fChain(0) {}
virtual ~TrackRecon() {}
virtual Int_t Version() const { return 2; }
virtual void Begin(TTree *tree);
virtual void SlaveBegin(TTree *tree);
virtual void Init(TTree *tree);
virtual Bool_t Notify();
virtual Bool_t Process(Long64_t entry);
virtual Int_t GetEntry(Long64_t entry, Int_t getall = 0) { return fChain ? fChain->GetTree()->GetEntry(entry, getall) : 0; }
virtual void SetOption(const char *option) { fOption = option; }
virtual void SetObject(TObject *obj) { fObject = obj; }
virtual void SetInputList(TList *input) { fInput = input; }
virtual TList *GetOutputList() const { return fOutput; }
virtual void SlaveTerminate();
virtual void Terminate();
void OldAnalysis();
ClassDef(TrackRecon, 0);
};
#endif
#ifdef TrackRecon_cxx
void TrackRecon::Init(TTree *tree){
void TrackRecon::Init(TTree *tree)
{
// Set branch addresses and branch pointers
if (!tree) return;
if (!tree)
return;
fChain = tree;
fChain->SetMakeClass(1);
fChain->SetBranchAddress("evID", &evID, &b_eventID);
fChain->SetBranchAddress("run", &run, &b_run);
sx3.SetDetDimension(24,12);
qqq.SetDetDimension(4,32);
pc.SetDetDimension(2,24);
sx3.SetDetDimension(24, 12);
qqq.SetDetDimension(4, 32);
pc.SetDetDimension(2, 24);
fChain->SetBranchAddress("sx3Multi", &sx3.multi, &b_sx3Multi);
fChain->SetBranchAddress("sx3ID", &sx3.id, &b_sx3ID);
fChain->SetBranchAddress("sx3Ch", &sx3.ch, &b_sx3Ch);
fChain->SetBranchAddress("sx3E", &sx3.e, &b_sx3E);
fChain->SetBranchAddress("sx3T", &sx3.t, &b_sx3T);
fChain->SetBranchAddress("sx3ID", &sx3.id, &b_sx3ID);
fChain->SetBranchAddress("sx3Ch", &sx3.ch, &b_sx3Ch);
fChain->SetBranchAddress("sx3E", &sx3.e, &b_sx3E);
fChain->SetBranchAddress("sx3T", &sx3.t, &b_sx3T);
fChain->SetBranchAddress("qqqMulti", &qqq.multi, &b_qqqMulti);
fChain->SetBranchAddress("qqqID", &qqq.id, &b_qqqID);
fChain->SetBranchAddress("qqqCh", &qqq.ch, &b_qqqCh);
fChain->SetBranchAddress("qqqE", &qqq.e, &b_qqqE);
fChain->SetBranchAddress("qqqT", &qqq.t, &b_qqqT);
fChain->SetBranchAddress("pcMulti", &pc.multi, &b_pcMulti);
fChain->SetBranchAddress("pcID", &pc.id, &b_pcID);
fChain->SetBranchAddress("pcCh", &pc.ch, &b_pcCh);
fChain->SetBranchAddress("pcE", &pc.e, &b_pcE);
fChain->SetBranchAddress("pcT", &pc.t, &b_pcT);
fChain->SetBranchAddress("qqqID", &qqq.id, &b_qqqID);
fChain->SetBranchAddress("qqqCh", &qqq.ch, &b_qqqCh);
fChain->SetBranchAddress("qqqE", &qqq.e, &b_qqqE);
fChain->SetBranchAddress("qqqT", &qqq.t, &b_qqqT);
fChain->SetBranchAddress("pcMulti", &pc.multi, &b_pcMulti);
fChain->SetBranchAddress("pcID", &pc.id, &b_pcID);
fChain->SetBranchAddress("pcCh", &pc.ch, &b_pcCh);
fChain->SetBranchAddress("pcE", &pc.e, &b_pcE);
fChain->SetBranchAddress("pcT", &pc.t, &b_pcT);
fChain->SetBranchAddress("miscMulti", &misc.multi, &b_miscMulti);
fChain->SetBranchAddress("miscID", &misc.id, &b_miscID);
fChain->SetBranchAddress("miscCh", &misc.ch, &b_miscCh);
fChain->SetBranchAddress("miscE", &misc.e, &b_miscE);
fChain->SetBranchAddress("miscT", &misc.t, &b_miscT);
fChain->SetBranchAddress("miscF", &misc.tf, &b_miscTf);
}
Bool_t TrackRecon::Notify(){
Bool_t TrackRecon::Notify()
{
return kTRUE;
}
void TrackRecon::SlaveBegin(TTree * /*tree*/){
TString option = GetOption();
void TrackRecon::SlaveBegin(TTree * /*tree*/)
{
// TString option = GetOption();
}
void TrackRecon::SlaveTerminate(){
void TrackRecon::SlaveTerminate()
{
}
#endif // #ifdef TrackRecon_cxx
#endif // #ifdef TrackRecon_cxx

187
anasen_fem/1dpotplot.py Executable file
View File

@ -0,0 +1,187 @@
#!/home/vsitaraman/ParaView-6.1.0-RC1-MPI-Linux-Python3.12-x86_64/bin/pvbatch
import sys
import numpy as np
from paraview.simple import *
import paraview.servermanager as sm
from paraview.vtk.numpy_interface import dataset_adapter as dsa
# ==========================================
# COMMAND LINE ARGUMENTS
# ==========================================
if len(sys.argv) < 2:
print("Usage: pvbatch 1dpotplot.py <z_locus in mm>")
sys.exit(1)
z_loc = float(sys.argv[1])
INPUT_FILE = f"wires2d/elfield_anasen_t0001.vtu"
OUTPUT_CSV = f"anode_to_cathode_1d_{z_loc}.csv"
OUTPUT_IMAGE = f"1D_Variation_Plot_{z_loc}.png"
print(f"--- Starting 1D Potential Extraction for Z = {z_loc} mm ---")
# ==========================================
# 1. EXACT GEOMETRIC CALCULATION
# ==========================================
print("Calculating theoretical wire coordinates...")
wireShift = 4.0
k = 2 * np.pi / 24.0
offset_a1 = -10.0 * k
offset_c1 = -5.5 * k
offset_a2 = offset_a1 + (wireShift * k)
offset_c2 = offset_c1 - (wireShift * k)
t = (z_loc + 174.3) / (2 * 174.3)
# Anode 0 theoretical position
xa_loc = (37.0 * np.cos(offset_a1)) + t * (37.0 * np.cos(offset_a2) - 37.0 * np.cos(offset_a1))
ya_loc = (37.0 * np.sin(offset_a1)) + t * (37.0 * np.sin(offset_a2) - 37.0 * np.sin(offset_a1))
# All Cathodes theoretical positions
xc1_all = np.array([42.0 * np.cos(k * i + offset_c1) for i in range(24)])
yc1_all = np.array([42.0 * np.sin(k * i + offset_c1) for i in range(24)])
xc2_all = np.array([42.0 * np.cos(k * i + offset_c2) for i in range(24)])
yc2_all = np.array([42.0 * np.sin(k * i + offset_c2) for i in range(24)])
xc_loc_all = xc1_all + t * (xc2_all - xc1_all)
yc_loc_all = yc1_all + t * (yc2_all - yc1_all)
# Nearest Cathode theoretical position
distances = np.sqrt((xc_loc_all - xa_loc)**2 + (yc_loc_all - ya_loc)**2)
nearest_c_idx = np.argmin(distances)
xc_loc = xc_loc_all[nearest_c_idx]
yc_loc = yc_loc_all[nearest_c_idx]
# Theoretical targets in meters
math_anode = np.array([xa_loc / 1000.0, ya_loc / 1000.0, 0.0])
math_cathode = np.array([xc_loc / 1000.0, yc_loc / 1000.0, 0.0])
# ==========================================
# 2. MESH DATA SNAPPING (Electrode-Constrained)
# ==========================================
print(f"Loading VTU file: {INPUT_FILE} to snap to electrode nodes...")
reader = XMLUnstructuredGridReader(FileName=[INPUT_FILE])
UpdatePipeline()
# Fetch data to Python
raw_data = sm.Fetch(reader)
wrapped_data = dsa.WrapDataObject(raw_data)
pts = wrapped_data.Points
pot = wrapped_data.PointData['potential']
print("\nPotential range:")
print(" min =", np.min(pot))
print(" max =", np.max(pot))
# -------------------------------------------------
# Find actual electrode nodes
# -------------------------------------------------
anode_candidates = np.where(pot > 640.0)[0]
if len(anode_candidates) == 0:
raise RuntimeError("No anode nodes found near 650 V")
cathode_candidates = np.where(np.abs(pot) < 1.0)[0]
if len(cathode_candidates) == 0:
raise RuntimeError("No cathode nodes found near 0 V")
print(f"Found {len(anode_candidates)} anode nodes")
print(f"Found {len(cathode_candidates)} cathode nodes")
# -------------------------------------------------
# Snap theoretical anode location to nearest
# actual anode node
# -------------------------------------------------
anode_dist = np.linalg.norm(
pts[anode_candidates] - math_anode,
axis=1
)
anode_idx = anode_candidates[np.argmin(anode_dist)]
true_anode_pos = pts[anode_idx]
true_anode_pot = pot[anode_idx]
# -------------------------------------------------
# Snap theoretical cathode location to nearest
# actual cathode node
# -------------------------------------------------
cathode_dist = np.linalg.norm(
pts[cathode_candidates] - math_cathode,
axis=1
)
cathode_idx = cathode_candidates[np.argmin(cathode_dist)]
true_cathode_pos = pts[cathode_idx]
true_cathode_pot = pot[cathode_idx]
# -------------------------------------------------
# Diagnostics
# -------------------------------------------------
anode_snap_error = np.linalg.norm(
true_anode_pos - math_anode
)
cathode_snap_error = np.linalg.norm(
true_cathode_pos - math_cathode
)
print("\nSelected electrode nodes:")
print(f"Anode:")
print(f" position = {true_anode_pos}")
print(f" potential = {true_anode_pot:.3f} V")
print(f" distance = {anode_snap_error*1e6:.1f} um")
print(f"\nCathode:")
print(f" position = {true_cathode_pos}")
print(f" potential = {true_cathode_pot:.3f} V")
print(f" distance = {cathode_snap_error*1e6:.1f} um")
# ==========================================
# 3. PARAVIEW EXTRACTION & PLOTTING
# ==========================================
print("Extracting data along the snapped line...")
plot_over_line = PlotOverLine(Input=reader)
# Apply the SNAPPED coordinates (converted to standard Python lists)
plot_over_line.Point1 = true_anode_pos.tolist()
plot_over_line.Point2 = true_cathode_pos.tolist()
plot_over_line.Resolution = 1000
SaveData(OUTPUT_CSV, proxy=plot_over_line)
print("Rendering plot...")
line_chart_view = CreateView('XYChartView')
line_chart_view.ViewSize = [1000, 600]
line_chart_view.ChartTitle = f'Potential & E-Field: Anode to Cathode (Z = {z_loc} mm)'
line_chart_view.LeftAxisTitle = 'Potential (V)'
line_chart_view.BottomAxisTitle = 'Distance from Anode (m)'
line_display = Show(plot_over_line, line_chart_view)
# Tell ParaView to draw BOTH variables
# line_display.SeriesVisibility = ['potential', 'electric field_Magnitude']
line_display.SeriesVisibility = ['potential']
# Style Potential (Red)
line_display.SeriesColor = ['potential', '1.0', '0.0', '0.0']
line_display.SeriesLineThickness = ['potential', '3']
# # Style Electric Field (Blue, Right Axis)
# line_display.SeriesColor = ['electric field_Magnitude', '0.0', '0.5', '1.0']
# line_display.SeriesLineThickness = ['electric field_Magnitude', '3']
# line_display.SeriesPlotCorner = ['electric field_Magnitude', '1']
# line_chart_view.RightAxisTitle = 'Electric Field (V/m)'
SaveScreenshot(OUTPUT_IMAGE, view=line_chart_view)
print(f"Saved plot screenshot to: {OUTPUT_IMAGE}")
print("--- Extraction Complete ---")

46
anasen_fem/README.md Executable file
View File

@ -0,0 +1,46 @@
### README for ANASEN fem simulations:
* There are a few iterations of these simulations that already exist. Be sure to also locate and refer to them if necessary.
* Install gmsh and its python api by running (Ubuntu 22.04 LTS)
```
sudo apt install gmsh python3-gmsh
```
* Gmsh gives us the tools to create a meshgrid that samples the 2d space appropriately to plot the field/equipotential lines.
* The output file typically has the .msh extension. This is read as input to Elmer, which is the FEM differential-equation solver.
* Install Elmer via the following steps:
```
sudo add-apt-repository ppa:elmer-csc-ubuntu/elmer-csc-ppa
sudo apt install elmerfem-csc-eg
```
* Install ParaView for visualizations by downloading from the Linux .tar.gz link at https://www.paraview.org/download/
- The current version is tested to work on Paraview 6.1.0. The default version in Ubuntu 22.04 repositories has some trouble with scripting
* v0.0.1, March 10 2026
- 2d simulations of fields only. gmsh for meshing, elmer for fem, paraview to plot
- Before running, open `paraview_plotter.py` to make the bash shebang (#!) point to the location of `pvpython` or `pvbatch`
- `python3 run.py` should run everything in order, and is hopefully all the files are self-documenting
* v0.0.2, September 2026
- Adds the Ramo weighting field and the `E . E_w` dot product for one cathode wire.
- `wires_gmsh2d_bc.py` now cuts the wires into the gas disk with `occ.fragment` instead of `mesh.embed`, so each wire boundary is a real edge of the domain. One cathode (optional 2nd argument, default 1) gets its own physical group, **tag 40**; the other 23 stay in tag 30.
- Two solves, same mesh, identical apart from the potentials:
| file | tag 40 | output |
|---|---|---|
| `wires2d.sif` | 0 V, like every other cathode | `wires2d/elfield_anasen_t0001.vtu` |
| `wires2d_weight.sif` | 1 V, all else 0 V | `wires2d/elfield_weight_t0001.vtu` |
- `dotproduct.py` reads both and writes `wires2d/dotproduct.vtu` with `DotProduct = E . E_w` (V/m^2). It aborts if the node sets differ or if either field is all zeros — both are silent failures otherwise.
- `paraview_dotproduct.py` renders it. `DOT_MIN`/`DOT_MAX` set the colour range and decide whether the picture shows anything.
- `paraview_plotter.py` gains two views: the equipotentials over one quadrant, and streamlines over the same quadrant. Streamlines are drift paths up to diffusion, so they show which wire collects charge from where; `SEED_POTENTIAL` and `SEED_STRIDE` control them.
- Six PNGs per z-locus, archived as before (`<Stem>_z_<count>_<z>[_quarter].png`). `Field_ouput` keeps its typo so the new files sort with the dozen already in `png/`.
- `STAGE` in `run.py`: `1` = mesh + physical solve, `2` = weighting solve + dot product reusing stage 1's mesh, `0` = both. Stage 2 never re-meshes, so it applies to whichever z stage 1 ran last.
- **Do not pass `-autoclean` to ElmerGrid.** It renumbers the physical groups (13/10/20/30/40 become 1/4/5/6/7), so every `Target Bodies`/`Target Boundaries` in the sifs matches nothing, Elmer solves nothing, and the potential comes out identically zero with no error.
- `mesh.recombine()` and `mesh.refine()` are both off: recombination ran over half an hour on the barrel surface without finishing, and refine took the mesh to ~16M nodes.
- Note on the solver stack: both sifs are the same file bar the potentials, so whatever `FluxSolver` does to `Electric Field` it does equally to both and the dot product stays consistent.
* v0.0.3, planned TODO
- Garfield to take Elmer results and perform charge-transport
- Sweep the weighting solve over all 24 cathodes rather than one at a time

View File

@ -0,0 +1,42 @@
# Charge-fraction prediction, 2026-10-28
Four slices solved for the defense figure `defense/figures/cfrac_pred.png`,
archived here beside the solves that were already in `wires2d/vtu_files`.
| index | z (mm) | phase in the cell | relative azimuth | anode-cathode gap |
|---|---|---|---|---|
| 10 | 0.0000 | midpoint | 7.500 deg | 6.220 mm |
| 10 | 19.6900 | crossover | 0.037 deg | 4.339 mm |
| 11 | 19.7940 | crossover | 0.003 deg | 4.339 mm |
| 12 | 29.8500 | quarter | 3.794 deg | 4.908 mm |
| 13 | 39.9200 | midpoint, next cell | 7.435 deg | 6.247 mm |
| 14 | 44.7820 | three eighths | 5.625 deg | 5.535 mm |
| 15 | 49.9800 | three quarters | 3.699 deg | 4.925 mm |
Indices 10 and 11 were already here; 12 to 15 were added by this study.
Each index has an `elfield_anasen_*` (physical solve, anodes at 660 V) and an
`elfield_weight_*` (Ramo weighting solve, cathode 1 at 1 V) on the same mesh.
The archived 10 and 11 ran at 650 V, the new ones at 660; only ratios are
used, and the extraction reads the electrode potential off the wire interior
rather than assuming it.
`dotproduct.py` was not run on the new slices: it writes another ~1 GB per
slice and only the anode surfaces are needed. `probe_fields.py` in the code
below computes the same product straight from the two field VTUs.
## Do not take E.E_w from the nodal "electric field" array
`FluxSolver` recovers the gradient element by element and a degenerate element
puts a spike on a node. The meshes for 12 to 15 carry ~960 nodes above
1e6 V/m where meshes 10 and 11 have none, so the extremum over a wire's
surface nodes lands on a spike: a re-solve of z = 19.794 returned -1.526e12
where the archived value is -2.569e8. Take it from the potential instead, as
`surface_field.py` does. That is stable to five figures across meshes.
## The code
`scratch/cfrac_fem_pred_2026-10-28/` in the analysis repository
(github.com/vs19g/ANASEN-Analysis_private, branch devel_vignesh). `run_all.sh`
reruns everything; `collect_slices.py` and `map_function.py` read the VTUs
here. The CSVs in this directory are their output, so the numbers can be read
without re-solving.

View File

@ -0,0 +1,28 @@
# Benchmark_SX3_ref/Benchmark_SX3_A1C1_cfrac_vs_sx3pczguess, alpha-source runs 12 and 18-21, 9850 entries
# main band only: c/(a+c) > 0.25 ; slice width 4.0 mm
z_mm,counts,median_cfrac,c_over_a
-42.00,271,0.41183,0.70018
-38.00,407,0.42125,0.72786
-34.00,415,0.44240,0.79339
-30.00,281,0.47118,0.89102
-26.00,94,0.50500,1.02020
-22.00,24,0.52333,1.09790
-14.00,263,0.50393,1.01584
-10.00,234,0.48917,0.95759
-6.00,263,0.42490,0.73883
-2.00,220,0.41763,0.71713
2.00,121,0.43272,0.76279
6.00,191,0.45483,0.83430
10.00,627,0.45713,0.84207
14.00,494,0.47207,0.89417
18.00,417,0.47070,0.88930
22.00,358,0.47882,0.91874
26.00,322,0.44903,0.81499
30.00,411,0.43383,0.76625
34.00,676,0.43226,0.76138
38.00,665,0.41607,0.71252
42.00,549,0.40439,0.67895
46.00,473,0.41838,0.71933
50.00,232,0.43873,0.78166
54.00,128,0.45327,0.82905
58.00,100,0.45320,0.82882
1 # Benchmark_SX3_ref/Benchmark_SX3_A1C1_cfrac_vs_sx3pczguess, alpha-source runs 12 and 18-21, 9850 entries
2 # main band only: c/(a+c) > 0.25 ; slice width 4.0 mm
3 z_mm,counts,median_cfrac,c_over_a
4 -42.00,271,0.41183,0.70018
5 -38.00,407,0.42125,0.72786
6 -34.00,415,0.44240,0.79339
7 -30.00,281,0.47118,0.89102
8 -26.00,94,0.50500,1.02020
9 -22.00,24,0.52333,1.09790
10 -14.00,263,0.50393,1.01584
11 -10.00,234,0.48917,0.95759
12 -6.00,263,0.42490,0.73883
13 -2.00,220,0.41763,0.71713
14 2.00,121,0.43272,0.76279
15 6.00,191,0.45483,0.83430
16 10.00,627,0.45713,0.84207
17 14.00,494,0.47207,0.89417
18 18.00,417,0.47070,0.88930
19 22.00,358,0.47882,0.91874
20 26.00,322,0.44903,0.81499
21 30.00,411,0.43383,0.76625
22 34.00,676,0.43226,0.76138
23 38.00,665,0.41607,0.71252
24 42.00,549,0.40439,0.67895
25 46.00,473,0.41838,0.71933
26 50.00,232,0.43873,0.78166
27 54.00,128,0.45327,0.82905
28 58.00,100,0.45320,0.82882

View File

@ -0,0 +1,228 @@
# How the predicted points on `cfrac_pred.png` are generated
2026-10-28. `defense/figures/cfrac_pred.png` is `cfrac.png` with open red
markers added: the single-cathode fraction predicted from the weighting-field
solves, over the measured A1C1 charge fraction (alpha-source runs 12 and
18--21, 9850 events). `cfrac.png` itself is unchanged.
## 1. The quantity
Shockley--Ramo gives the current induced on an electrode by an ion drifting at
$v = \mu E$ as $i = -q\mu\,(E\cdot E_w)$. The 3 us shaping and the 650 ns
coincidence keep only the first part of the ion drift, so what the readout
sees is set by $E\cdot E_w$ where the ions start, on the surface of the anode
that avalanched. This is the quantity on slide 21.
$E$ comes from the physical solve, anodes biased and everything else grounded.
$E_w$ comes from the Ramo weighting solve, one cathode at 1 V and everything
else grounded, on the same mesh. Both are the Gmsh/Elmer solves in
`anasen_fem`.
## 2. The slices
The wires are straight chords between the two end planes, so the lattice is a
hyperboloid: the relative azimuth of the anode and cathode lattices is not
linear in z, and the model's crossovers sit at z = 19.787 and 60.051 mm with
the cell midpoint at 39.92 mm. Seven slices were used, two already archived
and five solved for this:
| z (mm) | phase | relative azimuth | gap (mm) | $\lvert E\cdot E_w\rvert$ (V$^2$/m$^2$) |
|---|---|---|---|---|
| 19.794 | crossover | 0.003 deg | 4.339 | 2.9308e8 |
| 19.690 | crossover, a second mesh | 0.037 deg | 4.339 | 2.9308e8 |
| 29.850 | quarter | 3.794 deg | 4.908 | 2.5092e8 |
| 49.980 | three quarters | 3.699 deg | 4.925 | 2.5041e8 |
| 44.782 | three eighths | 5.625 deg | 5.535 | 2.1167e8 |
| 39.920 | midpoint, next cell | 7.500 deg | 6.247 | 1.7489e8 |
| 0.000 | midpoint | 7.500 deg | 6.220 | 1.7375e8 |
The two crossover solves are independent meshes at different z and agree to
five figures. A solve costs about an hour, mostly meshing 10.3 M nodes.
## 3. Reading the field off the surface
**Not** from Elmer's nodal `electric field` array. `FluxSolver` recovers the
gradient element by element, and one degenerate element puts a spike on a
node. Taking the extremum over a wire's surface nodes reproduces slide 21
exactly on the archived meshes, which happen to be clean, but it does not
survive a re-mesh: the new meshes carry around 960 nodes above $10^6$ V/m
where the archived ones have none, and a re-solve of z = 19.794 returned
$-1.5\times10^{12}$ for a configuration whose archived value is
$-2.57\times10^8$.
The potential is unaffected. In the gas annulus outside a wire it is harmonic
and the wire surface is an equipotential at $V_0$, so
$$V(r,\theta) = V_0 + b\ln(r/R) + \sum_m \left(r^m - R^{2m}r^{-m}\right)
\left(c_m\cos m\theta + d_m\sin m\theta\right)$$
is exact, and a least-squares fit of it over the few hundred to few thousand
nodes within $R$ to $R+0.3$ mm gives the normal field at the surface
analytically. Both solves hold the anode at a fixed potential, so at its
surface $E$ and $E_w$ are both normal to it and $E\cdot E_w$ is the product of
the two normal components; the value taken is its extremum over $\theta$,
which falls within 0.1 deg of the direction to the driven cathode.
$V_0$ is read off the wire interior rather than assumed. The interior is
meshed as gas with the wire boundary as a Dirichlet condition, so it solves to
exactly the electrode potential, and the two campaigns differ there: the
archived solves ran at 650 V and the current sifs read 660. Getting $V_0$
wrong by those 10 V leaves an offset the basis cannot represent, since every
basis function vanishes at $r=R$, and the fit absorbs it into the gradient.
With $V_0$ read from the interior the fit residual is 0.003 V and the answer
is stable to five figures over annulus thicknesses of 0.10 to 0.50 mm and over
2 to 12 azimuthal orders. It is also stable across meshes, which the nodal
recipe is not.
The two recipes agree on every ratio, which is all that enters the figure:
crossover/midpoint 1.687 against 1.711, and 4.68 against 4.67 for the
neighbouring anodes at $\pm15$ deg. Slide 21's 1.7x stands either way.
## 4. What function it follows
Each slice has 24 anodes at 15 deg intervals around the driven cathode, so one
solve samples the falloff at 12 separations at once, from 4.3 mm out to the
68 mm of the far side of the barrel. Seven slices give one curve: compared at
the azimuths they share, two meshes agree to five figures at the crossover and
at 15 deg, the quarter and three-quarter agree to 0.2%, and the two midpoints
to 0.05%. $E\cdot E_w$ is a function of the anode--cathode geometry alone,
which is what lets one table of azimuth serve every cell.
The curve is a **Lorentzian in the azimuthal arc**. The wires are infinite, so
this is a two-dimensional problem, and in 2D a line charge at height $h$ above
a grounded plane induces $\sigma(x) = (q/\pi)\,h/(h^2+x^2)$. Fitted over the
cell,
$$\lvert E\cdot E_w\rvert = \frac{D_0}{1 + (s/h)^2},
\qquad s = r_a\lvert\Delta\phi\rvert, \qquad h = 5.102\ \mathrm{mm},$$
with an rms of 0.49% over the nine points inside the cell. The fitted width is
16% above the geometric anode--cathode gap of 4.33 to 4.39 mm, which is what
the discreteness of the cathodes and the curvature of the barrel cost.
The three-eighths slice is a check of this rather than an input to it. Fitted
on the crossover, quarter and midpoint alone the Lorentzian gave $h = 5.100$
mm and put that slice at $2.117\times10^8$; solving it returned
$2.1167\times10^8$, a difference of 0.015%. Normalised at the crossover, over
one half cell:
| relative azimuth | fit | solved |
|---|---|---|
| 0.000 deg | 1.0000 | 1.0000 |
| 1.875 deg | 0.9591 | -- |
| 3.750 deg | 0.8542 | 0.8553 |
| 5.625 deg | 0.7226 | 0.7222 |
| 7.500 deg | 0.5944 | 0.5948 |
The Mathieson form, the standard one for wire chambers, is the
three-dimensional case: a point avalanche on a cathode *plane*. It is not this
geometry. Over one cell it fits as well, because the range is too narrow to
separate the two forms, but its parameters mean nothing here.
Away from the cell the Lorentzian is only local. It holds to 3% out to 11 deg,
and then the real falloff is faster -- 79% of it at 15 deg, 60% at 19 deg,
about a third beyond 30 deg -- because the cathodes are discrete wires that
screen each other rather than a continuous plane.
## 5. Placing the points on the figure
The model is
$$c/a = \lambda\,D(z),\qquad
D(z) = \lvert E\cdot E_w\rvert \ \text{on the fired anode's surface},$$
with one free scale $\lambda$. $D$ is tabulated against relative azimuth and
mapped back onto the settled crossover grid ($\pm19.7248$, $\pm59.8527$ mm),
which is what the red dashed lines in the figure mark; the azimuth runs
linearly from 0 at a crossover to 7.5 deg at a cell midpoint. The figure's
observable is $c/(a+c)$.
The markers are the solved phases. The curve drawn through them is the fitted
Lorentzian of section 4 rather than interpolation between those phases; it
passes through them to better than 0.5% in $c/a$, under a pixel at this scale.
$\lambda$ is fitted at **one point**, the z = +19.7248 mm crossover, which is
the best populated of them with 600 main-band events within $\pm3$ mm. The
overlay therefore tests the shape and the depth of the modulation, not its
level.
The band centre of the data is the median of $c/(a+c)$ over the main band,
$c/(a+c) > 0.25$, in 4 mm slices of z. The low band near 0.1 is a separate
population and is excluded.
## 6. What the comparison says
The predicted crossover/midpoint ratio in $c/a$ is **1.68**. Measured from the
band centre of the figure it is **1.22** against the z = 0 midpoint and
**1.32** against +39.79, and 1.19 to 1.34 over 24 variants of the band cut,
the window width and the statistic. The predicted modulation is about half
again as deep as the measured one.
Two things account for most of that, and both are about the figure's x axis
and its observable rather than about the fields.
**The x axis is not a measured z.** It is the SX3 track angle projected onto
the 37 mm anode radius, so every event is entered at a smeared z, and smearing
damps a modulation whose period is the cell. Fitting the first harmonic of
both curves gives an amplitude of 0.0342 in the data against 0.0525 predicted,
a ratio of 0.65; a Gaussian of $\sigma = 5.9$ mm would account for all of it.
**$c/(a+c)$ is not what the model predicts.** The model predicts $c/a$, and
converting assumes the anode signal does not vary along the cell. It does. On
the A1C2 benchmark sample, where the position comes from the A1C2
reconstruction rather than the projection, the raw anode median runs from
8798 at the cell midpoint to 7350 at the crossover, a spread of 18%. Against
that sharper position the three observables compare as follows, each scaled by
a single factor at the crossover:
| observable | rms against the model | measured midpoint-to-crossover |
|---|---|---|
| raw cathode | 15.1% | 1.13 |
| $c/a$ | 6.7% | 1.37 |
| $c/a$ from $c/(a+c)$ | 6.9% | 1.38 |
| model | -- | 1.63 over the same range |
$c/a$ is the better observable and the one the model actually predicts: the
avalanche gain cancels in the ratio, where it does not in the raw cathode. The
raw cathode varies by only 1.13 against a predicted 1.63, and most of what
modulation $c/a$ does show at the raw level is carried by the anode falling
towards the crossover rather than by the cathode rising.
With the sharper position the measured ratio rises from 1.22--1.32 to 1.37,
against 1.63 for the model folded the same way. The remainder is not accounted
for here. Candidates that have not been separated: the residual 2.57 mm
resolution of the A1C2 position, misassignment of events to the wrong cell
(the output carries a `cellsel_misclass` histogram for exactly this), the
cathode threshold, and the fact that the benchmark sample requires two
cathodes above threshold and so is not the same selection as a true A1C1
event.
## 7. The same thing in $c/a$
`cfrac_pred_ca.png` is the same data and the same model with $c/a$ on the y
axis instead of $c/(a+c)$. It is the more direct comparison: $c/a$ is what the
weighting field predicts, so the curve there is the fitted Lorentzian itself,
undistorted, and nothing has to be assumed about the anode signal. The model
runs from 0.9063 at a crossover to 0.5387 at a midpoint.
That figure is redrawn rather than composited, because $y o y/(1-y)$ is not
linear and the markers could not simply be moved. The margins and font sizes
are the ones measured off `cfrac.png`, which are also those of the macro that
made it, so the two match. The source bins are 0.005 wide in $c/(a+c)$ and
$0.005/(1-y)^2$ wide in $c/a$, so each is spread over the target bins its
edges span in proportion to the overlap; filling at the bin centre instead
leaves gaps that show as horizontal stripes.
## 8. Where everything lives
The solves are archived beside the earlier ones in
`anasen_fem/wires2d/vtu_files` as indices 12 to 15, with the derived tables and
a README in `anasen_fem/cfrac_pred_2026-10-28`.
The code is `scratch/cfrac_fem_pred_2026-10-28/` in the analysis repository.
`run_all.sh` reruns the whole chain. The figure is composited onto a copy of
`cfrac.png` rather than onto a regenerated plot, because no script for that
figure exists in either repository and redrawing it would have changed the
picture under the markers; the axis transform is read back out of the image
itself, the four red dashed verticals fixing the x scale to 0.02 px.

View File

@ -0,0 +1,5 @@
configuration new z repeats z new used diff
midpoint 39.920 0.000 1.7489e+08 1.7375e+08 +0.66%
quarter / three-quarter 49.980 29.850 2.5041e+08 2.5092e+08 -0.20%
largest disagreement 0.66%; the figure's modulation is 69%, so this does not bear on it

View File

@ -0,0 +1,8 @@
crossover (archived) z= 19.7940 |d_az|= 0.003 anodes [23] -2.9308e+08 -2.9308e+08
crossover, 2nd mesh (archived) z= 19.6900 |d_az|= 0.037 anodes [23] -2.9308e+08 -2.9308e+08
midpoint (archived) z= 0.0000 |d_az|= 7.500 anodes [23, 22] -1.7375e+08 -1.7375e+08 -1.7375e+08
quarter (new) z= 29.8500 |d_az|= 3.794 anodes [23] -2.5092e+08 -2.5092e+08
midpoint, next cell (new) z= 39.9200 |d_az|= 7.500 anodes [0, 23] -1.7489e+08 -1.7612e+08 -1.7366e+08
three quarters (new) z= 49.9800 |d_az|= 3.699 anodes [0] -2.5041e+08 -2.5041e+08
three eighths (new) z= 44.7820 |d_az|= 5.625 anodes [0] -2.1167e+08 -2.1167e+08
wrote slices_fit.csv

View File

@ -0,0 +1,110 @@
168 rows, 7 slices
collapse: slices compared at the azimuths they share
|d_az| crossover crossover_2nd midpoint midpoint_next quarter three_eighths three_quarter
0.0 2.9308e+08 2.9308e+08 - - - - -
4.0 - - - - 2.5092e+08 - 2.5041e+08
6.0 - - - - - 2.1167e+08 -
7.0 - - - 1.7612e+08 - - -
8.0 - - 1.7375e+08 1.7366e+08 - - -
9.0 - - - - - 1.4115e+08 -
11.0 - - - - 1.1293e+08 - 1.1060e+08
fitted over the cell (|d_az| <= 7.6 deg, 9 points):
Lorentzian 1/(1+(arc/h)^2) : h = 5.102 mm, rms 0.0049 (0.49%)
Mathieson (h fixed at the gap) : K3 = 1.930, rms 0.0056 (0.56%)
the geometric anode-cathode gap is 4.330 to 4.389 mm
extrapolation: anodes the cell fit never saw
|d_az| arc[mm] measured Lorentzian ratio
8.0 4.219 1.7372e+08 1.7422e+08 0.997
9.0 5.300 1.4115e+08 1.4108e+08 1.000
11.0 6.352 1.1176e+08 1.1504e+08 0.972
15.0 8.407 6.2616e+07 7.8964e+07 0.793
19.0 10.581 3.3326e+07 5.5340e+07 0.602
21.0 11.661 2.5520e+07 4.7134e+07 0.541
22.0 12.607 2.0513e+07 4.1281e+07 0.497
23.0 12.729 2.0475e+07 4.0602e+07 0.504
24.0 13.781 1.7098e+07 3.5361e+07 0.484
26.0 14.818 1.4266e+07 3.1090e+07 0.459
30.0 16.814 9.3546e+06 2.4734e+07 0.378
34.0 19.047 6.5436e+06 1.9639e+07 0.333
36.0 20.142 5.8540e+06 1.7688e+07 0.331
37.0 21.118 5.3724e+06 1.6178e+07 0.332
38.0 21.045 5.3095e+06 1.6284e+07 0.326
39.0 22.262 4.8845e+06 1.4639e+07 0.334
41.0 23.285 4.3965e+06 1.3439e+07 0.327
45.0 25.220 3.3175e+06 1.1533e+07 0.288
49.0 27.514 2.7932e+06 9.7520e+06 0.286
51.0 28.622 2.6716e+06 9.0341e+06 0.296
52.0 29.434 2.5395e+06 8.5570e+06 0.297
53.0 29.653 2.5524e+06 8.4345e+06 0.303
54.0 30.742 2.4121e+06 7.8632e+06 0.307
56.0 31.751 2.2352e+06 7.3839e+06 0.303
60.0 33.627 1.7979e+06 6.6010e+06 0.272
64.0 35.980 1.6430e+06 5.7823e+06 0.284
66.0 37.103 1.6105e+06 5.4441e+06 0.296
67.0 38.042 1.5707e+06 5.1834e+06 0.303
68.0 37.871 1.5565e+06 5.2292e+06 0.298
69.0 39.223 1.5011e+06 4.8810e+06 0.308
71.0 40.217 1.4101e+06 4.6463e+06 0.303
75.0 42.034 1.1748e+06 4.2592e+06 0.276
79.0 44.446 1.1222e+06 3.8152e+06 0.294
81.0 45.583 1.1143e+06 3.6296e+06 0.307
82.0 46.260 1.0882e+06 3.5254e+06 0.309
83.0 46.577 1.0947e+06 3.4782e+06 0.315
84.0 47.703 1.0574e+06 3.3177e+06 0.319
86.0 48.684 1.0016e+06 3.1868e+06 0.314
90.0 50.440 8.5453e+05 2.9709e+06 0.288
94.0 52.913 8.4006e+05 2.7023e+06 0.311
96.0 54.064 8.4082e+05 2.5894e+06 0.325
97.0 54.965 8.3265e+05 2.5059e+06 0.332
98.0 54.698 8.2620e+05 2.5303e+06 0.327
99.0 56.184 8.0766e+05 2.3993e+06 0.337
101.0 57.150 7.6964e+05 2.3195e+06 0.332
105.0 58.847 6.6855e+05 2.1886e+06 0.305
109.0 61.379 6.7132e+05 2.0130e+06 0.333
111.0 62.544 6.7610e+05 1.9392e+06 0.349
112.0 63.087 6.6789e+05 1.9062e+06 0.350
113.0 63.501 6.7212e+05 1.8816e+06 0.357
114.0 64.665 6.5545e+05 1.8149e+06 0.361
116.0 65.616 6.2759e+05 1.7629e+06 0.356
120.0 67.254 5.5350e+05 1.6786e+06 0.330
124.0 69.845 5.6538e+05 1.5570e+06 0.363
126.0 71.025 5.7228e+05 1.5060e+06 0.380
127.0 71.889 5.7164e+05 1.4702e+06 0.389
128.0 71.524 5.6744e+05 1.4851e+06 0.382
129.0 73.145 5.5911e+05 1.4203e+06 0.394
131.0 74.083 5.3764e+05 1.3848e+06 0.388
135.0 75.661 4.8056e+05 1.3279e+06 0.362
139.0 78.312 4.9815e+05 1.2399e+06 0.402
141.0 79.506 5.0640e+05 1.2031e+06 0.421
142.0 79.913 5.0405e+05 1.1909e+06 0.423
143.0 80.424 5.0740e+05 1.1759e+06 0.432
144.0 81.626 4.9829e+05 1.1416e+06 0.436
146.0 82.549 4.8099e+05 1.1163e+06 0.431
150.0 84.067 4.3539e+05 1.0765e+06 0.404
154.0 86.778 4.5748e+05 1.0105e+06 0.453
156.0 87.986 4.6690e+05 9.8307e+05 0.475
157.0 88.813 4.6955e+05 9.6492e+05 0.487
158.0 88.350 4.6625e+05 9.7501e+05 0.478
159.0 90.106 4.6241e+05 9.3749e+05 0.493
161.0 91.016 4.4806e+05 9.1892e+05 0.488
165.0 92.474 4.1067e+05 8.9025e+05 0.461
169.0 95.245 4.3703e+05 8.3936e+05 0.521
171.0 96.467 4.4772e+05 8.1828e+05 0.547
172.0 96.739 4.4858e+05 8.1369e+05 0.551
173.0 97.348 4.5166e+05 8.0358e+05 0.562
174.0 98.587 4.4633e+05 7.8356e+05 0.570
176.0 99.482 4.3405e+05 7.6956e+05 0.564
180.0 100.870 4.0281e+05 7.4858e+05 0.538
the half cell, normalised at the crossover:
d_az[deg] arc[mm] fit solved
0.000 0.000 1.0000 1.0000
1.875 1.054 0.9591 -
3.750 2.107 0.8542 0.8553
5.625 3.161 0.7226 0.7222
7.500 4.215 0.5944 0.5948
wrote function_map.png

View File

@ -0,0 +1,171 @@
# |E.E_w| at each anode surface, potential-fit recipe, annulus 0.30 mm, 6 orders
# arc_mm = r_anode * |d_az| ; gap_mm = r_cathode - r_anode
label,z_mm,anode,d_az_deg,sep_mm,arc_mm,r_a_mm,r_c_mm,gap_mm,dot_surf
crossover,19.7940,0,-14.9975,9.9284,8.4054,32.1117,36.4512,4.3394,-6.264028e+07
crossover,19.7940,1,-29.9975,18.2323,16.8123,32.1117,36.4512,4.3394,-9.357083e+06
crossover,19.7940,2,-44.9975,26.5410,25.2191,32.1117,36.4512,4.3394,-3.318036e+06
crossover,19.7940,3,-59.9975,34.4855,33.6259,32.1117,36.4512,4.3394,-1.798141e+06
crossover,19.7940,4,-74.9975,41.8790,42.0328,32.1117,36.4512,4.3394,-1.174901e+06
crossover,19.7940,5,-89.9975,48.5772,50.4396,32.1117,36.4512,4.3394,-8.545572e+05
crossover,19.7940,6,-104.9975,54.4578,58.8464,32.1117,36.4512,4.3394,-6.685716e+05
crossover,19.7940,7,-119.9975,59.4161,67.2533,32.1117,36.4512,4.3394,-5.535249e+05
crossover,19.7940,8,-134.9975,63.3651,75.6601,32.1117,36.4512,4.3394,-4.805962e+05
crossover,19.7940,9,-149.9975,66.2358,84.0669,32.1117,36.4512,4.3394,-4.354049e+05
crossover,19.7940,10,-164.9975,67.9785,92.4738,32.1117,36.4512,4.3394,-4.106824e+05
crossover,19.7940,11,-179.9975,68.5629,100.8806,32.1117,36.4512,4.3394,-4.028100e+05
crossover,19.7940,12,165.0025,67.9789,92.4766,32.1117,36.4512,4.3394,-4.106489e+05
crossover,19.7940,13,150.0025,66.2366,84.0697,32.1117,36.4512,4.3394,-4.353595e+05
crossover,19.7940,14,135.0025,63.3662,75.6629,32.1117,36.4512,4.3394,-4.805101e+05
crossover,19.7940,15,120.0025,59.4176,67.2561,32.1117,36.4512,4.3394,-5.534480e+05
crossover,19.7940,16,105.0025,54.4596,58.8492,32.1117,36.4512,4.3394,-6.685026e+05
crossover,19.7940,17,90.0025,48.5794,50.4424,32.1117,36.4512,4.3394,-8.544877e+05
crossover,19.7940,18,75.0025,41.8814,42.0356,32.1117,36.4512,4.3394,-1.174686e+06
crossover,19.7940,19,60.0025,34.4881,33.6287,32.1117,36.4512,4.3394,-1.797689e+06
crossover,19.7940,20,45.0025,26.5438,25.2219,32.1117,36.4512,4.3394,-3.316963e+06
crossover,19.7940,21,30.0025,18.2351,16.8151,32.1117,36.4512,4.3394,-9.351989e+06
crossover,19.7940,22,15.0025,9.9310,8.4082,32.1117,36.4512,4.3394,-6.258937e+07
crossover,19.7940,23,0.0025,4.3394,0.0014,32.1117,36.4512,4.3394,-2.930785e+08
crossover_2nd_mesh,19.6900,0,-15.0368,9.9491,8.4273,32.1110,36.4503,4.3393,-6.222104e+07
crossover_2nd_mesh,19.6900,1,-30.0368,18.2539,16.8339,32.1110,36.4503,4.3393,-9.314435e+06
crossover_2nd_mesh,19.6900,2,-45.0368,26.5618,25.2406,32.1110,36.4503,4.3393,-3.308520e+06
crossover_2nd_mesh,19.6900,3,-60.0368,34.5049,33.6472,32.1110,36.4503,4.3393,-1.794435e+06
crossover_2nd_mesh,19.6900,4,-75.0368,41.8966,42.0538,32.1110,36.4503,4.3393,-1.173097e+06
crossover_2nd_mesh,19.6900,5,-90.0368,48.5927,50.4605,32.1110,36.4503,4.3393,-8.535860e+05
crossover_2nd_mesh,19.6900,6,-105.0368,54.4708,58.8671,32.1110,36.4503,4.3393,-6.679819e+05
crossover_2nd_mesh,19.6900,7,-120.0368,59.4265,67.2738,32.1110,36.4503,4.3393,-5.531363e+05
crossover_2nd_mesh,19.6900,8,-135.0368,63.3726,75.6804,32.1110,36.4503,4.3393,-4.803347e+05
crossover_2nd_mesh,19.6900,9,-150.0368,66.2404,84.0871,32.1110,36.4503,4.3393,-4.352689e+05
crossover_2nd_mesh,19.6900,10,-165.0368,67.9800,92.4937,32.1110,36.4503,4.3393,-4.106183e+05
crossover_2nd_mesh,19.6900,11,179.9632,68.5614,100.8591,32.1110,36.4503,4.3393,-4.028197e+05
crossover_2nd_mesh,19.6900,12,164.9632,67.9743,92.4525,32.1110,36.4503,4.3393,-4.107492e+05
crossover_2nd_mesh,19.6900,13,149.9632,66.2290,84.0458,32.1110,36.4503,4.3393,-4.355214e+05
crossover_2nd_mesh,19.6900,14,134.9632,63.3558,75.6392,32.1110,36.4503,4.3393,-4.808003e+05
crossover_2nd_mesh,19.6900,15,119.9632,59.4046,67.2325,32.1110,36.4503,4.3393,-5.538755e+05
crossover_2nd_mesh,19.6900,16,104.9632,54.4442,58.8259,32.1110,36.4503,4.3393,-6.691537e+05
crossover_2nd_mesh,19.6900,17,89.9632,48.5617,50.4193,32.1110,36.4503,4.3393,-8.554925e+05
crossover_2nd_mesh,19.6900,18,74.9632,41.8619,42.0126,32.1110,36.4503,4.3393,-1.176564e+06
crossover_2nd_mesh,19.6900,19,59.9632,34.4672,33.6060,32.1110,36.4503,4.3393,-1.801464e+06
crossover_2nd_mesh,19.6900,20,44.9632,26.5218,25.1993,32.1110,36.4503,4.3393,-3.326643e+06
crossover_2nd_mesh,19.6900,21,29.9632,18.2127,16.7927,32.1110,36.4503,4.3393,-9.395021e+06
crossover_2nd_mesh,19.6900,22,14.9632,9.9099,8.3860,32.1110,36.4503,4.3393,-6.301273e+07
crossover_2nd_mesh,19.6900,23,-0.0368,4.3394,0.0206,32.1110,36.4503,4.3393,-2.930799e+08
midpoint,0.0000,0,-22.5000,14.0067,12.5832,32.0429,36.3731,4.3301,-2.039204e+07
midpoint,0.0000,1,-37.5000,22.3706,20.9721,32.0429,36.3731,4.3301,-5.294365e+06
midpoint,0.0000,2,-52.5000,30.5078,29.3609,32.0429,36.3731,4.3301,-2.528524e+06
midpoint,0.0000,3,-67.5000,38.1800,37.7497,32.0429,36.3731,4.3301,-1.551434e+06
midpoint,0.0000,4,-82.5000,45.2271,46.1385,32.0429,36.3731,4.3301,-1.083803e+06
midpoint,0.0000,5,-97.5000,51.5171,54.5273,32.0429,36.3731,4.3301,-8.233796e+05
midpoint,0.0000,6,-112.5000,56.9367,62.9162,32.0429,36.3731,4.3301,-6.653680e+05
midpoint,0.0000,7,-127.5000,61.3903,71.3050,32.0429,36.3731,4.3301,-5.654790e+05
midpoint,0.0000,8,-142.5000,64.8001,79.6938,32.0429,36.3731,4.3301,-5.023224e+05
midpoint,0.0000,9,-157.5000,67.1067,88.0826,32.0429,36.3731,4.3301,-4.647409e+05
midpoint,0.0000,10,-172.5000,68.2701,96.4715,32.0429,36.3731,4.3301,-4.470796e+05
midpoint,0.0000,11,172.5000,68.2701,96.4715,32.0429,36.3731,4.3301,-4.470037e+05
midpoint,0.0000,12,157.5000,67.1067,88.0826,32.0429,36.3731,4.3301,-4.645535e+05
midpoint,0.0000,13,142.5000,64.8001,79.6938,32.0429,36.3731,4.3301,-5.021926e+05
midpoint,0.0000,14,127.5000,61.3903,71.3050,32.0429,36.3731,4.3301,-5.655687e+05
midpoint,0.0000,15,112.5000,56.9367,62.9162,32.0429,36.3731,4.3301,-6.655079e+05
midpoint,0.0000,16,97.5000,51.5171,54.5273,32.0429,36.3731,4.3301,-8.236472e+05
midpoint,0.0000,17,82.5000,45.2271,46.1385,32.0429,36.3731,4.3301,-1.083890e+06
midpoint,0.0000,18,67.5000,38.1800,37.7497,32.0429,36.3731,4.3301,-1.551420e+06
midpoint,0.0000,19,52.5000,30.5078,29.3609,32.0429,36.3731,4.3301,-2.528568e+06
midpoint,0.0000,20,37.5000,22.3706,20.9721,32.0429,36.3731,4.3301,-5.294461e+06
midpoint,0.0000,21,22.5000,14.0067,12.5832,32.0429,36.3731,4.3301,-2.039219e+07
midpoint,0.0000,22,7.5000,6.2203,4.1944,32.0429,36.3731,4.3301,-1.737511e+08
midpoint,0.0000,23,-7.5000,6.2203,4.1944,32.0429,36.3731,4.3301,-1.737511e+08
quarter,29.8500,0,-11.2065,7.9883,6.2978,32.1992,36.5504,4.3512,-1.129326e+08
quarter,29.8500,1,-26.2065,16.1519,14.7276,32.1992,36.5504,4.3512,-1.436402e+07
quarter,29.8500,2,-41.2065,24.5331,23.1573,32.1992,36.5504,4.3512,-4.418369e+06
quarter,29.8500,3,-56.2065,32.6120,31.5870,32.1992,36.5504,4.3512,-2.244959e+06
quarter,29.8500,4,-71.2065,40.1799,40.0167,32.1992,36.5504,4.3512,-1.415750e+06
quarter,29.8500,5,-86.2065,47.0849,48.4465,32.1992,36.5504,4.3512,-1.005442e+06
quarter,29.8500,6,-101.2065,53.1993,56.8762,32.1992,36.5504,4.3512,-7.725405e+05
quarter,29.8500,7,-116.2065,58.4138,65.3059,32.1992,36.5504,4.3512,-6.298993e+05
quarter,29.8500,8,-131.2065,62.6366,73.7357,32.1992,36.5504,4.3512,-5.396007e+05
quarter,29.8500,9,-146.2065,65.7939,82.1654,32.1992,36.5504,4.3512,-4.827200e+05
quarter,29.8500,10,-161.2065,67.8308,90.5951,32.1992,36.5504,4.3512,-4.496201e+05
quarter,29.8500,11,-176.2065,68.7121,99.0248,32.1992,36.5504,4.3512,-4.355789e+05
quarter,29.8500,12,168.7935,68.4224,94.8589,32.1992,36.5504,4.3512,-4.384851e+05
quarter,29.8500,13,153.7935,66.9669,86.4292,32.1992,36.5504,4.3512,-4.589077e+05
quarter,29.8500,14,138.7935,64.3706,77.9994,32.1992,36.5504,4.3512,-4.996246e+05
quarter,29.8500,15,123.7935,60.6787,69.5697,32.1992,36.5504,4.3512,-5.669569e+05
quarter,29.8500,16,108.7935,55.9555,61.1400,32.1992,36.5504,4.3512,-6.730520e+05
quarter,29.8500,17,93.7935,50.2837,52.7103,32.1992,36.5504,4.3512,-8.419619e+05
quarter,29.8500,18,78.7935,43.7638,44.2805,32.1992,36.5504,4.3512,-1.124446e+06
quarter,29.8500,19,63.7935,36.5140,35.8508,32.1992,36.5504,4.3512,-1.645427e+06
quarter,29.8500,20,48.7935,28.6724,27.4211,32.1992,36.5504,4.3512,-2.794903e+06
quarter,29.8500,21,33.7935,20.4111,18.9914,32.1992,36.5504,4.3512,-6.532951e+06
quarter,29.8500,22,18.7935,12.0177,10.5616,32.1992,36.5504,4.3512,-3.317603e+07
quarter,29.8500,23,3.7935,4.9082,2.1319,32.1992,36.5504,4.3512,-2.509238e+08
midpoint_next_cell,39.9200,0,-7.4349,6.2465,4.1942,32.3219,36.6897,4.3678,-1.761237e+08
midpoint_next_cell,39.9200,1,-22.4349,14.0921,12.6560,32.3219,36.6897,4.3678,-2.075531e+07
midpoint_next_cell,39.9200,2,-37.4349,22.5289,21.1179,32.3219,36.6897,4.3678,-5.372362e+06
midpoint_next_cell,39.9200,3,-52.4349,30.7386,29.5797,32.3219,36.6897,4.3678,-2.561314e+06
midpoint_next_cell,39.9200,4,-67.4349,38.4800,38.0416,32.3219,36.6897,4.3678,-1.570653e+06
midpoint_next_cell,39.9200,5,-82.4349,45.5915,46.5034,32.3219,36.6897,4.3678,-1.096806e+06
midpoint_next_cell,39.9200,6,-97.4349,51.9398,54.9652,32.3219,36.6897,4.3678,-8.326541e+05
midpoint_next_cell,39.9200,7,-112.4349,57.4106,63.4271,32.3219,36.6897,4.3678,-6.727963e+05
midpoint_next_cell,39.9200,8,-127.4349,61.9074,71.8889,32.3219,36.6897,4.3678,-5.716440e+05
midpoint_next_cell,39.9200,9,-142.4349,65.3516,80.3508,32.3219,36.6897,4.3678,-5.076333e+05
midpoint_next_cell,39.9200,10,-157.4349,67.6832,88.8126,32.3219,36.6897,4.3678,-4.695465e+05
midpoint_next_cell,39.9200,11,-172.4349,68.8618,97.2745,32.3219,36.6897,4.3678,-4.516568e+05
midpoint_next_cell,39.9200,12,172.5651,68.8669,97.3479,32.3219,36.6897,4.3678,-4.516637e+05
midpoint_next_cell,39.9200,13,157.5651,67.6985,88.8861,32.3219,36.6897,4.3678,-4.694576e+05
midpoint_next_cell,39.9200,14,142.5651,65.3767,80.4243,32.3219,36.6897,4.3678,-5.074022e+05
midpoint_next_cell,39.9200,15,127.5651,61.9420,71.9624,32.3219,36.6897,4.3678,-5.712809e+05
midpoint_next_cell,39.9200,16,112.5651,57.4540,63.5006,32.3219,36.6897,4.3678,-6.721183e+05
midpoint_next_cell,39.9200,17,97.5651,51.9912,55.0387,32.3219,36.6897,4.3678,-8.315842e+05
midpoint_next_cell,39.9200,18,82.5651,45.6501,46.5769,32.3219,36.6897,4.3678,-1.094733e+06
midpoint_next_cell,39.9200,19,67.5651,38.5447,38.1150,32.3219,36.6897,4.3678,-1.566789e+06
midpoint_next_cell,39.9200,20,52.5651,30.8081,29.6532,32.3219,36.6897,4.3678,-2.552420e+06
midpoint_next_cell,39.9200,21,37.5651,22.6017,21.1914,32.3219,36.6897,4.3678,-5.339551e+06
midpoint_next_cell,39.9200,22,22.5651,14.1651,12.7295,32.3219,36.6897,4.3678,-2.047473e+07
midpoint_next_cell,39.9200,23,7.5651,6.3025,4.2677,32.3219,36.6897,4.3678,-1.736599e+08
three_quarter,49.9800,0,-3.6995,4.9249,2.0971,32.4791,36.8682,4.3891,-2.504102e+08
three_quarter,49.9800,1,-18.6995,12.0699,10.6001,32.4791,36.8682,4.3891,-3.347552e+07
three_quarter,49.9800,2,-33.6995,20.5354,19.1031,32.4791,36.8682,4.3891,-6.554253e+06
three_quarter,49.9800,3,-48.6995,28.8705,27.6061,32.4791,36.8682,4.3891,-2.791526e+06
three_quarter,49.9800,4,-63.6995,36.7835,36.1091,32.4791,36.8682,4.3891,-1.640473e+06
three_quarter,49.9800,5,-78.6995,44.1006,44.6121,32.4791,36.8682,4.3891,-1.120026e+06
three_quarter,49.9800,6,-93.6995,50.6821,53.1152,32.4791,36.8682,4.3891,-8.381522e+05
three_quarter,49.9800,7,-108.6995,56.4089,61.6182,32.4791,36.8682,4.3891,-6.695898e+05
three_quarter,49.9800,8,-123.6995,61.1794,70.1212,32.4791,36.8682,4.3891,-5.637983e+05
three_quarter,49.9800,9,-138.6995,64.9102,78.6242,32.4791,36.8682,4.3891,-4.966657e+05
three_quarter,49.9800,10,-153.6995,67.5361,87.1272,32.4791,36.8682,4.3891,-4.560533e+05
three_quarter,49.9800,11,-168.6995,69.0117,95.6302,32.4791,36.8682,4.3891,-4.355764e+05
three_quarter,49.9800,12,176.3005,69.3112,99.9389,32.4791,36.8682,4.3891,-4.325198e+05
three_quarter,49.9800,13,161.3005,68.4297,91.4359,32.4791,36.8682,4.3891,-4.464923e+05
three_quarter,49.9800,14,146.3005,66.3822,82.9329,32.4791,36.8682,4.3891,-4.792532e+05
three_quarter,49.9800,15,131.3005,63.2044,74.4299,32.4791,36.8682,4.3891,-5.356818e+05
three_quarter,49.9800,16,116.3005,58.9515,65.9269,32.4791,36.8682,4.3891,-6.252740e+05
three_quarter,49.9800,17,101.3005,53.6977,57.4239,32.4791,36.8682,4.3891,-7.667390e+05
three_quarter,49.9800,18,86.3005,47.5355,48.9209,32.4791,36.8682,4.3891,-9.977283e+05
three_quarter,49.9800,19,71.3005,40.5751,40.4179,32.4791,36.8682,4.3891,-1.404378e+06
three_quarter,49.9800,20,56.3005,32.9451,31.9149,32.4791,36.8682,4.3891,-2.225433e+06
three_quarter,49.9800,21,41.3005,24.7986,23.4119,32.4791,36.8682,4.3891,-4.374588e+06
three_quarter,49.9800,22,26.3005,16.3455,14.9089,32.4791,36.8682,4.3891,-1.416708e+07
three_quarter,49.9800,23,11.3005,8.1052,6.4059,32.4791,36.8682,4.3891,-1.105954e+08
three_eighths,44.7820,0,-5.6250,5.5348,3.1803,32.3935,36.7711,4.3775,-2.116655e+08
three_eighths,44.7820,1,-20.6250,13.1093,11.6609,32.3935,36.7711,4.3775,-2.552036e+07
three_eighths,44.7820,2,-35.6250,21.5643,20.1415,32.3935,36.7711,4.3775,-5.854026e+06
three_eighths,44.7820,3,-50.6250,29.8353,28.6221,32.3935,36.7711,4.3775,-2.671645e+06
three_eighths,44.7820,4,-65.6250,37.6599,37.1027,32.3935,36.7711,4.3775,-1.610535e+06
three_eighths,44.7820,5,-80.6250,44.8708,45.5833,32.3935,36.7711,4.3775,-1.114330e+06
three_eighths,44.7820,6,-95.6250,51.3319,54.0639,32.3935,36.7711,4.3775,-8.408221e+05
three_eighths,44.7820,7,-110.6250,56.9264,62.5445,32.3935,36.7711,4.3775,-6.761017e+05
three_eighths,44.7820,8,-125.6250,61.5555,71.0251,32.3935,36.7711,4.3775,-5.722770e+05
three_eighths,44.7820,9,-140.6250,65.1382,79.5058,32.3935,36.7711,4.3775,-5.064035e+05
three_eighths,44.7820,10,-155.6250,67.6121,87.9864,32.3935,36.7711,4.3775,-4.669031e+05
three_eighths,44.7820,11,-170.6250,68.9342,96.4670,32.3935,36.7711,4.3775,-4.477174e+05
three_eighths,44.7820,12,174.3750,69.0816,98.5871,32.3935,36.7711,4.3775,-4.463304e+05
three_eighths,44.7820,13,159.3750,68.0518,90.1065,32.3935,36.7711,4.3775,-4.624125e+05
three_eighths,44.7820,14,144.3750,65.8626,81.6259,32.3935,36.7711,4.3775,-4.982920e+05
three_eighths,44.7820,15,129.3750,62.5521,73.1453,32.3935,36.7711,4.3775,-5.591098e+05
three_eighths,44.7820,16,114.3750,58.1776,64.6646,32.3935,36.7711,4.3775,-6.554538e+05
three_eighths,44.7820,17,99.3750,52.8159,56.1840,32.3935,36.7711,4.3775,-8.076560e+05
three_eighths,44.7820,18,84.3750,46.5612,47.7034,32.3935,36.7711,4.3775,-1.057369e+06
three_eighths,44.7820,19,69.3750,39.5258,39.2228,32.3935,36.7711,4.3775,-1.501138e+06
three_eighths,44.7820,20,54.3750,31.8405,30.7422,32.3935,36.7711,4.3775,-2.412077e+06
three_eighths,44.7820,21,39.3750,23.6626,22.2616,32.3935,36.7711,4.3775,-4.884531e+06
three_eighths,44.7820,22,24.3750,15.2155,13.7810,32.3935,36.7711,4.3775,-1.709812e+07
three_eighths,44.7820,23,9.3750,7.1402,5.3004,32.3935,36.7711,4.3775,-1.411534e+08
1 # |E.E_w| at each anode surface, potential-fit recipe, annulus 0.30 mm, 6 orders
2 # arc_mm = r_anode * |d_az| ; gap_mm = r_cathode - r_anode
3 label,z_mm,anode,d_az_deg,sep_mm,arc_mm,r_a_mm,r_c_mm,gap_mm,dot_surf
4 crossover,19.7940,0,-14.9975,9.9284,8.4054,32.1117,36.4512,4.3394,-6.264028e+07
5 crossover,19.7940,1,-29.9975,18.2323,16.8123,32.1117,36.4512,4.3394,-9.357083e+06
6 crossover,19.7940,2,-44.9975,26.5410,25.2191,32.1117,36.4512,4.3394,-3.318036e+06
7 crossover,19.7940,3,-59.9975,34.4855,33.6259,32.1117,36.4512,4.3394,-1.798141e+06
8 crossover,19.7940,4,-74.9975,41.8790,42.0328,32.1117,36.4512,4.3394,-1.174901e+06
9 crossover,19.7940,5,-89.9975,48.5772,50.4396,32.1117,36.4512,4.3394,-8.545572e+05
10 crossover,19.7940,6,-104.9975,54.4578,58.8464,32.1117,36.4512,4.3394,-6.685716e+05
11 crossover,19.7940,7,-119.9975,59.4161,67.2533,32.1117,36.4512,4.3394,-5.535249e+05
12 crossover,19.7940,8,-134.9975,63.3651,75.6601,32.1117,36.4512,4.3394,-4.805962e+05
13 crossover,19.7940,9,-149.9975,66.2358,84.0669,32.1117,36.4512,4.3394,-4.354049e+05
14 crossover,19.7940,10,-164.9975,67.9785,92.4738,32.1117,36.4512,4.3394,-4.106824e+05
15 crossover,19.7940,11,-179.9975,68.5629,100.8806,32.1117,36.4512,4.3394,-4.028100e+05
16 crossover,19.7940,12,165.0025,67.9789,92.4766,32.1117,36.4512,4.3394,-4.106489e+05
17 crossover,19.7940,13,150.0025,66.2366,84.0697,32.1117,36.4512,4.3394,-4.353595e+05
18 crossover,19.7940,14,135.0025,63.3662,75.6629,32.1117,36.4512,4.3394,-4.805101e+05
19 crossover,19.7940,15,120.0025,59.4176,67.2561,32.1117,36.4512,4.3394,-5.534480e+05
20 crossover,19.7940,16,105.0025,54.4596,58.8492,32.1117,36.4512,4.3394,-6.685026e+05
21 crossover,19.7940,17,90.0025,48.5794,50.4424,32.1117,36.4512,4.3394,-8.544877e+05
22 crossover,19.7940,18,75.0025,41.8814,42.0356,32.1117,36.4512,4.3394,-1.174686e+06
23 crossover,19.7940,19,60.0025,34.4881,33.6287,32.1117,36.4512,4.3394,-1.797689e+06
24 crossover,19.7940,20,45.0025,26.5438,25.2219,32.1117,36.4512,4.3394,-3.316963e+06
25 crossover,19.7940,21,30.0025,18.2351,16.8151,32.1117,36.4512,4.3394,-9.351989e+06
26 crossover,19.7940,22,15.0025,9.9310,8.4082,32.1117,36.4512,4.3394,-6.258937e+07
27 crossover,19.7940,23,0.0025,4.3394,0.0014,32.1117,36.4512,4.3394,-2.930785e+08
28 crossover_2nd_mesh,19.6900,0,-15.0368,9.9491,8.4273,32.1110,36.4503,4.3393,-6.222104e+07
29 crossover_2nd_mesh,19.6900,1,-30.0368,18.2539,16.8339,32.1110,36.4503,4.3393,-9.314435e+06
30 crossover_2nd_mesh,19.6900,2,-45.0368,26.5618,25.2406,32.1110,36.4503,4.3393,-3.308520e+06
31 crossover_2nd_mesh,19.6900,3,-60.0368,34.5049,33.6472,32.1110,36.4503,4.3393,-1.794435e+06
32 crossover_2nd_mesh,19.6900,4,-75.0368,41.8966,42.0538,32.1110,36.4503,4.3393,-1.173097e+06
33 crossover_2nd_mesh,19.6900,5,-90.0368,48.5927,50.4605,32.1110,36.4503,4.3393,-8.535860e+05
34 crossover_2nd_mesh,19.6900,6,-105.0368,54.4708,58.8671,32.1110,36.4503,4.3393,-6.679819e+05
35 crossover_2nd_mesh,19.6900,7,-120.0368,59.4265,67.2738,32.1110,36.4503,4.3393,-5.531363e+05
36 crossover_2nd_mesh,19.6900,8,-135.0368,63.3726,75.6804,32.1110,36.4503,4.3393,-4.803347e+05
37 crossover_2nd_mesh,19.6900,9,-150.0368,66.2404,84.0871,32.1110,36.4503,4.3393,-4.352689e+05
38 crossover_2nd_mesh,19.6900,10,-165.0368,67.9800,92.4937,32.1110,36.4503,4.3393,-4.106183e+05
39 crossover_2nd_mesh,19.6900,11,179.9632,68.5614,100.8591,32.1110,36.4503,4.3393,-4.028197e+05
40 crossover_2nd_mesh,19.6900,12,164.9632,67.9743,92.4525,32.1110,36.4503,4.3393,-4.107492e+05
41 crossover_2nd_mesh,19.6900,13,149.9632,66.2290,84.0458,32.1110,36.4503,4.3393,-4.355214e+05
42 crossover_2nd_mesh,19.6900,14,134.9632,63.3558,75.6392,32.1110,36.4503,4.3393,-4.808003e+05
43 crossover_2nd_mesh,19.6900,15,119.9632,59.4046,67.2325,32.1110,36.4503,4.3393,-5.538755e+05
44 crossover_2nd_mesh,19.6900,16,104.9632,54.4442,58.8259,32.1110,36.4503,4.3393,-6.691537e+05
45 crossover_2nd_mesh,19.6900,17,89.9632,48.5617,50.4193,32.1110,36.4503,4.3393,-8.554925e+05
46 crossover_2nd_mesh,19.6900,18,74.9632,41.8619,42.0126,32.1110,36.4503,4.3393,-1.176564e+06
47 crossover_2nd_mesh,19.6900,19,59.9632,34.4672,33.6060,32.1110,36.4503,4.3393,-1.801464e+06
48 crossover_2nd_mesh,19.6900,20,44.9632,26.5218,25.1993,32.1110,36.4503,4.3393,-3.326643e+06
49 crossover_2nd_mesh,19.6900,21,29.9632,18.2127,16.7927,32.1110,36.4503,4.3393,-9.395021e+06
50 crossover_2nd_mesh,19.6900,22,14.9632,9.9099,8.3860,32.1110,36.4503,4.3393,-6.301273e+07
51 crossover_2nd_mesh,19.6900,23,-0.0368,4.3394,0.0206,32.1110,36.4503,4.3393,-2.930799e+08
52 midpoint,0.0000,0,-22.5000,14.0067,12.5832,32.0429,36.3731,4.3301,-2.039204e+07
53 midpoint,0.0000,1,-37.5000,22.3706,20.9721,32.0429,36.3731,4.3301,-5.294365e+06
54 midpoint,0.0000,2,-52.5000,30.5078,29.3609,32.0429,36.3731,4.3301,-2.528524e+06
55 midpoint,0.0000,3,-67.5000,38.1800,37.7497,32.0429,36.3731,4.3301,-1.551434e+06
56 midpoint,0.0000,4,-82.5000,45.2271,46.1385,32.0429,36.3731,4.3301,-1.083803e+06
57 midpoint,0.0000,5,-97.5000,51.5171,54.5273,32.0429,36.3731,4.3301,-8.233796e+05
58 midpoint,0.0000,6,-112.5000,56.9367,62.9162,32.0429,36.3731,4.3301,-6.653680e+05
59 midpoint,0.0000,7,-127.5000,61.3903,71.3050,32.0429,36.3731,4.3301,-5.654790e+05
60 midpoint,0.0000,8,-142.5000,64.8001,79.6938,32.0429,36.3731,4.3301,-5.023224e+05
61 midpoint,0.0000,9,-157.5000,67.1067,88.0826,32.0429,36.3731,4.3301,-4.647409e+05
62 midpoint,0.0000,10,-172.5000,68.2701,96.4715,32.0429,36.3731,4.3301,-4.470796e+05
63 midpoint,0.0000,11,172.5000,68.2701,96.4715,32.0429,36.3731,4.3301,-4.470037e+05
64 midpoint,0.0000,12,157.5000,67.1067,88.0826,32.0429,36.3731,4.3301,-4.645535e+05
65 midpoint,0.0000,13,142.5000,64.8001,79.6938,32.0429,36.3731,4.3301,-5.021926e+05
66 midpoint,0.0000,14,127.5000,61.3903,71.3050,32.0429,36.3731,4.3301,-5.655687e+05
67 midpoint,0.0000,15,112.5000,56.9367,62.9162,32.0429,36.3731,4.3301,-6.655079e+05
68 midpoint,0.0000,16,97.5000,51.5171,54.5273,32.0429,36.3731,4.3301,-8.236472e+05
69 midpoint,0.0000,17,82.5000,45.2271,46.1385,32.0429,36.3731,4.3301,-1.083890e+06
70 midpoint,0.0000,18,67.5000,38.1800,37.7497,32.0429,36.3731,4.3301,-1.551420e+06
71 midpoint,0.0000,19,52.5000,30.5078,29.3609,32.0429,36.3731,4.3301,-2.528568e+06
72 midpoint,0.0000,20,37.5000,22.3706,20.9721,32.0429,36.3731,4.3301,-5.294461e+06
73 midpoint,0.0000,21,22.5000,14.0067,12.5832,32.0429,36.3731,4.3301,-2.039219e+07
74 midpoint,0.0000,22,7.5000,6.2203,4.1944,32.0429,36.3731,4.3301,-1.737511e+08
75 midpoint,0.0000,23,-7.5000,6.2203,4.1944,32.0429,36.3731,4.3301,-1.737511e+08
76 quarter,29.8500,0,-11.2065,7.9883,6.2978,32.1992,36.5504,4.3512,-1.129326e+08
77 quarter,29.8500,1,-26.2065,16.1519,14.7276,32.1992,36.5504,4.3512,-1.436402e+07
78 quarter,29.8500,2,-41.2065,24.5331,23.1573,32.1992,36.5504,4.3512,-4.418369e+06
79 quarter,29.8500,3,-56.2065,32.6120,31.5870,32.1992,36.5504,4.3512,-2.244959e+06
80 quarter,29.8500,4,-71.2065,40.1799,40.0167,32.1992,36.5504,4.3512,-1.415750e+06
81 quarter,29.8500,5,-86.2065,47.0849,48.4465,32.1992,36.5504,4.3512,-1.005442e+06
82 quarter,29.8500,6,-101.2065,53.1993,56.8762,32.1992,36.5504,4.3512,-7.725405e+05
83 quarter,29.8500,7,-116.2065,58.4138,65.3059,32.1992,36.5504,4.3512,-6.298993e+05
84 quarter,29.8500,8,-131.2065,62.6366,73.7357,32.1992,36.5504,4.3512,-5.396007e+05
85 quarter,29.8500,9,-146.2065,65.7939,82.1654,32.1992,36.5504,4.3512,-4.827200e+05
86 quarter,29.8500,10,-161.2065,67.8308,90.5951,32.1992,36.5504,4.3512,-4.496201e+05
87 quarter,29.8500,11,-176.2065,68.7121,99.0248,32.1992,36.5504,4.3512,-4.355789e+05
88 quarter,29.8500,12,168.7935,68.4224,94.8589,32.1992,36.5504,4.3512,-4.384851e+05
89 quarter,29.8500,13,153.7935,66.9669,86.4292,32.1992,36.5504,4.3512,-4.589077e+05
90 quarter,29.8500,14,138.7935,64.3706,77.9994,32.1992,36.5504,4.3512,-4.996246e+05
91 quarter,29.8500,15,123.7935,60.6787,69.5697,32.1992,36.5504,4.3512,-5.669569e+05
92 quarter,29.8500,16,108.7935,55.9555,61.1400,32.1992,36.5504,4.3512,-6.730520e+05
93 quarter,29.8500,17,93.7935,50.2837,52.7103,32.1992,36.5504,4.3512,-8.419619e+05
94 quarter,29.8500,18,78.7935,43.7638,44.2805,32.1992,36.5504,4.3512,-1.124446e+06
95 quarter,29.8500,19,63.7935,36.5140,35.8508,32.1992,36.5504,4.3512,-1.645427e+06
96 quarter,29.8500,20,48.7935,28.6724,27.4211,32.1992,36.5504,4.3512,-2.794903e+06
97 quarter,29.8500,21,33.7935,20.4111,18.9914,32.1992,36.5504,4.3512,-6.532951e+06
98 quarter,29.8500,22,18.7935,12.0177,10.5616,32.1992,36.5504,4.3512,-3.317603e+07
99 quarter,29.8500,23,3.7935,4.9082,2.1319,32.1992,36.5504,4.3512,-2.509238e+08
100 midpoint_next_cell,39.9200,0,-7.4349,6.2465,4.1942,32.3219,36.6897,4.3678,-1.761237e+08
101 midpoint_next_cell,39.9200,1,-22.4349,14.0921,12.6560,32.3219,36.6897,4.3678,-2.075531e+07
102 midpoint_next_cell,39.9200,2,-37.4349,22.5289,21.1179,32.3219,36.6897,4.3678,-5.372362e+06
103 midpoint_next_cell,39.9200,3,-52.4349,30.7386,29.5797,32.3219,36.6897,4.3678,-2.561314e+06
104 midpoint_next_cell,39.9200,4,-67.4349,38.4800,38.0416,32.3219,36.6897,4.3678,-1.570653e+06
105 midpoint_next_cell,39.9200,5,-82.4349,45.5915,46.5034,32.3219,36.6897,4.3678,-1.096806e+06
106 midpoint_next_cell,39.9200,6,-97.4349,51.9398,54.9652,32.3219,36.6897,4.3678,-8.326541e+05
107 midpoint_next_cell,39.9200,7,-112.4349,57.4106,63.4271,32.3219,36.6897,4.3678,-6.727963e+05
108 midpoint_next_cell,39.9200,8,-127.4349,61.9074,71.8889,32.3219,36.6897,4.3678,-5.716440e+05
109 midpoint_next_cell,39.9200,9,-142.4349,65.3516,80.3508,32.3219,36.6897,4.3678,-5.076333e+05
110 midpoint_next_cell,39.9200,10,-157.4349,67.6832,88.8126,32.3219,36.6897,4.3678,-4.695465e+05
111 midpoint_next_cell,39.9200,11,-172.4349,68.8618,97.2745,32.3219,36.6897,4.3678,-4.516568e+05
112 midpoint_next_cell,39.9200,12,172.5651,68.8669,97.3479,32.3219,36.6897,4.3678,-4.516637e+05
113 midpoint_next_cell,39.9200,13,157.5651,67.6985,88.8861,32.3219,36.6897,4.3678,-4.694576e+05
114 midpoint_next_cell,39.9200,14,142.5651,65.3767,80.4243,32.3219,36.6897,4.3678,-5.074022e+05
115 midpoint_next_cell,39.9200,15,127.5651,61.9420,71.9624,32.3219,36.6897,4.3678,-5.712809e+05
116 midpoint_next_cell,39.9200,16,112.5651,57.4540,63.5006,32.3219,36.6897,4.3678,-6.721183e+05
117 midpoint_next_cell,39.9200,17,97.5651,51.9912,55.0387,32.3219,36.6897,4.3678,-8.315842e+05
118 midpoint_next_cell,39.9200,18,82.5651,45.6501,46.5769,32.3219,36.6897,4.3678,-1.094733e+06
119 midpoint_next_cell,39.9200,19,67.5651,38.5447,38.1150,32.3219,36.6897,4.3678,-1.566789e+06
120 midpoint_next_cell,39.9200,20,52.5651,30.8081,29.6532,32.3219,36.6897,4.3678,-2.552420e+06
121 midpoint_next_cell,39.9200,21,37.5651,22.6017,21.1914,32.3219,36.6897,4.3678,-5.339551e+06
122 midpoint_next_cell,39.9200,22,22.5651,14.1651,12.7295,32.3219,36.6897,4.3678,-2.047473e+07
123 midpoint_next_cell,39.9200,23,7.5651,6.3025,4.2677,32.3219,36.6897,4.3678,-1.736599e+08
124 three_quarter,49.9800,0,-3.6995,4.9249,2.0971,32.4791,36.8682,4.3891,-2.504102e+08
125 three_quarter,49.9800,1,-18.6995,12.0699,10.6001,32.4791,36.8682,4.3891,-3.347552e+07
126 three_quarter,49.9800,2,-33.6995,20.5354,19.1031,32.4791,36.8682,4.3891,-6.554253e+06
127 three_quarter,49.9800,3,-48.6995,28.8705,27.6061,32.4791,36.8682,4.3891,-2.791526e+06
128 three_quarter,49.9800,4,-63.6995,36.7835,36.1091,32.4791,36.8682,4.3891,-1.640473e+06
129 three_quarter,49.9800,5,-78.6995,44.1006,44.6121,32.4791,36.8682,4.3891,-1.120026e+06
130 three_quarter,49.9800,6,-93.6995,50.6821,53.1152,32.4791,36.8682,4.3891,-8.381522e+05
131 three_quarter,49.9800,7,-108.6995,56.4089,61.6182,32.4791,36.8682,4.3891,-6.695898e+05
132 three_quarter,49.9800,8,-123.6995,61.1794,70.1212,32.4791,36.8682,4.3891,-5.637983e+05
133 three_quarter,49.9800,9,-138.6995,64.9102,78.6242,32.4791,36.8682,4.3891,-4.966657e+05
134 three_quarter,49.9800,10,-153.6995,67.5361,87.1272,32.4791,36.8682,4.3891,-4.560533e+05
135 three_quarter,49.9800,11,-168.6995,69.0117,95.6302,32.4791,36.8682,4.3891,-4.355764e+05
136 three_quarter,49.9800,12,176.3005,69.3112,99.9389,32.4791,36.8682,4.3891,-4.325198e+05
137 three_quarter,49.9800,13,161.3005,68.4297,91.4359,32.4791,36.8682,4.3891,-4.464923e+05
138 three_quarter,49.9800,14,146.3005,66.3822,82.9329,32.4791,36.8682,4.3891,-4.792532e+05
139 three_quarter,49.9800,15,131.3005,63.2044,74.4299,32.4791,36.8682,4.3891,-5.356818e+05
140 three_quarter,49.9800,16,116.3005,58.9515,65.9269,32.4791,36.8682,4.3891,-6.252740e+05
141 three_quarter,49.9800,17,101.3005,53.6977,57.4239,32.4791,36.8682,4.3891,-7.667390e+05
142 three_quarter,49.9800,18,86.3005,47.5355,48.9209,32.4791,36.8682,4.3891,-9.977283e+05
143 three_quarter,49.9800,19,71.3005,40.5751,40.4179,32.4791,36.8682,4.3891,-1.404378e+06
144 three_quarter,49.9800,20,56.3005,32.9451,31.9149,32.4791,36.8682,4.3891,-2.225433e+06
145 three_quarter,49.9800,21,41.3005,24.7986,23.4119,32.4791,36.8682,4.3891,-4.374588e+06
146 three_quarter,49.9800,22,26.3005,16.3455,14.9089,32.4791,36.8682,4.3891,-1.416708e+07
147 three_quarter,49.9800,23,11.3005,8.1052,6.4059,32.4791,36.8682,4.3891,-1.105954e+08
148 three_eighths,44.7820,0,-5.6250,5.5348,3.1803,32.3935,36.7711,4.3775,-2.116655e+08
149 three_eighths,44.7820,1,-20.6250,13.1093,11.6609,32.3935,36.7711,4.3775,-2.552036e+07
150 three_eighths,44.7820,2,-35.6250,21.5643,20.1415,32.3935,36.7711,4.3775,-5.854026e+06
151 three_eighths,44.7820,3,-50.6250,29.8353,28.6221,32.3935,36.7711,4.3775,-2.671645e+06
152 three_eighths,44.7820,4,-65.6250,37.6599,37.1027,32.3935,36.7711,4.3775,-1.610535e+06
153 three_eighths,44.7820,5,-80.6250,44.8708,45.5833,32.3935,36.7711,4.3775,-1.114330e+06
154 three_eighths,44.7820,6,-95.6250,51.3319,54.0639,32.3935,36.7711,4.3775,-8.408221e+05
155 three_eighths,44.7820,7,-110.6250,56.9264,62.5445,32.3935,36.7711,4.3775,-6.761017e+05
156 three_eighths,44.7820,8,-125.6250,61.5555,71.0251,32.3935,36.7711,4.3775,-5.722770e+05
157 three_eighths,44.7820,9,-140.6250,65.1382,79.5058,32.3935,36.7711,4.3775,-5.064035e+05
158 three_eighths,44.7820,10,-155.6250,67.6121,87.9864,32.3935,36.7711,4.3775,-4.669031e+05
159 three_eighths,44.7820,11,-170.6250,68.9342,96.4670,32.3935,36.7711,4.3775,-4.477174e+05
160 three_eighths,44.7820,12,174.3750,69.0816,98.5871,32.3935,36.7711,4.3775,-4.463304e+05
161 three_eighths,44.7820,13,159.3750,68.0518,90.1065,32.3935,36.7711,4.3775,-4.624125e+05
162 three_eighths,44.7820,14,144.3750,65.8626,81.6259,32.3935,36.7711,4.3775,-4.982920e+05
163 three_eighths,44.7820,15,129.3750,62.5521,73.1453,32.3935,36.7711,4.3775,-5.591098e+05
164 three_eighths,44.7820,16,114.3750,58.1776,64.6646,32.3935,36.7711,4.3775,-6.554538e+05
165 three_eighths,44.7820,17,99.3750,52.8159,56.1840,32.3935,36.7711,4.3775,-8.076560e+05
166 three_eighths,44.7820,18,84.3750,46.5612,47.7034,32.3935,36.7711,4.3775,-1.057369e+06
167 three_eighths,44.7820,19,69.3750,39.5258,39.2228,32.3935,36.7711,4.3775,-1.501138e+06
168 three_eighths,44.7820,20,54.3750,31.8405,30.7422,32.3935,36.7711,4.3775,-2.412077e+06
169 three_eighths,44.7820,21,39.3750,23.6626,22.2616,32.3935,36.7711,4.3775,-4.884531e+06
170 three_eighths,44.7820,22,24.3750,15.2155,13.7810,32.3935,36.7711,4.3775,-1.709812e+07
171 three_eighths,44.7820,23,9.3750,7.1402,5.3004,32.3935,36.7711,4.3775,-1.411534e+08

View File

@ -0,0 +1,31 @@
solved slices: azimuth[deg] -> |E.E_w| [V^2/m^2]
0.020 2.9308e+08
3.700 2.5041e+08
3.793 2.5092e+08
5.625 2.1167e+08
7.500 1.7432e+08
crossover / midpoint, predicted c/a ratio : 1.681
scale fitted at z = +19.7248 mm : s = 3.0923e-09 (c/a = 0.9063 there)
predicted points
z[mm] |d_az| c/a c/(a+c)
-39.79 7.50 0.539 0.3503
-29.76 3.75 0.775 0.4367
-19.72 0.00 0.906 0.4754
-9.86 3.75 0.775 0.4367
0.00 7.50 0.539 0.3503
9.86 3.75 0.775 0.4367
19.72 0.00 0.906 0.4754
29.76 3.75 0.775 0.4367
39.79 7.50 0.539 0.3503
49.82 3.75 0.775 0.4367
59.85 0.00 0.906 0.4754
image : /home/vsitaraman/Dissertation_ch5/defense/figures/cfrac.png 1800x1238
red verticals : [ 390. 719.5 1043.5 1373. ]
x fit : px = 8.21194 * z + 881.500 (residuals [ 0.01 -0.02 0.02 -0.01] px)
frame rows : top 36 (y=0.80), bottom 1070 (y=0.00)
x range of frame : -107.34 .. 111.85 mm
y-axis title: set to 'cathode charge fraction', 55x589 px at x=40, centred on y=553
x-axis title: set to 'PC z [mm]', 273x55 px, centred on x=936.5 at y=1153
wrote /home/vsitaraman/Dissertation_ch5/defense/figures/cfrac_pred.png (1800x1238)

View File

@ -0,0 +1,8 @@
crossover z= 19.7940 nearest anode 0.003 deg, |E.E_w| 2.9308e+08
crossover_2nd_mesh z= 19.6900 nearest anode 0.037 deg, |E.E_w| 2.9308e+08
midpoint z= 0.0000 nearest anode 7.500 deg, |E.E_w| 1.7375e+08
quarter z= 29.8500 nearest anode 3.794 deg, |E.E_w| 2.5092e+08
midpoint_next_cell z= 39.9200 nearest anode 7.435 deg, |E.E_w| 1.7612e+08
three_quarter z= 49.9800 nearest anode 3.699 deg, |E.E_w| 2.5041e+08
three_eighths z= 44.7820 nearest anode 5.625 deg, |E.E_w| 2.1167e+08
wrote function_map.csv, 168 rows

View File

@ -0,0 +1,9 @@
# potential-fit recipe, annulus 0.30 mm, 6 azimuthal orders
z_mm,d_az_deg,dot_surf,label
19.7940,0.0025,-2.930785e+08,crossover (archived)
19.6900,0.0368,-2.930799e+08,crossover, 2nd mesh (archived)
0.0000,7.5000,-1.737511e+08,midpoint (archived)
29.8500,3.7935,-2.509238e+08,quarter (new)
39.9200,7.5000,-1.748918e+08,midpoint, next cell (new)
49.9800,3.6995,-2.504102e+08,three quarters (new)
44.7820,5.6250,-2.116655e+08,three eighths (new)
1 # potential-fit recipe, annulus 0.30 mm, 6 azimuthal orders
2 z_mm,d_az_deg,dot_surf,label
3 19.7940,0.0025,-2.930785e+08,crossover (archived)
4 19.6900,0.0368,-2.930799e+08,crossover, 2nd mesh (archived)
5 0.0000,7.5000,-1.737511e+08,midpoint (archived)
6 29.8500,3.7935,-2.509238e+08,quarter (new)
7 39.9200,7.5000,-1.748918e+08,midpoint, next cell (new)
8 49.9800,3.6995,-2.504102e+08,three quarters (new)
9 44.7820,5.6250,-2.116655e+08,three eighths (new)

View File

@ -0,0 +1,77 @@
# Raw Elmer solution kept for z = 44.7820 mm
Moved here on 2026-10-28 from the working directory `~/anasen_fem_cfrac_2026-10-28_B`,
which was then deleted. This is the one solve whose **raw solution** was kept, so that a
quantity the cfrac study did not export can still be recovered without re-solving.
Re-meshing and re-solving this configuration costs about an hour, almost all of it meshing
10.8 M nodes. Re-running a postprocessor on the solution kept here costs minutes.
## What this slice is
z = 44.7820 mm, the **three-eighths point** of the cell. It was not an input to the
Lorentzian fit: the fit was made on the crossover, quarter and midpoint alone, predicted
2.117e8 for this slice, and solving it returned 2.1167e8, a difference of 0.015%. It is the
blind test of the functional form in `../cfrac_pred_method.md`, section 4.
Relative azimuth 5.625 deg, anode-cathode gap 5.535 mm, |E.E_w| = 2.1167e8 V^2/m^2 at the
fired anode. `anode_dot.csv` holds all 24 anodes for this slice.
## Files
| | |
|---|---|
| `wires2d/elstatics.result` | the physical solve, anodes biased, everything else grounded |
| `wires2d/elstatics_weight.result` | the Ramo weighting solve, one cathode at 1 V |
| `wires2d/mesh.*`, `wires2d/entities.sif` | the ElmerGrid mesh these were solved on |
| `wires2d.sif`, `wires2d_weight.sif` | the two solver input files |
| `wires_gmsh2d_bc.py` | regenerates `wires2d.msh` for this slice |
| `geometry.py`, `probe_fields.py` | the wire placement and the node-recipe probe |
| `elmer*.log`, `elmergrid.log` | what the run did |
| `anode_dot.csv` | the probe output, 24 anodes |
A `.result` is only nodal values; it is unreadable without the mesh it was solved on, which
is why `mesh.*` is here and must stay with it.
**Not kept.** The two `elstatics*.ep` (3.4 GB) are the same solutions in the legacy
ElmerPost format, and `wires2d.msh` (805 MB) is the Gmsh source that `mesh.*` was built
from and that `wires_gmsh2d_bc.py` regenerates.
**The derived VTUs are elsewhere and are what the analysis actually reads:**
`../../wires2d/vtu_files/elfield_{anasen,weight}_14_44.7820.vtu`.
## Reusing it
To run another postprocessor without re-solving, point a sif at this mesh and restart from
the stored solution, roughly:
```
Header
Mesh DB "." "wires2d"
End
Simulation
Restart File = "elstatics.result"
Restart Position = 0
End
```
then add the solver you want. **Untested** -- nothing has been restarted from these files
yet, so treat the restart block as the documented shape rather than a verified recipe, and
check the first field it gives you against `anode_dot.csv` or the archived VTU before
trusting a new quantity.
One caveat carried over from the method doc: do **not** take the surface field from Elmer's
nodal `electric field` array. `FluxSolver` recovers the gradient element by element and
spikes on degenerate elements -- on these newer meshes about 960 nodes exceed 1e6 V/m where
the archived meshes have none. The potential is unaffected, and `surface_field.py` in the
analysis repo fits it harmonically instead.
## The two solves not kept
`~/anasen_fem_cfrac_2026-10-28` held z = 19.7940 (the crossover re-solve, the one whose
nodal recipe returned -1.5e12 against an archived -2.57e8) and
`~/anasen_fem_cfrac_2026-10-28_A` held z = 29.8500 (the quarter point). Both were deleted
with their folders; their VTUs are archived as indices 11 and 12.
The code that reads any of this is `scratch/cfrac_fem_pred_2026-10-28/` in the analysis
repository.

View File

@ -0,0 +1,25 @@
z_mm,anode,d_az_deg,nodes,dot_min,dot_mean,dist_mm
44.7820,0,-5.6250,130,-6.412815e+11,-5.725599e+09,5.5348
44.7820,23,9.3750,141,-5.458907e+09,-1.617053e+08,7.1402
44.7820,1,-20.6250,132,-1.758049e+11,-1.426033e+09,13.1093
44.7820,22,24.3750,143,-1.436395e+10,-1.844563e+08,15.2155
44.7820,2,-35.6250,131,-1.158139e+08,-5.139516e+06,21.5643
44.7820,21,39.3750,133,-1.207014e+09,-1.980558e+07,23.6626
44.7820,3,-50.6250,132,-9.650451e+07,-4.160549e+06,29.8353
44.7820,20,54.3750,133,-4.780039e+07,-2.048480e+06,31.8405
44.7820,4,-65.6250,139,-1.584056e+09,-2.129574e+07,37.6599
44.7820,19,69.3750,132,-8.705723e+10,-7.365464e+08,39.5258
44.7820,5,-80.6250,140,-3.382874e+07,-1.308908e+06,44.8708
44.7820,18,84.3750,130,-3.284999e+08,-3.913803e+06,46.5612
44.7820,6,-95.6250,132,-4.616585e+08,-4.135442e+06,51.3319
44.7820,17,99.3750,141,-2.609670e+07,-8.311030e+05,52.8159
44.7820,7,-110.6250,134,-4.229769e+10,-3.193509e+08,56.9264
44.7820,16,114.3750,139,-8.731587e+08,-6.853938e+06,58.1776
44.7820,8,-125.6250,129,-1.169752e+07,-4.738091e+05,61.5555
44.7820,15,129.3750,130,-2.620801e+09,-2.464152e+07,62.5521
44.7820,9,-140.6250,130,-2.939267e+10,-2.300810e+08,65.1382
44.7820,14,144.3750,133,-1.080835e+07,-4.263347e+05,65.8626
44.7820,10,-155.6250,134,-4.783587e+08,-3.926604e+06,67.6121
44.7820,13,159.3750,132,-5.782822e+08,-5.730639e+06,68.0518
44.7820,11,-170.6250,140,-2.061392e+07,-5.902745e+05,68.9342
44.7820,12,174.3750,130,-1.633216e+08,-2.588149e+06,69.0816
1 z_mm anode d_az_deg nodes dot_min dot_mean dist_mm
2 44.7820 0 -5.6250 130 -6.412815e+11 -5.725599e+09 5.5348
3 44.7820 23 9.3750 141 -5.458907e+09 -1.617053e+08 7.1402
4 44.7820 1 -20.6250 132 -1.758049e+11 -1.426033e+09 13.1093
5 44.7820 22 24.3750 143 -1.436395e+10 -1.844563e+08 15.2155
6 44.7820 2 -35.6250 131 -1.158139e+08 -5.139516e+06 21.5643
7 44.7820 21 39.3750 133 -1.207014e+09 -1.980558e+07 23.6626
8 44.7820 3 -50.6250 132 -9.650451e+07 -4.160549e+06 29.8353
9 44.7820 20 54.3750 133 -4.780039e+07 -2.048480e+06 31.8405
10 44.7820 4 -65.6250 139 -1.584056e+09 -2.129574e+07 37.6599
11 44.7820 19 69.3750 132 -8.705723e+10 -7.365464e+08 39.5258
12 44.7820 5 -80.6250 140 -3.382874e+07 -1.308908e+06 44.8708
13 44.7820 18 84.3750 130 -3.284999e+08 -3.913803e+06 46.5612
14 44.7820 6 -95.6250 132 -4.616585e+08 -4.135442e+06 51.3319
15 44.7820 17 99.3750 141 -2.609670e+07 -8.311030e+05 52.8159
16 44.7820 7 -110.6250 134 -4.229769e+10 -3.193509e+08 56.9264
17 44.7820 16 114.3750 139 -8.731587e+08 -6.853938e+06 58.1776
18 44.7820 8 -125.6250 129 -1.169752e+07 -4.738091e+05 61.5555
19 44.7820 15 129.3750 130 -2.620801e+09 -2.464152e+07 62.5521
20 44.7820 9 -140.6250 130 -2.939267e+10 -2.300810e+08 65.1382
21 44.7820 14 144.3750 133 -1.080835e+07 -4.263347e+05 65.8626
22 44.7820 10 -155.6250 134 -4.783587e+08 -3.926604e+06 67.6121
23 44.7820 13 159.3750 132 -5.782822e+08 -5.730639e+06 68.0518
24 44.7820 11 -170.6250 140 -2.061392e+07 -5.902745e+05 68.9342
25 44.7820 12 174.3750 130 -1.633216e+08 -2.588149e+06 69.0816

View File

@ -0,0 +1,882 @@
ELMER SOLVER (v 26.2) STARTED AT: 2026/10/28 09:45:04
ParCommInit: Initialize #PEs: 1
MAIN: OMP_NUM_THREADS not set. Using only 1 thread per task.
MAIN:
MAIN: =============================================================
MAIN: ElmerSolver finite element software, Welcome!
MAIN: This program is free software licensed under (L)GPL
MAIN: Copyright April 1st 1995 -> , CSC - IT Center for Science Ltd.
MAIN: Repository https://www.github.org/elmerCSC/elmerfem
MAIN: Homepage https://www.elmerfem.org/, Email elmeradm@csc.fi
MAIN: Version: 26.2-unknown (Rev: unknown, Compiled: 2026-10-16)
MAIN: Running one task without MPI parallelization.
MAIN: Running with just one thread per task.
MAIN: HYPRE library linked in.
MAIN: MUMPS library linked in.
MAIN: Lua interpreter linked in.
MAIN: =============================================================
MAIN:
MAIN:
MAIN: -------------------------------------
MAIN: Reading Model: wires2d.sif
LoadInputFile: Scanning input file: wires2d.sif
LoadInputFile: Scanning only size info
LoadInputFile: First time visiting
LoadInputFile: Reading base load of sif file
LoadInputFile: Loading input file: wires2d.sif
LoadInputFile: Reading base load of sif file
CheckKeyword: Unlisted keyword: [calculate vectors] in section: [solver 1]
CheckKeyword: Unlisted keyword: [calculate vectors] in section: [solver 2]
LoadInputFile: Number of BCs: 7
LoadInputFile: Number of Body Forces: 0
LoadInputFile: Number of Initial Conditions: 0
LoadInputFile: Number of Materials: 1
LoadInputFile: Number of Equations: 1
LoadInputFile: Number of Solvers: 4
LoadInputFile: Number of Bodies: 1
Loading user function library: [StatElecSolve]...[StatElecSolver_Init0]
Loading user function library: [FluxSolver]...[FluxSolver_Init0]
Loading user function library: [FluxSolver]...[FluxSolver_Init0]
Loading user function library: [ResultOutputSolve]...[ResultOutputSolver_Init0]
Loading user function library: [ResultOutputSolve]...[ResultOutputSolver_Init0]
Loading user function library: [SaveData]...[SaveScalars_Init0]
Loading user function library: [SaveData]...[SaveScalars_Init0]
LoadMesh: Loading serial mesh!
ElmerMeshReader: Performing step: 1
ElmerMeshReader: Base mesh name: ./wires2d
ReadHeaderFile: Reading header info from file: ./wires2d/mesh.header
InitializeMesh: Number of nodes in mesh: 10765324
InitializeMesh: Number of bulk elements in mesh: 5382347
InitializeMesh: Number of boundary elements in mesh: 4640
InitializeMesh: Initial number of max element nodes: 6
ElmerMeshReader: Performing step: 2
ReadNodesFile: Reading nodes from file: ./wires2d/mesh.nodes
SetMeshDimension: Dimension of mesh is: 2
SetMeshDimension: Max dimension of mesh is: 2
MapCoordinates: Scaling coordinates: 1.000E-03 1.000E-03 1.000E+00
ElmerMeshReader: Performing step: 3
ReadElementsFile: Reading bulk elements from file: ./wires2d/mesh.elements
ElmerMeshReader: Performing step: 4
ReadBoundaryFile: Reading boundary elements from file: ./wires2d/mesh.boundary
PermuteNodeNumbering: Performing node mapping
MapBodiesAndBCs: Remapping bodies
MapBodiesAndBCs: Minimum initial body index: 13
MapBodiesAndBCs: Maximum initial body index: 13
MapBodiesAndBCs: Remapping boundaries
MapBodiesAndBCs: Minimum initial boundary index: 1
MapBodiesAndBCs: Maximum initial boundary index: 40
ElmerMeshReader: Performing step: 5
ElmerMeshReader: Performing step: 6
LoadMesh: Loading mesh done
PrepareMesh: Preparing mesh for the future
LoadMesh: Preparing mesh done
LoadMesh: Elapsed REAL time: 14.8571 (s)
MeshStabParams: Computing stabilization parameters
MeshStabParams: Elapsed REAL time: 2.2454 (s)
MAIN: -------------------------------------
AddMeshCoordinates: Setting mesh coordinates and time
AddSolvers: Setting up 4 solvers
AddSolvers: Setting up solver 1: electrostatics
AddEquationBasics: Using procedure: StatElecSolve StatElecSolver
AddEquationBasics: Setting up solver: electrostatics
Loading user function library: [StatElecSolve]...[StatElecSolver_Init]
Loading user function library: [StatElecSolve]...[StatElecSolver_bulk]
Loading user function library: [StatElecSolve]...[StatElecSolver_bulk]
Loading user function library: [StatElecSolve]...[StatElecSolver]
AddEquationBasics: Creating standard variable: potential
OptimizeBandwidth: ---------------------------------------------------------
OptimizeBandwidth: Computing matrix structure for: electrostatics
OptimizeBandwidth: Initial bandwidth for electrostatics: 10752353
OptimizeBandwidth: Optimized bandwidth for electrostatics: 20840
OptimizeBandwidth: ---------------------------------------------------------
AddSolvers: Setting up solver 2: electric field
AddEquationBasics: Using procedure: FluxSolver FluxSolver
AddEquationBasics: Setting up solver: electric field
Loading user function library: [FluxSolver]...[FluxSolver_Init]
WARNING:: FluxSolver_init: No field computation requested, setting > Calculate Flux = True <
FluxSolver_init: Saving flux to: electric field Flux
Loading user function library: [FluxSolver]...[FluxSolver_bulk]
Loading user function library: [FluxSolver]...[FluxSolver_bulk]
Loading user function library: [FluxSolver]...[FluxSolver]
AddEquationBasics: Creating standard variable: electric field_temp
OptimizeBandwidth: ---------------------------------------------------------
OptimizeBandwidth: Computing matrix structure for: electric field
OptimizeBandwidth: Initial bandwidth for electric field: 10752353
OptimizeBandwidth: Optimized bandwidth for electric field: 20840
OptimizeBandwidth: ---------------------------------------------------------
AddSolvers: Setting up solver 3: result output
AddEquationBasics: Using procedure: ResultOutputSolve ResultOutputSolver
AddEquationBasics: Setting up solver: result output
Loading user function library: [ResultOutputSolve]...[ResultOutputSolver_Init]
Loading user function library: [ResultOutputSolve]...[ResultOutputSolver_Init]
Loading user function library: [ResultOutputSolve]...[ResultOutputSolver_bulk]
Loading user function library: [ResultOutputSolve]...[ResultOutputSolver_bulk]
Loading user function library: [ResultOutputSolve]...[ResultOutputSolver]
AddSolvers: Setting up solver 4: savescalars
AddEquationBasics: Using procedure: SaveData SaveScalars
AddEquationBasics: Setting up solver: savescalars
Loading user function library: [SaveData]...[SaveScalars_Init]
Loading user function library: [SaveData]...[SaveScalars_bulk]
Loading user function library: [SaveData]...[SaveScalars_bulk]
Loading user function library: [SaveData]...[SaveScalars]
AddTimeEtc: Setting time and other global variables
MAIN: Random seed initialized to: 314159265
SetInitialConditions: Setting up initial conditions (if any)
MAIN: Number of timesteps to be saved: 1
MAIN:
MAIN: -------------------------------------
MAIN: Steady state iteration: 1
MAIN: -------------------------------------
MAIN:
SingleSolver: Attempting to call solver: 1
SingleSolver: Solver Equation string is: electrostatics
StatElecSolver: ------------------------------------------------
StatElecSolver: Solving static electric field for insulators
StatElecSolver: Permittivity Of Vacuum = 8.854200E-12 (set explicitly in Constants)
DefaultStart: Starting solver: electrostatics
StatElecSolver: Performing non-vectorized bulk element assembly
StatElecSolverBulkAssembly: Elapsed REAL time: 36.5859 (s)
StatElecSolver: Assembly of boundary colour: 1
StatElecSolverBCAssembly: Elapsed REAL time: 0.0105 (s)
DefUtils::DefaultDirichletBCs: Setting Dirichlet boundary conditions
EnforceDirichletConditions: Enforcing total of 9280 Dirichlet conditions.
EnforceDirichletConditions: Applying Dirichlet conditions using scaled diagonal
ScaleLinearSystem: Creating > DiagScaling < vector of size 10765324
EnforceDirichletConditions: Dirichlet conditions enforced for dofs: 9280
DefaultSolve: Solving linear system with default routines
SolveSystem: Solving linear system
SolveLinearSystem: Assuming real valued linear system
SolveLinearSystem: Assuming serial linear system
ScaleLinearSystem: Creating > DiagScaling < vector of size 10765324
SolveLinearSystem: Linear System Preconditioning: ilu1
Prec0-ilu1: Elapsed REAL time: 0.0000 (s)
SolveLinearSystem: Serial linear System Solver: iterative
IterSolver: Using iterative method: cg
CRS_IncompleteLU: ILU(1) (Real), Performing Factorization:
CRS_IncompleteLU: Allocated LU matrix of size: 166400948
CRS_IncompleteLU: ILU(1) (Real), NOF nonzeros: 166400949
CRS_IncompleteLU: ILU(1) (Real), filling (%) : 134
CRS_IncompleteLU: ILU(1) (Real), Factorization time (s): 138.74
Prec-ilu1: Elapsed REAL time: 140.1287 (s)
1 0.5172E-01
2 0.3192E-01
3 0.1702E-01
4 0.1569E-01
5 0.1354E-01
6 0.9474E-02
7 0.8213E-02
8 0.8654E-02
9 0.6712E-02
10 0.6050E-02
11 0.6148E-02
12 0.5694E-02
13 0.5034E-02
14 0.5168E-02
15 0.4545E-02
16 0.4294E-02
17 0.4379E-02
18 0.3858E-02
19 0.3862E-02
20 0.3584E-02
21 0.3621E-02
22 0.3427E-02
23 0.3170E-02
24 0.3229E-02
25 0.3071E-02
26 0.2964E-02
27 0.2841E-02
28 0.2858E-02
29 0.2645E-02
30 0.2672E-02
31 0.2653E-02
32 0.2487E-02
33 0.2752E-02
34 0.2822E-02
35 0.2758E-02
36 0.2776E-02
37 0.2805E-02
38 0.2816E-02
39 0.2790E-02
40 0.2836E-02
41 0.2657E-02
42 0.2651E-02
43 0.2584E-02
44 0.2344E-02
45 0.2349E-02
46 0.2205E-02
47 0.2061E-02
48 0.1952E-02
49 0.1829E-02
50 0.1658E-02
51 0.1527E-02
52 0.1386E-02
53 0.1277E-02
54 0.1204E-02
55 0.1155E-02
56 0.1113E-02
57 0.1054E-02
58 0.1042E-02
59 0.9941E-03
60 0.9639E-03
61 0.9270E-03
62 0.8842E-03
63 0.8413E-03
64 0.7933E-03
65 0.7752E-03
66 0.7361E-03
67 0.7361E-03
68 0.7591E-03
69 0.7607E-03
70 0.7468E-03
71 0.7440E-03
72 0.7360E-03
73 0.7291E-03
74 0.7464E-03
75 0.7439E-03
76 0.7427E-03
77 0.7624E-03
78 0.7009E-03
79 0.6600E-03
80 0.6430E-03
81 0.6288E-03
82 0.6356E-03
83 0.6202E-03
84 0.6244E-03
85 0.5867E-03
86 0.5818E-03
87 0.5810E-03
88 0.5603E-03
89 0.5571E-03
90 0.5426E-03
91 0.5299E-03
92 0.5102E-03
93 0.4973E-03
94 0.4622E-03
95 0.4439E-03
96 0.4280E-03
97 0.4018E-03
98 0.3901E-03
99 0.3892E-03
100 0.3840E-03
101 0.3793E-03
102 0.3835E-03
103 0.3772E-03
104 0.3647E-03
105 0.3575E-03
106 0.3587E-03
107 0.3451E-03
108 0.3367E-03
109 0.3212E-03
110 0.3115E-03
111 0.3188E-03
112 0.3121E-03
113 0.3195E-03
114 0.3252E-03
115 0.3149E-03
116 0.3174E-03
117 0.3179E-03
118 0.3160E-03
119 0.3191E-03
120 0.3149E-03
121 0.3124E-03
122 0.3108E-03
123 0.3016E-03
124 0.2954E-03
125 0.2855E-03
126 0.2828E-03
127 0.2789E-03
128 0.2746E-03
129 0.2694E-03
130 0.2649E-03
131 0.2661E-03
132 0.2657E-03
133 0.2620E-03
134 0.2647E-03
135 0.2522E-03
136 0.2455E-03
137 0.2455E-03
138 0.2382E-03
139 0.2432E-03
140 0.2435E-03
141 0.2373E-03
142 0.2273E-03
143 0.2231E-03
144 0.2193E-03
145 0.2112E-03
146 0.2098E-03
147 0.2005E-03
148 0.2007E-03
149 0.2054E-03
150 0.2037E-03
151 0.2056E-03
152 0.2072E-03
153 0.2042E-03
154 0.2045E-03
155 0.2005E-03
156 0.1972E-03
157 0.1976E-03
158 0.2005E-03
159 0.2008E-03
160 0.1999E-03
161 0.2004E-03
162 0.1953E-03
163 0.1994E-03
164 0.2010E-03
165 0.2006E-03
166 0.2048E-03
167 0.2037E-03
168 0.2013E-03
169 0.2080E-03
170 0.2157E-03
171 0.2211E-03
172 0.2350E-03
173 0.2415E-03
174 0.2441E-03
175 0.2537E-03
176 0.2495E-03
177 0.2456E-03
178 0.2418E-03
179 0.2357E-03
180 0.2435E-03
181 0.2473E-03
182 0.2469E-03
183 0.2528E-03
184 0.2624E-03
185 0.2689E-03
186 0.2826E-03
187 0.2889E-03
188 0.2885E-03
189 0.2801E-03
190 0.2710E-03
191 0.2572E-03
192 0.2426E-03
193 0.2335E-03
194 0.2160E-03
195 0.2092E-03
196 0.2009E-03
197 0.1903E-03
198 0.1828E-03
199 0.1794E-03
200 0.1756E-03
201 0.1720E-03
202 0.1695E-03
203 0.1654E-03
204 0.1661E-03
205 0.1603E-03
206 0.1628E-03
207 0.1611E-03
208 0.1549E-03
209 0.1565E-03
210 0.1486E-03
211 0.1435E-03
212 0.1398E-03
213 0.1396E-03
214 0.1366E-03
215 0.1335E-03
216 0.1321E-03
217 0.1246E-03
218 0.1198E-03
219 0.1204E-03
220 0.1138E-03
221 0.1090E-03
222 0.1034E-03
223 0.9468E-04
224 0.9266E-04
225 0.8576E-04
226 0.7893E-04
227 0.7359E-04
228 0.6808E-04
229 0.6388E-04
230 0.6011E-04
231 0.5877E-04
232 0.5606E-04
233 0.5455E-04
234 0.5174E-04
235 0.4767E-04
236 0.4525E-04
237 0.4171E-04
238 0.3901E-04
239 0.3639E-04
240 0.3337E-04
241 0.3038E-04
242 0.2799E-04
243 0.2596E-04
244 0.2393E-04
245 0.2196E-04
246 0.2113E-04
247 0.1927E-04
248 0.1864E-04
249 0.1777E-04
250 0.1691E-04
251 0.1672E-04
252 0.1594E-04
253 0.1642E-04
254 0.1632E-04
255 0.1653E-04
256 0.1694E-04
257 0.1664E-04
258 0.1700E-04
259 0.1735E-04
260 0.1815E-04
261 0.1854E-04
262 0.1923E-04
263 0.1992E-04
264 0.1991E-04
265 0.2005E-04
266 0.1997E-04
267 0.1951E-04
268 0.2002E-04
269 0.1962E-04
270 0.1926E-04
271 0.1930E-04
272 0.1905E-04
273 0.1829E-04
274 0.1788E-04
275 0.1756E-04
276 0.1620E-04
277 0.1593E-04
278 0.1522E-04
279 0.1436E-04
280 0.1377E-04
281 0.1306E-04
282 0.1223E-04
283 0.1160E-04
284 0.1139E-04
285 0.1078E-04
286 0.1045E-04
287 0.1013E-04
288 0.9520E-05
289 0.9212E-05
290 0.8741E-05
291 0.8203E-05
292 0.8022E-05
293 0.7667E-05
294 0.7470E-05
295 0.7248E-05
296 0.7022E-05
297 0.6742E-05
298 0.6720E-05
299 0.6815E-05
300 0.6660E-05
301 0.6836E-05
302 0.6908E-05
303 0.6974E-05
304 0.7180E-05
305 0.7073E-05
306 0.6915E-05
307 0.6740E-05
308 0.6415E-05
309 0.6102E-05
310 0.5770E-05
311 0.5686E-05
312 0.5387E-05
313 0.5211E-05
314 0.5043E-05
315 0.4706E-05
316 0.4623E-05
317 0.4378E-05
318 0.4283E-05
319 0.4133E-05
320 0.4085E-05
321 0.4008E-05
322 0.3902E-05
323 0.3891E-05
324 0.3748E-05
325 0.3646E-05
326 0.3551E-05
327 0.3417E-05
328 0.3325E-05
329 0.3249E-05
330 0.3169E-05
331 0.3204E-05
332 0.3166E-05
333 0.3261E-05
334 0.3211E-05
335 0.3182E-05
336 0.3155E-05
337 0.3024E-05
338 0.3023E-05
339 0.3015E-05
340 0.3075E-05
341 0.3042E-05
342 0.3035E-05
343 0.2991E-05
344 0.2849E-05
345 0.2812E-05
346 0.2779E-05
347 0.2680E-05
348 0.2695E-05
349 0.2582E-05
350 0.2558E-05
351 0.2516E-05
352 0.2553E-05
353 0.2623E-05
354 0.2665E-05
355 0.2810E-05
356 0.2823E-05
357 0.2862E-05
358 0.2853E-05
359 0.2832E-05
360 0.2807E-05
361 0.2802E-05
362 0.2856E-05
363 0.2891E-05
364 0.2961E-05
365 0.3042E-05
366 0.3028E-05
367 0.3007E-05
368 0.3066E-05
369 0.3060E-05
370 0.3090E-05
371 0.3190E-05
372 0.3258E-05
373 0.3281E-05
374 0.3376E-05
375 0.3417E-05
376 0.3450E-05
377 0.3519E-05
378 0.3671E-05
379 0.3709E-05
380 0.3866E-05
381 0.4009E-05
382 0.3943E-05
383 0.4004E-05
384 0.3990E-05
385 0.3910E-05
386 0.3802E-05
387 0.3890E-05
388 0.3881E-05
389 0.3836E-05
390 0.3940E-05
391 0.3852E-05
392 0.3780E-05
393 0.3753E-05
394 0.3604E-05
395 0.3480E-05
396 0.3413E-05
397 0.3324E-05
398 0.3141E-05
399 0.3027E-05
400 0.2903E-05
401 0.2676E-05
402 0.2616E-05
403 0.2466E-05
404 0.2343E-05
405 0.2266E-05
406 0.2139E-05
407 0.2114E-05
408 0.2030E-05
409 0.1966E-05
410 0.1930E-05
411 0.1874E-05
412 0.1855E-05
413 0.1816E-05
414 0.1788E-05
415 0.1745E-05
416 0.1711E-05
417 0.1658E-05
418 0.1582E-05
419 0.1508E-05
420 0.1452E-05
421 0.1362E-05
422 0.1315E-05
423 0.1245E-05
424 0.1160E-05
425 0.1117E-05
426 0.1079E-05
427 0.1025E-05
428 0.1001E-05
429 0.9631E-06
430 0.9010E-06
431 0.8597E-06
432 0.8010E-06
433 0.7747E-06
434 0.7154E-06
435 0.6968E-06
436 0.6646E-06
437 0.6298E-06
438 0.6218E-06
439 0.5918E-06
440 0.5737E-06
441 0.5500E-06
442 0.5251E-06
443 0.4873E-06
444 0.4520E-06
445 0.4233E-06
446 0.3871E-06
447 0.3631E-06
448 0.3453E-06
449 0.3301E-06
450 0.3120E-06
451 0.3080E-06
452 0.2969E-06
453 0.2803E-06
454 0.2861E-06
455 0.2749E-06
456 0.2684E-06
457 0.2676E-06
458 0.2568E-06
459 0.2429E-06
460 0.2292E-06
461 0.2185E-06
462 0.1988E-06
463 0.1884E-06
464 0.1804E-06
465 0.1670E-06
466 0.1619E-06
467 0.1591E-06
468 0.1488E-06
469 0.1475E-06
470 0.1429E-06
471 0.1349E-06
472 0.1291E-06
473 0.1242E-06
474 0.1158E-06
475 0.1104E-06
476 0.1085E-06
477 0.9961E-07
478 0.9807E-07
479 0.9746E-07
480 0.9440E-07
481 0.9385E-07
482 0.9377E-07
483 0.9279E-07
484 0.9273E-07
485 0.9531E-07
486 0.9486E-07
487 0.9594E-07
488 0.9592E-07
489 0.9332E-07
490 0.9167E-07
491 0.8922E-07
492 0.8511E-07
493 0.8275E-07
494 0.7953E-07
495 0.7692E-07
496 0.7188E-07
497 0.6766E-07
498 0.6320E-07
499 0.5887E-07
500 0.5709E-07
501 0.5463E-07
502 0.5333E-07
503 0.5198E-07
504 0.5050E-07
505 0.4883E-07
506 0.4720E-07
507 0.4553E-07
508 0.4394E-07
509 0.4276E-07
510 0.4206E-07
511 0.4127E-07
512 0.4067E-07
513 0.4105E-07
514 0.4071E-07
515 0.4055E-07
516 0.4066E-07
517 0.4049E-07
518 0.3995E-07
519 0.3898E-07
520 0.3834E-07
521 0.3729E-07
522 0.3670E-07
523 0.3605E-07
524 0.3475E-07
525 0.3418E-07
526 0.3314E-07
527 0.3292E-07
528 0.3201E-07
529 0.3158E-07
530 0.3051E-07
531 0.2995E-07
532 0.3013E-07
533 0.2955E-07
534 0.2995E-07
535 0.2998E-07
536 0.2908E-07
537 0.2865E-07
538 0.2778E-07
539 0.2681E-07
540 0.2558E-07
541 0.2462E-07
542 0.2356E-07
543 0.2240E-07
544 0.2211E-07
545 0.2119E-07
546 0.2052E-07
547 0.1985E-07
548 0.1906E-07
549 0.1837E-07
550 0.1782E-07
551 0.1767E-07
552 0.1713E-07
553 0.1701E-07
554 0.1664E-07
555 0.1636E-07
556 0.1649E-07
557 0.1621E-07
558 0.1605E-07
559 0.1615E-07
560 0.1583E-07
561 0.1570E-07
562 0.1555E-07
563 0.1550E-07
564 0.1497E-07
565 0.1463E-07
566 0.1436E-07
567 0.1379E-07
568 0.1379E-07
569 0.1358E-07
570 0.1338E-07
571 0.1323E-07
572 0.1312E-07
573 0.1275E-07
574 0.1290E-07
575 0.1265E-07
576 0.1231E-07
577 0.1212E-07
578 0.1192E-07
579 0.1169E-07
580 0.1120E-07
581 0.1095E-07
582 0.1039E-07
583 0.9830E-08
583 0.9830E-08
ComputeNorm: Computing norm of solution
ComputeChange: NS (ITER=1) (NRM,RELC): ( 188.93505 2.0000000 ) :: electrostatics
DefaultFinish: Finished solver: electrostatics
Loading user function library: [StatElecSolve]...[StatElecSolver_post]
SingleSolver: Calling solver for postprocessing
StatElecSolver_post: ------------------------------------------------
StatElecSolver_post: Calculating postprocessing fields
StatElecSolver_post: Number of 1 postprocessing fields
StatElecSolver_post: Creating a weighting for scaling purposes from 7
StatElecSolver_post: Total Electric Energy: 8.120E-05
StatElecSolver_post: Effective Capacitance: 2.196E-10
ComputeNorm: Computing norm of solution
ComputeChange: SS (ITER=1) (NRM,RELC): ( 188.93505 2.0000000 ) :: electrostatics
SingleSolver: Attempting to call solver: 2
SingleSolver: Solver Equation string is: electric field
FluxSolver: -------------------------------------
FluxSolver: Computing the flux and/or gradient
FluxSolver: -------------------------------------
FluxSolver: Assembly Time: 55.003733873367310
DefaultSolve: Solving linear system with default routines
SolveSystem: Solving linear system
SolveLinearSystem: Assuming real valued linear system
SolveLinearSystem: Assuming serial linear system
ScaleLinearSystem: Creating > DiagScaling < vector of size 10765324
SolveLinearSystem: Linear System Preconditioning: ilu0
Prec0-ilu0: Elapsed REAL time: 0.0001 (s)
SolveLinearSystem: Serial linear System Solver: iterative
IterSolver: Using iterative method: cg
CRS_IncompleteLU: ILU(0) (Real), Performing Factorization:
CRS_IncompleteLU: Allocated LU matrix of size: 123796498
CRS_IncompleteLU: ILU(0) (Real), NOF nonzeros: 123796499
CRS_IncompleteLU: ILU(0) (Real), filling (%) : 100
CRS_IncompleteLU: ILU(0) (Real), Factorization time (s): 2.91
Prec-ilu0: Elapsed REAL time: 5.4674 (s)
10 0.9294E-10
10 0.9294E-10
DefaultSolve: Solving linear system with default routines
SolveSystem: Solving linear system
SolveLinearSystem: Assuming real valued linear system
SolveLinearSystem: Assuming serial linear system
ScaleLinearSystem: Creating > DiagScaling < vector of size 10765324
SolveLinearSystem: Linear System Preconditioning: ilu0
Prec0-ilu0: Elapsed REAL time: 0.0000 (s)
SolveLinearSystem: Serial linear System Solver: iterative
IterSolver: Using iterative method: cg
CRS_IncompleteLU: ILU(0) (Real), Performing Factorization:
CRS_IncompleteLU: ILU(0) (Real), NOF nonzeros: 123796499
CRS_IncompleteLU: ILU(0) (Real), filling (%) : 100
CRS_IncompleteLU: ILU(0) (Real), Factorization time (s): 1.85
Prec-ilu0: Elapsed REAL time: 1.9819 (s)
10 0.1357E-09
11 0.1565E-10
ComputeNorm: Computing norm of solution
ComputeChange: NS (ITER=2) (NRM,RELC): ( 0.97469715E+10 2.0000000 ) :: electric field
FluxSolver: Solution Time: 28.676481008529663
FluxSolver: Result Norm: 9746971516.0560455
FluxSolver: All done
FluxSolver: -------------------------------------
ComputeNorm: Computing norm of solution
ComputeChange: SS (ITER=1) (NRM,RELC): ( 0.97469715E+10 2.0000000 ) :: electric field
SingleSolver: Attempting to call solver: 3
SingleSolver: Solver Equation string is: result output
ResultOutputSolver: -------------------------------------
ResultOutputSolver: Saving with prefix: elfield_anasen
ResultOutputSolver: Dimension of mesh is: 2
ResultOutputSolver: Working on mesh 1: wires2d with 10765324 nodes
ResultOutputSolver: Creating list for saving - if not present
CreateListForSaving: Scalar Field 1 exists, creating no list!
ResultOutputSolver: Saving in unstructured VTK XML (.vtu) format
VtuOutputSolver: Saving results in VTK XML format with prefix: elfield_anasen
VtuOutputSolver: Saving number of partitions: 1
SolverOutputDirectory: Creating directory: wires2d
VtuOutputSolver: Full filename base is: wires2d/elfield_anasen
GenerateSaveMask: Number of active elements 5386987 out of 5386987
GenerateSaveMask: Number of geometry nodes 10765324 out of 10765324
VtuOutputSolver: Total number of elements to save: 5386987
VtuOutputSolver: Total number of geometry nodes to save: 10765324
VtuOutputSolver: Total number of dof nodes to save: 10765324
VtuOutputSolver: Writing the vtu file: wires2d/elfield_anasen_t0001.vtu
AscBinWriteInit: Initializing buffered ascii/binary writing
AscBinWriteInit: Writing in binary
AscBinWriteInit: Writing in double precision
AscBinWriteInit: Writing to unit number: 58
AscBinWriteInit: Size of buffer is: 10765324
WriteVtuFile: Writing nodal fields
WriteVtuFile: Saving 1-component variable: potential
WriteVtuFile: Saving 2-component variable: electric field
WriteVtuFile: Number of nodal fields written: 2
WriteVtuFile: Writing elemental fields
WriteVtuFile: Number of elemental fields written: 0
WriteVtuFile: Writing coordinates for each used node
WriteVtuFile: Writing the elemental connectivity data
WriteVtuFile: Writing nodal fields
WriteVtuFile: Saving 1-component variable: potential
WriteVtuFile: Saving 2-component variable: electric field
WriteVtuFile: Writing elemental fields
AscBinWriteFree: Terminating buffered ascii/binary writing
VtuOutputSolver: All done for now
ResultOutputSolver: -------------------------------------
WritePostFile: Saving results in ElmerPost format to file wires2d/elstatics.ep
SaveResult: -----------------------------------------
SaveResult: Saving results to file: wires2d/elstatics.result
SaveResult: Done writing results file
SaveResult: -----------------------------------------
SingleSolver: Attempting to call solver: 4
SingleSolver: Solver Equation string is: savescalars
SaveScalars: -----------------------------------------
SaveScalars: Saving scalar values of various kinds
SaveScalars: Performed 0 reduction operations
SaveScalars: Found 3 result scalars in simulation section
SaveScalars: Found 3 values to save in total
SaveScalars: Saving names of values to file: ./scalars.dat.names
SaveScalars: Saving values to file: ./scalars.dat
SaveScalars: -----------------------------------------
ReloadInputFile: Reloading input file
LoadInputFile: Loading input file:
MAIN: Reporting unused list entries for sif improvement!
MAIN: If you do not want these lines undefine > DEVEL_LISTUSAGE < !
Unused keywords:
Boundary Condition 1 calculate electric force
Boundary Condition 2 calculate electric force
Boundary Condition 3 calculate electric force
Boundary Condition 5 calculate electric force
Solver 1 calculate vectors
Solver 2 target variable
Solver 2 calculate vectors
MAIN: *** Elmer Solver: ALL DONE ***
Loading user function library: [StatElecSolve]...[StatElecSolver_Finalize]
Loading user function library: [StatElecSolve]...[StatElecSolver_Finalize]
Loading user function library: [FluxSolver]...[FluxSolver_Finalize]
Loading user function library: [FluxSolver]...[FluxSolver_Finalize]
Loading user function library: [ResultOutputSolve]...[ResultOutputSolver_Finalize]
Loading user function library: [ResultOutputSolve]...[ResultOutputSolver_Finalize]
Loading user function library: [SaveData]...[SaveScalars_Finalize]
Loading user function library: [SaveData]...[SaveScalars_Finalize]
MAIN: The end
SOLVER TOTAL TIME(CPU,REAL): 1293.09 1396.16
ELMER SOLVER FINISHED AT: 2026/10/28 10:08:20

View File

@ -0,0 +1,897 @@
ELMER SOLVER (v 26.2) STARTED AT: 2026/10/28 10:08:23
ParCommInit: Initialize #PEs: 1
MAIN: OMP_NUM_THREADS not set. Using only 1 thread per task.
MAIN:
MAIN: =============================================================
MAIN: ElmerSolver finite element software, Welcome!
MAIN: This program is free software licensed under (L)GPL
MAIN: Copyright April 1st 1995 -> , CSC - IT Center for Science Ltd.
MAIN: Repository https://www.github.org/elmerCSC/elmerfem
MAIN: Homepage https://www.elmerfem.org/, Email elmeradm@csc.fi
MAIN: Version: 26.2-unknown (Rev: unknown, Compiled: 2026-10-16)
MAIN: Running one task without MPI parallelization.
MAIN: Running with just one thread per task.
MAIN: HYPRE library linked in.
MAIN: MUMPS library linked in.
MAIN: Lua interpreter linked in.
MAIN: =============================================================
MAIN:
MAIN:
MAIN: -------------------------------------
MAIN: Reading Model: wires2d_weight.sif
LoadInputFile: Scanning input file: wires2d_weight.sif
LoadInputFile: Scanning only size info
LoadInputFile: First time visiting
LoadInputFile: Reading base load of sif file
LoadInputFile: Loading input file: wires2d_weight.sif
LoadInputFile: Reading base load of sif file
CheckKeyword: Unlisted keyword: [calculate vectors] in section: [solver 1]
CheckKeyword: Unlisted keyword: [calculate vectors] in section: [solver 2]
LoadInputFile: Number of BCs: 7
LoadInputFile: Number of Body Forces: 0
LoadInputFile: Number of Initial Conditions: 0
LoadInputFile: Number of Materials: 1
LoadInputFile: Number of Equations: 1
LoadInputFile: Number of Solvers: 4
LoadInputFile: Number of Bodies: 1
Loading user function library: [StatElecSolve]...[StatElecSolver_Init0]
Loading user function library: [FluxSolver]...[FluxSolver_Init0]
Loading user function library: [FluxSolver]...[FluxSolver_Init0]
Loading user function library: [ResultOutputSolve]...[ResultOutputSolver_Init0]
Loading user function library: [ResultOutputSolve]...[ResultOutputSolver_Init0]
Loading user function library: [SaveData]...[SaveScalars_Init0]
Loading user function library: [SaveData]...[SaveScalars_Init0]
LoadMesh: Loading serial mesh!
ElmerMeshReader: Performing step: 1
ElmerMeshReader: Base mesh name: ./wires2d
ReadHeaderFile: Reading header info from file: ./wires2d/mesh.header
InitializeMesh: Number of nodes in mesh: 10765324
InitializeMesh: Number of bulk elements in mesh: 5382347
InitializeMesh: Number of boundary elements in mesh: 4640
InitializeMesh: Initial number of max element nodes: 6
ElmerMeshReader: Performing step: 2
ReadNodesFile: Reading nodes from file: ./wires2d/mesh.nodes
SetMeshDimension: Dimension of mesh is: 2
SetMeshDimension: Max dimension of mesh is: 2
MapCoordinates: Scaling coordinates: 1.000E-03 1.000E-03 1.000E+00
ElmerMeshReader: Performing step: 3
ReadElementsFile: Reading bulk elements from file: ./wires2d/mesh.elements
ElmerMeshReader: Performing step: 4
ReadBoundaryFile: Reading boundary elements from file: ./wires2d/mesh.boundary
PermuteNodeNumbering: Performing node mapping
MapBodiesAndBCs: Remapping bodies
MapBodiesAndBCs: Minimum initial body index: 13
MapBodiesAndBCs: Maximum initial body index: 13
MapBodiesAndBCs: Remapping boundaries
MapBodiesAndBCs: Minimum initial boundary index: 1
MapBodiesAndBCs: Maximum initial boundary index: 40
ElmerMeshReader: Performing step: 5
ElmerMeshReader: Performing step: 6
LoadMesh: Loading mesh done
PrepareMesh: Preparing mesh for the future
LoadMesh: Preparing mesh done
LoadMesh: Elapsed REAL time: 77.1926 (s)
MeshStabParams: Computing stabilization parameters
MeshStabParams: Elapsed REAL time: 4.1362 (s)
MAIN: -------------------------------------
AddMeshCoordinates: Setting mesh coordinates and time
AddSolvers: Setting up 4 solvers
AddSolvers: Setting up solver 1: electrostatics
AddEquationBasics: Using procedure: StatElecSolve StatElecSolver
AddEquationBasics: Setting up solver: electrostatics
Loading user function library: [StatElecSolve]...[StatElecSolver_Init]
Loading user function library: [StatElecSolve]...[StatElecSolver_bulk]
Loading user function library: [StatElecSolve]...[StatElecSolver_bulk]
Loading user function library: [StatElecSolve]...[StatElecSolver]
AddEquationBasics: Creating standard variable: potential
OptimizeBandwidth: ---------------------------------------------------------
OptimizeBandwidth: Computing matrix structure for: electrostatics
OptimizeBandwidth: Initial bandwidth for electrostatics: 10752353
OptimizeBandwidth: Optimized bandwidth for electrostatics: 20840
OptimizeBandwidth: ---------------------------------------------------------
AddSolvers: Setting up solver 2: electric field
AddEquationBasics: Using procedure: FluxSolver FluxSolver
AddEquationBasics: Setting up solver: electric field
Loading user function library: [FluxSolver]...[FluxSolver_Init]
WARNING:: FluxSolver_init: No field computation requested, setting > Calculate Flux = True <
FluxSolver_init: Saving flux to: electric field Flux
Loading user function library: [FluxSolver]...[FluxSolver_bulk]
Loading user function library: [FluxSolver]...[FluxSolver_bulk]
Loading user function library: [FluxSolver]...[FluxSolver]
AddEquationBasics: Creating standard variable: electric field_temp
OptimizeBandwidth: ---------------------------------------------------------
OptimizeBandwidth: Computing matrix structure for: electric field
OptimizeBandwidth: Initial bandwidth for electric field: 10752353
OptimizeBandwidth: Optimized bandwidth for electric field: 20840
OptimizeBandwidth: ---------------------------------------------------------
AddSolvers: Setting up solver 3: result output
AddEquationBasics: Using procedure: ResultOutputSolve ResultOutputSolver
AddEquationBasics: Setting up solver: result output
Loading user function library: [ResultOutputSolve]...[ResultOutputSolver_Init]
Loading user function library: [ResultOutputSolve]...[ResultOutputSolver_Init]
Loading user function library: [ResultOutputSolve]...[ResultOutputSolver_bulk]
Loading user function library: [ResultOutputSolve]...[ResultOutputSolver_bulk]
Loading user function library: [ResultOutputSolve]...[ResultOutputSolver]
AddSolvers: Setting up solver 4: savescalars
AddEquationBasics: Using procedure: SaveData SaveScalars
AddEquationBasics: Setting up solver: savescalars
Loading user function library: [SaveData]...[SaveScalars_Init]
Loading user function library: [SaveData]...[SaveScalars_bulk]
Loading user function library: [SaveData]...[SaveScalars_bulk]
Loading user function library: [SaveData]...[SaveScalars]
AddTimeEtc: Setting time and other global variables
MAIN: Random seed initialized to: 314159265
SetInitialConditions: Setting up initial conditions (if any)
MAIN: Number of timesteps to be saved: 1
MAIN:
MAIN: -------------------------------------
MAIN: Steady state iteration: 1
MAIN: -------------------------------------
MAIN:
SingleSolver: Attempting to call solver: 1
SingleSolver: Solver Equation string is: electrostatics
StatElecSolver: ------------------------------------------------
StatElecSolver: Solving static electric field for insulators
StatElecSolver: Permittivity Of Vacuum = 8.854200E-12 (set explicitly in Constants)
DefaultStart: Starting solver: electrostatics
StatElecSolver: Performing non-vectorized bulk element assembly
StatElecSolverBulkAssembly: Elapsed REAL time: 51.8520 (s)
StatElecSolver: Assembly of boundary colour: 1
StatElecSolverBCAssembly: Elapsed REAL time: 0.0198 (s)
DefUtils::DefaultDirichletBCs: Setting Dirichlet boundary conditions
EnforceDirichletConditions: Enforcing total of 9280 Dirichlet conditions.
EnforceDirichletConditions: Applying Dirichlet conditions using scaled diagonal
ScaleLinearSystem: Creating > DiagScaling < vector of size 10765324
EnforceDirichletConditions: Dirichlet conditions enforced for dofs: 9280
DefaultSolve: Solving linear system with default routines
SolveSystem: Solving linear system
SolveLinearSystem: Assuming real valued linear system
SolveLinearSystem: Assuming serial linear system
ScaleLinearSystem: Creating > DiagScaling < vector of size 10765324
SolveLinearSystem: Linear System Preconditioning: ilu1
Prec0-ilu1: Elapsed REAL time: 0.0000 (s)
SolveLinearSystem: Serial linear System Solver: iterative
IterSolver: Using iterative method: cg
CRS_IncompleteLU: ILU(1) (Real), Performing Factorization:
CRS_IncompleteLU: Allocated LU matrix of size: 166400948
CRS_IncompleteLU: ILU(1) (Real), NOF nonzeros: 166400949
CRS_IncompleteLU: ILU(1) (Real), filling (%) : 134
CRS_IncompleteLU: ILU(1) (Real), Factorization time (s): 144.70
Prec-ilu1: Elapsed REAL time: 144.2477 (s)
1 0.5347E-01
2 0.3328E-01
3 0.1924E-01
4 0.1481E-01
5 0.1439E-01
6 0.1095E-01
7 0.8128E-02
8 0.8861E-02
9 0.7366E-02
10 0.5646E-02
11 0.6125E-02
12 0.5680E-02
13 0.4608E-02
14 0.5164E-02
15 0.4180E-02
16 0.4191E-02
17 0.4135E-02
18 0.3766E-02
19 0.3636E-02
20 0.3383E-02
21 0.3640E-02
22 0.2877E-02
23 0.3296E-02
24 0.3014E-02
25 0.2586E-02
26 0.3012E-02
27 0.2732E-02
28 0.2580E-02
29 0.2592E-02
30 0.2634E-02
31 0.2402E-02
32 0.2337E-02
33 0.2333E-02
34 0.2288E-02
35 0.2159E-02
36 0.2161E-02
37 0.2118E-02
38 0.1982E-02
39 0.2037E-02
40 0.1904E-02
41 0.1910E-02
42 0.1837E-02
43 0.1789E-02
44 0.1713E-02
45 0.1642E-02
46 0.1752E-02
47 0.1474E-02
48 0.1612E-02
49 0.1601E-02
50 0.1360E-02
51 0.1403E-02
52 0.1318E-02
53 0.1258E-02
54 0.1253E-02
55 0.1211E-02
56 0.1154E-02
57 0.1126E-02
58 0.1050E-02
59 0.1024E-02
60 0.9542E-03
61 0.9713E-03
62 0.9079E-03
63 0.8562E-03
64 0.9112E-03
65 0.8001E-03
66 0.7960E-03
67 0.7864E-03
68 0.7477E-03
69 0.6913E-03
70 0.6955E-03
71 0.6548E-03
72 0.6144E-03
73 0.6377E-03
74 0.5784E-03
75 0.5479E-03
76 0.5419E-03
77 0.5080E-03
78 0.4891E-03
79 0.4966E-03
80 0.4592E-03
81 0.4495E-03
82 0.4433E-03
83 0.4142E-03
84 0.4067E-03
85 0.4097E-03
86 0.4061E-03
87 0.3725E-03
88 0.3917E-03
89 0.3857E-03
90 0.3596E-03
91 0.3725E-03
92 0.3573E-03
93 0.3453E-03
94 0.3398E-03
95 0.3423E-03
96 0.3117E-03
97 0.2955E-03
98 0.2988E-03
99 0.2779E-03
100 0.2713E-03
101 0.2686E-03
102 0.2528E-03
103 0.2623E-03
104 0.2608E-03
105 0.2365E-03
106 0.2498E-03
107 0.2369E-03
108 0.2279E-03
109 0.2193E-03
110 0.2139E-03
111 0.2121E-03
112 0.2028E-03
113 0.2030E-03
114 0.1943E-03
115 0.1868E-03
116 0.1858E-03
117 0.1810E-03
118 0.1724E-03
119 0.1669E-03
120 0.1707E-03
121 0.1616E-03
122 0.1630E-03
123 0.1641E-03
124 0.1615E-03
125 0.1531E-03
126 0.1580E-03
127 0.1535E-03
128 0.1505E-03
129 0.1457E-03
130 0.1459E-03
131 0.1503E-03
132 0.1429E-03
133 0.1424E-03
134 0.1387E-03
135 0.1378E-03
136 0.1336E-03
137 0.1281E-03
138 0.1307E-03
139 0.1291E-03
140 0.1213E-03
141 0.1235E-03
142 0.1262E-03
143 0.1202E-03
144 0.1200E-03
145 0.1216E-03
146 0.1148E-03
147 0.1112E-03
148 0.1130E-03
149 0.1102E-03
150 0.1052E-03
151 0.1057E-03
152 0.1017E-03
153 0.1061E-03
154 0.1021E-03
155 0.9918E-04
156 0.1041E-03
157 0.9991E-04
158 0.9982E-04
159 0.9954E-04
160 0.1023E-03
161 0.1002E-03
162 0.9694E-04
163 0.1000E-03
164 0.9766E-04
165 0.9679E-04
166 0.9694E-04
167 0.9222E-04
168 0.9434E-04
169 0.9274E-04
170 0.8896E-04
171 0.9257E-04
172 0.9057E-04
173 0.8984E-04
174 0.9194E-04
175 0.9215E-04
176 0.9206E-04
177 0.9011E-04
178 0.8920E-04
179 0.8822E-04
180 0.8819E-04
181 0.8434E-04
182 0.8724E-04
183 0.8690E-04
184 0.8368E-04
185 0.8410E-04
186 0.8546E-04
187 0.8308E-04
188 0.8511E-04
189 0.8511E-04
190 0.8252E-04
191 0.8361E-04
192 0.8306E-04
193 0.8293E-04
194 0.8164E-04
195 0.8151E-04
196 0.7998E-04
197 0.7898E-04
198 0.8071E-04
199 0.7827E-04
200 0.7854E-04
201 0.7897E-04
202 0.8053E-04
203 0.7724E-04
204 0.7795E-04
205 0.7749E-04
206 0.7667E-04
207 0.8003E-04
208 0.7836E-04
209 0.7533E-04
210 0.7639E-04
211 0.7463E-04
212 0.7297E-04
213 0.7311E-04
214 0.7148E-04
215 0.7277E-04
216 0.7063E-04
217 0.7210E-04
218 0.7044E-04
219 0.7116E-04
220 0.7107E-04
221 0.6990E-04
222 0.7070E-04
223 0.6931E-04
224 0.6757E-04
225 0.6630E-04
226 0.6642E-04
227 0.6550E-04
228 0.6404E-04
229 0.6327E-04
230 0.6178E-04
231 0.6057E-04
232 0.5921E-04
233 0.5852E-04
234 0.5681E-04
235 0.5642E-04
236 0.5626E-04
237 0.5609E-04
238 0.5491E-04
239 0.5562E-04
240 0.5611E-04
241 0.5551E-04
242 0.5410E-04
243 0.5453E-04
244 0.5310E-04
245 0.5163E-04
246 0.5115E-04
247 0.5003E-04
248 0.5043E-04
249 0.4970E-04
250 0.4901E-04
251 0.5027E-04
252 0.4822E-04
253 0.4767E-04
254 0.4822E-04
255 0.4624E-04
256 0.4534E-04
257 0.4563E-04
258 0.4418E-04
259 0.4335E-04
260 0.4312E-04
261 0.4176E-04
262 0.4157E-04
263 0.3985E-04
264 0.3950E-04
265 0.3906E-04
266 0.3707E-04
267 0.3820E-04
268 0.3702E-04
269 0.3608E-04
270 0.3567E-04
271 0.3487E-04
272 0.3369E-04
273 0.3254E-04
274 0.3249E-04
275 0.3156E-04
276 0.3085E-04
277 0.2980E-04
278 0.2851E-04
279 0.2829E-04
280 0.2816E-04
281 0.2714E-04
282 0.2687E-04
283 0.2588E-04
284 0.2489E-04
285 0.2407E-04
286 0.2359E-04
287 0.2282E-04
288 0.2222E-04
289 0.2190E-04
290 0.2161E-04
291 0.2101E-04
292 0.2062E-04
293 0.2042E-04
294 0.1961E-04
295 0.1907E-04
296 0.1875E-04
297 0.1833E-04
298 0.1762E-04
299 0.1778E-04
300 0.1717E-04
301 0.1655E-04
302 0.1629E-04
303 0.1610E-04
304 0.1531E-04
305 0.1522E-04
306 0.1482E-04
307 0.1430E-04
308 0.1423E-04
309 0.1382E-04
310 0.1336E-04
311 0.1285E-04
312 0.1299E-04
313 0.1239E-04
314 0.1181E-04
315 0.1160E-04
316 0.1147E-04
317 0.1104E-04
318 0.1088E-04
319 0.1084E-04
320 0.1021E-04
321 0.1027E-04
322 0.1001E-04
323 0.9569E-05
324 0.9237E-05
325 0.9143E-05
326 0.8905E-05
327 0.8415E-05
328 0.8132E-05
329 0.8103E-05
330 0.7573E-05
331 0.7570E-05
332 0.7289E-05
333 0.6822E-05
334 0.6800E-05
335 0.6537E-05
336 0.6328E-05
337 0.6221E-05
338 0.6078E-05
339 0.5812E-05
340 0.5670E-05
341 0.5561E-05
342 0.5374E-05
343 0.5297E-05
344 0.5221E-05
345 0.4998E-05
346 0.4893E-05
347 0.4758E-05
348 0.4603E-05
349 0.4331E-05
350 0.4298E-05
351 0.4205E-05
352 0.3932E-05
353 0.3970E-05
354 0.3879E-05
355 0.3700E-05
356 0.3648E-05
357 0.3590E-05
358 0.3417E-05
359 0.3353E-05
360 0.3349E-05
361 0.3132E-05
362 0.3060E-05
363 0.2992E-05
364 0.2878E-05
365 0.2777E-05
366 0.2726E-05
367 0.2680E-05
368 0.2561E-05
369 0.2499E-05
370 0.2503E-05
371 0.2350E-05
372 0.2291E-05
373 0.2291E-05
374 0.2196E-05
375 0.2124E-05
376 0.2069E-05
377 0.1943E-05
378 0.1900E-05
379 0.1825E-05
380 0.1762E-05
381 0.1756E-05
382 0.1682E-05
383 0.1636E-05
384 0.1583E-05
385 0.1562E-05
386 0.1482E-05
387 0.1454E-05
388 0.1433E-05
389 0.1362E-05
390 0.1341E-05
391 0.1310E-05
392 0.1240E-05
393 0.1232E-05
394 0.1146E-05
395 0.1134E-05
396 0.1104E-05
397 0.1038E-05
398 0.1010E-05
399 0.9848E-06
400 0.9695E-06
401 0.9204E-06
402 0.8811E-06
403 0.8680E-06
404 0.8370E-06
405 0.8015E-06
406 0.7960E-06
407 0.7700E-06
408 0.7217E-06
409 0.7124E-06
410 0.7122E-06
411 0.6730E-06
412 0.6387E-06
413 0.6421E-06
414 0.6217E-06
415 0.5826E-06
416 0.5622E-06
417 0.5619E-06
418 0.5238E-06
419 0.5128E-06
420 0.5005E-06
421 0.4782E-06
422 0.4584E-06
423 0.4437E-06
424 0.4296E-06
425 0.4155E-06
426 0.4046E-06
427 0.3897E-06
428 0.3829E-06
429 0.3635E-06
430 0.3528E-06
431 0.3411E-06
432 0.3293E-06
433 0.3161E-06
434 0.3003E-06
435 0.2931E-06
436 0.2790E-06
437 0.2727E-06
438 0.2657E-06
439 0.2595E-06
440 0.2537E-06
441 0.2409E-06
442 0.2420E-06
443 0.2365E-06
444 0.2253E-06
445 0.2279E-06
446 0.2187E-06
447 0.2137E-06
448 0.2069E-06
449 0.1991E-06
450 0.1951E-06
451 0.1842E-06
452 0.1770E-06
453 0.1703E-06
454 0.1636E-06
455 0.1573E-06
456 0.1513E-06
457 0.1410E-06
458 0.1384E-06
459 0.1329E-06
460 0.1291E-06
461 0.1232E-06
462 0.1203E-06
463 0.1166E-06
464 0.1130E-06
465 0.1101E-06
466 0.1066E-06
467 0.1039E-06
468 0.1017E-06
469 0.1001E-06
470 0.9576E-07
471 0.9171E-07
472 0.8996E-07
473 0.8820E-07
474 0.8378E-07
475 0.8248E-07
476 0.8146E-07
477 0.7693E-07
478 0.7579E-07
479 0.7377E-07
480 0.7089E-07
481 0.7071E-07
482 0.6895E-07
483 0.6634E-07
484 0.6513E-07
485 0.6406E-07
486 0.6326E-07
487 0.6113E-07
488 0.5976E-07
489 0.5801E-07
490 0.5689E-07
491 0.5503E-07
492 0.5377E-07
493 0.5211E-07
494 0.5148E-07
495 0.4939E-07
496 0.4864E-07
497 0.4811E-07
498 0.4785E-07
499 0.4624E-07
500 0.4535E-07
501 0.4584E-07
502 0.4342E-07
503 0.4297E-07
504 0.4229E-07
505 0.4091E-07
506 0.4088E-07
507 0.4060E-07
508 0.3892E-07
509 0.3850E-07
510 0.3815E-07
511 0.3699E-07
512 0.3530E-07
513 0.3515E-07
514 0.3497E-07
515 0.3305E-07
516 0.3345E-07
517 0.3404E-07
518 0.3306E-07
519 0.3356E-07
520 0.3382E-07
521 0.3314E-07
522 0.3261E-07
523 0.3305E-07
524 0.3246E-07
525 0.3201E-07
526 0.3222E-07
527 0.3211E-07
528 0.3162E-07
529 0.3162E-07
530 0.3235E-07
531 0.3170E-07
532 0.3129E-07
533 0.3237E-07
534 0.3117E-07
535 0.3098E-07
536 0.3116E-07
537 0.3058E-07
538 0.3030E-07
539 0.3048E-07
540 0.3023E-07
541 0.2970E-07
542 0.3004E-07
543 0.2973E-07
544 0.2952E-07
545 0.2971E-07
546 0.2997E-07
547 0.2920E-07
548 0.2955E-07
549 0.2905E-07
550 0.2834E-07
551 0.2822E-07
552 0.2813E-07
553 0.2747E-07
554 0.2726E-07
555 0.2719E-07
556 0.2665E-07
557 0.2645E-07
558 0.2649E-07
559 0.2609E-07
560 0.2574E-07
561 0.2585E-07
562 0.2479E-07
563 0.2519E-07
564 0.2474E-07
565 0.2390E-07
566 0.2377E-07
567 0.2350E-07
568 0.2286E-07
569 0.2249E-07
570 0.2208E-07
571 0.2142E-07
572 0.2064E-07
573 0.2004E-07
574 0.1932E-07
575 0.1882E-07
576 0.1813E-07
577 0.1769E-07
578 0.1713E-07
579 0.1644E-07
580 0.1624E-07
581 0.1563E-07
582 0.1508E-07
583 0.1492E-07
584 0.1422E-07
585 0.1380E-07
586 0.1336E-07
587 0.1304E-07
588 0.1245E-07
589 0.1242E-07
590 0.1211E-07
591 0.1184E-07
592 0.1147E-07
593 0.1119E-07
594 0.1099E-07
595 0.1068E-07
596 0.1040E-07
597 0.1019E-07
598 0.9852E-08
598 0.9852E-08
ComputeNorm: Computing norm of solution
ComputeChange: NS (ITER=1) (NRM,RELC): ( 0.41800370E-01 2.0000000 ) :: electrostatics
DefaultFinish: Finished solver: electrostatics
Loading user function library: [StatElecSolve]...[StatElecSolver_post]
SingleSolver: Calling solver for postprocessing
StatElecSolver_post: ------------------------------------------------
StatElecSolver_post: Calculating postprocessing fields
StatElecSolver_post: Number of 1 postprocessing fields
StatElecSolver_post: Creating a weighting for scaling purposes from 7
StatElecSolver_post: Total Electric Energy: 7.297E-12
StatElecSolver_post: Effective Capacitance: 1.459E-11
ComputeNorm: Computing norm of solution
ComputeChange: SS (ITER=1) (NRM,RELC): ( 0.41800370E-01 2.0000000 ) :: electrostatics
SingleSolver: Attempting to call solver: 2
SingleSolver: Solver Equation string is: electric field
FluxSolver: -------------------------------------
FluxSolver: Computing the flux and/or gradient
FluxSolver: -------------------------------------
FluxSolver: Assembly Time: 34.690865039825439
DefaultSolve: Solving linear system with default routines
SolveSystem: Solving linear system
SolveLinearSystem: Assuming real valued linear system
SolveLinearSystem: Assuming serial linear system
ScaleLinearSystem: Creating > DiagScaling < vector of size 10765324
SolveLinearSystem: Linear System Preconditioning: ilu0
Prec0-ilu0: Elapsed REAL time: 0.0000 (s)
SolveLinearSystem: Serial linear System Solver: iterative
IterSolver: Using iterative method: cg
CRS_IncompleteLU: ILU(0) (Real), Performing Factorization:
CRS_IncompleteLU: Allocated LU matrix of size: 123796498
CRS_IncompleteLU: ILU(0) (Real), NOF nonzeros: 123796499
CRS_IncompleteLU: ILU(0) (Real), filling (%) : 100
CRS_IncompleteLU: ILU(0) (Real), Factorization time (s): 1.74
Prec-ilu0: Elapsed REAL time: 8.0068 (s)
10 0.1203E-09
11 0.1404E-10
DefaultSolve: Solving linear system with default routines
SolveSystem: Solving linear system
SolveLinearSystem: Assuming real valued linear system
SolveLinearSystem: Assuming serial linear system
ScaleLinearSystem: Creating > DiagScaling < vector of size 10765324
SolveLinearSystem: Linear System Preconditioning: ilu0
Prec0-ilu0: Elapsed REAL time: 0.0000 (s)
SolveLinearSystem: Serial linear System Solver: iterative
IterSolver: Using iterative method: cg
CRS_IncompleteLU: ILU(0) (Real), Performing Factorization:
CRS_IncompleteLU: ILU(0) (Real), NOF nonzeros: 123796499
CRS_IncompleteLU: ILU(0) (Real), filling (%) : 100
CRS_IncompleteLU: ILU(0) (Real), Factorization time (s): 1.76
Prec-ilu0: Elapsed REAL time: 1.8808 (s)
10 0.1758E-09
11 0.2016E-10
ComputeNorm: Computing norm of solution
ComputeChange: NS (ITER=2) (NRM,RELC): ( 21621.538 2.0000000 ) :: electric field
FluxSolver: Solution Time: 27.188190937042236
FluxSolver: Result Norm: 21621.538030194177
FluxSolver: All done
FluxSolver: -------------------------------------
ComputeNorm: Computing norm of solution
ComputeChange: SS (ITER=1) (NRM,RELC): ( 21621.538 2.0000000 ) :: electric field
SingleSolver: Attempting to call solver: 3
SingleSolver: Solver Equation string is: result output
ResultOutputSolver: -------------------------------------
ResultOutputSolver: Saving with prefix: elfield_weight
ResultOutputSolver: Dimension of mesh is: 2
ResultOutputSolver: Working on mesh 1: wires2d with 10765324 nodes
ResultOutputSolver: Creating list for saving - if not present
CreateListForSaving: Scalar Field 1 exists, creating no list!
ResultOutputSolver: Saving in unstructured VTK XML (.vtu) format
VtuOutputSolver: Saving results in VTK XML format with prefix: elfield_weight
VtuOutputSolver: Saving number of partitions: 1
SolverOutputDirectory: Creating directory: wires2d
VtuOutputSolver: Full filename base is: wires2d/elfield_weight
GenerateSaveMask: Number of active elements 5386987 out of 5386987
GenerateSaveMask: Number of geometry nodes 10765324 out of 10765324
VtuOutputSolver: Total number of elements to save: 5386987
VtuOutputSolver: Total number of geometry nodes to save: 10765324
VtuOutputSolver: Total number of dof nodes to save: 10765324
VtuOutputSolver: Writing the vtu file: wires2d/elfield_weight_t0001.vtu
AscBinWriteInit: Initializing buffered ascii/binary writing
AscBinWriteInit: Writing in binary
AscBinWriteInit: Writing in double precision
AscBinWriteInit: Writing to unit number: 58
AscBinWriteInit: Size of buffer is: 10765324
WriteVtuFile: Writing nodal fields
WriteVtuFile: Saving 1-component variable: potential
WriteVtuFile: Saving 2-component variable: electric field
WriteVtuFile: Number of nodal fields written: 2
WriteVtuFile: Writing elemental fields
WriteVtuFile: Number of elemental fields written: 0
WriteVtuFile: Writing coordinates for each used node
WriteVtuFile: Writing the elemental connectivity data
WriteVtuFile: Writing nodal fields
WriteVtuFile: Saving 1-component variable: potential
WriteVtuFile: Saving 2-component variable: electric field
WriteVtuFile: Writing elemental fields
AscBinWriteFree: Terminating buffered ascii/binary writing
VtuOutputSolver: All done for now
ResultOutputSolver: -------------------------------------
WritePostFile: Saving results in ElmerPost format to file wires2d/elstatics_weight.ep
SaveResult: -----------------------------------------
SaveResult: Saving results to file: wires2d/elstatics_weight.result
SaveResult: Done writing results file
SaveResult: -----------------------------------------
SingleSolver: Attempting to call solver: 4
SingleSolver: Solver Equation string is: savescalars
SaveScalars: -----------------------------------------
SaveScalars: Saving scalar values of various kinds
SaveScalars: Performed 0 reduction operations
SaveScalars: Found 3 result scalars in simulation section
SaveScalars: Found 3 values to save in total
SaveScalars: Saving names of values to file: ./scalars_weight.dat.names
SaveScalars: Saving values to file: ./scalars_weight.dat
SaveScalars: -----------------------------------------
ReloadInputFile: Reloading input file
LoadInputFile: Loading input file:
MAIN: Reporting unused list entries for sif improvement!
MAIN: If you do not want these lines undefine > DEVEL_LISTUSAGE < !
Unused keywords:
Boundary Condition 1 calculate electric force
Boundary Condition 2 calculate electric force
Boundary Condition 3 calculate electric force
Boundary Condition 5 calculate electric force
Solver 1 calculate vectors
Solver 2 target variable
Solver 2 calculate vectors
MAIN: *** Elmer Solver: ALL DONE ***
Loading user function library: [StatElecSolve]...[StatElecSolver_Finalize]
Loading user function library: [StatElecSolve]...[StatElecSolver_Finalize]
Loading user function library: [FluxSolver]...[FluxSolver_Finalize]
Loading user function library: [FluxSolver]...[FluxSolver_Finalize]
Loading user function library: [ResultOutputSolve]...[ResultOutputSolver_Finalize]
Loading user function library: [ResultOutputSolve]...[ResultOutputSolver_Finalize]
Loading user function library: [SaveData]...[SaveScalars_Finalize]
Loading user function library: [SaveData]...[SaveScalars_Finalize]
MAIN: The end
SOLVER TOTAL TIME(CPU,REAL): 1148.85 1276.22
ELMER SOLVER FINISHED AT: 2026/10/28 10:29:40

View File

@ -0,0 +1,376 @@
==================================================================
ElmerGrid mesh conversion and manipulation utility, Welcome!
Version: 26.2 (Rev: NA, Compiled: NA)
This program is free software licensed under GPL
==================================================================
Elmergrid reading in-line arguments
Output will be saved to file wires2d.
Elmergrid loading data:
-----------------------
Format chosen using the first line: $MeshFormat
Gmsh input file format is type 4.1 in ASCII.
Loading mesh in Gmsh format 4.1 from file wires2d.msh
Reading 146 entities in 0D
Reading 146 entities in 1D
Reading 146 entities in 2D
Allocating for 10765324 knots and 5386987 elements.
Leading element dimension is 2
BC name for physical group 1 is: hot_needle
BC name for physical group 2 is: ic_wire_1
BC name for physical group 3 is: ic_wire_2
BC name for physical group 10 is: guard_wires
BC name for physical group 20 is: anode_wires
BC name for physical group 30 is: cathode_wires
BC name for physical group 40 is: cathode_wire_selected
Body name for physical group 13 is: gas
Allocating lookup table for tags of size 146
Defined 146 0DIM entities with geometric tag range [1 146]
Defined 146 1DIM entities with geometric tag range [1 146]
Physical given tag range is [1 40]
Defined 146 2DIM entities with geometric tag range [1 146]
Physical given tag range is [13 13]
Physical tag offset for 0D is 40
Geometric tag offset for 0D is 186
Reading 10765324 nodes in 438 blocks.
Reading 5386987 elements in 291 blocks.
Mapping mesh tag 2 to physical tag 30 in 1DIM
Mapping mesh tag 3 to physical tag 30 in 1DIM
Mapping mesh tag 4 to physical tag 20 in 1DIM
Mapping mesh tag 5 to physical tag 20 in 1DIM
Mapping mesh tag 6 to physical tag 10 in 1DIM
Mapping mesh tag 7 to physical tag 40 in 1DIM
Mapping mesh tag 8 to physical tag 20 in 1DIM
Mapping mesh tag 9 to physical tag 30 in 1DIM
Mapping mesh tag 10 to physical tag 20 in 1DIM
Mapping mesh tag 11 to physical tag 10 in 1DIM
Mapping mesh tag 12 to physical tag 10 in 1DIM
Mapping mesh tag 13 to physical tag 10 in 1DIM
Mapping mesh tag 14 to physical tag 10 in 1DIM
Mapping mesh tag 15 to physical tag 2 in 1DIM
Mapping mesh tag 16 to physical tag 10 in 1DIM
Mapping mesh tag 17 to physical tag 10 in 1DIM
Mapping mesh tag 18 to physical tag 3 in 1DIM
Mapping mesh tag 19 to physical tag 2 in 1DIM
Mapping mesh tag 20 to physical tag 3 in 1DIM
Mapping mesh tag 21 to physical tag 30 in 1DIM
Mapping mesh tag 22 to physical tag 20 in 1DIM
Mapping mesh tag 23 to physical tag 10 in 1DIM
Mapping mesh tag 24 to physical tag 10 in 1DIM
Mapping mesh tag 25 to physical tag 10 in 1DIM
Mapping mesh tag 26 to physical tag 2 in 1DIM
Mapping mesh tag 27 to physical tag 30 in 1DIM
Mapping mesh tag 28 to physical tag 20 in 1DIM
Mapping mesh tag 29 to physical tag 10 in 1DIM
Mapping mesh tag 30 to physical tag 10 in 1DIM
Mapping mesh tag 31 to physical tag 3 in 1DIM
Mapping mesh tag 32 to physical tag 2 in 1DIM
Mapping mesh tag 33 to physical tag 3 in 1DIM
Mapping mesh tag 34 to physical tag 2 in 1DIM
Mapping mesh tag 35 to physical tag 3 in 1DIM
Mapping mesh tag 36 to physical tag 3 in 1DIM
Mapping mesh tag 37 to physical tag 30 in 1DIM
Mapping mesh tag 38 to physical tag 10 in 1DIM
Mapping mesh tag 39 to physical tag 20 in 1DIM
Mapping mesh tag 40 to physical tag 20 in 1DIM
Mapping mesh tag 41 to physical tag 30 in 1DIM
Mapping mesh tag 42 to physical tag 10 in 1DIM
Mapping mesh tag 43 to physical tag 2 in 1DIM
Mapping mesh tag 44 to physical tag 3 in 1DIM
Mapping mesh tag 45 to physical tag 2 in 1DIM
Mapping mesh tag 46 to physical tag 3 in 1DIM
Mapping mesh tag 47 to physical tag 10 in 1DIM
Mapping mesh tag 48 to physical tag 10 in 1DIM
Mapping mesh tag 49 to physical tag 2 in 1DIM
Mapping mesh tag 50 to physical tag 10 in 1DIM
Mapping mesh tag 51 to physical tag 20 in 1DIM
Mapping mesh tag 52 to physical tag 30 in 1DIM
Mapping mesh tag 53 to physical tag 10 in 1DIM
Mapping mesh tag 54 to physical tag 20 in 1DIM
Mapping mesh tag 55 to physical tag 30 in 1DIM
Mapping mesh tag 56 to physical tag 3 in 1DIM
Mapping mesh tag 57 to physical tag 2 in 1DIM
Mapping mesh tag 58 to physical tag 3 in 1DIM
Mapping mesh tag 59 to physical tag 10 in 1DIM
Mapping mesh tag 60 to physical tag 10 in 1DIM
Mapping mesh tag 61 to physical tag 2 in 1DIM
Mapping mesh tag 62 to physical tag 10 in 1DIM
Mapping mesh tag 63 to physical tag 20 in 1DIM
Mapping mesh tag 64 to physical tag 30 in 1DIM
Mapping mesh tag 65 to physical tag 3 in 1DIM
Mapping mesh tag 66 to physical tag 2 in 1DIM
Mapping mesh tag 67 to physical tag 10 in 1DIM
Mapping mesh tag 68 to physical tag 3 in 1DIM
Mapping mesh tag 69 to physical tag 10 in 1DIM
Mapping mesh tag 70 to physical tag 20 in 1DIM
Mapping mesh tag 71 to physical tag 10 in 1DIM
Mapping mesh tag 72 to physical tag 30 in 1DIM
Mapping mesh tag 73 to physical tag 30 in 1DIM
Mapping mesh tag 74 to physical tag 10 in 1DIM
Mapping mesh tag 75 to physical tag 20 in 1DIM
Mapping mesh tag 76 to physical tag 10 in 1DIM
Mapping mesh tag 77 to physical tag 2 in 1DIM
Mapping mesh tag 78 to physical tag 3 in 1DIM
Mapping mesh tag 79 to physical tag 10 in 1DIM
Mapping mesh tag 80 to physical tag 2 in 1DIM
Mapping mesh tag 81 to physical tag 3 in 1DIM
Mapping mesh tag 82 to physical tag 30 in 1DIM
Mapping mesh tag 83 to physical tag 20 in 1DIM
Mapping mesh tag 84 to physical tag 10 in 1DIM
Mapping mesh tag 85 to physical tag 2 in 1DIM
Mapping mesh tag 86 to physical tag 10 in 1DIM
Mapping mesh tag 87 to physical tag 10 in 1DIM
Mapping mesh tag 88 to physical tag 3 in 1DIM
Mapping mesh tag 89 to physical tag 2 in 1DIM
Mapping mesh tag 90 to physical tag 3 in 1DIM
Mapping mesh tag 91 to physical tag 30 in 1DIM
Mapping mesh tag 92 to physical tag 20 in 1DIM
Mapping mesh tag 93 to physical tag 10 in 1DIM
Mapping mesh tag 94 to physical tag 30 in 1DIM
Mapping mesh tag 95 to physical tag 20 in 1DIM
Mapping mesh tag 96 to physical tag 10 in 1DIM
Mapping mesh tag 97 to physical tag 2 in 1DIM
Mapping mesh tag 98 to physical tag 10 in 1DIM
Mapping mesh tag 99 to physical tag 10 in 1DIM
Mapping mesh tag 100 to physical tag 3 in 1DIM
Mapping mesh tag 101 to physical tag 2 in 1DIM
Mapping mesh tag 102 to physical tag 3 in 1DIM
Mapping mesh tag 103 to physical tag 10 in 1DIM
Mapping mesh tag 104 to physical tag 30 in 1DIM
Mapping mesh tag 105 to physical tag 20 in 1DIM
Mapping mesh tag 106 to physical tag 20 in 1DIM
Mapping mesh tag 107 to physical tag 10 in 1DIM
Mapping mesh tag 108 to physical tag 30 in 1DIM
Mapping mesh tag 109 to physical tag 1 in 1DIM
Mapping mesh tag 110 to physical tag 3 in 1DIM
Mapping mesh tag 111 to physical tag 3 in 1DIM
Mapping mesh tag 112 to physical tag 2 in 1DIM
Mapping mesh tag 113 to physical tag 2 in 1DIM
Mapping mesh tag 114 to physical tag 3 in 1DIM
Mapping mesh tag 115 to physical tag 2 in 1DIM
Mapping mesh tag 116 to physical tag 3 in 1DIM
Mapping mesh tag 117 to physical tag 10 in 1DIM
Mapping mesh tag 118 to physical tag 10 in 1DIM
Mapping mesh tag 119 to physical tag 20 in 1DIM
Mapping mesh tag 120 to physical tag 30 in 1DIM
Mapping mesh tag 121 to physical tag 2 in 1DIM
Mapping mesh tag 122 to physical tag 10 in 1DIM
Mapping mesh tag 123 to physical tag 10 in 1DIM
Mapping mesh tag 124 to physical tag 10 in 1DIM
Mapping mesh tag 125 to physical tag 20 in 1DIM
Mapping mesh tag 126 to physical tag 30 in 1DIM
Mapping mesh tag 127 to physical tag 2 in 1DIM
Mapping mesh tag 128 to physical tag 3 in 1DIM
Mapping mesh tag 129 to physical tag 2 in 1DIM
Mapping mesh tag 130 to physical tag 3 in 1DIM
Mapping mesh tag 131 to physical tag 10 in 1DIM
Mapping mesh tag 132 to physical tag 10 in 1DIM
Mapping mesh tag 133 to physical tag 2 in 1DIM
Mapping mesh tag 134 to physical tag 10 in 1DIM
Mapping mesh tag 135 to physical tag 10 in 1DIM
Mapping mesh tag 136 to physical tag 10 in 1DIM
Mapping mesh tag 137 to physical tag 10 in 1DIM
Mapping mesh tag 138 to physical tag 20 in 1DIM
Mapping mesh tag 139 to physical tag 30 in 1DIM
Mapping mesh tag 140 to physical tag 20 in 1DIM
Mapping mesh tag 141 to physical tag 30 in 1DIM
Mapping mesh tag 142 to physical tag 10 in 1DIM
Mapping mesh tag 143 to physical tag 20 in 1DIM
Mapping mesh tag 144 to physical tag 20 in 1DIM
Mapping mesh tag 145 to physical tag 30 in 1DIM
Mapping mesh tag 146 to physical tag 30 in 1DIM
Mapping mesh tag 1 to physical tag 13 in 2DIM
Mapping mesh tag 2 to physical tag 13 in 2DIM
Mapping mesh tag 3 to physical tag 13 in 2DIM
Mapping mesh tag 4 to physical tag 13 in 2DIM
Mapping mesh tag 5 to physical tag 13 in 2DIM
Mapping mesh tag 6 to physical tag 13 in 2DIM
Mapping mesh tag 7 to physical tag 13 in 2DIM
Mapping mesh tag 8 to physical tag 13 in 2DIM
Mapping mesh tag 9 to physical tag 13 in 2DIM
Mapping mesh tag 10 to physical tag 13 in 2DIM
Mapping mesh tag 11 to physical tag 13 in 2DIM
Mapping mesh tag 12 to physical tag 13 in 2DIM
Mapping mesh tag 13 to physical tag 13 in 2DIM
Mapping mesh tag 14 to physical tag 13 in 2DIM
Mapping mesh tag 15 to physical tag 13 in 2DIM
Mapping mesh tag 16 to physical tag 13 in 2DIM
Mapping mesh tag 17 to physical tag 13 in 2DIM
Mapping mesh tag 18 to physical tag 13 in 2DIM
Mapping mesh tag 19 to physical tag 13 in 2DIM
Mapping mesh tag 20 to physical tag 13 in 2DIM
Mapping mesh tag 21 to physical tag 13 in 2DIM
Mapping mesh tag 22 to physical tag 13 in 2DIM
Mapping mesh tag 23 to physical tag 13 in 2DIM
Mapping mesh tag 24 to physical tag 13 in 2DIM
Mapping mesh tag 25 to physical tag 13 in 2DIM
Mapping mesh tag 26 to physical tag 13 in 2DIM
Mapping mesh tag 27 to physical tag 13 in 2DIM
Mapping mesh tag 28 to physical tag 13 in 2DIM
Mapping mesh tag 29 to physical tag 13 in 2DIM
Mapping mesh tag 30 to physical tag 13 in 2DIM
Mapping mesh tag 31 to physical tag 13 in 2DIM
Mapping mesh tag 32 to physical tag 13 in 2DIM
Mapping mesh tag 33 to physical tag 13 in 2DIM
Mapping mesh tag 34 to physical tag 13 in 2DIM
Mapping mesh tag 35 to physical tag 13 in 2DIM
Mapping mesh tag 36 to physical tag 13 in 2DIM
Mapping mesh tag 37 to physical tag 13 in 2DIM
Mapping mesh tag 38 to physical tag 13 in 2DIM
Mapping mesh tag 39 to physical tag 13 in 2DIM
Mapping mesh tag 40 to physical tag 13 in 2DIM
Mapping mesh tag 41 to physical tag 13 in 2DIM
Mapping mesh tag 42 to physical tag 13 in 2DIM
Mapping mesh tag 43 to physical tag 13 in 2DIM
Mapping mesh tag 44 to physical tag 13 in 2DIM
Mapping mesh tag 45 to physical tag 13 in 2DIM
Mapping mesh tag 46 to physical tag 13 in 2DIM
Mapping mesh tag 47 to physical tag 13 in 2DIM
Mapping mesh tag 48 to physical tag 13 in 2DIM
Mapping mesh tag 49 to physical tag 13 in 2DIM
Mapping mesh tag 50 to physical tag 13 in 2DIM
Mapping mesh tag 51 to physical tag 13 in 2DIM
Mapping mesh tag 52 to physical tag 13 in 2DIM
Mapping mesh tag 53 to physical tag 13 in 2DIM
Mapping mesh tag 54 to physical tag 13 in 2DIM
Mapping mesh tag 55 to physical tag 13 in 2DIM
Mapping mesh tag 56 to physical tag 13 in 2DIM
Mapping mesh tag 57 to physical tag 13 in 2DIM
Mapping mesh tag 58 to physical tag 13 in 2DIM
Mapping mesh tag 59 to physical tag 13 in 2DIM
Mapping mesh tag 60 to physical tag 13 in 2DIM
Mapping mesh tag 61 to physical tag 13 in 2DIM
Mapping mesh tag 62 to physical tag 13 in 2DIM
Mapping mesh tag 63 to physical tag 13 in 2DIM
Mapping mesh tag 64 to physical tag 13 in 2DIM
Mapping mesh tag 65 to physical tag 13 in 2DIM
Mapping mesh tag 66 to physical tag 13 in 2DIM
Mapping mesh tag 67 to physical tag 13 in 2DIM
Mapping mesh tag 68 to physical tag 13 in 2DIM
Mapping mesh tag 69 to physical tag 13 in 2DIM
Mapping mesh tag 70 to physical tag 13 in 2DIM
Mapping mesh tag 71 to physical tag 13 in 2DIM
Mapping mesh tag 72 to physical tag 13 in 2DIM
Mapping mesh tag 73 to physical tag 13 in 2DIM
Mapping mesh tag 74 to physical tag 13 in 2DIM
Mapping mesh tag 75 to physical tag 13 in 2DIM
Mapping mesh tag 76 to physical tag 13 in 2DIM
Mapping mesh tag 77 to physical tag 13 in 2DIM
Mapping mesh tag 78 to physical tag 13 in 2DIM
Mapping mesh tag 79 to physical tag 13 in 2DIM
Mapping mesh tag 80 to physical tag 13 in 2DIM
Mapping mesh tag 81 to physical tag 13 in 2DIM
Mapping mesh tag 82 to physical tag 13 in 2DIM
Mapping mesh tag 83 to physical tag 13 in 2DIM
Mapping mesh tag 84 to physical tag 13 in 2DIM
Mapping mesh tag 85 to physical tag 13 in 2DIM
Mapping mesh tag 86 to physical tag 13 in 2DIM
Mapping mesh tag 87 to physical tag 13 in 2DIM
Mapping mesh tag 88 to physical tag 13 in 2DIM
Mapping mesh tag 89 to physical tag 13 in 2DIM
Mapping mesh tag 90 to physical tag 13 in 2DIM
Mapping mesh tag 91 to physical tag 13 in 2DIM
Mapping mesh tag 92 to physical tag 13 in 2DIM
Mapping mesh tag 93 to physical tag 13 in 2DIM
Mapping mesh tag 94 to physical tag 13 in 2DIM
Mapping mesh tag 95 to physical tag 13 in 2DIM
Mapping mesh tag 96 to physical tag 13 in 2DIM
Mapping mesh tag 97 to physical tag 13 in 2DIM
Mapping mesh tag 98 to physical tag 13 in 2DIM
Mapping mesh tag 99 to physical tag 13 in 2DIM
Mapping mesh tag 100 to physical tag 13 in 2DIM
Mapping mesh tag 101 to physical tag 13 in 2DIM
Mapping mesh tag 102 to physical tag 13 in 2DIM
Mapping mesh tag 103 to physical tag 13 in 2DIM
Mapping mesh tag 104 to physical tag 13 in 2DIM
Mapping mesh tag 105 to physical tag 13 in 2DIM
Mapping mesh tag 106 to physical tag 13 in 2DIM
Mapping mesh tag 107 to physical tag 13 in 2DIM
Mapping mesh tag 108 to physical tag 13 in 2DIM
Mapping mesh tag 109 to physical tag 13 in 2DIM
Mapping mesh tag 110 to physical tag 13 in 2DIM
Mapping mesh tag 111 to physical tag 13 in 2DIM
Mapping mesh tag 112 to physical tag 13 in 2DIM
Mapping mesh tag 113 to physical tag 13 in 2DIM
Mapping mesh tag 114 to physical tag 13 in 2DIM
Mapping mesh tag 115 to physical tag 13 in 2DIM
Mapping mesh tag 116 to physical tag 13 in 2DIM
Mapping mesh tag 117 to physical tag 13 in 2DIM
Mapping mesh tag 118 to physical tag 13 in 2DIM
Mapping mesh tag 119 to physical tag 13 in 2DIM
Mapping mesh tag 120 to physical tag 13 in 2DIM
Mapping mesh tag 121 to physical tag 13 in 2DIM
Mapping mesh tag 122 to physical tag 13 in 2DIM
Mapping mesh tag 123 to physical tag 13 in 2DIM
Mapping mesh tag 124 to physical tag 13 in 2DIM
Mapping mesh tag 125 to physical tag 13 in 2DIM
Mapping mesh tag 126 to physical tag 13 in 2DIM
Mapping mesh tag 127 to physical tag 13 in 2DIM
Mapping mesh tag 128 to physical tag 13 in 2DIM
Mapping mesh tag 129 to physical tag 13 in 2DIM
Mapping mesh tag 130 to physical tag 13 in 2DIM
Mapping mesh tag 131 to physical tag 13 in 2DIM
Mapping mesh tag 132 to physical tag 13 in 2DIM
Mapping mesh tag 133 to physical tag 13 in 2DIM
Mapping mesh tag 134 to physical tag 13 in 2DIM
Mapping mesh tag 135 to physical tag 13 in 2DIM
Mapping mesh tag 136 to physical tag 13 in 2DIM
Mapping mesh tag 137 to physical tag 13 in 2DIM
Mapping mesh tag 138 to physical tag 13 in 2DIM
Mapping mesh tag 139 to physical tag 13 in 2DIM
Mapping mesh tag 140 to physical tag 13 in 2DIM
Mapping mesh tag 141 to physical tag 13 in 2DIM
Mapping mesh tag 142 to physical tag 13 in 2DIM
Mapping mesh tag 143 to physical tag 13 in 2DIM
Mapping mesh tag 144 to physical tag 13 in 2DIM
Mapping mesh tag 145 to physical tag 13 in 2DIM
Mapping mesh tag 146 to physical tag 13 in 2DIM
Moving bulk elements to boundary elements
Assuming that boundaries names are already Ok!
Leading bulk elementtype is 306
Trailing bulk elementtype is 203
There are 4640 (out of 5386987) lower dimensional elements.
Node 2373 belongs to maximum of 10 elements
Found 4640 side elements that have two parents.
Found correctly 4640 side elements.
Parent elements were reordered up to index 5382347.
Moved 5382347 elements (out of 5386987) to new positions
Successfully read the mesh from the Gmsh input file wires2d.msh
Using physical numbering of entities
Elmergrid creating and manipulating meshes:
-------------------------------------------
Coordinates defined in 2 dimensions
Bounding box of all nodes in mesh:
X:[-500,500] Y:[-499.998,499.998] Z:[0,0]
Notice: the longest bounding box side length of [1000] is greater than 50.
ElmerGUI includes a library of material properties, defined in SI units. If using ElmerGUI,
then the geometry is expected to have meters as length. Geometry that exceeds 50 meters
in length or width or height may not be intended. Many Geometry generators assume
millimeters as the basic unit of length. Scaling the geometry from millimeters to meters
may be the desired action. For more help, search the Elmer users forum for posts
about SI units, or for posts about Coordinate Scaling.
Scaling can be accomplished in at least three ways, as follows:
1. Define the original geometry in meters, not millimeters.
2. Call ElmerGrid with -scale 0.001 0.001 0.001 as an option.
3. Add Coordinate Scaling = 0.001 to the simulation section of the sif file.
If using Elmer to analyze large geometry, such as a glacier, then ignore this notice.
Calculate Mesh Pieces in mesh:
There is a single piece in the mesh, so the mesh is conforming.
Elmergrid saving data with method 2:
-------------------------------------
Saving mesh in ElmerSolver format to directory wires2d.
Reusing an existing directory
Saving 10765324 coordinates to mesh.nodes.
Saving 5382347 element topologies to mesh.elements.
Saving boundary elements to mesh.boundary.
Saving header info to mesh.header.
Saving names info to mesh.names.
Saving entities info to entities.sif.
Thank you for using ElmerGrid!
Repository: https://github.com/ElmerCSC/elmerfem
Contact: elmeradm@csc.fi

View File

@ -0,0 +1,61 @@
"""Relative anode-cathode azimuth against z in the anasen_fem 2D model.
The mesher (anasen_fem/wires_gmsh2d_bc.py) places every wire by linear
interpolation in x and y between its plane-1 and plane-2 end points, so a wire
is a straight chord and the lattice is a hyperboloid: the radius shrinks to the
waist at z = 0 and the azimuth is NOT linear in z. This reproduces that
placement exactly and reports, for each z, the azimuth of the anode and cathode
lattices, their relative offset within one 15 deg cathode pitch, and the
smallest anode-cathode centre distance.
"""
import numpy as np
k = 2 * np.pi / 24.0
offset_a1 = -6 * k - 4 * k
offset_c1 = -6 * k + k / 2.0
wireShift = 4.0
offset_a2 = offset_a1 + wireShift * k
offset_c2 = offset_c1 - wireShift * k
i = np.arange(24)
xa1, ya1 = 37 * np.cos(-k * i + offset_a1), 37 * np.sin(-k * i + offset_a1)
xa2, ya2 = 37 * np.cos(-k * i + offset_a2), 37 * np.sin(-k * i + offset_a2)
xc1, yc1 = 42 * np.cos(k * i + offset_c1), 42 * np.sin(k * i + offset_c1)
xc2, yc2 = 42 * np.cos(k * i + offset_c2), 42 * np.sin(k * i + offset_c2)
def wires(z):
t = (z + 174.3) / (2 * 174.3)
xa, ya = xa1 + t * (xa2 - xa1), ya1 + t * (ya2 - ya1)
xc, yc = xc1 + t * (xc2 - xc1), yc1 + t * (yc2 - yc1)
return (xa, ya), (xc, yc)
def rel_azimuth_deg(z, cath=1):
"""Signed azimuth of the nearest anode from cathode `cath`, in degrees."""
(xa, ya), (xc, yc) = wires(z)
phi_c = np.arctan2(yc[cath], xc[cath])
d = np.degrees(np.arctan2(ya, xa) - phi_c)
d = (d + 180.0) % 360.0 - 180.0
j = np.argmin(np.abs(d))
return d[j], j
def min_distance(z, cath=1):
(xa, ya), (xc, yc) = wires(z)
r = np.hypot(xa - xc[cath], ya - yc[cath])
return r.min()
def radii(z):
(xa, ya), (xc, yc) = wires(z)
return np.hypot(xa, ya).mean(), np.hypot(xc, yc).mean()
if __name__ == "__main__":
print(" z[mm] rel_az[deg] anode r_a[mm] r_c[mm] min|a-c|[mm]")
for z in np.arange(-5, 70.001, 1.0):
d, j = rel_azimuth_deg(z)
ra, rc = radii(z)
print("%7.3f %9.4f %4d %7.3f %7.3f %8.4f"
% (z, d, j, ra, rc, min_distance(z)))

View File

@ -0,0 +1,96 @@
"""E.E_w on every anode wire surface, read straight from the Elmer VTUs.
Same quantity as anasen_fem/dotproduct.py, but by default it never writes the
~1 GB dotproduct VTU: only the anode surfaces are needed here, and a sweep over
z would otherwise cost 1 GB per slice. The node-set and all-zeros checks that
dotproduct.py makes are repeated, because both failures are silent. Pass
--dotvtu to read one of the archived dotproduct VTUs instead.
The value reported per anode is the extremum of E.E_w over the nodes lying on
that wire's surface. That is the recipe behind the numbers on slide 21 of the
defense: it returns -2.569e8 for the aligned anode at the z = 19.794 crossover
and -1.517e8 / -1.486e8 for the two anodes at the z = 0 midpoint, against the
-2.57e8, -1.50e8 and 1.7x quoted there.
"""
import argparse
import os
import sys
import numpy as np
import meshio
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from geometry import wires
ap = argparse.ArgumentParser()
ap.add_argument("z", type=float)
ap.add_argument("--phys", default="wires2d/elfield_anasen_t0001.vtu")
ap.add_argument("--weight", default="wires2d/elfield_weight_t0001.vtu")
ap.add_argument("--dotvtu", default=None,
help="an archived dotproduct VTU, read instead of the two "
"field VTUs")
ap.add_argument("--cath", type=int, default=1)
ap.add_argument("--wire-radius", type=float, default=0.254)
ap.add_argument("--band", type=float, default=0.02)
ap.add_argument("--csv", default=None)
a = ap.parse_args()
def array(m, wanted, path):
key = [k for k in m.point_data
if "".join(k.lower().split()) == "".join(wanted.lower().split())]
if not key:
sys.exit("%s: no %r array; has %s" % (path, wanted, sorted(m.point_data)))
v = np.asarray(m.point_data[key[0]], dtype=float)
if not np.any(v):
sys.exit("%s: %s is identically zero -- Elmer applied no boundary "
"condition (check the physical-group tags)." % (path, wanted))
return v
if a.dotvtu:
m = meshio.read(a.dotvtu)
pts3, dot = m.points, array(m, "DotProduct", a.dotvtu)
else:
mp = meshio.read(a.phys)
mw = meshio.read(a.weight)
if mp.points.shape != mw.points.shape or not np.allclose(
mp.points, mw.points, rtol=0.0, atol=1e-12):
sys.exit("the two solves were run on different meshes")
E = array(mp, "electric field", a.phys)
Ew = array(mw, "electric field", a.weight)
ncomp = min(E.shape[1], Ew.shape[1])
dot = np.einsum("ij,ij->i", E[:, :ncomp], Ew[:, :ncomp])
pts3 = mp.points
pts = pts3[:, :2] * 1000.0 # Elmer works in m; wires are in mm
(xa, ya), (xc, yc) = wires(a.z)
phi_c = np.degrees(np.arctan2(yc[a.cath], xc[a.cath]))
rows = []
for j in range(24):
r = np.hypot(pts[:, 0] - xa[j], pts[:, 1] - ya[j])
on = np.abs(r - a.wire_radius) < a.band
if not np.any(on):
continue
d = dot[on]
daz = (np.degrees(np.arctan2(ya[j], xa[j])) - phi_c + 180.0) % 360.0 - 180.0
rows.append((j, daz, int(on.sum()), float(d.min()), float(d.mean()),
float(np.hypot(xa[j] - xc[a.cath], ya[j] - yc[a.cath]))))
rows.sort(key=lambda r: abs(r[1]))
print("# z = %.4f mm, %d nodes" % (a.z, len(dot)))
print("anode d_az[deg] nodes min mean dist[mm]")
for j, daz, n, mn, mean, dist in rows[:7]:
print("%4d %9.3f %6d %10.3e %10.3e %8.3f" % (j, daz, n, mn, mean, dist))
if a.csv:
new = not os.path.exists(a.csv)
with open(a.csv, "a") as f:
if new:
f.write("z_mm,anode,d_az_deg,nodes,dot_min,dot_mean,dist_mm\n")
for j, daz, n, mn, mean, dist in rows:
f.write("%.4f,%d,%.4f,%d,%.6e,%.6e,%.4f\n"
% (a.z, j, daz, n, mn, mean, dist))
print("appended %d rows to %s" % (len(rows), a.csv))

View File

@ -0,0 +1 @@
8.119747066140E-005 8.600000000000E+002 2.195713106041E-010

View File

@ -0,0 +1,10 @@
Metadata for SaveScalars file: ./scalars.dat
Elmer version: 26.2
Elmer compilation date: 2026-10-16
Solver input file: wires2d.sif
File started at: 2026/10/28 10:08:02
Variables in columns of matrix:
1: res: electric energy
2: res: potential difference
3: res: effective capacitance

View File

@ -0,0 +1 @@
7.297444630138E-012 1.000000000000E+000 1.459488926028E-011

View File

@ -0,0 +1,10 @@
Metadata for SaveScalars file: ./scalars_weight.dat
Elmer version: 26.2
Elmer compilation date: 2026-10-16
Solver input file: wires2d_weight.sif
File started at: 2026/10/28 10:29:31
Variables in columns of matrix:
1: res: electric energy
2: res: potential difference
3: res: effective capacitance

View File

@ -0,0 +1,127 @@
Check Keywords Warn
Header
Mesh DB "." "wires2d"
End
Simulation
Coordinate System = Cartesian 2D
Simulation Type = Steady State
Steady State Max Iterations = 1
Output File = "elstatics.result"
Post File = "elstatics.ep"
Coordinate Scaling = 0.001 ! Converts mm from Gmsh to meters for Elmer
End
Constants
Permittivity Of Vacuum = 8.8542e-12
End
Body 1
Target Bodies(1) = 13
Equation = 1
Material = 1
End
Equation 1
Active Solvers(2) = 1 2
End
Material 1
Relative Permittivity = 1
End
Solver 1
Equation = Electrostatics
Procedure = "StatElecSolve" "StatElecSolver"
Variable = Potential
Variable DOFs = 1
Calculate Electric Field = True
Calculate Electric Flux = False
Linear System Solver = Iterative
Linear System Iterative Method = CG
Linear System Max Iterations = 5000
Linear System Convergence Tolerance = 1.0e-8
Linear System Preconditioning = ILU1
Calculate Vectors = Logical True
End
Solver 2
Equation = "Electric Field"
Procedure = "FluxSolver" "FluxSolver"
! Calculate from the potential
Target Variable = "Potential"
! Name of the output vector field in VTU
Flux Variable = String "Electric Field"
! Use 2D components (x, y)
Flux Coefficient = String "Permittivity"
Calculate Vectors = Logical True
End
Solver 3
Equation = Result Output
Procedure = "ResultOutputSolve" "ResultOutputSolver"
Output File Name = elfield_anasen ! Sets prefix for output files
Output Format = Vtu
! Optional: Select specific variables to save
Scalar Field 1 = Potential
Vector Field 1 = Electric Field
End
Solver 4
Exec Solver = After All
Equation = SaveScalars
Procedure = "SaveData" "SaveScalars"
Filename = "scalars.dat"
End
Boundary Condition 1
Target Boundaries = 1
Potential = -200
Calculate Electric Force = True
End
Boundary Condition 2
Target Boundaries = 2
Potential = 150
Calculate Electric Force = True
End
Boundary Condition 3
Target Boundaries = 3
Potential = 250
Calculate Electric Force = True
End
Boundary Condition 4
Target Boundaries = 10
Potential = 0
End
Boundary Condition 5
Target Boundaries = 20
Potential = 660
Calculate Electric Force = True
End
Boundary Condition 6
Target Boundaries = 30
Potential = 0
End
! The one cathode singled out in wires_gmsh2d_bc.py. Grounded here, exactly
! like the other 23; it is only separate so wires2d_weight.sif can drive it.
Boundary Condition 7
Target Boundaries = 40
Potential = 0
End

View File

@ -0,0 +1,132 @@
Check Keywords Warn
! Ramo weighting potential for the single cathode at tag 40: that electrode
! sits at 1 V and every other one at 0 V. Identical to wires2d.sif apart from
! the potentials and the output names, so both solves discretise the same way
! and E . E_w is meaningful node by node.
Header
Mesh DB "." "wires2d"
End
Simulation
Coordinate System = Cartesian 2D
Simulation Type = Steady State
Steady State Max Iterations = 1
Output File = "elstatics_weight.result"
Post File = "elstatics_weight.ep"
Coordinate Scaling = 0.001 ! Converts mm from Gmsh to meters for Elmer
End
Constants
Permittivity Of Vacuum = 8.8542e-12
End
Body 1
Target Bodies(1) = 13
Equation = 1
Material = 1
End
Equation 1
Active Solvers(2) = 1 2
End
Material 1
Relative Permittivity = 1
End
Solver 1
Equation = Electrostatics
Procedure = "StatElecSolve" "StatElecSolver"
Variable = Potential
Variable DOFs = 1
Calculate Electric Field = True
Calculate Electric Flux = False
Linear System Solver = Iterative
Linear System Iterative Method = CG
Linear System Max Iterations = 5000
Linear System Convergence Tolerance = 1.0e-8
Linear System Preconditioning = ILU1
Calculate Vectors = Logical True
End
Solver 2
Equation = "Electric Field"
Procedure = "FluxSolver" "FluxSolver"
! Calculate from the potential
Target Variable = "Potential"
! Name of the output vector field in VTU
Flux Variable = String "Electric Field"
! Use 2D components (x, y)
Flux Coefficient = String "Permittivity"
Calculate Vectors = Logical True
End
Solver 3
Equation = Result Output
Procedure = "ResultOutputSolve" "ResultOutputSolver"
Output File Name = elfield_weight ! Sets prefix for output files
Output Format = Vtu
! Optional: Select specific variables to save
Scalar Field 1 = Potential
Vector Field 1 = Electric Field
End
Solver 4
Exec Solver = After All
Equation = SaveScalars
Procedure = "SaveData" "SaveScalars"
Filename = "scalars_weight.dat"
End
Boundary Condition 1
Target Boundaries = 1
Potential = 0
Calculate Electric Force = True
End
Boundary Condition 2
Target Boundaries = 2
Potential = 0
Calculate Electric Force = True
End
Boundary Condition 3
Target Boundaries = 3
Potential = 0
Calculate Electric Force = True
End
Boundary Condition 4
Target Boundaries = 10
Potential = 0
End
Boundary Condition 5
Target Boundaries = 20
Potential = 0
Calculate Electric Force = True
End
Boundary Condition 6
Target Boundaries = 30
Potential = 0
End
! The one cathode singled out in wires_gmsh2d_bc.py. Grounded here, exactly
! like the other 23; it is only separate so wires2d_weight.sif can drive it.
Boundary Condition 7
Target Boundaries = 40
Potential = 1
End

View File

@ -0,0 +1,295 @@
# This script generates a 2D Gmsh geometry for the ANASEN detector's wire planes, including an optional hot needle and IC wires. The geometry is designed for adaptive meshing, with finer mesh near the wires and coarser mesh in empty space. The script takes a z-locus as a command-line argument to determine the interpolation between the two wire planes.
import numpy as np
import gmsh, sys
# --- Configuration Flags ---
include_ic_wires = True
include_needle = True
gmsh.initialize()
gmsh.model.add("adaptive_mesh")
gmsh.option.setNumber("General.NumThreads", 10)
# gmsh.option.setNumber("Mesh.Adapt.MaxNumberOfElements", 200000)
# gmsh.option.setNumber("Mesh.Adapt.MaxNumberOfNodes", 200000)
# gmsh.option.setNumber("Mesh.Adapt.MaxIter",5)
# gmsh.option.setNumber("Mesh.MeshSizeMin", 5e-3)
# gmsh.option.setNumber("Mesh.MeshSizeMax", 10.0)
gmsh.option.setNumber("Geometry.Tolerance", 4e-2)
# gmsh.option.setNumber("Mesh.MeshSizeExtendFromBoundary", 0)
# Only needed alongside mesh.recombine(), which is off. See the note there.
# gmsh.option.setNumber("Mesh.RecombinationAlgorithm", 2)
# gmsh.option.setNumber("Mesh.SubdivisionAlgorithm", 1)
gmsh.option.setNumber("Mesh.SecondOrderIncomplete", 1)
lc = 0.04
# z_loc = -174.3
if len(sys.argv) < 2:
print("Usage: python3 wires_gmsh2d_bc.py <z_locus in mm> [selected_cathode_index]")
quit()
z_loc = float(sys.argv[1])
# Cathode wire (0..23) given its own physical group, tag 40, so that
# wires2d_weight.sif can hold it at 1 V while everything else stays grounded.
selected_cathode = int(sys.argv[2]) if len(sys.argv) > 2 else 1
wireShift = 4.0
k = 2 * np.pi / 24.0
kg = k/2.0
# Plane 1 Offsets (-zmax/2)
# Anodes: -6*k (base) - 4*k (correction) = -10*k
offset_a1 = -6 * k - 4 * k
# Cathodes: -6*k (base) + 0.5*k (half-placement)
offset_c1 = -6 * k + (k / 2.0)
# Guard wires: aligned with cathodes
offset_g1 = offset_c1
# Plane 2 Offsets (+zmax/2) with Twist
offset_a2 = offset_a1 + (wireShift * k)
offset_c2 = offset_c1 - (wireShift * k)
offset_g2 = offset_c2
# 1 needle, 24 ic1, 24 ic2, 48 guard wires, 24 anodes, 24 cathodes
# needle at plane 1 at -zmax/2 no rotation
xarr_needle = np.array([0])
yarr_needle = np.array([0])
# ic1 wires, plane 1 at -zmax/2 no rotation
xarr_i11 = np.array([23 * np.cos(k * i) for i in range(24)])
yarr_i11 = np.array([23 * np.sin(k * i) for i in range(24)])
# ic1 wires, plane 1 at -zmax/2 no rotation
xarr_i21 = np.array([23 * np.cos(k * i + k / 2.0) for i in range(24)])
yarr_i21 = np.array([23 * np.sin(k * i + k / 2.0) for i in range(24)])
# --- Coordinate Arrays (Plane 1) ---
# Anodes: -k*i (Left-handed twist direction)
xarra_1 = np.array([37 * np.cos(-k * i + offset_a1) for i in range(24)])
yarra_1 = np.array([37 * np.sin(-k * i + offset_a1) for i in range(24)])
# Cathodes: +k*i (Right-handed twist direction)
xarrc_1 = np.array([42 * np.cos(k * i + offset_c1) for i in range(24)])
yarrc_1 = np.array([42 * np.sin(k * i + offset_c1) for i in range(24)])
# Guard Wires (48 wires, use kg spacing)
xarrg_1 = np.array([32 * np.cos(kg * i + offset_g1) for i in range(48)])
yarrg_1 = np.array([32 * np.sin(kg * i + offset_g1) for i in range(48)])
# needle at plane 2 at zmax/2
xarr_needle_2 = np.array([0])
yarr_needle_2 = np.array([0])
# #ic1 wires, plane 2 at zmax/2
xarr_i12 = np.array([23 * np.cos(k * i) for i in range(24)])
yarr_i12 = np.array([23 * np.sin(k * i) for i in range(24)])
# #ic2 wires, plane 2 at zmax/2
xarr_i22 = np.array([23 * np.cos(k * i + k / 2.0) for i in range(24)])
yarr_i22 = np.array([23 * np.sin(k * i + k / 2.0) for i in range(24)])
# --- Coordinate Arrays (Plane 2) ---
xarra_2 = np.array([37 * np.cos(-k * i + offset_a2) for i in range(24)])
yarra_2 = np.array([37 * np.sin(-k * i + offset_a2) for i in range(24)])
xarrc_2 = np.array([42 * np.cos(k * i + offset_c2) for i in range(24)])
yarrc_2 = np.array([42 * np.sin(k * i + offset_c2) for i in range(24)])
xarrg_2 = np.array([32 * np.cos(kg * i + offset_g2) for i in range(48)])
yarrg_2 = np.array([32 * np.sin(kg * i + offset_g2) for i in range(48)])
direction_needle_x = xarr_needle_2 - xarr_needle
direction_needle_y = yarr_needle_2 - yarr_needle
direction_ic1_x = xarr_i12 - xarr_i11
direction_ic1_y = yarr_i12 - yarr_i11
direction_ic2_x = xarr_i22 - xarr_i21
direction_ic2_y = yarr_i22 - yarr_i21
direction_guard_x = xarrg_2 - xarrg_1
direction_guard_y = yarrg_2 - yarrg_1
direction_anodes_x = xarra_2 - xarra_1
direction_anodes_y = yarra_2 - yarra_1
direction_cathodes_x = xarrc_2 - xarrc_1
direction_cathodes_y = yarrc_2 - yarrc_1
t = (z_loc + 174.3) / (2 * 174.3) # z=-174.3 is 0, z=+174.3 is 1
xloc_needle = xarr_needle + t * direction_needle_x
yloc_needle = yarr_needle + t * direction_needle_y
xloc_i1 = xarr_i11 + t * direction_ic1_x
yloc_i1 = yarr_i11 + t * direction_ic1_y
xloc_i2 = xarr_i21 + t * direction_ic2_x
yloc_i2 = yarr_i21 + t * direction_ic2_y
xloc_g = xarrg_1 + t * direction_guard_x
yloc_g = yarrg_1 + t * direction_guard_y
xloc_a = xarra_1 + t * direction_anodes_x
yloc_a = yarra_1 + t * direction_anodes_y
xloc_c = xarrc_1 + t * direction_cathodes_x
yloc_c = yarrc_1 + t * direction_cathodes_y
# wire_radius_a = 0.018 #mm
# wire_radius_c = 0.0762 #mm
# wire_radius_g = 0.0762 #mm
wire_radius = 0.254 # mm
needle = []
ic1_wires = []
ic2_wires = []
guard_wires = []
anode_wires = []
cathode_wires = []
iw1_tags = [(3, i) for i in range(24)]
iw2_tags = [(3, i + 24) for i in range(24)]
gw_tags = [(3, i + 48) for i in range(48)]
aw_tags = [(3, i) for i in range(24)]
cw_tags = [(3, i + 24) for i in range(24)]
# for i,[xa,ya,xc,yc] in enumerate(zip(xarra_1,yarra_1,xarrc_1,yarrc_1)):
# create Hot Needle (1 total)
for i, (xn, yn) in enumerate(zip(xloc_needle, yloc_needle)):
if include_needle:
ndisk = gmsh.model.occ.addDisk(xn, yn, 0, wire_radius, wire_radius)
needle.append(ndisk)
# create Guard Wires (48 total)
for xg, yg in zip(xloc_g, yloc_g):
gdisk = gmsh.model.occ.addDisk(xg, yg, 0, wire_radius, wire_radius)
guard_wires.append(gdisk)
# create Cathode Wires (24 total)
for xc, yc in zip(xloc_c, yloc_c):
cdisk = gmsh.model.occ.addDisk(xc, yc, 0, wire_radius, wire_radius)
cathode_wires.append(cdisk)
# create IC Anode and Cathode Wires (24 total each)
for i, (xa, ya) in enumerate(zip(xloc_a, yloc_a)):
adisk = gmsh.model.occ.addDisk(xa, ya, 0, wire_radius, wire_radius)
anode_wires.append(adisk)
# Place IC wires only if flag is True
if include_ic_wires:
i1disk = gmsh.model.occ.addDisk(
xloc_i1[i], yloc_i1[i], 0, wire_radius, wire_radius
)
i2disk = gmsh.model.occ.addDisk(
xloc_i2[i], yloc_i2[i], 0, wire_radius, wire_radius
)
ic1_wires.append(i1disk)
ic2_wires.append(i2disk)
anasen_barrel = gmsh.model.occ.addDisk(0, 0, 0, 500, 500)
# gmsh.model.occ.synchronize()
# gmsh.model.mesh.embed(1,anode_wires+cathode_wires,2,anasen_barrel)
gmsh.option.setNumber("Geometry.Tolerance", 1e-6)
gmsh.option.setNumber("Geometry.OCCFixDegenerated", 1)
gmsh.model.occ.synchronize()
# --- Surface Extraction ---
def get_surfs(disks):
surfs = []
for d in disks:
surfs += [
s[1] for s in gmsh.model.getBoundary([(2, d)], oriented=False) if s[0] == 1
]
return surfs
if not 0 <= selected_cathode < len(cathode_wires):
print("selected_cathode must be 0..%d" % (len(cathode_wires) - 1))
quit()
cathode_wires_other = [
disk for i, disk in enumerate(cathode_wires) if i != selected_cathode
]
# Cut the wire disks into the gas disk, replacing the old mesh.embed() call.
# embed only makes the mesher honour the curves; fragment splits the barrel
# into conforming surfaces, which is what lets one wire carry its own BC.
gmsh.model.occ.synchronize()
all_wire_disks = needle + guard_wires + cathode_wires + anode_wires
if include_ic_wires:
all_wire_disks += ic1_wires + ic2_wires
gmsh.model.occ.fragment([(2, anasen_barrel)], [(2, d) for d in all_wire_disks])
gmsh.model.occ.synchronize()
# fragment re-derives the bounding curves, so re-extract them.
needle_surfs = get_surfs(needle) if include_needle else []
gwire_surfs = get_surfs(guard_wires)
awire_surfs = get_surfs(anode_wires)
cwire_surfs = get_surfs(cathode_wires_other)
cwire_sel_surfs = get_surfs([cathode_wires[selected_cathode]])
i1wire_surfs = get_surfs(ic1_wires) if include_ic_wires else []
i2wire_surfs = get_surfs(ic2_wires) if include_ic_wires else []
all_active_wire_surfs = (
needle_surfs
+ gwire_surfs
+ awire_surfs
+ cwire_surfs
+ cwire_sel_surfs
+ i1wire_surfs
+ i2wire_surfs
)
f1 = gmsh.model.mesh.field.add("Distance")
gmsh.model.mesh.field.setNumbers(f1, "CurvesList", all_active_wire_surfs)
f2 = gmsh.model.mesh.field.add("Threshold")
gmsh.model.mesh.field.setNumber(f2, "InField", f1)
gmsh.model.mesh.field.setNumber(f2, "SizeMin", 0.05) # Fine mesh near wires
gmsh.model.mesh.field.setNumber(f2, "SizeMax", 5.0) # Large mesh in empty space
gmsh.model.mesh.field.setNumber(f2, "DistMin", 0.5) # Apply SizeMin within 1mm
gmsh.model.mesh.field.setNumber(f2, "DistMax", 15.0) # Transition to SizeMax by 20mm
gmsh.model.mesh.field.setAsBackgroundMesh(f2)
# --- Physical Groups ---
# These tags are the Target Bodies / Target Boundaries numbers in the .sif
# files. Do not renumber them without changing both sifs -- and note that
# ElmerGrid's -autoclean renumbers them for you, which silently breaks every
# boundary condition.
# Needle
if include_needle:
gmsh.model.addPhysicalGroup(1, needle_surfs, tag=1, name="hot_needle")
# IC Wires
if include_ic_wires:
gmsh.model.addPhysicalGroup(1, i1wire_surfs, tag=2, name="ic_wire_1")
gmsh.model.addPhysicalGroup(1, i2wire_surfs, tag=3, name="ic_wire_2")
# Proportional Counter Wires
gmsh.model.addPhysicalGroup(1, gwire_surfs, tag=10, name="guard_wires")
gmsh.model.addPhysicalGroup(1, awire_surfs, tag=20, name="anode_wires")
gmsh.model.addPhysicalGroup(1, cwire_surfs, tag=30, name="cathode_wires")
# The one cathode the weighting potential is solved for, kept out of tag 30 so
# wires2d_weight.sif can drive it independently.
gmsh.model.addPhysicalGroup(1, cwire_sel_surfs, tag=40, name="cathode_wire_selected")
# Gas Volume (2D). fragment split the barrel into many surfaces, so collect
# them all; naming the original disk alone would solve one sliver of the domain.
all_surfaces_2d = [s[1] for s in gmsh.model.getEntities(dim=2)]
gmsh.model.addPhysicalGroup(2, all_surfaces_2d, tag=13, name="gas")
gmsh.option.setNumber("Mesh.Algorithm", 5)
gmsh.model.mesh.generate(2)
gmsh.model.mesh.setOrder(2)
# recombine() ran for over half an hour on the barrel surface without
# finishing, and refine() took the mesh to ~16M nodes. Neither is needed: the
# Threshold field above already gives 0.05 mm elements on 0.254 mm wires.
# gmsh.model.mesh.recombine()
# gmsh.model.mesh.refine()
gmsh.write("wires2d.msh")
# gmsh.fltk.run()
gmsh.finalize()

2
anasen_fem/clean.sh Executable file
View File

@ -0,0 +1,2 @@
rm -f wires2d.msh
rm -rf wires2d/*

View File

@ -0,0 +1,2 @@
1
13 1.0

148
anasen_fem/dotproduct.py Normal file
View File

@ -0,0 +1,148 @@
#!/usr/bin/env python3
"""Node-by-node dot product of the physical field with a Ramo weighting field.
Reads the two Elmer VTUs from wires2d.sif and wires2d_weight.sif and writes one
carrying DotProduct = E . E_w. E is in V/m and E_w in 1/m (its electrode is
driven to 1 V), so DotProduct is in V/m^2.
By Shockley-Ramo a charge q drifting at v induces i = -q v . E_w, and for
v = mu E that is -q mu (E . E_w). So this map is the induced-current density up
to the constant -q*mu; --scale folds it in. The signs of q and mu differ
between the electron and ion components, so they are left to the caller.
python3 dotproduct.py
python3 dotproduct.py --scale -1.0e-4
"""
import argparse
import sys
import numpy as np
try:
import meshio
except ImportError:
sys.exit("meshio is required: pip install meshio")
def find_array(point_data, wanted, label):
"""Look up a point-data array, ignoring case and spacing.
Elmer lowercases variable names into the VTU and the spelling has varied
between versions, so match loosely and list what is there on failure.
"""
def norm(s):
return "".join(s.lower().split())
target = norm(wanted)
for key in point_data:
if norm(key) == target:
return point_data[key]
sys.exit(
"%s: no point-data array matching %r.\nAvailable arrays: %s"
% (label, wanted, sorted(point_data))
)
def as_vectors(arr, label):
"""Return an (N, ncomp) float array, tolerating flat or 2-component data."""
a = np.asarray(arr, dtype=float)
if a.ndim == 1:
sys.exit("%s: expected a vector field, got a scalar array" % label)
if a.ndim != 2:
sys.exit("%s: expected a 2D array, got shape %s" % (label, a.shape))
return a
def main():
ap = argparse.ArgumentParser(description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("--phys", default="wires2d/elfield_anasen_t0001.vtu",
help="VTU from the physical solve (wires2d.sif)")
ap.add_argument("--weight", default="wires2d/elfield_weight_t0001.vtu",
help="VTU from the weighting solve (wires2d_weight.sif)")
ap.add_argument("--out", default="wires2d/dotproduct.vtu",
help="output VTU")
ap.add_argument("--field", default="electric field",
help="name of the electric-field array in both inputs")
ap.add_argument("--scale", type=float, default=None,
help="constant (e.g. -q*mu) multiplying the dot product; "
"writes an extra ScaledDotProduct array")
ap.add_argument("--rtol", type=float, default=0.0,
help="relative tolerance for the node-position check")
ap.add_argument("--atol", type=float, default=1e-12,
help="absolute tolerance (m) for the node-position check")
args = ap.parse_args()
phys = meshio.read(args.phys)
weight = meshio.read(args.weight)
# Taken node by node, so both solves must have produced the same node list.
# If they did not, every value below is meaningless and nothing else checks.
if phys.points.shape != weight.points.shape:
sys.exit(
"Node counts differ: %s has %d nodes, %s has %d. Both solves must "
"read the same mesh (same wires2d/ directory)."
% (args.phys, len(phys.points), args.weight, len(weight.points))
)
if not np.allclose(phys.points, weight.points, rtol=args.rtol, atol=args.atol):
worst = np.max(np.linalg.norm(phys.points - weight.points, axis=1))
sys.exit(
"Node positions do not match (largest discrepancy %.3e m). The two "
"solves were run on different meshes." % worst
)
E = as_vectors(find_array(phys.point_data, args.field, args.phys), args.phys)
Ew = as_vectors(find_array(weight.point_data, args.field, args.weight), args.weight)
# A zero field means the solve never ran: Laplace with no Dirichlet
# condition applied has the trivial solution and Elmer reports no error.
# Without this the chain completes and writes zeros, and the only symptom
# is a blank plot.
for label, arr, path in (
("physical", E, args.phys),
("weighting", Ew, args.weight),
):
if not np.any(arr):
sys.exit(
"the %s field in %s is identically zero.\n"
"Elmer returned the trivial solution, which means no boundary "
"condition was applied. Compare the Target Bodies and Target "
"Boundaries numbers in the .sif against wires2d/mesh.names -- "
"ElmerGrid's -autoclean renumbers them." % (label, path)
)
# Elmer may pad 2D vectors to three components; compare only the
# components both arrays actually carry.
ncomp = min(E.shape[1], Ew.shape[1])
if E.shape[1] != Ew.shape[1]:
print("note: component counts differ (%d vs %d), using the first %d"
% (E.shape[1], Ew.shape[1], ncomp))
dot = np.einsum("ij,ij->i", E[:, :ncomp], Ew[:, :ncomp])
out = meshio.Mesh(
points=phys.points,
cells=phys.cells,
point_data=dict(phys.point_data),
cell_data=dict(phys.cell_data),
)
out.point_data["DotProduct"] = dot
out.point_data["WeightingField"] = Ew
if args.scale is not None:
out.point_data["ScaledDotProduct"] = args.scale * dot
meshio.write(args.out, out)
finite = dot[np.isfinite(dot)]
print("nodes : %d" % len(dot))
print("components used : %d" % ncomp)
print("DotProduct min : %.6e V/m^2" % finite.min())
print("DotProduct max : %.6e V/m^2" % finite.max())
print("DotProduct mean : %.6e V/m^2" % finite.mean())
if len(finite) != len(dot):
print("WARNING: %d non-finite nodes" % (len(dot) - len(finite)))
print("wrote %s" % args.out)
if __name__ == "__main__":
main()

107
anasen_fem/garfield_sim.py Normal file
View File

@ -0,0 +1,107 @@
import ROOT
import os
import sys
# 1. FIX: Manually load the Garfield library if it's not in the ROOT namespace
# Update this path to your actual installation location
garfield_lib_path = "/home/vs19g/garfieldpp/install/lib/libGarfield.so" #penguin path
# garfield_lib_path = "/home/vsitaraman/garfieldpp/install/lib/libGarfield.so" #laptop path
if os.path.exists(garfield_lib_path):
ROOT.gSystem.Load(garfield_lib_path)
else:
print(f"CRITICAL ERROR: {garfield_lib_path} not found.")
sys.exit(1)
# Verify access
try:
test_gas = ROOT.Garfield.MediumMagboltz()
except AttributeError:
print("ERROR: Garfield shared library loaded, but 'Garfield' namespace not found.")
sys.exit(1)
# --- 2. GAS SETUP (96% He, 4% CO2) ---
gas = ROOT.Garfield.MediumMagboltz()
gas_file = "He96_CO2_4_260Torr.gas"
if not os.path.exists(gas_file):
print("Generating new Magboltz gas table (260 Torr)...")
# --- Optimized Magboltz Settings ---
gas.SetComposition("he", 96.0, "co2", 4.0)
gas.SetTemperature(293.15)
gas.SetPressure(260.0)
# 1. Limit the Field Grid:
gas.SetFieldGrid(10., 80000., 20, True)
# 2. Reduce the precision slightly for the first run:
gas.GenerateGasTable(8)
gas.WriteGasFile(gas_file)
else:
print(f"Loading existing gas table: {gas_file}")
gas.LoadGasFile(gas_file)
# --- 3. FIELD MAP SETUP ---
fm = ROOT.Garfield.ComponentElmer2d()
fm.Initialise("wires2d/mesh.header",
"wires2d/mesh.elements",
"wires2d/mesh.nodes",
"dielectrics.dat",
"wires2d/elstatics.result",
"mm")
# Set the medium (Body 13 from your Gmsh script)
fm.SetMedium(0, gas)
# --- 4. SENSOR AND DRIFT SETUP ---
sensor = ROOT.Garfield.Sensor()
sensor.AddComponent(fm)
sensor.SetArea(-50.0, -50.0, -5.0, 50.0, 50.0, 5.0) #hardcoding the sesnsor area to define a psuedo 3d geometry
# Heavy Ion Drift (RKF) - Best for the general track
drift = ROOT.Garfield.DriftLineRKF()
drift.SetSensor(sensor)
# Electron Avalanche (Microscopic) - Best for high-field gain
aval = ROOT.Garfield.AvalancheMicroscopic()
aval.SetSensor(sensor)
# --- 5. EXECUTION ---
# Starting position (e.g., near the IC wires at r=23mm or closer to Anodes)
x0, y0, z0, t0 = 3.50, 0.0, 0.0, 0.0
print(f"Simulating heavy ion drift from r={x0}...")
drift.DriftIon(x0, y0, z0, t0)
# Create a file to store the heavy ion track
with open("heavy_ion_track.csv", "w") as f:
f.write("x,y,z,t\n")
# After running drift.DriftIon(x0, y0, z0, t0):
n_points = drift.GetNumberOfDriftLinePoints()
for i in range(n_points):
xi, yi, zi, ti = ROOT.double(0), ROOT.double(0), ROOT.double(0), ROOT.double(0)
drift.GetDriftLinePoint(i, xi, yi, zi, ti)
f.write(f"{xi},{yi},{zi},{ti}\n")
print(f"Simulating electron avalanche from r={x0}...")
# AvalancheElectron(x, y, z, t, energy, dx, dy, dz)
aval.AvalancheElectron(x0, y0, z0, t0, 0.1, 0.0, 0.0, 0.0)
with open("avalanche_endpoints.csv", "w") as f:
f.write("x,y,z,t\n")
# After aval.AvalancheElectron(...)
n_endpoints = aval.GetNumberOfEndpoints()
for i in range(n_endpoints):
# Get start and end points of each electron in the avalanche
x1, y1, z1, t1, e1 = ROOT.double(0), ROOT.double(0), ROOT.double(0), ROOT.double(0), ROOT.double(0)
x2, y2, z2, t2, e2, status = ROOT.double(0), ROOT.double(0), ROOT.double(0), ROOT.double(0), ROOT.double(0), ROOT.int(0)
aval.GetEndpoint(i, x1, y1, z1, t1, e1, x2, y2, z2, t2, e2, status)
# We save the endpoint (x2, y2, z2) where the electron was collected or attached
f.write(f"{x2},{y2},{z2},{t2}\n")
print("Simulation complete.")

114
anasen_fem/junk/wires.py Executable file
View File

@ -0,0 +1,114 @@
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
acolors = plt.get_cmap('tab20',24)
ccolors = plt.get_cmap('tab20',24)
k=-2*np.pi/24.
offset = 6*k + 3*k #-pi/2
xarra_1 = np.array([37*np.cos(k*i+offset) for i in np.arange(0,24)])
yarra_1 = np.array([37*np.sin(k*i+offset) for i in np.arange(0,24)])
labelsa_1 = np.array([i for i in np.arange(0,24)])
fig,ax = plt.subplots(figsize=(10,10))
ax.invert_yaxis()
ax.plot(xarra_1,yarra_1,"x",label="anode, z=-L/2")
for x,y,label in zip(xarra_1,yarra_1,labelsa_1):
ax.text(x,y,label)
kc=2*np.pi/24.
offsetc = -4*kc + 2*kc - np.pi/24 #-pi/4
xarrc_1 = np.array([42*np.cos(kc*i+offsetc) for i in np.arange(0,24)])
yarrc_1 = np.array([42*np.sin(kc*i+offsetc) for i in np.arange(0,24)])
labelsc_1 = np.array([i for i in np.arange(0,24)])
ax.plot(xarrc_1,yarrc_1,"o",label="cathode, z=-L/2, where they are picked up")
for x,y,label in zip(xarrc_1,yarrc_1,labelsc_1):
ax.text(x,y,label)
plt.title("z=-L/2 plane, beam going into the plane along +z, (+x right, +y down)")
plt.grid()
plt.legend()
plt.savefig("plane1.png")
plt.show()
fig,ax = plt.subplots(figsize=(10,10))
ax.invert_yaxis()
offset = offset-3*k
xarra_2 = np.array([37*np.cos(k*i+offset) for i in np.arange(0,24)])
yarra_2 = np.array([37*np.sin(k*i+offset) for i in np.arange(0,24)])
labelsa_2 = np.array([i for i in np.arange(0,24)])
ax.plot(xarra_2,yarra_2,"x",label="anode, z=+L/2, where they are picked up")
for x,y,label in zip(xarra_2,yarra_2,labelsa_2):
ax.text(x,y,label)
offsetc = offsetc-3*kc
xarrc_2 = np.array([42*np.cos(kc*i+offsetc) for i in np.arange(0,24)])
yarrc_2 = np.array([42*np.sin(kc*i+offsetc) for i in np.arange(0,24)])
labelsc_2 = np.array([i for i in np.arange(0,24)])
ax.plot(xarrc_2,yarrc_2,"o",label="cathode, z=+L/2")
for x,y,label in zip(xarrc_2,yarrc_2,labelsc_2):
ax.text(x,y,label)
plt.title("z=+L/2 plane, beam going into the plane along +z, (+x right, +y down)")
plt.grid()
plt.legend()
plt.savefig("plane2.png")
plt.show()
fig = plt.figure(figsize=(10,10))
ax = fig.add_subplot(111,projection='3d')
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_zlabel("z")
xx_a = np.array([[x1,x2] for x1,x2 in zip(xarra_1,xarra_2)])
yy_a = np.array([[y1,y2] for y1,y2 in zip(yarra_1,yarra_2)])
zz_a = np.array([[-173.5,173.5] for x1,x2 in zip(xarra_1,xarra_2)])
for i,[xx,yy,zz] in enumerate(zip(xx_a,yy_a,zz_a)):
ax.plot(xx,yy,zz,'-',color=acolors(i/24))
for i,[x,y,label] in enumerate(zip(xarra_1,yarra_1,labelsa_1)):
ax.text(x,y,-173,"a"+str(label),color=acolors(i/24))
for i,[x,y,label] in enumerate(zip(xarra_2,yarra_2,labelsa_2)):
ax.text(x,y,+173,"a"+str(label),color=acolors(i/24))
xx_c = np.array([[x1,x2] for x1,x2 in zip(xarrc_1,xarrc_2)])
yy_c = np.array([[y1,y2] for y1,y2 in zip(yarrc_1,yarrc_2)])
zz_c = np.array([[-173.5,173.5] for x1,x2 in zip(xarrc_1,xarrc_2)])
for i,[xx,yy,zz] in enumerate(zip(xx_c,yy_c,zz_c)):
ax.plot(xx,yy,zz,'--',color=ccolors(((47-i)%24)/24))
for i,[x,y,label] in enumerate(zip(xarrc_1,yarrc_1,labelsc_1)):
ax.text(x,y,-173,"c"+str(label),color=ccolors(((25-i)%24)/24))
for i,[x,y,label] in enumerate(zip(xarrc_2,yarrc_2,labelsc_2)):
ax.text(x,y,+173,"c"+str(label),color=ccolors(((47-i)%24)/24))
ax.view_init(elev=-53, azim=-106, roll=18)
plt.tight_layout()
plt.show()
phi_qqq = np.array([[2*np.pi*(-i*16+j+0.5)/(16*4) for i in range(4)] for j in range(16)])
print(phi_qqq)
#'''
for i,[phi1,phi2,phi3,phi4] in enumerate(phi_qqq):
ax.plot([50*np.cos(phi1),100*np.cos(phi1)],[50*np.sin(phi1),100*np.sin(phi1)],[100,100],'-',color='red')
ax.text(104*np.cos(phi1),104*np.sin(phi1),100,"0_%d"%(i),color="red")
ax.plot([50*np.cos(phi2),100*np.cos(phi2)],[50*np.sin(phi2),100*np.sin(phi2)],[100,100],'-',color='green')
ax.text(104*np.cos(phi2),104*np.sin(phi2),100,"1_%d"%(i),color="green")
ax.plot([50*np.cos(phi3),100*np.cos(phi3)],[50*np.sin(phi3),100*np.sin(phi3)],[100,100],'-',color='blue')
ax.text(104*np.cos(phi3),104*np.sin(phi3),100,"2_%d"%(i),color="blue")
ax.plot([50*np.cos(phi4),100*np.cos(phi4)],[50*np.sin(phi4),100*np.sin(phi4)],[100,100],'-',color='brown')
ax.text(104*np.cos(phi4),104*np.sin(phi4),100,"3_%d"%(i),color="brown")
#'''
#coords_qqq = np.array([[50*np.cos(phi),100*np.sin(phi),100] for phi in phi_qqq]).T
plt.tight_layout()
plt.show()

View File

@ -0,0 +1,71 @@
Check Keywords Warn
Header
Mesh DB "." "wires2d"
End
Simulation
Coordinate System = Cartesian 2D
Simulation Type = Steady State
Steady State Max Iterations = 1
Output File = "elstatics.result"
Post File = "elstatics.ep"
End
Constants
Permittivity Of Vacuum = 8.8542e-12
End
Body 1
Target Bodies(1) = 13
Equation = 1
Material = 1
End
Equation 1
Active Solvers(2) = 1 2
End
Material 1
Relative Permittivity = 1
End
Solver 1
Equation = Electrostatics
Procedure = "StatElecSolve" "StatElecSolver"
Variable = Potential
Variable DOFs = 1
Calculate Electric Field = True
Calculate Electric Flux = False
Linear System Solver = Iterative
Linear System Iterative Method = CG
Linear System Max Iterations = 500
Linear System Convergence Tolerance = 1.0e-8
Linear System Preconditioning = ILU1
End
Solver 2
Equation = Electric Force
Procedure = "ElectricForce" "StatElecForce"
End
Boundary Condition 1
Target Boundaries = 10
Potential = 650
Calculate Electric Force = True
End
Boundary Condition 2
Target Boundaries = 20
Potential = 0
End

91
anasen_fem/junk/wires_gmsh.py Executable file
View File

@ -0,0 +1,91 @@
import numpy as np
import gmsh
gmsh.initialize()
gmsh.model.add("adaptive_mesh")
gmsh.option.setNumber('General.NumThreads', 4)
#gmsh.option.setNumber("Mesh.Adapt.MaxNumberOfElements", 200000)
#gmsh.option.setNumber("Mesh.Adapt.MaxNumberOfNodes", 200000)
#gmsh.option.setNumber("Mesh.Adapt.MaxIter",5)
#gmsh.option.setNumber("Mesh.MeshSizeMin", 5e-3)
#gmsh.option.setNumber("Mesh.MeshSizeMax", 10.0)
#gmsh.option.setNumber("Mesh.CharacteristicLengthFromCurvature", 0)
lc = 0.01
#anodes, plane 1 at -zmax/2
k=-2*np.pi/24.
offset = 6*k + 3*k #-pi/2
xarra_1 = np.array([37*np.cos(k*i+offset) for i in np.arange(0,24)])
yarra_1 = np.array([37*np.sin(k*i+offset) for i in np.arange(0,24)])
#cathodes, plane 1 at -zmax/2
kc=2*np.pi/24.
offsetc = -4*kc + 2*kc - np.pi/24 #-pi/4
xarrc_1 = np.array([42*np.cos(kc*i+offsetc) for i in np.arange(0,24)])
yarrc_1 = np.array([42*np.sin(kc*i+offsetc) for i in np.arange(0,24)])
#anodes, plane 2 at +zmax/2
offset = offset-3*k
xarra_2 = np.array([37*np.cos(k*i+offset) for i in np.arange(0,24)])
yarra_2 = np.array([37*np.sin(k*i+offset) for i in np.arange(0,24)])
#cathodes, plane2 at +zmax/2
offsetc = offsetc-3*kc
xarrc_2 = np.array([42*np.cos(kc*i+offsetc) for i in np.arange(0,24)])
yarrc_2 = np.array([42*np.sin(kc*i+offsetc) for i in np.arange(0,24)])
pa1 = []
pa2 = []
pc1 = []
pc2 = []
wire_radius = 0.254 #mm
anode_wires = []
cathode_wires = []
aw_tags = [(3,i) for i in range(24)]
cw_tags = [(3,i+24) for i in range(24)]
for i,[xa,ya,xc,yc,xa2,ya2,xc2,yc2] in enumerate(zip(xarra_1,yarra_1,xarrc_1,yarrc_1,xarra_2,yarra_2,xarrc_2,yarrc_2)):
print(i,xa,ya,-178.3,xc,yc,-178.3,xa2,ya2,178.3,xc2,yc2,178.3)
anode_wires.append(gmsh.model.occ.addCylinder(xa,ya,-178.3,(xa2-xa),(ya2-ya),178.3*2,wire_radius,i)) #x,y,z of first face center, dx,dy,dz of the axis, then the wire radius
cathode_wires.append(gmsh.model.occ.addCylinder(xc,yc,-178.3,(xc2-xc),(yc2-yc),178.3*2,wire_radius,i+24)) #cathode tags 24-47, anode 0-23
anasen_barrel = gmsh.model.occ.addCylinder(0,0,-500,0,0,500+605,300,1234) #tag 1234
#anasen_barrel = gmsh.model.occ.addCylinder(0,0,-500,0,0,500+605,300,1234) #tag 1234
gmsh.model.occ.synchronize()
all_wires = aw_tags+cw_tags
gmsh.model.occ.fragment([(3,1234)],all_wires)
gmsh.model.occ.removeAllDuplicates()
gmsh.model.occ.synchronize()
gmsh.option.setNumber("Geometry.Tolerance", 1e-6)
gmsh.option.setNumber("Geometry.OCCFixDegenerated", 1)
gmsh.option.setNumber("Geometry.OCCFixSmallEdges", 1)
gmsh.option.setNumber("Geometry.OCCFixSmallFaces", 1)
wire_surfs = []
for w in anode_wires + cathode_wires:
wire_surfs += [s[1] for s in gmsh.model.getBoundary([(3,w)], oriented=False) if s[0] == 2]
#'''
f1 = gmsh.model.mesh.field.add("Distance")
gmsh.model.mesh.field.setNumbers(f1, "FacesList", wire_surfs) # Example curves
f2 = gmsh.model.mesh.field.add("Threshold")
gmsh.model.mesh.field.setNumber(f2, "InField", f1)
gmsh.model.mesh.field.setNumber(f2, "SizeMin", 0.05)
gmsh.model.mesh.field.setNumber(f2, "SizeMax", 5.)
gmsh.model.mesh.field.setNumber(f2, "DistMin", 1.)
gmsh.model.mesh.field.setNumber(f2, "DistMax", 20.)
gmsh.model.mesh.field.setAsBackgroundMesh(f2)
#'''
gmsh.option.setNumber("Mesh.Algorithm", 2)
gmsh.option.setNumber("Mesh.Algorithm3D", 1) # For 3D meshes
gmsh.model.mesh.generate(dim=3)
#gmsh.model.mesh.refine()
#gmsh.model.mesh.refine()
#gmsh.model.mesh.refine()
gmsh.fltk.run()
gmsh.finalize()

View File

@ -0,0 +1,87 @@
import numpy as np
import gmsh
gmsh.initialize()
#gmsh.model.add("adaptive_mesh")
gmsh.option.setNumber('General.NumThreads', 4)
#gmsh.option.setNumber("Mesh.Adapt.MaxNumberOfElements", 200000)
#gmsh.option.setNumber("Mesh.Adapt.MaxNumberOfNodes", 200000)
#gmsh.option.setNumber("Mesh.Adapt.MaxIter",5)
#gmsh.option.setNumber("Mesh.MeshSizeMin", 5e-3)
#gmsh.option.setNumber("Mesh.MeshSizeMax", 10.0)
gmsh.option.setNumber("Geometry.Tolerance", 1e-2)
#gmsh.option.setNumber("Mesh.CharacteristicLengthFromCurvature", 0)
lc = 0.04
#anodes, plane 1 at -zmax/2
k=-2*np.pi/24.
offset = 6*k + 3*k #-pi/2
xarra_1 = np.array([37*np.cos(k*i+offset) for i in np.arange(0,24)])
yarra_1 = np.array([37*np.sin(k*i+offset) for i in np.arange(0,24)])
#cathodes, plane 1 at -zmax/2
kc=2*np.pi/24.
offsetc = -4*kc + 2*kc - np.pi/24 #-pi/4
xarrc_1 = np.array([42*np.cos(kc*i+offsetc) for i in np.arange(0,24)])
yarrc_1 = np.array([42*np.sin(kc*i+offsetc) for i in np.arange(0,24)])
#anodes, plane 2 at +zmax/2
offset = offset-3*k
xarra_2 = np.array([37*np.cos(k*i+offset) for i in np.arange(0,24)])
yarra_2 = np.array([37*np.sin(k*i+offset) for i in np.arange(0,24)])
#cathodes, plane2 at +zmax/2
offsetc = offsetc-3*kc
xarrc_2 = np.array([42*np.cos(kc*i+offsetc) for i in np.arange(0,24)])
yarrc_2 = np.array([42*np.sin(kc*i+offsetc) for i in np.arange(0,24)])
wire_radius = 0.254 #mm
anode_wires = []
cathode_wires = []
aw_tags = [(3,i) for i in range(24)]
cw_tags = [(3,i+24) for i in range(24)]
for i,[xa,ya,xc,yc,xa2,ya2,xc2,yc2] in enumerate(zip(xarra_1,yarra_1,xarrc_1,yarrc_1,xarra_2,yarra_2,xarrc_2,yarrc_2)):
print(i,xa,ya,-178.3,xc,yc,-178.3,xa2,ya2,178.3,xc2,yc2,178.3)
pa1 = gmsh.model.occ.addPoint(xa,ya,-178.3,lc)
pa2 = gmsh.model.occ.addPoint(xa2,ya2,178.3,lc)
pc1 = gmsh.model.occ.addPoint(xc,yc,-178.3,lc)
pc2 = gmsh.model.occ.addPoint(xc2,yc2,178.3,lc)
linea = gmsh.model.occ.addLine(pa1,pa2)
linec = gmsh.model.occ.addLine(pc1,pc2)
anode_wires.append(linea)
cathode_wires.append(linec)
#anasen_barrel = gmsh.model.occ.addCylinder(0,0,-500,0,0,500+605,300,1234) #tag 1234
anasen_barrel = gmsh.model.occ.addCylinder(0,0,-200, 0,0,400, 300) #tag 1234
gmsh.model.occ.synchronize()
gmsh.model.mesh.embed(1,anode_wires+cathode_wires,3,anasen_barrel)
f1 = gmsh.model.mesh.field.add("Distance")
gmsh.model.mesh.field.setNumbers(f1,"CurvesList",anode_wires+cathode_wires)
f2 = gmsh.model.mesh.field.add("Threshold")
gmsh.model.mesh.field.setNumber(f2,"InField",f1)
gmsh.model.mesh.field.setNumber(f2,"SizeMin",0.08)
gmsh.model.mesh.field.setNumber(f2,"SizeMax",5)
gmsh.model.mesh.field.setNumber(f2,"DistMin",1)
gmsh.model.mesh.field.setNumber(f2,"DistMax",20)
gmsh.model.mesh.field.setAsBackgroundMesh(f2)
gmsh.model.addPhysicalGroup(1, anode_wires, tag=10)
gmsh.model.setPhysicalName(1,10,"anode_wires")
gmsh.model.addPhysicalGroup(1, cathode_wires, tag=20)
gmsh.model.setPhysicalName(1,20,"cathode_wires")
gmsh.option.setNumber("Mesh.Algorithm",6)
gmsh.option.setNumber("Mesh.Algorithm3D", 10) # For 3D meshes
gmsh.model.mesh.generate(dim=3)
gmsh.model.mesh.refine()
#gmsh.model.mesh.refine()
#gmsh.model.mesh.refine()
gmsh.fltk.run()
gmsh.finalize()

279
anasen_fem/paraview_dotproduct.py Executable file
View File

@ -0,0 +1,279 @@
#!/home/vsitaraman/ParaView-6.1.0-RC1-MPI-Linux-Python3.12-x86_64/bin/pvbatch
# Point the shebang above at your own pvbatch/pvpython, as for paraview_plotter.py.
#
# Renders the E . E_w map written by dotproduct.py.
#
# By Shockley-Ramo the dot product describes the current induced on ONE
# electrode: the cathode that wires2d_weight.sif held at 1 V. Everywhere else
# the number is just the tail of that electrode's weighting field, so the view
# is centred on that wire and nothing else. Its position is read out of the
# weighting solve rather than recomputed from the geometry -- the driven
# electrode is wherever the weighting potential reaches 1.
#
# Usage (from anasen_fem/):
# ./paraview_dotproduct.py [dotproduct.vtu] [output.png] [weight.vtu]
#
# Re-plotting an archived z needs its archived weighting solve as well, since
# that is where the driven cathode's position is read from:
# ./paraview_dotproduct.py wires2d/vtu_files/dotproduct_10_0.0000.vtu \
# png/DotProduct_output_z_10_0.0000.png \
# wires2d/vtu_files/elfield_weight_10_0.0000.vtu
import sys
import numpy as np
from paraview.simple import *
INPUT_VTU = sys.argv[1] if len(sys.argv) > 1 else "wires2d/dotproduct.vtu"
OUTPUT_IMAGE = sys.argv[2] if len(sys.argv) > 2 else "DotProduct_output.png"
# Weighting solve, used only to locate the driven cathode. Must be the one that
# belongs to this dot product -- the wire moves with z.
WEIGHT_VTU = sys.argv[3] if len(sys.argv) > 3 else "wires2d/elfield_weight_t0001.vtu"
# Half-height of the view in metres. Cathodes sit every 15 degrees at a radius
# of about 36.4 mm, so the neighbours either side are 9.5 mm away; 14 mm takes
# them both in with a margin.
# Image size. The structure round the driven cathode is much wider than it is
# tall, so a square frame spends most of its area on empty gas.
VIEW_SIZE = [2000, 1200]
# CameraParallelScale, which is the half-HEIGHT of the view in metres. The
# width follows from the aspect: half-height * (VIEW_SIZE[0] / VIEW_SIZE[1]).
# So widening the frame alone does not crop anything, it just shows more
# sideways; the scale has to come down with it. 0.0084 at 2000x1200 keeps the
# same 28 mm width as the old square 0.014 and trims the height to 16.8 mm.
ZOOM_SCALE = 0.0084
# The driven cathode swings round the axis as z changes, so with a fixed "up"
# every z comes out rotated by a different amount and the shots cannot be laid
# side by side. Aligning up with the outward radial direction through the
# cathode puts the detector axis below and the barrel wall above in every
# frame, whatever z. Set False for plain screen-aligned axes.
ALIGN_RADIAL = True
# Shift of the view centre along the outward radial, in metres. Up in the frame
# is the outward radial, so a NEGATIVE value walks the camera in towards the
# detector axis and the cathode rides higher in the picture. Positive drops it.
# Purely framing; it does not move the wire.
CENTER_SHIFT = -0.0036
# Second, wider shot over the same quadrant paraview_plotter.py frames, so the
# dot product can be laid beside the field and contour views of that z. Centre
# is at x < 0, y > 0; negate the y for the lower-left quadrant instead.
# Colour range for DotProduct. Both ends negative: E and E_w point in broadly
# opposite senses over most of the drift volume.
# Most of the painted patch lies between -2e7 and -2e6. Against a range
# reaching -3.1e8 that is the top few percent of the scale, so it all came out
# at the white end of Greens. Narrowing the range spreads those values over the
# green; anything stronger simply clamps to the darkest end, which only affects
# the last fraction of a millimetre at the wire surface.
DOT_MIN, DOT_MAX = -8.0e7, -3.0e6
# Opacity of the painted map. Anything at least as strong as OPAQUE_BELOW is
# drawn solid; from there it ramps down to transparent at FADE_TO, so the grey
# shows through with a soft round edge rather than the ragged one a hard cut
# leaves.
#
# The plateau matters more than either endpoint. Ramping all the way from
# DOT_MIN (-3.1e8) meant the opacity at -5e7 was only about 0.13, so the whole
# patch read as washed out however the fade point was set.
OPAQUE_BELOW = -2.0e7
FADE_TO = -2.0e6
# Coarse cut, only to keep the far field out of the render. Must be nearer zero
# than FADE_TO so its hard edge lands where the surface is already fully
# transparent and cannot be seen.
PAINT_CUT = -5.0e5
# "signed-log" maps sign and magnitude together through a signed logarithm and
# a diverging colour map; "linear" is the earlier one-sided Greens scale, which
# the constants above still drive.
COLOR_MODE = "signed-log"
# X0 sets where the mapping turns from linear to logarithmic, and so how much
# of the weak far field survives at all: at 1e6 the data map to -2.51 .. 1.62,
# against -3.51 .. 2.61 at 1e5. Raise it to paint less.
SYMLOG_X0 = 1.0e6
SYMLOG_LIMIT = 2.5 # symmetric colour limit; match it to the mapped range
SYMLOG_OPAQUE = 0.6 # |value| at which the map is fully opaque
SYMLOG_FADE = 0.3 # |value| below which it is transparent
# Shape of the two opacity ramps, as opposed to where they sit.
#
# A piecewise function's midpoint describes the segment running RIGHTWARD from
# each node, and the two ramps run in opposite senses -- falling on the
# negative side, rising on the positive. So a single midpoint below 0.5 applied
# to both makes the negative lobe fade out sooner while the positive lobe goes
# opaque sooner, rendering the two polarities at different effective
# thresholds. Mirroring it keeps them symmetric: below 0.5 both sides go opaque
# sooner, so more is painted, equally on each.
#
# Sharpness is the independent dial for how abrupt the edge is: 0 smooth, 1 a
# hard step. 0.5 is already close to a step.
SYMLOG_MIDPOINT = 0.3
SYMLOG_SHARPNESS = 0.0
DIVERGING_PRESET = "PRGn" # purple / white / green; zero sits at white
BACKGROUND = [0.35, 0.34, 0.33] # grey
# Equipotential lines over the whole frame, painted gas or not. Off by default:
# the map is the subject here, and the lines are already in the contour plots.
SHOW_CONTOURS = False
CONTOUR_BY = "potential"
CONTOUR_LEVELS = list(np.arange(0, 660, 660 / 40.0))
def threshold_between(source, array, low, high):
"""Threshold filter, tolerating the pre-5.10 property names."""
t = Threshold(Input=source, Scalars=["POINTS", array])
try:
t.LowerThreshold = low
t.UpperThreshold = high
t.ThresholdMethod = "Between"
except Exception:
t.ThresholdRange = [low, high]
return t
# --- locate the driven cathode: the weighting potential is 1 on it ---
weight_reader = XMLUnstructuredGridReader(FileName=[WEIGHT_VTU])
driven = threshold_between(weight_reader, "potential", 0.99, 1.01)
driven.UpdatePipeline()
info = driven.GetDataInformation()
if info.GetNumberOfPoints() == 0:
sys.exit(
"no nodes with weighting potential ~1 in %s -- the weighting solve did\n"
"not drive any electrode, so there is nothing to centre on." % WEIGHT_VTU
)
b = info.GetBounds()
CATHODE = [0.5 * (b[0] + b[1]), 0.5 * (b[2] + b[3]), 0.0]
print("driven cathode at x = %.5f m, y = %.5f m" % (CATHODE[0], CATHODE[1]))
radius = (CATHODE[0] ** 2 + CATHODE[1] ** 2) ** 0.5
outward = [CATHODE[0] / radius, CATHODE[1] / radius, 0.0] if radius > 0 else [0.0, 1.0, 0.0]
# Frame centre, offset from the wire along that radial.
CENTER = [
CATHODE[0] + CENTER_SHIFT * outward[0],
CATHODE[1] + CENTER_SHIFT * outward[1],
0.0,
]
Delete(driven)
Delete(weight_reader)
# --- the dot product map ---
reader = XMLUnstructuredGridReader(FileName=[INPUT_VTU])
renderView = GetActiveViewOrCreate("RenderView")
renderView.ViewSize = VIEW_SIZE
renderView.OrientationAxesVisibility = 1
renderView.UseColorPaletteForBackground = 0
renderView.Background = BACKGROUND
renderView.MultiSamples = 8
if COLOR_MODE == "signed-log":
# E . E_w spans about 3.5 decades negative and 2.6 positive, and 38% of the
# nodes are positive -- charge drifting there induces current of the
# opposite polarity on the cathode. A linear scale over one sign shows
# neither the decades nor the sign, so map through a signed log:
#
# sign(d) * log10(1 + |d| / SYMLOG_X0)
#
# sign() is not in the calculator namespace, hence d/(|d|+1); the +1 is
# negligible beside the 1e5 scale and the expression tends to 0 anyway
# where d does.
calc = PythonCalculator(Input=reader)
calc.ArrayName = "SignedLogDot"
calc.ArrayAssociation = "Point Data"
calc.Expression = (
"(DotProduct/(abs(DotProduct)+1.0))*log10(1.0+abs(DotProduct)/%g)"
% SYMLOG_X0
)
painted = calc
array = "SignedLogDot"
colour_range = (-SYMLOG_LIMIT, SYMLOG_LIMIT) # symmetric, so zero is neutral
preset = DIVERGING_PRESET
# Transparent either side of zero, opaque past SYMLOG_OPAQUE, both signs.
_m, _s = SYMLOG_MIDPOINT, SYMLOG_SHARPNESS
opacity_points = [
-SYMLOG_LIMIT, 1.0, 0.5, _s,
-SYMLOG_OPAQUE, 1.0, 1.0 - _m, _s, # mirrored: this ramp falls
-SYMLOG_FADE, 0.0, 0.5, _s,
SYMLOG_FADE, 0.0, _m, _s, # this one rises
SYMLOG_OPAQUE, 1.0, 0.5, _s,
SYMLOG_LIMIT, 1.0, 0.5, _s,
]
bar_title = "sign(E.Ew) log10(1+|E.Ew|/%g)" % SYMLOG_X0
else:
painted = threshold_between(reader, "DotProduct", -1.0e30, PAINT_CUT)
array = "DotProduct"
colour_range = (DOT_MIN, DOT_MAX)
preset = "Greens"
opacity_points = [
DOT_MIN, 1.0, 0.5, 0.0,
OPAQUE_BELOW, 1.0, 0.5, 0.0,
FADE_TO, 0.0, 0.5, 0.0,
]
bar_title = "E . E_w (V/m^2)"
surface_display = Show(painted, renderView)
surface_display.Representation = "Surface"
ColorBy(surface_display, ("POINTS", array))
dotLUT = GetColorTransferFunction(array)
dotLUT.ApplyPreset(preset, True)
dotLUT.RescaleTransferFunction(colour_range[0], colour_range[1])
# Points go (value, opacity, midpoint, sharpness) in ascending value.
dotPWF = GetOpacityTransferFunction(array)
dotPWF.Points = opacity_points
dotLUT.ScalarOpacityFunction = dotPWF
dotLUT.EnableOpacityMapping = 1
surface_display.SetScalarBarVisibility(renderView, True)
try:
GetScalarBar(dotLUT, renderView).Title = bar_title
except Exception as exc:
print("note: could not set the scalar bar title (%s)" % exc)
# --- contour lines over the whole field, including the unpainted gas ---
if SHOW_CONTOURS:
contour_filter = Contour(Input=reader, ContourBy=["POINTS", CONTOUR_BY])
contour_filter.Isosurfaces = CONTOUR_LEVELS
contour_display = Show(contour_filter, renderView)
contour_display.LineWidth = 1.0
contour_display.DiffuseColor = [1.0, 1.0, 1.0]
# ColorBy(rep, None) is rejected by ParaView 6.1 -- it forwards the
# association as the string 'NONE' and raises -- so set the property.
contour_display.ColorArrayName = [None, ""]
contour_display.SetScalarBarVisibility(renderView, False)
# --- camera: parallel projection, centred on the driven cathode ---
# ParaView resets the camera on a view's FIRST render, throwing away anything
# set beforehand, so burn that render here and set the camera after it.
# Measured: scale 0.008 set before the first Render comes back as 0.021; set
# again afterwards it holds.
Render()
view = GetActiveView()
view.CameraParallelScale = ZOOM_SCALE
view.CenterOfRotation = CENTER
view.CameraPosition = [CENTER[0], CENTER[1], 1.0]
view.CameraFocalPoint = CENTER
if ALIGN_RADIAL:
up = outward
print("view rotated %.1f deg so the outward radial is up"
% np.degrees(np.arctan2(up[0], up[1])))
else:
up = [0.0, 1.0, 0.0]
view.CameraViewUp = up
view.CameraParallelProjection = 1
Render()
SaveScreenshot(OUTPUT_IMAGE)
print("Saved %s" % OUTPUT_IMAGE)

268
anasen_fem/paraview_plotter.py Executable file
View File

@ -0,0 +1,268 @@
#!/home/vsitaraman/ParaView-6.1.0-RC1-MPI-Linux-Python3.12-x86_64/bin/pvbatch
# #######!/home/vsitaraman/ParaView-6.1.0-MPI-Linux-Python3.12-x86_64/bin/pvbatch
import numpy as np
import sys
from paraview.simple import *
# Optional argument so an archived VTU can be re-plotted without re-solving:
# ./paraview_plotter.py wires2d/vtu_files/elfield_anasen_10_0.0000.vtu
INPUT_VTU = sys.argv[1] if len(sys.argv) > 1 else "wires2d/elfield_anasen_t0001.vtu"
# --- contour settings ---
# The whole-PC view keeps the original sparse, evenly spaced levels; at that
# scale more lines just fill in solid. The quadrant gets the denser, biased
# set, since that is where the structure between wires is actually readable.
POT_MIN, POT_MAX = 0.0, 660.0
N_CONTOURS_FULL, CONTOUR_BIAS_FULL = 40, 1.0
N_CONTOURS_QUARTER, CONTOUR_BIAS_QUARTER = 80, 1.0
# Line widths. Thin lines wash out their own colour, so the quadrant views draw
# heavier than the full-PC one; tubes keep them smooth at 2000 px.
LINE_WIDTH_FULL = 3.0
LINE_WIDTH_QUARTER = 2.5
STREAM_LINE_WIDTH = 2.5
# Arrows are glyphed onto the contour points, so doubling the quadrant contour
# density doubled the arrow count too. This offsets that.
GLYPH_STRIDE = 96
# --- quadrant view ---
# Centre sits at radius 35.4 mm, near the outer edge of the wire band, which
# pushes the wires to one side and leaves the far corner empty. Pulled in to
# 31.8 mm so the band sits more centrally.
ZOOM_CENTER = [-0.0225, 0.0225, 0.0]
ZOOM_SCALE = 0.022 # half-height of the view in metres; larger = more in frame
def contour_levels(n, bias):
"""Isosurface values.
phi ~ ln(r) around a wire, so evenly spaced levels crowd onto the wire
surfaces and thin out in the gas. Raising a symmetric parameter to `bias`
pulls levels towards mid-range, where the saddles between wires are,
without adding more of them. 1.0 is plain linear spacing; much above 1.5
and the middle levels start landing on top of each other.
"""
u = np.linspace(-1.0, 1.0, n)
u = np.sign(u) * np.abs(u) ** bias
mid = 0.5 * (POT_MIN + POT_MAX)
return list(mid + 0.5 * (POT_MAX - POT_MIN) * u)
reader = XMLUnstructuredGridReader(FileName=[INPUT_VTU])
contour_filter = Contour(Input=reader,ContourBy = 'potential')
contour_filter.Isosurfaces = contour_levels(N_CONTOURS_FULL, CONTOUR_BIAS_FULL)
renderView = GetActiveViewOrCreate('RenderView')
renderView.ViewSize = [2000,2000]
renderView.OrientationAxesVisibility = 0 # Hide axis
renderView.UseColorPaletteForBackground=0
renderView.Background = [0.1, 0.1, 0.1] # Set background to dark gray (RGB 0-1)
renderView.MultiSamples = 8 # 0 disables it, 4-8 is usually sufficient
ResetCamera()
contour_display = Show(contour_filter, renderView)
contour_display.LineWidth = LINE_WIDTH_FULL
contour_display.RenderLinesAsTubes = 1 # Makes lines look smoother at high res
#colorbar
contour_display_potentialLUT = GetColorTransferFunction('potential', contour_display, separate=True)
contour_display_potentialLUT.ApplyPreset('Cool to Warm', True)
contour_display.SetScalarBarVisibility(renderView, True)
#axesGrid = renderView.AxesGridrfcxgdtv
#axesGrid.Visibility = 1
#axesGrid.XTitle = "x (mm)"
#axesGrid.YTitle = "y (mm)"
# 1. Get the active view
view = GetActiveView()
# 2. Define your desired coordinate ranges (x_min, x_max, y_min, y_max, z_min, z_max)
x_min, x_max = -0.05, 0.05
y_min, y_max = -0.05, 0.05
z_min, z_max = -0.05, 0.05
# 3. Calculate Center, Position, and Parallel Scale
center = [(x_min + x_max) / 2.0, (y_min + y_max) / 2.0, (z_min + z_max) / 2.0]
# Position the camera far away along Z to look at the center
position = [center[0], center[1], 1.0]
# Parallel scale defines how much of the scene is visible.
# It is usually half the height of the viewed area.
view.CameraParallelScale = max((x_max - x_min), (y_max - y_min))/1.6
# 4. Apply settings
view.CenterOfRotation = center
view.CameraPosition = position
view.CameraFocalPoint = center
view.CameraViewUp = [0.0, 1.0, 0.0] # Y-axis is up
# 5. Enable Parallel Projection (optional, often better for exact mapping)
view.CameraParallelProjection = 1
#ResetCamera()
Render()
SaveScreenshot("contour_output.png")
# Everything from here on is a quadrant view: denser levels, heavier lines.
contour_filter.Isosurfaces = contour_levels(N_CONTOURS_QUARTER, CONTOUR_BIAS_QUARTER)
contour_display.LineWidth = LINE_WIDTH_QUARTER
contour_display.RenderLinesAsTubes = 1
# 1. Get the active view
view = GetActiveView()
# 1. Set the Focal Point to the middle of the quadrant in metres
zoom_center = ZOOM_CENTER
# 2. Tighten the Parallel Scale
view.CameraParallelScale = ZOOM_SCALE
# 3. Position the Camera (0.5m away is fine)
view.CameraPosition = [zoom_center[0], zoom_center[1], 0.5]
view.CameraFocalPoint = zoom_center
view.CameraViewUp = [0.0, 1.0, 0.0]
# Equipotentials over one quadrant, saved before the arrows go on so this and
# Field_output.png below share a camera and overlay exactly.
Render()
SaveScreenshot("contour_quarter_output.png")
# pot_threshold = Threshold(Input=reader)
# pot_threshold.Scalars = ['POINTS', 'potential']
# pot_threshold.ThresholdMethod = 'Above Upper Threshold'
# pot_threshold.UpperThreshold = 100.0
# --- 2. Create the Glyph Filter (The Arrows) ---
# IMPORTANT: Use 'pot_threshold' as the Input, not the 'reader'
glyph = Glyph(Input=contour_filter, GlyphType='Arrow') #
# glyph = Glyph(Input=reader, GlyphType='Arrow') #this uses all field line snot just the ones from the equipotential lines shown
# Orientation Array: Use the 'electric field' vector from Elmer
glyph.OrientationArray = ['POINTS', 'electric field']
glyph.ScaleArray = ['POINTS', 'No scale array']
glyph.ScaleFactor = 0.001
glyph.GlyphMode = 'Every Nth Point'
glyph.Stride = GLYPH_STRIDE
# --- 3. Display the Glyphs ---
glyph_display = Show(glyph, renderView)
# Set the representation to Surface so we see the full arrow colors
glyph_display.Representation = 'Surface'
# This is the critical line: Color the arrows by the 'potential' scalar
ColorBy(glyph_display, ('POINTS', 'potential'))
glyph_display.LookupTable = contour_display_potentialLUT
contour_display_potentialLUT.RescaleTransferFunction(POT_MIN, POT_MAX)
# Optional: Disable the scalar bar for the arrows to avoid cluttering
# the existing 'potential' scalar bar.
glyph_display.SetScalarBarVisibility(renderView, False)
# --- 4. Final Render ---
Render()
SaveScreenshot("Field_output.png")
# --- Streamlines over the same quadrant ---
# Field lines rather than arrows: with no B field the drift velocity is
# parallel to E, so these are drift paths up to diffusion, i.e. a map of which
# wire collects charge from where. The glyphs above are fixed length, so they
# show direction only. Quadrant only; over the whole PC it is an unreadable mat.
# Equipotentials the streamlines start from. One level near the anodes seeds
# lines only in a ring around each anode, so with the lines truncated short the
# rest of the frame comes out empty. Spanning the range instead puts seeds
# right across the gas: every equipotential is a closed curve winding between
# the wires, so together they cover the volume.
SEED_POTENTIALS = [660.0, 600.0, 550.0, 500.0, 450.0, 400.0,350.0, 300.0,250.0, 250.0, 200.0, 150.0, 100.0, 50.0]
# Keep 1 seed point in N along the seed contours. Measured point counts per
# level, for working out the total:
#
# 600 V 4970 300 V 29971
# 500 V 8861 200 V 51558
# 400 V 16331 100 V 51645
# 50 V 27259
#
# 250 V and 150 V are not measured; from the trend they are of order 40000 and
# 50000, putting the nine levels near 280000 points. Stride 3 is then ~94000
# seeds, of which roughly an eighth land in the rendered quadrant. That is
# three times the previous seed count, but each line is now 4 mm instead of
# 20 mm, so the integration work is comparable.
SEED_STRIDE = 3
# Arc length each line is allowed to run, in metres. Field lines terminate on
# electrodes, so any line long enough to reach one will converge with every
# other line reaching the same wire -- that convergence is the field, not a
# plotting artefact, and the only way to avoid drawing it is to stop the lines
# short. The anode-cathode gap runs 4.3-6.2 mm depending on z, so 3 mm keeps
# every line clear of the cathodes. Raise it towards 0.005 for longer lines,
# and they will start meeting again at the wires.
# Short lines, and the gaps between them closed by seeding more planes rather
# than by making each line longer. 4 mm is just under the 4.3 mm closest
# anode-cathode approach, so no line quite reaches a wire and they stay
# separate instead of converging.
STREAM_MAX_LENGTH = 0.0005
# StreamTracer counts steps as well as length, and its step sizes are in
# cell-length units rather than metres: 0.2 of a cell by default, and cells are
# 0.05 mm at the wires. 2000 steps therefore buys about 20 mm at best and less
# wherever the mesh is fine, so the default caps the lines before
# STREAM_MAX_LENGTH ever does. Raise it well past what the length needs.
MAX_STEPS = 20000
STREAM_DIRECTION = 'BOTH' # 'BOTH' also draws the half that runs into the anode
STREAM_RANGE = [-200.0, 650.0] # full electrode span, needle to anode
# Clear the quadrant view of everything else so only the field lines show.
Hide(glyph, renderView)
Hide(contour_filter, renderView)
seed_contour = Contour(Input=reader, ContourBy=['POINTS', 'potential'])
seed_contour.Isosurfaces = SEED_POTENTIALS
# StreamTracer's own Point Cloud and Line seeds are 3D and this mesh is a
# single plane at z = 0, so they would mostly land outside it. Seeding from a
# contour of the mesh keeps every seed in the plane by construction.
seed_points = MaskPoints(Input=seed_contour)
seed_points.OnRatio = SEED_STRIDE
seed_points.RandomSampling = 0
stream = StreamTracerWithCustomSource(Input=reader, SeedSource=seed_points)
stream.Vectors = ['POINTS', 'electric field']
# FORWARD follows E, which runs from the anode outwards, so each line leaves
# its seed and heads for a cathode. With BOTH, the backward half of every line
# also climbs to the anode the seed ring encircles, and all of them meet there
# -- that is the joining. BOTH is still the honest full field line if you want
# it; this only changes what gets drawn.
stream.IntegrationDirection = STREAM_DIRECTION
stream.MaximumStreamlineLength = STREAM_MAX_LENGTH
stream.MaximumSteps = MAX_STEPS
# Keep integration in the z = 0 plane. Newer ParaView only, so tolerate it
# being absent.
try:
stream.SurfaceStreamlines = 1
except Exception as exc:
print("note: SurfaceStreamlines unavailable (%s)" % exc)
stream_display = Show(stream, renderView)
stream_display.Representation = 'Surface'
stream_display.LineWidth = STREAM_LINE_WIDTH
stream_display.RenderLinesAsTubes = 1
ColorBy(stream_display, ('POINTS', 'potential'))
streamLUT = GetColorTransferFunction('potential', stream_display, separate=True)
streamLUT.ApplyPreset('Cool to Warm', True)
streamLUT.RescaleTransferFunction(STREAM_RANGE[0], STREAM_RANGE[1])
stream_display.LookupTable = streamLUT
stream_display.SetScalarBarVisibility(renderView, True)
renderView.Background = [0.0, 0.0, 0.0]
Render()
SaveScreenshot("Streamlines_quarter_output.png")

113
anasen_fem/run.py Executable file
View File

@ -0,0 +1,113 @@
import os
import sys
# Cathode wire (0..23) that wires2d_weight.sif drives to 1 V. Passed to the
# mesher, which puts that one wire in its own physical group, tag 40.
SELECTED_CATHODE = 1
# 0 = everything in one pass
# 1 = mesh and the physical field only
# 2 = weighting field and dot product only, reusing stage 1's mesh
STAGE = 0
# gmsh comes from apt and lives in the system python3; meshio is pip-installed
# into the venv, which cannot see apt packages. Neither interpreter has both,
# so name them explicitly rather than relying on whichever python3 is on PATH.
MESH_PY = "/usr/bin/python3"
DOT_PY = os.path.expanduser("~/myenv/bin/python3")
def run(cmd):
"""os.system that stops on failure.
Worth the four lines: a solve that quietly did nothing once produced a
field of zeros, and every later step ran happily on it.
"""
print(cmd)
if os.system(cmd) != 0:
sys.exit("failed: " + cmd)
# z-loci to run, in mm.
#
# The wires twist: over z = 0 -> 174.3 the anode lattice turns 60 deg against
# the cathode lattice, which is exactly 4 cathode pitches of 15 deg. So the
# field pattern goes through the same cycle four times across that range, while
# the wire radii drift slowly and monotonically (anodes 32.0 -> 37.0 mm).
#
# A uniform 17.43 step samples the fast cycle 2.75 times per period, which
# aliases it -- consecutive slices land at relative azimuths 7.5, 14.1, 5.7,
# 12.2, 3.5 deg, jumping around rather than sweeping. Sample the two variations
# separately instead:
# - 8 slices across one pitch, which walks the azimuth in even ~2 deg steps
# at essentially fixed radius
# - 5 slices one whole pitch apart, which holds the azimuth near-fixed and
# walks the radius over its full range
# 12 z-loci in total, against 11 for the uniform sweep.
PITCH_Z = 174.3 / 4.0
Z_VALUES = sorted(set(
[round(i * PITCH_Z / 8.0, 4) for i in range(8)]
+ [round(i * PITCH_Z, 4) for i in range(5)]
))
# Running only two loci for now; delete this line for the full sweep.
# 19.794 is the crossover: the relative azimuth wraps through zero there, so an
# anode sits directly radially outside a cathode, and the closest anode-cathode
# approach over the whole range (4.339 mm) is at 19.707. It is deliberately off
# the grid above, whose nearest point is 21.7875.
# Z_VALUES = [19.690]
# Stage 2 reuses whatever single mesh is sitting in wires2d/, so it cannot span
# several z. Without this it would happily solve the weighting field on one z's
# mesh and archive the result under another z's name -- and dotproduct.py could
# not catch it, since the node sets would agree perfectly.
if STAGE == 2 and len(Z_VALUES) > 1:
sys.exit(
"STAGE 2 applies to the one mesh in wires2d/, which is whichever z\n"
"stage 1 ran last. Set Z_VALUES to that single z."
)
for count, val in enumerate(Z_VALUES, start=10):
print(val)
os.system("mkdir -p wires2d/mesh_files wires2d/sif_files wires2d/vtu_files png")
# Each stage archives its own output before the next one starts, so a
# failure in stage 2 does not throw away what stage 1 already produced.
if STAGE in (0, 1):
run("%s wires_gmsh2d_bc.py %s %d" % (MESH_PY, val, SELECTED_CATHODE))
run("ElmerGrid 14 2 wires2d.msh -2d")
run("ElmerSolver wires2d.sif")
run("./paraview_plotter.py")
os.system("cp wires2d.msh wires2d/mesh_files/wires2d%02d_%1.4f.msh"%(count,val))
os.system("cp wires2d.sif wires2d/sif_files/wires2d_%02d_%1.4f.sif"%(count,val))
os.system("cp wires2d/elfield_anasen_t0001.vtu wires2d/vtu_files/elfield_anasen_%02d_%1.4f.vtu"%(count,val))
os.system("cp contour_output.png png/Contour_output_z_%02d_%1.4f.png"%(count,val))
os.system("cp contour_quarter_output.png png/Contour_output_z_%02d_%1.4f_quarter.png"%(count,val))
os.system("cp Field_output.png png/Field_ouput_z_%02d_%1.4f.png"%(count,val))
os.system("cp Streamlines_quarter_output.png png/Streamlines_output_z_%02d_%1.4f_quarter.png"%(count,val))
# Stage 2 does not re-mesh: it reuses wires2d/ as stage 1 left it, which is
# what makes the node-by-node dot product valid.
if STAGE in (0, 2):
run("ElmerSolver wires2d_weight.sif")
run("%s dotproduct.py" % DOT_PY)
run("./paraview_dotproduct.py")
os.system("cp wires2d_weight.sif wires2d/sif_files/wires2d_weight_%02d_%1.4f.sif"%(count,val))
os.system("cp wires2d/elfield_weight_t0001.vtu wires2d/vtu_files/elfield_weight_%02d_%1.4f.vtu"%(count,val))
os.system("cp wires2d/dotproduct.vtu wires2d/vtu_files/dotproduct_%02d_%1.4f.vtu"%(count,val))
os.system("cp DotProduct_output.png png/DotProduct_output_z_%02d_%1.4f.png"%(count,val))
os.system("cp DotProduct_quarter_output.png png/DotProduct_output_z_%02d_%1.4f_quarter.png"%(count,val))
# os.system("python3 garfield_sim.py")
# (a bare "break" here runs only the first z, as the original did)
# os.system("tar -cvzf wiress2d/mesh.tar.gz wires2d/mesh_files")
# os.system("rm -rf wires2d/mesh_files/*")
# os.system("tar -cvzf wires2d/sif.tar.gz wires2d/sif_files")
# os.system("rm -rf wires2d/sif_files/*")
# os.system("tar -cvzf wires2d/vtu.tar.gz wires2d/vtu_files")
# os.system("rm -rf wires2d/vtu_files/*")

1
anasen_fem/scalars.dat Executable file
View File

@ -0,0 +1 @@
8.500000000000E+002

8
anasen_fem/scalars.dat.names Executable file
View File

@ -0,0 +1,8 @@
Metadata for SaveScalars file: ./scalars.dat
Elmer version: 26.2
Elmer compilation date: 2026-06-10
Solver input file: wires2d.sif
File started at: 2026/09/23 14:27:47
Variables in columns of matrix:
1: res: potential difference

View File

@ -0,0 +1 @@
1.000000000000E+000

View File

@ -0,0 +1,8 @@
Metadata for SaveScalars file: ./scalars_weight.dat
Elmer version: 26.2
Elmer compilation date: 2026-06-10
Solver input file: wires2d_weight.sif
File started at: 2026/09/23 14:50:46
Variables in columns of matrix:
1: res: potential difference

127
anasen_fem/wires2d.sif Executable file
View File

@ -0,0 +1,127 @@
Check Keywords Warn
Header
Mesh DB "." "wires2d"
End
Simulation
Coordinate System = Cartesian 2D
Simulation Type = Steady State
Steady State Max Iterations = 1
Output File = "elstatics.result"
Post File = "elstatics.ep"
Coordinate Scaling = 0.001 ! Converts mm from Gmsh to meters for Elmer
End
Constants
Permittivity Of Vacuum = 8.8542e-12
End
Body 1
Target Bodies(1) = 13
Equation = 1
Material = 1
End
Equation 1
Active Solvers(2) = 1 2
End
Material 1
Relative Permittivity = 1
End
Solver 1
Equation = Electrostatics
Procedure = "StatElecSolve" "StatElecSolver"
Variable = Potential
Variable DOFs = 1
Calculate Electric Field = True
Calculate Electric Flux = False
Linear System Solver = Iterative
Linear System Iterative Method = CG
Linear System Max Iterations = 5000
Linear System Convergence Tolerance = 1.0e-8
Linear System Preconditioning = ILU1
Calculate Vectors = Logical True
End
Solver 2
Equation = "Electric Field"
Procedure = "FluxSolver" "FluxSolver"
! Calculate from the potential
Target Variable = "Potential"
! Name of the output vector field in VTU
Flux Variable = String "Electric Field"
! Use 2D components (x, y)
Flux Coefficient = String "Permittivity"
Calculate Vectors = Logical True
End
Solver 3
Equation = Result Output
Procedure = "ResultOutputSolve" "ResultOutputSolver"
Output File Name = elfield_anasen ! Sets prefix for output files
Output Format = Vtu
! Optional: Select specific variables to save
Scalar Field 1 = Potential
Vector Field 1 = Electric Field
End
Solver 4
Exec Solver = After All
Equation = SaveScalars
Procedure = "SaveData" "SaveScalars"
Filename = "scalars.dat"
End
Boundary Condition 1
Target Boundaries = 1
Potential = -200
Calculate Electric Force = True
End
Boundary Condition 2
Target Boundaries = 2
Potential = 150
Calculate Electric Force = True
End
Boundary Condition 3
Target Boundaries = 3
Potential = 250
Calculate Electric Force = True
End
Boundary Condition 4
Target Boundaries = 10
Potential = 0
End
Boundary Condition 5
Target Boundaries = 20
Potential = 660
Calculate Electric Force = True
End
Boundary Condition 6
Target Boundaries = 30
Potential = 0
End
! The one cathode singled out in wires_gmsh2d_bc.py. Grounded here, exactly
! like the other 23; it is only separate so wires2d_weight.sif can drive it.
Boundary Condition 7
Target Boundaries = 40
Potential = 0
End

View File

@ -0,0 +1,132 @@
Check Keywords Warn
! Ramo weighting potential for the single cathode at tag 40: that electrode
! sits at 1 V and every other one at 0 V. Identical to wires2d.sif apart from
! the potentials and the output names, so both solves discretise the same way
! and E . E_w is meaningful node by node.
Header
Mesh DB "." "wires2d"
End
Simulation
Coordinate System = Cartesian 2D
Simulation Type = Steady State
Steady State Max Iterations = 1
Output File = "elstatics_weight.result"
Post File = "elstatics_weight.ep"
Coordinate Scaling = 0.001 ! Converts mm from Gmsh to meters for Elmer
End
Constants
Permittivity Of Vacuum = 8.8542e-12
End
Body 1
Target Bodies(1) = 13
Equation = 1
Material = 1
End
Equation 1
Active Solvers(2) = 1 2
End
Material 1
Relative Permittivity = 1
End
Solver 1
Equation = Electrostatics
Procedure = "StatElecSolve" "StatElecSolver"
Variable = Potential
Variable DOFs = 1
Calculate Electric Field = True
Calculate Electric Flux = False
Linear System Solver = Iterative
Linear System Iterative Method = CG
Linear System Max Iterations = 5000
Linear System Convergence Tolerance = 1.0e-8
Linear System Preconditioning = ILU1
Calculate Vectors = Logical True
End
Solver 2
Equation = "Electric Field"
Procedure = "FluxSolver" "FluxSolver"
! Calculate from the potential
Target Variable = "Potential"
! Name of the output vector field in VTU
Flux Variable = String "Electric Field"
! Use 2D components (x, y)
Flux Coefficient = String "Permittivity"
Calculate Vectors = Logical True
End
Solver 3
Equation = Result Output
Procedure = "ResultOutputSolve" "ResultOutputSolver"
Output File Name = elfield_weight ! Sets prefix for output files
Output Format = Vtu
! Optional: Select specific variables to save
Scalar Field 1 = Potential
Vector Field 1 = Electric Field
End
Solver 4
Exec Solver = After All
Equation = SaveScalars
Procedure = "SaveData" "SaveScalars"
Filename = "scalars_weight.dat"
End
Boundary Condition 1
Target Boundaries = 1
Potential = 0
Calculate Electric Force = True
End
Boundary Condition 2
Target Boundaries = 2
Potential = 0
Calculate Electric Force = True
End
Boundary Condition 3
Target Boundaries = 3
Potential = 0
Calculate Electric Force = True
End
Boundary Condition 4
Target Boundaries = 10
Potential = 0
End
Boundary Condition 5
Target Boundaries = 20
Potential = 0
Calculate Electric Force = True
End
Boundary Condition 6
Target Boundaries = 30
Potential = 0
End
! The one cathode singled out in wires_gmsh2d_bc.py. Grounded here, exactly
! like the other 23; it is only separate so wires2d_weight.sif can drive it.
Boundary Condition 7
Target Boundaries = 40
Potential = 1
End

295
anasen_fem/wires_gmsh2d_bc.py Executable file
View File

@ -0,0 +1,295 @@
# This script generates a 2D Gmsh geometry for the ANASEN detector's wire planes, including an optional hot needle and IC wires. The geometry is designed for adaptive meshing, with finer mesh near the wires and coarser mesh in empty space. The script takes a z-locus as a command-line argument to determine the interpolation between the two wire planes.
import numpy as np
import gmsh, sys
# --- Configuration Flags ---
include_ic_wires = True
include_needle = True
gmsh.initialize()
gmsh.model.add("adaptive_mesh")
gmsh.option.setNumber("General.NumThreads", 10)
# gmsh.option.setNumber("Mesh.Adapt.MaxNumberOfElements", 200000)
# gmsh.option.setNumber("Mesh.Adapt.MaxNumberOfNodes", 200000)
# gmsh.option.setNumber("Mesh.Adapt.MaxIter",5)
# gmsh.option.setNumber("Mesh.MeshSizeMin", 5e-3)
# gmsh.option.setNumber("Mesh.MeshSizeMax", 10.0)
gmsh.option.setNumber("Geometry.Tolerance", 4e-2)
# gmsh.option.setNumber("Mesh.MeshSizeExtendFromBoundary", 0)
# Only needed alongside mesh.recombine(), which is off. See the note there.
# gmsh.option.setNumber("Mesh.RecombinationAlgorithm", 2)
# gmsh.option.setNumber("Mesh.SubdivisionAlgorithm", 1)
gmsh.option.setNumber("Mesh.SecondOrderIncomplete", 1)
lc = 0.04
# z_loc = -174.3
if len(sys.argv) < 2:
print("Usage: python3 wires_gmsh2d_bc.py <z_locus in mm> [selected_cathode_index]")
quit()
z_loc = float(sys.argv[1])
# Cathode wire (0..23) given its own physical group, tag 40, so that
# wires2d_weight.sif can hold it at 1 V while everything else stays grounded.
selected_cathode = int(sys.argv[2]) if len(sys.argv) > 2 else 1
wireShift = 4.0
k = 2 * np.pi / 24.0
kg = k/2.0
# Plane 1 Offsets (-zmax/2)
# Anodes: -6*k (base) - 4*k (correction) = -10*k
offset_a1 = -6 * k - 4 * k
# Cathodes: -6*k (base) + 0.5*k (half-placement)
offset_c1 = -6 * k + (k / 2.0)
# Guard wires: aligned with cathodes
offset_g1 = offset_c1
# Plane 2 Offsets (+zmax/2) with Twist
offset_a2 = offset_a1 + (wireShift * k)
offset_c2 = offset_c1 - (wireShift * k)
offset_g2 = offset_c2
# 1 needle, 24 ic1, 24 ic2, 48 guard wires, 24 anodes, 24 cathodes
# needle at plane 1 at -zmax/2 no rotation
xarr_needle = np.array([0])
yarr_needle = np.array([0])
# ic1 wires, plane 1 at -zmax/2 no rotation
xarr_i11 = np.array([23 * np.cos(k * i) for i in range(24)])
yarr_i11 = np.array([23 * np.sin(k * i) for i in range(24)])
# ic1 wires, plane 1 at -zmax/2 no rotation
xarr_i21 = np.array([23 * np.cos(k * i + k / 2.0) for i in range(24)])
yarr_i21 = np.array([23 * np.sin(k * i + k / 2.0) for i in range(24)])
# --- Coordinate Arrays (Plane 1) ---
# Anodes: -k*i (Left-handed twist direction)
xarra_1 = np.array([37 * np.cos(-k * i + offset_a1) for i in range(24)])
yarra_1 = np.array([37 * np.sin(-k * i + offset_a1) for i in range(24)])
# Cathodes: +k*i (Right-handed twist direction)
xarrc_1 = np.array([42 * np.cos(k * i + offset_c1) for i in range(24)])
yarrc_1 = np.array([42 * np.sin(k * i + offset_c1) for i in range(24)])
# Guard Wires (48 wires, use kg spacing)
xarrg_1 = np.array([32 * np.cos(kg * i + offset_g1) for i in range(48)])
yarrg_1 = np.array([32 * np.sin(kg * i + offset_g1) for i in range(48)])
# needle at plane 2 at zmax/2
xarr_needle_2 = np.array([0])
yarr_needle_2 = np.array([0])
# #ic1 wires, plane 2 at zmax/2
xarr_i12 = np.array([23 * np.cos(k * i) for i in range(24)])
yarr_i12 = np.array([23 * np.sin(k * i) for i in range(24)])
# #ic2 wires, plane 2 at zmax/2
xarr_i22 = np.array([23 * np.cos(k * i + k / 2.0) for i in range(24)])
yarr_i22 = np.array([23 * np.sin(k * i + k / 2.0) for i in range(24)])
# --- Coordinate Arrays (Plane 2) ---
xarra_2 = np.array([37 * np.cos(-k * i + offset_a2) for i in range(24)])
yarra_2 = np.array([37 * np.sin(-k * i + offset_a2) for i in range(24)])
xarrc_2 = np.array([42 * np.cos(k * i + offset_c2) for i in range(24)])
yarrc_2 = np.array([42 * np.sin(k * i + offset_c2) for i in range(24)])
xarrg_2 = np.array([32 * np.cos(kg * i + offset_g2) for i in range(48)])
yarrg_2 = np.array([32 * np.sin(kg * i + offset_g2) for i in range(48)])
direction_needle_x = xarr_needle_2 - xarr_needle
direction_needle_y = yarr_needle_2 - yarr_needle
direction_ic1_x = xarr_i12 - xarr_i11
direction_ic1_y = yarr_i12 - yarr_i11
direction_ic2_x = xarr_i22 - xarr_i21
direction_ic2_y = yarr_i22 - yarr_i21
direction_guard_x = xarrg_2 - xarrg_1
direction_guard_y = yarrg_2 - yarrg_1
direction_anodes_x = xarra_2 - xarra_1
direction_anodes_y = yarra_2 - yarra_1
direction_cathodes_x = xarrc_2 - xarrc_1
direction_cathodes_y = yarrc_2 - yarrc_1
t = (z_loc + 174.3) / (2 * 174.3) # z=-174.3 is 0, z=+174.3 is 1
xloc_needle = xarr_needle + t * direction_needle_x
yloc_needle = yarr_needle + t * direction_needle_y
xloc_i1 = xarr_i11 + t * direction_ic1_x
yloc_i1 = yarr_i11 + t * direction_ic1_y
xloc_i2 = xarr_i21 + t * direction_ic2_x
yloc_i2 = yarr_i21 + t * direction_ic2_y
xloc_g = xarrg_1 + t * direction_guard_x
yloc_g = yarrg_1 + t * direction_guard_y
xloc_a = xarra_1 + t * direction_anodes_x
yloc_a = yarra_1 + t * direction_anodes_y
xloc_c = xarrc_1 + t * direction_cathodes_x
yloc_c = yarrc_1 + t * direction_cathodes_y
# wire_radius_a = 0.018 #mm
# wire_radius_c = 0.0762 #mm
# wire_radius_g = 0.0762 #mm
wire_radius = 0.254 # mm
needle = []
ic1_wires = []
ic2_wires = []
guard_wires = []
anode_wires = []
cathode_wires = []
iw1_tags = [(3, i) for i in range(24)]
iw2_tags = [(3, i + 24) for i in range(24)]
gw_tags = [(3, i + 48) for i in range(48)]
aw_tags = [(3, i) for i in range(24)]
cw_tags = [(3, i + 24) for i in range(24)]
# for i,[xa,ya,xc,yc] in enumerate(zip(xarra_1,yarra_1,xarrc_1,yarrc_1)):
# create Hot Needle (1 total)
for i, (xn, yn) in enumerate(zip(xloc_needle, yloc_needle)):
if include_needle:
ndisk = gmsh.model.occ.addDisk(xn, yn, 0, wire_radius, wire_radius)
needle.append(ndisk)
# create Guard Wires (48 total)
for xg, yg in zip(xloc_g, yloc_g):
gdisk = gmsh.model.occ.addDisk(xg, yg, 0, wire_radius, wire_radius)
guard_wires.append(gdisk)
# create Cathode Wires (24 total)
for xc, yc in zip(xloc_c, yloc_c):
cdisk = gmsh.model.occ.addDisk(xc, yc, 0, wire_radius, wire_radius)
cathode_wires.append(cdisk)
# create IC Anode and Cathode Wires (24 total each)
for i, (xa, ya) in enumerate(zip(xloc_a, yloc_a)):
adisk = gmsh.model.occ.addDisk(xa, ya, 0, wire_radius, wire_radius)
anode_wires.append(adisk)
# Place IC wires only if flag is True
if include_ic_wires:
i1disk = gmsh.model.occ.addDisk(
xloc_i1[i], yloc_i1[i], 0, wire_radius, wire_radius
)
i2disk = gmsh.model.occ.addDisk(
xloc_i2[i], yloc_i2[i], 0, wire_radius, wire_radius
)
ic1_wires.append(i1disk)
ic2_wires.append(i2disk)
anasen_barrel = gmsh.model.occ.addDisk(0, 0, 0, 500, 500)
# gmsh.model.occ.synchronize()
# gmsh.model.mesh.embed(1,anode_wires+cathode_wires,2,anasen_barrel)
gmsh.option.setNumber("Geometry.Tolerance", 1e-6)
gmsh.option.setNumber("Geometry.OCCFixDegenerated", 1)
gmsh.model.occ.synchronize()
# --- Surface Extraction ---
def get_surfs(disks):
surfs = []
for d in disks:
surfs += [
s[1] for s in gmsh.model.getBoundary([(2, d)], oriented=False) if s[0] == 1
]
return surfs
if not 0 <= selected_cathode < len(cathode_wires):
print("selected_cathode must be 0..%d" % (len(cathode_wires) - 1))
quit()
cathode_wires_other = [
disk for i, disk in enumerate(cathode_wires) if i != selected_cathode
]
# Cut the wire disks into the gas disk, replacing the old mesh.embed() call.
# embed only makes the mesher honour the curves; fragment splits the barrel
# into conforming surfaces, which is what lets one wire carry its own BC.
gmsh.model.occ.synchronize()
all_wire_disks = needle + guard_wires + cathode_wires + anode_wires
if include_ic_wires:
all_wire_disks += ic1_wires + ic2_wires
gmsh.model.occ.fragment([(2, anasen_barrel)], [(2, d) for d in all_wire_disks])
gmsh.model.occ.synchronize()
# fragment re-derives the bounding curves, so re-extract them.
needle_surfs = get_surfs(needle) if include_needle else []
gwire_surfs = get_surfs(guard_wires)
awire_surfs = get_surfs(anode_wires)
cwire_surfs = get_surfs(cathode_wires_other)
cwire_sel_surfs = get_surfs([cathode_wires[selected_cathode]])
i1wire_surfs = get_surfs(ic1_wires) if include_ic_wires else []
i2wire_surfs = get_surfs(ic2_wires) if include_ic_wires else []
all_active_wire_surfs = (
needle_surfs
+ gwire_surfs
+ awire_surfs
+ cwire_surfs
+ cwire_sel_surfs
+ i1wire_surfs
+ i2wire_surfs
)
f1 = gmsh.model.mesh.field.add("Distance")
gmsh.model.mesh.field.setNumbers(f1, "CurvesList", all_active_wire_surfs)
f2 = gmsh.model.mesh.field.add("Threshold")
gmsh.model.mesh.field.setNumber(f2, "InField", f1)
gmsh.model.mesh.field.setNumber(f2, "SizeMin", 0.05) # Fine mesh near wires
gmsh.model.mesh.field.setNumber(f2, "SizeMax", 5.0) # Large mesh in empty space
gmsh.model.mesh.field.setNumber(f2, "DistMin", 0.5) # Apply SizeMin within 1mm
gmsh.model.mesh.field.setNumber(f2, "DistMax", 15.0) # Transition to SizeMax by 20mm
gmsh.model.mesh.field.setAsBackgroundMesh(f2)
# --- Physical Groups ---
# These tags are the Target Bodies / Target Boundaries numbers in the .sif
# files. Do not renumber them without changing both sifs -- and note that
# ElmerGrid's -autoclean renumbers them for you, which silently breaks every
# boundary condition.
# Needle
if include_needle:
gmsh.model.addPhysicalGroup(1, needle_surfs, tag=1, name="hot_needle")
# IC Wires
if include_ic_wires:
gmsh.model.addPhysicalGroup(1, i1wire_surfs, tag=2, name="ic_wire_1")
gmsh.model.addPhysicalGroup(1, i2wire_surfs, tag=3, name="ic_wire_2")
# Proportional Counter Wires
gmsh.model.addPhysicalGroup(1, gwire_surfs, tag=10, name="guard_wires")
gmsh.model.addPhysicalGroup(1, awire_surfs, tag=20, name="anode_wires")
gmsh.model.addPhysicalGroup(1, cwire_surfs, tag=30, name="cathode_wires")
# The one cathode the weighting potential is solved for, kept out of tag 30 so
# wires2d_weight.sif can drive it independently.
gmsh.model.addPhysicalGroup(1, cwire_sel_surfs, tag=40, name="cathode_wire_selected")
# Gas Volume (2D). fragment split the barrel into many surfaces, so collect
# them all; naming the original disk alone would solve one sliver of the domain.
all_surfaces_2d = [s[1] for s in gmsh.model.getEntities(dim=2)]
gmsh.model.addPhysicalGroup(2, all_surfaces_2d, tag=13, name="gas")
gmsh.option.setNumber("Mesh.Algorithm", 5)
gmsh.model.mesh.generate(2)
gmsh.model.mesh.setOrder(2)
# recombine() ran for over half an hour on the barrel surface without
# finishing, and refine() took the mesh to ~16M nodes. Neither is needed: the
# Threshold field above already gives 0.05 mm elements on 0.254 mm wires.
# gmsh.model.mesh.recombine()
# gmsh.model.mesh.refine()
gmsh.write("wires2d.msh")
# gmsh.fltk.run()
gmsh.finalize()

89
centroids_edited.txt Normal file
View File

@ -0,0 +1,89 @@
HistogramIndex PeakNumber Centroid Amplitude Sigma
1 1 922.213
1 2 1885.55
1 3 2845.53
1 4 3810.32
2 1 955.591
2 2 1953.17
2 3 2949.37
2 4 3950.79
3 1 995.787
3 2 2036.58
3 3 3076.91
3 4 4112.05
4 1 1017.48
4 2 2080.19
4 3 3142.24
4 4 4206.1
5 1 1022.78
5 2 2091.21
5 3 3158.28
5 4 4226.97
6 1 1076.22
6 2 2203.37
6 3 3329.53
6 4 4457.69
7 1 977.46
7 2 1998.02
7 3 3017.36
7 4 4040.47
8 1 1049.74
8 2 2144.38
8 3 3238.2
8 4 4335.25
9 1 1000.59
9 2 2046.42
9 3 3090.29
9 4 4129.63
10 1 1014.92
10 2 2076.16
10 3 3134.59
10 4 4213.42
11 1 1004.85
11 2 2052.88
11 3 3100.3
11 4 4164.75
12 1 945.861
12 2 1932.49
12 3 2917.95
12 4 3955.15
13 1 998.307
13 2 2040.38
13 3 3078.76
13 4 4135.51
14 1 966.429
14 2 1972.15
14 3 2974.84
14 4 4056.41
15 1 958.352
15 2 1958.64
15 3 2957.7
15 4 3970.41
16 1 970.732
16 2 1977.63
16 3 2984.97
16 4 4002.56
17 1 1013.65
17 2 2064.9
17 3 3114.19
17 4 4190.98
18 1 975.538
18 2 1990.64
18 3 3005.46
18 4 4048.99
20 1 912.778
20 2 1866.83
20 3 2819.21
20 4 3781.63
21 1 1002.36
21 2 1989.95
21 3 2975.53
21 4 3986.71
22 1 1075.38
22 2 2144.25
22 3 3210.17
22 4 4312.84
23 1 988.828
23 2 2016.35
23 3 3044.19
23 4 4082.41

197
cross_section/README.md Executable file
View File

@ -0,0 +1,197 @@
# Cross-section pipeline (ANASEN active target)
This folder turns data into a cross section for any reaction in the ANASEN active target. There are three steps,
one script each, and one config file per study shared by all three:
```
root -l -b -q 'extract_yields.C+("examples/27Al_ap_runA.toml")' # 1. ROOT -> yields_<det>.csv
~/myenv/bin/python -B geometric_efficiency.py examples/27Al_ap_runA.toml # 2. detectors.py -> eps_<det>.csv ~10 s
~/myenv/bin/python -B cross_section.py examples/27Al_ap_runA.toml # 3. -> xs_<det>.csv, xs.png ~3 s
```
Run them in WSL from this folder (`~/ANASEN_analysis/cross_section`). Step 1 needs ROOT; steps 2 and 3 need
`~/myenv/bin/python`, not ROOT.
| file | what it does |
|---|---|
| `extract_yields.C` | **Step 1, the only step that reads the data.** It reads a TH1/TH2, or fills a histogram from a TTree with a cut, and writes a plain CSV histogram. |
| `geometric_efficiency.py` | Step 2: geometric efficiency per bin, a toy Monte Carlo of the reaction, with every hit rule taken from `detectors.py`. |
| `detectors.py` | **The detectors as they are now** (Run A): QQQ3, the four analysed SX3s, the counter, the beam tube and the PCB hole, plus the hit rules. To change the geometry, edit this file. |
| `cross_section.py` | Step 3: reads only CSVs (the yields and an efficiency) and computes σ(E_cm) or dσ/dΩ(θ_cm). |
| `common.py` | What the three scripts share: the config, the beam track E(z), the bins. |
| `examples/` | One commented config per study: `27Al_ap_runA.toml` and `paap_ta_runs_hist.toml`. |
| `examples/reference/` | Barnard 1964 (EXFOR A1113) for p + ⁴He. |
| `inputs/` | The beam track (`beam_tracks.csv`) and the TALYS states (`talys_27Al_ap.csv`). The p + ⁴He track is read from `../eloss_calculations/`. |
| `out/<study>/` | Each study's outputs: `yields_<det>.csv`, `eps_<det>.csv`, `xs_<det>.csv`, `xs.png`. |
## Starting a new study
1. Copy the closest example in `examples/` (excitation function: `27Al_ap_runA.toml`; angular distribution:
`paap_ta_runs_hist.toml`) and give it its own `out_dir`.
2. Fill in `[extract]` for your histogram or tree, then run step 1 and look at `yields_<det>.csv`.
3. Fill in `[reaction]`, `[beam]` and `[bins]`, then run step 2. Check that `eps_<det>.csv` is non-zero where you
expect events.
4. Fill in `[target]`, `[acceptance]` and any `[[reference]]`, then run step 3.
If the detectors have changed, edit `detectors.py`, not the configs.
![27Al(a,p)](out/27Al_ap/xs.png)
## The formula
| mode | result | formula |
|---|---|---|
| `excitation` | σ(E_cm), mb | Y / (N_b · n · dz · ε) |
| `angular` | dσ/dΩ(θ_cm) in one E_cm (or z) window, mb/sr | Y / (N_b · n · dz · ε · ΔΩ) |
- **Y:** the yield in the bin.
- **N_b:** the number of beam particles.
- **n:** target atoms per cm² per mm of path.
- **dz:** the length of beam path over which the beam's E_cm lies in the bin, read off the beam track E(z).
- **ε:** the efficiency of the bin.
## Step 1: `extract_yields.C`, data to CSV
`[extract] source` says where the events are:
- `"hist"`: a TH1 or TH2 in a ROOT file. A TH2 is projected on the axis that holds `variable`, or kept 2D with
`angle_axis` for the angular mode.
- `"tree"`: a ROOT TTree (`file` may be a glob or a list of runs), filled into `bins` after `cut`.
Cuts use `and` / `or` (the macro turns them into `&&` / `||`) and ROOT's TTree::Draw syntax.
**Histogram paths** are `"folder/name"`.
- **They are case-sensitive.** Production files of different vintages spell the detector differently: the 27Al
outputs use `_m27Alax+misc_qqq/...`, the proton runs' `Output_p/Al_protons.root` uses `elastic+misc_QQQ_a/...`.
Put the spelling in `[det_vars]`:
```toml
dets = ["qqq", "sx3"] # the names detectors.py knows
[det_vars.qqq]
DET = "QQQ"
[det_vars.sx3]
DET = "SX3"
[extract]
hist = "elastic+misc_{DET}_a/elastic_VertexReconZ_a_{DET}"
```
- **A name that itself contains `/`** (e.g. `dE3_E_AnodeQQQR_A1C2_(vertex_fix_z/100)=0.000000`) is found too.
- **If the path doesn't exist,** the macro stops and names the closest histogram paths in the file.
The CSV it writes, `<out_dir>/yields_<det>.csv`, is the hand-off between the steps. `cross_section.py` reads nothing
else, so a yield made any other way works too as long as it is in this format:
```
1D: <var>_lo, <var>_hi, counts, variance
2D: <var>_lo, <var>_hi, theta_cm_lo, theta_cm_hi, counts, variance (angular mode)
```
`<var>` is `z` (vertex z, mm), `e_beam` (MeV) or `ecm` (MeV). The bins may be finer than the analysis bins and need
not line up with them. Step 3 shares each one out by overlap.
It is run like this:
```
root -l -b -q 'extract_yields.C+("examples/27Al_ap_runA.toml")'
root -l -b -q 'extract_yields.C+("examples/27Al_ap_runA.toml", "out/elsewhere")' # optional output folder
```
- **Trees:** it fills the histogram with `TTree::Draw` on a `TChain`, so `file` may be a glob or a list of runs.
- **Cuts:** write them with `and` / `or` (e.g. `"det == 1 and e_si > 0.5"`). The macro turns them into `&&` / `||`.
- **TOML:** the macro has a small reader that handles only what these configs use: strings, numbers, one-line lists
and `{start, stop, step}` tables. It stops with an error on anything else.
- **Errors:** a missing file or tree, or a tree with no entries, stops it with exit code 1 rather than writing zeros.
- **One edge case:** an entry exactly on a tree histogram's last upper edge goes to ROOT's overflow.
## Step 2: `geometric_efficiency.py`, ε from the detector geometry
The toy runs once per bin:
- vertices uniform over the bin's path (angular mode: over the window);
- beam energy at each vertex from the nominal track;
- the residual state drawn from fixed weights, or from TALYS state cross sections at the bin's E_cm;
- isotropic CM emission (angular mode: inside the θ_cm bin);
- relativistic two-body kinematics and straight tracks.
The particle the silicon sees can be the ejectile or the recoil (`detect`). θ_cm is always the ejectile's, so with
`detect = "recoil"` the bins are still in the ejectile's CM angle. For p + ⁴He that is the proton's angle, Barnard's
convention, which TrackRecon's `elastic_VertexZ_vs_ThetaCM` histograms also use. The output has one column per
condition, each times the azimuthal coverage:
| column | the particle... |
|---|---|
| `eps_si` | reaches the analysed silicon (solid angle) |
| `eps_si_thr` | ...and has a vertex energy above the Si threshold. This is the ch5 "solid angle" convention: a kinematic cut, not geometry. |
| `eps_counter` | `eps_si`, and it crosses the guard, anode and cathode radii inside the counter |
| **`eps_geometric`** | `eps_counter`, and it clears the beam tube and the PCB hole. **This is the geometric efficiency.** |
Each column has a Monte Carlo error, `<column>_rel_err`. Gas energy loss, Si thresholds after the gas and the counter's
deposit threshold are **not** included, because they are not geometry. That is why the adopted `eps_tube` of
`../scratch/xs_27Al_lowE_2026-10-15` is lower than `eps_geometric`; to use it instead, see the commented
`[acceptance]` block in the example. The angular distribution is isotropic.
## Step 3: `cross_section.py`
`[acceptance] source` picks ε:
- `"geometric"`: step 2's file; `column` chooses which efficiency.
- `"csv"`: any file, matched on a bin edge or interpolated.
- `"value"`: one number for every bin.
- `"none"`: ε = 1, which gives σ_app.
`[[acceptance.extra]]` multiplies in further factors, such as an intrinsic efficiency. The output `xs_<det>.csv` keeps
every intermediate number: the bins, z ranges, dz, counts, σ_app, ε, σ and its errors (stat, ε, total), and the
references. The header lines record every input. The N_b and n uncertainties are a common scale, so the header gives
them as a percentage and they stay out of the error columns.
## The config (TOML)
Paths are relative to the config file, and `~` is the home folder. `{det}` is replaced by each entry of `dets`, and
so is every key of `[det_vars.<det>]`. Commented templates in `examples/`:
- `27Al_ap_runA.toml`: a TH2 from the merged Run A production file, all three steps.
- `paap_ta_runs_hist.toml`: p + ⁴He elastic **straight from a TrackRecon.C production file**, as for 27Al. It reads
the TH2 `elastic_VertexZ_vs_ThetaCM_cath_exgate_a_<DET>` (vertex z × proton θ_cm). `protonAlphaElastic_core` fills
that histogram since 2026-10-16 in every species folder, in four variants: all, `_cath`, `_exgate`, `_cath_exgate`.
- Input: `~/ANASEN_analysis/Output_p/results_run022.root` (run 22 alone, run_tr.sh block 5 with CUTLIST),
N_b = 1.35 pA × 672.65 s = 5.67 × 10⁹, vertex window z = −170 to −53 mm.
- On 2026-10-16 the merged runs 18–22 agreed with a per-hit route to 2–4% in QQQ3 counts and about 3% in dσ/dΩ.
| section | used by | keys |
|---|---|---|
| top | all | `title`, `mode` (`excitation` / `angular`), `dets` (names in `detectors.py`: `qqq`, `sx3`), `out_dir` |
| `[extract]` histogram | 1 | `source = "hist"`, `file`, `hist`, `variable`, `axis` (the axis holding `variable`), `angle_axis` (keep it 2D), `other_range` |
| `[extract]` tree | 1 | `source = "tree"`, `file` (glob or list), `tree`, `variable`, `branch`, `bins`, `cut`, `weight_branch`, `angle_branch` + `angle_bins` |
| `[reaction]` | 2, 3 | `beam_mass_u`, `target_mass_u`; for step 2 also `ejectile_mass_u`, `recoil_mass_u`, `detect`, and `states` *or* `states_csv`; optional `ecm_per_ebeam` |
| `[beam]` | 2, 3 | track: `track_csv` + `track_e_col` (+ `nominal_e_col`, `track_z_col`), *or* `eloss_table` + `e_entrance_mev` + `z_entrance_mm`. N_b: `n_beam` *or* `charge_c` *or* `current_a` + `time_s`; `charge_state`, `n_beam_rel_err`. For Run A runs, `../runlog/beam_exposure.py` gives `n_beam` for any set of runs from the elog's cup readings |
| `[bins]` | 2, 3 | `ecm_edges` (a list or `{start, stop, step}`); angular: `theta_edges_deg` and `window_ecm` *or* `window_z_mm` |
| `[efficiency]` | 2 | `n_per_bin` (default 400 000), `seed` |
| `[target]` | 3 | `pressure_torr`, `temperature_k`, `mole_fraction`, `atoms_per_molecule`, `rel_err`; *or* `n_per_cm2_per_mm` |
| `[yield]` | 3 | `csv`: only to read yields from somewhere other than step 1's output |
| `[acceptance]` | 3 | `source` and its keys (see above), `[[acceptance.extra]]` |
| `[[reference]]` | 3 | `csv`, `x_col`, `y_col`, `label`, `scale`, `dets` |
| `[plot]` | 3 | `yscale`, `xlim`, `ylim`, `xlabel`, `ylabel` |
## Example: p + ⁴He elastic scattering, Ta run 22 (`examples/paap_ta_runs_hist.toml`)
```
root -l -b -q 'extract_yields.C+("examples/paap_ta_runs_hist.toml")'
~/myenv/bin/python -B geometric_efficiency.py examples/paap_ta_runs_hist.toml
~/myenv/bin/python -B cross_section.py examples/paap_ta_runs_hist.toml
```
dσ/dΩ(θ_cm) from the α recoils (`detect = "recoil"`, bins in the proton's θ_cm, Barnard's convention), against
Barnard, Jones & Weil (1964). Histogram: `elastic_VertexZ_vs_ThetaCM_cath_exgate_a_<DET>` (cathode topologies,
graphical α cut, |E_x| < 1 MeV). The merged runs 18–22 gave QQQ3 0.44–0.54 × Barnard at 132–156°; SX3 is at 1–3%.
Not in `detectors.py`: the Ta holder rod (~28% of the azimuth downstream of each foil) and Si thresholds after the gas.
## Checks (run in the Windows repo, `/mnt/c/Users/sengi/ANASEN_analysis/cross_section`; the scripts are not in this copy)
- 27Al(α,p), Run A: yields, dz and nominal E_cm equal the earlier study's in every bin; ε_si_thr agrees with its
independent toy within the MC errors.
- Synthetic events with a known σ, ε and dσ/dΩ are returned with unit-Gaussian pulls.
- `extract_yields.C` and the Python version gave byte-identical yields on every example.
**Not verified:** the physics inputs are whatever the config and `detectors.py` say. The checks show the arithmetic and
the bookkeeping are right, and that `detectors.py` reproduces the geometry of `anasen_detectability.py`. They cannot
show that the geometry, a track or an N_b is right. In particular:
- The dE/dx scale of a `.dat` table is not written in the file. The live 250 Torr / 3% tables were regenerated on
2026-10-16 and match the 0.962 track to 3 keV.
- `detectors.py` gives QQQ3 0.967 of the azimuth, but TrackRecon drops QQQ pixels without a calibration, which leaves
about 0.675. QQQ3 ε is therefore about 1.4× too high, and its cross sections about 1.4× too low.
- Where only a sliver of solid angle is open, ε is tiny and σ explodes. Example: QQQ3 at E_cm 5.4 MeV, ε = 1.5 × 10⁻⁵,
behind the beam tube. The error bar shows it; no ε cut is applied.

183
cross_section/common.py Executable file
View File

@ -0,0 +1,183 @@
"""Shared by extract_yields.py, geometric_efficiency.py and cross_section.py: the config, the beam track, the bins.
The cross section in this active target:
sigma(E_cm) = Y / (N_b * n * dz * eps) mode = "excitation"
dsigma/dOmega(th) = Y / (N_b * n * dz * eps * dOmega) mode = "angular"
The beam loses energy along z, so a vertex at z means the reaction happened at the beam energy E(z) there.
An E_cm bin is the stretch of beam path dz over which the beam's E_cm lies in the bin.
"""
import os
import tomllib
from pathlib import Path
import numpy as np
import pandas as pd
# ============================================================ config
def read_config(config_file):
"""Read the TOML config. Returns the config and a function that turns paths in it into absolute paths
(relative paths are relative to the config file's folder)."""
config_file = Path(config_file).resolve()
with open(config_file, "rb") as f:
config = tomllib.load(f)
def full_path(p):
p = os.path.expanduser(str(p)) # "~/..." is the home folder, as in a shell
if p.startswith("/") or Path(p).is_absolute():
return p
return str((config_file.parent / p).resolve())
return config, full_path
def config_for_detector(config, det):
"""Copy of the config with "{det}" (and any key of [det_vars.<det>]) replaced in every string.
This lets one config serve several detectors, e.g. hist = "..._{det}"."""
replacements = {"det": det}
replacements.update(config.get("det_vars", {}).get(det, {}))
def replace(value):
if isinstance(value, str):
for key, new in replacements.items():
value = value.replace("{" + key + "}", str(new))
return value
if isinstance(value, dict):
return {k: replace(v) for k, v in value.items()}
if isinstance(value, list):
return [replace(v) for v in value]
return value
return replace(config)
def output_file(config, full_path, kind, det):
"""Where each step writes for one detector: <out_dir>/<kind>_<det>.csv, kind = yields, eps or xs."""
out_dir = Path(full_path(config.get("out_dir", "out")))
out_dir.mkdir(parents=True, exist_ok=True)
return out_dir / (f"{kind}_{det}.csv" if det else f"{kind}.csv")
def make_edges(spec):
"""Bin edges from a list [e0, e1, ...] or from {start = .., stop = .., step = ..} (stop included)."""
if isinstance(spec, dict):
n_bins = int(round((spec["stop"] - spec["start"]) / spec["step"]))
return spec["start"] + spec["step"] * np.arange(n_bins + 1)
return np.array(spec, dtype=float)
def write_csv(table, header_lines, out_file):
with open(out_file, "w", newline="\n") as f:
for line in header_lines:
f.write(f"# {line}\n")
table.to_csv(f, index=False, float_format="%.6g")
# ============================================================ beam track
class BeamTrack:
"""Beam energy (MeV, lab) along z (mm). The energy falls with z and is 0 past the stopping point.
`energy` is the track used to define the bins (the one the data were reconstructed with). `nominal` is an
optional second track (e.g. another stopping-power scale): it gives each bin's nominal E_cm and the beam
energy in the efficiency toy. It defaults to the same track."""
def __init__(self, z, energy, nominal=None, description=""):
self.z = np.asarray(z, dtype=float)
self.energy = np.asarray(energy, dtype=float)
self.nominal = self.energy if nominal is None else np.asarray(nominal, dtype=float)
self.description = description
def energy_at(self, z, nominal=False):
return np.interp(z, self.z, self.nominal if nominal else self.energy)
def z_where_energy_is(self, e):
"""The z where the beam has energy e.
e <= 0 gives the stopping point; e above the entrance energy gives the entrance z."""
if e <= 0:
stopped = np.flatnonzero(self.energy <= 0)
return float(self.z[stopped[0]]) if len(stopped) else float(self.z[-1])
# np.interp needs increasing x: energy decreases along z, so interpolate in -energy
return float(np.interp(-e, -self.energy, self.z))
def load_beam_track(beam, full_path):
"""Either a CSV with columns z and E (e.g. beam_tracks.csv), or an energy-loss table from
eloss_calculations/ plus the beam energy at the entrance."""
if "track_csv" in beam:
table = pd.read_csv(full_path(beam["track_csv"]), comment="#")
z = table[beam.get("track_z_col", "z_mm")]
energy = table[beam["track_e_col"]]
nominal = table[beam["nominal_e_col"]] if "nominal_e_col" in beam else None
text = f"{beam['track_csv']}, column {beam['track_e_col']}"
if nominal is not None:
text += f" (nominal E_cm from {beam['nominal_e_col']})"
return BeamTrack(z, energy, nominal, text)
if "eloss_table" in beam:
# Table columns: energy (MeV), thickness (mg/cm2), distance along the path (cm)
table = np.loadtxt(full_path(beam["eloss_table"]), comments="#")
table_energy, table_cm = table[:, 0], table[:, 2]
e_entrance, z_entrance = float(beam["e_entrance_mev"]), float(beam["z_entrance_mm"])
if e_entrance > table_energy[0]:
raise ValueError(f"e_entrance_mev = {e_entrance} is above the table's start energy {table_energy[0]}")
# Where in the table the beam starts, then walk along it in 0.1 mm steps
start_cm = np.interp(-e_entrance, -table_energy, table_cm)
z = np.arange(z_entrance, beam.get("z_max_mm", 400.0) + 0.05, 0.1)
path_cm = start_cm + (z - z_entrance) / 10.0
energy = np.interp(path_cm, table_cm, table_energy, right=0.0)
return BeamTrack(z, energy, None, f"{beam['eloss_table']}, {e_entrance} MeV at z = {z_entrance} mm")
raise KeyError("[beam] needs either track_csv or eloss_table")
def ecm_per_beam_energy(reaction):
"""E_cm = E_beam x m_target / (m_beam + m_target) (non-relativistic; or give ecm_per_ebeam directly)."""
if "ecm_per_ebeam" in reaction:
return float(reaction["ecm_per_ebeam"])
return reaction["target_mass_u"] / (reaction["beam_mass_u"] + reaction["target_mass_u"])
# ============================================================ bins
def energy_bins(bins, track, k):
"""E_cm bins and, for each, the stretch of beam path [z_up, z_down] where the beam's E_cm is inside it.
The higher energy is upstream, so z_up belongs to the bin's top edge."""
ecm_edges = make_edges(bins["ecm_edges"])
t = pd.DataFrame({"ecm_lo_mev": ecm_edges[:-1], "ecm_hi_mev": ecm_edges[1:]})
t["ecm_c_mev"] = 0.5 * (t["ecm_lo_mev"] + t["ecm_hi_mev"])
t["z_up_mm"] = [track.z_where_energy_is(e / k) for e in t["ecm_hi_mev"]]
t["z_c_mm"] = [track.z_where_energy_is(e / k) for e in t["ecm_c_mev"]]
t["z_down_mm"] = [track.z_where_energy_is(e / k) for e in t["ecm_lo_mev"]]
t["dz_mm"] = t["z_down_mm"] - t["z_up_mm"]
t["ecm_nominal_mev"] = k * track.energy_at(t["z_c_mm"], nominal=True)
# True if the beam enters the chamber already inside this bin (dz then starts at the entrance)
t["partial_top"] = t["ecm_hi_mev"] / k > track.energy[0]
t["d_omega_sr"] = 1.0 # not used in excitation mode
return t
def angle_bins(bins, track, k):
"""theta_cm bins inside one window, given in E_cm (window_ecm) or in vertex z (window_z_mm).
Returns the bins and the window as (ecm_lo, ecm_hi, z_up, z_down)."""
if "window_ecm" in bins:
ecm_lo, ecm_hi = bins["window_ecm"]
z_up, z_down = track.z_where_energy_is(ecm_hi / k), track.z_where_energy_is(ecm_lo / k)
else:
z_up, z_down = bins["window_z_mm"]
ecm_lo, ecm_hi = k * track.energy_at(z_down), k * track.energy_at(z_up)
theta_edges = make_edges(bins["theta_edges_deg"])
t = pd.DataFrame({"theta_lo_deg": theta_edges[:-1], "theta_hi_deg": theta_edges[1:]})
t["theta_c_deg"] = 0.5 * (t["theta_lo_deg"] + t["theta_hi_deg"])
t["d_omega_sr"] = 2 * np.pi * (np.cos(np.radians(t["theta_lo_deg"])) - np.cos(np.radians(t["theta_hi_deg"])))
t["z_up_mm"], t["z_down_mm"] = z_up, z_down
t["dz_mm"] = z_down - z_up
return t, (ecm_lo, ecm_hi, z_up, z_down)
def make_bins(config, track, k):
"""The bins of the config's mode. Returns (table, window or None)."""
if config.get("mode", "excitation") == "excitation":
return energy_bins(config["bins"], track, k), None
return angle_bins(config["bins"], track, k)

332
cross_section/cross_section.py Executable file
View File

@ -0,0 +1,332 @@
"""Step 3: the cross section, from the yields CSV (step 1) and an efficiency (step 2, or any other).
~/myenv/bin/python -B cross_section.py examples/27Al_ap_runA.toml # no ROOT needed; any python with pandas
sigma(E_cm) = Y / (N_b * n * dz * eps) mode = "excitation"
dsigma/dOmega(th) = Y / (N_b * n * dz * eps * dOmega) mode = "angular"
Y yield in the bin, from <out_dir>/yields_<det>.csv (or [yield] csv); see extract_yields.py for the format
N_b number of beam particles [beam]
n target atoms per cm^2 per mm of beam path [target]
dz length of beam path (mm) over which the beam's E_cm lies in the bin, from the beam track E(z)
eps efficiency of the bin [acceptance]
dOmega solid angle of the theta_cm bin (angular mode)
Writes <out_dir>/xs_<det>.csv (every intermediate number; the header records every input) and <out_dir>/xs.png.
WHAT IT DOES, STEP BY STEP (see analyse_detector())
1. beam track: beam energy E at every z
2. normalisation: N_b and n
3. bins: E_cm bins -> their z ranges and dz (or theta_cm bins inside one energy window)
4. yields: share the yield histogram's bins into those bins
5. efficiency: eps for each bin
6. cross section: the formula, with statistical and efficiency errors
"""
import argparse
import datetime
import re
from pathlib import Path
import numpy as np
import pandas as pd
import common
BOLTZMANN = 1.380649e-23 # J/K
PASCAL_PER_TORR = 133.322368
ELEMENTARY_CHARGE = 1.602176634e-19 # C
CM2_PER_MB = 1e-27
YIELD_VARIABLES = ("z", "e_beam", "ecm")
# ============================================================ normalisation
def number_of_beam_particles(beam):
"""N_b from: n_beam directly, or integrated charge, or current x time. Returns (N_b, relative error, text)."""
charge_state = beam.get("charge_state", 1)
if "n_beam" in beam:
n_b, text = beam["n_beam"], "given"
elif "charge_c" in beam:
n_b = beam["charge_c"] / (charge_state * ELEMENTARY_CHARGE)
text = f"{beam['charge_c']} C / ({charge_state} e)"
elif "current_a" in beam:
n_b = beam["current_a"] * beam["time_s"] / (charge_state * ELEMENTARY_CHARGE)
text = f"{beam['current_a']} A x {beam['time_s']} s / ({charge_state} e)"
else:
raise KeyError("[beam] needs n_beam, charge_c, or current_a and time_s")
return float(n_b), float(beam.get("n_beam_rel_err", 0.0)), text
def target_atoms_per_cm2_per_mm(target):
"""n = (gas molecules per cm^3) x (mole fraction of the target molecule) x (target atoms per molecule) x 0.1 cm/mm.
E.g. He in 250 Torr He + 3% CO2 at 293.15 K: mole_fraction 0.97, atoms_per_molecule 1 -> 7.988e17.
Returns (n, relative error, text)."""
if "n_per_cm2_per_mm" in target:
return float(target["n_per_cm2_per_mm"]), float(target.get("rel_err", 0.0)), "given"
pressure_pa = target["pressure_torr"] * PASCAL_PER_TORR
molecules_per_cm3 = pressure_pa / (BOLTZMANN * target["temperature_k"]) * 1e-6
fraction = target.get("mole_fraction", 1.0)
atoms = target.get("atoms_per_molecule", 1)
n = molecules_per_cm3 * fraction * atoms * 0.1
text = f"{target['pressure_torr']} Torr, {target['temperature_k']} K, mole fraction {fraction}, {atoms} atom(s)/molecule"
return n, float(target.get("rel_err", 0.0)), text
# ============================================================ yields
def read_yields(file):
"""The yields CSV of extract_yields.py. Returns (table, variable), variable = z, e_beam or ecm."""
table = pd.read_csv(file, comment="#")
for variable in YIELD_VARIABLES:
if f"{variable}_lo" in table.columns:
return table, variable
raise ValueError(f"{file}: no column z_lo, e_beam_lo or ecm_lo (see extract_yields.py for the format)")
def in_yield_units(variable, k, ecm_range, z_range):
"""Convert a bin, given both as an E_cm range and a z range, into the units of the yield's variable."""
if variable == "ecm":
return ecm_range
if variable == "e_beam":
return ecm_range[0] / k, ecm_range[1] / k
return z_range
def fraction_inside(src_lo, src_hi, lo, hi):
"""Fraction of each yield bin [src_lo, src_hi) that lies inside [lo, hi). Used to share the yield's bins
between the analysis bins when their edges do not line up."""
return np.clip(np.minimum(src_hi, hi) - np.maximum(src_lo, lo), 0, None) / (src_hi - src_lo)
def yields_excitation(table, variable, t, k):
"""Yield and its error in each E_cm bin."""
if "theta_cm_lo" in table.columns: # a 2D yield: add up the theta bins first
table = table.groupby([f"{variable}_lo", f"{variable}_hi"], as_index=False)[["counts", "variance"]].sum()
src_lo, src_hi = table[f"{variable}_lo"].to_numpy(), table[f"{variable}_hi"].to_numpy()
lo, hi = in_yield_units(variable, k, (t["ecm_lo_mev"], t["ecm_hi_mev"]), (t["z_up_mm"], t["z_down_mm"]))
counts, errors = [], []
for a, b in zip(lo, hi):
f = fraction_inside(src_lo, src_hi, a, b)
counts.append(np.sum(f * table["counts"]))
errors.append(np.sqrt(np.sum(f ** 2 * table["variance"])))
return np.array(counts), np.array(errors)
def yields_angular(table, variable, t, window, k):
"""Yield and its error in each theta_cm bin, for the part of the yield inside the energy (or z) window."""
if "theta_cm_lo" not in table.columns:
raise ValueError("angular mode needs a 2D yield (theta_cm_lo/hi columns): set angle_axis or angle_branch in [extract]")
ecm_lo, ecm_hi, z_up, z_down = window
w_lo, w_hi = in_yield_units(variable, k, (ecm_lo, ecm_hi), (z_up, z_down))
in_window = fraction_inside(table[f"{variable}_lo"].to_numpy(), table[f"{variable}_hi"].to_numpy(), w_lo, w_hi)
th_lo, th_hi = table["theta_cm_lo"].to_numpy(), table["theta_cm_hi"].to_numpy()
counts, errors = [], []
for a, b in zip(t["theta_lo_deg"], t["theta_hi_deg"]):
f = in_window * fraction_inside(th_lo, th_hi, a, b)
counts.append(np.sum(f * table["counts"]))
errors.append(np.sqrt(np.sum(f ** 2 * table["variance"])))
return np.array(counts), np.array(errors)
# ============================================================ efficiency
def efficiency(config, det, t, full_path):
"""eps and its relative error for every bin. [acceptance] source =
"geometric": <out_dir>/eps_<det>.csv from geometric_efficiency.py; `column` picks the efficiency
(default eps_geometric), its Monte Carlo error comes with it
"csv": any file: csv, x_col, eps_col, match = "bin" (same bin edge: our column `key` = their x_col)
or "interp" (interpolate at our column `key`; NaN outside the file's range), rel_err_col or rel_err
"value": one number for every bin (value, rel_err)
"none": eps = 1, the apparent cross section (the default)
[[acceptance.extra]] name, value, rel_err: further factors, multiplied in (e.g. an intrinsic efficiency)."""
eff = config.get("acceptance", {})
mode = config.get("mode", "excitation")
source = eff.get("source", "none")
n_bins = len(t)
eps, rel_err = np.ones(n_bins), np.zeros(n_bins)
text = "none (eps = 1: apparent cross section)"
if source == "geometric":
column = eff.get("column", "eps_geometric")
file = common.output_file(config, full_path, "eps", det)
eff = {"csv": str(file), "match": "bin", "eps_col": column, "rel_err_col": f"{column}_rel_err",
"x_col": "ecm_lo_mev" if mode == "excitation" else "theta_lo_deg", "extra": eff.get("extra", [])}
source = "csv"
if source == "csv":
table = pd.read_csv(full_path(eff["csv"]), comment="#")
match = eff.get("match", "interp")
default_key = {("excitation", "bin"): "ecm_lo_mev", ("excitation", "interp"): "ecm_nominal_mev",
("angular", "bin"): "theta_lo_deg", ("angular", "interp"): "theta_c_deg"}[(mode, match)]
key = eff.get("key", default_key)
their_x = table[eff["x_col"]].to_numpy(float)
their_eps = table[eff["eps_col"]].to_numpy(float)
if "rel_err_col" in eff:
their_err = table[eff["rel_err_col"]].to_numpy(float)
else:
their_err = np.full(len(table), eff.get("rel_err", 0.0))
ours = t[key].to_numpy(float)
if match == "bin":
for i, x in enumerate(ours):
same = np.flatnonzero(np.isclose(their_x, x, rtol=1e-6, atol=1e-9))
eps[i] = their_eps[same[0]] if len(same) else np.nan
rel_err[i] = their_err[same[0]] if len(same) else np.nan
else:
order = np.argsort(their_x)
eps = np.interp(ours, their_x[order], their_eps[order], left=np.nan, right=np.nan)
rel_err = np.interp(ours, their_x[order], their_err[order], left=np.nan, right=np.nan)
text = f"{eff['csv']}, column {eff['eps_col']} ({match} on {eff['x_col']} = our {key})"
elif source == "value":
eps = np.full(n_bins, float(eff["value"]))
rel_err = np.full(n_bins, float(eff.get("rel_err", 0.0)))
text = f"constant {eff['value']}"
elif source != "none":
raise ValueError(f"[acceptance] source must be geometric, csv, value or none, not {source!r}")
for extra in eff.get("extra", []):
eps = eps * extra["value"]
rel_err = np.hypot(rel_err, extra.get("rel_err", 0.0))
text += f" x {extra.get('name', 'extra factor')} {extra['value']}"
return eps, rel_err, text
# ============================================================ cross section
def cross_section(t, n_b, n, eps, eps_rel_err, name):
"""The formula. `name` is "sigma_mb" (excitation) or "dsdo_mb_sr" (angular). Adds columns to t:
<name>_app, <name>_app_err apparent cross section (eps = 1), statistical error
<name>, <name>_err_stat divided by eps
<name>_err_eps, <name>_err error from eps; total (stat and eps in quadrature)
The N_b and n uncertainties are a common scale for all bins and are NOT in these columns."""
exposure = n_b * n * t["dz_mm"] * t["d_omega_sr"] * CM2_PER_MB # events per mb (per mb/sr)
exposure = exposure.where(exposure > 0) # dz = 0 (past the stopping point) -> NaN
eps = pd.Series(eps, index=t.index).where(lambda e: e > 0) # eps = 0 -> NaN
t[f"{name}_app"] = t["counts"] / exposure
t[f"{name}_app_err"] = t["stat_err"] / exposure
t["eps"] = eps
t["eps_rel_err"] = eps_rel_err
t[name] = t[f"{name}_app"] / eps
t[f"{name}_err_stat"] = t[f"{name}_app_err"] / eps
t[f"{name}_err_eps"] = t[name] * t["eps_rel_err"]
t[f"{name}_err"] = np.hypot(t[f"{name}_err_stat"], t[f"{name}_err_eps"])
return t
def add_references(t, references, det, x_col, full_path):
"""Reference curves ([[reference]]: csv, x_col, y_col, label, optional scale and dets) interpolated at our x
(NaN outside their range). Returns the curves for the plot."""
curves = []
for ref in references or []:
if "dets" in ref and det not in ref["dets"]:
continue
table = pd.read_csv(full_path(ref["csv"]), comment="#").sort_values(ref["x_col"])
x = table[ref["x_col"]].to_numpy(float)
y = ref.get("scale", 1.0) * table[ref["y_col"]].to_numpy(float)
label = ref.get("label", Path(ref["csv"]).stem)
column = "ref_" + re.sub(r"\W+", "_", label).strip("_").lower()
t[column] = np.interp(t[x_col], x, y, left=np.nan, right=np.nan)
curves.append((label, x, y))
return curves
def analyse_detector(config, det, full_path):
"""Steps 1-6 for one detector. Returns the result table, its header lines, the x column, the cross-section
column name and the reference curves."""
mode = config.get("mode", "excitation")
k = common.ecm_per_beam_energy(config["reaction"])
# 1. beam track
track = common.load_beam_track(config["beam"], full_path)
# 2. normalisation
n_b, n_b_rel_err, n_b_text = number_of_beam_particles(config["beam"])
n, n_rel_err, n_text = target_atoms_per_cm2_per_mm(config["target"])
# 3. bins and 4. yields
yield_file = full_path(config["yield"]["csv"]) if "csv" in config.get("yield", {}) \
else common.output_file(config, full_path, "yields", det)
yields, variable = read_yields(yield_file)
t, window = common.make_bins(config, track, k)
if mode == "excitation":
t["counts"], t["stat_err"] = yields_excitation(yields, variable, t, k)
name, x_col, window_text = "sigma_mb", "ecm_nominal_mev", ""
else:
t["counts"], t["stat_err"] = yields_angular(yields, variable, t, window, k)
name, x_col = "dsdo_mb_sr", "theta_c_deg"
ecm_lo, ecm_hi, z_up, z_down = window
window_text = f"; window E_cm {ecm_lo:.4g} to {ecm_hi:.4g} MeV, z {z_up:.2f} to {z_down:.2f} mm"
# 5. efficiency and 6. cross section
eps, eps_rel_err, eps_text = efficiency(config, det, t, full_path)
t = cross_section(t, n_b, n, eps, eps_rel_err, name)
curves = add_references(t, config.get("reference"), det, x_col, full_path)
header = [
f"{config.get('title', '')} {det.upper()}: {mode} mode{window_text}",
f"yield: {yield_file} (binned in {variable})",
f"beam track: {track.description}; E_cm = {k:.6g} x E_beam",
f"N_b = {n_b:.6g} ({n_b_text}); n = {n:.6g} target atoms per cm2 per mm ({n_text})",
f"eps: {eps_text}",
f"common scale uncertainty from N_b and n: {100 * np.hypot(n_b_rel_err, n_rel_err):.3g} % (not in the error columns)",
f"written by cross_section.py on {datetime.date.today().isoformat()}",
]
return t, header, x_col, name, curves
def make_plot(results, config, out_file):
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
opts = config.get("plot", {})
excitation = config.get("mode", "excitation") == "excitation"
fig, ax = plt.subplots(figsize=opts.get("figsize", (9, 6)))
plotted_refs = set()
for det, (t, x_col, name, curves) in results.items():
ok = np.isfinite(t[name]) & (t[name] > 0)
ax.errorbar(t.loc[ok, x_col], t.loc[ok, name], yerr=t.loc[ok, f"{name}_err"],
fmt="o", ms=4, capsize=2, label=det.upper() or "data")
for label, x, y in curves:
if label not in plotted_refs:
ax.plot(x, y, "-", lw=1.2, label=label)
plotted_refs.add(label)
ax.set_xlabel(opts.get("xlabel", "E$_\\mathrm{cm}$ (MeV)" if excitation else "$\\theta_\\mathrm{cm}$ (deg)"))
ax.set_ylabel(opts.get("ylabel", "$\\sigma$ (mb)" if excitation else "d$\\sigma$/d$\\Omega$ (mb/sr)"))
ax.set_yscale(opts.get("yscale", "log"))
if "xlim" in opts:
ax.set_xlim(*opts["xlim"])
if "ylim" in opts:
ax.set_ylim(*opts["ylim"])
ax.set_title(config.get("title", ""))
ax.legend(fontsize=8)
fig.tight_layout()
fig.savefig(out_file, dpi=110)
plt.close(fig)
def main(argv=None):
parser = argparse.ArgumentParser(description="Step 3: cross section from the yields CSV and an efficiency.")
parser.add_argument("config", help="TOML config file")
args = parser.parse_args(argv)
config, full_path = common.read_config(args.config)
results = {}
for det in config.get("dets", [""]):
print(f"--- {det or 'data'}")
det_config = common.config_for_detector(config, det)
t, header, x_col, name, curves = analyse_detector(det_config, det, full_path)
common.write_csv(t, header, common.output_file(config, full_path, "xs", det))
results[det] = (t, x_col, name, curves)
print("\n".join(header[:-1]))
show = [x_col, "dz_mm" if name == "sigma_mb" else "d_omega_sr", "counts", f"{name}_app", "eps", name,
f"{name}_err"] + [c for c in t.columns if c.startswith("ref_")]
print(t[show].to_string(index=False, float_format=lambda v: f"{v:.4g}"), "\n")
out_png = common.output_file(config, full_path, "xs", "").with_suffix(".png")
make_plot(results, config, out_png)
print(f"written to {out_png.parent}")
return results
if __name__ == "__main__":
main()

80
cross_section/detectors.py Executable file
View File

@ -0,0 +1,80 @@
"""The ANASEN detectors as they are now (Run A, 27Al campaign), for the geometric efficiency.
geometric_efficiency.py reads everything about the detectors from here. Change a number here and every efficiency
computed afterwards follows it.
Sources:
- Si and counter: anasen_detectability.py of ~/TALYS/al027_a_scripts_stoichgas_dedx0962_2026-10-08
("detector geometry as used by TrackRecon.C"; counter radii from ClassPW.h)
- beam tube and PCB hole: the author, 2026-10-15 (../scratch/reaction_efficiency_tube_2026-10-15)
Geometry only: straight tracks from the vertex, no energy loss.
"""
import numpy as np
# ---------------------------------------------------------------- silicon
# QQQ3: the downstream annular detector
QQQ_Z_MM = 105.0 # TrackRecon.C qqq_z
QQQ_R_MM = (50.0, 100.0) # inner and outer active radius (16 rings: rho = 50 + 50/16 (ring + 0.5))
QQQ_PHI_FRACTION = 4 * 87.0 / 360.0 # 4 quadrants x 87 deg of wedges
# SX3: the barrel; only the four detectors TrackRecon.C builds events for, downstream half only
SX3_R_MM = 88.0
SX3_Z_MM = (-3.0, 72.0) # z_local + 75/2 - 3
SX3_IDS = (1, 3, 7, 9)
SX3_HALF_ANGLE = np.arctan(4 * 40.30 / (8.0 * 88.0 * np.cos(np.radians(15.0)))) # outer strip edge, each side
SX3_PHI_FRACTION = len(SX3_IDS) * 2 * SX3_HALF_ANGLE / (2 * np.pi)
PHI_FRACTION = {"qqq": QQQ_PHI_FRACTION, "sx3": SX3_PHI_FRACTION}
# Si energy thresholds (MeV), TrackRecon.C reaction_ax_core si_ecut. Used only for the eps_si_thr column.
SI_THRESHOLD_MEV = {"qqq": 0.45, "sx3": 0.6}
# ---------------------------------------------------------------- proportional counter
COUNTER_HALF_LENGTH_MM = 174.3
COUNTER_RADII_MM = (32.0, 37.0, 42.0) # guard, anode, cathode (ClassPW.h)
# ---------------------------------------------------------------- upstream apertures
# (name, radius mm, z mm of the face that binds). A track from an on-axis vertex only moves away from the axis,
# so an opening binds at its downstream end.
APERTURES = (("beam tube", 16.0, -189.0),
("PCB hole", 18.0, -174.3))
# ---------------------------------------------------------------- hit rules
# z_vertex in mm, theta = lab polar angle of the particle (radians). All return one True/False per track.
def hits_silicon(det, z_vertex, theta):
"""Does the straight track from z_vertex at angle theta reach the active silicon of `det` ("qqq" or "sx3")?
Polar angle only: multiply by PHI_FRACTION[det] for the azimuthal coverage.
A track that reaches the SX3 barrel's z range is counted as SX3, not QQQ."""
s, c = np.sin(theta), np.cos(theta)
with np.errstate(divide="ignore", invalid="ignore"):
z_at_sx3 = z_vertex + SX3_R_MM * c / s # z where the track reaches the barrel radius
on_sx3 = (z_at_sx3 >= SX3_Z_MM[0]) & (z_at_sx3 <= SX3_Z_MM[1])
if det == "sx3":
return on_sx3
r_at_qqq = np.where(c > 0, (QQQ_Z_MM - z_vertex) * s / c, -1.0) # radius where it reaches the QQQ plane
if det == "qqq":
return (QQQ_Z_MM > z_vertex) & (r_at_qqq >= QQQ_R_MM[0]) & (r_at_qqq <= QQQ_R_MM[1]) & ~on_sx3
raise ValueError(f"unknown detector {det!r}: detectors.py defines 'qqq' and 'sx3'")
def crosses_counter(z_vertex, theta):
"""Does the track cross the guard, anode and cathode radii inside the counter's length?"""
with np.errstate(divide="ignore", invalid="ignore"):
cot = np.cos(theta) / np.sin(theta)
ok = np.ones(np.shape(z_vertex), bool)
for r in COUNTER_RADII_MM:
ok &= np.abs(z_vertex + r * cot) <= COUNTER_HALF_LENGTH_MM
return ok
def clears_apertures(z_vertex, theta):
"""Does the track pass the beam tube and the PCB hole? Only vertices upstream of an aperture can be blocked
by it: blocked when the track is farther from the axis than the opening's radius at the opening's z."""
s, c = np.sin(theta), np.cos(theta)
tan_theta = s / np.where(c > 0, c, 1e-9) # backward tracks from upstream never reach the detectors
ok = np.ones(np.shape(z_vertex), bool)
for _name, radius, z_face in APERTURES:
ok &= ~((z_vertex < z_face) & ((z_face - z_vertex) * tan_theta > radius))
return ok

View File

@ -0,0 +1,88 @@
# 27Al(a,p)30Si excitation function, Run A, QQQ3 and SX3. Same inputs as ../../scratch/xs_27Al_lowE_2026-10-15.
#
# ~/myenv/bin/python -B extract_yields.py examples/27Al_ap_runA.toml # 1. ROOT -> out/27Al_ap_runA/yields_<det>.csv
# ~/myenv/bin/python -B geometric_efficiency.py examples/27Al_ap_runA.toml # 2. detectors.py -> eps_<det>.csv
# ~/myenv/bin/python -B cross_section.py examples/27Al_ap_runA.toml # 3. -> xs_<det>.csv, xs.png
#
# Each script reads the sections it needs. Paths are relative to this file's folder.
# "{det}" is replaced by each entry of `dets`.
title = "27Al(a,p)30Si, Run A"
mode = "excitation" # "excitation": sigma(E_cm); "angular": dsigma/dOmega(theta_cm)
dets = ["sx3", "qqq"] # names known to detectors.py
out_dir = "../out/27Al_ap"
# ------------------------------------------------------------- step 1: extract_yields.py
[extract]
# One fill per candidate: x = vertex z, y = tracked beam energy. Projected on y (all z, including overflow).
source = "hist"
file = "~/ANASEN_analysis/Output_27Al_X0_Y0/Output_27Al.root"
hist = "_m27Alax+misc_{det}/m27Alax_BeamEnergy_vs_VertexZ_{det}"
axis = "y"
variable = "e_beam"
# ------------------------------------------------------------- the reaction (steps 2 and 3)
[reaction]
beam_mass_u = 26.981538 # 27Al (atomic masses, as TrackRecon.C)
target_mass_u = 4.002603254 # 4He
ejectile_mass_u = 1.007825032 # p -> Q = 2.372 MeV for the ground state
recoil_mass_u = 29.973770 # 30Si
detect = "ejectile"
ecm_per_ebeam = 0.12918 # the rounded factor of the established analysis (the masses give 0.129182)
# 30Si states weighted by the TALYS (a,p) state cross sections at each bin's E_cm
states_csv = "../inputs/talys_27Al_ap.csv"
# or fixed weights: states = [{ex_mev = 0.0, weight = 1.0}, {ex_mev = 2.235, weight = 0.5}]
[beam]
# Beam energy along z;
# the nominal E_cm of each bin and the beam energy in the efficiency toy come from the 0.962 track.
track_csv = "../inputs/beam_tracks.csv"
track_z_col = "z_mm"
track_e_col = "e_beam_mev_0962"
# Alternative: build the track from an energy-loss table (check which dE/dx scale the table was made with):
# eloss_table = "../../eloss_calculations/aluminum_lookup_80MeV_250torr_3pc.dat"
# e_entrance_mev = 56.16
# z_entrance_mm = -454.0
n_beam = 1.4405e12 # Faraday cup, ch5 ssec:beam_exposure: the 45 runs (2.2096e12)
# For another set of runs: ../../runlog/beam_exposure.py "<runs>" --data-dir ../../Output_27Al_fb
n_beam_rel_err = 0.5
[bins]
ecm_edges = { start = 0.0, stop = 7.25, step = 0.25 }
# ------------------------------------------------------------- step 2: geometric_efficiency.py
[efficiency]
n_per_bin = 400000
seed = 20261016
# ------------------------------------------------------------- step 3: cross_section.py
[target]
# 4He in 250 Torr He + 3% CO2 at 20 C -> 7.988e17 He atoms per cm2 per mm
pressure_torr = 250.0
temperature_k = 293.15
mole_fraction = 0.97
atoms_per_molecule = 1
# [yield] csv = "..." # only to read yields from somewhere other than <out_dir>/yields_<det>.csv
[acceptance]
source = "geometric" # <out_dir>/eps_<det>.csv from step 2
column = "eps_geometric" # or eps_si, eps_si_thr, eps_counter
# To use the adopted efficiency of ../../scratch/xs_27Al_lowE_2026-10-15 instead (includes gas loss and thresholds):
# source = "csv"
# csv = "../../scratch/xs_27Al_lowE_2026-10-15/out/xs_{det}.csv"
# match = "bin"
# x_col = "ecm_lo_mev"
# eps_col = "eps_tube_adopted"
# rel_err_col = "eps_tube_rel_err"
[[reference]]
csv = "../inputs/talys_27Al_ap.csv"
x_col = "ecm_mev"
y_col = "sigma_total_mb"
label = "TALYS (a,p), discrete states"
[plot]
yscale = "log"
xlim = [0, 7.4]
ylim = [1e-3, 3e4]

View File

@ -0,0 +1,87 @@
# p + 4He elastic scattering, dsigma/dOmega(theta_cm), from the alpha recoils of the Run A Ta-foil runs 18-22, straight
# from a TrackRecon.C production file -- the same route as 27Al_ap_runA.toml. The histogram is
# elastic_VertexZ_vs_ThetaCM_<variant>_a_<DET>, filled by protonAlphaElastic_core since 2026-10-16.
# paap_ta_runs.toml is the same measurement from the p-alpha study's per-hit list.
#
# (once) process runs 18-22 with CUTLIST=pid_cuts/pid_cutlist.txt and merge them, e.g.
# bash scratch/paap_hist_2026-10-16/reprocess.sh (or run_tr.sh block 5, then hadd runs 18-22 only)
# ~/myenv/bin/python -B extract_yields.py examples/paap_ta_runs_hist.toml # or, the same in ROOT:
# root -l -b -q 'extract_yields.C+("examples/paap_ta_runs_hist.toml")'
# ~/myenv/bin/python -B geometric_efficiency.py examples/paap_ta_runs_hist.toml
# ~/myenv/bin/python -B cross_section.py examples/paap_ta_runs_hist.toml
#
# Paths are relative to this file's folder.
title = "p + 4He elastic, alpha recoils, Ta runs 18-22 (TrackRecon histograms)"
mode = "angular"
dets = ["qqq", "sx3"]
out_dir = "../out/paap_ta"
[det_vars.qqq] # TrackRecon.C spells the detector in upper case in these folders
DET = "QQQ"
[det_vars.sx3]
DET = "SX3"
# ------------------------------------------------------------- step 1: extract_yields.py / extract_yields.C
[extract]
# The merged output of runs 18-22 (not 17: its foil, at z = -135.7 mm, sits inside the vertex window below).
# Point this at your own merged file to use your own processing.
source = "hist"
file = "~/ANASEN_analysis/Output_p/Al_protons.root"
# Folder _a: hits the p/alpha cut (pid_alpha_<det>, CUTLIST) calls alpha. Variant _cath_exgate: cathode topologies
# (a1c1, a1c2, a2c1, a2c2) and |Ex| < 1 MeV under the alpha hypothesis, i.e. elastic. Others: "" (all topologies,
# no Ex gate), "_cath", "_exgate".
hist = "elastic+misc_{DET}_a/elastic_VertexZ_vs_ThetaCM_cath_exgate_a_{DET}"
axis = "x" # x = vertex z (mm)
angle_axis = "y" # y = proton theta_cm (deg)
variable = "z"
# ------------------------------------------------------------- the reaction (steps 2 and 3)
[reaction]
beam_mass_u = 1.007825032 # p
target_mass_u = 4.002603254 # 4He
ejectile_mass_u = 1.007825032 # p
recoil_mass_u = 4.002603254 # 4He
detect = "recoil" # the silicon sees the alpha; theta_cm is the proton's
states = [{ ex_mev = 0.0, weight = 1.0 }]
[beam]
# As paap_ta_runs.toml: 6.89 MeV protons into the gas; N_b = run 17's Ta-foil Rutherford current (1.35 pA) times
# the wall time of runs 18-22. The run log's 13 pA would give 2.6378e11 (../../runlog/beam_exposure.py "18-22").
eloss_table = "../../eloss_calculations/proton_lookup_30MeV_250torr_3pc.dat"
e_entrance_mev = 6.89
z_entrance_mm = -454.0
current_a = 1.35e-12
time_s = 3232.95
charge_state = 1
[bins]
window_z_mm = [-170.0, -53.0] # upstream of every run's foil by 25 mm for runs 18-22, inside the counter
theta_edges_deg = { start = 88.0, stop = 168.0, step = 8.0 }
# ------------------------------------------------------------- step 2: geometric_efficiency.py
[efficiency]
n_per_bin = 400000
seed = 20261016
# ------------------------------------------------------------- step 3: cross_section.py
[target]
pressure_torr = 250.0
temperature_k = 293.15
mole_fraction = 0.97
atoms_per_molecule = 1
[acceptance]
source = "geometric"
column = "eps_geometric"
[[reference]]
csv = "reference/barnard1964_A1113003.csv"
x_col = "theta_cm_deg"
y_col = "dsdo_window_mb_sr"
label = "Barnard 1964, at the window's E_p"
[plot]
yscale = "linear"
xlim = [80, 175]
ylim = [0, 130]

View File

@ -0,0 +1,144 @@
# Barnard, Jones & Weil, NP 50, 604 (1964), EXFOR A1113003: p + 4He elastic, dsigma/dOmega_cm (mb/sr)
# Legendre order 6 fits of the 6.016 and 6.977 MeV distributions; dsdo_window: log-linear in E_p at 6.7437 MeV,
# the beam energy at the centre of the vertex window -170.0 to -53.0 mm of paap_ta_runs.toml
# written by make_barnard_reference.py
theta_cm_deg,dsdo_6p016_mb_sr,dsdo_6p977_mb_sr,dsdo_window_mb_sr
26,514.406,470.065,480.465
27,504.693,461.948,471.98
28,494.922,453.754,463.423
29,485.115,445.501,454.81
30,475.295,437.203,446.161
31,465.481,428.878,437.491
32,455.692,420.54,428.816
33,445.946,412.203,420.153
34,436.261,403.88,411.513
35,426.651,395.584,402.912
36,417.132,387.326,394.36
37,407.715,379.117,385.87
38,398.413,370.966,377.45
39,389.235,362.882,369.111
40,380.191,354.874,360.861
41,371.288,346.947,352.705
42,362.532,339.108,344.652
43,353.928,331.362,336.705
44,345.481,323.714,328.869
45,337.192,316.166,321.147
46,329.064,308.722,313.542
47,321.097,301.384,306.056
48,313.29,294.152,298.688
49,305.642,287.028,291.44
50,298.152,280.012,284.312
51,290.816,273.103,277.302
52,283.631,266.301,270.408
53,276.592,259.604,263.63
54,269.697,253.011,256.964
55,262.94,246.52,250.409
56,256.315,240.129,243.962
57,249.819,233.836,237.62
58,243.445,227.639,231.379
59,237.189,221.535,225.238
60,231.045,215.523,219.192
61,225.008,209.599,213.24
62,219.074,203.763,207.379
63,213.237,198.013,201.606
64,207.495,192.345,195.918
65,201.842,186.761,190.315
66,196.276,181.257,184.794
67,190.793,175.833,179.353
68,185.391,170.488,173.993
69,180.069,165.223,168.711
70,174.824,160.037,163.508
71,169.656,154.931,158.384
72,164.564,149.905,153.339
73,159.549,144.961,148.375
74,154.61,140.099,143.492
75,149.75,135.322,138.691
76,144.969,130.63,133.975
77,140.27,126.028,129.346
78,135.655,121.516,124.807
79,131.127,117.097,120.359
80,126.688,112.775,116.005
81,122.342,108.552,111.75
82,118.093,104.431,107.595
83,113.945,100.416,103.545
84,109.9,96.5094,99.6024
85,105.964,92.7147,95.7706
86,102.14,89.0351,92.0532
87,98.4316,85.4736,88.4535
88,94.8431,82.0333,84.9746
89,91.378,78.7172,81.6197
90,88.0401,75.528,78.3918
91,84.8326,72.4683,75.2935
92,81.7586,69.5403,72.3274
93,78.8209,66.7463,69.4959
94,76.0221,64.0881,66.8009
95,73.3644,61.5673,64.2442
96,70.8494,59.1852,61.8272
97,68.4787,56.9427,59.5509
98,66.2533,54.8405,57.4162
99,64.1738,52.8789,55.4235
100,62.2404,51.058,53.5729
101,60.4529,49.3774,51.864
102,58.8107,47.8365,50.2963
103,57.3127,46.4343,48.8688
104,55.9576,45.1696,47.5803
105,54.7435,44.0406,46.4292
106,53.6684,43.0456,45.4134
107,52.7296,42.1824,44.5309
108,51.9245,41.4485,43.7792
109,51.25,40.8414,43.1556
110,50.7026,40.3582,42.657
111,50.279,39.9957,42.2804
112,49.9752,39.7509,42.0224
113,49.7873,39.6202,41.8794
114,49.7114,39.6002,41.8479
115,49.7433,39.6873,41.9241
116,49.8787,39.8777,42.1041
117,50.1134,40.1678,42.3841
118,50.4431,40.5537,42.7601
119,50.8637,41.0316,43.2282
120,51.3709,41.5977,43.7844
121,51.9605,42.2482,44.4248
122,52.6287,42.9794,45.1456
123,53.3713,43.7876,45.9431
124,54.1848,44.6692,46.8133
125,55.0653,45.6205,47.7529
126,56.0094,46.6381,48.7581
127,57.0138,47.7186,49.8256
128,58.0753,48.8588,50.9522
129,59.1907,50.0552,52.1344
130,60.3572,51.305,53.3694
131,61.5722,52.6049,54.654
132,62.8329,53.9521,55.9855
133,64.137,55.3437,57.3609
134,65.4821,56.7769,58.7776
135,66.8659,58.2489,60.2329
136,68.2862,59.757,61.7243
137,69.741,61.2987,63.2493
138,71.2282,62.8712,64.8053
139,72.7458,64.472,66.3899
140,74.2917,66.0985,68.0005
141,75.864,67.7481,69.6349
142,77.4604,69.4181,71.2903
143,79.079,71.1058,72.9644
144,80.7174,72.8086,74.6544
145,82.3734,74.5237,76.3578
146,84.0445,76.2483,78.0719
147,85.7282,77.9794,79.7937
148,87.4218,79.7141,81.5205
149,89.1225,81.4492,83.2491
150,90.8273,83.1817,84.9765
151,92.533,84.9082,86.6995
152,94.2363,86.6253,88.4146
153,95.9337,88.3298,90.1185
154,97.6217,90.0179,91.8076
155,99.2963,91.686,93.4782
156,100.954,93.3306,95.1267
157,102.59,94.9478,96.7491
158,104.2,96.5338,98.3415
159,105.781,98.0847,99.9001
160,107.328,99.5967,101.421
161,108.836,101.066,102.9
162,110.3,102.488,104.333
163,111.718,103.86,105.716
164,113.083,105.178,107.045
1 # Barnard, Jones & Weil, NP 50, 604 (1964), EXFOR A1113003: p + 4He elastic, dsigma/dOmega_cm (mb/sr)
2 # Legendre order 6 fits of the 6.016 and 6.977 MeV distributions; dsdo_window: log-linear in E_p at 6.7437 MeV,
3 # the beam energy at the centre of the vertex window -170.0 to -53.0 mm of paap_ta_runs.toml
4 # written by make_barnard_reference.py
5 theta_cm_deg,dsdo_6p016_mb_sr,dsdo_6p977_mb_sr,dsdo_window_mb_sr
6 26,514.406,470.065,480.465
7 27,504.693,461.948,471.98
8 28,494.922,453.754,463.423
9 29,485.115,445.501,454.81
10 30,475.295,437.203,446.161
11 31,465.481,428.878,437.491
12 32,455.692,420.54,428.816
13 33,445.946,412.203,420.153
14 34,436.261,403.88,411.513
15 35,426.651,395.584,402.912
16 36,417.132,387.326,394.36
17 37,407.715,379.117,385.87
18 38,398.413,370.966,377.45
19 39,389.235,362.882,369.111
20 40,380.191,354.874,360.861
21 41,371.288,346.947,352.705
22 42,362.532,339.108,344.652
23 43,353.928,331.362,336.705
24 44,345.481,323.714,328.869
25 45,337.192,316.166,321.147
26 46,329.064,308.722,313.542
27 47,321.097,301.384,306.056
28 48,313.29,294.152,298.688
29 49,305.642,287.028,291.44
30 50,298.152,280.012,284.312
31 51,290.816,273.103,277.302
32 52,283.631,266.301,270.408
33 53,276.592,259.604,263.63
34 54,269.697,253.011,256.964
35 55,262.94,246.52,250.409
36 56,256.315,240.129,243.962
37 57,249.819,233.836,237.62
38 58,243.445,227.639,231.379
39 59,237.189,221.535,225.238
40 60,231.045,215.523,219.192
41 61,225.008,209.599,213.24
42 62,219.074,203.763,207.379
43 63,213.237,198.013,201.606
44 64,207.495,192.345,195.918
45 65,201.842,186.761,190.315
46 66,196.276,181.257,184.794
47 67,190.793,175.833,179.353
48 68,185.391,170.488,173.993
49 69,180.069,165.223,168.711
50 70,174.824,160.037,163.508
51 71,169.656,154.931,158.384
52 72,164.564,149.905,153.339
53 73,159.549,144.961,148.375
54 74,154.61,140.099,143.492
55 75,149.75,135.322,138.691
56 76,144.969,130.63,133.975
57 77,140.27,126.028,129.346
58 78,135.655,121.516,124.807
59 79,131.127,117.097,120.359
60 80,126.688,112.775,116.005
61 81,122.342,108.552,111.75
62 82,118.093,104.431,107.595
63 83,113.945,100.416,103.545
64 84,109.9,96.5094,99.6024
65 85,105.964,92.7147,95.7706
66 86,102.14,89.0351,92.0532
67 87,98.4316,85.4736,88.4535
68 88,94.8431,82.0333,84.9746
69 89,91.378,78.7172,81.6197
70 90,88.0401,75.528,78.3918
71 91,84.8326,72.4683,75.2935
72 92,81.7586,69.5403,72.3274
73 93,78.8209,66.7463,69.4959
74 94,76.0221,64.0881,66.8009
75 95,73.3644,61.5673,64.2442
76 96,70.8494,59.1852,61.8272
77 97,68.4787,56.9427,59.5509
78 98,66.2533,54.8405,57.4162
79 99,64.1738,52.8789,55.4235
80 100,62.2404,51.058,53.5729
81 101,60.4529,49.3774,51.864
82 102,58.8107,47.8365,50.2963
83 103,57.3127,46.4343,48.8688
84 104,55.9576,45.1696,47.5803
85 105,54.7435,44.0406,46.4292
86 106,53.6684,43.0456,45.4134
87 107,52.7296,42.1824,44.5309
88 108,51.9245,41.4485,43.7792
89 109,51.25,40.8414,43.1556
90 110,50.7026,40.3582,42.657
91 111,50.279,39.9957,42.2804
92 112,49.9752,39.7509,42.0224
93 113,49.7873,39.6202,41.8794
94 114,49.7114,39.6002,41.8479
95 115,49.7433,39.6873,41.9241
96 116,49.8787,39.8777,42.1041
97 117,50.1134,40.1678,42.3841
98 118,50.4431,40.5537,42.7601
99 119,50.8637,41.0316,43.2282
100 120,51.3709,41.5977,43.7844
101 121,51.9605,42.2482,44.4248
102 122,52.6287,42.9794,45.1456
103 123,53.3713,43.7876,45.9431
104 124,54.1848,44.6692,46.8133
105 125,55.0653,45.6205,47.7529
106 126,56.0094,46.6381,48.7581
107 127,57.0138,47.7186,49.8256
108 128,58.0753,48.8588,50.9522
109 129,59.1907,50.0552,52.1344
110 130,60.3572,51.305,53.3694
111 131,61.5722,52.6049,54.654
112 132,62.8329,53.9521,55.9855
113 133,64.137,55.3437,57.3609
114 134,65.4821,56.7769,58.7776
115 135,66.8659,58.2489,60.2329
116 136,68.2862,59.757,61.7243
117 137,69.741,61.2987,63.2493
118 138,71.2282,62.8712,64.8053
119 139,72.7458,64.472,66.3899
120 140,74.2917,66.0985,68.0005
121 141,75.864,67.7481,69.6349
122 142,77.4604,69.4181,71.2903
123 143,79.079,71.1058,72.9644
124 144,80.7174,72.8086,74.6544
125 145,82.3734,74.5237,76.3578
126 146,84.0445,76.2483,78.0719
127 147,85.7282,77.9794,79.7937
128 148,87.4218,79.7141,81.5205
129 149,89.1225,81.4492,83.2491
130 150,90.8273,83.1817,84.9765
131 151,92.533,84.9082,86.6995
132 152,94.2363,86.6253,88.4146
133 153,95.9337,88.3298,90.1185
134 154,97.6217,90.0179,91.8076
135 155,99.2963,91.686,93.4782
136 156,100.954,93.3306,95.1267
137 157,102.59,94.9478,96.7491
138 158,104.2,96.5338,98.3415
139 159,105.781,98.0847,99.9001
140 160,107.328,99.5967,101.421
141 161,108.836,101.066,102.9
142 162,110.3,102.488,104.333
143 163,111.718,103.86,105.716
144 164,113.083,105.178,107.045

378
cross_section/extract_yields.C Executable file
View File

@ -0,0 +1,378 @@
// Step 1 in ROOT: yields out of ROOT, into a plain CSV histogram. The ROOT twin of extract_yields.py:
// same config, same output (<out_dir>/yields_<det>.csv), same format. Use whichever is handier.
//
// root -l -b -q 'extract_yields.C+("examples/27Al_ap_runA.toml")'
// root -l -b -q 'extract_yields.C+("examples/27Al_ap_runA.toml", "out/elsewhere")' // another output folder
// // (relative to where root runs)
// THE CSV FORMAT (cross_section.py reads only this)
// 1D: <var>_lo, <var>_hi, counts, variance
// 2D: <var>_lo, <var>_hi, theta_cm_lo, theta_cm_hi, counts, variance (angular mode)
// <var> is z (mm), e_beam (MeV) or ecm (MeV). One row per bin; under/overflow are not written.
//
// CONFIG: the [extract] section, `dets`, `out_dir`, `title` and [det_vars.<det>] of the study's TOML, exactly as for
// extract_yields.py (keys listed there). Two differences:
// - `cut` is handed to TTree::Draw. Write it with and / or (e.g. "det == 1 and e_si > 0.5"): they become && / ||
// here and stay valid pandas syntax for the Python version.
// - A tree histogram puts an entry exactly on the last upper edge in the overflow (numpy puts it in the last bin).
// The TOML reader below handles only what these configs use: "strings", numbers, one-line [lists] and
// { start = .., stop = .., step = .. } tables. Anything else stops with an error.
#include <TChain.h>
#include <TDirectory.h>
#include <TFile.h>
#include <TKey.h>
#include <TH1.h>
#include <TH2.h>
#include <TROOT.h>
#include <TString.h>
#include <TSystem.h>
#include <cmath>
#include <cstdio>
#include <fstream>
#include <functional>
#include <map>
#include <stdexcept>
#include <string>
#include <vector>
namespace xs {
using Section = std::map<std::string, std::string>; // key -> raw value text
using Config = std::map<std::string, Section>; // section name ("" = top level) -> keys
// ============================================================ minimal TOML reading
std::string Trim(const std::string& s)
{
const auto a = s.find_first_not_of(" \t\r\n");
if (a == std::string::npos) return "";
const auto b = s.find_last_not_of(" \t\r\n");
return s.substr(a, b - a + 1);
}
std::string StripComment(const std::string& line)
{
bool inString = false;
for (size_t i = 0; i < line.size(); ++i) {
if (line[i] == '"') inString = !inString;
if (line[i] == '#' && !inString) return line.substr(0, i);
}
return line;
}
Config ReadToml(const std::string& file)
{
std::ifstream in(file);
if (!in) throw std::runtime_error("cannot open config " + file);
Config config;
std::string section, line;
while (std::getline(in, line)) {
line = Trim(StripComment(line));
if (line.empty()) continue;
if (line.rfind("[[", 0) == 0) { section = line; continue; } // array tables (e.g. [[reference]]): not used here
if (line[0] == '[') { section = Trim(line.substr(1, line.size() - 2)); continue; }
const auto eq = line.find('=');
if (eq == std::string::npos) throw std::runtime_error("cannot read config line: " + line);
const std::string value = Trim(line.substr(eq + 1));
if ((value[0] == '[' && value.back() != ']') || (value[0] == '{' && value.back() != '}'))
throw std::runtime_error("lists and tables must be on one line: " + line);
config[section][Trim(line.substr(0, eq))] = value;
}
return config;
}
std::string Unquote(const std::string& v)
{
if (v.size() >= 2 && v.front() == '"' && v.back() == '"') return v.substr(1, v.size() - 2);
return v;
}
// "[a, b]" or "{ k = v, ... }" -> its items, split on commas outside quotes
std::vector<std::string> SplitItems(const std::string& v)
{
std::vector<std::string> items;
std::string inner = Trim(v.substr(1, v.size() - 2)), item;
bool inString = false;
for (char c : inner) {
if (c == '"') inString = !inString;
if (c == ',' && !inString) { items.push_back(Trim(item)); item.clear(); }
else item += c;
}
if (!Trim(item).empty()) items.push_back(Trim(item));
return items;
}
// Bin edges from [e0, e1, ...] or { start = .., stop = .., step = .. } (stop included), computed as numpy does
std::vector<double> Edges(const std::string& v)
{
std::vector<double> edges;
if (v.front() == '[') {
for (const auto& item : SplitItems(v)) edges.push_back(std::stod(item));
return edges;
}
std::map<std::string, double> table;
for (const auto& item : SplitItems(v)) {
const auto eq = item.find('=');
table[Trim(item.substr(0, eq))] = std::stod(Trim(item.substr(eq + 1)));
}
const double start = table.at("start"), stop = table.at("stop"), step = table.at("step");
const long nBins = std::lround((stop - start) / step);
for (long i = 0; i <= nBins; ++i) edges.push_back(start + step * i);
return edges;
}
// The [extract] section of one detector: every string with {det} and the [det_vars.<det>] keys replaced
Section ExtractSection(const Config& config, const std::string& det)
{
std::map<std::string, std::string> replacements = {{"det", det}};
const auto vars = config.find("det_vars." + det);
if (vars != config.end())
for (const auto& kv : vars->second) replacements[kv.first] = Unquote(kv.second);
const auto ex = config.find("extract");
if (ex == config.end()) throw std::runtime_error("the config has no [extract] section");
Section out;
for (const auto& kv : ex->second) {
TString value = kv.second.c_str();
for (const auto& r : replacements) value.ReplaceAll(("{" + r.first + "}").c_str(), r.second.c_str());
out[kv.first] = value.Data();
}
return out;
}
std::string Get(const Section& s, const std::string& key, const std::string& fallback = "\x01")
{
const auto it = s.find(key);
if (it != s.end()) return Unquote(it->second);
if (fallback == "\x01") throw std::runtime_error("[extract] needs the key " + key);
return fallback;
}
// ============================================================ the yields table
struct Row {
double lo, hi, thetaLo, thetaHi, counts, variance;
};
// Sum of weights squared of a bin (global index); a histogram without Sumw2 counts each entry once
double Variance(const TH1* h, int bin)
{
return h->GetSumw2N() > 0 ? h->GetSumw2()->At(bin) : h->GetBinContent(bin);
}
// The histogram at "folder/name". A name that itself contains '/' (e.g. "dE_(z/100)=1") is not reachable with Get(),
// so each split into an existing folder + a key of that folder is also tried. Not found: the error names the
// histograms whose path matches when case is ignored (e.g. "elastic+misc_qqq_a" for "elastic+misc_QQQ_a").
TH1* FindHistogram(TFile* file, const std::string& path)
{
if (auto* h = dynamic_cast<TH1*>(file->Get(path.c_str()))) return h;
// every split into folder + name: at each '/', from the last one back, and finally no folder at all
std::vector<std::pair<std::string, std::string>> splits;
for (size_t i = path.size(); i-- > 0;)
if (path[i] == '/') splits.emplace_back(path.substr(0, i), path.substr(i + 1));
splits.emplace_back("", path);
for (const auto& [folder, name] : splits) {
TDirectory* dir = folder.empty() ? static_cast<TDirectory*>(file) : file->GetDirectory(folder.c_str());
if (!dir) continue;
if (TKey* key = dir->GetKey(name.c_str()))
if (auto* h = dynamic_cast<TH1*>(key->ReadObj())) return h;
}
std::string close;
TString lower = path.c_str();
lower.ToLower();
std::function<void(TDirectory*, const std::string&)> walk = [&](TDirectory* dir, const std::string& prefix) {
for (auto* obj : *dir->GetListOfKeys()) {
auto* key = static_cast<TKey*>(obj);
const std::string full = prefix + key->GetName();
if (TString(key->GetClassName()).BeginsWith("TDirectory"))
walk(dir->GetDirectory(key->GetName()), full + "/");
else if (TString(full.c_str()).EqualTo(lower, TString::kIgnoreCase))
close += " '" + full + "'";
}
};
walk(file, "");
throw std::runtime_error("no histogram " + path + " in " + file->GetName() + (close.empty() ? "" : "; same path ignoring case:" + close) +
". Paths are case-sensitive; for upper-case names per detector use [det_vars], e.g. DET = \"QQQ\"");
}
std::vector<Row> FromHistogram(const Section& ex, const std::string& path, bool& twoD)
{
TFile* file = TFile::Open(path.c_str());
if (!file || file->IsZombie()) throw std::runtime_error("cannot open " + path);
const std::string name = Get(ex, "hist");
TH1* h = FindHistogram(file, name);
std::vector<Row> rows;
twoD = false;
if (h->GetDimension() == 1) {
for (int i = 1; i <= h->GetNbinsX(); ++i)
rows.push_back({h->GetXaxis()->GetBinLowEdge(i), h->GetXaxis()->GetBinUpEdge(i), 0, 0,
h->GetBinContent(i), Variance(h, i)});
file->Close();
return rows;
}
// TH2: `axis` holds the variable; the other axis is theta_cm (angle_axis given) or is summed
const bool varOnX = Get(ex, "axis", "x") == "x";
TH2* h2 = static_cast<TH2*>(h);
TAxis* varAxis = varOnX ? h2->GetXaxis() : h2->GetYaxis();
TAxis* otherAxis = varOnX ? h2->GetYaxis() : h2->GetXaxis();
const int nVar = varAxis->GetNbins(), nOther = otherAxis->GetNbins();
auto global = [&](int iVar, int iOther) { return varOnX ? h2->GetBin(iVar, iOther) : h2->GetBin(iOther, iVar); };
if (ex.count("angle_axis")) { // keep it 2D: one row per (variable bin, theta bin)
twoD = true;
for (int i = 1; i <= nVar; ++i)
for (int j = 1; j <= nOther; ++j)
rows.push_back({varAxis->GetBinLowEdge(i), varAxis->GetBinUpEdge(i), otherAxis->GetBinLowEdge(j),
otherAxis->GetBinUpEdge(j), h2->GetBinContent(global(i, j)), Variance(h2, global(i, j))});
file->Close();
return rows;
}
// project on the variable: the other axis summed whole (with under/overflow), or the fraction inside other_range
std::vector<double> weight(nOther + 2, 1.0);
if (ex.count("other_range")) {
const auto range = Edges(ex.at("other_range"));
weight.front() = weight.back() = 0.0;
for (int j = 1; j <= nOther; ++j) {
const double lo = otherAxis->GetBinLowEdge(j), hi = otherAxis->GetBinUpEdge(j);
weight[j] = std::max(0.0, std::min(hi, range[1]) - std::max(lo, range[0])) / (hi - lo);
}
}
for (int i = 1; i <= nVar; ++i) {
double counts = 0, variance = 0;
for (int j = 0; j <= nOther + 1; ++j) {
counts += weight[j] * h2->GetBinContent(global(i, j));
variance += weight[j] * weight[j] * Variance(h2, global(i, j));
}
rows.push_back({varAxis->GetBinLowEdge(i), varAxis->GetBinUpEdge(i), 0, 0, counts, variance});
}
file->Close();
return rows;
}
std::vector<Row> FromTree(const Section& ex, const std::vector<std::string>& files, bool& twoD)
{
TChain chain(Get(ex, "tree").c_str());
for (const auto& f : files) chain.Add(f.c_str()); // TChain::Add takes wildcards in the file name
if (chain.GetEntries() <= 0) // a missing tree or file must not become a table of zeros
throw std::runtime_error("no entries in tree " + Get(ex, "tree") + " of " + files.front() + (files.size() > 1 ? " ..." : ""));
// The cut, with and / or as && / ||; a weight branch multiplies it
TString cut = Get(ex, "cut", "").c_str();
cut.ReplaceAll(" and ", " && ").ReplaceAll(" or ", " || ");
if (cut.IsNull()) cut = "1";
TString selection = ex.count("weight_branch") ? TString::Format("(%s)*(%s)", Get(ex, "weight_branch").c_str(), cut.Data())
: cut;
const auto edges = Edges(ex.at("bins"));
const TString branch = Get(ex, "branch").c_str();
gROOT->cd();
std::vector<Row> rows;
twoD = ex.count("angle_branch") > 0;
if (!twoD) {
TH1D h("h_extract_yields", "", edges.size() - 1, edges.data());
h.Sumw2();
const Long64_t n = chain.Draw(branch + ">>h_extract_yields", selection, "goff");
printf(" %d file(s); %lld entries after the cut\n", chain.GetListOfFiles()->GetEntries(), n);
for (int i = 1; i <= h.GetNbinsX(); ++i)
rows.push_back({edges[i - 1], edges[i], 0, 0, h.GetBinContent(i), Variance(&h, i)});
return rows;
}
const auto angleEdges = Edges(ex.at("angle_bins"));
TH2D h("h_extract_yields2", "", edges.size() - 1, edges.data(), angleEdges.size() - 1, angleEdges.data());
h.Sumw2();
const Long64_t n = chain.Draw(TString(Get(ex, "angle_branch").c_str()) + ":" + branch + ">>h_extract_yields2",
selection, "goff"); // "y:x" in TTree::Draw
printf(" %d file(s); %lld entries after the cut\n", chain.GetListOfFiles()->GetEntries(), n);
for (size_t i = 1; i < edges.size(); ++i)
for (size_t j = 1; j < angleEdges.size(); ++j)
rows.push_back({edges[i - 1], edges[i], angleEdges[j - 1], angleEdges[j], h.GetBinContent(h.GetBin(i, j)),
Variance(&h, h.GetBin(i, j))});
return rows;
}
void WriteCsv(const std::vector<Row>& rows, bool twoD, const std::string& var, const std::vector<std::string>& header,
const std::string& outFile)
{
FILE* f = fopen(outFile.c_str(), "w");
if (!f) throw std::runtime_error("cannot write " + outFile);
for (const auto& line : header) fprintf(f, "# %s\n", line.c_str());
fprintf(f, twoD ? "%s_lo,%s_hi,theta_cm_lo,theta_cm_hi,counts,variance\n" : "%s_lo,%s_hi,counts,variance\n",
var.c_str(), var.c_str());
for (const auto& r : rows) {
if (twoD) fprintf(f, "%.6g,%.6g,%.6g,%.6g,%.6g,%.6g\n", r.lo, r.hi, r.thetaLo, r.thetaHi, r.counts, r.variance);
else fprintf(f, "%.6g,%.6g,%.6g,%.6g\n", r.lo, r.hi, r.counts, r.variance);
}
fclose(f);
}
} // namespace xs
void extract_yields(const char* configFile, const char* outDirOverride = "")
{
using namespace xs;
try {
const Config config = ReadToml(configFile);
const std::string configDir = gSystem->GetDirName(configFile).Data();
auto fullPath = [&](const std::string& p) -> std::string {
if (p.rfind("~/", 0) == 0 && getenv("HOME")) return std::string(getenv("HOME")) + p.substr(1); // "~/..." = home
return p.empty() || p[0] == '/' ? p : configDir + "/" + p;
};
const Section& top = config.count("") ? config.at("") : Section();
std::vector<std::string> dets = {""};
if (top.count("dets")) {
dets.clear();
for (const auto& d : SplitItems(top.at("dets"))) dets.push_back(Unquote(d));
}
const std::string outDir = *outDirOverride ? std::string(outDirOverride)
: fullPath(top.count("out_dir") ? Unquote(top.at("out_dir")) : "out");
gSystem->mkdir(outDir.c_str(), kTRUE);
const std::string title = top.count("title") ? Unquote(top.at("title")) : "";
for (const auto& det : dets) {
const Section ex = ExtractSection(config, det);
const std::string source = Get(ex, "source"), var = Get(ex, "variable");
if (source != "hist" && source != "tree")
throw std::runtime_error("[extract] source must be \"hist\" or \"tree\", not " + source +
(source == "csv" ? " (event-list CSVs: use extract_yields.py)" : ""));
std::vector<std::string> files; // `file` may be one path or a list
if (ex.at("file").front() == '[') for (const auto& f : SplitItems(ex.at("file"))) files.push_back(fullPath(Unquote(f)));
else files.push_back(fullPath(Get(ex, "file")));
const std::string fileText = ex.at("file").front() == '[' ? ex.at("file") : Get(ex, "file");
printf("--- %s: %s : %s\n", det.empty() ? "data" : det.c_str(), fileText.c_str(),
Get(ex, source == "hist" ? "hist" : "tree").c_str());
bool twoD = false;
std::vector<Row> rows;
std::string from;
if (source == "hist") {
rows = FromHistogram(ex, files.front(), twoD);
from = "histogram " + Get(ex, "hist");
} else if (source == "tree") {
rows = FromTree(ex, files, twoD);
from = "tree " + Get(ex, "tree") + ", branch " + Get(ex, "branch");
if (!Get(ex, "cut", "").empty()) from += ", cut: " + Get(ex, "cut");
} else {
throw std::runtime_error("[extract] source must be \"hist\" or \"tree\", not " + source +
(source == "csv" ? " (event-list CSVs: use extract_yields.py)" : ""));
}
TString detUpper = det.c_str();
detUpper.ToUpper();
const std::string outFile = outDir + "/" + (det.empty() ? "yields.csv" : "yields_" + det + ".csv");
WriteCsv(rows, twoD, var,
{title + " " + detUpper.Data() + ": yields, binned in " + var, "from " + fileText + " : " + from,
"written by extract_yields.C"},
outFile);
double total = 0;
for (const auto& r : rows) total += r.counts;
printf(" %.0f counts in %zu bins -> %s\n", total, rows.size(), outFile.c_str());
}
} catch (const std::exception& e) {
fprintf(stderr, "extract_yields.C: %s\n", e.what());
gSystem->Exit(1); // a failed extraction must not look like a finished one
}
}

View File

@ -0,0 +1,164 @@
"""Step 2: geometric efficiency of every bin, for the detectors as they are now (detectors.py).
~/myenv/bin/python -B geometric_efficiency.py examples/27Al_ap_runA.toml # ~1 min; numpy and pandas only
A toy Monte Carlo per bin (same bins as the cross section):
- vertices uniform over the bin's stretch of beam path (angular mode: over the window);
- beam energy at each vertex from the nominal beam track;
- the residual nucleus' state drawn from [reaction] states: fixed weights, or state cross sections
from a table (e.g. TALYS) at the bin's nominal E_cm;
- emission isotropic in the CM (angular mode: cos theta_cm uniform inside the theta bin, theta_cm of the ejectile);
- relativistic two-body kinematics, straight tracks from the vertex.
Writes <out_dir>/eps_<det>.csv, one row per bin, with these efficiencies (each times the azimuthal coverage):
eps_si the particle reaches the analysed silicon (solid angle)
eps_si_thr eps_si, and its energy at the vertex is above the Si threshold (the ch5 "solid angle" convention;
a kinematic cut, not geometry)
eps_counter eps_si, and it crosses the guard, anode and cathode radii inside the counter
eps_geometric eps_counter, and it clears the beam tube and the PCB hole <- THE GEOMETRIC EFFICIENCY
and the Monte Carlo relative error of each (<name>_rel_err).
Not included, because they are not geometry: energy loss in the gas, the Si threshold after the gas, the counter's
deposit threshold, intrinsic efficiencies. They are why the adopted eps_tube of ../scratch/xs_27Al_lowE_2026-10-15 is lower.
CONFIG
[reaction] beam_mass_u, target_mass_u, ejectile_mass_u, recoil_mass_u (atomic masses; Q from these)
detect = "ejectile" (default) or "recoil": the particle the silicon sees
states = [{ex_mev = 0.0, weight = 1.0}, ...] fixed state weights, or
states_csv = file with ecm_mev and one column ex_<Ex in MeV> per state (state cross sections)
[efficiency] n_per_bin (default 400000), seed
[beam], [bins], mode, dets as for the cross section.
"""
import argparse
import numpy as np
import pandas as pd
import common
import detectors
U = 931.49410242 # MeV per u
EFFICIENCIES = ("eps_si", "eps_si_thr", "eps_counter", "eps_geometric")
def two_body(t_beam, reaction, ex, cos_cm):
"""Relativistic two-body kinematics, beam on target at rest -> ejectile (at cos_cm in the CM) + recoil (state ex).
Returns the lab kinetic energy (MeV) and polar angle (rad) of the detected particle, and whether the channel is
open at this beam energy."""
m1, m2 = reaction["beam_mass_u"] * U, reaction["target_mass_u"] * U
m3, m4 = reaction["ejectile_mass_u"] * U, reaction["recoil_mass_u"] * U + ex
s = (m1 + m2) ** 2 + 2 * m2 * t_beam # invariant mass squared
sqrt_s = np.sqrt(s)
is_open = sqrt_s > m3 + m4
e_total = t_beam + m1 + m2
beta = np.sqrt(t_beam ** 2 + 2 * t_beam * m1) / e_total # of the CM in the lab
gamma = e_total / sqrt_s
p_cm = np.sqrt(np.clip((s - (m3 + m4) ** 2) * (s - (m3 - m4) ** 2), 0, None)) / (2 * sqrt_s)
sin_cm = np.sqrt(1 - cos_cm ** 2)
# the recoil goes the opposite way in the CM
mass, sign = (m3, 1.0) if reaction.get("detect", "ejectile") == "ejectile" else (m4, -1.0)
e_cm = np.sqrt(p_cm ** 2 + mass ** 2)
p_z = gamma * (sign * p_cm * cos_cm + beta * e_cm)
energy = gamma * (e_cm + beta * sign * p_cm * cos_cm)
theta = np.arctan2(p_cm * sin_cm, p_z)
return np.where(is_open, energy - mass, 0.0), theta, is_open
def q_value_mev(reaction):
return (reaction["beam_mass_u"] + reaction["target_mass_u"]
- reaction["ejectile_mass_u"] - reaction["recoil_mass_u"]) * U
def state_weights(reaction, ecm, states_table):
"""Excitation energies of the residual nucleus and their probabilities at this E_cm. States that are closed
(Ex >= Q + E_cm) get weight 0; if none is open, the ground state alone."""
if states_table is None:
states = reaction.get("states", [{"ex_mev": 0.0, "weight": 1.0}])
ex = np.array([s["ex_mev"] for s in states], float)
w = np.array([s["weight"] for s in states], float)
else:
columns = [c for c in states_table.columns if c.startswith("ex_")]
ex = np.array([float(c[3:]) for c in columns])
# the table row nearest this E_cm, among rows with any cross section (below the first such row, its weights)
rows = states_table[states_table[columns].sum(axis=1) > 0]
row = rows.iloc[np.argmin(np.abs(rows["ecm_mev"].to_numpy() - max(ecm, rows["ecm_mev"].min())))]
w = row[columns].to_numpy(float)
w = np.where(ex < q_value_mev(reaction) + ecm, w, 0.0)
if w.sum() <= 0:
w = (ex == 0).astype(float)
return ex, w / w.sum()
def efficiency_of_bin(det, reaction, track, z_range, cos_range, ecm, states_table, n, rng):
"""Toy for one bin: n reactions with vertices uniform in z_range and cos(theta_cm) uniform in cos_range."""
z = rng.uniform(z_range[0], z_range[1], n)
t_beam = track.energy_at(z, nominal=True)
ex_states, weights = state_weights(reaction, ecm, states_table)
ex = rng.choice(ex_states, size=n, p=weights)
cos_cm = rng.uniform(cos_range[0], cos_range[1], n)
energy, theta, is_open = two_body(t_beam, reaction, ex, cos_cm)
is_open &= t_beam > 0
on_si = is_open & detectors.hits_silicon(det, z, theta)
passed = {"eps_si": on_si,
"eps_si_thr": on_si & (energy > detectors.SI_THRESHOLD_MEV[det]),
"eps_counter": on_si & detectors.crosses_counter(z, theta)}
passed["eps_geometric"] = passed["eps_counter"] & detectors.clears_apertures(z, theta)
phi = detectors.PHI_FRACTION[det]
row = {"ground_state_weight": float(weights[ex_states == 0].sum()), "frac_open": float(is_open.mean())}
for name, ok in passed.items():
p = ok.mean()
row[name] = phi * p
row[f"{name}_rel_err"] = np.sqrt(p * (1 - p) / n) / p if p > 0 else np.nan # binomial
return row
def main(argv=None):
parser = argparse.ArgumentParser(description="Step 2: geometric efficiency per bin (detectors.py).")
parser.add_argument("config", help="TOML config file")
args = parser.parse_args(argv)
config, full_path = common.read_config(args.config)
for det in config.get("dets", [""]):
c = common.config_for_detector(config, det)
reaction, settings = c["reaction"], c.get("efficiency", {})
mode = c.get("mode", "excitation")
k = common.ecm_per_beam_energy(reaction)
track = common.load_beam_track(c["beam"], full_path)
bins, window = common.make_bins(c, track, k)
states_table = pd.read_csv(full_path(reaction["states_csv"]), comment="#") if "states_csv" in reaction else None
rng = np.random.default_rng(settings.get("seed", 20261016))
n = int(settings.get("n_per_bin", 400_000))
rows = []
for _, b in bins.iterrows():
if b["dz_mm"] <= 0: # past the beam's stopping point
rows.append({name: np.nan for name in EFFICIENCIES})
continue
if mode == "excitation":
ecm = b["ecm_nominal_mev"]
cos_range = (-1.0, 1.0)
else:
ecm = k * track.energy_at(0.5 * (b["z_up_mm"] + b["z_down_mm"]), nominal=True)
cos_range = (np.cos(np.radians(b["theta_hi_deg"])), np.cos(np.radians(b["theta_lo_deg"])))
rows.append(efficiency_of_bin(det, reaction, track, (b["z_up_mm"], b["z_down_mm"]), cos_range,
ecm, states_table, n, rng))
table = pd.concat([bins.reset_index(drop=True), pd.DataFrame(rows)], axis=1)
states_text = reaction.get("states_csv", reaction.get("states", "ground state only"))
header = [f"{c.get('title', '')} {det.upper()}: geometric efficiency per bin ({mode} mode), detectors.py",
f"beam track: {track.description}; Q = {q_value_mev(reaction):.4f} MeV; detected: "
f"{reaction.get('detect', 'ejectile')}; states: {states_text}",
f"isotropic CM, straight tracks, {n} reactions per bin; azimuthal coverage {detectors.PHI_FRACTION[det]:.4f}",
"eps_geometric = silicon + counter + apertures; eps_si_thr adds the Si threshold at the vertex energy",
"written by geometric_efficiency.py"]
out = common.output_file(config, full_path, "eps", det)
common.write_csv(table, header, out)
x_col = "ecm_nominal_mev" if mode == "excitation" else "theta_c_deg"
print(f"--- {det}: -> {out}")
print(table[[x_col, *EFFICIENCIES]].to_string(index=False, float_format=lambda v: f"{v:.4g}"))
if __name__ == "__main__":
main()

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More